UNPKG

10.3 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 ModuleNotFoundError = require("../ModuleNotFoundError");
9const RuntimeGlobals = require("../RuntimeGlobals");
10const WebpackError = require("../WebpackError");
11const { parseOptions } = require("../container/options");
12const LazySet = require("../util/LazySet");
13const createSchemaValidation = require("../util/create-schema-validation");
14const { parseRange } = require("../util/semver");
15const ConsumeSharedFallbackDependency = require("./ConsumeSharedFallbackDependency");
16const ConsumeSharedModule = require("./ConsumeSharedModule");
17const ConsumeSharedRuntimeModule = require("./ConsumeSharedRuntimeModule");
18const ProvideForSharedDependency = require("./ProvideForSharedDependency");
19const { resolveMatchedConfigs } = require("./resolveMatchedConfigs");
20const {
21 isRequiredVersion,
22 getDescriptionFile,
23 getRequiredVersionFromDescriptionFile
24} = require("./utils");
25
26/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumeSharedPluginOptions} ConsumeSharedPluginOptions */
27/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumesConfig} ConsumesConfig */
28/** @typedef {import("../Compiler")} Compiler */
29/** @typedef {import("../ResolverFactory").ResolveOptionsWithDependencyType} ResolveOptionsWithDependencyType */
30/** @typedef {import("./ConsumeSharedModule").ConsumeOptions} ConsumeOptions */
31
32const validate = createSchemaValidation(
33 require("../../schemas/plugins/sharing/ConsumeSharedPlugin.check.js"),
34 () => require("../../schemas/plugins/sharing/ConsumeSharedPlugin.json"),
35 {
36 name: "Consume Shared Plugin",
37 baseDataPath: "options"
38 }
39);
40
41/** @type {ResolveOptionsWithDependencyType} */
42const RESOLVE_OPTIONS = { dependencyType: "esm" };
43const PLUGIN_NAME = "ConsumeSharedPlugin";
44
45class ConsumeSharedPlugin {
46 /**
47 * @param {ConsumeSharedPluginOptions} options options
48 */
49 constructor(options) {
50 if (typeof options !== "string") {
51 validate(options);
52 }
53
54 /** @type {[string, ConsumeOptions][]} */
55 this._consumes = parseOptions(
56 options.consumes,
57 (item, key) => {
58 if (Array.isArray(item)) throw new Error("Unexpected array in options");
59 /** @type {ConsumeOptions} */
60 let result =
61 item === key || !isRequiredVersion(item)
62 ? // item is a request/key
63 {
64 import: key,
65 shareScope: options.shareScope || "default",
66 shareKey: key,
67 requiredVersion: undefined,
68 packageName: undefined,
69 strictVersion: false,
70 singleton: false,
71 eager: false
72 }
73 : // key is a request/key
74 // item is a version
75 {
76 import: key,
77 shareScope: options.shareScope || "default",
78 shareKey: key,
79 requiredVersion: parseRange(item),
80 strictVersion: true,
81 packageName: undefined,
82 singleton: false,
83 eager: false
84 };
85 return result;
86 },
87 (item, key) => ({
88 import: item.import === false ? undefined : item.import || key,
89 shareScope: item.shareScope || options.shareScope || "default",
90 shareKey: item.shareKey || key,
91 requiredVersion:
92 typeof item.requiredVersion === "string"
93 ? parseRange(item.requiredVersion)
94 : item.requiredVersion,
95 strictVersion:
96 typeof item.strictVersion === "boolean"
97 ? item.strictVersion
98 : item.import !== false && !item.singleton,
99 packageName: item.packageName,
100 singleton: !!item.singleton,
101 eager: !!item.eager
102 })
103 );
104 }
105
106 /**
107 * Apply the plugin
108 * @param {Compiler} compiler the compiler instance
109 * @returns {void}
110 */
111 apply(compiler) {
112 compiler.hooks.thisCompilation.tap(
113 PLUGIN_NAME,
114 (compilation, { normalModuleFactory }) => {
115 compilation.dependencyFactories.set(
116 ConsumeSharedFallbackDependency,
117 normalModuleFactory
118 );
119
120 let unresolvedConsumes, resolvedConsumes, prefixedConsumes;
121 const promise = resolveMatchedConfigs(compilation, this._consumes).then(
122 ({ resolved, unresolved, prefixed }) => {
123 resolvedConsumes = resolved;
124 unresolvedConsumes = unresolved;
125 prefixedConsumes = prefixed;
126 }
127 );
128
129 const resolver = compilation.resolverFactory.get(
130 "normal",
131 RESOLVE_OPTIONS
132 );
133
134 /**
135 * @param {string} context issuer directory
136 * @param {string} request request
137 * @param {ConsumeOptions} config options
138 * @returns {Promise<ConsumeSharedModule>} create module
139 */
140 const createConsumeSharedModule = (context, request, config) => {
141 const requiredVersionWarning = details => {
142 const error = new WebpackError(
143 `No required version specified and unable to automatically determine one. ${details}`
144 );
145 error.file = `shared module ${request}`;
146 compilation.warnings.push(error);
147 };
148 const directFallback =
149 config.import &&
150 /^(\.\.?(\/|$)|\/|[A-Za-z]:|\\\\)/.test(config.import);
151 return Promise.all([
152 new Promise(resolve => {
153 if (!config.import) return resolve();
154 const resolveContext = {
155 /** @type {LazySet<string>} */
156 fileDependencies: new LazySet(),
157 /** @type {LazySet<string>} */
158 contextDependencies: new LazySet(),
159 /** @type {LazySet<string>} */
160 missingDependencies: new LazySet()
161 };
162 resolver.resolve(
163 {},
164 directFallback ? compiler.context : context,
165 config.import,
166 resolveContext,
167 (err, result) => {
168 compilation.contextDependencies.addAll(
169 resolveContext.contextDependencies
170 );
171 compilation.fileDependencies.addAll(
172 resolveContext.fileDependencies
173 );
174 compilation.missingDependencies.addAll(
175 resolveContext.missingDependencies
176 );
177 if (err) {
178 compilation.errors.push(
179 new ModuleNotFoundError(null, err, {
180 name: `resolving fallback for shared module ${request}`
181 })
182 );
183 return resolve();
184 }
185 resolve(result);
186 }
187 );
188 }),
189 new Promise(resolve => {
190 if (config.requiredVersion !== undefined)
191 return resolve(config.requiredVersion);
192 let packageName = config.packageName;
193 if (packageName === undefined) {
194 if (/^(\/|[A-Za-z]:|\\\\)/.test(request)) {
195 // For relative or absolute requests we don't automatically use a packageName.
196 // If wished one can specify one with the packageName option.
197 return resolve();
198 }
199 const match = /^((?:@[^\\/]+[\\/])?[^\\/]+)/.exec(request);
200 if (!match) {
201 requiredVersionWarning(
202 "Unable to extract the package name from request."
203 );
204 return resolve();
205 }
206 packageName = match[0];
207 }
208
209 getDescriptionFile(
210 compilation.inputFileSystem,
211 context,
212 ["package.json"],
213 (err, result) => {
214 if (err) {
215 requiredVersionWarning(
216 `Unable to read description file: ${err}`
217 );
218 return resolve();
219 }
220 const { data, path: descriptionPath } = result;
221 if (!data) {
222 requiredVersionWarning(
223 `Unable to find description file in ${context}.`
224 );
225 return resolve();
226 }
227 const requiredVersion = getRequiredVersionFromDescriptionFile(
228 data,
229 packageName
230 );
231 if (typeof requiredVersion !== "string") {
232 requiredVersionWarning(
233 `Unable to find required version for "${packageName}" in description file (${descriptionPath}). It need to be in dependencies, devDependencies or peerDependencies.`
234 );
235 return resolve();
236 }
237 resolve(parseRange(requiredVersion));
238 }
239 );
240 })
241 ]).then(([importResolved, requiredVersion]) => {
242 return new ConsumeSharedModule(
243 directFallback ? compiler.context : context,
244 {
245 ...config,
246 importResolved,
247 import: importResolved ? config.import : undefined,
248 requiredVersion
249 }
250 );
251 });
252 };
253
254 normalModuleFactory.hooks.factorize.tapPromise(
255 PLUGIN_NAME,
256 ({ context, request, dependencies }) =>
257 // wait for resolving to be complete
258 promise.then(() => {
259 if (
260 dependencies[0] instanceof ConsumeSharedFallbackDependency ||
261 dependencies[0] instanceof ProvideForSharedDependency
262 ) {
263 return;
264 }
265 const match = unresolvedConsumes.get(request);
266 if (match !== undefined) {
267 return createConsumeSharedModule(context, request, match);
268 }
269 for (const [prefix, options] of prefixedConsumes) {
270 if (request.startsWith(prefix)) {
271 const remainder = request.slice(prefix.length);
272 return createConsumeSharedModule(context, request, {
273 ...options,
274 import: options.import
275 ? options.import + remainder
276 : undefined,
277 shareKey: options.shareKey + remainder
278 });
279 }
280 }
281 })
282 );
283 normalModuleFactory.hooks.createModule.tapPromise(
284 PLUGIN_NAME,
285 ({ resource }, { context, dependencies }) => {
286 if (
287 dependencies[0] instanceof ConsumeSharedFallbackDependency ||
288 dependencies[0] instanceof ProvideForSharedDependency
289 ) {
290 return Promise.resolve();
291 }
292 const options = resolvedConsumes.get(resource);
293 if (options !== undefined) {
294 return createConsumeSharedModule(context, resource, options);
295 }
296 return Promise.resolve();
297 }
298 );
299 compilation.hooks.additionalTreeRuntimeRequirements.tap(
300 PLUGIN_NAME,
301 (chunk, set) => {
302 set.add(RuntimeGlobals.module);
303 set.add(RuntimeGlobals.moduleCache);
304 set.add(RuntimeGlobals.moduleFactoriesAddOnly);
305 set.add(RuntimeGlobals.shareScopeMap);
306 set.add(RuntimeGlobals.initializeSharing);
307 set.add(RuntimeGlobals.hasOwnProperty);
308 compilation.addRuntimeModule(
309 chunk,
310 new ConsumeSharedRuntimeModule(set)
311 );
312 }
313 );
314 }
315 );
316 }
317}
318
319module.exports = ConsumeSharedPlugin;