TuBrief
구독 채널
비디오
커뮤니티

将 pnpm Monorepo 迁移至 Nub 的踩坑与解决方案

TuBrief 편집팀
2026년 7월 27일
0
Computing/Software

원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.

中文한국어EnglishEspañolहिन्दीFrançaisPortuguêsDeutschРусскийالعربيةBahasa Indonesia日本語

관련 영상

又有新的包管理器了!?(Bun 的替代方案)8:29

又有新的包管理器了!?(Bun 的替代方案)

Better Stack

커뮤니티의 다른 글

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

2026년 9월 13일

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

2026년 9월 13일

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

2026년 9월 13일

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

2026년 9월 13일

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

2026년 9월 12일

Apple Won the AI Race

2026년 9월 12일

댓글 (0)

Log in to leave a comment

아직 작성된 글이 없습니다

© 2026 . All rights reserved.

TuBrief
구독 채널
비디오
커뮤니티
로그인

将 pnpm Monorepo 迁移至 Nub 的踩坑与解决方案

Node.js 生态系统令人身心俱疲。叠加了 tsx、dotenv-cli、nvm 和 pnpm 的构建环境既臃肿,又经常在更新任何东西时搞乱脚本。当采用 Rust 编写的集成工具包 Nub 以将这套混乱的包管理打包为单个二进制文件之姿登场时,说实话在感到高兴的同时我也抱有怀疑。

实际将生产环境的 pnpm-workspace Monorepo 迁移到 Nub 后,发现它确实相当有吸引力。不过,过程可没有想象中那么顺畅。从依赖项冲突、24小时安全锁定策略,到 CI 缓存隔离问题,各种在实际业务中直接面临的阻碍接踵而至。

解决工作区迁移时出现的依赖树问题

Nub 的包引擎 aube 可以直接识别现有的 pnpm-workspace.yaml 和 package.json 中的 workspaces。正如其宣称与 pnpm-lock.yaml v9 模式在字节级兼容一样,它默认使用了现有的虚拟 Store 结构(node_modules/.store/)。

问题在于那些隐式预设了平铺(hoisted)node_modules 结构的旧版工具。迁移后直接运行就会抛出 MODULE_NOT_FOUND 报错并崩溃。为了让服务先跑起来,你需要使用 --node-linker hoisted 选项将包树展开再开始。

在应用缓存的 Warm 安装基准下,默认的 GVS 模式耗时 346 ms。Hoisted 模式虽然变慢了 4 倍以上,耗时 1461 ms,但仍比 pnpm v10+ 的 3453 ms 快了 2 倍以上。先通过 Hoisted 模式确保兼容性,随后在移除旧工具依赖的过程中过渡到默认的 GVS 模式是比较稳妥的做法。

`bash
#!/usr/bin/env bash
set -euo pipefail

echo "==> [1/4] 检查 Nub 二进制文件"
if ! command -v nub &> /dev/null; then
echo "Error: 未找到 Nub。请先运行 'npm i -g @nubjs/nub'。"
exit 1
fi

echo "==> [2/4] 检查 pnpm-lock.yaml 模式"
if [ -f "pnpm-lock.yaml" ]; then
nub pm use nub
fi

echo "==> [3/4] 在 package.json 中明确运行引擎"
node -e '
const fs = require("fs");
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
pkg.devEngines = pkg.devEngines || {};
pkg.devEngines.packageManager = {
name: "nub",
version: "^0.4.0",
onFail: "warn"
};
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2) + "\n");
'

echo "==> [4/4] 生成 Lockfile"
nub install --frozen-lockfile=false

`

使用该脚本将 pnpm-lock.yaml 转换为 nub.lock,每周可以节省 2 小时以上原本花在修脚本修到头疼上的时间。

24小时发布年龄限制与紧急 Hotfix 应对

Nub 的安全策略相当严格。它默认阻止生命周期脚本,若没有 npm Provenance 签名就会抛出 ERR_NUB_TRUST_DOWNGRADE。最让人措手不及的是 minimumReleaseAge 选项。发布未满 24 小时的包版本会被视为处于安全验证等待状态,从而禁止安装。

平时这是个绝佳的防线,但在爆发零日漏洞、需要立即部署 1 小时前刚刚发布的修复版本时,它就会变成难以逾越的高墙。此时需要在根目录的 package.json 中明确指定允许构建脚本的包,并通过命令进行放行。

`json
{
"name": "@org/monorepo-root",
"private": true,
"allowBuilds": {
"esbuild": true,
"sharp": true,
"@fast-cve/patch-pkg": true
}
}

`

绕过受阻的 Hotfix 包并应用更新的流程如下:

`bash

1. 手动批准并添加未满 24 小时的 Hotfix 包

nub add --allow-build=@fast-cve/patch-pkg @fast-cve/patch-pkg@1.0.1-hotfix

2. 批量批准进入批准队列的构建脚本

nub approve-builds

`

执行 nub approve-builds 会解封拦截列表并立即继续构建。如果在 OSV 数据库中被注册为恶意代码(MAL-*)的包,即使使用此命令也无法强行放行,这时就必须寻找更高版本的替代方案。

GitHub Actions 与 Docker 的缓存层隔离

要提升 CI/CD 速度,需要了解 Nub 的缓存路径。Node 二进制文件会放入 ~/.cache/nub/node/<version>/,而包 CAS(Content-Addressable Store)则分布在 ~/.cache/nub/ 与 node_modules/.store/ 中。

在 Docker 多阶段构建中,必须使用 BuildKit 的缓存挂载(cache mount)完全隔离安装层,才能发挥缓存效果。

`dockerfile
FROM ghcr.io/nubjs/nub:latest AS base
WORKDIR /app

FROM base AS dependencies
COPY package.json nub.lock pnpm-workspace.yaml ./
COPY packages/core/package.json ./packages/core/
COPY packages/api/package.json ./packages/api/

通过 BuildKit 缓存挂载复用存储库

RUN --mount=type=cache,target=/root/.cache/nub
nub ci --prefer-offline

FROM dependencies AS builder
COPY . .
RUN nub run build --filter=@org/api

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY --from=builder /app/packages/api/dist ./dist
COPY --from=builder /app/node_modules ./node_modules

EXPOSE 3000
CMD ["node", "dist/index.js"]

`

在 GitHub Actions 中,可以使用 nubjs/setup-nub@v0 来替代 actions/setup-node。

`yaml
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

  - uses: nubjs/setup-nub@v0
    with:
      cache: true

  - run: nub ci
  - run: nub -r run build
  - run: nub -r run test

`

将 setup-nub 与缓存相结合后,nub ci 的速度可以达到 346 ms 的水平。相比传统的 pnpm(3453 ms),流水线中的包安装时间大幅减少,整体 CI 构建时间缩减了一半。看到月终的云端基础设施账单,心情确实会变好。

使用 Shell 包装脚本统一团队开发环境

Nub 内置了基于 oxc 的内存转译器,无需 tsx 或 ts-node 即可直接运行 TypeScript 代码。它还会自动读取 .env 文件。在使用 pnpm run(442.7 ms)时每次都会出现的 Node.js 进程引导开销不复存在,nub run 的脚本执行时间缩短到了 14.7 ms。体感非常明显。

要防止团队成员之间的 Node.js 版本碎片化,只需在根目录下放置 .node-version 即可。

`bash
echo "22.15.0" > .node-version
nub src/index.ts

`

在 Node 22.15.0 及以上版本中,得益于同步 module.registerHooks(),冷启动延迟被彻底消除。为了在有新成员加入时无需繁琐解释就能一次性配好环境,配置一个自动化脚本是最干脆利落的选择。

`bash
#!/usr/bin/env bash
set -euo pipefail

echo "==> 开始配置开发环境"

if ! command -v nub &> /dev/null; then
if command -v brew &> /dev/null; then
brew install nubjs/tap/nub
else
npm install -g --ignore-scripts=false @nubjs/nub
fi
fi

将 pnpm、npm 命令自动路由至 Nub 引擎

nub pm shim
nub node install
nub install

if [ ! -f ".env.local" ] && [ -f ".env.example" ]; then
cp .env.example .env.local
fi

echo "==> 配置完成。请使用 'nub run dev' 运行。"

`

只要运行过 nub pm shim,即使习惯性地输入 pnpm install 或 npm run,也会被 Nub 执行器截获并处理。CLI 执行开销从 pnpm exec(191 ms)降低到 nubx(11 ms),此外每周还能省下至少 2 小时听着“在我电脑上明明好好的”这种话来进行排错的时间。