EXPRESSION,
ATTRIBUTE,
DIRECTIVE,
+ // containers
IF,
IF_BRANCH,
- FOR
+ FOR,
+ // codegen
+ CALL_EXPRESSION,
+ OBJECT_EXPRESSION,
+ PROPERTY,
+ ARRAY_EXPRESSION
}
export const enum ElementTypes {
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
attrs: AttributeNode[]
directives: DirectiveNode[]
children: ChildNode[]
+ codegenNode: CallExpression | undefined
}
export interface TextNode 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>
}
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
}
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
}
}
-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)
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,
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'
onError?: (error: CompilerError) => void
}
-interface TransformContext {
+export interface TransformContext {
transforms: Transform[]
emitError: (error: CompilerError) => void
parent: ParentNode
--- /dev/null
+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
+}