- 开发工具
- 前端
- 测试
【免费下载链接】react-cosmos
Sandbox for developing and testing UI components in isolation
React Cosmos 的开发者文档(dev.md)明确说明:docs/pages/docs/dev目录下收录的是一系列面向项目贡献者的技术文档——它们未经润色、不面向普通用户,但为贡献者提供了理解项目内部机制与技术路线图的第一手材料。本文以这三份核心文档——architecture.md、esm.md 与 tech-debt.md 为骨架,并结合当前仓库源码进行印证与深挖,帮助读者建立对 React Cosmos 整体架构的完整认知:三个核心组成部分各自的职责与插件边界、Renderer 的消息协议设计、ESM 迁移的已完成项与待办路线,以及维护者记录在案的技术债。读完后,你将既能从架构层面理解 Cosmos 的插件化设计,也能从源码层面追踪其启动链路与通信机制。
三大组成部分:Server、UI 与 Renderer
React Cosmos 由三个主要部分组成:Cosmos Server、Cosmos Renderer与Cosmos UI。官方架构文档用一张流程图清晰地展示了三者之间的关系(architecture.md):
从图中可以提炼出三条关键结论:
- Cosmos Server 只与 Cosmos UI 直接通信,它负责把 UI 供给给用户,并通过 WebSocket 与 UI 交换消息;
- Cosmos UI 与 Cosmos Renderer 之间是双向通信——UI 通过 postMessage 或 WebSocket 连接到一个或多个 Renderer;
- Cosmos Renderer 运行在用户的应用程序里,是连接"用户代码库"与"Cosmos 工作台"的桥梁。
下面逐一深入这三个部分。
Cosmos Server:Node.js 指挥中枢
Cosmos Server 是一个 Node.js 应用,它承载了两个 CLI 命令:启动开发服务器(dev server)与生成静态导出(static export)。文档列出的关键职责包括:
- 读取 Cosmos 配置(若不存在则创建默认配置);
- 检测用户的 fixture 与 decorator 模块路径;
- 基于用户数据供给 Cosmos UI 并对外提供访问;
- 运行 Server Plugins。
这些职责在源码中都有对应的落点。以开发服务器为例,startDevServer.ts 完整呈现了 Server 的启动链路:
let config = await detectCosmosConfig(); // ① 读取配置 const pluginConfigs = await getPluginConfigs({...}); // ② 收集插件配置 const serverPlugins = await getServerPlugins(...); // ③ 加载 Server Plugins config = await applyServerConfigPlugins({...}); // ④ 让插件有机会改写配置 const app = await createExpressApp(platform, config, pluginConfigs); // ⑤ 创建 HTTP 应用 const httpServer = await createHttpServer(config, app); const msgHandler = createMessageHandler(httpServer.server); // ⑥ 建立 WebSocket 消息通道其中detectCosmosConfig的实现(detectCosmosConfig.ts)展示了配置读取的三个优先级:优先支持 CLI 的--config path/to/cosmos.config.json(仅接受.json文件,不存在则报错);其次是--root-dir指定项目根目录;最后默认在根目录查找cosmos.config.json。若完全找不到配置文件,则调用createCosmosConfig生成一份默认配置并打印[Cosmos] Using default Cosmos config。
createExpressApp(expressApp.ts)则是"供给 UI"的具体实现:根路径/返回 Playground 的 HTML 页面(getDevPlaygroundHtml),/playground.bundle.js与/playground.bundle.js.map提供 UI 的打包产物,/_cosmos.ico提供站点图标。值得注意的设计细节是:HTTP 服务器会先行启动,让 Playground 尽早可用并显示加载屏,随后才逐个初始化 Server Plugins(见startDevServer.ts中await httpServer.start()在插件循环之前的顺序)。
Server Plugins 的职责与边界
Server Plugins 承担两类工作(server-plugins.md):
- 在用户的工具链(Vite、Webpack、Metro 等)中接入 Cosmos Renderer——这是每个官方打包器插件(如 react-cosmos-plugin-vite 与 react-cosmos-plugin-webpack)的核心职责;
- 任何需要 Node.js 环境的其他功能,例如访问文件系统、与 Cosmos UI 交换消息等。
从 cosmosPlugin/types.ts 可以看到 Server Plugin 的类型契约:每个插件由name、可选的config(配置改写钩子)、可选的devServer(开发服务器钩子)与可选的export(导出钩子)组成。其中devServer钩子接收config、platform、httpServer、express app与sendMessage消息发送函数,并允许返回一个清理回调(cleanup callback)——startDevServer.ts中会把这些回调收集起来,在服务器关闭时逐个执行,任何一个插件初始化失败都会中止启动并尝试清理已初始化的插件。
除了第三方插件,Cosmos 还内置了一组核心 Server Plugins(corePlugins/index.ts):portRetryPlugin(端口占用自动重试)、fixturesJsonPlugin(导出 fixtures.json)、httpProxyPlugin(HTTP 代理)、openFilePlugin(在编辑器中打开文件)、pluginEndpointPlugin(插件端点)、remoteRendererUrlPlugin(远程 Renderer URL)以及fixtureWatcherPlugin(基于 chokidar 的 fixture 文件监听;在单元测试环境下会跳过该插件以提升性能)。这组插件正是架构文档中"任何其他需要 Node.js 环境的功能"的最佳例证。
Cosmos UI:React 前端工作台
Cosmos UI 是一个 React 应用,让用户能够浏览并与 fixture 交互。文档列出的关键职责包括:
- 允许用户浏览与搜索 fixtures;
- 通过 postMessage 或 WebSocket 连接一个或多个 Renderer,并同步所选 fixture 与 fixture 状态;
- 运行 UI Plugins。
仓库中的 react-cosmos-ui 包即对应这一部分,其src/plugins目录下聚集了全部官方 UI 插件:PropsPanel(组件 props 交互控制)、ClassStatePanel(class 组件 state 控制)、InputsPanel、ResponsivePreview(响应式视口预览)、FixtureSearch(fixture 搜索)、FixtureTree(fixture 树)、FixtureBookmark(fixture 收藏)、RendererPreview与RendererSelect(多 Renderer 切换)等。这些插件通过 slots 目录下定义的插槽(如ControlPanelRowSlot、RendererActionSlot、NavPanelRowSlot)注入到 Playground 的固定位置,实现了"核心框架 + 可插拔功能"的架构。
UI Plugins 的职责定位是改善开发者体验的功能集合(ui-plugins.mdx):为组件 props 与 state 添加交互式控件、在响应式视口内预览组件、在用户默认编辑器中打开当前选中的 fixture——例如OpenFixtureButton(react-cosmos-plugin-open-fixture)插件正是"打开文件"类 UI 功能的代表。
Cosmos Renderer:无处不在的渲染包装器
Cosmos Renderer 是一个多用途的 React 包装器,文档强调它可以在多种宿主环境中运行:
- 浏览器(iframe 或新窗口);
- React Native;
- 服务器端——用于 React Server Components 场景。
它的职责是把用户的代码库连接到 Cosmos UI,具体包括(architecture.md):
- 通过 postMessage 或 WebSocket 连接 Cosmos UI;
- 导入 fixture 与 decorator 模块,揭示 fixture 名称并向 UI 上报完整的 fixture 列表;
- 按命令渲染 fixtures;
- 与 UI 同步 fixture 状态(双向数据流);
- 提供 Fixture Plugins 所需的 React Context。
仓库中 react-cosmos-renderer 包即该能力的核心实现:fixtureLoaders/ClientFixtureLoader.tsx与ServerFixtureLoader.tsx分别面向客户端与服务端,moduleLoaders目录提供StaticModuleLoader、AsyncModuleLoader与LazyModuleLoader(对应 lazy 模式);而不同的宿主由独立包提供——react-cosmos-dom(浏览器,mountDomRenderer)、react-cosmos-native(React Native)与 react-cosmos-next(Next.js 的 RSC 场景)。这种"核心渲染器 + 多宿主适配层"的结构,正是"可以在 iframe、新窗口、React Native 甚至服务端运行"这一说法背后的实现事实。
Renderer 消息协议:请求与响应
Renderer 与 UI 之间的消息被划分为Requests(请求)与Responses(响应)两类,完整类型定义位于 rendererConnect.ts:
- Requests:
pingRenderers(探测存活 Renderer)、reloadRenderer(按 rendererId 重载)、selectFixture(携带 rendererId、fixtureId 与 fixtureState 选中 fixture)、unselectFixture、setFixtureState(更新 fixture 状态;payload 同时携带 fixtureId,确保状态变更只与对应 fixture 配对); - Responses:
rendererReady(Renderer 就绪,可携带当前已选 fixture)、rendererError、fixtureListUpdate(fixture 列表更新)等。
文档特别强调了一个重要的协议语义(architecture.md):
虽然某些请求在逻辑上会自然伴随对应的响应,但它们本质上是异步单向消息,并不像 HTTP 调用那样存在直接的请求-响应配对。
这意味着消息协议是"发后即忘"的事件流,UI 与 Renderer 各自维护状态,通过事件驱动完成同步——这也解释了为何selectFixture与setFixtureState都要携带 rendererId/fixtureId:在多 Renderer 场景下(例如同时预览桌面端与移动端),每条消息都需要明确标识接收方与所作用的 fixture。
ESM 支持:已完成项与未来路线图
esm.md 记录了 React Cosmos 在 ESM 迁移上的完整状态,涉及四个影响面与优先级各不相同的子任务。
ESM Packages:已完成
将各包(包括服务端代码)以 ESM 形式发布已经完成。文档指出:不再依赖 Babel 运行时,安装后的 React Cosmos 包"本质上是剥离了 TypeScript 注解的源码",任何人都可以轻松检查与调试。较棘手的是把服务端运行时转换为 ESM——需要用 ESM 等价物替换require,同时在 Jest 中把新代码的局部 mock 回退到旧的 require 实现(因为 Jest 对 ESM 的支持在当时尚不成熟)。新的代码基"轻巧、面向未来",并将 React Cosmos 的使用门槛收窄到现代浏览器与 Node 16+。从仓库结构看,各包均同时提供index.js/client.js等入口文件与dist构建产物(如 react-cosmos 包根目录的index.js、index.d.ts),印证了"双格式发布"的现状。
ESM Fixtures:几乎可行,但前景存疑
"不经过打包器直接加载纯 ESM fixtures"目前处于几乎可行的状态。文档给出的需求清单如下:
- 以 ESM 发布 React Cosmos 的 utils 与 renderer API;
- 在生成的 index.html 中内嵌 fixture 与 decorator 映射,并通过
"module"脚本挂载渲染器; - 直接供给用户源码模块;
- 难点:供给用户的 NPM 依赖,并通过生成的 import maps 在渲染器索引中暴露它们——这需要一个"聪明"的静态服务器来解析并供给 node_modules(在 monorepo 中依赖可能嵌套或位于父目录);静态导出时 NPM 依赖还须被抽取并从新位置解析。
文档给出的 renderer index.html 示意(esm.md)展示了 ESM fixture 方案的最终形态:
<body> <div id="root"></div> <script type="importmap"> { "imports": { "react": "https://unpkg.com/es-react", "react-dom": "https://unpkg.com/es-react/react-dom", "react-is": "https://unpkg.com/es-react/react-is", "react-cosmos-core": "/node_modules/react-cosmos-core/dist/index.js", "react-cosmos-dom": "/node_modules/react-cosmos-dom/dist/index.js", "styled-components": "/node_modules/styled-components/dist/styled-components.esm.js" } } </script> <script type="module"> import fixture0 from './src/__fixtures__/Controls.js'; import fixture1 from './src/__fixtures__/HelloWorld.js'; import fixture2 from './src/__fixtures__/Props.js'; import decorator0 from './src/WelcomeMessage/cosmos.decorator.js'; import { mountDomRenderer } from 'react-cosmos-dom'; mountDomRenderer({ rendererConfig: {}, fixtures: { 'src/__fixtures__/Controls.tsx': { module: { default: fixture0 } }, 'src/__fixtures__/HelloWorld.ts': { module: { default: fixture1 } }, 'src/__fixtures__/Props.tsx': { module: { default: fixture2 } }, }, decorators: { 'src/WelcomeMessage/cosmos.decorator.tsx': decorator0, }, }); </script> </body>这段示例同时印证了两个重要事实:其一,mountDomRenderer正是 react-cosmos-dom 包暴露的浏览器挂载 API;其二,src/__fixtures__、cosmos.decorator.*等约定与仓库 examples/vite 与 examples/webpack 中的目录结构一一对应。
文档还给出了一则坦率的判断:越接近 ESM fixture 支持,越怀疑是否真有人会使用它——任何真实的前端项目最终都需要打包 NPM 依赖;与此同时,浏览器端加载 ESM fixture 还要求第三方库本身是纯 ESM(例如 React 并未以 ESM 形式发布)。因此维护者认为,在打包器方向上支持 Vite 是更有成效的投入。
ESM UI Plugins:可行但暂缓
以纯 ESM 编写 UI 插件是一个诱人的前景——它能降低插件作者的准入门槛,而且技术上是可行的(ESM 模块可以被脚本注入,或从 CJS 的 Cosmos UI 中动态导入)。所需条件与 ESM fixtures 类似:从 node_modules 供给 NPM 依赖并通过 Cosmos UI index.html 中的 import maps 暴露(例如styled-components这类带运行时依赖的库就需要映射),且 import maps 应针对已安装的 NPM 模块自动生成;静态导出时则需将 node_modules 一并导出、让 import maps 指向新位置。
文档给出的当前折中方案是:将共享依赖挂到全局window命名空间(例如利用 Webpack 的externals配置)来构建 UI 插件。这样打包的插件,待后续加入正式支持后可以轻松重新发布为 ESM。
ESM Cosmos UI:象征意义大于实际
以 ESM 形式供给 Cosmos UI 本身,文档直言这"目前主要是象征性的"——它既不能给用户带来实际帮助,也不是支持 ESM UI 插件的前提;相反,把 Cosmos UI 及其全部 NPM 依赖都以 ESM 供给,很可能降低运行时性能并复杂化静态导出。该条目仅作为路线图上的进度追踪而保留。从仓库现状看,Cosmos UI 依然以预构建 bundle 的形式提供(expressApp.ts 中对外暴露的是playground.bundle.js),与文档描述一致。
技术债与维护现状
tech-debt.md 记录了维护者当前承认的三类技术债,对想参与贡献的开发者极具参考价值。
固定版本依赖:react-error-overlay@6.0.9
项目整体保持依赖更新,但有一个例外:react-error-overlay@6.0.9必须固定版本(作为react-cosmos-plugin-webpack的依赖)。原因是 6.0.10+ 在 CRA 的 webpack-with-DefinePlugin 配置之外会损坏(bundle 中存在未加防护的process.env.NODE_ENV引用,对应 CRA 的回归问题);6.1.0(2025 年 2 月)只是重新发布,并未修复代码。该固定成本很低:这个包零运行时依赖、只包含一个约 360KB 的 bundle 文件。其现实的退出路径是彻底替换它——之所以暂时保留,是因为它提供了一个体验良好的默认错误覆盖层(支持"点击在编辑器中打开"),且只加重 webpack 插件的负担,而不影响 Cosmos 本身。
代码改进:启用 noUncheckedIndexedAccess
在 TypeScript 中启用noUncheckedIndexedAccess可以全面提升所有 Cosmos 包的质量,但需要先研究在"映射与缩减数组"场景下处理"映射键可能为 undefined"的常见方式——因为 TypeScript 无法推断映射后的键不为 undefined。维护者同时表示不希望为此堆砌不必要的检查,以免降低代码简洁性。对贡献者而言,这是参与代码质量改进的明确切入点。
NPM optionalDependencies
在从 Yarn 1.x 迁移到最新 NPM 时,为了在 Linux 与 Windows 上让 GitHub Actions 配合带版本的package-lock.json正常工作,需要为examples/vite/package.json与docs/package.json添加一些平台特定的 optional dependencies。这些依赖不会进入任何已发布的包,因此不影响用户,只是一个轻微的不便。
继续阅读
本主题对应的原始文档与关联资源均可在当前仓库中直接查看:
- dev.md 与三个子文档:architecture.md、esm.md、tech-debt.md;
- 插件体系:server-plugins.md、ui-plugins.mdx、fixture-plugins.md 以及 plugins.mdx;
- 配置说明:cosmos-config.mdx(对应架构文档中"读取 Cosmos 配置"的职责);
- 源码佐证:Server 启动链路见 startDevServer.ts,消息协议见 rendererConnect.ts,核心 Server Plugins 清单见 corePlugins/index.ts;
- 可运行的示例项目:examples/vite、examples/webpack 与 examples/todo。
- 开发工具
- 前端
- 测试
【免费下载链接】react-cosmos
Sandbox for developing and testing UI components in isolation
相关推荐
Material UI 2024 年度盘点:v6 发布、React 19 支持与通往 v7/ESM 的路线图
Material UI 2024 年度盘点:v6 发布、React 19 支持与通往 v7/ESM 的路线图 本文以 2024 12 11 发布于仓库官博的 m
前端UI组件设计系统终极Windows PS3手柄兼容方案:DsHidMini完全使用指南
终极Windows PS3手柄兼容方案:DsHidMini完全使用指南 还在为Windows系统无法识别你的PlayStation 3手柄而烦恼吗?DsHidM
开发工具前端测试终极GDScript编程学习指南:从零开始快速掌握Godot游戏开发
终极GDScript编程学习指南:从零开始快速掌握Godot游戏开发 想要学习游戏开发但不知从何入手?GDScript作为Godot引擎的官方脚本语言,以其简洁
开发工具前端测试
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考