在 airi 仓库工程中基于 tsdown 构建 SolidJS 组件库:unplugin-solid 集成与配置实战
2026/9/9 23:46:33 网站建设 项目流程

在 airi 仓库工程中基于 tsdown 构建 SolidJS 组件库:unplugin-solid 集成与配置实战

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

本文基于 airi 仓库内置的 tsdown 技能参考文档 recipe-solid.md,完整讲解如何用 tsdown(由 Rolldown 驱动的高性能库打包器)配合unplugin-solid构建 Solid 组件库。airi 是一个以 pnpm workspace 组织的巨型 monorepo,仓库内大量子包(如 packages/plugin-sdk/tsdown.config.ts)都通过 tsdown 完成发布物打包,本食谱正是该技能体系中面向 Solid 框架的库工程范式。读完本文,你将掌握 Solid 组件库的脚手架初始化、platform: 'neutral'跨运行时产物配置、类型声明生成、以及把solid-js正确外置为 peer dependency 的完整发布链路。

为什么 Solid 组件库需要专门的构建配方

与 React、Vue、Svelte 一样,Solid 是 tsdown 技能文档中明确列出的框架支持对象。在 SKILL.md 的 "Framework & Runtime Support" 表中,Solid 一栏的说明是 "SolidJS JSX transform",即 Solid 库的构建核心是把 Solid 的 JSX 语法编译为面向其细粒度响应式系统(createSignalcreateEffect等)的运行时调用。

关键在于:Solid 的 JSX 语义与标准 JSX 并不相同。Solid 组件里{count()}这类表达式、<Show><For>等控制流组件,都需要在编译期被特殊处理,JSX 编译器会为每个动态表达式生成细粒度的响应式订阅代码。因此打包 Solid 组件库时,不能像 React 那样依赖运行时自带的 JSX 转换,而必须接入专门的插件——这就是unplugin-solid

tsdown 构建在 Rolldown 之上,天然支持 JSX/TSX 语法解析,但把 JSX 正确编译成 Solid 语义代码需要 unplugin 这样的框架级插件参与 transform 阶段(见 advanced-plugins.md 中关于 unplugin 生态兼容性的说明)。

快速开始:脚手架初始化

最快捷的方式是直接使用create-tsdown官方脚手架创建带有 Solid 模板的项目:

npx create-tsdown@latest -t solid

执行后即得到一套预配置好的工程骨架,包含tsdown.config.tspackage.jsontsconfig.json与源码目录,后续只需在src/中编写.tsx组件即可。

版本前提:运行 tsdown 的构建环境需要 Node.js 22.18.0 或更高(来自 SKILL.md 的 Runtime Requirement)。不过这只是"运行构建工具"的要求——通过target选项可以把打包产物降到 ES2020 甚至node18/node20,产物本身并不被锁定在 Node 22+。对于需要发布到浏览器或低版本 Node 环境的库,请按此前提安排 CI。

如果你更习惯手动搭建,参考 guide-getting-started.md 的做法:先安装 tsdown 本体与 TypeScript,再添加下述配置文件即可:

pnpm add -D tsdown pnpm add -D typescript

最小可运行配置逐项拆解

原文档给出的 Solid 配方配置如下(这也是-t solid模板生成的tsdown.config.ts的核心形态):

import solid from 'unplugin-solid/rolldown' import { defineConfig } from 'tsdown' export default defineConfig({ entry: ['./src/index.ts'], platform: 'neutral', dts: true, plugins: [solid()], })

我们逐项拆解每个字段在 Solid 库场景下的含义:

配置项本食谱取值作用与说明
entry'./src/index.ts'库的打包入口。通常入口文件负责export所有公共组件、类型与工具函数。也支持多入口对象({ index: 'src/index.ts', Button: 'src/Button.tsx' })与 glob 模式,详见 option-entry.md
platform'neutral'声明产物与运行时无关(见下文"neutral 平台"专项解析)。对于要同时被浏览器、SSR、测试环境消费的组件库,这是推荐取值,详见 option-platform.md
dtstrue生成并打包.d.ts类型声明文件(见下文"DTS 生成"专项解析),详见 option-dts.md
plugins[solid()]挂载unplugin-solid的 Rolldown 适配器,负责把.tsx/.jsx中的 JSX 编译为 Solid 响应式调用

一个典型的组件库源码布局是:

// src/Button.tsx import type { JSX } from 'solid-js' interface ButtonProps { type?: 'primary' | 'secondary' onClick?: () => void children?: JSX.Element } export function Button(props: ButtonProps) { return ( <button class={`btn btn-${props.type ?? 'primary'}`} onClick={props.onClick}> {props.children} </button> ) }
// src/index.ts export { Button } from './Button' export type { ButtonProps } from './Button'

index.ts中集中 re-export,保证entry只需指向一个入口;props 相关的 interface 记得一并导出,以便dts: true生成的类型声明对消费方完整可用。

安装依赖:unplugin-solid 与它的/rolldown入口

原文档明确要求安装unplugin-solid

npm install -D unplugin-solid

在 airi 这类使用 pnpm workspace 的 monorepo 中,等价写法是pnpm add -D unplugin-solid(仓库根目录有 pnpm-workspace.yaml),但无论用哪个包管理器,它都必须是devDependency——构建期插件不应进入最终产物的运行时依赖。

这里有几个值得展开的工程细节:

  • unplugin 是"一个插件,多打包器通用"的统一抽象。正如 advanced-plugins.md 所总结,unplugin 生态的插件(如unplugin-auto-importunplugin-vue-components)可以按目标打包器从不同子路径导入。unplugin-solid同样暴露多个入口,其中最核心的形态包括面向 Vite、面向 Rollup/Rolldown 的适配层。
  • 本食谱导入的是unplugin-solid/rolldown,也就是专门为 Rolldown 准备的适配器。tsdown 底层由 Rolldown 驱动,因此应使用/rolldown入口,而不是把 Vite 或 Rollup 的入口强塞进plugins数组(那会导致类型不匹配,通常需要// @ts-expect-erroras any处理,详见 advanced-plugins.md 的 Troubleshooting 一节)。
  • 与之对照,advanced-plugins.md 中还给出过一种基于vite-plugin-solid的写法(面向 Vite 生态);而在 tsdown 语境下,Solid 的推荐路径就是本食谱的unplugin-solid/rolldown

如果你的solid-js需要从依赖/peer 角度避免被误打包,见下文"依赖外置"一节。

关键要点一:为什么用platform: 'neutral'

原文档把platform: 'neutral'列为第一条关键点,理由在于Solid 组件库通常是"通用库"(universal library):它既可能在浏览器被import,也可能在 Vitest/jsdom、SSR、甚至 Electron renderer 等环境中运行。若把平台写死为nodebrowser,产物的模块解析策略与内置模块处理就会带上平台假设。

参考 option-platform.md,tsdown 提供三种平台:

平台运行时假设内置模块处理适用场景
node(默认)Node.js自动解析 Node 内置模块(fspath等)服务端、CLI、工具链
browserWeb 浏览器使用 Node 内置模块时告警前端应用
neutral平台无关不做任何假设通用库(组件库、工具库)

neutral的具体表现(引自 option-platform.md):

  • 不对运行时做任何假设,不做内置模块的自动解析;
  • 模块解析只信任exports字段(默认mainFields: []),对运行时行为拥有完全控制;
  • 产物本身不能携带平台绑定代码,从而保证同一份 dist 在各类宿主中行为一致。

需要注意的配套要求:

  1. 依赖方 package 必须提供exports字段,否则neutral下解析会失败并提示The "main" field here was ignored. Main fields must be configured explicitly when using the "neutral" platform.此时需要在inputOptions.resolve.mainFields中显式声明:
    export default defineConfig({ platform: 'neutral', inputOptions: { resolve: { mainFields: ['module', 'main'], }, }, })
  2. CJS 格式的固有限制:tsdown 中cjs格式总是使用node平台且无法更改(参见 option-platform.md 的 "CJS Format Limitation")。因此如果需要同时产出 ESM + CJS,且追求最大兼容,常规做法是默认输出 ESM 走neutral,需要 CJS 消费方时另行说明或采用多配置分别构建。

关键要点二:dts: true生成类型声明

原文档第二条关键点是类型声明生成。tsdown 底层使用 rolldown-plugin-dts 来生成并打包.d.ts(详见 option-dts.md)。前提是项目内安装了 TypeScript

对组件库而言.d.ts是"库的可交付物"之一——消费方在 IDE 中补全 props、获得组件类型约束,都依赖产物中的声明文件。可用的增强配置包括:

dts 选项类型说明
sourcemapboolean生成声明文件的 source map(monorepo 场景定位源码很有用)
compilerOptionsobject覆盖 TS 编译器选项(如removeComments: false
oxcboolean强制使用 oxc-transform 加速声明生成(需配合isolatedDeclarations
tsconfigstring指定独立的 tsconfig(如./tsconfig.build.json
resolver'oxc' \| 'tsc'模块解析器,默认'oxc'(快);复杂第三方类型解析失败时切'tsc'(更兼容)
cjsDefault/sideEffectsbooleanCJS 默认导出处理 / 声明中的副作用保留

性能加速建议:在tsconfig.json开启isolatedDeclarations,声明生成会走 oxc-transform 的极快路径;若个别导出缺少显式类型标注,则无法使用该路径(isolated declarations 要求所有导出显式标注类型),此时 tsdown 会回退到 TypeScript 编译器。

{ "compilerOptions": { "isolatedDeclarations": true, "declaration": true, "declarationMap": true } }

在 airi 仓库的真实实践中,dts: true是最常见的标配。例如 packages/plugin-sdk/tsdown.config.ts 中,SDK 包同时配置了多入口entrydts: trueformat: 'esm'。这也印证了技能文档 README.md 中的核心建议:"Always generate type declarations for TypeScript libraries"。

关键要点三:Solid 插件与 JSX 编译的职责边界

原文档第三条关键点是:"The Solid plugin handles JSX compilation for Solid's reactive system"。

展开讲,unplugin-solid在 transform 阶段接管.tsx/.jsx的编译,完成两件关键工作:

  1. JSX → Solid 语义编译:把 JSX 表达式树编译成solid-js的运行时指令(组件实例化、createMemo/createSignal的细粒度订阅、<Show>/<For>/<Switch>等控制流的展开)。这正是 Solid 区别于 React 的核心——React 的响应式靠整体重渲染,Solid 靠编译期静态分析出动态边界并生成精确更新代码,所以编译这一步不可省略、也不可由通用 JSX 转换替代
  2. HMR 与开发体验相关的处理:在 watch/dev 场景下保证组件热更新与类型推导正确。

由此产生的实践推论:

  • 若你的.tsx源码没有经过该插件就直接产出,结果将是无法在 Solid 运行时工作的无效代码或语义错误,因此请始终把solid()放进plugins
  • 组件文件中请从'solid-js'显式导入组件类型与控制流所需 API,避免依赖隐式全局。
  • 插件在plugins数组中的顺序即执行顺序(参考 advanced-plugins.md);当存在多个 transform 插件时注意先后次序。

让库真正可发布:依赖外置、exports 与 peerDependencies

原文档聚焦"如何构建",但一个 Solid 组件库要能发给消费方,还必须处理好依赖边界。结合 option-dependencies.md 与 recipe-react.md 中的同类范式,推荐做法如下。

1. 把solid-js外置(neverBundle)

tsdown 默认会外置dependenciespeerDependenciesoptionalDependencies,但为了把意图写清楚并防止误打包(例如把运行时打入产物导致双份 solid-js、hooks/响应式状态割裂),应显式声明:

export default defineConfig({ entry: ['./src/index.ts'], platform: 'neutral', dts: true, plugins: [solid()], deps: { neverBundle: ['solid-js', /^solid-js\//], }, })

在 option-dependencies.md 的 "Framework Component" 示例中,solid-jsvuereactsvelte被并列列为典型的框架外置目标,支持字符串与正则两种写法,字符串精确匹配包名,正则用于命中命名空间下的子路径(如solid-js/websolid-js/html)。

2. 用peerDependencies声明宿主框架

组件库本身不携带框架运行时,应要求消费方自行安装solid-js

{ "name": "my-solid-library", "version": "1.0.0", "type": "module", "main": "./dist/index.cjs", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.cjs" } }, "files": ["dist"], "peerDependencies": { "solid-js": "^1.0.0" }, "devDependencies": { "solid-js": "^1.0.0", "tsdown": "^0.9.0", "typescript": "^5.0.0", "unplugin-solid": "^0.0.0" } }

说明:上述package.json的结构模式(exports分环境入口、files: ["dist"]、框架走peerDependencies)与同技能文档 recipe-react.md 中针对 React 的完整示例同构,此处将框架替换为solid-js即得。实际开发中若开启 tsdown 的exports: true,tsdown 会基于产物自动生成exports字段(见 SKILL.md 最佳实践第 6 条)。

3. 可选的产物优化项

结合技能库其他文档,以下选项可按需叠加(详见 option-output-format.md 与 option-dts.md):

export default defineConfig({ entry: ['./src/index.ts'], format: ['esm', 'cjs'], // 需要 CJS 时注意:cjs 恒为 node 平台 platform: 'neutral', dts: true, clean: true, // 构建前清空 outDir treeshake: true, // 摇树优化,减少包体积 minify: true, // 生产构建压缩 plugins: [solid()], deps: { neverBundle: ['solid-js', /^solid-js\//], }, })

故障排查速查表

综合原文档、advanced-plugins.md、option-platform.md 与 option-dts.md,将常见问题归纳如下:

现象原因与对策
插入了非 rolldown 入口的 Solid 插件后 TS 报类型错误优先改用unplugin-solid/rolldown;确需兼容时用// @ts-expect-erroras any标注
neutral平台下第三方依赖解析失败,提示 main field ignored上游包没有exports字段;用inputOptions.resolve.mainFields显式声明(如['module', 'main']
.d.ts生成很慢在 tsconfig 开启isolatedDeclarations(前提:所有导出显式标注类型),否则回退 TS 编译器
声明文件中缺类型检查dts: true已开启、TypeScript 已安装为 devDependency,且公共 API 全部有显式类型
运行时出现两份solid-js(响应式状态割裂)deps.neverBundle中加入'solid-js'/^solid-js\//,并在package.json声明peerDependencies
需要cjs但设置platform: 'browser'/'neutral'未生效CJS 恒使用 node 平台(option-platform.md 的固有限制),按需改用 ESM 或接受该行为
构建机 Node 版本过低启动失败tsdown 运行要求 Node.js 22.18.0+;产物目标版本用target选项另行指定

在 airi 仓库中的工程上下文

airi 是一个规模庞大、以 pnpm workspace + turbo 组织的 monorepo(根目录可见 pnpm-workspace.yaml、turbo.json、package.json)。在本仓库中:

  • tsdown 被广泛用作 TypeScript 子包的标准打包器,仓库内存在大量tsdown.config.ts。例如 packages/plugin-sdk/tsdown.config.ts 采用"多入口 +dts: true+format: 'esm'"的配置;SKILL.md 中亦整理了完整的选项索引与最佳实践清单。
  • tsdown 技能参考集(.agents/skills/tsdown/references/)将框架类打包配方按recipe-*.md组织,包含 React、Vue、Solid、Svelte、WASM 五份文档,本文聚焦的 recipe-solid.md 是其中面向 Solid 的一册。
  • 需要说明的是:本文论述的 Solid 打包配方面向构建"框架无关、可被 Solid 消费方复用"的通用组件库这一场景,其价值在于你无论把产物发给浏览器、SSR 还是 Electron renderer,都能保证一份 dist 直接可用。若你正在 airi 内新建一个需要被多种宿主消费的纯 TS 子包,其打包形态与 packages/plugin-sdk/tsdown.config.ts 类似;而一旦需要支撑 Solid 消费方,则在上述模式上叠加本食谱的plugins: [solid()]platform: 'neutral'即可。

相关阅读

  • advanced-plugins.md:Rolldown / Rollup / Unplugin / Vite 四类插件的兼容性与使用方式(含 framework-specific plugin 示例)
  • option-platform.md:node/browser/neutral平台语义、mainFields 解析规则与 CJS 平台限制
  • option-dts.md:类型声明生成的完整选项表、isolatedDeclarations加速与 declaration maps
  • option-dependencies.md:neverBundle/alwaysBundle/onlyBundle与默认外置规则
  • option-output-format.md:ESM / CJS / IIFE / UMD 产物格式
  • recipe-react.md:React 组件库的同构范式(JSX transform、React Compiler 与 peer 外置),可与本文对照学习
  • guide-getting-started.md:安装、首个产物与 CLI 基础

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询