Turborepo + Rollup 实战:用 Rollup 打包共享 UI 库并集成到 Next.js Monorepo
2026/9/19 22:09:56 网站建设 项目流程

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-nextjswith-vitewith-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-turbo@latest -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-config:ESLint flat config 集合 │ ├── config-typescript/ # @repo/typescript-config:共享 tsconfig 集合 │ └── ui/ # @repo/ui:Rollup 打包的 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-config:ESLint flat 配置包(包含@next/eslint-plugin-nexteslint-config-prettier),对外提供basenext-jsreact-internal三份配置;
  • @repo/typescript-config:共享的tsconfig.json集合(base.jsonnextjs.jsonreact-library.json),供各包通过 extends 继承;
  • @repo/ui:一个纯 React 组件库,编译工具是 Rollup,产物输出到dist/,是本文的主角。

每个包/应用都是 100% TypeScript,并统一配好了 TypeScript(静态类型检查)、ESLint(代码检查)、Prettier(代码格式化)三件套。

工作区与依赖声明

工作区由 pnpm-workspace.yaml 声明:

packages: - "apps/*" - "packages/*" onlyBuiltDependencies: - "@swc/core"
  • apps/*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": "pnpm@11.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.tsxdist/button.jsHeader.tsxdist/header.js,避免消费方把整个组件库一起打进包;
  • external: ["react/jsx-runtime"]:React 19 自动 JSX runtime 的产物不应打进库文件,而是作为 peer 由应用侧提供,防止 React 被重复打包;
  • @rollup/plugin-swc+runtime: "automatic":用 SWC 完成 TSX → JS 的转译(速度远快于 Babel),automatic运行时让 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 <button>Boop</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 text="Web" /> <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: false+persistent: true:dev 是常驻进程,不参与缓存,并标记为 persistent 让 Turbo 正确管理其生命周期。注意,ui包的 dev 脚本是pnpm build --watch(Rollup 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.json);
  • webbuild: 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-config(packages/config-eslint)提供 flat config 风格的三份配置:

  • base.js:基础规则,供所有包使用;
  • next-js.js:Next.js 专用(包含@next/eslint-plugin-nexteslint-config-prettier),供web使用;
  • react-internal.js:React 库内部规则,供@repo/ui使用。

各包的 eslint.config.mjs(及 packages/ui/eslint.config.mjs)通过 flat config 机制 import 对应配置,配合--max-warnings 0把任何 warning 都视为失败,保证 lint 质量。

@repo/typescript-config(packages/config-typescript)提供:

  • base.json:基础编译器选项;
  • nextjs.json:Next.js 应用专用(供web);
  • react-library.json:React 库专用(供@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(任务管道)dependsOnoutputsinputs等任务字段如何构成执行图与缓存键;
  • Caching(缓存):缓存命中判定、哈希输入与产物恢复机制;
  • Remote Caching(远程缓存):跨机器共享缓存的人工制品。

在本文档仓库中,也可以直接对照源码继续探索:完整的示例结构见 examples/with-rollup,根任务编排见 turbo.json,Rollup 打包细节见 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询