From 60a91c04e5000f70018f362ea41b92f780479c51 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 1 Apr 2018 20:03:44 -0700 Subject: [PATCH 001/180] Experimental scope hoisting --- src/Bundler.js | 7 +++- src/Pipeline.js | 4 ++- src/assets/JSAsset.js | 4 +++ src/packagers/JSConcatPackager.js | 28 +++++++++++++++ src/packagers/index.js | 2 +- src/visitors/hoist.js | 59 +++++++++++++++++++++++++++++++ src/worker.js | 13 ++++--- 7 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 src/packagers/JSConcatPackager.js create mode 100644 src/visitors/hoist.js diff --git a/src/Bundler.js b/src/Bundler.js index 376955b658c..37895c0d5ee 100644 --- a/src/Bundler.js +++ b/src/Bundler.js @@ -459,7 +459,12 @@ class Bundler extends EventEmitter { let startTime = Date.now(); let processed = this.cache && (await this.cache.read(asset.name)); if (!processed || asset.shouldInvalidate(processed.cacheData)) { - processed = await this.farm.run(asset.name, asset.package, this.options); + processed = await this.farm.run( + asset.name, + asset.id, + asset.package, + this.options + ); if (this.cache) { this.cache.write(asset.name, processed); } diff --git a/src/Pipeline.js b/src/Pipeline.js index 26ea70ba07c..168b5366792 100644 --- a/src/Pipeline.js +++ b/src/Pipeline.js @@ -11,8 +11,10 @@ class Pipeline { this.parser = new Parser(options); } - async process(path, pkg, options) { + async process(path, id, pkg, options) { let asset = this.parser.getAsset(path, pkg, options); + asset.id = id; + let generated = await this.processAsset(asset); let generatedMap = {}; for (let rendition of generated) { diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 7261c359f74..364e3b9bfa3 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -11,6 +11,7 @@ const babel = require('../transforms/babel'); const generate = require('babel-generator').default; const uglify = require('../transforms/uglify'); const SourceMap = require('../SourceMap'); +const hoist = require('../visitors/hoist'); const IMPORT_RE = /\b(?:import\b|export\b|require\s*\()/; const GLOBAL_RE = /\b(?:process|__dirname|__filename|global|Buffer)\b/; @@ -112,6 +113,9 @@ class JSAsset extends Asset { await babel(this); } + await this.parseIfNeeded(); + this.traverse(hoist); + if (this.options.minify) { await uglify(this); } diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js new file mode 100644 index 00000000000..a4b327bb92c --- /dev/null +++ b/src/packagers/JSConcatPackager.js @@ -0,0 +1,28 @@ +const Packager = require('./Packager'); +const t = require('babel-types'); + +class JSConcatPackager extends Packager { + async start() { + await this.write('(function () {\n'); + } + + async addAsset(asset) { + let js = asset.generated.js; + + for (let [dep, mod] of asset.depAssets) { + let depName = '$' + asset.id + '$require$' + t.toIdentifier(dep.name); + let moduleName = '$' + mod.id + '$exports'; + js = js.split(depName).join(moduleName); + } + + js = js.trim() + '\n'; + + await this.write(js); + } + + async end() { + await this.write('})();'); + } +} + +module.exports = JSConcatPackager; diff --git a/src/packagers/index.js b/src/packagers/index.js index 388c75f56ad..024bc550b31 100644 --- a/src/packagers/index.js +++ b/src/packagers/index.js @@ -1,4 +1,4 @@ -const JSPackager = require('./JSPackager'); +const JSPackager = require('./JSConcatPackager'); const CSSPackager = require('./CSSPackager'); const HTMLPackager = require('./HTMLPackager'); const SourceMapPackager = require('./SourceMapPackager'); diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js new file mode 100644 index 00000000000..6b2ab133c16 --- /dev/null +++ b/src/visitors/hoist.js @@ -0,0 +1,59 @@ +const matchesPattern = require('./matches-pattern'); +const t = require('babel-types'); + +module.exports = { + Program(path, asset) { + let scope = path.scope; + + // Re-crawl scope so we are sure to have all bindings. + scope.crawl(); + + // Rename each binding in the top-level scope to something unique. + for (let name in scope.bindings) { + let newName = '$' + asset.id + '$var$' + name; + scope.rename(name, newName); + } + + // Add variable that represents module.exports + let name = '$' + asset.id + '$exports'; + path.unshiftContainer('body', [ + t.variableDeclaration('var', [ + t.variableDeclarator(t.identifier(name), t.objectExpression([])) + ]) + ]); + + asset.isAstDirty = true; + }, + + MemberExpression(path, asset) { + if (matchesPattern(path.node, 'module.exports')) { + let name = '$' + asset.id + '$exports'; + path.replaceWith(t.identifier(name)); + } + }, + + ReferencedIdentifier(path, asset) { + if (path.node.name === 'exports') { + let name = '$' + asset.id + '$exports'; + path.replaceWith(t.identifier(name)); + } + }, + + CallExpression(path, asset) { + let {callee, arguments: args} = path.node; + + let isRequire = + t.isIdentifier(callee) && + callee.name === 'require' && + args.length === 1 && + t.isStringLiteral(args[0]) && + !path.scope.hasBinding('require'); + + if (isRequire) { + // Generate a variable name based on the current asset id and the module name to require. + // This will be replaced by the final variable name of the resolved asset in the packager. + let name = '$' + asset.id + '$require$' + t.toIdentifier(args[0].value); + path.replaceWith(t.identifier(name)); + } + } +}; diff --git a/src/worker.js b/src/worker.js index f763f36d1ed..3fed5c8bfe4 100644 --- a/src/worker.js +++ b/src/worker.js @@ -15,13 +15,16 @@ function init(options, isLocal = false) { } } -async function run(path, pkg, options, isWarmUp) { +exports.run = async function(path, id, pkg, options, isWarmUp, callback) { try { options.isWarmUp = isWarmUp; - return await pipeline.process(path, pkg, options); - } catch (e) { - e.fileName = path; - throw e; + var result = await pipeline.process(path, id, pkg, options); + + callback(null, result); + } catch (err) { + let returned = err; + returned.fileName = path; + callback(returned); } } From 03f1994fbb639f739cee832c796e0f45e6686895 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 1 Apr 2018 20:58:56 -0700 Subject: [PATCH 002/180] Handle more cases --- src/visitors/hoist.js | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 6b2ab133c16..0485422eba5 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -15,10 +15,12 @@ module.exports = { } // Add variable that represents module.exports - let name = '$' + asset.id + '$exports'; path.unshiftContainer('body', [ t.variableDeclaration('var', [ - t.variableDeclarator(t.identifier(name), t.objectExpression([])) + t.variableDeclarator( + getExportsIdentifier(asset), + t.objectExpression([]) + ) ]) ]); @@ -26,16 +28,28 @@ module.exports = { }, MemberExpression(path, asset) { - if (matchesPattern(path.node, 'module.exports')) { - let name = '$' + asset.id + '$exports'; - path.replaceWith(t.identifier(name)); + if ( + matchesPattern(path.node, 'module.exports') && + !path.scope.hasBinding('module') + ) { + path.replaceWith(getExportsIdentifier(asset)); } }, ReferencedIdentifier(path, asset) { - if (path.node.name === 'exports') { - let name = '$' + asset.id + '$exports'; - path.replaceWith(t.identifier(name)); + if (path.node.name === 'exports' && !path.scope.hasBinding('exports')) { + path.replaceWith(getExportsIdentifier(asset)); + } + }, + + AssignmentExpression(path, asset) { + let left = path.node.left; + if ( + t.isIdentifier(left) && + left.name === 'exports' && + !path.scope.hasBinding('exports') + ) { + path.get('left').replaceWith(getExportsIdentifier(asset)); } }, @@ -57,3 +71,7 @@ module.exports = { } } }; + +function getExportsIdentifier(asset) { + return t.identifier('$' + asset.id + '$exports'); +} From d5f46220eeb7f08cd0ecc7f0179d07ad89d77bf8 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 1 Apr 2018 22:03:54 -0700 Subject: [PATCH 003/180] Allow top-level --- src/transforms/babel.js | 7 ++++++- src/visitors/hoist.js | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/transforms/babel.js b/src/transforms/babel.js index 90cc6caa1c6..63d29c04616 100644 --- a/src/transforms/babel.js +++ b/src/transforms/babel.js @@ -81,7 +81,12 @@ async function getBabelConfig(asset) { if (asset.isES6Module) { return { internal: true, - plugins: [require('babel-plugin-transform-es2015-modules-commonjs')] + plugins: [ + [ + require('babel-plugin-transform-es2015-modules-commonjs'), + {allowTopLevelThis: true} + ] + ] }; } diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 0485422eba5..7aed9ace4fa 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -42,6 +42,12 @@ module.exports = { } }, + ThisExpression(path, asset) { + if (!path.scope.parent) { + path.replaceWith(getExportsIdentifier(asset)); + } + }, + AssignmentExpression(path, asset) { let left = path.node.left; if ( From f21dd4c3252bd0de6848661bdac431c98365f920 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 1 Apr 2018 23:40:25 -0700 Subject: [PATCH 004/180] Bail out of renaming variables when eval is used --- src/transforms/uglify.js | 2 +- src/visitors/hoist.js | 96 ++++++++++++++++++++++++++++++---------- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/transforms/uglify.js b/src/transforms/uglify.js index 49e45e52465..c2979bd8577 100644 --- a/src/transforms/uglify.js +++ b/src/transforms/uglify.js @@ -11,7 +11,7 @@ module.exports = async function(asset) { let options = { warnings: true, mangle: { - toplevel: true + // toplevel: true } }; diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 7aed9ace4fa..a95a3cbb9dc 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -1,49 +1,96 @@ const matchesPattern = require('./matches-pattern'); const t = require('babel-types'); +const template = require('babel-template'); + +const WRAPPER_TEMPLATE = template(` + var NAME = (function () { + var exports = this; + var module = {exports: this}; + BODY; + return module.exports; + }).call({}); +`); + +const EXPORTS_TEMPLATE = template('var NAME = {}'); module.exports = { - Program(path, asset) { - let scope = path.scope; + Program: { + enter(path) { + let hasEval = false; + path.traverse({ + CallExpression(path) { + let callee = path.node.callee; + if ( + t.isIdentifier(callee) && + callee.name === 'eval' && + !path.scope.hasBinding('eval', true) + ) { + hasEval = true; + path.stop(); + } + } + }); - // Re-crawl scope so we are sure to have all bindings. - scope.crawl(); + path.scope.setData('hasEval', hasEval); + }, - // Rename each binding in the top-level scope to something unique. - for (let name in scope.bindings) { - let newName = '$' + asset.id + '$var$' + name; - scope.rename(name, newName); - } + exit(path, asset) { + let scope = path.scope; + + if (scope.getData('hasEval')) { + path.replaceWith( + t.program([ + WRAPPER_TEMPLATE({ + NAME: getExportsIdentifier(asset), + BODY: path.node.body + }) + ]) + ); + } else { + // Re-crawl scope so we are sure to have all bindings. + scope.crawl(); + + // Rename each binding in the top-level scope to something unique. + for (let name in scope.bindings) { + let newName = '$' + asset.id + '$var$' + name; + scope.rename(name, newName); + } - // Add variable that represents module.exports - path.unshiftContainer('body', [ - t.variableDeclaration('var', [ - t.variableDeclarator( - getExportsIdentifier(asset), - t.objectExpression([]) - ) - ]) - ]); - - asset.isAstDirty = true; + // Add variable that represents module.exports + path.unshiftContainer('body', [ + EXPORTS_TEMPLATE({ + NAME: getExportsIdentifier(asset) + }) + ]); + } + + path.stop(); + asset.isAstDirty = true; + } }, MemberExpression(path, asset) { if ( matchesPattern(path.node, 'module.exports') && - !path.scope.hasBinding('module') + !path.scope.hasBinding('module') && + !path.scope.getData('hasEval') ) { path.replaceWith(getExportsIdentifier(asset)); } }, ReferencedIdentifier(path, asset) { - if (path.node.name === 'exports' && !path.scope.hasBinding('exports')) { + if ( + path.node.name === 'exports' && + !path.scope.hasBinding('exports') && + !path.scope.getData('hasEval') + ) { path.replaceWith(getExportsIdentifier(asset)); } }, ThisExpression(path, asset) { - if (!path.scope.parent) { + if (!path.scope.parent && !path.scope.getData('hasEval')) { path.replaceWith(getExportsIdentifier(asset)); } }, @@ -53,7 +100,8 @@ module.exports = { if ( t.isIdentifier(left) && left.name === 'exports' && - !path.scope.hasBinding('exports') + !path.scope.hasBinding('exports') && + !path.scope.getData('hasEval') ) { path.get('left').replaceWith(getExportsIdentifier(asset)); } From 7c8112543d95f3285a78fda21be00b0f35a75e92 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 2 Apr 2018 22:18:33 -0700 Subject: [PATCH 005/180] Bundle hoisting fixes --- src/Bundler.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Bundler.js b/src/Bundler.js index 37895c0d5ee..1bec09dd7d7 100644 --- a/src/Bundler.js +++ b/src/Bundler.js @@ -521,10 +521,7 @@ class Bundler extends EventEmitter { // If the asset is already in a bundle, it is shared. Move it to the lowest common ancestor. if (asset.parentBundle !== bundle) { let commonBundle = bundle.findCommonAncestor(asset.parentBundle); - if ( - asset.parentBundle !== commonBundle && - asset.parentBundle.type === commonBundle.type - ) { + if (asset.parentBundle.type === commonBundle.type) { this.moveAssetToBundle(asset, commonBundle); return; } @@ -538,13 +535,24 @@ class Bundler extends EventEmitter { } } + let isEntryAsset = + asset.parentBundle && asset.parentBundle.entryAsset === asset; + if (!bundle) { // Create the root bundle if it doesn't exist bundle = Bundle.createWithAsset(asset); } else if (dep && dep.dynamic) { + if (isEntryAsset) { + return; + } + // Create a new bundle for dynamic imports bundle = bundle.createChildBundle(asset); } else if (asset.type && !this.packagers.has(asset.type)) { + if (isEntryAsset) { + return; + } + // No packager is available for this asset type. Create a new bundle with only this asset. bundle.createSiblingBundle(asset); } else { @@ -579,7 +587,10 @@ class Bundler extends EventEmitter { moveAssetToBundle(asset, commonBundle) { // Never move the entry asset of a bundle, as it was explicitly requested to be placed in a separate bundle. - if (asset.parentBundle.entryAsset === asset) { + if ( + asset.parentBundle.entryAsset === asset || + asset.parentBundle === commonBundle + ) { return; } From cfa1c6337f2ecbfd7f2d763564a3bdf46af0a58e Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 2 Apr 2018 22:19:04 -0700 Subject: [PATCH 006/180] Support code splitting --- src/Bundler.js | 1 + src/builtins/prelude2.js | 40 +++++++++++ src/packagers/JSConcatPackager.js | 116 +++++++++++++++++++++++++++++- src/visitors/hoist.js | 16 +++++ 4 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 src/builtins/prelude2.js diff --git a/src/Bundler.js b/src/Bundler.js index 1bec09dd7d7..d4df4044ba6 100644 --- a/src/Bundler.js +++ b/src/Bundler.js @@ -492,6 +492,7 @@ class Bundler extends EventEmitter { // that changing it triggers a recompile of the parent. this.watch(dep.name, asset); } else { + dep.parent = asset.name; let assetDep = await this.resolveDep(asset, dep); if (assetDep) { await this.loadAsset(assetDep); diff --git a/src/builtins/prelude2.js b/src/builtins/prelude2.js new file mode 100644 index 00000000000..9b0b1a47afc --- /dev/null +++ b/src/builtins/prelude2.js @@ -0,0 +1,40 @@ +parcelRequire = (function (init) { + // Save the require from previous bundle to this closure if any + var previousRequire = typeof parcelRequire === 'function' && parcelRequire; + var nodeRequire = typeof require === 'function' && require; + var modules = {}; + + function localRequire(name, jumped) { + if (modules[name]) { + return modules[name]; + } + + // if we cannot find the module within our internal map or + // cache jump to the current global require ie. the last bundle + // that was added to the page. + var currentRequire = typeof parcelRequire === 'function' && parcelRequire; + if (!jumped && currentRequire) { + return currentRequire(name, true); + } + + // If there are other bundles on this page the require from the + // previous one is saved to 'previousRequire'. Repeat this as + // many times as there are bundles until the module is found or + // we exhaust the require chain. + if (previousRequire) { + return previousRequire(name, true); + } + + // Try the node require function if it exists. + if (nodeRequire && typeof name === 'string') { + return nodeRequire(name); + } + + var err = new Error('Cannot find module \'' + name + '\''); + err.code = 'MODULE_NOT_FOUND'; + throw err; + } + + modules = init(localRequire); + return localRequire; +}) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index a4b327bb92c..040853611a7 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -1,18 +1,65 @@ const Packager = require('./Packager'); const t = require('babel-types'); +const path = require('path'); +const fs = require('fs'); + +const prelude = fs + .readFileSync(path.join(__dirname, '../builtins/prelude2.js'), 'utf8') + .trim(); class JSConcatPackager extends Packager { async start() { - await this.write('(function () {\n'); + this.exposedModules = new Set(); + + await this.write(prelude + '(function (require) {\n'); + } + + getExportIdentifier(asset) { + return '$' + asset.id + '$exports'; } async addAsset(asset) { let js = asset.generated.js; + // If this module is referenced by another bundle, it needs to be exposed externally. + let isExposed = !Array.from(asset.parentDeps).every(dep => + this.bundle.assets.has(this.bundler.loadedAssets.get(dep.parent)) + ); + + if (isExposed) { + this.exposedModules.add(asset); + } + for (let [dep, mod] of asset.depAssets) { let depName = '$' + asset.id + '$require$' + t.toIdentifier(dep.name); - let moduleName = '$' + mod.id + '$exports'; + let moduleName = this.getExportIdentifier(mod); + + // If this module is not in the current bundle, generate a `require` call for it. + if (!this.bundle.assets.has(mod)) { + moduleName = `require(${mod.id})`; + } + js = js.split(depName).join(moduleName); + + let depResolve = + '$' + asset.id + '$require_resolve$' + t.toIdentifier(dep.name); + let resolved = '' + asset.id; + + if (dep.dynamic && this.bundle.childBundles.has(mod.parentBundle)) { + let bundles = [this.getBundleSpecifier(mod.parentBundle)]; + for (let child of mod.parentBundle.siblingBundles) { + if (!child.isEmpty) { + bundles.push(this.getBundleSpecifier(child)); + await this.addBundleLoader(child.type); + } + } + + bundles.push(mod.id); + resolved = JSON.stringify(bundles); + await this.addBundleLoader(mod.type); + } + + js = js.split(depResolve).join(resolved); } js = js.trim() + '\n'; @@ -20,8 +67,71 @@ class JSConcatPackager extends Packager { await this.write(js); } + getBundleSpecifier(bundle) { + let name = path.basename(bundle.name); + if (bundle.entryAsset) { + return [name, bundle.entryAsset.id]; + } + + return name; + } + + async addAssetToBundle(asset) { + if (this.bundle.assets.has(asset)) { + return; + } + this.bundle.addAsset(asset); + if (!asset.parentBundle) { + asset.parentBundle = this.bundle; + } + + // Add all dependencies as well + for (let child of asset.depAssets.values()) { + await this.addAssetToBundle(child, this.bundle); + } + + await this.addAsset(asset); + } + + async addBundleLoader(bundleType) { + let bundleLoader = this.bundler.loadedAssets.get( + require.resolve('../builtins/bundle-loader') + ); + if (!bundleLoader) { + bundleLoader = await this.bundler.getAsset('_bundle_loader'); + } + + if (bundleLoader) { + await this.addAssetToBundle(bundleLoader); + } else { + return; + } + + let loader = this.options.bundleLoaders[bundleType]; + if (loader) { + let asset = await this.bundler.getAsset(loader); + if (!this.bundle.assets.has(asset)) { + await this.addAssetToBundle(asset); + await this.write( + `${this.getExportIdentifier(bundleLoader)}.register(${JSON.stringify( + bundleType + )},${this.getExportIdentifier(asset)});\n` + ); + } + } + } + async end() { - await this.write('})();'); + if (this.exposedModules.size > 0) { + let exposed = []; + for (let m of this.exposedModules) { + exposed.push(`${m.id}: ${this.getExportIdentifier(m)}`); + } + + await this.write(`return {${exposed.join(', ')}};\n`); + } + + await this.write('});'); } } diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index a95a3cbb9dc..0616afd0ad1 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -118,11 +118,27 @@ module.exports = { !path.scope.hasBinding('require'); if (isRequire) { + if (!asset.dependencies.has(args[0].value)) { + return; + } + // Generate a variable name based on the current asset id and the module name to require. // This will be replaced by the final variable name of the resolved asset in the packager. let name = '$' + asset.id + '$require$' + t.toIdentifier(args[0].value); path.replaceWith(t.identifier(name)); } + + let isRequireResolve = + matchesPattern(callee, 'require.resolve') && + args.length === 1 && + t.isStringLiteral(args[0]) && + !path.scope.hasBinding('require'); + + if (isRequireResolve) { + let name = + '$' + asset.id + '$require_resolve$' + t.toIdentifier(args[0].value); + path.replaceWith(t.identifier(name)); + } } }; From 226dfbae3d1af35f514558083b3421edbb639d07 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 2 Apr 2018 23:18:03 -0700 Subject: [PATCH 007/180] Handle more hoisting edge cases --- src/builtins/prelude2.js | 1 + src/visitors/hoist.js | 62 ++++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/builtins/prelude2.js b/src/builtins/prelude2.js index 9b0b1a47afc..59f48775fbf 100644 --- a/src/builtins/prelude2.js +++ b/src/builtins/prelude2.js @@ -36,5 +36,6 @@ parcelRequire = (function (init) { } modules = init(localRequire); + localRequire.modules = modules; return localRequire; }) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 0616afd0ad1..f1b4d672356 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -16,28 +16,38 @@ const EXPORTS_TEMPLATE = template('var NAME = {}'); module.exports = { Program: { enter(path) { - let hasEval = false; + let shouldWrap = false; path.traverse({ CallExpression(path) { + // If we see an `eval` call, wrap the module in a function. + // Otherwise, local variables accessed inside the eval won't work. let callee = path.node.callee; if ( t.isIdentifier(callee) && callee.name === 'eval' && !path.scope.hasBinding('eval', true) ) { - hasEval = true; + shouldWrap = true; + path.stop(); + } + }, + + ReturnStatement(path) { + // Wrap in a function if we see a top-level return statement. + if (path.getFunctionParent().isProgram()) { + shouldWrap = true; path.stop(); } } }); - path.scope.setData('hasEval', hasEval); + path.scope.setData('shouldWrap', shouldWrap); }, exit(path, asset) { let scope = path.scope; - if (scope.getData('hasEval')) { + if (scope.getData('shouldWrap')) { path.replaceWith( t.program([ WRAPPER_TEMPLATE({ @@ -70,27 +80,41 @@ module.exports = { }, MemberExpression(path, asset) { - if ( - matchesPattern(path.node, 'module.exports') && - !path.scope.hasBinding('module') && - !path.scope.getData('hasEval') - ) { + if (path.scope.hasBinding('module') || path.scope.getData('shouldWrap')) { + return; + } + + if (matchesPattern(path.node, 'module.exports')) { path.replaceWith(getExportsIdentifier(asset)); } + + if (matchesPattern(path.node, 'module.id')) { + path.replaceWith(t.numericLiteral(asset.id)); + } + + if (matchesPattern(path.node, 'module.hot')) { + path.replaceWith(t.identifier('null')); + } + + if (matchesPattern(path.node, 'module.bundle.modules')) { + path.replaceWith( + t.memberExpression(t.identifier('require'), t.identifier('modules')) + ); + } }, ReferencedIdentifier(path, asset) { if ( path.node.name === 'exports' && !path.scope.hasBinding('exports') && - !path.scope.getData('hasEval') + !path.scope.getData('shouldWrap') ) { path.replaceWith(getExportsIdentifier(asset)); } }, ThisExpression(path, asset) { - if (!path.scope.parent && !path.scope.getData('hasEval')) { + if (!path.scope.parent && !path.scope.getData('shouldWrap')) { path.replaceWith(getExportsIdentifier(asset)); } }, @@ -101,12 +125,25 @@ module.exports = { t.isIdentifier(left) && left.name === 'exports' && !path.scope.hasBinding('exports') && - !path.scope.getData('hasEval') + !path.scope.getData('shouldWrap') ) { path.get('left').replaceWith(getExportsIdentifier(asset)); } }, + UnaryExpression(path) { + // Replace `typeof module` with "object" + if ( + path.node.operator === 'typeof' && + t.isIdentifier(path.node.argument) && + path.node.argument.name === 'module' && + !path.scope.hasBinding('module') && + !path.scope.getData('shouldWrap') + ) { + path.replaceWith(t.stringLiteral('object')); + } + }, + CallExpression(path, asset) { let {callee, arguments: args} = path.node; @@ -118,6 +155,7 @@ module.exports = { !path.scope.hasBinding('require'); if (isRequire) { + // Ignore require calls that were ignored earlier. if (!asset.dependencies.has(args[0].value)) { return; } From 89016f55db4bf3503ee10206deebb8fb3cb173a8 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 2 Apr 2018 23:18:30 -0700 Subject: [PATCH 008/180] Dedup added assets e.g. bundle loaders were getting added twice. --- src/packagers/JSConcatPackager.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 040853611a7..83b07d120bb 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -9,6 +9,7 @@ const prelude = fs class JSConcatPackager extends Packager { async start() { + this.addedAssets = new Set(); this.exposedModules = new Set(); await this.write(prelude + '(function (require) {\n'); @@ -19,6 +20,11 @@ class JSConcatPackager extends Packager { } async addAsset(asset) { + if (this.addedAssets.has(asset)) { + return; + } + + this.addedAssets.add(asset); let js = asset.generated.js; // If this module is referenced by another bundle, it needs to be exposed externally. @@ -26,7 +32,7 @@ class JSConcatPackager extends Packager { this.bundle.assets.has(this.bundler.loadedAssets.get(dep.parent)) ); - if (isExposed) { + if (isExposed || this.bundle.entryAsset === asset) { this.exposedModules.add(asset); } From fa235a54a2661520f8078258a60fe14455f26ac0 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 5 Apr 2018 00:18:21 +0200 Subject: [PATCH 009/180] Reimplement ES6 modules for scope hoisting --- src/Bundler.js | 6 ++ src/assets/JSAsset.js | 6 +- src/packagers/JSConcatPackager.js | 39 +++++++++ src/transforms/babel.js | 3 +- src/visitors/hoist.js | 129 +++++++++++++++++++++++++++++- 5 files changed, 179 insertions(+), 4 deletions(-) diff --git a/src/Bundler.js b/src/Bundler.js index d4df4044ba6..3e1164c6f06 100644 --- a/src/Bundler.js +++ b/src/Bundler.js @@ -470,6 +470,12 @@ class Bundler extends EventEmitter { } } + if (processed.generated['@reflect']) { + Object.assign(asset, processed.generated['@reflect']); + + delete processed.generated['@reflect']; + } + asset.buildTime = Date.now() - startTime; asset.generated = processed.generated; asset.hash = processed.hash; diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 364e3b9bfa3..10fe0f13887 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -29,6 +29,7 @@ class JSAsset extends Asset { this.outputCode = null; this.cacheData.env = {}; this.sourceMap = options.rendition ? options.rendition.sourceMap : null; + this.exports = []; } shouldInvalidate(cacheData) { @@ -173,7 +174,10 @@ class JSAsset extends Asset { return { js: code, - map: this.sourceMap + map: this.sourceMap, + '@reflect': { + exports: this.exports + } }; } diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 83b07d120bb..debaa2ffca5 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -11,10 +11,16 @@ class JSConcatPackager extends Packager { async start() { this.addedAssets = new Set(); this.exposedModules = new Set(); + this.exports = new Map(); + this.buffer = ''; await this.write(prelude + '(function (require) {\n'); } + async write(string) { + this.buffer += string; + } + getExportIdentifier(asset) { return '$' + asset.id + '$exports'; } @@ -24,7 +30,12 @@ class JSConcatPackager extends Packager { return; } + console.log('\n\n'); + console.log(asset.name, asset.exports); + console.log('\n\n'); + this.addedAssets.add(asset); + this.exports.set(asset.id, asset.exports); let js = asset.generated.js; // If this module is referenced by another bundle, it needs to be exposed externally. @@ -47,6 +58,14 @@ class JSConcatPackager extends Packager { js = js.split(depName).join(moduleName); + js = js + // $[asset.id]$named_import$a_js => $[module.id]$named_exports + .split('$' + asset.id + '$named_import$' + t.toIdentifier(dep.name)) + .join('$' + mod.id + '$named_export') + // $[asset.id]$expand_exports$a_js => $parcel$expand_exports([module.id]) + .split('$' + asset.id + '$expand_exports$' + t.toIdentifier(dep.name)) + .join('$parcel$expand_exports(' + mod.id + ',' + asset.id + ')'); + let depResolve = '$' + asset.id + '$require_resolve$' + t.toIdentifier(dep.name); let resolved = '' + asset.id; @@ -138,6 +157,26 @@ class JSConcatPackager extends Packager { } await this.write('});'); + + super.write( + this.buffer.replace( + /\$parcel\$expand_exports\(([\d]+),([\d]+)\)/, + (_, from, to) => { + const exports = this.exports.get(Number(from)); + + if (!exports) { + throw new Error(); + } + + return exports + .map( + name => + `var $${to}$named_export$${name} = $${from}$named_export$${name}` + ) + .join('\n'); + } + ) + ); } } diff --git a/src/transforms/babel.js b/src/transforms/babel.js index 63d29c04616..0857dcfbcf8 100644 --- a/src/transforms/babel.js +++ b/src/transforms/babel.js @@ -82,10 +82,11 @@ async function getBabelConfig(asset) { return { internal: true, plugins: [ + /** [ require('babel-plugin-transform-es2015-modules-commonjs'), {allowTopLevelThis: true} - ] + ]**/ ] }; } diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index f1b4d672356..c5b9e5051f8 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -11,7 +11,13 @@ const WRAPPER_TEMPLATE = template(` }).call({}); `); +const BREAK_COMMON_JS = true; + const EXPORTS_TEMPLATE = template('var NAME = {}'); +const EXPORT_ALL_TEMPLATE = template('Object.assign(EXPORTS, SOURCE)'); +const NAMED_EXPORT_TEMPLATE = BREAK_COMMON_JS + ? template('var NAME = INIT') + : template('var NAME = (EXPORTS.BINDING = INIT)'); module.exports = { Program: { @@ -57,13 +63,17 @@ module.exports = { ]) ); } else { + let namedExport = getNamedExportIdentifierName(asset); + // Re-crawl scope so we are sure to have all bindings. scope.crawl(); // Rename each binding in the top-level scope to something unique. for (let name in scope.bindings) { - let newName = '$' + asset.id + '$var$' + name; - scope.rename(name, newName); + if (name.indexOf(namedExport) === -1) { + let newName = '$' + asset.id + '$var$' + name; + scope.rename(name, newName); + } } // Add variable that represents module.exports @@ -177,9 +187,124 @@ module.exports = { '$' + asset.id + '$require_resolve$' + t.toIdentifier(args[0].value); path.replaceWith(t.identifier(name)); } + }, + ExportAllDeclaration(path, asset) { + if (BREAK_COMMON_JS) { + path.replaceWith( + t.identifier( + '$' + + asset.id + + '$expand_exports$' + + t.toIdentifier(path.node.source.value) + ) + ); + } else { + path.replaceWith( + EXPORT_ALL_TEMPLATE({ + EXPORTS: t.identifier('$' + asset.id + '$exports'), + SOURCE: t.identifier( + '$' + + asset.id + + '$require$' + + t.toIdentifier(path.node.source.value) + ) + }) + ); + } + }, + ImportDeclaration(path, asset) { + let {source, specifiers} = path.node; + + if (source && specifiers.length > 0) { + if (BREAK_COMMON_JS) { + path.replaceWith( + t.variableDeclaration( + 'var', + specifiers.map(specifier => + t.variableDeclarator( + specifier.local, + t.identifier( + '$' + + asset.id + + '$named_import$' + + t.toIdentifier(source.value) + + '$' + + specifier.imported.name + ) + ) + ) + ) + ); + } else { + path.replaceWith( + t.variableDeclaration( + 'var', + specifiers.map(specifier => + t.variableDeclarator( + specifier.local, + t.memberExpression( + t.identifier( + '$' + asset.id + '$require$' + t.toIdentifier(source.value) + ), + specifier.imported + ) + ) + ) + ) + ); + } + } + }, + ExportNamedDeclaration(path, asset) { + let {declaration, source, specifiers} = path.node; + + if (!source) { + let declarations; + + if (declaration) { + declarations = declaration.declarations.map(decl => { + asset.exports.push(decl.id.name); + + return getNamedExportVarDecl(asset, decl.id, decl.init); + }); + } else if (specifiers.length > 0) { + declarations = t.variableDeclaration( + 'var', + specifiers.map(specifier => { + asset.exports.push(specifier.exported.name); + + return getNamedExportVarDecl( + asset, + specifier.exported, + specifier.local + ); + }) + ); + } + + if (declarations.length) { + path.replaceWith(t.variableDeclaration('var', declarations)); + } + } } }; +function getNamedExportVarDecl(asset, name, init) { + let varName = getNamedExportIdentifierName(asset, name); + + return NAMED_EXPORT_TEMPLATE({ + NAME: t.identifier(varName), + EXPORTS: getExportsIdentifier(asset), + BINDING: name, + INIT: init + }).declarations[0]; +} + +function getNamedExportIdentifierName(asset, name = '') { + name = t.isIdentifier(name) ? name.name : name; + + return '$' + asset.id + '$named_export$' + name; +} function getExportsIdentifier(asset) { return t.identifier('$' + asset.id + '$exports'); } From 96b6ad2a8e68fec2acd72cb7b7098509089658e9 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 5 Apr 2018 01:24:31 +0200 Subject: [PATCH 010/180] First commonjs support --- src/packagers/JSConcatPackager.js | 43 ++++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index debaa2ffca5..2b2229f4e7f 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -30,10 +30,6 @@ class JSConcatPackager extends Packager { return; } - console.log('\n\n'); - console.log(asset.name, asset.exports); - console.log('\n\n'); - this.addedAssets.add(asset); this.exports.set(asset.id, asset.exports); let js = asset.generated.js; @@ -159,23 +155,34 @@ class JSConcatPackager extends Packager { await this.write('});'); super.write( - this.buffer.replace( - /\$parcel\$expand_exports\(([\d]+),([\d]+)\)/, - (_, from, to) => { - const exports = this.exports.get(Number(from)); + this.buffer + .replace( + /\$parcel\$expand_exports\(([\d]+),([\d]+)\)/g, + (_, from, to) => { + const exports = this.exports.get(Number(from)); + + if (!exports) { + throw new Error(); + } + + return exports + .map( + name => + `var $${to}$named_export$${name} = $${from}$named_export$${name}` + ) + .join('\n'); + } + ) + .replace(/\$([\d]+)\$named_export\$([^ ]+) =/g, (binding, id, name) => { + const exportBinding = `$${id}$exports`; - if (!exports) { - throw new Error(); + // if there is at least two occurences of the exports binding + if (this.buffer.split(exportBinding).length > 2) { + return `${binding} ${exportBinding}.${name} =`; } - return exports - .map( - name => - `var $${to}$named_export$${name} = $${from}$named_export$${name}` - ) - .join('\n'); - } - ) + return binding; + }) ); } } From 287183ad38f0ad2220945361ad4deb2961972e17 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 5 Apr 2018 01:38:07 +0200 Subject: [PATCH 011/180] Add a comment with the filename of each chunk --- src/packagers/JSConcatPackager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 2b2229f4e7f..5c6785f3d77 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -85,7 +85,7 @@ class JSConcatPackager extends Packager { js = js.trim() + '\n'; - await this.write(js); + await this.write(`// ${asset.name}\n${js}`); } getBundleSpecifier(bundle) { From 5bb206fb767f4e3f70ebb5b43ed33b4e41f538f3 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 5 Apr 2018 02:07:42 +0200 Subject: [PATCH 012/180] Replace scope instead of declaring vars --- src/visitors/hoist.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index c5b9e5051f8..6d098021b07 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -217,24 +217,18 @@ module.exports = { if (source && specifiers.length > 0) { if (BREAK_COMMON_JS) { - path.replaceWith( - t.variableDeclaration( - 'var', - specifiers.map(specifier => - t.variableDeclarator( - specifier.local, - t.identifier( - '$' + - asset.id + - '$named_import$' + - t.toIdentifier(source.value) + - '$' + - specifier.imported.name - ) - ) - ) + specifiers.forEach(specifier => + path.scope.rename( + specifier.local.name, + '$' + + asset.id + + '$named_import$' + + t.toIdentifier(source.value) + + '$' + + specifier.imported.name ) ); + path.remove(); } else { path.replaceWith( t.variableDeclaration( From a468a9dce9927452b7c027d5a9613f5610c2f110 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 5 Apr 2018 02:15:02 +0200 Subject: [PATCH 013/180] Use cacheData instead of generate() hack --- src/Bundler.js | 6 ------ src/assets/JSAsset.js | 7 ++----- src/packagers/JSConcatPackager.js | 2 +- src/visitors/hoist.js | 4 ++-- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/Bundler.js b/src/Bundler.js index 3e1164c6f06..d4df4044ba6 100644 --- a/src/Bundler.js +++ b/src/Bundler.js @@ -470,12 +470,6 @@ class Bundler extends EventEmitter { } } - if (processed.generated['@reflect']) { - Object.assign(asset, processed.generated['@reflect']); - - delete processed.generated['@reflect']; - } - asset.buildTime = Date.now() - startTime; asset.generated = processed.generated; asset.hash = processed.hash; diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 10fe0f13887..608258df05b 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -28,8 +28,8 @@ class JSAsset extends Asset { this.isES6Module = false; this.outputCode = null; this.cacheData.env = {}; + this.cacheData.exports = []; this.sourceMap = options.rendition ? options.rendition.sourceMap : null; - this.exports = []; } shouldInvalidate(cacheData) { @@ -174,10 +174,7 @@ class JSAsset extends Asset { return { js: code, - map: this.sourceMap, - '@reflect': { - exports: this.exports - } + map: this.sourceMap }; } diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 5c6785f3d77..fc5e01720d8 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -31,7 +31,7 @@ class JSConcatPackager extends Packager { } this.addedAssets.add(asset); - this.exports.set(asset.id, asset.exports); + this.exports.set(asset.id, asset.cacheData.exports); let js = asset.generated.js; // If this module is referenced by another bundle, it needs to be exposed externally. diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 6d098021b07..018ca5885a9 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -257,7 +257,7 @@ module.exports = { if (declaration) { declarations = declaration.declarations.map(decl => { - asset.exports.push(decl.id.name); + asset.cacheData.exports.push(decl.id.name); return getNamedExportVarDecl(asset, decl.id, decl.init); }); @@ -265,7 +265,7 @@ module.exports = { declarations = t.variableDeclaration( 'var', specifiers.map(specifier => { - asset.exports.push(specifier.exported.name); + asset.cacheData.exports.push(specifier.exported.name); return getNamedExportVarDecl( asset, From 8aee21f1d8e5fe126a90001a62c704cfd9c12e4f Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 5 Apr 2018 02:21:25 +0200 Subject: [PATCH 014/180] Fix cacheData --- src/Bundler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Bundler.js b/src/Bundler.js index d4df4044ba6..4d62b04dfa8 100644 --- a/src/Bundler.js +++ b/src/Bundler.js @@ -473,6 +473,7 @@ class Bundler extends EventEmitter { asset.buildTime = Date.now() - startTime; asset.generated = processed.generated; asset.hash = processed.hash; + asset.cacheData.exports = processed.cacheData.exports; // Call the delegate to get implicit dependencies let dependencies = processed.dependencies; From a55e336d529038e1deebb9e64b880016f22a9e42 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Thu, 5 Apr 2018 01:06:07 -0700 Subject: [PATCH 015/180] Track whether imports are from ES6 --- src/visitors/dependencies.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/visitors/dependencies.js b/src/visitors/dependencies.js index e697db75343..6e407082d4e 100644 --- a/src/visitors/dependencies.js +++ b/src/visitors/dependencies.js @@ -11,19 +11,19 @@ const serviceWorkerPattern = ['navigator', 'serviceWorker', 'register']; module.exports = { ImportDeclaration(node, asset) { asset.isES6Module = true; - addDependency(asset, node.source); + addDependency(asset, node.source, {isES6Import: true}); }, ExportNamedDeclaration(node, asset) { asset.isES6Module = true; if (node.source) { - addDependency(asset, node.source); + addDependency(asset, node.source, {isES6Import: true}); } }, ExportAllDeclaration(node, asset) { asset.isES6Module = true; - addDependency(asset, node.source); + addDependency(asset, node.source, {isES6Import: true}); }, ExportDefaultDeclaration(node, asset) { From a9f10c8bf8b8f5dcb51e1f3bfae88c2af206e5ca Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Thu, 5 Apr 2018 01:09:38 -0700 Subject: [PATCH 016/180] Hoisting improvements --- src/Bundler.js | 2 +- src/assets/JSAsset.js | 1 - src/packagers/JSConcatPackager.js | 90 +++++---- src/visitors/hoist.js | 294 +++++++++++++++++++----------- 4 files changed, 239 insertions(+), 148 deletions(-) diff --git a/src/Bundler.js b/src/Bundler.js index 4d62b04dfa8..c7e92ad1bb6 100644 --- a/src/Bundler.js +++ b/src/Bundler.js @@ -473,7 +473,7 @@ class Bundler extends EventEmitter { asset.buildTime = Date.now() - startTime; asset.generated = processed.generated; asset.hash = processed.hash; - asset.cacheData.exports = processed.cacheData.exports; + asset.cacheData = processed.cacheData; // Call the delegate to get implicit dependencies let dependencies = processed.dependencies; diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 608258df05b..364e3b9bfa3 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -28,7 +28,6 @@ class JSAsset extends Asset { this.isES6Module = false; this.outputCode = null; this.cacheData.env = {}; - this.cacheData.exports = []; this.sourceMap = options.rendition ? options.rendition.sourceMap : null; } diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index fc5e01720d8..fd60eed0e9f 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -54,13 +54,27 @@ class JSConcatPackager extends Packager { js = js.split(depName).join(moduleName); - js = js - // $[asset.id]$named_import$a_js => $[module.id]$named_exports - .split('$' + asset.id + '$named_import$' + t.toIdentifier(dep.name)) - .join('$' + mod.id + '$named_export') - // $[asset.id]$expand_exports$a_js => $parcel$expand_exports([module.id]) - .split('$' + asset.id + '$expand_exports$' + t.toIdentifier(dep.name)) - .join('$parcel$expand_exports(' + mod.id + ',' + asset.id + ')'); + + if (dep.isES6Import) { + if (mod.cacheData.isES6Module) { + js = js + .split('$' + asset.id + '$import$' + t.toIdentifier(dep.name)) + .join('$' + mod.id + '$export'); + } else { + js = js + .split( + '$' + + asset.id + + '$import$' + + t.toIdentifier(dep.name) + + '$default' + ) + .join('$' + mod.id + '$exports'); + js = js + .split('$' + asset.id + '$import$' + t.toIdentifier(dep.name) + '$') + .join('$' + mod.id + '$exports.'); + } + } let depResolve = '$' + asset.id + '$require_resolve$' + t.toIdentifier(dep.name); @@ -85,7 +99,7 @@ class JSConcatPackager extends Packager { js = js.trim() + '\n'; - await this.write(`// ${asset.name}\n${js}`); + await this.write(`\n/* ASSET: ${asset.name} */\n${js}`); } getBundleSpecifier(bundle) { @@ -154,36 +168,36 @@ class JSConcatPackager extends Packager { await this.write('});'); - super.write( - this.buffer - .replace( - /\$parcel\$expand_exports\(([\d]+),([\d]+)\)/g, - (_, from, to) => { - const exports = this.exports.get(Number(from)); - - if (!exports) { - throw new Error(); - } - - return exports - .map( - name => - `var $${to}$named_export$${name} = $${from}$named_export$${name}` - ) - .join('\n'); - } - ) - .replace(/\$([\d]+)\$named_export\$([^ ]+) =/g, (binding, id, name) => { - const exportBinding = `$${id}$exports`; - - // if there is at least two occurences of the exports binding - if (this.buffer.split(exportBinding).length > 2) { - return `${binding} ${exportBinding}.${name} =`; - } - - return binding; - }) - ); + // super.write( + // this.buffer + // .replace( + // /\$parcel\$expand_exports\(([\d]+),([\d]+)\)/g, + // (_, from, to) => { + // const exports = this.exports.get(Number(from)); + // + // if (!exports) { + // throw new Error(); + // } + // + // return exports + // .map( + // name => + // `var $${to}$named_export$${name} = $${from}$named_export$${name}` + // ) + // .join('\n'); + // } + // ) + // .replace(/\$([\d]+)\$named_export\$([^ ]+) =/g, (binding, id, name) => { + // const exportBinding = `$${id}$exports`; + // + // // if there is at least two occurences of the exports binding + // if (this.buffer.split(exportBinding).length > 2) { + // return `${binding} ${exportBinding}.${name} =`; + // } + // + // return binding; + // }) + // ); } } diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 018ca5885a9..7b9a42dbbe9 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -11,17 +11,14 @@ const WRAPPER_TEMPLATE = template(` }).call({}); `); -const BREAK_COMMON_JS = true; - -const EXPORTS_TEMPLATE = template('var NAME = {}'); -const EXPORT_ALL_TEMPLATE = template('Object.assign(EXPORTS, SOURCE)'); -const NAMED_EXPORT_TEMPLATE = BREAK_COMMON_JS - ? template('var NAME = INIT') - : template('var NAME = (EXPORTS.BINDING = INIT)'); +const EXPORT_ASSIGN_TEMPLATE = template('EXPORTS.NAME = LOCAL'); +// const EXPORT_ALL_TEMPLATE = template('Object.assign(EXPORTS, SOURCE)'); module.exports = { Program: { - enter(path) { + enter(path, asset) { + asset.cacheData.exports = {}; + let shouldWrap = false; path.traverse({ CallExpression(path) { @@ -63,25 +60,28 @@ module.exports = { ]) ); } else { - let namedExport = getNamedExportIdentifierName(asset); - // Re-crawl scope so we are sure to have all bindings. scope.crawl(); // Rename each binding in the top-level scope to something unique. for (let name in scope.bindings) { - if (name.indexOf(namedExport) === -1) { + if (!name.startsWith('$' + asset.id)) { let newName = '$' + asset.id + '$var$' + name; scope.rename(name, newName); } } - // Add variable that represents module.exports - path.unshiftContainer('body', [ - EXPORTS_TEMPLATE({ - NAME: getExportsIdentifier(asset) - }) - ]); + // Add variable that represents module.exports if it is referenced. + if (scope.hasGlobal(getExportsIdentifier(asset).name)) { + path.unshiftContainer('body', [ + t.variableDeclaration('var', [ + t.variableDeclarator( + getExportsIdentifier(asset), + t.objectExpression([]) + ) + ]) + ]); + } } path.stop(); @@ -172,8 +172,7 @@ module.exports = { // Generate a variable name based on the current asset id and the module name to require. // This will be replaced by the final variable name of the resolved asset in the packager. - let name = '$' + asset.id + '$require$' + t.toIdentifier(args[0].value); - path.replaceWith(t.identifier(name)); + path.replaceWith(getIdentifier(asset, 'require', args[0].value)); } let isRequireResolve = @@ -183,122 +182,201 @@ module.exports = { !path.scope.hasBinding('require'); if (isRequireResolve) { - let name = - '$' + asset.id + '$require_resolve$' + t.toIdentifier(args[0].value); - path.replaceWith(t.identifier(name)); - } - }, - ExportAllDeclaration(path, asset) { - if (BREAK_COMMON_JS) { - path.replaceWith( - t.identifier( - '$' + - asset.id + - '$expand_exports$' + - t.toIdentifier(path.node.source.value) - ) - ); - } else { - path.replaceWith( - EXPORT_ALL_TEMPLATE({ - EXPORTS: t.identifier('$' + asset.id + '$exports'), - SOURCE: t.identifier( - '$' + - asset.id + - '$require$' + - t.toIdentifier(path.node.source.value) - ) - }) - ); + path.replaceWith(getIdentifier(asset, 'require_resolve', args[0].value)); } }, + ImportDeclaration(path, asset) { - let {source, specifiers} = path.node; - - if (source && specifiers.length > 0) { - if (BREAK_COMMON_JS) { - specifiers.forEach(specifier => - path.scope.rename( - specifier.local.name, - '$' + - asset.id + - '$named_import$' + - t.toIdentifier(source.value) + - '$' + - specifier.imported.name - ) + // For each specifier, rename the local variables to point to the imported name. + // This will be replaced by the final variable name of the resolved asset in the packager. + for (let specifier of path.node.specifiers) { + if (t.isImportDefaultSpecifier(specifier)) { + path.scope.rename( + specifier.local.name, + getName(asset, 'import', path.node.source.value, 'default') ); - path.remove(); - } else { - path.replaceWith( - t.variableDeclaration( - 'var', - specifiers.map(specifier => - t.variableDeclarator( - specifier.local, - t.memberExpression( - t.identifier( - '$' + asset.id + '$require$' + t.toIdentifier(source.value) - ), - specifier.imported - ) - ) - ) + } else if (t.isImportSpecifier(specifier)) { + path.scope.rename( + specifier.local.name, + getName( + asset, + 'import', + path.node.source.value, + specifier.imported.name ) ); + } else if (t.isImportNamespaceSpecifier(specifier)) { + path.scope.rename( + specifier.local.name, + getName(asset, 'require', path.node.source.value) + ); } } + + path.remove(); }, - ExportNamedDeclaration(path, asset) { - let {declaration, source, specifiers} = path.node; - if (!source) { - let declarations; + ExportDefaultDeclaration(path, asset) { + let {declaration} = path.node; + let identifier = getIdentifier(asset, 'export', 'default'); - if (declaration) { - declarations = declaration.declarations.map(decl => { - asset.cacheData.exports.push(decl.id.name); + if (t.isExpression(declaration)) { + // Declare a variable to hold the exported value. + path.replaceWith( + t.variableDeclaration('var', [ + t.variableDeclarator(identifier, declaration) + ]) + ); + } else { + // Rename the declaration to the exported name. + path.replaceWith(declaration); + path.scope.rename(declaration.id.name, identifier.name); + } - return getNamedExportVarDecl(asset, decl.id, decl.init); - }); - } else if (specifiers.length > 0) { - declarations = t.variableDeclaration( - 'var', - specifiers.map(specifier => { - asset.cacheData.exports.push(specifier.exported.name); + // Add assignment to exports object for namespace imports and commonjs. + path.insertAfter( + EXPORT_ASSIGN_TEMPLATE({ + EXPORTS: getExportsIdentifier(asset), + NAME: t.identifier('default'), + LOCAL: identifier + }) + ); + + asset.cacheData.exports[identifier.name] = 'default'; + + // Mark the asset as an ES6 module, so we handle imports correctly in the packager. + asset.cacheData.isES6Module = true; + }, + + ExportNamedDeclaration(path, asset) { + let {declaration, source, specifiers} = path.node; + + if (source) { + for (let specifier of specifiers) { + // Create a variable to re-export from the imported module. + path.insertAfter( + t.variableDeclaration('var', [ + t.variableDeclarator( + getIdentifier(asset, 'export', specifier.exported.name), + getIdentifier(asset, 'import', source.value, specifier.local.name) + ) + ]) + ); - return getNamedExportVarDecl( + path.insertAfter( + EXPORT_ASSIGN_TEMPLATE({ + EXPORTS: getExportsIdentifier(asset), + NAME: t.identifier(specifier.exported.name), + LOCAL: getIdentifier( asset, - specifier.exported, - specifier.local - ); + 'import', + source.value, + specifier.local.name + ) }) ); } - if (declarations.length) { - path.replaceWith(t.variableDeclaration('var', declarations)); + path.remove(); + } else if (declaration) { + path.replaceWith(declaration); + + if ( + t.isFunctionDeclaration(declaration) || + t.isClassDeclaration(declaration) + ) { + addExport(asset, path, declaration.id, declaration.id); + } else if (t.isVariableDeclaration(declaration)) { + for (let d of declaration.declarations) { + if (t.isIdentifier(d.id)) { + addExport(asset, path, d.id, d.id); + } else if (t.isObjectPattern(d.id)) { + for (let prop of d.id.properties) { + if (t.isAssignmentPattern(prop.value)) { + // TODO + } else if (t.isRestProperty(prop)) { + // TODO + } + } + } else if (t.isArrayPattern(d.id) && d.id.elements) { + // TODO + } + } } + } else if (specifiers.length > 0) { + for (let specifier of specifiers) { + addExport(asset, path, specifier.local, specifier.exported); + } + + path.remove(); } + + // Mark the asset as an ES6 module, so we handle imports correctly in the packager. + asset.cacheData.isES6Module = true; + }, + + ExportAllDeclaration(path, asset) { + // if (BREAK_COMMON_JS) { + // path.replaceWith( + // t.identifier( + // '$' + + // asset.id + + // '$expand_exports$' + + // t.toIdentifier(path.node.source.value) + // ) + // ); + // } else { + // path.replaceWith( + // EXPORT_ALL_TEMPLATE({ + // EXPORTS: t.identifier('$' + asset.id + '$exports'), + // SOURCE: t.identifier( + // '$' + + // asset.id + + // '$require$' + + // t.toIdentifier(path.node.source.value) + // ) + // }) + // ); + // } + + asset.cacheData.isES6Module = true; } }; -function getNamedExportVarDecl(asset, name, init) { - let varName = getNamedExportIdentifierName(asset, name); +function addExport(asset, path, local, exported) { + asset.cacheData.exports[getName(asset, 'export', exported.name)] = + exported.name; - return NAMED_EXPORT_TEMPLATE({ - NAME: t.identifier(varName), - EXPORTS: getExportsIdentifier(asset), - BINDING: name, - INIT: init - }).declarations[0]; + path.insertAfter( + EXPORT_ASSIGN_TEMPLATE({ + EXPORTS: getExportsIdentifier(asset), + NAME: t.identifier(local.name), + LOCAL: getIdentifier(asset, 'export', exported.name) + }) + ); + + path.scope.rename(local.name, getName(asset, 'export', exported.name)); } -function getNamedExportIdentifierName(asset, name = '') { - name = t.isIdentifier(name) ? name.name : name; +function getName(asset, type, ...rest) { + return ( + '$' + + asset.id + + '$' + + type + + (rest.length + ? '$' + + rest + .map(name => (name === 'default' ? name : t.toIdentifier(name))) + .join('$') + : '') + ); +} - return '$' + asset.id + '$named_export$' + name; +function getIdentifier(asset, type, ...rest) { + return t.identifier(getName(asset, type, ...rest)); } + function getExportsIdentifier(asset) { - return t.identifier('$' + asset.id + '$exports'); + return getIdentifier(asset, 'exports'); } From f4cef6f7d67611375b117d5bb2820e238951c7b1 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Thu, 5 Apr 2018 01:10:20 -0700 Subject: [PATCH 017/180] Only write prelude when needed --- src/packagers/JSConcatPackager.js | 39 ++++++++++++++++--------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index fd60eed0e9f..d84dfd10a7b 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -11,14 +11,26 @@ class JSConcatPackager extends Packager { async start() { this.addedAssets = new Set(); this.exposedModules = new Set(); - this.exports = new Map(); - this.buffer = ''; - await this.write(prelude + '(function (require) {\n'); - } + for (let asset of this.bundle.assets) { + // If this module is referenced by another bundle, it needs to be exposed externally. + let isExposed = !Array.from(asset.parentDeps).every(dep => + this.bundle.assets.has(this.bundler.loadedAssets.get(dep.parent)) + ); + + if ( + isExposed || + (this.bundle.entryAsset === asset && this.bundle.parentBundle) + ) { + this.exposedModules.add(asset); + } + } - async write(string) { - this.buffer += string; + if (this.exposedModules.size > 0) { + await this.write(prelude + '(function (require) {\n'); + } else { + await this.write('(function () {\n'); + } } getExportIdentifier(asset) { @@ -31,18 +43,8 @@ class JSConcatPackager extends Packager { } this.addedAssets.add(asset); - this.exports.set(asset.id, asset.cacheData.exports); let js = asset.generated.js; - // If this module is referenced by another bundle, it needs to be exposed externally. - let isExposed = !Array.from(asset.parentDeps).every(dep => - this.bundle.assets.has(this.bundler.loadedAssets.get(dep.parent)) - ); - - if (isExposed || this.bundle.entryAsset === asset) { - this.exposedModules.add(asset); - } - for (let [dep, mod] of asset.depAssets) { let depName = '$' + asset.id + '$require$' + t.toIdentifier(dep.name); let moduleName = this.getExportIdentifier(mod); @@ -54,7 +56,6 @@ class JSConcatPackager extends Packager { js = js.split(depName).join(moduleName); - if (dep.isES6Import) { if (mod.cacheData.isES6Module) { js = js @@ -164,10 +165,10 @@ class JSConcatPackager extends Packager { } await this.write(`return {${exposed.join(', ')}};\n`); + } else { + await this.write('})();'); } - await this.write('});'); - // super.write( // this.buffer // .replace( From 2a9cdb7f8f2e2e0832ce63a380793ef6e3ff8380 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Fri, 6 Apr 2018 01:46:55 +0200 Subject: [PATCH 018/180] Support wildcard exports --- src/assets/JSAsset.js | 1 + src/packagers/JSConcatPackager.js | 21 ++++++++++ src/transforms/concat.js | 65 +++++++++++++++++++++++++++++++ src/visitors/hoist.js | 34 +++++----------- 4 files changed, 97 insertions(+), 24 deletions(-) create mode 100644 src/transforms/concat.js diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 364e3b9bfa3..608258df05b 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -28,6 +28,7 @@ class JSAsset extends Asset { this.isES6Module = false; this.outputCode = null; this.cacheData.env = {}; + this.cacheData.exports = []; this.sourceMap = options.rendition ? options.rendition.sourceMap : null; } diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index d84dfd10a7b..a318f9a89d9 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -3,14 +3,22 @@ const t = require('babel-types'); const path = require('path'); const fs = require('fs'); +const concat = require('../transforms/concat'); + const prelude = fs .readFileSync(path.join(__dirname, '../builtins/prelude2.js'), 'utf8') .trim(); class JSConcatPackager extends Packager { + async write(string) { + this.buffer += string; + } + async start() { this.addedAssets = new Set(); this.exposedModules = new Set(); + this.buffer = ''; + this.exports = new Map(); for (let asset of this.bundle.assets) { // If this module is referenced by another bundle, it needs to be exposed externally. @@ -43,6 +51,10 @@ class JSConcatPackager extends Packager { } this.addedAssets.add(asset); + this.exports.set( + asset.id, + Object.keys(asset.cacheData.exports).map(k => asset.cacheData.exports[k]) + ); let js = asset.generated.js; for (let [dep, mod] of asset.depAssets) { @@ -54,6 +66,14 @@ class JSConcatPackager extends Packager { moduleName = `require(${mod.id})`; } + js = js + // $[asset.id]$named_import$a_js => $[module.id]$named_exports + .split('$' + asset.id + '$named_import$' + t.toIdentifier(dep.name)) + .join('$' + mod.id + '$named_export') + // $[asset.id]$expand_exports$a_js => $parcel$expand_exports([module.id]) + .split('$' + asset.id + '$expand_exports$' + t.toIdentifier(dep.name)) + .join('$parcel$expand_exports(' + mod.id + ',' + asset.id + ')'); + js = js.split(depName).join(moduleName); if (dep.isES6Import) { @@ -169,6 +189,7 @@ class JSConcatPackager extends Packager { await this.write('})();'); } + super.write(concat(this.buffer, this.exports)); // super.write( // this.buffer // .replace( diff --git a/src/transforms/concat.js b/src/transforms/concat.js new file mode 100644 index 00000000000..bd8ecdc7033 --- /dev/null +++ b/src/transforms/concat.js @@ -0,0 +1,65 @@ +const babylon = require('babylon'); +const t = require('babel-types'); +const traverse = require('babel-traverse').default; +const generate = require('babel-generator').default; + +// TODO: minify +// TODO: source-map +module.exports = (code, exports) => { + const ast = babylon.parse(code); + let replacements = {}; + + traverse(ast, { + // TODO: not optimal + enter(path) { + path.traverse({ + CallExpression(path) { + let {arguments: args, callee} = path.node; + + if ( + t.isIdentifier(callee) && + callee.name === '$parcel$expand_exports' + ) { + let [from, to] = args; + + if ( + args.length !== 2 || + !t.isNumericLiteral(from) || + !t.isNumericLiteral(to) + ) { + throw new Error( + 'invariant: $parcel$expand_exports takes two number arguments' + ); + } + + const names = exports.get(from.value); + + // TODO: commonjs + if (!names) { + throw new Error(`Cannot find module ${to.value} exports`); + } + + // TODO: use path.scope.rename + names.forEach(name => { + const prev = `$${to.value}$export$${name}`; + const next = `$${from.value}$export$${name}`; + + replacements[prev] = next; + }); + + path.remove(); + } + } + }); + }, + Identifier(path) { + let {name} = path.node; + + if (name in replacements) { + path.replaceWith(t.identifier(replacements[name])); + } + } + }); + + return generate(ast, code).code; +}; diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 7b9a42dbbe9..8c6fc332503 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -316,28 +316,14 @@ module.exports = { }, ExportAllDeclaration(path, asset) { - // if (BREAK_COMMON_JS) { - // path.replaceWith( - // t.identifier( - // '$' + - // asset.id + - // '$expand_exports$' + - // t.toIdentifier(path.node.source.value) - // ) - // ); - // } else { - // path.replaceWith( - // EXPORT_ALL_TEMPLATE({ - // EXPORTS: t.identifier('$' + asset.id + '$exports'), - // SOURCE: t.identifier( - // '$' + - // asset.id + - // '$require$' + - // t.toIdentifier(path.node.source.value) - // ) - // }) - // ); - // } + path.replaceWith( + t.identifier( + '$' + + asset.id + + '$expand_exports$' + + t.toIdentifier(path.node.source.value) + ) + ); asset.cacheData.isES6Module = true; } @@ -347,13 +333,13 @@ function addExport(asset, path, local, exported) { asset.cacheData.exports[getName(asset, 'export', exported.name)] = exported.name; - path.insertAfter( + /*path.insertAfter( EXPORT_ASSIGN_TEMPLATE({ EXPORTS: getExportsIdentifier(asset), NAME: t.identifier(local.name), LOCAL: getIdentifier(asset, 'export', exported.name) }) - ); + );*/ path.scope.rename(local.name, getName(asset, 'export', exported.name)); } From c295470b97d3506e5eb15a75a1fd6954ebe9c159 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Thu, 5 Apr 2018 23:22:35 -0700 Subject: [PATCH 019/180] Implement export all a different way --- src/packagers/JSConcatPackager.js | 41 ++++++++++++++++++------------- src/visitors/dependencies.js | 2 +- src/visitors/hoist.js | 9 +------ 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index a318f9a89d9..e6c80b21ce6 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -3,16 +3,16 @@ const t = require('babel-types'); const path = require('path'); const fs = require('fs'); -const concat = require('../transforms/concat'); +// const concat = require('../transforms/concat'); const prelude = fs .readFileSync(path.join(__dirname, '../builtins/prelude2.js'), 'utf8') .trim(); class JSConcatPackager extends Packager { - async write(string) { - this.buffer += string; - } + // async write(string) { + // this.buffer += string; + // } async start() { this.addedAssets = new Set(); @@ -51,10 +51,6 @@ class JSConcatPackager extends Packager { } this.addedAssets.add(asset); - this.exports.set( - asset.id, - Object.keys(asset.cacheData.exports).map(k => asset.cacheData.exports[k]) - ); let js = asset.generated.js; for (let [dep, mod] of asset.depAssets) { @@ -66,16 +62,22 @@ class JSConcatPackager extends Packager { moduleName = `require(${mod.id})`; } - js = js - // $[asset.id]$named_import$a_js => $[module.id]$named_exports - .split('$' + asset.id + '$named_import$' + t.toIdentifier(dep.name)) - .join('$' + mod.id + '$named_export') - // $[asset.id]$expand_exports$a_js => $parcel$expand_exports([module.id]) - .split('$' + asset.id + '$expand_exports$' + t.toIdentifier(dep.name)) - .join('$parcel$expand_exports(' + mod.id + ',' + asset.id + ')'); - js = js.split(depName).join(moduleName); + // If this was an ES6 export all (e.g. export * from 'foo'), resolve to the original exports. + if (dep.isExportAll) { + for (let exp in mod.cacheData.exports) { + asset.cacheData.exports[ + '$' + asset.id + '$export$' + mod.cacheData.exports[exp] + ] = + mod.cacheData.exports[exp]; + this.exports.set( + '$' + asset.id + '$export$' + mod.cacheData.exports[exp], + exp + ); + } + } + if (dep.isES6Import) { if (mod.cacheData.isES6Module) { js = js @@ -118,6 +120,11 @@ class JSConcatPackager extends Packager { js = js.split(depResolve).join(resolved); } + // Replace all re-exported variables + for (let [name, replacement] of this.exports) { + js = js.split(name).join(replacement); + } + js = js.trim() + '\n'; await this.write(`\n/* ASSET: ${asset.name} */\n${js}`); @@ -189,7 +196,7 @@ class JSConcatPackager extends Packager { await this.write('})();'); } - super.write(concat(this.buffer, this.exports)); + // super.write(concat(this.buffer, this.exports)); // super.write( // this.buffer // .replace( diff --git a/src/visitors/dependencies.js b/src/visitors/dependencies.js index 6e407082d4e..04d2fc11feb 100644 --- a/src/visitors/dependencies.js +++ b/src/visitors/dependencies.js @@ -23,7 +23,7 @@ module.exports = { ExportAllDeclaration(node, asset) { asset.isES6Module = true; - addDependency(asset, node.source, {isES6Import: true}); + addDependency(asset, node.source, {isES6Import: true, isExportAll: true}); }, ExportDefaultDeclaration(node, asset) { diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 8c6fc332503..31715c0e7af 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -316,14 +316,7 @@ module.exports = { }, ExportAllDeclaration(path, asset) { - path.replaceWith( - t.identifier( - '$' + - asset.id + - '$expand_exports$' + - t.toIdentifier(path.node.source.value) - ) - ); + path.remove(); asset.cacheData.isES6Module = true; } From 951ccdcd825a97110c374c638ae7327925140f06 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Thu, 5 Apr 2018 23:23:18 -0700 Subject: [PATCH 020/180] Create commonjs exports object all at once if possible --- src/visitors/hoist.js | 48 ++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 31715c0e7af..c1aa9a07442 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -81,6 +81,22 @@ module.exports = { ) ]) ]); + } else if (Object.keys(asset.cacheData.exports).length > 0) { + path.pushContainer('body', [ + t.variableDeclaration('var', [ + t.variableDeclarator( + getExportsIdentifier(asset), + t.objectExpression( + Object.values(asset.cacheData.exports).map(k => + t.objectProperty( + t.identifier(k), + getIdentifier(asset, 'export', k) + ) + ) + ) + ) + ]) + ]); } } @@ -234,13 +250,15 @@ module.exports = { } // Add assignment to exports object for namespace imports and commonjs. - path.insertAfter( - EXPORT_ASSIGN_TEMPLATE({ - EXPORTS: getExportsIdentifier(asset), - NAME: t.identifier('default'), - LOCAL: identifier - }) - ); + if (path.scope.hasGlobal('module') || path.scope.hasGlobal('exports')) { + path.insertAfter( + EXPORT_ASSIGN_TEMPLATE({ + EXPORTS: getExportsIdentifier(asset), + NAME: t.identifier('default'), + LOCAL: identifier + }) + ); + } asset.cacheData.exports[identifier.name] = 'default'; @@ -326,13 +344,15 @@ function addExport(asset, path, local, exported) { asset.cacheData.exports[getName(asset, 'export', exported.name)] = exported.name; - /*path.insertAfter( - EXPORT_ASSIGN_TEMPLATE({ - EXPORTS: getExportsIdentifier(asset), - NAME: t.identifier(local.name), - LOCAL: getIdentifier(asset, 'export', exported.name) - }) - );*/ + if (path.scope.hasGlobal('module') || path.scope.hasGlobal('exports')) { + path.insertAfter( + EXPORT_ASSIGN_TEMPLATE({ + EXPORTS: getExportsIdentifier(asset), + NAME: t.identifier(local.name), + LOCAL: getIdentifier(asset, 'export', exported.name) + }) + ); + } path.scope.rename(local.name, getName(asset, 'export', exported.name)); } From 69576d6a8cb5eaea2155b819757ed8ca93a17f52 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 7 Apr 2018 03:22:01 +0200 Subject: [PATCH 021/180] Improve cjs es6 interop --- src/packagers/JSConcatPackager.js | 16 ++- src/transforms/concat.js | 164 ++++++++++++++++++++++-------- src/visitors/hoist.js | 22 +++- 3 files changed, 150 insertions(+), 52 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index e6c80b21ce6..f92aae4bc0f 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -3,22 +3,23 @@ const t = require('babel-types'); const path = require('path'); const fs = require('fs'); -// const concat = require('../transforms/concat'); +const concat = require('../transforms/concat'); const prelude = fs .readFileSync(path.join(__dirname, '../builtins/prelude2.js'), 'utf8') .trim(); class JSConcatPackager extends Packager { - // async write(string) { - // this.buffer += string; - // } + async write(string) { + this.buffer += string; + } async start() { this.addedAssets = new Set(); this.exposedModules = new Set(); this.buffer = ''; this.exports = new Map(); + this.moduleMap = new Map(); for (let asset of this.bundle.assets) { // If this module is referenced by another bundle, it needs to be exposed externally. @@ -52,11 +53,16 @@ class JSConcatPackager extends Packager { this.addedAssets.add(asset); let js = asset.generated.js; + let deps = {}; + + this.moduleMap.set(asset.id, deps); for (let [dep, mod] of asset.depAssets) { let depName = '$' + asset.id + '$require$' + t.toIdentifier(dep.name); let moduleName = this.getExportIdentifier(mod); + deps[dep.name] = mod.id; + // If this module is not in the current bundle, generate a `require` call for it. if (!this.bundle.assets.has(mod)) { moduleName = `require(${mod.id})`; @@ -196,7 +202,7 @@ class JSConcatPackager extends Packager { await this.write('})();'); } - // super.write(concat(this.buffer, this.exports)); + super.write(concat(this.buffer, this.exports, this.moduleMap)); // super.write( // this.buffer // .replace( diff --git a/src/transforms/concat.js b/src/transforms/concat.js index bd8ecdc7033..e822b7e8189 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -3,60 +3,140 @@ const t = require('babel-types'); const traverse = require('babel-traverse').default; const generate = require('babel-generator').default; +const EXPORTS_RE = /^\$([\d]+)\$exports$/; +const EXPORT_RE = /^\$([\d]+)\$export\$(.+)$/; + // TODO: minify // TODO: source-map -module.exports = (code, exports) => { + +module.exports = (code, exports, moduleMap) => { const ast = babylon.parse(code); let replacements = {}; + let aliases = {}; + let addedExports = new Set(); + + let resolveModule = (id, name) => moduleMap.get(id)[name]; traverse(ast, { - // TODO: not optimal - enter(path) { - path.traverse({ - CallExpression(path) { - let {arguments: args, callee} = path.node; - - if ( - t.isIdentifier(callee) && - callee.name === '$parcel$expand_exports' - ) { - let [from, to] = args; - - if ( - args.length !== 2 || - !t.isNumericLiteral(from) || - !t.isNumericLiteral(to) - ) { - throw new Error( - 'invariant: $parcel$expand_exports takes two number arguments' - ); - } - - const names = exports.get(from.value); - - // TODO: commonjs - if (!names) { - throw new Error(`Cannot find module ${to.value} exports`); - } - - // TODO: use path.scope.rename - names.forEach(name => { - const prev = `$${to.value}$export$${name}`; - const next = `$${from.value}$export$${name}`; - - replacements[prev] = next; - }); - - path.remove(); - } + CallExpression(path) { + let {arguments: args, callee} = path.node; + + if (!t.isIdentifier(callee)) { + return; + } + + if (callee.name === '$parcel$expand_exports') { + let [id, source] = args; + + if ( + args.length !== 2 || + !t.isNumericLiteral(id) || + !t.isStringLiteral(source) + ) { + throw new Error( + 'invariant: invalid signature, expected : $parcel$expand_exports(number, string)' + ); + } + + let sourceId = resolveModule(id.value, source.value); + + if (typeof sourceId === 'undefined') { + throw new Error(`Cannot find module "${source.value}"`); + } + + let alias = aliases[id.value] || (aliases[id.value] = new Set()); + + alias.add(sourceId); + path.remove(); + } else if (callee.name === '$parcel$require') { + let [id, name] = args; + + if ( + args.length !== 2 || + !t.isNumericLiteral(id) || + !t.isStringLiteral(name) + ) { + throw new Error( + 'invariant: invalid signature, expected : $parcel$require(string)' + ); + } + + const mod = resolveModule(id.value, name.value); + + if (typeof mod === 'undefined') { + throw new Error(`Cannot find module "${name.value}"`); } - }); + + path.replaceWith(t.identifier(`$${mod}$exports`)); + } }, Identifier(path) { let {name} = path.node; - if (name in replacements) { + if (typeof name !== 'string') { + return; + } + + if (replacements.hasOwnProperty(name)) { path.replaceWith(t.identifier(replacements[name])); + + return; + } + + let match = name.match(EXPORTS_RE); + + if (match) { + let id = Number(match[1]); + let alias = aliases[id]; + + if (!path.scope.hasBinding(name) && !addedExports.has(name)) { + let {bindings} = path.scope; + + addedExports.add(name); + path.getStatementParent().insertBefore( + t.variableDeclaration('var', [ + t.variableDeclarator( + t.identifier(name), + t.objectExpression( + Object.keys(bindings) + .map(key => { + let match = key.match(EXPORT_RE); + + if (!match) { + return null; + } + + let matchedId = Number(match[1]); + + if ( + matchedId !== id && + (!alias || !alias.has(matchedId)) + ) { + return null; + } + + let binding = bindings[key]; + let exportName = t.identifier(match[2]); + + if (binding.constant) { + return t.objectProperty(exportName, t.identifier(key)); + } else { + return t.objectMethod( + 'get', + exportName, + [], + t.blockStatement([ + t.returnStatement(t.identifier(key)) + ]) + ); + } + }) + .filter(property => property !== null) + ) + ) + ]) + ); + } } } }); diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index c1aa9a07442..1a1f844791a 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -12,7 +12,8 @@ const WRAPPER_TEMPLATE = template(` `); const EXPORT_ASSIGN_TEMPLATE = template('EXPORTS.NAME = LOCAL'); -// const EXPORT_ALL_TEMPLATE = template('Object.assign(EXPORTS, SOURCE)'); +const EXPORT_ALL_TEMPLATE = template('$parcel$expand_exports(ID, SOURCE)'); +const REQUIRE_CALL_TEMPLATE = template('$parcel$require(ID, SOURCE)'); module.exports = { Program: { @@ -82,7 +83,7 @@ module.exports = { ]) ]); } else if (Object.keys(asset.cacheData.exports).length > 0) { - path.pushContainer('body', [ + /*path.pushContainer('body', [ t.variableDeclaration('var', [ t.variableDeclarator( getExportsIdentifier(asset), @@ -96,7 +97,7 @@ module.exports = { ) ) ]) - ]); + ]);*/ } } @@ -188,7 +189,13 @@ module.exports = { // Generate a variable name based on the current asset id and the module name to require. // This will be replaced by the final variable name of the resolved asset in the packager. - path.replaceWith(getIdentifier(asset, 'require', args[0].value)); + // path.replaceWith(getIdentifier(asset, 'require', args[0].value)); + path.replaceWith( + REQUIRE_CALL_TEMPLATE({ + ID: t.numericLiteral(asset.id), + SOURCE: t.stringLiteral(args[0].value) + }) + ); } let isRequireResolve = @@ -334,7 +341,12 @@ module.exports = { }, ExportAllDeclaration(path, asset) { - path.remove(); + path.replaceWith( + EXPORT_ALL_TEMPLATE({ + ID: t.numericLiteral(asset.id), + SOURCE: t.stringLiteral(path.node.source.value) + }) + ); asset.cacheData.isES6Module = true; } From 7b02682a4e4e6b95dc8deb89d991f4f2cc212a6e Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 7 Apr 2018 03:31:02 +0200 Subject: [PATCH 022/180] fix $parcel$require signature --- src/transforms/concat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index e822b7e8189..1449e1a1922 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -57,7 +57,7 @@ module.exports = (code, exports, moduleMap) => { !t.isStringLiteral(name) ) { throw new Error( - 'invariant: invalid signature, expected : $parcel$require(string)' + 'invariant: invalid signature, expected : $parcel$require(number, string)' ); } From 5d6f119e714bff2ca9dbcaa76160e5d8f18d4f48 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 7 Apr 2018 23:09:59 +0200 Subject: [PATCH 023/180] rework wildcards --- src/packagers/JSConcatPackager.js | 33 +------- src/transforms/concat.js | 131 ++++++++++++++++++++---------- src/visitors/hoist.js | 9 +- 3 files changed, 90 insertions(+), 83 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index f92aae4bc0f..6bc89632f22 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -72,6 +72,9 @@ class JSConcatPackager extends Packager { // If this was an ES6 export all (e.g. export * from 'foo'), resolve to the original exports. if (dep.isExportAll) { + asset.cacheData.exports[`$${asset.id}$exports`] = `$${mod.id}$exports`; + this.exports.set(`$${asset.id}$exports`, `$${mod.id}$exports`); + for (let exp in mod.cacheData.exports) { asset.cacheData.exports[ '$' + asset.id + '$export$' + mod.cacheData.exports[exp] @@ -203,36 +206,6 @@ class JSConcatPackager extends Packager { } super.write(concat(this.buffer, this.exports, this.moduleMap)); - // super.write( - // this.buffer - // .replace( - // /\$parcel\$expand_exports\(([\d]+),([\d]+)\)/g, - // (_, from, to) => { - // const exports = this.exports.get(Number(from)); - // - // if (!exports) { - // throw new Error(); - // } - // - // return exports - // .map( - // name => - // `var $${to}$named_export$${name} = $${from}$named_export$${name}` - // ) - // .join('\n'); - // } - // ) - // .replace(/\$([\d]+)\$named_export\$([^ ]+) =/g, (binding, id, name) => { - // const exportBinding = `$${id}$exports`; - // - // // if there is at least two occurences of the exports binding - // if (this.buffer.split(exportBinding).length > 2) { - // return `${binding} ${exportBinding}.${name} =`; - // } - // - // return binding; - // }) - // ); } } diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 1449e1a1922..46fef1390e9 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -10,45 +10,66 @@ const EXPORT_RE = /^\$([\d]+)\$export\$(.+)$/; // TODO: source-map module.exports = (code, exports, moduleMap) => { - const ast = babylon.parse(code); - let replacements = {}; - let aliases = {}; + let ast = babylon.parse(code); let addedExports = new Set(); + let commentedBindings = new Set(); let resolveModule = (id, name) => moduleMap.get(id)[name]; - traverse(ast, { - CallExpression(path) { - let {arguments: args, callee} = path.node; + function replaceExportNode(id, name, path) { + function tail(symbol) { + // if the symbol is in the scope there is not need to remap it + if (path.scope.hasBinding(symbol)) { + return t.identifier(symbol); + } - if (!t.isIdentifier(callee)) { - return; + // if we have an export alias for this symbol + if (exports.has(symbol)) { + /* recursively lookup the symbol + * this is needed when we have deep export wildcards, like in the following: + * - a.js + * > export * from './b' + * - b.js + * > export * from './c' + * - c.js in es6 + * > export * from 'lodash' + * - c.js in cjs + * > module.exports = require('lodash') + */ + let node = tail(exports.get(symbol)); + + if (node) { + return node; + } } - if (callee.name === '$parcel$expand_exports') { - let [id, source] = args; + return null; + } + let node = tail(`$${id}$export$${name}`); - if ( - args.length !== 2 || - !t.isNumericLiteral(id) || - !t.isStringLiteral(source) - ) { - throw new Error( - 'invariant: invalid signature, expected : $parcel$expand_exports(number, string)' - ); - } + if (!node) { + // if there is no named export then lookup for a CommonJS export + let commonJs = `$${id}$exports`; + node = tail(commonJs); - let sourceId = resolveModule(id.value, source.value); + // if we have a CommonJS export return $id$exports.name + if (node) { + return t.memberExpression(node, t.identifier(name)); + } - if (typeof sourceId === 'undefined') { - throw new Error(`Cannot find module "${source.value}"`); - } + // if there is no binding for the symbol it'll probably fail at runtime + throw new Error(`Cannot find export "${name}" in module ${id}`); + } - let alias = aliases[id.value] || (aliases[id.value] = new Set()); + return node; + } - alias.add(sourceId); - path.remove(); - } else if (callee.name === '$parcel$require') { + traverse(ast, { + CallExpression(path) { + let {arguments: args, callee} = path.node; + + // each require('module') call gets replaced with $parcel$require(id, 'module') + if (t.isIdentifier(callee, {name: '$parcel$require'})) { let [id, name] = args; if ( @@ -77,22 +98,16 @@ module.exports = (code, exports, moduleMap) => { return; } - if (replacements.hasOwnProperty(name)) { - path.replaceWith(t.identifier(replacements[name])); - - return; - } - let match = name.match(EXPORTS_RE); if (match) { - let id = Number(match[1]); - let alias = aliases[id]; + // let id = Number(match[1]); if (!path.scope.hasBinding(name) && !addedExports.has(name)) { let {bindings} = path.scope; addedExports.add(name); + path.getStatementParent().insertBefore( t.variableDeclaration('var', [ t.variableDeclarator( @@ -107,27 +122,37 @@ module.exports = (code, exports, moduleMap) => { } let matchedId = Number(match[1]); + let exportName = match[2]; + // TODO: match correct id + let expr = replaceExportNode(matchedId, exportName, path); - if ( - matchedId !== id && - (!alias || !alias.has(matchedId)) - ) { + if (expr === null) { return null; } let binding = bindings[key]; - let exportName = t.identifier(match[2]); if (binding.constant) { - return t.objectProperty(exportName, t.identifier(key)); + return t.objectProperty(t.identifier(exportName), expr); } else { + if (!commentedBindings.has(binding)) { + commentedBindings.add(binding); + binding.constantViolations.forEach(path => + path + .getFunctionParent() + .addComment( + 'leading', + ` bailout: mutates ${generate(expr).code}`, + '\n' + ) + ); + } + return t.objectMethod( 'get', - exportName, + t.identifier(exportName), [], - t.blockStatement([ - t.returnStatement(t.identifier(key)) - ]) + t.blockStatement([t.returnStatement(expr)]) ); } }) @@ -137,6 +162,22 @@ module.exports = (code, exports, moduleMap) => { ]) ); } + + return; + } + + match = name.match(EXPORT_RE); + + if (match && !path.scope.hasBinding(name) && !addedExports.has(name)) { + let id = Number(match[1]); + let exportName = match[2]; + let node = replaceExportNode(id, exportName, path); + + addedExports.add(name); + + if (node !== undefined) { + path.replaceWith(node); + } } } }); diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 1a1f844791a..0236723c4dd 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -12,7 +12,6 @@ const WRAPPER_TEMPLATE = template(` `); const EXPORT_ASSIGN_TEMPLATE = template('EXPORTS.NAME = LOCAL'); -const EXPORT_ALL_TEMPLATE = template('$parcel$expand_exports(ID, SOURCE)'); const REQUIRE_CALL_TEMPLATE = template('$parcel$require(ID, SOURCE)'); module.exports = { @@ -341,13 +340,7 @@ module.exports = { }, ExportAllDeclaration(path, asset) { - path.replaceWith( - EXPORT_ALL_TEMPLATE({ - ID: t.numericLiteral(asset.id), - SOURCE: t.stringLiteral(path.node.source.value) - }) - ); - + path.remove(); asset.cacheData.isES6Module = true; } }; From a15768c7014db4e73736ab927a843167df2a5130 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 8 Apr 2018 00:23:15 -0700 Subject: [PATCH 024/180] Rename variable when default exported Instead of assigning it to another one --- src/visitors/hoist.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 0236723c4dd..7a9515cc393 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -242,7 +242,11 @@ module.exports = { let {declaration} = path.node; let identifier = getIdentifier(asset, 'export', 'default'); - if (t.isExpression(declaration)) { + if (t.isIdentifier(declaration)) { + // Rename the variable being exported. + path.remove(); + path.scope.rename(declaration.name, identifier.name); + } else if (t.isExpression(declaration)) { // Declare a variable to hold the exported value. path.replaceWith( t.variableDeclaration('var', [ From a4687e50c8c8a6b6eae1aecd26d3f1b5c6656d0b Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 8 Apr 2018 00:24:52 -0700 Subject: [PATCH 025/180] Handle more types of declarations --- src/visitors/hoist.js | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 7a9515cc393..798fbf64f44 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -309,27 +309,9 @@ module.exports = { } else if (declaration) { path.replaceWith(declaration); - if ( - t.isFunctionDeclaration(declaration) || - t.isClassDeclaration(declaration) - ) { - addExport(asset, path, declaration.id, declaration.id); - } else if (t.isVariableDeclaration(declaration)) { - for (let d of declaration.declarations) { - if (t.isIdentifier(d.id)) { - addExport(asset, path, d.id, d.id); - } else if (t.isObjectPattern(d.id)) { - for (let prop of d.id.properties) { - if (t.isAssignmentPattern(prop.value)) { - // TODO - } else if (t.isRestProperty(prop)) { - // TODO - } - } - } else if (t.isArrayPattern(d.id) && d.id.elements) { - // TODO - } - } + let identifiers = t.getBindingIdentifiers(declaration); + for (let id in identifiers) { + addExport(asset, path, identifiers[id], identifiers[id]); } } else if (specifiers.length > 0) { for (let specifier of specifiers) { From 002c2cfbe2c41709fc533969e705f2d860e74d94 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 8 Apr 2018 00:25:36 -0700 Subject: [PATCH 026/180] Handle named re-export extensions --- src/visitors/hoist.js | 46 ++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 798fbf64f44..301dd902989 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -281,28 +281,46 @@ module.exports = { if (source) { for (let specifier of specifiers) { + let local, exported; + + if (t.isExportDefaultSpecifier(specifier)) { + local = getIdentifier(asset, 'import', source.value, 'default'); + exported = specifier.exported; + } else if (t.isExportNamespaceSpecifier(specifier)) { + local = getIdentifier(asset, 'require', source.value); + exported = specifier.exported; + } else if (t.isExportSpecifier(specifier)) { + local = getIdentifier( + asset, + 'import', + source.value, + specifier.local.name + ); + exported = specifier.exported; + } + // Create a variable to re-export from the imported module. path.insertAfter( t.variableDeclaration('var', [ t.variableDeclarator( - getIdentifier(asset, 'export', specifier.exported.name), - getIdentifier(asset, 'import', source.value, specifier.local.name) + getIdentifier(asset, 'export', exported.name), + local ) ]) ); - path.insertAfter( - EXPORT_ASSIGN_TEMPLATE({ - EXPORTS: getExportsIdentifier(asset), - NAME: t.identifier(specifier.exported.name), - LOCAL: getIdentifier( - asset, - 'import', - source.value, - specifier.local.name - ) - }) - ); + if (path.scope.hasGlobal('module') || path.scope.hasGlobal('exports')) { + path.insertAfter( + EXPORT_ASSIGN_TEMPLATE({ + EXPORTS: getExportsIdentifier(asset), + NAME: t.identifier(exported.name), + LOCAL: local + }) + ); + } + + asset.cacheData.exports[getName(asset, 'export', exported.name)] = + exported.name; } path.remove(); From b215046b73cb77186112194a544ee4d2bcef2474 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 8 Apr 2018 00:29:28 -0700 Subject: [PATCH 027/180] Add test suite for scope hoisting --- .../commonjs/default-import/a.js | 3 + .../commonjs/default-import/b.js | 1 + .../commonjs/es6-commonjs-hybrid/a.js | 3 + .../commonjs/es6-commonjs-hybrid/b.js | 2 + .../commonjs/import-namespace/a.js | 3 + .../commonjs/import-namespace/b.js | 1 + .../scope-hoisting/commonjs/named-import/a.js | 3 + .../scope-hoisting/commonjs/named-import/b.js | 1 + .../require-default-export-declaration/a.js | 3 + .../require-default-export-declaration/b.js | 3 + .../require-default-export-expression/a.js | 3 + .../require-default-export-expression/b.js | 1 + .../require-default-export-variable/a.js | 3 + .../require-default-export-variable/b.js | 2 + .../require-named-export-declaration/a.js | 3 + .../require-named-export-declaration/b.js | 1 + .../require-named-export-variable/a.js | 3 + .../require-named-export-variable/b.js | 2 + .../commonjs/require-re-export-all/a.js | 3 + .../commonjs/require-re-export-all/b.js | 2 + .../commonjs/require-re-export-all/c.js | 1 + .../commonjs/require-re-export-default/a.js | 3 + .../commonjs/require-re-export-default/b.js | 2 + .../commonjs/require-re-export-default/c.js | 1 + .../commonjs/require-re-export-multiple/a.js | 3 + .../commonjs/require-re-export-multiple/b.js | 2 + .../commonjs/require-re-export-multiple/c.js | 1 + .../commonjs/require-re-export-multiple/d.js | 1 + .../commonjs/require-re-export-named/a.js | 3 + .../commonjs/require-re-export-named/b.js | 2 + .../commonjs/require-re-export-named/c.js | 1 + .../commonjs/require-re-export-namespace/a.js | 3 + .../commonjs/require-re-export-namespace/b.js | 2 + .../commonjs/require-re-export-namespace/c.js | 1 + .../commonjs/require-renamed-export/a.js | 3 + .../commonjs/require-renamed-export/b.js | 2 + .../scope-hoisting/commonjs/require/a.js | 3 + .../scope-hoisting/commonjs/require/b.js | 1 + .../es6/default-export-declaration/a.js | 3 + .../es6/default-export-declaration/b.js | 3 + .../es6/default-export-expression/a.js | 3 + .../es6/default-export-expression/b.js | 1 + .../es6/default-export-variable/a.js | 3 + .../es6/default-export-variable/b.js | 2 + .../scope-hoisting/es6/import-namespace/a.js | 3 + .../scope-hoisting/es6/import-namespace/b.js | 1 + .../es6/named-export-declaration/a.js | 3 + .../es6/named-export-declaration/b.js | 1 + .../es6/named-export-variable/a.js | 3 + .../es6/named-export-variable/b.js | 2 + .../scope-hoisting/es6/re-export-all/a.js | 3 + .../scope-hoisting/es6/re-export-all/b.js | 2 + .../scope-hoisting/es6/re-export-all/c.js | 1 + .../scope-hoisting/es6/re-export-default/a.js | 3 + .../scope-hoisting/es6/re-export-default/b.js | 2 + .../scope-hoisting/es6/re-export-default/c.js | 1 + .../es6/re-export-multiple/a.js | 3 + .../es6/re-export-multiple/b.js | 2 + .../es6/re-export-multiple/c.js | 1 + .../es6/re-export-multiple/d.js | 1 + .../scope-hoisting/es6/re-export-named/a.js | 3 + .../scope-hoisting/es6/re-export-named/b.js | 2 + .../scope-hoisting/es6/re-export-named/c.js | 1 + .../es6/re-export-namespace/a.js | 3 + .../es6/re-export-namespace/b.js | 2 + .../es6/re-export-namespace/c.js | 1 + .../scope-hoisting/es6/renamed-export/a.js | 3 + .../scope-hoisting/es6/renamed-export/b.js | 2 + .../scope-hoisting/es6/renamed-import/a.js | 3 + .../scope-hoisting/es6/renamed-import/b.js | 1 + test/scope-hoisting.js | 285 ++++++++++++++++++ test/utils.js | 6 +- 72 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 test/integration/scope-hoisting/commonjs/default-import/a.js create mode 100644 test/integration/scope-hoisting/commonjs/default-import/b.js create mode 100644 test/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/a.js create mode 100644 test/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/b.js create mode 100644 test/integration/scope-hoisting/commonjs/import-namespace/a.js create mode 100644 test/integration/scope-hoisting/commonjs/import-namespace/b.js create mode 100644 test/integration/scope-hoisting/commonjs/named-import/a.js create mode 100644 test/integration/scope-hoisting/commonjs/named-import/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-declaration/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-declaration/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-expression/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-expression/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-variable/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-default-export-variable/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-named-export-declaration/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-named-export-declaration/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-named-export-variable/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-named-export-variable/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-all/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-all/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-all/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-default/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-default/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-default/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-multiple/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-multiple/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-multiple/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-multiple/d.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-named/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-named/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-named/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-namespace/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-namespace/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-namespace/c.js create mode 100644 test/integration/scope-hoisting/commonjs/require-renamed-export/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-renamed-export/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require/b.js create mode 100644 test/integration/scope-hoisting/es6/default-export-declaration/a.js create mode 100644 test/integration/scope-hoisting/es6/default-export-declaration/b.js create mode 100644 test/integration/scope-hoisting/es6/default-export-expression/a.js create mode 100644 test/integration/scope-hoisting/es6/default-export-expression/b.js create mode 100644 test/integration/scope-hoisting/es6/default-export-variable/a.js create mode 100644 test/integration/scope-hoisting/es6/default-export-variable/b.js create mode 100644 test/integration/scope-hoisting/es6/import-namespace/a.js create mode 100644 test/integration/scope-hoisting/es6/import-namespace/b.js create mode 100644 test/integration/scope-hoisting/es6/named-export-declaration/a.js create mode 100644 test/integration/scope-hoisting/es6/named-export-declaration/b.js create mode 100644 test/integration/scope-hoisting/es6/named-export-variable/a.js create mode 100644 test/integration/scope-hoisting/es6/named-export-variable/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-all/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-all/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-all/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-default/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-default/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-default/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-multiple/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-multiple/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-multiple/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-multiple/d.js create mode 100644 test/integration/scope-hoisting/es6/re-export-named/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-named/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-named/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-namespace/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-namespace/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-namespace/c.js create mode 100644 test/integration/scope-hoisting/es6/renamed-export/a.js create mode 100644 test/integration/scope-hoisting/es6/renamed-export/b.js create mode 100644 test/integration/scope-hoisting/es6/renamed-import/a.js create mode 100644 test/integration/scope-hoisting/es6/renamed-import/b.js create mode 100644 test/scope-hoisting.js diff --git a/test/integration/scope-hoisting/commonjs/default-import/a.js b/test/integration/scope-hoisting/commonjs/default-import/a.js new file mode 100644 index 00000000000..0b1fbc04f48 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/default-import/a.js @@ -0,0 +1,3 @@ +import foo from './b'; + +output = foo; diff --git a/test/integration/scope-hoisting/commonjs/default-import/b.js b/test/integration/scope-hoisting/commonjs/default-import/b.js new file mode 100644 index 00000000000..4bbffde1044 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/default-import/b.js @@ -0,0 +1 @@ +module.exports = 2; diff --git a/test/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/a.js b/test/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/a.js new file mode 100644 index 00000000000..b02c428e581 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/a.js @@ -0,0 +1,3 @@ +const {a, b} = require('./b'); + +output = a + b; diff --git a/test/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/b.js b/test/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/b.js new file mode 100644 index 00000000000..00f2b7f6129 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/b.js @@ -0,0 +1,2 @@ +export var a = 2; +exports.b = 3; \ No newline at end of file diff --git a/test/integration/scope-hoisting/commonjs/import-namespace/a.js b/test/integration/scope-hoisting/commonjs/import-namespace/a.js new file mode 100644 index 00000000000..40fb3bc0a00 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/import-namespace/a.js @@ -0,0 +1,3 @@ +import * as test from './b'; + +output = test.foo; diff --git a/test/integration/scope-hoisting/commonjs/import-namespace/b.js b/test/integration/scope-hoisting/commonjs/import-namespace/b.js new file mode 100644 index 00000000000..ea0063ffb86 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/import-namespace/b.js @@ -0,0 +1 @@ +exports.foo = 2; diff --git a/test/integration/scope-hoisting/commonjs/named-import/a.js b/test/integration/scope-hoisting/commonjs/named-import/a.js new file mode 100644 index 00000000000..229761a608f --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/named-import/a.js @@ -0,0 +1,3 @@ +import {foo} from './b'; + +output = foo; diff --git a/test/integration/scope-hoisting/commonjs/named-import/b.js b/test/integration/scope-hoisting/commonjs/named-import/b.js new file mode 100644 index 00000000000..ea0063ffb86 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/named-import/b.js @@ -0,0 +1 @@ +exports.foo = 2; diff --git a/test/integration/scope-hoisting/commonjs/require-default-export-declaration/a.js b/test/integration/scope-hoisting/commonjs/require-default-export-declaration/a.js new file mode 100644 index 00000000000..3b74d7866f5 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-default-export-declaration/a.js @@ -0,0 +1,3 @@ +const foo = require('./b').default; + +output = foo(); diff --git a/test/integration/scope-hoisting/commonjs/require-default-export-declaration/b.js b/test/integration/scope-hoisting/commonjs/require-default-export-declaration/b.js new file mode 100644 index 00000000000..f99965aae75 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-default-export-declaration/b.js @@ -0,0 +1,3 @@ +export default function foo() { + return 2; +} diff --git a/test/integration/scope-hoisting/commonjs/require-default-export-expression/a.js b/test/integration/scope-hoisting/commonjs/require-default-export-expression/a.js new file mode 100644 index 00000000000..92ac4c99872 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-default-export-expression/a.js @@ -0,0 +1,3 @@ +const foo = require('./b').default; + +output = foo; diff --git a/test/integration/scope-hoisting/commonjs/require-default-export-expression/b.js b/test/integration/scope-hoisting/commonjs/require-default-export-expression/b.js new file mode 100644 index 00000000000..842e368a0a2 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-default-export-expression/b.js @@ -0,0 +1 @@ +export default 2; diff --git a/test/integration/scope-hoisting/commonjs/require-default-export-variable/a.js b/test/integration/scope-hoisting/commonjs/require-default-export-variable/a.js new file mode 100644 index 00000000000..92ac4c99872 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-default-export-variable/a.js @@ -0,0 +1,3 @@ +const foo = require('./b').default; + +output = foo; diff --git a/test/integration/scope-hoisting/commonjs/require-default-export-variable/b.js b/test/integration/scope-hoisting/commonjs/require-default-export-variable/b.js new file mode 100644 index 00000000000..00109805e6d --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-default-export-variable/b.js @@ -0,0 +1,2 @@ +var foo = 2; +export default foo; diff --git a/test/integration/scope-hoisting/commonjs/require-named-export-declaration/a.js b/test/integration/scope-hoisting/commonjs/require-named-export-declaration/a.js new file mode 100644 index 00000000000..821bed59980 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-named-export-declaration/a.js @@ -0,0 +1,3 @@ +const {foo} = require('./b'); + +output = foo; diff --git a/test/integration/scope-hoisting/commonjs/require-named-export-declaration/b.js b/test/integration/scope-hoisting/commonjs/require-named-export-declaration/b.js new file mode 100644 index 00000000000..2263b5a27c2 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-named-export-declaration/b.js @@ -0,0 +1 @@ +export var foo = 2; diff --git a/test/integration/scope-hoisting/commonjs/require-named-export-variable/a.js b/test/integration/scope-hoisting/commonjs/require-named-export-variable/a.js new file mode 100644 index 00000000000..821bed59980 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-named-export-variable/a.js @@ -0,0 +1,3 @@ +const {foo} = require('./b'); + +output = foo; diff --git a/test/integration/scope-hoisting/commonjs/require-named-export-variable/b.js b/test/integration/scope-hoisting/commonjs/require-named-export-variable/b.js new file mode 100644 index 00000000000..f9d330af7a5 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-named-export-variable/b.js @@ -0,0 +1,2 @@ +var foo = 2; +export {foo}; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-all/a.js b/test/integration/scope-hoisting/commonjs/require-re-export-all/a.js new file mode 100644 index 00000000000..558f70fe546 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-all/a.js @@ -0,0 +1,3 @@ +const {foo, bar, baz} = require('./b'); + +output = foo + bar + baz; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-all/b.js b/test/integration/scope-hoisting/commonjs/require-re-export-all/b.js new file mode 100644 index 00000000000..b60063c4f40 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-all/b.js @@ -0,0 +1,2 @@ +export * from './c'; +export var baz = 1; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-all/c.js b/test/integration/scope-hoisting/commonjs/require-re-export-all/c.js new file mode 100644 index 00000000000..d6a7a6a3e60 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-all/c.js @@ -0,0 +1 @@ +export var foo = 2, bar = 3; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-default/a.js b/test/integration/scope-hoisting/commonjs/require-re-export-default/a.js new file mode 100644 index 00000000000..ccc03bcb999 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-default/a.js @@ -0,0 +1,3 @@ +const {foo, baz} = require('./b'); + +output = foo + baz; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-default/b.js b/test/integration/scope-hoisting/commonjs/require-re-export-default/b.js new file mode 100644 index 00000000000..6c2f9933ca8 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-default/b.js @@ -0,0 +1,2 @@ +export foo from './c'; +export var baz = 1; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-default/c.js b/test/integration/scope-hoisting/commonjs/require-re-export-default/c.js new file mode 100644 index 00000000000..842e368a0a2 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-default/c.js @@ -0,0 +1 @@ +export default 2; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-multiple/a.js b/test/integration/scope-hoisting/commonjs/require-re-export-multiple/a.js new file mode 100644 index 00000000000..4e65a39e476 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-multiple/a.js @@ -0,0 +1,3 @@ +const {foo, bar, baz, d} = require('./b'); + +output = foo + bar + baz + d; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-multiple/b.js b/test/integration/scope-hoisting/commonjs/require-re-export-multiple/b.js new file mode 100644 index 00000000000..b60063c4f40 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-multiple/b.js @@ -0,0 +1,2 @@ +export * from './c'; +export var baz = 1; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-multiple/c.js b/test/integration/scope-hoisting/commonjs/require-re-export-multiple/c.js new file mode 100644 index 00000000000..d6a7a6a3e60 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-multiple/c.js @@ -0,0 +1 @@ +export var foo = 2, bar = 3; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-multiple/d.js b/test/integration/scope-hoisting/commonjs/require-re-export-multiple/d.js new file mode 100644 index 00000000000..cc3b2daa00e --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-multiple/d.js @@ -0,0 +1 @@ +export var d = 1; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-named/a.js b/test/integration/scope-hoisting/commonjs/require-re-export-named/a.js new file mode 100644 index 00000000000..ccc03bcb999 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-named/a.js @@ -0,0 +1,3 @@ +const {foo, baz} = require('./b'); + +output = foo + baz; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-named/b.js b/test/integration/scope-hoisting/commonjs/require-re-export-named/b.js new file mode 100644 index 00000000000..8a4642a5883 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-named/b.js @@ -0,0 +1,2 @@ +export {foo} from './c'; +export var baz = 1; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-named/c.js b/test/integration/scope-hoisting/commonjs/require-re-export-named/c.js new file mode 100644 index 00000000000..d6a7a6a3e60 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-named/c.js @@ -0,0 +1 @@ +export var foo = 2, bar = 3; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-namespace/a.js b/test/integration/scope-hoisting/commonjs/require-re-export-namespace/a.js new file mode 100644 index 00000000000..3fc48702c71 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-namespace/a.js @@ -0,0 +1,3 @@ +const {c, baz} = require('./b'); + +output = c.foo + c.bar + baz; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-namespace/b.js b/test/integration/scope-hoisting/commonjs/require-re-export-namespace/b.js new file mode 100644 index 00000000000..3f1e1af874a --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-namespace/b.js @@ -0,0 +1,2 @@ +export * as c from './c'; +export var baz = 1; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-namespace/c.js b/test/integration/scope-hoisting/commonjs/require-re-export-namespace/c.js new file mode 100644 index 00000000000..d6a7a6a3e60 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-namespace/c.js @@ -0,0 +1 @@ +export var foo = 2, bar = 3; diff --git a/test/integration/scope-hoisting/commonjs/require-renamed-export/a.js b/test/integration/scope-hoisting/commonjs/require-renamed-export/a.js new file mode 100644 index 00000000000..821bed59980 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-renamed-export/a.js @@ -0,0 +1,3 @@ +const {foo} = require('./b'); + +output = foo; diff --git a/test/integration/scope-hoisting/commonjs/require-renamed-export/b.js b/test/integration/scope-hoisting/commonjs/require-renamed-export/b.js new file mode 100644 index 00000000000..bfe56e11c81 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-renamed-export/b.js @@ -0,0 +1,2 @@ +var bar = 2; +export {bar as foo}; diff --git a/test/integration/scope-hoisting/commonjs/require/a.js b/test/integration/scope-hoisting/commonjs/require/a.js new file mode 100644 index 00000000000..de2c5da4b3b --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require/a.js @@ -0,0 +1,3 @@ +const foo = require('./b'); + +output = foo; diff --git a/test/integration/scope-hoisting/commonjs/require/b.js b/test/integration/scope-hoisting/commonjs/require/b.js new file mode 100644 index 00000000000..4bbffde1044 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require/b.js @@ -0,0 +1 @@ +module.exports = 2; diff --git a/test/integration/scope-hoisting/es6/default-export-declaration/a.js b/test/integration/scope-hoisting/es6/default-export-declaration/a.js new file mode 100644 index 00000000000..b37150cd87b --- /dev/null +++ b/test/integration/scope-hoisting/es6/default-export-declaration/a.js @@ -0,0 +1,3 @@ +import foo from './b'; + +output = foo(); diff --git a/test/integration/scope-hoisting/es6/default-export-declaration/b.js b/test/integration/scope-hoisting/es6/default-export-declaration/b.js new file mode 100644 index 00000000000..f99965aae75 --- /dev/null +++ b/test/integration/scope-hoisting/es6/default-export-declaration/b.js @@ -0,0 +1,3 @@ +export default function foo() { + return 2; +} diff --git a/test/integration/scope-hoisting/es6/default-export-expression/a.js b/test/integration/scope-hoisting/es6/default-export-expression/a.js new file mode 100644 index 00000000000..0b1fbc04f48 --- /dev/null +++ b/test/integration/scope-hoisting/es6/default-export-expression/a.js @@ -0,0 +1,3 @@ +import foo from './b'; + +output = foo; diff --git a/test/integration/scope-hoisting/es6/default-export-expression/b.js b/test/integration/scope-hoisting/es6/default-export-expression/b.js new file mode 100644 index 00000000000..842e368a0a2 --- /dev/null +++ b/test/integration/scope-hoisting/es6/default-export-expression/b.js @@ -0,0 +1 @@ +export default 2; diff --git a/test/integration/scope-hoisting/es6/default-export-variable/a.js b/test/integration/scope-hoisting/es6/default-export-variable/a.js new file mode 100644 index 00000000000..0b1fbc04f48 --- /dev/null +++ b/test/integration/scope-hoisting/es6/default-export-variable/a.js @@ -0,0 +1,3 @@ +import foo from './b'; + +output = foo; diff --git a/test/integration/scope-hoisting/es6/default-export-variable/b.js b/test/integration/scope-hoisting/es6/default-export-variable/b.js new file mode 100644 index 00000000000..00109805e6d --- /dev/null +++ b/test/integration/scope-hoisting/es6/default-export-variable/b.js @@ -0,0 +1,2 @@ +var foo = 2; +export default foo; diff --git a/test/integration/scope-hoisting/es6/import-namespace/a.js b/test/integration/scope-hoisting/es6/import-namespace/a.js new file mode 100644 index 00000000000..40fb3bc0a00 --- /dev/null +++ b/test/integration/scope-hoisting/es6/import-namespace/a.js @@ -0,0 +1,3 @@ +import * as test from './b'; + +output = test.foo; diff --git a/test/integration/scope-hoisting/es6/import-namespace/b.js b/test/integration/scope-hoisting/es6/import-namespace/b.js new file mode 100644 index 00000000000..2263b5a27c2 --- /dev/null +++ b/test/integration/scope-hoisting/es6/import-namespace/b.js @@ -0,0 +1 @@ +export var foo = 2; diff --git a/test/integration/scope-hoisting/es6/named-export-declaration/a.js b/test/integration/scope-hoisting/es6/named-export-declaration/a.js new file mode 100644 index 00000000000..229761a608f --- /dev/null +++ b/test/integration/scope-hoisting/es6/named-export-declaration/a.js @@ -0,0 +1,3 @@ +import {foo} from './b'; + +output = foo; diff --git a/test/integration/scope-hoisting/es6/named-export-declaration/b.js b/test/integration/scope-hoisting/es6/named-export-declaration/b.js new file mode 100644 index 00000000000..2263b5a27c2 --- /dev/null +++ b/test/integration/scope-hoisting/es6/named-export-declaration/b.js @@ -0,0 +1 @@ +export var foo = 2; diff --git a/test/integration/scope-hoisting/es6/named-export-variable/a.js b/test/integration/scope-hoisting/es6/named-export-variable/a.js new file mode 100644 index 00000000000..229761a608f --- /dev/null +++ b/test/integration/scope-hoisting/es6/named-export-variable/a.js @@ -0,0 +1,3 @@ +import {foo} from './b'; + +output = foo; diff --git a/test/integration/scope-hoisting/es6/named-export-variable/b.js b/test/integration/scope-hoisting/es6/named-export-variable/b.js new file mode 100644 index 00000000000..f9d330af7a5 --- /dev/null +++ b/test/integration/scope-hoisting/es6/named-export-variable/b.js @@ -0,0 +1,2 @@ +var foo = 2; +export {foo}; diff --git a/test/integration/scope-hoisting/es6/re-export-all/a.js b/test/integration/scope-hoisting/es6/re-export-all/a.js new file mode 100644 index 00000000000..603d59a6f90 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-all/a.js @@ -0,0 +1,3 @@ +import {foo, bar, baz} from './b'; + +output = foo + bar + baz; diff --git a/test/integration/scope-hoisting/es6/re-export-all/b.js b/test/integration/scope-hoisting/es6/re-export-all/b.js new file mode 100644 index 00000000000..b60063c4f40 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-all/b.js @@ -0,0 +1,2 @@ +export * from './c'; +export var baz = 1; diff --git a/test/integration/scope-hoisting/es6/re-export-all/c.js b/test/integration/scope-hoisting/es6/re-export-all/c.js new file mode 100644 index 00000000000..d6a7a6a3e60 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-all/c.js @@ -0,0 +1 @@ +export var foo = 2, bar = 3; diff --git a/test/integration/scope-hoisting/es6/re-export-default/a.js b/test/integration/scope-hoisting/es6/re-export-default/a.js new file mode 100644 index 00000000000..2a5e2f49538 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-default/a.js @@ -0,0 +1,3 @@ +import {foo, baz} from './b'; + +output = foo + baz; diff --git a/test/integration/scope-hoisting/es6/re-export-default/b.js b/test/integration/scope-hoisting/es6/re-export-default/b.js new file mode 100644 index 00000000000..6c2f9933ca8 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-default/b.js @@ -0,0 +1,2 @@ +export foo from './c'; +export var baz = 1; diff --git a/test/integration/scope-hoisting/es6/re-export-default/c.js b/test/integration/scope-hoisting/es6/re-export-default/c.js new file mode 100644 index 00000000000..842e368a0a2 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-default/c.js @@ -0,0 +1 @@ +export default 2; diff --git a/test/integration/scope-hoisting/es6/re-export-multiple/a.js b/test/integration/scope-hoisting/es6/re-export-multiple/a.js new file mode 100644 index 00000000000..6154922e1ad --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-multiple/a.js @@ -0,0 +1,3 @@ +import {foo, bar, baz, d} from './b'; + +output = foo + bar + baz + d; diff --git a/test/integration/scope-hoisting/es6/re-export-multiple/b.js b/test/integration/scope-hoisting/es6/re-export-multiple/b.js new file mode 100644 index 00000000000..b60063c4f40 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-multiple/b.js @@ -0,0 +1,2 @@ +export * from './c'; +export var baz = 1; diff --git a/test/integration/scope-hoisting/es6/re-export-multiple/c.js b/test/integration/scope-hoisting/es6/re-export-multiple/c.js new file mode 100644 index 00000000000..d6a7a6a3e60 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-multiple/c.js @@ -0,0 +1 @@ +export var foo = 2, bar = 3; diff --git a/test/integration/scope-hoisting/es6/re-export-multiple/d.js b/test/integration/scope-hoisting/es6/re-export-multiple/d.js new file mode 100644 index 00000000000..cc3b2daa00e --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-multiple/d.js @@ -0,0 +1 @@ +export var d = 1; diff --git a/test/integration/scope-hoisting/es6/re-export-named/a.js b/test/integration/scope-hoisting/es6/re-export-named/a.js new file mode 100644 index 00000000000..2a5e2f49538 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-named/a.js @@ -0,0 +1,3 @@ +import {foo, baz} from './b'; + +output = foo + baz; diff --git a/test/integration/scope-hoisting/es6/re-export-named/b.js b/test/integration/scope-hoisting/es6/re-export-named/b.js new file mode 100644 index 00000000000..8a4642a5883 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-named/b.js @@ -0,0 +1,2 @@ +export {foo} from './c'; +export var baz = 1; diff --git a/test/integration/scope-hoisting/es6/re-export-named/c.js b/test/integration/scope-hoisting/es6/re-export-named/c.js new file mode 100644 index 00000000000..d6a7a6a3e60 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-named/c.js @@ -0,0 +1 @@ +export var foo = 2, bar = 3; diff --git a/test/integration/scope-hoisting/es6/re-export-namespace/a.js b/test/integration/scope-hoisting/es6/re-export-namespace/a.js new file mode 100644 index 00000000000..eefc5013919 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-namespace/a.js @@ -0,0 +1,3 @@ +import {c, baz} from './b'; + +output = c.foo + c.bar + baz; diff --git a/test/integration/scope-hoisting/es6/re-export-namespace/b.js b/test/integration/scope-hoisting/es6/re-export-namespace/b.js new file mode 100644 index 00000000000..3f1e1af874a --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-namespace/b.js @@ -0,0 +1,2 @@ +export * as c from './c'; +export var baz = 1; diff --git a/test/integration/scope-hoisting/es6/re-export-namespace/c.js b/test/integration/scope-hoisting/es6/re-export-namespace/c.js new file mode 100644 index 00000000000..d6a7a6a3e60 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-namespace/c.js @@ -0,0 +1 @@ +export var foo = 2, bar = 3; diff --git a/test/integration/scope-hoisting/es6/renamed-export/a.js b/test/integration/scope-hoisting/es6/renamed-export/a.js new file mode 100644 index 00000000000..229761a608f --- /dev/null +++ b/test/integration/scope-hoisting/es6/renamed-export/a.js @@ -0,0 +1,3 @@ +import {foo} from './b'; + +output = foo; diff --git a/test/integration/scope-hoisting/es6/renamed-export/b.js b/test/integration/scope-hoisting/es6/renamed-export/b.js new file mode 100644 index 00000000000..bfe56e11c81 --- /dev/null +++ b/test/integration/scope-hoisting/es6/renamed-export/b.js @@ -0,0 +1,2 @@ +var bar = 2; +export {bar as foo}; diff --git a/test/integration/scope-hoisting/es6/renamed-import/a.js b/test/integration/scope-hoisting/es6/renamed-import/a.js new file mode 100644 index 00000000000..009fb8df016 --- /dev/null +++ b/test/integration/scope-hoisting/es6/renamed-import/a.js @@ -0,0 +1,3 @@ +import {foo as bar} from './b'; + +output = bar; diff --git a/test/integration/scope-hoisting/es6/renamed-import/b.js b/test/integration/scope-hoisting/es6/renamed-import/b.js new file mode 100644 index 00000000000..2263b5a27c2 --- /dev/null +++ b/test/integration/scope-hoisting/es6/renamed-import/b.js @@ -0,0 +1 @@ +export var foo = 2; diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js new file mode 100644 index 00000000000..636f58ebcfa --- /dev/null +++ b/test/scope-hoisting.js @@ -0,0 +1,285 @@ +const assert = require('assert'); +const {bundle, run} = require('./utils'); + +describe.only('scope hoisting', function() { + describe('es6', function() { + it('supports default imports and exports of expressions', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/es6/default-export-expression/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports default imports and exports of declarations', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/es6/default-export-declaration/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports default imports and exports of variables', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/es6/default-export-variable/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports named imports and exports of declarations', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/es6/named-export-declaration/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports named imports and exports of variables', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/named-export-variable/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports renaming imports', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/renamed-import/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports renaming exports', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/renamed-export/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports importing a namespace of exported values', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/import-namespace/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports re-exporting all exports from another module', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/re-export-all/a.js' + ); + + let output = run(b); + assert.equal(output, 6); + }); + + it('supports re-exporting all exports from multiple modules', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/re-export-multiple/a.js' + ); + + let output = run(b); + assert.equal(output, 7); + }); + + it('supports re-exporting individual named exports from another module', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/re-export-named/a.js' + ); + + let output = run(b); + assert.equal(output, 3); + }); + + it('supports re-exporting default exports from another module', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/re-export-default/a.js' + ); + + let output = run(b); + assert.equal(output, 3); + }); + + it('supports re-exporting a namespace from another module', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/re-export-namespace/a.js' + ); + + let output = run(b); + assert.equal(output, 6); + }); + }); + + describe('commonjs', function() { + it('supports require of commonjs modules', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/require/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports default imports of commonjs modules', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/default-import/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports named imports of commonjs modules', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/named-import/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports namespace imports of commonjs modules', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/import-namespace/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports require of es6 default export of expressions', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-default-export-expression/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports require of es6 default export of declarations', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-default-export-declaration/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports require of es6 default export of variables', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-default-export-variable/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports require of es6 named export of declarations', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-named-export-declaration/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports require of es6 named export of variables', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-named-export-variable/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports require of es6 renamed exports', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-renamed-export/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports require of es6 module re-exporting all exports from another module', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-re-export-all/a.js' + ); + + let output = run(b); + assert.equal(output, 7); + }); + + it('supports require of es6 module re-exporting all exports from multiple modules', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-re-export-multiple/a.js' + ); + + let output = run(b); + assert.equal(output, 7); + }); + + it('supports re-exporting individual named exports from another module', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-re-export-named/a.js' + ); + + let output = run(b); + assert.equal(output, 3); + }); + + it('supports re-exporting default exports from another module', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-re-export-default/a.js' + ); + + let output = run(b); + assert.equal(output, 3); + }); + + it('supports re-exporting a namespace from another module', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-re-export-namespace/a.js' + ); + + let output = run(b); + assert.equal(output, 6); + }); + + it('supports hybrid ES6 + commonjs mpdules', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/a.js' + ); + + let output = run(b); + assert.equal(output, 5); + }); + }); +}); diff --git a/test/utils.js b/test/utils.js index d26f5664a97..ef3ec7b56ab 100644 --- a/test/utils.js +++ b/test/utils.js @@ -154,7 +154,11 @@ function run(bundle, globals, opts = {}) { vm.runInContext(fs.readFileSync(bundle.name), ctx); if (opts.require !== false) { - return ctx.parcelRequire(bundle.entryAsset.id); + if (ctx.parcelRequire) { + return ctx.parcelRequire(bundle.entryAsset.id); + } else { + return ctx.output; + } } return ctx; From 0ab4b18f7766ddd7adfcefede07cc84ea4607390 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 8 Apr 2018 00:34:35 -0700 Subject: [PATCH 028/180] Resolve namespace imports when possible --- src/transforms/concat.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 46fef1390e9..dd2f9acfb5c 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -91,6 +91,24 @@ module.exports = (code, exports, moduleMap) => { path.replaceWith(t.identifier(`$${mod}$exports`)); } }, + MemberExpression(path) { + if (!path.isReferenced()) { + return; + } + + let {object, property} = path.node; + if (!t.isIdentifier(object) || !t.isIdentifier(property)) { + return; + } + + let match = object.name.match(EXPORTS_RE); + if (match) { + let exportName = '$' + match[1] + '$export$' + property.name; + if (path.scope.hasBinding(exportName)) { + path.replaceWith(t.identifier(exportName)); + } + } + }, Identifier(path) { let {name} = path.node; From b908b59438815851ca056e9daedcdb8b670d3e3d Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 8 Apr 2018 00:35:29 -0700 Subject: [PATCH 029/180] Get correct export names --- src/packagers/JSConcatPackager.js | 31 +++++++++++++++---------------- src/transforms/concat.js | 24 +++++++++++------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 6bc89632f22..d559292e30a 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -53,16 +53,13 @@ class JSConcatPackager extends Packager { this.addedAssets.add(asset); let js = asset.generated.js; - let deps = {}; - this.moduleMap.set(asset.id, deps); + this.moduleMap.set(asset.id, asset); for (let [dep, mod] of asset.depAssets) { let depName = '$' + asset.id + '$require$' + t.toIdentifier(dep.name); let moduleName = this.getExportIdentifier(mod); - deps[dep.name] = mod.id; - // If this module is not in the current bundle, generate a `require` call for it. if (!this.bundle.assets.has(mod)) { moduleName = `require(${mod.id})`; @@ -72,17 +69,17 @@ class JSConcatPackager extends Packager { // If this was an ES6 export all (e.g. export * from 'foo'), resolve to the original exports. if (dep.isExportAll) { - asset.cacheData.exports[`$${asset.id}$exports`] = `$${mod.id}$exports`; - this.exports.set(`$${asset.id}$exports`, `$${mod.id}$exports`); + // asset.cacheData.exports[`$${asset.id}$exports`] = `$${mod.id}$exports`; + // this.exports.set(`$${asset.id}$exports`, `$${mod.id}$exports`); for (let exp in mod.cacheData.exports) { - asset.cacheData.exports[ - '$' + asset.id + '$export$' + mod.cacheData.exports[exp] - ] = - mod.cacheData.exports[exp]; + let key = '$' + asset.id + '$export$' + mod.cacheData.exports[exp]; + asset.cacheData.exports[key] = mod.cacheData.exports[exp]; this.exports.set( - '$' + asset.id + '$export$' + mod.cacheData.exports[exp], - exp + key, + this.exports.get( + '$' + mod.id + '$export$' + mod.cacheData.exports[exp] + ) || exp ); } } @@ -130,13 +127,15 @@ class JSConcatPackager extends Packager { } // Replace all re-exported variables - for (let [name, replacement] of this.exports) { - js = js.split(name).join(replacement); - } js = js.trim() + '\n'; - await this.write(`\n/* ASSET: ${asset.name} */\n${js}`); + await this.write( + `\n/* ASSET: ${asset.id} - ${path.relative( + this.options.rootDir, + asset.name + )} */\n${js}` + ); } getBundleSpecifier(bundle) { diff --git a/src/transforms/concat.js b/src/transforms/concat.js index dd2f9acfb5c..e620a694581 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -14,7 +14,10 @@ module.exports = (code, exports, moduleMap) => { let addedExports = new Set(); let commentedBindings = new Set(); - let resolveModule = (id, name) => moduleMap.get(id)[name]; + let resolveModule = (id, name) => { + let module = moduleMap.get(id); + return module.depAssets.get(module.dependencies.get(name)).id; + }; function replaceExportNode(id, name, path) { function tail(symbol) { @@ -26,7 +29,7 @@ module.exports = (code, exports, moduleMap) => { // if we have an export alias for this symbol if (exports.has(symbol)) { /* recursively lookup the symbol - * this is needed when we have deep export wildcards, like in the following: + * this is needed when we have deep export wildcards, like in the following: * - a.js * > export * from './b' * - b.js @@ -122,7 +125,7 @@ module.exports = (code, exports, moduleMap) => { // let id = Number(match[1]); if (!path.scope.hasBinding(name) && !addedExports.has(name)) { - let {bindings} = path.scope; + let exports = moduleMap.get(+match[1]).cacheData.exports; addedExports.add(name); @@ -131,25 +134,20 @@ module.exports = (code, exports, moduleMap) => { t.variableDeclarator( t.identifier(name), t.objectExpression( - Object.keys(bindings) + Object.keys(exports) .map(key => { - let match = key.match(EXPORT_RE); - - if (!match) { + let binding = path.scope.getBinding(key); + if (!binding) { return null; } - let matchedId = Number(match[1]); - let exportName = match[2]; - // TODO: match correct id - let expr = replaceExportNode(matchedId, exportName, path); + let exportName = exports[key]; + let expr = replaceExportNode(+match[1], exportName, path); if (expr === null) { return null; } - let binding = bindings[key]; - if (binding.constant) { return t.objectProperty(t.identifier(exportName), expr); } else { From 48bb023c42686d5dd36716f283a8aa35ca5a9f64 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 9 Apr 2018 01:00:38 +0200 Subject: [PATCH 030/180] Fix wildcards --- src/assets/JSAsset.js | 1 - src/packagers/JSConcatPackager.js | 9 +++++---- src/transforms/concat.js | 33 +++++++++++++++++-------------- src/visitors/hoist.js | 4 +++- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 608258df05b..364e3b9bfa3 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -28,7 +28,6 @@ class JSAsset extends Asset { this.isES6Module = false; this.outputCode = null; this.cacheData.env = {}; - this.cacheData.exports = []; this.sourceMap = options.rendition ? options.rendition.sourceMap : null; } diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index d559292e30a..fe3369d2eb8 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -19,6 +19,7 @@ class JSConcatPackager extends Packager { this.exposedModules = new Set(); this.buffer = ''; this.exports = new Map(); + this.wildcards = new Map(); this.moduleMap = new Map(); for (let asset of this.bundle.assets) { @@ -55,6 +56,7 @@ class JSConcatPackager extends Packager { let js = asset.generated.js; this.moduleMap.set(asset.id, asset); + this.wildcards.set(asset.id, asset.cacheData.wildcards); for (let [dep, mod] of asset.depAssets) { let depName = '$' + asset.id + '$require$' + t.toIdentifier(dep.name); @@ -69,9 +71,6 @@ class JSConcatPackager extends Packager { // If this was an ES6 export all (e.g. export * from 'foo'), resolve to the original exports. if (dep.isExportAll) { - // asset.cacheData.exports[`$${asset.id}$exports`] = `$${mod.id}$exports`; - // this.exports.set(`$${asset.id}$exports`, `$${mod.id}$exports`); - for (let exp in mod.cacheData.exports) { let key = '$' + asset.id + '$export$' + mod.cacheData.exports[exp]; asset.cacheData.exports[key] = mod.cacheData.exports[exp]; @@ -204,7 +203,9 @@ class JSConcatPackager extends Packager { await this.write('})();'); } - super.write(concat(this.buffer, this.exports, this.moduleMap)); + super.write( + concat(this.buffer, this.exports, this.moduleMap, this.wildcards) + ); } } diff --git a/src/transforms/concat.js b/src/transforms/concat.js index e620a694581..1ff5bf0d655 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -9,7 +9,7 @@ const EXPORT_RE = /^\$([\d]+)\$export\$(.+)$/; // TODO: minify // TODO: source-map -module.exports = (code, exports, moduleMap) => { +module.exports = (code, exports, moduleMap, wildcards) => { let ast = babylon.parse(code); let addedExports = new Set(); let commentedBindings = new Set(); @@ -20,16 +20,18 @@ module.exports = (code, exports, moduleMap) => { }; function replaceExportNode(id, name, path) { - function tail(symbol) { + function tail(id, symbol) { + let computedSymbol = symbol(id); + // if the symbol is in the scope there is not need to remap it - if (path.scope.hasBinding(symbol)) { - return t.identifier(symbol); + if (path.scope.hasBinding(computedSymbol)) { + return t.identifier(computedSymbol); } - // if we have an export alias for this symbol - if (exports.has(symbol)) { + // if there is a wildcard for the module + if (wildcards.has(id)) { /* recursively lookup the symbol - * this is needed when we have deep export wildcards, like in the following: + * this is needed when there is deep export wildcards, like in the following: * - a.js * > export * from './b' * - b.js @@ -39,23 +41,24 @@ module.exports = (code, exports, moduleMap) => { * - c.js in cjs * > module.exports = require('lodash') */ - let node = tail(exports.get(symbol)); + let node = null; - if (node) { - return node; - } + wildcards + .get(id) + .find(name => (node = tail(resolveModule(id, name), symbol))); + + return node; } return null; } - let node = tail(`$${id}$export$${name}`); + let node = tail(id, id => `$${id}$export$${name}`); if (!node) { // if there is no named export then lookup for a CommonJS export - let commonJs = `$${id}$exports`; - node = tail(commonJs); + node = tail(id, id => `$${id}$exports`); - // if we have a CommonJS export return $id$exports.name + // if there is a CommonJS export return $id$exports.name if (node) { return t.memberExpression(node, t.identifier(name)); } diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 301dd902989..8c2b2b5afa5 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -18,6 +18,7 @@ module.exports = { Program: { enter(path, asset) { asset.cacheData.exports = {}; + asset.cacheData.wildcards = []; let shouldWrap = false; path.traverse({ @@ -344,8 +345,9 @@ module.exports = { }, ExportAllDeclaration(path, asset) { - path.remove(); + asset.cacheData.wildcards.push(path.node.source.value); asset.cacheData.isES6Module = true; + path.remove(); } }; From 337a06aa5d6d9c4109b707b3201bcb4bd03f6757 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 9 Apr 2018 01:04:08 +0200 Subject: [PATCH 031/180] Fix es6 wildcard test --- test/integration/scope-hoisting/es6/re-export-multiple/c.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/scope-hoisting/es6/re-export-multiple/c.js b/test/integration/scope-hoisting/es6/re-export-multiple/c.js index d6a7a6a3e60..75f71b809cf 100644 --- a/test/integration/scope-hoisting/es6/re-export-multiple/c.js +++ b/test/integration/scope-hoisting/es6/re-export-multiple/c.js @@ -1 +1,2 @@ export var foo = 2, bar = 3; +export * from './d' From c384d54443d8136fdafc8e3081e4bf8c9a951ac0 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 9 Apr 2018 03:35:49 +0200 Subject: [PATCH 032/180] Add export scope test --- .../scope-hoisting/commonjs/define-exports/a.js | 7 +++++++ test/scope-hoisting.js | 11 ++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 test/integration/scope-hoisting/commonjs/define-exports/a.js diff --git a/test/integration/scope-hoisting/commonjs/define-exports/a.js b/test/integration/scope-hoisting/commonjs/define-exports/a.js new file mode 100644 index 00000000000..5793ac2853d --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/define-exports/a.js @@ -0,0 +1,7 @@ +export const foo = 'bar' + +export function getExports() { + return exports +} + +output = getExports() === exports && getExports().foo diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index 636f58ebcfa..aa0df46c075 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -272,7 +272,7 @@ describe.only('scope hoisting', function() { assert.equal(output, 6); }); - it('supports hybrid ES6 + commonjs mpdules', async function() { + it('supports hybrid ES6 + commonjs modules', async function() { let b = await bundle( __dirname + '/integration/scope-hoisting/commonjs/es6-commonjs-hybrid/a.js' @@ -281,5 +281,14 @@ describe.only('scope hoisting', function() { let output = run(b); assert.equal(output, 5); }); + + it('define exports in the outermost scope', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/define-exports/a.js' + ); + + let output = run(b); + assert.equal(output, 'bar'); + }); }); }); From 46d8096e1a237f22c729d45ad693a4a8ec5549be Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 9 Apr 2018 03:38:16 +0200 Subject: [PATCH 033/180] Fix export module scope --- src/transforms/concat.js | 65 +++++++++++++++++++++++++++------------- src/visitors/hoist.js | 38 ----------------------- 2 files changed, 45 insertions(+), 58 deletions(-) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 1ff5bf0d655..01005b46ce1 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -20,6 +20,22 @@ module.exports = (code, exports, moduleMap, wildcards) => { }; function replaceExportNode(id, name, path) { + path = getOuterStatement(path); + + let node = tail(id, id => `$${id}$export$${name}`); + + if (!node) { + // if there is no named export then lookup for a CommonJS export + node = tail(id, id => `$${id}$exports`) || t.identifier(`$${id}$exports`); + + // if there is a CommonJS export return $id$exports.name + if (node) { + return t.memberExpression(node, t.identifier(name)); + } + } + + return node; + function tail(id, symbol) { let computedSymbol = symbol(id); @@ -52,22 +68,6 @@ module.exports = (code, exports, moduleMap, wildcards) => { return null; } - let node = tail(id, id => `$${id}$export$${name}`); - - if (!node) { - // if there is no named export then lookup for a CommonJS export - node = tail(id, id => `$${id}$exports`); - - // if there is a CommonJS export return $id$exports.name - if (node) { - return t.memberExpression(node, t.identifier(name)); - } - - // if there is no binding for the symbol it'll probably fail at runtime - throw new Error(`Cannot find export "${name}" in module ${id}`); - } - - return node; } traverse(ast, { @@ -125,14 +125,12 @@ module.exports = (code, exports, moduleMap, wildcards) => { let match = name.match(EXPORTS_RE); if (match) { - // let id = Number(match[1]); - if (!path.scope.hasBinding(name) && !addedExports.has(name)) { let exports = moduleMap.get(+match[1]).cacheData.exports; addedExports.add(name); - path.getStatementParent().insertBefore( + getOuterStatement(path).insertBefore( t.variableDeclaration('var', [ t.variableDeclarator( t.identifier(name), @@ -194,7 +192,7 @@ module.exports = (code, exports, moduleMap, wildcards) => { addedExports.add(name); - if (node !== undefined) { + if (node) { path.replaceWith(node); } } @@ -203,3 +201,30 @@ module.exports = (code, exports, moduleMap, wildcards) => { return generate(ast, code).code; }; + +function getOuterStatement(path) { + if (validate(path)) { + return path; + } + + return path.findParent(validate); + + function validate(path) { + if (!t.isStatement(path.node) || t.isBlockStatement(path.node)) { + return false; + } + + // TODO: use scope? + let outerBlocks = 0; + + path.findParent(parent => { + if (t.isBlockStatement(parent.node)) { + outerBlocks++; + } + + return false; + }); + + return outerBlocks === 1; + } +} diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 8c2b2b5afa5..ee1aeabf967 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -71,34 +71,6 @@ module.exports = { scope.rename(name, newName); } } - - // Add variable that represents module.exports if it is referenced. - if (scope.hasGlobal(getExportsIdentifier(asset).name)) { - path.unshiftContainer('body', [ - t.variableDeclaration('var', [ - t.variableDeclarator( - getExportsIdentifier(asset), - t.objectExpression([]) - ) - ]) - ]); - } else if (Object.keys(asset.cacheData.exports).length > 0) { - /*path.pushContainer('body', [ - t.variableDeclaration('var', [ - t.variableDeclarator( - getExportsIdentifier(asset), - t.objectExpression( - Object.values(asset.cacheData.exports).map(k => - t.objectProperty( - t.identifier(k), - getIdentifier(asset, 'export', k) - ) - ) - ) - ) - ]) - ]);*/ - } } path.stop(); @@ -355,16 +327,6 @@ function addExport(asset, path, local, exported) { asset.cacheData.exports[getName(asset, 'export', exported.name)] = exported.name; - if (path.scope.hasGlobal('module') || path.scope.hasGlobal('exports')) { - path.insertAfter( - EXPORT_ASSIGN_TEMPLATE({ - EXPORTS: getExportsIdentifier(asset), - NAME: t.identifier(local.name), - LOCAL: getIdentifier(asset, 'export', exported.name) - }) - ); - } - path.scope.rename(local.name, getName(asset, 'export', exported.name)); } From 6a6a63ad7813c7fe727bd4ba91d83e408f5d01cf Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 8 Apr 2018 22:31:30 -0700 Subject: [PATCH 034/180] Add tests for and fix live bindings Turns out the default export is NOT a live binding, but all other exports are. If the default export is mutated, we need to create a copy of it. Also fixes duplicate exports of the same variable as different names. --- src/packagers/JSConcatPackager.js | 7 +++++ src/transforms/concat.js | 14 ++++++--- src/visitors/hoist.js | 31 ++++++++++++++++--- .../commonjs/live-bindings/a.js | 5 +++ .../commonjs/live-bindings/b.js | 7 +++++ .../scope-hoisting/es6/live-bindings/a.js | 5 +++ .../scope-hoisting/es6/live-bindings/b.js | 7 +++++ .../scope-hoisting/es6/multi-export/a.js | 3 ++ .../scope-hoisting/es6/multi-export/b.js | 3 ++ test/scope-hoisting.js | 27 ++++++++++++++++ 10 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 test/integration/scope-hoisting/commonjs/live-bindings/a.js create mode 100644 test/integration/scope-hoisting/commonjs/live-bindings/b.js create mode 100644 test/integration/scope-hoisting/es6/live-bindings/a.js create mode 100644 test/integration/scope-hoisting/es6/live-bindings/b.js create mode 100644 test/integration/scope-hoisting/es6/multi-export/a.js create mode 100644 test/integration/scope-hoisting/es6/multi-export/b.js diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index fe3369d2eb8..22c20d29ee4 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -58,6 +58,13 @@ class JSConcatPackager extends Packager { this.moduleMap.set(asset.id, asset); this.wildcards.set(asset.id, asset.cacheData.wildcards); + for (let key in asset.cacheData.exports) { + let local = '$' + asset.id + '$export$' + asset.cacheData.exports[key]; + if (key !== local) { + this.exports.set(key, local); + } + } + for (let [dep, mod] of asset.depAssets) { let depName = '$' + asset.id + '$require$' + t.toIdentifier(dep.name); let moduleName = this.getExportIdentifier(mod); diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 01005b46ce1..ff328ce01ca 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -44,6 +44,10 @@ module.exports = (code, exports, moduleMap, wildcards) => { return t.identifier(computedSymbol); } + if (exports.has(computedSymbol)) { + return t.identifier(exports.get(computedSymbol)); + } + // if there is a wildcard for the module if (wildcards.has(id)) { /* recursively lookup the symbol @@ -126,7 +130,7 @@ module.exports = (code, exports, moduleMap, wildcards) => { if (match) { if (!path.scope.hasBinding(name) && !addedExports.has(name)) { - let exports = moduleMap.get(+match[1]).cacheData.exports; + let moduleExports = moduleMap.get(+match[1]).cacheData.exports; addedExports.add(name); @@ -135,14 +139,16 @@ module.exports = (code, exports, moduleMap, wildcards) => { t.variableDeclarator( t.identifier(name), t.objectExpression( - Object.keys(exports) + Object.keys(moduleExports) .map(key => { - let binding = path.scope.getBinding(key); + let binding = path.scope.getBinding( + exports.get(key) || key + ); if (!binding) { return null; } - let exportName = exports[key]; + let exportName = key.match(EXPORT_RE)[2]; let expr = replaceExportNode(+match[1], exportName, path); if (expr === null) { diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index ee1aeabf967..f7387d389e8 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -217,8 +217,8 @@ module.exports = { if (t.isIdentifier(declaration)) { // Rename the variable being exported. + safeRename(path, declaration.name, identifier.name); path.remove(); - path.scope.rename(declaration.name, identifier.name); } else if (t.isExpression(declaration)) { // Declare a variable to hold the exported value. path.replaceWith( @@ -228,8 +228,8 @@ module.exports = { ); } else { // Rename the declaration to the exported name. + safeRename(path, declaration.id.name, identifier.name); path.replaceWith(declaration); - path.scope.rename(declaration.id.name, identifier.name); } // Add assignment to exports object for namespace imports and commonjs. @@ -324,10 +324,31 @@ module.exports = { }; function addExport(asset, path, local, exported) { - asset.cacheData.exports[getName(asset, 'export', exported.name)] = - exported.name; + // Check if this identifier has already been exported. + // If so, create an export alias for it, otherwise, rename the local variable to an export. + if (asset.cacheData.exports[local.name]) { + asset.cacheData.exports[getName(asset, 'export', exported.name)] = + asset.cacheData.exports[local.name]; + } else { + asset.cacheData.exports[getName(asset, 'export', exported.name)] = + exported.name; + path.scope.rename(local.name, getName(asset, 'export', exported.name)); + } +} - path.scope.rename(local.name, getName(asset, 'export', exported.name)); +function safeRename(path, from, to) { + // If the binding that we're renaming is constant, it's safe to rename it. + // Otherwise, create a new binding that references the original. + let binding = path.scope.getBinding(from); + if (binding && binding.constant) { + path.scope.rename(from, to); + } else { + path.insertAfter( + t.variableDeclaration('var', [ + t.variableDeclarator(t.identifier(to), t.identifier(from)) + ]) + ); + } } function getName(asset, type, ...rest) { diff --git a/test/integration/scope-hoisting/commonjs/live-bindings/a.js b/test/integration/scope-hoisting/commonjs/live-bindings/a.js new file mode 100644 index 00000000000..44553148d3d --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/live-bindings/a.js @@ -0,0 +1,5 @@ +const b = require('./b'); + +b.changeFoo(3); + +output = b.default + b.foo + b.bar; diff --git a/test/integration/scope-hoisting/commonjs/live-bindings/b.js b/test/integration/scope-hoisting/commonjs/live-bindings/b.js new file mode 100644 index 00000000000..5b2892845f0 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/live-bindings/b.js @@ -0,0 +1,7 @@ +var foo = 2; +export default foo; +export {foo, foo as bar}; + +export function changeFoo(v) { + foo = v; +} diff --git a/test/integration/scope-hoisting/es6/live-bindings/a.js b/test/integration/scope-hoisting/es6/live-bindings/a.js new file mode 100644 index 00000000000..d3d7c6e6823 --- /dev/null +++ b/test/integration/scope-hoisting/es6/live-bindings/a.js @@ -0,0 +1,5 @@ +import b, {foo, bar, changeFoo} from './b'; + +changeFoo(3); + +output = b + foo + bar; diff --git a/test/integration/scope-hoisting/es6/live-bindings/b.js b/test/integration/scope-hoisting/es6/live-bindings/b.js new file mode 100644 index 00000000000..5b2892845f0 --- /dev/null +++ b/test/integration/scope-hoisting/es6/live-bindings/b.js @@ -0,0 +1,7 @@ +var foo = 2; +export default foo; +export {foo, foo as bar}; + +export function changeFoo(v) { + foo = v; +} diff --git a/test/integration/scope-hoisting/es6/multi-export/a.js b/test/integration/scope-hoisting/es6/multi-export/a.js new file mode 100644 index 00000000000..6cd12dd92ae --- /dev/null +++ b/test/integration/scope-hoisting/es6/multi-export/a.js @@ -0,0 +1,3 @@ +import b, {foo, bar} from './b'; + +output = b + foo + bar; diff --git a/test/integration/scope-hoisting/es6/multi-export/b.js b/test/integration/scope-hoisting/es6/multi-export/b.js new file mode 100644 index 00000000000..d553b25d2b7 --- /dev/null +++ b/test/integration/scope-hoisting/es6/multi-export/b.js @@ -0,0 +1,3 @@ +var foo = 2; +export default foo; +export {foo, foo as bar}; diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index aa0df46c075..74d2cfab734 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -123,6 +123,24 @@ describe.only('scope hoisting', function() { let output = run(b); assert.equal(output, 6); }); + + it('supports multiple exports of the same variable', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/multi-export/a.js' + ); + + let output = run(b); + assert.equal(output, 6); + }); + + it('supports live bindings of named exports', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/live-bindings/a.js' + ); + + let output = run(b); + assert.equal(output, 8); + }); }); describe('commonjs', function() { @@ -290,5 +308,14 @@ describe.only('scope hoisting', function() { let output = run(b); assert.equal(output, 'bar'); }); + + it('supports live bindings of named exports', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/live-bindings/a.js' + ); + + let output = run(b); + assert.equal(output, 8); + }); }); }); From 391d542c5fcdaf27bf08cefbf33ade88f29f9f13 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 8 Apr 2018 22:54:42 -0700 Subject: [PATCH 035/180] Exclude default from wildcard exports --- src/packagers/JSConcatPackager.js | 17 ++++++++-------- src/transforms/concat.js | 3 ++- .../require-re-export-exclude-default/a.js | 1 + .../require-re-export-exclude-default/b.js | 1 + .../require-re-export-exclude-default/c.js | 2 ++ .../es6/re-export-exclude-default/a.js | 3 +++ .../es6/re-export-exclude-default/b.js | 1 + .../es6/re-export-exclude-default/c.js | 2 ++ test/scope-hoisting.js | 20 +++++++++++++++++++ 9 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/b.js create mode 100644 test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/c.js create mode 100644 test/integration/scope-hoisting/es6/re-export-exclude-default/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-exclude-default/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-exclude-default/c.js diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 22c20d29ee4..dccdfdcecfd 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -79,14 +79,15 @@ class JSConcatPackager extends Packager { // If this was an ES6 export all (e.g. export * from 'foo'), resolve to the original exports. if (dep.isExportAll) { for (let exp in mod.cacheData.exports) { - let key = '$' + asset.id + '$export$' + mod.cacheData.exports[exp]; - asset.cacheData.exports[key] = mod.cacheData.exports[exp]; - this.exports.set( - key, - this.exports.get( - '$' + mod.id + '$export$' + mod.cacheData.exports[exp] - ) || exp - ); + let id = mod.cacheData.exports[exp]; + if (id !== 'default') { + let key = '$' + asset.id + '$export$' + id; + asset.cacheData.exports[key] = id; + this.exports.set( + key, + this.exports.get('$' + mod.id + '$export$' + id) || exp + ); + } } } diff --git a/src/transforms/concat.js b/src/transforms/concat.js index ff328ce01ca..134cd03f4a0 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -49,7 +49,8 @@ module.exports = (code, exports, moduleMap, wildcards) => { } // if there is a wildcard for the module - if (wildcards.has(id)) { + // default exports are excluded from wildcard exports + if (wildcards.has(id) && name !== 'default') { /* recursively lookup the symbol * this is needed when there is deep export wildcards, like in the following: * - a.js diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/a.js b/test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/a.js new file mode 100644 index 00000000000..a74ec1e77cc --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/a.js @@ -0,0 +1 @@ +output = require('./b'); diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/b.js b/test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/b.js new file mode 100644 index 00000000000..34a28aa08d0 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/b.js @@ -0,0 +1 @@ +export * from './c'; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/c.js b/test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/c.js new file mode 100644 index 00000000000..e1e7cfb81e0 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-re-export-exclude-default/c.js @@ -0,0 +1,2 @@ +export default 2; +export var foo = 3; diff --git a/test/integration/scope-hoisting/es6/re-export-exclude-default/a.js b/test/integration/scope-hoisting/es6/re-export-exclude-default/a.js new file mode 100644 index 00000000000..37ba9a3d1a8 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-exclude-default/a.js @@ -0,0 +1,3 @@ +import b, {foo} from './b'; + +output = {b, foo}; diff --git a/test/integration/scope-hoisting/es6/re-export-exclude-default/b.js b/test/integration/scope-hoisting/es6/re-export-exclude-default/b.js new file mode 100644 index 00000000000..34a28aa08d0 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-exclude-default/b.js @@ -0,0 +1 @@ +export * from './c'; diff --git a/test/integration/scope-hoisting/es6/re-export-exclude-default/c.js b/test/integration/scope-hoisting/es6/re-export-exclude-default/c.js new file mode 100644 index 00000000000..e1e7cfb81e0 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-exclude-default/c.js @@ -0,0 +1,2 @@ +export default 2; +export var foo = 3; diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index 74d2cfab734..9c7e849e569 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -124,6 +124,16 @@ describe.only('scope hoisting', function() { assert.equal(output, 6); }); + it('excludes default when re-exporting a module', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/es6/re-export-exclude-default/a.js' + ); + + let output = run(b); + assert.deepEqual(output, {b: undefined, foo: 3}); + }); + it('supports multiple exports of the same variable', async function() { let b = await bundle( __dirname + '/integration/scope-hoisting/es6/multi-export/a.js' @@ -290,6 +300,16 @@ describe.only('scope hoisting', function() { assert.equal(output, 6); }); + it('excludes default when re-exporting a module', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/require-re-export-exclude-default/a.js' + ); + + let output = run(b); + assert.deepEqual(output, {foo: 3}); + }); + it('supports hybrid ES6 + commonjs modules', async function() { let b = await bundle( __dirname + From a150352f011b173b90eebb6572d506d4299ff571 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 8 Apr 2018 23:04:38 -0700 Subject: [PATCH 036/180] Fix tests --- .../scope-hoisting/commonjs/require-re-export-multiple/c.js | 1 + test/scope-hoisting.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-multiple/c.js b/test/integration/scope-hoisting/commonjs/require-re-export-multiple/c.js index d6a7a6a3e60..e749b06020a 100644 --- a/test/integration/scope-hoisting/commonjs/require-re-export-multiple/c.js +++ b/test/integration/scope-hoisting/commonjs/require-re-export-multiple/c.js @@ -1 +1,2 @@ export var foo = 2, bar = 3; +export * from './d'; diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index 9c7e849e569..7e58f0d9822 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -257,7 +257,7 @@ describe.only('scope hoisting', function() { ); let output = run(b); - assert.equal(output, 7); + assert.equal(output, 6); }); it('supports require of es6 module re-exporting all exports from multiple modules', async function() { From 9f305c6250596a19d5845bf29889218a48c7b2d2 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 8 Apr 2018 23:38:53 -0700 Subject: [PATCH 037/180] Add failing test for export order --- .../scope-hoisting/commonjs/export-order/a.js | 3 +++ .../scope-hoisting/commonjs/export-order/b.js | 2 ++ test/scope-hoisting.js | 9 +++++++++ 3 files changed, 14 insertions(+) create mode 100644 test/integration/scope-hoisting/commonjs/export-order/a.js create mode 100644 test/integration/scope-hoisting/commonjs/export-order/b.js diff --git a/test/integration/scope-hoisting/commonjs/export-order/a.js b/test/integration/scope-hoisting/commonjs/export-order/a.js new file mode 100644 index 00000000000..b02c428e581 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/export-order/a.js @@ -0,0 +1,3 @@ +const {a, b} = require('./b'); + +output = a + b; diff --git a/test/integration/scope-hoisting/commonjs/export-order/b.js b/test/integration/scope-hoisting/commonjs/export-order/b.js new file mode 100644 index 00000000000..f05c9c574ed --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/export-order/b.js @@ -0,0 +1,2 @@ +exports.a = 2; +export var b = 3; diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index 7e58f0d9822..be28d1a3d24 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -320,6 +320,15 @@ describe.only('scope hoisting', function() { assert.equal(output, 5); }); + it('inserts commonjs exports object in the right place', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/export-order/a.js' + ); + + let output = run(b); + assert.equal(output, 5); + }); + it('define exports in the outermost scope', async function() { let b = await bundle( __dirname + '/integration/scope-hoisting/commonjs/define-exports/a.js' From a2deaf1213798245b2e4ba3f6913d4907ae51bcf Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 9 Apr 2018 19:19:40 -0700 Subject: [PATCH 038/180] More commonjs tests and few bug fixes --- src/packagers/JSConcatPackager.js | 2 +- src/visitors/hoist.js | 18 ++++- .../commonjs/module-object/a.js | 8 +++ .../commonjs/module-object/package.json | 3 + .../commonjs/require-resolve/a.js | 3 + .../commonjs/require-resolve/b.js | 1 + .../commonjs/this-reference-wrapped/a.js | 3 + .../commonjs/this-reference-wrapped/b.js | 2 + .../commonjs/this-reference/a.js | 3 + .../commonjs/this-reference/b.js | 1 + .../scope-hoisting/commonjs/wrap-eval/a.js | 3 + .../scope-hoisting/commonjs/wrap-eval/b.js | 2 + .../scope-hoisting/commonjs/wrap-return/a.js | 3 + .../scope-hoisting/commonjs/wrap-return/b.js | 3 + test/scope-hoisting.js | 65 +++++++++++++++++++ 15 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 test/integration/scope-hoisting/commonjs/module-object/a.js create mode 100644 test/integration/scope-hoisting/commonjs/module-object/package.json create mode 100644 test/integration/scope-hoisting/commonjs/require-resolve/a.js create mode 100644 test/integration/scope-hoisting/commonjs/require-resolve/b.js create mode 100644 test/integration/scope-hoisting/commonjs/this-reference-wrapped/a.js create mode 100644 test/integration/scope-hoisting/commonjs/this-reference-wrapped/b.js create mode 100644 test/integration/scope-hoisting/commonjs/this-reference/a.js create mode 100644 test/integration/scope-hoisting/commonjs/this-reference/b.js create mode 100644 test/integration/scope-hoisting/commonjs/wrap-eval/a.js create mode 100644 test/integration/scope-hoisting/commonjs/wrap-eval/b.js create mode 100644 test/integration/scope-hoisting/commonjs/wrap-return/a.js create mode 100644 test/integration/scope-hoisting/commonjs/wrap-return/b.js diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index dccdfdcecfd..d2298bcdfe6 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -114,7 +114,7 @@ class JSConcatPackager extends Packager { let depResolve = '$' + asset.id + '$require_resolve$' + t.toIdentifier(dep.name); - let resolved = '' + asset.id; + let resolved = '' + mod.id; if (dep.dynamic && this.bundle.childBundles.has(mod.parentBundle)) { let bundles = [this.getBundleSpecifier(mod.parentBundle)]; diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index f7387d389e8..db6dc39cc3d 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -13,6 +13,10 @@ const WRAPPER_TEMPLATE = template(` const EXPORT_ASSIGN_TEMPLATE = template('EXPORTS.NAME = LOCAL'); const REQUIRE_CALL_TEMPLATE = template('$parcel$require(ID, SOURCE)'); +const TYPEOF = { + module: 'object', + require: 'function' +}; module.exports = { Program: { @@ -40,6 +44,14 @@ module.exports = { // Wrap in a function if we see a top-level return statement. if (path.getFunctionParent().isProgram()) { shouldWrap = true; + path.replaceWith( + t.returnStatement( + t.memberExpression( + t.identifier('module'), + t.identifier('exports') + ) + ) + ); path.stop(); } } @@ -135,11 +147,11 @@ module.exports = { if ( path.node.operator === 'typeof' && t.isIdentifier(path.node.argument) && - path.node.argument.name === 'module' && - !path.scope.hasBinding('module') && + TYPEOF[path.node.argument.name] && + !path.scope.hasBinding(path.node.argument.name) && !path.scope.getData('shouldWrap') ) { - path.replaceWith(t.stringLiteral('object')); + path.replaceWith(t.stringLiteral(TYPEOF[path.node.argument.name])); } }, diff --git a/test/integration/scope-hoisting/commonjs/module-object/a.js b/test/integration/scope-hoisting/commonjs/module-object/a.js new file mode 100644 index 00000000000..8e7e26c9669 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/module-object/a.js @@ -0,0 +1,8 @@ +output = { + id: module.id, + hot: module.hot, + type: typeof module, + exports: exports, + exportsType: typeof exports, + require: typeof require +}; diff --git a/test/integration/scope-hoisting/commonjs/module-object/package.json b/test/integration/scope-hoisting/commonjs/module-object/package.json new file mode 100644 index 00000000000..e74061aadac --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/module-object/package.json @@ -0,0 +1,3 @@ +{ + "browserslist": ["last 1 Chrome versions"] +} diff --git a/test/integration/scope-hoisting/commonjs/require-resolve/a.js b/test/integration/scope-hoisting/commonjs/require-resolve/a.js new file mode 100644 index 00000000000..b5f996a1811 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-resolve/a.js @@ -0,0 +1,3 @@ +const foo = require('./b'); + +output = require.resolve('./b'); diff --git a/test/integration/scope-hoisting/commonjs/require-resolve/b.js b/test/integration/scope-hoisting/commonjs/require-resolve/b.js new file mode 100644 index 00000000000..4bbffde1044 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/require-resolve/b.js @@ -0,0 +1 @@ +module.exports = 2; diff --git a/test/integration/scope-hoisting/commonjs/this-reference-wrapped/a.js b/test/integration/scope-hoisting/commonjs/this-reference-wrapped/a.js new file mode 100644 index 00000000000..fa0aaf7fadf --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/this-reference-wrapped/a.js @@ -0,0 +1,3 @@ +const b = require('./b'); + +output = b.foo + b.bar; diff --git a/test/integration/scope-hoisting/commonjs/this-reference-wrapped/b.js b/test/integration/scope-hoisting/commonjs/this-reference-wrapped/b.js new file mode 100644 index 00000000000..9740b8e6158 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/this-reference-wrapped/b.js @@ -0,0 +1,2 @@ +this.foo = 2; +eval('this.bar = 4'); diff --git a/test/integration/scope-hoisting/commonjs/this-reference/a.js b/test/integration/scope-hoisting/commonjs/this-reference/a.js new file mode 100644 index 00000000000..afb22d072f4 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/this-reference/a.js @@ -0,0 +1,3 @@ +const b = require('./b'); + +output = b.foo; diff --git a/test/integration/scope-hoisting/commonjs/this-reference/b.js b/test/integration/scope-hoisting/commonjs/this-reference/b.js new file mode 100644 index 00000000000..559a5bfbffb --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/this-reference/b.js @@ -0,0 +1 @@ +this.foo = 2; diff --git a/test/integration/scope-hoisting/commonjs/wrap-eval/a.js b/test/integration/scope-hoisting/commonjs/wrap-eval/a.js new file mode 100644 index 00000000000..de2c5da4b3b --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/wrap-eval/a.js @@ -0,0 +1,3 @@ +const foo = require('./b'); + +output = foo; diff --git a/test/integration/scope-hoisting/commonjs/wrap-eval/b.js b/test/integration/scope-hoisting/commonjs/wrap-eval/b.js new file mode 100644 index 00000000000..ea0d3a0f6d8 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/wrap-eval/b.js @@ -0,0 +1,2 @@ +module.exports = 2; +eval('module.exports = 4'); diff --git a/test/integration/scope-hoisting/commonjs/wrap-return/a.js b/test/integration/scope-hoisting/commonjs/wrap-return/a.js new file mode 100644 index 00000000000..de2c5da4b3b --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/wrap-return/a.js @@ -0,0 +1,3 @@ +const foo = require('./b'); + +output = foo; diff --git a/test/integration/scope-hoisting/commonjs/wrap-return/b.js b/test/integration/scope-hoisting/commonjs/wrap-return/b.js new file mode 100644 index 00000000000..7716398359a --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/wrap-return/b.js @@ -0,0 +1,3 @@ +module.exports = 2; +return; +module.exports = 4; diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index be28d1a3d24..74570fb3ef4 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -346,5 +346,70 @@ describe.only('scope hoisting', function() { let output = run(b); assert.equal(output, 8); }); + + it('should wrap modules that use eval in a function', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/wrap-eval/a.js' + ); + + let output = run(b); + assert.equal(output, 4); + }); + + it('should wrap modules that have a top-level return', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/wrap-return/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports assigning to this as exports object', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/this-reference/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + + it('supports assigning to this as exports object in wrapped module', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/this-reference-wrapped/a.js' + ); + + let output = run(b); + assert.equal(output, 6); + }); + + it('supports module object properties', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/module-object/a.js' + ); + + // TODO: this test doesn't currently work in older browsers since babel + // replaces the typeof calls before we can get to them. + let output = run(b); + assert.equal(output.id, b.entryAsset.id); + assert.equal(output.hot, null); + assert.equal(output.type, 'object'); + assert.deepEqual(output.exports, {}); + assert.equal(output.exportsType, 'object'); + assert.equal(output.require, 'function'); + }); + + it('supports require.resolve calls', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/require-resolve/a.js' + ); + + let output = run(b); + assert.equal( + output, + Array.from(b.assets).find(a => a.name.endsWith('b.js')).id + ); + }); }); }); From ea743ae949fff2d564f705c5fe24cc9e307d4777 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 9 Apr 2018 19:43:58 -0700 Subject: [PATCH 039/180] Add test for dynamic import with scope hoisting --- src/packagers/JSConcatPackager.js | 19 ++++++++++++++++--- .../scope-hoisting/es6/dynamic-import/a.js | 5 +++++ .../scope-hoisting/es6/dynamic-import/b.js | 2 ++ test/scope-hoisting.js | 9 +++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 test/integration/scope-hoisting/es6/dynamic-import/a.js create mode 100644 test/integration/scope-hoisting/es6/dynamic-import/b.js diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index d2298bcdfe6..f972dfbc820 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -21,6 +21,7 @@ class JSConcatPackager extends Packager { this.exports = new Map(); this.wildcards = new Map(); this.moduleMap = new Map(); + this.needsPrelude = false; for (let asset of this.bundle.assets) { // If this module is referenced by another bundle, it needs to be exposed externally. @@ -33,10 +34,22 @@ class JSConcatPackager extends Packager { (this.bundle.entryAsset === asset && this.bundle.parentBundle) ) { this.exposedModules.add(asset); + this.needsPrelude = true; + } + + for (let mod of asset.depAssets.values()) { + if (!this.bundle.assets.has(mod)) { + this.needsPrelude = true; + break; + } } } - if (this.exposedModules.size > 0) { + if (this.needsPrelude) { + if (this.bundle.entryAsset) { + this.exposedModules.add(this.bundle.entryAsset); + } + await this.write(prelude + '(function (require) {\n'); } else { await this.write('(function () {\n'); @@ -200,13 +213,13 @@ class JSConcatPackager extends Packager { } async end() { - if (this.exposedModules.size > 0) { + if (this.needsPrelude) { let exposed = []; for (let m of this.exposedModules) { exposed.push(`${m.id}: ${this.getExportIdentifier(m)}`); } - await this.write(`return {${exposed.join(', ')}};\n`); + await this.write(`return {${exposed.join(', ')}};\n})`); } else { await this.write('})();'); } diff --git a/test/integration/scope-hoisting/es6/dynamic-import/a.js b/test/integration/scope-hoisting/es6/dynamic-import/a.js new file mode 100644 index 00000000000..4f774831494 --- /dev/null +++ b/test/integration/scope-hoisting/es6/dynamic-import/a.js @@ -0,0 +1,5 @@ +var b = import('./b'); + +export default b.then(function ({foo, bar}) { + return foo + bar; +}); diff --git a/test/integration/scope-hoisting/es6/dynamic-import/b.js b/test/integration/scope-hoisting/es6/dynamic-import/b.js new file mode 100644 index 00000000000..ad76adb4cfb --- /dev/null +++ b/test/integration/scope-hoisting/es6/dynamic-import/b.js @@ -0,0 +1,2 @@ +export var foo = 2; +export var bar = 3; diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index 74570fb3ef4..6ecc6396a4e 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -151,6 +151,15 @@ describe.only('scope hoisting', function() { let output = run(b); assert.equal(output, 8); }); + + it('supports dynamic import syntax for code splitting', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/dynamic-import/a.js' + ); + + let output = await run(b).default; + assert.equal(output, 5); + }); }); describe('commonjs', function() { From c35dbd6a38dfd78925ccbf1af3abb6f234a7f710 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Wed, 11 Apr 2018 07:59:37 +0200 Subject: [PATCH 040/180] Wrap commonjs modules that non-statically access --- src/visitors/hoist.js | 14 +++++++++++++ .../commonjs/wrap-module-computed/a.js | 2 ++ .../scope-hoisting/commonjs/wrap-module/a.js | 2 ++ test/scope-hoisting.js | 21 ++++++++++++++++++- 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 test/integration/scope-hoisting/commonjs/wrap-module-computed/a.js create mode 100644 test/integration/scope-hoisting/commonjs/wrap-module/a.js diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index db6dc39cc3d..7fc3df38ca9 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -54,6 +54,20 @@ module.exports = { ); path.stop(); } + }, + + ReferencedIdentifier(path) { + // We must wrap if `module` is referenced as a free identifier rather + // than a statically resolvable member expression. + if ( + path.node.name === 'module' && + (!path.parentPath.isMemberExpression() || path.parent.computed) && + !path.scope.hasBinding('module') && + !path.scope.getData('shouldWrap') + ) { + shouldWrap = true; + path.stop(); + } } }); diff --git a/test/integration/scope-hoisting/commonjs/wrap-module-computed/a.js b/test/integration/scope-hoisting/commonjs/wrap-module-computed/a.js new file mode 100644 index 00000000000..2b810bb628b --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/wrap-module-computed/a.js @@ -0,0 +1,2 @@ +exports.foo = 2; +output = module['exports']; diff --git a/test/integration/scope-hoisting/commonjs/wrap-module/a.js b/test/integration/scope-hoisting/commonjs/wrap-module/a.js new file mode 100644 index 00000000000..b7c1511cda1 --- /dev/null +++ b/test/integration/scope-hoisting/commonjs/wrap-module/a.js @@ -0,0 +1,2 @@ +exports.foo = 2; +output = module; diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index 6ecc6396a4e..9a17dbb6167 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -1,7 +1,7 @@ const assert = require('assert'); const {bundle, run} = require('./utils'); -describe.only('scope hoisting', function() { +describe('scope hoisting', function() { describe('es6', function() { it('supports default imports and exports of expressions', async function() { let b = await bundle( @@ -374,6 +374,25 @@ describe.only('scope hoisting', function() { assert.equal(output, 2); }); + it('should wrap modules that access `module` as a free variable', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/commonjs/wrap-module/a.js' + ); + + let output = run(b); + assert.deepEqual(output, {exports: {foo: 2}}); + }); + + it('should wrap modules that non-statically access `module`', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/commonjs/wrap-module-computed/a.js' + ); + + let output = run(b); + assert.deepEqual(output, {foo: 2}); + }); + it('supports assigning to this as exports object', async function() { let b = await bundle( __dirname + '/integration/scope-hoisting/commonjs/this-reference/a.js' From eab0c8664b74bd3d68fd047392f50837b49d3e5c Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Wed, 11 Apr 2018 08:04:48 +0200 Subject: [PATCH 041/180] Support exporting default anonymous declarations --- src/visitors/hoist.js | 4 ++-- .../scope-hoisting/es6/default-export-anonymous/a.js | 3 +++ .../scope-hoisting/es6/default-export-anonymous/b.js | 3 +++ test/scope-hoisting.js | 12 +++++++++++- 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 test/integration/scope-hoisting/es6/default-export-anonymous/a.js create mode 100644 test/integration/scope-hoisting/es6/default-export-anonymous/b.js diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 7fc3df38ca9..89848c5e6d0 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -245,11 +245,11 @@ module.exports = { // Rename the variable being exported. safeRename(path, declaration.name, identifier.name); path.remove(); - } else if (t.isExpression(declaration)) { + } else if (t.isExpression(declaration) || !declaration.id) { // Declare a variable to hold the exported value. path.replaceWith( t.variableDeclaration('var', [ - t.variableDeclarator(identifier, declaration) + t.variableDeclarator(identifier, t.toExpression(declaration)) ]) ); } else { diff --git a/test/integration/scope-hoisting/es6/default-export-anonymous/a.js b/test/integration/scope-hoisting/es6/default-export-anonymous/a.js new file mode 100644 index 00000000000..b37150cd87b --- /dev/null +++ b/test/integration/scope-hoisting/es6/default-export-anonymous/a.js @@ -0,0 +1,3 @@ +import foo from './b'; + +output = foo(); diff --git a/test/integration/scope-hoisting/es6/default-export-anonymous/b.js b/test/integration/scope-hoisting/es6/default-export-anonymous/b.js new file mode 100644 index 00000000000..e99e4f2be8d --- /dev/null +++ b/test/integration/scope-hoisting/es6/default-export-anonymous/b.js @@ -0,0 +1,3 @@ +export default function() { + return 2; +} diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index 9a17dbb6167..e1022e1c532 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -1,7 +1,7 @@ const assert = require('assert'); const {bundle, run} = require('./utils'); -describe('scope hoisting', function() { +describe.only('scope hoisting', function() { describe('es6', function() { it('supports default imports and exports of expressions', async function() { let b = await bundle( @@ -23,6 +23,16 @@ describe('scope hoisting', function() { assert.equal(output, 2); }); + it('supports default imports and exports of anonymous declarations', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/es6/default-export-anonymous/a.js' + ); + + let output = run(b); + assert.equal(output, 2); + }); + it('supports default imports and exports of variables', async function() { let b = await bundle( __dirname + From a729438cc1920580279776e24841bea5cc8dbc3b Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Wed, 11 Apr 2018 08:05:10 +0200 Subject: [PATCH 042/180] Pass asset id through pipeline --- src/Pipeline.js | 1 + src/worker.js | 13 +++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Pipeline.js b/src/Pipeline.js index 168b5366792..80ae5a0288e 100644 --- a/src/Pipeline.js +++ b/src/Pipeline.js @@ -54,6 +54,7 @@ class Pipeline { if (!(asset instanceof AssetType)) { let opts = Object.assign({rendition}, asset.options); let subAsset = new AssetType(asset.name, asset.package, opts); + subAsset.id = asset.id; subAsset.contents = value; subAsset.dependencies = asset.dependencies; diff --git a/src/worker.js b/src/worker.js index 3fed5c8bfe4..1b4c62acb2d 100644 --- a/src/worker.js +++ b/src/worker.js @@ -15,16 +15,13 @@ function init(options, isLocal = false) { } } -exports.run = async function(path, id, pkg, options, isWarmUp, callback) { +async function run(path, id, pkg, options, isWarmUp) { try { options.isWarmUp = isWarmUp; - var result = await pipeline.process(path, id, pkg, options); - - callback(null, result); - } catch (err) { - let returned = err; - returned.fileName = path; - callback(returned); + return await pipeline.process(path, id, pkg, options); + } catch (e) { + e.fileName = path; + throw e; } } From 7b9f599ae9e4d5e82e17219c6a8b5eee8a185900 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Wed, 11 Apr 2018 08:07:59 +0200 Subject: [PATCH 043/180] Allow `typeof module` to be static --- src/visitors/hoist.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 89848c5e6d0..9626dec6720 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -62,6 +62,10 @@ module.exports = { if ( path.node.name === 'module' && (!path.parentPath.isMemberExpression() || path.parent.computed) && + !( + path.parentPath.isUnaryExpression() && + path.parent.operator === 'typeof' + ) && !path.scope.hasBinding('module') && !path.scope.getData('shouldWrap') ) { From bb9a2981e3db116976f9914f8721616a13839ac3 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Fri, 13 Apr 2018 23:18:12 +0200 Subject: [PATCH 044/180] Export variables using Declaration.id if present --- src/visitors/hoist.js | 5 ++++- .../scope-hoisting/es6/export-binding-identifiers/a.js | 3 +++ .../scope-hoisting/es6/export-binding-identifiers/b.js | 3 +++ test/scope-hoisting.js | 10 ++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 test/integration/scope-hoisting/es6/export-binding-identifiers/a.js create mode 100644 test/integration/scope-hoisting/es6/export-binding-identifiers/b.js diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 9626dec6720..65b4cba3e9b 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -330,7 +330,10 @@ module.exports = { } else if (declaration) { path.replaceWith(declaration); - let identifiers = t.getBindingIdentifiers(declaration); + let identifiers = t.isIdentifier(declaration.id) + ? [declaration.id] + : t.getBindingIdentifiers(declaration); + for (let id in identifiers) { addExport(asset, path, identifiers[id], identifiers[id]); } diff --git a/test/integration/scope-hoisting/es6/export-binding-identifiers/a.js b/test/integration/scope-hoisting/es6/export-binding-identifiers/a.js new file mode 100644 index 00000000000..de4ad7b376d --- /dev/null +++ b/test/integration/scope-hoisting/es6/export-binding-identifiers/a.js @@ -0,0 +1,3 @@ +import {test} from './b' + +output = test(exports => Object.keys(exports)) diff --git a/test/integration/scope-hoisting/es6/export-binding-identifiers/b.js b/test/integration/scope-hoisting/es6/export-binding-identifiers/b.js new file mode 100644 index 00000000000..915389c370e --- /dev/null +++ b/test/integration/scope-hoisting/es6/export-binding-identifiers/b.js @@ -0,0 +1,3 @@ +export function test(arg) { + return arg(exports) +} diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index e1022e1c532..58d9a06f032 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -170,6 +170,16 @@ describe.only('scope hoisting', function() { let output = await run(b).default; assert.equal(output, 5); }); + + it('supports not exports function arguments', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/es6/export-binding-identifiers/a.js' + ); + + let output = run(b); + assert.deepEqual(output, ['test']); + }); }); describe('commonjs', function() { From 38a0951960d717756307ed7e57186aabe2d5484e Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sun, 15 Apr 2018 00:19:08 +0200 Subject: [PATCH 045/180] Revert to export assignments Build the export object in the asset instead of the packager --- src/transforms/concat.js | 115 ++++++++++++++++++--------------------- src/visitors/hoist.js | 76 ++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 73 deletions(-) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 134cd03f4a0..3f827dda1f6 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -12,7 +12,6 @@ const EXPORT_RE = /^\$([\d]+)\$export\$(.+)$/; module.exports = (code, exports, moduleMap, wildcards) => { let ast = babylon.parse(code); let addedExports = new Set(); - let commentedBindings = new Set(); let resolveModule = (id, name) => { let module = moduleMap.get(id); @@ -22,11 +21,11 @@ module.exports = (code, exports, moduleMap, wildcards) => { function replaceExportNode(id, name, path) { path = getOuterStatement(path); - let node = tail(id, id => `$${id}$export$${name}`); + let node = find(id, id => `$${id}$export$${name}`); if (!node) { // if there is no named export then lookup for a CommonJS export - node = tail(id, id => `$${id}$exports`) || t.identifier(`$${id}$exports`); + node = find(id, id => `$${id}$exports`) || t.identifier(`$${id}$exports`); // if there is a CommonJS export return $id$exports.name if (node) { @@ -36,7 +35,7 @@ module.exports = (code, exports, moduleMap, wildcards) => { return node; - function tail(id, symbol) { + function find(id, symbol) { let computedSymbol = symbol(id); // if the symbol is in the scope there is not need to remap it @@ -66,7 +65,7 @@ module.exports = (code, exports, moduleMap, wildcards) => { wildcards .get(id) - .find(name => (node = tail(resolveModule(id, name), symbol))); + .find(name => (node = find(resolveModule(id, name), symbol))); return node; } @@ -93,7 +92,7 @@ module.exports = (code, exports, moduleMap, wildcards) => { ); } - const mod = resolveModule(id.value, name.value); + let mod = resolveModule(id.value, name.value); if (typeof mod === 'undefined') { throw new Error(`Cannot find module "${name.value}"`); @@ -129,66 +128,60 @@ module.exports = (code, exports, moduleMap, wildcards) => { let match = name.match(EXPORTS_RE); - if (match) { - if (!path.scope.hasBinding(name) && !addedExports.has(name)) { + /*if (match) { + if (!path.scope.hasBinding(name)) { let moduleExports = moduleMap.get(+match[1]).cacheData.exports; - addedExports.add(name); - - getOuterStatement(path).insertBefore( - t.variableDeclaration('var', [ - t.variableDeclarator( - t.identifier(name), - t.objectExpression( - Object.keys(moduleExports) - .map(key => { - let binding = path.scope.getBinding( - exports.get(key) || key - ); - if (!binding) { - return null; - } - - let exportName = key.match(EXPORT_RE)[2]; - let expr = replaceExportNode(+match[1], exportName, path); - - if (expr === null) { - return null; - } - - if (binding.constant) { - return t.objectProperty(t.identifier(exportName), expr); - } else { - if (!commentedBindings.has(binding)) { - commentedBindings.add(binding); - binding.constantViolations.forEach(path => - path - .getFunctionParent() - .addComment( - 'leading', - ` bailout: mutates ${generate(expr).code}`, - '\n' - ) - ); - } - - return t.objectMethod( - 'get', - t.identifier(exportName), - [], - t.blockStatement([t.returnStatement(expr)]) - ); - } - }) - .filter(property => property !== null) - ) - ) - ]) - ); + getOuterStatement(path).scope.push({ + id: t.identifier(name), + init: t.objectExpression( + Object.keys(moduleExports) + .map(key => { + let binding = path.scope.getBinding( + exports.get(key) || key + ); + if (!binding) { + return null; + } + + let exportName = key.match(EXPORT_RE)[2]; + let expr = replaceExportNode(+match[1], exportName, path); + + if (expr === null) { + return null; + } + + if (binding.constant) { + return null; + } + + if (!commentedBindings.has(binding)) { + commentedBindings.add(binding); + binding.constantViolations.forEach(path => + path + .getFunctionParent() + .addComment( + 'leading', + ` bailout: mutates ${generate(expr).code}`, + '\n' + ) + ); + } + + return t.objectMethod( + 'get', + t.identifier(exportName), + [], + t.blockStatement([t.returnStatement(expr)]) + ); + }) + .filter(property => property !== null) + ) + }); } return; - } + }*/ match = name.match(EXPORT_RE); diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 65b4cba3e9b..91f9deef7b3 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -101,6 +101,34 @@ module.exports = { scope.rename(name, newName); } } + + // Add variable that represents module.exports if it is referenced. + if (scope.hasGlobal(getExportsIdentifier(asset).name)) { + path.unshiftContainer('body', [ + t.variableDeclaration('var', [ + t.variableDeclarator( + getExportsIdentifier(asset), + t.objectExpression([]) + ) + ]) + ]); + } else if (Object.keys(asset.cacheData.exports).length > 0) { + /*path.pushContainer('body', [ + t.variableDeclaration('var', [ + t.variableDeclarator( + getExportsIdentifier(asset), + t.objectExpression( + Object.values(asset.cacheData.exports).map(k => + t.objectProperty( + t.identifier(k), + getIdentifier(asset, 'export', k) + ) + ) + ) + ) + ]) + ]);*/ + } } path.stop(); @@ -245,9 +273,17 @@ module.exports = { let {declaration} = path.node; let identifier = getIdentifier(asset, 'export', 'default'); + path.insertAfter( + EXPORT_ASSIGN_TEMPLATE({ + EXPORTS: getExportsIdentifier(asset), + NAME: t.identifier('default'), + LOCAL: identifier + }) + ); + if (t.isIdentifier(declaration)) { // Rename the variable being exported. - safeRename(path, declaration.name, identifier.name); + safeRename(asset, path, declaration.name, identifier.name); path.remove(); } else if (t.isExpression(declaration) || !declaration.id) { // Declare a variable to hold the exported value. @@ -258,20 +294,13 @@ module.exports = { ); } else { // Rename the declaration to the exported name. - safeRename(path, declaration.id.name, identifier.name); + safeRename(asset, path, declaration.id.name, identifier.name); path.replaceWith(declaration); } // Add assignment to exports object for namespace imports and commonjs. - if (path.scope.hasGlobal('module') || path.scope.hasGlobal('exports')) { - path.insertAfter( - EXPORT_ASSIGN_TEMPLATE({ - EXPORTS: getExportsIdentifier(asset), - NAME: t.identifier('default'), - LOCAL: identifier - }) - ); - } + // if (path.scope.hasGlobal('module') || path.scope.hasGlobal('exports')) { + // } asset.cacheData.exports[identifier.name] = 'default'; @@ -365,11 +394,23 @@ function addExport(asset, path, local, exported) { } else { asset.cacheData.exports[getName(asset, 'export', exported.name)] = exported.name; + + let assignNode = EXPORT_ASSIGN_TEMPLATE({ + EXPORTS: getExportsIdentifier(asset), + NAME: t.identifier(local.name), + LOCAL: getIdentifier(asset, 'export', exported.name) + }); + + // Get all the node paths mutating the export and insert a CommonJS assignement. + path.scope + .getBinding(local.name) + .constantViolations.concat(path) + .forEach(path => path.insertAfter(assignNode)); path.scope.rename(local.name, getName(asset, 'export', exported.name)); } } -function safeRename(path, from, to) { +function safeRename(asset, path, from, to) { // If the binding that we're renaming is constant, it's safe to rename it. // Otherwise, create a new binding that references the original. let binding = path.scope.getBinding(from); @@ -381,6 +422,17 @@ function safeRename(path, from, to) { t.variableDeclarator(t.identifier(to), t.identifier(from)) ]) ); + + /*binding.constantViolations.forEach(path => + path.insertAfter( + MUTATE_EXPORT_TEMPLATE({ + LOCAL: t.identifier(from), + NAME: t.identifier('default'), + EXPORTS: getExportsIdentifier(asset), + EXPORT: t.identifier(to) + }) + ) + )*/ } } From d150de19187d21a238527d88a3b0c5bfb453f6a8 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sun, 15 Apr 2018 15:03:22 +0200 Subject: [PATCH 046/180] Clean up the code --- src/transforms/concat.js | 60 ++-------------------------------------- src/visitors/hoist.js | 26 ++++------------- 2 files changed, 8 insertions(+), 78 deletions(-) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 3f827dda1f6..8581c5750c9 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -126,64 +126,7 @@ module.exports = (code, exports, moduleMap, wildcards) => { return; } - let match = name.match(EXPORTS_RE); - - /*if (match) { - if (!path.scope.hasBinding(name)) { - let moduleExports = moduleMap.get(+match[1]).cacheData.exports; - - getOuterStatement(path).scope.push({ - id: t.identifier(name), - init: t.objectExpression( - Object.keys(moduleExports) - .map(key => { - let binding = path.scope.getBinding( - exports.get(key) || key - ); - if (!binding) { - return null; - } - - let exportName = key.match(EXPORT_RE)[2]; - let expr = replaceExportNode(+match[1], exportName, path); - - if (expr === null) { - return null; - } - - if (binding.constant) { - return null; - } - - if (!commentedBindings.has(binding)) { - commentedBindings.add(binding); - binding.constantViolations.forEach(path => - path - .getFunctionParent() - .addComment( - 'leading', - ` bailout: mutates ${generate(expr).code}`, - '\n' - ) - ); - } - - return t.objectMethod( - 'get', - t.identifier(exportName), - [], - t.blockStatement([t.returnStatement(expr)]) - ); - }) - .filter(property => property !== null) - ) - }); - } - - return; - }*/ - - match = name.match(EXPORT_RE); + let match = name.match(EXPORT_RE); if (match && !path.scope.hasBinding(name) && !addedExports.has(name)) { let id = Number(match[1]); @@ -202,6 +145,7 @@ module.exports = (code, exports, moduleMap, wildcards) => { return generate(ast, code).code; }; +// Finds a parent statement in the bundle IIFE body function getOuterStatement(path) { if (validate(path)) { return path; diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 91f9deef7b3..bad5a4363b2 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -113,7 +113,7 @@ module.exports = { ]) ]); } else if (Object.keys(asset.cacheData.exports).length > 0) { - /*path.pushContainer('body', [ + path.pushContainer('body', [ t.variableDeclaration('var', [ t.variableDeclarator( getExportsIdentifier(asset), @@ -127,7 +127,7 @@ module.exports = { ) ) ]) - ]);*/ + ]); } } @@ -273,6 +273,7 @@ module.exports = { let {declaration} = path.node; let identifier = getIdentifier(asset, 'export', 'default'); + // Add assignment to exports object for namespace imports and commonjs. path.insertAfter( EXPORT_ASSIGN_TEMPLATE({ EXPORTS: getExportsIdentifier(asset), @@ -283,7 +284,7 @@ module.exports = { if (t.isIdentifier(declaration)) { // Rename the variable being exported. - safeRename(asset, path, declaration.name, identifier.name); + safeRename(path, declaration.name, identifier.name); path.remove(); } else if (t.isExpression(declaration) || !declaration.id) { // Declare a variable to hold the exported value. @@ -294,14 +295,10 @@ module.exports = { ); } else { // Rename the declaration to the exported name. - safeRename(asset, path, declaration.id.name, identifier.name); + safeRename(path, declaration.id.name, identifier.name); path.replaceWith(declaration); } - // Add assignment to exports object for namespace imports and commonjs. - // if (path.scope.hasGlobal('module') || path.scope.hasGlobal('exports')) { - // } - asset.cacheData.exports[identifier.name] = 'default'; // Mark the asset as an ES6 module, so we handle imports correctly in the packager. @@ -410,7 +407,7 @@ function addExport(asset, path, local, exported) { } } -function safeRename(asset, path, from, to) { +function safeRename(path, from, to) { // If the binding that we're renaming is constant, it's safe to rename it. // Otherwise, create a new binding that references the original. let binding = path.scope.getBinding(from); @@ -422,17 +419,6 @@ function safeRename(asset, path, from, to) { t.variableDeclarator(t.identifier(to), t.identifier(from)) ]) ); - - /*binding.constantViolations.forEach(path => - path.insertAfter( - MUTATE_EXPORT_TEMPLATE({ - LOCAL: t.identifier(from), - NAME: t.identifier('default'), - EXPORTS: getExportsIdentifier(asset), - EXPORT: t.identifier(to) - }) - ) - )*/ } } From 65b1b13a7d4cf447a1dd0c707e8ae6bbf900cdba Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 16 Apr 2018 01:18:41 +0200 Subject: [PATCH 047/180] Implement CommonJS wildcard using pure functions --- src/packagers/JSConcatPackager.js | 18 ++++++++++++++- src/visitors/hoist.js | 37 ++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index f972dfbc820..aed0f24cbac 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -52,7 +52,23 @@ class JSConcatPackager extends Packager { await this.write(prelude + '(function (require) {\n'); } else { - await this.write('(function () {\n'); + await this.write(` +(function () { + function $parcel$exportWildcard(dest, source) { + Object.keys(source).forEach(function(key) { + if(key !== 'default') { + Object.defineProperty(dest, key, { + enumerable: true, + get() { + return source[key]; + } + }); + } + }); + + return dest; + } +`); } } diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index bad5a4363b2..28bb2073e24 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -12,6 +12,9 @@ const WRAPPER_TEMPLATE = template(` `); const EXPORT_ASSIGN_TEMPLATE = template('EXPORTS.NAME = LOCAL'); +const EXPORT_ALL_TEMPLATE = template( + 'var NAME = $parcel$exportWildcard(OLD_NAME, $parcel$require(ID, SOURCE));' +); const REQUIRE_CALL_TEMPLATE = template('$parcel$require(ID, SOURCE)'); const TYPEOF = { module: 'object', @@ -204,9 +207,16 @@ module.exports = { CallExpression(path, asset) { let {callee, arguments: args} = path.node; + if (t.isIdentifier(callee, {name: '$parcel$exportWildcard'})) { + // This hints Uglify and Babel that this CallExpression does not have any side-effects. + // This will make unsused CommonJS wildcards removed from the minified builds. + path.addComment('leading', '#__PURE__'); + + return; + } + let isRequire = - t.isIdentifier(callee) && - callee.name === 'require' && + t.isIdentifier(callee, {name: 'require'}) && args.length === 1 && t.isStringLiteral(args[0]) && !path.scope.hasBinding('require'); @@ -378,7 +388,28 @@ module.exports = { ExportAllDeclaration(path, asset) { asset.cacheData.wildcards.push(path.node.source.value); asset.cacheData.isES6Module = true; - path.remove(); + + let exportsName = getExportsIdentifier(asset); + let oldName = t.objectExpression([]); + + // If the export is already define rename it so we can reassign it + // We need to do this because Uglify does not remove pure functions calls if they use a reassigned variable : + // var b = {}; b = pureCall(b) // not removed + // var b$0 = {}; var b = pureCall(b$0) // removed + if (path.scope.hasBinding(exportsName.name)) { + oldName = path.scope.generateDeclaredUidIdentifier(exportsName.name); + + path.scope.rename(exportsName.name, oldName.name); + } + + path.replaceWith( + EXPORT_ALL_TEMPLATE({ + NAME: exportsName, + OLD_NAME: oldName, + SOURCE: t.stringLiteral(path.node.source.value), + ID: t.numericLiteral(asset.id) + }) + ); } }; From 9bc914f8ec6c404a3e4930fcdf1449bde595fc90 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 16 Apr 2018 01:21:10 +0200 Subject: [PATCH 048/180] typo --- src/visitors/hoist.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 28bb2073e24..d55d9cd16a8 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -392,7 +392,7 @@ module.exports = { let exportsName = getExportsIdentifier(asset); let oldName = t.objectExpression([]); - // If the export is already define rename it so we can reassign it + // If the export is already defined rename it so we can reassign it. // We need to do this because Uglify does not remove pure functions calls if they use a reassigned variable : // var b = {}; b = pureCall(b) // not removed // var b$0 = {}; var b = pureCall(b$0) // removed From e6ea095bd38a10f5f01d65730499e0078a687af1 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 17 Apr 2018 08:19:30 +0200 Subject: [PATCH 049/180] Use same wildcard helper as babel --- src/packagers/JSConcatPackager.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index aed0f24cbac..ea691c5c399 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -56,14 +56,13 @@ class JSConcatPackager extends Packager { (function () { function $parcel$exportWildcard(dest, source) { Object.keys(source).forEach(function(key) { - if(key !== 'default') { - Object.defineProperty(dest, key, { - enumerable: true, - get() { - return source[key]; - } - }); - } + if(key === 'default' || key === "__esModule") return; + Object.defineProperty(dest, key, { + enumerable: true, + get: function get() { + return source[key]; + } + }); }); return dest; From 5a6984ad6a9c1ff9687cdf0f24a3ba1cbf361ed9 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 21 Apr 2018 20:44:20 +0200 Subject: [PATCH 050/180] Fix remaining tests --- src/packagers/JSConcatPackager.js | 3 +- src/visitors/hoist.js | 57 +++++++++++-------- .../commonjs/require-re-export-default/b.js | 2 +- .../commonjs/require-re-export-namespace/b.js | 4 +- 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index ea691c5c399..8897666d51e 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -215,7 +215,8 @@ class JSConcatPackager extends Packager { let loader = this.options.bundleLoaders[bundleType]; if (loader) { - let asset = await this.bundler.getAsset(loader); + let target = this.options.target === 'node' ? 'node' : 'browser'; + let asset = await this.bundler.getAsset(loader[target]); if (!this.bundle.assets.has(asset)) { await this.addAssetToBundle(asset); await this.write( diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index d55d9cd16a8..11495c3d199 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -13,7 +13,7 @@ const WRAPPER_TEMPLATE = template(` const EXPORT_ASSIGN_TEMPLATE = template('EXPORTS.NAME = LOCAL'); const EXPORT_ALL_TEMPLATE = template( - 'var NAME = $parcel$exportWildcard(OLD_NAME, $parcel$require(ID, SOURCE));' + '$parcel$exportWildcard(OLD_NAME, $parcel$require(ID, SOURCE))' ); const REQUIRE_CALL_TEMPLATE = template('$parcel$require(ID, SOURCE)'); const TYPEOF = { @@ -105,17 +105,19 @@ module.exports = { } } - // Add variable that represents module.exports if it is referenced. - if (scope.hasGlobal(getExportsIdentifier(asset).name)) { + let exportsIdentifier = getExportsIdentifier(asset); + + // Add variable that represents module.exports if it is referenced and not declared. + if ( + scope.hasGlobal(exportsIdentifier.name) && + !scope.hasBinding(exportsIdentifier.name) + ) { path.unshiftContainer('body', [ t.variableDeclaration('var', [ - t.variableDeclarator( - getExportsIdentifier(asset), - t.objectExpression([]) - ) + t.variableDeclarator(exportsIdentifier, t.objectExpression([])) ]) ]); - } else if (Object.keys(asset.cacheData.exports).length > 0) { + } /* else if (Object.keys(asset.cacheData.exports).length > 0) { path.pushContainer('body', [ t.variableDeclaration('var', [ t.variableDeclarator( @@ -131,7 +133,7 @@ module.exports = { ) ]) ]); - } + }*/ } path.stop(); @@ -269,10 +271,10 @@ module.exports = { ) ); } else if (t.isImportNamespaceSpecifier(specifier)) { - path.scope.rename( - specifier.local.name, - getName(asset, 'require', path.node.source.value) - ); + path.scope.push({ + id: specifier.local, + init: t.identifier(getName(asset, 'require', path.node.source.value)) + }); } } @@ -336,6 +338,14 @@ module.exports = { specifier.local.name ); exported = specifier.exported; + + path.insertAfter( + EXPORT_ASSIGN_TEMPLATE({ + EXPORTS: getExportsIdentifier(asset), + NAME: exported, + LOCAL: local + }) + ); } // Create a variable to re-export from the imported module. @@ -393,7 +403,7 @@ module.exports = { let oldName = t.objectExpression([]); // If the export is already defined rename it so we can reassign it. - // We need to do this because Uglify does not remove pure functions calls if they use a reassigned variable : + // We need to do this because Uglify does not remove pure calls if they use a reassigned variable : // var b = {}; b = pureCall(b) // not removed // var b$0 = {}; var b = pureCall(b$0) // removed if (path.scope.hasBinding(exportsName.name)) { @@ -402,21 +412,22 @@ module.exports = { path.scope.rename(exportsName.name, oldName.name); } - path.replaceWith( - EXPORT_ALL_TEMPLATE({ - NAME: exportsName, + path.scope.push({ + id: exportsName, + init: EXPORT_ALL_TEMPLATE({ OLD_NAME: oldName, SOURCE: t.stringLiteral(path.node.source.value), ID: t.numericLiteral(asset.id) - }) - ); + }).expression + }); + path.remove(); } }; function addExport(asset, path, local, exported) { // Check if this identifier has already been exported. // If so, create an export alias for it, otherwise, rename the local variable to an export. - if (asset.cacheData.exports[local.name]) { + if (asset.cacheData.exports[exported.name]) { asset.cacheData.exports[getName(asset, 'export', exported.name)] = asset.cacheData.exports[local.name]; } else { @@ -425,8 +436,8 @@ function addExport(asset, path, local, exported) { let assignNode = EXPORT_ASSIGN_TEMPLATE({ EXPORTS: getExportsIdentifier(asset), - NAME: t.identifier(local.name), - LOCAL: getIdentifier(asset, 'export', exported.name) + NAME: t.identifier(exported.name), + LOCAL: getIdentifier(asset, 'export', local.name) }); // Get all the node paths mutating the export and insert a CommonJS assignement. @@ -434,7 +445,7 @@ function addExport(asset, path, local, exported) { .getBinding(local.name) .constantViolations.concat(path) .forEach(path => path.insertAfter(assignNode)); - path.scope.rename(local.name, getName(asset, 'export', exported.name)); + path.scope.rename(local.name, getName(asset, 'export', local.name)); } } diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-default/b.js b/test/integration/scope-hoisting/commonjs/require-re-export-default/b.js index 6c2f9933ca8..7b678240251 100644 --- a/test/integration/scope-hoisting/commonjs/require-re-export-default/b.js +++ b/test/integration/scope-hoisting/commonjs/require-re-export-default/b.js @@ -1,2 +1,2 @@ -export foo from './c'; +export {default as foo} from './c'; export var baz = 1; diff --git a/test/integration/scope-hoisting/commonjs/require-re-export-namespace/b.js b/test/integration/scope-hoisting/commonjs/require-re-export-namespace/b.js index 3f1e1af874a..43f5aa737b8 100644 --- a/test/integration/scope-hoisting/commonjs/require-re-export-namespace/b.js +++ b/test/integration/scope-hoisting/commonjs/require-re-export-namespace/b.js @@ -1,2 +1,4 @@ -export * as c from './c'; +import * as c from './c'; + +export {c}; export var baz = 1; From 3040363aa010454fd66563ff9a2aa6ea1fa90ff9 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 21 Apr 2018 23:10:14 +0200 Subject: [PATCH 051/180] Fix quotes --- src/packagers/JSConcatPackager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 8897666d51e..3c76420deb1 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -56,7 +56,7 @@ class JSConcatPackager extends Packager { (function () { function $parcel$exportWildcard(dest, source) { Object.keys(source).forEach(function(key) { - if(key === 'default' || key === "__esModule") return; + if(key === "default" || key === "__esModule") return; Object.defineProperty(dest, key, { enumerable: true, get: function get() { From e147e17608eadd2fb2631fb5ed67d876e5bc77f0 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 21 Apr 2018 23:10:34 +0200 Subject: [PATCH 052/180] Side effects --- src/visitors/hoist.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 11495c3d199..f71132310d8 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -209,7 +209,10 @@ module.exports = { CallExpression(path, asset) { let {callee, arguments: args} = path.node; - if (t.isIdentifier(callee, {name: '$parcel$exportWildcard'})) { + if ( + t.isIdentifier(callee, {name: '$parcel$exportWildcard'}) || + asset.package.sideEffects === false + ) { // This hints Uglify and Babel that this CallExpression does not have any side-effects. // This will make unsused CommonJS wildcards removed from the minified builds. path.addComment('leading', '#__PURE__'); From 200a9a778235a8e96ff34142b7117a2aa696a576 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 21 Apr 2018 23:10:47 +0200 Subject: [PATCH 053/180] Try to fix Windows test --- test/utils.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/utils.js b/test/utils.js index ef3ec7b56ab..5bb6bd4599d 100644 --- a/test/utils.js +++ b/test/utils.js @@ -12,11 +12,13 @@ beforeEach(async function() { // Windows needs a delay for the file handles to be released before deleting // is possible. Without a delay, rimraf fails on `beforeEach` for `/dist` if (process.platform === 'win32') { - await sleep(50); + await sleep(100); } // Unix based systems also need a delay but only half as much as windows await sleep(50); - rimraf.sync(path.join(__dirname, 'dist')); + await new Promise((resolve, reject) => + rimraf(path.join(__dirname, 'dist'), err => (err ? reject(err) : resolve())) + ); }); function sleep(ms) { From a4d86db444ef4bd68c5b9c320574bc55420b3d62 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 21 Apr 2018 23:26:34 +0200 Subject: [PATCH 054/180] Try fix windows test again --- test/utils.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/utils.js b/test/utils.js index 5bb6bd4599d..3c061a778b7 100644 --- a/test/utils.js +++ b/test/utils.js @@ -12,13 +12,23 @@ beforeEach(async function() { // Windows needs a delay for the file handles to be released before deleting // is possible. Without a delay, rimraf fails on `beforeEach` for `/dist` if (process.platform === 'win32') { + let count = 0; + let retry = 10; + await sleep(100); + + while (count++ < retry) { + try { + rimraf.sync(path.join(__dirname, 'dist')); + break; + } catch (_) { + continue; + } + } } // Unix based systems also need a delay but only half as much as windows await sleep(50); - await new Promise((resolve, reject) => - rimraf(path.join(__dirname, 'dist'), err => (err ? reject(err) : resolve())) - ); + rimraf.sync(path.join(__dirname, 'dist')); }); function sleep(ms) { From 18a0fa800f2615f8ccd6df9b6a293aeba56201b0 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sun, 22 Apr 2018 15:03:07 +0200 Subject: [PATCH 055/180] Only mark top-levels statements as pure --- src/visitors/hoist.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index f71132310d8..afa9ca3cd73 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -211,7 +211,8 @@ module.exports = { if ( t.isIdentifier(callee, {name: '$parcel$exportWildcard'}) || - asset.package.sideEffects === false + (asset.package.sideEffects === false && + t.isProgram(path.getStatementParent().parent)) // is it in a top-level statement? ) { // This hints Uglify and Babel that this CallExpression does not have any side-effects. // This will make unsused CommonJS wildcards removed from the minified builds. From bd8d9bdaed2d83addc5960811525ff41d4401b53 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sun, 22 Apr 2018 15:07:44 +0200 Subject: [PATCH 056/180] Revert "Try to fix Windows test" This reverts commit af5e4a778ad4f89bde86d3b8a024d7caec769901. --- test/utils.js | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/test/utils.js b/test/utils.js index 3c061a778b7..d26f5664a97 100644 --- a/test/utils.js +++ b/test/utils.js @@ -12,19 +12,7 @@ beforeEach(async function() { // Windows needs a delay for the file handles to be released before deleting // is possible. Without a delay, rimraf fails on `beforeEach` for `/dist` if (process.platform === 'win32') { - let count = 0; - let retry = 10; - - await sleep(100); - - while (count++ < retry) { - try { - rimraf.sync(path.join(__dirname, 'dist')); - break; - } catch (_) { - continue; - } - } + await sleep(50); } // Unix based systems also need a delay but only half as much as windows await sleep(50); @@ -166,11 +154,7 @@ function run(bundle, globals, opts = {}) { vm.runInContext(fs.readFileSync(bundle.name), ctx); if (opts.require !== false) { - if (ctx.parcelRequire) { - return ctx.parcelRequire(bundle.entryAsset.id); - } else { - return ctx.output; - } + return ctx.parcelRequire(bundle.entryAsset.id); } return ctx; From 34813b26194527af54703e8753f6b4f529d0a59f Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sun, 22 Apr 2018 15:23:45 +0200 Subject: [PATCH 057/180] Revert sideEffects --- src/visitors/hoist.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index afa9ca3cd73..11495c3d199 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -209,11 +209,7 @@ module.exports = { CallExpression(path, asset) { let {callee, arguments: args} = path.node; - if ( - t.isIdentifier(callee, {name: '$parcel$exportWildcard'}) || - (asset.package.sideEffects === false && - t.isProgram(path.getStatementParent().parent)) // is it in a top-level statement? - ) { + if (t.isIdentifier(callee, {name: '$parcel$exportWildcard'})) { // This hints Uglify and Babel that this CallExpression does not have any side-effects. // This will make unsused CommonJS wildcards removed from the minified builds. path.addComment('leading', '#__PURE__'); From 3dd775894294d900621860bf97b56d1338638783 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 23 Apr 2018 00:20:42 +0200 Subject: [PATCH 058/180] Fix tests --- test/utils.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/utils.js b/test/utils.js index d26f5664a97..ef3ec7b56ab 100644 --- a/test/utils.js +++ b/test/utils.js @@ -154,7 +154,11 @@ function run(bundle, globals, opts = {}) { vm.runInContext(fs.readFileSync(bundle.name), ctx); if (opts.require !== false) { - return ctx.parcelRequire(bundle.entryAsset.id); + if (ctx.parcelRequire) { + return ctx.parcelRequire(bundle.entryAsset.id); + } else { + return ctx.output; + } } return ctx; From 66b4232725b1c66e6fd4824ddfc1b0bcdd35a7db Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 23 Apr 2018 08:49:34 -0700 Subject: [PATCH 059/180] Move import/export linking to babel transform instead of string manipulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also throws when trying to import a name that wasn’t exported from an ES6 only module. --- src/packagers/JSConcatPackager.js | 37 ++++++++++++++++--------------- src/transforms/concat.js | 5 +++++ src/visitors/hoist.js | 36 ++++++++++++++++++++---------- test/scope-hoisting.js | 19 ++++++++++------ 4 files changed, 60 insertions(+), 37 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 3c76420deb1..f36bebe858b 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -119,24 +119,25 @@ class JSConcatPackager extends Packager { } } - if (dep.isES6Import) { - if (mod.cacheData.isES6Module) { - js = js - .split('$' + asset.id + '$import$' + t.toIdentifier(dep.name)) - .join('$' + mod.id + '$export'); - } else { - js = js - .split( - '$' + - asset.id + - '$import$' + - t.toIdentifier(dep.name) + - '$default' - ) - .join('$' + mod.id + '$exports'); - js = js - .split('$' + asset.id + '$import$' + t.toIdentifier(dep.name) + '$') - .join('$' + mod.id + '$exports.'); + if (dep.isES6Import && dep.ids) { + for (let id in dep.ids) { + let name = '$' + mod.id + '$export$' + id; + if (mod.cacheData.exports[name]) { + this.exports.set(dep.ids[id], name); + } else if (mod.cacheData.isCommonJS) { + if (id === 'default') { + name = '$' + mod.id + '$exports'; + } + + this.exports.set(dep.ids[id], name); + } else { + throw new Error( + `${path.relative( + this.options.rootDir, + mod.name + )} does not export '${id}'` + ); + } } } diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 8581c5750c9..626465649b6 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -139,6 +139,11 @@ module.exports = (code, exports, moduleMap, wildcards) => { path.replaceWith(node); } } + }, + ReferencedIdentifier(path) { + if (exports.has(path.node.name)) { + path.replaceWith(t.identifier(exports.get(path.node.name))); + } } }); diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 11495c3d199..28dd6077601 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -148,6 +148,7 @@ module.exports = { if (matchesPattern(path.node, 'module.exports')) { path.replaceWith(getExportsIdentifier(asset)); + asset.cacheData.isCommonJS = true; } if (matchesPattern(path.node, 'module.id')) { @@ -172,12 +173,14 @@ module.exports = { !path.scope.getData('shouldWrap') ) { path.replaceWith(getExportsIdentifier(asset)); + asset.cacheData.isCommonJS = true; } }, ThisExpression(path, asset) { if (!path.scope.parent && !path.scope.getData('shouldWrap')) { path.replaceWith(getExportsIdentifier(asset)); + asset.cacheData.isCommonJS = true; } }, @@ -190,6 +193,7 @@ module.exports = { !path.scope.getData('shouldWrap') ) { path.get('left').replaceWith(getExportsIdentifier(asset)); + asset.cacheData.isCommonJS = true; } }, @@ -252,24 +256,26 @@ module.exports = { }, ImportDeclaration(path, asset) { + let dep = asset.dependencies.get(path.node.source.value); + let ids = dep.ids || (dep.ids = {}); + // For each specifier, rename the local variables to point to the imported name. // This will be replaced by the final variable name of the resolved asset in the packager. for (let specifier of path.node.specifiers) { if (t.isImportDefaultSpecifier(specifier)) { - path.scope.rename( - specifier.local.name, - getName(asset, 'import', path.node.source.value, 'default') - ); + let name = getName(asset, 'import', path.node.source.value, 'default'); + ids['default'] = name; + path.scope.rename(specifier.local.name, name); } else if (t.isImportSpecifier(specifier)) { - path.scope.rename( - specifier.local.name, - getName( - asset, - 'import', - path.node.source.value, - specifier.imported.name - ) + let name = getName( + asset, + 'import', + path.node.source.value, + specifier.imported.name ); + + ids[specifier.imported.name] = name; + path.scope.rename(specifier.local.name, name); } else if (t.isImportNamespaceSpecifier(specifier)) { path.scope.push({ id: specifier.local, @@ -321,12 +327,16 @@ module.exports = { let {declaration, source, specifiers} = path.node; if (source) { + let dep = asset.dependencies.get(path.node.source.value); + let ids = dep.ids || (dep.ids = {}); + for (let specifier of specifiers) { let local, exported; if (t.isExportDefaultSpecifier(specifier)) { local = getIdentifier(asset, 'import', source.value, 'default'); exported = specifier.exported; + ids['default'] = local.name; } else if (t.isExportNamespaceSpecifier(specifier)) { local = getIdentifier(asset, 'require', source.value); exported = specifier.exported; @@ -346,6 +356,8 @@ module.exports = { LOCAL: local }) ); + + ids[specifier.local.name] = local.name; } // Create a variable to re-export from the imported module. diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index 58d9a06f032..95f922091f5 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -135,13 +135,18 @@ describe.only('scope hoisting', function() { }); it('excludes default when re-exporting a module', async function() { - let b = await bundle( - __dirname + - '/integration/scope-hoisting/es6/re-export-exclude-default/a.js' - ); - - let output = run(b); - assert.deepEqual(output, {b: undefined, foo: 3}); + let threw = false; + try { + await bundle( + __dirname + + '/integration/scope-hoisting/es6/re-export-exclude-default/a.js' + ); + } catch (err) { + threw = true; + assert.equal(err.message, "b.js does not export 'default'"); + } + + assert(threw); }); it('supports multiple exports of the same variable', async function() { From 1db6af03f5aa9148780c7749cff6c752890b0540 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 24 Apr 2018 00:20:50 +0200 Subject: [PATCH 060/180] Implement import/require logic in concat + Add CommonJS interop with default + Fix CommonJS exports using wrappers --- src/assets/RawAsset.js | 9 +- src/builtins/helpers.js | 24 +++ src/packagers/JSConcatPackager.js | 39 ++--- src/transforms/concat.js | 57 ++++++-- src/visitors/hoist.js | 138 ++++++++++-------- .../es6/import-commonjs-default/a.js | 4 + .../es6/import-commonjs-default/exports.js | 1 + .../es6/import-commonjs-default/wrapped.js | 3 + test/scope-hoisting.js | 12 +- 9 files changed, 188 insertions(+), 99 deletions(-) create mode 100644 src/builtins/helpers.js create mode 100644 test/integration/scope-hoisting/es6/import-commonjs-default/a.js create mode 100644 test/integration/scope-hoisting/es6/import-commonjs-default/exports.js create mode 100644 test/integration/scope-hoisting/es6/import-commonjs-default/wrapped.js diff --git a/src/assets/RawAsset.js b/src/assets/RawAsset.js index 4a1be0cdda5..d44abf2e306 100644 --- a/src/assets/RawAsset.js +++ b/src/assets/RawAsset.js @@ -18,9 +18,12 @@ class RawAsset extends Asset { this.generateBundleName() ); - return { - js: `module.exports=${JSON.stringify(pathToAsset)};` - }; + return [ + { + type: 'js', + value: `module.exports=${JSON.stringify(pathToAsset)};` + } + ]; } async generateHash() { diff --git a/src/builtins/helpers.js b/src/builtins/helpers.js new file mode 100644 index 00000000000..a4a695c7b06 --- /dev/null +++ b/src/builtins/helpers.js @@ -0,0 +1,24 @@ +/* eslint-disable no-unused-vars */ + +function $parcel$interopDefault(a) { + return a && a.__esModule + ? a.default + : a; +} + +function $parcel$exportWildcard(dest, source) { + Object.keys(source).forEach(function(key) { + if(key === "default" || key === "__esModule") { + return; + } + + Object.defineProperty(dest, key, { + enumerable: true, + get: function get() { + return source[key]; + } + }); + }); + + return dest; +} diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index f36bebe858b..1b66d94b629 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -1,5 +1,4 @@ const Packager = require('./Packager'); -const t = require('babel-types'); const path = require('path'); const fs = require('fs'); @@ -8,6 +7,10 @@ const concat = require('../transforms/concat'); const prelude = fs .readFileSync(path.join(__dirname, '../builtins/prelude2.js'), 'utf8') .trim(); +const helpers = + fs + .readFileSync(path.join(__dirname, '../builtins/helpers.js'), 'utf8') + .trim() + '\n'; class JSConcatPackager extends Packager { async write(string) { @@ -50,24 +53,9 @@ class JSConcatPackager extends Packager { this.exposedModules.add(this.bundle.entryAsset); } - await this.write(prelude + '(function (require) {\n'); + await this.write(prelude + '(function (require) {\n' + helpers); } else { - await this.write(` -(function () { - function $parcel$exportWildcard(dest, source) { - Object.keys(source).forEach(function(key) { - if(key === "default" || key === "__esModule") return; - Object.defineProperty(dest, key, { - enumerable: true, - get: function get() { - return source[key]; - } - }); - }); - - return dest; - } -`); + await this.write('(function () {\n' + helpers); } } @@ -93,7 +81,7 @@ class JSConcatPackager extends Packager { } } - for (let [dep, mod] of asset.depAssets) { + /* for (let [dep, mod] of asset.depAssets) { let depName = '$' + asset.id + '$require$' + t.toIdentifier(dep.name); let moduleName = this.getExportIdentifier(mod); @@ -102,7 +90,7 @@ class JSConcatPackager extends Packager { moduleName = `require(${mod.id})`; } - js = js.split(depName).join(moduleName); + // js = js.split(depName).join(moduleName); // If this was an ES6 export all (e.g. export * from 'foo'), resolve to the original exports. if (dep.isExportAll) { @@ -121,16 +109,17 @@ class JSConcatPackager extends Packager { if (dep.isES6Import && dep.ids) { for (let id in dep.ids) { + id = id.slice(1) let name = '$' + mod.id + '$export$' + id; if (mod.cacheData.exports[name]) { - this.exports.set(dep.ids[id], name); + this.exports.set(depName, name); } else if (mod.cacheData.isCommonJS) { if (id === 'default') { - name = '$' + mod.id + '$exports'; + name = '(/*#__PURE__* /$parcel$interopDefault($' + mod.id + '$exports))'; } - this.exports.set(dep.ids[id], name); - } else { + this.exports.set(depName, name); + } else if(false) { throw new Error( `${path.relative( this.options.rootDir, @@ -160,7 +149,7 @@ class JSConcatPackager extends Packager { } js = js.split(depResolve).join(resolved); - } + }*/ // Replace all re-exported variables diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 626465649b6..8130d0aab40 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -1,4 +1,5 @@ const babylon = require('babylon'); +const template = require('babel-template'); const t = require('babel-types'); const traverse = require('babel-traverse').default; const generate = require('babel-generator').default; @@ -6,19 +7,20 @@ const generate = require('babel-generator').default; const EXPORTS_RE = /^\$([\d]+)\$exports$/; const EXPORT_RE = /^\$([\d]+)\$export\$(.+)$/; +const DEFAULT_INTEROP_TEMPLATE = template('$parcel$interopDefault(MODULE)'); + // TODO: minify // TODO: source-map module.exports = (code, exports, moduleMap, wildcards) => { let ast = babylon.parse(code); - let addedExports = new Set(); let resolveModule = (id, name) => { let module = moduleMap.get(id); - return module.depAssets.get(module.dependencies.get(name)).id; + return module.depAssets.get(module.dependencies.get(name)); }; - function replaceExportNode(id, name, path) { + function replaceExportNode(id, name, path, commonJsAsMemberExpr = true) { path = getOuterStatement(path); let node = find(id, id => `$${id}$export$${name}`); @@ -29,7 +31,11 @@ module.exports = (code, exports, moduleMap, wildcards) => { // if there is a CommonJS export return $id$exports.name if (node) { - return t.memberExpression(node, t.identifier(name)); + if (commonJsAsMemberExpr) { + return t.memberExpression(node, t.identifier(name)); + } else { + return node; + } } } @@ -80,25 +86,54 @@ module.exports = (code, exports, moduleMap, wildcards) => { // each require('module') call gets replaced with $parcel$require(id, 'module') if (t.isIdentifier(callee, {name: '$parcel$require'})) { - let [id, name] = args; + let [id, source] = args; if ( args.length !== 2 || !t.isNumericLiteral(id) || - !t.isStringLiteral(name) + !t.isStringLiteral(source) ) { throw new Error( 'invariant: invalid signature, expected : $parcel$require(number, string)' ); } - let mod = resolveModule(id.value, name.value); + let mod = resolveModule(id.value, source.value).id; if (typeof mod === 'undefined') { - throw new Error(`Cannot find module "${name.value}"`); + throw new Error(`Cannot find module "${source.value}"`); } path.replaceWith(t.identifier(`$${mod}$exports`)); + } else if (t.isIdentifier(callee, {name: '$parcel$import'})) { + let [id, source, name] = args; + + if ( + args.length !== 3 || + !t.isNumericLiteral(id) || + !t.isStringLiteral(source) || + !t.isStringLiteral(name) + ) { + throw new Error( + 'invariant: invalid signature, expected : $parcel$import(number, string, string)' + ); + } + + let mod = resolveModule(id.value, source.value); + + if (typeof mod === 'undefined') { + throw new Error(`Cannot find module "${source.value}"`); + } + + if (name.value === 'default' && mod.cacheData.isCommonJS) { + path.replaceWith( + DEFAULT_INTEROP_TEMPLATE({ + MODULE: replaceExportNode(mod.id, name.value, path, false) + }) + ); + } else { + path.replaceWith(replaceExportNode(mod.id, name.value, path)); + } } }, MemberExpression(path) { @@ -128,16 +163,16 @@ module.exports = (code, exports, moduleMap, wildcards) => { let match = name.match(EXPORT_RE); - if (match && !path.scope.hasBinding(name) && !addedExports.has(name)) { + if (match && !path.scope.hasBinding(name)) { let id = Number(match[1]); let exportName = match[2]; let node = replaceExportNode(id, exportName, path); - addedExports.add(name); - if (node) { path.replaceWith(node); } + } else if (EXPORTS_RE.test(name) && !path.scope.hasBinding(name)) { + path.replaceWith(t.objectExpression([])); } }, ReferencedIdentifier(path) { diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 28dd6077601..3eb628231b3 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -11,6 +11,7 @@ const WRAPPER_TEMPLATE = template(` }).call({}); `); +const IMPORT_TEMPLATE = template('$parcel$import(ID, SOURCE, NAME)'); const EXPORT_ASSIGN_TEMPLATE = template('EXPORTS.NAME = LOCAL'); const EXPORT_ALL_TEMPLATE = template( '$parcel$exportWildcard(OLD_NAME, $parcel$require(ID, SOURCE))' @@ -38,6 +39,7 @@ module.exports = { callee.name === 'eval' && !path.scope.hasBinding('eval', true) ) { + asset.cacheData.isCommonJS = true; shouldWrap = true; path.stop(); } @@ -47,6 +49,7 @@ module.exports = { // Wrap in a function if we see a top-level return statement. if (path.getFunctionParent().isProgram()) { shouldWrap = true; + asset.cacheData.isCommonJS = true; path.replaceWith( t.returnStatement( t.memberExpression( @@ -72,6 +75,7 @@ module.exports = { !path.scope.hasBinding('module') && !path.scope.getData('shouldWrap') ) { + asset.cacheData.isCommonJS = true; shouldWrap = true; path.stop(); } @@ -117,23 +121,7 @@ module.exports = { t.variableDeclarator(exportsIdentifier, t.objectExpression([])) ]) ]); - } /* else if (Object.keys(asset.cacheData.exports).length > 0) { - path.pushContainer('body', [ - t.variableDeclaration('var', [ - t.variableDeclarator( - getExportsIdentifier(asset), - t.objectExpression( - Object.values(asset.cacheData.exports).map(k => - t.objectProperty( - t.identifier(k), - getIdentifier(asset, 'export', k) - ) - ) - ) - ) - ]) - ]); - }*/ + } } path.stop(); @@ -213,12 +201,17 @@ module.exports = { CallExpression(path, asset) { let {callee, arguments: args} = path.node; - if (t.isIdentifier(callee, {name: '$parcel$exportWildcard'})) { - // This hints Uglify and Babel that this CallExpression does not have any side-effects. - // This will make unsused CommonJS wildcards removed from the minified builds. - path.addComment('leading', '#__PURE__'); + if (t.isIdentifier(callee)) { + if ( + callee.name === '$parcel$exportWildcard' || + callee.name === '$parcel$interopDefault' + ) { + // This hints Uglify and Babel that this CallExpression does not have any side-effects. + // This will make unsused CommonJS wildcards removed from the minified builds. + path.addComment('leading', '#__PURE__'); - return; + return; + } } let isRequire = @@ -256,30 +249,36 @@ module.exports = { }, ImportDeclaration(path, asset) { - let dep = asset.dependencies.get(path.node.source.value); - let ids = dep.ids || (dep.ids = {}); - // For each specifier, rename the local variables to point to the imported name. // This will be replaced by the final variable name of the resolved asset in the packager. for (let specifier of path.node.specifiers) { if (t.isImportDefaultSpecifier(specifier)) { - let name = getName(asset, 'import', path.node.source.value, 'default'); - ids['default'] = name; - path.scope.rename(specifier.local.name, name); + rewriteBinding( + path, + specifier.local, + IMPORT_TEMPLATE({ + ID: t.numericLiteral(asset.id), + NAME: t.stringLiteral('default'), + SOURCE: path.node.source + }) + ); } else if (t.isImportSpecifier(specifier)) { - let name = getName( - asset, - 'import', - path.node.source.value, - specifier.imported.name + rewriteBinding( + path, + specifier.local, + IMPORT_TEMPLATE({ + ID: t.numericLiteral(asset.id), + SOURCE: path.node.source, + NAME: t.stringLiteral(specifier.imported.name) + }) ); - - ids[specifier.imported.name] = name; - path.scope.rename(specifier.local.name, name); } else if (t.isImportNamespaceSpecifier(specifier)) { path.scope.push({ id: specifier.local, - init: t.identifier(getName(asset, 'require', path.node.source.value)) + init: REQUIRE_CALL_TEMPLATE({ + ID: t.numericLiteral(asset.id), + SOURCE: path.node.source + }).expression }); } } @@ -294,7 +293,7 @@ module.exports = { // Add assignment to exports object for namespace imports and commonjs. path.insertAfter( EXPORT_ASSIGN_TEMPLATE({ - EXPORTS: getExportsIdentifier(asset), + EXPORTS: getExportsIdentifier(asset, path.scope), NAME: t.identifier('default'), LOCAL: identifier }) @@ -327,37 +326,37 @@ module.exports = { let {declaration, source, specifiers} = path.node; if (source) { - let dep = asset.dependencies.get(path.node.source.value); - let ids = dep.ids || (dep.ids = {}); - for (let specifier of specifiers) { let local, exported; if (t.isExportDefaultSpecifier(specifier)) { - local = getIdentifier(asset, 'import', source.value, 'default'); + local = IMPORT_TEMPLATE({ + ID: t.numericLiteral(asset.id), + SOURCE: source, + NAME: t.stringLiteral('default') + }); exported = specifier.exported; - ids['default'] = local.name; } else if (t.isExportNamespaceSpecifier(specifier)) { - local = getIdentifier(asset, 'require', source.value); + local = REQUIRE_CALL_TEMPLATE({ + ID: t.numericLiteral(asset.id), + SOURCE: source + }); exported = specifier.exported; } else if (t.isExportSpecifier(specifier)) { - local = getIdentifier( - asset, - 'import', - source.value, - specifier.local.name - ); + local = IMPORT_TEMPLATE({ + ID: t.numericLiteral(asset.id), + SOURCE: source, + NAME: t.stringLiteral(specifier.local.name) + }); exported = specifier.exported; path.insertAfter( EXPORT_ASSIGN_TEMPLATE({ - EXPORTS: getExportsIdentifier(asset), + EXPORTS: getExportsIdentifier(asset, path.scope), NAME: exported, - LOCAL: local + LOCAL: local.expression }) ); - - ids[specifier.local.name] = local.name; } // Create a variable to re-export from the imported module. @@ -365,7 +364,7 @@ module.exports = { t.variableDeclaration('var', [ t.variableDeclarator( getIdentifier(asset, 'export', exported.name), - local + local.expression ) ]) ); @@ -373,7 +372,7 @@ module.exports = { if (path.scope.hasGlobal('module') || path.scope.hasGlobal('exports')) { path.insertAfter( EXPORT_ASSIGN_TEMPLATE({ - EXPORTS: getExportsIdentifier(asset), + EXPORTS: getExportsIdentifier(asset, path.scope), NAME: t.identifier(exported.name), LOCAL: local }) @@ -447,7 +446,7 @@ function addExport(asset, path, local, exported) { exported.name; let assignNode = EXPORT_ASSIGN_TEMPLATE({ - EXPORTS: getExportsIdentifier(asset), + EXPORTS: getExportsIdentifier(asset, path.scope), NAME: t.identifier(exported.name), LOCAL: getIdentifier(asset, 'export', local.name) }); @@ -495,6 +494,27 @@ function getIdentifier(asset, type, ...rest) { return t.identifier(getName(asset, type, ...rest)); } -function getExportsIdentifier(asset) { - return getIdentifier(asset, 'exports'); +function getExportsIdentifier(asset, scope) { + if (scope && scope.getData('shouldWrap')) { + return t.identifier('exports'); + } else { + return getIdentifier(asset, 'exports'); + } +} + +// Replaces all references to a Binding by a replacement node. +function rewriteBinding(path, init, replacement) { + let newName = path.scope.generateDeclaredUidIdentifier(init.name); + + path.insertBefore( + t.variableDeclaration('var', [ + t.variableDeclarator( + newName, + t.isExpressionStatement(replacement) + ? replacement.expression + : replacement + ) + ]) + ); + path.scope.rename(init.name, newName.name); } diff --git a/test/integration/scope-hoisting/es6/import-commonjs-default/a.js b/test/integration/scope-hoisting/es6/import-commonjs-default/a.js new file mode 100644 index 00000000000..c2ecbdc135e --- /dev/null +++ b/test/integration/scope-hoisting/es6/import-commonjs-default/a.js @@ -0,0 +1,4 @@ +import foo from './wrapped' +import bar from './exports' + +output = foo() + bar() diff --git a/test/integration/scope-hoisting/es6/import-commonjs-default/exports.js b/test/integration/scope-hoisting/es6/import-commonjs-default/exports.js new file mode 100644 index 00000000000..9d178e4d54a --- /dev/null +++ b/test/integration/scope-hoisting/es6/import-commonjs-default/exports.js @@ -0,0 +1 @@ +exports = () => 'bar' diff --git a/test/integration/scope-hoisting/es6/import-commonjs-default/wrapped.js b/test/integration/scope-hoisting/es6/import-commonjs-default/wrapped.js new file mode 100644 index 00000000000..a785ce7a0b3 --- /dev/null +++ b/test/integration/scope-hoisting/es6/import-commonjs-default/wrapped.js @@ -0,0 +1,3 @@ +// triggers wrapping +eval('void 0') +module.exports = () => 'foo' diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index 95f922091f5..93c5f7506ea 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -176,7 +176,7 @@ describe.only('scope hoisting', function() { assert.equal(output, 5); }); - it('supports not exports function arguments', async function() { + it('should not export function arguments', async function() { let b = await bundle( __dirname + '/integration/scope-hoisting/es6/export-binding-identifiers/a.js' @@ -185,6 +185,16 @@ describe.only('scope hoisting', function() { let output = run(b); assert.deepEqual(output, ['test']); }); + + it('supports import default CommonJS interop', async function() { + let b = await bundle( + __dirname + + '/integration/scope-hoisting/es6/import-commonjs-default/a.js' + ); + + let output = run(b); + assert.deepEqual(output, 'foobar'); + }); }); describe('commonjs', function() { From a7ce45e8bef16e7082cd812a4cd2b595a4cfa6c7 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 24 Apr 2018 00:33:07 +0200 Subject: [PATCH 061/180] Remove wildcard lookup from packager transform --- src/packagers/JSConcatPackager.js | 6 +----- src/transforms/concat.js | 25 +------------------------ 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 1b66d94b629..07dfcb0a438 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -22,7 +22,6 @@ class JSConcatPackager extends Packager { this.exposedModules = new Set(); this.buffer = ''; this.exports = new Map(); - this.wildcards = new Map(); this.moduleMap = new Map(); this.needsPrelude = false; @@ -72,7 +71,6 @@ class JSConcatPackager extends Packager { let js = asset.generated.js; this.moduleMap.set(asset.id, asset); - this.wildcards.set(asset.id, asset.cacheData.wildcards); for (let key in asset.cacheData.exports) { let local = '$' + asset.id + '$export$' + asset.cacheData.exports[key]; @@ -230,9 +228,7 @@ class JSConcatPackager extends Packager { await this.write('})();'); } - super.write( - concat(this.buffer, this.exports, this.moduleMap, this.wildcards) - ); + super.write(concat(this.buffer, this.exports, this.moduleMap)); } } diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 8130d0aab40..1c99650038f 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -12,7 +12,7 @@ const DEFAULT_INTEROP_TEMPLATE = template('$parcel$interopDefault(MODULE)'); // TODO: minify // TODO: source-map -module.exports = (code, exports, moduleMap, wildcards) => { +module.exports = (code, exports, moduleMap) => { let ast = babylon.parse(code); let resolveModule = (id, name) => { @@ -53,29 +53,6 @@ module.exports = (code, exports, moduleMap, wildcards) => { return t.identifier(exports.get(computedSymbol)); } - // if there is a wildcard for the module - // default exports are excluded from wildcard exports - if (wildcards.has(id) && name !== 'default') { - /* recursively lookup the symbol - * this is needed when there is deep export wildcards, like in the following: - * - a.js - * > export * from './b' - * - b.js - * > export * from './c' - * - c.js in es6 - * > export * from 'lodash' - * - c.js in cjs - * > module.exports = require('lodash') - */ - let node = null; - - wildcards - .get(id) - .find(name => (node = find(resolveModule(id, name), symbol))); - - return node; - } - return null; } } From f7978a93b4acf84874ee58d126eb8e76b7b6680b Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 24 Apr 2018 10:55:28 +0200 Subject: [PATCH 062/180] Workaround for CSSAsset --- src/assets/CSSAsset.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/CSSAsset.js b/src/assets/CSSAsset.js index ff1f282781c..a877d56fbed 100644 --- a/src/assets/CSSAsset.js +++ b/src/assets/CSSAsset.js @@ -124,8 +124,8 @@ class CSSAsset extends Asset { }, { type: 'js', - value: js, - final: true + value: js + // final: true } ]; } From 1ec53aeffb076275ae6200246cf44f3f0c0abc13 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 24 Apr 2018 10:55:54 +0200 Subject: [PATCH 063/180] Add uglify without config --- src/packagers/JSConcatPackager.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 07dfcb0a438..644c83e2d60 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -1,4 +1,5 @@ const Packager = require('./Packager'); +const {minify} = require('uglify-es'); const path = require('path'); const fs = require('fs'); @@ -228,7 +229,29 @@ class JSConcatPackager extends Packager { await this.write('})();'); } - super.write(concat(this.buffer, this.exports, this.moduleMap)); + let output = concat(this.buffer, this.exports, this.moduleMap); + + if (this.options.minify) { + let result = minify(output, { + warnings: true, + compress: { + passes: 3, + unsafe: true, + pure_getters: true + }, + mangle: { + eval: true + } + }); + + if (result.error) { + throw result.error; + } + + output = result.code; + } + + super.write(output); } } From 156480131cb50edf918bf19f9662753e9aea9262 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 24 Apr 2018 10:56:19 +0200 Subject: [PATCH 064/180] Mark default import interop calls as pure --- src/transforms/concat.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 1c99650038f..f23823b7a90 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -111,6 +111,9 @@ module.exports = (code, exports, moduleMap) => { } else { path.replaceWith(replaceExportNode(mod.id, name.value, path)); } + } else if (t.isIdentifier(callee, {name: '$parcel$interopDefault'})) { + // This hints Uglify and Babel that this CallExpression does not have any side-effects. + path.addComment('leading', '#__PURE__'); } }, MemberExpression(path) { From c9add7cdb9440aa702a761e902da5718ee4a7768 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 24 Apr 2018 10:56:45 +0200 Subject: [PATCH 065/180] Fix binding registration --- src/visitors/hoist.js | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 3eb628231b3..d56d72ad245 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -202,10 +202,7 @@ module.exports = { let {callee, arguments: args} = path.node; if (t.isIdentifier(callee)) { - if ( - callee.name === '$parcel$exportWildcard' || - callee.name === '$parcel$interopDefault' - ) { + if (callee.name === '$parcel$exportWildcard') { // This hints Uglify and Babel that this CallExpression does not have any side-effects. // This will make unsused CommonJS wildcards removed from the minified builds. path.addComment('leading', '#__PURE__'); @@ -504,17 +501,17 @@ function getExportsIdentifier(asset, scope) { // Replaces all references to a Binding by a replacement node. function rewriteBinding(path, init, replacement) { - let newName = path.scope.generateDeclaredUidIdentifier(init.name); - - path.insertBefore( - t.variableDeclaration('var', [ - t.variableDeclarator( - newName, - t.isExpressionStatement(replacement) - ? replacement.expression - : replacement - ) - ]) - ); + let newName = path.scope.generateUidIdentifier(init.name); + let node = t.variableDeclaration('var', [ + t.variableDeclarator( + newName, + t.isExpressionStatement(replacement) + ? replacement.expression + : replacement + ) + ]); + let [nodePath] = path.insertBefore(node); + + path.scope.registerBinding('var', nodePath); path.scope.rename(init.name, newName.name); } From defd105e6a3fcd134ec45d9bca30d9459d63dff0 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 24 Apr 2018 21:46:18 +0200 Subject: [PATCH 066/180] Revert "Remove wildcard lookup from packager transform" This reverts commit dbd14b7a36b5c0a161cfe06edf698e9273241c2c. --- src/packagers/JSConcatPackager.js | 2 ++ src/transforms/concat.js | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 644c83e2d60..9926632d6e5 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -23,6 +23,7 @@ class JSConcatPackager extends Packager { this.exposedModules = new Set(); this.buffer = ''; this.exports = new Map(); + this.wildcards = new Map(); this.moduleMap = new Map(); this.needsPrelude = false; @@ -72,6 +73,7 @@ class JSConcatPackager extends Packager { let js = asset.generated.js; this.moduleMap.set(asset.id, asset); + this.wildcards.set(asset.id, asset.cacheData.wildcards); for (let key in asset.cacheData.exports) { let local = '$' + asset.id + '$export$' + asset.cacheData.exports[key]; diff --git a/src/transforms/concat.js b/src/transforms/concat.js index f23823b7a90..fc85f49b918 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -12,7 +12,7 @@ const DEFAULT_INTEROP_TEMPLATE = template('$parcel$interopDefault(MODULE)'); // TODO: minify // TODO: source-map -module.exports = (code, exports, moduleMap) => { +module.exports = (code, exports, moduleMap, wildcards) => { let ast = babylon.parse(code); let resolveModule = (id, name) => { @@ -53,6 +53,29 @@ module.exports = (code, exports, moduleMap) => { return t.identifier(exports.get(computedSymbol)); } + // if there is a wildcard for the module + // default exports are excluded from wildcard exports + if (wildcards.has(id) && name !== 'default') { + /* recursively lookup the symbol + * this is needed when there is deep export wildcards, like in the following: + * - a.js + * > export * from './b' + * - b.js + * > export * from './c' + * - c.js in es6 + * > export * from 'lodash' + * - c.js in cjs + * > module.exports = require('lodash') + */ + let node = null; + + wildcards + .get(id) + .find(name => (node = find(resolveModule(id, name), symbol))); + + return node; + } + return null; } } From eee8fa4ba82bbb2905243e4e8d7b0a82928584b7 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 24 Apr 2018 23:59:41 +0200 Subject: [PATCH 067/180] Implement $parcel$require$resolve in final transform --- src/packagers/JSConcatPackager.js | 32 +++++++++----- src/transforms/concat.js | 72 +++++++++++++++++++++++++------ src/visitors/hoist.js | 34 +++++++++------ 3 files changed, 102 insertions(+), 36 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 9926632d6e5..f862ba92a2b 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -14,7 +14,7 @@ const helpers = .trim() + '\n'; class JSConcatPackager extends Packager { - async write(string) { + write(string) { this.buffer += string; } @@ -54,9 +54,9 @@ class JSConcatPackager extends Packager { this.exposedModules.add(this.bundle.entryAsset); } - await this.write(prelude + '(function (require) {\n' + helpers); + this.write(prelude + '(function (require) {\n' + helpers); } else { - await this.write('(function () {\n' + helpers); + this.write('(function () {\n' + helpers); } } @@ -152,11 +152,23 @@ class JSConcatPackager extends Packager { js = js.split(depResolve).join(resolved); }*/ + for (let [dep, mod] of asset.depAssets) { + if (dep.dynamic && this.bundle.childBundles.has(mod.parentBundle)) { + for (let child of mod.parentBundle.siblingBundles) { + if (!child.isEmpty) { + await this.addBundleLoader(child.type); + } + } + + await this.addBundleLoader(mod.type); + } + } + // Replace all re-exported variables js = js.trim() + '\n'; - await this.write( + this.write( `\n/* ASSET: ${asset.id} - ${path.relative( this.options.rootDir, asset.name @@ -199,7 +211,7 @@ class JSConcatPackager extends Packager { } if (bundleLoader) { - await this.addAssetToBundle(bundleLoader); + this.addAssetToBundle(bundleLoader); } else { return; } @@ -209,8 +221,8 @@ class JSConcatPackager extends Packager { let target = this.options.target === 'node' ? 'node' : 'browser'; let asset = await this.bundler.getAsset(loader[target]); if (!this.bundle.assets.has(asset)) { - await this.addAssetToBundle(asset); - await this.write( + this.addAssetToBundle(asset); + this.write( `${this.getExportIdentifier(bundleLoader)}.register(${JSON.stringify( bundleType )},${this.getExportIdentifier(asset)});\n` @@ -226,12 +238,12 @@ class JSConcatPackager extends Packager { exposed.push(`${m.id}: ${this.getExportIdentifier(m)}`); } - await this.write(`return {${exposed.join(', ')}};\n})`); + this.write(`return {${exposed.join(', ')}};\n})`); } else { - await this.write('})();'); + this.write('})();'); } - let output = concat(this.buffer, this.exports, this.moduleMap); + let output = concat(this); if (this.options.minify) { let result = minify(output, { diff --git a/src/transforms/concat.js b/src/transforms/concat.js index fc85f49b918..caa5ee089d9 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -12,7 +12,8 @@ const DEFAULT_INTEROP_TEMPLATE = template('$parcel$interopDefault(MODULE)'); // TODO: minify // TODO: source-map -module.exports = (code, exports, moduleMap, wildcards) => { +module.exports = packager => { + let {buffer: code, exports, moduleMap, wildcards} = packager; let ast = babylon.parse(code); let resolveModule = (id, name) => { @@ -30,12 +31,8 @@ module.exports = (code, exports, moduleMap, wildcards) => { node = find(id, id => `$${id}$exports`) || t.identifier(`$${id}$exports`); // if there is a CommonJS export return $id$exports.name - if (node) { - if (commonJsAsMemberExpr) { - return t.memberExpression(node, t.identifier(name)); - } else { - return node; - } + if (commonJsAsMemberExpr) { + return t.memberExpression(node, t.identifier(name)); } } @@ -84,8 +81,12 @@ module.exports = (code, exports, moduleMap, wildcards) => { CallExpression(path) { let {arguments: args, callee} = path.node; + if (!t.isIdentifier(callee)) { + return; + } + // each require('module') call gets replaced with $parcel$require(id, 'module') - if (t.isIdentifier(callee, {name: '$parcel$require'})) { + if (callee.name === '$parcel$require') { let [id, source] = args; if ( @@ -101,11 +102,13 @@ module.exports = (code, exports, moduleMap, wildcards) => { let mod = resolveModule(id.value, source.value).id; if (typeof mod === 'undefined') { - throw new Error(`Cannot find module "${source.value}"`); + throw new Error( + `Cannot find module "${source.value}" in asset ${id.value}` + ); } path.replaceWith(t.identifier(`$${mod}$exports`)); - } else if (t.isIdentifier(callee, {name: '$parcel$import'})) { + } else if (callee.name === '$parcel$import') { let [id, source, name] = args; if ( @@ -122,7 +125,9 @@ module.exports = (code, exports, moduleMap, wildcards) => { let mod = resolveModule(id.value, source.value); if (typeof mod === 'undefined') { - throw new Error(`Cannot find module "${source.value}"`); + throw new Error( + `Cannot find module "${source.value}" in asset ${id.value}` + ); } if (name.value === 'default' && mod.cacheData.isCommonJS) { @@ -134,9 +139,40 @@ module.exports = (code, exports, moduleMap, wildcards) => { } else { path.replaceWith(replaceExportNode(mod.id, name.value, path)); } - } else if (t.isIdentifier(callee, {name: '$parcel$interopDefault'})) { + } else if (callee.name === '$parcel$interopDefault') { // This hints Uglify and Babel that this CallExpression does not have any side-effects. path.addComment('leading', '#__PURE__'); + } else if (callee.name === '$parcel$require$resolve') { + let [id, source] = args; + + if ( + args.length !== 2 || + !t.isNumericLiteral(id) || + !t.isStringLiteral(source) + ) { + throw new Error( + 'invariant: invalid signature, expected : $parcel$require$resolve(number, string)' + ); + } + + let mapped = moduleMap.get(id.value); + let dep = mapped.dependencies.get(source.value); + let mod = mapped.depAssets.get(dep); + let bundles; + + if (dep.dynamic && packager.bundle.childBundles.has(mod.parentBundle)) { + bundles = [packager.getBundleSpecifier(mod.parentBundle)]; + + for (let child of mod.parentBundle.siblingBundles) { + if (!child.isEmpty) { + bundles.push(packager.getBundleSpecifier(child)); + } + } + + bundles.push(mod.id); + } + + path.replaceWith(toNode(bundles || mod.id)); } }, MemberExpression(path) { @@ -215,3 +251,15 @@ function getOuterStatement(path) { return outerBlocks === 1; } } + +function toNode(object) { + if (typeof object === 'string') { + return t.stringLiteral(object); + } else if (typeof object === 'number') { + return t.numericLiteral(object); + } else if (Array.isArray(object)) { + return t.arrayExpression(object.map(toNode)); + } else { + throw new Error('Cannot serialize unsupported object type to AST'); + } +} diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index d56d72ad245..486c3fa1a0d 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -17,6 +17,9 @@ const EXPORT_ALL_TEMPLATE = template( '$parcel$exportWildcard(OLD_NAME, $parcel$require(ID, SOURCE))' ); const REQUIRE_CALL_TEMPLATE = template('$parcel$require(ID, SOURCE)'); +const REQUIRE_RESOLVE_CALL_TEMPLATE = template( + '$parcel$require$resolve(ID, SOURCE)' +); const TYPEOF = { module: 'object', require: 'function' @@ -211,13 +214,16 @@ module.exports = { } } - let isRequire = - t.isIdentifier(callee, {name: 'require'}) && - args.length === 1 && - t.isStringLiteral(args[0]) && - !path.scope.hasBinding('require'); + let ignore = + args.length !== 1 || + !t.isStringLiteral(args[0]) || + path.scope.hasBinding('require'); - if (isRequire) { + if (ignore) { + return; + } + + if (t.isIdentifier(callee, {name: 'require'})) { // Ignore require calls that were ignored earlier. if (!asset.dependencies.has(args[0].value)) { return; @@ -234,14 +240,14 @@ module.exports = { ); } - let isRequireResolve = - matchesPattern(callee, 'require.resolve') && - args.length === 1 && - t.isStringLiteral(args[0]) && - !path.scope.hasBinding('require'); - - if (isRequireResolve) { - path.replaceWith(getIdentifier(asset, 'require_resolve', args[0].value)); + if (matchesPattern(callee, 'require.resolve')) { + // path.replaceWith(getIdentifier(asset, 'require_resolve', args[0].value)); + path.replaceWith( + REQUIRE_RESOLVE_CALL_TEMPLATE({ + ID: t.numericLiteral(asset.id), + SOURCE: args[0] + }) + ); } }, From 38b7e17156578d374e1c3772283dbd18f6edae21 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Wed, 25 Apr 2018 00:47:33 +0200 Subject: [PATCH 068/180] Implement experimental exports remover --- src/transforms/concat.js | 54 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index caa5ee089d9..026d13fee3b 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -15,6 +15,7 @@ const DEFAULT_INTEROP_TEMPLATE = template('$parcel$interopDefault(MODULE)'); module.exports = packager => { let {buffer: code, exports, moduleMap, wildcards} = packager; let ast = babylon.parse(code); + let rootPath; let resolveModule = (id, name) => { let module = moduleMap.get(id); @@ -22,7 +23,9 @@ module.exports = packager => { }; function replaceExportNode(id, name, path, commonJsAsMemberExpr = true) { - path = getOuterStatement(path); + if (!rootPath) { + rootPath = getOuterStatement(path); + } let node = find(id, id => `$${id}$export$${name}`); @@ -42,7 +45,7 @@ module.exports = packager => { let computedSymbol = symbol(id); // if the symbol is in the scope there is not need to remap it - if (path.scope.hasBinding(computedSymbol)) { + if (rootPath.scope.hasBinding(computedSymbol)) { return t.identifier(computedSymbol); } @@ -158,7 +161,7 @@ module.exports = packager => { let mapped = moduleMap.get(id.value); let dep = mapped.dependencies.get(source.value); let mod = mapped.depAssets.get(dep); - let bundles; + let bundles = mod.id; if (dep.dynamic && packager.bundle.childBundles.has(mod.parentBundle)) { bundles = [packager.getBundleSpecifier(mod.parentBundle)]; @@ -172,7 +175,7 @@ module.exports = packager => { bundles.push(mod.id); } - path.replaceWith(toNode(bundles || mod.id)); + path.replaceWith(toNode(bundles)); } }, MemberExpression(path) { @@ -191,6 +194,10 @@ module.exports = packager => { if (path.scope.hasBinding(exportName)) { path.replaceWith(t.identifier(exportName)); } + + if (!rootPath) { + rootPath = getOuterStatement(path); + } } }, Identifier(path) { @@ -209,6 +216,10 @@ module.exports = packager => { if (node) { path.replaceWith(node); + } else { + throw new Error( + `Cannot find export "${exportName}" in module "${id}"` + ); } } else if (EXPORTS_RE.test(name) && !path.scope.hasBinding(name)) { path.replaceWith(t.objectExpression([])); @@ -218,6 +229,41 @@ module.exports = packager => { if (exports.has(path.node.name)) { path.replaceWith(t.identifier(exports.get(path.node.name))); } + }, + exit(path) { + if (!rootPath || !path.isProgram()) { + return; + } + + path = rootPath; + path.scope.crawl(); + Object.keys(path.scope.bindings) + .filter(name => EXPORTS_RE.test(name)) + .forEach(name => { + let binding = path.scope.getBinding(name); + // Is there any references which aren't also simple assignments? + let bailout = binding.referencePaths.some( + ({parentPath}) => + !parentPath.isMemberExpression() || + !parentPath.parentPath.isAssignmentExpression() + ); + + // Is so skip. + if (bailout) { + return; + } + + // Remove each assignement from the code + binding.referencePaths.forEach(({parentPath}) => { + if (parentPath.isMemberExpression()) { + console.log('Removing binding', name); + + parentPath.parentPath.remove(); + } else { + throw new Error('Unknown exports path type'); + } + }); + }); } }); From 945f1de95c1c31be8aafb6f00fdc8eba732ce796 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Wed, 25 Apr 2018 01:02:58 +0200 Subject: [PATCH 069/180] Fix missing await modifiers --- src/packagers/JSConcatPackager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index f862ba92a2b..7437965245f 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -211,7 +211,7 @@ class JSConcatPackager extends Packager { } if (bundleLoader) { - this.addAssetToBundle(bundleLoader); + await this.addAssetToBundle(bundleLoader); } else { return; } @@ -221,7 +221,7 @@ class JSConcatPackager extends Packager { let target = this.options.target === 'node' ? 'node' : 'browser'; let asset = await this.bundler.getAsset(loader[target]); if (!this.bundle.assets.has(asset)) { - this.addAssetToBundle(asset); + await this.addAssetToBundle(asset); this.write( `${this.getExportIdentifier(bundleLoader)}.register(${JSON.stringify( bundleType From 2ef1f7b3d1961d4e403e5a29125c5f462267fcef Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Wed, 25 Apr 2018 01:04:04 +0200 Subject: [PATCH 070/180] Revert "Revert "Remove wildcard lookup from packager transform"" This reverts commit 61bfbb84c5738157bca87ba614b90f023bb2ec90. --- src/packagers/JSConcatPackager.js | 2 -- src/transforms/concat.js | 25 +------------------------ 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 7437965245f..0a87b81131e 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -23,7 +23,6 @@ class JSConcatPackager extends Packager { this.exposedModules = new Set(); this.buffer = ''; this.exports = new Map(); - this.wildcards = new Map(); this.moduleMap = new Map(); this.needsPrelude = false; @@ -73,7 +72,6 @@ class JSConcatPackager extends Packager { let js = asset.generated.js; this.moduleMap.set(asset.id, asset); - this.wildcards.set(asset.id, asset.cacheData.wildcards); for (let key in asset.cacheData.exports) { let local = '$' + asset.id + '$export$' + asset.cacheData.exports[key]; diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 026d13fee3b..f86e97f25e6 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -13,7 +13,7 @@ const DEFAULT_INTEROP_TEMPLATE = template('$parcel$interopDefault(MODULE)'); // TODO: source-map module.exports = packager => { - let {buffer: code, exports, moduleMap, wildcards} = packager; + let {buffer: code, exports, moduleMap} = packager; let ast = babylon.parse(code); let rootPath; @@ -53,29 +53,6 @@ module.exports = packager => { return t.identifier(exports.get(computedSymbol)); } - // if there is a wildcard for the module - // default exports are excluded from wildcard exports - if (wildcards.has(id) && name !== 'default') { - /* recursively lookup the symbol - * this is needed when there is deep export wildcards, like in the following: - * - a.js - * > export * from './b' - * - b.js - * > export * from './c' - * - c.js in es6 - * > export * from 'lodash' - * - c.js in cjs - * > module.exports = require('lodash') - */ - let node = null; - - wildcards - .get(id) - .find(name => (node = find(resolveModule(id, name), symbol))); - - return node; - } - return null; } } From c6bac3ca50bb1771b3caa209c741aff5cf7c4fa5 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Fri, 27 Apr 2018 23:24:51 +0200 Subject: [PATCH 071/180] Revert "Revert "Revert "Remove wildcard lookup from packager transform""" This reverts commit 9b081f3a1cf24e3de3f03562733660338927d2c8. lol im sorry --- src/packagers/JSConcatPackager.js | 2 ++ src/transforms/concat.js | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 0a87b81131e..7437965245f 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -23,6 +23,7 @@ class JSConcatPackager extends Packager { this.exposedModules = new Set(); this.buffer = ''; this.exports = new Map(); + this.wildcards = new Map(); this.moduleMap = new Map(); this.needsPrelude = false; @@ -72,6 +73,7 @@ class JSConcatPackager extends Packager { let js = asset.generated.js; this.moduleMap.set(asset.id, asset); + this.wildcards.set(asset.id, asset.cacheData.wildcards); for (let key in asset.cacheData.exports) { let local = '$' + asset.id + '$export$' + asset.cacheData.exports[key]; diff --git a/src/transforms/concat.js b/src/transforms/concat.js index f86e97f25e6..026d13fee3b 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -13,7 +13,7 @@ const DEFAULT_INTEROP_TEMPLATE = template('$parcel$interopDefault(MODULE)'); // TODO: source-map module.exports = packager => { - let {buffer: code, exports, moduleMap} = packager; + let {buffer: code, exports, moduleMap, wildcards} = packager; let ast = babylon.parse(code); let rootPath; @@ -53,6 +53,29 @@ module.exports = packager => { return t.identifier(exports.get(computedSymbol)); } + // if there is a wildcard for the module + // default exports are excluded from wildcard exports + if (wildcards.has(id) && name !== 'default') { + /* recursively lookup the symbol + * this is needed when there is deep export wildcards, like in the following: + * - a.js + * > export * from './b' + * - b.js + * > export * from './c' + * - c.js in es6 + * > export * from 'lodash' + * - c.js in cjs + * > module.exports = require('lodash') + */ + let node = null; + + wildcards + .get(id) + .find(name => (node = find(resolveModule(id, name), symbol))); + + return node; + } + return null; } } From 4402c0db3b1554e85d53fc9796ce3e4d18056c72 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Fri, 27 Apr 2018 23:46:30 +0200 Subject: [PATCH 072/180] Fix wildcard lookup module resolving --- src/transforms/concat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 026d13fee3b..c9ac8107e2d 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -71,7 +71,7 @@ module.exports = packager => { wildcards .get(id) - .find(name => (node = find(resolveModule(id, name), symbol))); + .find(name => (node = find(resolveModule(id, name).id, symbol))); return node; } From 0ec5fd88dfcf049d8b8532f2924a1523994f0134 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Fri, 27 Apr 2018 23:47:23 +0200 Subject: [PATCH 073/180] Make re-export-default test ES6 compliant --- test/integration/scope-hoisting/es6/re-export-default/b.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/scope-hoisting/es6/re-export-default/b.js b/test/integration/scope-hoisting/es6/re-export-default/b.js index 6c2f9933ca8..7b678240251 100644 --- a/test/integration/scope-hoisting/es6/re-export-default/b.js +++ b/test/integration/scope-hoisting/es6/re-export-default/b.js @@ -1,2 +1,2 @@ -export foo from './c'; +export {default as foo} from './c'; export var baz = 1; From a3b09a556098c6722b61de7dc1a32fce6b14db73 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 28 Apr 2018 17:34:17 +0200 Subject: [PATCH 074/180] Only enable scope hoisting when hmr is disabled --- src/Bundler.js | 12 +++++++----- src/Pipeline.js | 5 ++++- src/assets/CSSAsset.js | 4 ++-- src/assets/JSAsset.js | 6 ++++-- src/assets/RawAsset.js | 9 +++------ src/packagers/index.js | 7 ++++--- src/transforms/babel.js | 5 ++--- 7 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/Bundler.js b/src/Bundler.js index c7e92ad1bb6..64c7ad027d6 100644 --- a/src/Bundler.js +++ b/src/Bundler.js @@ -32,7 +32,7 @@ class Bundler extends EventEmitter { this.resolver = new Resolver(this.options); this.parser = new Parser(this.options); - this.packagers = new PackagerRegistry(); + this.packagers = new PackagerRegistry(this.options); this.cache = this.options.cache ? new FSCache(this.options) : null; this.delegate = options.delegate || {}; this.bundleLoaders = {}; @@ -71,6 +71,10 @@ class Bundler extends EventEmitter { const watch = typeof options.watch === 'boolean' ? options.watch : !isProduction; const target = options.target || 'browser'; + const hmr = + target === 'node' + ? false + : typeof options.hmr === 'boolean' ? options.hmr : watch; return { production: isProduction, outDir: Path.resolve(options.outDir || 'dist'), @@ -84,10 +88,7 @@ class Bundler extends EventEmitter { minify: typeof options.minify === 'boolean' ? options.minify : isProduction, target: target, - hmr: - target === 'node' - ? false - : typeof options.hmr === 'boolean' ? options.hmr : watch, + hmr: hmr, https: options.https || false, logLevel: isNaN(options.logLevel) ? 3 : options.logLevel, mainFile: this.mainFile, @@ -103,6 +104,7 @@ class Bundler extends EventEmitter { typeof options.autoinstall === 'boolean' ? options.autoinstall : !isProduction, + scopeHoist: options.scopeHoist !== undefined ? options.scopeHoist : !hmr, contentHash: typeof options.contentHash === 'boolean' ? options.contentHash diff --git a/src/Pipeline.js b/src/Pipeline.js index 80ae5a0288e..17aa4dcda4a 100644 --- a/src/Pipeline.js +++ b/src/Pipeline.js @@ -41,7 +41,10 @@ class Pipeline { for (let rendition of this.iterateRenditions(asset)) { let {type, value} = rendition; - if (typeof value !== 'string' || rendition.final) { + if ( + typeof value !== 'string' || + (!asset.options.scopeHoist && rendition.final) + ) { generated.push(rendition); continue; } diff --git a/src/assets/CSSAsset.js b/src/assets/CSSAsset.js index a877d56fbed..ff1f282781c 100644 --- a/src/assets/CSSAsset.js +++ b/src/assets/CSSAsset.js @@ -124,8 +124,8 @@ class CSSAsset extends Asset { }, { type: 'js', - value: js - // final: true + value: js, + final: true } ]; } diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 364e3b9bfa3..571385b7dc0 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -114,9 +114,11 @@ class JSAsset extends Asset { } await this.parseIfNeeded(); - this.traverse(hoist); - if (this.options.minify) { + if (this.options.scopeHoist) { + this.traverse(hoist); + } else if (this.options.minify) { + // We minify in the Packager if scope hoisting is enabled await uglify(this); } } diff --git a/src/assets/RawAsset.js b/src/assets/RawAsset.js index d44abf2e306..4a1be0cdda5 100644 --- a/src/assets/RawAsset.js +++ b/src/assets/RawAsset.js @@ -18,12 +18,9 @@ class RawAsset extends Asset { this.generateBundleName() ); - return [ - { - type: 'js', - value: `module.exports=${JSON.stringify(pathToAsset)};` - } - ]; + return { + js: `module.exports=${JSON.stringify(pathToAsset)};` + }; } async generateHash() { diff --git a/src/packagers/index.js b/src/packagers/index.js index 024bc550b31..7c6e0b18150 100644 --- a/src/packagers/index.js +++ b/src/packagers/index.js @@ -1,17 +1,18 @@ -const JSPackager = require('./JSConcatPackager'); +const JSConcatPackager = require('./JSConcatPackager'); +const JSPackager = require('./JSPackager'); const CSSPackager = require('./CSSPackager'); const HTMLPackager = require('./HTMLPackager'); const SourceMapPackager = require('./SourceMapPackager'); const RawPackager = require('./RawPackager'); class PackagerRegistry { - constructor() { + constructor(options) { this.packagers = new Map(); - this.add('js', JSPackager); this.add('css', CSSPackager); this.add('html', HTMLPackager); this.add('map', SourceMapPackager); + this.add('js', options.scopeHoist ? JSConcatPackager : JSPackager); } add(type, packager) { diff --git a/src/transforms/babel.js b/src/transforms/babel.js index 0857dcfbcf8..c051e5c1f2b 100644 --- a/src/transforms/babel.js +++ b/src/transforms/babel.js @@ -78,15 +78,14 @@ babelTransform.getConfig = getConfig; async function getBabelConfig(asset) { // If asset is marked as an ES6 modules, this is a second pass after dependencies are extracted. // Just compile modules to CommonJS. - if (asset.isES6Module) { + if (asset.isES6Module && !asset.options.scopeHoist) { return { internal: true, plugins: [ - /** [ require('babel-plugin-transform-es2015-modules-commonjs'), {allowTopLevelThis: true} - ]**/ + ] ] }; } From 8333e8945acdee1ca54574a635a5bc4ce36445f4 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 28 Apr 2018 19:18:59 +0200 Subject: [PATCH 075/180] Prepare code to run all tests --- test/scope-hoisting.js | 5 ++++- test/utils.js | 12 ++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index 93c5f7506ea..aae925a9dbd 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -1,5 +1,8 @@ const assert = require('assert'); -const {bundle, run} = require('./utils'); +const {bundle: _bundle, run} = require('./utils'); + +const bundle = (name, opts = {}) => + _bundle(name, Object.assign({scopeHoist: true}, opts)); describe.only('scope hoisting', function() { describe('es6', function() { diff --git a/test/utils.js b/test/utils.js index ef3ec7b56ab..6967a4ae8af 100644 --- a/test/utils.js +++ b/test/utils.js @@ -23,6 +23,14 @@ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } +function normalizeOptions(opts = {}) { + if (opts.scopeHoist === undefined) { + opts.scopeHoist = false; + } + + return opts; +} + function bundler(file, opts) { return new Bundler( file, @@ -35,13 +43,13 @@ function bundler(file, opts) { hmr: false, logLevel: 0 }, - opts + normalizeOptions(opts) ) ); } function bundle(file, opts) { - return bundler(file, opts).bundle(); + return bundler(file, normalizeOptions(opts)).bundle(); } function prepareBrowserContext(bundle, globals) { From 6e996e5c9afde1a8a07c2623818321788a8d3fce Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 28 Apr 2018 19:47:44 +0200 Subject: [PATCH 076/180] Add failling test --- test/integration/scope-hoisting/es6/re-export-var/a.js | 3 +++ test/integration/scope-hoisting/es6/re-export-var/b.js | 3 +++ test/integration/scope-hoisting/es6/re-export-var/c.js | 7 +++++++ test/scope-hoisting.js | 9 +++++++++ 4 files changed, 22 insertions(+) create mode 100644 test/integration/scope-hoisting/es6/re-export-var/a.js create mode 100644 test/integration/scope-hoisting/es6/re-export-var/b.js create mode 100644 test/integration/scope-hoisting/es6/re-export-var/c.js diff --git a/test/integration/scope-hoisting/es6/re-export-var/a.js b/test/integration/scope-hoisting/es6/re-export-var/a.js new file mode 100644 index 00000000000..07897a84949 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-var/a.js @@ -0,0 +1,3 @@ +import {foo, bar} from './b' + +output = foo() + bar() diff --git a/test/integration/scope-hoisting/es6/re-export-var/b.js b/test/integration/scope-hoisting/es6/re-export-var/b.js new file mode 100644 index 00000000000..3ebf0b95a1f --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-var/b.js @@ -0,0 +1,3 @@ +import {foo, bar} from './c' + +export {foo, bar} diff --git a/test/integration/scope-hoisting/es6/re-export-var/c.js b/test/integration/scope-hoisting/es6/re-export-var/c.js new file mode 100644 index 00000000000..b2c661e0ce3 --- /dev/null +++ b/test/integration/scope-hoisting/es6/re-export-var/c.js @@ -0,0 +1,7 @@ +const foo = () => 'foo' + +function bar() { + return 'bar' +} + +export {foo, bar} diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index aae925a9dbd..abdc4e12bb2 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -198,6 +198,15 @@ describe.only('scope hoisting', function() { let output = run(b); assert.deepEqual(output, 'foobar'); }); + + it('supports exporting an import', async function() { + let b = await bundle( + __dirname + '/integration/scope-hoisting/es6/re-export-var/a.js' + ); + + let output = run(b); + assert.deepEqual(output, 'foobar'); + }); }); describe('commonjs', function() { From 1b94fe28eff81f69d61cb9f137556b9878cc311c Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 28 Apr 2018 19:48:29 +0200 Subject: [PATCH 077/180] Fix bugs and test, improve final bundle size --- src/transforms/concat.js | 122 +++++++++++++++++++++++++++------------ src/visitors/hoist.js | 89 ++++++++++++---------------- 2 files changed, 123 insertions(+), 88 deletions(-) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index c9ac8107e2d..f89cae8dfcc 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -1,3 +1,4 @@ +const {relative} = require('path'); const babylon = require('babylon'); const template = require('babel-template'); const t = require('babel-types'); @@ -22,7 +23,7 @@ module.exports = packager => { return module.depAssets.get(module.dependencies.get(name)); }; - function replaceExportNode(id, name, path, commonJsAsMemberExpr = true) { + function replaceExportNode(id, name, path) { if (!rootPath) { rootPath = getOuterStatement(path); } @@ -34,9 +35,7 @@ module.exports = packager => { node = find(id, id => `$${id}$exports`) || t.identifier(`$${id}$exports`); // if there is a CommonJS export return $id$exports.name - if (commonJsAsMemberExpr) { - return t.memberExpression(node, t.identifier(name)); - } + return t.memberExpression(node, t.identifier(name)); } return node; @@ -102,7 +101,7 @@ module.exports = packager => { ); } - let mod = resolveModule(id.value, source.value).id; + let mod = resolveModule(id.value, source.value); if (typeof mod === 'undefined') { throw new Error( @@ -110,7 +109,7 @@ module.exports = packager => { ); } - path.replaceWith(t.identifier(`$${mod}$exports`)); + path.replaceWith(t.identifier(`$${mod.id}$exports`)); } else if (callee.name === '$parcel$import') { let [id, source, name] = args; @@ -133,16 +132,28 @@ module.exports = packager => { ); } - if (name.value === 'default' && mod.cacheData.isCommonJS) { + let node = replaceExportNode(mod.id, name.value, path); + + // If the module has any CommonJS reference, it still can have export/import statements. + if (mod.cacheData.isCommonJS) { path.replaceWith( - DEFAULT_INTEROP_TEMPLATE({ - MODULE: replaceExportNode(mod.id, name.value, path, false) - }) + name.value === 'default' + ? DEFAULT_INTEROP_TEMPLATE({ + MODULE: t.isMemberExpression(node) ? node.object : node + }) + : node ); + } else if (t.isIdentifier(node)) { + path.replaceWith(node); } else { - path.replaceWith(replaceExportNode(mod.id, name.value, path)); + let relativePath = relative(packager.options.rootDir, mod.name); + + throw new Error(`${relativePath} does not export '${name.value}'`); } - } else if (callee.name === '$parcel$interopDefault') { + } else if ( + callee.name === '$parcel$interopDefault' || + callee.name === '$parcel$exportWildcard' + ) { // This hints Uglify and Babel that this CallExpression does not have any side-effects. path.addComment('leading', '#__PURE__'); } else if (callee.name === '$parcel$require$resolve') { @@ -230,47 +241,84 @@ module.exports = packager => { path.replaceWith(t.identifier(exports.get(path.node.name))); } }, - exit(path) { - if (!rootPath || !path.isProgram()) { - return; - } + Program: { + // A small optimization to remove unused CommonJS exports as sometimes Uglify doesn't remove them. + exit(path) { + if (!(path = rootPath)) { + return; + } - path = rootPath; - path.scope.crawl(); - Object.keys(path.scope.bindings) - .filter(name => EXPORTS_RE.test(name)) - .forEach(name => { - let binding = path.scope.getBinding(name); - // Is there any references which aren't also simple assignments? - let bailout = binding.referencePaths.some( - ({parentPath}) => - !parentPath.isMemberExpression() || - !parentPath.parentPath.isAssignmentExpression() - ); + // Recrawl to get all bindings. + path.scope.crawl(); - // Is so skip. - if (bailout) { + Object.keys(path.scope.bindings).forEach(name => { + let binding = getUnusedBinding(path, name); + + // If it is not safe to remove the binding don't touch it. + if (!binding) { return; } - // Remove each assignement from the code + // Remove the binding and all references to it. + binding.path.remove(); binding.referencePaths.forEach(({parentPath}) => { if (parentPath.isMemberExpression()) { - console.log('Removing binding', name); - - parentPath.parentPath.remove(); - } else { - throw new Error('Unknown exports path type'); + if (!parentPath.parentPath.removed) { + parentPath.parentPath.remove(); + } } }); }); + } } }); return generate(ast, code).code; }; -// Finds a parent statement in the bundle IIFE body +// Check if a binding is safe to remove and returns it if it is. +function getUnusedBinding(path, name) { + if (!EXPORTS_RE.test(name)) { + return null; + } + + let binding = path.scope.getBinding(name); + // Is there any references which aren't simple assignments? + let bailout = binding.referencePaths.some( + path => !isExportAssignment(path) && !isUnusedWildcard(path) + ); + + if (bailout) { + return null; + } else { + return binding; + } + + function isExportAssignment({parentPath}) { + return ( + // match "path.any = any;" + parentPath.isMemberExpression() && + parentPath.parentPath.isAssignmentExpression() && + parentPath.parentPath.node.left === parentPath.node + ); + } + + function isUnusedWildcard(path) { + let {parent, parentPath} = path; + + return ( + // match "var $id$exports = $parcel$exportWildcard(any, path);" + t.isCallExpression(parent) && + t.isIdentifier(parent.callee, {name: '$parcel$exportWildcard'}) && + parent.arguments[1] === path.node && + parentPath.parentPath.isVariableDeclarator() && + // check if the $id$exports variable is used + getUnusedBinding(path, parentPath.parent.id.name) !== null + ); + } +} + +// Finds a parent statement in the bundle IIFE body. function getOuterStatement(path) { if (validate(path)) { return path; diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 486c3fa1a0d..ffeeb9051f6 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -256,25 +256,29 @@ module.exports = { // This will be replaced by the final variable name of the resolved asset in the packager. for (let specifier of path.node.specifiers) { if (t.isImportDefaultSpecifier(specifier)) { - rewriteBinding( - path, - specifier.local, - IMPORT_TEMPLATE({ - ID: t.numericLiteral(asset.id), - NAME: t.stringLiteral('default'), - SOURCE: path.node.source - }) - ); + let {expression} = IMPORT_TEMPLATE({ + ID: t.numericLiteral(asset.id), + NAME: t.stringLiteral('default'), + SOURCE: path.node.source + }); + + path.scope + .getBinding(specifier.local.name) + .referencePaths.forEach(reference => + reference.replaceWith(expression) + ); } else if (t.isImportSpecifier(specifier)) { - rewriteBinding( - path, - specifier.local, - IMPORT_TEMPLATE({ - ID: t.numericLiteral(asset.id), - SOURCE: path.node.source, - NAME: t.stringLiteral(specifier.imported.name) - }) - ); + let {expression} = IMPORT_TEMPLATE({ + ID: t.numericLiteral(asset.id), + SOURCE: path.node.source, + NAME: t.stringLiteral(specifier.imported.name) + }); + + path.scope + .getBinding(specifier.local.name) + .referencePaths.forEach(reference => + reference.replaceWith(expression) + ); } else if (t.isImportNamespaceSpecifier(specifier)) { path.scope.push({ id: specifier.local, @@ -439,27 +443,27 @@ module.exports = { }; function addExport(asset, path, local, exported) { + let identifier = getIdentifier(asset, 'export', exported.name); + let assignNode = EXPORT_ASSIGN_TEMPLATE({ + EXPORTS: getExportsIdentifier(asset, path.scope), + NAME: t.identifier(exported.name), + LOCAL: identifier + }); + + path.scope + .getBinding(local.name) + .constantViolations.concat(path) + .forEach(path => path.insertAfter(assignNode)); + // Check if this identifier has already been exported. // If so, create an export alias for it, otherwise, rename the local variable to an export. - if (asset.cacheData.exports[exported.name]) { - asset.cacheData.exports[getName(asset, 'export', exported.name)] = + if (asset.cacheData.exports[local.name]) { + asset.cacheData.exports[identifier.name] = asset.cacheData.exports[local.name]; } else { - asset.cacheData.exports[getName(asset, 'export', exported.name)] = - exported.name; - - let assignNode = EXPORT_ASSIGN_TEMPLATE({ - EXPORTS: getExportsIdentifier(asset, path.scope), - NAME: t.identifier(exported.name), - LOCAL: getIdentifier(asset, 'export', local.name) - }); - + asset.cacheData.exports[identifier.name] = exported.name; // Get all the node paths mutating the export and insert a CommonJS assignement. - path.scope - .getBinding(local.name) - .constantViolations.concat(path) - .forEach(path => path.insertAfter(assignNode)); - path.scope.rename(local.name, getName(asset, 'export', local.name)); + path.scope.rename(local.name, identifier.name); } } @@ -504,20 +508,3 @@ function getExportsIdentifier(asset, scope) { return getIdentifier(asset, 'exports'); } } - -// Replaces all references to a Binding by a replacement node. -function rewriteBinding(path, init, replacement) { - let newName = path.scope.generateUidIdentifier(init.name); - let node = t.variableDeclaration('var', [ - t.variableDeclarator( - newName, - t.isExpressionStatement(replacement) - ? replacement.expression - : replacement - ) - ]); - let [nodePath] = path.insertBefore(node); - - path.scope.registerBinding('var', nodePath); - path.scope.rename(init.name, newName.name); -} From f51883fb0b73089203829f758ef2c42140371e41 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 28 Apr 2018 19:54:09 +0200 Subject: [PATCH 078/180] Remove commented code --- src/packagers/JSConcatPackager.js | 72 ------------------------------- 1 file changed, 72 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 7437965245f..fbb49f5011e 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -82,76 +82,6 @@ class JSConcatPackager extends Packager { } } - /* for (let [dep, mod] of asset.depAssets) { - let depName = '$' + asset.id + '$require$' + t.toIdentifier(dep.name); - let moduleName = this.getExportIdentifier(mod); - - // If this module is not in the current bundle, generate a `require` call for it. - if (!this.bundle.assets.has(mod)) { - moduleName = `require(${mod.id})`; - } - - // js = js.split(depName).join(moduleName); - - // If this was an ES6 export all (e.g. export * from 'foo'), resolve to the original exports. - if (dep.isExportAll) { - for (let exp in mod.cacheData.exports) { - let id = mod.cacheData.exports[exp]; - if (id !== 'default') { - let key = '$' + asset.id + '$export$' + id; - asset.cacheData.exports[key] = id; - this.exports.set( - key, - this.exports.get('$' + mod.id + '$export$' + id) || exp - ); - } - } - } - - if (dep.isES6Import && dep.ids) { - for (let id in dep.ids) { - id = id.slice(1) - let name = '$' + mod.id + '$export$' + id; - if (mod.cacheData.exports[name]) { - this.exports.set(depName, name); - } else if (mod.cacheData.isCommonJS) { - if (id === 'default') { - name = '(/*#__PURE__* /$parcel$interopDefault($' + mod.id + '$exports))'; - } - - this.exports.set(depName, name); - } else if(false) { - throw new Error( - `${path.relative( - this.options.rootDir, - mod.name - )} does not export '${id}'` - ); - } - } - } - - let depResolve = - '$' + asset.id + '$require_resolve$' + t.toIdentifier(dep.name); - let resolved = '' + mod.id; - - if (dep.dynamic && this.bundle.childBundles.has(mod.parentBundle)) { - let bundles = [this.getBundleSpecifier(mod.parentBundle)]; - for (let child of mod.parentBundle.siblingBundles) { - if (!child.isEmpty) { - bundles.push(this.getBundleSpecifier(child)); - await this.addBundleLoader(child.type); - } - } - - bundles.push(mod.id); - resolved = JSON.stringify(bundles); - await this.addBundleLoader(mod.type); - } - - js = js.split(depResolve).join(resolved); - }*/ - for (let [dep, mod] of asset.depAssets) { if (dep.dynamic && this.bundle.childBundles.has(mod.parentBundle)) { for (let child of mod.parentBundle.siblingBundles) { @@ -164,8 +94,6 @@ class JSConcatPackager extends Packager { } } - // Replace all re-exported variables - js = js.trim() + '\n'; this.write( From c2a27a30e4775663f8b9cbe4d809185381ed7495 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 28 Apr 2018 20:26:11 +0200 Subject: [PATCH 079/180] Fix live binding of ES6 imports --- src/transforms/concat.js | 48 ++++++++++++++++++++++++++++------------ src/visitors/hoist.js | 36 ++++++++++++++++-------------- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index f89cae8dfcc..d2759915a1c 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -111,16 +111,17 @@ module.exports = packager => { path.replaceWith(t.identifier(`$${mod.id}$exports`)); } else if (callee.name === '$parcel$import') { - let [id, source, name] = args; + let [id, source, name, replace] = args; if ( - args.length !== 3 || + args.length !== 4 || !t.isNumericLiteral(id) || !t.isStringLiteral(source) || - !t.isStringLiteral(name) + !t.isStringLiteral(name) || + !t.isBooleanLiteral(replace) ) { throw new Error( - 'invariant: invalid signature, expected : $parcel$import(number, string, string)' + 'invariant: invalid signature, expected : $parcel$import(number, string, string, boolean)' ); } @@ -136,20 +137,39 @@ module.exports = packager => { // If the module has any CommonJS reference, it still can have export/import statements. if (mod.cacheData.isCommonJS) { - path.replaceWith( - name.value === 'default' - ? DEFAULT_INTEROP_TEMPLATE({ - MODULE: t.isMemberExpression(node) ? node.object : node - }) - : node - ); - } else if (t.isIdentifier(node)) { - path.replaceWith(node); - } else { + if (name.value === 'default') { + node = DEFAULT_INTEROP_TEMPLATE({ + MODULE: t.isMemberExpression(node) ? node.object : node + }); + } + } else if (!t.isIdentifier(node)) { let relativePath = relative(packager.options.rootDir, mod.name); throw new Error(`${relativePath} does not export '${name.value}'`); } + + if (replace.value) { + if (!path.parentPath.isVariableDeclarator()) { + throw new Error( + 'invariant: "replace" used outside of a VariableDeclarator' + ); + } + + let {name} = path.parent.id; + let binding = path.scope.getBinding(name); + + binding.referencePaths.forEach(reference => + reference.replaceWith(node) + ); + binding.path.remove(); + path.scope.removeBinding(name); + + if (t.isIdentifier(node)) { + exports.set(name, node.name); + } + } else { + path.replaceWith(node); + } } else if ( callee.name === '$parcel$interopDefault' || callee.name === '$parcel$exportWildcard' diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index ffeeb9051f6..28218fa3486 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -11,7 +11,9 @@ const WRAPPER_TEMPLATE = template(` }).call({}); `); -const IMPORT_TEMPLATE = template('$parcel$import(ID, SOURCE, NAME)'); +const IMPORT_TEMPLATE = template( + '$parcel$import(ID, SOURCE, NAME, REPLACE_VAR)' +); const EXPORT_ASSIGN_TEMPLATE = template('EXPORTS.NAME = LOCAL'); const EXPORT_ALL_TEMPLATE = template( '$parcel$exportWildcard(OLD_NAME, $parcel$require(ID, SOURCE))' @@ -256,29 +258,27 @@ module.exports = { // This will be replaced by the final variable name of the resolved asset in the packager. for (let specifier of path.node.specifiers) { if (t.isImportDefaultSpecifier(specifier)) { - let {expression} = IMPORT_TEMPLATE({ + let {expression: init} = IMPORT_TEMPLATE({ ID: t.numericLiteral(asset.id), NAME: t.stringLiteral('default'), - SOURCE: path.node.source + SOURCE: path.node.source, + REPLACE_VAR: t.booleanLiteral(true) }); + let id = path.scope.generateUidIdentifier(specifier.local.name); - path.scope - .getBinding(specifier.local.name) - .referencePaths.forEach(reference => - reference.replaceWith(expression) - ); + path.scope.push({id, init}); + path.scope.rename(specifier.local.name, id.name); } else if (t.isImportSpecifier(specifier)) { - let {expression} = IMPORT_TEMPLATE({ + let {expression: init} = IMPORT_TEMPLATE({ ID: t.numericLiteral(asset.id), SOURCE: path.node.source, - NAME: t.stringLiteral(specifier.imported.name) + NAME: t.stringLiteral(specifier.imported.name), + REPLACE_VAR: t.booleanLiteral(true) }); + let id = path.scope.generateUidIdentifier(specifier.local.name); - path.scope - .getBinding(specifier.local.name) - .referencePaths.forEach(reference => - reference.replaceWith(expression) - ); + path.scope.push({id, init}); + path.scope.rename(specifier.local.name, id.name); } else if (t.isImportNamespaceSpecifier(specifier)) { path.scope.push({ id: specifier.local, @@ -340,7 +340,8 @@ module.exports = { local = IMPORT_TEMPLATE({ ID: t.numericLiteral(asset.id), SOURCE: source, - NAME: t.stringLiteral('default') + NAME: t.stringLiteral('default'), + REPLACE_VAR: t.booleanLiteral(false) }); exported = specifier.exported; } else if (t.isExportNamespaceSpecifier(specifier)) { @@ -353,7 +354,8 @@ module.exports = { local = IMPORT_TEMPLATE({ ID: t.numericLiteral(asset.id), SOURCE: source, - NAME: t.stringLiteral(specifier.local.name) + NAME: t.stringLiteral(specifier.local.name), + REPLACE_VAR: t.booleanLiteral(false) }); exported = specifier.exported; From 265d105cb46832fb9f5bdecbeddc9226e719f940 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sun, 29 Apr 2018 00:43:05 +0200 Subject: [PATCH 080/180] Fix and improve default export live bindings --- src/builtins/helpers.js | 4 +-- src/transforms/concat.js | 70 ++++++++++++++++++++++++++++++++-------- src/visitors/hoist.js | 11 ------- 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/builtins/helpers.js b/src/builtins/helpers.js index a4a695c7b06..0b26df4d5a9 100644 --- a/src/builtins/helpers.js +++ b/src/builtins/helpers.js @@ -2,8 +2,8 @@ function $parcel$interopDefault(a) { return a && a.__esModule - ? a.default - : a; + ? {d: a.default} + : {d: a}; } function $parcel$exportWildcard(dest, source) { diff --git a/src/transforms/concat.js b/src/transforms/concat.js index d2759915a1c..50f4cbc44c2 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -18,6 +18,9 @@ module.exports = packager => { let ast = babylon.parse(code); let rootPath; + // Share $parcel$interopDefault variables between modules + let interops = new Map(); + let resolveModule = (id, name) => { let module = moduleMap.get(id); return module.depAssets.get(module.dependencies.get(name)); @@ -134,13 +137,41 @@ module.exports = packager => { } let node = replaceExportNode(mod.id, name.value, path); + let interop = false; // If the module has any CommonJS reference, it still can have export/import statements. if (mod.cacheData.isCommonJS) { if (name.value === 'default') { - node = DEFAULT_INTEROP_TEMPLATE({ - MODULE: t.isMemberExpression(node) ? node.object : node - }); + node = t.isMemberExpression(node) ? node.object : node; + interop = true; + + let nodeName = + replace.value && t.isIdentifier(node) ? node.name : null; + let {id} = path.parent; + + if (nodeName !== null && interops.has(nodeName)) { + let name = t.identifier(interops.get(nodeName)); + + // Rename references to the variables to the cached interop name. + path.scope + .getBinding(id.name) + .referencePaths.forEach(reference => + reference.replaceWith( + t.memberExpression(name, t.identifier('d')) + ) + ); + path.scope.removeBinding(id.name); + path.parentPath.remove(); + + return; + } else { + node = DEFAULT_INTEROP_TEMPLATE({MODULE: node}); + + // Save the variable name of the interop call for further use. + if (nodeName !== null) { + interops.set(nodeName, id.name); + } + } } } else if (!t.isIdentifier(node)) { let relativePath = relative(packager.options.rootDir, mod.name); @@ -155,27 +186,38 @@ module.exports = packager => { ); } - let {name} = path.parent.id; - let binding = path.scope.getBinding(name); + let {id} = path.parent; + let binding = path.scope.getBinding(id.name); - binding.referencePaths.forEach(reference => - reference.replaceWith(node) - ); - binding.path.remove(); - path.scope.removeBinding(name); + if (interop) { + path.replaceWith(node); - if (t.isIdentifier(node)) { - exports.set(name, node.name); + binding.referencePaths.forEach(reference => + reference.replaceWith(t.memberExpression(id, t.identifier('d'))) + ); + } else { + path.scope.removeBinding(id.name); + + binding.path.remove(); + binding.referencePaths.forEach(reference => + reference.replaceWith(node) + ); + + if (t.isIdentifier(node)) { + exports.set(id.name, node.name); + } } } else { path.replaceWith(node); } } else if ( - callee.name === '$parcel$interopDefault' || - callee.name === '$parcel$exportWildcard' + (callee.name === '$parcel$interopDefault' || + callee.name === '$parcel$exportWildcard') && + !path.getData('markAsPure') ) { // This hints Uglify and Babel that this CallExpression does not have any side-effects. path.addComment('leading', '#__PURE__'); + path.setData('markAsPure', true); } else if (callee.name === '$parcel$require$resolve') { let [id, source] = args; diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 28218fa3486..3848acf23ee 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -205,17 +205,6 @@ module.exports = { CallExpression(path, asset) { let {callee, arguments: args} = path.node; - - if (t.isIdentifier(callee)) { - if (callee.name === '$parcel$exportWildcard') { - // This hints Uglify and Babel that this CallExpression does not have any side-effects. - // This will make unsused CommonJS wildcards removed from the minified builds. - path.addComment('leading', '#__PURE__'); - - return; - } - } - let ignore = args.length !== 1 || !t.isStringLiteral(args[0]) || From 7142473855205f2f44d3090b1552d3c75dd67d5d Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sun, 29 Apr 2018 02:04:47 +0200 Subject: [PATCH 081/180] Side effects --- src/visitors/hoist.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 3848acf23ee..1708061821e 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -210,6 +210,10 @@ module.exports = { !t.isStringLiteral(args[0]) || path.scope.hasBinding('require'); + if(asset.package && asset.package.sideEffects === false && !path.scope.parent && !path.getData('markAsPure')) { + path.setData('markAsPure', true) + path.addComment('leading', '#__PURE__') + } if (ignore) { return; } From 971a2691ea441b75c2ddc911d5f00a035a840982 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sun, 29 Apr 2018 17:47:46 +0200 Subject: [PATCH 082/180] Revert "Make re-export-default test ES6 compliant" This reverts commit 7058b88a0ae3d1b4c2ba279a0a2b3bef4b61a3b6. --- test/integration/scope-hoisting/es6/re-export-default/b.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/scope-hoisting/es6/re-export-default/b.js b/test/integration/scope-hoisting/es6/re-export-default/b.js index 7b678240251..6c2f9933ca8 100644 --- a/test/integration/scope-hoisting/es6/re-export-default/b.js +++ b/test/integration/scope-hoisting/es6/re-export-default/b.js @@ -1,2 +1,2 @@ -export {default as foo} from './c'; +export foo from './c'; export var baz = 1; From e8ba82009410c8048a0f9d8c38d8b8e4e91a8006 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sun, 29 Apr 2018 21:47:12 +0200 Subject: [PATCH 083/180] Fix process and Buffer --- src/visitors/globals.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/visitors/globals.js b/src/visitors/globals.js index e2107912d99..2910b38a460 100644 --- a/src/visitors/globals.js +++ b/src/visitors/globals.js @@ -2,19 +2,24 @@ const Path = require('path'); const types = require('babel-types'); const matchesPattern = require('./matches-pattern'); +const genRequire = (asset, name, module = name) => { + asset.addDependency(module) + + if(asset.options.scopeHoist) { + return `var ${name} = $parcel$require(${asset.id}, ${JSON.stringify(module)})` + } + else { + return `var ${name} = require(${JSON.stringify(module)})` + } +} + const VARS = { - process: asset => { - asset.addDependency('process'); - return 'var process = require("process");'; - }, + process: asset => genRequire(asset, 'process') + ';', global: () => 'var global = (1,eval)("this");', __dirname: asset => `var __dirname = ${JSON.stringify(Path.dirname(asset.name))};`, __filename: asset => `var __filename = ${JSON.stringify(asset.name)};`, - Buffer: asset => { - asset.addDependency('buffer'); - return 'var Buffer = require("buffer").Buffer;'; - } + Buffer: asset => `${genRequire(asset, 'Buffer', 'buffer')}.Buffer;` }; module.exports = { From f6db4bc61ec13e4a2b5d660c4bbd48fd70051556 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sun, 29 Apr 2018 21:47:34 +0200 Subject: [PATCH 084/180] Fix Vue.js --- src/assets/VueAsset.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/assets/VueAsset.js b/src/assets/VueAsset.js index 03ec991cc96..7100b2b3255 100644 --- a/src/assets/VueAsset.js +++ b/src/assets/VueAsset.js @@ -67,8 +67,26 @@ class VueAsset extends Asset { // Generate JS output. let js = this.ast.script ? generated[0].value : ''; - let supplemental = ` - var ${optsVar} = exports.default || module.exports; + let supplemental + + if(this.options.scopeHoist) { + let exportVar = `$${this.id}$export$default` + + if(js.indexOf(exportVar) === -1) { + supplemental = ` + var ${exportVar} = {}; + var ${optsVar} = ${exportVar}; + ` + } + else { + optsVar = exportVar + } + } + else { + supplemental = `var ${optsVar} = exports.default || module.exports;` + } + + supplemental += ` if (typeof ${optsVar} === 'function') { ${optsVar} = ${optsVar}.options; } From 9f0d0ed6ce8e41a3f4b6717618c36c4d8ad2abeb Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 30 Apr 2018 17:09:23 +0200 Subject: [PATCH 085/180] Improve Vue.js support --- src/assets/VueAsset.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/assets/VueAsset.js b/src/assets/VueAsset.js index 7100b2b3255..f6359525f7c 100644 --- a/src/assets/VueAsset.js +++ b/src/assets/VueAsset.js @@ -67,13 +67,13 @@ class VueAsset extends Asset { // Generate JS output. let js = this.ast.script ? generated[0].value : ''; - let supplemental + let supplemental = '' if(this.options.scopeHoist) { let exportVar = `$${this.id}$export$default` if(js.indexOf(exportVar) === -1) { - supplemental = ` + supplemental += ` var ${exportVar} = {}; var ${optsVar} = ${exportVar}; ` @@ -83,7 +83,7 @@ class VueAsset extends Asset { } } else { - supplemental = `var ${optsVar} = exports.default || module.exports;` + supplemental += `var ${optsVar} = exports.default || module.exports;` } supplemental += ` @@ -96,7 +96,7 @@ class VueAsset extends Asset { supplemental += this.compileCSSModules(generated, optsVar); supplemental += this.compileHMR(generated, optsVar); - if (this.options.minify && supplemental) { + if (this.options.minify && !this.options.scopeHoist && supplemental) { let {code, error} = minify(supplemental, {toplevel: true}); if (error) { throw error; From 627cfa613d1748d19a5c129ee5bd76c888205d8b Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 30 Apr 2018 17:18:14 +0200 Subject: [PATCH 086/180] Fix dynamic import --- src/packagers/JSConcatPackager.js | 10 +++++++- src/transforms/concat.js | 23 +++++++++++++++++-- .../scope-hoisting/es6/dynamic-import/a.js | 4 +++- .../scope-hoisting/es6/dynamic-import/b.js | 6 +++-- .../scope-hoisting/es6/dynamic-import/c.js | 3 +++ 5 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 test/integration/scope-hoisting/es6/dynamic-import/c.js diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index fbb49f5011e..0422f5d7dbb 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -162,11 +162,19 @@ class JSConcatPackager extends Packager { async end() { if (this.needsPrelude) { let exposed = []; + let prepareModule = []; for (let m of this.exposedModules) { + if(m.cacheData.isES6Module) { + prepareModule.push(`${this.getExportIdentifier(m)}.__esModule = true;`) + } + exposed.push(`${m.id}: ${this.getExportIdentifier(m)}`); } - this.write(`return {${exposed.join(', ')}};\n})`); + this.write(` + ${prepareModule.join('\n')} + return {${exposed.join(', ')}}; + })`); } else { this.write('})();'); } diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 50f4cbc44c2..f8daf6ad663 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -294,8 +294,27 @@ module.exports = packager => { `Cannot find export "${exportName}" in module "${id}"` ); } - } else if (EXPORTS_RE.test(name) && !path.scope.hasBinding(name)) { - path.replaceWith(t.objectExpression([])); + + return + } + + match = name.match(EXPORTS_RE) + + if (match && !path.scope.hasBinding(name)) { + let id = Number(match[1]); + + if(moduleMap.has(id)) { + path.replaceWith(t.objectExpression([])); + } + else { + path.replaceWith( + t.callExpression( + t.identifier('require'), + [t.numericLiteral(id)] + ) + ) + // throw new Error(`Module ${id} not found`) + } } }, ReferencedIdentifier(path) { diff --git a/test/integration/scope-hoisting/es6/dynamic-import/a.js b/test/integration/scope-hoisting/es6/dynamic-import/a.js index 4f774831494..a8038e64f53 100644 --- a/test/integration/scope-hoisting/es6/dynamic-import/a.js +++ b/test/integration/scope-hoisting/es6/dynamic-import/a.js @@ -1,5 +1,7 @@ +import {compute} from './c' + var b = import('./b'); export default b.then(function ({foo, bar}) { - return foo + bar; + return compute(foo, 0) + compute(bar, 0); }); diff --git a/test/integration/scope-hoisting/es6/dynamic-import/b.js b/test/integration/scope-hoisting/es6/dynamic-import/b.js index ad76adb4cfb..b5350fa6686 100644 --- a/test/integration/scope-hoisting/es6/dynamic-import/b.js +++ b/test/integration/scope-hoisting/es6/dynamic-import/b.js @@ -1,2 +1,4 @@ -export var foo = 2; -export var bar = 3; +import {compute} from './c' + +export var foo = compute(2, 1); +export var bar = compute(3, 1); diff --git a/test/integration/scope-hoisting/es6/dynamic-import/c.js b/test/integration/scope-hoisting/es6/dynamic-import/c.js new file mode 100644 index 00000000000..369d7211d62 --- /dev/null +++ b/test/integration/scope-hoisting/es6/dynamic-import/c.js @@ -0,0 +1,3 @@ +export function compute(x, q) { + return q ? x * 2 : x / 2 +} From f04151cae856b07334da778c5807753596e95256 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 30 Apr 2018 22:43:39 +0200 Subject: [PATCH 087/180] Implement fast renamer --- src/visitors/hoist.js | 6 +- src/visitors/renamer.js | 127 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/visitors/renamer.js diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 1708061821e..2dd7f2b24ae 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -1,6 +1,7 @@ const matchesPattern = require('./matches-pattern'); const t = require('babel-types'); const template = require('babel-template'); +const rename = require('./renamer') const WRAPPER_TEMPLATE = template(` var NAME = (function () { @@ -106,14 +107,17 @@ module.exports = { // Re-crawl scope so we are sure to have all bindings. scope.crawl(); + let bindings = {} // Rename each binding in the top-level scope to something unique. for (let name in scope.bindings) { if (!name.startsWith('$' + asset.id)) { let newName = '$' + asset.id + '$var$' + name; - scope.rename(name, newName); + bindings[name] = newName } } + rename(scope, bindings) + let exportsIdentifier = getExportsIdentifier(asset); // Add variable that represents module.exports if it is referenced and not declared. diff --git a/src/visitors/renamer.js b/src/visitors/renamer.js new file mode 100644 index 00000000000..8d4c693eed2 --- /dev/null +++ b/src/visitors/renamer.js @@ -0,0 +1,127 @@ +// A fork of babel-traverse Renamer class, optimized for renaming multiple bindings +// https://github.com/babel/babel/blob/v6.26.3/packages/babel-traverse/src/scope/lib/renamer.js + +const t = require('babel-types') + +const renameVisitor = { + ReferencedIdentifier({ node }, states) { + states.find(state => { + if (node.name === state.oldName) { + node.name = state.newName; + + return true + } + }) + }, + + Scope(path, states) { + states.find(state => { + if (!path.scope.bindingIdentifierEquals(state.oldName, state.binding.identifier)) { + path.skip(); + + return true + } + }) + }, + + 'AssignmentExpression|Declaration'(path, states) { + let ids = path.getOuterBindingIdentifiers(); + + states.find(state => { + for (let name in ids) { + if (name === state.oldName) { + ids[name].name = state.newName; + + return true + } + } + }) + } +}; + +class Renamer { + constructor(binding, oldName, newName) { + this.newName = newName; + this.oldName = oldName; + this.binding = binding; + } + + maybeConvertFromExportDeclaration(parentDeclar) { + let exportDeclar = parentDeclar.parentPath.isExportDeclaration() && parentDeclar.parentPath; + + if (!exportDeclar) { + return; + } + + // build specifiers that point back to this export declaration + let isDefault = exportDeclar.isExportDefaultDeclaration(); + + if (isDefault && (parentDeclar.isFunctionDeclaration() || + parentDeclar.isClassDeclaration()) && !parentDeclar.node.id) { + // Ensure that default class and function exports have a name so they have a identifier to + // reference from the export specifier list. + parentDeclar.node.id = parentDeclar.scope.generateUidIdentifier("default"); + } + + let bindingIdentifiers = parentDeclar.getOuterBindingIdentifiers(); + let specifiers = []; + + for (let name in bindingIdentifiers) { + let localName = name === this.oldName ? this.newName : name; + let exportedName = isDefault ? "default" : name; + + specifiers.push(t.exportSpecifier(t.identifier(localName), t.identifier(exportedName))); + } + + if (specifiers.length) { + let aliasDeclar = t.exportNamedDeclaration(null, specifiers); + + // hoist to the top if it's a function + if (parentDeclar.isFunctionDeclaration()) { + aliasDeclar._blockHoist = 3; + } + + exportDeclar.insertAfter(aliasDeclar); + exportDeclar.replaceWith(parentDeclar.node); + } + } + + prepare() { + let {path} = this.binding; + let parentDeclar = path.find((path) => path.isDeclaration() || path.isFunctionExpression()); + + if (parentDeclar) { + this.maybeConvertFromExportDeclaration(parentDeclar); + } + + return this + } + + rename() { + let {binding, oldName, newName} = this; + let {scope } = binding; + // scope.traverse(scope.block, renameVisitor, this); + scope.removeOwnBinding(oldName); + + scope.bindings[newName] = binding; + this.binding.identifier.name = newName; + + if (binding.type === "hoisted") { + // https://github.com/babel/babel/issues/2435 + // todo: hoist and convert function to a let + } + } +} + +module.exports = (scope, names) => { + let renamers = Object.keys(names).map(oldName => { + let binding = scope.getBinding(oldName); + let newName = names[oldName] + + return new Renamer(binding, oldName, newName).prepare() + }) + + scope.traverse(scope.block, renameVisitor, renamers) + + renamers.forEach(renamer => renamer.rename(scope)) +} From 4a89527401fec75595d98562438341fa21132521 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 1 May 2018 01:21:06 +0200 Subject: [PATCH 088/180] Optimizations, improve renaming performance --- src/transforms/concat.js | 29 +++++------ src/visitors/hoist.js | 76 +++++++++++++++++++++------- src/visitors/renamer.js | 104 ++++++++++++++++++++++++--------------- 3 files changed, 133 insertions(+), 76 deletions(-) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index f8daf6ad663..b523157fce1 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -273,13 +273,12 @@ module.exports = packager => { } } }, - Identifier(path) { + ReferencedIdentifier(path) { let {name} = path.node; if (typeof name !== 'string') { return; } - let match = name.match(EXPORT_RE); if (match && !path.scope.hasBinding(name)) { @@ -295,30 +294,26 @@ module.exports = packager => { ); } - return + return; } - - match = name.match(EXPORTS_RE) + + match = name.match(EXPORTS_RE); if (match && !path.scope.hasBinding(name)) { let id = Number(match[1]); - if(moduleMap.has(id)) { + if (moduleMap.has(id)) { path.replaceWith(t.objectExpression([])); - } - else { + } else { path.replaceWith( - t.callExpression( - t.identifier('require'), - [t.numericLiteral(id)] - ) - ) - // throw new Error(`Module ${id} not found`) + t.callExpression(t.identifier('require'), [t.numericLiteral(id)]) + ); } + + return; } - }, - ReferencedIdentifier(path) { - if (exports.has(path.node.name)) { + + if (exports.has(name)) { path.replaceWith(t.identifier(exports.get(path.node.name))); } }, diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 2dd7f2b24ae..64b6df95770 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -1,7 +1,7 @@ const matchesPattern = require('./matches-pattern'); const t = require('babel-types'); const template = require('babel-template'); -const rename = require('./renamer') +const rename = require('./renamer'); const WRAPPER_TEMPLATE = template(` var NAME = (function () { @@ -33,6 +33,7 @@ module.exports = { enter(path, asset) { asset.cacheData.exports = {}; asset.cacheData.wildcards = []; + asset.renamings = new Map(); let shouldWrap = false; path.traverse({ @@ -104,19 +105,23 @@ module.exports = { ]) ); } else { + let bindings = {}; + + asset.renamings.forEach( + (oldName, newName) => (bindings[oldName] = newName) + ); // Re-crawl scope so we are sure to have all bindings. scope.crawl(); - let bindings = {} // Rename each binding in the top-level scope to something unique. for (let name in scope.bindings) { - if (!name.startsWith('$' + asset.id)) { + if (!name.startsWith('$' + asset.id) && !(name in bindings)) { let newName = '$' + asset.id + '$var$' + name; - bindings[name] = newName + bindings[name] = newName; } } - rename(scope, bindings) + rename(scope, bindings); let exportsIdentifier = getExportsIdentifier(asset); @@ -214,9 +219,14 @@ module.exports = { !t.isStringLiteral(args[0]) || path.scope.hasBinding('require'); - if(asset.package && asset.package.sideEffects === false && !path.scope.parent && !path.getData('markAsPure')) { - path.setData('markAsPure', true) - path.addComment('leading', '#__PURE__') + if ( + asset.package && + asset.package.sideEffects === false && + !path.scope.parent && + !path.getData('markAsPure') + ) { + path.setData('markAsPure', true); + path.addComment('leading', '#__PURE__'); } if (ignore) { return; @@ -264,7 +274,9 @@ module.exports = { let id = path.scope.generateUidIdentifier(specifier.local.name); path.scope.push({id, init}); - path.scope.rename(specifier.local.name, id.name); + path.scope + .getBinding(specifier.local.name) + .referencePaths.forEach(path => path.replaceWith(id)); } else if (t.isImportSpecifier(specifier)) { let {expression: init} = IMPORT_TEMPLATE({ ID: t.numericLiteral(asset.id), @@ -275,7 +287,9 @@ module.exports = { let id = path.scope.generateUidIdentifier(specifier.local.name); path.scope.push({id, init}); - path.scope.rename(specifier.local.name, id.name); + path.scope + .getBinding(specifier.local.name) + .referencePaths.forEach(path => path.replaceWith(id)); } else if (t.isImportNamespaceSpecifier(specifier)) { path.scope.push({ id: specifier.local, @@ -305,7 +319,7 @@ module.exports = { if (t.isIdentifier(declaration)) { // Rename the variable being exported. - safeRename(path, declaration.name, identifier.name); + safeRename(path, asset, declaration.name, identifier.name); path.remove(); } else if (t.isExpression(declaration) || !declaration.id) { // Declare a variable to hold the exported value. @@ -316,7 +330,7 @@ module.exports = { ); } else { // Rename the declaration to the exported name. - safeRename(path, declaration.id.name, identifier.name); + safeRename(path, asset, declaration.id.name, identifier.name); path.replaceWith(declaration); } @@ -426,7 +440,7 @@ module.exports = { if (path.scope.hasBinding(exportsName.name)) { oldName = path.scope.generateDeclaredUidIdentifier(exportsName.name); - path.scope.rename(exportsName.name, oldName.name); + fastRename(exportsName.name, oldName.name); } path.scope.push({ @@ -441,6 +455,26 @@ module.exports = { } }; +// Doesn't actually rename, schedules the renaming at the end of the traversal +function fastRename(scope, asset, oldName, newName) { + if (asset.renamings.has(oldName)) { + asset.renamings.set(newName, asset.renamings.get(oldName)); + asset.renamings.delete(oldName); + } else { + asset.renamings.set(newName, oldName); + } +} + +function resolveRename(asset, name, cache = Array.from(asset.renamings)) { + let resolved = cache.find(entry => entry[1] === name); + + if (resolved) { + return resolveRename(asset, resolved[0], cache); + } + + return name; +} + function addExport(asset, path, local, exported) { let identifier = getIdentifier(asset, 'export', exported.name); let assignNode = EXPORT_ASSIGN_TEMPLATE({ @@ -454,24 +488,30 @@ function addExport(asset, path, local, exported) { .constantViolations.concat(path) .forEach(path => path.insertAfter(assignNode)); + let localName = getName(asset, 'export', local.name); + + if (!asset.cacheData.exports[localName]) { + localName = resolveRename(asset, local.name); + } + // Check if this identifier has already been exported. // If so, create an export alias for it, otherwise, rename the local variable to an export. - if (asset.cacheData.exports[local.name]) { + if (asset.cacheData.exports[localName]) { asset.cacheData.exports[identifier.name] = - asset.cacheData.exports[local.name]; + asset.cacheData.exports[localName]; } else { asset.cacheData.exports[identifier.name] = exported.name; // Get all the node paths mutating the export and insert a CommonJS assignement. - path.scope.rename(local.name, identifier.name); + fastRename(path.scope, asset, local.name, identifier.name); } } -function safeRename(path, from, to) { +function safeRename(path, asset, from, to) { // If the binding that we're renaming is constant, it's safe to rename it. // Otherwise, create a new binding that references the original. let binding = path.scope.getBinding(from); if (binding && binding.constant) { - path.scope.rename(from, to); + fastRename(path.scope, asset, from, to); } else { path.insertAfter( t.variableDeclaration('var', [ diff --git a/src/visitors/renamer.js b/src/visitors/renamer.js index 8d4c693eed2..4cf4e186a89 100644 --- a/src/visitors/renamer.js +++ b/src/visitors/renamer.js @@ -1,41 +1,45 @@ // A fork of babel-traverse Renamer class, optimized for renaming multiple bindings // https://github.com/babel/babel/blob/v6.26.3/packages/babel-traverse/src/scope/lib/renamer.js -const t = require('babel-types') +const t = require('babel-types'); const renameVisitor = { - ReferencedIdentifier({ node }, states) { + ReferencedIdentifier(path, states) { states.find(state => { - if (node.name === state.oldName) { - node.name = state.newName; - - return true + if ( + path.node.name === state.oldName && + path.scope.bindingIdentifierEquals( + state.oldName, + state.binding.identifier + ) + ) { + path.node.name = state.newName; + + return true; } - }) + }); }, + 'AssignmentExpression|Declaration'(path, states) { + let ids = path.getOuterBindingIdentifiers(); - Scope(path, states) { states.find(state => { - if (!path.scope.bindingIdentifierEquals(state.oldName, state.binding.identifier)) { - path.skip(); - - return true + if ( + !path.scope.bindingIdentifierEquals( + state.oldName, + state.binding.identifier + ) + ) { + return; } - }) - }, - 'AssignmentExpression|Declaration'(path, states) { - let ids = path.getOuterBindingIdentifiers(); + let id = ids[state.oldName]; - states.find(state => { - for (let name in ids) { - if (name === state.oldName) { - ids[name].name = state.newName; + if (id) { + id.name = state.newName; - return true - } + return true; } - }) + }); } }; @@ -47,7 +51,8 @@ class Renamer { } maybeConvertFromExportDeclaration(parentDeclar) { - let exportDeclar = parentDeclar.parentPath.isExportDeclaration() && parentDeclar.parentPath; + let exportDeclar = + parentDeclar.parentPath.isExportDeclaration() && parentDeclar.parentPath; if (!exportDeclar) { return; @@ -56,11 +61,17 @@ class Renamer { // build specifiers that point back to this export declaration let isDefault = exportDeclar.isExportDefaultDeclaration(); - if (isDefault && (parentDeclar.isFunctionDeclaration() || - parentDeclar.isClassDeclaration()) && !parentDeclar.node.id) { + if ( + isDefault && + (parentDeclar.isFunctionDeclaration() || + parentDeclar.isClassDeclaration()) && + !parentDeclar.node.id + ) { // Ensure that default class and function exports have a name so they have a identifier to // reference from the export specifier list. - parentDeclar.node.id = parentDeclar.scope.generateUidIdentifier("default"); + parentDeclar.node.id = parentDeclar.scope.generateUidIdentifier( + 'default' + ); } let bindingIdentifiers = parentDeclar.getOuterBindingIdentifiers(); @@ -68,9 +79,11 @@ class Renamer { for (let name in bindingIdentifiers) { let localName = name === this.oldName ? this.newName : name; - let exportedName = isDefault ? "default" : name; + let exportedName = isDefault ? 'default' : name; - specifiers.push(t.exportSpecifier(t.identifier(localName), t.identifier(exportedName))); + specifiers.push( + t.exportSpecifier(t.identifier(localName), t.identifier(exportedName)) + ); } if (specifiers.length) { @@ -88,25 +101,26 @@ class Renamer { prepare() { let {path} = this.binding; - let parentDeclar = path.find((path) => path.isDeclaration() || path.isFunctionExpression()); + let parentDeclar = path.find( + path => path.isDeclaration() || path.isFunctionExpression() + ); if (parentDeclar) { this.maybeConvertFromExportDeclaration(parentDeclar); } - return this + return this; } rename() { let {binding, oldName, newName} = this; - let {scope } = binding; - // scope.traverse(scope.block, renameVisitor, this); - scope.removeOwnBinding(oldName); + let {scope} = binding; + scope.removeOwnBinding(oldName); scope.bindings[newName] = binding; this.binding.identifier.name = newName; - if (binding.type === "hoisted") { + if (binding.type === 'hoisted') { // https://github.com/babel/babel/issues/2435 // todo: hoist and convert function to a let } @@ -116,12 +130,20 @@ class Renamer { module.exports = (scope, names) => { let renamers = Object.keys(names).map(oldName => { let binding = scope.getBinding(oldName); - let newName = names[oldName] - return new Renamer(binding, oldName, newName).prepare() - }) + if (!binding) { + throw new Error(`Cannot find variable ${oldName}`); + } + let newName = names[oldName]; - scope.traverse(scope.block, renameVisitor, renamers) + return new Renamer(binding, oldName, newName).prepare(); + }); - renamers.forEach(renamer => renamer.rename(scope)) -} + if (!renamers.length) { + return; + } + + scope.traverse(scope.block, renameVisitor, renamers); + + renamers.forEach(renamer => renamer.rename(scope)); +}; From 2076f06ea85326fba0b4a2cb2872664222e7a3a0 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 1 May 2018 02:16:25 +0200 Subject: [PATCH 089/180] Only throw when confident export doesnt exist --- src/transforms/concat.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index b523157fce1..c3fcb0b6a51 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -173,7 +173,11 @@ module.exports = packager => { } } } - } else if (!t.isIdentifier(node)) { + } else if ( + mod.cacheData.isES6Module && + !t.isIdentifier(node) && + moduleMap.has(mod.id) + ) { let relativePath = relative(packager.options.rootDir, mod.name); throw new Error(`${relativePath} does not export '${name.value}'`); From 5725c496081c6b169b0d9b263ad62327faeff705 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 1 May 2018 15:21:38 +0200 Subject: [PATCH 090/180] Wait for packager to write --- src/packagers/JSConcatPackager.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 0422f5d7dbb..aee53020351 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -164,8 +164,10 @@ class JSConcatPackager extends Packager { let exposed = []; let prepareModule = []; for (let m of this.exposedModules) { - if(m.cacheData.isES6Module) { - prepareModule.push(`${this.getExportIdentifier(m)}.__esModule = true;`) + if (m.cacheData.isES6Module) { + prepareModule.push( + `${this.getExportIdentifier(m)}.__esModule = true;` + ); } exposed.push(`${m.id}: ${this.getExportIdentifier(m)}`); @@ -201,7 +203,7 @@ class JSConcatPackager extends Packager { output = result.code; } - super.write(output); + await super.write(output); } } From 1e24cb9809dc33f407b15511f19dc1e6a419ed4c Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Fri, 4 May 2018 00:56:59 +0200 Subject: [PATCH 091/180] Fix browser entry-point resolution --- src/Resolver.js | 8 +++++++- .../resolve-entries/browser-multiple.js | 12 +++++++++--- .../pkg-browser-multiple/browser-entry.js | 3 +++ .../pkg-browser-multiple/package.json | 3 ++- test/javascript.js | 14 ++++++++++---- 5 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 test/integration/resolve-entries/pkg-browser-multiple/browser-entry.js diff --git a/src/Resolver.js b/src/Resolver.js index 56f7f03d8fa..245f5287e6e 100644 --- a/src/Resolver.js +++ b/src/Resolver.js @@ -267,10 +267,16 @@ class Resolver { } getPackageMain(pkg) { + let {browser} = pkg; + + if (typeof browser === 'object' && browser[pkg.name]) { + browser = browser[pkg.name]; + } + // libraries like d3.js specifies node.js specific files in the "main" which breaks the build // we use the "module" or "browser" field to get the full dependency tree if available. // If this is a linked module with a `source` field, use that as the entry point. - let main = [pkg.source, pkg.module, pkg.browser, pkg.main].find( + let main = [pkg.source, pkg.module, browser, pkg.main].find( entry => typeof entry === 'string' ); diff --git a/test/integration/resolve-entries/browser-multiple.js b/test/integration/resolve-entries/browser-multiple.js index 92950ec4248..c45bb19d085 100644 --- a/test/integration/resolve-entries/browser-multiple.js +++ b/test/integration/resolve-entries/browser-multiple.js @@ -1,7 +1,13 @@ -const required = require('./pkg-browser-multiple/projected') +const projected = require('./pkg-browser-multiple/projected') -if(required.test() !== 'pkg-browser-multiple') { +if(projected.test() !== 'pkg-browser-multiple') { throw new Error('Invalid module') } -export const test = required.test +const entry = require('./pkg-browser-multiple') + +if(entry.test() !== 'pkg-browser-multiple browser-entry') { + throw new Error('Invalid module') +} + +export const test = {projected, entry} diff --git a/test/integration/resolve-entries/pkg-browser-multiple/browser-entry.js b/test/integration/resolve-entries/pkg-browser-multiple/browser-entry.js new file mode 100644 index 00000000000..8388cdbe9ad --- /dev/null +++ b/test/integration/resolve-entries/pkg-browser-multiple/browser-entry.js @@ -0,0 +1,3 @@ +export function test() { + return 'pkg-browser-multiple browser-entry' +} diff --git a/test/integration/resolve-entries/pkg-browser-multiple/package.json b/test/integration/resolve-entries/pkg-browser-multiple/package.json index e3e274d981e..15a3ab99b29 100644 --- a/test/integration/resolve-entries/pkg-browser-multiple/package.json +++ b/test/integration/resolve-entries/pkg-browser-multiple/package.json @@ -2,6 +2,7 @@ "name": "pkg-browser-multiple", "main": "./does-not-exist.js", "browser": { - "./projected.js": "./projected-module.js" + "./projected.js": "./projected-module.js", + "pkg-browser-multiple": "browser-entry.js" } } \ No newline at end of file diff --git a/test/javascript.js b/test/javascript.js index 6000d04ad16..cd845961208 100644 --- a/test/javascript.js +++ b/test/javascript.js @@ -507,7 +507,11 @@ describe('javascript', function() { assertBundleTree(b, { name: 'browser-multiple.js', - assets: ['browser-multiple.js', 'projected-module.js'], + assets: [ + 'browser-multiple.js', + 'projected-module.js', + 'browser-entry.js' + ], childBundles: [ { type: 'map' @@ -515,10 +519,12 @@ describe('javascript', function() { ] }); - let output = run(b); + let {test: output} = run(b); - assert.equal(typeof output.test, 'function'); - assert.equal(output.test(), 'pkg-browser-multiple'); + assert.equal(typeof output.projected.test, 'function'); + assert.equal(typeof output.entry.test, 'function'); + assert.equal(output.projected.test(), 'pkg-browser-multiple'); + assert.equal(output.entry.test(), 'pkg-browser-multiple browser-entry'); }); it('should resolve the module field before main', async function() { From cb0d4ebdad0c1354176ff10bba1d43f1683d9a63 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Fri, 4 May 2018 22:05:57 +0200 Subject: [PATCH 092/180] Revert "Fix browser entry-point resolution" This reverts commit 1e24cb9809dc33f407b15511f19dc1e6a419ed4c. --- src/Resolver.js | 8 +------- .../resolve-entries/browser-multiple.js | 12 +++--------- .../pkg-browser-multiple/browser-entry.js | 3 --- .../pkg-browser-multiple/package.json | 3 +-- test/javascript.js | 14 ++++---------- 5 files changed, 9 insertions(+), 31 deletions(-) delete mode 100644 test/integration/resolve-entries/pkg-browser-multiple/browser-entry.js diff --git a/src/Resolver.js b/src/Resolver.js index 245f5287e6e..56f7f03d8fa 100644 --- a/src/Resolver.js +++ b/src/Resolver.js @@ -267,16 +267,10 @@ class Resolver { } getPackageMain(pkg) { - let {browser} = pkg; - - if (typeof browser === 'object' && browser[pkg.name]) { - browser = browser[pkg.name]; - } - // libraries like d3.js specifies node.js specific files in the "main" which breaks the build // we use the "module" or "browser" field to get the full dependency tree if available. // If this is a linked module with a `source` field, use that as the entry point. - let main = [pkg.source, pkg.module, browser, pkg.main].find( + let main = [pkg.source, pkg.module, pkg.browser, pkg.main].find( entry => typeof entry === 'string' ); diff --git a/test/integration/resolve-entries/browser-multiple.js b/test/integration/resolve-entries/browser-multiple.js index c45bb19d085..92950ec4248 100644 --- a/test/integration/resolve-entries/browser-multiple.js +++ b/test/integration/resolve-entries/browser-multiple.js @@ -1,13 +1,7 @@ -const projected = require('./pkg-browser-multiple/projected') +const required = require('./pkg-browser-multiple/projected') -if(projected.test() !== 'pkg-browser-multiple') { +if(required.test() !== 'pkg-browser-multiple') { throw new Error('Invalid module') } -const entry = require('./pkg-browser-multiple') - -if(entry.test() !== 'pkg-browser-multiple browser-entry') { - throw new Error('Invalid module') -} - -export const test = {projected, entry} +export const test = required.test diff --git a/test/integration/resolve-entries/pkg-browser-multiple/browser-entry.js b/test/integration/resolve-entries/pkg-browser-multiple/browser-entry.js deleted file mode 100644 index 8388cdbe9ad..00000000000 --- a/test/integration/resolve-entries/pkg-browser-multiple/browser-entry.js +++ /dev/null @@ -1,3 +0,0 @@ -export function test() { - return 'pkg-browser-multiple browser-entry' -} diff --git a/test/integration/resolve-entries/pkg-browser-multiple/package.json b/test/integration/resolve-entries/pkg-browser-multiple/package.json index 15a3ab99b29..e3e274d981e 100644 --- a/test/integration/resolve-entries/pkg-browser-multiple/package.json +++ b/test/integration/resolve-entries/pkg-browser-multiple/package.json @@ -2,7 +2,6 @@ "name": "pkg-browser-multiple", "main": "./does-not-exist.js", "browser": { - "./projected.js": "./projected-module.js", - "pkg-browser-multiple": "browser-entry.js" + "./projected.js": "./projected-module.js" } } \ No newline at end of file diff --git a/test/javascript.js b/test/javascript.js index cd845961208..6000d04ad16 100644 --- a/test/javascript.js +++ b/test/javascript.js @@ -507,11 +507,7 @@ describe('javascript', function() { assertBundleTree(b, { name: 'browser-multiple.js', - assets: [ - 'browser-multiple.js', - 'projected-module.js', - 'browser-entry.js' - ], + assets: ['browser-multiple.js', 'projected-module.js'], childBundles: [ { type: 'map' @@ -519,12 +515,10 @@ describe('javascript', function() { ] }); - let {test: output} = run(b); + let output = run(b); - assert.equal(typeof output.projected.test, 'function'); - assert.equal(typeof output.entry.test, 'function'); - assert.equal(output.projected.test(), 'pkg-browser-multiple'); - assert.equal(output.entry.test(), 'pkg-browser-multiple browser-entry'); + assert.equal(typeof output.test, 'function'); + assert.equal(output.test(), 'pkg-browser-multiple'); }); it('should resolve the module field before main', async function() { From b1dbe38fea1658e758aa722becc4ff716497cf13 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 5 May 2018 01:20:58 +0200 Subject: [PATCH 093/180] Source maps support --- src/Bundle.js | 5 +++ src/packagers/JSConcatPackager.js | 71 ++++++++++++++++++++++++++---- src/packagers/SourceMapPackager.js | 8 ++++ src/transforms/concat.js | 12 ++--- 4 files changed, 83 insertions(+), 13 deletions(-) diff --git a/src/Bundle.js b/src/Bundle.js index dc7d277c395..49cb65158f9 100644 --- a/src/Bundle.js +++ b/src/Bundle.js @@ -14,6 +14,7 @@ class Bundle { this.parentBundle = parent; this.entryAsset = null; this.assets = new Set(); + this.sourceMaps = []; this.childBundles = new Set(); this.siblingBundles = new Set(); this.siblingBundlesMap = new Map(); @@ -44,6 +45,10 @@ class Bundle { this.assets.delete(asset); } + extendSourceMap(...sourceMaps) { + this.sourceMaps.push(...sourceMaps); + } + addOffset(asset, line) { this.offsets.set(asset, line); } diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 5d89fa66e99..5c1d5272445 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -3,7 +3,10 @@ const {minify} = require('uglify-es'); const path = require('path'); const fs = require('fs'); +const SourceMap = require('../SourceMap'); const concat = require('../transforms/concat'); +const lineCounter = require('../utils/lineCounter'); +const urlJoin = require('../utils/urlJoin'); const prelude = fs .readFileSync(path.join(__dirname, '../builtins/prelude2.js'), 'utf8') @@ -14,14 +17,16 @@ const helpers = .trim() + '\n'; class JSConcatPackager extends Packager { - write(string) { - this.buffer += string; + write(string, lineCount = lineCounter(string)) { + this.lineOffset += lineCount - 1; + this.contents += string; } async start() { this.addedAssets = new Set(); this.exposedModules = new Set(); - this.buffer = ''; + this.contents = ''; + this.lineOffset = 1; this.exports = new Map(); this.wildcards = new Map(); this.moduleMap = new Map(); @@ -72,7 +77,7 @@ class JSConcatPackager extends Packager { } this.addedAssets.add(asset); - let js = asset.generated.js; + let {js, map} = asset.generated; this.moduleMap.set(asset.id, asset); this.wildcards.set(asset.id, asset.cacheData.wildcards); @@ -98,11 +103,13 @@ class JSConcatPackager extends Packager { js = js.trim() + '\n'; + this.bundle.addOffset(asset, this.lineOffset + 1); this.write( `\n/* ASSET: ${asset.id} - ${path.relative( this.options.rootDir, asset.name - )} */\n${js}` + )} */\n${js}`, + map && map.lineCount ? map.lineCount : undefined ); } @@ -183,10 +190,20 @@ class JSConcatPackager extends Packager { this.write('})();'); } - let output = concat(this); + let result = concat(this); + let {sourceMaps} = this.options; + let {code: output, rawMappings} = result; + + if (sourceMaps && rawMappings) { + this.bundle.extendSourceMap( + new SourceMap(rawMappings, { + [this.bundle.name]: this.contents + }) + ); + } if (this.options.minify) { - let result = minify(output, { + let opts = { warnings: true, compress: { passes: 3, @@ -196,7 +213,34 @@ class JSConcatPackager extends Packager { mangle: { eval: true } - }); + }; + + if (sourceMaps) { + let sourceMap = new SourceMap(); + + opts.output = { + source_map: { + add(source, gen_line, gen_col, orig_line, orig_col, name) { + sourceMap.addMapping({ + source, + name, + original: { + line: orig_line, + column: orig_col + }, + generated: { + line: gen_line, + column: gen_col + } + }); + } + } + }; + + this.bundle.extendSourceMap(sourceMap); + } + + let result = minify(output, opts); if (result.error) { throw result.error; @@ -205,6 +249,17 @@ class JSConcatPackager extends Packager { output = result.code; } + if (sourceMaps) { + // Add source map url if a map bundle exists + let mapBundle = this.bundle.siblingBundlesMap.get('map'); + if (mapBundle) { + output += `\n//# sourceMappingURL=${urlJoin( + this.options.publicURL, + path.basename(mapBundle.name) + )}`; + } + } + await super.write(output); } } diff --git a/src/packagers/SourceMapPackager.js b/src/packagers/SourceMapPackager.js index 148c7f84553..bcc97f08676 100644 --- a/src/packagers/SourceMapPackager.js +++ b/src/packagers/SourceMapPackager.js @@ -16,6 +16,14 @@ class SourceMapPackager extends Packager { async end() { let file = path.basename(this.bundle.name); + + for (let sourceMap of this.bundle.parentBundle.sourceMaps) { + this.sourceMap = await new SourceMap().extendSourceMap( + this.sourceMap, + sourceMap + ); + } + await this.write( this.sourceMap.stringify( file, diff --git a/src/transforms/concat.js b/src/transforms/concat.js index c3fcb0b6a51..a42ac7ba776 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -10,11 +10,8 @@ const EXPORT_RE = /^\$([\d]+)\$export\$(.+)$/; const DEFAULT_INTEROP_TEMPLATE = template('$parcel$interopDefault(MODULE)'); -// TODO: minify -// TODO: source-map - module.exports = packager => { - let {buffer: code, exports, moduleMap, wildcards} = packager; + let {contents: code, exports, moduleMap, wildcards} = packager; let ast = babylon.parse(code); let rootPath; @@ -353,7 +350,12 @@ module.exports = packager => { } }); - return generate(ast, code).code; + let opts = { + sourceMaps: packager.options.sourceMaps, + sourceFileName: packager.bundle.name + }; + + return generate(ast, opts, code); }; // Check if a binding is safe to remove and returns it if it is. From 3a4a39282b9ace799649433442986d070a2e8dbb Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 7 May 2018 19:53:29 +0200 Subject: [PATCH 094/180] Use asset.getPackage() instead of .package --- src/assets/JSAsset.js | 2 ++ src/visitors/hoist.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 382d63e43f3..613cba92578 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -131,6 +131,8 @@ class JSAsset extends Asset { await this.parseIfNeeded(); if (this.options.scopeHoist) { + await this.getPackage(); + this.traverse(hoist); } else if (this.options.minify) { // We minify in the Packager if scope hoisting is enabled diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 64b6df95770..e2e5b5036c2 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -220,8 +220,8 @@ module.exports = { path.scope.hasBinding('require'); if ( - asset.package && - asset.package.sideEffects === false && + asset._package && + asset._package.sideEffects === false && !path.scope.parent && !path.getData('markAsPure') ) { From 79e00ed8b8be719be8321c4c37732922d5e475d2 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 7 May 2018 22:58:06 +0200 Subject: [PATCH 095/180] Simplify stuff a bit --- src/packagers/JSConcatPackager.js | 5 ----- src/transforms/concat.js | 27 +++++++++++++++------------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 5c1d5272445..0d1ff598988 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -28,8 +28,6 @@ class JSConcatPackager extends Packager { this.contents = ''; this.lineOffset = 1; this.exports = new Map(); - this.wildcards = new Map(); - this.moduleMap = new Map(); this.needsPrelude = false; for (let asset of this.bundle.assets) { @@ -79,9 +77,6 @@ class JSConcatPackager extends Packager { this.addedAssets.add(asset); let {js, map} = asset.generated; - this.moduleMap.set(asset.id, asset); - this.wildcards.set(asset.id, asset.cacheData.wildcards); - for (let key in asset.cacheData.exports) { let local = '$' + asset.id + '$export$' + asset.cacheData.exports[key]; if (key !== local) { diff --git a/src/transforms/concat.js b/src/transforms/concat.js index a42ac7ba776..3bcbeb04ea2 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -11,15 +11,19 @@ const EXPORT_RE = /^\$([\d]+)\$export\$(.+)$/; const DEFAULT_INTEROP_TEMPLATE = template('$parcel$interopDefault(MODULE)'); module.exports = packager => { - let {contents: code, exports, moduleMap, wildcards} = packager; - let ast = babylon.parse(code); let rootPath; - + let {contents: code, exports, addedAssets} = packager; + let ast = babylon.parse(code); // Share $parcel$interopDefault variables between modules let interops = new Map(); + let assets = Array.from(addedAssets).reduce((acc, asset) => { + acc[asset.id] = asset; + + return acc; + }, {}); let resolveModule = (id, name) => { - let module = moduleMap.get(id); + let module = assets[id]; return module.depAssets.get(module.dependencies.get(name)); }; @@ -52,9 +56,8 @@ module.exports = packager => { return t.identifier(exports.get(computedSymbol)); } - // if there is a wildcard for the module // default exports are excluded from wildcard exports - if (wildcards.has(id) && name !== 'default') { + if (id in assets && name !== 'default') { /* recursively lookup the symbol * this is needed when there is deep export wildcards, like in the following: * - a.js @@ -68,9 +71,9 @@ module.exports = packager => { */ let node = null; - wildcards - .get(id) - .find(name => (node = find(resolveModule(id, name).id, symbol))); + assets[id].cacheData.wildcards.find( + name => (node = find(resolveModule(id, name).id, symbol)) + ); return node; } @@ -173,7 +176,7 @@ module.exports = packager => { } else if ( mod.cacheData.isES6Module && !t.isIdentifier(node) && - moduleMap.has(mod.id) + mod.id in assets ) { let relativePath = relative(packager.options.rootDir, mod.name); @@ -232,7 +235,7 @@ module.exports = packager => { ); } - let mapped = moduleMap.get(id.value); + let mapped = assets[id.value]; let dep = mapped.dependencies.get(source.value); let mod = mapped.depAssets.get(dep); let bundles = mod.id; @@ -303,7 +306,7 @@ module.exports = packager => { if (match && !path.scope.hasBinding(name)) { let id = Number(match[1]); - if (moduleMap.has(id)) { + if (id in assets) { path.replaceWith(t.objectExpression([])); } else { path.replaceWith( From d3844a4075241736a2e33b737a8dee9ba6864ea4 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 7 May 2018 23:13:48 +0200 Subject: [PATCH 096/180] Comment stuff a bit --- src/transforms/concat.js | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/transforms/concat.js b/src/transforms/concat.js index 3bcbeb04ea2..f0b7a3f5c0c 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -160,6 +160,7 @@ module.exports = packager => { t.memberExpression(name, t.identifier('d')) ) ); + // Remove the binding and its definition. path.scope.removeBinding(id.name); path.parentPath.remove(); @@ -266,8 +267,12 @@ module.exports = packager => { } let match = object.name.match(EXPORTS_RE); + + // If it's a $id$exports.name expression. if (match) { let exportName = '$' + match[1] + '$export$' + property.name; + + // Check if $id$export$name exists and if so, replace the node by it. if (path.scope.hasBinding(exportName)) { path.replaceWith(t.identifier(exportName)); } @@ -283,32 +288,37 @@ module.exports = packager => { if (typeof name !== 'string') { return; } + + // If it's a renamed export replace it with its alias. + if (exports.has(name)) { + path.replaceWith(t.identifier(exports.get(path.node.name))); + } + let match = name.match(EXPORT_RE); + // If it's an undefined $id$export$name identifier. if (match && !path.scope.hasBinding(name)) { let id = Number(match[1]); let exportName = match[2]; - let node = replaceExportNode(id, exportName, path); - if (node) { - path.replaceWith(node); - } else { - throw new Error( - `Cannot find export "${exportName}" in module "${id}"` - ); - } + // Check if there is a wildcard or an alias (Identifier), else use CommonJS (MemberExpression). + path.replaceWith(replaceExportNode(id, exportName, path)); return; } match = name.match(EXPORTS_RE); + // If it's an undefined $id$exports identifier. if (match && !path.scope.hasBinding(name)) { let id = Number(match[1]); + // If the id is in the bundle it may just be empty, replace with {}. if (id in assets) { path.replaceWith(t.objectExpression([])); - } else { + } + // Else it should be required from another bundle, replace with require(id). + else { path.replaceWith( t.callExpression(t.identifier('require'), [t.numericLiteral(id)]) ); @@ -316,10 +326,6 @@ module.exports = packager => { return; } - - if (exports.has(name)) { - path.replaceWith(t.identifier(exports.get(path.node.name))); - } }, Program: { // A small optimization to remove unused CommonJS exports as sometimes Uglify doesn't remove them. @@ -431,6 +437,7 @@ function getOuterStatement(path) { } } +// Turns plain objects into AST nodes. function toNode(object) { if (typeof object === 'string') { return t.stringLiteral(object); From dfb4d7d8e56fb034f57d9a15dde26d1359ada8a6 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Tue, 8 May 2018 16:35:38 +0200 Subject: [PATCH 097/180] Don't use Babel in transform with scope hoist --- src/assets/JSAsset.js | 17 +++++++++-------- src/transforms/babel.js | 9 ++------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index 613cba92578..a1b22ebd86d 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -124,19 +124,20 @@ class JSAsset extends Asset { } } - if (this.isES6Module) { - await babel(this); - } - - await this.parseIfNeeded(); - if (this.options.scopeHoist) { + await this.parseIfNeeded(); await this.getPackage(); this.traverse(hoist); - } else if (this.options.minify) { + } else { + if (this.isES6Module) { + await babel(this); + } + // We minify in the Packager if scope hoisting is enabled - await uglify(this); + if (this.options.minify) { + await uglify(this); + } } } diff --git a/src/transforms/babel.js b/src/transforms/babel.js index eda1f671b1e..841d6a65ba9 100644 --- a/src/transforms/babel.js +++ b/src/transforms/babel.js @@ -79,15 +79,10 @@ babelTransform.getConfig = getConfig; async function getBabelConfig(asset) { // If asset is marked as an ES6 modules, this is a second pass after dependencies are extracted. // Just compile modules to CommonJS. - if (asset.isES6Module && !asset.options.scopeHoist) { + if (asset.isES6Module) { return { internal: true, - plugins: [ - [ - require('babel-plugin-transform-es2015-modules-commonjs'), - {allowTopLevelThis: true} - ] - ] + plugins: [require('babel-plugin-transform-es2015-modules-commonjs')] }; } From 97bcd4bb28979f91b599d0a9336eed75ec4ea3f9 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 13 May 2018 18:41:46 -0700 Subject: [PATCH 098/180] Postprocess assets which generate js --- src/Pipeline.js | 8 +++----- src/assets/CSSAsset.js | 2 +- src/assets/GLSLAsset.js | 4 +--- src/assets/GlobAsset.js | 9 ++++++--- src/assets/GraphqlAsset.js | 4 +--- src/assets/JSONAsset.js | 6 ++---- src/assets/RawAsset.js | 9 ++++++--- src/assets/TOMLAsset.js | 7 ++++--- src/assets/YAMLAsset.js | 7 ++++--- 9 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Pipeline.js b/src/Pipeline.js index 59d82eae6f9..c72f6966fef 100644 --- a/src/Pipeline.js +++ b/src/Pipeline.js @@ -46,10 +46,7 @@ class Pipeline { for (let rendition of this.iterateRenditions(asset)) { let {type, value} = rendition; - if ( - typeof value !== 'string' || - (!asset.options.scopeHoist && rendition.final) - ) { + if (typeof value !== 'string' || rendition.final) { generated.push(rendition); continue; } @@ -104,7 +101,8 @@ class Pipeline { yield { type, value: asset.generated[type], - final: true + // for scope hoisting, we need to post process all JS + final: !(type === 'js' && this.options.scopeHoist) }; } } diff --git a/src/assets/CSSAsset.js b/src/assets/CSSAsset.js index c79bb355364..4cf066ac1f0 100644 --- a/src/assets/CSSAsset.js +++ b/src/assets/CSSAsset.js @@ -125,7 +125,7 @@ class CSSAsset extends Asset { { type: 'js', value: js, - final: true + hasDependencies: false } ]; } diff --git a/src/assets/GLSLAsset.js b/src/assets/GLSLAsset.js index 50019952cce..fb67e73242c 100644 --- a/src/assets/GLSLAsset.js +++ b/src/assets/GLSLAsset.js @@ -53,9 +53,7 @@ class GLSLAsset extends Asset { const glslifyBundle = await localRequire('glslify-bundle', this.name); let glsl = glslifyBundle(this.ast); - return { - js: `module.exports=${JSON.stringify(glsl)};` - }; + return `module.exports=${JSON.stringify(glsl)};`; } } diff --git a/src/assets/GlobAsset.js b/src/assets/GlobAsset.js index d0317581fb1..89ccf72e173 100644 --- a/src/assets/GlobAsset.js +++ b/src/assets/GlobAsset.js @@ -37,9 +37,12 @@ class GlobAsset extends Asset { } generate() { - return { - js: 'module.exports = ' + generate(this.contents) + ';' - }; + return [ + { + type: 'js', + value: 'module.exports = ' + generate(this.contents) + ';' + } + ]; } } diff --git a/src/assets/GraphqlAsset.js b/src/assets/GraphqlAsset.js index af15efe6edf..62e7a3563d9 100644 --- a/src/assets/GraphqlAsset.js +++ b/src/assets/GraphqlAsset.js @@ -13,9 +13,7 @@ class GraphqlAsset extends Asset { } generate() { - return { - js: `module.exports=${JSON.stringify(this.ast, false, 2)};` - }; + return `module.exports=${JSON.stringify(this.ast, false, 2)};`; } } diff --git a/src/assets/JSONAsset.js b/src/assets/JSONAsset.js index bc652b59814..b74d210756f 100644 --- a/src/assets/JSONAsset.js +++ b/src/assets/JSONAsset.js @@ -18,7 +18,7 @@ class JSONAsset extends Asset { this.ast ? JSON.stringify(this.ast, null, 2) : this.contents };`; - if (this.options.minify) { + if (this.options.minify && !this.options.scopeHoist) { let minified = minify(code); if (minified.error) { throw minified.error; @@ -27,9 +27,7 @@ class JSONAsset extends Asset { code = minified.code; } - return { - js: code - }; + return code; } } diff --git a/src/assets/RawAsset.js b/src/assets/RawAsset.js index 4a1be0cdda5..d44abf2e306 100644 --- a/src/assets/RawAsset.js +++ b/src/assets/RawAsset.js @@ -18,9 +18,12 @@ class RawAsset extends Asset { this.generateBundleName() ); - return { - js: `module.exports=${JSON.stringify(pathToAsset)};` - }; + return [ + { + type: 'js', + value: `module.exports=${JSON.stringify(pathToAsset)};` + } + ]; } async generateHash() { diff --git a/src/assets/TOMLAsset.js b/src/assets/TOMLAsset.js index 10c86dfed6b..ce1d77e8058 100644 --- a/src/assets/TOMLAsset.js +++ b/src/assets/TOMLAsset.js @@ -13,9 +13,10 @@ class TOMLAsset extends Asset { } generate() { - return { - js: serializeObject(this.ast, this.options.minify) - }; + return serializeObject( + this.ast, + this.options.minify && !this.options.scopeHoist + ); } } diff --git a/src/assets/YAMLAsset.js b/src/assets/YAMLAsset.js index 40c904ff6f8..327d7407180 100644 --- a/src/assets/YAMLAsset.js +++ b/src/assets/YAMLAsset.js @@ -13,9 +13,10 @@ class YAMLAsset extends Asset { } generate() { - return { - js: serializeObject(this.ast, this.options.minify) - }; + return serializeObject( + this.ast, + this.options.minify && !this.options.scopeHoist + ); } } From 644b1450d190fa8067c34d664870c81d83559ffe Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 13 May 2018 18:42:21 -0700 Subject: [PATCH 099/180] Run all tests with scope hoisting --- test/fs.js | 6 +- test/integration/sourcemap-reference/index.js | 3 +- test/javascript.js | 59 +++++++++++-------- test/scope-hoisting.js | 2 +- test/utils.js | 21 ++++--- test/watcher.js | 8 +-- 6 files changed, 55 insertions(+), 44 deletions(-) diff --git a/test/fs.js b/test/fs.js index fe458f34139..8d48fadfc22 100644 --- a/test/fs.js +++ b/test/fs.js @@ -13,7 +13,7 @@ describe('fs', function() { it('should inline a file as a buffer', async function() { let b = await bundle(__dirname + '/integration/fs-buffer/index.js'); let output = run(b); - assert.equal(output.constructor.name, 'Buffer'); + assert(output.constructor.name.includes('Buffer')); assert.equal(output.length, 5); }); @@ -87,7 +87,7 @@ describe('fs', function() { try { run(b); } catch (e) { - assert.equal(e.message, 'require(...).readFileSync is not a function'); + assert(e.message.includes('.readFileSync is not a function')); thrown = true; } @@ -104,7 +104,7 @@ describe('fs', function() { try { run(b); } catch (e) { - assert.equal(e.message, 'require(...).readFileSync is not a function'); + assert(e.message.includes('.readFileSync is not a function')); thrown = true; } diff --git a/test/integration/sourcemap-reference/index.js b/test/integration/sourcemap-reference/index.js index 298eba3f0a8..fc1219ac9a8 100644 --- a/test/integration/sourcemap-reference/index.js +++ b/test/integration/sourcemap-reference/index.js @@ -1 +1,2 @@ -import './data.json'; \ No newline at end of file +import data from './data.json'; +export default data; diff --git a/test/javascript.js b/test/javascript.js index 50af16704a6..267f51c8a32 100644 --- a/test/javascript.js +++ b/test/javascript.js @@ -641,19 +641,29 @@ describe('javascript', function() { }); it('should minify YAML for production', async function() { - await bundle(__dirname + '/integration/yaml/index.js', { + let b = await bundle(__dirname + '/integration/yaml/index.js', { + scopeHoist: false, production: true }); + let output = run(b); + assert.equal(typeof output, 'function'); + assert.equal(output(), 3); + let json = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); assert(json.includes('{a:1,b:{c:2}}')); }); it('should minify TOML for production', async function() { - await bundle(__dirname + '/integration/toml/index.js', { + let b = await bundle(__dirname + '/integration/toml/index.js', { + scopeHoist: false, production: true }); + let output = run(b); + assert.equal(typeof output, 'function'); + assert.equal(output(), 3); + let json = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); assert(json.includes('{a:1,b:{c:2}}')); }); @@ -662,40 +672,40 @@ describe('javascript', function() { await bundle(__dirname + '/integration/babel/index.js'); let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); - assert(file.includes('class Foo {}')); - assert(file.includes('class Bar {}')); + assert(!file.includes('function Foo')); + assert(!file.includes('function Bar')); }); it('should compile with babel with default engines if no config', async function() { await bundle(__dirname + '/integration/babel-default/index.js'); let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); - assert(!file.includes('class Foo {}')); - assert(!file.includes('class Bar {}')); + assert(file.includes('function Foo')); + assert(file.includes('function Bar')); }); it('should support compiling with babel using browserlist', async function() { await bundle(__dirname + '/integration/babel-browserslist/index.js'); let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); - assert(!file.includes('class Foo {}')); - assert(!file.includes('class Bar {}')); + assert(file.includes('function Foo')); + assert(file.includes('function Bar')); }); it('should support splitting babel-polyfill using browserlist', async function() { await bundle(__dirname + '/integration/babel-polyfill/index.js'); let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); - assert(file.includes('async function Bar() {}')); + assert(file.includes('async function')); assert(!file.includes('regenerator')); }); it('should support compiling with babel using browserslist for different environments', async function() { async function testBrowserListMultipleEnv(projectBasePath) { // Transpiled destructuring, like r = p.prop1, o = p.prop2, a = p.prop3; - const prodRegExp = /\w ?= ?\w\.prop1, ?\w ?= ?\w\.prop2, ?\w ?= ?\w\.prop3;/; + const prodRegExp = /\S+ ?= ?\S+\.prop1,\s*?\S+ ?= ?\S+\.prop2,\s*?\S+ ?= ?\S+\.prop3;/; // ES6 Destructuring, like in the source; - const devRegExp = /const ?{\s*prop1,\s*prop2,\s*prop3\s*} ?= ?.*/; + const devRegExp = /const ?{\s*prop1(:.+)?,\s*prop2(:.+)?,\s*prop3(:.+)?\s*} ?= ?.*/; let file; // Dev build test await bundle(__dirname + projectBasePath + '/index.js'); @@ -704,6 +714,7 @@ describe('javascript', function() { assert(prodRegExp.test(file) === false); // Prod build test await bundle(__dirname + projectBasePath + '/index.js', { + minify: false, production: true }); file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); @@ -723,8 +734,8 @@ describe('javascript', function() { await bundle(__dirname + '/integration/babel-node-modules/index.js'); let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); - assert(file.includes('class Foo {}')); - assert(!file.includes('class Bar {}')); + assert(/class \S+ \{\}/.test(file)); + assert(file.includes('function Bar')); }); it('should compile node_modules if legacy browserify options are found', async function() { @@ -733,8 +744,8 @@ describe('javascript', function() { ); let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); - assert(!file.includes('class Foo {}')); - assert(!file.includes('class Bar {}')); + assert(file.includes('function Foo')); + assert(file.includes('function Bar')); }); it('should compile node_modules with browserslist to app target', async function() { @@ -743,16 +754,16 @@ describe('javascript', function() { ); let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); - assert(!file.includes('class Foo {}')); - assert(!file.includes('class Bar {}')); + assert(file.includes('function Foo')); + assert(file.includes('function Bar')); }); it('should compile node_modules when symlinked with a source field in package.json', async function() { await bundle(__dirname + '/integration/babel-node-modules-source/index.js'); let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); - assert(!file.includes('class Foo {}')); - assert(!file.includes('class Bar {}')); + assert(file.includes('function Foo')); + assert(file.includes('function Bar')); }); it('should not compile node_modules with a source field in package.json when not symlinked', async function() { @@ -761,8 +772,8 @@ describe('javascript', function() { ); let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); - assert(file.includes('class Foo {}')); - assert(!file.includes('class Bar {}')); + assert(!file.includes('function Foo')); + assert(file.includes('function Bar')); }); it('should support compiling JSX', async function() { @@ -910,7 +921,7 @@ describe('javascript', function() { }; mockDefine.amd = true; - run(b, {define: mockDefine}); + run(b, {define: mockDefine, module: undefined}); assert.equal(test(), 'Test!'); }); @@ -919,7 +930,7 @@ describe('javascript', function() { global: 'testing' }); - const ctx = run(b, null, {require: false}); + const ctx = run(b, {module: undefined}, {require: false}); assert.equal(ctx.window.testing(), 'Test!'); }); @@ -931,7 +942,7 @@ describe('javascript', function() { }; mockDefine.amd = true; - run(b, {define: mockDefine}); + run(b, {define: mockDefine, module: undefined}); assert.equal(test, 2); }); }); diff --git a/test/scope-hoisting.js b/test/scope-hoisting.js index abdc4e12bb2..58410ba3012 100644 --- a/test/scope-hoisting.js +++ b/test/scope-hoisting.js @@ -4,7 +4,7 @@ const {bundle: _bundle, run} = require('./utils'); const bundle = (name, opts = {}) => _bundle(name, Object.assign({scopeHoist: true}, opts)); -describe.only('scope hoisting', function() { +describe('scope hoisting', function() { describe('es6', function() { it('supports default imports and exports of expressions', async function() { let b = await bundle( diff --git a/test/utils.js b/test/utils.js index 492345b3412..3cb5b250120 100644 --- a/test/utils.js +++ b/test/utils.js @@ -23,14 +23,6 @@ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } -function normalizeOptions(opts = {}) { - if (opts.scopeHoist === undefined) { - opts.scopeHoist = false; - } - - return opts; -} - function bundler(file, opts) { return new Bundler( file, @@ -43,13 +35,13 @@ function bundler(file, opts) { hmr: false, logLevel: 0 }, - normalizeOptions(opts) + opts ) ); } function bundle(file, opts) { - return bundler(file, normalizeOptions(opts)).bundle(); + return bundler(file, opts).bundle(); } function prepareBrowserContext(bundle, globals) { @@ -93,8 +85,11 @@ function prepareBrowserContext(bundle, globals) { } }; + var exports = {}; var ctx = Object.assign( { + exports, + module: {exports}, document: fakeDocument, WebSocket, console, @@ -124,6 +119,7 @@ function prepareNodeContext(bundle, globals) { var ctx = Object.assign( { module: mod, + exports: module.exports, __filename: bundle.name, __dirname: path.dirname(bundle.name), require: function(path) { @@ -164,9 +160,12 @@ function run(bundle, globals, opts = {}) { if (opts.require !== false) { if (ctx.parcelRequire) { return ctx.parcelRequire(bundle.entryAsset.id); - } else { + } else if (ctx.output) { return ctx.output; } + if (ctx.module) { + return ctx.module.exports; + } } return ctx; diff --git a/test/watcher.js b/test/watcher.js index beef708c431..21511c7f85f 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -231,8 +231,8 @@ describe('watcher', function() { await b.bundle(); let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); - assert(file.includes('class Foo {}')); - assert(file.includes('class Bar {}')); + assert(!file.includes('function Foo')); + assert(!file.includes('function Bar')); // Change babelrc, should recompile both files let babelrc = JSON.parse( @@ -243,7 +243,7 @@ describe('watcher', function() { await nextBundle(b); file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8'); - assert(!file.includes('class Foo {}')); - assert(!file.includes('class Bar {}')); + assert(file.includes('function Foo')); + assert(file.includes('function Bar')); }); }); From b9ae75bf9db2947e2230f63ea4cf125dedf84ed1 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 13 May 2018 18:45:31 -0700 Subject: [PATCH 100/180] Fix inserting globals with scope hoisting --- src/visitors/globals.js | 25 ++++++++++--------------- src/visitors/hoist.js | 16 ++++++++++++++-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/visitors/globals.js b/src/visitors/globals.js index 327ef0b40d7..b947081950a 100644 --- a/src/visitors/globals.js +++ b/src/visitors/globals.js @@ -1,25 +1,20 @@ const Path = require('path'); const types = require('babel-types'); -const genRequire = (asset, name, module = name) => { - asset.addDependency(module); - - if (asset.options.scopeHoist) { - return `var ${name} = $parcel$require(${asset.id}, ${JSON.stringify( - module - )})`; - } else { - return `var ${name} = require(${JSON.stringify(module)})`; - } -}; - const VARS = { - process: asset => genRequire(asset, 'process') + ';', - global: () => 'var global = arguments[3];', + process: asset => { + asset.addDependency('process'); + return 'var process = require("process");'; + }, + global: asset => + `var global = arguments[${asset.options.scopeHoist ? 0 : 3}];`, __dirname: asset => `var __dirname = ${JSON.stringify(Path.dirname(asset.name))};`, __filename: asset => `var __filename = ${JSON.stringify(asset.name)};`, - Buffer: asset => `${genRequire(asset, 'Buffer', 'buffer')}.Buffer;`, + Buffer: asset => { + asset.addDependency('buffer'); + return 'var Buffer = require("buffer").Buffer;'; + }, // Prevent AMD defines from working when loading UMD bundles. // Ideally the CommonJS check would come before the AMD check, but many // existing modules do the checks the opposite way leading to modules diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index e2e5b5036c2..5b5e3e58a0d 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -177,6 +177,20 @@ module.exports = { path.replaceWith(getExportsIdentifier(asset)); asset.cacheData.isCommonJS = true; } + + if (path.node.name === 'global' && !path.scope.hasBinding('global')) { + path.replaceWith(t.identifier('$parcel$global')); + asset.globals.delete('global'); + } + + let globalCode = asset.globals.get(path.node.name); + if (globalCode) { + path.scope + .getProgramParent() + .path.unshiftContainer('body', [template(globalCode)()]); + + asset.globals.delete(path.node.name); + } }, ThisExpression(path, asset) { @@ -240,7 +254,6 @@ module.exports = { // Generate a variable name based on the current asset id and the module name to require. // This will be replaced by the final variable name of the resolved asset in the packager. - // path.replaceWith(getIdentifier(asset, 'require', args[0].value)); path.replaceWith( REQUIRE_CALL_TEMPLATE({ ID: t.numericLiteral(asset.id), @@ -250,7 +263,6 @@ module.exports = { } if (matchesPattern(callee, 'require.resolve')) { - // path.replaceWith(getIdentifier(asset, 'require_resolve', args[0].value)); path.replaceWith( REQUIRE_RESOLVE_CALL_TEMPLATE({ ID: t.numericLiteral(asset.id), From 50af24d54b9b90f904116676599398b15d5097f3 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 13 May 2018 18:46:38 -0700 Subject: [PATCH 101/180] Don't generate a JS source map if coming from a pipeline without one --- src/Pipeline.js | 2 +- src/assets/JSAsset.js | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Pipeline.js b/src/Pipeline.js index c72f6966fef..f7c9cbc494f 100644 --- a/src/Pipeline.js +++ b/src/Pipeline.js @@ -57,7 +57,7 @@ class Pipeline { asset.name.slice(0, -inputType.length) + type ); if (!(asset instanceof AssetType)) { - let opts = Object.assign({rendition}, asset.options); + let opts = Object.assign({}, asset.options, {rendition}); let subAsset = new AssetType(asset.name, opts); subAsset.id = asset.id; subAsset.contents = value; diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index a1b22ebd86d..3b367333e82 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -30,7 +30,8 @@ class JSAsset extends Asset { this.isES6Module = false; this.outputCode = null; this.cacheData.env = {}; - this.sourceMap = options.rendition ? options.rendition.sourceMap : null; + this.rendition = options.rendition; + this.sourceMap = this.rendition ? this.rendition.sourceMap : null; } shouldInvalidate(cacheData) { @@ -142,6 +143,9 @@ class JSAsset extends Asset { } async generate() { + let enableSourceMaps = + this.options.sourceMaps && + (!this.rendition || !!this.rendition.sourceMap); let code; if (this.isAstDirty) { let opts = { @@ -151,7 +155,7 @@ class JSAsset extends Asset { let generated = generate(this.ast, opts, this.contents); - if (this.options.sourceMaps && generated.rawMappings) { + if (enableSourceMaps && generated.rawMappings) { let rawMap = new SourceMap(generated.rawMappings, { [this.relativeName]: this.contents }); @@ -173,7 +177,7 @@ class JSAsset extends Asset { code = this.outputCode || this.contents; } - if (this.options.sourceMaps && !this.sourceMap) { + if (enableSourceMaps && !this.sourceMap) { this.sourceMap = new SourceMap().generateEmptyMap( this.relativeName, this.contents @@ -182,7 +186,7 @@ class JSAsset extends Asset { if (this.globals.size > 0) { code = Array.from(this.globals.values()).join('\n') + '\n' + code; - if (this.options.sourceMaps) { + if (enableSourceMaps) { if (!(this.sourceMap instanceof SourceMap)) { this.sourceMap = await new SourceMap().addMap(this.sourceMap); } From d743a8ce9c4cb5312a860ba106b0134158100160 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 13 May 2018 18:46:52 -0700 Subject: [PATCH 102/180] Fix vue tests with scope hoisting --- src/assets/VueAsset.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/assets/VueAsset.js b/src/assets/VueAsset.js index 8a1209556e4..5dcb418ef33 100644 --- a/src/assets/VueAsset.js +++ b/src/assets/VueAsset.js @@ -70,15 +70,15 @@ class VueAsset extends Asset { let supplemental = ''; if (this.options.scopeHoist) { - let exportVar = `$${this.id}$export$default`; - - if (js.indexOf(exportVar) === -1) { - supplemental += ` - var ${exportVar} = {}; - var ${optsVar} = ${exportVar}; - `; - } else { - optsVar = exportVar; + optsVar = `$${this.id}$export$default`; + + if (!js.includes(optsVar)) { + optsVar = `$${this.id}$exports`; + if (!js.includes(optsVar)) { + supplemental += ` + var ${optsVar} = {}; + `; + } } } else { supplemental += `var ${optsVar} = exports.default || module.exports;`; From b9b35236fcd7fa539517d96795b89e58348617ee Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 13 May 2018 18:47:40 -0700 Subject: [PATCH 103/180] Remove 'use strict' directives when scope hoisting One strict mode module should not affect all other modules in the same scope --- src/visitors/hoist.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index 5b5e3e58a0d..c6763993b73 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -143,6 +143,14 @@ module.exports = { } }, + DirectiveLiteral(path) { + // Remove 'use strict' directives, since modules are concatenated - one strict mode + // module should not apply to all other modules in the same scope. + if (path.node.value === 'use strict') { + path.parentPath.remove(); + } + }, + MemberExpression(path, asset) { if (path.scope.hasBinding('module') || path.scope.getData('shouldWrap')) { return; From 6d796b554a0f0bb4b14a77fca4d7a1746868afbb Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 13 May 2018 18:48:51 -0700 Subject: [PATCH 104/180] Pass glob output through JS asset --- src/Parser.js | 4 ++-- src/Pipeline.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Parser.js b/src/Parser.js index 22ac505ce8e..8c6fc23b50e 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -61,8 +61,8 @@ class Parser { this.extensions[ext.toLowerCase()] = parser; } - findParser(filename) { - if (/[*+{}]/.test(filename) && glob.hasMagic(filename)) { + findParser(filename, fromPipeline) { + if (!fromPipeline && /[*+{}]/.test(filename) && glob.hasMagic(filename)) { return GlobAsset; } diff --git a/src/Pipeline.js b/src/Pipeline.js index f7c9cbc494f..55ef0927d17 100644 --- a/src/Pipeline.js +++ b/src/Pipeline.js @@ -54,7 +54,8 @@ class Pipeline { // Find an asset type for the rendition type. // If the asset is not already an instance of this asset type, process it. let AssetType = this.parser.findParser( - asset.name.slice(0, -inputType.length) + type + asset.name.slice(0, -inputType.length) + type, + true ); if (!(asset instanceof AssetType)) { let opts = Object.assign({}, asset.options, {rendition}); From 9ccaaf328eb9d49a3e37f4a33e19a8e8bc02d66b Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 13 May 2018 18:52:02 -0700 Subject: [PATCH 105/180] Fix preloading external bundles with scope hoisting --- src/builtins/bundle-loader.js | 16 ++---- src/builtins/prelude.js | 5 ++ src/builtins/prelude2.js | 4 ++ src/packagers/JSConcatPackager.js | 96 ++++++++++++++++++++++++------- src/visitors/hoist.js | 6 +- 5 files changed, 91 insertions(+), 36 deletions(-) diff --git a/src/builtins/bundle-loader.js b/src/builtins/bundle-loader.js index e60c0ae3119..f36878875f7 100644 --- a/src/builtins/bundle-loader.js +++ b/src/builtins/bundle-loader.js @@ -12,7 +12,10 @@ function loadBundlesLazy(bundles) { } catch (err) { if (err.code === 'MODULE_NOT_FOUND') { return new LazyPromise(function (resolve, reject) { - loadBundles(bundles) + loadBundles(bundles.slice(0, -1)) + .then(function () { + return require(id); + }) .then(resolve, reject); }); } @@ -22,12 +25,7 @@ function loadBundlesLazy(bundles) { } function loadBundles(bundles) { - var id = bundles[bundles.length - 1]; - - return Promise.all(bundles.slice(0, -1).map(loadBundle)) - .then(function () { - return require(id); - }); + return Promise.all(bundles.map(loadBundle)); } var bundleLoaders = {}; @@ -57,9 +55,7 @@ function loadBundle(bundle) { return bundles[bundle] = bundleLoader(getBundleURL() + bundle) .then(function (resolved) { if (resolved) { - module.bundle.modules[id] = [function (require, module) { - module.exports = resolved; - }, {}]; + module.bundle.register(id, resolved); } return resolved; diff --git a/src/builtins/prelude.js b/src/builtins/prelude.js index 8463e1c60b2..189d8cba73d 100644 --- a/src/builtins/prelude.js +++ b/src/builtins/prelude.js @@ -70,6 +70,11 @@ parcelRequire = (function (modules, cache, entry, globalName) { newRequire.modules = modules; newRequire.cache = cache; newRequire.parent = previousRequire; + newRequire.register = function (id, exports) { + modules[id] = [function (require, module) { + module.exports = exports; + }, {}]; + }; for (var i = 0; i < entry.length; i++) { newRequire(entry[i]); diff --git a/src/builtins/prelude2.js b/src/builtins/prelude2.js index 59f48775fbf..533d646b9c0 100644 --- a/src/builtins/prelude2.js +++ b/src/builtins/prelude2.js @@ -35,6 +35,10 @@ parcelRequire = (function (init) { throw err; } + localRequire.register = function register(id, exports) { + modules[id] = exports; + }; + modules = init(localRequire); localRequire.modules = modules; return localRequire; diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index 0d1ff598988..b6d24fef745 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -25,16 +25,18 @@ class JSConcatPackager extends Packager { async start() { this.addedAssets = new Set(); this.exposedModules = new Set(); + this.externalModules = new Set(); this.contents = ''; this.lineOffset = 1; this.exports = new Map(); this.needsPrelude = false; for (let asset of this.bundle.assets) { - // If this module is referenced by another bundle, it needs to be exposed externally. - let isExposed = !Array.from(asset.parentDeps).every(dep => - this.bundle.assets.has(this.bundler.loadedAssets.get(dep.parent)) - ); + // If this module is referenced by another JS bundle, it needs to be exposed externally. + let isExposed = !Array.from(asset.parentDeps).every(dep => { + let depAsset = this.bundler.loadedAssets.get(dep.parent); + return this.bundle.assets.has(depAsset) || depAsset.type !== 'js'; + }); if ( isExposed || @@ -47,7 +49,10 @@ class JSConcatPackager extends Packager { } for (let mod of asset.depAssets.values()) { - if (!this.bundle.assets.has(mod)) { + if ( + !this.bundle.assets.has(mod) && + this.options.bundleLoaders[asset.type] + ) { this.needsPrelude = true; break; } @@ -92,18 +97,37 @@ class JSConcatPackager extends Packager { } } - await this.addBundleLoader(mod.type); + await this.addBundleLoader(mod.type, true); + } else { + // If the dep isn't in this bundle, add it to the list of external modules to preload. + // Only do this if this is the root JS bundle, otherwise they will have already been + // loaded in parallel with this bundle as part of a dynamic import. + if ( + !this.bundle.assets.has(mod) && + (!this.bundle.parentBundle || this.bundle.parentBundle.type !== 'js') + ) { + this.externalModules.add(mod); + await this.addBundleLoader(mod.type); + } } } + if (this.bundle.entryAsset === asset && this.externalModules.size > 0) { + js = ` + function $parcel$entry() { + ${js.trim()} + } + `; + } + js = js.trim() + '\n'; this.bundle.addOffset(asset, this.lineOffset + 1); this.write( - `\n/* ASSET: ${asset.id} - ${path.relative( + `\n// ASSET: ${asset.id} - ${path.relative( this.options.rootDir, asset.name - )} */\n${js}`, + )}\n${js}`, map && map.lineCount ? map.lineCount : undefined ); } @@ -134,11 +158,16 @@ class JSConcatPackager extends Packager { await this.addAsset(asset); } - async addBundleLoader(bundleType) { + async addBundleLoader(bundleType, dynamic) { + let loader = this.options.bundleLoaders[bundleType]; + if (!loader) { + return; + } + let bundleLoader = this.bundler.loadedAssets.get( require.resolve('../builtins/bundle-loader') ); - if (!bundleLoader) { + if (!bundleLoader && !dynamic) { bundleLoader = await this.bundler.getAsset('_bundle_loader'); } @@ -148,22 +177,45 @@ class JSConcatPackager extends Packager { return; } - let loader = this.options.bundleLoaders[bundleType]; - if (loader) { - let target = this.options.target === 'node' ? 'node' : 'browser'; - let asset = await this.bundler.getAsset(loader[target]); - if (!this.bundle.assets.has(asset)) { - await this.addAssetToBundle(asset); - this.write( - `${this.getExportIdentifier(bundleLoader)}.register(${JSON.stringify( - bundleType - )},${this.getExportIdentifier(asset)});\n` - ); - } + let target = this.options.target === 'node' ? 'node' : 'browser'; + let asset = await this.bundler.getAsset(loader[target]); + if (!this.bundle.assets.has(asset)) { + await this.addAssetToBundle(asset); + this.write( + `${this.getExportIdentifier(bundleLoader)}.register(${JSON.stringify( + bundleType + )},${this.getExportIdentifier(asset)});\n` + ); } } async end() { + // Preload external modules before running entry point if needed + if (this.externalModules.size > 0) { + let bundleLoader = this.bundler.loadedAssets.get( + require.resolve('../builtins/bundle-loader') + ); + + let preload = []; + for (let mod of this.externalModules) { + // Find the bundle that has the module as its entry point + let bundle = Array.from(mod.bundles).find(b => b.entryAsset === mod); + if (bundle) { + preload.push([path.basename(bundle.name), mod.id]); + } + } + + let loads = `${this.getExportIdentifier( + bundleLoader + )}.load(${JSON.stringify(preload)})`; + if (this.bundle.entryAsset) { + loads += '.then($parcel$entry)'; + } + + loads += ';'; + this.write(loads); + } + if (this.needsPrelude) { let exposed = []; let prepareModule = []; diff --git a/src/visitors/hoist.js b/src/visitors/hoist.js index c6763993b73..985b2be095c 100644 --- a/src/visitors/hoist.js +++ b/src/visitors/hoist.js @@ -169,10 +169,8 @@ module.exports = { path.replaceWith(t.identifier('null')); } - if (matchesPattern(path.node, 'module.bundle.modules')) { - path.replaceWith( - t.memberExpression(t.identifier('require'), t.identifier('modules')) - ); + if (matchesPattern(path.node, 'module.bundle')) { + path.replaceWith(t.identifier('require')); } }, From 6978bde36d707190257545c2f248fd82d7016066 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 13 May 2018 18:52:56 -0700 Subject: [PATCH 106/180] Fix optional requires --- src/builtins/.eslintrc.json | 3 ++- src/builtins/helpers.js | 10 ++++++++-- src/transforms/concat.js | 19 +++++++++++++------ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/builtins/.eslintrc.json b/src/builtins/.eslintrc.json index 3361ed605a0..2c5e9f65270 100644 --- a/src/builtins/.eslintrc.json +++ b/src/builtins/.eslintrc.json @@ -7,6 +7,7 @@ "browser": true }, "rules": { - "no-global-assign": 1 + "no-global-assign": 1, + "no-unused-vars": 1 } } \ No newline at end of file diff --git a/src/builtins/helpers.js b/src/builtins/helpers.js index 0b26df4d5a9..e40bff949d3 100644 --- a/src/builtins/helpers.js +++ b/src/builtins/helpers.js @@ -1,5 +1,3 @@ -/* eslint-disable no-unused-vars */ - function $parcel$interopDefault(a) { return a && a.__esModule ? {d: a.default} @@ -22,3 +20,11 @@ function $parcel$exportWildcard(dest, source) { return dest; } + +function $parcel$missingModule(name) { + var err = new Error("Cannot find module '" + name + "'"); + err.code = 'MODULE_NOT_FOUND'; + throw err; +} + +var $parcel$global = this; diff --git a/src/transforms/concat.js b/src/transforms/concat.js index f0b7a3f5c0c..6a17a468e3d 100644 --- a/src/transforms/concat.js +++ b/src/transforms/concat.js @@ -9,6 +9,7 @@ const EXPORTS_RE = /^\$([\d]+)\$exports$/; const EXPORT_RE = /^\$([\d]+)\$export\$(.+)$/; const DEFAULT_INTEROP_TEMPLATE = template('$parcel$interopDefault(MODULE)'); +const THROW_TEMPLATE = template('$parcel$missingModule(MODULE)'); module.exports = packager => { let rootPath; @@ -106,13 +107,19 @@ module.exports = packager => { let mod = resolveModule(id.value, source.value); - if (typeof mod === 'undefined') { - throw new Error( - `Cannot find module "${source.value}" in asset ${id.value}` - ); + if (!mod) { + if (assets[id.value].dependencies.get(source.value).optional) { + path.replaceWith( + THROW_TEMPLATE({MODULE: t.stringLiteral(source.value)}) + ); + } else { + throw new Error( + `Cannot find module "${source.value}" in asset ${id.value}` + ); + } + } else { + path.replaceWith(t.identifier(`$${mod.id}$exports`)); } - - path.replaceWith(t.identifier(`$${mod.id}$exports`)); } else if (callee.name === '$parcel$import') { let [id, source, name, replace] = args; From 6f40a4a16157908a7fcdd76830783a5029e1cdde Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 13 May 2018 18:53:32 -0700 Subject: [PATCH 107/180] Support UMD exports --- src/packagers/JSConcatPackager.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/packagers/JSConcatPackager.js b/src/packagers/JSConcatPackager.js index b6d24fef745..f1e011c4b6d 100644 --- a/src/packagers/JSConcatPackager.js +++ b/src/packagers/JSConcatPackager.js @@ -216,6 +216,29 @@ class JSConcatPackager extends Packager { this.write(loads); } + if (this.bundle.entryAsset) { + let entryExports = this.getExportIdentifier(this.bundle.entryAsset); + + this.write(` + if (typeof exports === "object" && typeof module !== "undefined") { + // CommonJS + module.exports = ${entryExports}; + } else if (typeof define === "function" && define.amd) { + // RequireJS + define(function () { + return ${entryExports}; + }); + } ${ + this.options.global + ? `else { + //