diff --git a/docs/framework-integration/angular.md b/docs/framework-integration/angular.md index e63721bc2..e608d0438 100644 --- a/docs/framework-integration/angular.md +++ b/docs/framework-integration/angular.md @@ -230,7 +230,7 @@ import { defineCustomElements } from 'stencil-library/loader'; export class ComponentLibraryModule {} ``` -See the [documentation](../output-targets/dist.md#distribution-options) for more information on defining custom elements using the +See the [documentation](../output-targets/dist.md) for more information on defining custom elements using the `dist` output target, or [update the Angular output target](#do-i-have-to-use-the-dist-output-target) to use `dist-custom-elements`. ### Link Your Packages (Optional) diff --git a/docs/guides/publishing.md b/docs/guides/publishing.md index bb2f6dfdb..1c4acdf01 100644 --- a/docs/guides/publishing.md +++ b/docs/guides/publishing.md @@ -5,52 +5,155 @@ description: Publishing A Component Library slug: /publishing --- -# 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](../output-targets/01-overview.md) that are right for your use-case. -## Publishing to Node Package Manager (NPM) +## Use Cases + +To use your Stencil components in other projects, there are two different output targets to consider: [`dist`](../output-targets/dist.md) and [`dist-custom-elements`](../output-targets/custom-elements.md). 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. -The first step and highly recommended step is to -[publish the component library to NPM](https://docs.npmjs.com/getting-started/publishing-npm-packages). NPM is an online software registry for sharing libraries, tools, utilities, packages, etc. 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. +### Lazy Loading +If you prefer to have your components automatically loaded when used in your application, we recommend enabling the [`dist`](../output-targets/dist.md) 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. -## `package.json` +Your users can import your component library, e.g. called `my-design-system`, either via a `script` tag: -The purpose of the `package.json` file is to give other tools instructions on how to find the package's files, and to provide information about the package. For example, bundlers such as [Rollup](https://rollupjs.org/) and [Webpack](https://webpack.js.org/) use this configuration to locate the project's entry files. +```html + +``` -An advantage to using the compiler is that it is able to provide help on how to best set up the project for distribution. Below is a common setup found within a project's `package.json` file: +or by importing it in the bootstrap script of your application: + +```ts +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`: ```json { - "main": "dist/index.cjs.js", - "module": "dist/index.js", - "es2015": "dist/esm/index.mjs", - "es2017": "dist/esm/index.mjs", - "types": "dist/types/components.d.ts", - "unpkg": "dist/my-project-name/my-project-name.esm.js", - "collection:main": "dist/collection/index.js", - "collection": "dist/collection/collection-manifest.json", - "files": [ - "dist/", - "css/", - "loader/" - ] + "exports": "./dist/esm/my-design-system.js", + "main": "./dist/cjs/my-design-system.js", + "unpkg": "dist/my-design-system/my-design-system.esm.js", } ``` -| Property | Description | Recommended | -|----------|-----------------------------------------------------------------------------------------------------|-----------------------------------| -| `main` | Entry file in the CommonJS module format. | `dist/index.cjs.js` | -| `module` | Entry file in the ES module format. ES modules is the standardized and recommended format. | `dist/index.js` | -| `es2015` | Commonly used by framework bundling. | `dist/esm/index.mjs` | -| `es2017` | Commonly used by framework bundling. | `dist/esm/index.mjs` | -| `types` | Entry file to the project's types. | `dist/types/components.d.ts` | -| `unpkg` | Entry file for requests to the projects [unpkg](https://unpkg.com/) CDN. | `dist/{NAMESPACE}/{NAMESPACE}.js` | -| `files` | Array of files that should be included in a npm release. | `["dist/", "loader/"]` | +Read more about various options when it comes to configuring your project's components for lazy loading in the [`dist`](../output-targets/dist.md) 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`](https://www.npmjs.com/package/@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-targets/custom-elements.md) 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](../output-targets/custom-elements.md#making-assets-available)). + +You can use these standalone components by importing them via: + +```ts +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](https://nodejs.org/api/packages.html#exports) in your `package.json`: + +```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.: + +```js +// 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' +``` -The `collection` properties are used to allow lazy loading in other Stencil applications. +If you define exports targets for all your components as shown above and by using [`customElementsExportBehavior: 'auto-define-custom-elements'`](../output-targets/custom-elements.md#customelementsexportbehavior) as output target option, you can skip the `defineCustomElement` call and directly import the component where you need it: + +```ts +import 'my-design-system/my-component' +``` + +:::note +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-targets/custom-elements.md) 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: + +```js +export { setAssetPath, setPlatformOptions } from '@stencil/core/internal/client'; +``` + +:::note +The contents may look different if [`customElementsExportBehavior`](../output-targets/custom-elements.md#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](https://vitejs.dev/), [WebPack](https://webpack.js.org/) or [Rollup](https://rollupjs.org) 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: + +```tsx title="stencil.config.ts" +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: + +```json title="package.json" +{ + "types": "dist/components/index.d.ts", + "dependencies": { + "@stencil/core": "latest" + }, + ... +} +``` :::note -If you are distributing both the `dist` and `dist-custom-elements`, then it's best to pick one of them as the main entry. +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](https://www.npmjs.com/) 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](https://docs.npmjs.com/getting-started/publishing-npm-packages). 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. diff --git a/docs/output-targets/custom-elements.md b/docs/output-targets/custom-elements.md index 1e98c632d..b6fed7bbe 100644 --- a/docs/output-targets/custom-elements.md +++ b/docs/output-targets/custom-elements.md @@ -68,7 +68,7 @@ export const config: Config = { | `default` | No additional re-export or auto-definition behavior will be performed.

This value will be used if no explicit value is set in the config, or if a given value is not a valid option. | | `auto-define-custom-elements` | A component and its children will be automatically defined with the `CustomElementRegistry` when the component's module is imported. | | `bundle` | A utility `defineCustomElements()` function is exported from the `index.js` file of the output directory. This function can be used to quickly define all Stencil components in a project on the custom elements registry. | -| `single-export-module` | All component and custom element definition helper functions will be exported from the `index.js` file in the output directory. This file can be used as the root module when distributing your component library, see [below](#distributing-custom-elements) for more details. | +| `single-export-module` | All component and custom element definition helper functions will be exported from the `index.js` file in the output directory. This file can be used as the root module when distributing your component library, see [Publishing](/publishing) for more details. | :::note At this time, components that do not use JSX cannot be automatically @@ -97,6 +97,8 @@ Setting this flag to `true` results in the following behaviors: 2. Filenames will not be hashed. 3. All imports from packages under `@stencil/core/*` will be marked as external and therefore not included in the generated Rollup bundle. +Ensure that `@stencil/core` is included in your list of dependencies if you set this option to `true`. This is crucial to prevent any runtime errors. + ### generateTypeDeclarations _default: `true`_ @@ -128,32 +130,6 @@ _default: `false`_ Setting this flag to `true` will cause file minification to follow what is specified in the [Stencil config](../config/01-overview.md#minifyjs). _However_, if [`externalRuntime`](#externalruntime) is enabled, it will override this option and always result in minification being disabled. -## Consuming Custom Elements - -By default, the custom elements files will be written to `dist/components/`. This directory can be configured using the output target's [`dir`](#dir) config. - -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](#making-assets-available)). - -Below is an example of defining a custom element: - -```tsx -import { defineCustomElement } from 'my-library/dist/components/hello-world'; - -defineCustomElement(); // Same as manually calling: customElements.define('hello-world', HelloWorld); -``` - -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: - -```js -export { setAssetPath, setPlatformOptions } from '@stencil/core/internal/client'; -``` - -:::note -The contents may look different if [`customElementsExportBehavior`](#customelementsexportbehavior) is specified! -::: - ## Making Assets Available For performance reasons, the generated bundle does not include [local assets](../guides/assets.md) built within the JavaScript output, @@ -180,84 +156,6 @@ Make sure to copy the assets over to a public directory in your app. This config bundling, and where your assets can be loaded from. How the files are copied to the production build directory depends on the bundler or tooling. The configs below provide examples of how to do this automatically with popular bundlers. -## Distributing Custom Elements - -See our docs on [publishing a component library](../guides/publishing.md) for information on setting up the library's `package.json` file and publishing to a package manager. - -By default, custom elements will need to be imported from the [output directory](#dir) specified on the output target config: - -```tsx -import { MyComponent } from 'best-web-components/dist/components/my-component'; -``` - -However, the `module` property in the `package.json` can be modified to point to the custom element output: - -```tsx title="package.json" -{ - "module": "dist/components/index.js", - "dependencies": { - "@stencil/core": "latest" - }, - ... -} -``` - -:::note -Be sure to set `@stencil/core` as a dependency of the package as well. -::: - -As a result, components can alternatively be imported from the root of the published package: - -```tsx -import { MyComponent } from 'best-web-components'; -``` - -:::note -If you are distributing the output of both the -[`dist`](./dist.md) and `dist-custom-elements` targets, then -it's up to you to choose which one of them should be available in the -`module` entry. -::: - -### Usage in TypeScript - -If you plan to support consuming your component library in TypeScript you'll -need to set `generateTypeDeclarations: true` on the your output target in -`stencil.config.ts`, like so: - -```tsx title="stencil.config.ts" -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: - -```tsx title="package.json" -{ - "module": "dist/components/index.js", - "types": "dist/components/index.d.ts", - "dependencies": { - "@stencil/core": "latest" - }, - ... -} -``` - -:::note -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. -::: - ## Example Bundler Configs Instructions for consuming the custom elements bundle vary depending on the bundler you're using. These examples will help your users consume your components with webpack and Rollup. @@ -336,11 +234,3 @@ export default { ], }; ``` - -## How is this different from the "dist" output target? - -The `dist-custom-elements` 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 `dist` output target, on the other hand, is more for projects that want to allow components to lazy-load themselves, without having to setup bundling configurations to do so. - -Luckily, all builds 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. diff --git a/docs/output-targets/dist.md b/docs/output-targets/dist.md index 4279308d8..5c33af5d2 100644 --- a/docs/output-targets/dist.md +++ b/docs/output-targets/dist.md @@ -17,15 +17,6 @@ outputTargets: [ ] ``` - -## How is this different from "dist-custom-elements" output target? - -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`](https://www.npmjs.com/package/@ionic/core) comes with over 100 components, but a one 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. - -The `dist-custom-elements` on the other hand is a direct build of the custom element that extends `HTMLElement`, without any lazy-loading. The custom elements bundle does not apply polyfills, nor automatically define each custom elements. This may be preferred for projects that will handle bundling, lazy-loading and defining the custom elements themselves. - -Luckily, both builds can be generated at the same time, and shipped in the same distribution. It would be up to the consumer of your component library to decide which build to use. - ## Config ### collectionDir @@ -113,29 +104,3 @@ applyPolyfills().then(() => { ``` This is an alternative approach to e.g. loading the components directly through a script tag as mentioned below. Read more about `setNonce` and when to set it in our guide on [Content Security Policy Nonces](../guides/csp-nonce.md). - -## Distribution Options - -Each output target's form of bundling and distribution has its own pros and cons. Luckily you can just worry about writing good source code for your component. Stencil will handle generating the various bundles and consumers of your library can decide how to apply your components to their external projects. Below are a few of the options. - -### Script tag - -- Use a script tag linked to a CDN copy of your published NPM module, for example: ``. -- The initial script itself is extremely tiny and does not represent the entire library. It's only a small registry. -- You can use any or all components within your library anywhere within that webpage. -- It doesn't matter if the actual component was written within the HTML or created with vanilla JavaScript, jQuery, React, etc. -- Only the components used on that page will be requested and lazy-loaded. - -### Importing the `dist` library using a bundler - -- Run `npm install my-name --save` -- Add an `import` within the root component: `import my-component`; -- Stencil will automatically setup the lazy-loading capabilities for the Stencil library. -- Then you can use the element anywhere in your template, JSX, HTML etc. - -### Importing the `dist` library into another Stencil app - -- Run `npm install my-name --save` -- Add an `import` within the root component: `import my-component`; -- Stencil will automatically setup the lazy-loading capabilities for the Stencil library. -- Then you can use the element anywhere in your template, JSX, HTML etc.