// Test helper: compiles app/components/Menu/NavItem.vue with Vue's SFC // compiler and renders it with the server renderer. NuxtLink is stubbed as // a plain anchor (it renders one for a path in the app too), the component // resolves itself for the recursion, and the returned HTML has no hydration // comments. import { readFileSync, rmSync, writeFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' import { compileScript, parse } from 'vue/compiler-sfc' import { createSSRApp, defineComponent, h, type Component } from 'vue' import { renderToString } from 'vue/server-renderer' import type { MenuEntry } from '../app/utils/types.ts' const here = dirname(fileURLToPath(import.meta.url)) const source = join(here, '..', 'app', 'components', 'Menu', 'NavItem.vue') let compiled: Promise | null = null async function compileNavItem(): Promise { const { descriptor } = parse(readFileSync(source, 'utf8'), { filename: source }) const script = compileScript(descriptor, { id: 'navitem', inlineTemplate: true, genDefaultAs: '__sfc__' }) // The compiled module keeps the relative imports, so it is written next to // the source, imported (Node strips the types) and removed. const out = join(dirname(source), `.navitem-${process.pid}.compiled.ts`) writeFileSync(out, `${script.content}\nexport default __sfc__\n`) try { const mod = (await import(pathToFileURL(out).href)) as { default: Component } return mod.default } finally { rmSync(out, { force: true }) } } const NuxtLink = defineComponent({ props: { to: { type: String, required: true } }, setup(props, { slots, attrs }) { return () => h('a', { href: props.to, ...attrs }, slots.default?.()) }, }) export async function renderNavItem(opts: { entry: MenuEntry; routePath: string; open?: boolean }): Promise { compiled ??= compileNavItem() const NavItem = await compiled const app = createSSRApp({ render: () => h(NavItem, { entry: opts.entry, depth: 0, idBase: 'm-0', open: opts.open ?? false, routePath: opts.routePath, host: 'site.test', submenuLabel: 'Show submenu: :title', }), }) app.component('NuxtLink', NuxtLink) app.component('MenuNavItem', NavItem) // Hydration markers (, ) are dropped for readable assertions. return (await renderToString(app)).replace(//g, '') }