Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"pug": "^3.0.3",
"puppeteer": "~24.16.2",
"rimraf": "^6.0.1",
"rollup": "^4.46.4",
"rollup": "4.49.0",
"rollup-plugin-dts": "^6.2.3",
"rollup-plugin-esbuild": "^6.2.1",
"rollup-plugin-polyfill-node": "^0.13.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,86 @@ export function render(_ctx) {
}"
`;

exports[`compile > gen unique helper alias > should avoid conflicts with existing variable names 1`] = `
"import { child as _child2, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)

export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
const x0 = _child2(n0)
_renderEffect(() => _setText(x0, _toDisplayString(_ctx.foo)))
return n0
}"
`;

exports[`compile > gen unique node variables > should avoid binding conflicts for node vars (n*/x*) 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div> </div>")

export function render(_ctx, $props, $emit, $attrs, $slots) {
const n1 = t0()
const n3 = t0()
const x1 = _child(n1)
const x3 = _child(n3)
_renderEffect(() => {
const _foo = _ctx.foo
_setText(x1, _toDisplayString(_foo))
_setText(x3, _toDisplayString(_foo))
})
return [n1, n3]
}"
`;

exports[`compile > gen unique node variables > should bump old ref var (r*) on conflict 1`] = `
"import { createTemplateRefSetter as _createTemplateRefSetter, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")

export function render(_ctx, $props, $emit, $attrs, $slots) {
const _setTemplateRef = _createTemplateRefSetter()
const n1 = t0()
const n3 = t0()
const n4 = t0()
let r1
let r3
let r4
_renderEffect(() => {
const _bar = _ctx.bar
r1 = _setTemplateRef(n1, _bar, r1)
r3 = _setTemplateRef(n3, _bar, r3)
r4 = _setTemplateRef(n4, _bar, r4)
})
return [n1, n3, n4]
}"
`;

exports[`compile > gen unique node variables > should bump placeholder var (p*) on conflict 1`] = `
"import { child as _child, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div><div><div><span></span></div></div></div>", true)

export function render(_ctx, $props, $emit, $attrs, $slots) {
const n1 = t0()
const p1 = _child(n1)
const p3 = _child(p1)
const n0 = _child(p3)
_renderEffect(() => _setProp(n0, "id", _ctx.foo))
return n1
}"
`;

exports[`compile > gen unique node variables > should bump template var (t*) on conflict 1`] = `
"import { template as _template } from 'vue';
const t1 = _template("<div></div>")
const t3 = _template("<span></span>")
const t4 = _template("<p></p>")

export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t1()
const n1 = t3()
const n2 = t4()
return [n0, n1, n2]
}"
`;

exports[`compile > setInsertionState > next, child and nthChild should be above the setInsertionState 1`] = `
"import { resolveComponent as _resolveComponent, child as _child, next as _next, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, nthChild as _nthChild, createIf as _createIf, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div></div>")
Expand Down
95 changes: 95 additions & 0 deletions packages/compiler-vapor/__tests__/compile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,99 @@ describe('compile', () => {
expect(code).matchSnapshot()
})
})

describe('gen unique helper alias', () => {
test('should avoid conflicts with existing variable names', () => {
const code = compile(`<div>{{ foo }}</div>`, {
bindingMetadata: {
_child: BindingTypes.LITERAL_CONST,
_child1: BindingTypes.SETUP_REF,
},
})
expect(code).matchSnapshot()
expect(code).contains('child as _child2')
expect(code).contains('const x0 = _child2(n0)')
})
})

describe('gen unique node variables', () => {
test('should avoid binding conflicts for node vars (n*/x*)', () => {
const code = compile(`<div>{{ foo }}</div><div>{{ foo }}</div>`, {
bindingMetadata: {
n0: BindingTypes.SETUP_REACTIVE_CONST,
x0: BindingTypes.SETUP_MAYBE_REF,
n2: BindingTypes.SETUP_REACTIVE_CONST,
x2: BindingTypes.SETUP_MAYBE_REF,
},
})

expect(code).matchSnapshot()
expect(code).not.contains('const n0')
expect(code).not.contains('const x0')
expect(code).not.contains('const n2')
expect(code).not.contains('const x2')
expect(code).contains('const n1 = t0()')
expect(code).contains('const n3 = t0()')
expect(code).contains('const x1 = _child(n1)')
expect(code).contains('const x3 = _child(n3)')
})

test('should bump old ref var (r*) on conflict', () => {
const code = compile(
`<div :ref="bar" /><div :ref="bar" /><div :ref="bar" />`,
{
bindingMetadata: {
r0: BindingTypes.SETUP_REF,
r2: BindingTypes.SETUP_REF,
bar: BindingTypes.SETUP_REF,
},
},
)

expect(code).matchSnapshot()
expect(code).not.contains('let r0')
expect(code).not.contains('let r2')
expect(code).contains('let r1')
expect(code).contains('let r3')
expect(code).contains('let r4')
expect(code).contains('r1 = _setTemplateRef(n1, _bar, r1)')
expect(code).contains('r3 = _setTemplateRef(n3, _bar, r3)')
expect(code).contains('r4 = _setTemplateRef(n4, _bar, r4)')
})

test('should bump template var (t*) on conflict', () => {
const code = compile(`<div/><span/><p/>`, {
bindingMetadata: {
t0: BindingTypes.SETUP_REF,
t2: BindingTypes.SETUP_REF,
},
})

expect(code).matchSnapshot()
expect(code).not.contains('const t0 =')
expect(code).not.contains('const t2 =')
expect(code).contains('const t1 = _template("<div></div>")')
expect(code).contains('const t3 = _template("<span></span>")')
expect(code).contains('const t4 = _template("<p></p>")')
})

test('should bump placeholder var (p*) on conflict', () => {
const code = compile(
`<div><div><div><span :id="foo" /></div></div></div>`,
{
bindingMetadata: {
p0: BindingTypes.SETUP_REF,
p2: BindingTypes.SETUP_REF,
foo: BindingTypes.SETUP_REF,
},
},
)

expect(code).matchSnapshot()
expect(code).not.contains('const p0 = ')
expect(code).not.contains('const p2 = ')
expect(code).contains('const p1 = ')
expect(code).contains('const p3 = ')
})
})
})
91 changes: 82 additions & 9 deletions packages/compiler-vapor/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,35 @@ import {
genCall,
} from './generators/utils'
import { setTemplateRefIdent } from './generators/templateRef'
import { buildNextIdMap, getNextId } from './transform'

export type CodegenOptions = Omit<BaseCodegenOptions, 'optimizeImports'>

const idWithTrailingDigitsRE = /^([A-Za-z_$][\w$]*)(\d+)$/

export class CodegenContext {
options: Required<CodegenOptions>

helpers: Set<string> = new Set<string>([])
bindingNames: Set<string> = new Set<string>()

helpers: Map<string, string> = new Map()

helper = (name: CoreHelper | VaporHelper) => {
this.helpers.add(name)
return `_${name}`
helper = (name: CoreHelper | VaporHelper): string => {
if (this.helpers.has(name)) {
return this.helpers.get(name)!
}

const base = `_${name}`
if (this.bindingNames.size === 0 || !this.bindingNames.has(base)) {
this.helpers.set(name, base)
return base
}

const map = this.nextIdMap.get(base)
// start from 1 because "base" (no suffix) is already taken.
const alias = `${base}${getNextId(map, 1)}`
this.helpers.set(name, alias)
return alias
}

delegates: Set<string> = new Set<string>()
Expand Down Expand Up @@ -68,6 +86,56 @@ export class CodegenContext {
return [this.scopeLevel++, () => this.scopeLevel--] as const
}

private templateVars: Map<number, string> = new Map()
private nextIdMap: Map<string, Map<number, number>> = new Map()
private lastIdMap: Map<string, number> = new Map()
private lastTIndex: number = -1
private initNextIdMap(): void {
if (this.bindingNames.size === 0) return

// build a map of binding names to their occupied ids
const map = new Map<string, Set<number>>()
for (const name of this.bindingNames) {
const m = idWithTrailingDigitsRE.exec(name)
if (!m) continue

const prefix = m[1]
const num = Number(m[2])
let set = map.get(prefix)
if (!set) map.set(prefix, (set = new Set<number>()))
set.add(num)
}

for (const [prefix, nums] of map) {
if (nums.size === 0) continue
this.nextIdMap.set(prefix, buildNextIdMap(nums))
}
}

tName(i: number): string {
let name = this.templateVars.get(i)
if (name) return name

const map = this.nextIdMap.get('t')
let lastId = this.lastIdMap.get('t') || -1
for (let j = this.lastTIndex + 1; j <= i; j++) {
this.templateVars.set(
j,
(name = `t${(lastId = getNextId(map, Math.max(j, lastId + 1)))}`),
)
}
this.lastIdMap.set('t', lastId)
this.lastTIndex = i
return name!
}

pName(i: number): string {
const map = this.nextIdMap.get('p')
let lastId = this.lastIdMap.get('p') || -1
this.lastIdMap.set('p', (lastId = getNextId(map, Math.max(i, lastId + 1))))
return `p${lastId}`
}

constructor(
public ir: RootIRNode,
options: CodegenOptions,
Expand All @@ -90,6 +158,12 @@ export class CodegenContext {
}
this.options = extend(defaultOptions, options)
this.block = ir.block
this.bindingNames = new Set<string>(
this.options.bindingMetadata
? Object.keys(this.options.bindingMetadata)
: [],
)
this.initNextIdMap()
}
}

Expand All @@ -105,7 +179,6 @@ export function generate(
): VaporCodegenResult {
const [frag, push] = buildCodeFragment()
const context = new CodegenContext(ir, options)
const { helpers } = context
const { inline, bindingMetadata } = options
const functionName = 'render'

Expand Down Expand Up @@ -156,7 +229,7 @@ export function generate(
ast: ir,
preamble,
map: map && map.toJSON(),
helpers,
helpers: new Set<string>(Array.from(context.helpers.keys())),
}
}

Expand All @@ -169,11 +242,11 @@ function genDelegates({ delegates, helper }: CodegenContext) {
: ''
}

function genHelperImports({ helpers, helper, options }: CodegenContext) {
function genHelperImports({ helpers, options }: CodegenContext) {
let imports = ''
if (helpers.size) {
imports += `import { ${[...helpers]
.map(h => `${h} as _${h}`)
imports += `import { ${Array.from(helpers)
.map(([h, alias]) => `${h} as ${alias}`)
.join(', ')} } from '${options.runtimeModuleName}';\n`
}
return imports
Expand Down
9 changes: 5 additions & 4 deletions packages/compiler-vapor/src/generators/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
export function genTemplates(
templates: string[],
rootIndex: number | undefined,
{ helper }: CodegenContext,
context: CodegenContext,
): string {
return templates
.map(
(template, i) =>
`const t${i} = ${helper('template')}(${JSON.stringify(
`const ${context.tName(i)} = ${context.helper('template')}(${JSON.stringify(
template,
)}${i === rootIndex ? ', true' : ''})\n`,
)
Expand All @@ -27,7 +27,7 @@ export function genSelf(
const { id, template, operation, hasDynamicChild } = dynamic

if (id !== undefined && template !== undefined) {
push(NEWLINE, `const n${id} = t${template}()`)
push(NEWLINE, `const n${id} = ${context.tName(template)}()`)
push(...genDirectivesForElement(id, context))
}

Expand Down Expand Up @@ -75,7 +75,8 @@ export function genChildren(
const elementIndex = Number(index) + offset
// p for "placeholder" variables that are meant for possible reuse by
// other access paths
const variable = id === undefined ? `p${context.block.tempId++}` : `n${id}`
const variable =
id === undefined ? context.pName(context.block.tempId++) : `n${id}`
pushBlock(NEWLINE, `const ${variable} = `)

if (prev) {
Expand Down
Loading
Loading