手写乞丐版Lerna

216 阅读3分钟

手写乞丐版Lerna

大家好,我是每天坚持进步一点点的铁蛋儿,一个爱分享前端的老bady。这篇是分享手写Lerna,大家务必点赞收藏保存一下慢慢看,你们的关注是我更新的动力。

相关学习资源

本系列有配套视频,大家学习同时千万不要忘了三连 + 关注 + 分享,有道是喝水不忘挖井人~

Monorepo

随着一个项目的子项目越来越多的情况,就出现了多项目管理的问题,Monorepo设计方式就走到了大众的眼前(它是采取一种单Git仓库管理多个项目的架构设计),市面上主流的库像React、Bable、Vue。。。。都采用了这种方式管理。

为了方便大家使用Monorepo架构设计Lerna就出现,现在行业的标杆是Lerna,绝对大部分项目里面都是使用Lerna来管理多项目。

Lerna主要做的事情是初始化项目、安装依赖、更换版本、发布包,它是一个开原方案,你可以修改它。

但是更多的时候你用不上它,你看React、Vue团队都很多项目,它们为什么没用上lerna。 因为一些工具带来便利的同时,也会带来局限性。

呃。。。好吧。但是作为一个合格的架构师面对灵活多变的诉求核心的能力是把它造出来,而不是只能用工具。

只能用工具的话,躺平当个切图仔也挺好。但是作为一个有前途的程序员(卷的的起飞了) 要学会慢慢思考怎么做这些东西。

最主要的是最近它停止维护了。。。。

1. yarse-parser 是一个解析命令行参数的库,它最好用的地方是获取命令行参数

image-20220527173151686

最主要的是知道有哪些包,只要知道有package.json 就是一个软件包。

那就设计一个类这个软件包对应上,拥有这个软件包的行为。

2. 读当前软件包下的package.json

  import path from 'path'
  import fs from 'fs'
  import { exec } from 'child_process'
  import chalk from 'chalk'
  type PkgJSON = {
    name: string,
    version: [number, number, number],
    main: string,
    tiedan?: {
      type: "app" | "service" | "lib",
      port?: number,
      devlinks?: string[]
    },
    dependencies: {
      [dep: string]: string
    },
    devDependencies: {
      [dep: string]: string
    }
  }
  export class Package {
    private json: PkgJSON
    constructor(private dir: string) {
      // 有可能dir是相对路径 path出绝对路径
      const pkgJsonFilePath = path.resolve(dir, "package.json")
      // 读出package.json的所有东西
      const _json = this.parseJson(fs.readFileSync(pkgJsonFilePath, 'utf-8'))
      this.json = _json
      // 切成数组以后是字符串 还要map成数字
      this.json.version = _json.version.split('.').map((x: string) => parseInt(x))
    }
    // 获取package.json是字符串转成对象 
    private parseJson(str: string) {
      try {
        return JSON.parse(str)
      }
      catch (ex) {
        console.error("parse json error:@" + this.dir)
        throw ex
      }
    }
  }
  

3. 在当前软件包下执行命令

 // 当前目录下执行命令
   public async exec(cmd: string) {
     // TODO 命令行的颜色
     return new Promise<void>((resolve) => {
       // 当前路径执行命令
       const proc = exec(cmd, {
         cwd: this.dir
       })
       // 实时的信息输出
       proc.stdout!.on('data', (data) => {
         console.log(data)
       })
       // 实时错误信息输出
       proc.stderr!.on('data', (data) => {
         console.log(data)
       })
       // 进程clsoe的时候resolve
       proc.on('close', () => {
         resolve(null)
       })
     })
   }
 ​
 public getName() {
     return this.json.name
 }
 ​
 public async npmInstall() {
     console.log(chalk.green(("npm install " + this.getName())))
     await this.exec('pnpm') // 这里当然使用pnpm了
 }

4. 查看有多少个项目

扫描所有的项目,遍历所有的文件目录,收集值使用生成器函数(Generator),这样的话就不用刻意收集值了。

// dir: 路径 
// patten: 想要的文件
// exclude: 不想要的文件
export function* walk(dir: string, patten: RegExp, exclude: RegExp): Generator<[string, string]> {
const files = fs.readdirSync(dir)
for (let file of files) {
 const fullpath = path.resolve(dir, file)
 if (fullpath.match(exclude)) {
   continue
 }
 if (fullpath.match(patten)) {
   yield [file, dir]
 }
 if (fs.statSync(fullpath).isDirectory()) {
   yield* walk(fullpath, patten, exclude)
 }
}
}
// 用例
const result = walk(path.resolve(__dirname, '../'), /package.json$/, /node_modules|.git/)
​
console.log([...result])

5. 找到包以后,进行集中管理

import { Package } from './Package'export class Project {
private packages: Package[]
constructor(dirs: string[]) { 
 this.packages = dirs.map(dir => {
   return new Package(dir)
 })
}
​
async install() {
    await Promise.all(this.packages.map(pkg => pkg.npmInstall()))
    }
}

6. 运行多项目安装

 // walk的返回值第二个是dir
 const dirs = [...walk(path.resolve(__dirname, '../'), 
 /package.json$/, /node_modules|.git/)].map(x => x[1]) 

 ​
 const project = new Project(dirs)
 switch (cmd) {
   case "install":
     project.install()
     break
 }
 // package.json 启动 传入install 
 "scripts": {
     "dev": "ts-node ./scripts/main.ts -- install"
  },
  1. TODO
    • 多项目的link
    • 多项目build
    • version
    • publish

我是每天坚持进步一点点的铁蛋儿,如果你对技术有像一素的偏执,欢迎一起学习一起进步。