refactor(compiler-vapor): extract gen operation

This commit is contained in:
三咲智子 Kevin Deng 2024-01-21 13:43:23 +08:00
parent 31e8fa35c0
commit 1d11ed72fb
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
12 changed files with 424 additions and 383 deletions

View File

@ -1,50 +1,36 @@
import { import {
type CodegenOptions as BaseCodegenOptions, type CodegenOptions as BaseCodegenOptions,
BindingTypes,
type CodegenResult, type CodegenResult,
NewlineType, NewlineType,
type Position, type Position,
type SourceLocation, type SourceLocation,
advancePositionWithClone,
advancePositionWithMutation, advancePositionWithMutation,
createSimpleExpression,
isMemberExpression,
isSimpleIdentifier,
locStub, locStub,
walkIdentifiers,
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import { import {
type AppendNodeIRNode,
type CreateTextNodeIRNode,
type IRDynamicChildren, type IRDynamicChildren,
type IRExpression,
IRNodeTypes, IRNodeTypes,
type InsertNodeIRNode,
type OperationNode, type OperationNode,
type PrependNodeIRNode,
type RootIRNode, type RootIRNode,
type SetEventIRNode,
type SetHtmlIRNode,
type SetModelValueIRNode,
type SetPropIRNode,
type SetRefIRNode,
type SetTextIRNode,
type VaporHelper, type VaporHelper,
type WithDirectiveIRNode, type WithDirectiveIRNode,
} from './ir' } from './ir'
import { SourceMapGenerator } from 'source-map-js' import { SourceMapGenerator } from 'source-map-js'
import { camelize, isGloballyAllowed, isString, makeMap } from '@vue/shared' import { isString } from '@vue/shared'
import type { Identifier } from '@babel/types'
import type { ParserPlugin } from '@babel/parser' import type { ParserPlugin } from '@babel/parser'
import { genSetProp } from './generators/prop'
import { genCreateTextNode, genSetText } from './generators/text'
import { genSetEvent } from './generators/event'
import { genSetHtml } from './generators/html'
import { genSetRef } from './generators/ref'
import { genSetModelValue } from './generators/modelValue'
import { genAppendNode, genInsertNode, genPrependNode } from './generators/dom'
import { genWithDirective } from './generators/directive'
interface CodegenOptions extends BaseCodegenOptions { interface CodegenOptions extends BaseCodegenOptions {
expressionPlugins?: ParserPlugin[] expressionPlugins?: ParserPlugin[]
} }
// TODO: share this with compiler-core
const fnExpRE =
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
// remove when stable // remove when stable
// @ts-expect-error // @ts-expect-error
function checkNever(x: never): never {} function checkNever(x: never): never {}
@ -407,361 +393,3 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
return checkNever(oper) return checkNever(oper)
} }
} }
function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
const { pushFnCall, pushMulti, newline, vaporHelper, helper } = context
newline()
pushFnCall(
vaporHelper('setDynamicProp'),
`n${oper.element}`,
// 2. key name
() => {
if (oper.runtimeCamelize) {
pushFnCall(helper('camelize'), () => genExpression(oper.key, context))
} else if (oper.runtimePrefix) {
pushMulti([`\`${oper.runtimePrefix}\${`, `}\``], () =>
genExpression(oper.key, context),
)
} else {
genExpression(oper.key, context)
}
},
'undefined',
() => genExpression(oper.value, context),
)
}
function genSetText(oper: SetTextIRNode, context: CodegenContext) {
const { pushFnCall, newline, vaporHelper } = context
newline()
pushFnCall(vaporHelper('setText'), `n${oper.element}`, 'undefined', () =>
genExpression(oper.value, context),
)
}
function genSetHtml(oper: SetHtmlIRNode, context: CodegenContext) {
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(vaporHelper('setHtml'), `n${oper.element}`, 'undefined', () =>
genExpression(oper.value, context),
)
}
function genSetRef(oper: SetRefIRNode, context: CodegenContext) {
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(vaporHelper('setRef'), `n${oper.element}`, () =>
genExpression(oper.value, context),
)
}
function genSetModelValue(oper: SetModelValueIRNode, context: CodegenContext) {
const { vaporHelper, push, newline, pushFnCall } = context
newline()
pushFnCall(
vaporHelper('on'),
// 1st arg: event name
() => push(`n${oper.element}`),
// 2nd arg: event name
() => {
if (isString(oper.key)) {
push(JSON.stringify(`update:${camelize(oper.key)}`))
} else {
push('`update:${')
genExpression(oper.key, context)
push('}`')
}
},
// 3rd arg: event handler
() => {
push((context.isTS ? `($event: any)` : `$event`) + ' => ((')
// TODO handle not a ref
genExpression(oper.value, context)
push(') = $event)')
},
)
}
function genCreateTextNode(
oper: CreateTextNodeIRNode,
context: CodegenContext,
) {
const { pushNewline, pushFnCall, vaporHelper } = context
pushNewline(`const n${oper.id} = `)
pushFnCall(vaporHelper('createTextNode'), () =>
genExpression(oper.value, context),
)
}
function genInsertNode(oper: InsertNodeIRNode, context: CodegenContext) {
const { newline, pushFnCall, vaporHelper } = context
const elements = ([] as number[]).concat(oper.element)
let element = elements.map((el) => `n${el}`).join(', ')
if (elements.length > 1) element = `[${element}]`
newline()
pushFnCall(
vaporHelper('insert'),
element,
`n${oper.parent}`,
`n${oper.anchor}`,
)
}
function genPrependNode(oper: PrependNodeIRNode, context: CodegenContext) {
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(
vaporHelper('prepend'),
`n${oper.parent}`,
oper.elements.map((el) => `n${el}`).join(', '),
)
}
function genAppendNode(oper: AppendNodeIRNode, context: CodegenContext) {
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(
vaporHelper('append'),
`n${oper.parent}`,
oper.elements.map((el) => `n${el}`).join(', '),
)
}
function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
const { vaporHelper, push, newline, pushMulti, pushFnCall } = context
const { keys, nonKeys, options } = oper.modifiers
newline()
pushFnCall(
vaporHelper('on'),
// 1st arg: event name
() => push(`n${oper.element}`),
// 2nd arg: event name
() => {
if (oper.keyOverride) {
const find = JSON.stringify(oper.keyOverride[0])
const replacement = JSON.stringify(oper.keyOverride[1])
pushMulti(['(', ')'], () => genExpression(oper.key, context))
push(` === ${find} ? ${replacement} : `)
pushMulti(['(', ')'], () => genExpression(oper.key, context))
} else {
genExpression(oper.key, context)
}
},
// 3rd arg: event handler
() => {
const pushWithKeys = (fn: () => void) => {
push(`${vaporHelper('withKeys')}(`)
fn()
push(`, ${genArrayExpression(keys)})`)
}
const pushWithModifiers = (fn: () => void) => {
push(`${vaporHelper('withModifiers')}(`)
fn()
push(`, ${genArrayExpression(nonKeys)})`)
}
const pushNoop = (fn: () => void) => fn()
;(keys.length ? pushWithKeys : pushNoop)(() =>
(nonKeys.length ? pushWithModifiers : pushNoop)(() => {
genEventHandler(context)
}),
)
},
// 4th arg, gen options
!!options.length &&
(() => push(`{ ${options.map((v) => `${v}: true`).join(', ')} }`)),
)
function genEventHandler(context: CodegenContext) {
const exp = oper.value
if (exp && exp.content.trim()) {
const isMemberExp = isMemberExpression(exp.content, context)
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
const hasMultipleStatements = exp.content.includes(`;`)
if (isInlineStatement) {
push('$event => ')
push(hasMultipleStatements ? '{' : '(')
const knownIds = Object.create(null)
knownIds['$event'] = 1
genExpression(exp, context, knownIds)
push(hasMultipleStatements ? '}' : ')')
} else if (isMemberExp) {
push('(...args) => (')
genExpression(exp, context)
push(' && ')
genExpression(exp, context)
push('(...args))')
} else {
genExpression(exp, context)
}
} else {
push('() => {}')
}
}
}
function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) {
const { push, newline, pushFnCall, pushMulti, vaporHelper, bindingMetadata } =
context
const { dir, builtin } = oper
// TODO merge directive for the same node
newline()
pushFnCall(
vaporHelper('withDirectives'),
// 1st arg: node
`n${oper.element}`,
// 2nd arg: directives
() => {
push('[')
// directive
pushMulti(['[', ']', ', '], () => {
if (dir.name === 'show') {
push(vaporHelper('vShow'))
} else if (builtin) {
push(vaporHelper(builtin))
} else {
const directiveReference = camelize(`v-${dir.name}`)
// TODO resolve directive
if (bindingMetadata[directiveReference]) {
const directiveExpression =
createSimpleExpression(directiveReference)
directiveExpression.ast = null
genExpression(directiveExpression, context)
}
}
if (dir.exp) {
push(', () => ')
genExpression(dir.exp, context)
} else if (dir.arg || dir.modifiers.length) {
push(', void 0')
}
if (dir.arg) {
push(', ')
genExpression(dir.arg, context)
} else if (dir.modifiers.length) {
push(', void 0')
}
if (dir.modifiers.length) {
push(', ')
push('{ ')
push(genDirectiveModifiers(dir.modifiers))
push(' }')
}
})
push(']')
},
)
}
// TODO: other types (not only string)
function genArrayExpression(elements: string[]) {
return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]`
}
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
function genExpression(
node: IRExpression,
context: CodegenContext,
knownIds: Record<string, number> = Object.create(null),
): void {
const { push } = context
if (isString(node)) return push(node)
const { content: rawExpr, ast, isStatic, loc } = node
if (isStatic) {
return push(JSON.stringify(rawExpr), NewlineType.None, loc)
}
if (
__BROWSER__ ||
!context.prefixIdentifiers ||
!node.content.trim() ||
// there was a parsing error
ast === false ||
isGloballyAllowed(rawExpr) ||
isLiteralWhitelisted(rawExpr)
) {
return push(rawExpr, NewlineType.None, loc)
}
if (ast === null) {
// the expression is a simple identifier
return genIdentifier(rawExpr, context, loc)
}
const ids: Identifier[] = []
walkIdentifiers(
ast!,
(id, parent, parentStack, isReference, isLocal) => {
if (isLocal) return
ids.push(id)
},
false,
[],
knownIds,
)
if (ids.length) {
ids.sort((a, b) => a.start! - b.start!)
ids.forEach((id, i) => {
// range is offset by -1 due to the wrapping parens when parsed
const start = id.start! - 1
const end = id.end! - 1
const last = ids[i - 1]
const leadingText = rawExpr.slice(last ? last.end! - 1 : 0, start)
if (leadingText.length) push(leadingText, NewlineType.Unknown)
const source = rawExpr.slice(start, end)
genIdentifier(source, context, {
start: advancePositionWithClone(node.loc.start, source, start),
end: advancePositionWithClone(node.loc.start, source, end),
source,
})
if (i === ids.length - 1 && end < rawExpr.length) {
push(rawExpr.slice(end), NewlineType.Unknown)
}
})
} else {
push(rawExpr, NewlineType.Unknown)
}
}
function genIdentifier(
id: string,
{ inline, bindingMetadata, vaporHelper, push }: CodegenContext,
loc?: SourceLocation,
): void {
let name: string | undefined = id
if (inline) {
switch (bindingMetadata[id]) {
case BindingTypes.SETUP_REF:
name = id += '.value'
break
case BindingTypes.SETUP_MAYBE_REF:
id = `${vaporHelper('unref')}(${id})`
name = undefined
break
}
} else {
id = `_ctx.${id}`
}
push(id, NewlineType.None, loc, name)
}
function genDirectiveModifiers(modifiers: string[]) {
return modifiers
.map(
(value) =>
`${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
)
.join(', ')
}

View File

@ -0,0 +1,74 @@
import { createSimpleExpression, isSimpleIdentifier } from '@vue/compiler-dom'
import { camelize } from '@vue/shared'
import { genExpression } from './expression'
import type { CodegenContext } from '../generate'
import type { WithDirectiveIRNode } from '../ir'
export function genWithDirective(
oper: WithDirectiveIRNode,
context: CodegenContext,
) {
const { push, newline, pushFnCall, pushMulti, vaporHelper, bindingMetadata } =
context
const { dir, builtin } = oper
// TODO merge directive for the same node
newline()
pushFnCall(
vaporHelper('withDirectives'),
// 1st arg: node
`n${oper.element}`,
// 2nd arg: directives
() => {
push('[')
// directive
pushMulti(['[', ']', ', '], () => {
if (dir.name === 'show') {
push(vaporHelper('vShow'))
} else if (builtin) {
push(vaporHelper(builtin))
} else {
const directiveReference = camelize(`v-${dir.name}`)
// TODO resolve directive
if (bindingMetadata[directiveReference]) {
const directiveExpression =
createSimpleExpression(directiveReference)
directiveExpression.ast = null
genExpression(directiveExpression, context)
}
}
if (dir.exp) {
push(', () => ')
genExpression(dir.exp, context)
} else if (dir.arg || dir.modifiers.length) {
push(', void 0')
}
if (dir.arg) {
push(', ')
genExpression(dir.arg, context)
} else if (dir.modifiers.length) {
push(', void 0')
}
if (dir.modifiers.length) {
push(', ')
push('{ ')
push(genDirectiveModifiers(dir.modifiers))
push(' }')
}
})
push(']')
},
)
}
function genDirectiveModifiers(modifiers: string[]) {
return modifiers
.map(
(value) =>
`${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
)
.join(', ')
}

View File

@ -0,0 +1,43 @@
import type { CodegenContext } from '../generate'
import type {
AppendNodeIRNode,
InsertNodeIRNode,
PrependNodeIRNode,
} from '../ir'
export function genInsertNode(oper: InsertNodeIRNode, context: CodegenContext) {
const { newline, pushFnCall, vaporHelper } = context
const elements = ([] as number[]).concat(oper.element)
let element = elements.map((el) => `n${el}`).join(', ')
if (elements.length > 1) element = `[${element}]`
newline()
pushFnCall(
vaporHelper('insert'),
element,
`n${oper.parent}`,
`n${oper.anchor}`,
)
}
export function genPrependNode(
oper: PrependNodeIRNode,
context: CodegenContext,
) {
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(
vaporHelper('prepend'),
`n${oper.parent}`,
oper.elements.map((el) => `n${el}`).join(', '),
)
}
export function genAppendNode(oper: AppendNodeIRNode, context: CodegenContext) {
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(
vaporHelper('append'),
`n${oper.parent}`,
oper.elements.map((el) => `n${el}`).join(', '),
)
}

View File

@ -0,0 +1,87 @@
import { isMemberExpression } from '@vue/compiler-dom'
import type { CodegenContext } from '../generate'
import type { SetEventIRNode } from '../ir'
import { genExpression } from './expression'
// TODO: share this with compiler-core
const fnExpRE =
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
export function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
const { vaporHelper, push, newline, pushMulti, pushFnCall } = context
const { keys, nonKeys, options } = oper.modifiers
newline()
pushFnCall(
vaporHelper('on'),
// 1st arg: event name
() => push(`n${oper.element}`),
// 2nd arg: event name
() => {
if (oper.keyOverride) {
const find = JSON.stringify(oper.keyOverride[0])
const replacement = JSON.stringify(oper.keyOverride[1])
pushMulti(['(', ')'], () => genExpression(oper.key, context))
push(` === ${find} ? ${replacement} : `)
pushMulti(['(', ')'], () => genExpression(oper.key, context))
} else {
genExpression(oper.key, context)
}
},
// 3rd arg: event handler
() => {
const pushWithKeys = (fn: () => void) => {
push(`${vaporHelper('withKeys')}(`)
fn()
push(`, ${genArrayExpression(keys)})`)
}
const pushWithModifiers = (fn: () => void) => {
push(`${vaporHelper('withModifiers')}(`)
fn()
push(`, ${genArrayExpression(nonKeys)})`)
}
const pushNoop = (fn: () => void) => fn()
;(keys.length ? pushWithKeys : pushNoop)(() =>
(nonKeys.length ? pushWithModifiers : pushNoop)(() => {
genEventHandler(context)
}),
)
},
// 4th arg, gen options
!!options.length &&
(() => push(`{ ${options.map((v) => `${v}: true`).join(', ')} }`)),
)
function genEventHandler(context: CodegenContext) {
const exp = oper.value
if (exp && exp.content.trim()) {
const isMemberExp = isMemberExpression(exp.content, context)
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
const hasMultipleStatements = exp.content.includes(`;`)
if (isInlineStatement) {
push('$event => ')
push(hasMultipleStatements ? '{' : '(')
const knownIds = Object.create(null)
knownIds['$event'] = 1
genExpression(exp, context, knownIds)
push(hasMultipleStatements ? '}' : ')')
} else if (isMemberExp) {
push('(...args) => (')
genExpression(exp, context)
push(' && ')
genExpression(exp, context)
push('(...args))')
} else {
genExpression(exp, context)
}
} else {
push('() => {}')
}
}
}
function genArrayExpression(elements: string[]) {
return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]`
}

View File

@ -0,0 +1,102 @@
import {
BindingTypes,
NewlineType,
type SourceLocation,
advancePositionWithClone,
walkIdentifiers,
} from '@vue/compiler-dom'
import { isGloballyAllowed, isString, makeMap } from '@vue/shared'
import type { Identifier } from '@babel/types'
import type { IRExpression } from '../ir'
import type { CodegenContext } from '../generate'
export function genExpression(
node: IRExpression,
context: CodegenContext,
knownIds: Record<string, number> = Object.create(null),
): void {
const { push } = context
if (isString(node)) return push(node)
const { content: rawExpr, ast, isStatic, loc } = node
if (isStatic) {
return push(JSON.stringify(rawExpr), NewlineType.None, loc)
}
if (
__BROWSER__ ||
!context.prefixIdentifiers ||
!node.content.trim() ||
// there was a parsing error
ast === false ||
isGloballyAllowed(rawExpr) ||
isLiteralWhitelisted(rawExpr)
) {
return push(rawExpr, NewlineType.None, loc)
}
if (ast === null) {
// the expression is a simple identifier
return genIdentifier(rawExpr, context, loc)
}
const ids: Identifier[] = []
walkIdentifiers(
ast!,
(id, parent, parentStack, isReference, isLocal) => {
if (isLocal) return
ids.push(id)
},
false,
[],
knownIds,
)
if (ids.length) {
ids.sort((a, b) => a.start! - b.start!)
ids.forEach((id, i) => {
// range is offset by -1 due to the wrapping parens when parsed
const start = id.start! - 1
const end = id.end! - 1
const last = ids[i - 1]
const leadingText = rawExpr.slice(last ? last.end! - 1 : 0, start)
if (leadingText.length) push(leadingText, NewlineType.Unknown)
const source = rawExpr.slice(start, end)
genIdentifier(source, context, {
start: advancePositionWithClone(node.loc.start, source, start),
end: advancePositionWithClone(node.loc.start, source, end),
source,
})
if (i === ids.length - 1 && end < rawExpr.length) {
push(rawExpr.slice(end), NewlineType.Unknown)
}
})
} else {
push(rawExpr, NewlineType.Unknown)
}
}
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
function genIdentifier(
id: string,
{ inline, bindingMetadata, vaporHelper, push }: CodegenContext,
loc?: SourceLocation,
): void {
let name: string | undefined = id
if (inline) {
switch (bindingMetadata[id]) {
case BindingTypes.SETUP_REF:
name = id += '.value'
break
case BindingTypes.SETUP_MAYBE_REF:
id = `${vaporHelper('unref')}(${id})`
name = undefined
break
}
} else {
id = `_ctx.${id}`
}
push(id, NewlineType.None, loc, name)
}

View File

@ -0,0 +1,11 @@
import type { CodegenContext } from '../generate'
import type { SetHtmlIRNode } from '../ir'
import { genExpression } from './expression'
export function genSetHtml(oper: SetHtmlIRNode, context: CodegenContext) {
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(vaporHelper('setHtml'), `n${oper.element}`, 'undefined', () =>
genExpression(oper.value, context),
)
}

View File

@ -0,0 +1,35 @@
import { camelize, isString } from '@vue/shared'
import { genExpression } from './expression'
import type { SetModelValueIRNode } from '../ir'
import type { CodegenContext } from '../generate'
export function genSetModelValue(
oper: SetModelValueIRNode,
context: CodegenContext,
) {
const { vaporHelper, push, newline, pushFnCall } = context
newline()
pushFnCall(
vaporHelper('on'),
// 1st arg: event name
() => push(`n${oper.element}`),
// 2nd arg: event name
() => {
if (isString(oper.key)) {
push(JSON.stringify(`update:${camelize(oper.key)}`))
} else {
push('`update:${')
genExpression(oper.key, context)
push('}`')
}
},
// 3rd arg: event handler
() => {
push((context.isTS ? `($event: any)` : `$event`) + ' => ((')
// TODO handle not a ref
genExpression(oper.value, context)
push(') = $event)')
},
)
}

View File

@ -0,0 +1,27 @@
import type { CodegenContext } from '../generate'
import type { SetPropIRNode } from '../ir'
import { genExpression } from './expression'
export function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
const { pushFnCall, pushMulti, newline, vaporHelper, helper } = context
newline()
pushFnCall(
vaporHelper('setDynamicProp'),
`n${oper.element}`,
// 2. key name
() => {
if (oper.runtimeCamelize) {
pushFnCall(helper('camelize'), () => genExpression(oper.key, context))
} else if (oper.runtimePrefix) {
pushMulti([`\`${oper.runtimePrefix}\${`, `}\``], () =>
genExpression(oper.key, context),
)
} else {
genExpression(oper.key, context)
}
},
'undefined',
() => genExpression(oper.value, context),
)
}

View File

@ -0,0 +1,11 @@
import type { CodegenContext } from '../generate'
import type { SetRefIRNode } from '../ir'
import { genExpression } from './expression'
export function genSetRef(oper: SetRefIRNode, context: CodegenContext) {
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(vaporHelper('setRef'), `n${oper.element}`, () =>
genExpression(oper.value, context),
)
}

View File

@ -0,0 +1,22 @@
import type { CodegenContext } from '../generate'
import type { CreateTextNodeIRNode, SetTextIRNode } from '../ir'
import { genExpression } from './expression'
export function genSetText(oper: SetTextIRNode, context: CodegenContext) {
const { pushFnCall, newline, vaporHelper } = context
newline()
pushFnCall(vaporHelper('setText'), `n${oper.element}`, 'undefined', () =>
genExpression(oper.value, context),
)
}
export function genCreateTextNode(
oper: CreateTextNodeIRNode,
context: CodegenContext,
) {
const { pushNewline, pushFnCall, vaporHelper } = context
pushNewline(`const n${oper.id} = `)
pushFnCall(vaporHelper('createTextNode'), () =>
genExpression(oper.value, context),
)
}

View File

@ -1,4 +1,5 @@
import type { import type {
BindingTypes,
CompoundExpressionNode, CompoundExpressionNode,
DirectiveNode, DirectiveNode,
RootNode, RootNode,
@ -106,6 +107,7 @@ export interface SetModelValueIRNode extends BaseIRNode {
element: number element: number
key: IRExpression key: IRExpression
value: IRExpression value: IRExpression
bindingType?: BindingTypes
isComponent: boolean isComponent: boolean
} }

View File

@ -27,7 +27,6 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
// we assume v-model directives are always parsed // we assume v-model directives are always parsed
// (not artificially created by a transform) // (not artificially created by a transform)
const rawExp = exp.loc.source const rawExp = exp.loc.source
const expString = exp.content
// in SFC <script setup> inline mode, the exp may have been transformed into // in SFC <script setup> inline mode, the exp may have been transformed into
// _unref(exp) // _unref(exp)
@ -44,13 +43,13 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
return return
} }
const expString = exp.content
const maybeRef = const maybeRef =
!__BROWSER__ && !__BROWSER__ &&
context.options.inline && context.options.inline &&
(bindingType === BindingTypes.SETUP_LET || (bindingType === BindingTypes.SETUP_LET ||
bindingType === BindingTypes.SETUP_REF || bindingType === BindingTypes.SETUP_REF ||
bindingType === BindingTypes.SETUP_MAYBE_REF) bindingType === BindingTypes.SETUP_MAYBE_REF)
if ( if (
!expString.trim() || !expString.trim() ||
(!isMemberExpression(expString, context.options) && !maybeRef) (!isMemberExpression(expString, context.options) && !maybeRef)