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.json
的 workspaces
:
"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, /* 防止发布 */
}
之后就可以编写公共组件的代码逻辑。
同上操作,我们依次初始化 utils
和 http
公共方法依赖。分别修改各自的 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”。比如,把 components
、http
、utils
添加到 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-front
和 web-back
。它们自身的 package.json
都配置的了 scripts
命令配置,且有自己的 vite.config.ts
文件。
"scripts":{
"dev":"vite",
"build":"vite-tsc && vite build"
}
在现有的条件下,我们想要启动或者打包项目,必须每次单独 cd
进入某个项目的根目录执行 dev
或 build
命令。这不是我们所希望的,我们希望实现的是:
1、能够在根目录下,执行 dev
或 build
命令,能够自动启动或打包 packages
文件夹中的所有项目 (web-front
和 web-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平台上打开终端方法)。