1 | const Path = require('path');
|
2 | const crypto = require('crypto');
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | class Bundle {
|
11 | constructor(type, name, parent, options = {}) {
|
12 | this.type = type;
|
13 | this.name = name;
|
14 | this.parentBundle = parent;
|
15 | this.entryAsset = null;
|
16 | this.assets = new Set();
|
17 | this.childBundles = new Set();
|
18 | this.siblingBundles = new Set();
|
19 | this.siblingBundlesMap = new Map();
|
20 | this.offsets = new Map();
|
21 | this.totalSize = 0;
|
22 | this.bundleTime = 0;
|
23 | this.isolated = options.isolated;
|
24 | }
|
25 |
|
26 | static createWithAsset(asset, parentBundle, options) {
|
27 | let bundle = new Bundle(
|
28 | asset.type,
|
29 | Path.join(asset.options.outDir, asset.generateBundleName()),
|
30 | parentBundle,
|
31 | options
|
32 | );
|
33 |
|
34 | bundle.entryAsset = asset;
|
35 | bundle.addAsset(asset);
|
36 | return bundle;
|
37 | }
|
38 |
|
39 | addAsset(asset) {
|
40 | asset.bundles.add(this);
|
41 | this.assets.add(asset);
|
42 | if (
|
43 | this.type != 'map' &&
|
44 | this.type == asset.type &&
|
45 | asset.options.sourceMaps &&
|
46 | asset.sourceMaps
|
47 | ) {
|
48 | this.getSiblingBundle('map').addAsset(asset);
|
49 | }
|
50 | }
|
51 |
|
52 | removeAsset(asset) {
|
53 | asset.bundles.delete(this);
|
54 | this.assets.delete(asset);
|
55 | }
|
56 |
|
57 | addOffset(asset, line, column = 0) {
|
58 | this.offsets.set(asset, [line, column]);
|
59 | }
|
60 |
|
61 | getOffset(asset) {
|
62 | return this.offsets.get(asset) || [0, 0];
|
63 | }
|
64 |
|
65 | getSiblingBundle(type) {
|
66 | if (!type || type === this.type) {
|
67 | return this;
|
68 | }
|
69 |
|
70 | if (!this.siblingBundlesMap.has(type)) {
|
71 | let bundle = new Bundle(
|
72 | type,
|
73 | Path.join(
|
74 | Path.dirname(this.name),
|
75 |
|
76 |
|
77 | type === 'map'
|
78 | ? Path.basename(this.name) + '.' + type
|
79 | : Path.basename(this.name, Path.extname(this.name)) + '.' + type
|
80 | ),
|
81 | this
|
82 | );
|
83 |
|
84 | this.childBundles.add(bundle);
|
85 | this.siblingBundles.add(bundle);
|
86 | this.siblingBundlesMap.set(type, bundle);
|
87 | }
|
88 |
|
89 | return this.siblingBundlesMap.get(type);
|
90 | }
|
91 |
|
92 | createChildBundle(entryAsset, options = {}) {
|
93 | let bundle = Bundle.createWithAsset(entryAsset, this, options);
|
94 | this.childBundles.add(bundle);
|
95 | return bundle;
|
96 | }
|
97 |
|
98 | createSiblingBundle(entryAsset, options = {}) {
|
99 | let bundle = this.createChildBundle(entryAsset, options);
|
100 | this.siblingBundles.add(bundle);
|
101 | return bundle;
|
102 | }
|
103 |
|
104 | get isEmpty() {
|
105 | return this.assets.size === 0;
|
106 | }
|
107 |
|
108 | getBundleNameMap(contentHash, hashes = new Map()) {
|
109 | if (this.name) {
|
110 | let hashedName = this.getHashedBundleName(contentHash);
|
111 | hashes.set(Path.basename(this.name), hashedName);
|
112 | this.name = Path.join(Path.dirname(this.name), hashedName);
|
113 | }
|
114 |
|
115 | for (let child of this.childBundles.values()) {
|
116 | child.getBundleNameMap(contentHash, hashes);
|
117 | }
|
118 |
|
119 | return hashes;
|
120 | }
|
121 |
|
122 | getHashedBundleName(contentHash) {
|
123 |
|
124 |
|
125 |
|
126 | if (this.type == 'map') {
|
127 | return this.parentBundle.getHashedBundleName(contentHash) + '.map';
|
128 | }
|
129 |
|
130 | let basename = Path.basename(this.name);
|
131 |
|
132 | let ext = Path.extname(basename);
|
133 | let hash = (contentHash
|
134 | ? this.getHash()
|
135 | : Path.basename(this.name, ext)
|
136 | ).slice(-8);
|
137 | let entryAsset = this;
|
138 | while (!entryAsset.entryAsset && entryAsset.parentBundle) {
|
139 | entryAsset = entryAsset.parentBundle;
|
140 | }
|
141 | entryAsset = entryAsset.entryAsset;
|
142 | let name = Path.basename(entryAsset.name, Path.extname(entryAsset.name));
|
143 | let isMainEntry = entryAsset.options.entryFiles[0] === entryAsset.name;
|
144 | let isEntry =
|
145 | entryAsset.options.entryFiles.includes(entryAsset.name) ||
|
146 | Array.from(entryAsset.parentDeps).some(dep => dep.entry);
|
147 |
|
148 |
|
149 | if (isMainEntry && entryAsset.options.outFile) {
|
150 | let extname = Path.extname(entryAsset.options.outFile);
|
151 | if (extname) {
|
152 | ext = this.entryAsset ? extname : ext;
|
153 | name = Path.basename(entryAsset.options.outFile, extname);
|
154 | } else {
|
155 | name = entryAsset.options.outFile;
|
156 | }
|
157 | }
|
158 |
|
159 |
|
160 |
|
161 | if (isEntry) {
|
162 | return Path.join(
|
163 | Path.relative(
|
164 | entryAsset.options.rootDir,
|
165 | Path.dirname(entryAsset.name)
|
166 | ),
|
167 | name + ext
|
168 | ).replace(/\.\.(\/|\\)/g, '__$1');
|
169 | }
|
170 |
|
171 |
|
172 |
|
173 | if (name === 'index') {
|
174 | name = Path.basename(Path.dirname(entryAsset.name));
|
175 | }
|
176 |
|
177 |
|
178 | return name + '.' + hash + ext;
|
179 | }
|
180 |
|
181 | async package(bundler, oldHashes, newHashes = new Map()) {
|
182 | let promises = [];
|
183 | let mappings = [];
|
184 |
|
185 | if (!this.isEmpty) {
|
186 | let hash = this.getHash();
|
187 | newHashes.set(this.name, hash);
|
188 |
|
189 | if (!oldHashes || oldHashes.get(this.name) !== hash) {
|
190 | promises.push(this._package(bundler));
|
191 | }
|
192 | }
|
193 |
|
194 | for (let bundle of this.childBundles.values()) {
|
195 | if (bundle.type === 'map') {
|
196 | mappings.push(bundle);
|
197 | } else {
|
198 | promises.push(bundle.package(bundler, oldHashes, newHashes));
|
199 | }
|
200 | }
|
201 |
|
202 | await Promise.all(promises);
|
203 | for (let bundle of mappings) {
|
204 | await bundle.package(bundler, oldHashes, newHashes);
|
205 | }
|
206 | return newHashes;
|
207 | }
|
208 |
|
209 | async _package(bundler) {
|
210 | let Packager = bundler.packagers.get(this.type);
|
211 | let packager = new Packager(this, bundler);
|
212 |
|
213 | let startTime = Date.now();
|
214 | await packager.setup();
|
215 | await packager.start();
|
216 |
|
217 | let included = new Set();
|
218 | for (let asset of this.assets) {
|
219 | await this._addDeps(asset, packager, included);
|
220 | }
|
221 |
|
222 | await packager.end();
|
223 |
|
224 | this.totalSize = packager.getSize();
|
225 |
|
226 | let assetArray = Array.from(this.assets);
|
227 | let assetStartTime =
|
228 | this.type === 'map'
|
229 | ? 0
|
230 | : assetArray.sort((a, b) => a.startTime - b.startTime)[0].startTime;
|
231 | let assetEndTime =
|
232 | this.type === 'map'
|
233 | ? 0
|
234 | : assetArray.sort((a, b) => b.endTime - a.endTime)[0].endTime;
|
235 | let packagingTime = Date.now() - startTime;
|
236 | this.bundleTime = assetEndTime - assetStartTime + packagingTime;
|
237 | }
|
238 |
|
239 | async _addDeps(asset, packager, included) {
|
240 | if (!this.assets.has(asset) || included.has(asset)) {
|
241 | return;
|
242 | }
|
243 |
|
244 | included.add(asset);
|
245 |
|
246 | for (let depAsset of asset.depAssets.values()) {
|
247 | await this._addDeps(depAsset, packager, included);
|
248 | }
|
249 |
|
250 | await packager.addAsset(asset);
|
251 |
|
252 | const assetSize = packager.getSize() - this.totalSize;
|
253 | if (assetSize > 0) {
|
254 | this.addAssetSize(asset, assetSize);
|
255 | }
|
256 | }
|
257 |
|
258 | addAssetSize(asset, size) {
|
259 | asset.bundledSize = size;
|
260 | this.totalSize += size;
|
261 | }
|
262 |
|
263 | getParents() {
|
264 | let parents = [];
|
265 | let bundle = this;
|
266 |
|
267 | while (bundle) {
|
268 | parents.push(bundle);
|
269 | bundle = bundle.parentBundle;
|
270 | }
|
271 |
|
272 | return parents;
|
273 | }
|
274 |
|
275 | findCommonAncestor(bundle) {
|
276 |
|
277 | let ourParents = this.getParents();
|
278 | let theirParents = bundle.getParents();
|
279 |
|
280 |
|
281 | let a = ourParents.pop();
|
282 | let b = theirParents.pop();
|
283 | let last;
|
284 | while (a === b && ourParents.length > 0 && theirParents.length > 0) {
|
285 | last = a;
|
286 | a = ourParents.pop();
|
287 | b = theirParents.pop();
|
288 | }
|
289 |
|
290 | if (a === b) {
|
291 |
|
292 | return a;
|
293 | }
|
294 |
|
295 | return last;
|
296 | }
|
297 |
|
298 | getHash() {
|
299 | let hash = crypto.createHash('md5');
|
300 | for (let asset of this.assets) {
|
301 | hash.update(asset.hash);
|
302 | }
|
303 |
|
304 | return hash.digest('hex');
|
305 | }
|
306 | }
|
307 |
|
308 | module.exports = Bundle;
|