UNPKG

6.82 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy
4*/
5
6"use strict";
7
8const WebpackError = require("../WebpackError");
9const { parseOptions } = require("../container/options");
10const createSchemaValidation = require("../util/create-schema-validation");
11const ProvideForSharedDependency = require("./ProvideForSharedDependency");
12const ProvideSharedDependency = require("./ProvideSharedDependency");
13const ProvideSharedModuleFactory = require("./ProvideSharedModuleFactory");
14
15/** @typedef {import("../../declarations/plugins/sharing/ProvideSharedPlugin").ProvideSharedPluginOptions} ProvideSharedPluginOptions */
16/** @typedef {import("../Compilation")} Compilation */
17/** @typedef {import("../Compiler")} Compiler */
18
19const validate = createSchemaValidation(
20 require("../../schemas/plugins/sharing/ProvideSharedPlugin.check.js"),
21 () => require("../../schemas/plugins/sharing/ProvideSharedPlugin.json"),
22 {
23 name: "Provide Shared Plugin",
24 baseDataPath: "options"
25 }
26);
27
28/**
29 * @typedef {Object} ProvideOptions
30 * @property {string} shareKey
31 * @property {string} shareScope
32 * @property {string | undefined | false} version
33 * @property {boolean} eager
34 */
35
36/** @typedef {Map<string, { config: ProvideOptions, version: string | undefined | false }>} ResolvedProvideMap */
37
38class ProvideSharedPlugin {
39 /**
40 * @param {ProvideSharedPluginOptions} options options
41 */
42 constructor(options) {
43 validate(options);
44
45 /** @type {[string, ProvideOptions][]} */
46 this._provides = parseOptions(
47 options.provides,
48 item => {
49 if (Array.isArray(item))
50 throw new Error("Unexpected array of provides");
51 /** @type {ProvideOptions} */
52 const result = {
53 shareKey: item,
54 version: undefined,
55 shareScope: options.shareScope || "default",
56 eager: false
57 };
58 return result;
59 },
60 item => ({
61 shareKey: item.shareKey,
62 version: item.version,
63 shareScope: item.shareScope || options.shareScope || "default",
64 eager: !!item.eager
65 })
66 );
67 this._provides.sort(([a], [b]) => {
68 if (a < b) return -1;
69 if (b < a) return 1;
70 return 0;
71 });
72 }
73
74 /**
75 * Apply the plugin
76 * @param {Compiler} compiler the compiler instance
77 * @returns {void}
78 */
79 apply(compiler) {
80 /** @type {WeakMap<Compilation, ResolvedProvideMap>} */
81 const compilationData = new WeakMap();
82
83 compiler.hooks.compilation.tap(
84 "ProvideSharedPlugin",
85 (compilation, { normalModuleFactory }) => {
86 /** @type {ResolvedProvideMap} */
87 const resolvedProvideMap = new Map();
88 /** @type {Map<string, ProvideOptions>} */
89 const matchProvides = new Map();
90 /** @type {Map<string, ProvideOptions>} */
91 const prefixMatchProvides = new Map();
92 for (const [request, config] of this._provides) {
93 if (/^(\/|[A-Za-z]:\\|\\\\|\.\.?(\/|$))/.test(request)) {
94 // relative request
95 resolvedProvideMap.set(request, {
96 config,
97 version: config.version
98 });
99 } else if (/^(\/|[A-Za-z]:\\|\\\\)/.test(request)) {
100 // absolute path
101 resolvedProvideMap.set(request, {
102 config,
103 version: config.version
104 });
105 } else if (request.endsWith("/")) {
106 // module request prefix
107 prefixMatchProvides.set(request, config);
108 } else {
109 // module request
110 matchProvides.set(request, config);
111 }
112 }
113 compilationData.set(compilation, resolvedProvideMap);
114 const provideSharedModule = (
115 key,
116 config,
117 resource,
118 resourceResolveData
119 ) => {
120 let version = config.version;
121 if (version === undefined) {
122 let details = "";
123 if (!resourceResolveData) {
124 details = `No resolve data provided from resolver.`;
125 } else {
126 const descriptionFileData =
127 resourceResolveData.descriptionFileData;
128 if (!descriptionFileData) {
129 details =
130 "No description file (usually package.json) found. Add description file with name and version, or manually specify version in shared config.";
131 } else if (!descriptionFileData.version) {
132 details =
133 "No version in description file (usually package.json). Add version to description file, or manually specify version in shared config.";
134 } else {
135 version = descriptionFileData.version;
136 }
137 }
138 if (!version) {
139 const error = new WebpackError(
140 `No version specified and unable to automatically determine one. ${details}`
141 );
142 error.file = `shared module ${key} -> ${resource}`;
143 compilation.warnings.push(error);
144 }
145 }
146 resolvedProvideMap.set(resource, {
147 config,
148 version
149 });
150 };
151 normalModuleFactory.hooks.module.tap(
152 "ProvideSharedPlugin",
153 (module, { resource, resourceResolveData }, resolveData) => {
154 if (resolvedProvideMap.has(resource)) {
155 return module;
156 }
157 const { request } = resolveData;
158 {
159 const config = matchProvides.get(request);
160 if (config !== undefined) {
161 provideSharedModule(
162 request,
163 config,
164 resource,
165 resourceResolveData
166 );
167 resolveData.cacheable = false;
168 }
169 }
170 for (const [prefix, config] of prefixMatchProvides) {
171 if (request.startsWith(prefix)) {
172 const remainder = request.slice(prefix.length);
173 provideSharedModule(
174 resource,
175 {
176 ...config,
177 shareKey: config.shareKey + remainder
178 },
179 resource,
180 resourceResolveData
181 );
182 resolveData.cacheable = false;
183 }
184 }
185 return module;
186 }
187 );
188 }
189 );
190 compiler.hooks.finishMake.tapPromise("ProvideSharedPlugin", compilation => {
191 const resolvedProvideMap = compilationData.get(compilation);
192 if (!resolvedProvideMap) return Promise.resolve();
193 return Promise.all(
194 Array.from(
195 resolvedProvideMap,
196 ([resource, { config, version }]) =>
197 new Promise((resolve, reject) => {
198 compilation.addInclude(
199 compiler.context,
200 new ProvideSharedDependency(
201 config.shareScope,
202 config.shareKey,
203 version || false,
204 resource,
205 config.eager
206 ),
207 {
208 name: undefined
209 },
210 err => {
211 if (err) return reject(err);
212 resolve();
213 }
214 );
215 })
216 )
217 ).then(() => {});
218 });
219
220 compiler.hooks.compilation.tap(
221 "ProvideSharedPlugin",
222 (compilation, { normalModuleFactory }) => {
223 compilation.dependencyFactories.set(
224 ProvideForSharedDependency,
225 normalModuleFactory
226 );
227
228 compilation.dependencyFactories.set(
229 ProvideSharedDependency,
230 new ProvideSharedModuleFactory()
231 );
232 }
233 );
234 }
235}
236
237module.exports = ProvideSharedPlugin;