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 {
type CodegenOptions as BaseCodegenOptions,
BindingTypes,
type CodegenResult,
NewlineType,
type Position,
type SourceLocation,
advancePositionWithClone,
advancePositionWithMutation,
createSimpleExpression,
isMemberExpression,
isSimpleIdentifier,
locStub,
walkIdentifiers,
} from '@vue/compiler-dom'
import {
type AppendNodeIRNode,
type CreateTextNodeIRNode,
type IRDynamicChildren,
type IRExpression,
IRNodeTypes,
type InsertNodeIRNode,
type OperationNode,
type PrependNodeIRNode,
type RootIRNode,
type SetEventIRNode,
type SetHtmlIRNode,
type SetModelValueIRNode,
type SetPropIRNode,
type SetRefIRNode,
type SetTextIRNode,
type VaporHelper,
type WithDirectiveIRNode,
} from './ir'
import { SourceMapGenerator } from 'source-map-js'
import { camelize, isGloballyAllowed, isString, makeMap } from '@vue/shared'
import type { Identifier } from '@babel/types'
import { isString } from '@vue/shared'
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 {
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
// @ts-expect-error
function checkNever(x: never): never {}
@ -407,361 +393,3 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
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 {
BindingTypes,
CompoundExpressionNode,
DirectiveNode,
RootNode,
@ -106,6 +107,7 @@ export interface SetModelValueIRNode extends BaseIRNode {
element: number
key: IRExpression
value: IRExpression
bindingType?: BindingTypes
isComponent: boolean
}

View File

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