Skip to content

Commit

Permalink
Deterministic asset ids (#1694)
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Jul 11, 2018
1 parent b52548b commit e34a4d0
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 66 deletions.
15 changes: 12 additions & 3 deletions src/Asset.js
Expand Up @@ -10,8 +10,6 @@ const syncPromise = require('./utils/syncPromise');
const logger = require('./Logger');
const Resolver = require('./Resolver');

let ASSET_ID = 1;

/**
* An Asset represents a file in the dependency tree. Assets can have multiple
* parents that depend on it, and can be added to multiple output bundles.
Expand All @@ -20,7 +18,7 @@ let ASSET_ID = 1;
*/
class Asset {
constructor(name, options) {
this.id = ASSET_ID++;
this.id = null;
this.name = name;
this.basename = path.basename(this.name);
this.relativeName = path.relative(options.rootDir, this.name);
Expand Down Expand Up @@ -188,6 +186,17 @@ class Asset {
}

async process() {
// Generate the id for this asset, unless it has already been set.
// We do this here rather than in the constructor to avoid unnecessary work in the main process.
// In development, the id is just the relative path to the file, for easy debugging and performance.
// In production, we use a short hash of the relative path.
if (!this.id) {
this.id =
this.options.production || this.options.scopeHoist
? md5(this.relativeName, 'base64').slice(0, 4)
: this.relativeName;
}

if (!this.generated) {
await this.loadIfNeeded();
await this.pretransform();
Expand Down
3 changes: 1 addition & 2 deletions src/Bundler.js
Expand Up @@ -525,8 +525,7 @@ 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, asset.id);
processed.id = asset.id;
processed = await this.farm.run(asset.name);
cacheMiss = true;
}

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

async process(path, id, isWarmUp) {
async process(path, 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) {
generatedMap[rendition.type] = rendition.value;
}

return {
id: asset.id,
dependencies: Array.from(asset.dependencies.values()),
generated: generatedMap,
hash: asset.hash,
Expand Down
2 changes: 1 addition & 1 deletion src/builtins/hmr-runtime.js
Expand Up @@ -110,7 +110,7 @@ function getParents(bundle, id) {
for (d in modules[k][1]) {
dep = modules[k][1][d];
if (dep === id || (Array.isArray(dep) && dep[dep.length - 1] === id)) {
parents.push(+k);
parents.push(k);
}
}
}
Expand Down
17 changes: 9 additions & 8 deletions src/packagers/JSConcatPackager.js
Expand Up @@ -6,6 +6,7 @@ const urlJoin = require('../utils/urlJoin');
const walk = require('babylon-walk');
const babylon = require('babylon');
const t = require('babel-types');
const {getName, getIdentifier} = require('../scope-hoisting/utils');

const prelude = {
source: fs
Expand Down Expand Up @@ -125,9 +126,9 @@ class JSConcatPackager extends Packager {
}

getExportIdentifier(asset) {
let id = '$' + asset.id + '$exports';
let id = getName(asset, 'exports');
if (this.shouldWrap(asset)) {
return `($${asset.id}$init(), ${id})`;
return `(${getName(asset, 'init')}(), ${id})`;
}

return id;
Expand Down Expand Up @@ -317,13 +318,13 @@ class JSConcatPackager extends Packager {
}
}

let executed = `$${asset.id}$executed`;
let executed = getName(asset, 'executed');
decls.push(
t.variableDeclarator(t.identifier(executed), t.booleanLiteral(false))
);

let init = t.functionDeclaration(
t.identifier(`$${asset.id}$init`),
getIdentifier(asset, 'init'),
[],
t.blockStatement([
t.ifStatement(t.identifier(executed), t.returnStatement()),
Expand Down Expand Up @@ -504,7 +505,7 @@ class JSConcatPackager extends Packager {
);
}

exposed.push(`${m.id}: ${this.getExportIdentifier(m)}`);
exposed.push(`"${m.id}": ${this.getExportIdentifier(m)}`);
}

this.write(`
Expand Down Expand Up @@ -545,12 +546,12 @@ class JSConcatPackager extends Packager {
}

resolveModule(id, name) {
let module = this.assets.get(+id);
let module = this.assets.get(id);
return module.depAssets.get(module.dependencies.get(name));
}

findExportModule(id, name, replacements) {
let asset = this.assets.get(+id);
let asset = this.assets.get(id);
let exp =
asset &&
Object.prototype.hasOwnProperty.call(asset.cacheData.exports, name)
Expand Down Expand Up @@ -578,7 +579,7 @@ class JSConcatPackager extends Packager {

// If this is a wildcard import, resolve to the exports object.
if (asset && name === '*') {
exp = `$${id}$exports`;
exp = getName(asset, 'exports');
}

if (replacements && replacements.has(exp)) {
Expand Down
13 changes: 9 additions & 4 deletions src/packagers/JSPackager.js
Expand Up @@ -106,7 +106,10 @@ class JSPackager extends Packager {
async writeModule(id, code, deps = {}, map) {
let wrapped = this.first ? '' : ',';
wrapped +=
id + ':[function(require,module,exports) {\n' + (code || '') + '\n},';
JSON.stringify(id) +
':[function(require,module,exports) {\n' +
(code || '') +
'\n},';
wrapped += JSON.stringify(deps);
wrapped += ']';

Expand Down Expand Up @@ -154,7 +157,7 @@ class JSPackager extends Packager {
}

// Generate a module to register the bundle loaders that are needed
let loads = 'var b=require(' + bundleLoader.id + ');';
let loads = 'var b=require(' + JSON.stringify(bundleLoader.id) + ');';
for (let bundleType of this.bundleLoaders) {
let loader = this.options.bundleLoaders[bundleType];
if (loader) {
Expand All @@ -165,7 +168,7 @@ class JSPackager extends Packager {
'b.register(' +
JSON.stringify(bundleType) +
',require(' +
asset.id +
JSON.stringify(asset.id) +
'));';
}
}
Expand All @@ -183,7 +186,9 @@ class JSPackager extends Packager {

loads += 'b.load(' + JSON.stringify(preload) + ')';
if (this.bundle.entryAsset) {
loads += `.then(function(){require(${this.bundle.entryAsset.id});})`;
loads += `.then(function(){require(${JSON.stringify(
this.bundle.entryAsset.id
)});})`;
}

loads += ';';
Expand Down
21 changes: 11 additions & 10 deletions src/scope-hoisting/concat.js
Expand Up @@ -5,8 +5,9 @@ const traverse = require('babel-traverse').default;
const generate = require('babel-generator').default;
const treeShake = require('./shake');
const mangleScope = require('./mangler');
const {getName, getIdentifier} = require('./utils');

const EXPORTS_RE = /^\$([\d]+)\$exports$/;
const EXPORTS_RE = /^\$(.+?)\$exports$/;

const DEFAULT_INTEROP_TEMPLATE = template(
'var NAME = $parcel$interopDefault(MODULE)'
Expand Down Expand Up @@ -43,7 +44,7 @@ module.exports = (packager, ast) => {

// If the module is not in this bundle, create a `require` call for it.
if (!node && !mod) {
node = REQUIRE_TEMPLATE({ID: t.numericLiteral(id)}).expression;
node = REQUIRE_TEMPLATE({ID: t.stringLiteral(id)}).expression;
return interop(module, name, path, node);
}

Expand All @@ -55,7 +56,7 @@ module.exports = (packager, ast) => {

// If it is CommonJS, look for an exports object.
if (!node && mod.cacheData.isCommonJS) {
node = findSymbol(path, `$${id}$exports`);
node = findSymbol(path, getName(mod, 'exports'));
if (!node) {
return null;
}
Expand All @@ -82,7 +83,7 @@ module.exports = (packager, ast) => {
function interop(mod, originalName, path, node) {
// Handle interop for default imports of CommonJS modules.
if (mod.cacheData.isCommonJS && originalName === 'default') {
let name = `$${mod.id}$interop$default`;
let name = getName(mod, '$interop$default');
if (!path.scope.getBinding(name)) {
let [decl] = path.getStatementParent().insertBefore(
DEFAULT_INTEROP_TEMPLATE({
Expand All @@ -91,7 +92,7 @@ module.exports = (packager, ast) => {
})
);

let binding = path.scope.getBinding(`$${mod.id}$exports`);
let binding = path.scope.getBinding(getName(mod, 'exports'));
if (binding) {
binding.reference(decl.get('declarations.0.init'));
}
Expand Down Expand Up @@ -133,7 +134,7 @@ module.exports = (packager, ast) => {

if (
args.length !== 2 ||
!t.isNumericLiteral(id) ||
!t.isStringLiteral(id) ||
!t.isStringLiteral(source)
) {
throw new Error(
Expand All @@ -158,19 +159,19 @@ module.exports = (packager, ast) => {
if (assets.get(mod.id)) {
// Replace with nothing if the require call's result is not used.
if (!isUnusedValue(path)) {
let name = `$${mod.id}$exports`;
let name = getName(mod, 'exports');
node = t.identifier(replacements.get(name) || name);
}

// We need to wrap the module in a function when a require
// call happens inside a non top-level scope, e.g. in a
// function, if statement, or conditional expression.
if (mod.cacheData.shouldWrap) {
let call = t.callExpression(t.identifier(`$${mod.id}$init`), []);
let call = t.callExpression(getIdentifier(mod, 'init'), []);
node = node ? t.sequenceExpression([call, node]) : call;
}
} else {
node = REQUIRE_TEMPLATE({ID: t.numericLiteral(mod.id)}).expression;
node = REQUIRE_TEMPLATE({ID: t.stringLiteral(mod.id)}).expression;
}

if (node) {
Expand All @@ -184,7 +185,7 @@ module.exports = (packager, ast) => {

if (
args.length !== 2 ||
!t.isNumericLiteral(id) ||
!t.isStringLiteral(id) ||
!t.isStringLiteral(source)
) {
throw new Error(
Expand Down
38 changes: 8 additions & 30 deletions src/scope-hoisting/hoist.js
Expand Up @@ -2,6 +2,7 @@ const matchesPattern = require('../visitors/matches-pattern');
const t = require('babel-types');
const template = require('babel-template');
const rename = require('./renamer');
const {getName, getIdentifier, getExportIdentifier} = require('./utils');

const WRAPPER_TEMPLATE = template(`
var NAME = (function () {
Expand Down Expand Up @@ -119,8 +120,8 @@ module.exports = {

// 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;
if (!name.startsWith('$' + t.toIdentifier(asset.id))) {
let newName = getName(asset, 'var', name);
rename(scope, name, newName);
}
}
Expand Down Expand Up @@ -160,7 +161,7 @@ module.exports = {
}

if (matchesPattern(path.node, 'module.id')) {
path.replaceWith(t.numericLiteral(asset.id));
path.replaceWith(t.stringLiteral(asset.id));
}

if (matchesPattern(path.node, 'module.hot')) {
Expand Down Expand Up @@ -302,7 +303,7 @@ module.exports = {
// This will be replaced by the final variable name of the resolved asset in the packager.
path.replaceWith(
REQUIRE_CALL_TEMPLATE({
ID: t.numericLiteral(asset.id),
ID: t.stringLiteral(asset.id),
SOURCE: t.stringLiteral(args[0].value)
})
);
Expand All @@ -311,7 +312,7 @@ module.exports = {
if (matchesPattern(callee, 'require.resolve')) {
path.replaceWith(
REQUIRE_RESOLVE_CALL_TEMPLATE({
ID: t.numericLiteral(asset.id),
ID: t.stringLiteral(asset.id),
SOURCE: args[0]
})
);
Expand Down Expand Up @@ -455,7 +456,7 @@ module.exports = {
EXPORT_ALL_TEMPLATE({
OLD_NAME: getExportsIdentifier(asset),
SOURCE: t.stringLiteral(path.node.source.value),
ID: t.numericLiteral(asset.id)
ID: t.stringLiteral(asset.id)
})
);
}
Expand All @@ -464,7 +465,7 @@ module.exports = {
function addImport(asset, path) {
// Replace with a $parcel$require call so we know where to insert side effects.
let requireCall = REQUIRE_CALL_TEMPLATE({
ID: t.numericLiteral(asset.id),
ID: t.stringLiteral(asset.id),
SOURCE: t.stringLiteral(path.node.source.value)
});

Expand Down Expand Up @@ -539,29 +540,6 @@ function safeRename(path, asset, from, to) {
}
}

function getName(asset, type, ...rest) {
return (
'$' +
asset.id +
'$' +
type +
(rest.length
? '$' +
rest
.map(name => (name === 'default' ? name : t.toIdentifier(name)))
.join('$')
: '')
);
}

function getIdentifier(asset, type, ...rest) {
return t.identifier(getName(asset, type, ...rest));
}

function getExportIdentifier(asset, name) {
return getIdentifier(asset, 'export', name);
}

function getExportsIdentifier(asset, scope) {
if (scope && scope.getData('shouldWrap')) {
return t.identifier('exports');
Expand Down
2 changes: 1 addition & 1 deletion src/scope-hoisting/shake.js
@@ -1,6 +1,6 @@
const t = require('babel-types');

const EXPORTS_RE = /^\$([\d]+)\$exports$/;
const EXPORTS_RE = /^\$(.+?)\$exports$/;

/**
* This is a small small implementation of dead code removal specialized to handle
Expand Down

0 comments on commit e34a4d0

Please sign in to comment.