Vite 的 CSS Module 不支持 自定义路径,怎么破?

背景

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 提问者分享了自己写的插件,但是体验后发现不完全是理想中的效果。

所以我们决定自己解决。

自己动手丰衣足食.png

解决方案

方案 1 - 修改 node_modules

Vite 2.7.4 为例,来到/node_modules/vite/dist/node/chunks/dep-4b9dfa16.js这个文件

咱也不知道他怎么找到的.jpeg

魔改源码

找到这部分代码

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 文件,否则出错调试的时候不方便定位到源文件

下班哪,别走.jpeg

方案 2 - Vite 插件

上述方案可行,但是在每个项目都做一遍也挺麻烦,万一将来再加点改动还得再去每个项目同步,好像不是最优解。

于是我们决定采用方案2:写一款 Vite 插件

前置知识

  1. 首先需要了解,在 Vite 中,一切皆 Javascript。

    Vite 开发环境 的基本实现原理,是启动一个 server 拦截浏览器中通过 ESM 规范 发起的请求。根据请求路径找到本地对应的文件,经过简单处理后返回 ESM 规范JavaScript 代码片段

    返回的是js.jpeg

    比如我们执行的 import 'xx/xxx/styles.css'server 接收这条请求,返回给我们的并不是 css 资源,而是 js 代码

    返回的是js内容.jpg

    如上图所示,export default __vite__css 导出给 import 调用方,而上方的 updateStyle 是向页面注入 style 标签 和对应的 css 代码片段import.meta.hot用于热更新。

  2. 其次需要了解在 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模块化非模块化 的区别。

    记下来.jpeg

  3. 插件 API ,官网写的很明白,我们就不再做搬运工了。

    好好看文档.jpeg

解决思路

接下来我们需要制定解决问题的方案。

首先分析 源码 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, ...);
复制代码

按照这个思路开发我们的插件可以这样做:

  1. 接收用户的 CSS Module 的路径匹配,如果匹配失败则不处理。按照 Vite 插件的机制最终会交给 vite:css

  2. 接收用户的 postcss 配置 ,带上 postcss-modules 插件 交给 postcss 处理,在这里可以通过 postcss-modules 插件 得到 css 模块化前后 的选择器关系映射,是一个 json 对象;

  3. 在插件的 enforce: 'pre' 阶段直接返回上一步的代码;

  4. 在插件的 enforce: 'post' 阶段手动定义文件的内容:向页面注入 css、导出 模块化后选择器的映射关系(第二步得到的json对象) 、完成热更新。

一顿打代码.jpeg

具体的代码已经打包发布到 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()],
});
复制代码

启动项目,测试

又成了.jpeg

总结

平时我们用第三方依赖难免会有不适用自己场景的情况,而开源作者一般因为比较忙,或者理念不同难以短时间内提供支持,这时候就需要我们自己动手丰衣足食。

不管是 fork 源码魔改 还是 patch-package 都是非常好的方案。但 打补丁 虽然安全快捷,但会额外多一个文件和命令,如果不喜欢 打补丁 可以尝试用插件。

再说到 ViteVite + Vue3 的使用体验度毋庸置疑,但是接入 React 的体验还是稍逊些,不过这丝毫不影响 Vite 本身的亮点,依然值得深入学习研究。

目前 Vite 的生态正在不断完善,我们团队也逐步开始尝试,后续会推出更多相关文章,感兴趣的话多多关注哦~

相关文章

Vite 零基础极速入门

投稿来自:jarvis

Guess you like

Origin juejin.im/post/7047323207774240798