UNPKG

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