2019-09-20 11:05:51 +08:00
|
|
|
import {
|
|
|
|
RootNode,
|
|
|
|
ChildNode,
|
|
|
|
ElementNode,
|
|
|
|
IfNode,
|
|
|
|
ForNode,
|
|
|
|
TextNode,
|
|
|
|
CommentNode,
|
|
|
|
ExpressionNode,
|
2019-09-23 04:50:57 +08:00
|
|
|
NodeTypes,
|
|
|
|
JSChildNode,
|
|
|
|
CallExpression,
|
|
|
|
ArrayExpression,
|
|
|
|
ObjectExpression,
|
2019-09-27 21:25:52 +08:00
|
|
|
IfBranchNode,
|
|
|
|
SourceLocation,
|
2019-09-27 23:42:02 +08:00
|
|
|
Position,
|
|
|
|
InterpolationNode,
|
|
|
|
CompoundExpressionNode,
|
2019-09-28 08:29:20 +08:00
|
|
|
SimpleExpressionNode,
|
2019-09-28 10:25:32 +08:00
|
|
|
ElementTypes,
|
|
|
|
SlotFunctionExpression
|
2019-09-20 11:05:51 +08:00
|
|
|
} from './ast'
|
|
|
|
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
2019-09-25 10:39:20 +08:00
|
|
|
import {
|
|
|
|
advancePositionWithMutation,
|
|
|
|
assert,
|
|
|
|
isSimpleIdentifier
|
|
|
|
} from './utils'
|
2019-09-23 04:50:57 +08:00
|
|
|
import { isString, isArray } from '@vue/shared'
|
2019-09-25 03:49:02 +08:00
|
|
|
import {
|
|
|
|
RENDER_LIST,
|
|
|
|
TO_STRING,
|
|
|
|
CREATE_VNODE,
|
|
|
|
COMMENT
|
|
|
|
} from './runtimeConstants'
|
2019-09-23 04:50:57 +08:00
|
|
|
|
|
|
|
type CodegenNode = ChildNode | JSChildNode
|
2019-09-20 11:05:51 +08:00
|
|
|
|
|
|
|
export interface CodegenOptions {
|
2019-09-25 03:49:02 +08:00
|
|
|
// - Module mode will generate ES module import statements for helpers
|
|
|
|
// and export the render function as the default export.
|
|
|
|
// - Function mode will generate a single `const { helpers... } = Vue`
|
|
|
|
// statement and return the render function. It is meant to be used with
|
|
|
|
// `new Function(code)()` to generate a render function at runtime.
|
|
|
|
// Default: 'function'
|
2019-09-23 04:50:57 +08:00
|
|
|
mode?: 'module' | 'function'
|
2019-09-25 03:49:02 +08:00
|
|
|
// Prefix suitable identifiers with _ctx.
|
|
|
|
// If this option is false, the generated code will be wrapped in a
|
|
|
|
// `with (this) { ... }` block.
|
|
|
|
// Default: false
|
2019-09-24 01:29:41 +08:00
|
|
|
prefixIdentifiers?: boolean
|
2019-09-25 03:49:02 +08:00
|
|
|
// Generate source map?
|
|
|
|
// Default: false
|
|
|
|
sourceMap?: boolean
|
2019-09-20 11:05:51 +08:00
|
|
|
// Filename for source map generation.
|
2019-09-25 03:49:02 +08:00
|
|
|
// Default: `template.vue.html`
|
2019-09-20 11:05:51 +08:00
|
|
|
filename?: string
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface CodegenResult {
|
|
|
|
code: string
|
2019-09-25 21:23:34 +08:00
|
|
|
ast: RootNode
|
2019-09-20 11:05:51 +08:00
|
|
|
map?: RawSourceMap
|
|
|
|
}
|
|
|
|
|
2019-09-22 03:47:26 +08:00
|
|
|
export interface CodegenContext extends Required<CodegenOptions> {
|
2019-09-20 11:05:51 +08:00
|
|
|
source: string
|
|
|
|
code: string
|
|
|
|
line: number
|
|
|
|
column: number
|
|
|
|
offset: number
|
2019-09-23 04:50:57 +08:00
|
|
|
indentLevel: number
|
2019-09-20 11:05:51 +08:00
|
|
|
map?: SourceMapGenerator
|
2019-09-26 07:17:45 +08:00
|
|
|
helper(name: string): string
|
2019-09-27 02:55:53 +08:00
|
|
|
push(code: string, node?: CodegenNode, openOnly?: boolean): void
|
2019-09-27 21:25:52 +08:00
|
|
|
resetMapping(loc: SourceLocation): void
|
2019-09-23 04:50:57 +08:00
|
|
|
indent(): void
|
2019-09-23 05:06:46 +08:00
|
|
|
deindent(withoutNewLine?: boolean): void
|
2019-09-23 04:50:57 +08:00
|
|
|
newline(): void
|
2019-09-20 11:05:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function createCodegenContext(
|
|
|
|
ast: RootNode,
|
2019-09-23 04:50:57 +08:00
|
|
|
{
|
|
|
|
mode = 'function',
|
2019-09-24 01:29:41 +08:00
|
|
|
prefixIdentifiers = false,
|
2019-09-25 03:49:02 +08:00
|
|
|
sourceMap = false,
|
2019-09-23 04:50:57 +08:00
|
|
|
filename = `template.vue.html`
|
|
|
|
}: CodegenOptions
|
2019-09-20 11:05:51 +08:00
|
|
|
): CodegenContext {
|
|
|
|
const context: CodegenContext = {
|
2019-09-23 04:50:57 +08:00
|
|
|
mode,
|
2019-09-24 01:29:41 +08:00
|
|
|
prefixIdentifiers,
|
2019-09-25 03:49:02 +08:00
|
|
|
sourceMap,
|
2019-09-21 00:16:19 +08:00
|
|
|
filename,
|
2019-09-20 11:05:51 +08:00
|
|
|
source: ast.loc.source,
|
|
|
|
code: ``,
|
|
|
|
column: 1,
|
|
|
|
line: 1,
|
|
|
|
offset: 0,
|
2019-09-23 04:50:57 +08:00
|
|
|
indentLevel: 0,
|
2019-09-22 03:47:26 +08:00
|
|
|
|
2019-09-20 11:05:51 +08:00
|
|
|
// lazy require source-map implementation, only in non-browser builds!
|
2019-09-25 03:49:02 +08:00
|
|
|
map:
|
|
|
|
__BROWSER__ || !sourceMap
|
|
|
|
? undefined
|
|
|
|
: new (require('source-map')).SourceMapGenerator(),
|
2019-09-22 03:47:26 +08:00
|
|
|
|
2019-09-26 07:17:45 +08:00
|
|
|
helper(name) {
|
|
|
|
return prefixIdentifiers ? name : `_${name}`
|
|
|
|
},
|
2019-09-27 02:55:53 +08:00
|
|
|
push(code, node, openOnly) {
|
2019-09-23 04:50:57 +08:00
|
|
|
context.code += code
|
2019-09-27 21:25:52 +08:00
|
|
|
if (!__BROWSER__ && context.map) {
|
2019-09-20 11:05:51 +08:00
|
|
|
if (node) {
|
2019-09-26 22:13:46 +08:00
|
|
|
let name
|
2019-09-27 23:42:02 +08:00
|
|
|
if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) {
|
2019-09-26 22:13:46 +08:00
|
|
|
const content = node.content.replace(/^_ctx\./, '')
|
|
|
|
if (content !== node.content && isSimpleIdentifier(content)) {
|
|
|
|
name = content
|
|
|
|
}
|
|
|
|
}
|
2019-09-27 21:25:52 +08:00
|
|
|
addMapping(node.loc.start, name)
|
2019-09-20 11:05:51 +08:00
|
|
|
}
|
2019-09-27 21:25:52 +08:00
|
|
|
advancePositionWithMutation(context, code)
|
2019-09-27 02:55:53 +08:00
|
|
|
if (node && !openOnly) {
|
2019-09-27 21:25:52 +08:00
|
|
|
addMapping(node.loc.end)
|
2019-09-27 02:55:53 +08:00
|
|
|
}
|
2019-09-20 11:05:51 +08:00
|
|
|
}
|
2019-09-23 04:50:57 +08:00
|
|
|
},
|
2019-09-27 21:25:52 +08:00
|
|
|
resetMapping(loc: SourceLocation) {
|
|
|
|
if (!__BROWSER__ && context.map) {
|
|
|
|
addMapping(loc.start)
|
|
|
|
}
|
|
|
|
},
|
2019-09-23 04:50:57 +08:00
|
|
|
indent() {
|
|
|
|
newline(++context.indentLevel)
|
|
|
|
},
|
2019-09-23 05:06:46 +08:00
|
|
|
deindent(withoutNewLine = false) {
|
|
|
|
if (withoutNewLine) {
|
|
|
|
--context.indentLevel
|
|
|
|
} else {
|
|
|
|
newline(--context.indentLevel)
|
|
|
|
}
|
2019-09-23 04:50:57 +08:00
|
|
|
},
|
|
|
|
newline() {
|
|
|
|
newline(context.indentLevel)
|
2019-09-20 11:05:51 +08:00
|
|
|
}
|
|
|
|
}
|
2019-09-27 21:25:52 +08:00
|
|
|
|
|
|
|
function newline(n: number) {
|
|
|
|
context.push('\n' + ` `.repeat(n))
|
|
|
|
}
|
|
|
|
|
|
|
|
function addMapping(loc: Position, name?: string) {
|
|
|
|
context.map!.addMapping({
|
|
|
|
name,
|
|
|
|
source: context.filename,
|
|
|
|
original: {
|
|
|
|
line: loc.line,
|
|
|
|
column: loc.column - 1 // source-map column is 0 based
|
|
|
|
},
|
|
|
|
generated: {
|
|
|
|
line: context.line,
|
|
|
|
column: context.column - 1
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-25 03:49:02 +08:00
|
|
|
if (!__BROWSER__ && context.map) {
|
|
|
|
context.map.setSourceContent(filename, context.source)
|
2019-09-21 00:19:52 +08:00
|
|
|
}
|
2019-09-20 11:05:51 +08:00
|
|
|
return context
|
|
|
|
}
|
|
|
|
|
2019-09-23 04:50:57 +08:00
|
|
|
export function generate(
|
|
|
|
ast: RootNode,
|
|
|
|
options: CodegenOptions = {}
|
|
|
|
): CodegenResult {
|
|
|
|
const context = createCodegenContext(ast, options)
|
2019-09-24 01:29:41 +08:00
|
|
|
const { mode, push, prefixIdentifiers, indent, deindent, newline } = context
|
2019-09-26 07:17:45 +08:00
|
|
|
const hasImports = ast.imports.length
|
2019-09-26 10:29:37 +08:00
|
|
|
const useWithBlock = !prefixIdentifiers && mode !== 'module'
|
2019-09-26 03:09:58 +08:00
|
|
|
|
|
|
|
// preambles
|
2019-09-23 04:50:57 +08:00
|
|
|
if (mode === 'function') {
|
2019-09-26 03:09:58 +08:00
|
|
|
// Generate const declaration for helpers
|
|
|
|
// In prefix mode, we place the const declaration at top so it's done
|
|
|
|
// only once; But if we not prefixing, we place the decalration inside the
|
|
|
|
// with block so it doesn't incur the `in` check cost for every helper access.
|
2019-09-26 07:17:45 +08:00
|
|
|
if (hasImports) {
|
2019-09-26 03:09:58 +08:00
|
|
|
if (prefixIdentifiers) {
|
2019-09-26 07:17:45 +08:00
|
|
|
push(`const { ${ast.imports.join(', ')} } = Vue\n`)
|
2019-09-26 03:09:58 +08:00
|
|
|
} else {
|
|
|
|
// save Vue in a separate variable to avoid collision
|
|
|
|
push(`const _Vue = Vue`)
|
|
|
|
}
|
2019-09-23 11:07:36 +08:00
|
|
|
}
|
2019-09-26 02:13:33 +08:00
|
|
|
genHoists(ast.hoists, context)
|
2019-09-23 04:50:57 +08:00
|
|
|
push(`return `)
|
|
|
|
} else {
|
2019-09-23 11:07:36 +08:00
|
|
|
// generate import statements for helpers
|
2019-09-26 07:17:45 +08:00
|
|
|
if (hasImports) {
|
2019-09-26 10:29:37 +08:00
|
|
|
push(`import { ${ast.imports.join(', ')} } from "vue"\n`)
|
2019-09-23 11:07:36 +08:00
|
|
|
}
|
2019-09-26 02:13:33 +08:00
|
|
|
genHoists(ast.hoists, context)
|
2019-09-23 04:50:57 +08:00
|
|
|
push(`export default `)
|
|
|
|
}
|
2019-09-26 03:09:58 +08:00
|
|
|
|
|
|
|
// enter render function
|
2019-09-23 04:50:57 +08:00
|
|
|
push(`function render() {`)
|
2019-09-23 14:52:54 +08:00
|
|
|
indent()
|
2019-09-26 03:09:58 +08:00
|
|
|
|
2019-09-26 10:29:37 +08:00
|
|
|
if (useWithBlock) {
|
2019-09-26 03:09:58 +08:00
|
|
|
push(`with (this) {`)
|
|
|
|
indent()
|
|
|
|
// function mode const declarations should be inside with block
|
2019-09-26 07:17:45 +08:00
|
|
|
// also they should be renamed to avoid collision with user properties
|
2019-09-26 10:29:37 +08:00
|
|
|
if (hasImports) {
|
2019-09-26 07:17:45 +08:00
|
|
|
push(`const { ${ast.imports.map(n => `${n}: _${n}`).join(', ')} } = _Vue`)
|
2019-09-26 03:09:58 +08:00
|
|
|
newline()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
push(`const _ctx = this`)
|
|
|
|
newline()
|
|
|
|
}
|
|
|
|
|
2019-09-23 11:07:36 +08:00
|
|
|
// generate asset resolution statements
|
2019-09-23 14:52:54 +08:00
|
|
|
if (ast.statements.length) {
|
|
|
|
ast.statements.forEach(s => {
|
|
|
|
push(s)
|
|
|
|
newline()
|
|
|
|
})
|
|
|
|
newline()
|
|
|
|
}
|
2019-09-26 03:09:58 +08:00
|
|
|
|
|
|
|
// generate the VNode tree expression
|
2019-09-23 04:50:57 +08:00
|
|
|
push(`return `)
|
2019-09-26 07:17:45 +08:00
|
|
|
genChildren(ast.children, context, true)
|
2019-09-26 10:29:37 +08:00
|
|
|
|
|
|
|
if (useWithBlock) {
|
2019-09-23 04:50:57 +08:00
|
|
|
deindent()
|
|
|
|
push(`}`)
|
|
|
|
}
|
2019-09-26 10:29:37 +08:00
|
|
|
|
2019-09-23 04:50:57 +08:00
|
|
|
deindent()
|
|
|
|
push(`}`)
|
|
|
|
return {
|
2019-09-25 21:23:34 +08:00
|
|
|
ast,
|
2019-09-23 04:50:57 +08:00
|
|
|
code: context.code,
|
|
|
|
map: context.map ? context.map.toJSON() : undefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-26 02:13:33 +08:00
|
|
|
function genHoists(hoists: JSChildNode[], context: CodegenContext) {
|
|
|
|
hoists.forEach((exp, i) => {
|
|
|
|
context.push(`const _hoisted_${i + 1} = `)
|
|
|
|
genNode(exp, context)
|
|
|
|
context.newline()
|
|
|
|
})
|
|
|
|
context.newline()
|
|
|
|
}
|
|
|
|
|
2019-09-24 03:36:30 +08:00
|
|
|
// This will generate a single vnode call if:
|
2019-09-26 07:17:45 +08:00
|
|
|
// - The target position explicitly allows a single node (root, if, for)
|
2019-09-28 08:29:20 +08:00
|
|
|
// - The list has length === 1, AND The only child is a:
|
|
|
|
// - text
|
|
|
|
// - expression
|
|
|
|
// - <slot> outlet, which always produces an array
|
2019-09-24 03:36:30 +08:00
|
|
|
function genChildren(
|
|
|
|
children: ChildNode[],
|
|
|
|
context: CodegenContext,
|
2019-09-26 07:17:45 +08:00
|
|
|
allowSingle: boolean = false
|
2019-09-24 03:36:30 +08:00
|
|
|
) {
|
2019-09-25 03:49:02 +08:00
|
|
|
if (!children.length) {
|
|
|
|
return context.push(`null`)
|
|
|
|
}
|
2019-09-24 03:36:30 +08:00
|
|
|
const child = children[0]
|
2019-09-28 08:29:20 +08:00
|
|
|
const type = child.type
|
2019-09-24 03:36:30 +08:00
|
|
|
if (
|
|
|
|
children.length === 1 &&
|
2019-09-26 07:17:45 +08:00
|
|
|
(allowSingle ||
|
2019-09-28 08:29:20 +08:00
|
|
|
type === NodeTypes.TEXT ||
|
|
|
|
type === NodeTypes.INTERPOLATION ||
|
|
|
|
(type === NodeTypes.ELEMENT &&
|
|
|
|
(child as ElementNode).tagType === ElementTypes.SLOT))
|
2019-09-24 03:36:30 +08:00
|
|
|
) {
|
|
|
|
genNode(child, context)
|
2019-09-23 04:50:57 +08:00
|
|
|
} else {
|
|
|
|
genNodeListAsArray(children, context)
|
2019-09-20 11:05:51 +08:00
|
|
|
}
|
2019-09-23 04:50:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function genNodeListAsArray(
|
|
|
|
nodes: (string | CodegenNode | ChildNode[])[],
|
|
|
|
context: CodegenContext
|
|
|
|
) {
|
|
|
|
const multilines = nodes.length > 1
|
|
|
|
context.push(`[`)
|
|
|
|
multilines && context.indent()
|
|
|
|
genNodeList(nodes, context, multilines)
|
|
|
|
multilines && context.deindent()
|
2019-09-20 11:05:51 +08:00
|
|
|
context.push(`]`)
|
|
|
|
}
|
|
|
|
|
2019-09-23 04:50:57 +08:00
|
|
|
function genNodeList(
|
|
|
|
nodes: (string | CodegenNode | ChildNode[])[],
|
|
|
|
context: CodegenContext,
|
|
|
|
multilines: boolean = false
|
|
|
|
) {
|
|
|
|
const { push, newline } = context
|
|
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
|
|
const node = nodes[i]
|
|
|
|
if (isString(node)) {
|
|
|
|
push(node)
|
|
|
|
} else if (isArray(node)) {
|
2019-09-24 03:36:30 +08:00
|
|
|
genChildren(node, context)
|
2019-09-23 04:50:57 +08:00
|
|
|
} else {
|
|
|
|
genNode(node, context)
|
|
|
|
}
|
|
|
|
if (i < nodes.length - 1) {
|
|
|
|
if (multilines) {
|
|
|
|
push(',')
|
|
|
|
newline()
|
|
|
|
} else {
|
|
|
|
push(', ')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function genNode(node: CodegenNode, context: CodegenContext) {
|
2019-09-20 11:05:51 +08:00
|
|
|
switch (node.type) {
|
|
|
|
case NodeTypes.ELEMENT:
|
|
|
|
genElement(node, context)
|
|
|
|
break
|
|
|
|
case NodeTypes.TEXT:
|
|
|
|
genText(node, context)
|
|
|
|
break
|
2019-09-27 23:42:02 +08:00
|
|
|
case NodeTypes.SIMPLE_EXPRESSION:
|
2019-09-20 11:05:51 +08:00
|
|
|
genExpression(node, context)
|
|
|
|
break
|
2019-09-27 23:42:02 +08:00
|
|
|
case NodeTypes.INTERPOLATION:
|
|
|
|
genInterpolation(node, context)
|
|
|
|
break
|
|
|
|
case NodeTypes.COMPOUND_EXPRESSION:
|
|
|
|
genCompoundExpression(node, context)
|
|
|
|
break
|
2019-09-20 11:05:51 +08:00
|
|
|
case NodeTypes.COMMENT:
|
|
|
|
genComment(node, context)
|
|
|
|
break
|
|
|
|
case NodeTypes.IF:
|
|
|
|
genIf(node, context)
|
|
|
|
break
|
|
|
|
case NodeTypes.FOR:
|
|
|
|
genFor(node, context)
|
|
|
|
break
|
2019-09-23 04:50:57 +08:00
|
|
|
case NodeTypes.JS_CALL_EXPRESSION:
|
|
|
|
genCallExpression(node, context)
|
|
|
|
break
|
|
|
|
case NodeTypes.JS_OBJECT_EXPRESSION:
|
|
|
|
genObjectExpression(node, context)
|
|
|
|
break
|
|
|
|
case NodeTypes.JS_ARRAY_EXPRESSION:
|
|
|
|
genArrayExpression(node, context)
|
2019-09-23 14:52:54 +08:00
|
|
|
break
|
2019-09-28 10:25:32 +08:00
|
|
|
case NodeTypes.JS_SLOT_FUNCTION:
|
|
|
|
genSlotFunction(node, context)
|
|
|
|
break
|
2019-09-23 14:52:54 +08:00
|
|
|
default:
|
2019-09-28 10:25:32 +08:00
|
|
|
/* istanbul ignore if */
|
|
|
|
if (__DEV__) {
|
2019-09-23 14:52:54 +08:00
|
|
|
assert(false, `unhandled codegen node type: ${(node as any).type}`)
|
2019-09-28 10:25:32 +08:00
|
|
|
// make sure we exhaust all possible types
|
|
|
|
const exhaustiveCheck: never = node
|
|
|
|
return exhaustiveCheck
|
|
|
|
}
|
2019-09-20 11:05:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-23 04:50:57 +08:00
|
|
|
function genElement(node: ElementNode, context: CodegenContext) {
|
|
|
|
__DEV__ &&
|
|
|
|
assert(
|
|
|
|
node.codegenNode != null,
|
|
|
|
`AST is not transformed for codegen. ` +
|
|
|
|
`Apply appropriate transforms first.`
|
|
|
|
)
|
|
|
|
genCallExpression(node.codegenNode!, context, false)
|
|
|
|
}
|
2019-09-20 11:05:51 +08:00
|
|
|
|
2019-09-27 23:42:02 +08:00
|
|
|
function genText(
|
|
|
|
node: TextNode | SimpleExpressionNode,
|
|
|
|
context: CodegenContext
|
|
|
|
) {
|
2019-09-20 11:05:51 +08:00
|
|
|
context.push(JSON.stringify(node.content), node)
|
|
|
|
}
|
|
|
|
|
2019-09-27 23:42:02 +08:00
|
|
|
function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
|
|
|
|
const { content, isStatic } = node
|
|
|
|
context.push(isStatic ? JSON.stringify(content) : content, node)
|
|
|
|
}
|
|
|
|
|
|
|
|
function genInterpolation(node: InterpolationNode, context: CodegenContext) {
|
2019-09-26 07:17:45 +08:00
|
|
|
const { push, helper } = context
|
2019-09-27 23:42:02 +08:00
|
|
|
push(`${helper(TO_STRING)}(`)
|
|
|
|
genNode(node.content, context)
|
|
|
|
push(`)`)
|
|
|
|
}
|
|
|
|
|
|
|
|
function genCompoundExpression(
|
|
|
|
node: CompoundExpressionNode,
|
|
|
|
context: CodegenContext
|
|
|
|
) {
|
|
|
|
for (let i = 0; i < node.children!.length; i++) {
|
|
|
|
const child = node.children![i]
|
|
|
|
if (isString(child)) {
|
|
|
|
context.push(child)
|
|
|
|
} else {
|
|
|
|
genExpression(child, context)
|
|
|
|
}
|
2019-09-23 14:52:54 +08:00
|
|
|
}
|
2019-09-23 04:50:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function genExpressionAsPropertyKey(
|
|
|
|
node: ExpressionNode,
|
|
|
|
context: CodegenContext
|
|
|
|
) {
|
2019-09-24 09:22:52 +08:00
|
|
|
const { push } = context
|
2019-09-27 23:42:02 +08:00
|
|
|
if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
|
2019-09-24 09:22:52 +08:00
|
|
|
push(`[`)
|
|
|
|
genCompoundExpression(node, context)
|
|
|
|
push(`]`)
|
2019-09-27 23:42:02 +08:00
|
|
|
} else if (node.isStatic) {
|
2019-09-23 04:50:57 +08:00
|
|
|
// only quote keys if necessary
|
2019-09-27 23:42:02 +08:00
|
|
|
const text = isSimpleIdentifier(node.content)
|
|
|
|
? node.content
|
|
|
|
: JSON.stringify(node.content)
|
2019-09-24 09:22:52 +08:00
|
|
|
push(text, node)
|
2019-09-23 04:50:57 +08:00
|
|
|
} else {
|
2019-09-27 23:42:02 +08:00
|
|
|
push(`[${node.content}]`, node)
|
2019-09-23 14:52:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-20 11:05:51 +08:00
|
|
|
function genComment(node: CommentNode, context: CodegenContext) {
|
2019-09-25 03:49:02 +08:00
|
|
|
if (__DEV__) {
|
2019-09-26 07:17:45 +08:00
|
|
|
const { push, helper } = context
|
|
|
|
push(
|
|
|
|
`${helper(CREATE_VNODE)}(${helper(COMMENT)}, 0, ${JSON.stringify(
|
|
|
|
node.content
|
|
|
|
)})`,
|
2019-09-25 03:49:02 +08:00
|
|
|
node
|
|
|
|
)
|
|
|
|
}
|
2019-09-20 11:05:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// control flow
|
2019-09-23 04:50:57 +08:00
|
|
|
function genIf(node: IfNode, context: CodegenContext) {
|
|
|
|
genIfBranch(node.branches[0], node.branches, 1, context)
|
|
|
|
}
|
2019-09-20 11:05:51 +08:00
|
|
|
|
2019-09-23 04:50:57 +08:00
|
|
|
function genIfBranch(
|
2019-09-26 07:17:45 +08:00
|
|
|
{ condition, children }: IfBranchNode,
|
2019-09-23 04:50:57 +08:00
|
|
|
branches: IfBranchNode[],
|
|
|
|
nextIndex: number,
|
|
|
|
context: CodegenContext
|
|
|
|
) {
|
|
|
|
if (condition) {
|
|
|
|
// v-if or v-else-if
|
2019-09-23 09:10:22 +08:00
|
|
|
const { push, indent, deindent, newline } = context
|
2019-09-27 23:42:02 +08:00
|
|
|
if (condition.type === NodeTypes.SIMPLE_EXPRESSION) {
|
|
|
|
const needsQuote = !isSimpleIdentifier(condition.content)
|
|
|
|
needsQuote && push(`(`)
|
|
|
|
genExpression(condition, context)
|
|
|
|
needsQuote && push(`)`)
|
|
|
|
} else {
|
|
|
|
genCompoundExpression(condition, context)
|
|
|
|
}
|
2019-09-23 05:02:32 +08:00
|
|
|
indent()
|
2019-09-23 09:10:22 +08:00
|
|
|
context.indentLevel++
|
2019-09-23 05:02:32 +08:00
|
|
|
push(`? `)
|
2019-09-26 07:17:45 +08:00
|
|
|
genChildren(children, context, true)
|
2019-09-23 09:10:22 +08:00
|
|
|
context.indentLevel--
|
2019-09-23 05:02:32 +08:00
|
|
|
newline()
|
|
|
|
push(`: `)
|
2019-09-23 04:50:57 +08:00
|
|
|
if (nextIndex < branches.length) {
|
|
|
|
genIfBranch(branches[nextIndex], branches, nextIndex + 1, context)
|
|
|
|
} else {
|
|
|
|
context.push(`null`)
|
|
|
|
}
|
2019-09-23 05:06:46 +08:00
|
|
|
deindent(true /* without newline */)
|
2019-09-23 04:50:57 +08:00
|
|
|
} else {
|
|
|
|
// v-else
|
|
|
|
__DEV__ && assert(nextIndex === branches.length)
|
2019-09-26 07:17:45 +08:00
|
|
|
genChildren(children, context, true)
|
2019-09-23 04:50:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function genFor(node: ForNode, context: CodegenContext) {
|
2019-09-26 07:17:45 +08:00
|
|
|
const { push, helper, indent, deindent } = context
|
2019-09-23 04:50:57 +08:00
|
|
|
const { source, keyAlias, valueAlias, objectIndexAlias, children } = node
|
2019-09-27 02:55:53 +08:00
|
|
|
push(`${helper(RENDER_LIST)}(`, node, true)
|
2019-09-27 23:42:02 +08:00
|
|
|
genNode(source, context)
|
2019-09-23 09:10:22 +08:00
|
|
|
push(`, (`)
|
2019-09-23 04:50:57 +08:00
|
|
|
if (valueAlias) {
|
2019-09-24 01:25:18 +08:00
|
|
|
genExpression(valueAlias, context)
|
2019-09-23 04:50:57 +08:00
|
|
|
}
|
|
|
|
if (keyAlias) {
|
|
|
|
if (!valueAlias) {
|
2019-09-25 04:35:01 +08:00
|
|
|
push(`__value`)
|
2019-09-23 04:50:57 +08:00
|
|
|
}
|
|
|
|
push(`, `)
|
2019-09-24 01:25:18 +08:00
|
|
|
genExpression(keyAlias, context)
|
2019-09-23 04:50:57 +08:00
|
|
|
}
|
|
|
|
if (objectIndexAlias) {
|
|
|
|
if (!keyAlias) {
|
|
|
|
if (!valueAlias) {
|
2019-09-25 04:35:01 +08:00
|
|
|
push(`__value, __key`)
|
2019-09-23 09:10:22 +08:00
|
|
|
} else {
|
2019-09-25 04:35:01 +08:00
|
|
|
push(`, __key`)
|
2019-09-23 04:50:57 +08:00
|
|
|
}
|
|
|
|
}
|
2019-09-23 09:10:22 +08:00
|
|
|
push(`, `)
|
2019-09-24 01:25:18 +08:00
|
|
|
genExpression(objectIndexAlias, context)
|
2019-09-23 04:50:57 +08:00
|
|
|
}
|
2019-09-26 07:17:45 +08:00
|
|
|
push(`) => {`)
|
|
|
|
indent()
|
|
|
|
push(`return `)
|
|
|
|
genChildren(children, context, true)
|
|
|
|
deindent()
|
|
|
|
push(`})`)
|
2019-09-23 04:50:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// JavaScript
|
|
|
|
function genCallExpression(
|
|
|
|
node: CallExpression,
|
|
|
|
context: CodegenContext,
|
2019-09-23 09:10:22 +08:00
|
|
|
multilines = node.arguments.length > 2
|
2019-09-23 04:50:57 +08:00
|
|
|
) {
|
2019-09-28 08:29:20 +08:00
|
|
|
if (isString(node.callee)) {
|
|
|
|
context.push(node.callee + `(`, node, true)
|
|
|
|
} else {
|
|
|
|
genNode(node.callee, context)
|
|
|
|
context.push(`(`)
|
|
|
|
}
|
2019-09-23 04:50:57 +08:00
|
|
|
multilines && context.indent()
|
|
|
|
genNodeList(node.arguments, context, multilines)
|
|
|
|
multilines && context.deindent()
|
|
|
|
context.push(`)`)
|
|
|
|
}
|
|
|
|
|
|
|
|
function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
|
2019-09-27 21:25:52 +08:00
|
|
|
const { push, indent, deindent, newline, resetMapping } = context
|
2019-09-23 04:50:57 +08:00
|
|
|
const { properties } = node
|
|
|
|
const multilines = properties.length > 1
|
2019-09-27 02:55:53 +08:00
|
|
|
push(multilines ? `{` : `{ `)
|
2019-09-23 04:50:57 +08:00
|
|
|
multilines && indent()
|
|
|
|
for (let i = 0; i < properties.length; i++) {
|
2019-09-27 21:19:03 +08:00
|
|
|
const { key, value, loc } = properties[i]
|
2019-09-27 21:25:52 +08:00
|
|
|
resetMapping(loc) // reset source mapping for every property.
|
2019-09-23 04:50:57 +08:00
|
|
|
// key
|
|
|
|
genExpressionAsPropertyKey(key, context)
|
|
|
|
push(`: `)
|
|
|
|
// value
|
2019-09-26 00:39:46 +08:00
|
|
|
genNode(value, context)
|
2019-09-23 04:50:57 +08:00
|
|
|
if (i < properties.length - 1) {
|
2019-09-25 04:35:01 +08:00
|
|
|
// will only reach this if it's multilines
|
|
|
|
push(`,`)
|
|
|
|
newline()
|
2019-09-23 04:50:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
multilines && deindent()
|
2019-09-23 10:19:42 +08:00
|
|
|
push(multilines ? `}` : ` }`)
|
2019-09-23 04:50:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
|
|
|
|
genNodeListAsArray(node.elements, context)
|
|
|
|
}
|
2019-09-28 10:25:32 +08:00
|
|
|
|
|
|
|
function genSlotFunction(
|
|
|
|
node: SlotFunctionExpression,
|
|
|
|
context: CodegenContext
|
|
|
|
) {
|
|
|
|
context.push(`(`, node)
|
|
|
|
if (node.params) genNode(node.params, context)
|
|
|
|
context.push(`) => `)
|
|
|
|
// pre-normalized slots should always return arrays
|
|
|
|
genNodeListAsArray(node.returns, context)
|
|
|
|
}
|