尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Turborepo + Rollup 实战:用 Rollup 打包共享 UI 库并集成到 Next.js Monorepo

Turborepo + Rollup 实战:用 Rollup 打包共享 UI 库并集成到 Next.js Monorepo Turborepo Rollup 实战用 Rollup 打包共享 UI 库并集成到 Next.js Monorepo【免费下载链接】turboBuild system optimized for JavaScript and TypeScript, written in Rust项目地址: https://gitcode.com/gh_mirrors/tu/turbo导读本指南围绕 Turborepo 官方示例 examples/with-rollup 展开讲解如何在一个 Turborepo monorepo 中用 Rollup 将独立的 React 组件库repo/ui编译为可直接消费的产物并让 Next.js 应用web跨包引用它。读完本文你将掌握从create-turbo一键初始化、工作区划分、Rollup 多入口打包配置、TS/ESLint 配置包复用到 Turbo 任务编排build/dev/lint与本地/远程缓存的完整落地路径。该示例由社区维护见 meta.json目录结构小但链路完整非常适合作为纯库用 Rollup 打包、应用层用框架构建这一混合构建模式的参考蓝本。示例仓库总览Turborepo 官方为常见构建工具提供了大量-e示例如with-nextjs、with-vite、with-svelte等with-rollup是其中之一。通过 meta.json 可以确认其定位Monorepo with a single Next.js app sharing a UI library bundled with Rollup一个 Next.js 应用 一个用 Rollup 打包的共享 UI 库。使用 create-turbo 一键初始化原文档给出的创建方式是最直接、最不易出错的路径npx create-turbolatest -e with-rollup命令执行后会在当前目录生成一个名为my-turborepo的项目也可通过附加参数自定义名称。随后即可进入项目cd my-turborepo pnpm run build目录与包结构以当前仓库的 examples/with-rollup 为基准初始化后的结构如下with-rollup/ ├── apps/ │ └── web/ # Next.js 应用 │ ├── pages/index.tsx # 页面入口直接消费 repo/ui │ ├── next.config.js │ ├── package.json │ └── tsconfig.json ├── packages/ │ ├── config-eslint/ # repo/eslint-configESLint flat config 集合 │ ├── config-typescript/ # repo/typescript-config共享 tsconfig 集合 │ └── ui/ # repo/uiRollup 打包的 React 组件库 │ ├── Button.tsx │ ├── Header.tsx │ ├── rollup.config.js │ └── package.json ├── pnpm-workspace.yaml # 工作区声明apps/* 与 packages/* ├── turbo.json # Turbo 任务编排 └── package.json # 根脚本build / dev / lint / format四个核心组成部分web一个 Next.js 应用通过repo/ui/button、repo/ui/header两个子路径导入 UI 组件repo/eslint-configESLint flat 配置包包含next/eslint-plugin-next与eslint-config-prettier对外提供base、next-js、react-internal三份配置repo/typescript-config共享的tsconfig.json集合base.json、nextjs.json、react-library.json供各包通过 extends 继承repo/ui一个纯 React 组件库编译工具是 Rollup产物输出到dist/是本文的主角。每个包/应用都是 100% TypeScript并统一配好了 TypeScript静态类型检查、ESLint代码检查、Prettier代码格式化三件套。工作区与依赖声明工作区由 pnpm-workspace.yaml 声明packages: - apps/* - packages/* onlyBuiltDependencies: - swc/coreapps/*与packages/*两个通配符让 pnpm 把子目录识别为 workspace 成员onlyBuiltDependencies白名单允许swc/core执行安装后的原生构建脚本SWC 的 native 二进制需要 postinstall 编译/下载这是 Rollup 链路能正常工作的前提之一。根目录 package.json 统一了脚本入口与包管理器版本约束{ scripts: { build: turbo run build, dev: turbo run dev, lint: turbo run lint, format: prettier --write \**/*.{ts,tsx,md}\ }, packageManager: pnpm11.21.0, engines: { node: 24.19.0 } }跨包依赖通过 workspace 协议声明web依赖repo/ui: workspace:*repo/ui依赖repo/eslint-config: workspace:*与repo/typescript-config: workspace:*见 apps/web/package.json 与 packages/ui/package.json。Rollup 打包共享 UI 库多入口配置repo/ui的核心是 packages/ui/rollup.config.js。它没有采用单入口打包一个 bundle 的常见做法而是每个组件一个入口产出独立文件import swc from rollup/plugin-swc; export default [ { input: Button.tsx, output: { file: dist/button.js }, }, { input: Header.tsx, output: { file: dist/header.js }, }, ].map((entry) ({ ...entry, external: [react/jsx-runtime], plugins: [ swc({ swc: { jsc: { transform: { react: { runtime: automatic }, }, }, }, }), ], }));要点拆解多入口数组配置Rollup 支持导出配置数组每个元素是一个独立 build。这里Button.tsx→dist/button.js、Header.tsx→dist/header.js避免消费方把整个组件库一起打进包external: [react/jsx-runtime]React 19 自动 JSX runtime 的产物不应打进库文件而是作为 peer 由应用侧提供防止 React 被重复打包rollup/plugin-swcruntime: automatic用 SWC 完成 TSX → JS 的转译速度远快于 Babelautomatic运行时让 JSX 自动编译为jsx-runtime调用——这正是external需要声明react/jsx-runtime的原因两条配置是配套的编译只负责转换不做 tree-shaking 之外的额外优化产物是给消费方的最小可运行模块。package.json 的 exports 映射组件库通过 packages/ui/package.json 的exports字段对外暴露精确的入口并做到类型优先{ name: repo/ui, type: module, exports: { ./button: { types: ./Button.tsx, default: ./dist/button.js }, ./header: { types: ./Header.tsx, default: ./dist/header.js } }, scripts: { lint: eslint . --max-warnings 0, build: rollup --config, dev: pnpm build --watch } }type: module配合default: ./dist/button.js让 Rollup 输出的 ESM 产物可以被import直接消费types直接指向源文件./Button.tsx/./Header.tsx而不是生成.d.ts因为源码本身就是 TypeScript无需额外声明文件生成步骤这也保证了类型定义与实现永远同步exports只暴露./button与./header两个子路径外部无法通过repo/ui根路径或任意文件路径闯入包边界被严格锁定。组件源码与消费方式两个组件都非常简洁Button.tsx、Header.tsx// Button.tsx export const Button () { return buttonBoop/button; }; // Header.tsx export const Header ({ text }: { text: string }) { return h1{text}/h1; };应用侧在 apps/web/pages/index.tsx 中按子路径导入import { Button } from repo/ui/button; import { Header } from repo/ui/header; export default function Page() { return ( Header textWeb / Button / / ); }从源码结构看这条链路是rollup --config把 TSX 编译到dist/→ pnpm workspace 把repo/ui软链进web的 node_modules → Next.js 通过exports解析到dist/button.js并消费。中间没有任何手工复制产物的步骤全靠包解析完成。Turbo 任务编排build / dev / lint原文档给出了两个最常用的根级命令pnpm run build # 构建所有 apps 与 packages pnpm run dev # 开发所有 apps 与 packages它们都只是turbo run task的薄封装。真正的行为定义在根目录 turbo.json{ $schema: https://turborepo.org/schema.json, tasks: { build: { dependsOn: [^build], inputs: [$TURBO_DEFAULT$, .env*], outputs: [dist/**, .next/**, !.next/cache/**, !.next/dev/**] }, lint: { outputs: [] }, dev: { cache: false, persistent: true } } }逐项解读build.dependsOn: [^build]拓扑依赖。web的 build 会等待其依赖包repo/ui的 build 完成后再执行这正是先打包 UI 库、再构建 Next.js的保证同时各包之间没有依赖关系时可以被 Turbo 并行执行build.inputs哈希参与项。$TURBO_DEFAULT$代表各包自身的文件内容.env*让环境文件变化也触发缓存失效build.outputs声明可缓存的产物目录。dist/**覆盖 Rollup 的输出.next/**覆盖 Next.js 构建输出并显式排除.next/cache与.next/dev这些属于过程产物不应进入缓存lint.outputs: []lint 不产出文件因此不缓存产物dev.cache: falsepersistent: truedev 是常驻进程不参与缓存并标记为 persistent 让 Turbo 正确管理其生命周期。注意ui包的 dev 脚本是pnpm build --watchRollup watch 模式web的 dev 是next dev两者都会被turbo run dev拉起——persistent: true同时要求这类长驻任务必须被单独运行不能作为其他任务的依赖。在各包内部build/dev/lint的实现分别是repo/uibuild: rollup --configdev: pnpm build --watchlint: eslint . --max-warnings 0见 packages/ui/package.jsonwebbuild: next builddev: next devstart: next startlint: eslint . --max-warnings 0见 apps/web/package.json并在 next.config.js 中开启了reactStrictMode: true。共享 ESLint 与 TypeScript 配置与with-rollup其他示例一致这里也预设了两套共享配置包保证 monorepo 内 lint 与类型约束统一。repo/eslint-configpackages/config-eslint提供 flat config 风格的三份配置base.js基础规则供所有包使用next-js.jsNext.js 专用包含next/eslint-plugin-next与eslint-config-prettier供web使用react-internal.jsReact 库内部规则供repo/ui使用。各包的 eslint.config.mjs及 packages/ui/eslint.config.mjs通过 flat config 机制 import 对应配置配合--max-warnings 0把任何 warning 都视为失败保证 lint 质量。repo/typescript-configpackages/config-typescript提供base.json基础编译器选项nextjs.jsonNext.js 应用专用供webreact-library.jsonReact 库专用供repo/ui。各包 tsconfig 通过extends继承对应配置例如ui使用react-library.jsonweb使用nextjs.json实现一处定义、处处复用。本地缓存与远程缓存原文档强调默认情况下 Turborepo 会进行本地缓存——同一台机器上只要任务的输入哈希未变依赖文件、环境、上游产物等再次执行就会直接命中缓存并跳过重算。这与上文build.outputs的声明直接相关只有声明了outputs的任务才值得缓存lint这类无产物任务则天然跳过。在此基础上Turborepo 还支持Remote Caching远程缓存把缓存产物共享到云端让团队与 CI/CD 管道复用彼此已经构建过的结果从而显著缩短 CI 时长。启用流程如下cd my-turborepo npx turbo login # 使用 Vercel 账号认证 Turborepo CLI npx turbo link # 将项目与远程缓存关联turbo login完成 CLI 与账号的认证turbo link完成项目与远程缓存的绑定之后每个可缓存任务的上传/下载都会走远程缓存。原文档的说明以 Vercel 为默认实现Vercel Remote Cache 对所有套餐免费无需账号时可先注册再执行上述命令。需要留意的是远程缓存行为属于官方提供的配套服务若团队没有使用 Vercel也可以自行搭建符合 Turborepo 协议的自托管缓存端点但本示例默认链路仍是本地缓存 Vercel Remote Cache。进一步学习原文档推荐继续深入以下 Turborepo 核心概念对应官方文档Pipelines任务管道dependsOn、outputs、inputs等任务字段如何构成执行图与缓存键Caching缓存缓存命中判定、哈希输入与产物恢复机制Remote Caching远程缓存跨机器共享缓存的人工制品。在本文档仓库中也可以直接对照源码继续探索完整的示例结构见 examples/with-rollup根任务编排见 turbo.jsonRollup 打包细节见 packages/ui/rollup.config.js组件导出映射见 packages/ui/package.json应用消费端见 apps/web/pages/index.tsx。【免费下载链接】turboBuild system optimized for JavaScript and TypeScript, written in Rust项目地址: https://gitcode.com/gh_mirrors/tu/turbo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表