source: frontend/node_modules/webpack/lib/ContextModuleFactory.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 15.8 KB
Line 
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 { AsyncSeriesWaterfallHook, SyncWaterfallHook } = require("tapable");
10const ContextModule = require("./ContextModule");
11const ModuleFactory = require("./ModuleFactory");
12const ContextElementDependency = require("./dependencies/ContextElementDependency");
13const LazySet = require("./util/LazySet");
14const { cachedSetProperty } = require("./util/cleverMerge");
15const { createFakeHook } = require("./util/deprecation");
16const { join } = require("./util/fs");
17
18/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */
19/** @typedef {import("./Compilation").FileSystemDependencies} FileSystemDependencies */
20/** @typedef {import("./ContextModule").ContextModuleOptions} ContextModuleOptions */
21/** @typedef {import("./ContextModule").ResolveDependenciesCallback} ResolveDependenciesCallback */
22/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
23/** @typedef {import("./ModuleFactory").ModuleFactoryCallback} ModuleFactoryCallback */
24/** @typedef {import("./ResolverFactory")} ResolverFactory */
25/** @typedef {import("./dependencies/ContextDependency")} ContextDependency */
26/** @typedef {import("./dependencies/ContextDependency").ContextOptions} ContextOptions */
27
28/**
29 * Defines the shared type used by this module.
30 * @template T
31 * @typedef {import("./util/deprecation").FakeHook<T>} FakeHook<T>
32 */
33/** @typedef {import("./util/fs").IStats} IStats */
34/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
35/** @typedef {{ context: string, request: string }} ContextAlternativeRequest */
36
37/**
38 * Defines the context resolve data type used by this module.
39 * @typedef {object} ContextResolveData
40 * @property {string} context
41 * @property {string} request
42 * @property {ModuleFactoryCreateData["resolveOptions"]} resolveOptions
43 * @property {FileSystemDependencies} fileDependencies
44 * @property {FileSystemDependencies} missingDependencies
45 * @property {FileSystemDependencies} contextDependencies
46 * @property {ContextDependency[]} dependencies
47 */
48
49/** @typedef {ContextResolveData & ContextOptions} BeforeContextResolveData */
50/** @typedef {BeforeContextResolveData & { resource: string | string[], resourceQuery: string | undefined, resourceFragment: string | undefined, resolveDependencies: ContextModuleFactory["resolveDependencies"] }} AfterContextResolveData */
51
52const EMPTY_RESOLVE_OPTIONS = {};
53
54class ContextModuleFactory extends ModuleFactory {
55 /**
56 * Creates an instance of ContextModuleFactory.
57 * @param {ResolverFactory} resolverFactory resolverFactory
58 */
59 constructor(resolverFactory) {
60 super();
61 /** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[], ContextModuleOptions]>} */
62 const alternativeRequests = new AsyncSeriesWaterfallHook([
63 "modules",
64 "options"
65 ]);
66 this.hooks = Object.freeze({
67 /** @type {AsyncSeriesWaterfallHook<[BeforeContextResolveData], BeforeContextResolveData | false | void>} */
68 beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
69 /** @type {AsyncSeriesWaterfallHook<[AfterContextResolveData], AfterContextResolveData | false | void>} */
70 afterResolve: new AsyncSeriesWaterfallHook(["data"]),
71 /** @type {SyncWaterfallHook<[string[]]>} */
72 contextModuleFiles: new SyncWaterfallHook(["files"]),
73 /** @type {FakeHook<Pick<AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>, "tap" | "tapAsync" | "tapPromise" | "name">>} */
74 alternatives: createFakeHook(
75 {
76 name: "alternatives",
77 /** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["intercept"]} */
78 intercept: (interceptor) => {
79 throw new Error(
80 "Intercepting fake hook ContextModuleFactory.hooks.alternatives is not possible, use ContextModuleFactory.hooks.alternativeRequests instead"
81 );
82 },
83 /** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["tap"]} */
84 tap: (options, fn) => {
85 alternativeRequests.tap(options, fn);
86 },
87 /** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["tapAsync"]} */
88 tapAsync: (options, fn) => {
89 alternativeRequests.tapAsync(options, (items, _options, callback) =>
90 fn(items, callback)
91 );
92 },
93 /** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[]]>["tapPromise"]} */
94 tapPromise: (options, fn) => {
95 alternativeRequests.tapPromise(options, fn);
96 }
97 },
98 "ContextModuleFactory.hooks.alternatives has deprecated in favor of ContextModuleFactory.hooks.alternativeRequests with an additional options argument.",
99 "DEP_WEBPACK_CONTEXT_MODULE_FACTORY_ALTERNATIVES"
100 ),
101 alternativeRequests
102 });
103 /** @type {ResolverFactory} */
104 this.resolverFactory = resolverFactory;
105 }
106
107 /**
108 * Processes the provided data.
109 * @param {ModuleFactoryCreateData} data data object
110 * @param {ModuleFactoryCallback} callback callback
111 * @returns {void}
112 */
113 create(data, callback) {
114 const context = data.context;
115 const dependencies = /** @type {ContextDependency[]} */ (data.dependencies);
116 const resolveOptions = data.resolveOptions;
117 const dependency = dependencies[0];
118 /** @type {FileSystemDependencies} */
119 const fileDependencies = new LazySet();
120 /** @type {FileSystemDependencies} */
121 const missingDependencies = new LazySet();
122 /** @type {FileSystemDependencies} */
123 const contextDependencies = new LazySet();
124 this.hooks.beforeResolve.callAsync(
125 {
126 context,
127 dependencies,
128 layer: data.contextInfo.issuerLayer,
129 resolveOptions,
130 fileDependencies,
131 missingDependencies,
132 contextDependencies,
133 ...dependency.options
134 },
135 (err, beforeResolveResult) => {
136 if (err) {
137 return callback(err, {
138 fileDependencies,
139 missingDependencies,
140 contextDependencies
141 });
142 }
143
144 // Ignored
145 if (!beforeResolveResult) {
146 return callback(null, {
147 fileDependencies,
148 missingDependencies,
149 contextDependencies
150 });
151 }
152
153 const context = beforeResolveResult.context;
154 const request = beforeResolveResult.request;
155 const resolveOptions = beforeResolveResult.resolveOptions;
156
157 /** @type {undefined | string[]} */
158 let loaders;
159 /** @type {undefined | string} */
160 let resource;
161 let loadersPrefix = "";
162 const idx = request.lastIndexOf("!");
163 if (idx >= 0) {
164 let loadersRequest = request.slice(0, idx + 1);
165 /** @type {number} */
166 let i;
167 for (
168 i = 0;
169 i < loadersRequest.length && loadersRequest[i] === "!";
170 i++
171 ) {
172 loadersPrefix += "!";
173 }
174 loadersRequest = loadersRequest
175 .slice(i)
176 .replace(/!+$/, "")
177 .replace(/!{2,}/g, "!");
178 loaders = loadersRequest === "" ? [] : loadersRequest.split("!");
179 resource = request.slice(idx + 1);
180 } else {
181 loaders = [];
182 resource = request;
183 }
184
185 const contextResolver = this.resolverFactory.get(
186 "context",
187 dependencies.length > 0
188 ? cachedSetProperty(
189 resolveOptions || EMPTY_RESOLVE_OPTIONS,
190 "dependencyType",
191 dependencies[0].category
192 )
193 : resolveOptions
194 );
195 const loaderResolver = this.resolverFactory.get("loader");
196
197 asyncLib.parallel(
198 [
199 (callback) => {
200 const results = /** @type {ResolveRequest[]} */ ([]);
201 /**
202 * Processes the provided obj.
203 * @param {ResolveRequest} obj obj
204 * @returns {void}
205 */
206 const yield_ = (obj) => {
207 results.push(obj);
208 };
209
210 contextResolver.resolve(
211 {},
212 context,
213 resource,
214 {
215 fileDependencies,
216 missingDependencies,
217 contextDependencies,
218 yield: yield_
219 },
220 (err) => {
221 if (err) return callback(err);
222 callback(null, results);
223 }
224 );
225 },
226 (callback) => {
227 asyncLib.map(
228 loaders,
229 (loader, callback) => {
230 loaderResolver.resolve(
231 {},
232 context,
233 loader,
234 {
235 fileDependencies,
236 missingDependencies,
237 contextDependencies
238 },
239 (err, result) => {
240 if (err) return callback(err);
241 callback(null, result);
242 }
243 );
244 },
245 callback
246 );
247 }
248 ],
249 (err, result) => {
250 if (err) {
251 return callback(err, {
252 fileDependencies,
253 missingDependencies,
254 contextDependencies
255 });
256 }
257 let [contextResult, loaderResult] =
258 /** @type {[ResolveRequest[], string[]]} */ (result);
259 if (contextResult.length > 1) {
260 const first = contextResult[0];
261 contextResult = contextResult.filter((r) => r.path);
262 if (contextResult.length === 0) contextResult.push(first);
263 }
264 this.hooks.afterResolve.callAsync(
265 {
266 addon:
267 loadersPrefix +
268 loaderResult.join("!") +
269 (loaderResult.length > 0 ? "!" : ""),
270 resource:
271 contextResult.length > 1
272 ? /** @type {string[]} */ (contextResult.map((r) => r.path))
273 : /** @type {string} */ (contextResult[0].path),
274 resolveDependencies: this.resolveDependencies.bind(this),
275 resourceQuery: contextResult[0].query,
276 resourceFragment: contextResult[0].fragment,
277 ...beforeResolveResult
278 },
279 (err, result) => {
280 if (err) {
281 return callback(err, {
282 fileDependencies,
283 missingDependencies,
284 contextDependencies
285 });
286 }
287
288 // Ignored
289 if (!result) {
290 return callback(null, {
291 fileDependencies,
292 missingDependencies,
293 contextDependencies
294 });
295 }
296
297 return callback(null, {
298 module: new ContextModule(result.resolveDependencies, result),
299 fileDependencies,
300 missingDependencies,
301 contextDependencies
302 });
303 }
304 );
305 }
306 );
307 }
308 );
309 }
310
311 /**
312 * Resolves dependencies.
313 * @param {InputFileSystem} fs file system
314 * @param {ContextModuleOptions} options options
315 * @param {ResolveDependenciesCallback} callback callback function
316 * @returns {void}
317 */
318 resolveDependencies(fs, options, callback) {
319 const cmf = this;
320 const {
321 resource,
322 resourceQuery,
323 resourceFragment,
324 recursive,
325 regExp,
326 include,
327 exclude,
328 referencedExports,
329 category,
330 typePrefix,
331 attributes
332 } = options;
333 if (!regExp || !resource) return callback(null, []);
334
335 /**
336 * Adds directory checked.
337 * @param {string} ctx context
338 * @param {string} directory directory
339 * @param {Set<string>} visited visited
340 * @param {ResolveDependenciesCallback} callback callback
341 */
342 const addDirectoryChecked = (ctx, directory, visited, callback) => {
343 /** @type {NonNullable<InputFileSystem["realpath"]>} */
344 (fs.realpath)(directory, (err, _realPath) => {
345 if (err) return callback(err);
346 const realPath = /** @type {string} */ (_realPath);
347 if (visited.has(realPath)) return callback(null, []);
348 /** @type {Set<string> | undefined} */
349 let recursionStack;
350 addDirectory(
351 ctx,
352 directory,
353 (_, dir, callback) => {
354 if (recursionStack === undefined) {
355 recursionStack = new Set(visited);
356 recursionStack.add(realPath);
357 }
358 addDirectoryChecked(ctx, dir, recursionStack, callback);
359 },
360 callback
361 );
362 });
363 };
364
365 /**
366 * Adds the provided ctx to the context module factory.
367 * @param {string} ctx context
368 * @param {string} directory directory
369 * @param {(context: string, subResource: string, callback: () => void) => void} addSubDirectory addSubDirectoryFn
370 * @param {ResolveDependenciesCallback} callback callback
371 * @returns {void}
372 */
373 const addDirectory = (ctx, directory, addSubDirectory, callback) => {
374 fs.readdir(directory, (err, files) => {
375 if (err) return callback(err);
376 const processedFiles = cmf.hooks.contextModuleFiles.call(
377 /** @type {string[]} */ (files).map((file) => file.normalize("NFC"))
378 );
379 if (!processedFiles || processedFiles.length === 0) {
380 return callback(null, []);
381 }
382 asyncLib.map(
383 processedFiles.filter((p) => p.indexOf(".") !== 0),
384 (segment, callback) => {
385 const subResource = join(fs, directory, segment);
386
387 if (!exclude || !exclude.test(subResource)) {
388 fs.stat(subResource, (err, _stat) => {
389 if (err) {
390 if (err.code === "ENOENT") {
391 // ENOENT is ok here because the file may have been deleted between
392 // the readdir and stat calls.
393 return callback();
394 }
395 return callback(err);
396 }
397
398 const stat = /** @type {IStats} */ (_stat);
399
400 if (stat.isDirectory()) {
401 if (!recursive) return callback();
402 addSubDirectory(ctx, subResource, callback);
403 } else if (
404 stat.isFile() &&
405 (!include || include.test(subResource))
406 ) {
407 /** @type {{ context: string, request: string }} */
408 const obj = {
409 context: ctx,
410 request: `.${subResource.slice(ctx.length).replace(/\\/g, "/")}`
411 };
412
413 this.hooks.alternativeRequests.callAsync(
414 [obj],
415 options,
416 (err, alternatives) => {
417 if (err) return callback(err);
418 callback(
419 null,
420 /** @type {ContextAlternativeRequest[]} */
421 (alternatives)
422 .filter((obj) =>
423 regExp.test(/** @type {string} */ (obj.request))
424 )
425 .map((obj) => {
426 const dep = new ContextElementDependency(
427 `${obj.request}${resourceQuery}${resourceFragment}`,
428 obj.request,
429 typePrefix,
430 /** @type {string} */
431 (category),
432 referencedExports,
433 obj.context,
434 attributes
435 );
436 dep.optional = true;
437 return dep;
438 })
439 );
440 }
441 );
442 } else {
443 callback();
444 }
445 });
446 } else {
447 callback();
448 }
449 },
450 (err, result) => {
451 if (err) return callback(err);
452
453 if (!result) return callback(null, []);
454
455 /** @type {ContextElementDependency[]} */
456 const flattenedResult = [];
457
458 for (const item of result) {
459 if (item) flattenedResult.push(...item);
460 }
461
462 callback(null, flattenedResult);
463 }
464 );
465 });
466 };
467
468 /**
469 * Adds sub directory.
470 * @param {string} ctx context
471 * @param {string} dir dir
472 * @param {ResolveDependenciesCallback} callback callback
473 * @returns {void}
474 */
475 const addSubDirectory = (ctx, dir, callback) =>
476 addDirectory(ctx, dir, addSubDirectory, callback);
477
478 /**
479 * Processes the provided resource.
480 * @param {string} resource resource
481 * @param {ResolveDependenciesCallback} callback callback
482 */
483 const visitResource = (resource, callback) => {
484 if (typeof fs.realpath === "function") {
485 addDirectoryChecked(
486 resource,
487 resource,
488 /** @type {Set<string>} */
489 new Set(),
490 callback
491 );
492 } else {
493 addDirectory(resource, resource, addSubDirectory, callback);
494 }
495 };
496
497 if (typeof resource === "string") {
498 visitResource(resource, callback);
499 } else {
500 asyncLib.map(resource, visitResource, (err, _result) => {
501 if (err) return callback(err);
502 const result = /** @type {ContextElementDependency[][]} */ (_result);
503
504 // result dependencies should have unique userRequest
505 // ordered by resolve result
506 /** @type {Set<string>} */
507 const temp = new Set();
508 /** @type {ContextElementDependency[]} */
509 const res = [];
510 for (let i = 0; i < result.length; i++) {
511 const inner = result[i];
512 for (const el of inner) {
513 if (temp.has(el.userRequest)) continue;
514 res.push(el);
515 temp.add(el.userRequest);
516 }
517 }
518 callback(null, res);
519 });
520 }
521 }
522}
523
524module.exports = ContextModuleFactory;
Note: See TracBrowser for help on using the repository browser.