Skip to content

Commit e34a4d0

Browse files
authored
Deterministic asset ids (#1694)
1 parent b52548b commit e34a4d0

12 files changed

Lines changed: 86 additions & 66 deletions

File tree

src/Asset.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ const syncPromise = require('./utils/syncPromise');
1010
const logger = require('./Logger');
1111
const Resolver = require('./Resolver');
1212

13-
let ASSET_ID = 1;
14-
1513
/**
1614
* An Asset represents a file in the dependency tree. Assets can have multiple
1715
* parents that depend on it, and can be added to multiple output bundles.
@@ -20,7 +18,7 @@ let ASSET_ID = 1;
2018
*/
2119
class Asset {
2220
constructor(name, options) {
23-
this.id = ASSET_ID++;
21+
this.id = null;
2422
this.name = name;
2523
this.basename = path.basename(this.name);
2624
this.relativeName = path.relative(options.rootDir, this.name);
@@ -188,6 +186,17 @@ class Asset {
188186
}
189187

190188
async process() {
189+
// Generate the id for this asset, unless it has already been set.
190+
// We do this here rather than in the constructor to avoid unnecessary work in the main process.
191+
// In development, the id is just the relative path to the file, for easy debugging and performance.
192+
// In production, we use a short hash of the relative path.
193+
if (!this.id) {
194+
this.id =
195+
this.options.production || this.options.scopeHoist
196+
? md5(this.relativeName, 'base64').slice(0, 4)
197+
: this.relativeName;
198+
}
199+
191200
if (!this.generated) {
192201
await this.loadIfNeeded();
193202
await this.pretransform();

src/Bundler.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,8 +525,7 @@ class Bundler extends EventEmitter {
525525
let processed = this.cache && (await this.cache.read(asset.name));
526526
let cacheMiss = false;
527527
if (!processed || asset.shouldInvalidate(processed.cacheData)) {
528-
processed = await this.farm.run(asset.name, asset.id);
529-
processed.id = asset.id;
528+
processed = await this.farm.run(asset.name);
530529
cacheMiss = true;
531530
}
532531

src/Pipeline.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,21 @@ class Pipeline {
1111
this.parser = new Parser(options);
1212
}
1313

14-
async process(path, id, isWarmUp) {
14+
async process(path, isWarmUp) {
1515
let options = this.options;
1616
if (isWarmUp) {
1717
options = Object.assign({isWarmUp}, options);
1818
}
1919

2020
let asset = this.parser.getAsset(path, options);
21-
asset.id = id;
22-
2321
let generated = await this.processAsset(asset);
2422
let generatedMap = {};
2523
for (let rendition of generated) {
2624
generatedMap[rendition.type] = rendition.value;
2725
}
2826

2927
return {
28+
id: asset.id,
3029
dependencies: Array.from(asset.dependencies.values()),
3130
generated: generatedMap,
3231
hash: asset.hash,

src/builtins/hmr-runtime.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ function getParents(bundle, id) {
110110
for (d in modules[k][1]) {
111111
dep = modules[k][1][d];
112112
if (dep === id || (Array.isArray(dep) && dep[dep.length - 1] === id)) {
113-
parents.push(+k);
113+
parents.push(k);
114114
}
115115
}
116116
}

src/packagers/JSConcatPackager.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const urlJoin = require('../utils/urlJoin');
66
const walk = require('babylon-walk');
77
const babylon = require('babylon');
88
const t = require('babel-types');
9+
const {getName, getIdentifier} = require('../scope-hoisting/utils');
910

1011
const prelude = {
1112
source: fs
@@ -125,9 +126,9 @@ class JSConcatPackager extends Packager {
125126
}
126127

127128
getExportIdentifier(asset) {
128-
let id = '$' + asset.id + '$exports';
129+
let id = getName(asset, 'exports');
129130
if (this.shouldWrap(asset)) {
130-
return `($${asset.id}$init(), ${id})`;
131+
return `(${getName(asset, 'init')}(), ${id})`;
131132
}
132133

133134
return id;
@@ -317,13 +318,13 @@ class JSConcatPackager extends Packager {
317318
}
318319
}
319320

320-
let executed = `$${asset.id}$executed`;
321+
let executed = getName(asset, 'executed');
321322
decls.push(
322323
t.variableDeclarator(t.identifier(executed), t.booleanLiteral(false))
323324
);
324325

325326
let init = t.functionDeclaration(
326-
t.identifier(`$${asset.id}$init`),
327+
getIdentifier(asset, 'init'),
327328
[],
328329
t.blockStatement([
329330
t.ifStatement(t.identifier(executed), t.returnStatement()),
@@ -504,7 +505,7 @@ class JSConcatPackager extends Packager {
504505
);
505506
}
506507

507-
exposed.push(`${m.id}: ${this.getExportIdentifier(m)}`);
508+
exposed.push(`"${m.id}": ${this.getExportIdentifier(m)}`);
508509
}
509510

510511
this.write(`
@@ -545,12 +546,12 @@ class JSConcatPackager extends Packager {
545546
}
546547

547548
resolveModule(id, name) {
548-
let module = this.assets.get(+id);
549+
let module = this.assets.get(id);
549550
return module.depAssets.get(module.dependencies.get(name));
550551
}
551552

552553
findExportModule(id, name, replacements) {
553-
let asset = this.assets.get(+id);
554+
let asset = this.assets.get(id);
554555
let exp =
555556
asset &&
556557
Object.prototype.hasOwnProperty.call(asset.cacheData.exports, name)
@@ -578,7 +579,7 @@ class JSConcatPackager extends Packager {
578579

579580
// If this is a wildcard import, resolve to the exports object.
580581
if (asset && name === '*') {
581-
exp = `$${id}$exports`;
582+
exp = getName(asset, 'exports');
582583
}
583584

584585
if (replacements && replacements.has(exp)) {

src/packagers/JSPackager.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ class JSPackager extends Packager {
106106
async writeModule(id, code, deps = {}, map) {
107107
let wrapped = this.first ? '' : ',';
108108
wrapped +=
109-
id + ':[function(require,module,exports) {\n' + (code || '') + '\n},';
109+
JSON.stringify(id) +
110+
':[function(require,module,exports) {\n' +
111+
(code || '') +
112+
'\n},';
110113
wrapped += JSON.stringify(deps);
111114
wrapped += ']';
112115

@@ -154,7 +157,7 @@ class JSPackager extends Packager {
154157
}
155158

156159
// Generate a module to register the bundle loaders that are needed
157-
let loads = 'var b=require(' + bundleLoader.id + ');';
160+
let loads = 'var b=require(' + JSON.stringify(bundleLoader.id) + ');';
158161
for (let bundleType of this.bundleLoaders) {
159162
let loader = this.options.bundleLoaders[bundleType];
160163
if (loader) {
@@ -165,7 +168,7 @@ class JSPackager extends Packager {
165168
'b.register(' +
166169
JSON.stringify(bundleType) +
167170
',require(' +
168-
asset.id +
171+
JSON.stringify(asset.id) +
169172
'));';
170173
}
171174
}
@@ -183,7 +186,9 @@ class JSPackager extends Packager {
183186

184187
loads += 'b.load(' + JSON.stringify(preload) + ')';
185188
if (this.bundle.entryAsset) {
186-
loads += `.then(function(){require(${this.bundle.entryAsset.id});})`;
189+
loads += `.then(function(){require(${JSON.stringify(
190+
this.bundle.entryAsset.id
191+
)});})`;
187192
}
188193

189194
loads += ';';

src/scope-hoisting/concat.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ const traverse = require('babel-traverse').default;
55
const generate = require('babel-generator').default;
66
const treeShake = require('./shake');
77
const mangleScope = require('./mangler');
8+
const {getName, getIdentifier} = require('./utils');
89

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

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

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

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

5657
// If it is CommonJS, look for an exports object.
5758
if (!node && mod.cacheData.isCommonJS) {
58-
node = findSymbol(path, `$${id}$exports`);
59+
node = findSymbol(path, getName(mod, 'exports'));
5960
if (!node) {
6061
return null;
6162
}
@@ -82,7 +83,7 @@ module.exports = (packager, ast) => {
8283
function interop(mod, originalName, path, node) {
8384
// Handle interop for default imports of CommonJS modules.
8485
if (mod.cacheData.isCommonJS && originalName === 'default') {
85-
let name = `$${mod.id}$interop$default`;
86+
let name = getName(mod, '$interop$default');
8687
if (!path.scope.getBinding(name)) {
8788
let [decl] = path.getStatementParent().insertBefore(
8889
DEFAULT_INTEROP_TEMPLATE({
@@ -91,7 +92,7 @@ module.exports = (packager, ast) => {
9192
})
9293
);
9394

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

134135
if (
135136
args.length !== 2 ||
136-
!t.isNumericLiteral(id) ||
137+
!t.isStringLiteral(id) ||
137138
!t.isStringLiteral(source)
138139
) {
139140
throw new Error(
@@ -158,19 +159,19 @@ module.exports = (packager, ast) => {
158159
if (assets.get(mod.id)) {
159160
// Replace with nothing if the require call's result is not used.
160161
if (!isUnusedValue(path)) {
161-
let name = `$${mod.id}$exports`;
162+
let name = getName(mod, 'exports');
162163
node = t.identifier(replacements.get(name) || name);
163164
}
164165

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

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

185186
if (
186187
args.length !== 2 ||
187-
!t.isNumericLiteral(id) ||
188+
!t.isStringLiteral(id) ||
188189
!t.isStringLiteral(source)
189190
) {
190191
throw new Error(

src/scope-hoisting/hoist.js

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const matchesPattern = require('../visitors/matches-pattern');
22
const t = require('babel-types');
33
const template = require('babel-template');
44
const rename = require('./renamer');
5+
const {getName, getIdentifier, getExportIdentifier} = require('./utils');
56

67
const WRAPPER_TEMPLATE = template(`
78
var NAME = (function () {
@@ -119,8 +120,8 @@ module.exports = {
119120

120121
// Rename each binding in the top-level scope to something unique.
121122
for (let name in scope.bindings) {
122-
if (!name.startsWith('$' + asset.id)) {
123-
let newName = '$' + asset.id + '$var$' + name;
123+
if (!name.startsWith('$' + t.toIdentifier(asset.id))) {
124+
let newName = getName(asset, 'var', name);
124125
rename(scope, name, newName);
125126
}
126127
}
@@ -160,7 +161,7 @@ module.exports = {
160161
}
161162

162163
if (matchesPattern(path.node, 'module.id')) {
163-
path.replaceWith(t.numericLiteral(asset.id));
164+
path.replaceWith(t.stringLiteral(asset.id));
164165
}
165166

166167
if (matchesPattern(path.node, 'module.hot')) {
@@ -302,7 +303,7 @@ module.exports = {
302303
// This will be replaced by the final variable name of the resolved asset in the packager.
303304
path.replaceWith(
304305
REQUIRE_CALL_TEMPLATE({
305-
ID: t.numericLiteral(asset.id),
306+
ID: t.stringLiteral(asset.id),
306307
SOURCE: t.stringLiteral(args[0].value)
307308
})
308309
);
@@ -311,7 +312,7 @@ module.exports = {
311312
if (matchesPattern(callee, 'require.resolve')) {
312313
path.replaceWith(
313314
REQUIRE_RESOLVE_CALL_TEMPLATE({
314-
ID: t.numericLiteral(asset.id),
315+
ID: t.stringLiteral(asset.id),
315316
SOURCE: args[0]
316317
})
317318
);
@@ -455,7 +456,7 @@ module.exports = {
455456
EXPORT_ALL_TEMPLATE({
456457
OLD_NAME: getExportsIdentifier(asset),
457458
SOURCE: t.stringLiteral(path.node.source.value),
458-
ID: t.numericLiteral(asset.id)
459+
ID: t.stringLiteral(asset.id)
459460
})
460461
);
461462
}
@@ -464,7 +465,7 @@ module.exports = {
464465
function addImport(asset, path) {
465466
// Replace with a $parcel$require call so we know where to insert side effects.
466467
let requireCall = REQUIRE_CALL_TEMPLATE({
467-
ID: t.numericLiteral(asset.id),
468+
ID: t.stringLiteral(asset.id),
468469
SOURCE: t.stringLiteral(path.node.source.value)
469470
});
470471

@@ -539,29 +540,6 @@ function safeRename(path, asset, from, to) {
539540
}
540541
}
541542

542-
function getName(asset, type, ...rest) {
543-
return (
544-
'$' +
545-
asset.id +
546-
'$' +
547-
type +
548-
(rest.length
549-
? '$' +
550-
rest
551-
.map(name => (name === 'default' ? name : t.toIdentifier(name)))
552-
.join('$')
553-
: '')
554-
);
555-
}
556-
557-
function getIdentifier(asset, type, ...rest) {
558-
return t.identifier(getName(asset, type, ...rest));
559-
}
560-
561-
function getExportIdentifier(asset, name) {
562-
return getIdentifier(asset, 'export', name);
563-
}
564-
565543
function getExportsIdentifier(asset, scope) {
566544
if (scope && scope.getData('shouldWrap')) {
567545
return t.identifier('exports');

src/scope-hoisting/shake.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const t = require('babel-types');
22

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

55
/**
66
* This is a small small implementation of dead code removal specialized to handle

0 commit comments

Comments
 (0)