refactor(compiler-vapor): generate unique helper aliases to prevent collisions with user variables

This commit is contained in:
daiwei 2025-08-29 11:02:24 +08:00
parent ea397b7fa5
commit ad525c43e2
3 changed files with 59 additions and 9 deletions

View File

@ -280,6 +280,18 @@ export function render(_ctx) {
}"
`;
exports[`compile > 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 > 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>")

View File

@ -268,4 +268,18 @@ describe('compile', () => {
expect(code).matchSnapshot()
})
})
describe('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)')
})
})
})

View File

@ -24,11 +24,31 @@ export type CodegenOptions = Omit<BaseCodegenOptions, 'optimizeImports'>
export class CodegenContext {
options: Required<CodegenOptions>
helpers: Set<string> = new Set<string>([])
bindingNames: Set<string> = new Set<string>()
helper = (name: CoreHelper | VaporHelper) => {
this.helpers.add(name)
return `_${name}`
helpers: Map<string, string> = new Map()
helper = (name: CoreHelper | VaporHelper): string => {
if (this.helpers.has(name)) {
return this.helpers.get(name)!
}
const base = `_${name}`
if (this.bindingNames.size === 0) {
this.helpers.set(name, base)
return base
}
// check whether an alias is already used bindings
let alias = base
let i = 0
while (this.bindingNames.has(alias)) {
i++
alias = `${base}${i}`
}
this.helpers.set(name, alias)
return alias
}
delegates: Set<string> = new Set<string>()
@ -90,6 +110,11 @@ 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)
: [],
)
}
}
@ -105,7 +130,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'
@ -156,7 +180,7 @@ export function generate(
ast: ir,
preamble,
map: map && map.toJSON(),
helpers,
helpers: new Set<string>(Array.from(context.helpers.keys())),
}
}
@ -169,11 +193,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