Repository navigation
Conversation
Lets a @javascript annotation render as a <script type="module"> tag instead of a classic <script>, so hand-authored or CDN-hosted ES modules can be loaded at runtime through annotations without going through Vite. For build-time bundled ES modules @jsmodule remains the right tool. The new @JavaScript.Type enum has values SCRIPT (default, current behavior) and MODULE. The annotation gains a type() attribute that selects between them. To make @javascript the unified entry point on the programmatic side as well, this commit also: - adds a new Page.addJavaScript(String url, LoadMode loadMode, JavaScript.Type type) overload that handles both classic <script> and <script type="module"> tags, with full LoadMode support for both; - delegates the existing addJavaScript(String, LoadMode) and addJavaScript(String) overloads to the new method with Type.SCRIPT; - deprecates Page.addJsModule(String) — recommend addJavaScript(url, loadMode, Type.MODULE) instead. The deprecated method keeps working for backwards compatibility. UIInternals.addExternalDependencies routes both @javascript runtime values and external @jsmodule values through the new addJavaScript overload. @javascript values pass js.loadMode() and js.type() straight through, so type=MODULE supports LAZY and INLINE load modes just like type=SCRIPT. FrontendClassVisitor.JSAnnotationVisitor reads the type enum via a new visitEnum override and skips MODULE-typed values from the bundle imports collection. The type attribute does not exist on @jsmodule, so visitEnum is a no-op for it. Existing @javascript usages keep their behavior: bare relative values default to type=SCRIPT and continue to bundle (legacy interpretation), external URLs continue to render as runtime <script> tags.
@javascript values with a runtime prefix (context://, base://, /abs, http(s)://, //) are now served as runtime <script> elements rather than bundled, matching how the corresponding @Stylesheet values are resolved. - New FrontendDependencyUrlResolver.isRuntimeDependencyUrl recognises the runtime prefix table (http(s)://, //, context://, base://, /abs) and is the single source of truth for distinguishing runtime URLs from bundler import specifiers. - UIInternals treats such @javascript values as runtime dependencies via isRuntimeJavaScript, alongside type=MODULE values. - The scanner (FrontendClassVisitor) filters runtime-prefixed @javascript values out of the bundle imports. - Bare-relative @javascript values still bundle for backwards compatibility but FrontendDependencies emits a one-time consolidated build-time warning per (class, value) recommending migration.
|
|
Merge main. |
Resolve conflicts with the squash-merged #24239 (type attribute for @javascript) by taking main's version and re-applying this branch's runtime-prefix change on top. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The merge commit picked up copyright header rewrites from the formatter hook in files unrelated to this change. Restore them to main's version and update the resolveRuntimeJavaScript Javadoc to match the values that now reach it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Memory footprintNo change compared with the base commit a59da97. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removes the duplicated prefix check, which also lower-cased without Locale.ROOT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Fixed the java:S1449 issue: URL prefix checks now lower-case with |
|
merge main |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@Artur- Merged latest |
|
Runtime-prefixed flowchart LR
subgraph Before
direction TB
B1["JavaScript annotation ((foo.js/redacted) -->|"build-time scan"| B2["FrontendClassVisitor.JSAnnotationVisitor: not type=MODULE, so bundle it"]
B1 -->|"render time"| B3["UIInternals.isRuntimeJavaScript(): not MODULE, not UrlUtil.isExternal() - not runtime"]
B2 --> B4["classInfo.scripts (bundle import)"]
B3 --> B5["no runtime script tag emitted"]
end
subgraph After
direction TB
A1["JavaScript annotation ((foo.js/redacted) -->|"build-time scan"| A2["FrontendDependencyUrlResolver.isRuntimeDependencyUrl() true - skip bundle"]:::changed
A1 -->|"render time"| A3["FrontendDependencyUrlResolver.isRuntimeDependencyUrl() true - is runtime"]:::changed
A2 --> A4["excluded from classInfo.scripts"]
A3 --> A5["UIInternals.addRuntimeDependencies() emits script tag"]:::changed
end
Before ~~~ After
classDef changed stroke:#c9a227,stroke-width:3px
Before this PR, the build-time scanner ( Diagram Bot draws the mechanism this pull request touches; it does not review the change. Verify it against the diff.
|
vaadin-review-bot
left a comment
There was a problem hiding this comment.
Reviewed the changes — left 1 comment and collected 2 findings below that could not be attached to the diff.
| Finding | |
|---|---|
| Only the bytecode scanner excludes runtime-prefixed @javascript values; the full scanner, which is the default in dev mode, still bundles them | |
| 📝 | The @javascript javadoc still documents bundled bare-relative values as normal behavior while the build now calls that usage deprecated |
| 📝 | The deprecation warning's first two suggestions do not work for files in the frontend directory |
FullDependenciesScanner.collectScripts got the Type.MODULE exclusion but not the new runtime-prefix exclusion. It is the scanner selected whenever optimizeBundle is false, which is the default for dev mode (DevModeInitializer) and for BuildFrontendUtil's dev-bundle path.
context:// and base:// survive this because AbstractUpdateImports.resolveModules filters those prefixes later, but a leading-slash value does not: @JavaScript("/absolute.js") still ends up as import '/absolute.js'; in the generated imports while also being added as a runtime <script>. So the feature behaves one way in a production build and another way in dev.
The module-exclusion equivalent has a test in both scanners (FullDependenciesScannerTest.getScripts_javaScriptWithTypeModule_excludedFromBundleImports); the runtime-prefix exclusion only has one in FrontendDependenciesTest.
FullDependenciesScanner.java:395 · correctness
📝 The @javascript javadoc still documents bundled bare-relative values as normal behavior while the build now calls that usage deprecated
Two mismatches introduced by this change:
- The class javadoc says "Other paths used in the
value()method are considered as relative tofrontenddirectory and they are bundled". Leading-slash andbase://values are no longer bundled. - Nothing in the annotation says the bundled interpretation is deprecated, and neither
@JavaScriptnorvalue()carries@Deprecated, yet every build now prints "uses the deprecated bundled interpretation" for the usage the javadoc presents as the normal one.
JavaScript.java:55 ·documentation
| } | ||
| for (String value : classInfo.scripts) { | ||
| if (warnedJavaScriptKeys.add(className + ':' + value)) { | ||
| log().warn( |
There was a problem hiding this comment.
📝 The deprecation warning's first two suggestions do not work for files in the frontend directory
Bare-relative @JavaScript values are documented as relative to the frontend directory. Both context:// and Type.MODULE resolve against the servlet context root, where such a file is not served, so following either suggestion gives a 404 unless the file is also moved out of frontend/. Only the @JsModule suggestion works as written.
The warning also fires for classes from third-party add-ons, which the application developer cannot change and cannot silence.
FrontendDependencies.java:247 · documentation
There was a problem hiding this comment.
@vaadin-review-bot Good catch. I reworded the warning. Its first suggestion now says to replace @JavaScript with @JsModule using the same value, which works as-is for files in frontend/. For loading at runtime, it now says the file must first be moved out of frontend/ to a statically served location (e.g. src/main/resources/META-INF/resources), and only then be referenced with context:// or type = Type.MODULE. Since add-on annotations can't be changed by the app developer, the warning is now logged once per scan with all usages listed, instead of once per annotation. It also notes that usages in add-ons need to be updated by the add-on maintainer.
The full dependencies scanner, used in dev mode, still bundled @javascript values with a leading slash. Exclude runtime-prefixed values there as well so dev and production builds agree. Log the deprecation warning once per scan listing all usages, with suggestions that work for files located in the frontend directory, and document the deprecated bundled interpretation in the javadoc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Verify that all bundled @javascript usages end up in a single warning, that no warning is logged when only runtime values are used, and that @jsmodule values with a runtime prefix are still bundled by the full scanner. Make the warning wording apply to npm package values too and stop promising a build warning in the javadoc, as dev mode does not emit it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vaadin-review-bot Addressed the two findings that weren't attached to the diff:
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Replaced the lambda with |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add http(s):// and protocol-relative values to the scanner samples so the tests check they are kept out of the bundle, and explain why runtime developmentOnly scripts are filtered in production mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
External @jsmodule URLs are added to the page at runtime and never go through the bundle, so developmentOnly = true was ignored for them and the module was loaded in production too. Apply the same check as for runtime @javascript values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The build drops every value with a scheme (UrlUtil.isExternal) from the bundle, so a @javascript value such as ftp://... that has none of the runtime prefixes was neither bundled nor loaded. Treat such values as runtime values again, as before, both in the scanners and in UIInternals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also check that such values are not reported as missing from the production bundle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixtures used a frontend:// pseudo-scheme, which the bundler drops like any other URL with a scheme and which is now treated as a runtime value by the scanner as well. Use plain relative values so the tests keep exercising bundled script collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
It looks like there's an issue with hotswap: if I change the value for a |
A @javascript value that is loaded at runtime is added as a script element, which cannot be removed from the page again. Refreshing the UI after the value was changed or removed kept the old script and added the new one next to it. Reload the UIs using the component instead. The check for runtime @javascript values moves to FrontendDependencyUrlResolver so that UIInternals and the hotswapper share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read the scripts after the redefinition through the component metadata as well, so scripts of @uses targets do not look removed and trigger a reload on every redefinition. Add tests for closing UIs, Type.MODULE values, clearing the recorded state and isRuntimeJavaScript. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@mcollovati Good catch. A script that has run cannot be removed from the page, so a refresh can only add the new |
|





@javascript values with a runtime prefix (context://, base://, /abs, http(s)://, //) are now served as runtime <script> elements rather than bundled, matching how the corresponding @Stylesheet values are resolved.
FrontendClassVisitorfor production andFullDependenciesScannerfor dev mode) filter runtime-prefixed@JavaScriptvalues out of the bundle imports.@JavaScriptvalues are still bundled for backwards compatibility, butFrontendDependencieslogs a single build-time warning that lists every such usage. The warning recommends@JsModulefor bundling, or moving the file to a static location and using a runtime prefix. The@JavaScriptjavadoc documents this interpretation as deprecated.Behavior change: before this change,
@JavaScript("/foo.js")pointing atfrontend/foo.jswas bundled, because the bundler resolves a leading slash against the frontend folder. It now renders<script src="/foo.js">resolved against the context root, which returns 404 unless the file is also served as a static resource. No build-time warning covers this form, since a leading slash is now a valid runtime URL and the scanner cannot tell the two uses apart. To keep bundling such a file, switch to@JsModule("./foo.js"). This needs a release note.Production-mode fix:
developmentOnly = trueis now honoured for dependencies that are loaded at runtime instead of bundled. Before, a runtime@JavaScriptvalue (runtime prefix orType.MODULE) or an external@JsModuleURL such as@JsModule(value = "https://…/dev-helper.js", developmentOnly = true)was also loaded in production. The@JsModulecase and the prefixed@JavaScriptcases other than a leading/were already affected before this PR.Hotswap: a runtime
@JavaScriptvalue is added as a<script>element, which cannot be removed from the page. When such a value is changed or removed on a component in use, the newJavaScriptHotswapperreloads the affected UIs instead of refreshing them, so the old script is not left running next to the new one. Adding a value still only refreshes. To share the runtime check,FrontendDependencyUrlResolver.isRuntimeJavaScript(JavaScript)is added andComponentMetaData.DependencyInfo.getJavaScripts()is made public (both internal-use classes).