各位观众老爷,晚上好! 咳咳,今天咱们聊聊 JavaScript Monorepo 的那些事儿。说白了,就是如何用工具(比如 Lerna 和 Nx)来管理一大堆 JavaScript 项目,让它们像一个大家庭一样和谐相处,一起搞事情。
啥是 Monorepo?为啥要用它?
首先,咱们得搞明白 Monorepo 是个啥玩意儿。简单来说,就是把多个项目(或者说包)的代码都放在同一个代码仓库里。 这跟传统的每个项目一个仓库的模式(俗称 Polyrepo)不太一样。
为啥要用 Monorepo 呢? 主要有这么几个好处:
- 代码共享更容易: 如果多个项目都需要用到同一个组件或者工具函数,Monorepo 可以让你直接引用,不用复制粘贴,避免代码冗余。
- 依赖管理更方便: 所有的项目都在同一个仓库里,你可以更容易地管理它们之间的依赖关系,统一升级依赖版本。
- 代码复用性更高: 将公共模块提取到共享包中,各项目可以共享使用,减少重复开发,提高代码复用率。
- 原子提交更容易: 如果一个功能需要修改多个项目,你可以一次提交所有修改,保证功能的一致性。
- 方便协作: 所有的项目都在同一个仓库里,团队成员可以更容易地了解整个项目的结构和各个项目之间的关系。
当然,Monorepo 也有一些缺点,比如:
- 仓库体积更大: 所有的项目代码都在同一个仓库里,仓库体积肯定会更大,clone 和 checkout 的时间可能会更长。
- 构建时间更长: 如果所有的项目都需要构建,构建时间可能会更长。
- 权限管理更复杂: 需要更精细的权限管理,避免不同项目之间的代码互相干扰。
特性 | Monorepo | Polyrepo |
---|---|---|
代码共享 | 容易,直接引用 | 困难,需要发布到 npm 或者使用其他方式共享 |
依赖管理 | 方便,统一管理 | 复杂,需要手动管理每个项目的依赖 |
原子提交 | 容易,一次提交所有修改 | 困难,需要多次提交 |
代码复用 | 高,共享包可以被多个项目使用 | 低,重复开发的可能性较高 |
仓库体积 | 大 | 小 |
构建时间 | 可能更长 | 可能更短 |
权限管理 | 复杂 | 简单 |
协作方式 | 更容易了解整个项目结构和各个项目之间的关系 | 项目间隔离,需要更多沟通 |
Lerna:Monorepo 的老牌管家
Lerna 是一个老牌的 Monorepo 管理工具,它可以帮你管理多个包的发布、版本控制和依赖关系。
Lerna 的核心功能:
lerna bootstrap
: 安装所有项目的依赖,并且将项目之间的依赖关系链接起来。lerna publish
: 发布所有有更新的项目到 npm。lerna version
: 更新所有项目的版本号,并且生成 changelog。lerna run
: 在所有项目中运行指定的 npm script。lerna exec
: 在所有项目中执行指定的命令。
Lerna 的使用方法:
-
安装 Lerna:
npm install --global lerna
-
初始化 Lerna:
lerna init
这会在你的项目根目录下生成
lerna.json
和packages
目录。lerna.json
是 Lerna 的配置文件,packages
目录用来存放所有的项目。 -
创建项目:
在
packages
目录下创建你的项目,每个项目都是一个独立的 npm 包。例如,我们创建两个项目:
package-a
和package-b
。mkdir packages/package-a cd packages/package-a npm init -y cd ../../ mkdir packages/package-b cd packages/package-b npm init -y cd ../../
-
配置
lerna.json
:打开
lerna.json
文件,配置你的项目。{ "packages": [ "packages/*" ], "version": "independent", "npmClient": "npm", "useWorkspaces": true, "command": { "publish": { "ignoreChanges": [ "ignored-file", "*.md" ] } } }
packages
: 指定项目所在的目录,这里我们使用packages/*
表示packages
目录下所有的目录都是一个项目。version
: 指定版本控制模式,independent
表示每个项目独立控制版本号。 还有固定模式:fixed
, 所有包使用同一个版本号。npmClient
: 指定使用的 npm 客户端,这里我们使用npm
。useWorkspaces
: 是否使用 npm workspaces。command.publish.ignoreChanges
: 配置发布时忽略的变更文件。
-
安装依赖:
lerna bootstrap
这会安装所有项目的依赖,并且将项目之间的依赖关系链接起来。 如果
useWorkspaces
设置为true
, Lerna 会使用 npm/yarn/pnpm workspaces 功能,将依赖安装到项目根目录的node_modules
目录下,减少依赖重复安装。 -
发布项目:
lerna publish
这会发布所有有更新的项目到 npm。 Lerna 会自动检测哪些项目有更新,并且提示你输入新的版本号。
Lerna 的一些高级用法:
-
使用 scope: 可以使用 scope 来限制命令的执行范围。
lerna run test --scope=@my-org/package-a
这会在
@my-org/package-a
项目中运行test
命令。 -
使用 ignore: 可以使用 ignore 来忽略一些项目。
{ "packages": [ "packages/*", "!packages/package-c" ], "version": "independent" }
这会忽略
packages/package-c
项目。
Lerna 的优点:
- 简单易用,容易上手。
- 功能完善,满足 Monorepo 管理的基本需求。
- 社区活跃,有大量的插件和工具可以使用。
Lerna 的缺点:
- 性能较差,构建速度慢。
- 对 TypeScript 的支持不够好。
- 配置比较复杂。
Lerna 示例代码:
// packages/package-a/index.js
export function helloFromA() {
console.log("Hello from package A!");
}
// packages/package-b/index.js
import { helloFromA } from '@my-org/package-a';
export function helloFromB() {
console.log("Hello from package B!");
helloFromA();
}
// packages/package-b/package.json
{
"name": "@my-org/package-b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"dependencies": {
"@my-org/package-a": "*" // 依赖本地的 package-a
},
"keywords": [],
"author": "",
"license": "ISC"
}
Nx:Monorepo 的性能王者
Nx 是一个更现代化的 Monorepo 管理工具,它专注于提高构建速度和开发效率。
Nx 的核心功能:
- 依赖图分析: Nx 可以分析项目之间的依赖关系,并且生成依赖图。
- 增量构建: Nx 可以根据依赖图,只构建受影响的项目,大大提高构建速度。
- 缓存: Nx 可以缓存构建结果,避免重复构建。
- 代码生成: Nx 可以根据模板生成代码,提高开发效率。
- 插件: Nx 有大量的插件可以使用,支持各种框架和工具。
Nx 的使用方法:
-
安装 Nx:
npm install --global nx
-
创建 Nx 工作区:
npx create-nx-workspace my-workspace
这会创建一个新的 Nx 工作区。 Nx 会提示你选择一个预设的配置,例如 React, Angular 或者 Node.js。
-
创建项目:
nx generate @nx/react:application my-app
这会创建一个新的 React 项目。 你可以使用
nx generate
命令创建各种类型的项目,例如 library, component, service 等。 -
构建项目:
nx build my-app
这会构建
my-app
项目。 Nx 会自动分析项目之间的依赖关系,并且只构建受影响的项目。 -
运行项目:
nx serve my-app
这会运行
my-app
项目。
Nx 的一些高级用法:
-
使用 affected: 可以使用
affected
命令来查看受影响的项目。nx affected:build
这会构建所有受影响的项目。
-
使用 task runner: Nx 使用 task runner 来执行构建、测试和 lint 等任务。 你可以自定义 task runner 的配置。
-
使用 plugins: Nx 有大量的插件可以使用,支持各种框架和工具。 例如,可以使用
@nx/eslint
插件来配置 ESLint, 使用@nx/jest
插件来配置 Jest。
Nx 的优点:
- 性能优秀,构建速度快。
- 对 TypeScript 的支持非常好。
- 功能强大,可以满足各种复杂的 Monorepo 管理需求。
- 社区活跃,有大量的插件和工具可以使用。
Nx 的缺点:
- 学习曲线较陡峭,需要学习 Nx 的各种概念和配置。
- 配置比较复杂。
Nx 示例代码:
// libs/my-lib/src/index.ts
export function helloFromLib() {
console.log("Hello from my lib!");
}
// apps/my-app/src/app/app.tsx
import { helloFromLib } from '@my-workspace/my-lib';
function App() {
return (
<div>
<h1>Hello World!</h1>
<button onClick={helloFromLib}>Say Hello from Lib</button>
</div>
);
}
export default App;
// nx.json (部分)
{
"npmScope": "my-workspace",
"affected": {
"defaultBase": "main"
},
"implicitDependencies": {
"workspace.json": "*",
"package.json": {
"dependencies": "*",
"devDependencies": "*"
},
"tsconfig.base.json": "*",
"tslint.json": "*",
".eslintrc.json": "*",
"nx.json": "*"
},
"tasksRunnerOptions": {
"default": {
"runner": "@nrwl/nx-cloud",
"options": {
"cacheableOperations": [
"build",
"lint",
"test",
"e2e"
],
"accessToken": "YOUR_NX_CLOUD_ACCESS_TOKEN"
}
}
},
"targetDefaults": {
"build": {
"dependsOn": [
"^build"
],
"inputs": [
"default",
"{workspaceRoot}/babel.config.json",
"{workspaceRoot}/.swcrc"
]
}
},
"namedInputs": {
"default": [
"{projectRoot}/**/*",
"sharedGlobals"
],
"sharedGlobals": []
},
"generators": {
"@nrwl/react": {
"application": {
"style": "css",
"linter": "eslint",
"unitTestRunner": "jest",
"e2eTestRunner": "cypress"
},
"library": {
"linter": "eslint",
"unitTestRunner": "jest"
},
"component": {
"style": "css"
}
}
}
}
共享配置:让 Monorepo 更和谐
在 Monorepo 中,共享配置是非常重要的,它可以保证所有项目的代码风格、构建流程和测试环境一致。
常见的共享配置方式:
-
使用
tsconfig.json
: 可以将 TypeScript 的配置放在根目录下的tsconfig.json
文件中,然后在每个项目的tsconfig.json
文件中继承根目录下的tsconfig.json
文件。// tsconfig.json (根目录) { "compilerOptions": { "target": "es5", "module": "commonjs", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true } } // packages/package-a/tsconfig.json { "extends": "../../tsconfig.json", "compilerOptions": { "rootDir": "src", "outDir": "dist" }, "include": ["src"] }
-
使用 ESLint: 可以将 ESLint 的配置放在根目录下的
.eslintrc.json
文件中,然后在每个项目的.eslintrc.json
文件中继承根目录下的.eslintrc.json
文件。// .eslintrc.json (根目录) { "env": { "browser": true, "es2021": true }, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended" ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 12, "sourceType": "module" }, "plugins": [ "@typescript-eslint" ], "rules": { "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": ["warn"] } } // packages/package-a/.eslintrc.json { "extends": "../../.eslintrc.json", "rules": { "no-console": "warn" } }
-
使用 Prettier: 可以将 Prettier 的配置放在根目录下的
.prettierrc.js
文件中,然后在每个项目中都使用相同的 Prettier 配置。// .prettierrc.js (根目录) module.exports = { semi: false, trailingComma: "all", singleQuote: true, printWidth: 120, };
-
使用 husky 和 lint-staged: 可以使用 husky 和 lint-staged 来在提交代码之前自动运行 lint 和 format 命令,保证代码质量。
// package.json (根目录) { "devDependencies": { "husky": "^7.0.0", "lint-staged": "^12.0.0" }, "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "*.{js,jsx,ts,tsx,json,md}": [ "prettier --write", "eslint --fix" ] } }
优化构建流程:让 Monorepo 飞起来
在 Monorepo 中,构建流程的优化非常重要,它可以大大提高开发效率。
常见的优化构建流程的方式:
- 使用增量构建: 只构建受影响的项目,避免重复构建。 Nx 默认支持增量构建。
- 使用缓存: 缓存构建结果,避免重复构建。 Nx 默认支持缓存。
- 并行构建: 并行构建多个项目,提高构建速度。 Lerna 和 Nx 都支持并行构建。
- 使用 Webpack Module Federation: 使用 Webpack Module Federation 可以将 Monorepo 中的项目拆分成多个独立的模块,然后动态加载这些模块。 这样可以减少构建时间和部署时间。
- 代码分割: 使用代码分割可以将 Monorepo 中的项目拆分成多个小的 bundle,然后按需加载这些 bundle。 这样可以提高应用的加载速度。
Lerna vs Nx:选哪个?
Lerna 和 Nx 都是优秀的 Monorepo 管理工具,选择哪个取决于你的具体需求。
- 如果你的项目比较简单,对性能要求不高,可以选择 Lerna。 Lerna 简单易用,容易上手,可以满足 Monorepo 管理的基本需求。
- 如果你的项目比较复杂,对性能要求很高,可以选择 Nx。 Nx 性能优秀,功能强大,可以满足各种复杂的 Monorepo 管理需求。
特性 | Lerna | Nx |
---|---|---|
易用性 | 简单易用,容易上手 | 学习曲线较陡峭,需要学习 Nx 的各种概念和配置 |
性能 | 性能较差,构建速度慢 | 性能优秀,构建速度快 |
TypeScript 支持 | 对 TypeScript 的支持不够好 | 对 TypeScript 的支持非常好 |
功能 | 功能完善,满足 Monorepo 管理的基本需求 | 功能强大,可以满足各种复杂的 Monorepo 管理需求 |
配置 | 配置比较简单 | 配置比较复杂 |
社区 | 社区活跃,有大量的插件和工具可以使用 | 社区活跃,有大量的插件和工具可以使用 |
总而言之,Lerna 就像一位老管家,经验丰富,处理基本事务得心应手;Nx 则像一位年轻的 CEO,充满活力,擅长优化流程,提升效率。选择谁,取决于你家的规模和发展目标。
好了,今天的讲座就到这里,希望对大家有所帮助! 祝大家在 Monorepo 的世界里玩得开心!