Skip to content

fix: rebundle when the bundle misses a template source - #25484

Open
totally-not-ai[bot] wants to merge 7 commits into
mainfrom
fix/skip-template-copy-when-prod-bundle-reused
Open

fix: rebundle when the bundle misses a template source#25484
totally-not-ai[bot] wants to merge 7 commits into
mainfrom
fix/skip-template-copy-when-prod-bundle-reused

Conversation

@totally-not-ai

@totally-not-ai totally-not-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Problem

TaskCopyTemplateFiles copies the JavaScript source of every Template class into config/templates, where the template parser reads it at runtime to map the @Id fields of a template. That folder is part of the bundle, so when a matching bundle is reused the sources it holds are unpacked back into place.

The copy task ran again anyway and resolved every @JsModule value against node_modules — which npm install had not created, because reusing the bundle is exactly what skips it. On a clean checkout with no node_modules and no build output, the build-frontend goal failed for any template whose source lives in an npm package, with a bare Unable to locate file ....

Fix

The bundle becomes the source of truth for those templates:

  • Bundle validation (BundleValidationUtil) now requires the bundle in use — application-specific, default, or dev — to contain the template source of every Template class whose @JsModule points into an npm package, and requires a rebuild when it does not. Whether a template needs the bundle is decided by looking the source up (frontend folder, then jar resources) rather than by the ./ prefix of the @JsModule value, since a template packaged in an add-on jar is available on every build even when referenced by a bare path. Templates in the project or in an add-on jar are always on disk and keep being copied on every build, so they need no check. A rebuild triggered this way is reported as flow/rebundle-reason-missing-template.
  • TaskCopyTemplateFiles keeps a template it cannot resolve when the bundle has already provided it, and only tolerates that on a non-bundle build — a bundle build has npm install behind it, so an unresolvable source is a genuine error even if an earlier build left a copy in the output folder (which would otherwise hide a template that an npm package upgrade has moved). The error message now names the template class and the places that were searched.
  • The @JsModule scan and the template target folder are extracted into getTemplateJsModules(...) / getTemplateDirectory(...) so bundle validation and the copy task agree on what is expected where.

Supporting additions

  • CompressUtil.hasFileInZip(File, String) — check for an entry without extracting it.
  • ProdBundleUtils.hasBundleFile(File, ClassFinder, String) — the same check against whichever production bundle is in use (application bundle if present, otherwise the default bundle on the class path).

Tests

  • TaskCopyTemplateFilesTest: on a build that reuses the bundle, a project template is still copied from the frontend folder, while a template living in an npm package comes from the bundle and is not looked up in the missing node_modules; on a bundle build a missing source is still an error.
  • BundleValidationTest: a reused production bundle that is missing a template source forces a rebuild; one that contains it does not. Development mode already set copyTemplates only when the dev bundle is actually rebuilt — the added tests state the same expectation for production mode.
  • Round-trip coverage: a template copied out of node_modules into the templates folder, compressed into the bundle, and reused on a checkout that has neither node_modules nor the build output.
  • CompressUtilTest: reading a bundle that is not a zip surfaces the ZipException as-is rather than wrapped.

API Changes

com.vaadin.flow.internal.CompressUtil

// Added
public static boolean hasFileInZip(File zip, String filename) throws IOException // checks for an entry without extracting it

com.vaadin.flow.server.frontend.ProdBundleUtils

// Added
public static boolean hasBundleFile(File projectDir, ClassFinder finder, String filename) throws IOException // checks the application bundle, falling back to the default bundle on the class path

When a matching production bundle is reused, npm install is skipped and
node_modules is never created, but TaskCopyTemplateFiles is still
scheduled and resolves every @jsmodule of a Template class against
node_modules, failing the build-frontend goal on a clean checkout.

Development mode already sets copyTemplates only when the dev bundle is
actually rebuilt; the added tests state the same expectation for
production mode.
The reported failure happens while TaskCopyTemplateFiles resolves the
@jsmodule values, so assert there instead of on the NodeTasks command
list: on a build that reuses the bundle, a project template is still
copied from the frontend folder, while a template that lives in an npm
package comes from the bundle and must not be looked up in the missing
node_modules.
TaskCopyTemplateFiles copies the JavaScript source of every Template
class into config/templates, where the template parser reads it at
runtime to map the @id fields of a template. That folder is part of the
bundle, so when a matching bundle is reused the sources it holds are
unpacked back into place. The copy ran again anyway and resolved every
@jsmodule against node_modules, which npm install had not created,
failing the build for any template that lives in an npm package.

The bundle is now the source of truth for those templates: bundle
validation requires the bundle in use, whichever one it is, to contain
the template source of every Template class whose @jsmodule points into
an npm package, and requires a build when it does not. Templates in the
project or in an add-on jar are always available on disk and keep being
copied on every build, so they need no check.

TaskCopyTemplateFiles then keeps a template it cannot resolve when the
bundle has already provided it, and names the template class in the
error when the source is nowhere to be found.
A bundle build has npm install behind it, so a template source that
cannot be found is an error even when an earlier build left a copy of it
in the output folder, which would otherwise hide a template that an npm
package upgrade has moved.

Whether a template source needs the bundle is now decided by looking it
up rather than by the './' prefix of the @jsmodule value, as a template
packaged in an add-on jar is available on every build even when it is
referenced by a bare path.
The fix rests on the template sources being part of the bundle: cover
copying one from node_modules, compressing the folder it is copied into
into the bundle, and reusing that bundle on a checkout that has neither
node_modules nor the build output.
@github-actions github-actions Bot added the +0.0.1 label Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Test Results

 1 434 files  ± 0   1 518 suites  ±0   1h 35m 41s ⏱️ - 26m 51s
11 879 tests +14  11 812 ✅ +14  67 💤 ±0  0 ❌ ±0 
12 198 runs  +14  12 130 ✅ +14  68 💤 ±0  0 ❌ ±0 

Results for commit 6368b12. ± Comparison against base commit 3b704d9.

♻️ This comment has been updated with latest results.

ZipException is an IOException, so the check for a file in a zip lets it
through as it is instead of wrapping it.
@sonarqubecloud

sonarqubecloud Bot commented Sep 4, 2026

Copy link
Copy Markdown

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants