ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Astro Container API + Vitest:对组件、React Island 与动态路由进行单元测试的官方示例详解

Astro Container API + Vitest:对组件、React Island 与动态路由进行单元测试的官方示例详解 Astro Container API Vitest对组件、React Island 与动态路由进行单元测试的官方示例详解【免费下载链接】astroThe web framework for content-driven websites. ⭐️ Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/as/astroAstro 官方仓库内置了一个专门演示「用 Vitest 测试 Astro 项目」的示例工程examples/container-with-vitest其核心思路是利用 Container API 在测试运行时直接渲染.astro组件而不需要启动完整的开发服务器或构建产物。读完本文你将掌握vitest.config.ts中基于getViteConfig的环境搭建方式、renderToString的slots/props/params/request等渲染选项以及如何在纯 Node 环境中测试带 React client 指令的组件。一、示例工程定位、创建方式与目录结构官方 README 对该示例的定位是一句话用 Vitest Container API 来测试 Astro 组件见 examples/container-with-vitest/README.md。它提供了标准的模板创建命令npm create astrolatest -- --template container-with-vitest该示例工程在 monorepo 中的文件布局如下三个测试文件分别对应三类典型测试场景test/Card.test.ts —— 纯 Astro 组件插槽slot与嵌套组件渲染test/ReactWrapper.test.ts —— 客户端框架组件React的 SSR 渲染与 hydration 标记test/[locale].test.ts —— 动态路由页面模拟路由参数与Requestvitest.config.ts —— 关键配置让 Vitest 复用 Astro 的 Vite 管线src/components/Card.astro、src/components/CounterLight.astro、src/components/ReactWrapper.astro、src/pages/[locale].astro —— 被测试的组件与页面依赖与运行环境见 package.jsonNode 版本要求node: 22.12.0测试脚本test: vitest run一次性运行非 watch 模式依赖astro、vitest、astrojs/react、react/react-domastro.config.ts 中注册了 React 集成integrations: [react()]tsconfig.json 则继承astro/tsconfigs/strict并把**/*纳入类型检查范围——测试文件本身也受严格类型约束。二、测试环境搭建的关键用getViteConfig打通.astro模块整个示例最重要的一行配置在 vitest.config.ts/// reference typesvitest/config / import { getViteConfig } from astro/config; export default getViteConfig({ test: { /* for example, use global to avoid globals imports (describe, test, expect): */ // globals: true, }, });它把「Vitest 的test配置」交给astro/config导出的getViteConfig去包裹。为什么这一步必不可少因为.astro文件不是 Vite 默认认识的模块必须经过 Astro 的 Vite 插件astrojs/vite-plugin-astro编译管线才能被 import。getViteConfig的作用就是从 Astro 配置出发生成一份完整解析后的 Vite 配置再与用户传入的配置这里包含test段合并使 Vitest 启动的 Vite 环境天然具备编译.astro的能力。从 packages/astro/src/config/index.ts 的实现可以看到getViteConfig的实际工作过程返回一个异步的 Vite 配置函数从中解构出mode和command由于 Vite 的command是serve | build而 Astro 用dev | build内部先做了serve → dev的映射动态导入vite、resolveConfig/createSettings、createVite、集成 hooks 等模块——注释明确说明「用动态 import 避免在不使用时引入依赖」通过resolveConfigrunHookConfigSetup解析 Astro 配置即读取项目中的astro.config.ts本例中的 React 集成就是这样被激活的用createRoutesList扫描路由再用createVite生成 Astro 的 Vite 配置最后mergeConfig(viteConfig, userViteConfig)把用户传入的test配置合并进去返回。也就是说Vitest 拿到的是一份「Astro 完整管线 你的 test 选项」的配置test/*.test.ts里可以直接import Card from ../src/components/Card.astro。三、Container API 基础create()与renderToString()三个测试文件都从astro/container引入容器import { experimental_AstroContainer as AstroContainer } from astro/container;其实现位于 packages/astro/src/container/index.ts。从源码结构看容器的核心方法有两个AstroContainer.create(containerOptions)创建容器实例。create()的参数见源码 L363-L370支持streaming、manifest、renderers、resolve、astroConfig等选项其中renderers用于注入客户端框架React、Vue 等的容器渲染器manifest则允许复用真实应用的渲染清单。renderToString(component, options)把组件渲染成 HTML 字符串。源码实现L535-L545非常简洁——先把slots中的字符串值统一标记为「slot 字符串」然后委托给renderToResponse并取response.text()。而renderToResponseL566-L598揭示了容器的本质它把一次「组件渲染」模拟成一次真实的请求处理——构造默认Request缺省为https://example.com/、插入RouteData路由条目、创建FetchState然后走handleMiddleware(state, handlePages)的标准页面处理链。这也解释了为什么渲染选项里可以传request和params。ContainerRenderOptions的主要字段有选项作用示例中的使用slots以字符串或已渲染 HTML 填充组件的slotCard的默认插槽props传入组件 frontmatter 解构的Astro.propsCounterLight的countparams模拟动态路由参数Astro.params[locale]页面的locale: enrequest自定义Request控制 URL 与请求上下文new Request(http://example.com/en)locals注入Astro.locals示例未使用routeTypepage或endpoint默认page示例未使用api.ts为 endpoint 页面partial是否按部分渲染处理默认true示例未使用streaming/renderers/manifest/resolve/astroConfigcreate()阶段的容器级选项renderers用于 React 测试四、实战场景一测试插槽与嵌套 Astro 组件Card.astro 是一个带默认插槽的组件CounterLight.astro 是接收countprop 的组件。对应的 Card.test.ts 覆盖了两类用例import { experimental_AstroContainer as AstroContainer } from astro/container; import { expect, test } from vitest; import Card from ../src/components/Card.astro; import CounterLight from ../src/components/CounterLight.astro; test(Card with slots, async () { const container await AstroContainer.create(); const result await container.renderToString(Card, { slots: { default: Card content, }, }); expect(result).toContain(This is a card); expect(result).toContain(Card content); }); test(Card with nested CounterLight, async () { const container await AstroContainer.create(); const counterLight await container.renderToString(CounterLight, { props: { count: 1 } }); const result await container.renderToString(Card, { slots: { default: counterLight, }, }); expect(result).toContain(This is a card); expect(result).toContain(counterLight); });两个值得注意的写法插槽可以直接传字符串。第一个用例把Card content作为默认插槽内容传入断言渲染结果同时包含组件自身的This is a card和插槽内容——这正是renderToString中markAllSlotsAsSlotString所处理的场景。可以「先渲染子组件、再把 HTML 作为插槽传给父组件」。第二个用例先单独渲染CounterLight通过props: { count: 1 }传参再把得到的 HTML 字符串塞进Card的插槽验证嵌套组合的完整性。这为测试「组件组合关系」提供了不依赖路由的手段。五、实战场景二测试 React 组件与 client 指令的 hydration 标记当组件树里包含客户端框架组件如 Counter.jsx 配合Counter initialCount{5} client:load /使用的 ReactWrapper.astro时需要向容器注入 React 的容器渲染器。ReactWrapper.test.ts 的完整写法是import { loadRenderers } from astro:container; import { getContainerRenderer } from astrojs/react/container-renderer; import { experimental_AstroContainer as AstroContainer } from astro/container; import { expect, test } from vitest; import ReactWrapper from ../src/components/ReactWrapper.astro; const renderers await loadRenderers([getContainerRenderer()]); const container await AstroContainer.create({ renderers, }); test(ReactWrapper with react renderer, async () { const result await container.renderToString(ReactWrapper); expect(result).toContain(Counter); expect(result).toContain(Count: !-- --5); expect(result, Includes client hydration reference).toContain( renderer-urlastrojs/react/client.js, ); });三个要点loadRenderers来自虚拟模块astro:containergetContainerRenderer来自astrojs/react/container-renderer——即 React 集成专门暴露给容器环境的渲染器入口模块顶层就执行了await loadRenderers顶层 await容器在模块加载时一次性创建好所有用例共享断言验证了 SSR 输出的两个特征Count: !-- --5是 React SSR 在插值前后注入注释标记的典型形态说明 React 确实走了服务端渲染路径renderer-urlastrojs/react/client.js则证明client:load指令生成了客户端 hydration 所需的 island 元数据。这两条断言共同确认了「SSR HTML 正确 客户端加载引用正确」。六、实战场景三测试动态路由页面params 与 request​[locale].astro 是一个带getStaticPaths()的动态路由页面frontmatter 中从Astro.params解构出locale并渲染为pLocale: {locale}/p。对应的 [locale].test.ts 展示了如何为动态路由构造渲染上下文import { experimental_AstroContainer as AstroContainer } from astro/container; import { expect, test } from vitest; import Locale from ../src/pages/[locale].astro; test(Dynamic route, async () { const container await AstroContainer.create(); // ts-ignore const result await container.renderToString(Locale, { params: { locale: en, }, request: new Request(http://example.com/en), }); expect(result).toContain(Locale: en); });要点解析params对应Astro.params不传params: { locale: en }页面里的{locale}会是undefinedrequest对应 URL 上下文容器会用它解析路径源码中renderToResponse以options?.request ?? new Request(https://example.com/)取 URL测试里传入http://example.com/en与路由参数保持一致// ts-ignore的原因params的静态类型由该页面的getStaticPaths推导而测试侧直接构造对象字面量时类型系统无法确认其合法性示例工程选择用ts-ignore绕过——这是在 strict 模式下使用 Container API 测试动态路由时的一个现实细节。七、小结这套测试方案的边界与适用前提适用前提Node22.12.0示例package.json的engines声明并在vitest.config.ts中通过getViteConfig复用 Astro 的 Vite 管线能测什么.astro组件的静态渲染输出、插槽组合、prop 传递、动态路由参数、客户端框架组件的 SSR HTML 与 hydration 元数据甚至 endpointrouteType: endpoint容器做了什么从 packages/astro/src/container/index.ts 的renderToResponse实现看容器把组件渲染包装进真实的路由/中间件处理链handleMiddleware(state, handlePages)因此渲染结果与运行时行为高度一致与项目自身单测的区分Astro monorepo 内部包如packages/astro的单元测试约定使用node:test规则见 reference/unit-testing.md本示例面向的是用户侧项目——在自己的 Astro 站点里用 Vitest 测试组件与页面。参考入口示例 READMEexamples/container-with-vitest/README.md、容器 API 实现packages/astro/src/container/index.ts、getViteConfig实现packages/astro/src/config/index.ts、容器内部单测packages/astro/test/units/render/container.test.ts。【免费下载链接】astroThe web framework for content-driven websites. ⭐️ Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/as/astro创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表