前言
在之前介绍了 Web Component 组件库有什么优势,这篇讲一下如何使用 Stencil 搭建 web component 组件库
Stencil 是一个开源的编译器,用于构建可复用、可扩展的 Web Component 组件库,是由 Ionic 团队开源的,方面我们以一种简单高效的方式来创建和管理 Web Component 组件库。
为什么选择Stencil
选择 Stencil 的原因在于它的跨框架兼容性、性能优化以及对标准 Web Components 的支持
Stencil的优势和特点
-
性能优化:自动懒加载、异步渲染等特性使得组件加载更快。
-
跨框架:生成的组件可以在多个框架中使用,如Vue、Angular、React等。
-
JSX 支持:可以使用类似于 React 的方式来编写组件。
-
TypeScript持:使用强类型语言来编写组件,提高了代码的可读性和可维护性
-
测试工具:提供了一套完整的测试工具,可以方便地进行单元测试和端到端测试。
-
社区支持:由Ionic团队维护,社区活跃,资源丰富。
Stencil 搭建开发环境
使用 Stencil 提供的脚手架安装项目,进入 packages
目录,执行创建命令
shell
# npm
npm init stencil
# pnpm
pnpm create stencil
执行完后出现交互性的界面,有三种模式,构建组件库选择第一个 component
- component:是构建一个可以用到任何地方的 Web Components 组件库
- app:是用来构建一个静态网站或者一个应用,支持路由
- ionic-pwa:构建一个 pwa 应用
选择"component"选项将提示您输入项目的名称。
css
✔ Pick a starter › component
? Project name › components
成功创建我们的项目之后,CLI将向控制台输出如下内容
安装依赖,执行 npm start
启动项目
目录结构
初始化目录结构,最外层的目录结构
- dist、loader:默认的输出目录,编译后的文件
- src:源文件目录,包含了组件的代码、样式和测试
- tsconfig.json 是用来配置 ts 的一些 config 配置。
- www :启动本地开发环境后,经过 stencil 编译后 src 的 js 文件和
index.html
会被执行复制过移动到 www 文件夹中去,而启动的热服务器(live server)访问就会被映射到此文件夹,从而进行渲染和调试。
lua
├── dist
├── loader
├── src
├── package.json
├── readme.md
├── stencil.config.ts
├── tsconfig.json
└── www
组件和配置目录结构
创建组件
在新建项目后,package.json
默认添加了如下脚本命令,其中 stencil generate
是创建组件的命令
json
{
"scripts": {
# 构建组件
"build": "stencil build",
# 启动开发
"start": "stencil build --dev --watch --serve",
# 端对端测试
"test": "stencil test --spec --e2e",
"test.watch": "stencil test --spec --e2e --watchAll",
# 创建组件
"generate": "stencil generate"
}
}
运行 pnpm generate
,提示创建的组件是否要添加 样式、单元测试、端对端测试文件
输入组件名称 my-button
,直接回车在 components
目录下创建一个 my-button
组件目录
Stencil 使用 tsx
进行开发,类似 react ,提供完整的声明周期函数和装饰器等功能,查看 my-component.tsx
文件
- @Component() 声明一个新的 Web 组件
- @Prop() 声明一个公开的属性/属性
jsx
import { Component, Prop, h } from '@stencil/core';
import { format } from '../../utils/utils';
@Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true,
})
export class MyComponent {
// 声明一个公开的属性/属性
@Prop() first: string;
/**
* The middle name
*/
@Prop() middle: string;
/**
* The last name
*/
@Prop() last: string;
private getText(): string {
return format(this.first, this.middle, this.last);
}
render() {
return <div>Hello, World! I'm {this.getText()}</div>;
}
}
使用组件像普通 html
标签一样使用,传入 first
、last
会被 @Prop
声明的属性接收,修改它们的值,组件内部会响应式的更新渲染视图
jsx
<my-component first="Stencil" last="'Don't call me a framework' JS"></my-component>
stencil 工作原理
入口文件
src/index.html
是用来启动本地 dev 环境时候的一个入口文件,在初始化过程中,它会默认添加需要的 js 产物的路径,如下:
html
<head>
<script type="module" src="/build/editor.esm.js"></script>
<script nomodule src="/build/editor.js"></script>
</head>
<body>
<my-component first="Stencil" last="'Don't call me a framework' JS"></my-component>
</body>
在启动后,根据 stencil.config.ts
命名空间字段设置如 namespace: 'editor'
,会将编译 js 文件生成在 dist/editor
目录下,同时会拷贝一份到 www/build
文件夹提供给服务器访问,src/index.html
也会一并被复制过去
可以看到在启动后服务器访问的 js 目录 www/build
和 dist/editor
文件是一样的
www 本地调式目录
在启动本地命令 stencil build --dev --watch --serve
后,改动下组件的内容后会发现,www
文件夹内的资源文件也随之更新,浏览器相应界面的也发生变化,执行逻辑
所以 www
文件夹存放的是开发调试所包含资源文件,build
文件夹是编译的产物;host.config.json
会存放一些本地热更新服务器的配置,比如缓存的配置等;而 index.html
则是经过压缩的 src/index.html
文件。
stencil.config.ts 配置
stencil.config.ts
文件是 stencil 工具的配置文件,可以在此个性化的配置打包功能,比如:
- globalScript:全局脚本入口文件,可以用来实现在初始化组件,进行挂载的时候进行一些逻辑的处理
- globalStyle : 全局的样式路径,如设置
var
函数的主题变量 - plugins 插件 : 配置一些编译过程中的额外功能,比如
sass
插件可以用来适配scss
文件的编译,postcss
可以用来压缩、增加一些兼容性的代码来适配各种浏览器等等,官方还提供了以下插件:@stencil/less
、@stencil/postcss
、@stencil/sass
、@stencil/stylus
- outputTargets:根据配置 target 进行打包
dist / loader 打包产物
执行 pnpm run build
命令进行打包,最终生成编译产物如下,包括 dist
和 loader
两个目录
通过观察 stencil
默认生成的 package.json
文件中 files
自动的配置也能知道,最终输出的文件资源 dist
和 loader
发布到 npm 上
json
"files": [
"dist/",
"loader/"
],
那么这两个文件夹有什么关系呢,我们先看下 loader 文件夹 package.json
的内容和其他文件的内容
loader 目录
json
{
"name": "editor-loader",
"private": true,
"typings": "./index.d.ts",
"module": "./index.js",
"main": "./index.cjs.js",
"jsnext:main": "./index.es2017.js",
"es2015": "./index.es2017.js",
"es2017": "./index.es2017.js",
"unpkg": "./cdn.js"
}
// index.js
export * from '../dist/esm/polyfills/index.js';
export * from '../dist/esm/loader.js';
// index.es2017.js
export * from '../dist/esm/polyfills/index.js';
export * from '../dist/esm/loader.js';
//index.cjs.js
module.exports = require('../dist/cjs/loader.cjs.js');
module.exports.applyPolyfills = function() { return Promise.resolve() };
可以看出,loader 文件夹是兼容 ESModule
和 Common.js
模块规范,根据当前的使用环境,分别引入 dist 的不同的文件夹产物内,比如 cjs 的引入类型会加载 ../dist/cjs/loader.cjs.js
;es6+ 的引入会加载 polyfills
文件和 esm 模式的资源文件 ../dist/esm/loader.js
。
dist 目录
在默认的 stencil.config.ts 配置下,打包结果为
├── cjs
├── collection
├── components
├── esm
├── editor
├── index.cjs.js
├── index.js
└── types
其中 cjs 和 esm 的逻辑几乎相同,esm 只是多了一些兼容性的代码,它们都是为了适配不同的环境所构造的不同产物
stencil 初始化配置里面默认包含了 dist-custom-elements
,意思就是可以单独打包出符合 web components 规范的组件 class
打包的组件单独引入会进行自动注册
js
import { HelloWorld } from 'my-library/dist/components/hello-world';
customElements.define('hello-world', HelloWorld);
打包的文件夹的内容与 www/build
文件内容相同,因为 stencil 也默认开启了 www 编译的 outTarget,所以也会编译出一份可以直接用于本地环境的文件
json
outputTargets: [
{
type: 'dist',
esmLoaderPath: '../loader',
},
{
type: 'dist-custom-elements',
customElementsExportBehavior: 'auto-define-custom-elements',
externalRuntime: false,
},
{
type: 'docs-readme',
},
{
type: 'www',
serviceWorker: null, // disable service workers
},
],
如果能对新技术开发组件库感兴趣,也欢迎加入stencil-component-ui,给个 star 鼓励一下 👏👏