-
Notifications
You must be signed in to change notification settings - Fork 21
Strict mode
ES6 modules support both default and named imports and exports:
// default
import foo from 'foo';
var bar = foo.toUpperCase();
export default bar;
// named
import { foo, bar } from 'baz';
var qux = ( foo + bar ).toUpperCase();
export { qux };
See jsmodules.io for an explanation of the difference.
This is a good design, but it poses problems for developers who want to use ES6 modules with existing codebases. A CommonJS representation of the first example above might look like this:
var foo = require('foo').default;
var bar = foo.toUpperCase();
exports.default = bar;
As long as foo
is also a transpiled ES6 module with a default
property (or a CommonJS module that had a default
property added by design), that's fine - as far as this module is concerned. But if foo
is an external library, that almost certainly won't be the case.
On the other side of the fence, if someone were to require this converted ES6 module from within another CommonJS module, they'd have the same problem in reverse - they'd have to know that they needed to append .default
to all their require()
statements.
The reasons for this design decision are good, but it poses a huge problem for interoperability. No matter how well designed ES6 modules are, if no-one can use modules from the npm ecosystem inside their ES6-authored apps - or if libraries written with ES6 modules can't be used by anyone else - then they'll continue to be a 'standard' in name only.
Interoperability is a top priority for Esperanto.
Some of the brightest minds in the industry have wrestled with interoperability. See these threads! There are many open issues on many different projects attempting to tackle this problem.
Most of the proposed solutions introduce a lot of complexity. But that complexity melts away if - for the purposes of interoperability - you sacrifice named imports and exports at the boundaries of your code.
Esperanto would transpile the module above like so:
var foo = require('foo');
var bar = foo.toUpperCase();
module.exports = bar;
(Actually, that's not true - it adds a use strict
pragma and wraps it in an IIFE bound to the global scope, because that's what the ES6 spec mandates. But you get the idea.)
We can now publish this ES6-authored module, and it will work seamlessly with existing code, without forcing other people to use a particular module loader, or making it necessary to gain industry-wide agreement on which clever hack we're going to use.
You can! Within a bundle, there are no restrictions. Let's say we're building a library that calculates the mean value of an array of numbers:
// math-utils.js
import assert from 'assert'; // node's builtin assert module
export function total ( arr ) {
return arr.reduce( function ( total, num ) {
assert.equal( typeof num, 'number' ); // error otherwise
return total + num;
}, 0 );
}
// mean.js
import { total } from './math-utils';
export default function ( arr ) {
if ( !arr.length ) return 0;
return total( arr ) / arr.length;
}
Here, the math-utils
module has a named export, total
, which the main module, mean
, imports. We can bundle this up into a single file, using the node API:
esperanto.bundle({ entry: 'mean.js' }).then( function ( bundle ) {
var transpiled = bundle.toCjs();
fs.writeFile( 'built/mean.js', transpiled );
});
Because the bundler couldn't find a file called assert.js
, it keeps it as an external reference. So the end result looks something like this:
var assert = require( 'assert' );
function total ( arr ) {
return arr.reduce( function ( total, num ) {
assert.equal( typeof num, 'number' ); // error otherwise
return total + num;
}, 0 );
}
module.exports = function ( arr ) {
if ( !arr.length ) return 0;
return total( arr ) / arr.length;
};
So you can happily use everything good about ES6 modules inside your code - the restriction only comes into play at the boundaries of your code.
Strict mode is for when you don't want even that minimal restriction, and don't particularly care about interoperability. With strict mode enabled, you can use named imports and exports everywhere - but consumers of your code must remember to do...
var mean = require( 'mean' ).default;
...rather than
var mean = require( 'mean' );
Note that default imports from other CommonJS/AMD modules will still generally work - there's a runtime check that goes something like this:
var assert = require( 'assert' );
var assert__default = ('default' in assert ? assert.default : assert);
Any references to assert
in your code would be replaced with assert__default
.