wip: element transforms

This commit is contained in:
Evan You 2019-09-21 15:47:26 -04:00
parent b275f8697d
commit 93440bba97
18 changed files with 158 additions and 19 deletions

View File

@ -15,9 +15,15 @@ export const enum NodeTypes {
EXPRESSION,
ATTRIBUTE,
DIRECTIVE,
// containers
IF,
IF_BRANCH,
FOR
FOR,
// codegen
CALL_EXPRESSION,
OBJECT_EXPRESSION,
PROPERTY,
ARRAY_EXPRESSION
}
export const enum ElementTypes {
@ -32,7 +38,22 @@ export interface Node {
loc: SourceLocation
}
// The node's range. The `start` is inclusive and `end` is exclusive.
// [start, end)
export interface SourceLocation {
start: Position
end: Position
source: string
}
export interface Position {
offset: number // from start of file
line: number
column: number
}
export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode
export type ChildNode =
| ElementNode
| ExpressionNode
@ -55,6 +76,7 @@ export interface ElementNode extends Node {
attrs: AttributeNode[]
directives: DirectiveNode[]
children: ChildNode[]
codegenNode: CallExpression | undefined
}
export interface TextNode extends Node {
@ -108,16 +130,34 @@ export interface ForNode extends Node {
children: ChildNode[]
}
export interface Position {
offset: number // from start of file
line: number
column: number
// We also include a subset of JavaScript AST for code generation
// purposes. The AST is intentioanlly minimal just to meet the exact needs of
// Vue render function generation.
type CodegenNode =
| string
| CallExpression
| ObjectExpression
| ArrayExpression
| ExpressionNode
export interface CallExpression extends Node {
type: NodeTypes.CALL_EXPRESSION
callee: string // can only be imported runtime helpers, so no source location
arguments: Array<CodegenNode | ChildNode[]>
}
// The node's range. The `start` is inclusive and `end` is exclusive.
// [start, end)
export interface SourceLocation {
start: Position
end: Position
source: string
export interface ObjectExpression extends Node {
type: NodeTypes.OBJECT_EXPRESSION
properties: Array<Property>
}
export interface Property extends Node {
type: NodeTypes.PROPERTY
key: ExpressionNode
value: ExpressionNode
}
export interface ArrayExpression extends Node {
type: NodeTypes.ARRAY_EXPRESSION
elements: Array<CodegenNode>
}

View File

@ -26,14 +26,15 @@ export interface CodegenResult {
map?: RawSourceMap
}
interface CodegenContext extends Required<CodegenOptions> {
export interface CodegenContext extends Required<CodegenOptions> {
source: string
code: string
line: number
column: number
offset: number
indent: number
identifiers: Set<string>
imports: Set<string>
knownIdentifiers: Set<string>
map?: SourceMapGenerator
push(generatedCode: string, astNode?: ChildNode): void
}
@ -77,11 +78,14 @@ function createCodegenContext(
line: 1,
offset: 0,
indent: 0,
identifiers: new Set(),
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
@ -145,7 +149,7 @@ function genNode(node: ChildNode, context: CodegenContext) {
}
}
function genElement(el: ElementNode, context: CodegenContext) {}
function genElement(node: ElementNode, context: CodegenContext) {}
function genText(node: TextNode | ExpressionNode, context: CodegenContext) {
context.push(JSON.stringify(node.content), node)

View File

@ -1,14 +1,16 @@
import { parse, ParserOptions } from './parse'
import { transform, TransformOptions } from './transform'
import { generate, CodegenOptions, CodegenResult } from './codegen'
import { RootNode } from './ast'
import { isString } from '@vue/shared'
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
export function compile(
template: string,
template: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
const ast = parse(template, options)
const ast = isString(template) ? parse(template, options) : template
transform(ast, {
...options,
@ -27,9 +29,15 @@ export {
transform,
createDirectiveTransform,
TransformOptions,
TransformContext,
Transform,
DirectiveTransform
} from './transform'
export { generate, CodegenOptions, CodegenResult } from './codegen'
export {
generate,
CodegenOptions,
CodegenContext,
CodegenResult
} from './codegen'
export { ErrorCodes, CompilerError, createCompilerError } from './errors'
export * from './ast'

View File

@ -22,7 +22,7 @@ export interface TransformOptions {
onError?: (error: CompilerError) => void
}
interface TransformContext {
export interface TransformContext {
transforms: Transform[]
emitError: (error: CompilerError) => void
parent: ParentNode

View File

@ -0,0 +1,87 @@
import { Transform, TransformContext } from '../transform'
import {
NodeTypes,
ElementTypes,
CallExpression,
ObjectExpression,
ElementNode
} from '../ast'
// generate a JavaScript AST for this element's codegen
export const prepareElementForCodegen: Transform = (node, context) => {
if (node.type === NodeTypes.ELEMENT) {
if (
node.tagType === ElementTypes.ELEMENT ||
node.tagType === ElementTypes.COMPONENT
) {
const isComponent = node.tagType === ElementTypes.ELEMENT
const hasProps = node.attrs.length > 0 || node.directives.length > 0
const hasChildren = node.children.length > 0
const args: CallExpression['arguments'] = [
isComponent ? node.tag : `"${node.tag}"`
]
// props
if (hasProps) {
args.push(buildProps(node))
}
// children
if (hasChildren) {
if (!hasProps) {
// placeholder for null props, but use `0` for more condense code
args.push(`0`)
}
args.push(isComponent ? buildSlots(node, context) : node.children)
}
node.codegenNode = {
type: NodeTypes.CALL_EXPRESSION,
loc: node.loc,
callee: `h`,
arguments: args
}
}
}
}
function buildProps({ loc, attrs }: ElementNode): ObjectExpression {
return {
type: NodeTypes.OBJECT_EXPRESSION,
loc,
// At this stage we will only process static attrs. Directive bindings will
// be handled by their respective transforms which adds/modifies the props.
properties: attrs.map(({ name, value, loc }) => {
return {
type: NodeTypes.PROPERTY,
loc,
key: {
type: NodeTypes.EXPRESSION,
loc,
content: name,
isStatic: true
},
value: {
type: NodeTypes.EXPRESSION,
loc: value ? value.loc : loc,
content: value ? value.content : '',
isStatic: true
}
}
})
}
}
function buildSlots(
{ loc, children }: ElementNode,
context: TransformContext
): ObjectExpression {
const slots: ObjectExpression = {
type: NodeTypes.OBJECT_EXPRESSION,
loc,
properties: []
}
// TODO
return slots
}