html-minifier-next 8.6.0 → 8.7.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.
- package/README.md +5 -1
- package/dist/types/htmlminifier.d.ts.map +1 -1
- package/dist/types/htmlparser.d.ts +1 -0
- package/dist/types/htmlparser.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/htmlminifier.js +136 -40
- package/src/htmlparser.js +4 -0
package/README.md
CHANGED
|
@@ -249,6 +249,8 @@ you get the following output (condensed, `\n` represents an actual line break):
|
|
|
249
249
|
|
|
250
250
|
Where the modifiers disagree, the preserving one wins—`conservativeCollapse` and `preserveLineBreaks` do not let `collapseInlineTagWhitespace` remove a space or line break entirely.
|
|
251
251
|
|
|
252
|
+
Regardless of these options, whitespace at the start and end of the output goes, and so do lines that a removed comment or tag leaves with nothing but whitespace (blank lines of the source stay). Whitespace stays, however, where it’s kept verbatim (in `pre` or `textarea`, or in `pre`, `textarea`, or `script` content left open at the end), next to custom fragments where their handling keeps it (see `trimCustomFragments`), at either end of partial markup (cf. `partialMarkup`), and where `conservativeCollapse` or `preserveLineBreaks` keep it.
|
|
253
|
+
|
|
252
254
|
### Sorting attributes and style classes
|
|
253
255
|
|
|
254
256
|
`sortAttributes` and `sortClassNames` reorder attributes and class names by frequency, so that repeated markup looks more alike. This doesn’t change the plain-text size of the output, only how well it compresses: `sortAttributes` makes Gzip and Brotli output slightly smaller on average, though not on every page (see [“Optimizing for compression”](#optimizing-for-compression)); `sortClassNames` doesn’t help, and alongside `sortAttributes` cancels most of its gain with Brotli.
|
|
@@ -677,7 +679,7 @@ By default, HMN parses markup into a complete tree structure, then modifies it (
|
|
|
677
679
|
|
|
678
680
|
_Input markup (e.g., `<p id="">foo`) → Internal representation of markup in a form of tree (e.g., `{ tag: "p", attr: "id", children: ["foo"] }`) → Transformation of internal representation (e.g., removal of `id` attribute) → Output of resulting markup (e.g., `<p>foo</p>`)_
|
|
679
681
|
|
|
680
|
-
For partial HTML fragments (such as template includes, SSI fragments, or closing tags without opening tags), use the `partialMarkup: true` option. This preserves stray end tags (closing tags without corresponding opening tags) and prevents auto-closing of unclosed tags at the end of input. Note that normal HTML auto-closing rules still apply during parsing—for example, a closing parent tag will still auto-close its unclosed child elements.
|
|
682
|
+
For partial HTML fragments (such as template includes, SSI fragments, or closing tags without opening tags), use the `partialMarkup: true` option. This preserves stray end tags (closing tags without corresponding opening tags) and prevents auto-closing of unclosed tags at the end of input. Note that normal HTML auto-closing rules still apply during parsing—for example, a closing parent tag will still auto-close its unclosed child elements. Whitespace at the start and end of a fragment stays, too, as it may separate the fragment from what surrounds it (unless `collapseWhitespace` removes it).
|
|
681
683
|
|
|
682
684
|
To validate complete HTML markup, use [the W3C validator](https://validator.w3.org/) or one of [several validator packages](https://meiert.com/blog/html-validator-packages/).
|
|
683
685
|
|
|
@@ -842,6 +844,8 @@ Parameters:
|
|
|
842
844
|
|
|
843
845
|
To compare branches (A/B run), execute `npm run benchmark -- --save` on `main`, then `npm run benchmark` on the branch to see the deltas. Add `--core` on both ends when measuring changes to HMN rather than bundled minifiers, or `--cold` when measuring changes to the CSS, JS, or SVG minification paths.
|
|
844
846
|
|
|
847
|
+
For changes of around 1% or less, use `npm run benchmark:ab -- main` instead: It times the working tree against any Git ref in the same session, runs both in turn per file, averages both loading orders, and reports `collapseWhitespace` on and off separately. Add `--aa` to compare the ref with a copy of itself—the noise floor any finding must clear. The script’s header lists further parameters.
|
|
848
|
+
|
|
845
849
|
Reported times are the *fastest* iteration, not the median: Interference can only make a run slower, so the minimum is the most stable estimate. Each run also reports its noise—how far the reported figure moves between the first and second half of the iterations—and any delta smaller than that is marked `within noise` rather than shown as a win or a regression. Raise `--iterations` until the noise sits below the change you are trying to measure.
|
|
846
850
|
|
|
847
851
|
#### Profiling
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AA4E/D,YAAkB,aAAa,GAI/B;IAAe,IAAI,EAAZ,MAAM,CACb;IAAgB,KAAK,AAArB,CACA,EADO,MAAM,CACb;IAAgB,KAAK,AAArB,CACA,EADO,MAAM,CACb;IAAgB,YAAY,AAA5B,CACA,EADO,MAAM,CACb;IAAgB,UAAU,AAA1B,CACA,EADO,MAAM,CACb;IAAgB,WAAW,AAA3B,CAA4B,EAArB,MAAM,CAAe;CAAA,CAAA;AAI5B,YAAkB,eAAe,GAIjC;;;;;;;IAA2G,qBAAqB,AAAhI,CAMA,EANO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,qBAAqB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,KAAK,OAAO,CAMxG;;;;;;;;;;IAAuE,YAAY,AAAnF,CASA,EATO,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,KAAK,OAAO,CAAC,GAAG,IAAI,CASpE;;;;;;;;;IAA8D,WAAW,AAAzE,CAQA,EARO,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG,IAAI,CAQ3D;;;;;;;;;IAA4C,YAAY,AAAxD,CAQA,EARO,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,IAAI,CAQzC;;;;;;;IAA0H,iBAAiB,AAA3I,CAMA,EANO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,SAAS,EAAE,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,KAAK,OAAO,CAMvH;;;;;;;;;;IAAgB,QAAQ,AAAxB,CASA,EATO,MAAM,CASb;;;;;;;;;;IAAgB,OAAO,AAAvB,CASA,EATO,MAAM,CASb;;;;;;;;;;IAAgB,QAAQ,AAAxB,CASA,EATO,MAAM,CASb;;;;;;;;IAAiB,aAAa,AAA9B,CAOC,EAPM,OAAO,CAOb;;;;;;;;;IAAiB,2BAA2B,AAA5C,CAQD,EARQ,OAAO,CAQf;;;;;;;;IAAiB,yBAAyB,AAA1C,CAOA,EAPO,OAAO,CAOd;;;;;;;;IAAiB,2BAA2B,AAA5C,CAOA,EAPO,OAAO,CAOd;;;;;;;;;;IAAiB,qBAAqB,AAAtC,CASA,EATO,OAAO,CASd;;;;;;;;IAAiB,kBAAkB,AAAnC,CAOA,EAPO,OAAO,CAOd;;;;;;;;IAAiB,oBAAoB,AAArC,CAOA,EAPO,OAAO,CAOd;;;;;;;;IAAiB,qBAAqB,AAAtC,CAOA,EAPO,OAAO,CAOd;;;;;;;IAAiB,oBAAoB,AAArC,CAMA,EANO,OAAO,CAMd;;;;;;;;IAAkB,gBAAgB,AAAlC,CAOA,EAPO,MAAM,EAAE,CAOf;;;;;;IAAgB,kBAAkB,AAAlC,CAKA,EALO,MAAM,CAKb;;;;;;IAA4B,kBAAkB,AAA9C,CAKA,EALO,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAKzB;;;;;;;IAAkB,qBAAqB,AAAvC,CAMA,EANO,MAAM,EAAE,CAMf;;;;;;;;;IAAiB,qBAAqB,AAAtC,CAQA,EARO,OAAO,CAQd;;;;;;;IAAiB,cAAc,AAA/B,CAOA,EAPO,OAAO,CAOd;;;;;;;;IAAkB,oBAAoB,AAAtC,CAOA,EAPO,MAAM,EAAE,CAOf;;;;;;;;;IAAkB,qBAAqB,AAAvC,CAQA,EARO,MAAM,EAAE,CAQf;;;;;;;IAAiB,wBAAwB,AAAzC,CAMA,EANO,OAAO,CAMd;;;;;;;IAA2B,oBAAoB,AAA/C,CAMA,EANO,SAAS,CAAC,MAAM,CAAC,CAMxB;;;;;;;;IAAiB,gBAAgB,AAAjC,CAOA,EAPO,OAAO,CAOd;;;;;;;;IAAoC,GAAG,AAAvC,CAOA,EAPO,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAOjC;;;;;;;;IAAgB,cAAc,AAA9B,CAOA,EAPO,MAAM,CAOb;;;;;;;;IAAgB,aAAa,AAA7B,CAOA,EAPO,MAAM,CAOb;;;;;;;;IAAiB,YAAY,AAA7B,CAOA,EAPO,OAAO,CAOd;;;;;;;;;;IAAwK,SAAS,AAAjL,CASA,EATO,OAAO,GAAG,OAAO,CAAC,OAAO,cAAc,EAAE,gBAAgB,CAAC,OAAO,cAAc,EAAE,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CASrK;;;;;;;;;;;;;;IAAqK,QAAQ,AAA7K,CAaA,EAbO,OAAO,GAAG,OAAO,QAAQ,EAAE,aAAa,GAAG;QAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAC,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAalK;;;;;;;;;;IAA2F,UAAU,AAArG,CASA,EATO,OAAO,GAAG,MAAM,GAAG;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAC,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CASxF;;;;;;;;;;;IAA0B,SAAS,AAAnC,CAUA,EAVO,OAAO,GAAG,MAAM,CAUvB;;;;;;;IAAiB,wBAAwB,AAAzC,CAMA,EANO,OAAO,CAMd;;;;;;;;;;IAAiB,aAAa,AAA9B,CASA,EATO,OAAO,CASd;;;;;;;;IAAiB,kBAAkB,AAAnC,CAOA,EAPO,OAAO,CAOd;;;;;;;;IAAgB,MAAM,AAAtB,CAOA,EAPO,MAAM,CAOb;;;;;;;IAAiB,yBAAyB,AAA1C,CAMA,EANO,OAAO,CAMd;;;;;;;;;;IAAkB,cAAc,AAAhC,CASA,EATO,MAAM,EAAE,CASf;;;;;;;IAAoB,cAAc,AAAlC,CAMA,EANO,IAAI,GAAG,GAAG,CAMjB;;;;;;;IAAiB,qBAAqB,AAAtC,CAMA,EANO,OAAO,CAMd;;;;;;;;IAAiB,cAAc,AAA/B,CAOC,EAPM,OAAO,CAOb;;;;;;;IAAiB,2BAA2B,AAA5C,CAMD,EANQ,OAAO,CAMf;;;;;;;;;IAAgE,qBAAqB,AAArF,CAQA,EARO,OAAO,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,CAQ7D;;;;;;;;IAAiB,mBAAmB,AAApC,CAOA,EAPO,OAAO,CAOd;;;;;;;;;;;;;;;;;;;;;;;;;;IAAkB,yBAAyB,AAA3C,CAyBA,EAzBO,MAAM,EAAE,CAyBf;;;;;;;;IAAiB,kBAAkB,AAAnC,CAOA,EAPO,OAAO,CAOd;;;;;;;;IAAiB,yBAAyB,AAA1C,CAOA,EAPO,OAAO,CAOd;;;;;;;;;;IAAiB,mBAAmB,AAApC,CASA,EATO,OAAO,CASd;;;;;;;;;;;;;;;IAA0E,eAAe,AAAzF,CAcA,EAdO,OAAO,GAAG;QAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAC,CAcvE;;;;;;;;;IAAmE,cAAc,AAAjF,CAQA,EARO,OAAO,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,IAAI,CAAC,CAQhE;;;;;;;;;IAA+C,cAAc,AAA7D,CAQA,EARO,OAAO,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAQ5C;;;;;;;;;IAAiB,mBAAmB,AAApC,CAQA,EARO,OAAO,CAQd;;;;;;;IAAiB,eAAe,AAAhC,CAKF,EALS,OAAO,CAKhB;CAAA,CAAA;AA0EE,YAAkB,UAAU,GAG5B;;;;IAAe,IAAI,EAAZ,MAAM,CACb;;;;IAAe,IAAI,EAAZ,MAAM,CACb;;;;IAAe,IAAI,EAAZ,MAAM,CACb;;;;IAAe,KAAK,EAAb,MAAM,CACf;CAAA,CAAA;AAsyDD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,IAFhB;IAAE,GAAG,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,UAAU,CAAA;CAAE,CAQhE;AA2FD;;;;GAIG;AACH,eAAO,MAAM,MAAM,UAJR,MAAM,YACN,eAAe,KACb,OAAO,CAAC,MAAM,CAsE1B,CAAC;AAIF,OAAO,EACL,OAAO,EACP,SAAS,EACT,cAAc,EACf,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAeG,
|
|
1
|
+
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAeG,YAaG,iBAAiB,GAbV;IACR,KAAK,CAAC,WAAW;IACjB,GAAG,CAAC,WAAW;IACf,KAAK,CAAC,WAAW;IACjB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW;IACnB,QAAQ,CAAC,WAAW;IACpB,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;CACxC,CAAmB;AA6NvB,qBAAa,UAAU;IAMd,IAAI;IACJ,OAAO;IANd;;;OAGG;IACH,YAAY,IAAI,EAHL,MAGK,EAAE,OAAO,EAFd,iBAEc,EAGxB;IAEK,KAAK,kBA8yBV;CACF"}
|
package/package.json
CHANGED
package/src/htmlminifier.js
CHANGED
|
@@ -640,6 +640,10 @@ const RE_END_TAG = /^<\//;
|
|
|
640
640
|
const RE_WHITESPACE_ONLY = /^\s+$/;
|
|
641
641
|
const RE_TABS = /\t/g;
|
|
642
642
|
const RE_TRAILING_WHITESPACE = /\s$/;
|
|
643
|
+
const RE_LINE_BREAK_BLANKS_END = /(?:\r\n?|\n)[ \t\f]*$/;
|
|
644
|
+
const RE_BLANKS_LINE_BREAK_START = /^[ \t\f]*(?:\r\n?|\n)/;
|
|
645
|
+
const RE_BLANKS_START = /^[ \t\f]+/;
|
|
646
|
+
const RE_BLANKS_ONLY = /^[ \t\f]+$/;
|
|
643
647
|
|
|
644
648
|
// Pre-compiled patterns for `htmlmin:ignore` block content analysis
|
|
645
649
|
const RE_HTML_COMMENT_START = /^\s*<!--/;
|
|
@@ -1079,6 +1083,10 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1079
1083
|
let optionalStartTag = '';
|
|
1080
1084
|
let optionalEndTag = '';
|
|
1081
1085
|
let optionalEndTagEmitted = false;
|
|
1086
|
+
// Whether a node was removed since the last output
|
|
1087
|
+
let removed = false;
|
|
1088
|
+
// Whether the latest text was kept verbatim, as in `pre` or `script`
|
|
1089
|
+
let textVerbatim = false;
|
|
1082
1090
|
/** @type {string[]} */
|
|
1083
1091
|
const ignoredMarkupChunks = [];
|
|
1084
1092
|
/** @type {Array<RegExpExecArray | null>} */
|
|
@@ -1300,11 +1308,88 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1300
1308
|
function bufferPush(/** @type {string} */ entry, /** @type {boolean} */ isTag) {
|
|
1301
1309
|
buffer.push(entry);
|
|
1302
1310
|
bufferTags?.push(isTag);
|
|
1311
|
+
if (entry) removed = false;
|
|
1303
1312
|
}
|
|
1304
1313
|
|
|
1305
1314
|
function bufferTruncate(/** @type {number} */ length) {
|
|
1306
1315
|
buffer.length = Math.max(0, length);
|
|
1307
1316
|
if (bufferTags) bufferTags.length = buffer.length;
|
|
1317
|
+
removed = true;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// The nearest entries before and from `index` that aren’t empty, as removed nodes leave them
|
|
1321
|
+
/** @returns {[number, number]} */
|
|
1322
|
+
function findNeighbors(/** @type {number} */ index) {
|
|
1323
|
+
let before = index - 1;
|
|
1324
|
+
while (before >= 0 && buffer[before] === '') {
|
|
1325
|
+
before--;
|
|
1326
|
+
}
|
|
1327
|
+
let after = index;
|
|
1328
|
+
while (after < buffer.length && buffer[after] === '') {
|
|
1329
|
+
after++;
|
|
1330
|
+
}
|
|
1331
|
+
return [before, after];
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// With whitespace kept as is, a removed node must not leave a line of nothing but
|
|
1335
|
+
// whitespace behind: The text around the removal (at `index`) drops that line. The start
|
|
1336
|
+
// of the output counts as a line break, and blank lines of the source stay.
|
|
1337
|
+
function removeWhitespaceLine(/** @type {number} */ index) {
|
|
1338
|
+
const [first, after] = findNeighbors(index);
|
|
1339
|
+
// The end of the output is left to `trimOutputWhitespace`
|
|
1340
|
+
if (after === buffer.length) {
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const next = buffer[after] ?? '';
|
|
1344
|
+
if (!RE_BLANKS_LINE_BREAK_START.test(next)) {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
// Blanks between nodes removed from the same line belong to that line
|
|
1348
|
+
let before = first;
|
|
1349
|
+
while (before >= 0 && (buffer[before] === '' || RE_BLANKS_ONLY.test(buffer[before] ?? ''))) {
|
|
1350
|
+
before--;
|
|
1351
|
+
}
|
|
1352
|
+
const prev = buffer[before] ?? '';
|
|
1353
|
+
if (before >= 0 && !RE_LINE_BREAK_BLANKS_END.test(prev)) {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
for (let i = before + 1; i <= first; i++) {
|
|
1357
|
+
buffer[i] = '';
|
|
1358
|
+
}
|
|
1359
|
+
// The line break goes on the side of the text before the removal, so that the text after
|
|
1360
|
+
// it keeps its own for a removal that may follow
|
|
1361
|
+
if (before < 0) {
|
|
1362
|
+
buffer[after] = next.replace(RE_BLANKS_LINE_BREAK_START, '');
|
|
1363
|
+
} else {
|
|
1364
|
+
buffer[before] = prev.replace(RE_LINE_BREAK_BLANKS_END, '');
|
|
1365
|
+
buffer[after] = next.replace(RE_BLANKS_START, '');
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// Whitespace at either end of the output goes, but not verbatim whitespace, nor whitespace
|
|
1370
|
+
// that `conservativeCollapse` or `preserveLineBreaks` promise to keep, nor that of partial
|
|
1371
|
+
// markup, whose ends join other markup
|
|
1372
|
+
function trimOutputWhitespace() {
|
|
1373
|
+
if (options.partialMarkup || (options.collapseWhitespace && (options.conservativeCollapse || options.preserveLineBreaks))) {
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
// Tags start with `<` and end with `>`, so whitespace at either end belongs to text
|
|
1377
|
+
if (!textVerbatim) {
|
|
1378
|
+
let index = buffer.length - 1;
|
|
1379
|
+
while (index >= 0 && (buffer[index] === '' || RE_WS_ONLY.test(buffer[index] ?? ''))) {
|
|
1380
|
+
buffer[index--] = '';
|
|
1381
|
+
}
|
|
1382
|
+
if (index >= 0) {
|
|
1383
|
+
buffer[index] = (buffer[index] ?? '').replace(RE_WS_END, '');
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
let index = 0;
|
|
1387
|
+
while (index < buffer.length && (buffer[index] === '' || RE_WS_ONLY.test(buffer[index] ?? ''))) {
|
|
1388
|
+
buffer[index++] = '';
|
|
1389
|
+
}
|
|
1390
|
+
if (index < buffer.length) {
|
|
1391
|
+
buffer[index] = (buffer[index] ?? '').replace(RE_WS_START, '');
|
|
1392
|
+
}
|
|
1308
1393
|
}
|
|
1309
1394
|
|
|
1310
1395
|
function removeStartTag() {
|
|
@@ -1332,14 +1417,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1332
1417
|
// single character it is due: a line break under `preserveLineBreaks`, a no-break space
|
|
1333
1418
|
// where the run holds one, a space under `conservativeCollapse`
|
|
1334
1419
|
function mergeWhitespaceRuns(/** @type {number} */ index) {
|
|
1335
|
-
|
|
1336
|
-
while (before >= 0 && buffer[before] === '') {
|
|
1337
|
-
before--;
|
|
1338
|
-
}
|
|
1339
|
-
let after = index;
|
|
1340
|
-
while (after < buffer.length && buffer[after] === '') {
|
|
1341
|
-
after++;
|
|
1342
|
-
}
|
|
1420
|
+
const [before, after] = findNeighbors(index);
|
|
1343
1421
|
if (before < 0 || after >= buffer.length) {
|
|
1344
1422
|
return;
|
|
1345
1423
|
}
|
|
@@ -1357,7 +1435,8 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1357
1435
|
}
|
|
1358
1436
|
}
|
|
1359
1437
|
|
|
1360
|
-
|
|
1438
|
+
// `insideNoTrim` stands in for the stack once the end tag that omits this one has left it
|
|
1439
|
+
function removeEndTag(/** @type {boolean} */ insideNoTrim = false) {
|
|
1361
1440
|
let index = buffer.length - 1;
|
|
1362
1441
|
while (index > 0 && !RE_END_TAG.test(buffer[index] ?? '')) {
|
|
1363
1442
|
index--;
|
|
@@ -1365,13 +1444,17 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1365
1444
|
// Text that follows the end tag is kept as is, so drop the tag alone. Whitespace kept
|
|
1366
1445
|
// verbatim—in `pre`, in `textarea`, or wherever `canTrimWhitespace` says so—counts as
|
|
1367
1446
|
// kept text too, no matter how whitespace is collapsed elsewhere.
|
|
1368
|
-
const noTrim = stackNoTrimWhitespace.length > 0;
|
|
1447
|
+
const noTrim = insideNoTrim || stackNoTrimWhitespace.length > 0;
|
|
1369
1448
|
if (index < buffer.length - 1 && (keepsWhitespace() || noTrim) && RE_END_TAG.test(buffer[index] ?? '')) {
|
|
1370
1449
|
buffer.splice(index, 1);
|
|
1371
1450
|
bufferTags?.splice(index, 1);
|
|
1372
|
-
//
|
|
1373
|
-
if (
|
|
1374
|
-
|
|
1451
|
+
// Collapsed whitespace can merge, kept whitespace can leave a line of only whitespace
|
|
1452
|
+
if (!noTrim) {
|
|
1453
|
+
if (options.collapseWhitespace) {
|
|
1454
|
+
mergeWhitespaceRuns(index);
|
|
1455
|
+
} else {
|
|
1456
|
+
removeWhitespaceLine(index);
|
|
1457
|
+
}
|
|
1375
1458
|
}
|
|
1376
1459
|
return;
|
|
1377
1460
|
}
|
|
@@ -1452,7 +1535,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1452
1535
|
// Multiple newlines are likely intentional formatting, single newline is often a template artifact
|
|
1453
1536
|
// Treat CRLF (`\r\n`), CR (`\r`), and LF (`\n`) as single line-ending units
|
|
1454
1537
|
if (textNextTag && textNextTag === '/' + topTag && /[^\r\n](?:\r\n|\r|\n)[ \t]*$/.test(text)) {
|
|
1455
|
-
text = text.replace(
|
|
1538
|
+
text = text.replace(RE_LINE_BREAK_BLANKS_END, '');
|
|
1456
1539
|
}
|
|
1457
1540
|
}
|
|
1458
1541
|
}
|
|
@@ -1569,7 +1652,11 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1569
1652
|
}
|
|
1570
1653
|
}
|
|
1571
1654
|
charsPrevTag = /^\s*$/.test(text) ? textPrevTag : 'comment';
|
|
1572
|
-
|
|
1655
|
+
const rawText = Boolean(text) && holdsRawText();
|
|
1656
|
+
if (text) {
|
|
1657
|
+
textVerbatim = rawText || stackNoTrimWhitespace.length > 0;
|
|
1658
|
+
}
|
|
1659
|
+
if (options.decodeEntities && text && !rawText) {
|
|
1573
1660
|
// Escape any `&` symbols that start either:
|
|
1574
1661
|
// 1. a legacy-named character reference (i.e., one that doesn’t end with `;`)
|
|
1575
1662
|
// 2. or any other character reference (i.e., one that does end with `;`)
|
|
@@ -1594,7 +1681,11 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1594
1681
|
if (text) {
|
|
1595
1682
|
hasChars = true;
|
|
1596
1683
|
}
|
|
1684
|
+
const followsRemoval = removed && Boolean(text) && !options.collapseWhitespace && !stackNoTrimWhitespace.length;
|
|
1597
1685
|
bufferPush(text, false);
|
|
1686
|
+
if (followsRemoval) {
|
|
1687
|
+
removeWhitespaceLine(buffer.length - 1);
|
|
1688
|
+
}
|
|
1598
1689
|
}
|
|
1599
1690
|
|
|
1600
1691
|
// Comment finalization (sync): Optional tag handling, `htmlmin:ignore` whitespace collapsing, buffer push
|
|
@@ -1858,19 +1949,18 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1858
1949
|
optionalEndTagEmitted = false;
|
|
1859
1950
|
}
|
|
1860
1951
|
|
|
1861
|
-
// Set whitespace flags for nested tags (e.g., `<code>` within a `<pre>`)
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1952
|
+
// Set whitespace flags for nested tags (e.g., `<code>` within a `<pre>`); verbatim
|
|
1953
|
+
// whitespace is tracked without `collapseWhitespace` too, as `removeWhitespaceLine` spares it
|
|
1954
|
+
if (options.collapseWhitespace && !stackNoTrimWhitespace.length) {
|
|
1955
|
+
squashTrailingWhitespace(tag);
|
|
1956
|
+
}
|
|
1957
|
+
if (!unary) {
|
|
1958
|
+
if (!canTrimWhitespace(tag, attrs) || stackNoTrimWhitespace.length) {
|
|
1959
|
+
stackNoTrimWhitespace.push(tag);
|
|
1960
|
+
if (tag === 'pre' || tag === 'textarea') preTextareaDepth++;
|
|
1865
1961
|
}
|
|
1866
|
-
if (!
|
|
1867
|
-
|
|
1868
|
-
stackNoTrimWhitespace.push(tag);
|
|
1869
|
-
if (tag === 'pre' || tag === 'textarea') preTextareaDepth++;
|
|
1870
|
-
}
|
|
1871
|
-
if (!canCollapseWhitespace(tag, attrs) || stackNoCollapseWhitespace.length) {
|
|
1872
|
-
stackNoCollapseWhitespace.push(tag);
|
|
1873
|
-
}
|
|
1962
|
+
if (options.collapseWhitespace && (!canCollapseWhitespace(tag, attrs) || stackNoCollapseWhitespace.length)) {
|
|
1963
|
+
stackNoCollapseWhitespace.push(tag);
|
|
1874
1964
|
}
|
|
1875
1965
|
}
|
|
1876
1966
|
|
|
@@ -2004,19 +2094,18 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2004
2094
|
}
|
|
2005
2095
|
|
|
2006
2096
|
// Check if current tag is in a whitespace stack
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
}
|
|
2013
|
-
} else {
|
|
2014
|
-
squashTrailingWhitespace('/' + tag);
|
|
2015
|
-
}
|
|
2016
|
-
if (stackNoCollapseWhitespace.length &&
|
|
2017
|
-
tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
|
|
2018
|
-
stackNoCollapseWhitespace.pop();
|
|
2097
|
+
const insideNoTrim = stackNoTrimWhitespace.length > 0;
|
|
2098
|
+
if (stackNoTrimWhitespace.length) {
|
|
2099
|
+
if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
|
|
2100
|
+
if (tag === 'pre' || tag === 'textarea') preTextareaDepth--;
|
|
2101
|
+
stackNoTrimWhitespace.pop();
|
|
2019
2102
|
}
|
|
2103
|
+
} else if (options.collapseWhitespace) {
|
|
2104
|
+
squashTrailingWhitespace('/' + tag);
|
|
2105
|
+
}
|
|
2106
|
+
if (stackNoCollapseWhitespace.length &&
|
|
2107
|
+
tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
|
|
2108
|
+
stackNoCollapseWhitespace.pop();
|
|
2020
2109
|
}
|
|
2021
2110
|
|
|
2022
2111
|
let isElementEmpty = false;
|
|
@@ -2038,7 +2127,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2038
2127
|
// (https://html.spec.whatwg.org/multipage/syntax.html#optional-tags);
|
|
2039
2128
|
// except for `</dt>` or `</thead>`, end tags may be omitted if no more content in parent element
|
|
2040
2129
|
if (tag && optionalEndTag && optionalEndTagEmitted && !trailingElements.has(optionalEndTag) && (optionalEndTag !== 'p' || (!pInlineElements.has(tag) && !tag.includes('-')))) {
|
|
2041
|
-
removeEndTag();
|
|
2130
|
+
removeEndTag(insideNoTrim);
|
|
2042
2131
|
}
|
|
2043
2132
|
optionalEndTag = optionalEndTags.has(tag) ? tag : '';
|
|
2044
2133
|
optionalEndTagEmitted = true;
|
|
@@ -2145,12 +2234,16 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2145
2234
|
text = prefix + text + suffix;
|
|
2146
2235
|
} else {
|
|
2147
2236
|
text = '';
|
|
2237
|
+
removed = true;
|
|
2148
2238
|
}
|
|
2149
2239
|
} else {
|
|
2150
2240
|
text = prefix + text + suffix;
|
|
2151
2241
|
}
|
|
2152
2242
|
commentFinalize(text);
|
|
2153
2243
|
},
|
|
2244
|
+
strayEnd: function () {
|
|
2245
|
+
removed = true;
|
|
2246
|
+
},
|
|
2154
2247
|
doctype: function (/** @type {string} */ doctype) {
|
|
2155
2248
|
bufferPush(options.useShortDoctype
|
|
2156
2249
|
? '<!doctype' +
|
|
@@ -2190,9 +2283,12 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
2190
2283
|
removeEndTag();
|
|
2191
2284
|
}
|
|
2192
2285
|
}
|
|
2286
|
+
// Collapsing also trims whitespace before trailing inline end tags; trimming the output
|
|
2287
|
+
// catches what it leaves at either end, like a space after a removed leading comment
|
|
2193
2288
|
if (options.collapseWhitespace) {
|
|
2194
2289
|
squashTrailingWhitespace('br');
|
|
2195
2290
|
}
|
|
2291
|
+
trimOutputWhitespace();
|
|
2196
2292
|
|
|
2197
2293
|
return joinResultSegments(buffer, bufferTags ?? [], [uidAttr, uidIgnore].filter(marker => marker !== undefined), options, uidPattern
|
|
2198
2294
|
? function (/** @type {string} */ str) {
|
package/src/htmlparser.js
CHANGED
|
@@ -19,6 +19,7 @@ import { endlessRawTextElements, escapableRawTextElements, genericRawTextElement
|
|
|
19
19
|
* chars?: Function,
|
|
20
20
|
* comment?: Function,
|
|
21
21
|
* doctype?: Function,
|
|
22
|
+
* strayEnd?: Function,
|
|
22
23
|
* continueOnParseError?: boolean | undefined,
|
|
23
24
|
* partialMarkup?: boolean | undefined,
|
|
24
25
|
* wantsNextTag?: boolean | undefined,
|
|
@@ -1063,6 +1064,9 @@ export class HTMLParser {
|
|
|
1063
1064
|
if (handler.end) {
|
|
1064
1065
|
handler.end(tagName, []);
|
|
1065
1066
|
}
|
|
1067
|
+
} else if (handler.strayEnd) {
|
|
1068
|
+
// An end tag with nothing to close is dropped
|
|
1069
|
+
handler.strayEnd();
|
|
1066
1070
|
}
|
|
1067
1071
|
// Returned so the parse loop can skip lowercasing the name again
|
|
1068
1072
|
return lowerTagName;
|