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') + } + }) })