您现在的位置是:首页 >技术交流 >vite + vue3 + storybook + ts 搭建组件库记录网站首页技术交流
vite + vue3 + storybook + ts 搭建组件库记录
目标
- 只按需引入,不依赖babel-import-plugin 插件。
- 第三方依赖都不打包。
- 用原生fetch请求数据。
- 仅支持esmodule。配置package.json type:"module"
搭建
根据storybook 官网文档,需要在已有的项目中运行
npx storybook@latest init
也就是事先需要通过vite创建一个项目。
npm create vite@latest
之后再运行storybook 的命令,storybook会自动分析使用的构建工具,安装好需要的依赖,并自动新增文件。
storybook 增加了/src/stories目录,用于存放组件。
默认情况下,/src/stories 中放着 .vue, .stories, .css 文件,由于我希望把组件源代码单独放在一个文件夹下
目录预设
因此在项目根目录 增加 /packages 目录,下面存放以组件名称命名的文件夹。以每个文件夹为单位表示为一个组件。
/src/stories 中.stories 文件引用/packages 目录下的.vue 用于生成storybook演示文档。
对于vite 的普通打包模式下,入口必须为index.html,因此需要使用vite 支持的lib模式打包。
lib模式下,对于图片等静态资源,处理为base64内联到代码中。且不支持配置。
base64的问题在于增大js体积,且不利于相同图片的复用。这块待研究。
构建vue组件
直接使用vite build 命令,会使用项目根目录下的vite.config.js 对指定的入口进行构建。构建产物默认输出到dist目录下。
lib模式下,构建产物有
- index.js
- style.css
对于一个vue组件来说,这种格式正式我想要的。
而/package 目录下面会有好多组件,因此需要借助vite 的JavsScript API 进行循环
vite JS API 构建 /packages 目录
根据官网描述,使用下面js代码, 可以通过js启动vite 打包。
import { build } from 'vite';
build({
//... vite config
})
这里的 build() 方法传入的config对象,会和项目下的 vite.config.js 合并。
所以将一些公共的配置可以写在 vite.config.js下
vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import dts from 'vite-plugin-dts';
// https://vitejs.dev/config/
export default defineConfig({
build: {
rollupOptions: {
external: ['vue'], // 排除三方包
},
},
plugins: [
vue(),
vueJsx(),
dts({
outputDir: './lib',
entryRoot: './packages',
}),
],
});
build.js 主要的构建方法
/**
* 构建一个组件
* @param {string} compName 组件名称,对应packages目录下的文件夹名称
*/
async function buildAComponent(compName) {
const entry = path.join(packagesDir, compName, 'index.ts');
const out = path.join(outDir, compName);
await build({
build: {
outDir: out,
lib: {
entry: [entry],
formats: ['es'],
},
},
});
}
这个是构建一个组件的方法,因此,我只要通过node 的 fs 模块读取并分析/packages 路径下的文件夹,遍历出来构建即可。
build.js代码
import { readdirSync, writeFileSync } from 'fs';
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import { build } from 'vite';
const __dirname = dirname(fileURLToPath(import.meta.url));
const packagesDir = path.join(__dirname, './packages');
const outDir = path.join(__dirname, './lib');
async function main() {
const packages = readdirSync(packagesDir);
let indexFileContent = '';
const exportNames = [];
const promise = [];
for (const folderName of packages) {
// const stats = lstatSync(path.join(packagesDir, compName));
// if (stats.isDirectory()) {
// // 如果是目录,则构建
// }
const firstLetter = folderName[0];
const isFirstLetterUpperCase = /[A-Z]/.test(firstLetter);
if (isFirstLetterUpperCase && folderName.indexOf('.ts') === -1) {
promise.push(buildAComponent(folderName));
indexFileContent += `import { ${folderName} } from './${folderName}/index.js';
`;
exportNames.push(folderName);
}
}
await Promise.all(promise);
indexFileContent += `export { ${exportNames.join(', ')} };`;
writeFileSync(path.join(outDir, 'index.js'), indexFileContent);
console.log('created: lib/index.js');
}
main();
/**
* 构建一个组件
* @param {string} compName 组件名称,对应packages目录下的文件夹名称
*/
async function buildAComponent(compName) {
const entry = path.join(packagesDir, compName, 'index.ts');
const out = path.join(outDir, compName);
await build({
build: {
outDir: out,
lib: {
entry: [entry],
formats: ['es'],
},
},
});
}
main 函数中判断文件夹名称前面是大写字母开头的才当作组件来处理。
构建结果
- /package/Button
- index.ts
- style.less
- Button.vue
这一个目录入口为index.ts 构建完成后,输出到lib
- /lib/Button
- index.js
- style.css
至此,主要构建完成了
库入口
由于一个npm库需要有一个默认的入口js,因此需要在/lib 目录下面增加一个index.js 用于导入lib 下面所有组件,并导出。这部分代码在上面 build.js 中已经体现。
/lib/index.js
export { Button } from './Button/index.ts';
package.json 增加配置
{
"main": "lib/index.js",
"module": "lib/index.js",
"files": [
"lib",
"packages"
],
//...
}
d.ts生成
使用vite-plugin-dts 插件
import dts from 'vite-plugin-dts';
// ...
plugins:[
// vue(),
// vueJsx(),
dts({
outputDir: './lib',
entryRoot: './packages',
}),
]
构建dts速度有点慢 。
这样配置,会在lib/Button/ 下面增加d.ts 文件了。
与库的默认入口文件一样,需要指定默认declare 文件,package.json types 指定为 /packages/main.ts。(这里暂时指定.ts 文件,可能在一些构建工具中不识别,因为那些工具只识别d.ts 文件)
实现自动引入
一个组件默认的构建结果为index.js, style.css
鉴于使用的情况下引入 import { Button } from 'xxx'; 之外还要 import 'xxx/lib/Button/style.css';
使用babel-import-plugin 可以解决按需引入样式问题。
由于我的目标是不使用babel-import-pluing 因此,完成这个效果,只需要在/lib/Button/index.js 文件的最上方引入/lib/Button/style.css即可。
方案
- 使用node fs读写文件。
- 借助vite plugin
使用vite plugin 实现
因为不想用node
由于vite基于rollup,根据rollup 官网文档,我们可以在插件的 generateBundle 钩子中在生成产物前操作文件内容。
代码
/**
* 在产物js上导入css
*/
function () {
return {
name: 'auto-import-style',
generateBundle(options, bundle) {
bundle['index.js'].code = 'import "./style.css";
' + bundle['index.js'].code;
},
};
},
npm上也有类似的插件 vite-plugin-libcss
构建vue组件要点
不能将vue依赖打包进代码中,否则会导致组件无法使用。
打包组件的产物js中,最上方应该为 import {} from 'vue'; 这样的代码。
通过配置vite.config.js 下的rollupOptions.external即可:
build: {
rollupOptions: {
external: ['vue'],
// output: {
// globals: {
// vue: 'Vue', // umd需要
// },
// },
},
},
这里注释掉rollupOptions.output.globals 原因是默认构建lib会输入umd 和 es 模块的文件。我构建时会指定 build.lib.formats: ['es'] 就不需要了。
增量构建方案预设
由于现在我看使用vite-plugin-dts 生成d.ts 文件使用的时间过长。先制定一下增量构建的方案。
鉴于在正常开发过程中,始终用master分支作为正式分支。
因此在开发分支中,可以借助git 来对比当前开发分支与master分支的区别
使用node 的child_process 执行命令
git diff HEAD master --stat
可检查当前分支,对比master,哪些文件有变化。
得到控制台输出后,分析产物,确定package.json 中哪些组件有变动。以此仅构建变更过的组件。
vite可能会覆盖lib目录。构建前先保存一版lib目录?还是有官方配置。暂不研究。