webpack 5.106.2 → 5.107.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/README.md +2 -2
  2. package/lib/APIPlugin.js +1 -1
  3. package/lib/Cache.js +3 -6
  4. package/lib/CompatibilityPlugin.js +8 -7
  5. package/lib/Compilation.js +34 -26
  6. package/lib/Compiler.js +4 -13
  7. package/lib/ContextModule.js +2 -2
  8. package/lib/DefinePlugin.js +2 -2
  9. package/lib/Dependency.js +22 -1
  10. package/lib/DependencyTemplate.js +2 -1
  11. package/lib/EnvironmentPlugin.js +1 -1
  12. package/lib/EvalSourceMapDevToolPlugin.js +8 -9
  13. package/lib/ExternalModule.js +76 -15
  14. package/lib/ExternalModuleFactoryPlugin.js +5 -0
  15. package/lib/FileSystemInfo.js +187 -72
  16. package/lib/Generator.js +3 -3
  17. package/lib/HotModuleReplacementPlugin.js +26 -8
  18. package/lib/IgnorePlugin.js +2 -1
  19. package/lib/Module.js +19 -18
  20. package/lib/ModuleFactory.js +1 -1
  21. package/lib/ModuleSourceTypeConstants.js +31 -1
  22. package/lib/ModuleTypeConstants.js +12 -3
  23. package/lib/MultiCompiler.js +2 -2
  24. package/lib/NodeStuffPlugin.js +1 -1
  25. package/lib/NormalModule.js +13 -31
  26. package/lib/NormalModuleFactory.js +10 -2
  27. package/lib/Parser.js +1 -1
  28. package/lib/ProgressPlugin.js +129 -56
  29. package/lib/RuntimeGlobals.js +5 -5
  30. package/lib/RuntimeModule.js +9 -7
  31. package/lib/RuntimePlugin.js +11 -0
  32. package/lib/WarnCaseSensitiveModulesPlugin.js +70 -2
  33. package/lib/WarnDeprecatedOptionPlugin.js +1 -1
  34. package/lib/WarnNoModeSetPlugin.js +16 -1
  35. package/lib/Watching.js +2 -3
  36. package/lib/WebpackError.js +3 -77
  37. package/lib/WebpackIsIncludedPlugin.js +1 -1
  38. package/lib/WebpackOptionsApply.js +13 -1
  39. package/lib/asset/AssetBytesGenerator.js +6 -2
  40. package/lib/asset/AssetGenerator.js +22 -8
  41. package/lib/asset/AssetModulesPlugin.js +3 -1
  42. package/lib/asset/AssetSourceGenerator.js +6 -2
  43. package/lib/buildChunkGraph.js +4 -6
  44. package/lib/cache/PackFileCacheStrategy.js +4 -4
  45. package/lib/cli.js +3 -1
  46. package/lib/config/defaults.js +197 -10
  47. package/lib/config/normalization.js +3 -1
  48. package/lib/css/CssGenerator.js +320 -105
  49. package/lib/css/CssInjectStyleRuntimeModule.js +44 -42
  50. package/lib/css/CssLoadingRuntimeModule.js +22 -4
  51. package/lib/{CssModule.js → css/CssModule.js} +15 -15
  52. package/lib/css/CssModulesPlugin.js +166 -86
  53. package/lib/css/CssParser.js +566 -269
  54. package/lib/css/walkCssTokens.js +148 -2
  55. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +1 -1
  56. package/lib/dependencies/CommonJsDependencyHelpers.js +63 -0
  57. package/lib/dependencies/CommonJsExportRequireDependency.js +54 -10
  58. package/lib/dependencies/CommonJsExportsParserPlugin.js +1 -1
  59. package/lib/dependencies/CommonJsFullRequireDependency.js +32 -9
  60. package/lib/dependencies/CommonJsImportsParserPlugin.js +4 -3
  61. package/lib/dependencies/CommonJsRequireDependency.js +67 -4
  62. package/lib/dependencies/ContextDependency.js +1 -1
  63. package/lib/dependencies/ContextDependencyHelpers.js +1 -1
  64. package/lib/dependencies/CreateRequireParserPlugin.js +1 -1
  65. package/lib/dependencies/CriticalDependencyWarning.js +1 -1
  66. package/lib/dependencies/CssIcssExportDependency.js +332 -67
  67. package/lib/dependencies/CssIcssImportDependency.js +49 -7
  68. package/lib/dependencies/CssIcssSymbolDependency.js +11 -3
  69. package/lib/dependencies/CssImportDependency.js +8 -0
  70. package/lib/dependencies/CssUrlDependency.js +25 -0
  71. package/lib/dependencies/HarmonyDetectionParserPlugin.js +1 -1
  72. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +8 -7
  73. package/lib/dependencies/HarmonyExportExpressionDependency.js +22 -14
  74. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +110 -3
  75. package/lib/dependencies/HarmonyImportDependency.js +10 -2
  76. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +22 -1
  77. package/lib/dependencies/HarmonyImportSpecifierDependency.js +1 -1
  78. package/lib/{HarmonyLinkingError.js → dependencies/HarmonyLinkingError.js} +5 -3
  79. package/lib/dependencies/HtmlInlineScriptDependency.js +133 -0
  80. package/lib/dependencies/HtmlInlineStyleDependency.js +101 -0
  81. package/lib/dependencies/HtmlScriptSrcDependency.js +318 -0
  82. package/lib/dependencies/HtmlSourceDependency.js +127 -0
  83. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +1 -1
  84. package/lib/dependencies/ImportParserPlugin.js +2 -2
  85. package/lib/dependencies/ImportPhase.js +1 -1
  86. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +1 -1
  87. package/lib/{RequireJsStuffPlugin.js → dependencies/RequireJsStuffPlugin.js} +7 -7
  88. package/lib/dependencies/SystemPlugin.js +1 -1
  89. package/lib/dependencies/WebAssemblyImportDependency.js +1 -1
  90. package/lib/dependencies/WorkerPlugin.js +2 -2
  91. package/lib/{DelegatedModule.js → dll/DelegatedModule.js} +31 -31
  92. package/lib/{DelegatedModuleFactoryPlugin.js → dll/DelegatedModuleFactoryPlugin.js} +4 -4
  93. package/lib/{DelegatedPlugin.js → dll/DelegatedPlugin.js} +2 -2
  94. package/lib/{DllEntryPlugin.js → dll/DllEntryPlugin.js} +4 -4
  95. package/lib/{DllModule.js → dll/DllModule.js} +24 -24
  96. package/lib/{DllModuleFactory.js → dll/DllModuleFactory.js} +4 -4
  97. package/lib/{DllPlugin.js → dll/DllPlugin.js} +6 -5
  98. package/lib/{DllReferencePlugin.js → dll/DllReferencePlugin.js} +14 -14
  99. package/lib/{LibManifestPlugin.js → dll/LibManifestPlugin.js} +9 -9
  100. package/lib/{AsyncDependencyToInitialChunkError.js → errors/AsyncDependencyToInitialChunkError.js} +2 -2
  101. package/lib/errors/BuildCycleError.js +1 -1
  102. package/lib/{ChunkRenderError.js → errors/ChunkRenderError.js} +1 -1
  103. package/lib/{CodeGenerationError.js → errors/CodeGenerationError.js} +1 -1
  104. package/lib/{CommentCompilationWarning.js → errors/CommentCompilationWarning.js} +3 -3
  105. package/lib/{ConcurrentCompilationError.js → errors/ConcurrentCompilationError.js} +4 -2
  106. package/lib/{EnvironmentNotSupportAsyncWarning.js → errors/EnvironmentNotSupportAsyncWarning.js} +4 -4
  107. package/lib/{HookWebpackError.js → errors/HookWebpackError.js} +5 -5
  108. package/lib/{IgnoreErrorModuleFactory.js → errors/IgnoreErrorModuleFactory.js} +4 -4
  109. package/lib/{InvalidDependenciesModuleWarning.js → errors/InvalidDependenciesModuleWarning.js} +3 -3
  110. package/lib/errors/JSONParseError.js +114 -0
  111. package/lib/{ModuleBuildError.js → errors/ModuleBuildError.js} +5 -5
  112. package/lib/{ModuleDependencyError.js → errors/ModuleDependencyError.js} +2 -2
  113. package/lib/{ModuleDependencyWarning.js → errors/ModuleDependencyWarning.js} +4 -4
  114. package/lib/{ModuleError.js → errors/ModuleError.js} +5 -5
  115. package/lib/{ModuleHashingError.js → errors/ModuleHashingError.js} +1 -1
  116. package/lib/{ModuleNotFoundError.js → errors/ModuleNotFoundError.js} +2 -2
  117. package/lib/{ModuleParseError.js → errors/ModuleParseError.js} +8 -6
  118. package/lib/{ModuleRestoreError.js → errors/ModuleRestoreError.js} +1 -1
  119. package/lib/{ModuleStoreError.js → errors/ModuleStoreError.js} +1 -1
  120. package/lib/{ModuleWarning.js → errors/ModuleWarning.js} +5 -5
  121. package/lib/{NodeStuffInWebError.js → errors/NodeStuffInWebError.js} +4 -4
  122. package/lib/errors/NonErrorEmittedError.js +28 -0
  123. package/lib/{UnhandledSchemeError.js → errors/UnhandledSchemeError.js} +2 -2
  124. package/lib/{UnsupportedFeatureWarning.js → errors/UnsupportedFeatureWarning.js} +3 -3
  125. package/lib/errors/WebpackError.js +84 -0
  126. package/lib/html/HtmlGenerator.js +379 -0
  127. package/lib/html/HtmlModulesPlugin.js +433 -0
  128. package/lib/html/HtmlParser.js +1489 -0
  129. package/lib/html/walkHtmlTokens.js +2733 -0
  130. package/lib/ids/IdHelpers.js +2 -1
  131. package/lib/index.js +34 -15
  132. package/lib/javascript/JavascriptModulesPlugin.js +89 -8
  133. package/lib/javascript/JavascriptParser.js +197 -16
  134. package/lib/javascript/JavascriptParserHelpers.js +1 -1
  135. package/lib/json/JsonParser.js +7 -16
  136. package/lib/library/AbstractLibraryPlugin.js +1 -1
  137. package/lib/library/EnableLibraryPlugin.js +1 -1
  138. package/lib/{FalseIIFEUmdWarning.js → library/FalseIIFEUmdWarning.js} +1 -1
  139. package/lib/library/ModuleLibraryPlugin.js +74 -0
  140. package/lib/node/NodeEnvironmentPlugin.js +4 -2
  141. package/lib/node/nodeConsole.js +113 -64
  142. package/lib/optimize/ConcatenatedModule.js +51 -6
  143. package/lib/optimize/InnerGraph.js +1 -1
  144. package/lib/optimize/InnerGraphPlugin.js +11 -1
  145. package/lib/optimize/MinMaxSizeWarning.js +4 -4
  146. package/lib/optimize/ModuleConcatenationPlugin.js +15 -7
  147. package/lib/optimize/RealContentHashPlugin.js +89 -26
  148. package/lib/optimize/SideEffectsFlagPlugin.js +111 -3
  149. package/lib/optimize/SplitChunksPlugin.js +1 -1
  150. package/lib/performance/AssetsOverSizeLimitWarning.js +2 -2
  151. package/lib/performance/EntrypointsOverSizeLimitWarning.js +2 -2
  152. package/lib/performance/NoAsyncChunksWarning.js +5 -3
  153. package/lib/performance/SizeLimitsPlugin.js +1 -1
  154. package/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js +4 -1
  155. package/lib/rules/UseEffectRulePlugin.js +4 -3
  156. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +119 -13
  157. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +35 -0
  158. package/lib/schemes/DataUriPlugin.js +13 -1
  159. package/lib/schemes/VirtualUrlPlugin.js +1 -1
  160. package/lib/serialization/SerializerMiddleware.js +2 -2
  161. package/lib/sharing/ConsumeSharedPlugin.js +2 -2
  162. package/lib/sharing/ConsumeSharedRuntimeModule.js +8 -4
  163. package/lib/sharing/ProvideSharedModule.js +1 -1
  164. package/lib/sharing/ProvideSharedPlugin.js +1 -1
  165. package/lib/sharing/resolveMatchedConfigs.js +1 -1
  166. package/lib/stats/DefaultStatsFactoryPlugin.js +2 -2
  167. package/lib/stats/DefaultStatsPresetPlugin.js +1 -1
  168. package/lib/stats/DefaultStatsPrinterPlugin.js +1 -1
  169. package/lib/stats/StatsFactory.js +1 -1
  170. package/lib/typescript/TypeScriptPlugin.js +210 -0
  171. package/lib/url/URLParserPlugin.js +2 -2
  172. package/lib/util/AsyncQueue.js +2 -2
  173. package/lib/util/Hash.js +2 -2
  174. package/lib/util/LocConverter.js +53 -0
  175. package/lib/util/SortableSet.js +1 -1
  176. package/lib/util/cleverMerge.js +2 -2
  177. package/lib/util/comparators.js +3 -3
  178. package/lib/util/concatenate.js +3 -3
  179. package/lib/util/conventions.js +42 -1
  180. package/lib/util/createMappings.js +118 -0
  181. package/lib/{formatLocation.js → util/formatLocation.js} +2 -2
  182. package/lib/{SizeFormatHelpers.js → util/formatSize.js} +3 -1
  183. package/lib/util/fs.js +8 -8
  184. package/lib/util/hash/md4.js +1 -1
  185. package/lib/util/hash/xxhash64.js +1 -1
  186. package/lib/util/identifier.js +48 -0
  187. package/lib/util/internalSerializables.js +35 -19
  188. package/lib/util/magicComment.js +10 -7
  189. package/lib/util/parseJson.js +2 -73
  190. package/lib/util/source.js +21 -0
  191. package/lib/util/topologicalSort.js +69 -0
  192. package/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js +2 -2
  193. package/lib/wasm-async/AsyncWebAssemblyParser.js +1 -1
  194. package/lib/wasm-sync/UnsupportedWebAssemblyFeatureError.js +5 -3
  195. package/lib/wasm-sync/WasmFinalizeExportsPlugin.js +1 -1
  196. package/lib/wasm-sync/WebAssemblyInInitialChunkError.js +5 -3
  197. package/lib/webpack.js +3 -1
  198. package/package.json +22 -20
  199. package/schemas/WebpackOptions.check.js +1 -1
  200. package/schemas/WebpackOptions.json +118 -3
  201. package/schemas/plugins/{DllPlugin.check.d.ts → HtmlGeneratorOptions.check.d.ts} +1 -1
  202. package/schemas/plugins/HtmlGeneratorOptions.check.js +6 -0
  203. package/schemas/plugins/HtmlGeneratorOptions.json +3 -0
  204. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  205. package/schemas/plugins/ProgressPlugin.json +22 -0
  206. package/schemas/plugins/{DllReferencePlugin.check.d.ts → css/CssAutoOrModuleParserOptions.check.d.ts} +1 -1
  207. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +6 -0
  208. package/schemas/plugins/css/CssAutoOrModuleParserOptions.json +3 -0
  209. package/schemas/plugins/dll/DllPlugin.check.d.ts +7 -0
  210. package/schemas/plugins/dll/DllReferencePlugin.check.d.ts +7 -0
  211. package/types.d.ts +810 -101
  212. package/lib/CaseSensitiveModulesWarning.js +0 -80
  213. package/lib/GraphHelpers.js +0 -49
  214. package/lib/NoModeWarning.js +0 -23
  215. package/lib/css/CssMergeStyleSheetsRuntimeModule.js +0 -57
  216. /package/lib/{AbstractMethodError.js → errors/AbstractMethodError.js} +0 -0
  217. /package/schemas/plugins/{DllPlugin.check.js → dll/DllPlugin.check.js} +0 -0
  218. /package/schemas/plugins/{DllPlugin.json → dll/DllPlugin.json} +0 -0
  219. /package/schemas/plugins/{DllReferencePlugin.check.js → dll/DllReferencePlugin.check.js} +0 -0
  220. /package/schemas/plugins/{DllReferencePlugin.json → dll/DllReferencePlugin.json} +0 -0
@@ -5,13 +5,10 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ const path = require("path");
8
9
  const vm = require("vm");
9
- const CommentCompilationWarning = require("../CommentCompilationWarning");
10
- const ModuleDependencyWarning = require("../ModuleDependencyWarning");
11
10
  const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants");
12
11
  const Parser = require("../Parser");
13
- const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
14
- const WebpackError = require("../WebpackError");
15
12
  const ConstDependency = require("../dependencies/ConstDependency");
16
13
  const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency");
17
14
  const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency");
@@ -19,12 +16,18 @@ const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency
19
16
  const CssImportDependency = require("../dependencies/CssImportDependency");
20
17
  const CssUrlDependency = require("../dependencies/CssUrlDependency");
21
18
  const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
19
+ const CommentCompilationWarning = require("../errors/CommentCompilationWarning");
20
+ const ModuleDependencyWarning = require("../errors/ModuleDependencyWarning");
21
+ const UnsupportedFeatureWarning = require("../errors/UnsupportedFeatureWarning");
22
+ const WebpackError = require("../errors/WebpackError");
23
+ const LocConverter = require("../util/LocConverter");
22
24
  const binarySearchBounds = require("../util/binarySearchBounds");
23
25
  const { parseResource } = require("../util/identifier");
24
26
  const {
25
27
  createMagicCommentContext,
26
28
  webpackCommentRegExp
27
29
  } = require("../util/magicComment");
30
+ const topologicalSort = require("../util/topologicalSort");
28
31
  const walkCssTokens = require("./walkCssTokens");
29
32
 
30
33
  /** @typedef {import("../Module").BuildInfo} BuildInfo */
@@ -32,8 +35,9 @@ const walkCssTokens = require("./walkCssTokens");
32
35
  /** @typedef {import("../Parser").ParserState} ParserState */
33
36
  /** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
34
37
  /** @typedef {import("./walkCssTokens").CssTokenCallbacks} CssTokenCallbacks */
38
+ /** @typedef {import("../../declarations/WebpackOptions").CssAutoOrModuleParserOptions} CssAutoOrModuleParserOptions */
35
39
  /** @typedef {import("../../declarations/WebpackOptions").CssModuleParserOptions} CssModuleParserOptions */
36
- /** @typedef {import("../CssModule")} CssModule */
40
+ /** @typedef {import("./CssModule")} CssModule */
37
41
 
38
42
  /** @typedef {[number, number]} Range */
39
43
  /** @typedef {{ line: number, column: number }} Position */
@@ -52,6 +56,7 @@ const CC_TILDE = "~".charCodeAt(0);
52
56
  const CC_EQUAL = "=".charCodeAt(0);
53
57
  const CC_FULL_STOP = ".".charCodeAt(0);
54
58
  const CC_EXCLAMATION = "!".charCodeAt(0);
59
+ const CC_AMPERSAND = "&".charCodeAt(0);
55
60
 
56
61
  // https://www.w3.org/TR/css-syntax-3/#newline
57
62
  // We don't have `preprocessing` stage, so we need specify all of them
@@ -128,138 +133,7 @@ const normalizeUrl = (str, isString) => {
128
133
  return str;
129
134
  };
130
135
 
131
- const regexSingleEscape = /[ -,./:-@[\]^`{-~]/;
132
- const regexExcessiveSpaces =
133
- /(^|\\+)?(\\[A-F0-9]{1,6})\u0020(?![a-fA-F0-9\u0020])/g;
134
-
135
- /**
136
- * Returns escaped identifier.
137
- * @param {string} str string
138
- * @returns {string} escaped identifier
139
- */
140
- const escapeIdentifier = (str) => {
141
- let output = "";
142
- let counter = 0;
143
-
144
- while (counter < str.length) {
145
- const character = str.charAt(counter++);
146
-
147
- /** @type {string} */
148
- let value;
149
-
150
- if (/[\t\n\f\r\v]/.test(character)) {
151
- const codePoint = character.charCodeAt(0);
152
-
153
- value = `\\${codePoint.toString(16).toUpperCase()} `;
154
- } else if (character === "\\" || regexSingleEscape.test(character)) {
155
- value = `\\${character}`;
156
- } else {
157
- value = character;
158
- }
159
-
160
- output += value;
161
- }
162
-
163
- const firstChar = str.charAt(0);
164
-
165
- if (/^-[-\d]/.test(output)) {
166
- output = `\\-${output.slice(1)}`;
167
- } else if (/\d/.test(firstChar)) {
168
- output = `\\3${firstChar} ${output.slice(1)}`;
169
- }
170
-
171
- // Remove spaces after `\HEX` escapes that are not followed by a hex digit,
172
- // since they’re redundant. Note that this is only possible if the escape
173
- // sequence isn’t preceded by an odd number of backslashes.
174
- output = output.replace(regexExcessiveSpaces, ($0, $1, $2) => {
175
- if ($1 && $1.length % 2) {
176
- // It’s not safe to remove the space, so don’t.
177
- return $0;
178
- }
179
-
180
- // Strip the space.
181
- return ($1 || "") + $2;
182
- });
183
-
184
- return output;
185
- };
186
-
187
- const CONTAINS_ESCAPE = /\\/;
188
-
189
- /**
190
- * Returns hex.
191
- * @param {string} str string
192
- * @returns {[string, number] | undefined} hex
193
- */
194
- const gobbleHex = (str) => {
195
- const lower = str.toLowerCase();
196
- let hex = "";
197
- let spaceTerminated = false;
198
-
199
- for (let i = 0; i < 6 && lower[i] !== undefined; i++) {
200
- const code = lower.charCodeAt(i);
201
- // check to see if we are dealing with a valid hex char [a-f|0-9]
202
- const valid = (code >= 97 && code <= 102) || (code >= 48 && code <= 57);
203
- // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
204
- spaceTerminated = code === 32;
205
- if (!valid) break;
206
- hex += lower[i];
207
- }
208
-
209
- if (hex.length === 0) return undefined;
210
-
211
- const codePoint = Number.parseInt(hex, 16);
212
- const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff;
213
-
214
- // Add special case for
215
- // "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
216
- // https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
217
- if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) {
218
- return ["\uFFFD", hex.length + (spaceTerminated ? 1 : 0)];
219
- }
220
-
221
- return [
222
- String.fromCodePoint(codePoint),
223
- hex.length + (spaceTerminated ? 1 : 0)
224
- ];
225
- };
226
-
227
- /**
228
- * Unescape identifier.
229
- * @param {string} str string
230
- * @returns {string} unescaped string
231
- */
232
- const unescapeIdentifier = (str) => {
233
- const needToProcess = CONTAINS_ESCAPE.test(str);
234
- if (!needToProcess) return str;
235
- let ret = "";
236
- for (let i = 0; i < str.length; i++) {
237
- if (str[i] === "\\") {
238
- const gobbled = gobbleHex(str.slice(i + 1, i + 7));
239
- if (gobbled !== undefined) {
240
- ret += gobbled[0];
241
- i += gobbled[1];
242
- continue;
243
- }
244
- // Retain a pair of \\ if double escaped `\\\\`
245
- // https://github.com/postcss/postcss-selector-parser/commit/268c9a7656fb53f543dc620aa5b73a30ec3ff20e
246
- if (str[i + 1] === "\\") {
247
- ret += "\\";
248
- i += 1;
249
- continue;
250
- }
251
- // if \\ is at the end of the string retain it
252
- // https://github.com/postcss/postcss-selector-parser/commit/01a6b346e3612ce1ab20219acc26abdc259ccefb
253
- if (str.length === i + 1) {
254
- ret += str[i];
255
- }
256
- continue;
257
- }
258
- ret += str[i];
259
- }
260
-
261
- return ret;
262
- };
136
+ const { escapeIdentifier, unescapeIdentifier } = walkCssTokens;
263
137
 
264
138
  /**
265
139
  * A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo.
@@ -531,51 +405,6 @@ const getKnownProperties = (options = {}) => {
531
405
  return knownProperties;
532
406
  };
533
407
 
534
- class LocConverter {
535
- /**
536
- * Creates an instance of LocConverter.
537
- * @param {string} input input
538
- */
539
- constructor(input) {
540
- this._input = input;
541
- this.line = 1;
542
- this.column = 0;
543
- this.pos = 0;
544
- }
545
-
546
- /**
547
- * Returns location converter.
548
- * @param {number} pos position
549
- * @returns {LocConverter} location converter
550
- */
551
- get(pos) {
552
- if (this.pos !== pos) {
553
- if (this.pos < pos) {
554
- const str = this._input.slice(this.pos, pos);
555
- let i = str.lastIndexOf("\n");
556
- if (i === -1) {
557
- this.column += str.length;
558
- } else {
559
- this.column = str.length - i - 1;
560
- this.line++;
561
- while (i > 0 && (i = str.lastIndexOf("\n", i - 1)) !== -1) {
562
- this.line++;
563
- }
564
- }
565
- } else {
566
- let i = this._input.lastIndexOf("\n", this.pos);
567
- while (i >= pos) {
568
- this.line--;
569
- i = i > 0 ? this._input.lastIndexOf("\n", i - 1) : -1;
570
- }
571
- this.column = pos - i;
572
- }
573
- this.pos = pos;
574
- }
575
- return this;
576
- }
577
- }
578
-
579
408
  const EMPTY_COMMENT_OPTIONS = {
580
409
  options: null,
581
410
  errors: null
@@ -596,7 +425,7 @@ const eatUntilLeftCurly = walkCssTokens.eatUntil("{");
596
425
  * @property {("pure" | "global" | "local" | "auto")=} defaultMode default mode
597
426
  */
598
427
 
599
- /** @typedef {CssModuleParserOptions & CssParserOwnOptions} CssParserOptions */
428
+ /** @typedef {CssAutoOrModuleParserOptions & CssParserOwnOptions} CssParserOptions */
600
429
 
601
430
  class CssParser extends Parser {
602
431
  /**
@@ -644,6 +473,27 @@ class CssParser extends Parser {
644
473
  );
645
474
  }
646
475
 
476
+ /**
477
+ * Emits a build error for the provided range.
478
+ * @param {ParserState} state parser state
479
+ * @param {string} message error message
480
+ * @param {LocConverter} locConverter location converter
481
+ * @param {number} start start offset
482
+ * @param {number} end end offset
483
+ */
484
+ _emitError(state, message, locConverter, start, end) {
485
+ const { line: sl, column: sc } = locConverter.get(start);
486
+ const { line: el, column: ec } = locConverter.get(end);
487
+
488
+ const err = new WebpackError(message);
489
+ err.module = state.module;
490
+ err.loc = {
491
+ start: { line: sl, column: sc },
492
+ end: { line: el, column: ec }
493
+ };
494
+ state.module.addError(err);
495
+ }
496
+
647
497
  /**
648
498
  * Parses the provided source and updates the parser state.
649
499
  * @param {string | Buffer | PreparsedAst} source the source to parse
@@ -660,6 +510,10 @@ class CssParser extends Parser {
660
510
  source = source.slice(1);
661
511
  }
662
512
 
513
+ const unescapeIdentifierCached = unescapeIdentifier.bindCache(
514
+ state.compilation.compiler.root
515
+ );
516
+
663
517
  let mode = this.defaultMode;
664
518
 
665
519
  const module = state.module;
@@ -675,6 +529,40 @@ class CssParser extends Parser {
675
529
  }
676
530
 
677
531
  const isModules = mode === "global" || mode === "local";
532
+
533
+ const parsedModuleResource = parseResource(
534
+ /** @type {string} */ (module.getResource())
535
+ );
536
+
537
+ /**
538
+ * Check whether a request points back to the current module
539
+ * (e.g. `composes: foo from "./self.module.css"` inside `self.module.css`).
540
+ * Only relative requests are checked — aliases / package / absolute requests
541
+ * fall through to the normal import path. Requests with a `?query` or
542
+ * `#fragment` are only treated as self when the parent module's resource
543
+ * has the same query/fragment, since `NormalModuleFactory` keys modules
544
+ * on the full resource string.
545
+ * @param {string} request request string from `from "<request>"`
546
+ * @returns {boolean} true if request resolves to the current module
547
+ */
548
+ const isSelfReferenceRequest = (request) => {
549
+ if (!/^\.{1,2}\//.test(request)) return false;
550
+ if (!module.context) return false;
551
+ const parsedRequest = parseResource(request);
552
+ if (parsedRequest.query !== parsedModuleResource.query) return false;
553
+ if (parsedRequest.fragment !== parsedModuleResource.fragment) {
554
+ return false;
555
+ }
556
+ try {
557
+ return (
558
+ path.resolve(module.context, parsedRequest.path) ===
559
+ parsedModuleResource.path
560
+ );
561
+ } catch (_err) {
562
+ return false;
563
+ }
564
+ };
565
+
678
566
  const knownProperties = getKnownProperties({
679
567
  animation: this.options.animation,
680
568
  container: this.options.container,
@@ -683,7 +571,11 @@ class CssParser extends Parser {
683
571
  });
684
572
 
685
573
  /** @type {BuildMeta} */
686
- (module.buildMeta).isCSSModule = isModules;
574
+ (module.buildMeta).isCssModule = isModules;
575
+ if (/** @type {CssModule} */ (module).exportType === "style") {
576
+ /** @type {BuildMeta} */
577
+ (module.buildMeta).needIdInConcatenation = true;
578
+ }
687
579
 
688
580
  const locConverter = new LocConverter(source);
689
581
 
@@ -707,10 +599,121 @@ class CssParser extends Parser {
707
599
  /** @type {string[]} */
708
600
  let lastLocalIdentifiers = [];
709
601
 
710
- /** @typedef {{ value?: string, importName?: string, localName?: string }} IcssDefinition */
602
+ const pureMode = isModules && Boolean(this.options.pure);
603
+ /** @type {boolean} */
604
+ let currentSelectorHasLocal = false;
605
+ /** Whether any comma-separated selector in the current rule's prelude was impure. */
606
+ let currentRuleHasImpureSelector = false;
607
+ /** Offset just after the previous `}` (or 0) — used as the prelude start. */
608
+ let currentRulePreludeStart = 0;
609
+ /** Pure-mode flags (only meaningful when `pureMode` is true). */
610
+ let pureNoCheck = false;
611
+ let pureIgnorePending = false;
612
+ let nextBlockChildrenSkip = false;
613
+ let nextBlockTreatAsLeaf = false;
614
+ let seenTopLevelRule = false;
615
+ // True after an at-rule keyword and before the next `{` or `;`. Used so
616
+ // identifiers inside the at-rule prelude (e.g. `min-width` inside
617
+ // `@media (min-width: 768px)`) don't get counted as declarations.
618
+ let inAtRulePrelude = false;
619
+ /**
620
+ * One entry per open block. `skipOwn` skips this rule's own check (set
621
+ * when the parent passed down `skipChildren`, e.g. `from`/`to` inside
622
+ * `@keyframes`). `skipChildren` is propagated to descendants. `ignored`
623
+ * is per-rule only (PCSL semantics for `cssmodules-pure-ignore`).
624
+ * `ancestorHadLocal` lets nested rules inherit purity from a
625
+ * local-bearing ancestor.
626
+ * @type {{
627
+ * ignored: boolean,
628
+ * skipOwn: boolean,
629
+ * skipChildren: boolean,
630
+ * treatAsLeaf: boolean,
631
+ * ancestorHadLocal: boolean,
632
+ * impure: boolean,
633
+ * hasDirectDecl: boolean,
634
+ * hasNestedBlock: boolean,
635
+ * isRulePrelude: boolean,
636
+ * preludeStart: number,
637
+ * preludeEnd: number,
638
+ * }[]}
639
+ */
640
+ const pureBlockStack = [];
641
+
642
+ const PURE_IGNORE_RE = /^\s*cssmodules-pure-ignore(?:\s|$)/;
643
+ const PURE_NO_CHECK_RE = /^\s*cssmodules-pure-no-check(?:\s|$)/;
644
+
645
+ /**
646
+ * @returns {(typeof pureBlockStack)[number] | undefined} top of stack
647
+ */
648
+ const pureTop = () => pureBlockStack[pureBlockStack.length - 1];
649
+
650
+ /**
651
+ * Was the parent rule pure overall (its own selectors pure or any
652
+ * ancestor pure)? Used both for ancestor-inheritance and `&`-resolution.
653
+ * @returns {boolean} true if any ancestor (self inclusive) provided a local
654
+ */
655
+ const parentEffectivePure = () => {
656
+ const top = pureTop();
657
+ return top ? top.ancestorHadLocal : false;
658
+ };
659
+
660
+ /**
661
+ * Marks the just-finished comma-separated selector (or whole prelude
662
+ * at `{`) as impure if it lacks a local and no ancestor compensates.
663
+ */
664
+ const finalizeSelector = () => {
665
+ if (!currentSelectorHasLocal && !parentEffectivePure()) {
666
+ currentRuleHasImpureSelector = true;
667
+ }
668
+ currentSelectorHasLocal = false;
669
+ };
670
+
671
+ /**
672
+ * Reports a pure-mode violation covering the entire rule prelude.
673
+ * @param {number} start prelude start offset
674
+ * @param {number} end prelude end offset (`{` position)
675
+ */
676
+ const reportPureRule = (start, end) => {
677
+ const slice = source.slice(start, end);
678
+ const lead = /** @type {RegExpExecArray} */ (
679
+ /^(?:\s|\/\*[\s\S]*?\*\/)*/.exec(slice)
680
+ )[0].length;
681
+ const trail = /** @type {RegExpExecArray} */ (/\s*$/.exec(slice))[0]
682
+ .length;
683
+ const from = start + lead;
684
+ const to = end - trail;
685
+ if (to <= from) return;
686
+ this._emitError(
687
+ state,
688
+ `Selector "${source.slice(from, to)}" is not pure (pure selectors must contain at least one local class or id)`,
689
+ locConverter,
690
+ from,
691
+ to
692
+ );
693
+ };
694
+
695
+ /** @typedef {{ value?: string, importName?: string, localName?: string, request?: string }} IcssDefinition */
711
696
  /** @type {Map<string, IcssDefinition>} */
712
697
  const icssDefinitions = new Map();
713
698
 
699
+ // Tracks `composes: <name> from "<file>"` declarations to enforce a
700
+ // predictable file load order across rules (port of
701
+ // postcss-modules-extract-imports#138). Each rule's composes order
702
+ // is a partial ordering: if `.x` composes `b from "./b"` before
703
+ // `c from "./c"`, then `b.css` must load before `c.css` so `c` can
704
+ // override `b` in the cascade. Edges are added inline as the rule
705
+ // is parsed; at end-of-parse the first composes-import dep of each
706
+ // file is tagged with `sourceOrder` according to a topological
707
+ // sort (`NormalModule#build` reorders by `sourceOrder` for us).
708
+ /** @type {Map<string, Set<string>>} */
709
+ const composesGraph = new Map();
710
+ /** @type {Map<string, CssIcssImportDependency>} */
711
+ const composesFirstFileImport = new Map();
712
+ /** @type {string | undefined} */
713
+ let currentRulePrevComposesFile;
714
+ /** @type {Set<string>} */
715
+ const currentRuleComposesFiles = new Set();
716
+
714
717
  /**
715
718
  * Checks whether this css parser is next nested syntax.
716
719
  * @param {string} input input
@@ -762,9 +765,11 @@ class CssParser extends Parser {
762
765
  const { line: sl, column: sc } = locConverter.get(start);
763
766
  const { line: el, column: ec } = locConverter.get(end);
764
767
 
768
+ const value = input.slice(start + 2, end - 2);
769
+
765
770
  /** @type {Comment} */
766
771
  const comment = {
767
- value: input.slice(start + 2, end - 2),
772
+ value,
768
773
  range: [start, end],
769
774
  loc: {
770
775
  start: { line: sl, column: sc },
@@ -772,6 +777,19 @@ class CssParser extends Parser {
772
777
  }
773
778
  };
774
779
  this.comments.push(comment);
780
+
781
+ if (pureMode) {
782
+ if (PURE_IGNORE_RE.test(value)) {
783
+ pureIgnorePending = true;
784
+ } else if (
785
+ PURE_NO_CHECK_RE.test(value) &&
786
+ scope === CSS_MODE_TOP_LEVEL &&
787
+ !seenTopLevelRule
788
+ ) {
789
+ pureNoCheck = true;
790
+ }
791
+ }
792
+
775
793
  return end;
776
794
  };
777
795
 
@@ -790,7 +808,7 @@ class CssParser extends Parser {
790
808
  });
791
809
  if (!tokens[3]) return end;
792
810
  const semi = tokens[3][1];
793
- if (!tokens[0]) {
811
+ if (!tokens[0] || (tokens[0][4] && !isModules)) {
794
812
  this._emitWarning(
795
813
  state,
796
814
  `Expected URL in '${input.slice(start, semi)}'`,
@@ -802,7 +820,48 @@ class CssParser extends Parser {
802
820
  }
803
821
 
804
822
  const urlToken = tokens[0];
805
- const url = normalizeUrl(input.slice(urlToken[2], urlToken[3]), true);
823
+ /** @type {string} */
824
+ let url;
825
+ if (urlToken[4]) {
826
+ // URL given as identifier — resolve via CSS Modules @value.
827
+ const name = input.slice(urlToken[2], urlToken[3]);
828
+ const def = icssDefinitions.get(name);
829
+ if (!def) {
830
+ this._emitWarning(
831
+ state,
832
+ `Unknown '@value' identifier '${name}' in '${input.slice(start, semi)}'`,
833
+ locConverter,
834
+ start,
835
+ semi
836
+ );
837
+ // Consume the whole at-rule so the unresolved identifier
838
+ // doesn't get re-tokenized and accidentally substituted
839
+ // into a malformed `@import` in the output.
840
+ const dep = new ConstDependency("", [start, semi]);
841
+ module.addPresentationalDependency(dep);
842
+ return semi;
843
+ }
844
+ if (def.value === undefined) {
845
+ this._emitWarning(
846
+ state,
847
+ `'@value' identifier '${name}' was imported from another module and cannot be used as the URL of '@import' — only locally defined values are supported here`,
848
+ locConverter,
849
+ start,
850
+ semi
851
+ );
852
+ const dep = new ConstDependency("", [start, semi]);
853
+ module.addPresentationalDependency(dep);
854
+ return semi;
855
+ }
856
+ const raw = def.value.trim();
857
+ url =
858
+ (raw.startsWith('"') && raw.endsWith('"')) ||
859
+ (raw.startsWith("'") && raw.endsWith("'"))
860
+ ? normalizeUrl(raw.slice(1, -1), true)
861
+ : normalizeUrl(raw, false);
862
+ } else {
863
+ url = normalizeUrl(input.slice(urlToken[2], urlToken[3]), true);
864
+ }
806
865
  const newline = walkCssTokens.eatWhiteLine(input, semi);
807
866
  const { options, errors: commentErrors } = this.parseCommentOptions([
808
867
  end,
@@ -886,6 +945,17 @@ class CssParser extends Parser {
886
945
  );
887
946
  dep.setLoc(sl, sc, el, ec);
888
947
  module.addDependency(dep);
948
+ // `text` and `css-style-sheet` parents inline the imported
949
+ // module's rendered CSS at build time, which means we read the
950
+ // imported module's `codeGenerationResults` (and through it the
951
+ // results of any assets the import references). Registering this
952
+ // as a code-generation dependency tells the compilation scheduler
953
+ // to generate the imported subtree before us.
954
+ const exportType = /** @type {import("./CssModule")} */ (module)
955
+ .exportType;
956
+ if (exportType === "text" || exportType === "css-style-sheet") {
957
+ module.addCodeGenerationDependency(dep);
958
+ }
889
959
 
890
960
  return newline;
891
961
  };
@@ -1004,9 +1074,32 @@ class CssParser extends Parser {
1004
1074
  return end;
1005
1075
  }
1006
1076
  }
1007
- const value = normalizeUrl(input.slice(contentStart, contentEnd), false);
1077
+ let value = normalizeUrl(input.slice(contentStart, contentEnd), false);
1008
1078
  // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
1009
1079
  if (value.length === 0) return end;
1080
+ if (isModules) {
1081
+ const def = icssDefinitions.get(value);
1082
+ if (def) {
1083
+ if (def.value !== undefined) {
1084
+ const raw = def.value.trim();
1085
+ value =
1086
+ (raw.startsWith('"') && raw.endsWith('"')) ||
1087
+ (raw.startsWith("'") && raw.endsWith("'"))
1088
+ ? normalizeUrl(raw.slice(1, -1), true)
1089
+ : normalizeUrl(raw, false);
1090
+ if (value.length === 0) return end;
1091
+ } else {
1092
+ this._emitWarning(
1093
+ state,
1094
+ `'@value' identifier '${value}' was imported from another module and cannot be used inside 'url()' — only locally defined values are supported here`,
1095
+ locConverter,
1096
+ start,
1097
+ end
1098
+ );
1099
+ return end;
1100
+ }
1101
+ }
1102
+ }
1010
1103
  const dep = new CssUrlDependency(value, [start, end], "url");
1011
1104
  const { line: sl, column: sc } = locConverter.get(start);
1012
1105
  const { line: el, column: ec } = locConverter.get(end);
@@ -1086,7 +1179,7 @@ class CssParser extends Parser {
1086
1179
  * @param {string} value value to resolve
1087
1180
  * @param {string=} localName override local name
1088
1181
  * @param {boolean=} isCustomProperty true when it is custom property, otherwise false
1089
- * @returns {string | [string, string]} resolved reexport (`localName` and `importName`)
1182
+ * @returns {string | [string, string] | [string, string, string]} resolved reexport (`localName`, `importName` and optional `request` of the active `@value` import)
1090
1183
  */
1091
1184
  const getReexport = (value, localName, isCustomProperty) => {
1092
1185
  const reexport = icssDefinitions.get(
@@ -1095,10 +1188,11 @@ class CssParser extends Parser {
1095
1188
 
1096
1189
  if (reexport) {
1097
1190
  if (reexport.importName) {
1098
- return [
1099
- reexport.localName || (isCustomProperty ? `--${value}` : value),
1100
- reexport.importName
1101
- ];
1191
+ const resolvedLocalName =
1192
+ reexport.localName || (isCustomProperty ? `--${value}` : value);
1193
+ return reexport.request
1194
+ ? [resolvedLocalName, reexport.importName, reexport.request]
1195
+ : [resolvedLocalName, reexport.importName];
1102
1196
  }
1103
1197
 
1104
1198
  if (isCustomProperty) {
@@ -1192,7 +1286,10 @@ class CssParser extends Parser {
1192
1286
  dep.setLoc(sl, sc, el, ec);
1193
1287
  module.addDependency(dep);
1194
1288
 
1195
- icssDefinitions.set(name, { importName: value });
1289
+ icssDefinitions.set(name, {
1290
+ importName: value,
1291
+ request: /** @type {string} */ (request)
1292
+ });
1196
1293
  } else if (type === 1) {
1197
1294
  const dep = new CssIcssExportDependency(name, getReexport(value));
1198
1295
  const { line: sl, column: sc } = locConverter.get(start);
@@ -1409,7 +1506,7 @@ class CssParser extends Parser {
1409
1506
  dep.setLoc(sl, sc, el, ec);
1410
1507
  module.addDependency(dep);
1411
1508
 
1412
- icssDefinitions.set(localName, { importName });
1509
+ icssDefinitions.set(localName, { importName, request: from });
1413
1510
  }
1414
1511
 
1415
1512
  {
@@ -1491,7 +1588,8 @@ class CssParser extends Parser {
1491
1588
  def.localName || name,
1492
1589
  [start, end],
1493
1590
  def.value,
1494
- def.importName
1591
+ def.importName,
1592
+ def.request
1495
1593
  );
1496
1594
  dep.setLoc(sl, sc, el, ec);
1497
1595
  module.addDependency(dep);
@@ -1520,7 +1618,9 @@ class CssParser extends Parser {
1520
1618
  {
1521
1619
  identifier(input, start, end) {
1522
1620
  if (type === 1) {
1523
- let identifier = unescapeIdentifier(input.slice(start, end));
1621
+ let identifier = unescapeIdentifierCached(
1622
+ input.slice(start, end)
1623
+ );
1524
1624
  const { line: sl, column: sc } = locConverter.get(start);
1525
1625
  const { line: el, column: ec } = locConverter.get(end);
1526
1626
  const isDashedIdent = isDashedIdentifier(identifier);
@@ -1576,7 +1676,9 @@ class CssParser extends Parser {
1576
1676
  {
1577
1677
  string(_input, start, end) {
1578
1678
  if (!found && options.string) {
1579
- const value = unescapeIdentifier(input.slice(start + 1, end - 1));
1679
+ const value = unescapeIdentifierCached(
1680
+ input.slice(start + 1, end - 1)
1681
+ );
1580
1682
  const { line: sl, column: sc } = locConverter.get(start);
1581
1683
  const { line: el, column: ec } = locConverter.get(end);
1582
1684
  const dep = new CssIcssExportDependency(
@@ -1589,6 +1691,7 @@ class CssParser extends Parser {
1589
1691
  dep.setLoc(sl, sc, el, ec);
1590
1692
  module.addDependency(dep);
1591
1693
  found = true;
1694
+ if (pureMode) currentSelectorHasLocal = true;
1592
1695
  }
1593
1696
  return end;
1594
1697
  },
@@ -1597,7 +1700,7 @@ class CssParser extends Parser {
1597
1700
  const value = input.slice(start, end);
1598
1701
 
1599
1702
  if (options.identifier) {
1600
- const identifier = unescapeIdentifier(value);
1703
+ const identifier = unescapeIdentifierCached(value);
1601
1704
 
1602
1705
  if (
1603
1706
  options.identifier instanceof RegExp &&
@@ -1620,6 +1723,7 @@ class CssParser extends Parser {
1620
1723
  dep.setLoc(sl, sc, el, ec);
1621
1724
  module.addDependency(dep);
1622
1725
  found = true;
1726
+ if (pureMode) currentSelectorHasLocal = true;
1623
1727
  }
1624
1728
  }
1625
1729
  return end;
@@ -1638,6 +1742,7 @@ class CssParser extends Parser {
1638
1742
 
1639
1743
  if (!found && type) {
1640
1744
  found = true;
1745
+ if (type === 1 && pureMode) currentSelectorHasLocal = true;
1641
1746
  return processLocalOrGlobalFunction(input, type, start, end);
1642
1747
  }
1643
1748
 
@@ -1665,7 +1770,7 @@ class CssParser extends Parser {
1665
1770
  const processDashedIdent = (input, start, end) => {
1666
1771
  const customIdent = walkCssTokens.eatIdentSequence(input, start);
1667
1772
  if (!customIdent) return end;
1668
- const identifier = unescapeIdentifier(
1773
+ const identifier = unescapeIdentifierCached(
1669
1774
  input.slice(customIdent[0] + 2, customIdent[1])
1670
1775
  );
1671
1776
  const afterCustomIdent = walkCssTokens.eatWhitespaceAndComments(
@@ -1842,7 +1947,7 @@ class CssParser extends Parser {
1842
1947
  propertyName === "grid-template" ||
1843
1948
  propertyName === "grid-template-areas"
1844
1949
  ) {
1845
- const areas = unescapeIdentifier(
1950
+ const areas = unescapeIdentifierCached(
1846
1951
  input.slice(start + 1, end - 1)
1847
1952
  );
1848
1953
  const matches = matchAll(/\b\w+\b/g, areas);
@@ -1936,7 +2041,7 @@ class CssParser extends Parser {
1936
2041
  const { line: sl, column: sc } = locConverter.get(value[0]);
1937
2042
  const { line: el, column: ec } = locConverter.get(value[1]);
1938
2043
  const [start, end, isString] = value;
1939
- const name = unescapeIdentifier(
2044
+ const name = unescapeIdentifierCached(
1940
2045
  isString
1941
2046
  ? input.slice(start + 1, end - 1)
1942
2047
  : input.slice(start, end)
@@ -1976,7 +2081,7 @@ class CssParser extends Parser {
1976
2081
  const lastLocalIdentifier = lastLocalIdentifiers[0];
1977
2082
  let end = pos;
1978
2083
 
1979
- /** @type {Set<[number, number]>} */
2084
+ /** @type {Set<[number, number, boolean]>} */
1980
2085
  const classNames = new Set();
1981
2086
 
1982
2087
  while (true) {
@@ -2022,18 +2127,20 @@ class CssParser extends Parser {
2022
2127
 
2023
2128
  if (isComma || isSemicolon || isRightCurly) {
2024
2129
  if (className) {
2025
- classNames.add(className);
2130
+ classNames.add([className[0], className[1], isGlobalFunction]);
2026
2131
  }
2027
2132
 
2028
- for (const className of classNames) {
2029
- const [start, end] = className;
2030
- const identifier = unescapeIdentifier(input.slice(start, end));
2133
+ for (const entry of classNames) {
2134
+ const [start, end, isGlobal] = entry;
2135
+ const identifier = unescapeIdentifierCached(
2136
+ input.slice(start, end)
2137
+ );
2031
2138
  const dep = new CssIcssExportDependency(
2032
2139
  lastLocalIdentifier,
2033
2140
  getReexport(identifier),
2034
2141
  [start, end],
2035
- !isGlobalFunction,
2036
- isGlobalFunction
2142
+ !isGlobal,
2143
+ isGlobal
2037
2144
  ? CssIcssExportDependency.EXPORT_MODE.APPEND
2038
2145
  : CssIcssExportDependency.EXPORT_MODE.SELF_REFERENCE,
2039
2146
  CssIcssExportDependency.EXPORT_TYPE.COMPOSES
@@ -2063,12 +2170,53 @@ class CssParser extends Parser {
2063
2170
 
2064
2171
  if (from) {
2065
2172
  const request = input.slice(from[0] + 1, from[1] - 1);
2173
+ const selfReference = isSelfReferenceRequest(request);
2066
2174
 
2067
- for (const className of classNames) {
2068
- const [start, end] = className;
2069
- const identifier = unescapeIdentifier(input.slice(start, end));
2175
+ if (!selfReference && !currentRuleComposesFiles.has(request)) {
2176
+ currentRuleComposesFiles.add(request);
2177
+ if (
2178
+ currentRulePrevComposesFile !== undefined &&
2179
+ currentRulePrevComposesFile !== request
2180
+ ) {
2181
+ let successors = composesGraph.get(
2182
+ currentRulePrevComposesFile
2183
+ );
2184
+ if (!successors) {
2185
+ successors = new Set();
2186
+ composesGraph.set(currentRulePrevComposesFile, successors);
2187
+ }
2188
+ successors.add(request);
2189
+ }
2190
+ currentRulePrevComposesFile = request;
2191
+ }
2192
+
2193
+ for (const entry of classNames) {
2194
+ const [start, end] = entry;
2195
+ const identifier = unescapeIdentifierCached(
2196
+ input.slice(start, end)
2197
+ );
2070
2198
  const { line: sl, column: sc } = locConverter.get(start);
2071
2199
  const { line: el, column: ec } = locConverter.get(end);
2200
+
2201
+ if (selfReference) {
2202
+ // `composes: foo from "./self.module.css"` from inside
2203
+ // `self.module.css` — collapse to a self-reference, like
2204
+ // `composes: foo` without `from`. When the composed name
2205
+ // equals the local class name, it's a true no-op.
2206
+ if (identifier === lastLocalIdentifier) continue;
2207
+ const dep = new CssIcssExportDependency(
2208
+ lastLocalIdentifier,
2209
+ getReexport(identifier),
2210
+ [start, end],
2211
+ true,
2212
+ CssIcssExportDependency.EXPORT_MODE.SELF_REFERENCE,
2213
+ CssIcssExportDependency.EXPORT_TYPE.COMPOSES
2214
+ );
2215
+ dep.setLoc(sl, sc, el, ec);
2216
+ module.addDependency(dep);
2217
+ continue;
2218
+ }
2219
+
2072
2220
  const localName = `__ICSS_IMPORT_${counter++}__`;
2073
2221
 
2074
2222
  {
@@ -2082,6 +2230,9 @@ class CssParser extends Parser {
2082
2230
  );
2083
2231
  dep.setLoc(sl, sc, el, ec);
2084
2232
  module.addDependency(dep);
2233
+ if (!composesFirstFileImport.has(request)) {
2234
+ composesFirstFileImport.set(request, dep);
2235
+ }
2085
2236
  }
2086
2237
 
2087
2238
  {
@@ -2104,9 +2255,9 @@ class CssParser extends Parser {
2104
2255
  from = walkCssTokens.eatIdentSequence(input, pos);
2105
2256
 
2106
2257
  if (from && input.slice(from[0], from[1]) === "global") {
2107
- for (const className of classNames) {
2108
- const [start, end] = className;
2109
- const identifier = unescapeIdentifier(
2258
+ for (const entry of classNames) {
2259
+ const [start, end] = entry;
2260
+ const identifier = unescapeIdentifierCached(
2110
2261
  input.slice(start, end)
2111
2262
  );
2112
2263
  const dep = new CssIcssExportDependency(
@@ -2139,7 +2290,7 @@ class CssParser extends Parser {
2139
2290
  }
2140
2291
  }
2141
2292
  } else if (className) {
2142
- classNames.add(className);
2293
+ classNames.add([className[0], className[1], isGlobalFunction]);
2143
2294
  } else {
2144
2295
  const end = eatUntilSemi(input, pos);
2145
2296
  this._emitWarning(
@@ -2170,7 +2321,7 @@ class CssParser extends Parser {
2170
2321
  */
2171
2322
  const processIdSelector = (input, start, end) => {
2172
2323
  const valueStart = start + 1;
2173
- const name = unescapeIdentifier(input.slice(valueStart, end));
2324
+ const name = unescapeIdentifierCached(input.slice(valueStart, end));
2174
2325
  const dep = new CssIcssExportDependency(
2175
2326
  name,
2176
2327
  getReexport(name),
@@ -2182,6 +2333,7 @@ class CssParser extends Parser {
2182
2333
  const { line: el, column: ec } = locConverter.get(end);
2183
2334
  dep.setLoc(sl, sc, el, ec);
2184
2335
  module.addDependency(dep);
2336
+ if (pureMode) currentSelectorHasLocal = true;
2185
2337
  return end;
2186
2338
  };
2187
2339
 
@@ -2195,7 +2347,7 @@ class CssParser extends Parser {
2195
2347
  const processClassSelector = (input, start, end) => {
2196
2348
  const ident = walkCssTokens.skipCommentsAndEatIdentSequence(input, end);
2197
2349
  if (!ident) return end;
2198
- const name = unescapeIdentifier(input.slice(ident[0], ident[1]));
2350
+ const name = unescapeIdentifierCached(input.slice(ident[0], ident[1]));
2199
2351
  lastLocalIdentifiers.push(name);
2200
2352
  const dep = new CssIcssExportDependency(
2201
2353
  name,
@@ -2208,6 +2360,7 @@ class CssParser extends Parser {
2208
2360
  const { line: el, column: ec } = locConverter.get(ident[1]);
2209
2361
  dep.setLoc(sl, sc, el, ec);
2210
2362
  module.addDependency(dep);
2363
+ if (pureMode) currentSelectorHasLocal = true;
2211
2364
  return ident[1];
2212
2365
  };
2213
2366
 
@@ -2222,7 +2375,7 @@ class CssParser extends Parser {
2222
2375
  end = walkCssTokens.eatWhitespaceAndComments(input, end)[0];
2223
2376
  const identifier = walkCssTokens.eatIdentSequence(input, end);
2224
2377
  if (!identifier) return end;
2225
- const name = unescapeIdentifier(
2378
+ const name = unescapeIdentifierCached(
2226
2379
  input.slice(identifier[0], identifier[1])
2227
2380
  );
2228
2381
  if (name.toLowerCase() !== "class") {
@@ -2258,7 +2411,7 @@ class CssParser extends Parser {
2258
2411
 
2259
2412
  const classNameStart = value[2] ? value[0] : value[0] + 1;
2260
2413
  const classNameEnd = value[2] ? value[1] : value[1] - 1;
2261
- const className = unescapeIdentifier(
2414
+ const className = unescapeIdentifierCached(
2262
2415
  input.slice(classNameStart, classNameEnd)
2263
2416
  );
2264
2417
  const dep = new CssIcssExportDependency(
@@ -2278,44 +2431,97 @@ class CssParser extends Parser {
2278
2431
  walkCssTokens(source, 0, {
2279
2432
  comment,
2280
2433
  leftCurlyBracket: (input, start, end) => {
2281
- switch (scope) {
2282
- case CSS_MODE_TOP_LEVEL: {
2283
- allowImportAtRule = false;
2284
- scope = CSS_MODE_IN_BLOCK;
2285
-
2286
- if (isModules) {
2287
- blockNestingLevel = 1;
2288
- isNextRulePrelude = isNextNestedSyntax(input, end);
2289
- }
2290
-
2291
- break;
2292
- }
2293
- case CSS_MODE_IN_BLOCK: {
2294
- if (isModules) {
2295
- blockNestingLevel++;
2296
- isNextRulePrelude = isNextNestedSyntax(input, end);
2297
- }
2298
- break;
2299
- }
2434
+ const wasTopLevel = scope === CSS_MODE_TOP_LEVEL;
2435
+ if (wasTopLevel) {
2436
+ allowImportAtRule = false;
2437
+ scope = CSS_MODE_IN_BLOCK;
2438
+ } else if (scope !== CSS_MODE_IN_BLOCK) {
2439
+ return end;
2440
+ }
2441
+ if (!isModules) return end;
2442
+ if (pureMode) {
2443
+ inAtRulePrelude = false;
2444
+ if (wasTopLevel) seenTopLevelRule = true;
2445
+ const isRulePrelude = isNextRulePrelude;
2446
+ if (isRulePrelude) finalizeSelector();
2447
+ const top = pureTop();
2448
+ if (top) top.hasNestedBlock = true;
2449
+ const inheritedSkip = top ? top.skipChildren : false;
2450
+ pureBlockStack.push({
2451
+ ignored: pureIgnorePending,
2452
+ skipOwn: inheritedSkip,
2453
+ skipChildren: nextBlockChildrenSkip || inheritedSkip,
2454
+ treatAsLeaf: nextBlockTreatAsLeaf,
2455
+ // "this rule is fully pure" (no impure comma-segment) OR any
2456
+ // ancestor pure. Matches PCSL's `[isPureSelectorSymbol]`.
2457
+ ancestorHadLocal:
2458
+ parentEffectivePure() ||
2459
+ (isRulePrelude && !currentRuleHasImpureSelector),
2460
+ impure: isRulePrelude && currentRuleHasImpureSelector,
2461
+ hasDirectDecl: false,
2462
+ hasNestedBlock: false,
2463
+ isRulePrelude,
2464
+ preludeStart: currentRulePreludeStart,
2465
+ preludeEnd: start
2466
+ });
2467
+ pureIgnorePending = false;
2468
+ nextBlockChildrenSkip = false;
2469
+ nextBlockTreatAsLeaf = false;
2470
+ currentRuleHasImpureSelector = false;
2471
+ currentSelectorHasLocal = false;
2472
+ currentRulePreludeStart = end;
2300
2473
  }
2474
+ blockNestingLevel = wasTopLevel ? 1 : blockNestingLevel + 1;
2475
+ isNextRulePrelude = isNextNestedSyntax(input, end);
2301
2476
  return end;
2302
2477
  },
2303
2478
  rightCurlyBracket: (input, start, end) => {
2304
- switch (scope) {
2305
- case CSS_MODE_IN_BLOCK: {
2306
- if (--blockNestingLevel === 0) {
2307
- scope = CSS_MODE_TOP_LEVEL;
2308
-
2309
- if (isModules) {
2310
- isNextRulePrelude = true;
2311
- modeData = undefined;
2312
- lastLocalIdentifiers = [];
2313
- }
2314
- } else if (isModules) {
2315
- isNextRulePrelude = isNextNestedSyntax(input, end);
2479
+ if (scope !== CSS_MODE_IN_BLOCK) return end;
2480
+ const closing = blockNestingLevel === 1;
2481
+ if (closing) {
2482
+ scope = CSS_MODE_TOP_LEVEL;
2483
+ blockNestingLevel = 0;
2484
+ if (!isModules) return end;
2485
+ isNextRulePrelude = true;
2486
+ modeData = undefined;
2487
+ lastLocalIdentifiers = [];
2488
+ currentRulePrevComposesFile = undefined;
2489
+ currentRuleComposesFiles.clear();
2490
+ } else {
2491
+ blockNestingLevel--;
2492
+ if (!isModules) return end;
2493
+ isNextRulePrelude = isNextNestedSyntax(input, end);
2494
+ }
2495
+ if (pureMode) {
2496
+ const frame = pureBlockStack.pop();
2497
+ if (frame) {
2498
+ // PCSL throws on impure rules whose body has any non-rule
2499
+ // content (declaration, empty body). Rules whose body is
2500
+ // only nested rules are skipped — child rules carry the
2501
+ // check themselves.
2502
+ if (
2503
+ !pureNoCheck &&
2504
+ !frame.ignored &&
2505
+ !frame.skipOwn &&
2506
+ frame.isRulePrelude &&
2507
+ frame.impure &&
2508
+ (frame.hasDirectDecl ||
2509
+ !frame.hasNestedBlock ||
2510
+ frame.treatAsLeaf)
2511
+ ) {
2512
+ reportPureRule(frame.preludeStart, frame.preludeEnd);
2513
+ }
2514
+ // Propagate "has direct declaration" through at-rule frames
2515
+ // so a parent rule containing only e.g. `@media { decl }` is
2516
+ // still treated as "rule with declarations".
2517
+ if (!frame.isRulePrelude && frame.hasDirectDecl) {
2518
+ const parent = pureTop();
2519
+ if (parent) parent.hasDirectDecl = true;
2316
2520
  }
2317
- break;
2318
2521
  }
2522
+ currentRuleHasImpureSelector = false;
2523
+ currentSelectorHasLocal = false;
2524
+ currentRulePreludeStart = end;
2319
2525
  }
2320
2526
  return end;
2321
2527
  },
@@ -2334,7 +2540,16 @@ class CssParser extends Parser {
2334
2540
  },
2335
2541
  atKeyword: (input, start, end) => {
2336
2542
  const name = input.slice(start, end).toLowerCase();
2543
+ const wasTopLevel = scope === CSS_MODE_TOP_LEVEL;
2544
+ if (pureMode) {
2545
+ inAtRulePrelude = true;
2546
+ // Match PCSL's `isPureCheckDisabled`: any non-comment top-level
2547
+ // node (including `;`-terminated at-rules like `@import`) seals
2548
+ // the leading-comments window.
2549
+ if (wasTopLevel) seenTopLevelRule = true;
2550
+ }
2337
2551
 
2552
+ let pos = end;
2338
2553
  switch (name) {
2339
2554
  case "@namespace": {
2340
2555
  this._emitWarning(
@@ -2345,13 +2560,15 @@ class CssParser extends Parser {
2345
2560
  end
2346
2561
  );
2347
2562
 
2348
- return eatUntilSemi(input, start);
2563
+ pos = eatUntilSemi(input, start);
2564
+ break;
2349
2565
  }
2350
2566
  case "@charset": {
2351
2567
  const atRuleEnd = eatUntilSemi(input, start);
2352
2568
 
2353
2569
  if (/** @type {CssModule} */ (module).exportType === "style") {
2354
- return atRuleEnd;
2570
+ pos = atRuleEnd;
2571
+ break;
2355
2572
  }
2356
2573
 
2357
2574
  const dep = new ConstDependency("", [start, atRuleEnd + 1]);
@@ -2360,7 +2577,8 @@ class CssParser extends Parser {
2360
2577
  const value = walkCssTokens.eatString(input, end);
2361
2578
 
2362
2579
  if (!value) {
2363
- return atRuleEnd;
2580
+ pos = atRuleEnd;
2581
+ break;
2364
2582
  }
2365
2583
 
2366
2584
  /** @type {BuildInfo} */
@@ -2368,11 +2586,13 @@ class CssParser extends Parser {
2368
2586
  .slice(value[0] + 1, value[1] - 1)
2369
2587
  .toUpperCase();
2370
2588
 
2371
- return atRuleEnd;
2589
+ pos = atRuleEnd;
2590
+ break;
2372
2591
  }
2373
2592
  case "@import": {
2374
2593
  if (!this.options.import) {
2375
- return eatUntilSemi(input, end);
2594
+ pos = eatUntilSemi(input, end);
2595
+ break;
2376
2596
  }
2377
2597
 
2378
2598
  if (!allowImportAtRule) {
@@ -2383,43 +2603,57 @@ class CssParser extends Parser {
2383
2603
  start,
2384
2604
  end
2385
2605
  );
2386
- return eatUntilSemi(input, end);
2606
+ pos = eatUntilSemi(input, end);
2607
+ break;
2387
2608
  }
2388
2609
 
2389
- return processAtImport(input, start, end);
2610
+ pos = processAtImport(input, start, end);
2611
+ break;
2390
2612
  }
2391
2613
  default: {
2392
2614
  if (isModules) {
2393
2615
  if (name === "@value") {
2394
- return processAtValue(input, start, end);
2616
+ pos = processAtValue(input, start, end);
2617
+ break;
2395
2618
  } else if (
2396
2619
  this.options.animation &&
2397
2620
  OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name) &&
2398
2621
  isLocalMode()
2399
2622
  ) {
2400
- return processLocalAtRule(input, end, {
2623
+ if (pureMode) {
2624
+ nextBlockChildrenSkip = true;
2625
+ nextBlockTreatAsLeaf = true;
2626
+ }
2627
+ pos = processLocalAtRule(input, end, {
2401
2628
  string: true,
2402
2629
  identifier: true
2403
2630
  });
2631
+ break;
2404
2632
  } else if (
2405
2633
  this.options.customIdents &&
2406
2634
  name === "@counter-style" &&
2407
2635
  isLocalMode()
2408
2636
  ) {
2409
- return processLocalAtRule(input, end, {
2637
+ if (pureMode) {
2638
+ nextBlockChildrenSkip = true;
2639
+ nextBlockTreatAsLeaf = true;
2640
+ }
2641
+ pos = processLocalAtRule(input, end, {
2410
2642
  identifier: true
2411
2643
  });
2644
+ break;
2412
2645
  } else if (
2413
2646
  this.options.container &&
2414
2647
  name === "@container" &&
2415
2648
  isLocalMode()
2416
2649
  ) {
2417
- return processLocalAtRule(input, end, {
2650
+ pos = processLocalAtRule(input, end, {
2418
2651
  identifier: /^(none|and|or|not)$/
2419
2652
  });
2653
+ break;
2420
2654
  } else if (name === "@scope") {
2421
2655
  isNextRulePrelude = true;
2422
- return end;
2656
+ break;
2423
2657
  }
2424
2658
 
2425
2659
  isNextRulePrelude = false;
@@ -2427,12 +2661,48 @@ class CssParser extends Parser {
2427
2661
  }
2428
2662
  }
2429
2663
 
2430
- return end;
2664
+ // If the at-rule consumed its own `;` (for `@import`/`@value`/
2665
+ // `@charset`/`@namespace`), advance the prelude pointer so a
2666
+ // later impure rule's reported selector doesn't include this
2667
+ // at-rule's text. Body-bearing at-rules return at `{` — let
2668
+ // `leftCurlyBracket` handle those.
2669
+ if (pureMode && wasTopLevel && pos > end) {
2670
+ let probe = pos - 1;
2671
+ while (
2672
+ probe > end &&
2673
+ walkCssTokens.isWhiteSpace(input.charCodeAt(probe))
2674
+ ) {
2675
+ probe--;
2676
+ }
2677
+ if (input.charCodeAt(probe) === CC_SEMICOLON) {
2678
+ currentRulePreludeStart = pos;
2679
+ }
2680
+ }
2681
+
2682
+ return pos;
2431
2683
  },
2432
2684
  semicolon: (input, start, end) => {
2433
2685
  if (isModules && scope === CSS_MODE_IN_BLOCK) {
2434
2686
  isNextRulePrelude = isNextNestedSyntax(input, end);
2435
2687
  }
2688
+ if (pureMode) {
2689
+ if (scope === CSS_MODE_IN_BLOCK) {
2690
+ if (
2691
+ balanced.length === 0 &&
2692
+ !isNextRulePrelude &&
2693
+ !inAtRulePrelude
2694
+ ) {
2695
+ const top = pureTop();
2696
+ if (top) top.hasDirectDecl = true;
2697
+ }
2698
+ } else if (scope === CSS_MODE_TOP_LEVEL && balanced.length === 0) {
2699
+ // Top-level `;` ends a statement (e.g. `@import "x";`).
2700
+ // Advance the prelude pointer so a later impure rule's
2701
+ // reported selector doesn't include the preceding text.
2702
+ currentRulePreludeStart = end;
2703
+ }
2704
+ inAtRulePrelude = false;
2705
+ }
2436
2706
  return end;
2437
2707
  },
2438
2708
  identifier: (input, start, end) => {
@@ -2454,6 +2724,10 @@ class CssParser extends Parser {
2454
2724
  switch (scope) {
2455
2725
  case CSS_MODE_IN_BLOCK: {
2456
2726
  if (isModules && !isNextRulePrelude) {
2727
+ if (balanced.length === 0 && !inAtRulePrelude) {
2728
+ const top = pureTop();
2729
+ if (top) top.hasDirectDecl = true;
2730
+ }
2457
2731
  // Handle only top level values and not inside functions
2458
2732
  return processLocalDeclaration(input, start, end);
2459
2733
  }
@@ -2465,12 +2739,17 @@ class CssParser extends Parser {
2465
2739
  return end;
2466
2740
  },
2467
2741
  delim: (input, start, end) => {
2742
+ const ch = input.charCodeAt(start);
2743
+ if (ch === CC_FULL_STOP && isNextRulePrelude && isLocalMode()) {
2744
+ return processClassSelector(input, start, end);
2745
+ }
2468
2746
  if (
2469
- input.charCodeAt(start) === CC_FULL_STOP &&
2747
+ ch === CC_AMPERSAND &&
2470
2748
  isNextRulePrelude &&
2471
- isLocalMode()
2749
+ parentEffectivePure() &&
2750
+ pureMode
2472
2751
  ) {
2473
- return processClassSelector(input, start, end);
2752
+ currentSelectorHasLocal = true;
2474
2753
  }
2475
2754
 
2476
2755
  return end;
@@ -2677,6 +2956,7 @@ class CssParser extends Parser {
2677
2956
  if (isModules && balanced.length === 0) {
2678
2957
  // Reset stack for `:global .class :local .class-other` selector after
2679
2958
  modeData = undefined;
2959
+ if (pureMode && isNextRulePrelude) finalizeSelector();
2680
2960
  }
2681
2961
 
2682
2962
  lastTokenEndForComments = start;
@@ -2688,6 +2968,23 @@ class CssParser extends Parser {
2688
2968
  /** @type {BuildInfo} */
2689
2969
  (module.buildInfo).strict = true;
2690
2970
 
2971
+ // Topologically sort the files referenced by `composes ... from`
2972
+ // declarations and tag each file's first import dep with the
2973
+ // resulting `sourceOrder`. `NormalModule#build` then reorders the
2974
+ // deps via `sortWithSourceOrder` so the bundle loads them in
2975
+ // cascade-correct order. Files stuck in a cycle are not visited
2976
+ // and keep their natural loc-based position.
2977
+ if (composesFirstFileImport.size > 1) {
2978
+ topologicalSort(
2979
+ composesGraph,
2980
+ [...composesFirstFileImport.keys()],
2981
+ (file, i) => {
2982
+ /** @type {CssIcssImportDependency} */
2983
+ (composesFirstFileImport.get(file)).sourceOrder = i;
2984
+ }
2985
+ );
2986
+ }
2987
+
2691
2988
  const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta);
2692
2989
 
2693
2990
  buildMeta.exportsType = this.options.namedExports ? "namespace" : "default";