Skip to content

Babel Modules

Anton edited this page Jan 28, 2020 · 12 revisions

Since Compilerv20190709, the modules imports from Babel have been working semi-correctly, because although it is possible to compile a Babel-transpiled module with Closure Compiler, the compatibility between the two is not ensured for default exports.

The following source code is used in the example on this page.

Source (@a-la/fixture-babel)
/**
 * A function that returns `erte`.
 */
const erte = () => {
  return 'erte'
}

/**
 * A function that returns `c`.
 * @param {string} input
 */
export const c = (input) => {
  return 'c' + (input ? `-${input}` : '')
}

/**
 * A function that returns `b`.
 * @param {number} times
 */
export const b = (times) => {
  return 'b' + (times ? `-${times}` : '')
}

export default erte
Babel-compiled
Show Code
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = exports.b = exports.c = void 0;

/**
 * A function that returns `erte`.
 */
const erte = () => {
  return 'erte';
};
/**
 * A function that returns `c`.
 * @param {string} input
 */


const c = input => {
  return 'c' + (input ? `-${input}` : '');
};
/**
 * A function that returns `b`.
 * @param {number} times
 */


exports.c = c;

const b = times => {
  return 'b' + (times ? `-${times}` : '');
};

exports.b = b;
var _default = erte;
exports.default = _default;

Because of how CommonJS Compatibility is implemented, all RequireJS modules will only make accessible a single default object, without named exports.

The script to import Babel-compiled modules in ES6 Code is now:
import erte from '@fixture/babel'

console.log(erte.default())
console.log(erte.c(''))
console.log(erte.b(''))
  • ✅ Do import a single default object.
  • ⛔️ Do NOT import named exports.

Although it's impossible to use such code with Babel runtime, the code above can be compiled using Google Closure Compiler.

Command & Warnings
java -jar /Users/zavr/node_modules/google-closure-compiler-java/compiler.jar \
--compilation_level ADVANCED --language_out ECMASCRIPT_2018 --formatting \
PRETTY_PRINT --process_common_js_modules --package_json_entry_names module,main \
--entry_point e/b.js --externs node_modules/@externs/nodejs/v8/global.js \
--externs node_modules/@externs/nodejs/v8/global/buffer.js --externs \
node_modules/@externs/nodejs/v8/nodejs.js
Modules: b/i.js
Running Google Closure Compiler 20200112
b/i.js:6: WARNING - [JSC_TYPE_MISMATCH] assignment to property b of module$b$i.default
found   : undefined
required: function(?): ?
exports.default = exports.b = exports.c = void 0;
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

b/i.js:6: WARNING - [JSC_TYPE_MISMATCH] assignment to property c of module$b$i.default
found   : undefined
required: function(string): ?
exports.default = exports.b = exports.c = void 0;
                              ^^^^^^^^^^^^^^^^^^

0 error(s), 2 warning(s), 94.4% typed
The command generates some warnings, but no errors.
The generated code and output
'use strict';
var a = {};
Object.defineProperty(a, "__esModule", {value:!0});
a.default = a.a = a.b = void 0;
a.b = b => "c" + (b ? `-${b}` : "");
a.a = b => "b" + (b ? `-${b}` : "");
a.default = () => "erte";
console.log(a.default());
console.log(a.b(""));
console.log(a.a(""));
erte
c
b
Trying to execute the output produces the correct result. OK, but what happens when we actually try to execute such program with @babel/register? This is needed for testing and development.
MacBook:fixture-babel zavr$ node erte
erte/erte.js:7
console.log(_build.default.default());
                                  ^

TypeError: _build.default.default is not a function
    at Object.<anonymous> (erte/erte.js:3:13)
    at Module._compile (module.js:653:30)
    at Module._compile (pirates/index.js:99:24)
    at Module._extensions..js (module.js:664:10)
    at Object.newLoader [as .js] (pirates/index.js:104:7)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)
    at Function.Module._load (module.js:498:3)
    at Module.require (module.js:597:17)
    at require (internal/module.js:11:18)

Conclusion

  • No IDE support, e.g., VS Code does not infer .default property.
  • No development environment, because Babel-runtime will throw on trying to access the .default property.
Because of referring to the default import as .default, the compatibility with Babel is broken for default imports. Therefore, it's better to use ÀLaMode for transpilation which is compatible with Closure Compiler.

To see what happens when trying to import { named } exports, refer to the example below.

import erte, { c, b } from '@fixture/babel'

console.log(erte())
console.log(c())
console.log(b())
Command & Generated JS
java -jar /Users/zavr/node_modules/google-closure-compiler-java/compiler.jar \
--compilation_level ADVANCED --language_out ECMASCRIPT_2018 --formatting \
PRETTY_PRINT --process_common_js_modules --package_json_entry_names module,main \
--entry_point e/1.js --externs node_modules/@externs/nodejs/v8/global.js \
--externs node_modules/@externs/nodejs/v8/global/buffer.js --externs \
node_modules/@externs/nodejs/v8/nodejs.js
Modules: b/i.js
Running Google Closure Compiler 20200112
depack e/1 -c -a -p --process_common_js_modules stderr
Exit code 2
e/1.js:1: ERROR - [JSC_DOES_NOT_HAVE_EXPORT] Requested module does not have an export "b".
import erte, { c, b } from '../b/i'
^

e/1.js:1: ERROR - [JSC_DOES_NOT_HAVE_EXPORT] Requested module does not have an export "c".
import erte, { c, b } from '../b/i'
^

2 error(s), 0 warning(s)
stdout

<EMPTY>

The named import syntax on CommonJS modules is not supported unless there is an ECMA6 version of the script which will be detected by static analysis in the module field of the package.json file. Thus it's a good idea to publish the module also with the build for the compiler to include the source code of the package in another package being built.

Clone this wiki locally