webpack 5.110.2 → 5.111.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 (226) hide show
  1. package/README.md +5 -2
  2. package/lib/APIPlugin.js +23 -7
  3. package/lib/AutomaticPrefetchPlugin.js +1 -1
  4. package/lib/Cache.js +8 -3
  5. package/lib/CacheFacade.js +1 -1
  6. package/lib/ChunkGraph.js +20 -18
  7. package/lib/CircularModulesPlugin.js +157 -55
  8. package/lib/CleanPlugin.js +2 -2
  9. package/lib/Compilation.js +321 -303
  10. package/lib/Compiler.js +209 -46
  11. package/lib/ConcatenationScope.js +25 -35
  12. package/lib/ContextModule.js +17 -81
  13. package/lib/ContextModuleFactory.js +1 -1
  14. package/lib/CopyPlugin.js +1054 -0
  15. package/lib/DefinePlugin.js +24 -8
  16. package/lib/DotenvPlugin.js +41 -13
  17. package/lib/ExternalModule.js +41 -14
  18. package/lib/FileSystemInfo.js +17 -14
  19. package/lib/FlagDependencyExportsPlugin.js +45 -21
  20. package/lib/FlagDependencyUsagePlugin.js +8 -2
  21. package/lib/HotModuleReplacementPlugin.js +53 -7
  22. package/lib/JavascriptMetaInfoPlugin.js +5 -3
  23. package/lib/MainTemplate.js +5 -1
  24. package/lib/Module.js +16 -7
  25. package/lib/MultiCompiler.js +1 -1
  26. package/lib/MultiWatching.js +1 -1
  27. package/lib/NormalModule.js +58 -32
  28. package/lib/NormalModuleFactory.js +1 -1
  29. package/lib/RuntimeGlobals.js +21 -7
  30. package/lib/RuntimeModule.js +28 -12
  31. package/lib/RuntimePlugin.js +33 -3
  32. package/lib/RuntimeTemplate.js +794 -477
  33. package/lib/SourceMapDevToolPlugin.js +1 -1
  34. package/lib/Watching.js +9 -0
  35. package/lib/WebpackOptionsApply.js +84 -91
  36. package/lib/asset/AssetGenerator.js +25 -8
  37. package/lib/asset/AssetModule.js +3 -0
  38. package/lib/asset/WebManifestParser.js +7 -5
  39. package/lib/async-modules/AsyncModuleHelpers.js +10 -4
  40. package/lib/cache/AddBuildDependenciesPlugin.js +33 -4
  41. package/lib/cache/IdleFileCachePlugin.js +2 -2
  42. package/lib/cache/PackFileCacheStrategy.js +14 -1
  43. package/lib/config/browserslistTargetHandler.js +19 -0
  44. package/lib/config/defaults.js +110 -64
  45. package/lib/config/normalization.js +100 -20
  46. package/lib/config/target.js +10 -0
  47. package/lib/container/ContainerReferencePlugin.js +6 -0
  48. package/lib/container/RemoteRuntimeModule.js +9 -0
  49. package/lib/container/declaredRemotes.js +39 -0
  50. package/lib/css/CssGenerator.js +8 -0
  51. package/lib/css/CssInjectStyleRuntimeModule.js +9 -0
  52. package/lib/css/CssLoadingRuntimeModule.js +94 -36
  53. package/lib/css/CssModule.js +1 -1
  54. package/lib/css/CssModulesPlugin.js +7 -6
  55. package/lib/css/CssParser.js +144 -11
  56. package/lib/css/CssServerStylesRuntimeModule.js +9 -0
  57. package/lib/css/cssMinify.js +11 -3
  58. package/lib/css/data.js +2814 -678
  59. package/lib/css/syntax.js +4082 -248
  60. package/lib/debug/ProfilingPlugin.js +4 -0
  61. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -3
  62. package/lib/dependencies/AMDRuntimeModules.js +18 -0
  63. package/lib/dependencies/CommonJsExportRequireDependency.js +3 -1
  64. package/lib/dependencies/CommonJsExportsParserPlugin.js +12 -13
  65. package/lib/dependencies/CommonJsImportsParserPlugin.js +1 -0
  66. package/lib/dependencies/CommonJsPlugin.js +19 -3
  67. package/lib/dependencies/CommonJsRequireDependency.js +32 -9
  68. package/lib/dependencies/ContextDependencyTemplateAsRequireCall.js +20 -7
  69. package/lib/dependencies/CssIcssExportDependency.js +7 -1
  70. package/lib/dependencies/HarmonyCompatibilityDependency.js +58 -4
  71. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +8 -3
  72. package/lib/dependencies/HarmonyExportExpressionDependency.js +44 -29
  73. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +171 -32
  74. package/lib/dependencies/HarmonyExportInitFragment.js +99 -19
  75. package/lib/dependencies/HarmonyExportSpecifierDependency.js +18 -30
  76. package/lib/dependencies/HarmonyImportBareSideEffectDependency.js +59 -0
  77. package/lib/dependencies/HarmonyImportDependency.js +99 -41
  78. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +20 -3
  79. package/lib/dependencies/HarmonyImportSpecifierDependency.js +24 -1
  80. package/lib/dependencies/HarmonyModulesPlugin.js +20 -0
  81. package/lib/dependencies/HtmlEntryDependency.js +62 -2
  82. package/lib/dependencies/ImportContextDependency.js +43 -3
  83. package/lib/dependencies/ImportDependency.js +8 -29
  84. package/lib/dependencies/ImportMetaMainDependency.js +104 -0
  85. package/lib/dependencies/ImportMetaPlugin.js +59 -15
  86. package/lib/dependencies/ImportParserPlugin.js +24 -14
  87. package/lib/dependencies/RequireHeaderDependency.js +20 -21
  88. package/lib/dependencies/SystemRuntimeModule.js +9 -0
  89. package/lib/dependencies/WorkerDependency.js +1 -2
  90. package/lib/dependencies/WorkletDependency.js +1 -2
  91. package/lib/dependencies/importOptionsCheck.js +47 -0
  92. package/lib/dll/DelegatedModule.js +18 -1
  93. package/lib/dll/DllReferencePlugin.js +11 -8
  94. package/lib/dll/LibManifestPlugin.js +1 -1
  95. package/lib/errors/AnalyzableBailoutsWarning.js +41 -0
  96. package/lib/errors/DotenvFileError.js +29 -0
  97. package/lib/errors/DuplicateModulesWarning.js +12 -7
  98. package/lib/errors/EmptyCopyPatternWarning.js +26 -0
  99. package/lib/errors/ExternalStarReexportError.js +44 -0
  100. package/lib/errors/ImportedBindingAssignmentError.js +30 -0
  101. package/lib/errors/MissingSourceMapsWarning.js +36 -0
  102. package/lib/errors/ModuleParseError.js +2 -1
  103. package/lib/errors/UnusedAssetsWarning.js +36 -0
  104. package/lib/errors/UnusedFederationWarning.js +35 -0
  105. package/lib/errors/UnusedModulesWarning.js +38 -0
  106. package/lib/esm/ExportWebpackRequireRuntimeModule.js +9 -0
  107. package/lib/esm/ModuleChunkFormatPlugin.js +28 -3
  108. package/lib/esm/ModuleChunkLoadingPlugin.js +30 -11
  109. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +92 -60
  110. package/lib/hmr/HotModuleReplacement.runtime.js +8 -2
  111. package/lib/hmr/HotModuleReplacementRuntimeModule.js +9 -0
  112. package/lib/html/HtmlGenerator.js +11 -5
  113. package/lib/html/HtmlModulesPlugin.js +360 -84
  114. package/lib/html/HtmlParser.js +13 -6
  115. package/lib/html/builtinEmbeddedRenderer.js +107 -0
  116. package/lib/html/data.js +121 -7
  117. package/lib/html/htmlMinify.js +84 -113
  118. package/lib/html/syntax.js +1067 -569
  119. package/lib/ids/HashedModuleIdsPlugin.js +2 -1
  120. package/lib/index.js +3 -0
  121. package/lib/javascript/JavascriptModule.js +1 -0
  122. package/lib/javascript/JavascriptModulesPlugin.js +52 -22
  123. package/lib/javascript/JavascriptParser.js +1255 -728
  124. package/lib/javascript/StartupHelpers.js +27 -3
  125. package/lib/javascript/data.js +172 -0
  126. package/lib/javascript/grammar.js +5546 -0
  127. package/lib/javascript/parser.js +1494 -0
  128. package/lib/javascript/regexp.js +1762 -0
  129. package/lib/javascript/regexpData.js +51 -0
  130. package/lib/javascript/syntax.js +8696 -5225
  131. package/lib/library/ModuleLibraryPlugin.js +116 -5
  132. package/lib/node/ReadFileChunkLoadingRuntimeModule.js +22 -0
  133. package/lib/node/RequireChunkLoadingRuntimeModule.js +22 -0
  134. package/lib/optimize/ConcatenatedModule.js +133 -25
  135. package/lib/optimize/ConstExportsPlugin.js +50 -39
  136. package/lib/optimize/InlineExports.js +20 -123
  137. package/lib/optimize/MangleExportsPlugin.js +10 -0
  138. package/lib/optimize/ModuleConcatenationPlugin.js +24 -1
  139. package/lib/optimize/RealContentHashPlugin.js +68 -9
  140. package/lib/optimize/SideEffectsFlagPlugin.js +17 -9
  141. package/lib/performance/AnalyzableBailoutsPlugin.js +115 -0
  142. package/lib/performance/CacheEffectivenessPlugin.js +4 -3
  143. package/lib/performance/DuplicateModulesPlugin.js +57 -13
  144. package/lib/performance/EvalUsagePlugin.js +5 -2
  145. package/lib/performance/MissingSourceMapsPlugin.js +115 -0
  146. package/lib/performance/PureAnnotationsPlugin.js +6 -3
  147. package/lib/performance/TopLevelThisPlugin.js +5 -4
  148. package/lib/performance/UnusedAssetsPlugin.js +109 -0
  149. package/lib/performance/UnusedFederationPlugin.js +147 -0
  150. package/lib/performance/UnusedModulesPlugin.js +141 -0
  151. package/lib/performance/UnusedReexportsPlugin.js +1 -55
  152. package/lib/performance/isOnlyReexported.js +65 -0
  153. package/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js +25 -5
  154. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +2 -0
  155. package/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js +9 -0
  156. package/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js +9 -0
  157. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +9 -0
  158. package/lib/prefetch/ResourceHintPlugin.js +7 -3
  159. package/lib/prefetch/ResourceHintRuntimeModule.js +9 -0
  160. package/lib/prefetch/StartupAssetHintRuntimeModule.js +9 -0
  161. package/lib/rules/RuleSetCompiler.js +211 -4
  162. package/lib/runtime/AsyncModuleRuntimeModule.js +105 -67
  163. package/lib/runtime/AutoPublicPathRuntimeModule.js +9 -0
  164. package/lib/runtime/BaseUriRuntimeModule.js +9 -0
  165. package/lib/runtime/ChunkNameRuntimeModule.js +9 -0
  166. package/lib/runtime/EnsureChunkRuntimeModule.js +35 -16
  167. package/lib/runtime/GetChunkFilenameRuntimeModule.js +9 -0
  168. package/lib/runtime/GetFullHashRuntimeModule.js +9 -0
  169. package/lib/runtime/GetMainFilenameRuntimeModule.js +9 -0
  170. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +9 -0
  171. package/lib/runtime/GlobalRuntimeModule.js +9 -0
  172. package/lib/runtime/HasOwnPropertyRuntimeModule.js +9 -0
  173. package/lib/runtime/HelperRuntimeModule.js +10 -0
  174. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +338 -217
  175. package/lib/runtime/NonceRuntimeModule.js +9 -0
  176. package/lib/runtime/OnChunksLoadedRuntimeModule.js +67 -20
  177. package/lib/runtime/PublicPathRuntimeModule.js +9 -0
  178. package/lib/runtime/RuntimeIdRuntimeModule.js +9 -0
  179. package/lib/runtime/SpecNamespaceObjectRuntimeModule.js +90 -0
  180. package/lib/runtime/StartupChunkDependenciesRuntimeModule.js +9 -0
  181. package/lib/runtime/StartupEntrypointRuntimeModule.js +9 -0
  182. package/lib/runtime/SystemContextRuntimeModule.js +9 -0
  183. package/lib/runtime/ToBinaryRuntimeModule.js +9 -0
  184. package/lib/schemes/HttpUriPlugin.js +22 -5
  185. package/lib/serialization/FileMiddleware.js +15 -2
  186. package/lib/serialization/ObjectMiddleware.js +138 -106
  187. package/lib/sharing/ConsumeSharedPlugin.js +12 -0
  188. package/lib/sharing/ConsumeSharedRuntimeModule.js +11 -0
  189. package/lib/sharing/ShareRuntimeModule.js +9 -0
  190. package/lib/sharing/declaredShared.js +47 -0
  191. package/lib/stats/DefaultStatsFactoryPlugin.js +3 -1
  192. package/lib/stats/DefaultStatsPrinterPlugin.js +3 -0
  193. package/lib/util/ArrayQueue.js +3 -3
  194. package/lib/util/SourceProcessor.js +76 -69
  195. package/lib/util/URLAbsoluteSpecifier.js +3 -1
  196. package/lib/util/async.js +292 -0
  197. package/lib/util/buildDiagnostics.js +69 -0
  198. package/lib/util/conventions.js +5 -1
  199. package/lib/util/fs.js +27 -1
  200. package/lib/util/handlerKeys.js +40 -0
  201. package/lib/util/identifier.js +20 -0
  202. package/lib/util/internalSerializables.js +6 -0
  203. package/lib/util/registerExternalSerializer.js +6 -45
  204. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +9 -0
  205. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +9 -0
  206. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +9 -0
  207. package/lib/web/JsonpChunkLoadingRuntimeModule.js +36 -0
  208. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +19 -0
  209. package/package.json +22 -16
  210. package/schemas/WebpackOptions.check.js +1 -1
  211. package/schemas/WebpackOptions.json +388 -76
  212. package/schemas/plugins/LoaderOptionsPlugin.check.js +1 -1
  213. package/schemas/plugins/ManifestPlugin.check.js +1 -1
  214. package/schemas/plugins/SourceMapDevToolPlugin.check.js +1 -1
  215. package/schemas/plugins/asset/AssetGeneratorOptions.check.js +1 -1
  216. package/schemas/plugins/asset/AssetResourceGeneratorOptions.check.js +1 -1
  217. package/schemas/plugins/container/ContainerPlugin.check.js +1 -1
  218. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  219. package/schemas/plugins/debug/ProfilingPlugin.check.js +1 -1
  220. package/schemas/plugins/dll/DllReferencePlugin.check.js +1 -1
  221. package/schemas/plugins/ids/HashedModuleIdsPlugin.check.js +1 -1
  222. package/schemas/plugins/schemes/HttpUriPlugin.check.js +1 -1
  223. package/types.d.ts +1440 -331
  224. package/lib/dependencies/ExportBindingInitFragment.js +0 -191
  225. package/lib/errors/EntrypointOverlapWarning.js +0 -45
  226. package/lib/performance/EntrypointOverlapPlugin.js +0 -122
@@ -4,6 +4,7 @@
4
4
 
5
5
  "use strict";
6
6
 
7
+ const { basename, extname } = require("path");
7
8
  const { pathToFileURL } = require("url");
8
9
  const { AsyncSeriesHook, AsyncSeriesWaterfallHook } = require("tapable");
9
10
  const { RawSource } = require("webpack-sources");
@@ -19,10 +20,18 @@ const ConstDependency = require("../dependencies/ConstDependency");
19
20
  const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
20
21
  const WebpackError = require("../errors/WebpackError");
21
22
  const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
22
- const { compareModulesByFullName } = require("../util/comparators");
23
+ const ConcatenatedModule = require("../optimize/ConcatenatedModule");
24
+ const {
25
+ compareModulesByFullName,
26
+ compareModulesByIdentifier
27
+ } = require("../util/comparators");
23
28
  const createHash = require("../util/createHash");
24
29
  const createHooksRegistry = require("../util/createHooksRegistry");
25
- const { getUndoPath, makePathsRelative } = require("../util/identifier");
30
+ const {
31
+ getUndoPath,
32
+ makePathsRelative,
33
+ parseResource
34
+ } = require("../util/identifier");
26
35
  const implicitTypeLoaderFallback = require("../util/implicitTypeLoaderFallback");
27
36
  const lazyModule = require("../util/lazyModule");
28
37
  const memoize = require("../util/memoize");
@@ -45,20 +54,41 @@ const getHtmlParser = lazyModule(() => require("./HtmlParser"));
45
54
  // html generation only happens once a build actually has an html module
46
55
  const getHtmlGenerator = memoize(() => require("./HtmlGenerator"));
47
56
 
57
+ // only reached once a page names a CSS chunk of its own
58
+ const getCssModulesPlugin = memoize(() => require("../css/CssModulesPlugin"));
59
+
48
60
  /**
49
61
  * @import {
50
62
  * EntryDescriptionNormalized,
63
+ * FilenameTemplate,
51
64
  * OutputHtmlOptions,
52
65
  * HtmlFaviconIcon
53
66
  * } from "../../declarations/WebpackOptions"
54
67
  */
68
+ /**
69
+ * @import {
70
+ * OutputNormalizedWithDefaults as OutputOptions
71
+ * } from "../config/defaults"
72
+ */
55
73
  /** @typedef {import("../../declarations/WebpackOptions").OutputHtmlOptions["favicon"]} FaviconOption */
56
74
  /** @typedef {import("../../declarations/WebpackOptions").OutputHtmlOptions["manifest"]} ManifestOption */
57
75
  /** @typedef {Exclude<HtmlFaviconIcon, string>} FaviconIcon */
76
+ /** @import Chunk, { ChunkFilenameTemplate } from "../Chunk" */
77
+ /** @import ChunkGraph from "../ChunkGraph" */
58
78
  /** @import Compiler from "../Compiler" */
79
+ /** @import Module from "../Module" */
80
+ /** @typedef {import("../Compilation").EntryData} EntryData */
59
81
  /** @import { HtmlModuleBuildInfo } from "./HtmlModule" */
60
82
  /** @import HtmlParser from "./HtmlParser" */
61
- /** @typedef {{ request: string, entryName: string, type: "script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch", css?: boolean }} HtmlEntryInfo */
83
+ /** @typedef {{ request: string, entryName: string, index: number, type: "script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch", css?: boolean }} HtmlEntryInfo */
84
+ /**
85
+ * What one extracted entry needs to be named. Recorded in page order once
86
+ * every page is built, and resolved to filenames once chunks exist.
87
+ * @typedef {object} PendingName
88
+ * @property {string} base name the tag's url (or its page) asks for, before numbering
89
+ * @property {FilenameTemplate} jsTemplate `output.filename` for this entry, as configured
90
+ * @property {string=} freeName name of a page-only entry, which emits nothing and leaves it free
91
+ */
62
92
 
63
93
  /** @typedef {{ outputName: string }} HtmlTransformHtmlContext */
64
94
  /** @typedef {{ outputName: string }} HtmlEmittedContext */
@@ -222,9 +252,130 @@ const isIntegrityEnabled = (integrity) =>
222
252
  // `\.html`/`\.css` request matchers for the synthetic `output.html` wrapper.
223
253
  const HTML_REQUEST_RE = /\.html(\?|$)/i;
224
254
  const CSS_REQUEST_RE = /\.css(\?|$)/i;
225
- // The wrapper's own resource — the media type must end here, so that a
226
- // `data:text/htmlx,` entry (JavaScript to `DataUriPlugin`) is not one.
227
- const HTML_DATA_URL_RE = /^data:text\/html[,;]/i;
255
+ /**
256
+ * The entry a page is the whole of: nothing imports the module and the entry
257
+ * asks for nothing else, so the entry's JavaScript would be the page itself.
258
+ * @param {Compilation} compilation compilation
259
+ * @param {Module} module html module
260
+ * @returns {{ name: string, data: EntryData } | undefined} the page's own entry, when it has one
261
+ */
262
+ const getPageEntry = (compilation, module) => {
263
+ const { moduleGraph } = compilation;
264
+ for (const connection of moduleGraph.getIncomingConnections(module)) {
265
+ if (connection.originModule) return undefined;
266
+ }
267
+ for (const [name, data] of compilation.entries) {
268
+ if (data.dependencies.length !== 1) continue;
269
+ if (data.includeDependencies.length !== 0) continue;
270
+ if (moduleGraph.getModule(data.dependencies[0]) === module) {
271
+ return { name, data };
272
+ }
273
+ }
274
+ return undefined;
275
+ };
276
+
277
+ /**
278
+ * The entry of a page that emits nothing but its markup, so its chunk carries
279
+ * no JavaScript. An `output.library` asks for the page's exports, and keeps it.
280
+ * @param {Compilation} compilation compilation
281
+ * @param {Module} module html module
282
+ * @returns {{ name: string, data: EntryData } | undefined} the entry, when the page is all it emits
283
+ */
284
+ const getPageOnlyEntry = (compilation, module) => {
285
+ const entry = getPageEntry(compilation, module);
286
+ if (entry === undefined) return undefined;
287
+ const { library } = entry.data.options;
288
+ const configured =
289
+ library === undefined ? compilation.outputOptions.library : library;
290
+ return configured === undefined ? entry : undefined;
291
+ };
292
+
293
+ /**
294
+ * The basename a tag's url lends to the entry it becomes, so
295
+ * `src="./my-script.js"` emits `my-script.js`. Undefined for an inline body.
296
+ * @param {string} request the tag's request
297
+ * @returns {string | undefined} the basename, when the url carries one
298
+ */
299
+ const getRequestBaseName = (request) => {
300
+ // An inline `<script>` body is a `data:` request webpack writes itself.
301
+ if (request.startsWith("data:")) return undefined;
302
+ const { path: file } = parseResource(request);
303
+ return basename(file, extname(file)) || undefined;
304
+ };
305
+
306
+ /**
307
+ * Claims `base`, numbering it while something else already emits under that
308
+ * name, so two tags sharing a basename stay distinct.
309
+ * @param {string} base preferred name
310
+ * @param {Set<string>} taken names this kind of output already emits under
311
+ * @returns {string} the claimed name
312
+ */
313
+ const claimName = (base, taken) => {
314
+ let name = base;
315
+ for (let index = 1; taken.has(name); index++) name = `${base}${index}`;
316
+ taken.add(name);
317
+ return name;
318
+ };
319
+
320
+ // Placeholders that tell chunks apart on their own, so a template carrying
321
+ // one needs no `[name]`. Not `[id]`: an extracted entry's id is synthetic.
322
+ const CONTENT_PLACEHOLDER_REGEXP =
323
+ /\[(?:chunkhash|contenthash|fullhash|hash)[\]:]/;
324
+
325
+ // Basename stem of a filename template — what `[name]` stands in for, keeping
326
+ // the directory, extension and query the template asked for.
327
+ const TEMPLATE_STEM_REGEXP = /(^|\/)[^/?]*?((?:\.[^./?]+)*)(\?.*)?$/;
328
+
329
+ const NAME_PLACEHOLDER_REGEXP = /\[name\]/g;
330
+
331
+ /**
332
+ * A filename template usable for a page's extracted entries: it must tell one
333
+ * entry from another, or every entry of the page would be emitted to the same
334
+ * file. A template naming neither the chunk nor its content lends its directory
335
+ * and extension to a `[name]` instead.
336
+ * @param {string} template configured template
337
+ * @param {string} extension extension to fall back to when the template names none
338
+ * @returns {string} a template that tells the page's entries apart
339
+ */
340
+ const getNamedTemplate = (template, extension) => {
341
+ if (
342
+ template.includes("[name]") ||
343
+ CONTENT_PLACEHOLDER_REGEXP.test(template)
344
+ ) {
345
+ return template;
346
+ }
347
+ return template.replace(
348
+ TEMPLATE_STEM_REGEXP,
349
+ (_, dir, ext, query) => `${dir}[name]${ext || extension}${query || ""}`
350
+ );
351
+ };
352
+
353
+ /**
354
+ * The filename one extracted entry emits under, with `name` filling the
355
+ * `[name]` its template carries or the stem it leaves behind.
356
+ * @param {string} template configured template
357
+ * @param {string} extension extension to fall back to when the template names none
358
+ * @param {string} name the name this entry claimed
359
+ * @returns {string} the entry's filename template
360
+ */
361
+ const fillNamedTemplate = (template, extension, name) =>
362
+ getNamedTemplate(template, extension).replace(NAME_PLACEHOLDER_REGEXP, name);
363
+
364
+ /**
365
+ * The same, for a template `output.filename` states as a function: it is called
366
+ * at emit, and the path it returns read for a stem the same way.
367
+ * @param {FilenameTemplate} template configured template
368
+ * @param {string} extension extension to fall back to when the template names none
369
+ * @param {string} name the name this entry claimed
370
+ * @returns {ChunkFilenameTemplate} the entry's filename template
371
+ */
372
+ const getEntryTemplate = (template, extension, name) => {
373
+ if (typeof template === "function") {
374
+ return (pathData, assetInfo) =>
375
+ fillNamedTemplate(template(pathData, assetInfo), extension, name);
376
+ }
377
+ return fillNamedTemplate(template, extension, name);
378
+ };
228
379
 
229
380
  /**
230
381
  * Requests an `output.html` page must load for an entry: its `dependOn`
@@ -241,7 +392,12 @@ const collectHtmlEntryRequests = (name, entries) => {
241
392
  const seenRequests = new Set();
242
393
  /** @type {Set<string>} */
243
394
  const visited = new Set();
244
- const walk = (/** @type {string} */ entryName) => {
395
+ /**
396
+ * Collects an entry's non-html requests, `dependOn` entries included.
397
+ * @param {string} entryName entry to walk
398
+ * @returns {void}
399
+ */
400
+ const walk = (entryName) => {
245
401
  if (visited.has(entryName)) return;
246
402
  visited.add(entryName);
247
403
  const desc = entries[entryName];
@@ -289,7 +445,7 @@ class HtmlModulesPlugin {
289
445
  * digest of `content`, with `nonNumericOnlyHash` applied — webpack's
290
446
  * standard `[contenthash]` recipe.
291
447
  * @param {string | Buffer} content content to hash
292
- * @param {import("../../declarations/WebpackOptions").Output} outputOptions output options
448
+ * @param {OutputOptions} outputOptions output options
293
449
  * @returns {string} content hash
294
450
  */
295
451
  static computeContentHash(content, outputOptions) {
@@ -311,7 +467,7 @@ class HtmlModulesPlugin {
311
467
  * initial chunks, `output.htmlChunkFilename` otherwise — the HTML counterpart
312
468
  * of `CssModulesPlugin.getChunkFilenameTemplate`.
313
469
  * @param {import("../Chunk")} chunk chunk
314
- * @param {import("../../declarations/WebpackOptions").Output} outputOptions output options
470
+ * @param {OutputOptions} outputOptions output options
315
471
  * @returns {import("../Chunk").ChunkFilenameTemplate} used filename template
316
472
  */
317
473
  static getChunkFilenameTemplate(chunk, outputOptions) {
@@ -480,11 +636,11 @@ class HtmlModulesPlugin {
480
636
  // `finishMake` creates their entries without scanning the whole module
481
637
  // graph), the stylesheet entry names (read in `afterChunks`) and the html
482
638
  // options of every emitted page, keyed by its asset name.
483
- /** @type {WeakMap<import("../Compilation"), { htmlModules: Set<import("../Module")>, stylesheetEntries: Set<string>, htmlAssetOptions: Map<string, OutputHtmlOptions> }>} */
639
+ /** @type {WeakMap<import("../Compilation"), { htmlModules: Set<Module>, stylesheetEntries: Set<string>, pendingNames: Map<string, PendingName>, htmlAssetOptions: Map<string, OutputHtmlOptions> }>} */
484
640
  const compilationState = new WeakMap();
485
641
  /**
486
642
  * @param {import("../Compilation")} compilation compilation
487
- * @returns {{ htmlModules: Set<import("../Module")>, stylesheetEntries: Set<string>, htmlAssetOptions: Map<string, OutputHtmlOptions> }} per-compilation state
643
+ * @returns {{ htmlModules: Set<Module>, stylesheetEntries: Set<string>, pendingNames: Map<string, PendingName>, htmlAssetOptions: Map<string, OutputHtmlOptions> }} per-compilation state
488
644
  */
489
645
  const getState = (compilation) => {
490
646
  let state = compilationState.get(compilation);
@@ -492,6 +648,7 @@ class HtmlModulesPlugin {
492
648
  state = {
493
649
  htmlModules: new Set(),
494
650
  stylesheetEntries: new Set(),
651
+ pendingNames: new Map(),
495
652
  htmlAssetOptions: new Map()
496
653
  };
497
654
  compilationState.set(compilation, state);
@@ -499,14 +656,21 @@ class HtmlModulesPlugin {
499
656
  return state;
500
657
  };
501
658
  compiler.hooks.finishMake.tapAsync(PLUGIN_NAME, (compilation, callback) => {
502
- const { htmlModules, stylesheetEntries } = getState(compilation);
659
+ const { htmlModules, stylesheetEntries, pendingNames } =
660
+ getState(compilation);
661
+ /** @type {Set<string>} */
662
+ const extractedNames = new Set();
663
+ // The page each `type: "html"` link built, so the naming walk below can
664
+ // follow the links in document order instead of in build order.
665
+ /** @type {Map<string, Module>} */
666
+ const linkedPages = new Map();
503
667
 
504
668
  // Collect the entries an HTML module asks for. Only the script chains
505
669
  // (`script`, `script-module`) share a runtime via a leader-only
506
670
  // `dependOn`; `modulepreload`, `stylesheet` and `html` links are all
507
671
  // independent entries (a CSS or page entry must not chain into a JS
508
672
  // leader, or the chunk would mix unrelated outputs).
509
- /** @type {(module: import("../Module")) => { context: string, request: string, name: string, dependOn: string[] | undefined }[]} */
673
+ /** @type {(module: Module) => { context: string, request: string, name: string, dependOn: string[] | undefined }[]} */
510
674
  const collectEntrySpecs = (module) => {
511
675
  const { htmlEntries } = /** @type {HtmlModuleBuildInfo} */ (
512
676
  module.buildInfo
@@ -528,10 +692,11 @@ class HtmlModulesPlugin {
528
692
  leaderName = entry.entryName;
529
693
  }
530
694
  // Stylesheet entries and `as="style"` preload/prefetch entries emit
531
- // a CSS chunk, so they need the CSS filename template below.
532
- if (groupKind === "stylesheet" || entry.css) {
695
+ // a CSS chunk, so they are named from `output.cssFilename` alone.
696
+ if (groupKind === "stylesheet" || Boolean(entry.css)) {
533
697
  stylesheetEntries.add(entry.entryName);
534
698
  }
699
+ extractedNames.add(entry.entryName);
535
700
  specs.push({
536
701
  context,
537
702
  request: entry.request,
@@ -543,9 +708,70 @@ class HtmlModulesPlugin {
543
708
  return specs;
544
709
  };
545
710
 
711
+ // Records what a page's entries ask to be named, then walks its links —
712
+ // once every page is built, so the order is the documents', not the builds'.
713
+ /** @type {(module: Module, seen: Set<Module>) => void} */
714
+ const recordNames = (module, seen) => {
715
+ if (seen.has(module)) return;
716
+ seen.add(module);
717
+ const { htmlEntries } = /** @type {HtmlModuleBuildInfo} */ (
718
+ module.buildInfo
719
+ );
720
+ if (!htmlEntries) return;
721
+ const pageEntry = getPageOnlyEntry(compilation, module);
722
+ const namedEntry =
723
+ pageEntry !== undefined && !extractedNames.has(pageEntry.name)
724
+ ? pageEntry
725
+ : undefined;
726
+ const { resource } = /** @type {NormalModule} */ (module);
727
+ // An `output.html` page is a wrapper webpack writes around an entry, so
728
+ // what it loads is that entry's own output and takes its name.
729
+ const wrapsEntry =
730
+ namedEntry !== undefined &&
731
+ typeof resource === "string" &&
732
+ resource.startsWith("data:text/html");
733
+ const { filename } = compilation.outputOptions;
734
+ // Kept as configured, a function included, and read for a name once the
735
+ // chunks exist.
736
+ const jsTemplate =
737
+ namedEntry && namedEntry.data.options.filename !== undefined
738
+ ? namedEntry.data.options.filename
739
+ : filename;
740
+ // In document order, so of two tags sharing a basename the first a
741
+ // reader sees keeps it. A linked page is named on emit instead.
742
+ /** @type {HtmlEntryInfo[]} */
743
+ const ordered = [];
744
+ /** @type {HtmlEntryInfo[]} */
745
+ const links = [];
746
+ for (const [groupKind, group] of Object.entries(htmlEntries)) {
747
+ for (const entry of group) {
748
+ (groupKind === "html" ? links : ordered).push(entry);
749
+ }
750
+ }
751
+ ordered.sort((a, b) => a.index - b.index);
752
+ for (const entry of ordered) {
753
+ // An inline `<script>` body has no url to be named after, so it
754
+ // keeps the name of its own entry, page hash and tag index.
755
+ const base =
756
+ wrapsEntry && namedEntry
757
+ ? namedEntry.name
758
+ : getRequestBaseName(entry.request) || entry.entryName;
759
+ pendingNames.set(entry.entryName, {
760
+ base,
761
+ jsTemplate,
762
+ freeName: namedEntry && namedEntry.name
763
+ });
764
+ }
765
+ links.sort((a, b) => a.index - b.index);
766
+ for (const link of links) {
767
+ const page = linkedPages.get(link.entryName);
768
+ if (page) recordNames(page, seen);
769
+ }
770
+ };
771
+
546
772
  // Push one collected reference as a compilation entry; resolves with
547
773
  // the built entry module.
548
- /** @type {(spec: { context: string, request: string, name: string, dependOn: string[] | undefined }) => Promise<import("../Module") | null>} */
774
+ /** @type {(spec: { context: string, request: string, name: string, dependOn: string[] | undefined }) => Promise<Module | null>} */
549
775
  const addEntry = (spec) =>
550
776
  new Promise((resolve, reject) => {
551
777
  compilation.addEntry(
@@ -553,10 +779,8 @@ class HtmlModulesPlugin {
553
779
  EntryPlugin.createDependency(spec.request, { name: spec.name }),
554
780
  {
555
781
  name: spec.name,
556
- // Each entry gets its own filename from the synthetic entry name so
557
- // it doesn't collide with `output.filename`. CSS entries set their
558
- // `.css` name via `cssFilenameTemplate` below; `html` page entries
559
- // emit their file via `renderManifest`.
782
+ // A placeholder `afterChunks` overwrites for every entry it
783
+ // names; only a linked page keeps it, and it emits no JavaScript.
560
784
  filename: compilation.outputOptions.chunkFilename || "[name].js",
561
785
  dependOn: spec.dependOn
562
786
  },
@@ -570,7 +794,7 @@ class HtmlModulesPlugin {
570
794
  // every HTML entry already gets. `processed` guards diamonds and cycles.
571
795
  /** @type {WeakSet<import("../Module")>} */
572
796
  const processed = new WeakSet();
573
- /** @type {(module: import("../Module")) => Promise<void>} */
797
+ /** @type {(module: Module) => Promise<void>} */
574
798
  const processEntry = async (module) => {
575
799
  if (processed.has(module)) return;
576
800
  processed.add(module);
@@ -578,6 +802,7 @@ class HtmlModulesPlugin {
578
802
  collectEntrySpecs(module).map(async (spec) => {
579
803
  const entryModule = await addEntry(spec);
580
804
  if (entryModule && entryModule.type === HTML_MODULE_TYPE) {
805
+ linkedPages.set(spec.name, entryModule);
581
806
  await processEntry(entryModule);
582
807
  }
583
808
  })
@@ -585,11 +810,15 @@ class HtmlModulesPlugin {
585
810
  };
586
811
  // Seed with the HTML modules seen during make (config entries /
587
812
  // imports, fresh or cache-restored); linked pages are reached by
588
- // recursion above.
589
- Promise.all([...htmlModules].map(processEntry)).then(
590
- () => callback(),
591
- callback
592
- );
813
+ // recursion above. Sorted because they are tracked in build-completion
814
+ // order, which the names must not depend on.
815
+ const seeds = [...htmlModules].sort(compareModulesByIdentifier);
816
+ Promise.all(seeds.map(processEntry)).then(() => {
817
+ /** @type {Set<Module>} */
818
+ const named = new Set();
819
+ for (const seed of seeds) recordNames(seed, named);
820
+ callback();
821
+ }, callback);
593
822
  });
594
823
 
595
824
  // `csp` injects a `<meta http-equiv="Content-Security-Policy">` and
@@ -613,12 +842,13 @@ class HtmlModulesPlugin {
613
842
  );
614
843
  }
615
844
  const { htmlModules } = getState(compilation);
616
- // Record HTML modules as they appear — freshly built
617
- // (`succeedModule`) or restored from cache (`stillValidModule`) —
618
- // so `finishMake` creates their entries without scanning every
619
- // module in the graph. Tracking only records; entries are still
620
- // added (and awaited) in `finishMake`.
621
- const trackHtmlModule = (/** @type {import("../Module")} */ module) => {
845
+ /**
846
+ * Records HTML modules as they are built or restored, so `finishMake`
847
+ * creates their entries without scanning the whole module graph.
848
+ * @param {import("../Module")} module built or restored module
849
+ * @returns {void}
850
+ */
851
+ const trackHtmlModule = (module) => {
622
852
  if (module.type !== HTML_MODULE_TYPE) return;
623
853
  // enabled here, not on the compilation hook: an integrity build
624
854
  // with no html module must not load the generator
@@ -719,26 +949,6 @@ class HtmlModulesPlugin {
719
949
  // a content-hashed filename makes a substring hit a real reference.
720
950
  /** @type {Set<string>} */
721
951
  const deletionCandidates = new Set(inlinedFiles);
722
- // A wrapper entry's chunk only rebuilds the page's own markup; the
723
- // page loads the `<script src>` entries the parser split out of it.
724
- for (const entrypoint of compilation.entrypoints.values()) {
725
- const chunk = entrypoint.getEntrypointChunk();
726
- if (!chunk) continue;
727
- let wrapperOnly = false;
728
- for (const entryModule of compilation.chunkGraph.getChunkEntryModulesIterable(
729
- chunk
730
- )) {
731
- const { resource } = /** @type {import("../NormalModule")} */ (
732
- entryModule
733
- );
734
- wrapperOnly =
735
- typeof resource === "string" &&
736
- HTML_DATA_URL_RE.test(resource);
737
- if (!wrapperOnly) break;
738
- }
739
- if (!wrapperOnly) continue;
740
- for (const file of chunk.files) deletionCandidates.add(file);
741
- }
742
952
  // Materialize each other asset's source once (it can be expensive to
743
953
  // render) and test all candidates against it, rather than
744
954
  // re-materializing every asset per candidate.
@@ -790,26 +1000,57 @@ class HtmlModulesPlugin {
790
1000
  // entry emits to a distinct file derived from `output.cssFilename`
791
1001
  // (or `output.cssChunkFilename` for non-initial CSS chunks).
792
1002
  compilation.hooks.afterChunks.tap(PLUGIN_NAME, () => {
793
- const { stylesheetEntries } = getState(compilation);
794
- if (stylesheetEntries.size === 0) return;
795
- for (const entryName of stylesheetEntries) {
1003
+ const { stylesheetEntries, pendingNames } = getState(compilation);
1004
+ if (pendingNames.size === 0) return;
1005
+ const { chunkGraph, outputOptions } = compilation;
1006
+ const jsExtension = outputOptions.module ? ".mjs" : ".js";
1007
+ // Claimed in the order the pages recorded them, which is the
1008
+ // documents' order rather than their builds'.
1009
+ const takenJsNames = new Set(compilation.entries.keys());
1010
+ const takenCssNames = new Set(takenJsNames);
1011
+ // An extracted entry emits nothing under its own synthetic name, so
1012
+ // that name never stands in the way of the one its tag asks for.
1013
+ for (const entryName of pendingNames.keys()) {
1014
+ takenJsNames.delete(entryName);
1015
+ takenCssNames.delete(entryName);
1016
+ }
1017
+ for (const { freeName } of pendingNames.values()) {
1018
+ if (freeName === undefined) continue;
1019
+ // A page-only entry emits nothing of its own, so its name is free for
1020
+ // the entries extracted from it — `app.html` keeps `app.js`.
1021
+ takenJsNames.delete(freeName);
1022
+ takenCssNames.delete(freeName);
1023
+ }
1024
+ for (const [entryName, { base, jsTemplate }] of pendingNames) {
796
1025
  const entrypoint = compilation.entrypoints.get(entryName);
797
1026
  if (!entrypoint) continue;
798
1027
  const chunk = entrypoint.getEntrypointChunk();
799
1028
  if (!chunk) continue;
800
- // Each html-derived stylesheet entry uses the
801
- // `cssChunkFilename` template — even though the entry
802
- // chunk technically `canBeInitial()`, we deliberately
803
- // avoid `cssFilename` here because that template often
804
- // has no per-entry placeholder (it's derived from
805
- // `output.filename`, which can be a literal like
806
- // `bundle0.js`), and multiple `<link rel="stylesheet">`
807
- // tags would then collide on the same emitted `.css`
808
- // file. `cssChunkFilename` is derived from
809
- // `output.chunkFilename` which webpack auto-extends
810
- // with `[id].` when needed, guaranteeing uniqueness.
811
- chunk.cssFilenameTemplate =
812
- compilation.outputOptions.cssChunkFilename;
1029
+ // A stylesheet entry emits no JavaScript, so it claims no JS name and
1030
+ // leaves that one to a `<script src>` of the same basename.
1031
+ const emitsCss = stylesheetEntries.has(entryName);
1032
+ if (!emitsCss) {
1033
+ // `output.filename` named after the tag's url, the way an entry
1034
+ // point is named. `Compilation` set it from the placeholder above.
1035
+ chunk.filenameTemplate = getEntryTemplate(
1036
+ jsTemplate,
1037
+ jsExtension,
1038
+ claimName(base, takenJsNames)
1039
+ );
1040
+ }
1041
+ // A `<script src>` whose chunk carries no CSS claims no CSS name, so
1042
+ // a `<link>` of the same basename still gets the plain one.
1043
+ if (
1044
+ !emitsCss &&
1045
+ !getCssModulesPlugin().chunkHasCss(chunk, chunkGraph)
1046
+ ) {
1047
+ continue;
1048
+ }
1049
+ chunk.cssFilenameTemplate = getEntryTemplate(
1050
+ outputOptions.cssFilename,
1051
+ ".css",
1052
+ claimName(base, takenCssNames)
1053
+ );
813
1054
  }
814
1055
  });
815
1056
  compilation.dependencyTemplates.set(
@@ -927,7 +1168,8 @@ class HtmlModulesPlugin {
927
1168
  );
928
1169
  return new (getHtmlGenerator())(
929
1170
  generatorOptions,
930
- compilation.moduleGraph
1171
+ compilation.moduleGraph,
1172
+ (module) => getPageOnlyEntry(compilation, module) !== undefined
931
1173
  );
932
1174
  });
933
1175
 
@@ -981,15 +1223,15 @@ class HtmlModulesPlugin {
981
1223
  /** @type {WeakMap<import("webpack-sources").Source, { resolvedContent: string, contentHash: string }>} */
982
1224
  const resolvedSentinelHashCache = new WeakMap();
983
1225
 
984
- // Compute a linked/entry HTML page's emitted filename + chunk-URL-resolved
985
- // content + content hash for one (module, chunk). Shared by the emit loop
986
- // below and the page-link resolver. The content hash covers only the
987
- // chunk-URL-resolved source — links to *other* pages stay sentinels here,
988
- // so a page's filename doesn't depend on the filenames of pages it links to.
989
- const computePageEmit = (
990
- /** @type {NormalModule} */ module,
991
- /** @type {import("../Chunk")} */ chunk
992
- ) => {
1226
+ /**
1227
+ * Computes an HTML page's emitted filename, chunk-URL-resolved content and
1228
+ * content hash. Links to other pages stay sentinels, so a page's filename
1229
+ * never depends on the filenames of the pages it links to.
1230
+ * @param {NormalModule} module the page's html module
1231
+ * @param {import("../Chunk")} chunk chunk the page is emitted from
1232
+ * @returns {{ resolvedContent: string, contentHash: string, filename: string, info: import("../Compilation").AssetInfo } | undefined} the page's emit, or `undefined` when it generates no html
1233
+ */
1234
+ const computePageEmit = (module, chunk) => {
993
1235
  const { chunkGraph, outputOptions } = compilation;
994
1236
  const codeGenResult =
995
1237
  /** @type {import("../CodeGenerationResults")} */ (
@@ -1066,14 +1308,15 @@ class HtmlModulesPlugin {
1066
1308
  };
1067
1309
  };
1068
1310
 
1069
- // Emitted filename of a linked `type: "html"` page, keyed by its entry
1070
- // name. Looked up from the page's own entry chunk so it's independent of
1071
- // chunk render order; cached for the compilation.
1072
1311
  /** @type {Map<string, string>} */
1073
1312
  const htmlPageFilenameCache = new Map();
1074
- const computePageFilenameByEntry = (
1075
- /** @type {string} */ entryName
1076
- ) => {
1313
+ /**
1314
+ * Emitted filename of a linked `type: "html"` page, read from the page's
1315
+ * own entry chunk so chunk render order cannot affect it.
1316
+ * @param {string} entryName entry name of the linked page
1317
+ * @returns {string} the page's emitted filename, `"data:,"` when it has none
1318
+ */
1319
+ const computePageFilenameByEntry = (entryName) => {
1077
1320
  const cached = htmlPageFilenameCache.get(entryName);
1078
1321
  if (cached !== undefined) return cached;
1079
1322
  let filename = "data:,";
@@ -1204,6 +1447,16 @@ class HtmlModulesPlugin {
1204
1447
  jsHooks.render.tap(PLUGIN_NAME, (source, renderContext) => {
1205
1448
  // No HTML modules ⇒ no sentinels; skip materializing the JS source.
1206
1449
  if (htmlModules.size === 0) return source;
1450
+ // Chunks without HTML modules can't carry HTML sentinels — avoid
1451
+ // ConcatSource.source() on the common pure-JS chunk path.
1452
+ if (
1453
+ !HtmlModulesPlugin.chunkHasHtml(
1454
+ renderContext.chunk,
1455
+ compilation.chunkGraph
1456
+ )
1457
+ ) {
1458
+ return source;
1459
+ }
1207
1460
  const raw = source.source();
1208
1461
  if (typeof raw !== "string") return source;
1209
1462
  // Every sentinel shares this prefix — one scan answers the common
@@ -1274,6 +1527,29 @@ class HtmlModulesPlugin {
1274
1527
  }
1275
1528
  );
1276
1529
  }
1530
+
1531
+ /**
1532
+ * Returns true, when the chunk has html.
1533
+ * @param {Chunk} chunk chunk
1534
+ * @param {ChunkGraph} chunkGraph chunk graph
1535
+ * @returns {boolean} true, when the chunk has html
1536
+ */
1537
+ static chunkHasHtml(chunk, chunkGraph) {
1538
+ if (chunkGraph.getChunkModulesIterableBySourceType(chunk, HTML_TYPE)) {
1539
+ return true;
1540
+ }
1541
+ // Scope-hoisted HTML loses HTML_TYPE on the ConcatenatedModule, but its
1542
+ // JS export can still carry unresolved sentinels that need this path.
1543
+ for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
1544
+ if (module instanceof HtmlModule) return true;
1545
+ if (module instanceof ConcatenatedModule) {
1546
+ for (const inner of module.modules) {
1547
+ if (inner instanceof HtmlModule) return true;
1548
+ }
1549
+ }
1550
+ }
1551
+ return false;
1552
+ }
1277
1553
  }
1278
1554
 
1279
1555
  /**