使用 Vite 建立灵活的外部仓库

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/ 目录下,所以我就要检查这个目录,并且生成对应的分包配置。

vite.config.js
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),只改剩下两个。

vite.config.js
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 或以上。

some-effect.js
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 时遇到什么问题,欢迎提问。如果有什么经验,也欢迎分享。

如果您觉得文章内容对您有用,不妨支持我创作更多有价值的分享:


已发布

分类

来自

标签:

评论

欢迎吐槽,共同进步

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理