@depup/webpack 5.110.1-depup.0 → 5.110.3-depup.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 (70) hide show
  1. package/README.md +4 -4
  2. package/changes.json +3 -3
  3. package/lib/ChunkGraph.js +20 -18
  4. package/lib/Compilation.js +1 -1
  5. package/lib/ConcatenationScope.js +25 -35
  6. package/lib/DotenvPlugin.js +41 -13
  7. package/lib/ExportsInfo.js +10 -0
  8. package/lib/FlagDependencyUsagePlugin.js +8 -2
  9. package/lib/NormalModule.js +13 -20
  10. package/lib/RuntimePlugin.js +3 -0
  11. package/lib/RuntimeTemplate.js +437 -432
  12. package/lib/async-modules/AsyncModuleHelpers.js +10 -4
  13. package/lib/config/defaults.js +26 -16
  14. package/lib/config/normalization.js +18 -8
  15. package/lib/css/CssLoadingRuntimeModule.js +32 -13
  16. package/lib/css/CssModulesPlugin.js +9 -17
  17. package/lib/css/data.js +13 -3
  18. package/lib/css/syntax.js +78 -10
  19. package/lib/dependencies/CommonJsRequireDependency.js +18 -4
  20. package/lib/dependencies/ContextDependencyTemplateAsRequireCall.js +20 -7
  21. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +10 -4
  22. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +2 -0
  23. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +143 -17
  24. package/lib/dependencies/HarmonyImportDependency.js +94 -41
  25. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +93 -14
  26. package/lib/dependencies/HarmonyImportSideEffectDependency.js +114 -0
  27. package/lib/dependencies/HtmlEntryDependency.js +12 -2
  28. package/lib/dependencies/ImportContextDependency.js +43 -3
  29. package/lib/dependencies/ImportDependency.js +8 -29
  30. package/lib/dependencies/ImportParserPlugin.js +24 -14
  31. package/lib/dependencies/RequireHeaderDependency.js +20 -21
  32. package/lib/dependencies/WorkerAndWorkletPlugin.js +1 -0
  33. package/lib/dependencies/WorkerDependency.js +6 -12
  34. package/lib/dependencies/WorkletDependency.js +7 -11
  35. package/lib/dependencies/importOptionsCheck.js +47 -0
  36. package/lib/errors/DotenvFileError.js +29 -0
  37. package/lib/errors/ImportedBindingAssignmentError.js +30 -0
  38. package/lib/esm/ModuleChunkLoadingPlugin.js +6 -9
  39. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +7 -1
  40. package/lib/html/HtmlModulesPlugin.js +84 -38
  41. package/lib/html/syntax.js +417 -169
  42. package/lib/javascript/JavascriptModulesPlugin.js +5 -10
  43. package/lib/loaders/LoaderRunner.js +33 -5
  44. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +8 -15
  45. package/lib/optimize/ConcatenatedModule.js +46 -7
  46. package/lib/optimize/ConstExportsPlugin.js +5 -0
  47. package/lib/optimize/InlineExports.js +121 -0
  48. package/lib/optimize/RealContentHashPlugin.js +111 -35
  49. package/lib/prefetch/ResourceHintPlugin.js +12 -13
  50. package/lib/runtime/AsyncModuleRuntimeModule.js +5 -1
  51. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +11 -1
  52. package/lib/schemes/HttpUriPlugin.js +22 -5
  53. package/lib/serialization/FileMiddleware.js +15 -2
  54. package/lib/typescript/TypeScriptPlugin.js +8 -10
  55. package/lib/util/ArrayQueue.js +3 -3
  56. package/lib/util/SourceProcessor.js +24 -8
  57. package/lib/util/URLAbsoluteSpecifier.js +3 -1
  58. package/lib/util/conventions.js +5 -1
  59. package/lib/util/createMappings.js +25 -3
  60. package/lib/util/internalSerializables.js +2 -0
  61. package/lib/util/removeBOM.js +82 -1
  62. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +5 -3
  63. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +5 -3
  64. package/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js +7 -12
  65. package/lib/web/FetchCompileAsyncWasmPlugin.js +10 -6
  66. package/lib/web/FetchCompileWasmPlugin.js +4 -2
  67. package/package.json +7 -7
  68. package/schemas/WebpackOptions.check.js +1 -1
  69. package/schemas/WebpackOptions.json +9 -10
  70. package/types.d.ts +95 -27
@@ -12,7 +12,7 @@ const {
12
12
  } = require("../ModuleSourceTypeConstants");
13
13
  const GenericSourceProcessor = require("../util/SourceProcessor");
14
14
 
15
- const { deferredWrite } = GenericSourceProcessor;
15
+ const { deferredWrite, declineDeferredWrites } = GenericSourceProcessor;
16
16
 
17
17
  /** @import { CssEnvironment, CssProcessOptions, CssTransformOptions } from "../css/syntax" */
18
18
  /** @import { OutputHtmlOptions } from "../../declarations/WebpackOptions" */
@@ -5237,11 +5237,13 @@ const _makeProcessingInstructionNode = (target, data, start, end) => {
5237
5237
  // Marker entry in the active-formatting-elements list (never a valid ref).
5238
5238
  const AFE_MARKER = -1;
5239
5239
 
5240
- // Clone of an element's attribute run: keep name/value (and the serializer
5241
- // name flag) but drop source offsets so the consumer doesn't emit a duplicate
5242
- // dependency for the reopened element's spans. Values are materialized since
5243
- // the offsets are gone.
5244
- const cloneAttrs = (/** @type {HtmlElement} */ el) => {
5240
+ /**
5241
+ * Clones an element's attribute run, dropping source offsets so the consumer
5242
+ * emits no duplicate dependency for the reopened element's spans.
5243
+ * @param {HtmlElement} el element whose attributes are cloned
5244
+ * @returns {AttributeRun} the cloned run
5245
+ */
5246
+ const cloneAttrs = (el) => {
5245
5247
  const start = _nodeAttrStarts[el];
5246
5248
  const count = _nodeAttrCounts[el];
5247
5249
  const newStart = _attrCount + 1;
@@ -5252,14 +5254,15 @@ const cloneAttrs = (/** @type {HtmlElement} */ el) => {
5252
5254
  return { start: newStart, count };
5253
5255
  };
5254
5256
 
5255
- // Merge a repeated `<html>`/`<body>` tag's attributes into the element: only
5256
- // names not already present are added (in source order after the existing
5257
- // ones). Runs are contiguous, so any addition re-allocates the whole run; the
5258
- // old slots are orphaned (at most once per repeated tag, rare).
5259
- const mergeAttrs = (
5260
- /** @type {HtmlElement} */ el,
5261
- /** @type {AttributeRun} */ run
5262
- ) => {
5257
+ /**
5258
+ * Merges a repeated `<html>`/`<body>` tag's attributes into the element, adding
5259
+ * only names not already present. Runs are contiguous, so any addition
5260
+ * re-allocates the whole run and orphans the old slots.
5261
+ * @param {HtmlElement} el element receiving the attributes
5262
+ * @param {AttributeRun} run attributes of the repeated tag
5263
+ * @returns {void}
5264
+ */
5265
+ const mergeAttrs = (el, run) => {
5263
5266
  const start = _nodeAttrStarts[el];
5264
5267
  const count = _nodeAttrCounts[el];
5265
5268
  let extra = 0;
@@ -5335,9 +5338,13 @@ const TOKEN_PROCESSING_INSTRUCTION = 7;
5335
5338
  /** @typedef {{ type: typeof TOKEN_EOF }} EofToken */
5336
5339
  /** @typedef {{ type: typeof TOKEN_PROCESSING_INSTRUCTION, name: string, data: string, start: number, end: number }} ProcessingInstructionToken the target rides in `name`, as the doctype's does */
5337
5340
 
5338
- // Every insertion mode handles a processing instruction token on the same arc
5339
- // as a comment token, so the arcs test both.
5340
- const isCommentOrProcessingInstruction = (/** @type {Token} */ t) =>
5341
+ /**
5342
+ * Every insertion mode handles a processing instruction token on the same arc
5343
+ * as a comment token, so the arcs test both.
5344
+ * @param {Token} t current token
5345
+ * @returns {t is CommentToken | ProcessingInstructionToken} true for a comment or processing instruction token
5346
+ */
5347
+ const isCommentOrProcessingInstruction = (t) =>
5341
5348
  t.type === TOKEN_COMMENT || t.type === TOKEN_PROCESSING_INSTRUCTION;
5342
5349
 
5343
5350
  /**
@@ -5399,9 +5406,12 @@ const TEMPLATE_START_TAG_MODES = new Map(
5399
5406
  })
5400
5407
  );
5401
5408
 
5402
- // MathML/SVG specials handled via namespace checks below.
5403
-
5404
- const isSpecial = (/** @type {HtmlElement} */ el) => {
5409
+ /**
5410
+ * MathML/SVG specials are handled via namespace checks rather than one table.
5411
+ * @param {HtmlElement} el element to test
5412
+ * @returns {boolean} true when the element is "special" (§13.2.4.2)
5413
+ */
5414
+ const isSpecial = (el) => {
5405
5415
  const ns = _namespaceOf(el);
5406
5416
  const tag = _tagNameOf(el);
5407
5417
  if (ns === NS_HTML) return SPECIAL.has(tag);
@@ -5410,8 +5420,12 @@ const isSpecial = (/** @type {HtmlElement} */ el) => {
5410
5420
  return false;
5411
5421
  };
5412
5422
 
5413
- // `<font color|face|size>` breaks out of foreign content (§13.2.6.5).
5414
- const hasFontBreakoutAttr = (/** @type {AttributeRun} */ run) => {
5423
+ /**
5424
+ * `<font color|face|size>` breaks out of foreign content (§13.2.6.5).
5425
+ * @param {AttributeRun} run the tag's attributes
5426
+ * @returns {boolean} true when one of the breakout attributes is present
5427
+ */
5428
+ const hasFontBreakoutAttr = (run) => {
5415
5429
  for (let i = run.start; i < run.start + run.count; i++) {
5416
5430
  if (FONT_BREAKOUT_ATTRS.has(_attrNames[i])) return true;
5417
5431
  }
@@ -5590,9 +5604,13 @@ const ATTR_NAME_INTERN = buildNameInternTable(
5590
5604
  )
5591
5605
  );
5592
5606
 
5593
- // Hoisted so the many `open.some(...)` "is there an open HTML <template>?"
5594
- // checks reuse one predicate instead of allocating an arrow per call.
5595
- const isHtmlTemplateEl = (/** @type {HtmlElement} */ e) =>
5607
+ /**
5608
+ * Hoisted so the many `open.some(...)` "is there an open HTML `<template>`?"
5609
+ * checks reuse one predicate instead of allocating an arrow per call.
5610
+ * @param {HtmlElement} e element to test
5611
+ * @returns {boolean} true for an HTML `<template>` element
5612
+ */
5613
+ const isHtmlTemplateEl = (e) =>
5596
5614
  _tagNameOf(e) === "template" && _namespaceOf(e) === NS_HTML;
5597
5615
 
5598
5616
  /**
@@ -5651,12 +5669,15 @@ const isQuirky = (name, pub, sys) => {
5651
5669
  return false;
5652
5670
  };
5653
5671
 
5654
- const mkEl = (
5655
- /** @type {string} */ tagName,
5656
- /** @type {number} */ ns,
5657
- /** @type {AttributeRun} */ attrs,
5658
- /** @type {TagPos | null | undefined} */ pos
5659
- ) => {
5672
+ /**
5673
+ * Allocates an element node.
5674
+ * @param {string} tagName tag name
5675
+ * @param {number} ns namespace flag (`NS_HTML`, `NS_MATHML`, `NS_SVG`)
5676
+ * @param {AttributeRun} attrs the element's attributes
5677
+ * @param {TagPos | null | undefined} pos source position, absent for synthesized elements
5678
+ * @returns {HtmlElement} the new element
5679
+ */
5680
+ const mkEl = (tagName, ns, attrs, pos) => {
5660
5681
  // Inlined element allocation: every column is written exactly once (the
5661
5682
  // generic _allocNode would zero five columns mkEl immediately overwrites).
5662
5683
  const el = ++_nodeCount;
@@ -5702,7 +5723,12 @@ const effParent = (parent) => {
5702
5723
  return tc !== 0 ? tc : parent;
5703
5724
  };
5704
5725
 
5705
- const isScopeBoundary = (/** @type {HtmlElement} */ el) => {
5726
+ /**
5727
+ * Elements that stop a "have an element in scope" walk (§13.2.4.2).
5728
+ * @param {HtmlElement} el element to test
5729
+ * @returns {boolean} true when the element bounds a scope
5730
+ */
5731
+ const isScopeBoundary = (el) => {
5706
5732
  if (_namespaceOf(el) === NS_HTML) return HTML_SCOPE.has(_tagNameOf(el));
5707
5733
  if (_namespaceOf(el) === NS_MATHML) {
5708
5734
  return MATHML_SPECIAL.has(_tagNameOf(el));
@@ -5713,10 +5739,13 @@ const isScopeBoundary = (/** @type {HtmlElement} */ el) => {
5713
5739
  return false;
5714
5740
  };
5715
5741
 
5716
- // Attribute values are stored raw (offsets into the source), so two unresolved
5717
- // values compare by range — `_attrValueOf` would slice a string per side just
5718
- // to throw it away.
5719
- /** @type {(i: number, j: number) => boolean} */
5742
+ /**
5743
+ * Compares two attribute values, by source range while both are unresolved —
5744
+ * `_attrValueOf` would slice a string per side just to throw it away.
5745
+ * @param {number} i first attribute id
5746
+ * @param {number} j second attribute id
5747
+ * @returns {boolean} true when both values are equal
5748
+ */
5720
5749
  const _attrValueEquals = (i, j) => {
5721
5750
  if (_attrValues[i] === null && _attrValues[j] === null) {
5722
5751
  const start = _attrValueStarts[i];
@@ -5736,10 +5765,14 @@ const _attrValueEquals = (i, j) => {
5736
5765
  return _attrValueOf(i) === _attrValueOf(j);
5737
5766
  };
5738
5767
 
5739
- const sameAttrs = (
5740
- /** @type {HtmlElement} */ a,
5741
- /** @type {HtmlElement} */ b
5742
- ) => {
5768
+ /**
5769
+ * Compares two elements' attribute runs, as the active-formatting-elements
5770
+ * "Noah's Ark" clause does.
5771
+ * @param {HtmlElement} a first element
5772
+ * @param {HtmlElement} b second element
5773
+ * @returns {boolean} true when both carry the same attributes
5774
+ */
5775
+ const sameAttrs = (a, b) => {
5743
5776
  const aStart = _nodeAttrStarts[a];
5744
5777
  const aCount = _nodeAttrCounts[a];
5745
5778
  const bStart = _nodeAttrStarts[b];
@@ -5754,12 +5787,20 @@ const sameAttrs = (
5754
5787
  return true;
5755
5788
  };
5756
5789
 
5757
- const startsWithWs = (/** @type {string} */ s) => {
5790
+ /**
5791
+ * @param {string} s text to test
5792
+ * @returns {boolean} true when the text starts with HTML whitespace
5793
+ */
5794
+ const startsWithWs = (s) => {
5758
5795
  const c = s.charCodeAt(0);
5759
5796
  return c === 0x09 || c === 0x0a || c === 0x0c || c === 0x0d || c === 0x20;
5760
5797
  };
5761
5798
 
5762
- const isAllWs = (/** @type {string} */ s) => {
5799
+ /**
5800
+ * @param {string} s text to test
5801
+ * @returns {boolean} true when the text is HTML whitespace only
5802
+ */
5803
+ const isAllWs = (s) => {
5763
5804
  for (let i = 0; i < s.length; i++) {
5764
5805
  const c = s.charCodeAt(i);
5765
5806
  // HTML whitespace: tab / LF / FF / CR / space. A charCodeAt loop avoids
@@ -5807,10 +5848,18 @@ const collapseWhitespaceRuns = (s) => {
5807
5848
  return last === 0 ? s : out + s.slice(last);
5808
5849
  };
5809
5850
 
5810
- const mathmlTextIntegrationPoint = (/** @type {HtmlElement} */ el) =>
5851
+ /**
5852
+ * @param {HtmlElement} el element to test
5853
+ * @returns {boolean} true for a MathML text integration point (§13.2.6.5)
5854
+ */
5855
+ const mathmlTextIntegrationPoint = (el) =>
5811
5856
  _namespaceOf(el) === NS_MATHML && MATHML_TEXT_INTEGRATION.has(_tagNameOf(el));
5812
5857
 
5813
- const htmlIntegrationPoint = (/** @type {HtmlElement} */ el) => {
5858
+ /**
5859
+ * @param {HtmlElement} el element to test
5860
+ * @returns {boolean} true for an HTML integration point (§13.2.6.5)
5861
+ */
5862
+ const htmlIntegrationPoint = (el) => {
5814
5863
  if (_namespaceOf(el) === NS_MATHML && _tagNameOf(el) === "annotation-xml") {
5815
5864
  const enc = _findAttr(_nodeAttrStarts[el], _nodeAttrCounts[el], "encoding");
5816
5865
  if (enc !== 0) {
@@ -5830,13 +5879,14 @@ const htmlIntegrationPoint = (/** @type {HtmlElement} */ el) => {
5830
5879
  return false;
5831
5880
  };
5832
5881
 
5833
- // Adjust a foreign start tag's attribute run in place (the run is consumed
5834
- // only by this tag's element): SVG camelCase names are rewritten, and
5835
- // namespaced names get the serializer-name flag (see `_attrSerializedName`).
5836
- const adjustForeignAttrs = (
5837
- /** @type {AttributeRun} */ run,
5838
- /** @type {number} */ ns
5839
- ) => {
5882
+ /**
5883
+ * Rewrites SVG camelCase names and flags namespaced ones for the serializer.
5884
+ * In place: the run is consumed only by this tag's element.
5885
+ * @param {AttributeRun} run the tag's attributes
5886
+ * @param {number} ns namespace flag of the tag
5887
+ * @returns {AttributeRun} the same run
5888
+ */
5889
+ const adjustForeignAttrs = (run, ns) => {
5840
5890
  for (let i = run.start; i < run.start + run.count; i++) {
5841
5891
  const name = _attrNames[i];
5842
5892
  if (
@@ -5854,9 +5904,13 @@ const adjustForeignAttrs = (
5854
5904
  return run;
5855
5905
  };
5856
5906
 
5857
- // Only `definitionurl` is rewritten (in place — the run is consumed only by
5858
- // this tag's element); the camelCase name serializes as itself.
5859
- const adjustMathmlAttrs = (/** @type {AttributeRun} */ run) => {
5907
+ /**
5908
+ * Only `definitionurl` is rewritten (in place — the run is consumed only by
5909
+ * this tag's element); the camelCase name serializes as itself.
5910
+ * @param {AttributeRun} run the tag's attributes
5911
+ * @returns {AttributeRun} the same run
5912
+ */
5913
+ const adjustMathmlAttrs = (run) => {
5860
5914
  for (let i = run.start; i < run.start + run.count; i++) {
5861
5915
  if (_attrNames[i] === "definitionurl") _attrNames[i] = "definitionURL";
5862
5916
  }
@@ -5956,10 +6010,13 @@ const adjustedCurrent = () => {
5956
6010
  return cur();
5957
6011
  };
5958
6012
 
5959
- const appendTo = (
5960
- /** @type {HtmlNodeRef} */ parent,
5961
- /** @type {HtmlNodeRef} */ node
5962
- ) => {
6013
+ /**
6014
+ * Appends a node, merging it into the previous sibling when both are text.
6015
+ * @param {HtmlNodeRef} parent container
6016
+ * @param {HtmlNodeRef} node node to append
6017
+ * @returns {void}
6018
+ */
6019
+ const appendTo = (parent, node) => {
5963
6020
  const p = effParent(parent);
5964
6021
  const last = _nodeLastChildren[p];
5965
6022
  if (
@@ -5979,10 +6036,12 @@ const appendTo = (
5979
6036
  // allocation per inserted node.
5980
6037
  /** @type {InsertionPlace} */
5981
6038
  const sharedPlace = { parent: doc, beforeNode: 0 };
5982
- const placeAt = (
5983
- /** @type {HtmlNodeRef} */ parent,
5984
- /** @type {HtmlNodeRef} */ beforeNode
5985
- ) => {
6039
+ /**
6040
+ * @param {HtmlNodeRef} parent container
6041
+ * @param {HtmlNodeRef} beforeNode node to insert before (`0` = append)
6042
+ * @returns {InsertionPlace} the shared insertion place
6043
+ */
6044
+ const placeAt = (parent, beforeNode) => {
5986
6045
  sharedPlace.parent = parent;
5987
6046
  sharedPlace.beforeNode = beforeNode;
5988
6047
  return sharedPlace;
@@ -6030,10 +6089,13 @@ const appropriatePlace = () => {
6030
6089
  return placeAt(target, 0);
6031
6090
  };
6032
6091
 
6033
- const insertAtPlace = (
6034
- /** @type {InsertionPlace} */ place,
6035
- /** @type {HtmlNodeRef} */ node
6036
- ) => {
6092
+ /**
6093
+ * Inserts a node at an insertion place, appending when `beforeNode` is `0`.
6094
+ * @param {InsertionPlace} place where the node goes
6095
+ * @param {HtmlNodeRef} node node to insert
6096
+ * @returns {void}
6097
+ */
6098
+ const insertAtPlace = (place, node) => {
6037
6099
  const before = place.beforeNode;
6038
6100
  if (before !== 0) {
6039
6101
  const p = effParent(place.parent);
@@ -6084,11 +6146,14 @@ const _recordFostered = (table, node) => {
6084
6146
  _fosteredLog.push(table, node);
6085
6147
  };
6086
6148
 
6087
- const insertCharacters = (
6088
- /** @type {string} */ data,
6089
- /** @type {number} */ start,
6090
- /** @type {number} */ end
6091
- ) => {
6149
+ /**
6150
+ * Inserts a character run at the appropriate place (§13.2.6.1).
6151
+ * @param {string} data decoded text
6152
+ * @param {number} start source offset of the run
6153
+ * @param {number} end source offset past the run
6154
+ * @returns {void}
6155
+ */
6156
+ const insertCharacters = (data, start, end) => {
6092
6157
  const place = appropriatePlace();
6093
6158
  if (_nodeTypes[place.parent] === NodeType.Document) return; // never insert text into document
6094
6159
  // `skip.text`: drop every `Text` node — construction already used the
@@ -6149,11 +6214,12 @@ const insertCharacters = (
6149
6214
  };
6150
6215
 
6151
6216
  /**
6217
+ * Every insertion mode inserts a processing instruction where it inserts a
6218
+ * comment (§13.2.6.4). `skip.comments` drops comments alone.
6152
6219
  * @param {CommentToken | ProcessingInstructionToken} t comment or processing instruction token
6153
6220
  * @param {InsertionPlace=} place explicit insertion place
6221
+ * @returns {void}
6154
6222
  */
6155
- // Every insertion mode inserts a processing instruction where it inserts a
6156
- // comment (§13.2.6.4). `skip.comments` drops comments alone.
6157
6223
  const insertCommentOrProcessingInstruction = (t, place) => {
6158
6224
  const isPi = t.type === TOKEN_PROCESSING_INSTRUCTION;
6159
6225
  if (skipComments && !isPi) return;
@@ -6166,11 +6232,14 @@ const insertCommentOrProcessingInstruction = (t, place) => {
6166
6232
  );
6167
6233
  };
6168
6234
 
6169
- const insertHtmlElement = (
6170
- /** @type {string} */ tagName,
6171
- /** @type {AttributeRun} */ attrs,
6172
- /** @type {TagPos | null} */ pos
6173
- ) => {
6235
+ /**
6236
+ * Inserts an HTML element and pushes it onto the open element stack.
6237
+ * @param {string} tagName tag name
6238
+ * @param {AttributeRun} attrs the element's attributes
6239
+ * @param {TagPos | null} pos source position, `null` for a synthesized element
6240
+ * @returns {HtmlElement} the inserted element
6241
+ */
6242
+ const insertHtmlElement = (tagName, attrs, pos) => {
6174
6243
  const el = mkEl(tagName, NS_HTML, attrs, pos);
6175
6244
  const place = appropriatePlace();
6176
6245
  insertAtPlace(place, el);
@@ -6178,12 +6247,15 @@ const insertHtmlElement = (
6178
6247
  return el;
6179
6248
  };
6180
6249
 
6181
- const insertForeignElement = (
6182
- /** @type {string} */ tagName,
6183
- /** @type {number} */ ns,
6184
- /** @type {AttributeRun} */ attrs,
6185
- /** @type {TagPos | null} */ pos
6186
- ) => {
6250
+ /**
6251
+ * Inserts a MathML/SVG element and pushes it onto the open element stack.
6252
+ * @param {string} tagName tag name
6253
+ * @param {number} ns namespace flag (`NS_MATHML`, `NS_SVG`)
6254
+ * @param {AttributeRun} attrs the element's attributes
6255
+ * @param {TagPos | null} pos source position, `null` for a synthesized element
6256
+ * @returns {HtmlElement} the inserted element
6257
+ */
6258
+ const insertForeignElement = (tagName, ns, attrs, pos) => {
6187
6259
  const el = mkEl(tagName, ns, attrs, pos);
6188
6260
  const place = appropriatePlace();
6189
6261
  insertAtPlace(place, el);
@@ -6198,10 +6270,12 @@ const insertForeignElement = (
6198
6270
  const SCOPE_DEFAULT = 0;
6199
6271
  const SCOPE_BUTTON = 1;
6200
6272
  const SCOPE_LIST_ITEM = 2;
6201
- const isBoundaryForKind = (
6202
- /** @type {HtmlElement} */ el,
6203
- /** @type {number} */ kind
6204
- ) => {
6273
+ /**
6274
+ * @param {HtmlElement} el element to test
6275
+ * @param {number} kind scope kind (`SCOPE_DEFAULT`, `SCOPE_BUTTON`, `SCOPE_LIST_ITEM`)
6276
+ * @returns {boolean} true when the element bounds that scope
6277
+ */
6278
+ const isBoundaryForKind = (el, kind) => {
6205
6279
  if (isScopeBoundary(el)) return true;
6206
6280
  if (_namespaceOf(el) !== NS_HTML) return false;
6207
6281
  if (kind === SCOPE_BUTTON) return _tagNameOf(el) === "button";
@@ -6210,12 +6284,14 @@ const isBoundaryForKind = (
6210
6284
  }
6211
6285
  return false;
6212
6286
  };
6213
- // "have an element in scope": walk the open stack from the top until the
6214
- // named HTML element is found (true) or a scope boundary is hit (false).
6215
- const hasNameInScope = (
6216
- /** @type {string} */ tagName,
6217
- /** @type {number} */ kind
6218
- ) => {
6287
+ /**
6288
+ * "have an element in scope": walks the open stack from the top until the
6289
+ * named HTML element is found (true) or a scope boundary is hit (false).
6290
+ * @param {string} tagName tag name to look for
6291
+ * @param {number} kind scope kind (`SCOPE_DEFAULT`, `SCOPE_BUTTON`, `SCOPE_LIST_ITEM`)
6292
+ * @returns {boolean} true when the element is in that scope
6293
+ */
6294
+ const hasNameInScope = (tagName, kind) => {
6219
6295
  for (let i = open.length - 1; i >= 0; i--) {
6220
6296
  const el = open[i];
6221
6297
  if (_namespaceOf(el) === NS_HTML && _tagNameOf(el) === tagName) {
@@ -6225,13 +6301,26 @@ const hasNameInScope = (
6225
6301
  }
6226
6302
  return false;
6227
6303
  };
6228
- const inScope = (/** @type {string} */ tagName) =>
6229
- hasNameInScope(tagName, SCOPE_DEFAULT);
6230
- const inButtonScope = (/** @type {string} */ tagName) =>
6231
- hasNameInScope(tagName, SCOPE_BUTTON);
6232
- const inListItemScope = (/** @type {string} */ tagName) =>
6233
- hasNameInScope(tagName, SCOPE_LIST_ITEM);
6234
- const inScopeEl = (/** @type {HtmlElement} */ target) => {
6304
+ /**
6305
+ * @param {string} tagName tag name to look for
6306
+ * @returns {boolean} true when the element is in scope
6307
+ */
6308
+ const inScope = (tagName) => hasNameInScope(tagName, SCOPE_DEFAULT);
6309
+ /**
6310
+ * @param {string} tagName tag name to look for
6311
+ * @returns {boolean} true when the element is in button scope
6312
+ */
6313
+ const inButtonScope = (tagName) => hasNameInScope(tagName, SCOPE_BUTTON);
6314
+ /**
6315
+ * @param {string} tagName tag name to look for
6316
+ * @returns {boolean} true when the element is in list item scope
6317
+ */
6318
+ const inListItemScope = (tagName) => hasNameInScope(tagName, SCOPE_LIST_ITEM);
6319
+ /**
6320
+ * @param {HtmlElement} target element to look for
6321
+ * @returns {boolean} true when that element itself is in scope
6322
+ */
6323
+ const inScopeEl = (target) => {
6235
6324
  for (let i = open.length - 1; i >= 0; i--) {
6236
6325
  const el = open[i];
6237
6326
  if (el === target) return true;
@@ -6239,8 +6328,11 @@ const inScopeEl = (/** @type {HtmlElement} */ target) => {
6239
6328
  }
6240
6329
  return false;
6241
6330
  };
6242
- // `target` is a single tag name (the common case) or a Set of names.
6243
- const inTableScope = (/** @type {string | Set<string>} */ target) => {
6331
+ /**
6332
+ * @param {string | Set<string>} target a single tag name (the common case) or a set of names
6333
+ * @returns {boolean} true when one of them is in table scope
6334
+ */
6335
+ const inTableScope = (target) => {
6244
6336
  const set = typeof target === "string" ? null : target;
6245
6337
  for (let i = open.length - 1; i >= 0; i--) {
6246
6338
  const el = open[i];
@@ -6279,15 +6371,25 @@ const generateImpliedEndTagsThorough = () => {
6279
6371
  };
6280
6372
 
6281
6373
  // ---- active formatting elements ----
6282
- // `splice` allocates an array for the removed element on every call; the list
6283
- // is short, so shifting in place is both cheaper and allocation-free.
6284
- /** @type {(i: number) => void} */
6374
+
6375
+ /**
6376
+ * `splice` allocates an array for the removed element on every call; the list
6377
+ * is short, so shifting in place is both cheaper and allocation-free.
6378
+ * @param {number} i position in `afe` to remove
6379
+ * @returns {void}
6380
+ */
6285
6381
  const afeRemoveAt = (i) => {
6286
6382
  const last = afe.length - 1;
6287
6383
  for (let k = i; k < last; k++) afe[k] = afe[k + 1];
6288
6384
  afe.pop();
6289
6385
  };
6290
- const pushAfe = (/** @type {HtmlElement} */ el) => {
6386
+ /**
6387
+ * Pushes a formatting element, applying the "Noah's Ark" clause: a third
6388
+ * equal entry since the last marker drops the earliest of them.
6389
+ * @param {HtmlElement} el formatting element to push
6390
+ * @returns {void}
6391
+ */
6392
+ const pushAfe = (el) => {
6291
6393
  let count = 0;
6292
6394
  for (let i = afe.length - 1; i >= 0; i--) {
6293
6395
  const e = afe[i];
@@ -6349,7 +6451,12 @@ const closePElement = () => {
6349
6451
  }
6350
6452
  };
6351
6453
 
6352
- const popUntil = (/** @type {string} */ tagName) => {
6454
+ /**
6455
+ * Pops the open element stack through the named HTML element.
6456
+ * @param {string} tagName tag name to pop through
6457
+ * @returns {void}
6458
+ */
6459
+ const popUntil = (tagName) => {
6353
6460
  while (open.length) {
6354
6461
  const el = /** @type {HtmlElement} */ (popOpen());
6355
6462
  _nodeEnds[el] = tokenEnd;
@@ -6358,7 +6465,12 @@ const popUntil = (/** @type {string} */ tagName) => {
6358
6465
  }
6359
6466
  }
6360
6467
  };
6361
- const popUntilOneOf = (/** @type {Set<string>} */ set) => {
6468
+ /**
6469
+ * Pops the open element stack through the first of the named HTML elements.
6470
+ * @param {Set<string>} set tag names to pop through
6471
+ * @returns {void}
6472
+ */
6473
+ const popUntilOneOf = (set) => {
6362
6474
  while (open.length) {
6363
6475
  const el = /** @type {HtmlElement} */ (popOpen());
6364
6476
  _nodeEnds[el] = tokenEnd;
@@ -6432,14 +6544,14 @@ const resetInsertionMode = () => {
6432
6544
  };
6433
6545
 
6434
6546
  // ---------- token processing ----------
6435
- // Split a character token's leading whitespace; per the spec each character
6436
- // is its own token, so a mixed run can straddle a mode change. Inserts the
6437
- // leading whitespace when `insert`, returns the non-whitespace remainder
6438
- // token (or null when the token was entirely whitespace).
6439
- const leadingWs = (
6440
- /** @type {CharToken} */ t,
6441
- /** @type {boolean} */ insert
6442
- ) => {
6547
+ /**
6548
+ * Splits a character token's leading whitespace; per the spec each character
6549
+ * is its own token, so a mixed run can straddle a mode change.
6550
+ * @param {CharToken} t character token
6551
+ * @param {boolean} insert whether to insert the leading whitespace
6552
+ * @returns {CharToken | null} the non-whitespace remainder, `null` when the token was all whitespace
6553
+ */
6554
+ const leadingWs = (t, insert) => {
6443
6555
  const m = /^[\t\n\f\r ]+/.exec(t.data);
6444
6556
  const ws = m ? m[0] : "";
6445
6557
  // `t.data` is decoded, the offsets are raw, so counting one in the other
@@ -6458,7 +6570,12 @@ const leadingWs = (
6458
6570
  };
6459
6571
  };
6460
6572
 
6461
- const process = (/** @type {Token} */ t) => {
6573
+ /**
6574
+ * Runs one token through tree construction (§13.2.6).
6575
+ * @param {Token} t token to process
6576
+ * @returns {void}
6577
+ */
6578
+ const process = (t) => {
6462
6579
  // Track the current token's end so explicit closes can set element `.end`.
6463
6580
  // Dispatch on `type` instead of the `in` operator, which goes megamorphic
6464
6581
  // across the token union and shows up on the per-token hot path.
@@ -6480,10 +6597,12 @@ const process = (/** @type {Token} */ t) => {
6480
6597
  runMode(t);
6481
6598
  };
6482
6599
 
6483
- const shouldUseForeignRules = (
6484
- /** @type {HtmlElement} */ ac,
6485
- /** @type {Token} */ t
6486
- ) => {
6600
+ /**
6601
+ * @param {HtmlElement} ac adjusted current node
6602
+ * @param {Token} t token to process
6603
+ * @returns {boolean} true when the token follows the foreign content rules
6604
+ */
6605
+ const shouldUseForeignRules = (ac, t) => {
6487
6606
  if (_namespaceOf(ac) === NS_HTML) return false;
6488
6607
  if (t.type === TOKEN_START_TAG) {
6489
6608
  if (
@@ -6513,7 +6632,12 @@ const shouldUseForeignRules = (
6513
6632
  return false;
6514
6633
  };
6515
6634
 
6516
- const foreignContent = (/** @type {Token} */ t) => {
6635
+ /**
6636
+ * "in foreign content" rules (§13.2.6.5).
6637
+ * @param {Token} t token to process
6638
+ * @returns {void}
6639
+ */
6640
+ const foreignContent = (t) => {
6517
6641
  if (t.type === TOKEN_CHAR) {
6518
6642
  const data = inputHasNul ? t.data.replace(/\0/g, "�") : t.data;
6519
6643
  insertCharacters(data, t.start, t.end);
@@ -6612,10 +6736,13 @@ const foreignContent = (/** @type {Token} */ t) => {
6612
6736
  };
6613
6737
 
6614
6738
  // ---------- adoption agency algorithm ----------
6615
- const adoptionAgency = (
6616
- /** @type {string} */ subject,
6617
- /** @type {TagPos | null} */ pos
6618
- ) => {
6739
+ /**
6740
+ * Adoption agency algorithm (§13.2.6.4.7).
6741
+ * @param {string} subject tag name of the end tag being handled
6742
+ * @param {TagPos | null} pos source position of that end tag
6743
+ * @returns {boolean} false when the caller must fall back to "any other end tag"
6744
+ */
6745
+ const adoptionAgency = (subject, pos) => {
6619
6746
  // step 1
6620
6747
  const c = cur();
6621
6748
  if (
@@ -6752,7 +6879,11 @@ const adoptionAgency = (
6752
6879
  return true;
6753
6880
  };
6754
6881
 
6755
- const placeForCommonAncestor = (/** @type {HtmlElement} */ commonAncestor) => {
6882
+ /**
6883
+ * @param {HtmlElement} commonAncestor common ancestor node of the adoption agency
6884
+ * @returns {InsertionPlace} where the moved node goes
6885
+ */
6886
+ const placeForCommonAncestor = (commonAncestor) => {
6756
6887
  if (
6757
6888
  TABLE_CONTEXT.has(_tagNameOf(commonAncestor)) &&
6758
6889
  _namespaceOf(commonAncestor) === NS_HTML
@@ -6766,7 +6897,12 @@ const placeForCommonAncestor = (/** @type {HtmlElement} */ commonAncestor) => {
6766
6897
  return { parent: commonAncestor, beforeNode: 0 };
6767
6898
  };
6768
6899
 
6769
- const detach = (/** @type {HtmlNodeRef} */ node) => {
6900
+ /**
6901
+ * Unlinks a node from its parent so it can be inserted again.
6902
+ * @param {HtmlNodeRef} node node to detach
6903
+ * @returns {void}
6904
+ */
6905
+ const detach = (node) => {
6770
6906
  const p = _nodeParents[node];
6771
6907
  if (p === 0) return;
6772
6908
  let prev = 0;
@@ -6788,11 +6924,13 @@ const detach = (/** @type {HtmlNodeRef} */ node) => {
6788
6924
  /** @type {Record<string, (t: Token) => void>} */
6789
6925
  const modes = {};
6790
6926
 
6791
- // Dispatch the current insertion mode. An integer switch (cases ordered by
6792
- // frequency) keeps each `modes.x(t)` call site monomorphic, where a keyed
6793
- // `modes[mode]` load + indirect call would defeat inlining on the per-token
6794
- // hot path.
6795
- const runMode = (/** @type {Token} */ t) => {
6927
+ /**
6928
+ * Dispatches the current insertion mode. An integer switch keeps each
6929
+ * `modes.x(t)` call site monomorphic, where `modes[mode]` would not.
6930
+ * @param {Token} t token to process
6931
+ * @returns {void}
6932
+ */
6933
+ const runMode = (t) => {
6796
6934
  switch (mode) {
6797
6935
  case MODE_IN_BODY:
6798
6936
  return modes.inBody(t);
@@ -7137,9 +7275,13 @@ const closeIfPInButtonScope = () => {
7137
7275
  if (inButtonScope("p")) closePElement();
7138
7276
  };
7139
7277
 
7140
- // "any other end tag" in body: pop to the matching open element, stopping at
7141
- // the first special element; also the adoption agency's no-element fallback.
7142
- const anyOtherEndTag = (/** @type {string} */ name) => {
7278
+ /**
7279
+ * "any other end tag" in body: pops to the matching open element, stopping at
7280
+ * the first special element; also the adoption agency's no-element fallback.
7281
+ * @param {string} name tag name of the end tag
7282
+ * @returns {void}
7283
+ */
7284
+ const anyOtherEndTag = (name) => {
7143
7285
  for (let i = open.length - 1; i >= 0; i--) {
7144
7286
  const node = open[i];
7145
7287
  if (_namespaceOf(node) === NS_HTML && _tagNameOf(node) === name) {
@@ -7154,7 +7296,12 @@ const anyOtherEndTag = (/** @type {string} */ name) => {
7154
7296
  }
7155
7297
  };
7156
7298
 
7157
- const startTagInBody = (/** @type {StartTagToken} */ t) => {
7299
+ /**
7300
+ * "in body" start tag rules (§13.2.6.4.7).
7301
+ * @param {StartTagToken} t start tag token
7302
+ * @returns {void}
7303
+ */
7304
+ const startTagInBody = (t) => {
7158
7305
  const name = t.name;
7159
7306
  if (name === "html") {
7160
7307
  if (open.some(isHtmlTemplateEl)) {
@@ -7479,7 +7626,12 @@ const startTagInBody = (/** @type {StartTagToken} */ t) => {
7479
7626
  insertHtmlElement(name, t.attrs, t.pos);
7480
7627
  };
7481
7628
 
7482
- const endTagInBody = (/** @type {EndTagToken} */ t) => {
7629
+ /**
7630
+ * "in body" end tag rules (§13.2.6.4.7).
7631
+ * @param {EndTagToken} t end tag token
7632
+ * @returns {void}
7633
+ */
7634
+ const endTagInBody = (t) => {
7483
7635
  const name = t.name;
7484
7636
  if (name === "template") return modes.inHead(t);
7485
7637
  if (name === "select") {
@@ -7573,15 +7725,24 @@ const endTagInBody = (/** @type {EndTagToken} */ t) => {
7573
7725
  anyOtherEndTag(name);
7574
7726
  };
7575
7727
 
7576
- // generic RCDATA/RAWTEXT: tokenizer already emits the text + end tag, so we
7577
- // just insert the element and switch to "text" mode; text mode appends chars
7578
- // and the matching end tag pops.
7579
- const genericRawtext = (/** @type {StartTagToken} */ t) => {
7728
+ /**
7729
+ * Generic RAWTEXT: inserts the element and switches to "text" mode, which
7730
+ * appends the characters the tokenizer already emits and pops on the end tag.
7731
+ * @param {StartTagToken} t start tag token
7732
+ * @returns {void}
7733
+ */
7734
+ const genericRawtext = (t) => {
7580
7735
  insertHtmlElement(t.name, t.attrs, t.pos);
7581
7736
  originalMode = mode;
7582
7737
  mode = MODE_TEXT;
7583
7738
  };
7584
- const genericRcdata = (/** @type {StartTagToken} */ t, swallow = false) => {
7739
+ /**
7740
+ * Generic RCDATA, as `genericRawtext` for the elements whose text is decoded.
7741
+ * @param {StartTagToken} t start tag token
7742
+ * @param {boolean=} swallow whether a newline right after the tag is dropped
7743
+ * @returns {void}
7744
+ */
7745
+ const genericRcdata = (t, swallow = false) => {
7585
7746
  insertHtmlElement(t.name, t.attrs, t.pos);
7586
7747
  if (swallow) t.swallowNewline = true;
7587
7748
  originalMode = mode;
@@ -11508,11 +11669,19 @@ const _settleFosteredRuns = (path) => {
11508
11669
  _fosterHolders = holders;
11509
11670
  };
11510
11671
 
11672
+ // Stands in for what the print chooses the bytes of: a raw-text body it
11673
+ // rewrites whole, or an attribute value it respells.
11674
+ const _DIGEST_OPAQUE = "\u0003";
11675
+
11511
11676
  /**
11512
11677
  * The tree as a string, for the one check the printer cannot make from the tree
11513
- * alone: whether what it printed parses back to what it printed from. Values are
11514
- * compared as the DOM holds them, so the same attribute written with a different
11515
- * quote or escape is the same attribute.
11678
+ * alone: whether what it printed parses back to what it printed from. Read in
11679
+ * the form the print is licensed to leave — comments gone, a run of text
11680
+ * collapsed and joined, the elements and attributes the options drop dropped,
11681
+ * every value the printer respells standing in for itself — so an equal digest
11682
+ * says the print moved nothing, not that it changed nothing. What a repair does
11683
+ * move (a node under another parent, an attribute a bad quote swallowed)
11684
+ * reshapes the tree, and that is what parts the two.
11516
11685
  * @param {HtmlNodeRef} root the node whose children are read
11517
11686
  * @returns {string} a rendering equal for equal trees
11518
11687
  */
@@ -11525,6 +11694,15 @@ const _digest = (root) => {
11525
11694
  const stack = [];
11526
11695
  const first = A.firstChild(root);
11527
11696
  if (first !== 0) stack.push(first, 0);
11697
+ // A run of text siblings reads as one string: dropping the comment or the
11698
+ // empty element between two of them is what joins them in the output.
11699
+ let pendingText = "";
11700
+ let pendingDepth = 0;
11701
+ const flushText = () => {
11702
+ const text = _asciiTrim(pendingText);
11703
+ pendingText = "";
11704
+ if (text !== "") out.push(`${pendingDepth}\u0001t\u0001${text}`);
11705
+ };
11528
11706
  while (stack.length !== 0) {
11529
11707
  const depth = /** @type {number} */ (stack.pop());
11530
11708
  const node = /** @type {HtmlNodeRef} */ (stack.pop());
@@ -11532,27 +11710,87 @@ const _digest = (root) => {
11532
11710
  const next = A.nextSibling(node);
11533
11711
  if (next !== 0) stack.push(next, depth);
11534
11712
  const type = A.type(node);
11535
- if (type !== NodeType.Element) {
11536
- out.push(`${depth}\u0001${type}\u0001${A.data(node)}`);
11713
+ // Not flushed: a comment minifying drops leaves the text around it one run.
11714
+ if (type === NodeType.Comment) continue;
11715
+ if (type === NodeType.Text) {
11716
+ if (depth !== pendingDepth) flushText();
11717
+ pendingDepth = depth;
11718
+ const parent = A.parentOf(node);
11719
+ pendingText +=
11720
+ parent !== 0 && LITERAL_TEXT_PARENTS.has(A.tagName(parent))
11721
+ ? _DIGEST_OPAQUE
11722
+ : collapseWhitespaceRuns(A.data(node));
11537
11723
  continue;
11538
11724
  }
11539
- out.push(
11540
- `${depth}\u0001e\u0001${A.namespace(node)}\u0001${A.tagName(node)}`
11541
- );
11542
- const attributes = [];
11543
- for (const a of A.attributes(node)) {
11544
- // The value as the DOM holds it: the same value can be written with a
11545
- // different quote and a different escape and still be the same attribute.
11546
- attributes.push(
11547
- `${a.serializedName || a.name}=${decodeEntities(a.value, true)}`
11548
- );
11725
+ if (type === NodeType.Element) {
11726
+ // Same as a comment: an element the print drops joins what surrounds it.
11727
+ if (
11728
+ _isRemovableEmptyElement(A, node) ||
11729
+ (_absorbedStyles !== null && _absorbedStyles.has(node))
11730
+ ) {
11731
+ continue;
11732
+ }
11733
+ flushText();
11734
+ const elementName = A.tagName(node);
11735
+ const html = A.namespace(node) === NS_HTML;
11736
+ out.push(`${depth}\u0001e\u0001${A.namespace(node)}\u0001${elementName}`);
11737
+ // Per element, as in `_minifyOpenTag`: every attribute of one tag gets the
11738
+ // same answer out of these tables.
11739
+ const markers =
11740
+ html && _removeRedundantAttributes !== "none"
11741
+ ? REDUNDANT_TYPE_ATTRIBUTES[elementName]
11742
+ : undefined;
11743
+ const defaults =
11744
+ html && _removeRedundantAttributes === "all"
11745
+ ? REDUNDANT_DEFAULT_ATTRIBUTES[elementName]
11746
+ : undefined;
11747
+ const attributes = [];
11748
+ for (const a of A.attributes(node)) {
11749
+ if (
11750
+ html &&
11751
+ (((markers !== undefined || defaults !== undefined) &&
11752
+ _isRedundantAttribute(
11753
+ markers,
11754
+ defaults,
11755
+ elementName,
11756
+ a.name,
11757
+ a.value
11758
+ )) ||
11759
+ _isRemovableEmptyAttribute(elementName, a.name, a.value))
11760
+ ) {
11761
+ continue;
11762
+ }
11763
+ const name = a.serializedName || a.name;
11764
+ if (REWRITABLE_ATTRIBUTES.has(a.name) || a.name === "srcdoc") {
11765
+ attributes.push(`${name}=${_DIGEST_OPAQUE}`);
11766
+ continue;
11767
+ }
11768
+ // The value as the DOM holds it — the same value can be written with a
11769
+ // different quote and a different escape and still be the same
11770
+ // attribute — with a boolean spelled by its own name as the bare
11771
+ // presence the printer writes it back as.
11772
+ attributes.push(
11773
+ `${name}=${
11774
+ html &&
11775
+ a.value.length === a.name.length &&
11776
+ _isBooleanAttribute(elementName, a.name) &&
11777
+ _asciiLowerCase(a.value) === a.name
11778
+ ? ""
11779
+ : decodeEntities(a.value, true)
11780
+ }`
11781
+ );
11782
+ }
11783
+ attributes.sort();
11784
+ out.push(attributes.join("\u0002"));
11785
+ const content = A.templateContent(node);
11786
+ const inner = content !== 0 ? A.firstChild(content) : A.firstChild(node);
11787
+ if (inner !== 0) stack.push(inner, depth + 1);
11788
+ continue;
11549
11789
  }
11550
- attributes.sort();
11551
- out.push(attributes.join("\u0002"));
11552
- const content = A.templateContent(node);
11553
- const inner = content !== 0 ? A.firstChild(content) : A.firstChild(node);
11554
- if (inner !== 0) stack.push(inner, depth + 1);
11790
+ flushText();
11791
+ out.push(`${depth}\u0001${type}\u0001${A.data(node)}`);
11555
11792
  }
11793
+ flushText();
11556
11794
  return out.join("\n");
11557
11795
  };
11558
11796
 
@@ -12549,12 +12787,11 @@ const grammar = (input, visitors, writer, options) => {
12549
12787
  _printDepth = 0;
12550
12788
  try {
12551
12789
  const root = parseHtml(input, 0, options);
12552
- let digest = "";
12553
12790
  if (writer !== undefined) {
12554
12791
  if (_fosteredLog !== null) _settleFosteredRuns(A);
12555
12792
  // A shape no arrangement of tags reaches, or foster parenting — whose repair
12556
12793
  // is checked, not trusted. No real document reaches either.
12557
- if (_printFromSource || _fosteredLog !== null) digest = _digest(root);
12794
+ const verify = _printFromSource || _fosteredLog !== null;
12558
12795
  _walkRun(_nodeParents[root], root, _nodeNextSiblings[root]);
12559
12796
  // A single root: take its text as the output (the printer read every node's
12560
12797
  // source into the store during the walk, so this needs no source). A root
@@ -12562,9 +12799,20 @@ const grammar = (input, visitors, writer, options) => {
12562
12799
  if (_printOpen[0] === 1) writer.dropStore();
12563
12800
  else writer.take(root);
12564
12801
  // Print, then read the output back: the source always parses to its own
12565
- // tree, so it stands in when the printed form does not.
12566
- if (digest !== "") {
12567
- const printed = writer.result();
12802
+ // tree, so it stands in when the printed form does not. Digested after the
12803
+ // walk, which is what settles the elements the print left out.
12804
+ if (verify) {
12805
+ const digest = _digest(root);
12806
+ // A deferred body prints as a marker, which reads back as text where
12807
+ // its node stood — put it back, or the whole document is handed back.
12808
+ const printed =
12809
+ _deferEmbeddedSource === undefined ||
12810
+ _deferEmbeddedSource.length === 0
12811
+ ? writer.result()
12812
+ : declineDeferredWrites(writer.result(), _deferEmbeddedSource);
12813
+ // Both are keyed by node id, which the next parse hands out afresh.
12814
+ _emptyElements.clear();
12815
+ _absorbedStyles = null;
12568
12816
  if (_digest(parseHtml(printed, 0, options)) !== digest) {
12569
12817
  writer.replaceAll(input);
12570
12818
  }