likes
comments
collection
share

npm-workspaces-实现monorepo的总结

作者站长头像
站长
· 阅读数 15

1、个人对Monoreop的理解

个人认为 Monoreop 实质就是 组件(依赖)抽离。即,将多个项目的公共依赖抽取到特定位置,使得项目不必重复安装和维护公共组件(依赖)。

比如,有多个 Vue 项目都依赖一个公共的组件库 element-plus。如果采用 ==multirepo==,那么此时在每个项目的根目录中就需要多次安装 element-plus

更复杂一点的场景,如果 我们对 element-plus 中 Table 组件进行了二次封装,那么就需要在每个项目中都需要写封装代码。特别是,涉及到公共组件的bug修复,不可避免地需要修改多个项目的相同组件。

如果采用 ==monorepo==,此时只需要在根目录下载一次 element-plus,多个项目就能够使用该组件。对于公共组件的封装和修改也是一样。

2、npm workspaces 实现 monorepo

使用总结:不建议业务级项目使用 npm workspaces 实现 monoreop。

存在的不足之处:

1、离线的字体文件,无法正确解析 src 引用。因此无法抽离成公共文件。

2、非 Windows 平台,无法实现一次启动多个项目。

以 Vue3 + Vite + Pinia 为例。

2.1 基本框架搭建

可以使用 vite 脚手架,生成一个项目的公共框架。

npm create vite my-project

目录结构大致如下:

public
src
.gitgnore
index.html
package.json
README.md
tsconfig.json
tsconfig.node.json
vite.config.ts

2.2 workspaces 初始化

  • packages 项目的创建

在终端中执行命令:

npm init -w packages/web-front -y

命令执行完成后,在项目根目录会生成一个文件夹 packages,文件目录结构如下:

- packages
    - web-front
        - package.json

同时,根目录的 package.json 文件,会出现 workspaces 的配置:

 "workspaces": [
    "packages/web-front", /* 或者 "package\\web-front" */
  ]

后续操作: (1)将根目录的所有文件(除了 package.json.gitigore 文件)剪切到 web-front 文件夹中;

(2)将 根据目录 package.json 文件中的 "scripts" 配置剪切到 web-front 的文件夹中的 package.json 文件中;

(3)设置 web-front 文件夹中 packages.json 文件配置 "private": true

注意: 初始化 web-xxx 项目同上操作,必须在终端依次执行 npm init packages/web-xxx -y。不要使用一些非官方文档写的 "workspaces":{"packages/*"} 这种配置方式,会有莫名奇妙的bug。

重复上述操作,初始化 web-back 项目。此时,根目录的 package.jsonworkspaces

 "workspaces": [
    "packages/web-front",
    "packages/web-back",
  ]
  • components 公共组件

在终端执行命令:

npm init -w components -y

此时,根目录会生成一个 components 文件夹,里面有一个 package.json 文件。文件目录结构如下:

- components
  - package.json

修改 package.json 文件

{
    "name": "@yp/components", /* 公共组件名,安装时要使用的包名 */
    "private": true, /* 防止发布 */
}

之后就可以编写公共组件的代码逻辑。

同上操作,我们依次初始化 utilshttp 公共方法依赖。分别修改各自的 package.json:

// utils/package.json
// utils 封装公共方法,比如 数学计算,数据类型判断 等
{
    "name":"@yp/utils",
    "private": true
}
// http/package.json
// http 负责封装统一的 axios 请求
{
    "name":"@yp/http",
    "private": true
}

做完这些操作后,根目录的 package.json 中的 workspaces 会展示成这样:

{
    "workspaces": [
        "packapges/web-front",
        "packages/web-front",
        "components",
        "http",
        "utils"
    ]
}

2.3 根目录 typescript 的配置

  • global.d.ts 抽离全局的 ts 类型

示例:

interface Window {
  devBaseApiURL: string;
  proBaseApiURL: string;
}

interface Pagination {
  currentPage: number;
  pageSize: number;
}
  • tsconfig.json 设置全局 ts conifg

特别注意以下字段的配置

"compilerOptions":{
    "baseUrl": ".",
    "paths": {
      "@jl/http": ["http"],
      "@jl/utils": ["utils"]
    }
},
includes: [
    "packages/**/*.ts",
    "packages/**/*.d.ts",
    "packages/**/*.tsx",
    "packages/**/*.vue",
    "components/**/*.ts",
    "components/**/*.d.ts",
    "components/**/*.tsx",
    "components/**/*.vue"
]

3、组件/依赖的安装

3.1 全局依赖的安装

如果所有的项目都依赖某个组件或包,比如 element-plus。 可以执行以下命令,将依赖信息添加到根package.json中。

npm install elment-plus

3.2 局部依赖的安装

如果某个组件/依赖只有某个项目需要,则可以执行以下命令,将依赖项添加到自身的 package.json 中。比如,http 项目依赖 axios

npm i axios -w @yp/http  /* 注意, `@yp/http` 是自身 `package.json` 中 `name` 字段的值  */

3.3 项目内公共组件的安装

如果想要将自己封装的公共组件,添加到 packages 文件中的某一个项目,操作同 "3.2"。比如,把 componentshttputils 添加到 packages/web-front 项目中。需执行一下命令:

/* 注意:
    1、`web-front` 是自身 `package.json` 中的 `name` 字段的值;
    2、`@yp/components` & `@yp/http` & `@yp/utils` 都是自身  `package.json` 中的 `name` 字段的值
*/
npm i @yp/components -w web-front 
npm i @yp/http -w web-front
npm i @yp/utils -w web-front

这样,在 packages/web-front 就可以正常使用公共组件了。

示例:

// App.vue
<script>
import http from "@yp/http"
import uitls from "@yp/uitls"
htpp.get("index").then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

uitls.add(1,2)
</script

4、dev & build 命令

上述示例中,我们实际上有两个务级项目 web-frontweb-back。它们自身的 package.json 都配置的了 scripts 命令配置,且有自己的 vite.config.ts 文件。

"scripts":{
    "dev":"vite",
    "build":"vite-tsc && vite build"
}

在现有的条件下,我们想要启动或者打包项目,必须每次单独 cd 进入某个项目的根目录执行 devbuild 命令。这不是我们所希望的,我们希望实现的是:

1、能够在根目录下,执行 devbuild 命令,能够自动启动或打包 packages 文件夹中的所有项目 (web-frontweb-back);

2、通过传递命令参数,启动或打包指定的项目。比如 npm run dev:front,仅启动 web-front 项目。

4.1 build 命令

npm 官网文档提供了思路在工作区上下文中运行命令

/
    workspaces: 在所有配置的工作区的上下文中运行该命令;
    --if-present: 如果工作区配置了 `build` 命令
/
npm run build --workspackes --if-present;

// 仅执行 web-front 项目中的 build 命令
npm run build -w=web-front

因此,我们可以在根目录的 package.json中的 scripts 字段,添加如下打包命令配置:

"scripts":{
    "build": "npm run build --workspaces --if-present", /* 打包素有项目 */
    "buld:front": "npm run build -w=web-front", /* 仅打包 web-front */
    "build:back": "npm run build -w=web-back", /* 仅打包 web-back */
}

同时,我们修改 packages/** 文件下每个项目的 vite.config.ts 将打包好的项目放到根目录的 dist 文件夹下的指定的文件夹中,这样就实现了打包文件 统一管理。

vite.config.ts 打包配置示例

//  packages/web-front/vite.config.ts
build:{
    rollupOpitons:{
        output: {
            dir: path.resolve(__dirname, "../.../dist/web-front")
        }
    
}

4.2 dev 命令的配置

dev 命令和 build 命令的区别在于:无法通过 npm run dev --workspaces --if-present 启动所有的项目。因为,命令终端只有一个,只能启动一个端口。

我们先配置,单个项目的启动命令。同 build 命令:

"scripts":{
    "dev:front": "npm run dev -w=web-front", /* 仅启动 web-front */
    "dev:back": "npm run dev -w=web-back", /* 仅启动 web-back */
}

如何启动全部项目呢?

需要我们在根目录手写一个 dev.js,然后配置 dev 命令执行该 js 文件。

"scripts":{
    "dev": "node ./dev.js", /* 启动 所有项目 */
}

dev.js 的作用:

获取 workspaces 的配置,逐个打开终端,执行每个项目的 dev 命令

代码如下:

// dev.js
const childProcess = require("child_process");
const pkJosn = require("./package.json");

const workspaces = pkJosn.workspaces;
const projects = workspaces.filter((p) => p.startsWith("packages/"));
const pLen = projects.length - 1;
projects.forEach((p,,index) => {
  run(p,index);
});
function run(project,index) {
  childProcess.exec(
    `start cmd.exe /K npm run dev --workspace=${project} --if-present`
  );
  if(index === pLen) {
      setTimeout(()=> {
          console.log("all dev success!");
          process.exit(); // 当前程序退出
      },1000)
  }
}

注意:上述代码仅支持在 Windows 平台上执行,Mac 没有相应的解决方案(希望有大神能告知,nodejs 在Mac平台上打开终端方法)。