UNPKG

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