Stencil 搭建 Web Component 组件库项目(工作原理解析)

前言

在之前介绍了 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 标签一样使用,传入 firstlast 会被 @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/builddist/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 命令进行打包,最终生成编译产物如下,包括 distloader 两个目录

通过观察 stencil 默认生成的 package.json 文件中 files 自动的配置也能知道,最终输出的文件资源 distloader 发布到 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 文件夹是兼容 ESModuleCommon.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 鼓励一下 👏👏

相关推荐
鑫宝Code1 小时前
【React】React Router:深入理解前端路由的工作原理
前端·react.js·前端框架
沉默璇年10 小时前
react中useMemo的使用场景
前端·react.js·前端框架
2401_8827275711 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
红绿鲤鱼12 小时前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
zhenryx14 小时前
前端-react(class组件和Hooks)
前端·react.js·前端框架
Thomas游戏开发15 小时前
Unity3D 逻辑服的Entity, ComponentData与System划分详解
前端框架·unity3d·游戏开发
前端青山1 天前
webpack进阶(一)
前端·javascript·webpack·前端框架·node.js
沉默璇年1 天前
react中Fragment的使用场景
前端·react.js·前端框架
Fanfffff7202 天前
React中组件通信的几种方式
前端·react.js·前端框架
前端青山2 天前
React 中的Props特性及其应用
前端·javascript·react.js·前端框架