Vite多入口组件库构建方案,自带样式导入和treeshaking功能

您所在的位置:网站首页 css样式在浏览器里无效 Vite多入口组件库构建方案,自带样式导入和treeshaking功能

Vite多入口组件库构建方案,自带样式导入和treeshaking功能

2023-06-13 12:39| 来源: 网络整理| 查看: 265

背景

Vite 在 Web 项目开发中大放异彩,但其实也是可以用于库项目开发的,但是在组件库开发时,通常涉及多入口。

虽然 Vite 3.2+ 以后支持了多入口配置,但是并没有提供相关选项让我们将 CSS 和组件关联起来,这样即便我们打包成功,也无法判断组件和样式的关系,从而给库的导出和使用带来不便。

基于这个背景,我们需要编写一个插件尝试寻找二者关系,并顺势注入 CSS 引用,这样就不用再关心样式和入口的关系了。

样式自动导入

作为一个库(主要是组件库),我们希望在引用组件时自动导入样式:

/** component-lib/dist/button.js */ import './assets/button.css'; // 这是我们插件要做的事情,将这行代码注入进来 ... export default Button; /** component-lib/dist/index.js */ import Button from './button.js'; import xxx from './xxx.js'; export { Button, xxx }; ... /** In our project's main file */ // 引用 Button 的同时将一起引入对应的样式文件 import { Button } from 'component-lib';

如上所示,其实要做的很简单,添加一行 import './style.css';; 到生成文件的顶部即可,多个就是多行。作为库的提供者,应该尽可能的提供灵活性,将如何处理这些样式文件的任务,交给用户侧的构建工具。

市面上大部分声称自动注入 CSS 的 Vite 插件,都是采用 document.createElement('style') 这样的方式进行的,这并不优雅,并且他假设了当前是浏览器的 DOM 环境。

所以我们的问题就变成了,如何将样式文件和入口文件关联起来?

其实,Vite 在插件的生命周期中,为每一个 chunk 对象都注入了一个属性 viteMetadata,我们可以通过这个属性获取到当前 chunk 关联了哪些资源文件,其中就包括 CSS。

核心代码

基于上面的分析,我们在插件钩子 renderChunk 中进行注入即可,这是最简单和行之有效的方法。

export function plugin(){ return { name: 'vite:inject-css', apply: 'build', enforce: 'post', config() { const { rollupOptions, ...lib } = libOptions || {} as LibOptions; return { build: { /** * 需要打开这一项,否则多入口下也只会有一个 style.css, 单入口则不受影响。 */ cssCodeSplit: true, }, }; }, renderChunk(code, chunk) { if (!chunk.viteMetadata) return; const { importedCss } = chunk.viteMetadata; if (!importedCss.size) return; let result = code; for (const cssFileName of importedCss) { let cssFilePath = path.relative(path.dirname(chunk.fileName), cssFileName); cssFilePath = cssFilePath.startsWith('.') ? cssFilePath : `./${cssFilePath}`; result = `import '${cssFilePath}';\n${result}` } return result; }, } }

需要注意的是,需要打开 build.cssCodeSplit,主要是因为在内部实现中,CSS 代码分割开启时才会在 viteMetadata 中记录 chunk 对应那些资源文件,只有文件关系被记录下来,我们才能正常注入。

上方代码是一个最简版本,主要展示核心逻辑,作为生产使用还缺少 sourcemap 的支持,更完善的版本已经发布为 vite-plugin-lib-inject-css,有兴趣的小伙伴可以进行尝试。

多入口构建

如何使用 Vite 创建一个开箱即用,具备 Tree-shaking 功能,还能自动导入样式的组件库呢?

大多数组件库都提供了两种使用方式,一种是全量引入:

import Vue from 'vue'; import XxxUI from 'component-lib'; Vue.use(XxxUI);

另一种是按需引入。这种方式通常需要搭配一个第三方插件进行转换,例如 babel-plugin-import

import { Button } from 'component-lib'; // ↓ ↓ ↓ transformed by plugin ↓ ↓ ↓ import Button from 'component-lib/dist/button/index.js' import 'component-lib/dist/button/style.css'

但最好的使用方式应该是,在正常使用具名导入时,就能自动引入样式,并进行 Tree-shaking。

幸运的是,ES Module 天然具备静态分析能力,主流工具基本都实现了基于 ESM 的 Tree-shaking 功能,比如 webpack/rollup/vite。

那么我们只需要以下两步,就能大功告成

将产物格式输出为 ES Module → 开箱即用的 Tree-shaking 功能使用上面的插件进行样式注入 → 自动导入样式

需要注意的是,CSS 文件的导入是具有副作用的,我们还需要在库的 package.json 文件中声明 sideEffects 字段,防止用户侧构建时 CSS 文件被意外移除。

{ "name": "component-lib", "version": "1.0.0", "main": "dist/index.mjs", "sideEffects": [ "**/*.css" ] } 配置示例

上文提到的插件中,第一个参数为可选参数,照搬了 build.lib 的相关配置项。除此之外还提供了一些工具函数,目的是简化配置。

以下是一份多入口组件库的配置示例:

// vite.config.ts import { libInjectCss, scanEntries } from 'vite-plugin-lib-inject-css'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [ libInjectCss(), // 快速使用,其他配置自己写在 build.lib 里即可 // 参数是 build.lib 的 alias,目的是为了集中配置 libInjectCss({ format: ['es'], entry: { index: 'src/index.ts', // Don't forget the main entry! button: 'src/components/button/index.ts', select: 'src/components/select/index.ts', // Uses with a similar directory structure. ...scanEntries([ 'src/components', 'src/hooks', ]) }, rollupOptions: { output: { // Put chunk files at /chunks chunkFileNames: 'chunks/[name].[hash].js', // Put chunk styles at /styles assetFileNames: 'assets/[name][extname]', }, }, }), ], })

我们的产物结构如下:

--dist ----chunks ----assets --index.mjs --button.mjs --select.mjs ...

随便点开一个组件,比如 dist/button.mjs

import './assets/index2.css' import { xxx } from 'vue'; const v = ...; export { v as Button }

可以看到,产物中正确的注入了相关的样式文件,使命达成。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3