Skip to content

CommonJS Compatibility

Anton edited this page Dec 6, 2019 · 7 revisions

Depack works best with ES6 modules. All new code should be written with import/export statements because it's the standard that takes the implementations away from hacking assignments to module.exports which people used to use in a variety of possibly imaginable ways, e.g.,

Show lazyProperty use from depd
lazyProperty(module.exports, 'eventListenerCount', function eventListenerCount () {
  return EventEmitter.listenerCount || require('./event-listener-count')
})

/**
 * Define a lazy property.
 */

function lazyProperty (obj, prop, getter) {
  function get () {
    var val = getter()

    Object.defineProperty(obj, prop, {
      configurable: true,
      enumerable: true,
      value: val
    })

    return val
  }

  Object.defineProperty(obj, prop, {
    configurable: true,
    enumerable: true,
    get: get
  })
}
Show module.exports use from debug
module.exports = require('./common')(exports);

const {formatters} = module.exports;

Unlike the examples above, the infrastructure built with Depack always uses modules when writing JavaScript, because that's the main static analysis method supported by Depack. The require statements are also supported, but when a require is broken up by a comment like require(/*depack*/'dep'), it is kept and the module will be dynamically loaded at runtime.

ES6 modules make the correct static analysis of programs possible since exports now are not some random object that can change at runtime by code, but a set of concrete APIs, that is, the single default and all named exports. When every single dependency of the compiled file is a module, there are no issues or special things to think about. However, when a package tries to use a CommonJS module, there are the following compatibility rules dictated by the current version of GCC.

Enabling Processing Of CommonJS Modules

The Closure Compiler requires a special flag --process_common_js_modules to enable processing CommonJS modules, otherwise, files will be treated as ES6 modules and when trying to make an import, there would be an error saying that the properties being imported are not found in the module:

// ecma.js
import commonJs from './common-js'

console.log('Requiring a CommonJS'
  + ' from ecma:')
console.log(commonJs)



// The default import
// using the import keyword 🙏🏾
// 2018+ JavaScript <3
// common-js
const commonJs2 = require('./common-js2')

module.exports = () => {
  console.log('Def CJS')
}
module.exports['named'] = () => {
  console.log('Named CJS')
}

console.log('Requiring CJS '
  + 'from CJS:')
console.log(commonJs2)
Exit code 1
e/2/ecma.js:2: ERROR - [JSC_DOES_NOT_HAVE_EXPORT] Requested module does not have an export "default".
import commonJs from './common-js'
^

1 error(s), 0 warning(s)

Depack will perform static analysis by looking at all dependencies recursively. When it sees an import (or a require statement) that references an external package, it will find its package.json to find out the main and module fields. If the main field is found, the package is marked as CommonJS module, and the --process_common_js_modules flag will be added, otherwise, the file is just added with --js flag.

The require statement will not trigger the addition of the flag if:

  • It appears in the entry file passed to Depack. It makes it possible to do common things like the example below:
    const version = require('../package.json')['version']
    console.log(version)
    // 1. the compiled file must have the same relative path as source
    // 2. use only for bins, as if used in a lib, the container package's
    //    package.json will be read.
  • It's not pure, e.g., a comment is added require(/**/'depack')

In scenarios above, the compiler will leave the require call as it is because there was no process_common_js_modules flag. However, if there were other packages in CommonJS format (required via the main field of their package.json), ALL requires will be processed. If Depack didn't detect a CommonJS module when you know there is one, just add the flag manually. Finally, Depack assumes that the entry point is in ES6.

Single Default Export

It is not possible to import named exports from CommonJS modules. There is a single default export that must be imported, and named exports will be its properties.

tldr;

import commonJs from 'common-js'
commonJs('hello')
commonJs.named('world')
A CommonJS package required from an ECMA module will have only a single default exported object, with named exports defined on it.
import commonJs from 'common-js'
commonJs('hello')
commonJs.named('world')
There are no named exports to be used in destructuring of the import statement.
// ecma.js
import commonJs from './common-js'

console.log('Requiring a CommonJS'
  + ' from ecma:')
console.log(commonJs)



// The default import
// using the import keyword 🙏🏾
// 2018+ JavaScript <3
ECMA modules' compatibility works by calling the default export from code, and named exports from within the imported object's namespace, i.e., without named imports but by referencing the imported object.
// common-js
const commonJs2 = require('./common-js2')

module.exports = () => {
  console.log('Def CJS')
}
module.exports['named'] = () => {
  console.log('Named CJS')
}

console.log('Requiring CJS '
  + 'from CJS:')
console.log(commonJs2)
// common-js2.js
module.exports = () => {
  console.log('Def CJS2')
}
exports.named = () => {
  console.log('Named CJS2')
}





// standard Node require
The CommonJS can be required by other CommonJS modules in the standard require way. Although the non-quoted properties on exports will be renamed, this is an internal optimisation meaning that in code, all references to the properties will also be renamed.
To use CJS from Ecma, import a single default export, and access its properties for named exports.
Show Compiled Version
'use strict';
var a = {};
a = () => {
  console.log("Def CJS2");
};
a.a = () => {
  console.log("Named CJS2");
};
var b = () => {
  console.log("Def CJS");
};
b.named = () => {
  console.log("Named CJS");
};
console.log("Requiring CJS from CJS:");
console.log(a);
console.log("Requiring a CommonJS from ecma:");
console.log(b);
Requiring CJS from CJS:
{ [Function: a] a: [Function] }
Requiring a CommonJS from ecma:
{ [Function: b] named: [Function] }
Clone this wiki locally