Vite + storybook 搭建自己的 React 组件库
Vite 以原生 ESM 方式提供源码,使用 esbuild 预构建依赖,将启动速度提高了10-100倍,用 Rollup 打包代码(不排除未来使用 esbuild 打包代码)
为什么选择 Vite ?
尤雨溪主题演讲《2022 前端生态趋势》在工具链一节中的工具链的抽象层次提到过关于 Vite 的定位。
- browserify/webpack/rollup,专注于打包,抽象层次低。
也就是想要用这些工具做一个应用时候,需要大量使用第三方插件,以及大量的配置。
- Parcel/Vue-CLI/CRA 专注于应用,抽象层次高。
相对而言的缺点是比较复杂庞大的黑盒。 当你需要自定义的定制时,会不可避免的遇到和默认功能出现一些冲突的时候。
- Vite 的 CLI 专注于应用,抽象层次高(有很多开箱即用的配置)。API 专注于支持上层框架,抽象层次中。
有很多新出来的框架都基于 Vite 作为底层的工具链。
最后选择 Vite 而非 webpack,除了 Vite 的速度之外,易集成和开箱即用的配置,也是选择的因素。
Storybook-builder-vite 使得 storybook + webpack 的默认变成了 storybook + webpack/vite。
Vite 库模式的配置
先使用 Vite 搭建项目,并选择你需要的模板。
npm init vite
把 src 内的文件清空,index.html 也可以删了。
构建 es
和 umd
格式代码
vite.config.ts 的配置:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { resolve } from "path";
export default defineConfig({
plugins: [react()],
build: {
lib: {
// 入口文件将包含可以由你的包的用户导入的导出:
entry: resolve(__dirname, "src/components/index.tsx"),
name: "ReactComponents",
fileName: (format) => `react-components.${format}.js`,
},
rollupOptions: {
// 确保外部化处理那些你不想打包进库的依赖
external: ["react", "react-dom"],
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {
react: "React",
"react-dom": "react-dom",
},
},
},
},
});
目前构建出来的代码是只有 js,而没有 ts 类型声明的。
构建增加 ts 类型声明
Vite 虽然天然支持引入 .ts
文件,但 Vite 是使用 esbuild 将 Typescript 转译为 Javascript,而 esbuild 不做任何类型检查或声明输出。
所以要生成类型声明,我们可以使用tsc
。 tsconfig.json 配置如下:
"compilerOptions": {
// 指定输出目录
"outDir": "dist",
// 不生成输出文件
"noEmit": false,
// 指定编译的文件
"include": ["src/**/*"],
}
还需要在 tsconfig.node.json
(专门用于 vite.config.ts
的 TypeScript 配置文件)配置如下:
{
"exclude": ["src/stories/*"]
}
这样打包生成 ts 声明的时候,可以排除文档这一块。
配置 package.json
"scripts": {
"build": "vite build && tsc --declaration --emitDeclarationOnly",
}
tsc 的命令一定要在 vite build 之后执行,不然生成的声明会被清空。 (打包出来的结果不会把你的 .d.ts 给打包进去,改成 .ts 就可以了)
指定 npm 包加载的入口文件
配置 package.json
"main": "./dist/react-components.umd.js",
"module": "./dist/react-components.es.js",
"typings": "./dist/components/index.d.ts",
"exports": {
".": {
"import": "./dist/react-components.es.js",
"require": "./dist/react-components.umd.js"
}
}
更多关于 package.json 文件相关的配置,可以看看 这篇文章 (这篇文章没有提到 exports
,这个字段主要是用来限制导出的,作为补充可以看看 这篇 )
关于发布 NPM 包
发布的时候你希望只发布打包的文件,可在 package.json 中通过 files 字段去控制。
"files": [ "dist" ]
最后 npm publish 发布就好了。
组件内置引入 css
当你直接引用组件时,会发现样式丢失了。
查看 vite 打包出来的 index.es.js
文件,可以发现并没有 import css,所以在直接使用组件的时候,会导致需要单独引入组件样式,才能生效。
可以使用 vite-plugin-libcss 插件,在打包出来的 index.es.js
的第一行自动加上 import style.css
,解决需要单独引用组件样式的问题。
到目前为止,关于库的打包发布就结束了,下一步,文档。
storybook 文档
初始化文档
在 vite 的项目内执行以下代码,初始化 Storybook。
npx sb init --builder @storybook/builder-vite
//or
npx storybook init --builder vite
Storybook 的一个主要优势是用插件扩展 Storybook 的用户界面和行为(Controls,Docs,Accessibility),大多数功能都是以插件实现的。 (这里可以找到 storybook 核心团队开发的 “官方” 插件)
默认情况下,Storybook 会默认安装“基本”插件(@storybook/addon-essentials),增加用户初始体验。
QA
如何查看完整源代码
我们点击 show code 显示的是经过精简的源代码片段。
简单组件是没问题的,但如果组件复杂一点的话,其实是并不友好。因为无法直接复制这个示例组件的完整代码直接进行复用。
源文档块 似乎无法通过配置取消自动精简源代码,需要安装 @storybook/addon-storysource 插件,这可以帮助我们看到 stories 内的源代码。
这时候 show code 显示代码,就显得有点重复多余了,可通过配置 parameters,把 show code 给关掉。
export default {
parameters: {
docs: {
page: null,
},
},
};
配置路径别名
在配置 Vite 的 resolve.alias 路径别名时,需要注意始终使用绝对路径,相对路径的别名值会被原封不动地使用,导致无法正常解析。
但默认情况下,@storybook/builder-vite 是不会读取 vite.config.ts 文件。
所以需要在 .storybook/main.js|ts
内配置:
const { mergeConfig } = require('vite');
const path = require('path');
module.exports = {
async viteFinal(config) {
return mergeConfig(config, {
resolve: {
alias: { ReactComponents: path.resolve(__dirname, '../src/components/index.tsx') },
},
});
},
};
ts 也要记得配置下,在 tsconfig.json
内配置
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"ReactComponents": ["src/components/index.tsx"]
}
},
}
预览文档
如何预览自己打包出来的组件库文档呢?
因为打包出来的文件夹名字是 storybook-static,所以让我们在 package.json 写入以下代码:
"scripts": {
"preview-storybook": "npx http-server storybook-static"
},
执行一下,然后就可以看到本地服务启动的可以访问的链接啦。
实践建议
- 当示例涉及到多个组件时,或参数数量较多时。可以通过 argTypes 对参数进行分组,从而使结构更清晰。
export default {
argTypes: {
param: {
table: { category: 'Group' },
},
},
};
- 编写 stories 时,开发者应该像用户一样使用组件(这是在 dumi 中学到的)。
// 正确示例
import { Button } from 'ReactComponents';
// 错误示例,用户不知道 Button 组件是哪里来的
import Button from './index.tsx';
import Button from '@/Button/index.tsx';
同时,记得把 stories 内的 Default export 写到最下面
// Button.stories.js|jsx
import React from 'react';
export const Primary = () => <Button primary>Button</Button>;
export default {
title: 'Button',
component: Button,
};
这样子在查看组件源码的时候,会更加的友好。
按照这一原则,demo 还能直接被用户直接拷贝到项目中使用。
参考 :
转载自:https://juejin.cn/post/7183265085884104763