UNPKG

31.8 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 { getContext } = require("loader-runner");
9const asyncLib = require("neo-async");
10const {
11 AsyncSeriesBailHook,
12 SyncWaterfallHook,
13 SyncBailHook,
14 SyncHook,
15 HookMap
16} = require("tapable");
17const ChunkGraph = require("./ChunkGraph");
18const Module = require("./Module");
19const ModuleFactory = require("./ModuleFactory");
20const ModuleGraph = require("./ModuleGraph");
21const NormalModule = require("./NormalModule");
22const BasicEffectRulePlugin = require("./rules/BasicEffectRulePlugin");
23const BasicMatcherRulePlugin = require("./rules/BasicMatcherRulePlugin");
24const ObjectMatcherRulePlugin = require("./rules/ObjectMatcherRulePlugin");
25const RuleSetCompiler = require("./rules/RuleSetCompiler");
26const UseEffectRulePlugin = require("./rules/UseEffectRulePlugin");
27const LazySet = require("./util/LazySet");
28const { getScheme } = require("./util/URLAbsoluteSpecifier");
29const { cachedCleverMerge, cachedSetProperty } = require("./util/cleverMerge");
30const { join } = require("./util/fs");
31const { parseResource } = require("./util/identifier");
32
33/** @typedef {import("../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */
34/** @typedef {import("./Generator")} Generator */
35/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
36/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
37/** @typedef {import("./Parser")} Parser */
38/** @typedef {import("./ResolverFactory")} ResolverFactory */
39/** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */
40/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
41
42/**
43 * @typedef {Object} ResolveData
44 * @property {ModuleFactoryCreateData["contextInfo"]} contextInfo
45 * @property {ModuleFactoryCreateData["resolveOptions"]} resolveOptions
46 * @property {string} context
47 * @property {string} request
48 * @property {Record<string, any> | undefined} assertions
49 * @property {ModuleDependency[]} dependencies
50 * @property {string} dependencyType
51 * @property {Object} createData
52 * @property {LazySet<string>} fileDependencies
53 * @property {LazySet<string>} missingDependencies
54 * @property {LazySet<string>} contextDependencies
55 * @property {boolean} cacheable allow to use the unsafe cache
56 */
57
58/**
59 * @typedef {Object} ResourceData
60 * @property {string} resource
61 * @property {string} path
62 * @property {string} query
63 * @property {string} fragment
64 * @property {string=} context
65 */
66
67/** @typedef {ResourceData & { data: Record<string, any> }} ResourceDataWithData */
68
69const EMPTY_RESOLVE_OPTIONS = {};
70const EMPTY_PARSER_OPTIONS = {};
71const EMPTY_GENERATOR_OPTIONS = {};
72const EMPTY_ELEMENTS = [];
73
74const MATCH_RESOURCE_REGEX = /^([^!]+)!=!/;
75
76const loaderToIdent = data => {
77 if (!data.options) {
78 return data.loader;
79 }
80 if (typeof data.options === "string") {
81 return data.loader + "?" + data.options;
82 }
83 if (typeof data.options !== "object") {
84 throw new Error("loader options must be string or object");
85 }
86 if (data.ident) {
87 return data.loader + "??" + data.ident;
88 }
89 return data.loader + "?" + JSON.stringify(data.options);
90};
91
92const stringifyLoadersAndResource = (loaders, resource) => {
93 let str = "";
94 for (const loader of loaders) {
95 str += loaderToIdent(loader) + "!";
96 }
97 return str + resource;
98};
99
100/**
101 * @param {string} resultString resultString
102 * @returns {{loader: string, options: string|undefined}} parsed loader request
103 */
104const identToLoaderRequest = resultString => {
105 const idx = resultString.indexOf("?");
106 if (idx >= 0) {
107 const loader = resultString.substr(0, idx);
108 const options = resultString.substr(idx + 1);
109 return {
110 loader,
111 options
112 };
113 } else {
114 return {
115 loader: resultString,
116 options: undefined
117 };
118 }
119};
120
121const needCalls = (times, callback) => {
122 return err => {
123 if (--times === 0) {
124 return callback(err);
125 }
126 if (err && times > 0) {
127 times = NaN;
128 return callback(err);
129 }
130 };
131};
132
133const mergeGlobalOptions = (globalOptions, type, localOptions) => {
134 const parts = type.split("/");
135 let result;
136 let current = "";
137 for (const part of parts) {
138 current = current ? `${current}/${part}` : part;
139 const options = globalOptions[current];
140 if (typeof options === "object") {
141 if (result === undefined) {
142 result = options;
143 } else {
144 result = cachedCleverMerge(result, options);
145 }
146 }
147 }
148 if (result === undefined) {
149 return localOptions;
150 } else {
151 return cachedCleverMerge(result, localOptions);
152 }
153};
154
155// TODO webpack 6 remove
156const deprecationChangedHookMessage = (name, hook) => {
157 const names = hook.taps
158 .map(tapped => {
159 return tapped.name;
160 })
161 .join(", ");
162
163 return (
164 `NormalModuleFactory.${name} (${names}) is no longer a waterfall hook, but a bailing hook instead. ` +
165 "Do not return the passed object, but modify it instead. " +
166 "Returning false will ignore the request and results in no module created."
167 );
168};
169
170const ruleSetCompiler = new RuleSetCompiler([
171 new BasicMatcherRulePlugin("test", "resource"),
172 new BasicMatcherRulePlugin("scheme"),
173 new BasicMatcherRulePlugin("mimetype"),
174 new BasicMatcherRulePlugin("dependency"),
175 new BasicMatcherRulePlugin("include", "resource"),
176 new BasicMatcherRulePlugin("exclude", "resource", true),
177 new BasicMatcherRulePlugin("resource"),
178 new BasicMatcherRulePlugin("resourceQuery"),
179 new BasicMatcherRulePlugin("resourceFragment"),
180 new BasicMatcherRulePlugin("realResource"),
181 new BasicMatcherRulePlugin("issuer"),
182 new BasicMatcherRulePlugin("compiler"),
183 new BasicMatcherRulePlugin("issuerLayer"),
184 new ObjectMatcherRulePlugin("assert", "assertions"),
185 new ObjectMatcherRulePlugin("descriptionData"),
186 new BasicEffectRulePlugin("type"),
187 new BasicEffectRulePlugin("sideEffects"),
188 new BasicEffectRulePlugin("parser"),
189 new BasicEffectRulePlugin("resolve"),
190 new BasicEffectRulePlugin("generator"),
191 new BasicEffectRulePlugin("layer"),
192 new UseEffectRulePlugin()
193]);
194
195class NormalModuleFactory extends ModuleFactory {
196 /**
197 * @param {Object} param params
198 * @param {string=} param.context context
199 * @param {InputFileSystem} param.fs file system
200 * @param {ResolverFactory} param.resolverFactory resolverFactory
201 * @param {ModuleOptions} param.options options
202 * @param {Object=} param.associatedObjectForCache an object to which the cache will be attached
203 * @param {boolean=} param.layers enable layers
204 */
205 constructor({
206 context,
207 fs,
208 resolverFactory,
209 options,
210 associatedObjectForCache,
211 layers = false
212 }) {
213 super();
214 this.hooks = Object.freeze({
215 /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
216 resolve: new AsyncSeriesBailHook(["resolveData"]),
217 /** @type {HookMap<AsyncSeriesBailHook<[ResourceDataWithData, ResolveData], true | void>>} */
218 resolveForScheme: new HookMap(
219 () => new AsyncSeriesBailHook(["resourceData", "resolveData"])
220 ),
221 /** @type {HookMap<AsyncSeriesBailHook<[ResourceDataWithData, ResolveData], true | void>>} */
222 resolveInScheme: new HookMap(
223 () => new AsyncSeriesBailHook(["resourceData", "resolveData"])
224 ),
225 /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
226 factorize: new AsyncSeriesBailHook(["resolveData"]),
227 /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
228 beforeResolve: new AsyncSeriesBailHook(["resolveData"]),
229 /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
230 afterResolve: new AsyncSeriesBailHook(["resolveData"]),
231 /** @type {AsyncSeriesBailHook<[ResolveData["createData"], ResolveData], TODO>} */
232 createModule: new AsyncSeriesBailHook(["createData", "resolveData"]),
233 /** @type {SyncWaterfallHook<[Module, ResolveData["createData"], ResolveData], TODO>} */
234 module: new SyncWaterfallHook(["module", "createData", "resolveData"]),
235 createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
236 parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
237 createGenerator: new HookMap(
238 () => new SyncBailHook(["generatorOptions"])
239 ),
240 generator: new HookMap(
241 () => new SyncHook(["generator", "generatorOptions"])
242 )
243 });
244 this.resolverFactory = resolverFactory;
245 this.ruleSet = ruleSetCompiler.compile([
246 {
247 rules: options.defaultRules
248 },
249 {
250 rules: options.rules
251 }
252 ]);
253 this.context = context || "";
254 this.fs = fs;
255 this._globalParserOptions = options.parser;
256 this._globalGeneratorOptions = options.generator;
257 /** @type {Map<string, WeakMap<Object, TODO>>} */
258 this.parserCache = new Map();
259 /** @type {Map<string, WeakMap<Object, Generator>>} */
260 this.generatorCache = new Map();
261 /** @type {Set<Module>} */
262 this._restoredUnsafeCacheEntries = new Set();
263
264 const cacheParseResource = parseResource.bindCache(
265 associatedObjectForCache
266 );
267
268 this.hooks.factorize.tapAsync(
269 {
270 name: "NormalModuleFactory",
271 stage: 100
272 },
273 (resolveData, callback) => {
274 this.hooks.resolve.callAsync(resolveData, (err, result) => {
275 if (err) return callback(err);
276
277 // Ignored
278 if (result === false) return callback();
279
280 // direct module
281 if (result instanceof Module) return callback(null, result);
282
283 if (typeof result === "object")
284 throw new Error(
285 deprecationChangedHookMessage("resolve", this.hooks.resolve) +
286 " Returning a Module object will result in this module used as result."
287 );
288
289 this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
290 if (err) return callback(err);
291
292 if (typeof result === "object")
293 throw new Error(
294 deprecationChangedHookMessage(
295 "afterResolve",
296 this.hooks.afterResolve
297 )
298 );
299
300 // Ignored
301 if (result === false) return callback();
302
303 const createData = resolveData.createData;
304
305 this.hooks.createModule.callAsync(
306 createData,
307 resolveData,
308 (err, createdModule) => {
309 if (!createdModule) {
310 if (!resolveData.request) {
311 return callback(new Error("Empty dependency (no request)"));
312 }
313
314 createdModule = new NormalModule(createData);
315 }
316
317 createdModule = this.hooks.module.call(
318 createdModule,
319 createData,
320 resolveData
321 );
322
323 return callback(null, createdModule);
324 }
325 );
326 });
327 });
328 }
329 );
330 this.hooks.resolve.tapAsync(
331 {
332 name: "NormalModuleFactory",
333 stage: 100
334 },
335 (data, callback) => {
336 const {
337 contextInfo,
338 context,
339 dependencies,
340 dependencyType,
341 request,
342 assertions,
343 resolveOptions,
344 fileDependencies,
345 missingDependencies,
346 contextDependencies
347 } = data;
348 const loaderResolver = this.getResolver("loader");
349
350 /** @type {ResourceData | undefined} */
351 let matchResourceData = undefined;
352 /** @type {string} */
353 let unresolvedResource;
354 /** @type {{loader: string, options: string|undefined}[]} */
355 let elements;
356 let noPreAutoLoaders = false;
357 let noAutoLoaders = false;
358 let noPrePostAutoLoaders = false;
359
360 const contextScheme = getScheme(context);
361 /** @type {string | undefined} */
362 let scheme = getScheme(request);
363
364 if (!scheme) {
365 /** @type {string} */
366 let requestWithoutMatchResource = request;
367 const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request);
368 if (matchResourceMatch) {
369 let matchResource = matchResourceMatch[1];
370 if (matchResource.charCodeAt(0) === 46) {
371 // 46 === ".", 47 === "/"
372 const secondChar = matchResource.charCodeAt(1);
373 if (
374 secondChar === 47 ||
375 (secondChar === 46 && matchResource.charCodeAt(2) === 47)
376 ) {
377 // if matchResources startsWith ../ or ./
378 matchResource = join(this.fs, context, matchResource);
379 }
380 }
381 matchResourceData = {
382 resource: matchResource,
383 ...cacheParseResource(matchResource)
384 };
385 requestWithoutMatchResource = request.substr(
386 matchResourceMatch[0].length
387 );
388 }
389
390 scheme = getScheme(requestWithoutMatchResource);
391
392 if (!scheme && !contextScheme) {
393 const firstChar = requestWithoutMatchResource.charCodeAt(0);
394 const secondChar = requestWithoutMatchResource.charCodeAt(1);
395 noPreAutoLoaders = firstChar === 45 && secondChar === 33; // startsWith "-!"
396 noAutoLoaders = noPreAutoLoaders || firstChar === 33; // startsWith "!"
397 noPrePostAutoLoaders = firstChar === 33 && secondChar === 33; // startsWith "!!";
398 const rawElements = requestWithoutMatchResource
399 .slice(
400 noPreAutoLoaders || noPrePostAutoLoaders
401 ? 2
402 : noAutoLoaders
403 ? 1
404 : 0
405 )
406 .split(/!+/);
407 unresolvedResource = rawElements.pop();
408 elements = rawElements.map(identToLoaderRequest);
409 scheme = getScheme(unresolvedResource);
410 } else {
411 unresolvedResource = requestWithoutMatchResource;
412 elements = EMPTY_ELEMENTS;
413 }
414 } else {
415 unresolvedResource = request;
416 elements = EMPTY_ELEMENTS;
417 }
418
419 const resolveContext = {
420 fileDependencies,
421 missingDependencies,
422 contextDependencies
423 };
424
425 /** @type {ResourceDataWithData} */
426 let resourceData;
427
428 let loaders;
429
430 const continueCallback = needCalls(2, err => {
431 if (err) return callback(err);
432
433 // translate option idents
434 try {
435 for (const item of loaders) {
436 if (typeof item.options === "string" && item.options[0] === "?") {
437 const ident = item.options.substr(1);
438 if (ident === "[[missing ident]]") {
439 throw new Error(
440 "No ident is provided by referenced loader. " +
441 "When using a function for Rule.use in config you need to " +
442 "provide an 'ident' property for referenced loader options."
443 );
444 }
445 item.options = this.ruleSet.references.get(ident);
446 if (item.options === undefined) {
447 throw new Error(
448 "Invalid ident is provided by referenced loader"
449 );
450 }
451 item.ident = ident;
452 }
453 }
454 } catch (e) {
455 return callback(e);
456 }
457
458 if (!resourceData) {
459 // ignored
460 return callback(null, dependencies[0].createIgnoredModule(context));
461 }
462
463 const userRequest =
464 (matchResourceData !== undefined
465 ? `${matchResourceData.resource}!=!`
466 : "") +
467 stringifyLoadersAndResource(loaders, resourceData.resource);
468
469 const settings = {};
470 const useLoadersPost = [];
471 const useLoaders = [];
472 const useLoadersPre = [];
473
474 // handle .webpack[] suffix
475 let resource;
476 let match;
477 if (
478 matchResourceData &&
479 typeof (resource = matchResourceData.resource) === "string" &&
480 (match = /\.webpack\[([^\]]+)\]$/.exec(resource))
481 ) {
482 settings.type = match[1];
483 matchResourceData.resource = matchResourceData.resource.slice(
484 0,
485 -settings.type.length - 10
486 );
487 } else {
488 settings.type = "javascript/auto";
489 const resourceDataForRules = matchResourceData || resourceData;
490 const result = this.ruleSet.exec({
491 resource: resourceDataForRules.path,
492 realResource: resourceData.path,
493 resourceQuery: resourceDataForRules.query,
494 resourceFragment: resourceDataForRules.fragment,
495 scheme,
496 assertions,
497 mimetype: matchResourceData
498 ? ""
499 : resourceData.data.mimetype || "",
500 dependency: dependencyType,
501 descriptionData: matchResourceData
502 ? undefined
503 : resourceData.data.descriptionFileData,
504 issuer: contextInfo.issuer,
505 compiler: contextInfo.compiler,
506 issuerLayer: contextInfo.issuerLayer || ""
507 });
508 for (const r of result) {
509 if (r.type === "use") {
510 if (!noAutoLoaders && !noPrePostAutoLoaders) {
511 useLoaders.push(r.value);
512 }
513 } else if (r.type === "use-post") {
514 if (!noPrePostAutoLoaders) {
515 useLoadersPost.push(r.value);
516 }
517 } else if (r.type === "use-pre") {
518 if (!noPreAutoLoaders && !noPrePostAutoLoaders) {
519 useLoadersPre.push(r.value);
520 }
521 } else if (
522 typeof r.value === "object" &&
523 r.value !== null &&
524 typeof settings[r.type] === "object" &&
525 settings[r.type] !== null
526 ) {
527 settings[r.type] = cachedCleverMerge(settings[r.type], r.value);
528 } else {
529 settings[r.type] = r.value;
530 }
531 }
532 }
533
534 let postLoaders, normalLoaders, preLoaders;
535
536 const continueCallback = needCalls(3, err => {
537 if (err) {
538 return callback(err);
539 }
540 const allLoaders = postLoaders;
541 if (matchResourceData === undefined) {
542 for (const loader of loaders) allLoaders.push(loader);
543 for (const loader of normalLoaders) allLoaders.push(loader);
544 } else {
545 for (const loader of normalLoaders) allLoaders.push(loader);
546 for (const loader of loaders) allLoaders.push(loader);
547 }
548 for (const loader of preLoaders) allLoaders.push(loader);
549 let type = settings.type;
550 const resolveOptions = settings.resolve;
551 const layer = settings.layer;
552 if (layer !== undefined && !layers) {
553 return callback(
554 new Error(
555 "'Rule.layer' is only allowed when 'experiments.layers' is enabled"
556 )
557 );
558 }
559 try {
560 Object.assign(data.createData, {
561 layer:
562 layer === undefined ? contextInfo.issuerLayer || null : layer,
563 request: stringifyLoadersAndResource(
564 allLoaders,
565 resourceData.resource
566 ),
567 userRequest,
568 rawRequest: request,
569 loaders: allLoaders,
570 resource: resourceData.resource,
571 context:
572 resourceData.context || getContext(resourceData.resource),
573 matchResource: matchResourceData
574 ? matchResourceData.resource
575 : undefined,
576 resourceResolveData: resourceData.data,
577 settings,
578 type,
579 parser: this.getParser(type, settings.parser),
580 parserOptions: settings.parser,
581 generator: this.getGenerator(type, settings.generator),
582 generatorOptions: settings.generator,
583 resolveOptions
584 });
585 } catch (e) {
586 return callback(e);
587 }
588 callback();
589 });
590 this.resolveRequestArray(
591 contextInfo,
592 this.context,
593 useLoadersPost,
594 loaderResolver,
595 resolveContext,
596 (err, result) => {
597 postLoaders = result;
598 continueCallback(err);
599 }
600 );
601 this.resolveRequestArray(
602 contextInfo,
603 this.context,
604 useLoaders,
605 loaderResolver,
606 resolveContext,
607 (err, result) => {
608 normalLoaders = result;
609 continueCallback(err);
610 }
611 );
612 this.resolveRequestArray(
613 contextInfo,
614 this.context,
615 useLoadersPre,
616 loaderResolver,
617 resolveContext,
618 (err, result) => {
619 preLoaders = result;
620 continueCallback(err);
621 }
622 );
623 });
624
625 this.resolveRequestArray(
626 contextInfo,
627 contextScheme ? this.context : context,
628 elements,
629 loaderResolver,
630 resolveContext,
631 (err, result) => {
632 if (err) return continueCallback(err);
633 loaders = result;
634 continueCallback();
635 }
636 );
637
638 const defaultResolve = context => {
639 if (/^($|\?)/.test(unresolvedResource)) {
640 resourceData = {
641 resource: unresolvedResource,
642 data: {},
643 ...cacheParseResource(unresolvedResource)
644 };
645 continueCallback();
646 }
647
648 // resource without scheme and with path
649 else {
650 const normalResolver = this.getResolver(
651 "normal",
652 dependencyType
653 ? cachedSetProperty(
654 resolveOptions || EMPTY_RESOLVE_OPTIONS,
655 "dependencyType",
656 dependencyType
657 )
658 : resolveOptions
659 );
660 this.resolveResource(
661 contextInfo,
662 context,
663 unresolvedResource,
664 normalResolver,
665 resolveContext,
666 (err, resolvedResource, resolvedResourceResolveData) => {
667 if (err) return continueCallback(err);
668 if (resolvedResource !== false) {
669 resourceData = {
670 resource: resolvedResource,
671 data: resolvedResourceResolveData,
672 ...cacheParseResource(resolvedResource)
673 };
674 }
675 continueCallback();
676 }
677 );
678 }
679 };
680
681 // resource with scheme
682 if (scheme) {
683 resourceData = {
684 resource: unresolvedResource,
685 data: {},
686 path: undefined,
687 query: undefined,
688 fragment: undefined,
689 context: undefined
690 };
691 this.hooks.resolveForScheme
692 .for(scheme)
693 .callAsync(resourceData, data, err => {
694 if (err) return continueCallback(err);
695 continueCallback();
696 });
697 }
698
699 // resource within scheme
700 else if (contextScheme) {
701 resourceData = {
702 resource: unresolvedResource,
703 data: {},
704 path: undefined,
705 query: undefined,
706 fragment: undefined,
707 context: undefined
708 };
709 this.hooks.resolveInScheme
710 .for(contextScheme)
711 .callAsync(resourceData, data, (err, handled) => {
712 if (err) return continueCallback(err);
713 if (!handled) return defaultResolve(this.context);
714 continueCallback();
715 });
716 }
717
718 // resource without scheme and without path
719 else defaultResolve(context);
720 }
721 );
722 }
723
724 cleanupForCache() {
725 for (const module of this._restoredUnsafeCacheEntries) {
726 ChunkGraph.clearChunkGraphForModule(module);
727 ModuleGraph.clearModuleGraphForModule(module);
728 module.cleanupForCache();
729 }
730 }
731
732 /**
733 * @param {ModuleFactoryCreateData} data data object
734 * @param {function(Error=, ModuleFactoryResult=): void} callback callback
735 * @returns {void}
736 */
737 create(data, callback) {
738 const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies);
739 const context = data.context || this.context;
740 const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;
741 const dependency = dependencies[0];
742 const request = dependency.request;
743 const assertions = dependency.assertions;
744 const contextInfo = data.contextInfo;
745 const fileDependencies = new LazySet();
746 const missingDependencies = new LazySet();
747 const contextDependencies = new LazySet();
748 const dependencyType =
749 (dependencies.length > 0 && dependencies[0].category) || "";
750 /** @type {ResolveData} */
751 const resolveData = {
752 contextInfo,
753 resolveOptions,
754 context,
755 request,
756 assertions,
757 dependencies,
758 dependencyType,
759 fileDependencies,
760 missingDependencies,
761 contextDependencies,
762 createData: {},
763 cacheable: true
764 };
765 this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
766 if (err) {
767 return callback(err, {
768 fileDependencies,
769 missingDependencies,
770 contextDependencies,
771 cacheable: false
772 });
773 }
774
775 // Ignored
776 if (result === false) {
777 return callback(null, {
778 fileDependencies,
779 missingDependencies,
780 contextDependencies,
781 cacheable: resolveData.cacheable
782 });
783 }
784
785 if (typeof result === "object")
786 throw new Error(
787 deprecationChangedHookMessage(
788 "beforeResolve",
789 this.hooks.beforeResolve
790 )
791 );
792
793 this.hooks.factorize.callAsync(resolveData, (err, module) => {
794 if (err) {
795 return callback(err, {
796 fileDependencies,
797 missingDependencies,
798 contextDependencies,
799 cacheable: false
800 });
801 }
802
803 const factoryResult = {
804 module,
805 fileDependencies,
806 missingDependencies,
807 contextDependencies,
808 cacheable: resolveData.cacheable
809 };
810
811 callback(null, factoryResult);
812 });
813 });
814 }
815
816 resolveResource(
817 contextInfo,
818 context,
819 unresolvedResource,
820 resolver,
821 resolveContext,
822 callback
823 ) {
824 resolver.resolve(
825 contextInfo,
826 context,
827 unresolvedResource,
828 resolveContext,
829 (err, resolvedResource, resolvedResourceResolveData) => {
830 if (err) {
831 return this._resolveResourceErrorHints(
832 err,
833 contextInfo,
834 context,
835 unresolvedResource,
836 resolver,
837 resolveContext,
838 (err2, hints) => {
839 if (err2) {
840 err.message += `
841An fatal error happened during resolving additional hints for this error: ${err2.message}`;
842 err.stack += `
843
844An fatal error happened during resolving additional hints for this error:
845${err2.stack}`;
846 return callback(err);
847 }
848 if (hints && hints.length > 0) {
849 err.message += `
850${hints.join("\n\n")}`;
851 }
852 callback(err);
853 }
854 );
855 }
856 callback(err, resolvedResource, resolvedResourceResolveData);
857 }
858 );
859 }
860
861 _resolveResourceErrorHints(
862 error,
863 contextInfo,
864 context,
865 unresolvedResource,
866 resolver,
867 resolveContext,
868 callback
869 ) {
870 asyncLib.parallel(
871 [
872 callback => {
873 if (!resolver.options.fullySpecified) return callback();
874 resolver
875 .withOptions({
876 fullySpecified: false
877 })
878 .resolve(
879 contextInfo,
880 context,
881 unresolvedResource,
882 resolveContext,
883 (err, resolvedResource) => {
884 if (!err && resolvedResource) {
885 const resource = parseResource(resolvedResource).path.replace(
886 /^.*[\\/]/,
887 ""
888 );
889 return callback(
890 null,
891 `Did you mean '${resource}'?
892BREAKING CHANGE: The request '${unresolvedResource}' failed to resolve only because it was resolved as fully specified
893(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
894The extension in the request is mandatory for it to be fully specified.
895Add the extension to the request.`
896 );
897 }
898 callback();
899 }
900 );
901 },
902 callback => {
903 if (!resolver.options.enforceExtension) return callback();
904 resolver
905 .withOptions({
906 enforceExtension: false,
907 extensions: []
908 })
909 .resolve(
910 contextInfo,
911 context,
912 unresolvedResource,
913 resolveContext,
914 (err, resolvedResource) => {
915 if (!err && resolvedResource) {
916 let hint = "";
917 const match = /(\.[^.]+)(\?|$)/.exec(unresolvedResource);
918 if (match) {
919 const fixedRequest = unresolvedResource.replace(
920 /(\.[^.]+)(\?|$)/,
921 "$2"
922 );
923 if (resolver.options.extensions.has(match[1])) {
924 hint = `Did you mean '${fixedRequest}'?`;
925 } else {
926 hint = `Did you mean '${fixedRequest}'? Also note that '${match[1]}' is not in 'resolve.extensions' yet and need to be added for this to work?`;
927 }
928 } else {
929 hint = `Did you mean to omit the extension or to remove 'resolve.enforceExtension'?`;
930 }
931 return callback(
932 null,
933 `The request '${unresolvedResource}' failed to resolve only because 'resolve.enforceExtension' was specified.
934${hint}
935Including the extension in the request is no longer possible. Did you mean to enforce including the extension in requests with 'resolve.extensions: []' instead?`
936 );
937 }
938 callback();
939 }
940 );
941 },
942 callback => {
943 if (
944 /^\.\.?\//.test(unresolvedResource) ||
945 resolver.options.preferRelative
946 ) {
947 return callback();
948 }
949 resolver.resolve(
950 contextInfo,
951 context,
952 `./${unresolvedResource}`,
953 resolveContext,
954 (err, resolvedResource) => {
955 if (err || !resolvedResource) return callback();
956 const moduleDirectories = resolver.options.modules
957 .map(m => (Array.isArray(m) ? m.join(", ") : m))
958 .join(", ");
959 callback(
960 null,
961 `Did you mean './${unresolvedResource}'?
962Requests that should resolve in the current directory need to start with './'.
963Requests that start with a name are treated as module requests and resolve within module directories (${moduleDirectories}).
964If changing the source code is not an option there is also a resolve options called 'preferRelative' which tries to resolve these kind of requests in the current directory too.`
965 );
966 }
967 );
968 }
969 ],
970 (err, hints) => {
971 if (err) return callback(err);
972 callback(null, hints.filter(Boolean));
973 }
974 );
975 }
976
977 resolveRequestArray(
978 contextInfo,
979 context,
980 array,
981 resolver,
982 resolveContext,
983 callback
984 ) {
985 if (array.length === 0) return callback(null, array);
986 asyncLib.map(
987 array,
988 (item, callback) => {
989 resolver.resolve(
990 contextInfo,
991 context,
992 item.loader,
993 resolveContext,
994 (err, result) => {
995 if (
996 err &&
997 /^[^/]*$/.test(item.loader) &&
998 !/-loader$/.test(item.loader)
999 ) {
1000 return resolver.resolve(
1001 contextInfo,
1002 context,
1003 item.loader + "-loader",
1004 resolveContext,
1005 err2 => {
1006 if (!err2) {
1007 err.message =
1008 err.message +
1009 "\n" +
1010 "BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.\n" +
1011 ` You need to specify '${item.loader}-loader' instead of '${item.loader}',\n` +
1012 " see https://webpack.js.org/migrate/3/#automatic-loader-module-name-extension-removed";
1013 }
1014 callback(err);
1015 }
1016 );
1017 }
1018 if (err) return callback(err);
1019
1020 const parsedResult = identToLoaderRequest(result);
1021 const resolved = {
1022 loader: parsedResult.loader,
1023 options:
1024 item.options === undefined
1025 ? parsedResult.options
1026 : item.options,
1027 ident: item.options === undefined ? undefined : item.ident
1028 };
1029 return callback(null, resolved);
1030 }
1031 );
1032 },
1033 callback
1034 );
1035 }
1036
1037 getParser(type, parserOptions = EMPTY_PARSER_OPTIONS) {
1038 let cache = this.parserCache.get(type);
1039
1040 if (cache === undefined) {
1041 cache = new WeakMap();
1042 this.parserCache.set(type, cache);
1043 }
1044
1045 let parser = cache.get(parserOptions);
1046
1047 if (parser === undefined) {
1048 parser = this.createParser(type, parserOptions);
1049 cache.set(parserOptions, parser);
1050 }
1051
1052 return parser;
1053 }
1054
1055 /**
1056 * @param {string} type type
1057 * @param {{[k: string]: any}} parserOptions parser options
1058 * @returns {Parser} parser
1059 */
1060 createParser(type, parserOptions = {}) {
1061 parserOptions = mergeGlobalOptions(
1062 this._globalParserOptions,
1063 type,
1064 parserOptions
1065 );
1066 const parser = this.hooks.createParser.for(type).call(parserOptions);
1067 if (!parser) {
1068 throw new Error(`No parser registered for ${type}`);
1069 }
1070 this.hooks.parser.for(type).call(parser, parserOptions);
1071 return parser;
1072 }
1073
1074 getGenerator(type, generatorOptions = EMPTY_GENERATOR_OPTIONS) {
1075 let cache = this.generatorCache.get(type);
1076
1077 if (cache === undefined) {
1078 cache = new WeakMap();
1079 this.generatorCache.set(type, cache);
1080 }
1081
1082 let generator = cache.get(generatorOptions);
1083
1084 if (generator === undefined) {
1085 generator = this.createGenerator(type, generatorOptions);
1086 cache.set(generatorOptions, generator);
1087 }
1088
1089 return generator;
1090 }
1091
1092 createGenerator(type, generatorOptions = {}) {
1093 generatorOptions = mergeGlobalOptions(
1094 this._globalGeneratorOptions,
1095 type,
1096 generatorOptions
1097 );
1098 const generator = this.hooks.createGenerator
1099 .for(type)
1100 .call(generatorOptions);
1101 if (!generator) {
1102 throw new Error(`No generator registered for ${type}`);
1103 }
1104 this.hooks.generator.for(type).call(generator, generatorOptions);
1105 return generator;
1106 }
1107
1108 getResolver(type, resolveOptions) {
1109 return this.resolverFactory.get(type, resolveOptions);
1110 }
1111}
1112
1113module.exports = NormalModuleFactory;