Browse Source

Merge pull request #66 from rollup/esperanto-friendly

Implement features needed by Esperanto
contingency-plan
Rich Harris 10 years ago
parent
commit
f9ea94c835
  1. 30
      src/Bundle.js
  2. 48
      src/Module.js
  3. 6
      src/Statement.js
  4. 19
      src/finalisers/amd.js
  5. 25
      src/finalisers/cjs.js
  6. 16
      src/finalisers/iife.js
  7. 12
      src/finalisers/shared/getExportBlock.js
  8. 10
      src/finalisers/shared/getInteropBlock.js
  9. 26
      src/finalisers/umd.js
  10. 4
      src/rollup.js
  11. 7
      test/form/banner-and-footer/_config.js
  12. 7
      test/form/banner-and-footer/_expected/amd.js
  13. 5
      test/form/banner-and-footer/_expected/cjs.js
  14. 3
      test/form/banner-and-footer/_expected/es6.js
  15. 7
      test/form/banner-and-footer/_expected/iife.js
  16. 11
      test/form/banner-and-footer/_expected/umd.js
  17. 1
      test/form/banner-and-footer/main.js
  18. 2
      test/form/external-imports-custom-names/_expected/amd.js
  19. 2
      test/form/external-imports-custom-names/_expected/iife.js
  20. 2
      test/form/external-imports-custom-names/_expected/umd.js
  21. 1
      test/form/external-imports/_expected/amd.js
  22. 1
      test/form/external-imports/_expected/iife.js
  23. 1
      test/form/external-imports/_expected/umd.js
  24. 3
      test/form/removes-existing-sourcemap-comments/_config.js
  25. 9
      test/form/removes-existing-sourcemap-comments/_expected/amd.js
  26. 7
      test/form/removes-existing-sourcemap-comments/_expected/cjs.js
  27. 5
      test/form/removes-existing-sourcemap-comments/_expected/es6.js
  28. 9
      test/form/removes-existing-sourcemap-comments/_expected/iife.js
  29. 13
      test/form/removes-existing-sourcemap-comments/_expected/umd.js
  30. 5
      test/form/removes-existing-sourcemap-comments/foo.js
  31. 5
      test/form/removes-existing-sourcemap-comments/main.js
  32. 10
      test/function/deconflicts-exports/_config.js
  33. 8
      test/function/deconflicts-exports/main.js
  34. 13
      test/function/deconflicts-external-imports/_config.js
  35. 5
      test/function/deconflicts-external-imports/a.js
  36. 5
      test/function/deconflicts-external-imports/b.js
  37. 5
      test/function/deconflicts-external-imports/main.js
  38. 10
      test/function/export-and-import-reference-share-var/_config.js
  39. 2
      test/function/export-and-import-reference-share-var/foo.js
  40. 13
      test/function/export-and-import-reference-share-var/main.js
  41. 3
      test/function/import-namespace-from-internal-module-renamed/_config.js
  42. 3
      test/function/import-namespace-from-internal-module-renamed/foo.js
  43. 3
      test/function/import-namespace-from-internal-module-renamed/main.js
  44. 34
      test/function/uses-supplied-ast/_config.js

30
src/Bundle.js

@ -39,7 +39,9 @@ export default class Bundle {
this.statements = null;
this.externalModules = [];
this.internalNamespaceModules = [];
this.assumedGlobals = blank();
this.assumedGlobals.exports = true; // TODO strictly speaking, this only applies with non-ES6, non-default-only bundles
}
build () {
@ -91,16 +93,14 @@ export default class Bundle {
// Assign names to external modules
this.externalModules.forEach( module => {
// TODO is this right?
let name = makeLegalIdentifier( module.suggestedNames['*'] || module.suggestedNames.default || module.id );
if ( definers[ name ] ) {
while ( definers[ name ] ) {
conflicts[ name ] = true;
} else {
definers[ name ] = [];
name = `_${name}`;
}
definers[ name ].push( module );
definers[ name ] = [ module ];
module.name = name;
this.assumedGlobals[ name ] = true;
});
@ -186,12 +186,24 @@ export default class Bundle {
return this.modulePromises[ importee ];
}
if ( id === importer ) {
throw new Error( `A module cannot import itself (${id})` );
}
if ( !this.modulePromises[ id ] ) {
this.modulePromises[ id ] = Promise.resolve( this.load( id, this.loadOptions ) )
.then( source => {
let ast;
if ( typeof source === 'object' ) {
ast = source.ast;
source = source.code;
}
const module = new Module({
id,
source,
ast,
bundle: this
});
@ -315,7 +327,10 @@ export default class Bundle {
const exportKeys = keys( module.exports );
return `var ${module.getCanonicalName('*', format === 'es6')} = {\n` +
exportKeys.map( key => `${indentString}get ${key} () { return ${module.getCanonicalName(key, format === 'es6')}; }` ).join( ',\n' ) +
exportKeys.map( key => {
const localName = module.exports[ key ].localName;
return `${indentString}get ${key} () { return ${module.getCanonicalName(localName, format === 'es6')}; }`;
}).join( ',\n' ) +
`\n};\n\n`;
}).join( '' );
@ -335,6 +350,9 @@ export default class Bundle {
indentString: getIndentString( magicString, options )
}, options );
if ( options.banner ) magicString.prepend( options.banner + '\n' );
if ( options.footer ) magicString.append( '\n' + options.footer );
const code = magicString.toString();
let map = null;

48
src/Module.js

@ -29,7 +29,7 @@ function isEmptyExportedVarDeclaration ( node, module, allBundleExports, es6 ) {
}
export default class Module {
constructor ({ id, source, bundle }) {
constructor ({ id, source, ast, bundle }) {
this.source = source;
this.bundle = bundle;
@ -41,10 +41,17 @@ export default class Module {
filename: id
});
// remove existing sourceMappingURL comments
const pattern = /\/\/#\s+sourceMappingURL=.+\n?/g;
let match;
while ( match = pattern.exec( source ) ) {
this.magicString.remove( match.index, match.index + match[0].length );
}
this.suggestedNames = blank();
this.comments = [];
this.statements = this._parse();
this.statements = this._parse( ast );
// imports and exports, indexed by ID
this.imports = blank();
@ -510,21 +517,22 @@ export default class Module {
}
// TODO rename this to parse, once https://github.com/rollup/rollup/issues/42 is fixed
_parse () {
// Try to extract a list of top-level statements/declarations. If
// the parse fails, attach file info and abort
let ast;
try {
ast = parse( this.source, {
ecmaVersion: 6,
sourceType: 'module',
onComment: ( block, text, start, end ) => this.comments.push({ block, text, start, end })
});
} catch ( err ) {
err.code = 'PARSE_ERROR';
err.file = this.id; // see above - not necessarily true, but true enough
throw err;
_parse ( ast ) {
// The ast can be supplied programmatically (but usually won't be)
if ( !ast ) {
// Try to extract a list of top-level statements/declarations. If
// the parse fails, attach file info and abort
try {
ast = parse( this.source, {
ecmaVersion: 6,
sourceType: 'module',
onComment: ( block, text, start, end ) => this.comments.push({ block, text, start, end })
});
} catch ( err ) {
err.code = 'PARSE_ERROR';
err.file = this.id; // see above - not necessarily true, but true enough
throw err;
}
}
walk( ast, {
@ -633,7 +641,11 @@ export default class Module {
// split up/remove var declarations as necessary
if ( statement.node.isSynthetic ) {
magicString.insert( statement.start, `${statement.node.kind} ` );
// insert `var/let/const` if necessary
if ( !allBundleExports[ statement.node.declarations[0].id.name ] ) {
magicString.insert( statement.start, `${statement.node.kind} ` );
}
magicString.overwrite( statement.end, statement.next, ';\n' ); // TODO account for trailing newlines
}

6
src/Statement.js

@ -90,7 +90,11 @@ export default class Statement {
}
if ( newScope ) {
Object.defineProperty( node, '_scope', { value: newScope });
Object.defineProperty( node, '_scope', {
value: newScope,
configurable: true
});
scope = newScope;
}
},

19
src/finalisers/amd.js

@ -1,5 +1,6 @@
import { getName, quoteId } from '../utils/map-helpers';
import getInteropBlock from './shared/getInteropBlock';
import getExportBlock from './shared/getExportBlock';
export default function amd ( bundle, magicString, { exportMode, indentString }, options ) {
let deps = bundle.externalModules.map( quoteId );
@ -11,27 +12,17 @@ export default function amd ( bundle, magicString, { exportMode, indentString },
}
const params =
( options.moduleId ? `['${options.moduleId}'], ` : `` ) +
( options.moduleId ? `'${options.moduleId}', ` : `` ) +
( deps.length ? `[${deps.join( ', ' )}], ` : `` );
const intro = `define(${params}function (${args.join( ', ' )}) { 'use strict';\n\n`;
const useStrict = options.useStrict !== false ? ` 'use strict';` : ``;
const intro = `define(${params}function (${args.join( ', ' )}) {${useStrict}\n\n`;
// var foo__default = 'default' in foo ? foo['default'] : foo;
const interopBlock = getInteropBlock( bundle );
if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' );
const exports = bundle.entryModule.exports;
let exportBlock;
if ( exportMode === 'default' ) {
exportBlock = `return ${bundle.entryModule.getCanonicalName('default')};`;
} else {
exportBlock = bundle.toExport.map( name => {
return `exports.${name} = ${exports[name].localName};`;
}).join( '\n' );
}
const exportBlock = getExportBlock( bundle, exportMode );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
return magicString

25
src/finalisers/cjs.js

@ -1,5 +1,7 @@
export default function cjs ( bundle, magicString, { exportMode }) {
let intro = `'use strict';\n\n`;
import getExportBlock from './shared/getExportBlock';
export default function cjs ( bundle, magicString, { exportMode }, options ) {
let intro = options.useStrict === false ? `` : `'use strict';\n\n`;
// TODO handle empty imports, once they're supported
const importBlock = bundle.externalModules
@ -21,23 +23,8 @@ export default function cjs ( bundle, magicString, { exportMode }) {
magicString.prepend( intro );
let exportBlock;
if ( exportMode === 'default' && bundle.entryModule.exports.default ) {
exportBlock = `module.exports = ${bundle.entryModule.getCanonicalName('default')};`;
} else if ( exportMode === 'named' ) {
exportBlock = bundle.toExport
.map( key => {
const specifier = bundle.entryModule.exports[ key ];
const name = bundle.entryModule.getCanonicalName( specifier.localName );
return `exports.${key} = ${name};`;
})
.join( '\n' );
}
if ( exportBlock ) {
magicString.append( '\n\n' + exportBlock );
}
const exportBlock = getExportBlock( bundle, exportMode, 'module.exports =' );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
return magicString;
}

16
src/finalisers/iife.js

@ -1,6 +1,7 @@
import { blank } from '../utils/object';
import { getName } from '../utils/map-helpers';
import getInteropBlock from './shared/getInteropBlock';
import getExportBlock from './shared/getExportBlock';
export default function iife ( bundle, magicString, { exportMode, indentString }, options ) {
const globalNames = options.globals || blank();
@ -20,19 +21,20 @@ export default function iife ( bundle, magicString, { exportMode, indentString }
args.unshift( 'exports' );
}
let intro = `(function (${args}) { 'use strict';\n\n`;
const useStrict = options.useStrict !== false ? ` 'use strict';` : ``;
let intro = `(function (${args}) {${useStrict}\n\n`;
let outro = `\n\n})(${dependencies});`;
// var foo__default = 'default' in foo ? foo['default'] : foo;
const interopBlock = getInteropBlock( bundle );
if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' );
if ( exportMode === 'default' ) {
intro = `var ${options.moduleName} = ${intro}`;
magicString.append( `\n\nreturn ${bundle.entryModule.getCanonicalName('default')};` );
}
// TODO named exports
// var foo__default = 'default' in foo ? foo['default'] : foo;
const interopBlock = getInteropBlock( bundle );
if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' );
const exportBlock = getExportBlock( bundle, exportMode );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
return magicString
.indent( indentString )

12
src/finalisers/shared/getExportBlock.js

@ -0,0 +1,12 @@
export default function getExportBlock ( bundle, exportMode, mechanism = 'return' ) {
if ( exportMode === 'default' ) {
return `${mechanism} ${bundle.entryModule.getCanonicalName('default')};`;
}
return bundle.toExport
.map( name => {
const prop = name === 'default' ? `['default']` : `.${name}`;
return `exports${prop} = ${bundle.entryModule.getCanonicalName(name)};`;
})
.join( '\n' );
}

10
src/finalisers/shared/getInteropBlock.js

@ -1,6 +1,12 @@
export default function getInteropBlock ( bundle ) {
return bundle.externalModules
.filter( module => module.needsDefault && module.needsNamed )
.map( module => `var ${module.name}__default = 'default' in ${module.name} ? ${module.name}['default'] : ${module.name};` )
.map( module => {
return module.needsDefault ?
( module.needsNamed ?
`var ${module.name}__default = 'default' in ${module.name} ? ${module.name}['default'] : ${module.name};` :
`${module.name} = 'default' in ${module.name} ? ${module.name}['default'] : ${module.name};` ) :
null;
})
.filter( Boolean )
.join( '\n' );
}

26
src/finalisers/umd.js

@ -1,6 +1,7 @@
import { blank } from '../utils/object';
import { getName, quoteId, req } from '../utils/map-helpers';
import getInteropBlock from './shared/getInteropBlock';
import getExportBlock from './shared/getExportBlock';
export default function umd ( bundle, magicString, { exportMode, indentString }, options ) {
if ( exportMode !== 'none' && !options.moduleName ) {
@ -26,18 +27,20 @@ export default function umd ( bundle, magicString, { exportMode, indentString },
}
const amdParams =
( options.moduleId ? `['${options.moduleId}'], ` : `` ) +
( options.moduleId ? `'${options.moduleId}', ` : `` ) +
( amdDeps.length ? `[${amdDeps.join( ', ' )}], ` : `` );
const cjsExport = exportMode === 'default' ? `module.exports = ` : ``;
const defaultExport = exportMode === 'default' ? `global.${options.moduleName} = ` : '';
const useStrict = options.useStrict !== false ? ` 'use strict';` : ``;
const intro =
`(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? ${cjsExport}factory(${cjsDeps.join( ', ' )}) :
typeof define === 'function' && define.amd ? define(${amdParams}factory) :
${defaultExport}factory(${globalDeps});
}(this, function (${args}) { 'use strict';
}(this, function (${args}) {${useStrict}
`.replace( /^\t\t/gm, '' ).replace( /^\t/gm, magicString.getIndentString() );
@ -45,23 +48,8 @@ export default function umd ( bundle, magicString, { exportMode, indentString },
const interopBlock = getInteropBlock( bundle );
if ( interopBlock ) magicString.prepend( interopBlock + '\n\n' );
const exports = bundle.entryModule.exports;
let exportBlock;
if ( exportMode === 'default' ) {
const canonicalName = bundle.entryModule.getCanonicalName( 'default' );
exportBlock = `return ${canonicalName};`;
} else {
exportBlock = bundle.toExport.map( name => {
const canonicalName = bundle.entryModule.getCanonicalName( exports[ name ].localName );
return `exports.${name} = ${canonicalName};`;
}).join( '\n' );
}
if ( exportBlock ) {
magicString.append( '\n\n' + exportBlock );
}
const exportBlock = getExportBlock( bundle, exportMode );
if ( exportBlock ) magicString.append( '\n\n' + exportBlock );
return magicString
.trim()

4
src/rollup.js

@ -1,5 +1,6 @@
import { basename } from './utils/path';
import { writeFile } from 'sander';
import { keys } from './utils/object';
import Bundle from './Bundle';
let SOURCEMAPPING_URL = 'sourceMa';
@ -14,6 +15,9 @@ export function rollup ( options ) {
return bundle.build().then( () => {
return {
imports: bundle.externalModules.map( module => module.id ),
exports: keys( bundle.entryModule.exports ),
generate: options => bundle.render( options ),
write: options => {
if ( !options || !options.dest ) {

7
test/form/banner-and-footer/_config.js

@ -0,0 +1,7 @@
module.exports = {
description: 'adds a banner/footer',
options: {
banner: '/* this is a banner */',
footer: '/* this is a footer */'
}
};

7
test/form/banner-and-footer/_expected/amd.js

@ -0,0 +1,7 @@
/* this is a banner */
define(function () { 'use strict';
console.log( 'hello world' );
});
/* this is a footer */

5
test/form/banner-and-footer/_expected/cjs.js

@ -0,0 +1,5 @@
/* this is a banner */
'use strict';
console.log( 'hello world' );
/* this is a footer */

3
test/form/banner-and-footer/_expected/es6.js

@ -0,0 +1,3 @@
/* this is a banner */
console.log( 'hello world' );
/* this is a footer */

7
test/form/banner-and-footer/_expected/iife.js

@ -0,0 +1,7 @@
/* this is a banner */
(function () { 'use strict';
console.log( 'hello world' );
})();
/* this is a footer */

11
test/form/banner-and-footer/_expected/umd.js

@ -0,0 +1,11 @@
/* this is a banner */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
factory();
}(this, function () { 'use strict';
console.log( 'hello world' );
}));
/* this is a footer */

1
test/form/banner-and-footer/main.js

@ -0,0 +1 @@
console.log( 'hello world' );

2
test/form/external-imports-custom-names/_expected/amd.js

@ -1,5 +1,7 @@
define(['jquery'], function ($) { 'use strict';
$ = 'default' in $ ? $['default'] : $;
$( function () {
$( 'body' ).html( '<h1>hello world!</h1>' );
});

2
test/form/external-imports-custom-names/_expected/iife.js

@ -1,5 +1,7 @@
(function ($) { 'use strict';
$ = 'default' in $ ? $['default'] : $;
$( function () {
$( 'body' ).html( '<h1>hello world!</h1>' );
});

2
test/form/external-imports-custom-names/_expected/umd.js

@ -4,6 +4,8 @@
factory(global.jQuery);
}(this, function ($) { 'use strict';
$ = 'default' in $ ? $['default'] : $;
$( function () {
$( 'body' ).html( '<h1>hello world!</h1>' );
});

1
test/form/external-imports/_expected/amd.js

@ -1,5 +1,6 @@
define(['factory', 'baz', 'shipping-port', 'alphabet'], function (factory, baz, containers, alphabet) { 'use strict';
factory = 'default' in factory ? factory['default'] : factory;
var alphabet__default = 'default' in alphabet ? alphabet['default'] : alphabet;
factory( null );

1
test/form/external-imports/_expected/iife.js

@ -1,5 +1,6 @@
(function (factory,baz,containers,alphabet) { 'use strict';
factory = 'default' in factory ? factory['default'] : factory;
var alphabet__default = 'default' in alphabet ? alphabet['default'] : alphabet;
factory( null );

1
test/form/external-imports/_expected/umd.js

@ -4,6 +4,7 @@
factory(global.factory,global.baz,global.containers,global.alphabet);
}(this, function (factory,baz,containers,alphabet) { 'use strict';
factory = 'default' in factory ? factory['default'] : factory;
var alphabet__default = 'default' in alphabet ? alphabet['default'] : alphabet;
factory( null );

3
test/form/removes-existing-sourcemap-comments/_config.js

@ -0,0 +1,3 @@
module.exports = {
description: 'removes existing sourcemap comments'
};

9
test/form/removes-existing-sourcemap-comments/_expected/amd.js

@ -0,0 +1,9 @@
define(function () { 'use strict';
function foo () {
return 42;
}
console.log( foo() );
});

7
test/form/removes-existing-sourcemap-comments/_expected/cjs.js

@ -0,0 +1,7 @@
'use strict';
function foo () {
return 42;
}
console.log( foo() );

5
test/form/removes-existing-sourcemap-comments/_expected/es6.js

@ -0,0 +1,5 @@
function foo () {
return 42;
}
console.log( foo() );

9
test/form/removes-existing-sourcemap-comments/_expected/iife.js

@ -0,0 +1,9 @@
(function () { 'use strict';
function foo () {
return 42;
}
console.log( foo() );
})();

13
test/form/removes-existing-sourcemap-comments/_expected/umd.js

@ -0,0 +1,13 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
factory();
}(this, function () { 'use strict';
function foo () {
return 42;
}
console.log( foo() );
}));

5
test/form/removes-existing-sourcemap-comments/foo.js

@ -0,0 +1,5 @@
export default function () {
return 42;
}
//# sourceMappingURL=foo.js.map

5
test/form/removes-existing-sourcemap-comments/main.js

@ -0,0 +1,5 @@
import foo from './foo';
console.log( foo() );
//# sourceMappingURL=main.js.map

10
test/function/deconflicts-exports/_config.js

@ -0,0 +1,10 @@
var assert = require( 'assert' );
module.exports = {
description: 'renames variables named `exports` if necessary',
exports: function ( exports ) {
assert.deepEqual( Object.keys( exports ), [ 'a', 'b' ] );
assert.equal( exports.a, 'A' );
assert.equal( exports.b, 42 );
}
};

8
test/function/deconflicts-exports/main.js

@ -0,0 +1,8 @@
var exports = {
number: 21
};
export var a = 'A';
export var b = exports.number * 2;
assert.deepEqual( Object.keys( exports ), [ 'number' ]);

13
test/function/deconflicts-external-imports/_config.js

@ -0,0 +1,13 @@
module.exports = {
description: 'deconflicts external imports',
context: {
require: function ( id ) {
return function () {
return id;
};
}
},
options: {
external: [ 'foo', 'bar' ]
}
};

5
test/function/deconflicts-external-imports/a.js

@ -0,0 +1,5 @@
import foo from 'foo';
export default function () {
assert.equal( foo(), 'foo' );
}

5
test/function/deconflicts-external-imports/b.js

@ -0,0 +1,5 @@
import foo from 'bar';
export default function () {
assert.equal( foo(), 'bar' );
}

5
test/function/deconflicts-external-imports/main.js

@ -0,0 +1,5 @@
import a from './a';
import b from './b';
a();
b();

10
test/function/export-and-import-reference-share-var/_config.js

@ -0,0 +1,10 @@
var assert = require( 'assert' );
module.exports = {
description: 'allows export and import reference to share name',
exports: function ( exports ) {
assert.equal( exports.b, 9 );
}
};
// adapted from es6-module-transpiler

2
test/function/export-and-import-reference-share-var/foo.js

@ -0,0 +1,2 @@
export var a = 1;
assert.equal(a, 1);

13
test/function/export-and-import-reference-share-var/main.js

@ -0,0 +1,13 @@
import { a } from './foo';
// This variable declaration is going to be altered because `b` needs to be
// re-written. We need to make sure that the `a` re-writing and the unaffected
// `c` declarator are not being clobbered by that alteration.
var a_ = a, b = 9, c = 'c';
assert.equal(a, 1);
assert.equal(a_, 1);
assert.equal(b, 9);
assert.equal(c, 'c');
export { b };

3
test/function/import-namespace-from-internal-module-renamed/_config.js

@ -0,0 +1,3 @@
module.exports = {
description: 'correctly exports x as y inside a bundle'
};

3
test/function/import-namespace-from-internal-module-renamed/foo.js

@ -0,0 +1,3 @@
var x = 42;
export { x as y };

3
test/function/import-namespace-from-internal-module-renamed/main.js

@ -0,0 +1,3 @@
import * as foo from './foo';
assert.equal( foo.y, 42 );

34
test/function/uses-supplied-ast/_config.js

@ -0,0 +1,34 @@
var acorn = require( 'acorn' );
var modules = {
'main': 'import foo from \'foo\';\nfoo();',
// the code points to './bar' but the AST points to './baz', so we
// can check the AST is being used
'foo': {
code: 'import bar from \'bar\';\nexport default function foo () {\n\tconsole.log( bar );\n}',
ast: acorn.parse( 'import bar from \'baz\';\nexport default function foo () {\n\tconsole.log( bar );\n}', {
ecmaVersion: 6,
sourceType: 'module'
})
},
'baz': 'export default 42;'
};
module.exports = {
description: 'uses supplied AST',
options: {
resolveId: function ( importee, importer ) {
if ( !importer ) return 'main';
return importee;
},
load: function ( id ) {
if ( id === 'bar' ) {
throw new Error( 'loaded incorrect module' );
}
return modules[ id ];
}
}
};
Loading…
Cancel
Save