UNPKG

12.7 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const asyncLib = require("neo-async");
9const Queue = require("./util/Queue");
10
11/** @typedef {import("./Compiler")} Compiler */
12/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
13/** @typedef {import("./Dependency")} Dependency */
14/** @typedef {import("./Dependency").ExportSpec} ExportSpec */
15/** @typedef {import("./Dependency").ExportsSpec} ExportsSpec */
16/** @typedef {import("./ExportsInfo")} ExportsInfo */
17/** @typedef {import("./Module")} Module */
18
19class FlagDependencyExportsPlugin {
20 /**
21 * Apply the plugin
22 * @param {Compiler} compiler the compiler instance
23 * @returns {void}
24 */
25 apply(compiler) {
26 compiler.hooks.compilation.tap(
27 "FlagDependencyExportsPlugin",
28 compilation => {
29 const moduleGraph = compilation.moduleGraph;
30 const cache = compilation.getCache("FlagDependencyExportsPlugin");
31 compilation.hooks.finishModules.tapAsync(
32 "FlagDependencyExportsPlugin",
33 (modules, callback) => {
34 const logger = compilation.getLogger(
35 "webpack.FlagDependencyExportsPlugin"
36 );
37 let statRestoredFromCache = 0;
38 let statNoExports = 0;
39 let statFlaggedUncached = 0;
40 let statNotCached = 0;
41 let statQueueItemsProcessed = 0;
42
43 /** @type {Queue<Module>} */
44 const queue = new Queue();
45
46 // Step 1: Try to restore cached provided export info from cache
47 logger.time("restore cached provided exports");
48 asyncLib.each(
49 modules,
50 (module, callback) => {
51 const exportsInfo = moduleGraph.getExportsInfo(module);
52 if (!module.buildMeta || !module.buildMeta.exportsType) {
53 if (exportsInfo.otherExportsInfo.provided !== null) {
54 // It's a module without declared exports
55 statNoExports++;
56 exportsInfo.setHasProvideInfo();
57 exportsInfo.setUnknownExportsProvided();
58 return callback();
59 }
60 }
61 if (
62 module.buildInfo.cacheable !== true ||
63 typeof module.buildInfo.hash !== "string"
64 ) {
65 statFlaggedUncached++;
66 // Enqueue uncacheable module for determining the exports
67 queue.enqueue(module);
68 exportsInfo.setHasProvideInfo();
69 return callback();
70 }
71 cache.get(
72 module.identifier(),
73 module.buildInfo.hash,
74 (err, result) => {
75 if (err) return callback(err);
76
77 if (result !== undefined) {
78 statRestoredFromCache++;
79 moduleGraph
80 .getExportsInfo(module)
81 .restoreProvided(result);
82 } else {
83 statNotCached++;
84 // Without cached info enqueue module for determining the exports
85 queue.enqueue(module);
86 exportsInfo.setHasProvideInfo();
87 }
88 callback();
89 }
90 );
91 },
92 err => {
93 logger.timeEnd("restore cached provided exports");
94 if (err) return callback(err);
95
96 /** @type {Set<Module>} */
97 const modulesToStore = new Set();
98
99 /** @type {Map<Module, Set<Module>>} */
100 const dependencies = new Map();
101
102 /** @type {Module} */
103 let module;
104
105 /** @type {ExportsInfo} */
106 let exportsInfo;
107
108 /** @type {Map<Dependency, ExportsSpec>} */
109 const exportsSpecsFromDependencies = new Map();
110
111 let cacheable = true;
112 let changed = false;
113
114 /**
115 * @param {DependenciesBlock} depBlock the dependencies block
116 * @returns {void}
117 */
118 const processDependenciesBlock = depBlock => {
119 for (const dep of depBlock.dependencies) {
120 processDependency(dep);
121 }
122 for (const block of depBlock.blocks) {
123 processDependenciesBlock(block);
124 }
125 };
126
127 /**
128 * @param {Dependency} dep the dependency
129 * @returns {void}
130 */
131 const processDependency = dep => {
132 const exportDesc = dep.getExports(moduleGraph);
133 if (!exportDesc) return;
134 exportsSpecsFromDependencies.set(dep, exportDesc);
135 };
136
137 /**
138 * @param {Dependency} dep dependency
139 * @param {ExportsSpec} exportDesc info
140 * @returns {void}
141 */
142 const processExportsSpec = (dep, exportDesc) => {
143 const exports = exportDesc.exports;
144 const globalCanMangle = exportDesc.canMangle;
145 const globalFrom = exportDesc.from;
146 const globalPriority = exportDesc.priority;
147 const globalTerminalBinding =
148 exportDesc.terminalBinding || false;
149 const exportDeps = exportDesc.dependencies;
150 if (exportDesc.hideExports) {
151 for (const name of exportDesc.hideExports) {
152 const exportInfo = exportsInfo.getExportInfo(name);
153 exportInfo.unsetTarget(dep);
154 }
155 }
156 if (exports === true) {
157 // unknown exports
158 if (
159 exportsInfo.setUnknownExportsProvided(
160 globalCanMangle,
161 exportDesc.excludeExports,
162 globalFrom && dep,
163 globalFrom,
164 globalPriority
165 )
166 ) {
167 changed = true;
168 }
169 } else if (Array.isArray(exports)) {
170 /**
171 * merge in new exports
172 * @param {ExportsInfo} exportsInfo own exports info
173 * @param {(ExportSpec | string)[]} exports list of exports
174 */
175 const mergeExports = (exportsInfo, exports) => {
176 for (const exportNameOrSpec of exports) {
177 let name;
178 let canMangle = globalCanMangle;
179 let terminalBinding = globalTerminalBinding;
180 let exports = undefined;
181 let from = globalFrom;
182 let fromExport = undefined;
183 let priority = globalPriority;
184 let hidden = false;
185 if (typeof exportNameOrSpec === "string") {
186 name = exportNameOrSpec;
187 } else {
188 name = exportNameOrSpec.name;
189 if (exportNameOrSpec.canMangle !== undefined)
190 canMangle = exportNameOrSpec.canMangle;
191 if (exportNameOrSpec.export !== undefined)
192 fromExport = exportNameOrSpec.export;
193 if (exportNameOrSpec.exports !== undefined)
194 exports = exportNameOrSpec.exports;
195 if (exportNameOrSpec.from !== undefined)
196 from = exportNameOrSpec.from;
197 if (exportNameOrSpec.priority !== undefined)
198 priority = exportNameOrSpec.priority;
199 if (exportNameOrSpec.terminalBinding !== undefined)
200 terminalBinding = exportNameOrSpec.terminalBinding;
201 if (exportNameOrSpec.hidden !== undefined)
202 hidden = exportNameOrSpec.hidden;
203 }
204 const exportInfo = exportsInfo.getExportInfo(name);
205
206 if (
207 exportInfo.provided === false ||
208 exportInfo.provided === null
209 ) {
210 exportInfo.provided = true;
211 changed = true;
212 }
213
214 if (
215 exportInfo.canMangleProvide !== false &&
216 canMangle === false
217 ) {
218 exportInfo.canMangleProvide = false;
219 changed = true;
220 }
221
222 if (terminalBinding && !exportInfo.terminalBinding) {
223 exportInfo.terminalBinding = true;
224 changed = true;
225 }
226
227 if (exports) {
228 const nestedExportsInfo = exportInfo.createNestedExportsInfo();
229 mergeExports(nestedExportsInfo, exports);
230 }
231
232 if (
233 from &&
234 (hidden
235 ? exportInfo.unsetTarget(dep)
236 : exportInfo.setTarget(
237 dep,
238 from,
239 fromExport === undefined ? [name] : fromExport,
240 priority
241 ))
242 ) {
243 changed = true;
244 }
245
246 // Recalculate target exportsInfo
247 const target = exportInfo.getTarget(moduleGraph);
248 let targetExportsInfo = undefined;
249 if (target) {
250 const targetModuleExportsInfo = moduleGraph.getExportsInfo(
251 target.module
252 );
253 targetExportsInfo = targetModuleExportsInfo.getNestedExportsInfo(
254 target.export
255 );
256 // add dependency for this module
257 const set = dependencies.get(target.module);
258 if (set === undefined) {
259 dependencies.set(target.module, new Set([module]));
260 } else {
261 set.add(module);
262 }
263 }
264
265 if (exportInfo.exportsInfoOwned) {
266 if (
267 exportInfo.exportsInfo.setRedirectNamedTo(
268 targetExportsInfo
269 )
270 ) {
271 changed = true;
272 }
273 } else if (
274 exportInfo.exportsInfo !== targetExportsInfo
275 ) {
276 exportInfo.exportsInfo = targetExportsInfo;
277 changed = true;
278 }
279 }
280 };
281 mergeExports(exportsInfo, exports);
282 }
283 // store dependencies
284 if (exportDeps) {
285 cacheable = false;
286 for (const exportDependency of exportDeps) {
287 // add dependency for this module
288 const set = dependencies.get(exportDependency);
289 if (set === undefined) {
290 dependencies.set(exportDependency, new Set([module]));
291 } else {
292 set.add(module);
293 }
294 }
295 }
296 };
297
298 const notifyDependencies = () => {
299 const deps = dependencies.get(module);
300 if (deps !== undefined) {
301 for (const dep of deps) {
302 queue.enqueue(dep);
303 }
304 }
305 };
306
307 logger.time("figure out provided exports");
308 while (queue.length > 0) {
309 module = queue.dequeue();
310
311 statQueueItemsProcessed++;
312
313 exportsInfo = moduleGraph.getExportsInfo(module);
314
315 cacheable = true;
316 changed = false;
317
318 exportsSpecsFromDependencies.clear();
319 moduleGraph.freeze();
320 processDependenciesBlock(module);
321 moduleGraph.unfreeze();
322 for (const [
323 dep,
324 exportsSpec
325 ] of exportsSpecsFromDependencies) {
326 processExportsSpec(dep, exportsSpec);
327 }
328
329 if (cacheable) {
330 modulesToStore.add(module);
331 }
332
333 if (changed) {
334 notifyDependencies();
335 }
336 }
337 logger.timeEnd("figure out provided exports");
338
339 logger.log(
340 `${Math.round(
341 (100 * (statFlaggedUncached + statNotCached)) /
342 (statRestoredFromCache +
343 statNotCached +
344 statFlaggedUncached +
345 statNoExports)
346 )}% of exports of modules have been determined (${statNoExports} no declared exports, ${statNotCached} not cached, ${statFlaggedUncached} flagged uncacheable, ${statRestoredFromCache} from cache, ${
347 statQueueItemsProcessed -
348 statNotCached -
349 statFlaggedUncached
350 } additional calculations due to dependencies)`
351 );
352
353 logger.time("store provided exports into cache");
354 asyncLib.each(
355 modulesToStore,
356 (module, callback) => {
357 if (
358 module.buildInfo.cacheable !== true ||
359 typeof module.buildInfo.hash !== "string"
360 ) {
361 // not cacheable
362 return callback();
363 }
364 cache.store(
365 module.identifier(),
366 module.buildInfo.hash,
367 moduleGraph
368 .getExportsInfo(module)
369 .getRestoreProvidedData(),
370 callback
371 );
372 },
373 err => {
374 logger.timeEnd("store provided exports into cache");
375 callback(err);
376 }
377 );
378 }
379 );
380 }
381 );
382
383 /** @type {WeakMap<Module, any>} */
384 const providedExportsCache = new WeakMap();
385 compilation.hooks.rebuildModule.tap(
386 "FlagDependencyExportsPlugin",
387 module => {
388 providedExportsCache.set(
389 module,
390 moduleGraph.getExportsInfo(module).getRestoreProvidedData()
391 );
392 }
393 );
394 compilation.hooks.finishRebuildingModule.tap(
395 "FlagDependencyExportsPlugin",
396 module => {
397 moduleGraph
398 .getExportsInfo(module)
399 .restoreProvided(providedExportsCache.get(module));
400 }
401 );
402 }
403 );
404 }
405}
406
407module.exports = FlagDependencyExportsPlugin;
408
\No newline at end of file