UNPKG

4.94 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 Factory = require("enhanced-resolve").ResolverFactory;
9const { HookMap, SyncHook, SyncWaterfallHook } = require("tapable");
10const {
11 cachedCleverMerge,
12 removeOperations,
13 resolveByProperty
14} = require("./util/cleverMerge");
15
16/** @typedef {import("enhanced-resolve").ResolveOptions} ResolveOptions */
17/** @typedef {import("enhanced-resolve").Resolver} Resolver */
18/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} WebpackResolveOptions */
19/** @typedef {import("../declarations/WebpackOptions").ResolvePluginInstance} ResolvePluginInstance */
20
21/** @typedef {WebpackResolveOptions & {dependencyType?: string, resolveToContext?: boolean }} ResolveOptionsWithDependencyType */
22/**
23 * @typedef {Object} WithOptions
24 * @property {function(Partial<ResolveOptionsWithDependencyType>): ResolverWithOptions} withOptions create a resolver with additional/different options
25 */
26
27/** @typedef {Resolver & WithOptions} ResolverWithOptions */
28
29// need to be hoisted on module level for caching identity
30const EMPTY_RESOLVE_OPTIONS = {};
31
32/**
33 * @param {ResolveOptionsWithDependencyType} resolveOptionsWithDepType enhanced options
34 * @returns {ResolveOptions} merged options
35 */
36const convertToResolveOptions = resolveOptionsWithDepType => {
37 const { dependencyType, plugins, ...remaining } = resolveOptionsWithDepType;
38
39 // check type compat
40 /** @type {Partial<ResolveOptions>} */
41 const partialOptions = {
42 ...remaining,
43 plugins:
44 plugins &&
45 /** @type {ResolvePluginInstance[]} */ (plugins.filter(
46 item => item !== "..."
47 ))
48 };
49
50 if (!partialOptions.fileSystem) {
51 throw new Error(
52 "fileSystem is missing in resolveOptions, but it's required for enhanced-resolve"
53 );
54 }
55 // These weird types validate that we checked all non-optional properties
56 const options = /** @type {Partial<ResolveOptions> & Pick<ResolveOptions, "fileSystem">} */ (partialOptions);
57
58 return removeOperations(
59 resolveByProperty(options, "byDependency", dependencyType)
60 );
61};
62
63/**
64 * @typedef {Object} ResolverCache
65 * @property {WeakMap<Object, ResolverWithOptions>} direct
66 * @property {Map<string, ResolverWithOptions>} stringified
67 */
68
69module.exports = class ResolverFactory {
70 constructor() {
71 this.hooks = Object.freeze({
72 /** @type {HookMap<SyncWaterfallHook<[ResolveOptionsWithDependencyType]>>} */
73 resolveOptions: new HookMap(
74 () => new SyncWaterfallHook(["resolveOptions"])
75 ),
76 /** @type {HookMap<SyncHook<[Resolver, ResolveOptions, ResolveOptionsWithDependencyType]>>} */
77 resolver: new HookMap(
78 () => new SyncHook(["resolver", "resolveOptions", "userResolveOptions"])
79 )
80 });
81 /** @type {Map<string, ResolverCache>} */
82 this.cache = new Map();
83 }
84
85 /**
86 * @param {string} type type of resolver
87 * @param {ResolveOptionsWithDependencyType=} resolveOptions options
88 * @returns {ResolverWithOptions} the resolver
89 */
90 get(type, resolveOptions = EMPTY_RESOLVE_OPTIONS) {
91 let typedCaches = this.cache.get(type);
92 if (!typedCaches) {
93 typedCaches = {
94 direct: new WeakMap(),
95 stringified: new Map()
96 };
97 this.cache.set(type, typedCaches);
98 }
99 const cachedResolver = typedCaches.direct.get(resolveOptions);
100 if (cachedResolver) {
101 return cachedResolver;
102 }
103 const ident = JSON.stringify(resolveOptions);
104 const resolver = typedCaches.stringified.get(ident);
105 if (resolver) {
106 typedCaches.direct.set(resolveOptions, resolver);
107 return resolver;
108 }
109 const newResolver = this._create(type, resolveOptions);
110 typedCaches.direct.set(resolveOptions, newResolver);
111 typedCaches.stringified.set(ident, newResolver);
112 return newResolver;
113 }
114
115 /**
116 * @param {string} type type of resolver
117 * @param {ResolveOptionsWithDependencyType} resolveOptionsWithDepType options
118 * @returns {ResolverWithOptions} the resolver
119 */
120 _create(type, resolveOptionsWithDepType) {
121 /** @type {ResolveOptionsWithDependencyType} */
122 const originalResolveOptions = { ...resolveOptionsWithDepType };
123
124 const resolveOptions = convertToResolveOptions(
125 this.hooks.resolveOptions.for(type).call(resolveOptionsWithDepType)
126 );
127 const resolver = /** @type {ResolverWithOptions} */ (Factory.createResolver(
128 resolveOptions
129 ));
130 if (!resolver) {
131 throw new Error("No resolver created");
132 }
133 /** @type {WeakMap<Partial<ResolveOptionsWithDependencyType>, ResolverWithOptions>} */
134 const childCache = new WeakMap();
135 resolver.withOptions = options => {
136 const cacheEntry = childCache.get(options);
137 if (cacheEntry !== undefined) return cacheEntry;
138 const mergedOptions = cachedCleverMerge(originalResolveOptions, options);
139 const resolver = this.get(type, mergedOptions);
140 childCache.set(options, resolver);
141 return resolver;
142 };
143 this.hooks.resolver
144 .for(type)
145 .call(resolver, resolveOptions, originalResolveOptions);
146 return resolver;
147 }
148};