普通视图

发现新文章,点击刷新页面。
昨天以前首页

npm、yarn、pnpm实现monorepo并使用changset管理版本

作者 追日出
2025年4月13日 16:18

在前端开发领域中有很多库和框架他都是使用的monorepo架构实现的,它的好处有很多,可以很方便的管理自己的目录结构,还可以实现分包的功能等。比如在vue3中就使用到了这个架构,它会将runtime阶段和complier阶段和响应式阶段进行更细致的划分。当我们需要什么功能就可以引入单独的包来实现。

像这种多包的概念,我们每次更新都要去手动更改package.json中的版本,这样会很麻烦。可以使用changeset来管理我们每个包的版本。接下来我们就来实现一下。

mkdir monorepo-npm-yarn-pnpm
cd monorepo-npm-yarn-pnpm

我有这样一个场景,我有一个logger方法就是用来记录函数调用日志或者接口日志等等的一些日志信息的。我们用monorepo架构的方式来实现一下,这种场景确实不需要monorepo架构,但是为了掩饰使用我们就用这一个工具吧。

npm monorepo

先来看npm如何实现monorepo

mkdir monorepo-npm
cd monorepo-npm

npm init -y

npm install typescript @types/node --save-dev // 安装typescript和node类型提示

npx tsc --init // 初始化tsconfig

修改tsconfig.json修改成如下内容

{
  "compilerOptions": {
    "outDir": "dist",
    "types": [ "node" ],
    "target": "es2016", 
    "module": "NodeNext", 
    "moduleResolution": "NodeNext",
    "declaration": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
  }
}

然后创建两个包

npm init -w packages/core -y
npm init -w packages/logger -y

可以看到两个包已经创建完成,package.json也自动加了workspaces配置项。

image.png

我们需要在core中添加logger的依赖

npm install logger --workspace core          

可以看到core的依赖中引入了logger的包并且在node_modules中创建了looger的软链接。

image.png

改下包的名称,然后install一下。

image.png

image.png

接下来我们初始化子包的tsconfig文件。这里我们需要使用npm exec --workspace 子包名称 -- 命令这样的命令来找到子包并运行命令。

npm exec --workspace @spring-npm/core -- npx tsc --init
npm exec --workspace @spring-npm/logger -- npx tsc --init

然后修改tsconfig.json文件和上面的一样就行。

接下来我们来写logger日志的方法。

这里需要使用chalk第三方工具库来为我们的日志信息添加颜色。当我们需要为子包安装包的时候需要使用命令npm install --workspace 包名 库名

npm install --workspace @spring-npm/logger chalk

首先在package.json中添加type: 'module'的配置,让nodejs支持esmodule的模块规范。

image.png

这个loggerFun方法就根据日志类型打印不同的颜色的日志信息,非常简单。信息包含了函数调用的时间、函数名称以及函数的返回值。

import chalk from "chalk";

export enum LogLevel {
  DEBUG,
  INFO,
  WARN,
  ERROR,
}

export function loggerFun<T>(type: LogLevel, funName: string, message: string, reutrnValue: T = {} as T): void {
  const time = new Date().toLocaleTimeString();

  let resultValue: string = "";
  if (Object.prototype.toString.call(reutrnValue) == "[object Object]") {
    resultValue = JSON.stringify(reutrnValue);
  } else if (Object.prototype.toString.call(reutrnValue) == "[object Array]") {
    resultValue = "[" + (reutrnValue as Array<any>).join(", ") + "]";
  } else {
    resultValue = reutrnValue + "";
  }

  if (type === LogLevel.DEBUG) {
    debugLogger(funName, message, resultValue, time);
  } else if (type === LogLevel.INFO) {
    infoLogger(funName, message, resultValue, time);
  } else if (type === LogLevel.WARN) {
    warnLogger(funName, message, resultValue, time);
  } else if (type === LogLevel.ERROR) {
    errorLogger(funName, message, resultValue, time);
  } else {
    console.log(chalk.whiteBright(`[] ${time} - ${funName} - returned: ${reutrnValue}`));
  }
}

function debugLogger(funName: string, message: string, reutrnValue: string, time: string) {
  console.log(chalk.blueBright(`[DEBUG] ${time} - ${funName} - ${message} - returned: ${reutrnValue}`));
}

function infoLogger(funName: string, message: string, reutrnValue: string, time: string) {
  console.log(chalk.greenBright(`[INFO] ${time} - ${funName} - ${message} - returned: ${reutrnValue}`));
}

function warnLogger(funName: string, message: string, reutrnValue: string, time: string) {
  console.log(chalk.yellowBright(`[WARN] ${time} - ${funName} - ${message} - returned: ${reutrnValue}`));
}

function errorLogger(funName: string, message: string, reutrnValue: string, time: string) {
  console.log(chalk.redBright(`[ERROR] ${time} - ${funName} - ${message} - returned: ${reutrnValue}`));
}

接下来我们对这个方法进行tsc编译成js文件,然后修改成package.json将入口指向变异后的文件。

npm exec --workspace @spring-npm/logger -- npx tsc

修改package.json

image.png

这里logger包就完成了。

接下来写core的代码。

import { loggerFun, LogLevel } from "@spring-npm/logger";

function add(a: number | string, b: number | string): number {
  loggerFun(LogLevel.DEBUG, add.name, "进入函数");
  if (typeof a !== "number" || typeof b !== "number") {
    loggerFun(LogLevel.ERROR, add.name, "参数错误");
    throw new Error("参数错误");
  }
  loggerFun(LogLevel.INFO, add.name, "完成", a + b);
  return a + b;
}

add(1, 2);
add(1, '2');

image.png

changeset 版本管理

到这里monorepo的架构就完成了,功能也实现了。接下来我们结合changeset来进行版本管理。

登陆npm,然后添加一个组织

image.png

因为我们是@spring-npm开头的包,所以我们就叫spring-npm

image.png

接下来将corelogger包在package.json添加属性,都改成公有的

  "publishConfig": {
    "access": "public"
  }

执行npm adduser登陆npm

npm adduser

如果你npm弹出来的是中文页面,那么就需要将淘宝镜像改成npm的镜像。我是使用nrm来改的。

nrm use npm

image.png

这样就登陆成功了。

然后安装changeset

npm install --save-dev @changesets/cli

初始化changeset,会生产一个.changeset目录,里面就是记录你的版本信息的。

npx changeset init

image.png

因为changeset是基于git代码提交变动的,所以我们要初始化git仓库。

git init

git add . 

git commit -m 'first commit'

然后执行npx changeset add,创建一次changeset记录

npx changeset add

image.png

这里我们选择更改小版本minor

然后npx changset version 来修改版本

npx changeset version

image.png

image.png

可以看到版本全部修改,修改的是小版本。

image.png

然后提交git,并发布到npm上

git add .
git commit -m 'version 1.1.0'

npx changeset publish

image.png

这里就发布完成。

然后将coreloggerindex.ts都加个回车,我们修改大版本试一下。

image.png

然后上面的步骤都是一样的。

image.png

这里升级大版本也完成了。这里就是npm + monorepo + changeset就完成了。

yarn

yarn 和 pnpm 其实都是差不多的,只有命令不同。这里就不带着大家一一去写了。只有执行子包的命令和安装子包的第三方库有些命令上的不同。其它都是一样的,使用changeset也是一样的。

yarn 初始化两个目录

npm init -w packages/logger -y 
npm init -w packages/core -y

yarn core引用logger包

yarn workspace core add logger@1.0.0

yarn 为子包安装依赖

yarn workspace cli add chalk commander

yarn 执行子包命令

cd packages/core
npx tsc --init

pnpm

pnpm添加pnpm-workspace.yaml文件,指定子包目录。

packages: - 'packages/*'

创建两个包

mkdir packages packages/core packages/logger 
cd packages/core 
npm init -y 
cd ../logger 
npm init -y

为core包添加logger的依赖

pnpm --filter logger add core --workspace

执行子包的命令

pnpm --filter logger exec npx tsc --init

子包添加依赖

pnpm --filter logger add chalk

这里pnpm为主包添加依赖需要使用-w

pnpm add --save-dev -w @changesets/cli

总结

pnpm monorepo会更好用一点,npm和yarn都是存在幽灵依赖的问题,同时npm的monorepo受版本的影响。pnpm安装包更快没有幽灵依赖的问题。

无论是npm、yarn还是pnpm它们的monorepo架构都是相同的,只有执行子包的命令不同。只需要知道执行子包的命令就行了。写法上都是通用了。大家有时间可以将yarn和pnpm都实现一遍。如果遇到什么问题可以评论区讨论一下。

❌
❌