Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/render/createRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export interface RenderedTemplate {
}

export interface Renderer {
render(input: string | Component, config: MaizzleConfig, opts?: { source?: string }): Promise<RenderedTemplate>
render(input: string | Component, config: MaizzleConfig, opts?: { source?: string; props?: Record<string, any> }): Promise<RenderedTemplate>
invalidate(filePath: string): Promise<void>
invalidateAll(): Promise<void>
close(): Promise<void>
Expand Down Expand Up @@ -412,7 +412,7 @@ export async function createRenderer(
const server = await createServer(finalConfig)

return {
async render(input: string | Component, config: MaizzleConfig, opts?: { source?: string }): Promise<RenderedTemplate> {
async render(input: string | Component, config: MaizzleConfig, opts?: { source?: string; props?: Record<string, any> }): Promise<RenderedTemplate> {
let component: Component
let configKey: InjectionKey<MaizzleConfig>
let contextKey: InjectionKey<RenderContext>
Expand Down Expand Up @@ -469,7 +469,7 @@ export async function createRenderer(
}

const head = createHead({ disableDefaults: true })
const app = createSSRApp(component)
const app = createSSRApp(component, opts?.props)
app.use(head)

// Register user Vue plugins, directives, and global properties
Expand Down
3 changes: 2 additions & 1 deletion src/render/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export async function render(
}

const resolvedConfig = resolveConfigObject(config)
const { props, ...templateConfig } = resolvedConfig

/**
* Reuse a renderer started by the Vite plugin when one is active.
Expand All @@ -59,7 +60,7 @@ export async function render(
&& ['.vue', '.md'].includes(extname(template))
&& !template.includes('\n')

const rendered = await renderer.render(isFile ? resolve(template) : template, resolvedConfig)
const rendered = await renderer.render(isFile ? resolve(template) : template, templateConfig, { props })
let html = rendered.html

const doctype = rendered.doctype ?? rendered.templateConfig.doctype ?? '<!DOCTYPE html>'
Expand Down
65 changes: 65 additions & 0 deletions src/tests/render/props.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { rmSync } from 'node:fs'
import { render } from '../../render/index.ts'
import { createTempProject } from './_helpers.ts'

describe('render props', () => {
let tempDir: string
const originalCwd = process.cwd()

beforeEach(() => {
tempDir = createTempProject()
process.chdir(tempDir)
})

afterEach(() => {
process.chdir(originalCwd)
rmSync(tempDir, { recursive: true, force: true })
})

it('passes props to the template via defineProps', async () => {
const result = await render(`
<script setup>
defineProps(['name'])
</script>
<template>
<div>Hello {{ name }}</div>
</template>
`, {
props: { name: 'Ava' },
})

expect(result.html).toContain('Hello Ava')
})

it('does not leak props into useConfig() or the returned config', async () => {
const result = await render(`
<script setup>
const config = useConfig()
</script>
<template>
<div>{{ config.props === undefined ? 'clean' : 'leaked' }}</div>
</template>
`, {
props: { name: 'Ava' },
})

expect(result.html).toContain('clean')
expect(result.config.props).toBeUndefined()
})

it('does not render a declared prop as a fallthrough attribute', async () => {
const result = await render(`
<script setup>
defineProps(['name'])
</script>
<template>
<div>{{ name }}</div>
</template>
`, {
props: { name: 'Ava' },
})

expect(result.html).not.toContain('name="Ava"')
})
})
12 changes: 12 additions & 0 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,18 @@ export interface MaizzleConfig {
* }
*/
vue?: VueConfig
/**
* Props passed to the template's root component when rendering
* programmatically. Map 1:1 to the template's `defineProps`. Not
* merged into `useConfig()`.
*
* Props not declared via `defineProps` fall through as HTML
* attributes on the root element, so declare every prop you pass.
*
* @example
* render('./welcome.vue', { props: { name: 'Ava', plan: 'Pro' } })
*/
props?: Record<string, any>
Comment on lines +747 to +758

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== config.ts excerpt ==\n'
sed -n '700,790p' src/types/config.ts

printf '\n== search props usages ==\n'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' '\bprops\b' src test tests . | head -n 200

Repository: maizzle/framework

Length of output: 27234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== find render/config handling ==\n'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' 'useConfig\(|config\.props|props:\s*\{' src

printf '\n== locate tests mentioning props ==\n'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' 'props' ./*.test.* src/**/*.test.* test/**/*.test.* tests/**/*.test.* 2>/dev/null || true

printf '\n== outline likely render entry files ==\n'
fd -a 'render.*(ts|js|mjs|cjs|vue)$' src .

Repository: maizzle/framework

Length of output: 22319


props should be treated as a breaking reserved key
MaizzleConfig already allows arbitrary top-level keys, so introducing a special props field changes the contract for anyone using config.props as custom data. Namespace it or call out the backward incompatibility in release notes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/config.ts` around lines 747 - 758, The new MaizzleConfig `props`
field is introducing a reserved top-level key that can conflict with existing
custom `config.props` usage. Update the config typing around the `props`
property in `src/types/config.ts` to treat this as a breaking change: either
namespace the programmatic render props field so it doesn’t collide with
arbitrary config keys, or explicitly mark/document `props` as reserved and
incompatible with prior custom usage. Use the `MaizzleConfig` type and its
`props` declaration to locate the change.


// Events

Expand Down
Loading