mirror of https://github.com/vuejs/core.git
refactor(compiler-vapor): extract gen operation
This commit is contained in:
parent
31e8fa35c0
commit
1d11ed72fb
|
@ -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(', ')
|
||||
}
|
||||
|
|
|
@ -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(', ')
|
||||
}
|
|
@ -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(', '),
|
||||
)
|
||||
}
|
|
@ -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(', ')}]`
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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),
|
||||
)
|
||||
}
|
|
@ -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)')
|
||||
},
|
||||
)
|
||||
}
|
|
@ -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),
|
||||
)
|
||||
}
|
|
@ -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),
|
||||
)
|
||||
}
|
|
@ -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),
|
||||
)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue