feat(compiler): element codegen
This commit is contained in:
parent
40307d9642
commit
3a177a18d2
@ -8,15 +8,24 @@ describe('compiler: codegen', () => {
|
|||||||
const { code, map } = generate(ast, {
|
const { code, map } = generate(ast, {
|
||||||
filename: `foo.vue`
|
filename: `foo.vue`
|
||||||
})
|
})
|
||||||
expect(code).toBe(`["hello ", world]`)
|
expect(code).toBe(
|
||||||
|
`return function render() {
|
||||||
|
with (this) {
|
||||||
|
return [
|
||||||
|
"hello ",
|
||||||
|
world
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
|
||||||
expect(map!.sources).toEqual([`foo.vue`])
|
expect(map!.sources).toEqual([`foo.vue`])
|
||||||
expect(map!.sourcesContent).toEqual([source])
|
expect(map!.sourcesContent).toEqual([source])
|
||||||
|
|
||||||
const consumer = await new SourceMapConsumer(map as RawSourceMap)
|
const consumer = await new SourceMapConsumer(map as RawSourceMap)
|
||||||
const pos = consumer.originalPositionFor({
|
const pos = consumer.originalPositionFor({
|
||||||
line: 1,
|
line: 5,
|
||||||
column: 11
|
column: 6
|
||||||
})
|
})
|
||||||
expect(pos).toMatchObject({
|
expect(pos).toMatchObject({
|
||||||
line: 1,
|
line: 1,
|
||||||
|
@ -20,10 +20,10 @@ export const enum NodeTypes {
|
|||||||
IF_BRANCH,
|
IF_BRANCH,
|
||||||
FOR,
|
FOR,
|
||||||
// codegen
|
// codegen
|
||||||
CALL_EXPRESSION,
|
JS_CALL_EXPRESSION,
|
||||||
OBJECT_EXPRESSION,
|
JS_OBJECT_EXPRESSION,
|
||||||
PROPERTY,
|
JS_PROPERTY,
|
||||||
ARRAY_EXPRESSION
|
JS_ARRAY_EXPRESSION
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum ElementTypes {
|
export const enum ElementTypes {
|
||||||
@ -129,36 +129,35 @@ export interface ForNode extends Node {
|
|||||||
children: ChildNode[]
|
children: ChildNode[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// We also include a subset of JavaScript AST for code generation
|
// We also include a number of JavaScript AST nodes for code generation.
|
||||||
// purposes. The AST is intentioanlly minimal just to meet the exact needs of
|
// The AST is an intentioanlly minimal subset just to meet the exact needs of
|
||||||
// Vue render function generation.
|
// Vue render function generation.
|
||||||
type CodegenNode =
|
export type JSChildNode =
|
||||||
| string
|
|
||||||
| CallExpression
|
| CallExpression
|
||||||
| ObjectExpression
|
| ObjectExpression
|
||||||
| ArrayExpression
|
| ArrayExpression
|
||||||
| ExpressionNode
|
| ExpressionNode
|
||||||
|
|
||||||
export interface CallExpression extends Node {
|
export interface CallExpression extends Node {
|
||||||
type: NodeTypes.CALL_EXPRESSION
|
type: NodeTypes.JS_CALL_EXPRESSION
|
||||||
callee: string // can only be imported runtime helpers, so no source location
|
callee: string // can only be imported runtime helpers, so no source location
|
||||||
arguments: Array<CodegenNode | ChildNode[]>
|
arguments: Array<string | JSChildNode | ChildNode[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ObjectExpression extends Node {
|
export interface ObjectExpression extends Node {
|
||||||
type: NodeTypes.OBJECT_EXPRESSION
|
type: NodeTypes.JS_OBJECT_EXPRESSION
|
||||||
properties: Array<Property>
|
properties: Array<Property>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Property extends Node {
|
export interface Property extends Node {
|
||||||
type: NodeTypes.PROPERTY
|
type: NodeTypes.JS_PROPERTY
|
||||||
key: ExpressionNode
|
key: ExpressionNode
|
||||||
value: ExpressionNode
|
value: ExpressionNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ArrayExpression extends Node {
|
export interface ArrayExpression extends Node {
|
||||||
type: NodeTypes.ARRAY_EXPRESSION
|
type: NodeTypes.JS_ARRAY_EXPRESSION
|
||||||
elements: Array<CodegenNode>
|
elements: Array<string | JSChildNode>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createArrayExpression(
|
export function createArrayExpression(
|
||||||
@ -166,7 +165,7 @@ export function createArrayExpression(
|
|||||||
loc: SourceLocation
|
loc: SourceLocation
|
||||||
): ArrayExpression {
|
): ArrayExpression {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.ARRAY_EXPRESSION,
|
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||||
loc,
|
loc,
|
||||||
elements
|
elements
|
||||||
}
|
}
|
||||||
@ -177,7 +176,7 @@ export function createObjectExpression(
|
|||||||
loc: SourceLocation
|
loc: SourceLocation
|
||||||
): ObjectExpression {
|
): ObjectExpression {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.OBJECT_EXPRESSION,
|
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||||
loc,
|
loc,
|
||||||
properties
|
properties
|
||||||
}
|
}
|
||||||
@ -189,7 +188,7 @@ export function createObjectProperty(
|
|||||||
loc: SourceLocation
|
loc: SourceLocation
|
||||||
): Property {
|
): Property {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.PROPERTY,
|
type: NodeTypes.JS_PROPERTY,
|
||||||
loc,
|
loc,
|
||||||
key,
|
key,
|
||||||
value
|
value
|
||||||
@ -215,7 +214,7 @@ export function createCallExpression(
|
|||||||
loc: SourceLocation
|
loc: SourceLocation
|
||||||
): CallExpression {
|
): CallExpression {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
loc,
|
loc,
|
||||||
callee,
|
callee,
|
||||||
arguments: args
|
arguments: args
|
||||||
|
@ -7,16 +7,26 @@ import {
|
|||||||
TextNode,
|
TextNode,
|
||||||
CommentNode,
|
CommentNode,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
NodeTypes
|
NodeTypes,
|
||||||
|
JSChildNode,
|
||||||
|
CallExpression,
|
||||||
|
ArrayExpression,
|
||||||
|
ObjectExpression,
|
||||||
|
IfBranchNode
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||||
import { advancePositionWithMutation } from './utils'
|
import { advancePositionWithMutation, assert } from './utils'
|
||||||
|
import { isString, isArray } from '@vue/shared'
|
||||||
|
import { RENDER_LIST_HELPER } from './transforms/vFor'
|
||||||
|
|
||||||
|
type CodegenNode = ChildNode | JSChildNode
|
||||||
|
|
||||||
export interface CodegenOptions {
|
export interface CodegenOptions {
|
||||||
// Assume ES module environment. If true, will generate import statements for
|
// will generate import statements for
|
||||||
// runtime helpers; otherwise will grab the helpers from global `Vue`.
|
// runtime helpers; otherwise will grab the helpers from global `Vue`.
|
||||||
// default: false
|
// default: false
|
||||||
module?: boolean
|
mode?: 'module' | 'function'
|
||||||
|
useWith?: boolean
|
||||||
// Filename for source map generation.
|
// Filename for source map generation.
|
||||||
filename?: string
|
filename?: string
|
||||||
}
|
}
|
||||||
@ -32,52 +42,34 @@ export interface CodegenContext extends Required<CodegenOptions> {
|
|||||||
line: number
|
line: number
|
||||||
column: number
|
column: number
|
||||||
offset: number
|
offset: number
|
||||||
indent: number
|
indentLevel: number
|
||||||
imports: Set<string>
|
imports: Set<string>
|
||||||
knownIdentifiers: Set<string>
|
knownIdentifiers: Set<string>
|
||||||
map?: SourceMapGenerator
|
map?: SourceMapGenerator
|
||||||
push(generatedCode: string, astNode?: ChildNode): void
|
push(code: string, node?: CodegenNode): void
|
||||||
}
|
indent(): void
|
||||||
|
deindent(): void
|
||||||
export function generate(
|
newline(): void
|
||||||
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(
|
function createCodegenContext(
|
||||||
ast: RootNode,
|
ast: RootNode,
|
||||||
{ module = false, filename = `template.vue.html` }: CodegenOptions
|
{
|
||||||
|
mode = 'function',
|
||||||
|
useWith = true,
|
||||||
|
filename = `template.vue.html`
|
||||||
|
}: CodegenOptions
|
||||||
): CodegenContext {
|
): CodegenContext {
|
||||||
const context: CodegenContext = {
|
const context: CodegenContext = {
|
||||||
module,
|
mode,
|
||||||
|
useWith,
|
||||||
filename,
|
filename,
|
||||||
source: ast.loc.source,
|
source: ast.loc.source,
|
||||||
code: ``,
|
code: ``,
|
||||||
column: 1,
|
column: 1,
|
||||||
line: 1,
|
line: 1,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
indent: 0,
|
indentLevel: 0,
|
||||||
imports: new Set(),
|
imports: new Set(),
|
||||||
knownIdentifiers: new Set(),
|
knownIdentifiers: new Set(),
|
||||||
|
|
||||||
@ -86,9 +78,8 @@ function createCodegenContext(
|
|||||||
? undefined
|
? undefined
|
||||||
: new (require('source-map')).SourceMapGenerator(),
|
: new (require('source-map')).SourceMapGenerator(),
|
||||||
|
|
||||||
push(generatedCode, node) {
|
push(code, node?: CodegenNode) {
|
||||||
// TODO handle indent
|
context.code += code
|
||||||
context.code += generatedCode
|
|
||||||
if (context.map) {
|
if (context.map) {
|
||||||
if (node) {
|
if (node) {
|
||||||
context.map.addMapping({
|
context.map.addMapping({
|
||||||
@ -103,30 +94,113 @@ function createCodegenContext(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
advancePositionWithMutation(
|
advancePositionWithMutation(context, code, code.length)
|
||||||
context,
|
|
||||||
generatedCode,
|
|
||||||
generatedCode.length
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
indent() {
|
||||||
|
newline(++context.indentLevel)
|
||||||
|
},
|
||||||
|
deindent() {
|
||||||
|
newline(--context.indentLevel)
|
||||||
|
},
|
||||||
|
newline() {
|
||||||
|
newline(context.indentLevel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const newline = (n: number) => context.push('\n' + ` `.repeat(n))
|
||||||
if (!__BROWSER__) {
|
if (!__BROWSER__) {
|
||||||
context.map!.setSourceContent(filename, context.source)
|
context.map!.setSourceContent(filename, context.source)
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
function genChildren(children: ChildNode[], context: CodegenContext) {
|
export function generate(
|
||||||
context.push(`[`)
|
ast: RootNode,
|
||||||
for (let i = 0; i < children.length; i++) {
|
options: CodegenOptions = {}
|
||||||
genNode(children[i], context)
|
): CodegenResult {
|
||||||
if (i < children.length - 1) context.push(', ')
|
const context = createCodegenContext(ast, options)
|
||||||
|
// TODO handle different output for module mode and IIFE mode
|
||||||
|
const { mode, push, useWith, indent, deindent } = context
|
||||||
|
if (mode === 'function') {
|
||||||
|
// TODO generate const declarations for helpers
|
||||||
|
push(`return `)
|
||||||
|
} else {
|
||||||
|
// TODO generate import statements for helpers
|
||||||
|
push(`export default `)
|
||||||
}
|
}
|
||||||
|
push(`function render() {`)
|
||||||
|
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(`]`)
|
context.push(`]`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function genNode(node: ChildNode, context: CodegenContext) {
|
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) {
|
switch (node.type) {
|
||||||
case NodeTypes.ELEMENT:
|
case NodeTypes.ELEMENT:
|
||||||
genElement(node, context)
|
genElement(node, context)
|
||||||
@ -146,20 +220,55 @@ function genNode(node: ChildNode, context: CodegenContext) {
|
|||||||
case NodeTypes.FOR:
|
case NodeTypes.FOR:
|
||||||
genFor(node, context)
|
genFor(node, context)
|
||||||
break
|
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) {}
|
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) {
|
function genText(node: TextNode | ExpressionNode, context: CodegenContext) {
|
||||||
context.push(JSON.stringify(node.content), node)
|
context.push(JSON.stringify(node.content), node)
|
||||||
}
|
}
|
||||||
|
|
||||||
function genExpression(node: ExpressionNode, context: CodegenContext) {
|
function genExpression(node: ExpressionNode, context: CodegenContext) {
|
||||||
if (!__BROWSER__) {
|
// if (node.codegenNode) {
|
||||||
// TODO parse expression content and rewrite identifiers
|
// 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)
|
||||||
}
|
}
|
||||||
context.push(node.content, node)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function genComment(node: CommentNode, context: CodegenContext) {
|
function genComment(node: CommentNode, context: CodegenContext) {
|
||||||
@ -167,6 +276,107 @@ function genComment(node: CommentNode, context: CodegenContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// control flow
|
// control flow
|
||||||
function genIf(node: IfNode, context: CodegenContext) {}
|
function genIf(node: IfNode, context: CodegenContext) {
|
||||||
|
genIfBranch(node.branches[0], node.branches, 1, context)
|
||||||
|
}
|
||||||
|
|
||||||
function genFor(node: ForNode, context: CodegenContext) {}
|
function genIfBranch(
|
||||||
|
{ condition, children }: IfBranchNode,
|
||||||
|
branches: IfBranchNode[],
|
||||||
|
nextIndex: number,
|
||||||
|
context: CodegenContext
|
||||||
|
) {
|
||||||
|
if (condition) {
|
||||||
|
// v-if or v-else-if
|
||||||
|
context.push(`(${condition.content})`, condition)
|
||||||
|
context.push(`?`)
|
||||||
|
genChildren(children, context)
|
||||||
|
context.push(`:`)
|
||||||
|
if (nextIndex < branches.length) {
|
||||||
|
genIfBranch(branches[nextIndex], branches, nextIndex + 1, context)
|
||||||
|
} else {
|
||||||
|
context.push(`null`)
|
||||||
|
}
|
||||||
|
} 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_HELPER}(`, node)
|
||||||
|
genExpression(source, context)
|
||||||
|
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)
|
||||||
|
push(`, `)
|
||||||
|
}
|
||||||
|
if (keyAlias) {
|
||||||
|
if (!valueAlias) {
|
||||||
|
push(`_, `)
|
||||||
|
}
|
||||||
|
push(keyAlias.content, keyAlias)
|
||||||
|
push(`, `)
|
||||||
|
}
|
||||||
|
if (objectIndexAlias) {
|
||||||
|
if (!keyAlias) {
|
||||||
|
if (!valueAlias) {
|
||||||
|
push(`_, `)
|
||||||
|
}
|
||||||
|
push(`_, `)
|
||||||
|
}
|
||||||
|
push(objectIndexAlias.content, objectIndexAlias)
|
||||||
|
}
|
||||||
|
context.push(`) => `)
|
||||||
|
genChildren(children, context)
|
||||||
|
context.push(`)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JavaScript
|
||||||
|
function genCallExpression(
|
||||||
|
node: CallExpression,
|
||||||
|
context: CodegenContext,
|
||||||
|
multilines = node.arguments.length > 1
|
||||||
|
) {
|
||||||
|
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(`{`, 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(`}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
|
||||||
|
genNodeListAsArray(node.elements, context)
|
||||||
|
}
|
||||||
|
@ -3,6 +3,9 @@ import { transform, TransformOptions } from './transform'
|
|||||||
import { generate, CodegenOptions, CodegenResult } from './codegen'
|
import { generate, CodegenOptions, CodegenResult } from './codegen'
|
||||||
import { RootNode } from './ast'
|
import { RootNode } from './ast'
|
||||||
import { isString } from '@vue/shared'
|
import { isString } from '@vue/shared'
|
||||||
|
import { transformIf } from './transforms/vIf'
|
||||||
|
import { transformFor } from './transforms/vFor'
|
||||||
|
import { prepareElementForCodegen } from './transforms/element'
|
||||||
|
|
||||||
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
||||||
|
|
||||||
@ -15,7 +18,9 @@ export function compile(
|
|||||||
transform(ast, {
|
transform(ast, {
|
||||||
...options,
|
...options,
|
||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
// TODO include built-in core transforms
|
transformIf,
|
||||||
|
transformFor,
|
||||||
|
prepareElementForCodegen,
|
||||||
...(options.nodeTransforms || []) // user transforms
|
...(options.nodeTransforms || []) // user transforms
|
||||||
],
|
],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
|
@ -24,11 +24,16 @@ export const prepareElementForCodegen: NodeTransform = (node, context) => {
|
|||||||
node.tagType === ElementTypes.ELEMENT ||
|
node.tagType === ElementTypes.ELEMENT ||
|
||||||
node.tagType === ElementTypes.COMPONENT
|
node.tagType === ElementTypes.COMPONENT
|
||||||
) {
|
) {
|
||||||
const isComponent = node.tagType === ElementTypes.ELEMENT
|
const isComponent = node.tagType === ElementTypes.COMPONENT
|
||||||
const hasProps = node.props.length > 0
|
const hasProps = node.props.length > 0
|
||||||
const hasChildren = node.children.length > 0
|
const hasChildren = node.children.length > 0
|
||||||
let runtimeDirectives: DirectiveNode[] | undefined
|
let runtimeDirectives: DirectiveNode[] | undefined
|
||||||
|
|
||||||
|
if (isComponent) {
|
||||||
|
// TODO inject import for `resolveComponent`
|
||||||
|
// TODO inject statement for resolving component
|
||||||
|
}
|
||||||
|
|
||||||
const args: CallExpression['arguments'] = [
|
const args: CallExpression['arguments'] = [
|
||||||
// TODO inject resolveComponent dep to root
|
// TODO inject resolveComponent dep to root
|
||||||
isComponent ? node.tag : `"${node.tag}"`
|
isComponent ? node.tag : `"${node.tag}"`
|
||||||
@ -49,9 +54,11 @@ export const prepareElementForCodegen: NodeTransform = (node, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { loc } = node
|
const { loc } = node
|
||||||
|
// TODO inject import for `h`
|
||||||
const vnode = createCallExpression(`h`, args, loc)
|
const vnode = createCallExpression(`h`, args, loc)
|
||||||
|
|
||||||
if (runtimeDirectives) {
|
if (runtimeDirectives && runtimeDirectives.length) {
|
||||||
|
// TODO inject import for `applyDirectives`
|
||||||
node.codegenNode = createCallExpression(
|
node.codegenNode = createCallExpression(
|
||||||
`applyDirectives`,
|
`applyDirectives`,
|
||||||
[
|
[
|
||||||
@ -170,7 +177,8 @@ function createDirectiveArgs(
|
|||||||
dir: DirectiveNode,
|
dir: DirectiveNode,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
): ArrayExpression {
|
): ArrayExpression {
|
||||||
// TODO inject resolveDirective dep to root
|
// TODO inject import for `resolveDirective`
|
||||||
|
// TODO inject statement for resolving directive
|
||||||
const dirArgs: ArrayExpression['elements'] = [dir.name]
|
const dirArgs: ArrayExpression['elements'] = [dir.name]
|
||||||
const { loc } = dir
|
const { loc } = dir
|
||||||
if (dir.exp) dirArgs.push(dir.exp)
|
if (dir.exp) dirArgs.push(dir.exp)
|
||||||
|
9
packages/compiler-core/src/transforms/expression.ts
Normal file
9
packages/compiler-core/src/transforms/expression.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// - Parse expressions in templates into more detailed JavaScript ASTs so that
|
||||||
|
// source-maps are more accurate
|
||||||
|
//
|
||||||
|
// - Prefix identifiers with `_ctx.` so that they are accessed from the render
|
||||||
|
// context
|
||||||
|
//
|
||||||
|
// - This transform is only applied in non-browser builds because it relies on
|
||||||
|
// an additional JavaScript parser. In the browser, there is no source-map
|
||||||
|
// support and the code is wrapped in `with (this) { ... }`.
|
@ -7,13 +7,17 @@ const forAliasRE = /([\s\S]*?)(?:(?<=\))|\s+)(?:in|of)\s+([\s\S]*)/
|
|||||||
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
||||||
const stripParensRE = /^\(|\)$/g
|
const stripParensRE = /^\(|\)$/g
|
||||||
|
|
||||||
|
export const RENDER_LIST_HELPER = `renderList`
|
||||||
|
|
||||||
export const transformFor = createStructuralDirectiveTransform(
|
export const transformFor = createStructuralDirectiveTransform(
|
||||||
'for',
|
'for',
|
||||||
(node, dir, context) => {
|
(node, dir, context) => {
|
||||||
if (dir.exp) {
|
if (dir.exp) {
|
||||||
|
// TODO inject helper import
|
||||||
const aliases = parseAliasExpressions(dir.exp.content)
|
const aliases = parseAliasExpressions(dir.exp.content)
|
||||||
|
|
||||||
if (aliases) {
|
if (aliases) {
|
||||||
|
// TODO inject identifiers to context
|
||||||
context.replaceNode({
|
context.replaceNode({
|
||||||
type: NodeTypes.FOR,
|
type: NodeTypes.FOR,
|
||||||
loc: node.loc,
|
loc: node.loc,
|
||||||
|
@ -61,6 +61,6 @@ export function advancePositionWithMutation(
|
|||||||
|
|
||||||
export function assert(condition: boolean, msg?: string) {
|
export function assert(condition: boolean, msg?: string) {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
throw new Error(msg || `unexpected parser condition`)
|
throw new Error(msg || `unexpected compiler condition`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@ import {
|
|||||||
import { parserOptionsMinimal } from './parserOptionsMinimal'
|
import { parserOptionsMinimal } from './parserOptionsMinimal'
|
||||||
import { parserOptionsStandard } from './parserOptionsStandard'
|
import { parserOptionsStandard } from './parserOptionsStandard'
|
||||||
|
|
||||||
export * from '@vue/compiler-core'
|
|
||||||
|
|
||||||
export function compile(
|
export function compile(
|
||||||
template: string,
|
template: string,
|
||||||
options: CompilerOptions = {}
|
options: CompilerOptions = {}
|
||||||
@ -21,3 +19,5 @@ export function compile(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export * from '@vue/compiler-core'
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
Data,
|
Data,
|
||||||
currentInstance,
|
|
||||||
Component,
|
Component,
|
||||||
SetupContext
|
SetupContext
|
||||||
} from './component'
|
} from './component'
|
||||||
@ -11,9 +10,7 @@ import {
|
|||||||
isString,
|
isString,
|
||||||
isObject,
|
isObject,
|
||||||
isArray,
|
isArray,
|
||||||
EMPTY_OBJ,
|
EMPTY_OBJ
|
||||||
capitalize,
|
|
||||||
camelize
|
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { computed } from './apiReactivity'
|
import { computed } from './apiReactivity'
|
||||||
import { watch, WatchOptions, CleanupRegistrator } from './apiWatch'
|
import { watch, WatchOptions, CleanupRegistrator } from './apiWatch'
|
||||||
@ -29,12 +26,10 @@ import {
|
|||||||
onUnmounted
|
onUnmounted
|
||||||
} from './apiLifecycle'
|
} from './apiLifecycle'
|
||||||
import { DebuggerEvent, reactive } from '@vue/reactivity'
|
import { DebuggerEvent, reactive } from '@vue/reactivity'
|
||||||
import { warn } from './warning'
|
|
||||||
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
|
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
|
||||||
import { Directive } from './directives'
|
import { Directive } from './directives'
|
||||||
import { VNodeChild } from './vnode'
|
import { VNodeChild } from './vnode'
|
||||||
import { ComponentPublicInstance } from './componentPublicInstanceProxy'
|
import { ComponentPublicInstance } from './componentPublicInstanceProxy'
|
||||||
import { currentRenderingInstance } from './componentRenderUtils'
|
|
||||||
|
|
||||||
interface ComponentOptionsBase<
|
interface ComponentOptionsBase<
|
||||||
Props,
|
Props,
|
||||||
@ -387,32 +382,3 @@ function applyMixins(
|
|||||||
applyOptions(instance, mixins[i], true)
|
applyOptions(instance, mixins[i], true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveComponent(name: string): Component | undefined {
|
|
||||||
return resolveAsset('components', name) as any
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveDirective(name: string): Directive | undefined {
|
|
||||||
return resolveAsset('directives', name) as any
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveAsset(type: 'components' | 'directives', name: string) {
|
|
||||||
const instance = currentRenderingInstance || currentInstance
|
|
||||||
if (instance) {
|
|
||||||
let camelized
|
|
||||||
const registry = instance[type]
|
|
||||||
const res =
|
|
||||||
registry[name] ||
|
|
||||||
registry[(camelized = camelize(name))] ||
|
|
||||||
registry[capitalize(camelized)]
|
|
||||||
if (__DEV__ && !res) {
|
|
||||||
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
} else if (__DEV__) {
|
|
||||||
warn(
|
|
||||||
`resolve${capitalize(type.slice(0, -1))} ` +
|
|
||||||
`can only be used in render() or setup().`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -40,6 +40,7 @@ export const PublicInstanceProxyHandlers = {
|
|||||||
// return the value from propsProxy for ref unwrapping and readonly
|
// return the value from propsProxy for ref unwrapping and readonly
|
||||||
return (propsProxy as any)[key]
|
return (propsProxy as any)[key]
|
||||||
} else {
|
} else {
|
||||||
|
// TODO simplify this?
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case '$data':
|
case '$data':
|
||||||
return data
|
return data
|
||||||
@ -79,6 +80,7 @@ export const PublicInstanceProxyHandlers = {
|
|||||||
},
|
},
|
||||||
has(target: ComponentInternalInstance, key: string): boolean {
|
has(target: ComponentInternalInstance, key: string): boolean {
|
||||||
const { renderContext, data, props } = target
|
const { renderContext, data, props } = target
|
||||||
|
// TODO handle $xxx properties
|
||||||
return (
|
return (
|
||||||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
||||||
hasOwn(renderContext, key) ||
|
hasOwn(renderContext, key) ||
|
||||||
|
2
packages/runtime-core/src/helpers/renderList.ts
Normal file
2
packages/runtime-core/src/helpers/renderList.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// TODO
|
||||||
|
export function renderList() {}
|
34
packages/runtime-core/src/helpers/resolveAssets.ts
Normal file
34
packages/runtime-core/src/helpers/resolveAssets.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { currentRenderingInstance } from '../componentRenderUtils'
|
||||||
|
import { currentInstance, Component } from '../component'
|
||||||
|
import { Directive } from '../directives'
|
||||||
|
import { camelize, capitalize } from '@vue/shared'
|
||||||
|
import { warn } from '../warning'
|
||||||
|
|
||||||
|
export function resolveComponent(name: string): Component | undefined {
|
||||||
|
return resolveAsset('components', name) as any
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveDirective(name: string): Directive | undefined {
|
||||||
|
return resolveAsset('directives', name) as any
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAsset(type: 'components' | 'directives', name: string) {
|
||||||
|
const instance = currentRenderingInstance || currentInstance
|
||||||
|
if (instance) {
|
||||||
|
let camelized
|
||||||
|
const registry = instance[type]
|
||||||
|
const res =
|
||||||
|
registry[name] ||
|
||||||
|
registry[(camelized = camelize(name))] ||
|
||||||
|
registry[capitalize(camelized)]
|
||||||
|
if (__DEV__ && !res) {
|
||||||
|
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
} else if (__DEV__) {
|
||||||
|
warn(
|
||||||
|
`resolve${capitalize(type.slice(0, -1))} ` +
|
||||||
|
`can only be used in render() or setup().`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -37,7 +37,8 @@ export {
|
|||||||
|
|
||||||
// Internal, for compiler generated code
|
// Internal, for compiler generated code
|
||||||
export { applyDirectives } from './directives'
|
export { applyDirectives } from './directives'
|
||||||
export { resolveComponent, resolveDirective } from './componentOptions'
|
export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
|
||||||
|
export { renderList } from './helpers/renderList'
|
||||||
|
|
||||||
// Internal, for integration with runtime compiler
|
// Internal, for integration with runtime compiler
|
||||||
export { registerRuntimeCompiler } from './component'
|
export { registerRuntimeCompiler } from './component'
|
||||||
|
@ -8,7 +8,7 @@ function compileToFunction(
|
|||||||
options?: CompilerOptions
|
options?: CompilerOptions
|
||||||
): RenderFunction {
|
): RenderFunction {
|
||||||
const { code } = compile(template, options)
|
const { code } = compile(template, options)
|
||||||
return new Function(`with(this){return ${code}}`) as RenderFunction
|
return new Function(code)() as RenderFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
registerRuntimeCompiler(compileToFunction)
|
registerRuntimeCompiler(compileToFunction)
|
||||||
|
Loading…
Reference in New Issue
Block a user