Skip to content

Commit

Permalink
Scope hoisting for ES6 and CommonJS modules (#1135)
Browse files Browse the repository at this point in the history
  • Loading branch information
fathyb authored and devongovett committed Jun 14, 2018
1 parent 066e30d commit 0ac4e29
Show file tree
Hide file tree
Showing 214 changed files with 3,299 additions and 129 deletions.
3 changes: 1 addition & 2 deletions package.json
Expand Up @@ -106,8 +106,7 @@
"format": "prettier --write \"./{src,bin,test}/**/*.{js,json,md}\"",
"build": "yarn minify && babel src -d lib && ncp src/builtins lib/builtins",
"prepublish": "yarn build",
"minify":
"terser -c -m -o src/builtins/prelude.min.js src/builtins/prelude.js",
"minify": "terser -c -m -o src/builtins/prelude.min.js src/builtins/prelude.js && terser -c -m -o src/builtins/prelude2.min.js src/builtins/prelude2.js",
"precommit": "npm run lint && lint-staged",
"lint":
"eslint . && prettier \"./{src,bin,test}/**/*.{js,json,md}\" --list-different",
Expand Down
33 changes: 23 additions & 10 deletions src/Bundler.js
Expand Up @@ -35,7 +35,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 = {};
Expand Down Expand Up @@ -91,6 +91,14 @@ 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;
const scopeHoist =
options.scopeHoist !== undefined ? options.scopeHoist : false;
return {
production: isProduction,
outDir: Path.resolve(options.outDir || 'dist'),
Expand All @@ -104,19 +112,15 @@ 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,
entryFiles: this.entryFiles,
hmrPort: options.hmrPort || 0,
rootDir: getRootDir(this.entryFiles),
sourceMaps:
typeof options.sourceMaps === 'boolean' ? options.sourceMaps : true,
(typeof options.sourceMaps === 'boolean' ? options.sourceMaps : true) &&
!scopeHoist,
hmrHostname:
options.hmrHostname ||
(options.target === 'electron' ? 'localhost' : ''),
Expand All @@ -126,6 +130,7 @@ class Bundler extends EventEmitter {
typeof options.autoinstall === 'boolean'
? options.autoinstall
: !isProduction,
scopeHoist: scopeHoist,
contentHash:
typeof options.contentHash === 'boolean'
? options.contentHash
Expand Down Expand Up @@ -246,6 +251,8 @@ class Bundler extends EventEmitter {
asset.invalidateBundle();
}

logger.status(emoji.progress, `Producing bundles...`);

// Create a root bundle to hold all of the entry assets, and add them to the tree.
this.mainBundle = new Bundle();
for (let asset of this.entryAssets) {
Expand All @@ -271,6 +278,8 @@ class Bundler extends EventEmitter {
this.hmr.emitUpdate(changedAssets);
}

logger.status(emoji.progress, `Packaging...`);

// Package everything up
this.bundleHashes = await this.mainBundle.package(
this,
Expand Down Expand Up @@ -318,7 +327,7 @@ class Bundler extends EventEmitter {
}

await this.loadPlugins();

if (!this.options.env) {
await loadEnv(Path.join(this.options.rootDir, 'index'));
this.options.env = process.env;
Expand Down Expand Up @@ -508,14 +517,17 @@ class Bundler extends EventEmitter {
let processed = this.cache && (await this.cache.read(asset.name));
let cacheMiss = false;
if (!processed || asset.shouldInvalidate(processed.cacheData)) {
processed = await this.farm.run(asset.name);
processed = await this.farm.run(asset.name, asset.id);
processed.id = asset.id;
cacheMiss = true;
}

asset.endTime = Date.now();
asset.buildTime = asset.endTime - asset.startTime;
asset.id = processed.id;
asset.generated = processed.generated;
asset.hash = processed.hash;
asset.cacheData = processed.cacheData;

// Call the delegate to get implicit dependencies
let dependencies = processed.dependencies;
Expand All @@ -535,6 +547,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);
Expand Down
2 changes: 1 addition & 1 deletion src/FSCache.js
Expand Up @@ -6,7 +6,7 @@ const pkg = require('../package.json');
const logger = require('./Logger');

// These keys can affect the output, so if they differ, the cache should not match
const OPTION_KEYS = ['publicURL', 'minify', 'hmr', 'target'];
const OPTION_KEYS = ['publicURL', 'minify', 'hmr', 'target', 'scopeHoist'];

class FSCache {
constructor(options) {
Expand Down
4 changes: 2 additions & 2 deletions src/Parser.js
Expand Up @@ -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;
}

Expand Down
15 changes: 10 additions & 5 deletions src/Pipeline.js
Expand Up @@ -11,13 +11,15 @@ class Pipeline {
this.parser = new Parser(options);
}

async process(path, isWarmUp) {
async process(path, id, isWarmUp) {
let options = this.options;
if (isWarmUp) {
options = Object.assign({isWarmUp}, options);
}

let asset = this.parser.getAsset(path, options);
asset.id = id;

let generated = await this.processAsset(asset);
let generatedMap = {};
for (let rendition of generated) {
Expand Down Expand Up @@ -52,17 +54,19 @@ 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({rendition}, asset.options);
let opts = Object.assign({}, asset.options, {rendition});
let subAsset = new AssetType(asset.name, opts);
subAsset.id = asset.id;
subAsset.contents = value;
subAsset.dependencies = asset.dependencies;
subAsset.cacheData = Object.assign(asset.cacheData, subAsset.cacheData);

let processed = await this.processAsset(subAsset);
generated = generated.concat(processed);
Object.assign(asset.cacheData, subAsset.cacheData);
asset.hash = md5(asset.hash + subAsset.hash);
} else {
generated.push(rendition);
Expand Down Expand Up @@ -98,7 +102,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)
};
}
}
Expand Down
79 changes: 68 additions & 11 deletions src/SourceMap.js
Expand Up @@ -149,8 +149,8 @@ class SourceMap {
column: mapping.original.column
});

if (!originalMapping.line) {
return false;
if (!originalMapping || !originalMapping.line) {
return;
}

this.addMapping({
Expand Down Expand Up @@ -182,7 +182,60 @@ class SourceMap {
return this;
}

findClosest(line, column, key = 'original') {
findClosestGenerated(line, column) {
if (line < 1) {
throw new Error('Line numbers must be >= 1');
}

if (column < 0) {
throw new Error('Column numbers must be >= 0');
}

if (this.mappings.length < 1) {
return undefined;
}

let startIndex = 0;
let stopIndex = this.mappings.length - 1;
let middleIndex = (stopIndex + startIndex) >>> 1;

while (
startIndex < stopIndex &&
this.mappings[middleIndex].generated.line !== line
) {
let mid = this.mappings[middleIndex].generated.line;
if (line < mid) {
stopIndex = middleIndex - 1;
} else if (line > mid) {
startIndex = middleIndex + 1;
}
middleIndex = (stopIndex + startIndex) >>> 1;
}

let mapping = this.mappings[middleIndex];
if (!mapping || mapping.generated.line !== line) {
return this.mappings.length - 1;
}

while (
middleIndex >= 1 &&
this.mappings[middleIndex - 1].generated.line === line
) {
middleIndex--;
}

while (
middleIndex < this.mappings.length - 1 &&
this.mappings[middleIndex + 1].generated.line === line &&
column > this.mappings[middleIndex].generated.column
) {
middleIndex++;
}

return middleIndex;
}

findClosest(line, column, key) {
if (line < 1) {
throw new Error('Line numbers must be >= 1');
}
Expand Down Expand Up @@ -211,7 +264,7 @@ class SourceMap {
middleIndex = Math.floor((stopIndex + startIndex) / 2);
}

let mapping = this.mappings[middleIndex];
var mapping = this.mappings[middleIndex];
if (!mapping || mapping[key].line !== line) {
return this.mappings.length - 1;
}
Expand All @@ -235,16 +288,20 @@ class SourceMap {
}

originalPositionFor(generatedPosition) {
let index = this.findClosest(
let index = this.findClosestGenerated(
generatedPosition.line,
generatedPosition.column,
'generated'
generatedPosition.column
);
let mapping = this.mappings[index];
if (!mapping) {
return null;
}

return {
source: this.mappings[index].source,
name: this.mappings[index].name,
line: this.mappings[index].original.line,
column: this.mappings[index].original.column
source: mapping.source,
name: mapping.name,
line: mapping.original.line,
column: mapping.original.column
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/assets/CSSAsset.js
Expand Up @@ -125,7 +125,7 @@ class CSSAsset extends Asset {
{
type: 'js',
value: js,
final: true
hasDependencies: false
}
];
}
Expand Down
4 changes: 1 addition & 3 deletions src/assets/GLSLAsset.js
Expand Up @@ -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)};`;
}
}

Expand Down
9 changes: 6 additions & 3 deletions src/assets/GlobAsset.js
Expand Up @@ -38,9 +38,12 @@ class GlobAsset extends Asset {
}

generate() {
return {
js: 'module.exports = ' + generate(this.contents) + ';'
};
return [
{
type: 'js',
value: 'module.exports = ' + generate(this.contents) + ';'
}
];
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/assets/GraphqlAsset.js
Expand Up @@ -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)};`;
}
}

Expand Down

0 comments on commit 0ac4e29

Please sign in to comment.