Skip to main content
Version: v5 (Preview)

Upgrading to Stencil v5

This guide takes a Stencil v4 project to v5. Most of the work is done by the stencil migrate command. The rest is a short list of changes to make by hand, grouped below by where they are.

Upgrade from the latest Stencil v4. If your project is on an older major version, upgrade one major version at a time.

For the reasoning behind each change, see the list of breaking changes.

Before You Start​

  • Use Node.js 22 or later.
  • Commit your work, so that you can review what the migration changes.
  • Check that your project builds on Stencil v4.

1. Install Stencil v5​

npm install @stencil/core@beta --save-dev
note

While Stencil v5 is in preview, it's published under the beta tag.

If your project uses Stencil's framework output targets, update them too. They support both v4 and v5:

npm install @stencil/react-output-target@latest @stencil/vue-output-target@latest @stencil/angular-output-target@latest --save-dev

2. Run the Migration​

Preview the changes first. This command lists every change and modifies nothing:

npx stencil migrate --dry-run

Then apply them:

npx stencil migrate

stencil build also detects a project that needs migrating, and offers to run the migration for you.

The migration updates your Stencil config and your component files:

InChange
Componentsshadow, scoped, and formAssociated become the encapsulation option and @AttachInternals()
ComponentsImports of raw .css, .svg, and text files gain a ?stencil suffix
ConfigOutput targets are renamed: dist to loader-bundle, dist-custom-elements to standalone, dist-hydrate-script to ssr
ConfigesmLoaderPath becomes loaderPath, adjusted so that the loader stays where it was
Configextras becomes compat, and its slot-fix options become lightDomPatches
ConfigrollupConfig becomes rolldownConfig
ConfigbuildDist, buildDocs, and devMode are replaced by skipInDev on each output target
ConfighashFileNames and hashedFileNameLength move onto the loader-bundle and www output targets
Configextras.addGlobalStyleToComponents and globalStyle become one global-style output target, with input and inject options
ConfigOptions that now match the default are removed, such as serviceWorker: null

Review the diff before you go on.

3. Update tsconfig.json​

Stencil v4 shipped its own copy of TypeScript. Stencil v5 uses the TypeScript installed in your project, and installs TypeScript 6 if your project has none. TypeScript 6 rejects two settings that Stencil v4 projects commonly have.

Change moduleResolution from node to bundler. Otherwise the build fails with Option 'moduleResolution=node10' is deprecated:

tsconfig.json
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}

TypeScript 6 also turns on strict by default, and a decorated member with no initial value, such as @Prop() name: string;, then fails with Property 'name' has no initializer. To keep these members as they are, turn that one check off:

tsconfig.json
{
"compilerOptions": {
"strictPropertyInitialization": false
}
}

To stay on TypeScript 5 instead, add it to your project's devDependencies.

Set target to es2022 or later, to match the browsers Stencil v5 supports. Stencil v4 projects usually have es2017. That still builds, but newer syntax compiles to older, larger code, and Stencil warns about it on every build:

tsconfig.json
{
"compilerOptions": {
"target": "es2022"
}
}

4. Update package.json​

The output targets write to new directories, and the main entry files end in .js instead of .esm.js:

Stencil v4Stencil v5
dist/ (dist output target)dist/loader-bundle/
dist/components/dist/standalone/
hydrate/dist/ssr/
dist/types/dist/types/
dist/collection/dist/collection/
dist/my-library/my-library.esm.jsdist/loader-bundle/my-library/my-library.js (my-library.esm.js is kept beside it, and loads my-library.js)

Update the main, module, types, unpkg, and exports fields to match. The build warns when main or module point to a file that doesn't exist. It doesn't check unpkg or your exports entries, so check those yourself.

Stencil can also write the exports field for you. See generateExportMaps and Publishing.

Two more changes affect what you publish:

  • CommonJS is opt-in. The loader-bundle and ssr output targets write ES modules only. If your consumers still need CommonJS, set cjs: true on the output target. Setting "type": "module" in your package.json is recommended either way.
  • The browser build has moved. Pages that load your library from a CDN use a URL that ends in dist/my-library/my-library.esm.js. To keep that URL working, set buildDir: '../' on the loader-bundle output target. The browser build is then written to dist/my-library/ as before, and my-library.esm.js remains as a file that forwards to my-library.js.

Then update your scripts:

  • Remove --prod. Production is the default build, and --dev opts out of it.
  • Remove --no-open from dev server commands. The dev server no longer opens a browser unless you pass --open.
  • Remove --docs. A production build already generates your documentation output targets. To generate them in a development build, set skipInDev: false on them, or run stencil docs.
  • Remove stencil telemetry commands.
  • Replace stencil test, as described in Move your tests.

5. Update Your Code​

The migration doesn't make these changes, because they depend on what your code means to do.

Components​

Replace hostData() with <Host>. hostData() has been deprecated since Stencil 1, and is ignored in v5, with no warning:

- hostData() {
- return { role: 'alert' };
- }
render() {
- return <slot />;
+ return (
+ <Host role="alert">
+ <slot />
+ </Host>
+ );
}

Update componentShouldUpdate(). It now runs once for each render, and receives one argument that holds every change:

- componentShouldUpdate(newVal, oldVal, propName) {
- return !(propName === 'name' && newVal === oldVal);
- }
+ componentShouldUpdate(changes: ComponentShouldUpdateChanges<this>) {
+ return !(changes.name && changes.name.newVal === changes.name.oldVal);
+ }

A method that still takes three parameters compiles, and receives undefined for the second and third. The build warns about each one.

Check boolean props that are set to "false". A boolean prop is now true whenever its attribute is present, as with native HTML elements. <my-cmp disabled="false"> gave false in v4 and gives true in v5. Remove the attribute, or set the property instead. To keep accepting the string, see @AttrDeserialize().

Check @Watch() handlers that ran before the first render. A watcher no longer runs until its component has rendered once. If you need it to run on the initial value, use @Watch('name', { immediate: true }).

Wrap functional components that return an array. A FunctionalComponent must now return one node or null, so wrap an array in a fragment: <>{items}</>.

Config​

  • Remove buildEs5 and any extras.__deprecated__* options. ES5 builds and their polyfills are gone, and v5 supports browsers that implement ES2022.
  • Remove the testing section.
  • If you use the docs-custom-elements-manifest output without setting file, the manifest now goes to dist/custom-elements.json instead of the project root. Delete the old custom-elements.json and update anything that read it, or set file: 'custom-elements.json' to keep it where it was. Stencil also sets the customElements field in your package.json to the manifest's path.
  • Add { type: 'docs-readme' } to outputTargets if you rely on generated component readme files. They're no longer generated unless you ask for them.
  • Set serviceWorker: true on a www output target if your app relies on a service worker. It's no longer generated by default.
  • Check your copy tasks. ignore now adds to Stencil's default exclusions instead of replacing them, and directory copies apply them too, so __mocks__/, dist/ folders and .gitignore files are no longer copied. Give anything you need from those its own task. A copy from outside src now needs a dest.
  • If your project imports from another Stencil library to bundle that library's components into its own, list the library in collections.
  • If you use Rollup plugins, see Module Bundling. Stencil now bundles with Rolldown, which runs most Rollup plugins and replaces @rollup/plugin-commonjs and @rollup/plugin-node-resolve.

Code That Uses Your Library​

  • Remove calls to applyPolyfills(). The loader no longer exports it.

  • If server code reads the result of the ssr output's streamToString() as a Node.js stream, update it: the function now returns a web ReadableStream. See Server-Side Rendering.

  • Update imports of Stencil's internal packages:

    Stencil v4Stencil v5
    @stencil/core/internal@stencil/core/runtime
    @stencil/core/internal/client@stencil/core/runtime/client
    @stencil/core/internal/hydrate@stencil/core/runtime/server
    @stencil/core/cli@stencil/cli
    @stencil/core/dev-server@stencil/dev-server
    @stencil/core/compiler/stencil.js@stencil/core/compiler
  • Use import, not require(), to load any @stencil/core module from a Node.js script. The package is now ES modules only.

6. Move Your Tests​

The stencil test command, with its Jest and Puppeteer integrations, is removed.

7. Build and Check​

Run a build, and fix any warnings it reports:

npm run build

Then check the things a build can't:

  • Load your library the way your consumers do, from a <script> tag or through a bundler.
  • If you publish framework wrappers, build them against the new output.
  • Run npm pack --dry-run, and confirm that every path in your package.json is in the package.