UNPKG

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