UNPKG

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