0. Vite 与 ESM
与 Webpack 不同,Vite 以 ESM 为其唯一的模块管理规范。首先,在开发环境,它会把每个文件编译成独立的 ESM 模块,实现非常快速的热加载。其次,编译打包时,它默认的目标环境是 ES2019,支持 ESM,所以模块打包后,也会使用 ESM 加载。
这给我们带来一个好处:如果用 Vite 开发项目,并且对其进行分包,构建之后放到线上(比如发布到 NPM);接下来我们就可以在其它项目中,使用 ESM 方式加载这个项目的代码。
举个简单的例子。lodash 有一个同步发布的 lodash-es 包,功能完全一致,只是使用 ESM 构建,我们可以直接在代码中 import forEach from 'https://unpkg.com/lodash-es@4.17.21/forEach.js'
引用。一方面可以节省我们自己的带宽;另一方面如果用户在其它应用里使用过同一个库,就可以提高速度。
1. Vite 分包
Vite 把构建过程委托给 Rollup,所以构建时分包需要传参给 rollupOptions
。
在某个项目中,我需要整合一批 codepen 上的绘图效果,这些效果都放在 /src/effects/
目录下,所以我就要检查这个目录,并且生成对应的分包配置。
export default defineConfig(async () => {
// 从 v10.10 开始,node.js 的 `fs.readdir()` 函数支持 `withFileTypes` 参数,使用这个参数可以直接返回 `fs.Dirent` 对象,类似使用 `fs.stat()` 得到的 `fs.Stats` 对象。方便我们判断对象类型
const files = await readdir(effectsDirectory, {withFileTypes: true});
// 把目录下的内容分为两类,一个是基础类库,一个是不同特效
const [effects, baseFiles] = files.reduce(([effects, base], file) => {
const {name} = file;
if (file.isDirectory()) {
effects.push(name);
} else if (file.isFile()) {
base.push(name);
}
return [effects, base];
}, [[], []]);
return {
build: {
rollupOptions: {
manualChunks(id) {
// effects/some-effect 下的文件按目录分别打包
const effect = effects.find(effect => id.includes(`/${effect}/`));
if (effect) {
return effect;
}
// 效果基类打包成一个文件,因为效果只需要基类,所以从主体剥离
const baseFile = baseFiles.find(base => id.endsWith(base) && !/p5/i.test(id));
if (baseFile) {
return 'BaseEffect';
}
// p5 是个很大的效果库,官方不提供 esm 包,只能单独打一个
if (/p5/i.test(id)) {
return 'p5';
}
// 其它依赖正常打包,只在本项目中使用,不会被引用
return id.includes('node_modules') ? 'vendor' : 'chuck';
},
},
// 这个第3节会解释
target: 'es2020',
},
},
}
2. 去掉文件名中的 hash
Vite 很贴心的帮我们给生成的文件都加上了 hash。在独立项目中,给文件名加 hash 可以有效避免缓存问题;但是作为外部仓库的话,无法确定的 hash 会增加业务项目的开发难度,所以我希望构建时输出到特定版本号的目录里,然后去掉文件名中的 hash。
这个操作同样需要修改 rollupOptions
。rollup 有三个不同的选项分别处理不同的命名,这里我们可以忽略入口文件(entryFileNames
),只改剩下两个。
export default defineConfig(() => {
return {
build: {
rollupOptions: {
output: {
// 资源文件,包括 css
assetFileNames: 'assets/[name].[ext]',
// 分包文件
chunkFileNames: '[name].js',
},
},
},
};
});
3. 动态加载 CSS
使用 Vite 开发时,我们同样可以在代码里 import
样式等非 JS 素材。构建时,Vite 会把它们处理后放在合适的地方。
可惜的是,Vite 并不会帮我们自动加载分包后的素材。需要我们手动处理。这时就要利用 import.meta.url
,它会返回当前模块的 URL,配合前面的的文件名策略,我们就可以完成动态加载,而不需要业务项目的开发者手动处理。
但是 Vite 默认的版本基线是 ES2019,并不支持 import.meta.url
,所以我们需要把 build.target
设置成 ES2020 或以上。
let isCssLoaded = false;
// 只有未加载且处于生产环境才加载 css。
if (!isCssLoaded && __IS_PROD__) {
const link = document.createElement('link');
link.rel = 'stylesheet';
// 这一步非常重要,因为 vite/rollup 有 bug,会把 `import.meta.url` 翻译成 `self.location`,导致出错
const baseUrl = import.meta.url;
link.href = new URL('./assets/particle-orb.css', baseUrl).toString();
document.head.appendChild(link);
link.onload = () => {
isCssLoaded = true;
}
}
4. 总结
新的技术选型总能给我们带来新的可能,ESM 之后,我们在项目之间复用代码也有了新的选择,赶紧用起来吧。
如果你在使用 Vite 或者 ESM 时遇到什么问题,欢迎提问。如果有什么经验,也欢迎分享。
欢迎吐槽,共同进步