2022 年自己投入了 Vite 的怀抱,开始参加到 Vite 社区中,陆续开发了一些插件。
Vite 秉承了开箱即用,简化配置的思路,的确显著晋升了前端开发体验。
然而在类库模式的构建上却有所欠缺,只能解决单个输出和单输出出的状况,构建场景繁多,Vite 社区上目前也没有可间接应用的工具,所以才有了开发一个 对立构建 插件的想法。
目前 vite-plugin-build 插件已能够间接应用,也录入了 Vite 官网 awesome-vite,心愿也刚好能满足一些人的须要。
什么是对立构建?
因为没有特地好的叫法,自己暂且把这叫做 对立构建 ,自己把 对立构建 演绎为如下构建:
-
Bundle 构建
即
Vite
(也是 Rollup)的库打包模式,单输出文件,单输入 bundle 文件,如果没有设置内部依赖(external)所有波及的依赖包都会打包到一个 bundle 文件中。长处:反对 umd 格局,浏览器中可作为内部依赖,不受业务代码 bundle 影响,可利用浏览器缓存机制,进步加载性能。
毛病:不反对 Tree Shaking 没有应用到的代码也会加载进来,因为打包到一个 bundle 文件,本地源码可读性差。
-
文件夹构建(文件到文件转换器,file-to-file transformer)
文件夹所有的合乎格局的文件(
['ts', 'tsx', 'js', 'jsx', 'vue', 'svelte']
)会转换为对应同名的.js
文件,只反对commonjs
和es
格局。转换的时候,所有的
import
依赖包不会打包进来,依据须要转换的格局转换为commonjs
的require
或者es
的import
语法。长处:es 模式可反对 Tree Shaking,本地源码可读性高。
毛病:代码在 Webpack、Vite 这些构建工具中会和业务代码一起打包到 bundle 文件中,很难利用跨站点缓存劣势。
-
生成 TypeScript 申明文件
反对原生 TypeScript、Vue TypeScript 和 Svelte TypeScript 申明文件的生成(如果有其余类型的框架也能够在此拓展)。
vite-plugin-build
通过 Vite 配合应用 @vitejs/plugin-react
、@vitejs/plugin-vue
、@sveltejs/vite-plugin-svelte
可反对下面的三种构建形式。
为什么要开发一个 Vite 对立构建插件?
理由一,Vite 构建场景繁多,不反对如下场景:
- 多输出多输入(多输出多 bundle)
- 转换文件夹(文件转文件的转换形式,不打包成一个 bundle 文件)
- 生成 TypeScript 申明文件
理由二,Vite 社区不足可间接代替的工具。
Vite Github 上官网插件库应用的是 unbuild,是一款对立构建的工具,尽管挺不便的,然而 unbuild 更偏向于解决纯 JavaScript 或者 TypeScript 的代码,对于 React、Vue、Svelte 等浏览器 UI 类型相干的打包不足相干的转换解决。
理由三,Vite 体系一条龙服务。
Vite 对立构建插件,可在一些场景下让 Vite、Vitest 造成以个闭环体系,无需用到其余的构建和单元测试工具,一个 Vite 配置文件闯天下。
萌发想法
Vite
还没有衰亡的之前,公司组内业务组件和集体的一些 Github 我的项目一开始是应用 Rollup
进行构建,Rollup
并不能开箱即用,还须要各种插件配置,绝对较繁琐。
起初应用 Vite
的库模式来代替原生的 Rollup
能够缩小不少的插件配置,不过整个文件夹的所有文件独自转换为 commonjs 和 es 的格局,还是须要通过 Vite
提供的 build
API 实现,如下是通过指定文件夹下的所有指标文件,而后遍历运行 build
形式实现(还须要多一个 Vite 配置文件来配置):
const fs = require('fs');
const path = require('path');
const spawn = require('cross-spawn');
const srcDir = path.resolve(__dirname, '../src');
// 所有 src 文件夹包含子文件夹的 js、ts、jsx、tsx 文件门路数组
const srcFilePaths = getTargetDirFilePaths(srcDir);
srcFilePaths.forEach((file) => {const fileRelativePath = path.relative(srcDir, file);
spawn(
'npm',
['run', 'vite', '--', 'build', '--mode', fileRelativePath, '--outDir', 'es', '--config', './vite.file.config.ts'],
{stdio: 'inherit',},
);
});
同时还须要配置 npm run tsc
生成申明文件,pacakge.json
scripts
字段比拟繁琐:
{
"scripts": {
"tsc:es": "tsc --declarationDir es",
"tsc:lib": "tsc --declarationDir lib",
"tsc": "npm run tsc:lib && npm run tsc:es",
"vite": "vite",
"build:lib": "vite build",
"build:file": "node ./scripts/buildFiles.js",
"build": "npm run tsc && npm run build:file && npm run build:lib"
}
}
新的我的项目,就间接复制批改一下,尽管也能达到构建的目标,然而就是不够不便,自己懒 , 所以还是想有没有更简略点的形式?如下方应用一个 script
就能够解决?
{
"scripts": {"build": "vite build"}
}
实现思路
首先齐全通过 Vite 配置文件是无奈实现对立构建的性能,即通过失常运行一次 Vite 构建服务无奈实现对立构建的性能。
还是得 屡次运行 Vite 构建服务 来实现此性能(外表上使用者无感知)。
实现的关键点
Bundle 构建
Vite 库模式就是 bundle 构建模式,不过只能设置一个入口文件,一个 bundle 输入文件。
理论场景可能须要多个入口文件,多个 bundle 输入文件,这个性能不简单,通过遍历多个 vite build
构建即可实现,代码大抵如下:
import {build} from 'vite';
const buildPs = lastBuildOptions.map((buildOption) => {
return build({
...viteConfig, // 透传 vite.config.ts 或者 vite.config.js 的用户配置,插件须要过滤本身(vite-plugin-build)mode: 'production',
configFile: false,
logLevel: 'error',
build: buildOption,
});
});
await Promise.all(buildPs);
文件夹构建
实现思路也不简单,如下:
- 获取文件夹中所有合乎的文件门路
- 遍历所有文件门路,运行
vite build
构建 - 因为 Vue 和 Svelte 的 import 是须要带后缀名的,须要额定移除文件内容中的
.vue
和.svelte
后缀名。
简略代码实现大抵如下:
import {build} from 'vite';
import fg from 'fast-glob';
const {
inputFolder = 'src',
extensions = ['ts', 'tsx', 'js', 'jsx', 'vue', 'svelte'],
ignoreInputs,
...restOptions,
} = options;
// 获取默认为我的项目根目录下 src 文件夹包含子文件夹的所有 js、ts、jsx、tsx、vue、sevele 文件门路,除开 .test.*、.spec.* 和 .d.ts 三种后缀名的文件
// 返回格局为 ['src/**/*.ts', 'src/**/*.tsx']
const srcFilePaths = fg.sync([`${inputFolder}/**/*.{${extensions.join(',')}}`], {ignore: ignoreInputs || [`**/*.spec.*`, '**/*.test.*', '**/*.d.ts', '**/__tests__/**'],
});
const buildPromiseAll = srcFilePaths.map((fileRelativePath) => build({
...viteConfig, // 透传 vite.config.ts 或者 vite.config.js 的用户配置,插件须要过滤本身(vite-plugin-build)mode: 'production',
configFile: false,
logLevel: 'error',
build: {
lib: {
entry: fileRelativePath,
...
},
...restOptions
},
}));
await Promise.all(buildPromiseAll);
await removeSuffix(); // 移除生成文件内容中的 `.vue` 和 `.svelte` 后缀名
TypeScript 申明文件生成
前端 UI 框架的多种多样,像 Vue、Svlete 这类有本身自定义的语法,TypeScript 的语法须要非凡反对,生成申明文件天然也须要非凡解决。
原生 TypeScript
原生的 TypeScript 官网间接提供 tsc
工具可间接应用,通过间接运行 tsc
bin 文件,并传递对应的配置即可实现。
简略的代码实现如下:
import spawn from 'cross-spawn';
const {rootDir, outputDir} = options;
const tscPath = path.resolve(require.resolve('typescript').split('node_modules')[0], 'node_modules/.bin/tsc');
spawn.sync(
tscPath,
['--rootDir', rootDir, '--declaration', '--emitDeclarationOnly', '--declarationDir', outputDir],
{stdio: 'ignore',},
);
Vue TypeScript
Vue 3 进去后,对 TypeScript 的反对就比较完善了,Vue 社区的 vue-tsc
能够用来代替 tsc
,用法放弃和 tsc
统一。
有一点不一样的是 vue-tsc
生成的申明文件是 .vue.d.ts
后缀名的,所以须要重命名为 .d.ts
后缀名。
简略的代码实现如下:
import spawn from 'cross-spawn';
const {rootDir, outputDir} = options;
const vueTscPath = path.resolve(require.resolve('vue-tsc/out/proxy').split('node_modules')[0],
'node_modules/.bin/vue-tsc',
);
spawn.sync(
vueTscPath,
['--rootDir', rootDir, '--declaration', '--emitDeclarationOnly', '--declarationDir', outputDir],
{stdio: 'ignore',},
);
if (isVue) {renameVueTdsFileName(); // 重命名 .vue.d.ts 为 .d.ts
}
Svelte TypeScript
Svelte 社区没有像 Vue 那么弱小,没有相似 vue-tsc
这样的工具,最终找到 svelte-type-generator 能够实现,参考 svelte-type-generator
的代码实现了,生成申明文件的性能(临时不反对 tsc
cli 的性能)。
const {compile} = require('svelte-tsc');
const {rootDir, outputDir} = options;
compile({
rootDir,
declaration: true,
emitDeclarationOnly: true,
declarationDir: outputDir,
});
if (isVue) {renameSvelteTdsFileName(); // 重命名 .svelte.d.ts 为 .svelte.d.ts
}
打印构建信息
因为是触发运行多个 vite build
所以如果间接输入默认的构建信息,那么会显得凌乱,无奈像运行一个 vite build
输入构建信息。
所以得拦挡和暗藏原有的构建信息,自定义输入新的构建信息。
-
拦挡和暗藏原有的构建信息,通过重写
console.log
和console.warn
能够达到目标,代码如下:export const restoreConsole = {...console}; export class InterceptConsole { public log: typeof console.log; public warn: typeof console.warn; public clear: typeof console.clear; constructor() {const { log, warn} = console; this.log = log; this.warn = warn; } silent() {console.log = () => {}; console.warn = () => {}; } restore() { console.log = this.log; console.warn = this.warn; } }
-
自定义输入新的构建信息
性能参考 Vite 的内置 reporter 插件,通过对应的钩子函数,实现构建信息的输入。
最终成果
Github 例子
vanilla
vanilla-ts
react
react-ts
vue
vue-ts
svelte
svelte-ts
所有例子,运行如下命令即可
$ npm install
$ npm run build
Codesandbox 在线例子
- vanilla-ts
- react-ts
- vue-ts
- svelte-ts
后续
如果感兴趣的也能够退出一起建设,目前 svelte-tsc
实现和 vue-tsc
一样的用法,是有点挑战性的。
- 生成申明文件反对配置 tsconfig 配置文件门路
svelte-tsc
反对 bin 文件,性能参照vue-tsc
,反对所有tsc
的 cli 选项。