Repository navigation
New HTML packager is broken聽#10365
Description
馃悰 bug report
The new HTML packager will just reorder tags it thinks are "invalid". This can affect templating tools like thymeleaf.
It also just seems to not emit the <head> tag for some reason in my reproduction sample??
馃帥 Configuration (.babelrc, package.json, cli command)
To reproduce this, create a new parcel project with pnpm create parcel vanilla reproduction, then modify the src/index.html with the below changes.
馃 Expected Behavior
Given something like
<!doctype html>
<html lang="en" xmlns:layout="https://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Parcel Vanilla App</title>
<link rel="stylesheet" href="style.css">
<th:block th:insert="~{templates/fragments/common-head :: commonHead(${title}, ${description}, ~{:: #extra-head})}"/>
<th:block id="extra-head"></th:block>
</head>
<body>
<h1>Parcel Vanilla App</h1>
<p>Edit <code>src/index.html</code> to get started!</p>
</body>
</html>The emitted html would be roughly similar
馃槸 Current Behavior
It instead emits:
<!DOCTYPE html><html lang=en xmlns:layout=https://www.ultraq.net.nz/thymeleaf/layout xmlns:th=http://www.thymeleaf.org><meta charset=utf-8><meta name=viewport content="width=device-width, initial-scale=1.0"><title>Parcel Vanilla App</title><link rel=stylesheet href=/reproduction.d064d61f.css><th:block th:insert="~{templates/fragments/common-head :: commonHead(${title}, ${description}, ~{:: #extra-head})}">
<th:block id=extra-head></th:block>
<h1>Parcel Vanilla App</h1>
<p>Edit <code>src/index.html</code> to get started!</p>
</th:block>Issues:
- the first
th:blocktag was closed with/>. for some reason, it decided that actually it wasn't closed and that EVERYTHING else is part of it. - removed the
<head>and<body>tags entirely?? (when initially encountered, it did not do this, it just moved the<th:block>tags from the<head>into the<body> - does not close the
<html>tag - I don't think
xmlns:layout=https://www.ultraq.net.nz/thymeleaf/layoutis a valid attribute, but I could be wrong.
When I originally encountered this, I did not encounter the last two, but that might be because I was using the older @parcel/optimizer-htmlnano, so those may be caused by it.
馃拋 Possible Solution
The issue has to do with how the old parcel HTML packager was ported from js to rust.
As a temporary measure, I'm just going to vendor the old js packager.
馃敠 Context
I am using Thymeleaf, which uses custom tags for templating.
馃捇 Code Sample
See above.
馃實 Your Environment
| Software | Version(s) |
|---|---|
| Parcel | 2.16.4 |
| Node | v26.9.0 |
| npm/Yarn | pnpm 9.12.0 |
| Operating System | Arch Linux 7.1.4-arch1-1 x86_64 |
Activity
devongovett commentedon Sep 28, 2026
Pretty sure these are all spec-compliant optimizations. If you paste this into an HTML page and load it in a browser you get the same structure (I did that here: https://codepen.io/editor/devongovett/pen/01a0e570-066c-76f0-be7f-ee6b4add17a5).
/>itself is actually ignored by HTML parsers. Read more here https://developer.mozilla.org/en-US/docs/Glossary/Void_elementTherefore, if you load the original HTML in a browser, and load the optimized HTML in a browser, they should produce the exact same DOM structure. It seems like this input isn't really HTML but more of a templating language. If you're relying on a non-spec compliant HTML parser where these differ, then you can disable optimization.
solonovamax commentedon Sep 29, 2026
the issue is, this is breaking thymeleaf.
I have a
<th:block>tag in my<head>because it is going to have content injected into it.parcel cannot know which tags will & won't be injected into it, so it should not be moving the tag out of the
<head>.and, when transforming
into
this causes thymeleaf to not actually be able to find the
extra-headblock, so if there is anything in it then it will be ignored.if you really want to stick as closely to the spec as possible, then this should at the very least emit a warning to let the user know that it happened.
but, imo the behaviour of moving
<th:block>out of the<head>is definitely an issue, because it is an unknown element/namespace.devongovett commentedon Sep 29, 2026
I mean, I think the issue is that your input is not really HTML at all but a custom templating language. So I think it makes sense to either process that before sending it to parcel, disable the html optimizer plugin in your parcelrc, or use a different file extension for non-HTML so parcel doesn't process it as if it was HTML.
solonovamax commentedon Sep 30, 2026
although not in this particular example, it's used for server-side templating, so the HTML is not 100% static
the thing is, I want it to perform some optimizations on it. there is a lot of unecessary whitespace and comments which can be removed
I'd like to just have parsel not perform any of the more "aggressive" optimizations to it (mainly omitting optional tags). I'd be happy with just the basic optimizations like whitespace removal & comment removal.
I'd prefer if, by default, the optimizer took a more conservative approach, however I'd be fine if it was at least offered as a configuration option.
alternatively, the original html packager and transformers could be provided as packages, similar to how someone can choose to use
@parcel/optimizer-htmlnano(though, I would also argue that the packager & transformer should aim to modify the html as little as possible and any modifications outside of resolving dependencies are the responsibility of the optimizer)tevinch commentedon Oct 3, 2026
The old JavaScript HTML packages can be installed from npm under aliases, so you can keep Parcel 2.16.4 without vendoring the packager. The versions that worked here were
@parcel/transformer-html@2.14.4and@parcel/packager-html@2.14.4(2.15.4 already uses the newer parser).I put the exact configuration and a runnable reproduction here: Thymeleaf template build example. It selects both old HTML plugins, enables PostHTML's
recognizeSelfClosing, and uses htmlnano for conservative whitespace/comment reduction while preserving Thymeleaf processing comments and explicit tags.With your original input, both the default 2.16.4 build and
--no-optimizestill lost the dynamic head fragment after actual Thymeleaf rendering. With this configuration, Thymeleaf 3.1.5 rendered the same parsed structure as the source template. The extended fixture also checks conditional content, template comments, emitted CSS/JS and a repeated cached build.This is a temporary compatibility option, not an upstream fix. The checks use standard Thymeleaf HTML mode; they do not cover the Layout Dialect or inline Thymeleaf JavaScript/CSS expressions, which Parcel may still optimize in an earlier stage. I hope it helps remove the need to maintain a copied packager while keeping the server-side rendering step.