背景
2021 年 前端界关于 Vite 的热度一直不减,近期 Vite2.x 基本趋于稳定所以决定在生产环境接入尝试。
在体验了如丝般顺滑的 Vue3 + Vite 后,接下来的目标指向 React。
公司前端团队中大部分 React 项目的 CSS Module 方案 均采用 react-css-modules ,而 Vite 不支持这种方式,只对 xx.module.css
这一类文件做模块化。
摘自 Vite文档:任何以 .module.css 为后缀名的 CSS 文件都被认为是一个 CSS modules 文件
如果不解决此问题,接入 Vite 势必会对业务代码造成影响,而且项目越大改动成本越高。
遇到同样问题的小伙伴不只有我们,早在之前就有人提了这条 issue ,不过并没有得到尤大的肯定,于是issue 提问者分享了自己写的插件,但是体验后发现不完全是理想中的效果。
所以我们决定自己解决。
解决方案
方案 1 - 修改 node_modules
以 Vite 2.7.4
为例,来到/node_modules/vite/dist/node/chunks/dep-4b9dfa16.js
这个文件
魔改源码
找到这部分代码
const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\\?)`;
const cssModuleRE = new RegExp(`\\.module${cssLangs}`);
复制代码
可以看到,cssModuleRE
就是模块化 CSS 的文件名匹配规则,限制了必须是 xx.module.(css|less|sass|scss|styl|stylus|pcss|postcss)
的形式,所以我们需要在这里入手,让这个 匹配规则 支持在 vite.config.js 中配置。
我们希望像 webpack loader 这样,
-
webpack.config.js
module: { rules: [ { test: /\.(css|less|sass|scss|styl|stylus)$/, loaders: ['style-loader/useable', 'css-loader'], }, ]; } 复制代码
文件名匹配规则也采用
test
字段,减少大家的心智负担 -
vite.config.js
css: { modules: { test: /.(css|less|sass|scss|styl|stylus|pcss|postcss)/ } }, 复制代码
然后对源码文件作如下改动:
const configFile = path__default.resolve(process.cwd(), 'vite.config.js');
const userCssModuleRe = configFile.css
? configFile.css.modules
? configFile.css.modules.test
: null
: null;
const cssModuleRE = userCssModuleRe || new RegExp(`\\.module${cssLangs}`);
复制代码
即:如果有配置 css.modules.test
,就用用户的 自定义匹配规则,否则继续使用 原有规则 。
改好后测试一下效果,
打补丁
我们知道 node_modules 的改动是一时的,而且改动不是永久性且其他人没法使用。
所以我们需要用到 patch-package
在项目 根目录 依次执行以下命令
yarn add patch-package
// 对`node_modules`的改动打补丁
npx patch-package vite
复制代码
执行完毕后可以看到项目根目录下额外增加了一个 patches
文件夹,里面有个文件叫做vite+2.7.4.patch
,
表示对 Vite2.7.4
版本打的补丁信息。
在 package.json
中增加命令 postinstall
,将来安装依赖时可以自动在 vite2.7.4 版本 上打补丁代码
"scripts": {
"postinstall":"patch-package"
},
复制代码
ps:最后记得改一下 map
文件,否则出错调试的时候不方便定位到源文件
方案 2 - Vite 插件
上述方案可行,但是在每个项目都做一遍也挺麻烦,万一将来再加点改动还得再去每个项目同步,好像不是最优解。
于是我们决定采用方案2:写一款 Vite 插件
前置知识
-
首先需要了解,在 Vite 中,一切皆 Javascript。
Vite 开发环境 的基本实现原理,是启动一个 server 拦截浏览器中通过 ESM 规范 发起的请求。根据请求路径找到本地对应的文件,经过简单处理后返回 ESM 规范 的 JavaScript 代码片段。
比如我们执行的
import 'xx/xxx/styles.css'
,server 接收这条请求,返回给我们的并不是 css 资源,而是 js 代码。如上图所示,
export default __vite__css
导出给import
调用方,而上方的updateStyle
是向页面注入 style 标签 和对应的 css 代码片段 ,import.meta.hot
用于热更新。 -
其次需要了解在 Vite 中 普通 CSS 文件 和 模块化 CSS 文件 有何区别
对于 普通 css 资源,
export default
导出的就是 css 代码,和updateStyle
注入的一致,对于 CSS Module 资源,
export default
导出的是模块化后选择器的 映射关系,大概长这样:
{ 'wrapper': '_wrapper_uguz8_5', 'title': '_title_uguz8_5', } 复制代码
updateStyle
第二个参数是模块化后的 css 代码._wrapper_uguz8_5 { color: #0af; } ._title_uguz8_9 { font-size: 16px; } 复制代码
这是 CSS模块化 与 非模块化 的区别。
-
插件 API ,官网写的很明白,我们就不再做搬运工了。
解决思路
接下来我们需要制定解决问题的方案。
首先分析 源码 vite:css 的实现思路
// 1. 如果是纯CSS 并且。。。。 则不需要解析,直接返回
if ( lang === 'css' && !postcssConfig && !isModule && !needInlineImport && !hasUrl) {
return { code }
}
// 2. 尝试用 CSS 预处理语言处理,生成纯CSS
const preprocessResult = await preProcessor(code, ...);
// 3. postcss处理
const postcssResult = await (await import('postcss')).default(postcssPlugins).process(code, ...);
复制代码
按照这个思路开发我们的插件可以这样做:
-
接收用户的 CSS Module 的路径匹配,如果匹配失败则不处理。按照 Vite 插件的机制最终会交给 vite:css ;
-
接收用户的 postcss 配置 ,带上 postcss-modules 插件 交给 postcss 处理,在这里可以通过 postcss-modules 插件 得到 css 模块化前后 的选择器关系映射,是一个
json
对象; -
在插件的
enforce: 'pre'
阶段直接返回上一步的代码; -
在插件的
enforce: 'post'
阶段手动定义文件的内容:向页面注入css
、导出 模块化后选择器的映射关系(第二步得到的json对象) 、完成热更新。
具体的代码已经打包发布到 npm 仓库,可以安装 vite-plugin-style-modules 使用。
安装启动一把嗦
安装
npm install vite-plugin-style-modules
复制代码
使用插件
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import viteCssModule from 'vite-plugin-style-modules';
// 支持ESM和CommonJS两种方式引入
// const viteCssModule = require('vite-plugin-style-modules')
export default defineConfig({
plugins: [react(), viteCssModule()],
});
复制代码
启动项目,测试
总结
平时我们用第三方依赖难免会有不适用自己场景的情况,而开源作者一般因为比较忙,或者理念不同难以短时间内提供支持,这时候就需要我们自己动手丰衣足食。
不管是 fork
源码魔改 还是 patch-package
都是非常好的方案。但 打补丁 虽然安全快捷,但会额外多一个文件和命令,如果不喜欢 打补丁 可以尝试用插件。
再说到 Vite,Vite + Vue3 的使用体验度毋庸置疑,但是接入 React 的体验还是稍逊些,不过这丝毫不影响 Vite 本身的亮点,依然值得深入学习研究。
目前 Vite 的生态正在不断完善,我们团队也逐步开始尝试,后续会推出更多相关文章,感兴趣的话多多关注哦~
相关文章
投稿来自:jarvis