Skip to content

New HTML packager is broken聽#10365

Open
Open

Description

@solonovamax

馃悰 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:block tag 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/layout is 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

  1. devongovett commented on Sep 28, 2026

    @devongovett
    Member

    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).

    Therefore, 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.

  2. solonovamax commented on Sep 29, 2026

    @solonovamax
    ContributorAuthor

    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).

    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

    <th:block th:insert="~{templates/fragments/common-head :: commonHead(${title}, ${description}, ~{:: #extra-head})}"/>
    <th:block id="extra-head"></th:block>

    into

    <th:block th:insert="~{templates/fragments/common-head :: commonHead(${title}, ${description}, ~{:: #extra-head})}">
    <th:block id="extra-head"></th:block>
    <!-- ... -->
    </th:block>

    this causes thymeleaf to not actually be able to find the extra-head block, 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.

  3. devongovett commented on Sep 29, 2026

    @devongovett
    Member

    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.

  4. solonovamax commented on Sep 30, 2026

    @solonovamax
    ContributorAuthor

    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,

    although not in this particular example, it's used for server-side templating, so the HTML is not 100% static

    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.

    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)

  5. tevinch commented on Oct 3, 2026

    @tevinch

    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.4 and @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-optimize still 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions