1 | const path = require('path');
|
2 | const Packager = require('./Packager');
|
3 | const getExisting = require('../utils/getExisting');
|
4 | const urlJoin = require('../utils/urlJoin');
|
5 | const lineCounter = require('../utils/lineCounter');
|
6 | const objectHash = require('../utils/objectHash');
|
7 |
|
8 | const prelude = getExisting(
|
9 | path.join(__dirname, '../builtins/prelude.min.js'),
|
10 | path.join(__dirname, '../builtins/prelude.js')
|
11 | );
|
12 |
|
13 | class JSPackager extends Packager {
|
14 | async start() {
|
15 | this.first = true;
|
16 | this.dedupe = new Map();
|
17 | this.bundleLoaders = new Set();
|
18 | this.externalModules = new Set();
|
19 |
|
20 | let preludeCode = this.options.minify ? prelude.minified : prelude.source;
|
21 | if (this.options.target === 'electron') {
|
22 | preludeCode =
|
23 | `process.env.HMR_PORT=${
|
24 | this.options.hmrPort
|
25 | };process.env.HMR_HOSTNAME=${JSON.stringify(
|
26 | this.options.hmrHostname
|
27 | )};` + preludeCode;
|
28 | }
|
29 | await this.write(preludeCode + '({');
|
30 | this.lineOffset = lineCounter(preludeCode);
|
31 | }
|
32 |
|
33 | async addAsset(asset) {
|
34 |
|
35 |
|
36 | let isExposed = !Array.from(asset.parentDeps).every(dep => {
|
37 | let depAsset = this.bundler.loadedAssets.get(dep.parent);
|
38 | return this.bundle.assets.has(depAsset) || depAsset.type !== 'js';
|
39 | });
|
40 |
|
41 | if (!isExposed) {
|
42 | let key = this.dedupeKey(asset);
|
43 | if (this.dedupe.has(key)) {
|
44 | return;
|
45 | }
|
46 |
|
47 |
|
48 | if (!this.options.hmr) {
|
49 | this.dedupe.set(key, asset.id);
|
50 | }
|
51 | }
|
52 |
|
53 | let deps = {};
|
54 | for (let [dep, mod] of asset.depAssets) {
|
55 |
|
56 | if (dep.dynamic) {
|
57 | let bundles = [this.getBundleSpecifier(mod.parentBundle)];
|
58 | for (let child of mod.parentBundle.siblingBundles) {
|
59 | if (!child.isEmpty) {
|
60 | bundles.push(this.getBundleSpecifier(child));
|
61 | this.bundleLoaders.add(child.type);
|
62 | }
|
63 | }
|
64 |
|
65 | bundles.push(mod.id);
|
66 | deps[dep.name] = bundles;
|
67 | this.bundleLoaders.add(mod.type);
|
68 | } else {
|
69 | deps[dep.name] = this.dedupe.get(this.dedupeKey(mod)) || mod.id;
|
70 |
|
71 |
|
72 |
|
73 |
|
74 | if (!this.bundle.assets.has(mod)) {
|
75 | this.externalModules.add(mod);
|
76 | if (
|
77 | !this.bundle.parentBundle ||
|
78 | this.bundle.isolated ||
|
79 | this.bundle.parentBundle.type !== 'js'
|
80 | ) {
|
81 | this.bundleLoaders.add(mod.type);
|
82 | }
|
83 | }
|
84 | }
|
85 | }
|
86 |
|
87 | this.bundle.addOffset(asset, this.lineOffset);
|
88 | await this.writeModule(
|
89 | asset.id,
|
90 | asset.generated.js,
|
91 | deps,
|
92 | asset.generated.map
|
93 | );
|
94 | }
|
95 |
|
96 | getBundleSpecifier(bundle) {
|
97 | let name = path.relative(path.dirname(this.bundle.name), bundle.name);
|
98 | if (bundle.entryAsset) {
|
99 | return [name, bundle.entryAsset.id];
|
100 | }
|
101 |
|
102 | return name;
|
103 | }
|
104 |
|
105 | dedupeKey(asset) {
|
106 |
|
107 |
|
108 |
|
109 | let deps = Array.from(asset.depAssets.values(), dep => dep.name).sort();
|
110 | return objectHash([asset.generated.js, deps]);
|
111 | }
|
112 |
|
113 | async writeModule(id, code, deps = {}, map) {
|
114 | let wrapped = this.first ? '' : ',';
|
115 | wrapped +=
|
116 | JSON.stringify(id) +
|
117 | ':[function(require,module,exports) {\n' +
|
118 | (code || '') +
|
119 | '\n},';
|
120 | wrapped += JSON.stringify(deps);
|
121 | wrapped += ']';
|
122 |
|
123 | this.first = false;
|
124 | await this.write(wrapped);
|
125 |
|
126 |
|
127 | let lineCount = map && map.lineCount ? map.lineCount : lineCounter(code);
|
128 | this.lineOffset += 1 + lineCount;
|
129 | }
|
130 |
|
131 | async addAssetToBundle(asset) {
|
132 | if (this.bundle.assets.has(asset)) {
|
133 | return;
|
134 | }
|
135 | this.bundle.addAsset(asset);
|
136 | if (!asset.parentBundle) {
|
137 | asset.parentBundle = this.bundle;
|
138 | }
|
139 |
|
140 |
|
141 | for (let child of asset.depAssets.values()) {
|
142 | await this.addAssetToBundle(child);
|
143 | }
|
144 |
|
145 | await this.addAsset(asset);
|
146 | }
|
147 |
|
148 | async writeBundleLoaders() {
|
149 | if (this.bundleLoaders.size === 0) {
|
150 | return false;
|
151 | }
|
152 |
|
153 | let bundleLoader = this.bundler.loadedAssets.get(
|
154 | require.resolve('../builtins/bundle-loader')
|
155 | );
|
156 | if (this.externalModules.size > 0 && !bundleLoader) {
|
157 | bundleLoader = await this.bundler.getAsset('_bundle_loader');
|
158 | }
|
159 |
|
160 | if (bundleLoader) {
|
161 | await this.addAssetToBundle(bundleLoader);
|
162 | } else {
|
163 | return;
|
164 | }
|
165 |
|
166 |
|
167 | let loads = 'var b=require(' + JSON.stringify(bundleLoader.id) + ');';
|
168 | for (let bundleType of this.bundleLoaders) {
|
169 | let loader = this.options.bundleLoaders[bundleType];
|
170 | if (loader) {
|
171 | let target = this.options.target === 'node' ? 'node' : 'browser';
|
172 | let asset = await this.bundler.getAsset(loader[target]);
|
173 | await this.addAssetToBundle(asset);
|
174 | loads +=
|
175 | 'b.register(' +
|
176 | JSON.stringify(bundleType) +
|
177 | ',require(' +
|
178 | JSON.stringify(asset.id) +
|
179 | '));';
|
180 | }
|
181 | }
|
182 |
|
183 |
|
184 | if (this.externalModules.size > 0) {
|
185 | let preload = [];
|
186 | for (let mod of this.externalModules) {
|
187 |
|
188 | let bundle = Array.from(mod.bundles).find(b => b.entryAsset === mod);
|
189 | if (bundle) {
|
190 | preload.push([path.basename(bundle.name), mod.id]);
|
191 | }
|
192 | }
|
193 |
|
194 | loads += 'b.load(' + JSON.stringify(preload) + ')';
|
195 | if (this.bundle.entryAsset) {
|
196 | loads += `.then(function(){require(${JSON.stringify(
|
197 | this.bundle.entryAsset.id
|
198 | )});})`;
|
199 | }
|
200 |
|
201 | loads += ';';
|
202 | }
|
203 |
|
204 |
|
205 | await this.writeModule(0, loads, {});
|
206 | return true;
|
207 | }
|
208 |
|
209 | async end() {
|
210 | let entry = [];
|
211 |
|
212 |
|
213 | if (this.options.hmr) {
|
214 | let asset = await this.bundler.getAsset(
|
215 | require.resolve('../builtins/hmr-runtime')
|
216 | );
|
217 | await this.addAssetToBundle(asset);
|
218 | entry.push(asset.id);
|
219 | }
|
220 |
|
221 | if (await this.writeBundleLoaders()) {
|
222 | entry.push(0);
|
223 | }
|
224 |
|
225 | if (this.bundle.entryAsset && this.externalModules.size === 0) {
|
226 | entry.push(this.bundle.entryAsset.id);
|
227 | }
|
228 |
|
229 | await this.write(
|
230 | '},{},' +
|
231 | JSON.stringify(entry) +
|
232 | ', ' +
|
233 | JSON.stringify(this.options.global || null) +
|
234 | ')'
|
235 | );
|
236 | if (this.options.sourceMaps) {
|
237 |
|
238 | let mapBundle = this.bundle.siblingBundlesMap.get('map');
|
239 | if (mapBundle) {
|
240 | let mapUrl = urlJoin(
|
241 | this.options.publicURL,
|
242 | path.relative(this.options.outDir, mapBundle.name)
|
243 | );
|
244 | await this.write(`\n//# sourceMappingURL=${mapUrl}`);
|
245 | }
|
246 | }
|
247 | await super.end();
|
248 | }
|
249 | }
|
250 |
|
251 | module.exports = JSPackager;
|