UNPKG

24.1 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 RawModule = require("./RawModule");
20const BasicEffectRulePlugin = require("./rules/BasicEffectRulePlugin");
21const BasicMatcherRulePlugin = require("./rules/BasicMatcherRulePlugin");
22const DescriptionDataMatcherRulePlugin = require("./rules/DescriptionDataMatcherRulePlugin");
23const RuleSetCompiler = require("./rules/RuleSetCompiler");
24const UseEffectRulePlugin = require("./rules/UseEffectRulePlugin");
25const LazySet = require("./util/LazySet");
26const { getScheme } = require("./util/URLAbsoluteSpecifier");
27const { cachedCleverMerge, cachedSetProperty } = require("./util/cleverMerge");
28const { join } = require("./util/fs");
29const { parseResource } = require("./util/identifier");
30
31/** @typedef {import("../declarations/WebpackOptions").ModuleOptions} ModuleOptions */
32/** @typedef {import("./Generator")} Generator */
33/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
34/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
35/** @typedef {import("./Parser")} Parser */
36/** @typedef {import("./ResolverFactory")} ResolverFactory */
37/** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */
38/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
39
40/**
41 * @typedef {Object} ResolveData
42 * @property {ModuleFactoryCreateData["contextInfo"]} contextInfo
43 * @property {ModuleFactoryCreateData["resolveOptions"]} resolveOptions
44 * @property {string} context
45 * @property {string} request
46 * @property {ModuleDependency[]} dependencies
47 * @property {Object} createData
48 * @property {LazySet<string>} fileDependencies
49 * @property {LazySet<string>} missingDependencies
50 * @property {LazySet<string>} contextDependencies
51 * @property {boolean} cacheable allow to use the unsafe cache
52 */
53
54/**
55 * @typedef {Object} ResourceData
56 * @property {string} resource
57 * @property {string} path
58 * @property {string} query
59 * @property {string} fragment
60 */
61
62/** @typedef {ResourceData & { data: Record<string, any> }} ResourceDataWithData */
63
64const EMPTY_RESOLVE_OPTIONS = {};
65
66const MATCH_RESOURCE_REGEX = /^([^!]+)!=!/;
67
68const loaderToIdent = data => {
69 if (!data.options) {
70 return data.loader;
71 }
72 if (typeof data.options === "string") {
73 return data.loader + "?" + data.options;
74 }
75 if (typeof data.options !== "object") {
76 throw new Error("loader options must be string or object");
77 }
78 if (data.ident) {
79 return data.loader + "??" + data.ident;
80 }
81 return data.loader + "?" + JSON.stringify(data.options);
82};
83
84const stringifyLoadersAndResource = (loaders, resource) => {
85 let str = "";
86 for (const loader of loaders) {
87 str += loaderToIdent(loader) + "!";
88 }
89 return str + resource;
90};
91
92/**
93 * @param {string} resultString resultString
94 * @returns {{loader: string, options: string|undefined}} parsed loader request
95 */
96const identToLoaderRequest = resultString => {
97 const idx = resultString.indexOf("?");
98 if (idx >= 0) {
99 const loader = resultString.substr(0, idx);
100 const options = resultString.substr(idx + 1);
101 return {
102 loader,
103 options
104 };
105 } else {
106 return {
107 loader: resultString,
108 options: undefined
109 };
110 }
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
125// TODO webpack 6 remove
126const deprecationChangedHookMessage = name =>
127 `NormalModuleFactory.${name} is no longer a waterfall hook, but a bailing hook instead. ` +
128 "Do not return the passed object, but modify it instead. " +
129 "Returning false will ignore the request and results in no module created.";
130
131const dependencyCache = new WeakMap();
132
133const ruleSetCompiler = new RuleSetCompiler([
134 new BasicMatcherRulePlugin("test", "resource"),
135 new BasicMatcherRulePlugin("mimetype"),
136 new BasicMatcherRulePlugin("dependency"),
137 new BasicMatcherRulePlugin("include", "resource"),
138 new BasicMatcherRulePlugin("exclude", "resource", true),
139 new BasicMatcherRulePlugin("resource"),
140 new BasicMatcherRulePlugin("resourceQuery"),
141 new BasicMatcherRulePlugin("resourceFragment"),
142 new BasicMatcherRulePlugin("realResource"),
143 new BasicMatcherRulePlugin("issuer"),
144 new BasicMatcherRulePlugin("compiler"),
145 new DescriptionDataMatcherRulePlugin(),
146 new BasicEffectRulePlugin("type"),
147 new BasicEffectRulePlugin("sideEffects"),
148 new BasicEffectRulePlugin("parser"),
149 new BasicEffectRulePlugin("resolve"),
150 new BasicEffectRulePlugin("generator"),
151 new UseEffectRulePlugin()
152]);
153
154class NormalModuleFactory extends ModuleFactory {
155 /**
156 * @param {Object} param params
157 * @param {string=} param.context context
158 * @param {InputFileSystem} param.fs file system
159 * @param {ResolverFactory} param.resolverFactory resolverFactory
160 * @param {ModuleOptions} param.options options
161 * @param {Object=} param.associatedObjectForCache an object to which the cache will be attached
162 */
163 constructor({
164 context,
165 fs,
166 resolverFactory,
167 options,
168 associatedObjectForCache
169 }) {
170 super();
171 this.hooks = Object.freeze({
172 /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
173 resolve: new AsyncSeriesBailHook(["resolveData"]),
174 /** @type {HookMap<AsyncSeriesBailHook<[ResourceDataWithData, ResolveData], true | void>>} */
175 resolveForScheme: new HookMap(
176 () => new AsyncSeriesBailHook(["resourceData", "resolveData"])
177 ),
178 /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
179 factorize: new AsyncSeriesBailHook(["resolveData"]),
180 /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
181 beforeResolve: new AsyncSeriesBailHook(["resolveData"]),
182 /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
183 afterResolve: new AsyncSeriesBailHook(["resolveData"]),
184 /** @type {AsyncSeriesBailHook<[ResolveData["createData"], ResolveData], TODO>} */
185 createModule: new AsyncSeriesBailHook(["createData", "resolveData"]),
186 /** @type {SyncWaterfallHook<[Module, ResolveData["createData"], ResolveData], TODO>} */
187 module: new SyncWaterfallHook(["module", "createData", "resolveData"]),
188 createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
189 parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
190 createGenerator: new HookMap(
191 () => new SyncBailHook(["generatorOptions"])
192 ),
193 generator: new HookMap(
194 () => new SyncHook(["generator", "generatorOptions"])
195 )
196 });
197 this.resolverFactory = resolverFactory;
198 this.ruleSet = ruleSetCompiler.compile([
199 {
200 rules: options.defaultRules
201 },
202 {
203 rules: options.rules
204 }
205 ]);
206 this.unsafeCache = !!options.unsafeCache;
207 this.cachePredicate =
208 typeof options.unsafeCache === "function"
209 ? options.unsafeCache
210 : () => true;
211 this.context = context || "";
212 this.fs = fs;
213 /**
214 * @type {Map<string, WeakMap<Object, TODO>>}
215 */
216 this.parserCache = new Map();
217 /**
218 * @type {Map<string, WeakMap<Object, Generator>>}
219 */
220 this.generatorCache = new Map();
221
222 const cacheParseResource = parseResource.bindCache(
223 associatedObjectForCache
224 );
225
226 this.hooks.factorize.tapAsync(
227 {
228 name: "NormalModuleFactory",
229 stage: 100
230 },
231 (resolveData, callback) => {
232 this.hooks.resolve.callAsync(resolveData, (err, result) => {
233 if (err) return callback(err);
234
235 // Ignored
236 if (result === false) return callback();
237
238 // direct module
239 if (result instanceof Module) return callback(null, result);
240
241 if (typeof result === "object")
242 throw new Error(
243 deprecationChangedHookMessage("resolve") +
244 " Returning a Module object will result in this module used as result."
245 );
246
247 this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
248 if (err) return callback(err);
249
250 if (typeof result === "object")
251 throw new Error(deprecationChangedHookMessage("afterResolve"));
252
253 // Ignored
254 if (result === false) return callback();
255
256 const createData = resolveData.createData;
257
258 this.hooks.createModule.callAsync(
259 createData,
260 resolveData,
261 (err, createdModule) => {
262 if (!createdModule) {
263 if (!resolveData.request) {
264 return callback(new Error("Empty dependency (no request)"));
265 }
266
267 createdModule = new NormalModule(createData);
268 }
269
270 createdModule = this.hooks.module.call(
271 createdModule,
272 createData,
273 resolveData
274 );
275
276 return callback(null, createdModule);
277 }
278 );
279 });
280 });
281 }
282 );
283 this.hooks.resolve.tapAsync(
284 {
285 name: "NormalModuleFactory",
286 stage: 100
287 },
288 (data, callback) => {
289 const {
290 contextInfo,
291 context,
292 dependencies,
293 request,
294 resolveOptions,
295 fileDependencies,
296 missingDependencies,
297 contextDependencies
298 } = data;
299 const dependencyType =
300 (dependencies.length > 0 && dependencies[0].category) || "";
301 const loaderResolver = this.getResolver("loader");
302
303 /** @type {ResourceData | undefined} */
304 let matchResourceData = undefined;
305 /** @type {string} */
306 let requestWithoutMatchResource = request;
307 const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request);
308 if (matchResourceMatch) {
309 let matchResource = matchResourceMatch[1];
310 if (matchResource.charCodeAt(0) === 46) {
311 // 46 === ".", 47 === "/"
312 const secondChar = matchResource.charCodeAt(1);
313 if (
314 secondChar === 47 ||
315 (secondChar === 46 && matchResource.charCodeAt(2) === 47)
316 ) {
317 // if matchResources startsWith ../ or ./
318 matchResource = join(this.fs, context, matchResource);
319 }
320 }
321 matchResourceData = {
322 resource: matchResource,
323 ...cacheParseResource(matchResource)
324 };
325 requestWithoutMatchResource = request.substr(
326 matchResourceMatch[0].length
327 );
328 }
329
330 const firstChar = requestWithoutMatchResource.charCodeAt(0);
331 const secondChar = requestWithoutMatchResource.charCodeAt(1);
332 const noPreAutoLoaders = firstChar === 45 && secondChar === 33; // startsWith "-!"
333 const noAutoLoaders = noPreAutoLoaders || firstChar === 33; // startsWith "!"
334 const noPrePostAutoLoaders = firstChar === 33 && secondChar === 33; // startsWith "!!";
335 const rawElements = requestWithoutMatchResource
336 .slice(
337 noPreAutoLoaders || noPrePostAutoLoaders ? 2 : noAutoLoaders ? 1 : 0
338 )
339 .split(/!+/);
340 const unresolvedResource = rawElements.pop();
341 const elements = rawElements.map(identToLoaderRequest);
342
343 const resolveContext = {
344 fileDependencies,
345 missingDependencies,
346 contextDependencies
347 };
348
349 /** @type {ResourceDataWithData} */
350 let resourceData;
351 /** @type {string | undefined} */
352 const scheme = getScheme(unresolvedResource);
353
354 let loaders;
355
356 const continueCallback = needCalls(2, err => {
357 if (err) return callback(err);
358
359 // translate option idents
360 try {
361 for (const item of loaders) {
362 if (typeof item.options === "string" && item.options[0] === "?") {
363 const ident = item.options.substr(1);
364 if (ident === "[[missing ident]]") {
365 throw new Error(
366 "No ident is provided by referenced loader. " +
367 "When using a function for Rule.use in config you need to " +
368 "provide an 'ident' property for referenced loader options."
369 );
370 }
371 item.options = this.ruleSet.references.get(ident);
372 if (item.options === undefined) {
373 throw new Error(
374 "Invalid ident is provided by referenced loader"
375 );
376 }
377 item.ident = ident;
378 }
379 }
380 } catch (e) {
381 return callback(e);
382 }
383
384 if (!resourceData) {
385 // ignored
386 return callback(
387 null,
388 new RawModule(
389 "/* (ignored) */",
390 `ignored|${request}`,
391 `${request} (ignored)`
392 )
393 );
394 }
395
396 const userRequest =
397 (matchResourceData !== undefined
398 ? `${matchResourceData.resource}!=!`
399 : "") +
400 stringifyLoadersAndResource(loaders, resourceData.resource);
401
402 const resourceDataForRules = matchResourceData || resourceData;
403 const result = this.ruleSet.exec({
404 resource: resourceDataForRules.path,
405 realResource: resourceData.path,
406 resourceQuery: resourceDataForRules.query,
407 resourceFragment: resourceDataForRules.fragment,
408 mimetype: matchResourceData ? "" : resourceData.data.mimetype || "",
409 dependency: dependencyType,
410 descriptionData: matchResourceData
411 ? undefined
412 : resourceData.data.descriptionFileData,
413 issuer: contextInfo.issuer,
414 compiler: contextInfo.compiler
415 });
416 const settings = {};
417 const useLoadersPost = [];
418 const useLoaders = [];
419 const useLoadersPre = [];
420 for (const r of result) {
421 if (r.type === "use") {
422 if (!noAutoLoaders && !noPrePostAutoLoaders) {
423 useLoaders.push(r.value);
424 }
425 } else if (r.type === "use-post") {
426 if (!noPrePostAutoLoaders) {
427 useLoadersPost.push(r.value);
428 }
429 } else if (r.type === "use-pre") {
430 if (!noPreAutoLoaders && !noPrePostAutoLoaders) {
431 useLoadersPre.push(r.value);
432 }
433 } else if (
434 typeof r.value === "object" &&
435 r.value !== null &&
436 typeof settings[r.type] === "object" &&
437 settings[r.type] !== null
438 ) {
439 settings[r.type] = cachedCleverMerge(settings[r.type], r.value);
440 } else {
441 settings[r.type] = r.value;
442 }
443 }
444
445 let postLoaders, normalLoaders, preLoaders;
446
447 const continueCallback = needCalls(3, err => {
448 if (err) {
449 return callback(err);
450 }
451 const allLoaders = postLoaders;
452 if (matchResourceData === undefined) {
453 for (const loader of loaders) allLoaders.push(loader);
454 for (const loader of normalLoaders) allLoaders.push(loader);
455 } else {
456 for (const loader of normalLoaders) allLoaders.push(loader);
457 for (const loader of loaders) allLoaders.push(loader);
458 }
459 for (const loader of preLoaders) allLoaders.push(loader);
460 const type = settings.type;
461 const resolveOptions = settings.resolve;
462 Object.assign(data.createData, {
463 request: stringifyLoadersAndResource(
464 allLoaders,
465 resourceData.resource
466 ),
467 userRequest,
468 rawRequest: request,
469 loaders: allLoaders,
470 resource: resourceData.resource,
471 matchResource: matchResourceData
472 ? matchResourceData.resource
473 : undefined,
474 resourceResolveData: resourceData.data,
475 settings,
476 type,
477 parser: this.getParser(type, settings.parser),
478 generator: this.getGenerator(type, settings.generator),
479 resolveOptions
480 });
481 callback();
482 });
483 this.resolveRequestArray(
484 contextInfo,
485 this.context,
486 useLoadersPost,
487 loaderResolver,
488 resolveContext,
489 (err, result) => {
490 postLoaders = result;
491 continueCallback(err);
492 }
493 );
494 this.resolveRequestArray(
495 contextInfo,
496 this.context,
497 useLoaders,
498 loaderResolver,
499 resolveContext,
500 (err, result) => {
501 normalLoaders = result;
502 continueCallback(err);
503 }
504 );
505 this.resolveRequestArray(
506 contextInfo,
507 this.context,
508 useLoadersPre,
509 loaderResolver,
510 resolveContext,
511 (err, result) => {
512 preLoaders = result;
513 continueCallback(err);
514 }
515 );
516 });
517
518 this.resolveRequestArray(
519 contextInfo,
520 context,
521 elements,
522 loaderResolver,
523 resolveContext,
524 (err, result) => {
525 if (err) return continueCallback(err);
526 loaders = result;
527 continueCallback();
528 }
529 );
530
531 // resource with scheme
532 if (scheme) {
533 resourceData = {
534 resource: unresolvedResource,
535 data: {},
536 path: undefined,
537 query: undefined,
538 fragment: undefined
539 };
540 this.hooks.resolveForScheme
541 .for(scheme)
542 .callAsync(resourceData, data, err => {
543 if (err) return continueCallback(err);
544 continueCallback();
545 });
546 }
547
548 // resource without scheme and without path
549 else if (/^($|\?|#)/.test(unresolvedResource)) {
550 resourceData = {
551 resource: unresolvedResource,
552 data: {},
553 ...cacheParseResource(unresolvedResource)
554 };
555 continueCallback();
556 }
557
558 // resource without scheme and with path
559 else {
560 const normalResolver = this.getResolver(
561 "normal",
562 dependencyType
563 ? cachedSetProperty(
564 resolveOptions || EMPTY_RESOLVE_OPTIONS,
565 "dependencyType",
566 dependencyType
567 )
568 : resolveOptions
569 );
570 this.resolveResource(
571 contextInfo,
572 context,
573 unresolvedResource,
574 normalResolver,
575 resolveContext,
576 (err, resolvedResource, resolvedResourceResolveData) => {
577 if (err) return continueCallback(err);
578 if (resolvedResource !== false) {
579 resourceData = {
580 resource: resolvedResource,
581 data: resolvedResourceResolveData,
582 ...cacheParseResource(resolvedResource)
583 };
584 }
585 continueCallback();
586 }
587 );
588 }
589 }
590 );
591 }
592
593 /**
594 * @param {ModuleFactoryCreateData} data data object
595 * @param {function(Error=, ModuleFactoryResult=): void} callback callback
596 * @returns {void}
597 */
598 create(data, callback) {
599 const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies);
600 if (this.unsafeCache) {
601 const cacheEntry = dependencyCache.get(dependencies[0]);
602 if (cacheEntry) return callback(null, cacheEntry);
603 }
604 const context = data.context || this.context;
605 const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;
606 const dependency = dependencies[0];
607 const request = dependency.request;
608 const contextInfo = data.contextInfo;
609 const fileDependencies = new LazySet();
610 const missingDependencies = new LazySet();
611 const contextDependencies = new LazySet();
612 /** @type {ResolveData} */
613 const resolveData = {
614 contextInfo,
615 resolveOptions,
616 context,
617 request,
618 dependencies,
619 fileDependencies,
620 missingDependencies,
621 contextDependencies,
622 createData: {},
623 cacheable: true
624 };
625 this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
626 if (err) {
627 return callback(err, {
628 fileDependencies,
629 missingDependencies,
630 contextDependencies
631 });
632 }
633
634 // Ignored
635 if (result === false) {
636 return callback(null, {
637 fileDependencies,
638 missingDependencies,
639 contextDependencies
640 });
641 }
642
643 if (typeof result === "object")
644 throw new Error(deprecationChangedHookMessage("beforeResolve"));
645
646 this.hooks.factorize.callAsync(resolveData, (err, module) => {
647 if (err) {
648 return callback(err, {
649 fileDependencies,
650 missingDependencies,
651 contextDependencies
652 });
653 }
654
655 const factoryResult = {
656 module,
657 fileDependencies,
658 missingDependencies,
659 contextDependencies
660 };
661
662 if (
663 this.unsafeCache &&
664 resolveData.cacheable &&
665 module &&
666 this.cachePredicate(module)
667 ) {
668 for (const d of dependencies) {
669 dependencyCache.set(d, factoryResult);
670 }
671 }
672
673 callback(null, factoryResult);
674 });
675 });
676 }
677
678 resolveResource(
679 contextInfo,
680 context,
681 unresolvedResource,
682 resolver,
683 resolveContext,
684 callback
685 ) {
686 resolver.resolve(
687 contextInfo,
688 context,
689 unresolvedResource,
690 resolveContext,
691 (err, resolvedResource, resolvedResourceResolveData) => {
692 if (err) {
693 if (resolver.options.fullySpecified) {
694 resolver
695 .withOptions({
696 fullySpecified: false
697 })
698 .resolve(
699 contextInfo,
700 context,
701 unresolvedResource,
702 resolveContext,
703 (err2, resolvedResource) => {
704 if (!err2 && resolvedResource) {
705 const resource = parseResource(
706 resolvedResource
707 ).path.replace(/^.*[\\/]/, "");
708 err.message += `
709Did you mean '${resource}'?
710BREAKING CHANGE: The request '${unresolvedResource}' failed to resolve only because it was resolved as fully specified
711(probably because the origin is a '*.mjs' file or a '*.js' file where the package.json contains '"type": "module"').
712The extension in the request is mandatory for it to be fully specified.
713Add the extension to the request.`;
714 }
715 callback(err);
716 }
717 );
718 return;
719 }
720 }
721 callback(err, resolvedResource, resolvedResourceResolveData);
722 }
723 );
724 }
725
726 resolveRequestArray(
727 contextInfo,
728 context,
729 array,
730 resolver,
731 resolveContext,
732 callback
733 ) {
734 if (array.length === 0) return callback(null, array);
735 asyncLib.map(
736 array,
737 (item, callback) => {
738 resolver.resolve(
739 contextInfo,
740 context,
741 item.loader,
742 resolveContext,
743 (err, result) => {
744 if (
745 err &&
746 /^[^/]*$/.test(item.loader) &&
747 !/-loader$/.test(item.loader)
748 ) {
749 return resolver.resolve(
750 contextInfo,
751 context,
752 item.loader + "-loader",
753 resolveContext,
754 err2 => {
755 if (!err2) {
756 err.message =
757 err.message +
758 "\n" +
759 "BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders.\n" +
760 ` You need to specify '${item.loader}-loader' instead of '${item.loader}',\n` +
761 " see https://webpack.js.org/migrate/3/#automatic-loader-module-name-extension-removed";
762 }
763 callback(err);
764 }
765 );
766 }
767 if (err) return callback(err);
768
769 const parsedResult = identToLoaderRequest(result);
770 const resolved = {
771 loader: parsedResult.loader,
772 options:
773 item.options === undefined
774 ? parsedResult.options
775 : item.options,
776 ident: item.options === undefined ? undefined : item.ident
777 };
778 return callback(null, resolved);
779 }
780 );
781 },
782 callback
783 );
784 }
785
786 getParser(type, parserOptions = EMPTY_RESOLVE_OPTIONS) {
787 let cache = this.parserCache.get(type);
788
789 if (cache === undefined) {
790 cache = new WeakMap();
791 this.parserCache.set(type, cache);
792 }
793
794 let parser = cache.get(parserOptions);
795
796 if (parser === undefined) {
797 parser = this.createParser(type, parserOptions);
798 cache.set(parserOptions, parser);
799 }
800
801 return parser;
802 }
803
804 /**
805 * @param {string} type type
806 * @param {{[k: string]: any}} parserOptions parser options
807 * @returns {Parser} parser
808 */
809 createParser(type, parserOptions = {}) {
810 const parser = this.hooks.createParser.for(type).call(parserOptions);
811 if (!parser) {
812 throw new Error(`No parser registered for ${type}`);
813 }
814 this.hooks.parser.for(type).call(parser, parserOptions);
815 return parser;
816 }
817
818 getGenerator(type, generatorOptions = EMPTY_RESOLVE_OPTIONS) {
819 let cache = this.generatorCache.get(type);
820
821 if (cache === undefined) {
822 cache = new WeakMap();
823 this.generatorCache.set(type, cache);
824 }
825
826 let generator = cache.get(generatorOptions);
827
828 if (generator === undefined) {
829 generator = this.createGenerator(type, generatorOptions);
830 cache.set(generatorOptions, generator);
831 }
832
833 return generator;
834 }
835
836 createGenerator(type, generatorOptions = {}) {
837 const generator = this.hooks.createGenerator
838 .for(type)
839 .call(generatorOptions);
840 if (!generator) {
841 throw new Error(`No generator registered for ${type}`);
842 }
843 this.hooks.generator.for(type).call(generator, generatorOptions);
844 return generator;
845 }
846
847 getResolver(type, resolveOptions) {
848 return this.resolverFactory.get(type, resolveOptions);
849 }
850}
851
852module.exports = NormalModuleFactory;