Directly point to source code when referencing dependencies in monorepo
Tags: monorepo, javascript
Published: 2022-01-13 16:07:40

## Scenes

In a monorepo project, we may have dozens of lib modules, and if the lib module needs to be published outside the monorepo, it must be packaged as js, and `main/module` points to the packaged js file, so that everyone can use it .

For example a simple lib module like the following

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

The source code may be directly pointed to in package.json when there is no need to publish it originally

> This optimization of lib buildless in monorepo is mentioned in [How to reduce the initialization time of lib in monorepo](https://blog.rxliuli.com/p/f35319c382cf488082a1df13dad35005/)

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

When published, it needs to be modified to

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

This leads us to add at least one setup script for the first time pulling the project to perform the initialization of all modules in batches. For example the command for pnpm might be `pnpm --filter .run setup`

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

If there are only one or two modules, it probably won't take much time. But if there are dozens of modules (in our production project, about 37), even if the initial build of a module only takes a few seconds, it will take minutes cumulatively. There are many practices

- All initialized only for the first time, and subsequent updates are rebuilt by the modules used by the user. For example [antv series of open source projects](https://github.com/antvis/G6)
- Use typescript's [project-references](https://www.typescriptlang.org/docs/handbook/project-references.html) to point directly to the ts source files. For example open source project [Maskbook](https://github.com/DimensionDev/Maskbook/blob/develop/tsconfig.json)
- Cache built files to avoid rebuilds. For example [nx.js](https://nx.dev/), [yarn-plugin-change](https://github.com/rxliuli/liuli-tools/tree/master/libs/yarn-plugin-changed), [ultra-runner](https://github.com/folke/ultra-runner)
- Speed ​​up the build of each module. I tried to use esbuild to refactor [@liuli-util/cli](https://github.com/rxliuli/liuli-tools/tree/master/apps/liuli-cli)

Since most of our web projects are developed based on vite, we consider creating a vite/rollup plugin to rewrite module resolve, and rewrite the imported module directly to the source code instead of dist/index.js, even if this will increase each The time for module development, but on average no more than 10 other libs depend on each module, the extra time is almost insignificant (mainly in a nodejs process and compiled with esbuild).

## accomplish

Before implementing it myself, I also retrieved existing plugins, such as [@rollup/plugin-alias](https://www.npmjs.com/package/@rollup/plugin-alias), but its configuration is static For example, we need to configure `@liuli-util/*` to all point to `@liuli-util/*/src/index.ts`, which needs to be configured separately for each module.

```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',
        },
        // maybe more
      ],
    }),
  ],
})
```

And I expect to focus on doing this, so I developed a separate plugin `rollup-plugin-ts-alias`

```ts
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
    },
  }
}
```

## use

> The plugin has been published to npm [@liuli-util/rollup-plugin-ts-alias](https://www.npmjs.com/package/@liuli-util/rollup-plugin-ts-alias)

Install

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

configure

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

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

After that, the source code of lib can be hot-updated by directly modifying the source code of lib in monorepo, no need to start additional terminal, and no need to add `setup` command for full initialization. As you can see below, the dependent lib [@liuli-util/react-router](https://www.npmjs.com/package/@liuli-util/react-router) has been pointed to the source code

![1642080421952](https://blog.rxliuli.com/resource/7c84fdda7f6d4e74943e7bde752176c1.png)

## problem

- How should a large monorepo solve performance and collaboration issues?
- [Or do we not need to use a monorepo?](https://medium.com/@mattklein123/monorepos-please-dont-e9a279be011b)