diff --git a/package-lock.json b/package-lock.json index df9e8f4b..56523733 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,6 +57,7 @@ "unplugin-vue-markdown": "^32.0.0", "uqr": "^0.1.3", "vite": "^8.0.16", + "vue-component-type-helpers": "^3.3.6", "vue-router": "^5.0.2" }, "bin": { @@ -8596,10 +8597,9 @@ } }, "node_modules/vue-component-type-helpers": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.3.tgz", - "integrity": "sha512-x4nsFpy5Pe8fqPzp/5vkTPeTTDBpAx4WVtV47Ejt0+2FQrq4pRRsJs7JmYRqMFzTu/LW+pCWEjQ3YVCkPV7f9g==", - "dev": true, + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.6.tgz", + "integrity": "sha512-FkljacAwJ9BUoSUdpFe3VDy0sGigNlTH9+2zcXUWmZOjN8swiCkl3t48wOJun0OsUd2cEIda1l04tsxMiKIIrQ==", "license": "MIT" }, "node_modules/vue-router": { diff --git a/package.json b/package.json index a844e28b..d1e30904 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "unplugin-vue-markdown": "^32.0.0", "uqr": "^0.1.3", "vite": "^8.0.16", + "vue-component-type-helpers": "^3.3.6", "vue-router": "^5.0.2" }, "peerDependencies": { diff --git a/src/render/index.ts b/src/render/index.ts index 46738906..f86de9bd 100644 --- a/src/render/index.ts +++ b/src/render/index.ts @@ -5,6 +5,7 @@ import { createPlaintext } from '../plaintext.ts' import { stripForHtml, stripForPlaintext } from '../utils/output-markers.ts' import defu from 'defu' import type { Component } from 'vue' +import type { ComponentProps } from 'vue-component-type-helpers' import type { MaizzleConfig } from '../types/index.ts' import { createRenderer } from './createRenderer.ts' import { getActiveRenderer } from './active.ts' @@ -19,13 +20,26 @@ export interface RenderResult { plaintext?: string } +/** + * Render a Vue component to a fully-transformed HTML string. + */ +export async function render( + component: TComponent, + config?: Partial>>, +): Promise; + /** * Render a Vue SFC email template to a fully-transformed HTML string. * Accepts a file path or a raw SFC source string. */ export async function render( - template: string | Component, + template: string, config?: Partial, +): Promise; + +export async function render( + template: string | TComponent, + config?: Partial>>, ): Promise { if (template == null) { throw new Error( diff --git a/src/tests/render/props.test.ts b/src/tests/render/props.test.ts index 8f05ff2b..094a8fcc 100644 --- a/src/tests/render/props.test.ts +++ b/src/tests/render/props.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { defineComponent, h, type PropType } from 'vue' import { rmSync } from 'node:fs' import { render } from '../../render/index.ts' import { createTempProject } from './_helpers.ts' @@ -32,6 +33,30 @@ describe('render props', () => { expect(result.html).toContain('Hello Ava') }) + it('passes props to strongly typed component', async () => { + const component = defineComponent({ + props: { + title: { + type: String as PropType<'Mr' | 'Ms' | 'Mx'>, + required: true, + }, + name: { + type: String as PropType, + required: true, + }, + }, + setup(props) { + return () => h('div', `Hello ${props.title} ${props.name}`) + } + }) + + const result = await render(component, { + props: { title: 'Mx', name: 'Ava' }, + }) + + expect(result.html).toContain('Hello Mx Ava') + }) + it('does not leak props into useConfig() or the returned config', async () => { const result = await render(`