UNPKG

12.2 kBJavaScriptView Raw
1var rollup = require('rollup');
2var traverseTree = require('./arithmetic').traverseTree;
3var getConditionModule = require('./trace').getConditionModule;
4var extend = require('./utils').extend;
5var getAlias = require('./utils').getAlias;
6var pluginBundleHook = require('./compile').pluginBundleHook;
7
8exports.rollupTree = function(loader, tree, entryPoints, traceOpts, compileOpts, outputOpts) {
9 /*
10 * 1. Determine the tree entry points and optimization points
11 *
12 * eg for the tree:
13 *
14 * A -> B -> C
15 * D -> C
16 *
17 * A and D are the entry points.
18 * Optimization points are ES module entry points to be optimized
19 *
20 */
21
22 entryPoints = entryPoints.concat([]);
23
24 var optimizationPoints = [];
25
26 var entryMap = {};
27
28 function isESM(moduleName) {
29 return tree[moduleName] && tree[moduleName].metadata && tree[moduleName].metadata.format == 'esm' && !tree[moduleName].metadata.originalSource;
30 }
31
32 // for each module in the tree, we traverse the whole tree
33 // we then relate each module in the tree to the first traced entry point
34 Object.keys(tree).forEach(function(entryPoint) {
35 traverseTree(tree, entryPoint, function(depName, parentName) {
36 // esm from a non-esm parent means this is an optimization entry point from the linking alogorithm perspective
37 if (parentName && isESM(depName) && !isESM(parentName) && optimizationPoints.indexOf(depName) == -1)
38 optimizationPoints.push(depName);
39
40 // if we have a entryMap for the given module, then stop
41 if (entryMap[depName])
42 return false;
43
44 if (parentName)
45 entryMap[depName] = entryPoint;
46 }, traceOpts);
47 });
48
49 // the entry points are then the modules not represented in entryMap
50 Object.keys(tree).forEach(function(entryPoint) {
51 if (!entryMap[entryPoint] && tree[entryPoint] && entryPoints.indexOf(entryPoint) == -1)
52 entryPoints.push(entryPoint);
53 });
54
55 // if all the entry points are ES modules,
56 // then we can create a single dummy entry point
57 // that represents the tree
58 var esmEntryPoints = 0;
59 entryPoints.forEach(function(entryPoint) {
60 if (tree[entryPoint].metadata && tree[entryPoint].metadata.format == 'esm')
61 esmEntryPoints ++;
62 });
63
64 if (esmEntryPoints > 1 && esmEntryPoints == entryPoints.length) {
65 var dummySource = 'export * from "' + entryPoints[0] + '";\n';
66 var dummyDepMap = {};
67
68 entryPoints.forEach(function(entryPoint) {
69 dummyDepMap[entryPoint] = entryPoint;
70
71 dummySource += 'import "' + entryPoint + '";';
72 });
73
74 tree['@dummy-entry-point'] = {
75 name: '@dummy-entry-point',
76 path: null,
77 metadata: { format: 'esm' },
78 deps: entryPoints,
79 depMap: dummyDepMap,
80 source: dummySource
81 };
82 entryPoints = ['@dummy-entry-point'];
83 }
84
85 // optimization points are then es module entry points
86 entryPoints.forEach(function(entryPoint) {
87 if (isESM(entryPoint) && optimizationPoints.indexOf(entryPoint) == -1)
88 optimizationPoints.push(entryPoint);
89 });
90
91 /*
92 * 2. Determine unoptimizable modules, splitting them out into their own optimization points
93 *
94 * eg for the tree:
95 * A -> B -> C -> D
96 * E -> C -> D
97 *
98 * A, E are top-level entry points detected by the previous step
99 * (and hence optimization points if they are es modules)
100 * C is not optimizable because it has two unique parent entry points
101 * (which is what this step looks for)
102 * So C becomes its own optimization point
103 * Leading to D inlining into C and B inlining into A
104 *
105 */
106
107 // for each module in the tree, we track its parent optimization point
108 // as soon as a module has two parent entry points, it is not optimizable
109 // and we set it to undefined here. It then becomes its own optimizationPoint.
110 var optimizationParentMap = {};
111
112 // build up the parent entry point map as above
113 // we use for over forEach because this list will grow as we go
114 for (var i = 0; i < optimizationPoints.length; i++) {
115 var entryPoint = optimizationPoints[i];
116 traverseTree(tree, entryPoint, function(depName, parentName) {
117
118 // we only traverse ES module tree subgraphs
119 if (!isESM(depName))
120 return false;
121
122 if (depName == entryPoint)
123 return;
124
125 // dont traverse through other entry points
126 if (optimizationPoints.indexOf(depName) != -1)
127 return false;
128
129 if (!optimizationParentMap[depName]) {
130 optimizationParentMap[depName] = entryPoint;
131 return;
132 }
133
134 // module in two separate entry point graphs -> it becomes its own optimization entry point
135 if (optimizationParentMap[depName] != entryPoint) {
136 optimizationParentMap[depName] = undefined;
137
138 // this new optimization point will then be traversed in turn as part of this loop later
139 optimizationPoints.push(depName);
140 }
141 }, traceOpts);
142 }
143
144 /*
145 * 3. Given complete optimization points, populate subgraph externals
146 *
147 * eg for the graph
148 * A -> B -> C
149 *
150 * Where A is the optimization point, and C is not ESM, another optimization point,
151 * or not contained in our build tree, then we mark 'C' as an external.
152 *
153 * That is, optimizationGraphExternals[A] = [C]
154 *
155 * This externals input is used in the Rollup API.
156 * This way we just optimize B into A, retaining an explicit dependency on C.
157 */
158
159 var inlinedModules = [];
160 var optimizationGraphExternals = {};
161
162 optimizationPoints.forEach(function(entryPoint) {
163 // the subgraph object is the list of modules in the subgraph
164 // and the list of modules that are "external" boundaries of the subgraph
165 var externals = [];
166
167 // this traversal is a bit odd, since we need to traverse the full
168 // dependency graph to detect externals, not just the direct build graph
169 traverseTree(tree, entryPoint, function(depName, parentName) {
170 if (!isESM(depName) || (depName != entryPoint && optimizationPoints.indexOf(depName) != -1))
171 return false;
172
173 var depLoad = tree[depName];
174 depLoad.deps && depLoad.deps.forEach(function(depName) {
175 depName = depLoad.depMap[depName];
176 if (depName == entryPoint)
177 return;
178
179 // anything not ESM, not in the tree, or an optimization point, is external
180 if (!isESM(depName) || optimizationPoints.indexOf(depName) != -1) {
181 if (externals.indexOf(depName) == -1)
182 externals.push(depName);
183 }
184 else {
185 if (inlinedModules.indexOf(depName) == -1)
186 inlinedModules.push(depName);
187 }
188 }, traceOpts);
189 });
190
191 optimizationGraphExternals[entryPoint] = externals;
192 });
193
194 // finally we rollup each optimization graph
195 var rolledUpTree = {};
196 Object.keys(tree).forEach(function(moduleName) {
197 if (inlinedModules.indexOf(moduleName) == -1)
198 rolledUpTree[moduleName] = tree[moduleName];
199 });
200
201 // compute the inlineMap
202 var inlineMap = {};
203 inlinedModules.forEach(function(moduleName) {
204 var optimizationParent = optimizationParentMap[moduleName];
205 (inlineMap[optimizationParent] = inlineMap[optimizationParent] || []).push(moduleName);
206 });
207
208 // if every module in the tree is rolled-up, then we can do a full tree rollup
209 var fullTreeRollup = entryPoints.length == 1 && optimizationPoints.length == 1 && Object.keys(optimizationGraphExternals).length == 1;
210
211 return Promise.all(Object.keys(optimizationGraphExternals).map(function(entryPoint) {
212 var externals = optimizationGraphExternals[entryPoint];
213 var loadList = [];
214
215 // if all externals are outside the tree then this really is a full tree rollup
216 // also @node/x requires mean we do need sfx core (pending a better output of these)
217 if (fullTreeRollup)
218 externals.forEach(function(external) {
219 if (external.substr(0, 5) == '@node' || tree[external])
220 fullTreeRollup = false;
221 });
222
223 var aliasedExternals = externals.map(function(external) {
224 var alias = getAlias(loader, external) || externals;
225 if (alias.indexOf('#:') != -1)
226 alias = alias.replace('#:', '/');
227 return alias;
228 });
229
230 return rollup.rollup({
231 entry: entryPoint,
232 external: aliasedExternals,
233 plugins: [{
234 resolveId: function(id, importer, options) {
235 var resolved = importer ? tree[importer].depMap[id] : id;
236 var externalIndex = externals.indexOf(resolved);
237 if (externalIndex != -1)
238 return aliasedExternals[externalIndex];
239 return resolved;
240 },
241 load: function(id, options) {
242 if (loadList.indexOf(tree[id]) == -1)
243 loadList.push(tree[id]);
244 loadList.push(tree[id]);
245 return {
246 code: tree[id].metadata.originalSource || tree[id].source,
247 map: tree[id].metadata.sourceMap
248 };
249 }
250 }],
251 onwarn: function(message) {}
252 })
253 .then(function(bundle) {
254 var entryPointLoad = tree[entryPoint];
255
256 var defaultExport = compileOpts.defaultExport;
257 if (entryPointLoad.metadata.format == 'register')
258 throw new Error('Assertion failed: internal format should be "system" not "register".');
259
260 if (entryPointLoad.metadata.format != 'esm' && entryPointLoad.metadata.format != 'system')
261 defaultExport = true;
262
263 var generateOptions = {
264 sourceMap: !!compileOpts.sourceMaps,
265 exports: defaultExport ? 'default' : 'named',
266 dest: 'output.js' // workaround for rollup/rollup#1015
267 };
268
269 // for a full tree rollup, we pass all the output options into rollup itself
270 if (fullTreeRollup) {
271 generateOptions.format = compileOpts.format;
272 if (generateOptions.format == 'global')
273 generateOptions.format = 'iife';
274 if (generateOptions.format == 'esm')
275 generateOptions.format = 'es6';
276
277 if ((generateOptions.format == 'iife' || generateOptions.format == 'umd') &&
278 !compileOpts.globalName)
279 throw new Error('The globalName option must be set for full-tree rollup global and UMD builds.');
280
281 if (compileOpts.globalName)
282 generateOptions.moduleName = compileOpts.globalName;
283
284 if (compileOpts.globalDeps)
285 generateOptions.globals = compileOpts.globalDeps;
286 }
287
288 var output = bundle.generate(generateOptions);
289
290 // convert sources list into paths
291 if (output.map) {
292 output.map.sources = output.map.sources.map(function(name) {
293 name = loader.getCanonicalName(loader.decanonicalize(name));
294 return tree[name] && tree[name].path || loader.decanonicalize(name);
295 });
296 }
297
298 if (fullTreeRollup)
299 return {
300 source: output.code,
301 sourceMap: output.map
302 };
303
304 // replace the entry point module itself with the inlined subgraph module
305 var curInlined = inlineMap[entryPoint] || [];
306
307 // the process of running rollup will itself normalize all dependencies
308 // the depMap then just becomes the identity map for non-externals
309 var inlinedDepMap = {};
310 aliasedExternals.forEach(function(dep, index) {
311 inlinedDepMap[dep] = externals[index];
312 });
313
314 rolledUpTree[entryPoint] = extend(extend({}, entryPointLoad), {
315 deps: aliasedExternals,
316 depMap: inlinedDepMap,
317 metadata: extend(extend({}, entryPointLoad.metadata), {
318 originalSource: undefined,
319 sourceMap: output.map
320 }),
321 source: output.code,
322 compactedLoads: loadList
323 });
324 });
325 }))
326 .then(function(outputs) {
327 if (fullTreeRollup) {
328 // for the full tree rollup case, we need to run the plugin bundle hook as we skip compile entirely
329 return pluginBundleHook(loader, Object.keys(tree).map(function(name) {
330 return tree[name];
331 }).filter(function(load) {
332 return load;
333 }), compileOpts, outputOpts)
334 .then(function(pluginResult) {
335 return {
336 outputs: outputs.concat(pluginResult.outputs),
337 assetList: pluginResult.assetList
338 };
339 });
340 }
341
342 return {
343 tree: rolledUpTree,
344 inlineMap: inlineMap
345 };
346 });
347};