2023-12-01 22:12:19 +08:00
|
|
|
import {
|
|
|
|
type CodegenOptions,
|
|
|
|
type CodegenResult,
|
|
|
|
type Position,
|
2023-12-02 00:49:13 +08:00
|
|
|
type SourceLocation,
|
2023-12-01 22:12:19 +08:00
|
|
|
NewlineType,
|
|
|
|
advancePositionWithMutation,
|
|
|
|
locStub,
|
2023-12-01 23:30:21 +08:00
|
|
|
BindingTypes,
|
2023-12-03 18:36:01 +08:00
|
|
|
createSimpleExpression,
|
2023-12-06 00:15:57 +08:00
|
|
|
walkIdentifiers,
|
|
|
|
advancePositionWithClone,
|
2023-12-07 01:41:17 +08:00
|
|
|
isSimpleIdentifier,
|
2023-12-01 22:12:19 +08:00
|
|
|
} from '@vue/compiler-dom'
|
2023-11-24 15:25:34 +08:00
|
|
|
import {
|
2023-12-01 23:30:21 +08:00
|
|
|
type IRDynamicChildren,
|
2023-11-24 15:25:34 +08:00
|
|
|
type RootIRNode,
|
2023-12-05 17:13:25 +08:00
|
|
|
type SetPropIRNode,
|
|
|
|
type IRExpression,
|
|
|
|
type OperationNode,
|
|
|
|
type VaporHelper,
|
|
|
|
type SetEventIRNode,
|
|
|
|
type WithDirectiveIRNode,
|
|
|
|
type SetTextIRNode,
|
2023-12-06 00:15:57 +08:00
|
|
|
type SetHtmlIRNode,
|
|
|
|
type CreateTextNodeIRNode,
|
|
|
|
type InsertNodeIRNode,
|
|
|
|
type PrependNodeIRNode,
|
|
|
|
type AppendNodeIRNode,
|
2023-11-24 15:25:34 +08:00
|
|
|
IRNodeTypes,
|
|
|
|
} from './ir'
|
2023-12-01 22:12:19 +08:00
|
|
|
import { SourceMapGenerator } from 'source-map-js'
|
2023-12-09 23:25:01 +08:00
|
|
|
import { camelize, isGloballyAllowed, isString, makeMap } from '@vue/shared'
|
2023-12-06 00:15:57 +08:00
|
|
|
import type { Identifier } from '@babel/types'
|
2023-11-17 03:01:19 +08:00
|
|
|
|
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
|
|
|
|
pushWithNewline(
|
|
|
|
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-01 22:12:19 +08:00
|
|
|
indent(): void
|
2023-12-01 22:45:08 +08:00
|
|
|
deindent(): 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 = {},
|
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,
|
|
|
|
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-01 22:45:08 +08:00
|
|
|
pushWithNewline(code, newlineIndex, node) {
|
|
|
|
context.newline()
|
|
|
|
context.push(code, newlineIndex, node)
|
|
|
|
},
|
2023-12-01 22:12:19 +08:00
|
|
|
indent() {
|
2023-12-01 22:45:08 +08:00
|
|
|
++context.indentLevel
|
2023-12-01 22:12:19 +08:00
|
|
|
},
|
2023-12-01 22:45:08 +08:00
|
|
|
deindent() {
|
|
|
|
--context.indentLevel
|
2023-12-01 22:12:19 +08:00
|
|
|
},
|
|
|
|
newline() {
|
|
|
|
newline(context.indentLevel)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
function newline(n: number) {
|
|
|
|
context.push(`\n${` `.repeat(n)}`, NewlineType.Start)
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
// @ts-ignore it is possible to be null
|
|
|
|
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-01 22:45:08 +08:00
|
|
|
const { push, pushWithNewline, indent, deindent, newline } = ctx
|
2023-12-01 05:18:20 +08:00
|
|
|
const { vaporHelper, helpers, vaporHelpers } = ctx
|
|
|
|
|
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-01 23:30:21 +08:00
|
|
|
pushWithNewline(`export function ${functionName}(_ctx) {`)
|
2023-12-01 22:12:19 +08:00
|
|
|
}
|
2023-12-01 22:45:08 +08:00
|
|
|
indent()
|
2023-11-17 03:01:19 +08:00
|
|
|
|
2023-11-26 03:53:47 +08:00
|
|
|
ir.template.forEach((template, i) => {
|
|
|
|
if (template.type === IRNodeTypes.TEMPLATE_FACTORY) {
|
2023-12-01 22:12:19 +08:00
|
|
|
// TODO source map?
|
2023-12-01 22:45:08 +08:00
|
|
|
pushWithNewline(
|
2023-12-01 22:12:19 +08:00
|
|
|
`const t${i} = ${vaporHelper('template')}(${JSON.stringify(
|
|
|
|
template.template,
|
2023-12-01 22:45:08 +08:00
|
|
|
)})`,
|
2023-12-01 22:12:19 +08:00
|
|
|
)
|
2023-11-26 03:53:47 +08:00
|
|
|
} else {
|
|
|
|
// fragment
|
2023-12-01 22:45:08 +08:00
|
|
|
pushWithNewline(
|
|
|
|
`const t0 = ${vaporHelper('fragment')}()\n`,
|
|
|
|
NewlineType.End,
|
|
|
|
)
|
2023-11-26 03:53:47 +08:00
|
|
|
}
|
|
|
|
})
|
2023-11-17 03:01:19 +08:00
|
|
|
|
2023-11-26 02:13:59 +08:00
|
|
|
{
|
2023-12-01 22:45:08 +08:00
|
|
|
pushWithNewline(`const n${ir.dynamic.id} = t0()`)
|
|
|
|
|
2023-11-27 05:16:21 +08:00
|
|
|
const children = genChildren(ir.dynamic.children)
|
|
|
|
if (children) {
|
2023-12-01 22:45:08 +08:00
|
|
|
pushWithNewline(
|
|
|
|
`const ${children} = ${vaporHelper('children')}(n${ir.dynamic.id})`,
|
2023-12-01 22:12:19 +08:00
|
|
|
)
|
2023-11-26 03:08:35 +08:00
|
|
|
}
|
2023-11-23 23:42:08 +08:00
|
|
|
|
2023-12-04 16:08:15 +08:00
|
|
|
for (const oper of ir.operation.filter(
|
|
|
|
(oper): oper is WithDirectiveIRNode =>
|
|
|
|
oper.type === IRNodeTypes.WITH_DIRECTIVE,
|
|
|
|
)) {
|
|
|
|
genWithDirective(oper, ctx)
|
|
|
|
}
|
|
|
|
|
2023-11-24 20:29:05 +08:00
|
|
|
for (const operation of ir.operation) {
|
2023-12-01 22:45:08 +08:00
|
|
|
genOperation(operation, ctx)
|
2023-11-23 23:42:08 +08:00
|
|
|
}
|
2023-12-04 16:08:15 +08:00
|
|
|
|
2023-12-01 23:30:21 +08:00
|
|
|
for (const { operations } of ir.effect) {
|
2023-12-01 22:45:08 +08:00
|
|
|
pushWithNewline(`${vaporHelper('effect')}(() => {`)
|
|
|
|
indent()
|
2023-11-24 20:29:05 +08:00
|
|
|
for (const operation of operations) {
|
2023-12-01 22:45:08 +08:00
|
|
|
genOperation(operation, ctx)
|
2023-11-24 20:29:05 +08:00
|
|
|
}
|
2023-12-01 22:45:08 +08:00
|
|
|
deindent()
|
|
|
|
pushWithNewline('})')
|
2023-11-24 20:29:05 +08:00
|
|
|
}
|
2023-12-04 16:08:15 +08:00
|
|
|
|
2023-11-24 20:29:05 +08:00
|
|
|
// TODO multiple-template
|
2023-11-26 03:53:47 +08:00
|
|
|
// TODO return statement in IR
|
2023-12-01 22:45:08 +08:00
|
|
|
pushWithNewline(`return n${ir.dynamic.id}`)
|
2023-11-24 20:29:05 +08:00
|
|
|
}
|
2023-11-17 03:01:19 +08:00
|
|
|
|
2023-12-01 22:45:08 +08:00
|
|
|
deindent()
|
|
|
|
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 = ''
|
2023-11-24 11:07:31 +08:00
|
|
|
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';`
|
2023-11-24 11:07:31 +08:00
|
|
|
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 = ''
|
|
|
|
// TODO
|
|
|
|
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
|
2023-11-27 06:22:10 +08:00
|
|
|
if (child.ghost && child.placeholder === null && childrenLength === 0) {
|
|
|
|
offset--
|
2023-11-27 05:16:21 +08:00
|
|
|
continue
|
2023-11-27 06:22:10 +08:00
|
|
|
}
|
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-11-30 05:11:59 +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)
|
|
|
|
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-11-30 05:11:59 +08:00
|
|
|
}
|
2023-12-01 23:30:21 +08:00
|
|
|
|
2023-12-05 17:13:25 +08:00
|
|
|
function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
|
2023-12-09 18:41:59 +08:00
|
|
|
const { push, pushWithNewline, vaporHelper, helper } = context
|
2023-12-05 17:13:25 +08:00
|
|
|
pushWithNewline(`${vaporHelper('setAttr')}(n${oper.element}, `)
|
2023-12-09 18:41:59 +08:00
|
|
|
if (oper.runtimeCamelize) push(`${helper('camelize')}(`)
|
2023-12-09 04:06:46 +08:00
|
|
|
genExpression(oper.key, context)
|
2023-12-09 18:41:59 +08:00
|
|
|
if (oper.runtimeCamelize) push(`)`)
|
2023-12-05 17:13:25 +08:00
|
|
|
push(`, undefined, `)
|
|
|
|
genExpression(oper.value, context)
|
|
|
|
push(')')
|
|
|
|
}
|
2023-12-01 23:30:21 +08:00
|
|
|
|
2023-12-05 17:13:25 +08:00
|
|
|
function genSetText(oper: SetTextIRNode, context: CodegenContext) {
|
|
|
|
const { push, pushWithNewline, vaporHelper } = context
|
|
|
|
pushWithNewline(`${vaporHelper('setText')}(n${oper.element}, undefined, `)
|
|
|
|
genExpression(oper.value, context)
|
|
|
|
push(')')
|
|
|
|
}
|
2023-12-01 23:30:21 +08:00
|
|
|
|
2023-12-05 17:13:25 +08:00
|
|
|
function genSetHtml(oper: SetHtmlIRNode, context: CodegenContext) {
|
|
|
|
const { push, pushWithNewline, vaporHelper } = context
|
|
|
|
pushWithNewline(`${vaporHelper('setHtml')}(n${oper.element}, undefined, `)
|
|
|
|
genExpression(oper.value, context)
|
|
|
|
push(')')
|
|
|
|
}
|
2023-12-01 23:30:21 +08:00
|
|
|
|
2023-12-05 17:13:25 +08:00
|
|
|
function genCreateTextNode(
|
|
|
|
oper: CreateTextNodeIRNode,
|
|
|
|
context: CodegenContext,
|
|
|
|
) {
|
|
|
|
const { push, pushWithNewline, vaporHelper } = context
|
|
|
|
pushWithNewline(`const n${oper.id} = ${vaporHelper('createTextNode')}(`)
|
|
|
|
genExpression(oper.value, context)
|
|
|
|
push(')')
|
|
|
|
}
|
2023-12-01 23:30:21 +08:00
|
|
|
|
2023-12-05 17:13:25 +08:00
|
|
|
function genInsertNode(oper: InsertNodeIRNode, context: CodegenContext) {
|
|
|
|
const { pushWithNewline, vaporHelper } = context
|
|
|
|
const elements = ([] as number[]).concat(oper.element)
|
|
|
|
let element = elements.map((el) => `n${el}`).join(', ')
|
|
|
|
if (elements.length > 1) element = `[${element}]`
|
|
|
|
pushWithNewline(
|
|
|
|
`${vaporHelper('insert')}(${element}, n${
|
|
|
|
oper.parent
|
|
|
|
}${`, n${oper.anchor}`})`,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
function genPrependNode(oper: PrependNodeIRNode, context: CodegenContext) {
|
|
|
|
const { pushWithNewline, vaporHelper } = context
|
|
|
|
pushWithNewline(
|
|
|
|
`${vaporHelper('prepend')}(n${oper.parent}, ${oper.elements
|
|
|
|
.map((el) => `n${el}`)
|
|
|
|
.join(', ')})`,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
function genAppendNode(oper: AppendNodeIRNode, context: CodegenContext) {
|
|
|
|
const { pushWithNewline, vaporHelper } = context
|
|
|
|
pushWithNewline(
|
|
|
|
`${vaporHelper('append')}(n${oper.parent}, ${oper.elements
|
|
|
|
.map((el) => `n${el}`)
|
|
|
|
.join(', ')})`,
|
|
|
|
)
|
2023-12-01 23:30:21 +08:00
|
|
|
}
|
2023-12-03 03:49:44 +08:00
|
|
|
|
|
|
|
function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
|
|
|
|
const { vaporHelper, push, pushWithNewline } = context
|
|
|
|
|
|
|
|
pushWithNewline(`${vaporHelper('on')}(n${oper.element}, `)
|
|
|
|
// second arg: event name
|
2023-12-09 04:06:46 +08:00
|
|
|
genExpression(oper.key, context)
|
2023-12-03 03:49:44 +08:00
|
|
|
push(', ')
|
|
|
|
|
|
|
|
const { keys, nonKeys, options } = oper.modifiers
|
|
|
|
if (keys.length) {
|
|
|
|
push(`${vaporHelper('withKeys')}(`)
|
|
|
|
}
|
|
|
|
if (nonKeys.length) {
|
|
|
|
push(`${vaporHelper('withModifiers')}(`)
|
|
|
|
}
|
2023-12-06 18:43:01 +08:00
|
|
|
|
|
|
|
// gen event handler
|
|
|
|
push('(...args) => (')
|
|
|
|
genExpression(oper.value, context)
|
|
|
|
push(' && ')
|
2023-12-03 03:49:44 +08:00
|
|
|
genExpression(oper.value, context)
|
2023-12-06 18:43:01 +08:00
|
|
|
push('(...args))')
|
|
|
|
|
2023-12-03 03:49:44 +08:00
|
|
|
if (nonKeys.length) {
|
|
|
|
push(`, ${genArrayExpression(nonKeys)})`)
|
|
|
|
}
|
|
|
|
if (keys.length) {
|
|
|
|
push(`, ${genArrayExpression(keys)})`)
|
|
|
|
}
|
|
|
|
if (options.length) {
|
|
|
|
push(`, { ${options.map((v) => `${v}: true`).join(', ')} }`)
|
|
|
|
}
|
|
|
|
|
|
|
|
push(')')
|
|
|
|
}
|
2023-12-04 16:08:15 +08:00
|
|
|
|
|
|
|
function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) {
|
|
|
|
const { push, pushWithNewline, vaporHelper, bindingMetadata } = context
|
2023-12-07 01:41:17 +08:00
|
|
|
const { dir } = oper
|
2023-12-04 16:08:15 +08:00
|
|
|
|
|
|
|
// TODO merge directive for the same node
|
|
|
|
pushWithNewline(`${vaporHelper('withDirectives')}(n${oper.element}, [[`)
|
|
|
|
|
2023-12-08 17:34:33 +08:00
|
|
|
if (dir.name === 'show') {
|
|
|
|
push(vaporHelper('vShow'))
|
|
|
|
} 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-07 01:41:17 +08:00
|
|
|
if (dir.exp) {
|
2023-12-08 17:34:33 +08:00
|
|
|
push(', () => ')
|
2023-12-07 01:41:17 +08:00
|
|
|
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(' }')
|
2023-12-04 16:08:15 +08:00
|
|
|
}
|
|
|
|
push(']])')
|
|
|
|
return
|
|
|
|
}
|
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')
|
|
|
|
|
2023-12-06 00:15:57 +08:00
|
|
|
function genExpression(node: IRExpression, context: CodegenContext): void {
|
|
|
|
const { push } = context
|
|
|
|
if (isString(node)) return push(node)
|
|
|
|
|
|
|
|
const { content: rawExpr, ast, isStatic, loc } = node
|
|
|
|
if (__BROWSER__) {
|
|
|
|
return push(rawExpr)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
!context.prefixIdentifiers ||
|
|
|
|
!node.content.trim() ||
|
|
|
|
// there was a parsing error
|
2023-12-08 17:34:33 +08:00
|
|
|
ast === false ||
|
2023-12-09 23:25:01 +08:00
|
|
|
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
|
|
|
}
|
|
|
|
if (isStatic) {
|
2023-12-06 14:25:15 +08:00
|
|
|
return push(JSON.stringify(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) => {
|
|
|
|
ids.push(id)
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
)
|
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
|
|
|
}
|
2023-12-07 01:41:17 +08:00
|
|
|
|
|
|
|
function genDirectiveModifiers(modifiers: string[]) {
|
|
|
|
return modifiers
|
|
|
|
.map(
|
|
|
|
(value) =>
|
|
|
|
`${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
|
|
|
|
)
|
|
|
|
.join(', ')
|
|
|
|
}
|