# lerna-cli **Repository Path**: longmo666/lerna-cli ## Basic Information - **Project Name**: lerna-cli - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-10-24 - **Last Updated**: 2024-10-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 创建一个 monorepo 项目 ## 创建一个项目 ```shell npm init -y ``` 项目中安装开发依赖 lerna: ```shell pnpm i -D lerna ``` 配置命令: ```json { "scripts": { "lerna-init": "lerna init", "lerna-create": "lerna create" } } ``` 搭建多包环境: 建立 `pnpm-workspace.yaml` 文件,并且配置: ```yaml packages: - 'packages/*' ``` ## 初始化 lerna 配置: ```shell pnpm lerna-init ``` ## 创建 cli 的核心包: ```shell pnpm lerna-create @frontend-dev-cli/core ``` 这样之后,lerna 就给我们创建好了一个包的默认模板。 ## 集成 ts 我们的 cli 全部采用 ts 进行开发,所以我们需要搭建一套多包的 ts 环境。 我们在根目录下新建一个 ts 的配置文件 `tsconfig.json`: ```json5 { "compilerOptions": { "module": "commonjs", "declaration": true, "outDir": "./build", "noImplicitAny": false, "removeComments": true, "noLib": false, "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es6", "sourceMap": true, "lib": [ "es6", "esnext", "dom" ], "types": [ "node" ], "resolveJsonModule": true }, "include": [ "src/**/*.ts" // 明确指定匹配 .ts 文件 ], "exclude": [ "node_modules", "**/*.spec.ts", "package.json" ] } ``` 在根目录下新建一个 src 测试文件夹,在里面加入 index.ts 以及 a.ts 两个测试 ts 的文件: ```ts // a.ts export const a = () => { console.log('adjddj'); } export const b = () => { console.log('adjddj'); } ``` ```ts // index.ts import {a} from './a' export default function main() { a() console.log('main') } main() ``` 然后执行以下命令在根目录安装 typescript 的开发依赖: ```shell pnpm i -D typescript -w ``` 然后,配置编译的脚本: ```json { "build": "tsc" } ``` ```text error TS2688: Cannot find type definition file for 'node'. The file is in the program because: Entry point of type library 'node' specified in compilerOptions ``` 出现这个错误的原因是因为我们在 ts 编译配置中的: ```json { "types": [ "node" ] } ``` 设置 ts 的编译的宿主环境是 node,但是 ts 没有找到 node 的类型文件。所以我们执行: ```shell pnpm i -D @types/node -w ``` 安装 node 的类型文件。然后再次执行构建命令 构建完成了。 在主项目中测试完毕之后我们再到创建的 core 子项目中去新建一个 index.ts 文件并且再子项目中配置构建命令: ```json { "build": "tsc" } ``` 配置子应用的 ts 配置文件 `tsconfig.json`: ```json { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "./build" }, "include": [ "./lib" ] } ``` 直接继承了父应用的全局配置,并且指定了本应用的编译目录和输出目录。 然后我们尝试再 core 项目中执行 build 命令: ```shell pnpm build ``` 可以看到在子项目中是可以成功调用到父项目中安装的脚本命令的,并且成功按照父项目中统一配置的 ts 配置文件的规则来进行编译了。 这就是 monorepo 架构的好处。 包括像 eslint 这些代码格式校验工具,jest 这些测试工具,我们都只需要再根项目下配置依次就可以了 子项目中直接就可以集成根项目中的配置。 至此,ts环境准备完毕。 ## 打通多包项目之间的调用关系 我们先新建一个新的包: ```shell pnpm lerna-create @frontend-dev-cli/utils ``` 加入如下的工具导出: ```ts export const sum = (a: number, b: number) => a + b; ``` 在这个子项目中加入同样的编译命令以及 ts 配置文件 `tsconfig.json`: ```json { "build": "tsc" } ``` 在 utils 这个子项目中执行 build 命令,产生构建结果 然后我们调整 package.json 中的 files 配置: ```json { "files": [ "build" ] } ``` 也就是当执行 publish 操作的时候,我们只需要上传 build 目录里面的内容就可以了。 然后我们尝试在 core 这个子项目中引入utils这个项目。 我们切换到 core 项目中,执行以下命令: ```shell pnpm link @frontend-dev-cli/utils ``` 这样,utils这个子项目就被链接进来了(查看core包的package.json中的dependencies中就会有`@frontend-dev-cli/utils`) 然后我们直接在 core 中引入 utils 中导出的内容: ```ts import {sum} from "@frontend-dev-cli/utils"; sum(1, 2) ``` 我们执行构建 core 项目的命令 正常构建了,我们执行以下构建的结果: ```shell node build/core.js ``` 至此。子应用之间的调用也测试通过了。 ## 优化开发体验 优化开发体验主要是两个方面: 每一个子应用中只要ts代码变化了都需要重新触发ts代码的重新构建 我们在主项目中需要一个一次性可以执行所有子项目构建操作的命令 先解决第一个问题,这个问题很好解决,我们可以在每一个子应用中的 tsc 命令调用的时候加入 watch 参数: ```json { "scripts": { "build": "tsc --watch" }, } ``` 我们重新执行 core 中的build命令,然后重新改以下源码: 此时tsc就会一直监控源码的变化,一旦源码变化就会自动编译,并将编译结果输出到 build 目录。 我们就可以看到最新的代码执行结果了。 要解决第二个问题。其实目前有两种常见解决方案: 利用我们已经安装的 lerna 工具,lerna 中有支持一次性并行执行的命令。 比较新的一个工具:turbo。这个工具相对效率更高,体验更好,我们本次采用这个工具来解决这个问题。 首先还是在全局安装依赖: ```shell pnpm i -D turbo -w ``` 然后再根目录下新建`turbo.json` 文件,配置如下内容: ```json { "tasks": { "build": { "dependsOn": [], "outputs": ["build/**"] } } } ``` 然后调整根目录下的 build 命令并且指定包管理器: ```json { "packageManager": "pnpm@8.9.2", "scripts": { "lerna-init": "lerna init", "lerna-create": "lerna create", "build": "turbo run build" }, } ``` 再根目录下执行 pnpm build turbo 对编译结果是进行了本地缓存的以及加速的,所以编译非常的快,体验很棒 至此我们就优化了本地开发的编译问题。 ## 利用 lerna 来进行 monorepo 发包: 首先不管是 lerna 还是 pnpm 发布包之前都必须提交本地 git, 所以请先将自己本地的 git 改动全部提交到远程仓库上。 然后我们就在package.json 中添加如下的发包命令 ```json { "lerna-publish": "lerna publish" } ``` 然后我们执行命令,利用 lerna 进行发包: ```sh pnpm lerna-publish ``` 然后进入一系列和 lerna 的交互之后,就可以进行 lerna 的发包了。 发布完成之后,我们可前往 npm 仓库查看 至此,我们利用 lerna 完成了多包项目的发包操作。 我们再来提前扩展一个点哈,因为我们后面就要实现多包工程自动化发布的cicd。 而cicd肯定是在服务器上自动执行的,不能够有交互。 lerna实际上是考虑到了这一点的,它的命令行提供了如下的参数: ```json { "lerna-publish": "lerna publish 0.0.2 --yes" } ``` 可以直接指定所有的子包发布的版本以及跳过所有的交互命令行了。 而且在绝大多数情况下,尤其是在要实现自动化cicd的 monorepo 项目,保持所有子包版本的统一性是最佳的实践。 lerna在包发布完成之后,会自动基于现在的新发布的版本来自动打上一个git tag,以及自动把这个版本推送到远程分支 自动更新对统一作用域包的版本依赖,都会自动更新