同事:唉,你写的代码有bug!

1,007 阅读6分钟

那是一个温暖宜人的下午,温暖的阳光洒进办公室,微风轻轻吹拂,带来阵阵清新的空气。小羽戴着耳机坐在办公桌前,耳机里传来轻柔的音乐,伴随着键盘清脆的敲击声,组成了一首独特的交响曲。桌上的咖啡杯散发出淡淡的香气,偶尔会抬起头,望向窗外,享受片刻的安宁

下午的时光似乎总是流逝得特别快。阳光逐渐西斜,金色的光线透过窗户洒在地板上,形成了一道道柔和的光影。看了一眼时间,嘿嘿,马上到干饭时间了,糖醋排骨龙井虾仁宫保鸡丁鱼香茄子清蒸鲈鱼咱们马上就来大战三百回合【擦下口水】。

突然感觉到有人轻轻拍了拍的肩膀。摘下耳机,回过头去,只见同事站在他身后,说:"唉,你写的代码有bug!"

嗯?您确定!!!

帅哥写的代码怎么可能有bug,不可能,绝对不可能,就算有也不承认!【手动狗头】

说是这么说,但是有问题还是得解决的,对吧。

来到同事的工位前,同事指着屏幕中的一行代码,"你看,上次你引用了一个vite插件后,我现在这样子引用less文件,编译就报错了"。

大概的示意图如下:

唉,这是个什么神奇的报错?(因为alias的配置是正确的,可以看到ts资源引用是正常的)

又到了掐指一算的时候了,这必定是alias的问题,用相对路径一定可以解决。

果然没错,可以看到换成相对路径是正常的。

ok,问题解决。

干饭的时间到,民以食为天,目标食堂,全速前进!

但是还是无法解释用alias引用less文件会报错的问题。

这到底是为啥啊???

带着疑惑以及浑身的不得劲回到家中,继续排查这个问题。

在前面的报错提示中,咱们可以看到错误来源是vite-plugin-react-css-module这个npm包中的。

那就老方法,先找到这个npm包对应的github仓库是否有对应的issue?

emmm,issues就两个,但是不是我们想要的。

居然没人遇到这个问题?

那就只能看下代码实现了,可以发现这个vite插件是引用了一个三方的babel插件,babel-plugin-react-css-modules

走,咱们移步到babel-plugin-react-css-modules的仓库中,看看能不能找到什么线索

然后发现这个其实应该是属于babel-plugin-react-css-modules这个库的bug,但是看作者17年的说法,并不打算对alias进行支持。

啊,这。。。。

wtf,这怎么搞,作者都不打算支持了。

嘿嘿,别着急,咱们继续往下看。

说不定就能找到答案呢【手动狗头】

看,后续有大佬给出答案,可以通过babel-plugin-module-resolver这个plugin,给css-modules插件添加alias相关的支持。

emmm,但是有一个问题,你发现了嘛

就是如果直接在babel中使用babel-plugin-module-resolver的话,则需要维护两份alias,这对于喜欢偷懒的程序员朋友来说就非常不友好啦,但这又不属于vite-plugin-react-css-module这个包的bug。并且这个包好像有三年没更新了,看了一眼代码,感觉还挺简单的,于是决定借鉴一下vite-plugin-react-css-module自己新搞一个包吧。

那咱们先来写一个最简版的vite plugin,运行起来后,就可以在控制台中看到咱们写的vite插件的log输出了,如下图

这里是一些vite自定义插件相关的参数,需要用到的时候看下就好

name:

插件的名称,用于在日志中标识插件。

enforce:

用于指定插件的执行顺序。可以是 "pre"(在其他插件之前执行)或 "post"(在其他插件之后执行)。

apply:

指定插件的应用场景,可以是 "build"(仅在构建时使用)或 "serve"(仅在开发服务器时使用)。

config:

用于修改 Vite 配置。接收当前的 Vite 配置和环境变量。

configResolved:

在配置解析完成后调用,接收解析后的 Vite 配置。

configureServer:

用于配置开发服务器。在开发服务器启动时调用,接收 server 实例作为参数。

transformIndexHtml:

用于转换 index.html 文件。可以对 HTML 进行操作,如注入脚本或样式。

resolveId:

用于自定义模块解析逻辑。接收待解析的模块 ID 和导入模块的来源。

load:

用于自定义加载模块内容。接收模块 ID,并返回模块的代码。

transform:

用于转换模块内容。接收模块代码和模块 ID,返回转换后的代码。

handleHotUpdate:

用于处理热更新事件。接收更新的模块和 server 实例,返回需要更新的模块集合。

buildStart:

在构建开始时调用,可以进行一些准备工作。

buildEnd:

在构建结束时调用,可以进行一些清理工作。

generateBundle:

在生成 bundle 文件时调用,可以对生成的文件进行操作。

writeBundle:

 在 bundle 文件写入磁盘时调用。

然后就是参考原版的插件(cjs),并将其改造为esm版本的代码。

这是原版的代码,总共60行,还是比较简单的,咱们简单的分析一下,它做了哪些操作?

好的,那咱们还有一个问题,就是怎么引入babel-plugin-module-resolver这个插件并动态配置alias呢?

这里比较简单,直接公布答案吧

babel-plugin-module-resolver的引入和原来的css-module插件一样,在plugin中引入既可,而alias则可以先创建一个局部的变量,然后在config钩子中将alias相关参数赋值过去,最后到transform钩子中使用就好了

下面是全部的代码:

 // ts-check
import { transformSync } from "@babel/core";
import babelPluginReactCssModules from "babel-plugin-react-css-modules";
import babelPluginModuleResolver from "babel-plugin-module-resolver";

enum EEnforce {
  PRE = "pre",
  POST = "post",
}

function viteReactStyleName(opt) {
  const { ...options } = opt;
  let _alias;
  return {
    name: "vite-react-stylename",
    enforce: EEnforce.PRE,
    config(confing) {
      _alias = confing.resolve.alias;
    },
    transform(code, id) {
      // 排除不处理的文件
      if (!/.(t|j)sx?$/.test(id) || id.includes("node_modules")) {
        return null;
      }
      if (!id.endsWith("x") && !code.includes("react")) {
        return null;
      }
      // 需要使用的解析插件
      const parserPlugins = ["jsx", "importMeta"];
      if (/.tsx?$/.test(id)) {
        parserPlugins.push("typescript", "decorators-legacy");
      }
      const isReasonReact = id.endsWith(".bs.js");

      const result = transformSync(code, {
        babelrc: false,
        configFile: false,
        filename: id,
        parserOpts: {
          sourceType: "module",
          allowAwaitOutsideFunction: true,
          plugins: parserPlugins,
        },
        plugins: [
          [
            babelPluginModuleResolver,
            {
              alias: _alias,
              extensions: [".js", ".jsx", ".tsx", ".ts"],
            },
          ],
          [
            babelPluginReactCssModules,
            {
              autoResolveMultipleImports: true,
              exclude: "node_modules",
              ...options,
            },
          ],
        ],
        ast: !isReasonReact,
        sourceMaps: true,
        sourceFileName: id,
      });
      return {
        code: result.code,
        map: result.map,
      };
    },
  };
}
export default viteReactStyleName;

ps:目前这块已经单独发布了npm包,想使用的小伙伴,直接通过pnpm i vite-react-stylename -D进行安装,使用方式和原版的插件一样~

github地址:github.com/baizeteam/b…

npm地址:www.npmjs.com/package/vit…

此时,咱们将原vite-plugin-react-css-module的引入路径改成咱们新vite插件的路径,就可以发现使用alias引入的方式也变得正常啦。这下子才是真的搞定收工!

本文差不多就到这里啦,如果看这篇文章后,感觉有收获的小伙伴们可以点赞+收藏哦~

img

如果想和小羽交流技术可以加下wx,也欢迎小伙伴们来和小羽唠唠嗑,嘿嘿~