vue3-yuanma/packages/compiler-core/src/codegen.ts
2019-09-21 15:47:26 -04:00

173 lines
4.1 KiB
TypeScript

import {
RootNode,
ChildNode,
ElementNode,
IfNode,
ForNode,
TextNode,
CommentNode,
ExpressionNode,
NodeTypes
} from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map'
import { advancePositionWithMutation } from './utils'
export interface CodegenOptions {
// Assume ES module environment. If true, will generate import statements for
// runtime helpers; otherwise will grab the helpers from global `Vue`.
// default: false
module?: 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
indent: number
imports: Set<string>
knownIdentifiers: Set<string>
map?: SourceMapGenerator
push(generatedCode: string, astNode?: ChildNode): void
}
export function generate(
ast: RootNode,
options: CodegenOptions = {}
): CodegenResult {
const context = createCodegenContext(ast, options)
if (context.module) {
// TODO inject import statements on RootNode
context.push(`export function render() {\n`)
context.indent++
context.push(` return `)
}
if (ast.children.length === 1) {
genNode(ast.children[0], context)
} else {
genChildren(ast.children, context)
}
if (context.module) {
context.indent--
context.push(`\n}`)
}
return {
code: context.code,
map: context.map ? context.map.toJSON() : undefined
}
}
function createCodegenContext(
ast: RootNode,
{ module = false, filename = `template.vue.html` }: CodegenOptions
): CodegenContext {
const context: CodegenContext = {
module,
filename,
source: ast.loc.source,
code: ``,
column: 1,
line: 1,
offset: 0,
indent: 0,
imports: new Set(),
knownIdentifiers: new Set(),
// lazy require source-map implementation, only in non-browser builds!
map: __BROWSER__
? undefined
: new (require('source-map')).SourceMapGenerator(),
push(generatedCode, node) {
// TODO handle indent
context.code += generatedCode
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,
generatedCode,
generatedCode.length
)
}
}
}
if (!__BROWSER__) {
context.map!.setSourceContent(filename, context.source)
}
return context
}
function genChildren(children: ChildNode[], context: CodegenContext) {
context.push(`[`)
for (let i = 0; i < children.length; i++) {
genNode(children[i], context)
if (i < children.length - 1) context.push(', ')
}
context.push(`]`)
}
function genNode(node: ChildNode, 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
}
}
function genElement(node: ElementNode, context: CodegenContext) {}
function genText(node: TextNode | ExpressionNode, context: CodegenContext) {
context.push(JSON.stringify(node.content), node)
}
function genExpression(node: ExpressionNode, context: CodegenContext) {
if (!__BROWSER__) {
// TODO parse expression content and rewrite identifiers
}
context.push(node.content, node)
}
function genComment(node: CommentNode, context: CodegenContext) {
context.push(`<!--${node.content}-->`, node)
}
// control flow
function genIf(node: IfNode, context: CodegenContext) {}
function genFor(node: ForNode, context: CodegenContext) {}