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