ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Remix UI 演示应用全解析:`packages/ui/demo` 的启动、Demo 发现机制与自定义编写指南

Remix UI 演示应用全解析:`packages/ui/demo` 的启动、Demo 发现机制与自定义编写指南 Remix UI 演示应用全解析packages/ui/demo的启动、Demo 发现机制与自定义编写指南【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remixpackages/ui/demo是remix-run/ui包Remix 的 UI 运行时、无头原语与样式化组件集合自带的一个小型 Remix 应用用于集中浏览和运行 UI 原语与样式化组件的各类演示。本文基于该目录的 README 展开结合其源码实现完整讲解如何启动演示应用、理解其每次请求动态扫描 Demo 文件的发现机制、掌握 Demo 元数据注释语法以及如何编写并注册属于自己的 Demo——阅读后你既能立刻跑起这个演示站点也能为任意组件贡献新的交互示例。一、这个 Demo 应用是什么从目录结构看packages/ui/demo 本身是一个完整的 Remix 应用它拥有自己的 package.json名为ui-demo私有包、server.ts基于remix/node-fetch-server的 HTTP 服务器、config/下的路由与渲染配置以及app/demo-runner/下的 Demo 发现与展示逻辑。它承担两项职责组件展示把remix-run/ui提供的 Accordion、Button、Menu、Tabs 等第一方组件模块的示例统一汇总成一个可浏览的索引页开发调试让 UI 包的维护者与使用者无需搭建完整业务应用就能在隔离环境中逐项验证组件的交互行为、样式与可访问性。从源码的组件模块清单见 view.tsx可以看到内置组件演示覆盖了accordion、anchor、breadcrumbs、button、checkbox、combobox、input、listbox、menu、popover、radio、select、tabs、toggle共 14 个模块除此之外还有animation、runtime等更宽泛的交互与运行时演示以及demo/cases/下的独立游乐场示例如draggable拖拽、drummer节奏合成器。二、启动演示应用一条命令两个进程README 给出的启动方式非常简洁pnpm -C packages/ui/demo dev然后打开http://localhost:44100即可浏览索引页。从 package.json 可以看到dev脚本的完整分解dev: pnpm run --filter \.\ --parallel \/^dev:/\, dev:server: NODE_ENVdevelopment node --import remix/node-tsx server.ts, dev:browser: node --import remix/node-tsx scripts/build-browser.ts --watch也就是说pnpm -C packages/ui/demo dev会通过--parallel并行启动两个子进程dev:server以NODE_ENVdevelopment启动 Remix 服务端。入口 server.ts 使用createRequestListener把 Node HTTP 请求转交给router.fetch(request)端口通过process.env.PORT覆盖默认44100与 README 一致。同时注册了SIGINT/SIGTERM优雅退出逻辑。dev:browser以--watch模式运行scripts/build-browser.ts负责把客户端入口与 Demo 模块打包成浏览器可用的产物如/assets/entry.js。这种服务端渲染 浏览器产物构建分离的架构正是 Remix 全栈框架在纯 Node 环境下的典型用法。服务端渲染 HTML 骨架客户端脚本接管交互二者通过/assets/*下的产物衔接。启动时由于 router.tsx 在开发环境注入了logger()中间件终端会打印每个请求的日志staticFiles(./public, { cacheControl: no-store, etag: false, index: false, lastModified: false })则用于静态资源服务且显式禁用了各种缓存与协商策略保证演示资源总是最新。三、Demo 如何被发现基于文件名的动态扫描README 只给了三句话概括发现机制但其背后的实现相当精巧。核心逻辑全部位于 discovery.ts。3.1 扫描根与匹配规则const DEMO_DIRECTORY path.resolve(url.fileURLToPath(new URL(../.., import.meta.url))) const UI_DIRECTORY path.resolve(DEMO_DIRECTORY, ..) const DEMO_ROOTS [path.join(DEMO_DIRECTORY, cases), path.join(UI_DIRECTORY, src)] const DEMO_FILE_REGEX /\.demo\.(tsx|ts)$/discoverDemoFiles()会在每次请求时README 强调 on every request递归遍历两个 Demo 根目录packages/ui/demo/cases—— 应用内的独立游乐场示例packages/ui/src—— UI 包源码树中所有组件模块目录。凡是文件名匹配*.demo.ts或*.demo.tsx的文件都会被收集walkDemoFiles 会跳过node_modules、build、public、点开头的隐藏目录以及*.bundled.js。这意味着新增一个组件示例只需在对应模块目录放一个xxx.demo.tsx文件无需修改任何注册表或配置文件——这正是该演示应用零配置可扩展的设计核心。3.2 相对路径与 URL 映射每个 Demo 的访问地址规则是/demo/*filename其中*filename是 Demo 文件相对其演示根目录的路径。具体换算在 getRelativeDemoPath以UI_DIRECTORY即packages/ui为基准计算相对路径若路径以demo/cases/开头则去掉demo/前缀最终得到如cases/draggable/draggable.demo.tsx这样的相对路径。于是一个源码文件最终会被映射为三类 URL字段生成方式示例href/demo/${relativePath}/demo/cases/draggable/draggable.demo.tsxassetHref/assets/demos/${relativePath 去掉 .demo 后缀换为 .js}?v版本号/assets/demos/cases/draggable/draggable.js?v...importHref基于绝对路径的file://URLfile:///.../draggable.demo.tsx其中assetHref上的?v查询参数取自文件的mtimeMs.toString(36)createFileVersion即修改时间的 36 进制表示——文件一变URL 就变天然完成缓存失效无需手动清缓存。3.3 排序、标题与安全校验收集到的 Demo 会按目录 → 元数据order→ 标题三级排序compareDemoFiles。标题生成有优先级getDemoTitle若文件位于src/module/module.demo.tsx模块总览型优先用模块名人性化后的名称如accordion→Accordion否则使用 JSDoc 元数据中的name最后兜底使用文件名的人性化处理去掉.demo后缀按-/_分词并首字母大写拼接。在对外暴露 URL 之前normalizeFilename 还做了安全校验拒绝绝对路径、./..路径穿越../、/../、以及不匹配*.demo.(ts|tsx)的路径从源头防止通过 URL 参数读取任意文件。从源码结构看findDemoFile()在校验通过后才会在discoverDemoFiles()结果中做精确匹配找不到则返回undefined由路由层响应 404。四、路由与渲染链路从请求到 HTML 流4.1 路由定义路由只有两条routes.tsconst demoRoutes { index: get(/), show: get(/demo/*filename), }/演示索引页/demo/*filename通配符捕获 Demo 的相对路径交给控制器。router.tsx 用createRouter({ middleware })组装中间件链并把这两条路由映射到app/demo-runner/controller.tsx导出的控制器。4.2 控制器索引与单个 Democontroller.tsx 定义了index与show两个 actionindex调用discoverDemoFiles()得到全部 Demo渲染DemoIndexDocument响应头设置Cache-Control: no-store因为扫描发生在每次请求时不能缓存show通过context.params.filename查找 Demo找不到就返回 404 纯文本找到后执行两步关键操作let DemoComponent clientEntry(${demo.assetHref}#default, await loadDemoModule(demo)) return render(context, DemoDocument DemoComponent{DemoComponent} demo{demo} /, { ... })loadDemoModule见 discovery.ts用带版本号的动态import()加载 Demo 模块并要求模块必须默认导出一个组件函数否则抛出明确错误。clientEntry则将服务端组件与浏览器端可用的资源地址绑定实现服务端渲染与客户端水合的双端一致性。4.3 服务端渲染HTML 流与 Frame 解析render.tsx 是渲染的核心renderToStream(node, {...})把组件树渲染为 HTML 流再通过createHtmlResponse包装成响应。这里值得注意resolveFrame回调——它负责解析组件中内嵌的 frame嵌套页面片段构造带Accept: text/html与X-Remix-Frame: true头的请求转发给本应用路由器并跟随最多 10 次重定向followFrameRedirects最终把 frame 的 HTML 文本嵌入页面。这正是 Remix 服务端组合多个页面片段能力的落地实现允许一个 Demo 页面内嵌其他路由的实时渲染结果。4.4 视图层索引分组与布局view.tsx 中两个文档组件DemoIndexDocument索引页标题栏显示{demos.length} demo files found across the UI package.并把所有 Demo 分为两大节Built-in components内置组件来自src/且模块在 14 个组件模块白名单内的 Demo按模块分组展示General demos通用演示其余全部cases/下的游乐场、animation、runtime等同样按模块分组。 分组后按模块名排序渲染成链接列表。DemoDocument单个 Demo 页面根据元数据layout决定舞台样式默认铺满全屏layout center时使用place-items: center的网格布局与color-mix背景把 Demo 内容居中呈现在画布上centerDemoStageCss。所有样式均通过remix/ui的css({...})与mix属性组织页面head会引入/assets/entry.js以激活客户端交互。五、Demo 元数据注释控制标题、排序与布局在扫描文件内容时readDemoMetadata 会解析文件顶部首个 JSDoc 块/** ... */提取四个可选标签标签类型作用namestring自定义 Demo 标题优先级高于文件名人性化结果低于模块总览标题descriptionstring描述当前在索引页与 Demo 数据结构中携带layoutcenter目前唯一支持的布局值设置为center时居中展示 Demo 内容ordernumber分组内的排序权重数字越小越靠前缺省按Number.MAX_SAFE_INTEGER处理以 accordion.demo.tsx 为例一个标准的总览 Demo 会这样声明元数据/** * name Accordion Overview * description A single-open disclosure list that keeps settings, billing, or notification rules in one calm section. * layout center * order 1 */ export default function Example() { return () ( Accordion defaultValueaccount {/* ... */} /Accordion ) }注意layout的解析是白名单式的isDemoLayout只有center会被接受其余值静默忽略避免非法值破坏布局。order则要求是有限数值Number.isFinite否则忽略。六、动手写一个自己的 Demo结合上述机制编写 Demo 的完整步骤是放置文件在packages/ui/src/module/或packages/ui/demo/cases/name/下新建xxx.demo.tsx默认导出组件函数模块必须export default function (handle) { return () (...) }即默认导出返回一个渲染函数的组件工厂loadDemoModule会强制校验这一点可选声明 JSDoc 元数据按上表填写name、description、layout、order保存即生效无需重启——索引页每次请求都会重新扫描页面刷新后新 Demo 立即出现在对应分组中而?v版本号机制确保浏览器不会因缓存看不到更新。参考 draggable.demo.tsx 的完整形态它通过name/description声明元数据在组件内部直接使用mix{[draggable(true)]}应用自定义 mixin并配以内联样式呈现可拖拽方块。这个文件同时展示了普通 tsx 组件 拖拽 mixin这类通用演示的写法。七、静态预渲染与发布除了开发模式该应用还内置了静态站点生成能力。scripts/prerender.ts 是一个基于路由器内建的爬虫从/出发递归router.fetch每个 URL把 HTML 输出为outputDir/path/index.html并顺着页面中的a/link链接继续爬取跳过外链与relpreload/prefetch剥离锚点片段。它还会先把public/目录整体拷贝到输出目录确保未被爬虫发现的静态资源如 sourcemap、字体也能随站点发布。404 的演示内链不会导致构建失败只会打印告警跳过。对应的 package scriptsprerender: pnpm run build:browser node --import remix/node-tsx scripts/prerender.ts, prerender:serve: npx http-server -p 3000 build/site, start: pnpm run build:browser node --import remix/node-tsx server.tsprerender先构建浏览器产物再生成build/site静态目录prerender:serve用http-server在 3000 端口预览静态产物start构建浏览器产物后以服务端渲染模式直接运行。开发模式dev、静态托管prerender prerender:serve、服务端渲染start三种形态覆盖了从本地调试到发布的完整链路。八、相关源码导航如果想进一步研究以下文件是理解该演示应用的关键入口packages/ui/demo/README.md —— 官方说明本文主体依据packages/ui/demo/app/demo-runner/discovery.ts —— Demo 扫描、元数据解析、URL 映射与安全校验packages/ui/demo/app/demo-runner/controller.tsx —— 索引与单 Demo 的请求处理packages/ui/demo/app/demo-runner/view.tsx —— 索引页分组与 Demo 舞台布局packages/ui/demo/config/routes.ts、router.tsx、render.tsx —— 路由、中间件与流式渲染packages/ui/demo/scripts/prerender.ts —— 静态站点爬虫packages/ui/src/accordion/accordion.demo.tsx 与 packages/ui/demo/cases/draggable/draggable.demo.tsx —— 两类 Demo 文件的真实示例。总而言之packages/ui/demo演示了一个极具实用价值的工程模式以文件名为约定、以请求时扫描为机制、以 JSDoc 注释为元数据的零注册演示系统。无论你是想快速查看remix-run/ui各组件的实际效果还是打算为某个组件贡献示例掌握本文的启动命令、URL 映射规则与元数据语法就足以在这个框架内自由地浏览与扩展。【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表