UNPKG

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