UNPKG

11.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("./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 (exports === true) {
126 // unknown exports
127 if (
128 exportsInfo.setUnknownExportsProvided(
129 globalCanMangle,
130 exportDesc.excludeExports,
131 globalFrom && dep,
132 globalFrom
133 )
134 ) {
135 changed = true;
136 }
137 } else if (Array.isArray(exports)) {
138 /**
139 * merge in new exports
140 * @param {ExportsInfo} exportsInfo own exports info
141 * @param {(ExportSpec | string)[]} exports list of exports
142 */
143 const mergeExports = (exportsInfo, exports) => {
144 for (const exportNameOrSpec of exports) {
145 let name;
146 let canMangle = globalCanMangle;
147 let terminalBinding = globalTerminalBinding;
148 let exports = undefined;
149 let from = globalFrom;
150 let fromExport = undefined;
151 if (typeof exportNameOrSpec === "string") {
152 name = exportNameOrSpec;
153 } else {
154 name = exportNameOrSpec.name;
155 if (exportNameOrSpec.canMangle !== undefined)
156 canMangle = exportNameOrSpec.canMangle;
157 if (exportNameOrSpec.export !== undefined)
158 fromExport = exportNameOrSpec.export;
159 if (exportNameOrSpec.exports !== undefined)
160 exports = exportNameOrSpec.exports;
161 if (exportNameOrSpec.from !== undefined)
162 from = exportNameOrSpec.from;
163 if (exportNameOrSpec.terminalBinding !== undefined)
164 terminalBinding = exportNameOrSpec.terminalBinding;
165 }
166 const exportInfo = exportsInfo.getExportInfo(name);
167
168 if (exportInfo.provided === false) {
169 exportInfo.provided = true;
170 changed = true;
171 }
172
173 if (
174 exportInfo.canMangleProvide !== false &&
175 canMangle === false
176 ) {
177 exportInfo.canMangleProvide = false;
178 changed = true;
179 }
180
181 if (terminalBinding && !exportInfo.terminalBinding) {
182 exportInfo.terminalBinding = true;
183 changed = true;
184 }
185
186 if (exports) {
187 const nestedExportsInfo = exportInfo.createNestedExportsInfo();
188 mergeExports(nestedExportsInfo, exports);
189 }
190
191 if (
192 from &&
193 exportInfo.setTarget(
194 dep,
195 from,
196 fromExport === undefined ? [name] : fromExport
197 )
198 ) {
199 changed = true;
200 }
201
202 // Recalculate target exportsInfo
203 const target = exportInfo.getTarget(moduleGraph);
204 let targetExportsInfo = undefined;
205 if (target) {
206 const targetModuleExportsInfo = moduleGraph.getExportsInfo(
207 target.module
208 );
209 targetExportsInfo = targetModuleExportsInfo.getNestedExportsInfo(
210 target.export
211 );
212 // add dependency for this module
213 const set = dependencies.get(target.module);
214 if (set === undefined) {
215 dependencies.set(target.module, new Set([module]));
216 } else {
217 set.add(module);
218 }
219 }
220
221 if (exportInfo.exportsInfoOwned) {
222 if (
223 exportInfo.exportsInfo.setRedirectNamedTo(
224 targetExportsInfo
225 )
226 ) {
227 changed = true;
228 }
229 } else if (
230 exportInfo.exportsInfo !== targetExportsInfo
231 ) {
232 exportInfo.exportsInfo = targetExportsInfo;
233 changed = true;
234 }
235 }
236 };
237 mergeExports(exportsInfo, exports);
238 }
239 // store dependencies
240 if (exportDeps) {
241 cacheable = false;
242 for (const exportDependency of exportDeps) {
243 // add dependency for this module
244 const set = dependencies.get(exportDependency);
245 if (set === undefined) {
246 dependencies.set(exportDependency, new Set([module]));
247 } else {
248 set.add(module);
249 }
250 }
251 }
252 };
253
254 const notifyDependencies = () => {
255 const deps = dependencies.get(module);
256 if (deps !== undefined) {
257 for (const dep of deps) {
258 queue.enqueue(dep);
259 }
260 }
261 };
262
263 logger.time("figure out provided exports");
264 while (queue.length > 0) {
265 module = queue.dequeue();
266
267 statQueueItemsProcessed++;
268
269 exportsInfo = moduleGraph.getExportsInfo(module);
270 if (!module.buildMeta || !module.buildMeta.exportsType) {
271 if (exportsInfo.otherExportsInfo.provided !== null) {
272 // It's a module without declared exports
273 exportsInfo.setUnknownExportsProvided();
274 modulesToStore.add(module);
275 notifyDependencies();
276 }
277 } else {
278 // It's a module with declared exports
279
280 cacheable = true;
281 changed = false;
282
283 processDependenciesBlock(module);
284
285 if (cacheable) {
286 modulesToStore.add(module);
287 }
288
289 if (changed) {
290 notifyDependencies();
291 }
292 }
293 }
294 logger.timeEnd("figure out provided exports");
295
296 logger.log(
297 `${Math.round(
298 100 -
299 (100 * statRestoredFromCache) /
300 (statRestoredFromCache +
301 statNotCached +
302 statFlaggedUncached)
303 )}% of exports of modules have been determined (${statNotCached} not cached, ${statFlaggedUncached} flagged uncacheable, ${statRestoredFromCache} from cache, ${
304 statQueueItemsProcessed -
305 statNotCached -
306 statFlaggedUncached
307 } additional calculations due to dependencies)`
308 );
309
310 logger.time("store provided exports into cache");
311 asyncLib.each(
312 modulesToStore,
313 (module, callback) => {
314 if (
315 module.buildInfo.cacheable !== true ||
316 typeof module.buildInfo.hash !== "string"
317 ) {
318 // not cacheable
319 return callback();
320 }
321 cache.store(
322 module.identifier(),
323 module.buildInfo.hash,
324 moduleGraph
325 .getExportsInfo(module)
326 .getRestoreProvidedData(),
327 callback
328 );
329 },
330 err => {
331 logger.timeEnd("store provided exports into cache");
332 callback(err);
333 }
334 );
335 }
336 );
337 }
338 );
339
340 /** @type {WeakMap<Module, any>} */
341 const providedExportsCache = new WeakMap();
342 compilation.hooks.rebuildModule.tap(
343 "FlagDependencyExportsPlugin",
344 module => {
345 providedExportsCache.set(
346 module,
347 moduleGraph.getExportsInfo(module).getRestoreProvidedData()
348 );
349 }
350 );
351 compilation.hooks.finishRebuildingModule.tap(
352 "FlagDependencyExportsPlugin",
353 module => {
354 moduleGraph
355 .getExportsInfo(module)
356 .restoreProvided(providedExportsCache.get(module));
357 }
358 );
359 }
360 );
361 }
362}
363
364module.exports = FlagDependencyExportsPlugin;
365
\No newline at end of file