What happened?
A <script src> or <link rel="stylesheet"> referenced by more than one of the app layout, page layout, and page is emitted once per occurrence in the built HTML — for the natural pattern "app layout has the shared bundle, page layout and page also reference it", the output contains the same tag three times, plus three modulepreload / preload hints for it.
Consequences beyond bloat:
- A classic (non-module) duplicate executes repeatedly — double event listeners, double side effects — since only module semantics dedupe by URL in the browser.
- Duplicated stylesheets apply twice, which can produce cascade-order surprises.
- Duplicated preload hints waste requests.
There is no warning; the developer only notices when inspecting the output (or debugging a double-fired listener).
Steps to reproduce
- Reference the same resource in all three places, e.g.:
<!-- src/layouts/app.html and src/layouts/page.html and src/pages/index.html -->
<script type="module" src="/scripts/shared.js"></script>
<link rel="stylesheet" href="/styles/shared.css" />
greenwood build
- Inspect
public/index.html.
Observed: 3× the shared <script> tag, 3× the shared stylesheet <link>, 3× modulepreload, 3× preload. Expected: each once.
Environment
- Greenwood v0.34.0 (reproduced on current
master)
- NodeJS v22.20
- Linux
Additional Context
Two compounding spots:
packages/cli/src/lib/layout-utils.js — mergeContentIntoLayout builds mergedLinks (line 143) and mergedScripts (line 186) by concatenating parent + child head tags with no dedupe by src/href, so each layout-merge pass preserves duplicates.
packages/cli/src/lib/resource-utils.js:214 — matchingRoute.resources is built with resources.map(...), preserving duplicates (only the compilation.resources Map is deduped), and the standard HTML plugin injects one preload hint per occurrence while rewriting each tag.
Happy to submit a PR: dedupe merged <script src>/<link href> tags in the layout merge (keeping the first occurrence, so app layout → page layout → page order is preserved), plus a Set on matchingRoute.resources, with regression assertions folded into the existing build.default.workspace-layouts-page-and-app test case.
(Found during the same audit pass as #1707/#1709/#1711 etc.)
What happened?
A
<script src>or<link rel="stylesheet">referenced by more than one of the app layout, page layout, and page is emitted once per occurrence in the built HTML — for the natural pattern "app layout has the shared bundle, page layout and page also reference it", the output contains the same tag three times, plus threemodulepreload/preloadhints for it.Consequences beyond bloat:
There is no warning; the developer only notices when inspecting the output (or debugging a double-fired listener).
Steps to reproduce
greenwood buildpublic/index.html.Observed: 3× the shared
<script>tag, 3× the shared stylesheet<link>, 3×modulepreload, 3×preload. Expected: each once.Environment
master)Additional Context
Two compounding spots:
packages/cli/src/lib/layout-utils.js—mergeContentIntoLayoutbuildsmergedLinks(line 143) andmergedScripts(line 186) by concatenating parent + child head tags with no dedupe bysrc/href, so each layout-merge pass preserves duplicates.packages/cli/src/lib/resource-utils.js:214—matchingRoute.resourcesis built withresources.map(...), preserving duplicates (only thecompilation.resourcesMap is deduped), and the standard HTML plugin injects one preload hint per occurrence while rewriting each tag.Happy to submit a PR: dedupe merged
<script src>/<link href>tags in the layout merge (keeping the first occurrence, so app layout → page layout → page order is preserved), plus aSetonmatchingRoute.resources, with regression assertions folded into the existingbuild.default.workspace-layouts-page-and-apptest case.(Found during the same audit pass as #1707/#1709/#1711 etc.)