source: frontend/node_modules/mini-css-extract-plugin/dist/index.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 46.4 KB
Line 
1"use strict";
2
3const path = require("path");
4const {
5 validate
6} = require("schema-utils");
7const {
8 SyncWaterfallHook
9} = require("tapable");
10const schema = require("./plugin-options.json");
11const {
12 ABSOLUTE_PUBLIC_PATH,
13 AUTO_PUBLIC_PATH,
14 BASE_URI,
15 MODULE_TYPE,
16 SINGLE_DOT_PATH_SEGMENT,
17 compareModulesByIdentifier,
18 compileBooleanMatcher,
19 getUndoPath,
20 trueFn
21} = require("./utils");
22
23/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
24/** @typedef {import("webpack").Compiler} Compiler */
25/** @typedef {import("webpack").Compilation} Compilation */
26/** @typedef {import("webpack").ChunkGraph} ChunkGraph */
27/** @typedef {import("webpack").Chunk} Chunk */
28/** @typedef {import("webpack").ChunkGroup} ChunkGroup */
29/** @typedef {import("webpack").Module} Module */
30/** @typedef {import("webpack").Dependency} Dependency */
31/** @typedef {import("webpack").sources.Source} Source */
32/** @typedef {import("webpack").Configuration} Configuration */
33/** @typedef {import("webpack").WebpackError} WebpackError */
34/** @typedef {import("webpack").AssetInfo} AssetInfo */
35/** @typedef {import("./loader.js").Dependency} LoaderDependency */
36
37/** @typedef {NonNullable<Required<Configuration>['output']['filename']>} Filename */
38/** @typedef {NonNullable<Required<Configuration>['output']['chunkFilename']>} ChunkFilename */
39
40/**
41 * @typedef {object} LoaderOptions
42 * @property {string | ((resourcePath: string, rootContext: string) => string)=} publicPath public path
43 * @property {boolean=} emit true when need to emit, otherwise false
44 * @property {boolean=} esModule need to generate ES module syntax
45 * @property {string=} layer a layer
46 * @property {boolean=} defaultExport true when need to use default export, otherwise false
47 */
48
49/**
50 * @typedef {object} PluginOptions
51 * @property {Filename=} filename filename
52 * @property {ChunkFilename=} chunkFilename chunk filename
53 * @property {boolean=} ignoreOrder true when need to ignore order, otherwise false
54 * @property {string | ((linkTag: HTMLLinkElement) => void)=} insert link insert place or a custom insert function
55 * @property {Record<string, string>=} attributes link attributes
56 * @property {string | false | "text/css"=} linkType value of a link type attribute
57 * @property {boolean=} runtime true when need to generate runtime code, otherwise false
58 * @property {boolean=} experimentalUseImportModule true when need to use `experimentalUseImportModule` API, otherwise false
59 */
60
61/**
62 * @typedef {object} NormalizedPluginOptions
63 * @property {Filename=} filename filename
64 * @property {ChunkFilename=} chunkFilename chunk filename
65 * @property {boolean} ignoreOrder true when need to ignore order, otherwise false
66 * @property {string | ((linkTag: HTMLLinkElement) => void)=} insert a link insert place or a custom insert function
67 * @property {Record<string, string>=} attributes link attributes
68 * @property {string | false | "text/css"=} linkType value of a link type attribute
69 * @property {boolean} runtime true when need to generate runtime code, otherwise false
70 * @property {boolean=} experimentalUseImportModule true when need to use `experimentalUseImportModule` API, otherwise false
71 */
72
73/**
74 * @typedef {object} RuntimeOptions
75 * @property {string | ((linkTag: HTMLLinkElement) => void)=} insert a link insert place or a custom insert function
76 * @property {string | false | "text/css"} linkType value of a link type attribute
77 * @property {Record<string, string>=} attributes link attributes
78 */
79
80const pluginName = "mini-css-extract-plugin";
81const pluginSymbol = Symbol(pluginName);
82const DEFAULT_FILENAME = "[name].css";
83/**
84 * @type {Set<string>}
85 */
86const TYPES = new Set([MODULE_TYPE]);
87/**
88 * @type {ReturnType<Module["codeGeneration"]>}
89 */
90const CODE_GENERATION_RESULT = {
91 sources: new Map(),
92 runtimeRequirements: new Set()
93};
94
95// eslint-disable-next-line jsdoc/reject-any-type
96/** @typedef {{ context: string | null, identifier: string, identifierIndex: number, content: Buffer, sourceMap?: Buffer, media?: string, supports?: string, layer?: any, assetsInfo?: Map<string, AssetInfo>, assets?: { [key: string]: Source } }} CssModuleDependency */
97/** @typedef {Module & { content: Buffer, media?: string, sourceMap?: Buffer, supports?: string, layer?: string, assets?: { [key: string]: Source }, assetsInfo?: Map<string, AssetInfo> }} CssModule */
98/** @typedef {{ new (dependency: CssModuleDependency): CssModule }} CssModuleConstructor */
99/** @typedef {Dependency & CssModuleDependency} CssDependency */
100/** @typedef {Omit<LoaderDependency, "context">} CssDependencyOptions */
101/** @typedef {{ new (loaderDependency: CssDependencyOptions, context: string | null, identifierIndex: number): CssDependency }} CssDependencyConstructor */
102
103/**
104 * @typedef {object} VarNames
105 * @property {string} tag tag
106 * @property {string} chunkId chunk id
107 * @property {string} href href
108 * @property {string} resolve resolve
109 * @property {string} reject reject
110 */
111
112/**
113 * @typedef {object} MiniCssExtractPluginCompilationHooks
114 * @property {import("tapable").SyncWaterfallHook<[string, VarNames], string>} beforeTagInsert before tag insert hook
115 * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload link preload hook
116 * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch link prefetch hook
117 */
118
119/**
120 * @type {WeakMap<Compiler["webpack"], CssModuleConstructor>}
121 */
122const cssModuleCache = new WeakMap();
123/**
124 * @type {WeakMap<Compiler["webpack"], CssDependencyConstructor>}
125 */
126const cssDependencyCache = new WeakMap();
127/**
128 * @type {WeakSet<Compiler["webpack"]>}
129 */
130const registered = new WeakSet();
131
132/** @type {WeakMap<Compilation, MiniCssExtractPluginCompilationHooks>} */
133const compilationHooksMap = new WeakMap();
134class MiniCssExtractPlugin {
135 /**
136 * @param {Compiler["webpack"]} webpack webpack
137 * @returns {CssModuleConstructor} CSS module constructor
138 */
139 static getCssModule(webpack) {
140 /**
141 * Prevent creation of multiple CssModule classes to allow other integrations to get the current CssModule.
142 */
143 if (cssModuleCache.has(webpack)) {
144 return /** @type {CssModuleConstructor} */cssModuleCache.get(webpack);
145 }
146 class CssModule extends webpack.Module {
147 /**
148 * @param {CssModuleDependency} dependency css module dependency
149 */
150 constructor({
151 context,
152 identifier,
153 identifierIndex,
154 content,
155 layer,
156 supports,
157 media,
158 sourceMap,
159 assets,
160 assetsInfo
161 }) {
162 super(MODULE_TYPE, /** @type {string | undefined} */context);
163 this.id = "";
164 this._context = context;
165 this._identifier = identifier;
166 this._identifierIndex = identifierIndex;
167 this.content = content;
168 this.layer = layer;
169 this.supports = supports;
170 this.media = media;
171 this.sourceMap = sourceMap;
172 this.assets = assets;
173 this.assetsInfo = assetsInfo;
174 this._needBuild = true;
175 }
176
177 // no source() so webpack 4 doesn't do add stuff to the bundle
178
179 size() {
180 return this.content.length;
181 }
182 identifier() {
183 return `css|${this._identifier}|${this._identifierIndex}|${this.layer || ""}|${this.supports || ""}|${this.media}}}`;
184 }
185
186 /**
187 * @param {Parameters<Module["readableIdentifier"]>[0]} requestShortener request shortener
188 * @returns {ReturnType<Module["readableIdentifier"]>} readable identifier
189 */
190 readableIdentifier(requestShortener) {
191 return `css ${requestShortener.shorten(this._identifier)}${this._identifierIndex ? ` (${this._identifierIndex})` : ""}${this.layer ? ` (layer ${this.layer})` : ""}${this.supports ? ` (supports ${this.supports})` : ""}${this.media ? ` (media ${this.media})` : ""}`;
192 }
193 getSourceTypes() {
194 return TYPES;
195 }
196 codeGeneration() {
197 return CODE_GENERATION_RESULT;
198 }
199 nameForCondition() {
200 const resource = /** @type {string} */
201 this._identifier.split("!").pop();
202 const idx = resource.indexOf("?");
203 if (idx >= 0) {
204 return resource.slice(0, Math.max(0, idx));
205 }
206 return resource;
207 }
208
209 /**
210 * @param {Module} module a module
211 */
212 updateCacheModule(module) {
213 if (!this.content.equals(/** @type {CssModule} */module.content) || this.layer !== /** @type {CssModule} */module.layer || this.supports !== /** @type {CssModule} */module.supports || this.media !== /** @type {CssModule} */module.media || (this.sourceMap ? !this.sourceMap.equals(/** @type {Uint8Array} * */
214 /** @type {CssModule} */module.sourceMap) : false) || this.assets !== /** @type {CssModule} */module.assets || this.assetsInfo !== /** @type {CssModule} */module.assetsInfo) {
215 this._needBuild = true;
216 this.content = /** @type {CssModule} */module.content;
217 this.layer = /** @type {CssModule} */module.layer;
218 this.supports = /** @type {CssModule} */module.supports;
219 this.media = /** @type {CssModule} */module.media;
220 this.sourceMap = /** @type {CssModule} */module.sourceMap;
221 this.assets = /** @type {CssModule} */module.assets;
222 this.assetsInfo = /** @type {CssModule} */module.assetsInfo;
223 }
224 }
225 needRebuild() {
226 return this._needBuild;
227 }
228
229 /**
230 * @param {Parameters<Module["needBuild"]>[0]} context context info
231 * @param {Parameters<Module["needBuild"]>[1]} callback callback function, returns true, if the module needs a rebuild
232 */
233 needBuild(context, callback) {
234 callback(undefined, this._needBuild);
235 }
236
237 /**
238 * @param {Parameters<Module["build"]>[0]} options options
239 * @param {Parameters<Module["build"]>[1]} compilation compilation
240 * @param {Parameters<Module["build"]>[2]} resolver resolver
241 * @param {Parameters<Module["build"]>[3]} fileSystem file system
242 * @param {Parameters<Module["build"]>[4]} callback callback
243 */
244 build(options, compilation, resolver, fileSystem, callback) {
245 this.buildInfo = {
246 assets: this.assets,
247 assetsInfo: this.assetsInfo,
248 cacheable: true,
249 hash: (/** @type {string} */
250
251 this._computeHash(/** @type {string} */
252 compilation.outputOptions.hashFunction))
253 };
254 this.buildMeta = {};
255 this._needBuild = false;
256 callback();
257 }
258
259 /**
260 * @private
261 * @param {string} hashFunction hash function
262 * @returns {string | Buffer} hash digest
263 */
264 _computeHash(hashFunction) {
265 const hash = webpack.util.createHash(hashFunction);
266 hash.update(this.content);
267 if (this.layer) {
268 hash.update(this.layer);
269 }
270 hash.update(this.supports || "");
271 hash.update(this.media || "");
272 hash.update(this.sourceMap || "");
273 return hash.digest("hex");
274 }
275
276 /**
277 * @param {Parameters<Module["updateHash"]>[0]} hash hash
278 * @param {Parameters<Module["updateHash"]>[1]} context context
279 */
280 updateHash(hash, context) {
281 super.updateHash(hash, context);
282 hash.update(/** @type {string} */
283 /** @type {NonNullable<Module["buildInfo"]>} */
284 this.buildInfo.hash);
285 }
286
287 /**
288 * @param {Parameters<Module["serialize"]>[0]} context serializer context
289 */
290 serialize(context) {
291 const {
292 write
293 } = context;
294 write(this._context);
295 write(this._identifier);
296 write(this._identifierIndex);
297 write(this.content);
298 write(this.layer);
299 write(this.supports);
300 write(this.media);
301 write(this.sourceMap);
302 write(this.assets);
303 write(this.assetsInfo);
304 write(this._needBuild);
305 super.serialize(context);
306 }
307
308 /**
309 * @param {Parameters<Module["deserialize"]>[0]} context deserializer context
310 */
311 deserialize(context) {
312 this._needBuild = context.read();
313 super.deserialize(context);
314 }
315 }
316 cssModuleCache.set(webpack, CssModule);
317 webpack.util.serialization.register(CssModule, path.resolve(__dirname, "CssModule"), null, {
318 serialize(instance, context) {
319 instance.serialize(context);
320 },
321 deserialize(context) {
322 const {
323 read
324 } = context;
325 const contextModule = read();
326 const identifier = read();
327 const identifierIndex = read();
328 const content = read();
329 const layer = read();
330 const supports = read();
331 const media = read();
332 const sourceMap = read();
333 const assets = read();
334 const assetsInfo = read();
335 const dep = new CssModule({
336 context: contextModule,
337 identifier,
338 identifierIndex,
339 content,
340 layer,
341 supports,
342 media,
343 sourceMap,
344 assets,
345 assetsInfo
346 });
347 dep.deserialize(context);
348 return dep;
349 }
350 });
351 return CssModule;
352 }
353
354 /**
355 * @param {Compiler["webpack"]} webpack webpack
356 * @returns {CssDependencyConstructor} CSS dependency constructor
357 */
358 static getCssDependency(webpack) {
359 /**
360 * Prevent creation of multiple CssDependency classes to allow other integrations to get the current CssDependency.
361 */
362 if (cssDependencyCache.has(webpack)) {
363 return /** @type {CssDependencyConstructor} */cssDependencyCache.get(webpack);
364 }
365 class CssDependency extends webpack.Dependency {
366 /**
367 * @param {CssDependencyOptions} loaderDependency loader dependency
368 * @param {string | null} context context
369 * @param {number} identifierIndex identifier index
370 */
371 constructor({
372 identifier,
373 content,
374 layer,
375 supports,
376 media,
377 sourceMap
378 }, context, identifierIndex) {
379 super();
380 this.identifier = identifier;
381 this.identifierIndex = identifierIndex;
382 this.content = content;
383 this.layer = layer;
384 this.supports = supports;
385 this.media = media;
386 this.sourceMap = sourceMap;
387 this.context = context;
388 /** @type {{ [key: string]: Source } | undefined}} */
389 this.assets = undefined;
390 /** @type {Map<string, AssetInfo> | undefined} */
391 this.assetsInfo = undefined;
392 }
393
394 /**
395 * @returns {ReturnType<Dependency["getResourceIdentifier"]>} a resource identifier
396 */
397 getResourceIdentifier() {
398 return `css-module-${this.identifier}-${this.identifierIndex}`;
399 }
400
401 /**
402 * @returns {ReturnType<Dependency["getModuleEvaluationSideEffectsState"]>} side effect state
403 */
404 getModuleEvaluationSideEffectsState() {
405 return webpack.ModuleGraphConnection.TRANSITIVE_ONLY;
406 }
407
408 /**
409 * @param {Parameters<Dependency["serialize"]>[0]} context serializer context
410 */
411 serialize(context) {
412 const {
413 write
414 } = context;
415 write(this.identifier);
416 write(this.content);
417 write(this.layer);
418 write(this.supports);
419 write(this.media);
420 write(this.sourceMap);
421 write(this.context);
422 write(this.identifierIndex);
423 write(this.assets);
424 write(this.assetsInfo);
425 super.serialize(context);
426 }
427
428 /**
429 * @param {Parameters<Dependency["deserialize"]>[0]} context deserializer context
430 */
431 deserialize(context) {
432 super.deserialize(context);
433 }
434 }
435 cssDependencyCache.set(webpack, CssDependency);
436 webpack.util.serialization.register(CssDependency, path.resolve(__dirname, "CssDependency"), null, {
437 serialize(instance, context) {
438 instance.serialize(context);
439 },
440 deserialize(context) {
441 const {
442 read
443 } = context;
444 const dep = new CssDependency({
445 identifier: read(),
446 content: read(),
447 layer: read(),
448 supports: read(),
449 media: read(),
450 sourceMap: read()
451 }, read(), read());
452 const assets = read();
453 const assetsInfo = read();
454 dep.assets = assets;
455 dep.assetsInfo = assetsInfo;
456 dep.deserialize(context);
457 return dep;
458 }
459 });
460 return CssDependency;
461 }
462
463 /**
464 * Returns all hooks for the given compilation
465 * @param {Compilation} compilation the compilation
466 * @returns {MiniCssExtractPluginCompilationHooks} hooks
467 */
468 static getCompilationHooks(compilation) {
469 let hooks = compilationHooksMap.get(compilation);
470 if (!hooks) {
471 hooks = {
472 beforeTagInsert: new SyncWaterfallHook(["source", "varNames"], "string"),
473 linkPreload: new SyncWaterfallHook(["source", "chunk"]),
474 linkPrefetch: new SyncWaterfallHook(["source", "chunk"])
475 };
476 compilationHooksMap.set(compilation, hooks);
477 }
478 return hooks;
479 }
480
481 /**
482 * @param {PluginOptions=} options options
483 */
484 constructor(options = {}) {
485 validate(/** @type {Schema} */schema, options, {
486 baseDataPath: "options"
487 });
488
489 /**
490 * @private
491 * @type {WeakMap<Chunk, Set<CssModule>>}
492 */
493 this._sortedModulesCache = new WeakMap();
494
495 /**
496 * @private
497 * @type {NormalizedPluginOptions}
498 */
499 this.options = {
500 ignoreOrder: false,
501 // TODO remove in the next major release
502 experimentalUseImportModule: undefined,
503 runtime: true,
504 ...options
505 };
506
507 /**
508 * @private
509 * @type {RuntimeOptions}
510 */
511 this.runtimeOptions = {
512 insert: options.insert,
513 linkType:
514 // Todo in next major release set default to "false"
515 typeof options.linkType === "boolean" && /** @type {boolean} */options.linkType === true || typeof options.linkType === "undefined" ? "text/css" : options.linkType,
516 attributes: options.attributes
517 };
518 }
519
520 /**
521 * @param {Compiler} compiler compiler
522 */
523 apply(compiler) {
524 // Finally normalize filenames based on compiler options
525 const normalizedFilename = this.options.filename || compiler.options.output.cssFilename || DEFAULT_FILENAME;
526 let normalizedChunkFilename = this.options.chunkFilename || compiler.options.output.cssChunkFilename;
527 if (!normalizedChunkFilename) {
528 if (typeof normalizedFilename !== "function") {
529 const hasName = /** @type {string} */normalizedFilename.includes("[name]");
530 const hasId = /** @type {string} */normalizedFilename.includes("[id]");
531 const hasChunkHash = /** @type {string} */
532 normalizedFilename.includes("[chunkhash]");
533 const hasContentHash = /** @type {string} */
534 normalizedFilename.includes("[contenthash]");
535
536 // Anything changing depending on chunk is fine
537 if (hasChunkHash || hasContentHash || hasName || hasId) {
538 normalizedChunkFilename = normalizedFilename;
539 } else {
540 // Otherwise prefix "[id]." in front of the basename to make it changing
541 normalizedChunkFilename = /** @type {string} */
542 normalizedFilename.replace(/(^|\/)([^/]*(?:\?|$))/, "$1[id].$2");
543 }
544 } else {
545 normalizedChunkFilename = "[id].css";
546 }
547 }
548 const {
549 webpack
550 } = compiler;
551 if (this.options.experimentalUseImportModule && typeof (/** @type {Compiler["options"]["experiments"] & { executeModule?: boolean }} */
552 compiler.options.experiments.executeModule) === "undefined") {
553 /** @type {Compiler["options"]["experiments"] & { executeModule?: boolean }} */
554
555 // @ts-expect-error TODO remove in the next major release
556 compiler.options.experiments.executeModule = true;
557 }
558
559 // TODO bug in webpack, remove it after it will be fixed
560 // webpack tries to `require` loader firstly when serializer doesn't found
561 if (!registered.has(webpack)) {
562 registered.add(webpack);
563 webpack.util.serialization.registerLoader(/^mini-css-extract-plugin\//, trueFn);
564 }
565 const {
566 splitChunks
567 } = compiler.options.optimization;
568 if (splitChunks && /** @type {string[]} */splitChunks.defaultSizeTypes.includes("...")) {
569 /** @type {string[]} */
570 splitChunks.defaultSizeTypes.push(MODULE_TYPE);
571 }
572 const CssModule = MiniCssExtractPlugin.getCssModule(webpack);
573 const CssDependency = MiniCssExtractPlugin.getCssDependency(webpack);
574 const {
575 NormalModule
576 } = compiler.webpack;
577 compiler.hooks.compilation.tap(pluginName, compilation => {
578 const {
579 loader: normalModuleHook
580 } = NormalModule.getCompilationHooks(compilation);
581 normalModuleHook.tap(pluginName,
582 /**
583 * @param {object} loaderContext loader context
584 */
585 loaderContext => {
586 /** @type {object & { [pluginSymbol]: { experimentalUseImportModule: boolean | undefined } }} */
587 loaderContext[pluginSymbol] = {
588 experimentalUseImportModule: this.options.experimentalUseImportModule
589 };
590 });
591 });
592 compiler.hooks.thisCompilation.tap(pluginName, compilation => {
593 class CssModuleFactory {
594 /**
595 * @param {{ dependencies: Dependency[] }} dependencies
596 * @param {(err?: null | Error, result?: CssModule) => void} callback
597 */
598
599 create({
600 dependencies: [dependency]
601 }, callback) {
602 callback(undefined, new CssModule(/** @type {CssDependency} */dependency));
603 }
604 }
605 compilation.dependencyFactories.set(CssDependency,
606 // @ts-expect-error TODO fix in the next major release and fix using `CssModuleFactory extends webpack.ModuleFactory`
607 new CssModuleFactory());
608 class CssDependencyTemplate {
609 apply() {}
610 }
611 compilation.dependencyTemplates.set(CssDependency, new CssDependencyTemplate());
612 compilation.hooks.renderManifest.tap(pluginName,
613 /**
614 * @param {ReturnType<Compilation["getRenderManifest"]>} result result
615 * @param {Parameters<Compilation["getRenderManifest"]>[0]} chunk chunk
616 * @returns {ReturnType<Compilation["getRenderManifest"]>} a rendered manifest
617 */
618 (result, {
619 chunk
620 }) => {
621 const {
622 chunkGraph
623 } = compilation;
624 const {
625 HotUpdateChunk
626 } = webpack;
627
628 // We don't need hot update chunks for css
629 // We will use the real asset instead to update
630 if (chunk instanceof HotUpdateChunk) {
631 return result;
632 }
633 const renderedModules = /** @type {CssModule[]} */
634
635 [...this.getChunkModules(chunk, chunkGraph)].filter(module => module.type === MODULE_TYPE);
636 const filenameTemplate = /** @type {string} */
637
638 chunk.canBeInitial() ? normalizedFilename : normalizedChunkFilename;
639 if (renderedModules.length > 0) {
640 result.push({
641 render: () => this.renderContentAsset(compiler, compilation, chunk, renderedModules, compilation.runtimeTemplate.requestShortener, filenameTemplate, {
642 contentHashType: MODULE_TYPE,
643 chunk
644 }),
645 filenameTemplate,
646 pathOptions: {
647 chunk,
648 contentHashType: MODULE_TYPE
649 },
650 identifier: `${pluginName}.${chunk.id}`,
651 hash: chunk.contentHash[MODULE_TYPE]
652 });
653 }
654 return result;
655 });
656 compilation.hooks.contentHash.tap(pluginName, chunk => {
657 const {
658 outputOptions,
659 chunkGraph
660 } = compilation;
661 const modules = this.sortModules(compilation, chunk, /** @type {CssModule[]} */
662 chunkGraph.getChunkModulesIterableBySourceType(chunk, MODULE_TYPE), compilation.runtimeTemplate.requestShortener);
663 if (modules && modules.size > 0) {
664 const {
665 hashFunction,
666 hashDigest,
667 hashDigestLength
668 } = outputOptions;
669 const {
670 createHash
671 } = compiler.webpack.util;
672 const hash = createHash(/** @type {string} */hashFunction);
673 for (const m of modules) {
674 hash.update(chunkGraph.getModuleHash(m, chunk.runtime));
675 }
676 chunk.contentHash[MODULE_TYPE] = /** @type {string} */
677 hash.digest(hashDigest).slice(0, Math.max(0, /** @type {number} */hashDigestLength));
678 }
679 });
680
681 // All the code below is dedicated to the runtime and can be skipped when the `runtime` option is `false`
682 if (!this.options.runtime) {
683 return;
684 }
685 const {
686 Template,
687 RuntimeGlobals,
688 RuntimeModule,
689 runtime
690 } = webpack;
691
692 /**
693 * @param {Chunk} mainChunk
694 * @param {Compilation} compilation
695 * @returns {Record<string, number>}
696 */
697
698 const getCssChunkObject = (mainChunk, compilation) => {
699 /** @type {Record<string, number>} */
700 const obj = {};
701 const {
702 chunkGraph
703 } = compilation;
704 for (const chunk of mainChunk.getAllAsyncChunks()) {
705 const modules = chunkGraph.getOrderedChunkModulesIterable(chunk, compareModulesByIdentifier);
706 for (const module of modules) {
707 if (module.type === MODULE_TYPE) {
708 obj[(/** @type {string} */chunk.id)] = 1;
709 break;
710 }
711 }
712 }
713 return obj;
714 };
715
716 /**
717 * @param {Chunk} chunk chunk
718 * @param {ChunkGraph} chunkGraph chunk graph
719 * @returns {boolean} true, when the chunk has css
720 */
721 function chunkHasCss(chunk, chunkGraph) {
722 // this function replace:
723 // const chunkHasCss = require("webpack/lib/css/CssModulesPlugin").chunkHasCss;
724 return Boolean(chunkGraph.getChunkModulesIterableBySourceType(chunk, "css/mini-extract"));
725 }
726 class CssLoadingRuntimeModule extends RuntimeModule {
727 /**
728 * @param {Set<string>} runtimeRequirements runtime Requirements
729 * @param {RuntimeOptions} runtimeOptions runtime options
730 */
731 constructor(runtimeRequirements, runtimeOptions) {
732 super("css loading", 10);
733 this.runtimeRequirements = runtimeRequirements;
734 this.runtimeOptions = runtimeOptions;
735 }
736 generate() {
737 const {
738 chunkGraph,
739 chunk,
740 runtimeRequirements
741 } = this;
742 const {
743 runtimeTemplate,
744 outputOptions: {
745 crossOriginLoading
746 }
747 } = /** @type {Compilation} */this.compilation;
748 const chunkMap = getCssChunkObject(/** @type {Chunk} */chunk, /** @type {Compilation} */this.compilation);
749 const withLoading = runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) && Object.keys(chunkMap).length > 0;
750 const withHmr = runtimeRequirements.has(RuntimeGlobals.hmrDownloadUpdateHandlers);
751 if (!withLoading && !withHmr) {
752 return "";
753 }
754 const conditionMap = /** @type {ChunkGraph} */chunkGraph.getChunkConditionMap(/** @type {Chunk} */chunk, chunkHasCss);
755 const hasCssMatcher = compileBooleanMatcher(conditionMap);
756 const withPrefetch = runtimeRequirements.has(RuntimeGlobals.prefetchChunkHandlers);
757 const withPreload = runtimeRequirements.has(RuntimeGlobals.preloadChunkHandlers);
758 const {
759 linkPreload,
760 linkPrefetch
761 } = MiniCssExtractPlugin.getCompilationHooks(compilation);
762 return Template.asString(['if (typeof document === "undefined") return;', `var createStylesheet = ${runtimeTemplate.basicFunction("chunkId, fullhref, oldTag, resolve, reject", ['var linkTag = document.createElement("link");', this.runtimeOptions.attributes ? Template.asString(Object.entries(this.runtimeOptions.attributes).map(entry => {
763 const [key, value] = entry;
764 return `linkTag.setAttribute(${JSON.stringify(key)}, ${JSON.stringify(value)});`;
765 })) : "", 'linkTag.rel = "stylesheet";', this.runtimeOptions.linkType ? `linkTag.type = ${JSON.stringify(this.runtimeOptions.linkType)};` : "", `if (${RuntimeGlobals.scriptNonce}) {`, Template.indent(`linkTag.nonce = ${RuntimeGlobals.scriptNonce};`), "}", `var onLinkComplete = ${runtimeTemplate.basicFunction("event", ["// avoid mem leaks.", "linkTag.onerror = linkTag.onload = null;", "if (event.type === 'load') {", Template.indent(["resolve();"]), "} else {", Template.indent(["var errorType = event && event.type;", "var realHref = event && event.target && event.target.href || fullhref;", 'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + errorType + ": " + realHref + ")");', 'err.name = "ChunkLoadError";',
766 // TODO remove `code` in the future major release to align with webpack
767 'err.code = "CSS_CHUNK_LOAD_FAILED";', "err.type = errorType;", "err.request = realHref;", "if (linkTag.parentNode) linkTag.parentNode.removeChild(linkTag)", "reject(err);"]), "}"])}`, "linkTag.onerror = linkTag.onload = onLinkComplete;", "linkTag.href = fullhref;", crossOriginLoading ? Template.asString(["if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {", Template.indent(`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), "}"]) : "", MiniCssExtractPlugin.getCompilationHooks(compilation).beforeTagInsert.call("", {
768 tag: "linkTag",
769 chunkId: "chunkId",
770 href: "fullhref",
771 resolve: "resolve",
772 reject: "reject"
773 }) || "", typeof this.runtimeOptions.insert !== "undefined" ? typeof this.runtimeOptions.insert === "function" ? `(${this.runtimeOptions.insert.toString()})(linkTag)` : Template.asString([`var target = document.querySelector("${this.runtimeOptions.insert}");`, "target.parentNode.insertBefore(linkTag, target.nextSibling);"]) : Template.asString(["if (oldTag) {", Template.indent(["oldTag.parentNode.insertBefore(linkTag, oldTag.nextSibling);"]), "} else {", Template.indent(["document.head.appendChild(linkTag);"]), "}"]), "return linkTag;"])};`, `var findStylesheet = ${runtimeTemplate.basicFunction("href, fullhref", ['var existingLinkTags = document.getElementsByTagName("link");', "for(var i = 0; i < existingLinkTags.length; i++) {", Template.indent(["var tag = existingLinkTags[i];", 'var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");', 'if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return tag;']), "}", 'var existingStyleTags = document.getElementsByTagName("style");', "for(var i = 0; i < existingStyleTags.length; i++) {", Template.indent(["var tag = existingStyleTags[i];", 'var dataHref = tag.getAttribute("data-href");', "if(dataHref === href || dataHref === fullhref) return tag;"]), "}"])};`, `var loadStylesheet = ${runtimeTemplate.basicFunction("chunkId", `return new Promise(${runtimeTemplate.basicFunction("resolve, reject", [`var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`, `var fullhref = ${RuntimeGlobals.publicPath} + href;`, "if(findStylesheet(href, fullhref)) return resolve();", "createStylesheet(chunkId, fullhref, null, resolve, reject);"])});`)}`, withLoading ? Template.asString(["// object to store loaded CSS chunks", "var installedCssChunks = {", Template.indent(/** @type {string[]} */
774 (/** @type {Chunk} */chunk.ids).map(id => `${JSON.stringify(id)}: 0`).join(",\n")), "};", "", `${RuntimeGlobals.ensureChunkHandlers}.miniCss = ${runtimeTemplate.basicFunction("chunkId, promises", [`var cssChunks = ${JSON.stringify(chunkMap)};`, "if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);", "else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {", Template.indent([`promises.push(installedCssChunks[chunkId] = loadStylesheet(chunkId).then(${runtimeTemplate.basicFunction("", "installedCssChunks[chunkId] = 0;")}, ${runtimeTemplate.basicFunction("e", ["delete installedCssChunks[chunkId];", "throw e;"])}));`]), "}"])};`]) : "// no chunk loading", "", withHmr ? Template.asString(["var oldTags = [];", "var newTags = [];", `var applyHandler = ${runtimeTemplate.basicFunction("options", [`return { dispose: ${runtimeTemplate.basicFunction("", ["for(var i = 0; i < oldTags.length; i++) {", Template.indent(["var oldTag = oldTags[i];", "if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);"]), "}", "oldTags.length = 0;"])}, apply: ${runtimeTemplate.basicFunction("", ['for(var i = 0; i < newTags.length; i++) newTags[i].rel = "stylesheet";', "newTags.length = 0;"])} };`])}`, `${RuntimeGlobals.hmrDownloadUpdateHandlers}.miniCss = ${runtimeTemplate.basicFunction("chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList", ["applyHandlers.push(applyHandler);", `chunkIds.forEach(${runtimeTemplate.basicFunction("chunkId", [`var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`, `var fullhref = ${RuntimeGlobals.publicPath} + href;`, "var oldTag = findStylesheet(href, fullhref);", "if(!oldTag) return;", `promises.push(new Promise(${runtimeTemplate.basicFunction("resolve, reject", [`var tag = createStylesheet(chunkId, fullhref, oldTag, ${runtimeTemplate.basicFunction("", ['tag.as = "style";', 'tag.rel = "preload";', "resolve();"])}, reject);`, "oldTags.push(oldTag);", "newTags.push(tag);"])}));`])});`])}`]) : "// no hmr", "", withPrefetch && withLoading && hasCssMatcher !== false ? `${RuntimeGlobals.prefetchChunkHandlers}.miniCss = ${runtimeTemplate.basicFunction("chunkId", [`if((!${RuntimeGlobals.hasOwnProperty}(installedCssChunks, chunkId) || installedCssChunks[chunkId] === undefined) && ${hasCssMatcher === true ? "true" : hasCssMatcher("chunkId")}) {`, Template.indent(["installedCssChunks[chunkId] = null;", linkPrefetch.call(Template.asString(["var link = document.createElement('link');", crossOriginLoading ? `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};` : "", `if (${RuntimeGlobals.scriptNonce}) {`, Template.indent(`link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`), "}", 'link.rel = "prefetch";', 'link.as = "style";', `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.require}.miniCssF(chunkId);`]), /** @type {Chunk} */chunk), "document.head.appendChild(link);"]), "}"])};` : "// no prefetching", "", withPreload && withLoading && hasCssMatcher !== false ? `${RuntimeGlobals.preloadChunkHandlers}.miniCss = ${runtimeTemplate.basicFunction("chunkId", [`if((!${RuntimeGlobals.hasOwnProperty}(installedCssChunks, chunkId) || installedCssChunks[chunkId] === undefined) && ${hasCssMatcher === true ? "true" : hasCssMatcher("chunkId")}) {`, Template.indent(["installedCssChunks[chunkId] = null;", linkPreload.call(Template.asString(["var link = document.createElement('link');", "link.charset = 'utf-8';", `if (${RuntimeGlobals.scriptNonce}) {`, Template.indent(`link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`), "}", 'link.rel = "preload";', 'link.as = "style";', `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.require}.miniCssF(chunkId);`, crossOriginLoading ? crossOriginLoading === "use-credentials" ? 'link.crossOrigin = "use-credentials";' : Template.asString(["if (link.href.indexOf(window.location.origin + '/') !== 0) {", Template.indent(`link.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), "}"]) : ""]), /** @type {Chunk} */chunk), "document.head.appendChild(link);"]), "}"])};` : "// no preloaded"]);
775 }
776 }
777 const enabledChunks = new WeakSet();
778
779 /**
780 * @param {Chunk} chunk chunk
781 * @param {Set<string>} set set with runtime requirement
782 */
783 const handler = (chunk, set) => {
784 if (enabledChunks.has(chunk)) {
785 return;
786 }
787 enabledChunks.add(chunk);
788 if (typeof normalizedChunkFilename === "string" && /\[(full)?hash(:\d+)?\]/.test(normalizedChunkFilename)) {
789 set.add(RuntimeGlobals.getFullHash);
790 }
791 set.add(RuntimeGlobals.publicPath);
792 compilation.addRuntimeModule(chunk, new runtime.GetChunkFilenameRuntimeModule(MODULE_TYPE, "mini-css", `${RuntimeGlobals.require}.miniCssF`,
793 /**
794 * @param {Chunk} referencedChunk a referenced chunk
795 * @returns {ReturnType<import("webpack").runtime.GetChunkFilenameRuntimeModule["getFilenameForChunk"]>} a template value
796 */
797 referencedChunk => {
798 if (!referencedChunk.contentHash[MODULE_TYPE]) {
799 return false;
800 }
801 return referencedChunk.canBeInitial() ? (/** @type {Filename} */normalizedFilename) : (/** @type {ChunkFilename} */normalizedChunkFilename);
802 }, set.has(RuntimeGlobals.hmrDownloadUpdateHandlers)));
803 compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set, this.runtimeOptions));
804 };
805 compilation.hooks.runtimeRequirementInTree.for(RuntimeGlobals.ensureChunkHandlers).tap(pluginName, handler);
806 compilation.hooks.runtimeRequirementInTree.for(RuntimeGlobals.hmrDownloadUpdateHandlers).tap(pluginName, handler);
807 compilation.hooks.runtimeRequirementInTree.for(RuntimeGlobals.prefetchChunkHandlers).tap(pluginName, handler);
808 compilation.hooks.runtimeRequirementInTree.for(RuntimeGlobals.preloadChunkHandlers).tap(pluginName, handler);
809 });
810 }
811
812 /**
813 * @private
814 * @param {Chunk} chunk chunk
815 * @param {ChunkGraph} chunkGraph chunk graph
816 * @returns {Iterable<Module>} modules
817 */
818 getChunkModules(chunk, chunkGraph) {
819 return typeof chunkGraph !== "undefined" ? chunkGraph.getOrderedChunkModulesIterable(chunk, compareModulesByIdentifier) : chunk.modulesIterable;
820 }
821
822 /**
823 * @private
824 * @param {Compilation} compilation compilation
825 * @param {Chunk} chunk chunk
826 * @param {CssModule[]} modules modules
827 * @param {Compilation["requestShortener"]} requestShortener request shortener
828 * @returns {Set<CssModule>} css modules
829 */
830 sortModules(compilation, chunk, modules, requestShortener) {
831 let usedModules = this._sortedModulesCache.get(chunk);
832 if (usedModules || !modules) {
833 return /** @type {Set<CssModule>} */usedModules;
834 }
835
836 /** @type {CssModule[]} */
837 const modulesList = [...modules];
838 // Store dependencies for modules
839 /** @type {Map<CssModule, Set<CssModule>>} */
840 const moduleDependencies = new Map(modulesList.map(m => [m, (/** @type {Set<CssModule>} */
841 new Set())]));
842 /** @type {Map<CssModule, Map<CssModule, Set<ChunkGroup>>>} */
843 const moduleDependenciesReasons = new Map(modulesList.map(m => [m, new Map()]));
844 // Get ordered list of modules per chunk group
845 // This loop also gathers dependencies from the ordered lists
846 // Lists are in reverse order to allow to use Array.pop()
847 /** @type {CssModule[][]} */
848 const modulesByChunkGroup = Array.from(chunk.groupsIterable, chunkGroup => {
849 const sortedModules = modulesList.map(module => ({
850 module,
851 index: chunkGroup.getModulePostOrderIndex(module)
852 })).filter(item => item.index !== undefined).sort((a, b) => /** @type {number} */b.index - (/** @type {number} */a.index)).map(item => item.module);
853 for (let i = 0; i < sortedModules.length; i++) {
854 const set = moduleDependencies.get(sortedModules[i]);
855 const reasons = /** @type {Map<CssModule, Set<ChunkGroup>>} */
856 moduleDependenciesReasons.get(sortedModules[i]);
857 for (let j = i + 1; j < sortedModules.length; j++) {
858 const module = sortedModules[j];
859
860 /** @type {Set<CssModule>} */
861 set.add(module);
862 const reason = reasons.get(module) || (/** @type {Set<ChunkGroup>} */new Set());
863 reason.add(chunkGroup);
864 reasons.set(module, reason);
865 }
866 }
867 return sortedModules;
868 });
869
870 // set with already included modules in correct order
871 usedModules = new Set();
872
873 /**
874 * @param {CssModule} m a css module
875 * @returns {boolean} true when module unused, otherwise false
876 */
877 const unusedModulesFilter = m => !(/** @type {Set<CssModule>} */usedModules.has(m));
878 while (usedModules.size < modulesList.length) {
879 let success = false;
880 let bestMatch;
881 let bestMatchDeps;
882
883 // get first module where dependencies are fulfilled
884 for (const list of modulesByChunkGroup) {
885 // skip and remove already added modules
886 while (list.length > 0 && usedModules.has(list[list.length - 1])) {
887 list.pop();
888 }
889
890 // skip empty lists
891 if (list.length !== 0) {
892 const module = list[list.length - 1];
893 const deps = /** @type {Set<CssModule>} */
894 moduleDependencies.get(module);
895 // determine dependencies that are not yet included
896 const failedDeps = [...deps].filter(unusedModulesFilter);
897
898 // store best match for fallback behavior
899 if (!bestMatchDeps || bestMatchDeps.length > failedDeps.length) {
900 bestMatch = list;
901 bestMatchDeps = failedDeps;
902 }
903 if (failedDeps.length === 0) {
904 // use this module and remove it from list
905 usedModules.add(/** @type {CssModule} */list.pop());
906 success = true;
907 break;
908 }
909 }
910 }
911 if (!success) {
912 // no module found => there is a conflict
913 // use list with fewest failed deps
914 // and emit a warning
915 const fallbackModule = /** @type {CssModule[]} */bestMatch.pop();
916 if (!this.options.ignoreOrder) {
917 const reasons = moduleDependenciesReasons.get(/** @type {CssModule} */fallbackModule);
918 compilation.warnings.push(/** @type {WebpackError} */
919
920 new Error([`chunk ${chunk.name || chunk.id} [${pluginName}]`, "Conflicting order. Following module has been added:", ` * ${ /** @type {CssModule} */fallbackModule.readableIdentifier(requestShortener)}`, "despite it was not able to fulfill desired ordering with these modules:", ... /** @type {CssModule[]} */bestMatchDeps.map(m => {
921 const goodReasonsMap = moduleDependenciesReasons.get(m);
922 const goodReasons = goodReasonsMap && goodReasonsMap.get(/** @type {CssModule} */fallbackModule);
923 const failedChunkGroups = Array.from(/** @type {Set<ChunkGroup>} */
924
925 /** @type {Map<CssModule, Set<ChunkGroup>>} */
926 reasons.get(m), cg => cg.name).join(", ");
927 const goodChunkGroups = goodReasons && Array.from(goodReasons, cg => cg.name).join(", ");
928 return [` * ${m.readableIdentifier(requestShortener)}`, ` - couldn't fulfill desired order of chunk group(s) ${failedChunkGroups}`, goodChunkGroups && ` - while fulfilling desired order of chunk group(s) ${goodChunkGroups}`].filter(Boolean).join("\n");
929 })].join("\n")));
930 }
931 usedModules.add(/** @type {CssModule} */fallbackModule);
932 }
933 }
934 this._sortedModulesCache.set(chunk, usedModules);
935 return usedModules;
936 }
937
938 /**
939 * @private
940 * @param {Compiler} compiler compiler
941 * @param {Compilation} compilation compilation
942 * @param {Chunk} chunk chunk
943 * @param {CssModule[]} modules modules
944 * @param {Compiler["requestShortener"]} requestShortener request shortener
945 * @param {string} filenameTemplate filename template
946 * @param {Parameters<Exclude<Required<Configuration>['output']['filename'], string | undefined>>[0]} pathData path data
947 * @returns {Source} source
948 */
949 renderContentAsset(compiler, compilation, chunk, modules, requestShortener, filenameTemplate, pathData) {
950 const usedModules = this.sortModules(compilation, chunk, modules, requestShortener);
951 const {
952 ConcatSource,
953 SourceMapSource,
954 RawSource
955 } = compiler.webpack.sources;
956 const source = new ConcatSource();
957 const externalsSource = new ConcatSource();
958 for (const module of usedModules) {
959 let content = module.content.toString();
960 const readableIdentifier = module.readableIdentifier(requestShortener);
961 const startsWithAtRuleImport = content.startsWith("@import url");
962 let header;
963 if (compilation.outputOptions.pathinfo) {
964 // From https://github.com/webpack/webpack/blob/29eff8a74ecc2f87517b627dee451c2af9ed3f3f/lib/ModuleInfoHeaderPlugin.js#L191-L194
965 const reqStr = readableIdentifier.replace(/\*\//g, "*_/");
966 const reqStrStar = "*".repeat(reqStr.length);
967 const headerStr = `/*!****${reqStrStar}****!*\\\n !*** ${reqStr} ***!\n \\****${reqStrStar}****/\n`;
968 header = new RawSource(headerStr);
969 }
970 if (startsWithAtRuleImport) {
971 if (typeof header !== "undefined") {
972 externalsSource.add(header);
973 }
974
975 // HACK for IE
976 // http://stackoverflow.com/a/14676665/1458162
977 if (module.media || module.supports || typeof module.layer === "string") {
978 let atImportExtra = "";
979 const needLayer = typeof module.layer === "string";
980 if (needLayer) {
981 atImportExtra += module.layer.length > 0 ? ` layer(${module.layer})` : " layer";
982 }
983 if (module.supports) {
984 atImportExtra += ` supports(${module.supports})`;
985 }
986 if (module.media) {
987 atImportExtra += ` ${module.media}`;
988 }
989
990 // insert media into the @import
991 // this is rar
992 // TODO improve this and parse the CSS to support multiple medias
993 content = content.replace(/;|\s*$/, `${atImportExtra};`);
994 }
995 externalsSource.add(content);
996 externalsSource.add("\n");
997 } else {
998 if (typeof header !== "undefined") {
999 source.add(header);
1000 }
1001 if (module.supports) {
1002 source.add(`@supports (${module.supports}) {\n`);
1003 }
1004 if (module.media) {
1005 source.add(`@media ${module.media} {\n`);
1006 }
1007 const needLayer = typeof module.layer === "string";
1008 if (needLayer) {
1009 source.add(`@layer${module.layer.length > 0 ? ` ${module.layer}` : ""} {\n`);
1010 }
1011 const {
1012 path: filename
1013 } = compilation.getPathWithInfo(filenameTemplate, pathData);
1014 const undoPath = getUndoPath(filename, compiler.outputPath, false);
1015
1016 // replacements
1017 content = content.replace(new RegExp(ABSOLUTE_PUBLIC_PATH, "g"), "");
1018 content = content.replace(new RegExp(SINGLE_DOT_PATH_SEGMENT, "g"), ".");
1019 content = content.replace(new RegExp(AUTO_PUBLIC_PATH, "g"), undoPath);
1020 const entryOptions = chunk.getEntryOptions();
1021 const baseUriReplacement = entryOptions && entryOptions.baseUri || undoPath;
1022 content = content.replace(new RegExp(BASE_URI, "g"), baseUriReplacement);
1023 if (module.sourceMap) {
1024 source.add(new SourceMapSource(content, readableIdentifier, module.sourceMap.toString()));
1025 } else {
1026 source.add(new RawSource(content));
1027 }
1028 source.add("\n");
1029 if (needLayer) {
1030 source.add("}\n");
1031 }
1032 if (module.media) {
1033 source.add("}\n");
1034 }
1035 if (module.supports) {
1036 source.add("}\n");
1037 }
1038 }
1039 }
1040 return new ConcatSource(externalsSource, source);
1041 }
1042}
1043MiniCssExtractPlugin.pluginName = pluginName;
1044MiniCssExtractPlugin.pluginSymbol = pluginSymbol;
1045MiniCssExtractPlugin.loader = require.resolve("./loader");
1046module.exports = MiniCssExtractPlugin;
Note: See TracBrowser for help on using the repository browser.