搭建 Monorepo 工程

pnpm + workspace

思考 🤔:什么是工作空间?

答案:工作空间可以看作是一个共享的区域,所有用于工作的资源都可以从这个区域获取到。

Monorepo 背后的思想:创建一个公共的空间,服务于多个项目,把多个项目用到的共有的一些东西,提取出来

生活中工作空间

在这个工作空间中,通常会包含与工作相关的所有工具和资源,比如办公桌、电脑、文具和文件柜等。这个工作空间是一个集中完成特定任务的地方,所有需要用到的东西都可以在这里找到,方便你高效地完成工作。

软件开发中的工作空间

在软件开发中,工作空间通常指一个用于组织和管理项目文件、资源和工具的逻辑容器。它通常是一个文件夹结构,用于将相关的项目文件、代码、设置和其他资源集中放置在一起。

工作空间的概念在不同的编程语言和开发工具中可能略有不同,但其基本目标都是提供一个集中式环境,以帮助开发者管理和协同开发多个项目。主要功能包括:

  • 组织和管理项目文件
  • 跨项目共享设置和工具
  • 支持协同开发

pnpm 中的工作空间

在 pnpm 中,工作空间就是一个管理多个包的环境,它通过独特的依赖管理方式极大地提高了效率。pnpm 的工作空间支持符号链接和硬链接机制,使得不同包之间能够高效地共享依赖,同时保证每个包的独立性。

pnpm 工作空间特点:

  • 高效的依赖管理
  • 节省磁盘空间
  • 跨项目的高效协作

pnpm 的工作空间为大型 Monorepo 项目提供了一个强大而灵活的开发环境,使得管理和开发多个包变得更加简单和高效。

pnpm 中定义工作空间

在根目录有一个 pnpm-workspace.yaml 的文件,该文件用于定义哪些包会被包含在 workspace 工作空间中,默认情况下,所有子目录下的所有包都会被包含在 works ace 里面。

示例:

packages:
  # packages/ 下所有子包,但是不包括子包下面的包
  - "packages/*"
  # components/ 下所有的包,包含子包下面的子包
  - "components/**"
  # 排除 test 目录
  - "!**/test/**"

注意这里表示包范围的语法使用的是 Glob 表示法。

实战演练

创建基于 pnpm + workspace 的 Monorepo 工程,并在工程中封装一个公共的函数库。

安装依赖到工作空间里面:

pnpm add <包名> --workspace-root
or
pnpm add <包名> -w

安装工作空间的一个包到工作空间另一个包里面:

pnpm add <包名B> --workspace --filter <包名A>

该命令表示将 B 包安装到 A 包里面,也就是说 B 包成为了 A 包的一个依赖。其中 B 包后面的 --workspace 参数表示该包来自于工作空间,而非 npm 远程仓库,--filter 表示安装到 A 包里面。

创建一个基于 pnpm + workspace 的 Monorepo 工程,完整步骤:

  1. 创建一个根目录,并初始化 pnpm workspace,例如:

    mkdir monorepo 
    cd monorepo
    pnpm init // 初始化 pnpm workspace,如果本地没有安装pnpm包的话,需要先安装或者使用 npx pnpm init
    
  2. 创建 pnpm-workspace.yaml 文件,并定义工作空间,例如:

    packages:
      - "components/\*"
      - "utils/\*"
      - "projects/\*"
    
  3. 创建目录:

monorepo/components //公共组件存放的目录
monorepo/utils //公共函数存放的目录
monorepo/projects //存放多个项目的目录

4.在utils下创建一个公共的工具库,tools目录,并切换到该目录下,使用pnpm init初始化项目,并安装依赖,例如:

pnpm init

现在tools目录下已经创建了package.json文件,可以安装依赖,tools目录就是一个包了

接下来我们将会使用typescript来开发我们的工具库,那么我们要把typescript安装在那个目录呢?

我们的typescript多个包都需要用到,所以我们需要安装到工作空间里面,而不是安装到某个包里面,这样可以保证多个项目使用的typescript版本保持一致

pnpm add typescript -D --workspace-root

执行完上面的代码之后就可以在项目的根目录package.json文件里面看到typescript的依赖了

接下来我们就可以愉快的开发我们的工具库了,在tools下面创建一个src目录,并创建一个index.ts文件,一个sum.ts文件,并编写我们的工具库,例如:

//sum.ts
export function sum(a: number, b: number): number {
  return a + b;
}

//index.ts
export * from './sum';

至此,我们的工具库就算开发完成了,接下来我们需要对我们的工具库进行测试,在tools目录下创建一个tests目录存放我们的测试文件,这里我们选择vitest作为我们的测试框架,同样,除了tools需要测试,我们也需要对其他的模块进行测试,所以我们还是把vitest安装到工作空间里面:

pnpm add vitest -D --workspace-root

安装好之后,我们就可以开始编写我们的测试文件了,在tests目录下创建一个sum.test.ts文件,并编写我们的测试代码,例如:

import { sum } from "../src/sum";

test("测试sum方法", () => {
  const result = sum(10, 2);
  expect(result).toBe(12);
});

测试文件写完之后,我们需要运行测试文件,检查测试结果,我们在tools/package.json文件里面添加一下命令:

"test": "vitest"

然后在终端中运行该命令:

pnpm test

这个时候,终端中汇报一个错:

ReferenceError: test is not defined

这个时候我们需要在项目根目录下创建一个vitest.config.ts文件,并编写我们的配置文件,例如:

import {defineConfig} from 'vitest/config'
export default defineConfig({
  test: {
    globals: true,//表示vitest启用全局模式
    environment: 'node',
  },
})

配置完成之后,再次运行测试文件,就会发现我们的测试结果是正确的,我们的工具库开发完成了。

然后我们就需要对我们的工具库函数进行打包,以便其他开发人员可以使用
对于打包我们主要有两件事情要做,一个是调整typescript的配置选项,另一个是使用rollup打包
首先我们使用npx tsc --init在tools目录下创建一个tsconfig.json文件,并编写我们的配置选项,例如:

{
  "compilerOptions": {
    "target": "ES2015",//表示编译成es6的代码
    "module": "ES2015",//指定使用的模块系统
    "declaration": true,//生成类型声明文件
    "declarationDir": "./dist/types",//生成声明文件的目录
    "esModuleInterop": true,//允许导入非模块的包(启用es模块和common模块操作的互相支持)
    "forceConsistentCasingInFileNames": true//强制文件名大小写匹配
  },
  "include": ["src/**/*"]//告诉编译器要编译的文件
}

配置好tsconfig.json文件之后,我们就可以开始打包我们的工具库了,首先安装打包相关的依赖:

pnpm add rollup rollup-plugin-typescript2 @babel/preset-env @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-json @rollup/plugin-node-resolve  -D --workspace-root

在tools目录下创建一个rollup.config.js文件,并编写我们的配置文件:

// tools/rollup.config.js

// 导入各种插件
import typescript from "rollup-plugin-typescript2";
import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import json from "@rollup/plugin-json";
import babel from "@rollup/plugin-babel";

const extensions = [".js", ".ts"];

// 导出一个数组,数组里面每一个对象对应一种格式的配置
export default [
  // CommonJS
  {
    input: "src/index.ts",
    output: {
      file: "dist/index.cjs",
      format: "cjs",
    },
    plugins: [
      typescript({
        useTsconfigDeclarationDir: true,
      }),
      resolve({ extensions }),
      commonjs(),
      json(),
    ],
  },
  // ESM
  {
    input: "src/index.ts",
    output: {
      file: "dist/index.js",
      format: "es",
    },
    plugins: [
      typescript({
        useTsconfigDeclarationDir: true,
      }),
      resolve({ extensions }),
      commonjs(),
      json(),
    ],
  },
  // Browser-compatible
  {
    input: "src/index.ts",
    output: {
      file: "dist/index.browser.js",
      format: "iife",
      name: "jsTools",
    },
    plugins: [
      typescript({
        useTsconfigDeclarationDir: true,
      }),
      resolve({ extensions }),
      commonjs(),
      json(),
      babel({
        exclude: "node_modules/**",
        extensions,
        babelHelpers: "bundled",
        presets: [
          [
            "@babel/preset-env",
            {
              targets: "> 0.25%, not dead",
            },
          ],
        ],
      }),
    ],
  },
];

安装好依赖之后,我们需要添加一个打包的命令,在tools目录下创建一个package.json文件,并添加以下代码:

"scripts": {
  "build": "rollup -c"
}

接下来,我们就可以运行打包命令了,在终端中运行以下命令:

pnpm build

打包完成之后,我们就可以得到一个dist目录,里面有index.cjs,index.js,index.browser.js三个文件,分别对应三种格式的代码,我们可以根据需要选择使用哪种格式的代码。

接下来我们需要在别的项目中引入我们的工具库,还有一件非常重要的事情,我们需要在package.json中指定一下我们的入口文件,
以便其他包含我们的工具库时,可以正确地解析我们的代码。
在package.json中添加以下代码:

"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/types/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",//esm
      "require": "./dist/index.cjs"//commonjs
    }
  },

做完所有的这些操作之后,我们huidao项目根目录下,就可以使用我们的工具库了。

我们回到项目根目录下的projects目录下,来测试下是否可以正常使用我们的工具库。
新建一个test-tools的目录

mkdir test-tools //projects/test-tools

然后使用pnpm init 初始化一个package.json文件
然后通过mkdir src &&cd ./src &&touch index.ts一个src目录,并创建一个index.ts文件

接下来我们要在index.ts里面使用我们的工具库,tools是存放在工作空间里面的,那么我们如何安装一个工作空间的包呢?我们可以通过一下的命令来实现:

pnpm add <包名B> --workspace --filter <包名A>

所以我们只需要运行:

pnpm add tools --workspace --filter test-tools

就可以了,运行完这个命令之后,就可以在test-tools的package.json文件中看到我们的工具库依赖了。

image.png

这表示test-tools的依赖tools来自于工作空间,而不是单独的包。这样当我们的tools发生变化时,我们在使用的时候也会自动更新。

现在我们就可以愉快的使用工具库了。
在index.ts里面添加以下代码:

import { sum } from "tools";
console.log(sum(1, 2));
console.log(sum(10, 2));

然后在package.json中添加以下代码:

"type": "module",
"scripts": {
  "dev": "tsc && node dist/index.js"
}

然后运行pnpm dev就可以看到在控制台打印出了我们的结果了。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容