UNPKG

11.9 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
6
7const Path = require('path');
8
9const crypto = require('crypto');
10/**
11 * A Bundle represents an output file, containing multiple assets. Bundles can have
12 * child bundles, which are bundles that are loaded dynamically from this bundle.
13 * Child bundles are also produced when importing an asset of a different type from
14 * the bundle, e.g. importing a CSS file from JS.
15 */
16
17
18class Bundle {
19 constructor(type, name, parent, options = {}) {
20 this.type = type;
21 this.name = name;
22 this.parentBundle = parent;
23 this.entryAsset = null;
24 this.assets = new Set();
25 this.childBundles = new Set();
26 this.siblingBundles = new Set();
27 this.siblingBundlesMap = new Map();
28 this.offsets = new Map();
29 this.totalSize = 0;
30 this.bundleTime = 0;
31 this.isolated = options.isolated;
32 }
33
34 static createWithAsset(asset, parentBundle, options) {
35 let bundle = new Bundle(asset.type, Path.join(asset.options.outDir, asset.generateBundleName()), parentBundle, options);
36 bundle.entryAsset = asset;
37 bundle.addAsset(asset);
38 return bundle;
39 }
40
41 addAsset(asset) {
42 asset.bundles.add(this);
43 this.assets.add(asset);
44
45 if (this.type != 'map' && this.type == asset.type && asset.options.sourceMaps && asset.sourceMaps) {
46 this.getSiblingBundle('map').addAsset(asset);
47 }
48 }
49
50 removeAsset(asset) {
51 asset.bundles.delete(this);
52 this.assets.delete(asset);
53 }
54
55 addOffset(asset, line, column = 0) {
56 this.offsets.set(asset, [line, column]);
57 }
58
59 getOffset(asset) {
60 return this.offsets.get(asset) || [0, 0];
61 }
62
63 getSiblingBundle(type) {
64 if (!type || type === this.type) {
65 return this;
66 }
67
68 if (!this.siblingBundlesMap.has(type)) {
69 let bundle = new Bundle(type, Path.join(Path.dirname(this.name), // keep the original extension for source map files, so we have
70 // .js.map instead of just .map
71 type === 'map' ? Path.basename(this.name) + '.' + type : Path.basename(this.name, Path.extname(this.name)) + '.' + type), this);
72 this.childBundles.add(bundle);
73 this.siblingBundles.add(bundle);
74 this.siblingBundlesMap.set(type, bundle);
75 }
76
77 return this.siblingBundlesMap.get(type);
78 }
79
80 createChildBundle(entryAsset, options = {}) {
81 let bundle = Bundle.createWithAsset(entryAsset, this, options);
82 this.childBundles.add(bundle);
83 return bundle;
84 }
85
86 createSiblingBundle(entryAsset, options = {}) {
87 let bundle = this.createChildBundle(entryAsset, options);
88 this.siblingBundles.add(bundle);
89 return bundle;
90 }
91
92 get isEmpty() {
93 return this.assets.size === 0;
94 }
95
96 getBundleNameMap(contentHash, hashes = new Map()) {
97 if (this.name) {
98 let hashedName = this.getHashedBundleName(contentHash);
99 hashes.set(Path.basename(this.name), hashedName);
100 this.name = Path.join(Path.dirname(this.name), hashedName);
101 }
102
103 var _iteratorNormalCompletion = true;
104 var _didIteratorError = false;
105 var _iteratorError = undefined;
106
107 try {
108 for (var _iterator = this.childBundles.values()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
109 let child = _step.value;
110 child.getBundleNameMap(contentHash, hashes);
111 }
112 } catch (err) {
113 _didIteratorError = true;
114 _iteratorError = err;
115 } finally {
116 try {
117 if (!_iteratorNormalCompletion && _iterator.return != null) {
118 _iterator.return();
119 }
120 } finally {
121 if (_didIteratorError) {
122 throw _iteratorError;
123 }
124 }
125 }
126
127 return hashes;
128 }
129
130 getHashedBundleName(contentHash) {
131 // If content hashing is enabled, generate a hash from all assets in the bundle.
132 // Otherwise, use a hash of the filename so it remains consistent across builds.
133 if (this.type == 'map') {
134 return this.parentBundle.getHashedBundleName(contentHash) + '.map';
135 }
136
137 let basename = Path.basename(this.name);
138 let ext = Path.extname(basename);
139 let hash = (contentHash ? this.getHash() : Path.basename(this.name, ext)).slice(-8);
140 let entryAsset = this;
141
142 while (!entryAsset.entryAsset && entryAsset.parentBundle) {
143 entryAsset = entryAsset.parentBundle;
144 }
145
146 entryAsset = entryAsset.entryAsset;
147 let name = Path.basename(entryAsset.name, Path.extname(entryAsset.name));
148 let isMainEntry = entryAsset.options.entryFiles[0] === entryAsset.name;
149 let isEntry = entryAsset.options.entryFiles.includes(entryAsset.name) || Array.from(entryAsset.parentDeps).some(dep => dep.entry); // If this is the main entry file, use the output file option as the name if provided.
150
151 if (isMainEntry && entryAsset.options.outFile) {
152 let extname = Path.extname(entryAsset.options.outFile);
153
154 if (extname) {
155 ext = this.entryAsset ? extname : ext;
156 name = Path.basename(entryAsset.options.outFile, extname);
157 } else {
158 name = entryAsset.options.outFile;
159 }
160 } // If this is an entry asset, don't hash. Return a relative path
161 // from the main file so we keep the original file paths.
162
163
164 if (isEntry) {
165 return Path.join(Path.relative(entryAsset.options.rootDir, Path.dirname(entryAsset.name)), name + ext).replace(/\.\.(\/|\\)/g, '__$1');
166 } // If this is an index file, use the parent directory name instead
167 // which is probably more descriptive.
168
169
170 if (name === 'index') {
171 name = Path.basename(Path.dirname(entryAsset.name));
172 } // Add the content hash and extension.
173
174
175 return name + '.' + hash + ext;
176 }
177
178 package(bundler, oldHashes, newHashes = new Map()) {
179 var _this = this;
180
181 return (0, _asyncToGenerator2.default)(function* () {
182 let promises = [];
183 let mappings = [];
184
185 if (!_this.isEmpty) {
186 let hash = _this.getHash();
187
188 newHashes.set(_this.name, hash);
189
190 if (!oldHashes || oldHashes.get(_this.name) !== hash) {
191 promises.push(_this._package(bundler));
192 }
193 }
194
195 var _iteratorNormalCompletion2 = true;
196 var _didIteratorError2 = false;
197 var _iteratorError2 = undefined;
198
199 try {
200 for (var _iterator2 = _this.childBundles.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
201 let bundle = _step2.value;
202
203 if (bundle.type === 'map') {
204 mappings.push(bundle);
205 } else {
206 promises.push(bundle.package(bundler, oldHashes, newHashes));
207 }
208 }
209 } catch (err) {
210 _didIteratorError2 = true;
211 _iteratorError2 = err;
212 } finally {
213 try {
214 if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
215 _iterator2.return();
216 }
217 } finally {
218 if (_didIteratorError2) {
219 throw _iteratorError2;
220 }
221 }
222 }
223
224 yield Promise.all(promises);
225
226 for (var _i = 0; _i < mappings.length; _i++) {
227 let bundle = mappings[_i];
228 yield bundle.package(bundler, oldHashes, newHashes);
229 }
230
231 return newHashes;
232 })();
233 }
234
235 _package(bundler) {
236 var _this2 = this;
237
238 return (0, _asyncToGenerator2.default)(function* () {
239 let Packager = bundler.packagers.get(_this2.type);
240 let packager = new Packager(_this2, bundler);
241 let startTime = Date.now();
242 yield packager.setup();
243 yield packager.start();
244 let included = new Set();
245 var _iteratorNormalCompletion3 = true;
246 var _didIteratorError3 = false;
247 var _iteratorError3 = undefined;
248
249 try {
250 for (var _iterator3 = _this2.assets[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
251 let asset = _step3.value;
252 yield _this2._addDeps(asset, packager, included);
253 }
254 } catch (err) {
255 _didIteratorError3 = true;
256 _iteratorError3 = err;
257 } finally {
258 try {
259 if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
260 _iterator3.return();
261 }
262 } finally {
263 if (_didIteratorError3) {
264 throw _iteratorError3;
265 }
266 }
267 }
268
269 yield packager.end();
270 _this2.totalSize = packager.getSize();
271 let assetArray = Array.from(_this2.assets);
272 let assetStartTime = _this2.type === 'map' ? 0 : assetArray.sort((a, b) => a.startTime - b.startTime)[0].startTime;
273 let assetEndTime = _this2.type === 'map' ? 0 : assetArray.sort((a, b) => b.endTime - a.endTime)[0].endTime;
274 let packagingTime = Date.now() - startTime;
275 _this2.bundleTime = assetEndTime - assetStartTime + packagingTime;
276 })();
277 }
278
279 _addDeps(asset, packager, included) {
280 var _this3 = this;
281
282 return (0, _asyncToGenerator2.default)(function* () {
283 if (!_this3.assets.has(asset) || included.has(asset)) {
284 return;
285 }
286
287 included.add(asset);
288 var _iteratorNormalCompletion4 = true;
289 var _didIteratorError4 = false;
290 var _iteratorError4 = undefined;
291
292 try {
293 for (var _iterator4 = asset.depAssets.values()[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
294 let depAsset = _step4.value;
295 yield _this3._addDeps(depAsset, packager, included);
296 }
297 } catch (err) {
298 _didIteratorError4 = true;
299 _iteratorError4 = err;
300 } finally {
301 try {
302 if (!_iteratorNormalCompletion4 && _iterator4.return != null) {
303 _iterator4.return();
304 }
305 } finally {
306 if (_didIteratorError4) {
307 throw _iteratorError4;
308 }
309 }
310 }
311
312 yield packager.addAsset(asset);
313
314 const assetSize = packager.getSize() - _this3.totalSize;
315
316 if (assetSize > 0) {
317 _this3.addAssetSize(asset, assetSize);
318 }
319 })();
320 }
321
322 addAssetSize(asset, size) {
323 asset.bundledSize = size;
324 this.totalSize += size;
325 }
326
327 getParents() {
328 let parents = [];
329 let bundle = this;
330
331 while (bundle) {
332 parents.push(bundle);
333 bundle = bundle.parentBundle;
334 }
335
336 return parents;
337 }
338
339 findCommonAncestor(bundle) {
340 // Get a list of parent bundles going up to the root
341 let ourParents = this.getParents();
342 let theirParents = bundle.getParents(); // Start from the root bundle, and find the first bundle that's different
343
344 let a = ourParents.pop();
345 let b = theirParents.pop();
346 let last;
347
348 while (a === b && ourParents.length > 0 && theirParents.length > 0) {
349 last = a;
350 a = ourParents.pop();
351 b = theirParents.pop();
352 }
353
354 if (a === b) {
355 // One bundle descended from the other
356 return a;
357 }
358
359 return last;
360 }
361
362 getHash() {
363 let hash = crypto.createHash('md5');
364 var _iteratorNormalCompletion5 = true;
365 var _didIteratorError5 = false;
366 var _iteratorError5 = undefined;
367
368 try {
369 for (var _iterator5 = this.assets[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
370 let asset = _step5.value;
371 hash.update(asset.hash);
372 }
373 } catch (err) {
374 _didIteratorError5 = true;
375 _iteratorError5 = err;
376 } finally {
377 try {
378 if (!_iteratorNormalCompletion5 && _iterator5.return != null) {
379 _iterator5.return();
380 }
381 } finally {
382 if (_didIteratorError5) {
383 throw _iteratorError5;
384 }
385 }
386 }
387
388 return hash.digest('hex');
389 }
390
391}
392
393module.exports = Bundle;
\No newline at end of file