From 8df547996750f0753dc71a8461b3f0ac9eff3b0c Mon Sep 17 00:00:00 2001 From: mikhail Date: Mon, 21 Sep 2026 11:44:18 +0300 Subject: [PATCH 1/2] docs: describe how an add-on pins npm versions Vaadin reads every versions file in META-INF/VAADIN/versions/ on the classpath, so any jar can pin the npm versions its Java code needs. Nothing described the folder or the file format. Co-Authored-By: Claude Opus 5 (1M context) --- .../components/package-component.adoc | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/articles/building-apps/components/package-component.adoc b/articles/building-apps/components/package-component.adoc index cba0e24085..c6ea91400e 100644 --- a/articles/building-apps/components/package-component.adoc +++ b/articles/building-apps/components/package-component.adoc @@ -141,6 +141,36 @@ If you want the CSS to be included in the frontend bundle instead of served as a See <> for details on shadow DOM styling. +[role="since:com.vaadin:vaadin@V25.3"] +== Pinning npm Versions + +`@NpmPackage` declares which npm package a component needs, but the version that ends up installed can still be decided elsewhere — by another add-on, or by a transitive dependency of one. To decide it yourself, ship a versions file in the JAR. Vaadin reads every `.json` file in `META-INF/VAADIN/versions/` on the classpath, from whichever JAR ships it, and installs the packages they name at the versions they give. + +---- +src/main/resources/META-INF/VAADIN/versions/my-components.json +---- + +[source,json] +---- +{ + "components": { + "color-picker": { + "npmName": "@example/color-picker", + "npmVersion": "3.2.1" + } + } +} +---- + +An object with an `npmName` is one pinned package, and its version comes from `npmVersion` — or from `jsVersion`, which the files of the platform use. The keys around it group the entries and carry no meaning, so nest them however suits the add-on. The pinned version is written into the generated `package.json`, which is what the package manager installs from. + +Every versions file on the classpath is merged into one set of packages: + +- A package named in several files is expected to have the same version in each. Where the versions differ, the newest one is installed and a warning names both files. +- A package with neither `npmVersion` nor `jsVersion` is left unpinned, and a warning names it. +- A file that cannot be read is skipped on its own. The other files still pin what they declare. + + == Testing the Add-On The starter includes Jetty and is configured with `jetty:run`, so you can start a local server by running: From 715d12620eb2597e6464f4aea86d9ec79776398d Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:26:40 +0000 Subject: [PATCH 2/2] docs: cover exclusions and what overrides a pinned npm version An entry can name the packages the pinned one replaces, and a version that a scanned @NpmPackage annotation or the application's own package.json declares wins over the pinned one. --- .../components/package-component.adoc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/articles/building-apps/components/package-component.adoc b/articles/building-apps/components/package-component.adoc index c6ea91400e..13c8ff50b2 100644 --- a/articles/building-apps/components/package-component.adoc +++ b/articles/building-apps/components/package-component.adoc @@ -164,6 +164,23 @@ src/main/resources/META-INF/VAADIN/versions/my-components.json An object with an `npmName` is one pinned package, and its version comes from `npmVersion` — or from `jsVersion`, which the files of the platform use. The keys around it group the entries and carry no meaning, so nest them however suits the add-on. The pinned version is written into the generated `package.json`, which is what the package manager installs from. +An entry can also name the packages that the pinned one replaces, with an `exclusions` array. Those packages are left out of the installation, which is how a newer package supersedes the one it was split from or renamed from: + +[source,json] +---- +{ + "components": { + "color-picker": { + "npmName": "@example/color-picker", + "npmVersion": "3.2.1", + "exclusions": ["@example/legacy-color-picker"] + } + } +} +---- + +Pinning decides the version of a package that nothing else in the project asks for by name. A version that a scanned `@NpmPackage` annotation declares, or that the application sets in its own `package.json`, is used instead, so an application can still override what an add-on pins. + Every versions file on the classpath is merged into one set of packages: - A package named in several files is expected to have the same version in each. Where the versions differ, the newest one is installed and a warning names both files.