vue3-core/packages/compiler-vapor/src/generate.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

768 lines
20 KiB
TypeScript
Raw Normal View History

2023-12-01 22:12:19 +08:00
import {
type CodegenOptions as BaseCodegenOptions,
2023-12-29 22:05:33 +08:00
BindingTypes,
2023-12-01 22:12:19 +08:00
type CodegenResult,
2023-12-29 22:05:33 +08:00
NewlineType,
2023-12-01 22:12:19 +08:00
type Position,
2023-12-02 00:49:13 +08:00
type SourceLocation,
2023-12-29 22:05:33 +08:00
advancePositionWithClone,
2023-12-01 22:12:19 +08:00
advancePositionWithMutation,
2023-12-03 18:36:01 +08:00
createSimpleExpression,
isMemberExpression,
isSimpleIdentifier,
2023-12-29 22:05:33 +08:00
locStub,
walkIdentifiers,
2023-12-01 22:12:19 +08:00
} from '@vue/compiler-dom'
2023-11-24 15:25:34 +08:00
import {
2023-12-29 22:05:33 +08:00
type AppendNodeIRNode,
type CreateTextNodeIRNode,
2023-12-01 23:30:21 +08:00
type IRDynamicChildren,
2023-12-05 17:13:25 +08:00
type IRExpression,
2023-12-29 22:05:33 +08:00
IRNodeTypes,
type InsertNodeIRNode,
2023-12-05 17:13:25 +08:00
type OperationNode,
2023-12-29 22:05:33 +08:00
type PrependNodeIRNode,
type RootIRNode,
2023-12-05 17:13:25 +08:00
type SetEventIRNode,
2023-12-06 00:15:57 +08:00
type SetHtmlIRNode,
2024-01-21 02:16:30 +08:00
type SetModelValueIRNode,
2023-12-29 22:05:33 +08:00
type SetPropIRNode,
2024-01-20 23:48:10 +08:00
type SetRefIRNode,
2023-12-29 22:05:33 +08:00
type SetTextIRNode,
type VaporHelper,
type WithDirectiveIRNode,
2023-11-24 15:25:34 +08:00
} from './ir'
2023-12-01 22:12:19 +08:00
import { SourceMapGenerator } from 'source-map-js'
import { camelize, isGloballyAllowed, isString, makeMap } from '@vue/shared'
2023-12-06 00:15:57 +08:00
import type { Identifier } from '@babel/types'
import type { ParserPlugin } from '@babel/parser'
interface CodegenOptions extends BaseCodegenOptions {
expressionPlugins?: ParserPlugin[]
}
2023-11-17 03:01:19 +08:00
// TODO: share this with compiler-core
const fnExpRE =
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
2023-11-24 14:44:57 +08:00
// remove when stable
2023-12-01 22:12:19 +08:00
// @ts-expect-error
function checkNever(x: never): never {}
2023-12-01 23:30:21 +08:00
export interface CodegenContext extends Required<CodegenOptions> {
2023-12-01 22:12:19 +08:00
source: string
code: string
line: number
column: number
offset: number
indentLevel: number
map?: SourceMapGenerator
2023-12-02 00:07:24 +08:00
push(
code: string,
newlineIndex?: number,
2023-12-02 00:49:13 +08:00
loc?: SourceLocation,
name?: string,
2023-12-02 00:07:24 +08:00
): void
2023-12-10 01:26:19 +08:00
pushNewline(
2023-12-02 00:07:24 +08:00
code: string,
newlineIndex?: number,
2023-12-02 00:49:13 +08:00
loc?: SourceLocation,
name?: string,
2023-12-02 00:07:24 +08:00
): void
2023-12-10 01:01:57 +08:00
pushMulti(
codes: [left: string, right: string, segment?: string],
2023-12-10 01:26:19 +08:00
...fn: Array<false | string | (() => void)>
2023-12-10 01:01:57 +08:00
): void
2023-12-10 01:26:19 +08:00
pushFnCall(name: string, ...args: Array<false | string | (() => void)>): void
2023-12-10 01:05:26 +08:00
withIndent(fn: () => void): void
2023-12-01 22:12:19 +08:00
newline(): void
2023-11-24 14:44:57 +08:00
2023-12-01 05:18:20 +08:00
helpers: Set<string>
vaporHelpers: Set<string>
helper(name: string): string
vaporHelper(name: string): string
}
2023-12-01 22:12:19 +08:00
function createCodegenContext(
ir: RootIRNode,
{
mode = 'function',
prefixIdentifiers = mode === 'module',
sourceMap = false,
filename = `template.vue.html`,
scopeId = null,
optimizeImports = false,
runtimeGlobalName = `Vue`,
runtimeModuleName = `vue`,
ssrRuntimeModuleName = 'vue/server-renderer',
ssr = false,
isTS = false,
inSSR = false,
2023-12-01 23:30:21 +08:00
inline = false,
bindingMetadata = {},
expressionPlugins = [],
2023-12-01 22:12:19 +08:00
}: CodegenOptions,
) {
2023-12-01 05:18:20 +08:00
const { helpers, vaporHelpers } = ir
2023-12-01 22:12:19 +08:00
const context: CodegenContext = {
mode,
prefixIdentifiers,
sourceMap,
filename,
scopeId,
optimizeImports,
runtimeGlobalName,
runtimeModuleName,
ssrRuntimeModuleName,
ssr,
isTS,
inSSR,
2023-12-01 23:30:21 +08:00
bindingMetadata,
expressionPlugins,
2023-12-01 23:30:21 +08:00
inline,
2023-12-01 22:12:19 +08:00
source: ir.source,
code: ``,
column: 1,
line: 1,
offset: 0,
indentLevel: 0,
2023-12-01 05:18:20 +08:00
helpers,
vaporHelpers,
helper(name: string) {
helpers.add(name)
2023-12-01 08:26:01 +08:00
return `_${name}`
2023-12-01 05:18:20 +08:00
},
vaporHelper(name: VaporHelper) {
vaporHelpers.add(name)
2023-12-01 08:26:01 +08:00
return `_${name}`
2023-12-01 05:18:20 +08:00
},
2023-12-02 00:49:13 +08:00
push(code, newlineIndex = NewlineType.None, loc, name) {
2023-12-01 22:12:19 +08:00
context.code += code
if (!__BROWSER__ && context.map) {
2023-12-02 00:49:13 +08:00
if (loc) addMapping(loc.start, name)
2023-12-01 22:12:19 +08:00
if (newlineIndex === NewlineType.Unknown) {
// multiple newlines, full iteration
advancePositionWithMutation(context, code)
} else {
// fast paths
context.offset += code.length
if (newlineIndex === NewlineType.None) {
// no newlines; fast path to avoid newline detection
if (__TEST__ && code.includes('\n')) {
throw new Error(
`CodegenContext.push() called newlineIndex: none, but contains` +
`newlines: ${code.replace(/\n/g, '\\n')}`,
)
}
context.column += code.length
} else {
// single newline at known index
if (newlineIndex === NewlineType.End) {
newlineIndex = code.length - 1
}
if (
__TEST__ &&
(code.charAt(newlineIndex) !== '\n' ||
code.slice(0, newlineIndex).includes('\n') ||
code.slice(newlineIndex + 1).includes('\n'))
) {
throw new Error(
`CodegenContext.push() called with newlineIndex: ${newlineIndex} ` +
`but does not conform: ${code.replace(/\n/g, '\\n')}`,
)
}
context.line++
context.column = code.length - newlineIndex
}
}
2023-12-02 00:49:13 +08:00
if (loc && loc !== locStub) {
addMapping(loc.end)
2023-12-01 22:12:19 +08:00
}
}
},
2023-12-10 01:26:19 +08:00
pushNewline(code, newlineIndex, node) {
2023-12-01 22:45:08 +08:00
context.newline()
context.push(code, newlineIndex, node)
},
2023-12-10 01:01:57 +08:00
pushMulti([left, right, seg], ...fns) {
fns = fns.filter(Boolean)
context.push(left)
for (let i = 0; i < fns.length; i++) {
2023-12-10 01:26:19 +08:00
const fn = fns[i] as string | (() => void)
if (isString(fn)) context.push(fn)
else fn()
2023-12-10 01:01:57 +08:00
if (seg && i < fns.length - 1) context.push(seg)
}
context.push(right)
},
2023-12-10 01:26:19 +08:00
pushFnCall(name, ...args) {
context.push(name)
context.pushMulti(['(', ')', ', '], ...args)
},
2023-12-10 01:05:26 +08:00
withIndent(fn) {
2023-12-01 22:45:08 +08:00
++context.indentLevel
2023-12-10 01:05:26 +08:00
fn()
2023-12-01 22:45:08 +08:00
--context.indentLevel
2023-12-01 22:12:19 +08:00
},
newline() {
2023-12-10 01:26:19 +08:00
context.push(`\n${` `.repeat(context.indentLevel)}`, NewlineType.Start)
2023-12-01 22:12:19 +08:00
},
}
function addMapping(loc: Position, name: string | null = null) {
// we use the private property to directly add the mapping
// because the addMapping() implementation in source-map-js has a bunch of
// unnecessary arg and validation checks that are pure overhead in our case.
const { _names, _mappings } = context.map!
if (name !== null && !_names.has(name)) _names.add(name)
_mappings.add({
originalLine: loc.line,
originalColumn: loc.column - 1, // source-map column is 0 based
generatedLine: context.line,
generatedColumn: context.column - 1,
source: filename,
2023-12-29 22:05:33 +08:00
// @ts-expect-error it is possible to be null
2023-12-01 22:12:19 +08:00
name,
})
}
if (!__BROWSER__ && sourceMap) {
// lazy require source-map implementation, only in non-browser builds
context.map = new SourceMapGenerator()
context.map.setSourceContent(filename, context.source)
context.map._sources.add(filename)
2023-12-01 05:18:20 +08:00
}
2023-12-01 22:12:19 +08:00
return context
2023-12-01 05:18:20 +08:00
}
2023-11-17 03:01:19 +08:00
// IR -> JS codegen
export function generate(
2023-11-23 23:42:08 +08:00
ir: RootIRNode,
2023-11-24 11:15:33 +08:00
options: CodegenOptions = {},
2023-11-17 03:01:19 +08:00
): CodegenResult {
2023-12-01 05:18:20 +08:00
const ctx = createCodegenContext(ir, options)
2023-12-10 01:05:26 +08:00
const {
push,
2023-12-10 01:26:19 +08:00
pushNewline,
2023-12-10 01:05:26 +08:00
withIndent,
newline,
helpers,
vaporHelper,
vaporHelpers,
} = ctx
2023-12-01 05:18:20 +08:00
2023-12-01 22:12:19 +08:00
const functionName = 'render'
const isSetupInlined = !!options.inline
if (isSetupInlined) {
2023-12-01 22:45:08 +08:00
push(`(() => {`)
2023-12-01 22:12:19 +08:00
} else {
2023-12-02 00:35:30 +08:00
// placeholder for preamble
newline()
2023-12-10 01:26:19 +08:00
pushNewline(`export function ${functionName}(_ctx) {`)
2023-12-01 22:12:19 +08:00
}
2023-11-17 03:01:19 +08:00
2023-12-10 01:05:26 +08:00
withIndent(() => {
ir.template.forEach((template, i) => {
if (template.type === IRNodeTypes.TEMPLATE_FACTORY) {
// TODO source map?
2023-12-10 01:26:19 +08:00
pushNewline(
2023-12-10 01:05:26 +08:00
`const t${i} = ${vaporHelper('template')}(${JSON.stringify(
template.template,
)})`,
)
} else {
// fragment
2023-12-10 01:26:19 +08:00
pushNewline(
2023-12-10 01:05:26 +08:00
`const t0 = ${vaporHelper('fragment')}()\n`,
NewlineType.End,
)
}
})
2023-12-01 22:45:08 +08:00
2023-12-10 01:05:26 +08:00
{
2023-12-10 01:26:19 +08:00
pushNewline(`const n${ir.dynamic.id} = t0()`)
2023-11-23 23:42:08 +08:00
2023-12-10 01:05:26 +08:00
const children = genChildren(ir.dynamic.children)
if (children) {
2023-12-10 01:26:19 +08:00
pushNewline(
2023-12-10 01:05:26 +08:00
`const ${children} = ${vaporHelper('children')}(n${ir.dynamic.id})`,
)
}
2023-12-04 16:08:15 +08:00
2023-12-10 01:05:26 +08:00
for (const oper of ir.operation.filter(
(oper): oper is WithDirectiveIRNode =>
oper.type === IRNodeTypes.WITH_DIRECTIVE,
)) {
genWithDirective(oper, ctx)
}
2023-12-04 16:08:15 +08:00
2023-12-10 01:05:26 +08:00
for (const operation of ir.operation) {
2023-12-01 22:45:08 +08:00
genOperation(operation, ctx)
2023-11-24 20:29:05 +08:00
}
2023-12-04 16:08:15 +08:00
2023-12-10 01:05:26 +08:00
for (const { operations } of ir.effect) {
pushNewline(`${vaporHelper('renderEffect')}(() => {`)
2023-12-10 01:05:26 +08:00
withIndent(() => {
for (const operation of operations) {
genOperation(operation, ctx)
}
})
2023-12-10 01:26:19 +08:00
pushNewline('})')
2023-12-10 01:05:26 +08:00
}
// TODO multiple-template
// TODO return statement in IR
2023-12-10 01:26:19 +08:00
pushNewline(`return n${ir.dynamic.id}`)
2023-12-10 01:05:26 +08:00
}
})
2023-11-17 03:01:19 +08:00
2023-12-01 22:45:08 +08:00
newline()
2023-12-01 22:12:19 +08:00
if (isSetupInlined) {
2023-12-01 22:45:08 +08:00
push('})()')
2023-12-01 22:12:19 +08:00
} else {
2023-12-01 22:45:08 +08:00
push('}')
2023-12-01 22:12:19 +08:00
}
2023-12-02 00:35:30 +08:00
let preamble = ''
if (vaporHelpers.size)
2023-12-02 00:35:30 +08:00
// TODO: extract import codegen
preamble = `import { ${[...vaporHelpers]
.map((h) => `${h} as _${h}`)
.join(', ')} } from 'vue/vapor';`
if (helpers.size)
2023-12-02 00:35:30 +08:00
preamble = `import { ${[...helpers]
.map((h) => `${h} as _${h}`)
.join(', ')} } from 'vue';`
if (!isSetupInlined) {
ctx.code = preamble + ctx.code
}
2023-11-17 03:01:19 +08:00
return {
2023-12-01 22:12:19 +08:00
code: ctx.code,
2023-11-23 23:42:08 +08:00
ast: ir as any,
2023-12-02 00:35:30 +08:00
preamble,
2023-12-01 22:12:19 +08:00
map: ctx.map ? ctx.map.toJSON() : undefined,
2023-11-23 23:42:08 +08:00
}
2023-12-01 05:18:20 +08:00
}
2023-11-24 15:25:34 +08:00
2023-12-01 23:30:21 +08:00
function genChildren(children: IRDynamicChildren) {
2023-11-27 05:16:21 +08:00
let code = ''
let offset = 0
2023-11-23 23:42:08 +08:00
for (const [index, child] of Object.entries(children)) {
2023-11-27 05:16:21 +08:00
const childrenLength = Object.keys(child.children).length
if (child.ghost && child.placeholder === null && childrenLength === 0) {
offset--
2023-11-27 05:16:21 +08:00
continue
}
2023-11-27 05:16:21 +08:00
code += ` ${Number(index) + offset}: [`
const id = child.ghost ? child.placeholder : child.id
if (id !== null) code += `n${id}`
const childrenString = childrenLength && genChildren(child.children)
if (childrenString) code += `, ${childrenString}`
code += '],'
2023-11-17 03:01:19 +08:00
}
2023-11-27 05:16:21 +08:00
if (!code) return ''
return `{${code}}`
2023-11-17 03:01:19 +08:00
}
2023-12-05 17:13:25 +08:00
function genOperation(oper: OperationNode, context: CodegenContext) {
// TODO: cache old value
switch (oper.type) {
case IRNodeTypes.SET_PROP:
return genSetProp(oper, context)
case IRNodeTypes.SET_TEXT:
return genSetText(oper, context)
case IRNodeTypes.SET_EVENT:
return genSetEvent(oper, context)
case IRNodeTypes.SET_HTML:
return genSetHtml(oper, context)
2024-01-20 23:48:10 +08:00
case IRNodeTypes.SET_REF:
return genSetRef(oper, context)
2024-01-21 02:16:30 +08:00
case IRNodeTypes.SET_MODEL_VALUE:
return genSetModelValue(oper, context)
2023-12-05 17:13:25 +08:00
case IRNodeTypes.CREATE_TEXT_NODE:
return genCreateTextNode(oper, context)
case IRNodeTypes.INSERT_NODE:
return genInsertNode(oper, context)
case IRNodeTypes.PREPEND_NODE:
return genPrependNode(oper, context)
case IRNodeTypes.APPEND_NODE:
return genAppendNode(oper, context)
case IRNodeTypes.WITH_DIRECTIVE:
// generated, skip
return
default:
return checkNever(oper)
}
}
2023-12-01 23:30:21 +08:00
2023-12-05 17:13:25 +08:00
function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
const { pushFnCall, pushMulti, newline, vaporHelper, helper } = context
2023-12-10 01:26:19 +08:00
newline()
pushFnCall(
vaporHelper('setDynamicProp'),
2023-12-10 01:26:19 +08:00
`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),
)
2023-12-10 01:26:19 +08:00
} else {
genExpression(oper.key, context)
}
},
'undefined',
() => genExpression(oper.value, context),
)
2023-12-05 17:13:25 +08:00
}
2023-12-01 23:30:21 +08:00
2023-12-05 17:13:25 +08:00
function genSetText(oper: SetTextIRNode, context: CodegenContext) {
2023-12-10 01:26:19 +08:00
const { pushFnCall, newline, vaporHelper } = context
newline()
pushFnCall(vaporHelper('setText'), `n${oper.element}`, 'undefined', () =>
genExpression(oper.value, context),
)
2023-12-05 17:13:25 +08:00
}
2023-12-01 23:30:21 +08:00
2023-12-05 17:13:25 +08:00
function genSetHtml(oper: SetHtmlIRNode, context: CodegenContext) {
2023-12-10 01:26:19 +08:00
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(vaporHelper('setHtml'), `n${oper.element}`, 'undefined', () =>
genExpression(oper.value, context),
)
2023-12-05 17:13:25 +08:00
}
2023-12-01 23:30:21 +08:00
2024-01-20 23:48:10 +08:00
function genSetRef(oper: SetRefIRNode, context: CodegenContext) {
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(vaporHelper('setRef'), `n${oper.element}`, () =>
genExpression(oper.value, context),
)
}
2024-01-21 02:16:30 +08:00
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)')
},
)
}
2023-12-05 17:13:25 +08:00
function genCreateTextNode(
oper: CreateTextNodeIRNode,
context: CodegenContext,
) {
2023-12-10 01:26:19 +08:00
const { pushNewline, pushFnCall, vaporHelper } = context
pushNewline(`const n${oper.id} = `)
pushFnCall(vaporHelper('createTextNode'), () =>
genExpression(oper.value, context),
)
2023-12-05 17:13:25 +08:00
}
2023-12-01 23:30:21 +08:00
2023-12-05 17:13:25 +08:00
function genInsertNode(oper: InsertNodeIRNode, context: CodegenContext) {
2023-12-10 01:26:19 +08:00
const { newline, pushFnCall, vaporHelper } = context
2023-12-05 17:13:25 +08:00
const elements = ([] as number[]).concat(oper.element)
let element = elements.map((el) => `n${el}`).join(', ')
if (elements.length > 1) element = `[${element}]`
2023-12-10 01:26:19 +08:00
newline()
pushFnCall(
vaporHelper('insert'),
element,
`n${oper.parent}`,
`n${oper.anchor}`,
2023-12-05 17:13:25 +08:00
)
}
function genPrependNode(oper: PrependNodeIRNode, context: CodegenContext) {
2023-12-10 01:26:19 +08:00
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(
vaporHelper('prepend'),
`n${oper.parent}`,
oper.elements.map((el) => `n${el}`).join(', '),
2023-12-05 17:13:25 +08:00
)
}
function genAppendNode(oper: AppendNodeIRNode, context: CodegenContext) {
2023-12-10 01:26:19 +08:00
const { newline, pushFnCall, vaporHelper } = context
newline()
pushFnCall(
vaporHelper('append'),
`n${oper.parent}`,
oper.elements.map((el) => `n${el}`).join(', '),
2023-12-05 17:13:25 +08:00
)
2023-12-01 23:30:21 +08:00
}
function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
2023-12-10 01:26:19 +08:00
const { vaporHelper, push, newline, pushMulti, pushFnCall } = context
2023-12-10 00:06:20 +08:00
const { keys, nonKeys, options } = oper.modifiers
2023-12-06 18:43:01 +08:00
2023-12-10 01:26:19 +08:00
newline()
pushFnCall(
vaporHelper('on'),
2023-12-10 01:01:57 +08:00
// 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()
2023-12-10 01:01:57 +08:00
;(keys.length ? pushWithKeys : pushNoop)(() =>
(nonKeys.length ? pushWithModifiers : pushNoop)(() => {
genEventHandler(context)
}),
)
2023-12-10 01:01:57 +08:00
},
// 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('() => {}')
}
}
}
2023-12-04 16:08:15 +08:00
function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) {
2023-12-10 01:26:19 +08:00
const { push, newline, pushFnCall, pushMulti, vaporHelper, bindingMetadata } =
context
2024-01-21 02:16:30 +08:00
const { dir, builtin } = oper
2023-12-04 16:08:15 +08:00
// TODO merge directive for the same node
2023-12-10 01:26:19 +08:00
newline()
pushFnCall(
vaporHelper('withDirectives'),
// 1st arg: node
`n${oper.element}`,
// 2nd arg: directives
() => {
push('[')
// directive
pushMulti(['[', ']', ', '], () => {
if (dir.name === 'show') {
push(vaporHelper('vShow'))
2024-01-21 02:16:30 +08:00
} else if (builtin) {
push(vaporHelper(builtin))
2023-12-10 01:26:19 +08:00
} else {
const directiveReference = camelize(`v-${dir.name}`)
// TODO resolve directive
if (bindingMetadata[directiveReference]) {
const directiveExpression =
createSimpleExpression(directiveReference)
directiveExpression.ast = null
genExpression(directiveExpression, context)
}
}
2023-12-04 16:08:15 +08:00
2023-12-10 01:26:19 +08:00
if (dir.exp) {
push(', () => ')
genExpression(dir.exp, context)
} else if (dir.arg || dir.modifiers.length) {
push(', void 0')
}
2023-12-10 01:26:19 +08:00
if (dir.arg) {
push(', ')
genExpression(dir.arg, context)
} else if (dir.modifiers.length) {
push(', void 0')
}
2023-12-10 01:26:19 +08:00
if (dir.modifiers.length) {
push(', ')
push('{ ')
push(genDirectiveModifiers(dir.modifiers))
push(' }')
}
})
push(']')
},
)
2023-12-04 16:08:15 +08:00
}
2023-12-05 17:13:25 +08:00
// TODO: other types (not only string)
function genArrayExpression(elements: string[]) {
return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]`
}
2023-12-08 17:34:33 +08:00
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
function genExpression(
node: IRExpression,
context: CodegenContext,
knownIds: Record<string, number> = Object.create(null),
): void {
2023-12-06 00:15:57 +08:00
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)
2023-12-06 00:15:57 +08:00
}
if (
__BROWSER__ ||
2023-12-06 00:15:57 +08:00
!context.prefixIdentifiers ||
!node.content.trim() ||
// there was a parsing error
2023-12-08 17:34:33 +08:00
ast === false ||
isGloballyAllowed(rawExpr) ||
2023-12-08 17:34:33 +08:00
isLiteralWhitelisted(rawExpr)
2023-12-06 00:15:57 +08:00
) {
2023-12-06 14:25:15 +08:00
return push(rawExpr, NewlineType.None, loc)
2023-12-06 00:15:57 +08:00
}
2023-12-05 17:13:25 +08:00
2023-12-06 00:15:57 +08:00
if (ast === null) {
// the expression is a simple identifier
return genIdentifier(rawExpr, context, loc)
}
2023-12-05 17:13:25 +08:00
2023-12-06 00:15:57 +08:00
const ids: Identifier[] = []
walkIdentifiers(
ast!,
(id, parent, parentStack, isReference, isLocal) => {
if (isLocal) return
2023-12-06 00:15:57 +08:00
ids.push(id)
},
false,
[],
knownIds,
2023-12-06 00:15:57 +08:00
)
2023-12-06 14:25:15 +08:00
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)
}
2023-12-06 00:15:57 +08:00
})
2023-12-06 14:25:15 +08:00
} else {
push(rawExpr, NewlineType.Unknown)
}
2023-12-06 00:15:57 +08:00
}
function genIdentifier(
id: string,
{ inline, bindingMetadata, vaporHelper, push }: CodegenContext,
loc?: SourceLocation,
): void {
let name: string | undefined = id
if (inline) {
switch (bindingMetadata[id]) {
2023-12-05 17:13:25 +08:00
case BindingTypes.SETUP_REF:
2023-12-06 00:15:57 +08:00
name = id += '.value'
2023-12-05 17:13:25 +08:00
break
case BindingTypes.SETUP_MAYBE_REF:
2023-12-06 00:15:57 +08:00
id = `${vaporHelper('unref')}(${id})`
name = undefined
2023-12-05 17:13:25 +08:00
break
}
2023-12-06 00:15:57 +08:00
} else {
id = `_ctx.${id}`
2023-12-05 17:13:25 +08:00
}
2023-12-06 00:15:57 +08:00
push(id, NewlineType.None, loc, name)
2023-12-05 17:13:25 +08:00
}
function genDirectiveModifiers(modifiers: string[]) {
return modifiers
.map(
(value) =>
`${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
)
.join(', ')
}