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) {
|
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 | }
|
24 |
|
25 | static createWithAsset(asset, parentBundle) {
|
26 | let bundle = new Bundle(
|
27 | asset.type,
|
28 | Path.join(asset.options.outDir, asset.generateBundleName()),
|
29 | parentBundle
|
30 | );
|
31 |
|
32 | bundle.entryAsset = asset;
|
33 | bundle.addAsset(asset);
|
34 | return bundle;
|
35 | }
|
36 |
|
37 | addAsset(asset) {
|
38 | asset.bundles.add(this);
|
39 | this.assets.add(asset);
|
40 | }
|
41 |
|
42 | removeAsset(asset) {
|
43 | asset.bundles.delete(this);
|
44 | this.assets.delete(asset);
|
45 | }
|
46 |
|
47 | addOffset(asset, line) {
|
48 | this.offsets.set(asset, line);
|
49 | }
|
50 |
|
51 | getOffset(asset) {
|
52 | return this.offsets.get(asset) || 0;
|
53 | }
|
54 |
|
55 | getSiblingBundle(type) {
|
56 | if (!type || type === this.type) {
|
57 | return this;
|
58 | }
|
59 |
|
60 | if (!this.siblingBundlesMap.has(type)) {
|
61 | let bundle = new Bundle(
|
62 | type,
|
63 | Path.join(
|
64 | Path.dirname(this.name),
|
65 | Path.basename(this.name, Path.extname(this.name)) + '.' + type
|
66 | ),
|
67 | this
|
68 | );
|
69 |
|
70 | this.childBundles.add(bundle);
|
71 | this.siblingBundles.add(bundle);
|
72 | this.siblingBundlesMap.set(type, bundle);
|
73 | }
|
74 |
|
75 | return this.siblingBundlesMap.get(type);
|
76 | }
|
77 |
|
78 | createChildBundle(entryAsset) {
|
79 | let bundle = Bundle.createWithAsset(entryAsset, this);
|
80 | this.childBundles.add(bundle);
|
81 | return bundle;
|
82 | }
|
83 |
|
84 | createSiblingBundle(entryAsset) {
|
85 | let bundle = this.createChildBundle(entryAsset);
|
86 | this.siblingBundles.add(bundle);
|
87 | return bundle;
|
88 | }
|
89 |
|
90 | get isEmpty() {
|
91 | return this.assets.size === 0;
|
92 | }
|
93 |
|
94 | getBundleNameMap(contentHash, hashes = new Map()) {
|
95 | let hashedName = this.getHashedBundleName(contentHash);
|
96 | hashes.set(Path.basename(this.name), hashedName);
|
97 | this.name = Path.join(Path.dirname(this.name), hashedName);
|
98 |
|
99 | for (let child of this.childBundles.values()) {
|
100 | child.getBundleNameMap(contentHash, hashes);
|
101 | }
|
102 |
|
103 | return hashes;
|
104 | }
|
105 |
|
106 | getHashedBundleName(contentHash) {
|
107 |
|
108 |
|
109 | let ext = Path.extname(this.name);
|
110 | let hash = (contentHash
|
111 | ? this.getHash()
|
112 | : Path.basename(this.name, ext)
|
113 | ).slice(-8);
|
114 | let entryAsset = this.entryAsset || this.parentBundle.entryAsset;
|
115 | let name = Path.basename(entryAsset.name, Path.extname(entryAsset.name));
|
116 | let isMainEntry = entryAsset.name === entryAsset.options.mainFile;
|
117 | let isEntry =
|
118 | isMainEntry || Array.from(entryAsset.parentDeps).some(dep => dep.entry);
|
119 |
|
120 |
|
121 | if (isMainEntry && entryAsset.options.outFile) {
|
122 | name = entryAsset.options.outFile;
|
123 | }
|
124 |
|
125 |
|
126 |
|
127 | if (isEntry) {
|
128 | return Path.join(
|
129 | Path.relative(
|
130 | Path.dirname(entryAsset.options.mainFile),
|
131 | Path.dirname(entryAsset.name)
|
132 | ),
|
133 | name + ext
|
134 | );
|
135 | }
|
136 |
|
137 |
|
138 |
|
139 | if (name === 'index') {
|
140 | name = Path.basename(Path.dirname(entryAsset.name));
|
141 | }
|
142 |
|
143 |
|
144 | return name + '.' + hash + ext;
|
145 | }
|
146 |
|
147 | async package(bundler, oldHashes, newHashes = new Map()) {
|
148 | if (this.isEmpty) {
|
149 | return newHashes;
|
150 | }
|
151 |
|
152 | let hash = this.getHash();
|
153 | newHashes.set(this.name, hash);
|
154 |
|
155 | let promises = [];
|
156 | let mappings = [];
|
157 | if (!oldHashes || oldHashes.get(this.name) !== hash) {
|
158 | promises.push(this._package(bundler));
|
159 | }
|
160 |
|
161 | for (let bundle of this.childBundles.values()) {
|
162 | if (bundle.type === 'map') {
|
163 | mappings.push(bundle);
|
164 | } else {
|
165 | promises.push(bundle.package(bundler, oldHashes, newHashes));
|
166 | }
|
167 | }
|
168 |
|
169 | await Promise.all(promises);
|
170 | for (let bundle of mappings) {
|
171 | await bundle.package(bundler, oldHashes, newHashes);
|
172 | }
|
173 | return newHashes;
|
174 | }
|
175 |
|
176 | async _package(bundler) {
|
177 | let Packager = bundler.packagers.get(this.type);
|
178 | let packager = new Packager(this, bundler);
|
179 |
|
180 | let startTime = Date.now();
|
181 | await packager.setup();
|
182 | await packager.start();
|
183 |
|
184 | let included = new Set();
|
185 | for (let asset of this.assets) {
|
186 | await this._addDeps(asset, packager, included);
|
187 | }
|
188 |
|
189 | await packager.end();
|
190 |
|
191 | this.bundleTime = Date.now() - startTime;
|
192 | for (let asset of this.assets) {
|
193 | this.bundleTime += asset.buildTime;
|
194 | }
|
195 | }
|
196 |
|
197 | async _addDeps(asset, packager, included) {
|
198 | if (!this.assets.has(asset) || included.has(asset)) {
|
199 | return;
|
200 | }
|
201 |
|
202 | included.add(asset);
|
203 |
|
204 | for (let depAsset of asset.depAssets.values()) {
|
205 | await this._addDeps(depAsset, packager, included);
|
206 | }
|
207 |
|
208 | await packager.addAsset(asset);
|
209 | this.addAssetSize(asset, packager.getSize() - this.totalSize);
|
210 | }
|
211 |
|
212 | addAssetSize(asset, size) {
|
213 | asset.bundledSize = size;
|
214 | this.totalSize += size;
|
215 | }
|
216 |
|
217 | getParents() {
|
218 | let parents = [];
|
219 | let bundle = this;
|
220 |
|
221 | while (bundle) {
|
222 | parents.push(bundle);
|
223 | bundle = bundle.parentBundle;
|
224 | }
|
225 |
|
226 | return parents;
|
227 | }
|
228 |
|
229 | findCommonAncestor(bundle) {
|
230 |
|
231 | let ourParents = this.getParents();
|
232 | let theirParents = bundle.getParents();
|
233 |
|
234 |
|
235 | let a = ourParents.pop();
|
236 | let b = theirParents.pop();
|
237 | let last;
|
238 | while (a === b && ourParents.length > 0 && theirParents.length > 0) {
|
239 | last = a;
|
240 | a = ourParents.pop();
|
241 | b = theirParents.pop();
|
242 | }
|
243 |
|
244 | if (a === b) {
|
245 |
|
246 | return a;
|
247 | }
|
248 |
|
249 | return last;
|
250 | }
|
251 |
|
252 | getHash() {
|
253 | let hash = crypto.createHash('md5');
|
254 | for (let asset of this.assets) {
|
255 | hash.update(asset.hash);
|
256 | }
|
257 |
|
258 | return hash.digest('hex');
|
259 | }
|
260 | }
|
261 |
|
262 | module.exports = Bundle;
|