diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
index b10a98d32cb..c692095d2f8 100644
--- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
@@ -140,7 +140,7 @@ export function render(_ctx) {
exports[`compile > directives > v-pre > basic 1`] = `
"import { template as _template } from 'vue';
-const t0 = _template("
{{ bar }}
", true)
+const t0 = _template(" {{ bar }}
", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
@@ -150,7 +150,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
-const t0 = _template(" {{ bar }}
")
+const t0 = _template(" {{ bar }}
")
const t1 = _template("
")
export function render(_ctx, $props, $emit, $attrs, $slots) {
diff --git a/packages/compiler-vapor/__tests__/compile.spec.ts b/packages/compiler-vapor/__tests__/compile.spec.ts
index 178021d13dd..f134bd23bf6 100644
--- a/packages/compiler-vapor/__tests__/compile.spec.ts
+++ b/packages/compiler-vapor/__tests__/compile.spec.ts
@@ -71,7 +71,7 @@ describe('compile', () => {
expect(code).toMatchSnapshot()
expect(code).contains(
- JSON.stringify(' {{ bar }}
'),
+ JSON.stringify(' {{ bar }}
'),
)
expect(code).not.contains('effect')
})
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
index 7aa56aa9c2f..ddeda802316 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
@@ -354,7 +354,7 @@ export function render(_ctx) {
exports[`compiler: element transform > props + children 1`] = `
"import { template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
", true)
export function render(_ctx) {
const n0 = t0()
@@ -399,7 +399,37 @@ export function render(_ctx) {
exports[`compiler: element transform > static props 1`] = `
"import { template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
+
+exports[`compiler: element transform > static props mixed quoting 1`] = `
+"import { template as _template } from 'vue';
+const t0 = _template("bar\\">
", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
+
+exports[`compiler: element transform > static props quoted 1`] = `
+"import { template as _template } from 'vue';
+const t0 = _template("bar\\">
", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
+
+exports[`compiler: element transform > static props unquoted 1`] = `
+"import { template as _template } from 'vue';
+const t0 = _template("
", true)
export function render(_ctx) {
const n0 = t0()
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
index 4ea0db55fe5..60fdfd89435 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
@@ -633,7 +633,7 @@ export function render(_ctx) {
exports[`compiler v-bind > with constant value 1`] = `
"import { setProp as _setProp, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
index 5ef064974c0..40199b451f8 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
@@ -116,7 +116,7 @@ export function render(_ctx) {
exports[`compiler: vModel transform > should support input (checkbox) 1`] = `
"import { applyCheckboxModel as _applyCheckboxModel, template as _template } from 'vue';
-const t0 = _template(" ", true)
+const t0 = _template(" ", true)
export function render(_ctx) {
const n0 = t0()
@@ -138,7 +138,7 @@ export function render(_ctx) {
exports[`compiler: vModel transform > should support input (radio) 1`] = `
"import { applyRadioModel as _applyRadioModel, template as _template } from 'vue';
-const t0 = _template(" ", true)
+const t0 = _template(" ", true)
export function render(_ctx) {
const n0 = t0()
@@ -149,7 +149,7 @@ export function render(_ctx) {
exports[`compiler: vModel transform > should support input (text) 1`] = `
"import { applyTextModel as _applyTextModel, template as _template } from 'vue';
-const t0 = _template(" ", true)
+const t0 = _template(" ", true)
export function render(_ctx) {
const n0 = t0()
diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
index a693db4ad39..44413e8088d 100644
--- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
@@ -576,7 +576,45 @@ describe('compiler: element transform', () => {
`
`,
)
- const template = '
'
+ const template = '
'
+ expect(code).toMatchSnapshot()
+ expect(code).contains(JSON.stringify(template))
+ expect(ir.template).toMatchObject([template])
+ expect(ir.block.effect).lengthOf(0)
+ })
+
+ test('static props unquoted', () => {
+ const { code, ir } = compileWithElementTransform(
+ `
`,
+ )
+
+ const template = '
'
+ expect(code).toMatchSnapshot()
+ expect(code).contains(JSON.stringify(template))
+ expect(ir.template).toMatchObject([template])
+ expect(ir.block.effect).lengthOf(0)
+ })
+
+ test('static props quoted', () => {
+ const { code, ir } = compileWithElementTransform(
+ `
`,
+ )
+
+ const template =
+ '
'
+ expect(code).toMatchSnapshot()
+ expect(code).contains(JSON.stringify(template))
+ expect(ir.template).toMatchObject([template])
+ expect(ir.block.effect).lengthOf(0)
+ })
+
+ test('static props mixed quoting', () => {
+ const { code, ir } = compileWithElementTransform(
+ `
`,
+ )
+
+ const template =
+ '
'
expect(code).toMatchSnapshot()
expect(code).contains(JSON.stringify(template))
expect(ir.template).toMatchObject([template])
@@ -588,7 +626,7 @@ describe('compiler: element transform', () => {
`
`,
)
- const template = '
'
+ const template = '
'
expect(code).toMatchSnapshot()
expect(code).contains(JSON.stringify(template))
expect(ir.template).toMatchObject([template])
diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts
index 05153e729af..8cc71a82136 100644
--- a/packages/compiler-vapor/src/transforms/transformElement.ts
+++ b/packages/compiler-vapor/src/transforms/transformElement.ts
@@ -221,11 +221,30 @@ function transformNativeElement(
getEffectIndex,
)
} else {
+ let needsQuoting = false
+
for (const prop of propsResult[1]) {
const { key, values } = prop
if (key.isStatic && values.length === 1 && values[0].isStatic) {
- template += ` ${key.content}`
- if (values[0].content) template += `="${values[0].content}"`
+ const value = values[0].content
+
+ if (!needsQuoting) template += ` `
+ template += key.content
+
+ if (value) {
+ // https://html.spec.whatwg.org/multipage/introduction.html#intro-early-example
+ needsQuoting = /[\s>]|^["'=]/.test(value)
+
+ if (needsQuoting) {
+ const encoded = value.replace(/"/g, '"')
+
+ template += `="${encoded}"`
+ } else {
+ template += `=${value}`
+ }
+ } else {
+ needsQuoting = false
+ }
} else {
dynamicProps.push(key.content)
context.registerEffect(
diff --git a/packages/runtime-vapor/__tests__/dom/template.spec.ts b/packages/runtime-vapor/__tests__/dom/template.spec.ts
index 85de30987a5..b10d7abfd9f 100644
--- a/packages/runtime-vapor/__tests__/dom/template.spec.ts
+++ b/packages/runtime-vapor/__tests__/dom/template.spec.ts
@@ -40,4 +40,25 @@ describe('api: template', () => {
expect(nthChild(root, 2)).toBe(root.childNodes[2])
expect(next(b)).toBe(root.childNodes[2])
})
+
+ test('attribute quote omission', () => {
+ {
+ const t = template('
')
+ const root = t() as HTMLElement
+
+ expect(root.attributes).toHaveLength(3)
+ expect(root.getAttribute('id')).toBe('foo')
+ expect(root.getAttribute('class')).toBe('bar')
+ expect(root.getAttribute('alt')).toBe('`<="foo')
+ }
+
+ {
+ const t = template('
')
+ const root = t() as HTMLElement
+
+ expect(root.attributes).toHaveLength(2)
+ expect(root.getAttribute('id')).toBe('foo>bar')
+ expect(root.getAttribute('class')).toBe('has whitespace')
+ }
+ })
})