1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 | const { ConcatSource } = require("webpack-sources");
|
9 | const HotUpdateChunk = require("../HotUpdateChunk");
|
10 | const RuntimeGlobals = require("../RuntimeGlobals");
|
11 | const SelfModuleFactory = require("../SelfModuleFactory");
|
12 | const CssExportDependency = require("../dependencies/CssExportDependency");
|
13 | const CssImportDependency = require("../dependencies/CssImportDependency");
|
14 | const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
|
15 | const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
|
16 | const CssUrlDependency = require("../dependencies/CssUrlDependency");
|
17 | const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
|
18 | const { compareModulesByIdentifier } = require("../util/comparators");
|
19 | const createSchemaValidation = require("../util/create-schema-validation");
|
20 | const createHash = require("../util/createHash");
|
21 | const memoize = require("../util/memoize");
|
22 | const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
|
23 | const CssExportsGenerator = require("./CssExportsGenerator");
|
24 | const CssGenerator = require("./CssGenerator");
|
25 | const CssParser = require("./CssParser");
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | const getCssLoadingRuntimeModule = memoize(() =>
|
34 | require("./CssLoadingRuntimeModule")
|
35 | );
|
36 |
|
37 | const getSchema = name => {
|
38 | const { definitions } = require("../../schemas/WebpackOptions.json");
|
39 | return {
|
40 | definitions,
|
41 | oneOf: [{ $ref: `#/definitions/${name}` }]
|
42 | };
|
43 | };
|
44 |
|
45 | const validateGeneratorOptions = createSchemaValidation(
|
46 | require("../../schemas/plugins/css/CssGeneratorOptions.check.js"),
|
47 | () => getSchema("CssGeneratorOptions"),
|
48 | {
|
49 | name: "Css Modules Plugin",
|
50 | baseDataPath: "parser"
|
51 | }
|
52 | );
|
53 | const validateParserOptions = createSchemaValidation(
|
54 | require("../../schemas/plugins/css/CssParserOptions.check.js"),
|
55 | () => getSchema("CssParserOptions"),
|
56 | {
|
57 | name: "Css Modules Plugin",
|
58 | baseDataPath: "parser"
|
59 | }
|
60 | );
|
61 |
|
62 | const escapeCss = (str, omitOptionalUnderscore) => {
|
63 | const escaped = `${str}`.replace(
|
64 |
|
65 | /[^a-zA-Z0-9_\u0081-\uffff-]/g,
|
66 | s => `\\${s}`
|
67 | );
|
68 | return !omitOptionalUnderscore && /^(?!--)[0-9_-]/.test(escaped)
|
69 | ? `_${escaped}`
|
70 | : escaped;
|
71 | };
|
72 |
|
73 | const plugin = "CssModulesPlugin";
|
74 |
|
75 | class CssModulesPlugin {
|
76 | |
77 |
|
78 |
|
79 | constructor({ exportsOnly = false }) {
|
80 | this._exportsOnly = exportsOnly;
|
81 | }
|
82 | |
83 |
|
84 |
|
85 |
|
86 |
|
87 | apply(compiler) {
|
88 | compiler.hooks.compilation.tap(
|
89 | plugin,
|
90 | (compilation, { normalModuleFactory }) => {
|
91 | const selfFactory = new SelfModuleFactory(compilation.moduleGraph);
|
92 | compilation.dependencyFactories.set(
|
93 | CssUrlDependency,
|
94 | normalModuleFactory
|
95 | );
|
96 | compilation.dependencyTemplates.set(
|
97 | CssUrlDependency,
|
98 | new CssUrlDependency.Template()
|
99 | );
|
100 | compilation.dependencyTemplates.set(
|
101 | CssLocalIdentifierDependency,
|
102 | new CssLocalIdentifierDependency.Template()
|
103 | );
|
104 | compilation.dependencyFactories.set(
|
105 | CssSelfLocalIdentifierDependency,
|
106 | selfFactory
|
107 | );
|
108 | compilation.dependencyTemplates.set(
|
109 | CssSelfLocalIdentifierDependency,
|
110 | new CssSelfLocalIdentifierDependency.Template()
|
111 | );
|
112 | compilation.dependencyTemplates.set(
|
113 | CssExportDependency,
|
114 | new CssExportDependency.Template()
|
115 | );
|
116 | compilation.dependencyFactories.set(
|
117 | CssImportDependency,
|
118 | normalModuleFactory
|
119 | );
|
120 | compilation.dependencyTemplates.set(
|
121 | CssImportDependency,
|
122 | new CssImportDependency.Template()
|
123 | );
|
124 | compilation.dependencyTemplates.set(
|
125 | StaticExportsDependency,
|
126 | new StaticExportsDependency.Template()
|
127 | );
|
128 | normalModuleFactory.hooks.createParser
|
129 | .for("css")
|
130 | .tap(plugin, parserOptions => {
|
131 | validateParserOptions(parserOptions);
|
132 | return new CssParser();
|
133 | });
|
134 | normalModuleFactory.hooks.createParser
|
135 | .for("css/global")
|
136 | .tap(plugin, parserOptions => {
|
137 | validateParserOptions(parserOptions);
|
138 | return new CssParser({
|
139 | allowPseudoBlocks: false,
|
140 | allowModeSwitch: false
|
141 | });
|
142 | });
|
143 | normalModuleFactory.hooks.createParser
|
144 | .for("css/module")
|
145 | .tap(plugin, parserOptions => {
|
146 | validateParserOptions(parserOptions);
|
147 | return new CssParser({
|
148 | defaultMode: "local"
|
149 | });
|
150 | });
|
151 | normalModuleFactory.hooks.createGenerator
|
152 | .for("css")
|
153 | .tap(plugin, generatorOptions => {
|
154 | validateGeneratorOptions(generatorOptions);
|
155 | return this._exportsOnly
|
156 | ? new CssExportsGenerator()
|
157 | : new CssGenerator();
|
158 | });
|
159 | normalModuleFactory.hooks.createGenerator
|
160 | .for("css/global")
|
161 | .tap(plugin, generatorOptions => {
|
162 | validateGeneratorOptions(generatorOptions);
|
163 | return this._exportsOnly
|
164 | ? new CssExportsGenerator()
|
165 | : new CssGenerator();
|
166 | });
|
167 | normalModuleFactory.hooks.createGenerator
|
168 | .for("css/module")
|
169 | .tap(plugin, generatorOptions => {
|
170 | validateGeneratorOptions(generatorOptions);
|
171 | return this._exportsOnly
|
172 | ? new CssExportsGenerator()
|
173 | : new CssGenerator();
|
174 | });
|
175 | const orderedCssModulesPerChunk = new WeakMap();
|
176 | compilation.hooks.afterCodeGeneration.tap("CssModulesPlugin", () => {
|
177 | const { chunkGraph } = compilation;
|
178 | for (const chunk of compilation.chunks) {
|
179 | if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) {
|
180 | orderedCssModulesPerChunk.set(
|
181 | chunk,
|
182 | this.getOrderedChunkCssModules(chunk, chunkGraph, compilation)
|
183 | );
|
184 | }
|
185 | }
|
186 | });
|
187 | compilation.hooks.contentHash.tap("CssModulesPlugin", chunk => {
|
188 | const {
|
189 | chunkGraph,
|
190 | outputOptions: {
|
191 | hashSalt,
|
192 | hashDigest,
|
193 | hashDigestLength,
|
194 | hashFunction
|
195 | }
|
196 | } = compilation;
|
197 | const modules = orderedCssModulesPerChunk.get(chunk);
|
198 | if (modules === undefined) return;
|
199 | const hash = createHash(hashFunction);
|
200 | if (hashSalt) hash.update(hashSalt);
|
201 | for (const module of modules) {
|
202 | hash.update(chunkGraph.getModuleHash(module, chunk.runtime));
|
203 | }
|
204 | const digest = (hash.digest(hashDigest));
|
205 | chunk.contentHash.css = nonNumericOnlyHash(digest, hashDigestLength);
|
206 | });
|
207 | compilation.hooks.renderManifest.tap(plugin, (result, options) => {
|
208 | const { chunkGraph } = compilation;
|
209 | const { hash, chunk, codeGenerationResults } = options;
|
210 |
|
211 | if (chunk instanceof HotUpdateChunk) return result;
|
212 |
|
213 | const modules = orderedCssModulesPerChunk.get(chunk);
|
214 | if (modules !== undefined) {
|
215 | result.push({
|
216 | render: () =>
|
217 | this.renderChunk({
|
218 | chunk,
|
219 | chunkGraph,
|
220 | codeGenerationResults,
|
221 | uniqueName: compilation.outputOptions.uniqueName,
|
222 | modules
|
223 | }),
|
224 | filenameTemplate: CssModulesPlugin.getChunkFilenameTemplate(
|
225 | chunk,
|
226 | compilation.outputOptions
|
227 | ),
|
228 | pathOptions: {
|
229 | hash,
|
230 | runtime: chunk.runtime,
|
231 | chunk,
|
232 | contentHashType: "css"
|
233 | },
|
234 | identifier: `css${chunk.id}`,
|
235 | hash: chunk.contentHash.css
|
236 | });
|
237 | }
|
238 | return result;
|
239 | });
|
240 | const enabledChunks = new WeakSet();
|
241 | const handler = (chunk, set) => {
|
242 | if (enabledChunks.has(chunk)) {
|
243 | return;
|
244 | }
|
245 | enabledChunks.add(chunk);
|
246 |
|
247 | set.add(RuntimeGlobals.publicPath);
|
248 | set.add(RuntimeGlobals.getChunkCssFilename);
|
249 | set.add(RuntimeGlobals.hasOwnProperty);
|
250 | set.add(RuntimeGlobals.moduleFactoriesAddOnly);
|
251 | set.add(RuntimeGlobals.makeNamespaceObject);
|
252 |
|
253 | const CssLoadingRuntimeModule = getCssLoadingRuntimeModule();
|
254 | compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set));
|
255 | };
|
256 | compilation.hooks.runtimeRequirementInTree
|
257 | .for(RuntimeGlobals.hasCssModules)
|
258 | .tap(plugin, handler);
|
259 | compilation.hooks.runtimeRequirementInTree
|
260 | .for(RuntimeGlobals.ensureChunkHandlers)
|
261 | .tap(plugin, handler);
|
262 | compilation.hooks.runtimeRequirementInTree
|
263 | .for(RuntimeGlobals.hmrDownloadUpdateHandlers)
|
264 | .tap(plugin, handler);
|
265 | }
|
266 | );
|
267 | }
|
268 |
|
269 | getModulesInOrder(chunk, modules, compilation) {
|
270 | if (!modules) return [];
|
271 |
|
272 | const modulesList = [...modules];
|
273 |
|
274 |
|
275 |
|
276 | const modulesByChunkGroup = Array.from(chunk.groupsIterable, chunkGroup => {
|
277 | const sortedModules = modulesList
|
278 | .map(module => {
|
279 | return {
|
280 | module,
|
281 | index: chunkGroup.getModulePostOrderIndex(module)
|
282 | };
|
283 | })
|
284 | .filter(item => item.index !== undefined)
|
285 | .sort((a, b) => b.index - a.index)
|
286 | .map(item => item.module);
|
287 |
|
288 | return { list: sortedModules, set: new Set(sortedModules) };
|
289 | });
|
290 |
|
291 | if (modulesByChunkGroup.length === 1)
|
292 | return modulesByChunkGroup[0].list.reverse();
|
293 |
|
294 | const compareModuleLists = ({ list: a }, { list: b }) => {
|
295 | if (a.length === 0) {
|
296 | return b.length === 0 ? 0 : 1;
|
297 | } else {
|
298 | if (b.length === 0) return -1;
|
299 | return compareModulesByIdentifier(a[a.length - 1], b[b.length - 1]);
|
300 | }
|
301 | };
|
302 |
|
303 | modulesByChunkGroup.sort(compareModuleLists);
|
304 |
|
305 | const finalModules = [];
|
306 |
|
307 | for (;;) {
|
308 | const failedModules = new Set();
|
309 | const list = modulesByChunkGroup[0].list;
|
310 | if (list.length === 0) {
|
311 |
|
312 | break;
|
313 | }
|
314 | let selectedModule = list[list.length - 1];
|
315 | let hasFailed = undefined;
|
316 | outer: for (;;) {
|
317 | for (const { list, set } of modulesByChunkGroup) {
|
318 | if (list.length === 0) continue;
|
319 | const lastModule = list[list.length - 1];
|
320 | if (lastModule === selectedModule) continue;
|
321 | if (!set.has(selectedModule)) continue;
|
322 | failedModules.add(selectedModule);
|
323 | if (failedModules.has(lastModule)) {
|
324 |
|
325 | hasFailed = lastModule;
|
326 | continue;
|
327 | }
|
328 | selectedModule = lastModule;
|
329 | hasFailed = false;
|
330 | continue outer;
|
331 | }
|
332 | break;
|
333 | }
|
334 | if (hasFailed) {
|
335 |
|
336 | if (compilation) {
|
337 |
|
338 | compilation.warnings.push(
|
339 | new Error(
|
340 | `chunk ${
|
341 | chunk.name || chunk.id
|
342 | }\nConflicting order between ${hasFailed.readableIdentifier(
|
343 | compilation.requestShortener
|
344 | )} and ${selectedModule.readableIdentifier(
|
345 | compilation.requestShortener
|
346 | )}`
|
347 | )
|
348 | );
|
349 | }
|
350 | selectedModule = hasFailed;
|
351 | }
|
352 |
|
353 | finalModules.push(selectedModule);
|
354 |
|
355 | for (const { list, set } of modulesByChunkGroup) {
|
356 | const lastModule = list[list.length - 1];
|
357 | if (lastModule === selectedModule) list.pop();
|
358 | else if (hasFailed && set.has(selectedModule)) {
|
359 | const idx = list.indexOf(selectedModule);
|
360 | if (idx >= 0) list.splice(idx, 1);
|
361 | }
|
362 | }
|
363 | modulesByChunkGroup.sort(compareModuleLists);
|
364 | }
|
365 | return finalModules;
|
366 | }
|
367 |
|
368 | getOrderedChunkCssModules(chunk, chunkGraph, compilation) {
|
369 | return [
|
370 | ...this.getModulesInOrder(
|
371 | chunk,
|
372 | chunkGraph.getOrderedChunkModulesIterableBySourceType(
|
373 | chunk,
|
374 | "css-import",
|
375 | compareModulesByIdentifier
|
376 | ),
|
377 | compilation
|
378 | ),
|
379 | ...this.getModulesInOrder(
|
380 | chunk,
|
381 | chunkGraph.getOrderedChunkModulesIterableBySourceType(
|
382 | chunk,
|
383 | "css",
|
384 | compareModulesByIdentifier
|
385 | ),
|
386 | compilation
|
387 | )
|
388 | ];
|
389 | }
|
390 |
|
391 | renderChunk({
|
392 | uniqueName,
|
393 | chunk,
|
394 | chunkGraph,
|
395 | codeGenerationResults,
|
396 | modules
|
397 | }) {
|
398 | const source = new ConcatSource();
|
399 | const metaData = [];
|
400 | for (const module of modules) {
|
401 | try {
|
402 | const codeGenResult = codeGenerationResults.get(module, chunk.runtime);
|
403 |
|
404 | const s =
|
405 | codeGenResult.sources.get("css") ||
|
406 | codeGenResult.sources.get("css-import");
|
407 | if (s) {
|
408 | source.add(s);
|
409 | source.add("\n");
|
410 | }
|
411 | const exports =
|
412 | codeGenResult.data && codeGenResult.data.get("css-exports");
|
413 | const moduleId = chunkGraph.getModuleId(module) + "";
|
414 | metaData.push(
|
415 | `${
|
416 | exports
|
417 | ? Array.from(exports, ([n, v]) => {
|
418 | const shortcutValue = `${
|
419 | uniqueName ? uniqueName + "-" : ""
|
420 | }${moduleId}-${n}`;
|
421 | return v === shortcutValue
|
422 | ? `${escapeCss(n)}/`
|
423 | : v === "--" + shortcutValue
|
424 | ? `${escapeCss(n)}%`
|
425 | : `${escapeCss(n)}(${escapeCss(v)})`;
|
426 | }).join("")
|
427 | : ""
|
428 | }${escapeCss(moduleId)}`
|
429 | );
|
430 | } catch (e) {
|
431 | e.message += `\nduring rendering of css ${module.identifier()}`;
|
432 | throw e;
|
433 | }
|
434 | }
|
435 | source.add(
|
436 | `head{--webpack-${escapeCss(
|
437 | (uniqueName ? uniqueName + "-" : "") + chunk.id,
|
438 | true
|
439 | )}:${metaData.join(",")};}`
|
440 | );
|
441 | return source;
|
442 | }
|
443 |
|
444 | static getChunkFilenameTemplate(chunk, outputOptions) {
|
445 | if (chunk.cssFilenameTemplate) {
|
446 | return chunk.cssFilenameTemplate;
|
447 | } else if (chunk.canBeInitial()) {
|
448 | return outputOptions.cssFilename;
|
449 | } else {
|
450 | return outputOptions.cssChunkFilename;
|
451 | }
|
452 | }
|
453 |
|
454 | static chunkHasCss(chunk, chunkGraph) {
|
455 | return (
|
456 | !!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css") ||
|
457 | !!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css-import")
|
458 | );
|
459 | }
|
460 | }
|
461 |
|
462 | module.exports = CssModulesPlugin;
|