test: tests for codegen
This commit is contained in:
@@ -117,12 +117,14 @@ export interface ExpressionNode extends Node {
|
||||
export interface IfNode extends Node {
|
||||
type: NodeTypes.IF
|
||||
branches: IfBranchNode[]
|
||||
isRoot: boolean
|
||||
}
|
||||
|
||||
export interface IfBranchNode extends Node {
|
||||
type: NodeTypes.IF_BRANCH
|
||||
condition: ExpressionNode | undefined // else
|
||||
children: ChildNode[]
|
||||
isRoot: boolean
|
||||
}
|
||||
|
||||
export interface ForNode extends Node {
|
||||
@@ -203,14 +205,15 @@ export function createObjectProperty(
|
||||
export function createExpression(
|
||||
content: string,
|
||||
isStatic: boolean,
|
||||
loc: SourceLocation
|
||||
loc: SourceLocation,
|
||||
isInterpolation = false
|
||||
): ExpressionNode {
|
||||
return {
|
||||
type: NodeTypes.EXPRESSION,
|
||||
loc,
|
||||
content,
|
||||
isStatic,
|
||||
isInterpolation: false
|
||||
isInterpolation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,17 +17,33 @@ import {
|
||||
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||
import { advancePositionWithMutation, assert } from './utils'
|
||||
import { isString, isArray } from '@vue/shared'
|
||||
import { RENDER_LIST, TO_STRING } from './runtimeConstants'
|
||||
import {
|
||||
RENDER_LIST,
|
||||
TO_STRING,
|
||||
CREATE_VNODE,
|
||||
COMMENT
|
||||
} 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
|
||||
// - 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'
|
||||
mode?: 'module' | 'function'
|
||||
// Prefix suitable identifiers with _ctx.
|
||||
// If this option is false, the generated code will be wrapped in a
|
||||
// `with (this) { ... }` block.
|
||||
// Default: false
|
||||
prefixIdentifiers?: boolean
|
||||
// Generate source map?
|
||||
// Default: false
|
||||
sourceMap?: boolean
|
||||
// Filename for source map generation.
|
||||
// Default: `template.vue.html`
|
||||
filename?: string
|
||||
}
|
||||
|
||||
@@ -55,12 +71,14 @@ function createCodegenContext(
|
||||
{
|
||||
mode = 'function',
|
||||
prefixIdentifiers = false,
|
||||
sourceMap = false,
|
||||
filename = `template.vue.html`
|
||||
}: CodegenOptions
|
||||
): CodegenContext {
|
||||
const context: CodegenContext = {
|
||||
mode,
|
||||
prefixIdentifiers,
|
||||
sourceMap,
|
||||
filename,
|
||||
source: ast.loc.source,
|
||||
code: ``,
|
||||
@@ -70,9 +88,10 @@ function createCodegenContext(
|
||||
indentLevel: 0,
|
||||
|
||||
// lazy require source-map implementation, only in non-browser builds!
|
||||
map: __BROWSER__
|
||||
? undefined
|
||||
: new (require('source-map')).SourceMapGenerator(),
|
||||
map:
|
||||
__BROWSER__ || !sourceMap
|
||||
? undefined
|
||||
: new (require('source-map')).SourceMapGenerator(),
|
||||
|
||||
push(code, node?: CodegenNode) {
|
||||
context.code += code
|
||||
@@ -108,8 +127,8 @@ function createCodegenContext(
|
||||
}
|
||||
}
|
||||
const newline = (n: number) => context.push('\n' + ` `.repeat(n))
|
||||
if (!__BROWSER__) {
|
||||
context.map!.setSourceContent(filename, context.source)
|
||||
if (!__BROWSER__ && context.map) {
|
||||
context.map.setSourceContent(filename, context.source)
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -174,6 +193,9 @@ function genChildren(
|
||||
context: CodegenContext,
|
||||
asRoot: boolean = false
|
||||
) {
|
||||
if (!children.length) {
|
||||
return context.push(`null`)
|
||||
}
|
||||
const child = children[0]
|
||||
if (
|
||||
children.length === 1 &&
|
||||
@@ -321,7 +343,12 @@ function genCompoundExpression(node: ExpressionNode, context: CodegenContext) {
|
||||
}
|
||||
|
||||
function genComment(node: CommentNode, context: CodegenContext) {
|
||||
context.push(`<!--${node.content}-->`, node)
|
||||
if (__DEV__) {
|
||||
context.push(
|
||||
`${CREATE_VNODE}(${COMMENT}, 0, ${JSON.stringify(node.content)})`,
|
||||
node
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// control flow
|
||||
@@ -330,7 +357,7 @@ function genIf(node: IfNode, context: CodegenContext) {
|
||||
}
|
||||
|
||||
function genIfBranch(
|
||||
{ condition, children }: IfBranchNode,
|
||||
{ condition, children, isRoot }: IfBranchNode,
|
||||
branches: IfBranchNode[],
|
||||
nextIndex: number,
|
||||
context: CodegenContext
|
||||
@@ -344,7 +371,7 @@ function genIfBranch(
|
||||
indent()
|
||||
context.indentLevel++
|
||||
push(`? `)
|
||||
genChildren(children, context)
|
||||
genChildren(children, context, isRoot)
|
||||
context.indentLevel--
|
||||
newline()
|
||||
push(`: `)
|
||||
@@ -357,7 +384,7 @@ function genIfBranch(
|
||||
} else {
|
||||
// v-else
|
||||
__DEV__ && assert(nextIndex === branches.length)
|
||||
genChildren(children, context)
|
||||
genChildren(children, context, isRoot)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Name mapping constants for runtime helpers that need to be imported in
|
||||
// generated code. Make sure these are correctly exported in the runtime!
|
||||
export const FRAGMENT = `Fragment`
|
||||
export const PORTAL = `Portal`
|
||||
export const COMMENT = `Comment`
|
||||
export const TEXT = `Text`
|
||||
export const SUSPENSE = `Suspense`
|
||||
export const CREATE_VNODE = `createVNode`
|
||||
export const RESOLVE_COMPONENT = `resolveComponent`
|
||||
export const RESOLVE_DIRECTIVE = `resolveDirective`
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from './ast'
|
||||
import { isString, isArray } from '@vue/shared'
|
||||
import { CompilerError, defaultOnError } from './errors'
|
||||
import { TO_STRING } from './runtimeConstants'
|
||||
import { TO_STRING, COMMENT, CREATE_VNODE } from './runtimeConstants'
|
||||
|
||||
// There are two types of transforms:
|
||||
//
|
||||
@@ -49,11 +49,11 @@ export interface TransformOptions {
|
||||
}
|
||||
|
||||
export interface TransformContext extends Required<TransformOptions> {
|
||||
root: RootNode
|
||||
imports: Set<string>
|
||||
statements: string[]
|
||||
identifiers: { [name: string]: number | undefined }
|
||||
parent: ParentNode
|
||||
ancestors: ParentNode[]
|
||||
childIndex: number
|
||||
currentNode: ChildNode | null
|
||||
replaceNode(node: ChildNode): void
|
||||
@@ -73,6 +73,7 @@ function createTransformContext(
|
||||
}: TransformOptions
|
||||
): TransformContext {
|
||||
const context: TransformContext = {
|
||||
root,
|
||||
imports: new Set(),
|
||||
statements: [],
|
||||
identifiers: {},
|
||||
@@ -81,7 +82,6 @@ function createTransformContext(
|
||||
directiveTransforms,
|
||||
onError,
|
||||
parent: root,
|
||||
ancestors: [],
|
||||
childIndex: 0,
|
||||
currentNode: null,
|
||||
replaceNode(node) {
|
||||
@@ -139,7 +139,6 @@ export function traverseChildren(
|
||||
parent: ParentNode,
|
||||
context: TransformContext
|
||||
) {
|
||||
const ancestors = context.ancestors.concat(parent)
|
||||
let i = 0
|
||||
const nodeRemoved = () => {
|
||||
i--
|
||||
@@ -149,7 +148,6 @@ export function traverseChildren(
|
||||
if (isString(child)) continue
|
||||
context.currentNode = child
|
||||
context.parent = parent
|
||||
context.ancestors = ancestors
|
||||
context.childIndex = i
|
||||
context.onNodeRemoved = nodeRemoved
|
||||
traverseNode(child, context)
|
||||
@@ -180,6 +178,12 @@ export function traverseNode(node: ChildNode, context: TransformContext) {
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case NodeTypes.COMMENT:
|
||||
context.imports.add(CREATE_VNODE)
|
||||
// inject import for the Comment symbol, which is needed for creating
|
||||
// comment nodes with `createVNode`
|
||||
context.imports.add(COMMENT)
|
||||
break
|
||||
case NodeTypes.EXPRESSION:
|
||||
// no need to traverse, but we need to inject toString helper
|
||||
if (node.isInterpolation) {
|
||||
|
||||
@@ -19,10 +19,14 @@ export const transformIf = createStructuralDirectiveTransform(
|
||||
processExpression(dir.exp, context)
|
||||
}
|
||||
if (dir.name === 'if') {
|
||||
// check if this v-if is root - so that in codegen we can avoid generating
|
||||
// arrays for each branch
|
||||
const isRoot = context.parent === context.root
|
||||
context.replaceNode({
|
||||
type: NodeTypes.IF,
|
||||
loc: node.loc,
|
||||
branches: [createIfBranch(node, dir)]
|
||||
branches: [createIfBranch(node, dir, isRoot)],
|
||||
isRoot
|
||||
})
|
||||
} else {
|
||||
// locate the adjacent v-if
|
||||
@@ -39,7 +43,7 @@ export const transformIf = createStructuralDirectiveTransform(
|
||||
if (sibling && sibling.type === NodeTypes.IF) {
|
||||
// move the node to the if node's branches
|
||||
context.removeNode()
|
||||
const branch = createIfBranch(node, dir)
|
||||
const branch = createIfBranch(node, dir, sibling.isRoot)
|
||||
if (__DEV__ && comments.length) {
|
||||
branch.children = [...comments, ...branch.children]
|
||||
}
|
||||
@@ -63,11 +67,16 @@ export const transformIf = createStructuralDirectiveTransform(
|
||||
}
|
||||
)
|
||||
|
||||
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
|
||||
function createIfBranch(
|
||||
node: ElementNode,
|
||||
dir: DirectiveNode,
|
||||
isRoot: boolean
|
||||
): IfBranchNode {
|
||||
return {
|
||||
type: NodeTypes.IF_BRANCH,
|
||||
loc: node.loc,
|
||||
condition: dir.name === 'else' ? undefined : dir.exp,
|
||||
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
|
||||
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node],
|
||||
isRoot
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user