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 {
|
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(', ')
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue