在 monorepo 中引用依赖时直接指向源码

本文最后更新于:2022年1月13日 晚上

场景

在 monorepo 项目中,我们可能有几十个 lib 模块,而 lib 模块如果需要发布到 monorepo 外,则必须打包为 js,并且将 main/module 指向打包后的 js 文件,以便所有人都能使用。

例如下面这样一个简单的 lib 模块

  • lib-a
    • src
    • README.md
    • package.json
    • tsconfig.json

原先不需要发布时 package.json 中可能直接指向源码

这种 monorepo 中 lib 无构建的优化方式在 如何减少 monorepo 中 lib 的初始化时间 提到过

{
  "name": "lib-a",
  "main": "src/index.ts",
  "module": "src/index.ts",
  "types": "src/index.ts"
}

而当发布后,则需要修改为

{
  "name": "lib-a",
  "main": "dist/index.js",
  "module": "dist/index.esm.js",
  "types": "src/index.ts"
}

这就导致我们至少需要添加一个 setup 脚本,以供第一次拉取项目的人批量执行所有模块的初始化动作。例如 pnpm 的命令可能是 pnpm --filter . run setup

{
  "scripts": {
    "setup": "npm run build"
  }
}

如果模块只有一两个,那么可能不会花太多时间。但如果模块有几十个(我们的生产项目中,大约有 37 个),就算一个模块的初始化构建仅需要几秒钟,那么累积起来也需要几分钟。目前有许多做法

由于吾辈大部分 web 项目均基于 vite 开发,所以便考虑使用创建一个 vite/rollup 插件来重写 module resolve,将 import 的模块直接重写指向源码而非 dist/index.js,即便这样会增加每个模块开发时的时间,但平均到每个模块中依赖的其他 lib 不超过 10 个,额外增加的时间几乎是微不足道的(主要是在一个 nodejs 进程并且使用 esbuild 编译)。

实现

在自行实现之前,吾辈也检索过现有的插件,例如 @rollup/plugin-alias,但它的配置是静态的,例如吾辈需要配置 @liuli-util/* 全部指向 @liuli-util/*/src/index.ts,这需要为每个模块单独配置。

import { defineConfig } from "rollup";
import alias from "@rollup/plugin-alias";

export default defineConfig({
  plugins: [
    alias({
      entries: [
        {
          find: "@liuli-util/async",
          replacement: "@liuli-util/async/src/index.ts",
        },
        {
          find: "@liuli-util/array",
          replacement: "@liuli-util/array/src/index.ts",
        },
        // 可能还有更多
      ],
    }),
  ],
});

而吾辈期望专注于做这件事情,所以便单独开发了一个插件 rollup-plugin-ts-alias

import { Plugin, ResolveIdResult } from "rollup";
import { pathExists } from "fs-extra";

export function tsAlias(
  includes: (string | RegExp)[],
  excludes: (string | RegExp)[] = []
): Plugin & { enforce: "pre" | "post" } {
  return {
    name: "rollup-plugin-ts-alias",
    enforce: "pre",
    async resolveId(source: string): Promise<ResolveIdResult> {
      excludes.push(/\/.*\//);
      const predicate = (item: string | RegExp) =>
        typeof item === "string" ? source.startsWith(item) : item.test(source);
      if (includes.some(predicate) && !excludes.some(predicate)) {
        let res: string;
        try {
          res = require.resolve(source + "/src/index.ts");
        } catch (e) {
          return null;
        }
        if (!(await pathExists(res))) {
          console.warn("path not exists: ", res);
          return null;
        }
        console.log("rewrite: ", res);
        return res;
      }
      return null;
    },
  };
}

使用

该插件已发布至 npm @liuli-util/rollup-plugin-ts-alias

安装

pnpm i -D @liuli-util/rollup-plugin-ts-alias

配置

// vite.config.ts
import { tsAlias } from "@liuli-util/rollup-plugin-ts-alias";

export default defineConfig({
  plugins: [tsAlias(["@liuli-util/"])],
});

之后,在 monorepo 直接修改 lib 的源码便能热更新,不需要启动额外的 terminal,也不再需要添加 setup 命令供全部初始化。下面可以看到,依赖的 lib @liuli-util/react-router 已经被指向源码了

1642080421952

问题


在 monorepo 中引用依赖时直接指向源码
https://blog.rxliuli.com/p/cf2eba92f2c74b38a150ee9ed32779a8/
作者
rxliuli
发布于
2022年1月13日
许可协议