| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397 |
- import {
- RootNode,
- ChildNode,
- ElementNode,
- IfNode,
- ForNode,
- TextNode,
- CommentNode,
- ExpressionNode,
- NodeTypes,
- JSChildNode,
- CallExpression,
- ArrayExpression,
- ObjectExpression,
- IfBranchNode
- } from './ast'
- import { SourceMapGenerator, RawSourceMap } from 'source-map'
- import { advancePositionWithMutation, assert } from './utils'
- import { isString, isArray } from '@vue/shared'
- import { RENDER_LIST } from './runtimeConstants'
- type CodegenNode = ChildNode | JSChildNode
- export interface CodegenOptions {
- // will generate import statements for
- // runtime helpers; otherwise will grab the helpers from global `Vue`.
- // default: false
- mode?: 'module' | 'function'
- useWith?: boolean
- // Filename for source map generation.
- filename?: string
- }
- export interface CodegenResult {
- code: string
- map?: RawSourceMap
- }
- export interface CodegenContext extends Required<CodegenOptions> {
- source: string
- code: string
- line: number
- column: number
- offset: number
- indentLevel: number
- map?: SourceMapGenerator
- push(code: string, node?: CodegenNode): void
- indent(): void
- deindent(withoutNewLine?: boolean): void
- newline(): void
- }
- function createCodegenContext(
- ast: RootNode,
- {
- mode = 'function',
- useWith = true,
- filename = `template.vue.html`
- }: CodegenOptions
- ): CodegenContext {
- const context: CodegenContext = {
- mode,
- useWith,
- filename,
- source: ast.loc.source,
- code: ``,
- column: 1,
- line: 1,
- offset: 0,
- indentLevel: 0,
- // lazy require source-map implementation, only in non-browser builds!
- map: __BROWSER__
- ? undefined
- : new (require('source-map')).SourceMapGenerator(),
- push(code, node?: CodegenNode) {
- context.code += code
- if (context.map) {
- if (node) {
- context.map.addMapping({
- source: context.filename,
- original: {
- line: node.loc.start.line,
- column: node.loc.start.column - 1 // source-map column is 0 based
- },
- generated: {
- line: context.line,
- column: context.column - 1
- }
- })
- }
- advancePositionWithMutation(context, code, code.length)
- }
- },
- indent() {
- newline(++context.indentLevel)
- },
- deindent(withoutNewLine = false) {
- if (withoutNewLine) {
- --context.indentLevel
- } else {
- newline(--context.indentLevel)
- }
- },
- newline() {
- newline(context.indentLevel)
- }
- }
- const newline = (n: number) => context.push('\n' + ` `.repeat(n))
- if (!__BROWSER__) {
- context.map!.setSourceContent(filename, context.source)
- }
- return context
- }
- export function generate(
- ast: RootNode,
- options: CodegenOptions = {}
- ): CodegenResult {
- const context = createCodegenContext(ast, options)
- const { mode, push, useWith, indent, deindent } = context
- const imports = ast.imports.join(', ')
- if (mode === 'function') {
- // generate const declarations for helpers
- if (imports) {
- push(`const { ${imports} } = Vue\n\n`)
- }
- push(`return `)
- } else {
- // generate import statements for helpers
- if (imports) {
- push(`import { ${imports} } from 'vue'\n\n`)
- }
- push(`export default `)
- }
- push(`function render() {`)
- // generate asset resolution statements
- ast.statements.forEach(s => push(s + `\n`))
- if (useWith) {
- indent()
- push(`with (this) {`)
- }
- indent()
- push(`return `)
- genChildren(ast.children, context)
- if (useWith) {
- deindent()
- push(`}`)
- }
- deindent()
- push(`}`)
- return {
- code: context.code,
- map: context.map ? context.map.toJSON() : undefined
- }
- }
- // This will generate a single vnode call if the list has length === 1.
- function genChildren(children: ChildNode[], context: CodegenContext) {
- if (children.length === 1) {
- genNode(children[0], context)
- } else {
- genNodeListAsArray(children, context)
- }
- }
- 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()
- context.push(`]`)
- }
- 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)) {
- // plain code string
- // note not adding quotes here because this can be any code,
- // not just plain strings.
- push(node)
- } else if (isArray(node)) {
- // child VNodes in a h() call
- // not using genChildren here because we want them to always be an array
- genNodeListAsArray(node, context)
- } else {
- genNode(node, context)
- }
- if (i < nodes.length - 1) {
- if (multilines) {
- push(',')
- newline()
- } else {
- push(', ')
- }
- }
- }
- }
- function genNode(node: CodegenNode, context: CodegenContext) {
- switch (node.type) {
- case NodeTypes.ELEMENT:
- genElement(node, context)
- break
- case NodeTypes.TEXT:
- genText(node, context)
- break
- case NodeTypes.EXPRESSION:
- genExpression(node, context)
- break
- case NodeTypes.COMMENT:
- genComment(node, context)
- break
- case NodeTypes.IF:
- genIf(node, context)
- break
- case NodeTypes.FOR:
- genFor(node, context)
- break
- 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)
- }
- }
- 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)
- }
- function genText(node: TextNode | ExpressionNode, context: CodegenContext) {
- context.push(JSON.stringify(node.content), node)
- }
- function genExpression(node: ExpressionNode, context: CodegenContext) {
- // if (node.codegenNode) {
- // TODO handle transformed expression
- // }
- const text = node.isStatic ? JSON.stringify(node.content) : node.content
- context.push(text, node)
- }
- function genExpressionAsPropertyKey(
- node: ExpressionNode,
- context: CodegenContext
- ) {
- // if (node.codegenNode) {
- // TODO handle transformed expression
- // }
- if (node.isStatic) {
- // only quote keys if necessary
- const text = /^\d|[^\w]/.test(node.content)
- ? JSON.stringify(node.content)
- : node.content
- context.push(text, node)
- } else {
- context.push(`[${node.content}]`, node)
- }
- }
- function genComment(node: CommentNode, context: CodegenContext) {
- context.push(`<!--${node.content}-->`, node)
- }
- // control flow
- function genIf(node: IfNode, context: CodegenContext) {
- genIfBranch(node.branches[0], node.branches, 1, context)
- }
- function genIfBranch(
- { condition, children }: IfBranchNode,
- branches: IfBranchNode[],
- nextIndex: number,
- context: CodegenContext
- ) {
- if (condition) {
- // v-if or v-else-if
- const { push, indent, deindent, newline } = context
- push(`(${condition.content})`, condition)
- indent()
- context.indentLevel++
- push(`? `)
- genChildren(children, context)
- context.indentLevel--
- newline()
- push(`: `)
- if (nextIndex < branches.length) {
- genIfBranch(branches[nextIndex], branches, nextIndex + 1, context)
- } else {
- context.push(`null`)
- }
- deindent(true /* without newline */)
- } else {
- // v-else
- __DEV__ && assert(nextIndex === branches.length)
- genChildren(children, context)
- }
- }
- function genFor(node: ForNode, context: CodegenContext) {
- const { push } = context
- const { source, keyAlias, valueAlias, objectIndexAlias, children } = node
- push(`${RENDER_LIST}(`, node)
- genExpression(source, context)
- push(`, (`)
- if (valueAlias) {
- // not using genExpression here because these aliases can only be code
- // that is valid in the function argument position, so the parse rule can
- // be off and they don't need identifier prefixing anyway.
- push(valueAlias.content, valueAlias)
- }
- if (keyAlias) {
- if (!valueAlias) {
- push(`_`)
- }
- push(`, `)
- push(keyAlias.content, keyAlias)
- }
- if (objectIndexAlias) {
- if (!keyAlias) {
- if (!valueAlias) {
- push(`_, __`)
- } else {
- push(`__`)
- }
- }
- push(`, `)
- push(objectIndexAlias.content, objectIndexAlias)
- }
- push(`) => `)
- genChildren(children, context)
- push(`)`)
- }
- // JavaScript
- function genCallExpression(
- node: CallExpression,
- context: CodegenContext,
- multilines = node.arguments.length > 2
- ) {
- context.push(node.callee + `(`, node)
- multilines && context.indent()
- genNodeList(node.arguments, context, multilines)
- multilines && context.deindent()
- context.push(`)`)
- }
- function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
- const { push, indent, deindent, newline } = context
- const { properties } = node
- const multilines = properties.length > 1
- push(multilines ? `{` : `{ `, node)
- multilines && indent()
- for (let i = 0; i < properties.length; i++) {
- const { key, value } = properties[i]
- // key
- genExpressionAsPropertyKey(key, context)
- push(`: `)
- // value
- genExpression(value, context)
- if (i < properties.length - 1) {
- if (multilines) {
- push(`,`)
- newline()
- } else {
- push(`, `)
- }
- }
- }
- multilines && deindent()
- push(multilines ? `}` : ` }`)
- }
- function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
- genNodeListAsArray(node.elements, context)
- }
|