一、Lerna包缓存的核心价值

很多做大型前端项目的朋友,应该都遇到过这样的糟心事:一个项目里塞了十几个甚至几十个子包,每次改完代码要构建,明明只有一个子包改了一行,整个项目却要花十几分钟甚至半小时重新编译所有子包;更烦的是发布的时候,没改的包还要再打包一遍,慢不说还容易出问题。

Lerna就是专门用来管这种多包项目(专业点叫Monorepo)的工具,而包缓存就是Lerna里能解决上面糟心事的核心功能。简单说,它的作用就是把之前构建过的包的结果存起来,下次再构建的时候,如果这个包的代码没改,就直接拿之前存的结果用,不用再重新编译。

举个例子,你有个叫utils的工具包,里面是各种通用函数,第一次构建的时候Lerna会把它的编译结果存到缓存里。之后你改了另一个叫app的业务包,构建的时候Lerna会检查utils的代码、依赖有没有变化,发现没变就直接用缓存里的结果,只编译app,速度一下子就能提好几倍。

二、Lerna包缓存的基础配置

要用上Lerna的缓存,首先得确保你用的是Lerna 4及以上的版本,老版本的缓存功能不完善。接下来我们一步步说怎么配置,所有示例统一用Node.js + Lerna的技术栈,先明确技术栈:Node.js 16+、Lerna 6.4.1、npm 8+。

2.1 开启基础缓存

Lerna 4以后,缓存功能默认是开启的,但默认配置可能不够用,我们先从最基础的配置改起。首先在项目根目录的lerna.json文件里加缓存相关的配置:

{
  "version": "independent", // 子包版本独立管理,也可以用fixed模式
  "npmClient": "npm", // 用npm做包管理工具,也可以换成yarn
  "command": {
    "build": {
      "cache": true, // 开启构建缓存
      "cacheLocation": "./node_modules/.cache/lerna", // 缓存文件存的位置
      "ignore": ["src/**/*.test.ts"] // 忽略这些文件的变化,改了也不触发重新构建
    }
  }
}

这里的ignore配置很实用,比如你改了测试文件,测试文件的变化不会影响包的编译结果,所以可以把测试文件排除,避免没必要的重新构建。

2.2 配置缓存的内容校验规则

默认情况下,Lerna判断一个包要不要重新构建,会检查包的package.json、src目录下的所有文件、还有依赖的版本有没有变化。但有时候我们需要自定义这个规则,比如某个子包的编译结果只和src下的index.ts有关,其他文件改了不影响,就可以这么配置:

{
  "version": "independent",
  "npmClient": "npm",
  "command": {
    "build": {
      "cache": true,
      "cacheLocation": "./node_modules/.cache/lerna",
      "ignore": ["src/**/*.test.ts"],
      "contentHash": ["src/index.ts"] // 只根据index.ts的内容判断缓存是否有效
    }
  }
}

这个配置的意思是,只有src/index.ts的内容变了,才会重新构建这个包,其他文件改了都不会触发。如果你的子包结构固定,这个配置能大大减少缓存失效的概率。

三、缓存优化的进阶技巧

基础配置能解决大部分问题,但如果要进一步提升效率,还有几个进阶技巧可以用。

3.1 配置缓存的过期时间

有时候缓存里的文件会越来越多,占硬盘空间,或者某个包的缓存虽然有效,但放了太久,你想定期清理。可以给缓存加过期时间,超过时间的缓存会自动失效:

{
  "version": "independent",
  "npmClient": "npm",
  "command": {
    "build": {
      "cache": true,
      "cacheLocation": "./node_modules/.cache/lerna",
      "ignore": ["src/**/*.test.ts"],
      "cacheMaxAge": 86400000 // 缓存过期时间,单位是毫秒,这里是1天
    }
  }
}

这个配置适合那些经常会有大版本更新的项目,比如你每周会发一次大版本,那么缓存保留1天就够了,不用留太久占空间。

3.2 配置缓存的共享规则

如果你的团队是多人协作,大家各自构建的时候都会生成自己的缓存,能不能把缓存共享起来?比如A同事构建过的包,B同事直接用,这样大家都省时间。

Lerna支持把缓存存到共享服务器上,比如用S3或者自己搭的存储服务。我们先看本地共享的配置,适合团队用同一个局域网服务器的情况:

{
  "version": "independent",
  "npmClient": "npm",
  "command": {
    "build": {
      "cache": true,
      "cacheLocation": "./node_modules/.cache/lerna",
      "ignore": ["src/**/*.test.ts"],
      "cacheProvider": "s3", // 用S3作为缓存提供者
      "cacheProviderOptions": {
        "bucket": "lerna-cache-bucket", // 存储缓存的桶名
        "region": "us-east-1", // 存储的区域
        "accessKeyId": "你的访问密钥ID",
        "secretAccessKey": "你的访问密钥"
      }
    }
  }
}

配置好之后,只要有一个人构建过某个包,其他人构建的时候就会从S3拉取缓存,不用自己再编译。这个配置对团队协作的项目特别有用,尤其是大家都在改同一个大项目的时候,能省很多重复的时间。

3.3 配置发布时的缓存复用

发布的时候,Lerna默认会重新构建所有要发布的包,哪怕这些包之前已经构建过。我们可以配置发布的时候复用缓存,避免重复构建:

{
  "version": "independent",
  "npmClient": "npm",
  "command": {
    "build": {
      "cache": true,
      "cacheLocation": "./node_modules/.cache/lerna",
      "ignore": ["src/**/*.test.ts"]
    },
    "publish": {
      "useNpm": true,
      "buildCache": true // 发布时复用构建缓存
    }
  }
}

这个配置的意思是,发布的时候会检查每个要发布的包有没有有效的缓存,如果有就直接用缓存里的结果打包发布,不用再重新构建。比如你之前已经构建过utils包,现在要发布它,就直接拿之前的结果用,速度快很多。

四、缓存的应用场景、优缺点和注意事项

4.1 应用场景

Lerna的包缓存适合所有用Lerna管理的Monorepo项目,尤其是以下几种场景:

  1. 子包数量多的项目:比如有20个以上子包的项目,每次构建都重新编译所有包,缓存能把构建时间从几十分钟降到几分钟。
  2. 频繁修改的项目:比如业务迭代快,每周都要改好几个子包,每次改都重新构建,缓存能节省大量时间。
  3. 团队协作的项目:多人同时开发,共享缓存能让大家不用重复构建,提升整个团队的效率。
  4. 发布频繁的项目:比如每周都要发布多个子包,发布时复用缓存能加快发布流程,减少发布时间。

4.2 技术优缺点

优点

  1. 提升构建速度:最核心的优点,能大幅减少重复构建的时间,尤其是子包多的项目,效果非常明显。
  2. 节省资源:不用重复编译,能减少CPU、内存的消耗,也能节省硬盘空间(如果配置了缓存过期)。
  3. 提升发布效率:发布时复用缓存,能加快发布流程,减少发布过程中可能出现的问题。
  4. 配置简单:大部分配置只要改lerna.json就行,不用写复杂的脚本,容易上手。

缺点

  1. 缓存失效的风险:如果配置不当,比如contentHash配置错了,会导致本该重新构建的包没重新构建,或者不该重新构建的包频繁重新构建。
  2. 共享缓存的安全问题:如果用S3等共享缓存,密钥管理不当可能会有安全风险,比如密钥泄露导致缓存被篡改。
  3. 缓存占空间:如果不配置过期时间,缓存会越来越大,占硬盘空间,尤其是子包多的项目,缓存可能会占几个G的空间。
  4. 依赖变化的影响:如果某个子包的依赖版本变了,缓存会失效,需要重新构建,这是正常的,但如果依赖版本经常变,缓存的效果会打折扣。

4.3 注意事项

  1. 版本要够:一定要用Lerna 4及以上的版本,老版本的缓存功能有很多bug,效果不好。
  2. 配置要合理:ignore和contentHash的配置要符合项目的实际情况,比如如果你的子包的编译结果依赖package.json里的某个字段,就不要把package.json排除在contentHash之外。
  3. 定期清理缓存:如果不配置过期时间,要定期手动清理缓存,比如执行npx lerna clean命令,能清理所有缓存和node_modules。
  4. 共享缓存的安全:如果用S3等共享缓存,密钥要妥善保管,不要硬编码在lerna.json里,最好用环境变量来管理,比如:
{
  "version": "independent",
  "npmClient": "npm",
  "command": {
    "build": {
      "cache": true,
      "cacheLocation": "./node_modules/.cache/lerna",
      "cacheProvider": "s3",
      "cacheProviderOptions": {
        "bucket": "lerna-cache-bucket",
        "region": "us-east-1",
        "accessKeyId": "${AWS_ACCESS_KEY_ID}",
        "secretAccessKey": "${AWS_SECRET_ACCESS_KEY}"
      }
    }
  }
}

然后在环境变量里配置AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY,这样就不会把密钥暴露在配置文件里了。 5. 测试缓存效果:配置完缓存后,要测试一下效果,比如改一个子包的代码,看构建时间有没有减少,改一个被ignore的文件,看缓存有没有失效,确保配置正确。

五、常见问题排查

有时候配置了缓存,但发现缓存没生效,或者效果不好,可能是以下几个原因:

  1. Lerna版本太低:检查Lerna的版本,用npx lerna -v命令查看,如果版本低于4,升级到最新版本。
  2. 配置写错了:检查lerna.json里的配置,比如cache有没有设为true,cacheLocation的路径对不对,有没有语法错误。
  3. 缓存被清理了:比如执行了npx lerna clean命令,或者node_modules被删了,缓存就没了,需要重新构建生成缓存。
  4. 依赖版本变化:如果某个子包的依赖版本变了,缓存会失效,需要重新构建,这是正常的。
  5. 共享缓存配置错误:如果用了S3等共享缓存,检查bucket、region、密钥有没有配置对,网络能不能连接到S3。

如果遇到问题,可以先执行npx lerna build --verbose命令,看详细的构建日志,日志里会说明为什么某个包的缓存失效了,能帮你快速定位问题。

六、文章总结

Lerna的包缓存是提升Monorepo构建和发布效率的重要工具,合理配置缓存能大幅减少重复构建的时间,提升开发和发布的效率。基础配置能解决大部分问题,进阶配置比如共享缓存、发布时复用缓存能进一步提升效率,尤其是团队协作的项目。

配置缓存的时候要注意版本、配置合理性、安全和定期清理缓存,遇到问题可以通过日志排查。只要配置得当,Lerna的包缓存能给大型Monorepo项目带来非常明显的效率提升,让开发者不用再花大量时间等待构建和发布,把精力放在业务开发上。