UNPKG

29.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 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 const type = settings.type;
500 const resolveOptions = settings.resolve;
501 const layer = settings.layer;
502 if (layer !== undefined && !layers) {
503 return callback(
504 new Error(
505 "'Rule.layer' is only allowed when 'experiments.layers' is enabled"
506 )
507 );
508 }
509 Object.assign(data.createData, {
510 layer:
511 layer === undefined ? contextInfo.issuerLayer || null : layer,
512 request: stringifyLoadersAndResource(
513 allLoaders,
514 resourceData.resource
515 ),
516 userRequest,
517 rawRequest: request,
518 loaders: allLoaders,
519 resource: resourceData.resource,
520 matchResource: matchResourceData
521 ? matchResourceData.resource
522 : undefined,
523 resourceResolveData: resourceData.data,
524 settings,
525 type,
526 parser: this.getParser(type, settings.parser),
527 parserOptions: settings.parser,
528 generator: this.getGenerator(type, settings.generator),
529 generatorOptions: settings.generator,
530 resolveOptions
531 });
532 callback();
533 });
534 this.resolveRequestArray(
535 contextInfo,
536 this.context,
537 useLoadersPost,
538 loaderResolver,
539 resolveContext,
540 (err, result) => {
541 postLoaders = result;
542 continueCallback(err);
543 }
544 );
545 this.resolveRequestArray(
546 contextInfo,
547 this.context,
548 useLoaders,
549 loaderResolver,
550 resolveContext,
551 (err, result) => {
552 normalLoaders = result;
553 continueCallback(err);
554 }
555 );
556 this.resolveRequestArray(
557 contextInfo,
558 this.context,
559 useLoadersPre,
560 loaderResolver,
561 resolveContext,
562 (err, result) => {
563 preLoaders = result;
564 continueCallback(err);
565 }
566 );
567 });
568
569 this.resolveRequestArray(
570 contextInfo,
571 context,
572 elements,
573 loaderResolver,
574 resolveContext,
575 (err, result) => {
576 if (err) return continueCallback(err);
577 loaders = result;
578 continueCallback();
579 }
580 );
581
582 // resource with scheme
583 if (scheme) {
584 resourceData = {
585 resource: unresolvedResource,
586 data: {},
587 path: undefined,
588 query: undefined,
589 fragment: undefined
590 };
591 this.hooks.resolveForScheme
592 .for(scheme)
593 .callAsync(resourceData, data, err => {
594 if (err) return continueCallback(err);
595 continueCallback();
596 });
597 }
598
599 // resource without scheme and without path
600 else if (/^($|\?)/.test(unresolvedResource)) {
601 resourceData = {
602 resource: unresolvedResource,
603 data: {},
604 ...cacheParseResource(unresolvedResource)
605 };
606 continueCallback();
607 }
608
609 // resource without scheme and with path
610 else {
611 const normalResolver = this.getResolver(
612 "normal",
613 dependencyType
614 ? cachedSetProperty(
615 resolveOptions || EMPTY_RESOLVE_OPTIONS,
616 "dependencyType",
617 dependencyType
618 )
619 : resolveOptions
620 );
621 this.resolveResource(
622 contextInfo,
623 context,
624 unresolvedResource,
625 normalResolver,
626 resolveContext,
627 (err, resolvedResource, resolvedResourceResolveData) => {
628 if (err) return continueCallback(err);
629 if (resolvedResource !== false) {
630 resourceData = {
631 resource: resolvedResource,
632 data: resolvedResourceResolveData,
633 ...cacheParseResource(resolvedResource)
634 };
635 }
636 continueCallback();
637 }
638 );
639 }
640 }
641 );
642 }
643
644 /**
645 * @param {ModuleFactoryCreateData} data data object
646 * @param {function(Error=, ModuleFactoryResult=): void} callback callback
647 * @returns {void}
648 */
649 create(data, callback) {
650 const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies);
651 if (this.unsafeCache) {
652 const cacheEntry = unsafeCacheDependencies.get(dependencies[0]);
653 if (cacheEntry) {
654 const { module } = cacheEntry;
655 if (!this._restoredUnsafeCacheEntries.has(module)) {
656 const data = unsafeCacheData.get(module);
657 module.restoreFromUnsafeCache(data, this);
658 this._restoredUnsafeCacheEntries.add(module);
659 }
660 return callback(null, cacheEntry);
661 }
662 }
663 const context = data.context || this.context;
664 const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;
665 const dependency = dependencies[0];
666 const request = dependency.request;
667 const contextInfo = data.contextInfo;
668 const fileDependencies = new LazySet();
669 const missingDependencies = new LazySet();
670 const contextDependencies = new LazySet();
671 /** @type {ResolveData} */
672 const resolveData = {
673 contextInfo,
674 resolveOptions,
675 context,
676 request,
677 dependencies,
678 fileDependencies,
679 missingDependencies,
680 contextDependencies,
681 createData: {},
682 cacheable: true
683 };
684 this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
685 if (err) {
686 return callback(err, {
687 fileDependencies,
688 missingDependencies,
689 contextDependencies
690 });
691 }
692
693 // Ignored
694 if (result === false) {
695 return callback(null, {
696 fileDependencies,
697 missingDependencies,
698 contextDependencies
699 });
700 }
701
702 if (typeof result === "object")
703 throw new Error(
704 deprecationChangedHookMessage(
705 "beforeResolve",
706 this.hooks.beforeResolve
707 )
708 );
709
710 this.hooks.factorize.callAsync(resolveData, (err, module) => {
711 if (err) {
712 return callback(err, {
713 fileDependencies,
714 missingDependencies,
715 contextDependencies
716 });
717 }
718
719 const factoryResult = {
720 module,
721 fileDependencies,
722 missingDependencies,
723 contextDependencies
724 };
725
726 if (
727 this.unsafeCache &&
728 resolveData.cacheable &&
729 module &&
730 module.restoreFromUnsafeCache &&
731 this.cachePredicate(module)
732 ) {
733 for (const d of dependencies) {
734 unsafeCacheDependencies.set(d, factoryResult);
735 }
736 if (!unsafeCacheData.has(module)) {
737 unsafeCacheData.set(module, module.getUnsafeCacheData());
738 }
739 }
740
741 callback(null, factoryResult);
742 });
743 });
744 }
745
746 resolveResource(
747 contextInfo,
748 context,
749 unresolvedResource,
750 resolver,
751 resolveContext,
752 callback
753 ) {
754 resolver.resolve(
755 contextInfo,
756 context,
757 unresolvedResource,
758 resolveContext,
759 (err, resolvedResource, resolvedResourceResolveData) => {
760 if (err) {
761 return this._resolveResourceErrorHints(
762 err,
763 contextInfo,
764 context,
765 unresolvedResource,
766 resolver,
767 resolveContext,
768 (err2, hints) => {
769 if (err2) {
770 err.message += `
771An fatal error happened during resolving additional hints for this error: ${err2.message}`;
772 err.stack += `
773
774An fatal error happened during resolving additional hints for this error:
775${err2.stack}`;
776 return callback(err);
777 }
778 if (hints && hints.length > 0) {
779 err.message += `
780${hints.join("\n\n")}`;
781 }
782 callback(err);
783 }
784 );
785 }
786 callback(err, resolvedResource, resolvedResourceResolveData);
787 }
788 );
789 }
790
791 _resolveResourceErrorHints(
792 error,
793 contextInfo,
794 context,
795 unresolvedResource,
796 resolver,
797 resolveContext,
798 callback
799 ) {
800 asyncLib.parallel(
801 [
802 callback => {
803 if (!resolver.options.fullySpecified) return callback();
804 resolver
805 .withOptions({
806 fullySpecified: false
807 })
808 .resolve(
809 contextInfo,
810 context,
811 unresolvedResource,
812 resolveContext,
813 (err, resolvedResource) => {
814 if (!err && resolvedResource) {
815 const resource = parseResource(resolvedResource).path.replace(
816 /^.*[\\/]/,
817 ""
818 );
819 return callback(
820 null,
821 `Did you mean '${resource}'?
822BREAKING CHANGE: The request '${unresolvedResource}' failed to resolve only because it was resolved as fully specified
823(probably because the origin is a '*.mjs' file or a '*.js' file where the package.json contains '"type": "module"').
824The extension in the request is mandatory for it to be fully specified.
825Add the extension to the request.`
826 );
827 }
828 callback();
829 }
830 );
831 },
832 callback => {
833 if (!resolver.options.enforceExtension) return callback();
834 resolver
835 .withOptions({
836 enforceExtension: false,
837 extensions: []
838 })
839 .resolve(
840 contextInfo,
841 context,
842 unresolvedResource,
843 resolveContext,
844 (err, resolvedResource) => {
845 if (!err && resolvedResource) {
846 let hint = "";
847 const match = /(\.[^.]+)(\?|$)/.exec(unresolvedResource);
848 if (match) {
849 const fixedRequest = unresolvedResource.replace(
850 /(\.[^.]+)(\?|$)/,
851 "$2"
852 );
853 if (resolver.options.extensions.has(match[1])) {
854 hint = `Did you mean '${fixedRequest}'?`;
855 } else {
856 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?`;
857 }
858 } else {
859 hint = `Did you mean to omit the extension or to remove 'resolve.enforceExtension'?`;
860 }
861 return callback(
862 null,
863 `The request '${unresolvedResource}' failed to resolve only because 'resolve.enforceExtension' was specified.
864${hint}
865Including the extension in the request is no longer possible. Did you mean to enforce including the extension in requests with 'resolve.extensions: []' instead?`
866 );
867 }
868 callback();
869 }
870 );
871 },
872 callback => {
873 if (
874 /^\.\.?\//.test(unresolvedResource) ||
875 resolver.options.preferRelative
876 ) {
877 return callback();
878 }
879 resolver.resolve(
880 contextInfo,
881 context,
882 `./${unresolvedResource}`,
883 resolveContext,
884 (err, resolvedResource) => {
885 if (err || !resolvedResource) return callback();
886 const moduleDirectories = resolver.options.modules
887 .map(m => (Array.isArray(m) ? m.join(", ") : m))
888 .join(", ");
889 callback(
890 null,
891 `Did you mean './${unresolvedResource}'?
892Requests that should resolve in the current directory need to start with './'.
893Requests that start with a name are treated as module requests and resolve within module directories (${moduleDirectories}).
894If 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.`
895 );
896 }
897 );
898 }
899 ],
900 (err, hints) => {
901 if (err) return callback(err);
902 callback(null, hints.filter(Boolean));
903 }
904 );
905 }
906
907 resolveRequestArray(
908 contextInfo,
909 context,
910 array,
911 resolver,
912 resolveContext,
913 callback
914 ) {
915 if (array.length === 0) return callback(null, array);
916 asyncLib.map(
917 array,
918 (item, callback) => {
919 resolver.resolve(
920 contextInfo,
921 context,
922 item.loader,
923 resolveContext,
924 (err, result) => {
925 if (
926 err &&
927 /^[^/]*$/.test(item.loader) &&
928 !/-loader$/.test(item.loader)
929 ) {
930 return resolver.resolve(
931 contextInfo,
932 context,
933 item.loader + "-loader",
934 resolveContext,
935 err2 => {
936 if (!err2) {
937 err.message =
938 err.message +
939 "\n" +
940 "BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.\n" +
941 ` You need to specify '${item.loader}-loader' instead of '${item.loader}',\n` +
942 " see https://webpack.js.org/migrate/3/#automatic-loader-module-name-extension-removed";
943 }
944 callback(err);
945 }
946 );
947 }
948 if (err) return callback(err);
949
950 const parsedResult = identToLoaderRequest(result);
951 const resolved = {
952 loader: parsedResult.loader,
953 options:
954 item.options === undefined
955 ? parsedResult.options
956 : item.options,
957 ident: item.options === undefined ? undefined : item.ident
958 };
959 return callback(null, resolved);
960 }
961 );
962 },
963 callback
964 );
965 }
966
967 getParser(type, parserOptions = EMPTY_PARSER_OPTIONS) {
968 let cache = this.parserCache.get(type);
969
970 if (cache === undefined) {
971 cache = new WeakMap();
972 this.parserCache.set(type, cache);
973 }
974
975 let parser = cache.get(parserOptions);
976
977 if (parser === undefined) {
978 parser = this.createParser(type, parserOptions);
979 cache.set(parserOptions, parser);
980 }
981
982 return parser;
983 }
984
985 /**
986 * @param {string} type type
987 * @param {{[k: string]: any}} parserOptions parser options
988 * @returns {Parser} parser
989 */
990 createParser(type, parserOptions = {}) {
991 parserOptions = mergeGlobalOptions(
992 this._globalParserOptions,
993 type,
994 parserOptions
995 );
996 const parser = this.hooks.createParser.for(type).call(parserOptions);
997 if (!parser) {
998 throw new Error(`No parser registered for ${type}`);
999 }
1000 this.hooks.parser.for(type).call(parser, parserOptions);
1001 return parser;
1002 }
1003
1004 getGenerator(type, generatorOptions = EMPTY_GENERATOR_OPTIONS) {
1005 let cache = this.generatorCache.get(type);
1006
1007 if (cache === undefined) {
1008 cache = new WeakMap();
1009 this.generatorCache.set(type, cache);
1010 }
1011
1012 let generator = cache.get(generatorOptions);
1013
1014 if (generator === undefined) {
1015 generator = this.createGenerator(type, generatorOptions);
1016 cache.set(generatorOptions, generator);
1017 }
1018
1019 return generator;
1020 }
1021
1022 createGenerator(type, generatorOptions = {}) {
1023 generatorOptions = mergeGlobalOptions(
1024 this._globalGeneratorOptions,
1025 type,
1026 generatorOptions
1027 );
1028 const generator = this.hooks.createGenerator
1029 .for(type)
1030 .call(generatorOptions);
1031 if (!generator) {
1032 throw new Error(`No generator registered for ${type}`);
1033 }
1034 this.hooks.generator.for(type).call(generator, generatorOptions);
1035 return generator;
1036 }
1037
1038 getResolver(type, resolveOptions) {
1039 return this.resolverFactory.get(type, resolveOptions);
1040 }
1041}
1042
1043module.exports = NormalModuleFactory;