Publishing A Component Library
There are numerous strategies to publish and distribute your component library to be consumed by external projects. One of the benefits of Stencil is that is makes it easy to generate the various output targets that are right for your use-case.
Use Cases
To use your Stencil components in other projects, there are two different output targets to consider: dist
and dist-custom-elements
. Both export your components for different use cases. Luckily, both can be generated at the same time, using the same source code, and shipped in the same distribution. It would be up to the consumer of your component library to decide which build to use.
Lazy Loading
If you prefer to have your components automatically loaded when used in your application, we recommend enabling the dist
output target. The bundle gives you a small entry file that registers all your components and defers loading the full component logic until it is rendered in your application. It doesn't matter if the actual application is written in HTML or created with vanilla JavaScript, jQuery, React, etc.
Your users can import your component library, e.g. called my-design-system
, either via a script
tag:
<script type="module" src="https://unpkg.com/my-design-system"></script>
or by importing it in the bootstrap script of your application:
import 'my-design-system';
To ensure that the right entry file is loaded when importing the project, define the following fields in your package.json
:
{
"exports": "./dist/esm/my-design-system.js",
"main": "./dist/cjs/my-design-system.js",
"unpkg": "dist/my-design-system/my-design-system.esm.js",
}
Read more about various options when it comes to configuring your project's components for lazy loading in the dist
output target section.
Considerations
To start, Stencil was designed to lazy-load itself only when the component was actually used on a page. There are many benefits to this approach, such as simply adding a script tag to any page and the entire library is available for use, yet only the components actually used are downloaded. For example, @ionic/core
comes with over 100 components, but a webpage may only need ion-toggle
. Instead of requesting the entire component library, or generating a custom bundle for just ion-toggle
, the dist
output target is able to generate a tiny entry build ready to load any of its components on-demand.
However be aware that this approach is not ideal in all cases. It requires your application to ship the bundled components as static assets in order for them to load properly. Furthermore, having many nested component dependencies can have an impact on the performance of your application. For example, given you have a component CmpA
which uses a Stencil component CmpB
which itself uses another Stencil component CmpC
. In order to fully render CmpA
the browser has to load 3 scripts sequentially which can result in undesired rendering delays.
Standalone
The dist-custom-elements
output target builds each component as a stand-alone class that extends HTMLElement
. The output is a standardized custom element with the styles already attached and without any of Stencil's lazy-loading. This may be preferred for projects that are already handling bundling, lazy-loading and defining the custom elements themselves.
The generated files will each export a component class and will already have the styles bundled. However, this build does not define the custom elements or apply any polyfills. Static assets referenced within components will need to be set using setAssetPath
(see Making Assets Available).
You can use these standalone components by importing them via:
import { MyComponent, defineCustomElementMyComponent } from 'my-design-system'
// register to CustomElementRegistry
defineCustomElementMyComponent()
// or extend custom element via
class MyCustomComponent extends MyComponent {
// ...
}
define('my-custom-component', MyCustomComponent)
To ensure that the right entry file is loaded when importing the project, define different exports fields in your package.json
:
{
"exports": {
".": {
"import": "./dist/components/index.js",
"types": "./dist/components/index.d.ts"
},
"./my-component": {
"import": "./dist/components/my-component.js",
"types": "./dist/components/my-component.d.ts"
}
},
"types": "dist/components/index.d.ts",
}
This allows us to map certain import paths to specific components within our project and allows users to only import the component code they are interested in and reduce the amount of code that needs to downloaded by the browser, e.g.:
// this import loads all compiled components
import { MyComponent } from 'my-design-system'
// only import compiled code for MyComponent
import { MyComponent } from 'my-design-system/my-component'
If you define exports targets for all your components as shown above and by using customElementsExportBehavior: 'auto-define-custom-elements'
as output target option, you can skip the defineCustomElement
call and directly import the component where you need it:
import 'my-design-system/my-component'
If you are distributing both the dist
and dist-custom-elements
, then it's best to pick one of them as the main entry depending on which use case is more prominent.
Read more about various options when it comes to distributing your components as standalone components in the dist-custom-elements
output target section.
The output directory will also contain an index.js
file which exports some helper methods by default. The contents of the file will look something like:
export { setAssetPath, setPlatformOptions } from '@stencil/core/internal/client';
The contents may look different if customElementsExportBehavior
is specified!
Considerations
The dist-custom-elements
is a direct build of the custom element that extends HTMLElement
, without any lazy-loading. This distribution strategy may be preferred for projects that use an external bundler such as Vite, WebPack or Rollup to compile the application. They ensure that only the components used within your application are bundled into compilation.
Usage in TypeScript
If you plan to support consuming your component library in TypeScript you'll need to set generateTypeDeclarations: true
on the output target in your stencil.config.ts
, like so:
import { Config } from '@stencil/core';
export const config: Config = {
outputTargets: [
{
type: 'dist-custom-elements',
generateTypeDeclarations: true,
},
// ...
],
// ...
};
Then you can set the types
property in package.json
so that consumers of your package can find the type definitions, like so:
{
"types": "dist/components/index.d.ts",
"dependencies": {
"@stencil/core": "latest"
},
...
}
If you set the dir
property on the output target config, replace dist/components
in the above snippet with the path set in the config.
Publishing to NPM
NPM is an online software registry for sharing libraries, tools, utilities, packages, etc. To make your Stencil project widely available to be consumed, it's recommended to publish the component library to NPM. Once the library is published to NPM, other projects are able to add your component library as a dependency and use the components within their own projects.