feat(compiler): handle runtime helper injection
This commit is contained in:
parent
914087edea
commit
8076ce1f28
File diff suppressed because it is too large
Load Diff
@ -65,6 +65,8 @@ export type ChildNode =
|
|||||||
export interface RootNode extends Node {
|
export interface RootNode extends Node {
|
||||||
type: NodeTypes.ROOT
|
type: NodeTypes.ROOT
|
||||||
children: ChildNode[]
|
children: ChildNode[]
|
||||||
|
imports: string[]
|
||||||
|
statements: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ElementNode extends Node {
|
export interface ElementNode extends Node {
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||||
import { advancePositionWithMutation, assert } from './utils'
|
import { advancePositionWithMutation, assert } from './utils'
|
||||||
import { isString, isArray } from '@vue/shared'
|
import { isString, isArray } from '@vue/shared'
|
||||||
import { RENDER_LIST_HELPER } from './transforms/vFor'
|
import { RENDER_LIST } from './runtimeConstants'
|
||||||
|
|
||||||
type CodegenNode = ChildNode | JSChildNode
|
type CodegenNode = ChildNode | JSChildNode
|
||||||
|
|
||||||
@ -43,8 +43,6 @@ export interface CodegenContext extends Required<CodegenOptions> {
|
|||||||
column: number
|
column: number
|
||||||
offset: number
|
offset: number
|
||||||
indentLevel: number
|
indentLevel: number
|
||||||
imports: Set<string>
|
|
||||||
knownIdentifiers: Set<string>
|
|
||||||
map?: SourceMapGenerator
|
map?: SourceMapGenerator
|
||||||
push(code: string, node?: CodegenNode): void
|
push(code: string, node?: CodegenNode): void
|
||||||
indent(): void
|
indent(): void
|
||||||
@ -70,8 +68,6 @@ function createCodegenContext(
|
|||||||
line: 1,
|
line: 1,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
indentLevel: 0,
|
indentLevel: 0,
|
||||||
imports: new Set(),
|
|
||||||
knownIdentifiers: new Set(),
|
|
||||||
|
|
||||||
// lazy require source-map implementation, only in non-browser builds!
|
// lazy require source-map implementation, only in non-browser builds!
|
||||||
map: __BROWSER__
|
map: __BROWSER__
|
||||||
@ -123,16 +119,24 @@ export function generate(
|
|||||||
options: CodegenOptions = {}
|
options: CodegenOptions = {}
|
||||||
): CodegenResult {
|
): CodegenResult {
|
||||||
const context = createCodegenContext(ast, options)
|
const context = createCodegenContext(ast, options)
|
||||||
// TODO handle different output for module mode and IIFE mode
|
|
||||||
const { mode, push, useWith, indent, deindent } = context
|
const { mode, push, useWith, indent, deindent } = context
|
||||||
|
const imports = ast.imports.join(', ')
|
||||||
if (mode === 'function') {
|
if (mode === 'function') {
|
||||||
// TODO generate const declarations for helpers
|
// generate const declarations for helpers
|
||||||
|
if (imports) {
|
||||||
|
push(`const { ${imports} } = Vue\n\n`)
|
||||||
|
}
|
||||||
push(`return `)
|
push(`return `)
|
||||||
} else {
|
} else {
|
||||||
// TODO generate import statements for helpers
|
// generate import statements for helpers
|
||||||
|
if (imports) {
|
||||||
|
push(`import { ${imports} } from 'vue'\n\n`)
|
||||||
|
}
|
||||||
push(`export default `)
|
push(`export default `)
|
||||||
}
|
}
|
||||||
push(`function render() {`)
|
push(`function render() {`)
|
||||||
|
// generate asset resolution statements
|
||||||
|
ast.statements.forEach(s => push(s + `\n`))
|
||||||
if (useWith) {
|
if (useWith) {
|
||||||
indent()
|
indent()
|
||||||
push(`with (this) {`)
|
push(`with (this) {`)
|
||||||
@ -317,7 +321,7 @@ function genIfBranch(
|
|||||||
function genFor(node: ForNode, context: CodegenContext) {
|
function genFor(node: ForNode, context: CodegenContext) {
|
||||||
const { push } = context
|
const { push } = context
|
||||||
const { source, keyAlias, valueAlias, objectIndexAlias, children } = node
|
const { source, keyAlias, valueAlias, objectIndexAlias, children } = node
|
||||||
push(`${RENDER_LIST_HELPER}(`, node)
|
push(`${RENDER_LIST}(`, node)
|
||||||
genExpression(source, context)
|
genExpression(source, context)
|
||||||
push(`, (`)
|
push(`, (`)
|
||||||
if (valueAlias) {
|
if (valueAlias) {
|
||||||
|
@ -82,6 +82,8 @@ export function parse(content: string, options: ParserOptions = {}): RootNode {
|
|||||||
return {
|
return {
|
||||||
type: NodeTypes.ROOT,
|
type: NodeTypes.ROOT,
|
||||||
children: parseChildren(context, TextModes.DATA, []),
|
children: parseChildren(context, TextModes.DATA, []),
|
||||||
|
imports: [],
|
||||||
|
statements: [],
|
||||||
loc: getSelection(context, start)
|
loc: getSelection(context, start)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
packages/compiler-core/src/runtimeConstants.ts
Normal file
8
packages/compiler-core/src/runtimeConstants.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// 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 CREATE_ELEMENT = `h`
|
||||||
|
export const RESOLVE_COMPONENT = `resolveComponent`
|
||||||
|
export const RESOLVE_DIRECTIVE = `resolveDirective`
|
||||||
|
export const APPLY_DIRECTIVES = `applyDirectives`
|
||||||
|
export const RENDER_LIST = `renderList`
|
||||||
|
export const CAPITALIZE = `capitalize`
|
@ -43,6 +43,9 @@ export interface TransformOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TransformContext extends Required<TransformOptions> {
|
export interface TransformContext extends Required<TransformOptions> {
|
||||||
|
imports: Set<string>
|
||||||
|
statements: string[]
|
||||||
|
identifiers: { [name: string]: true }
|
||||||
parent: ParentNode
|
parent: ParentNode
|
||||||
ancestors: ParentNode[]
|
ancestors: ParentNode[]
|
||||||
childIndex: number
|
childIndex: number
|
||||||
@ -52,16 +55,14 @@ export interface TransformContext extends Required<TransformOptions> {
|
|||||||
onNodeRemoved: () => void
|
onNodeRemoved: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transform(root: RootNode, options: TransformOptions) {
|
|
||||||
const context = createTransformContext(root, options)
|
|
||||||
traverseChildren(root, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
function createTransformContext(
|
function createTransformContext(
|
||||||
root: RootNode,
|
root: RootNode,
|
||||||
options: TransformOptions
|
options: TransformOptions
|
||||||
): TransformContext {
|
): TransformContext {
|
||||||
const context: TransformContext = {
|
const context: TransformContext = {
|
||||||
|
imports: new Set(),
|
||||||
|
statements: [],
|
||||||
|
identifiers: {},
|
||||||
nodeTransforms: options.nodeTransforms || [],
|
nodeTransforms: options.nodeTransforms || [],
|
||||||
directiveTransforms: options.directiveTransforms || {},
|
directiveTransforms: options.directiveTransforms || {},
|
||||||
onError: options.onError || defaultOnError,
|
onError: options.onError || defaultOnError,
|
||||||
@ -103,11 +104,21 @@ function createTransformContext(
|
|||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transform(root: RootNode, options: TransformOptions) {
|
||||||
|
const context = createTransformContext(root, options)
|
||||||
|
traverseChildren(root, context)
|
||||||
|
root.imports = [...context.imports]
|
||||||
|
root.statements = context.statements
|
||||||
|
}
|
||||||
|
|
||||||
export function traverseChildren(
|
export function traverseChildren(
|
||||||
parent: ParentNode,
|
parent: ParentNode,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
) {
|
) {
|
||||||
|
// ancestors and identifiers need to be cached here since they may get
|
||||||
|
// replaced during a child's traversal
|
||||||
const ancestors = context.ancestors.concat(parent)
|
const ancestors = context.ancestors.concat(parent)
|
||||||
|
const identifiers = context.identifiers
|
||||||
let i = 0
|
let i = 0
|
||||||
const nodeRemoved = () => {
|
const nodeRemoved = () => {
|
||||||
i--
|
i--
|
||||||
@ -117,6 +128,7 @@ export function traverseChildren(
|
|||||||
context.ancestors = ancestors
|
context.ancestors = ancestors
|
||||||
context.childIndex = i
|
context.childIndex = i
|
||||||
context.onNodeRemoved = nodeRemoved
|
context.onNodeRemoved = nodeRemoved
|
||||||
|
context.identifiers = identifiers
|
||||||
traverseNode((context.currentNode = parent.children[i]), context)
|
traverseNode((context.currentNode = parent.children[i]), context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,14 @@ import {
|
|||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { isArray } from '@vue/shared'
|
import { isArray } from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
|
import {
|
||||||
|
CREATE_ELEMENT,
|
||||||
|
APPLY_DIRECTIVES,
|
||||||
|
RESOLVE_DIRECTIVE,
|
||||||
|
RESOLVE_COMPONENT
|
||||||
|
} from '../runtimeConstants'
|
||||||
|
|
||||||
|
const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
|
||||||
|
|
||||||
// generate a JavaScript AST for this element's codegen
|
// generate a JavaScript AST for this element's codegen
|
||||||
export const prepareElementForCodegen: NodeTransform = (node, context) => {
|
export const prepareElementForCodegen: NodeTransform = (node, context) => {
|
||||||
@ -28,15 +36,20 @@ export const prepareElementForCodegen: NodeTransform = (node, context) => {
|
|||||||
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
|
||||||
|
let componentIdentifier: string | undefined
|
||||||
|
|
||||||
if (isComponent) {
|
if (isComponent) {
|
||||||
// TODO inject import for `resolveComponent`
|
context.imports.add(RESOLVE_COMPONENT)
|
||||||
// TODO inject statement for resolving component
|
componentIdentifier = `_component_${toValidId(node.tag)}`
|
||||||
|
context.statements.push(
|
||||||
|
`const ${componentIdentifier} = ${RESOLVE_COMPONENT}(${JSON.stringify(
|
||||||
|
node.tag
|
||||||
|
)})`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const args: CallExpression['arguments'] = [
|
const args: CallExpression['arguments'] = [
|
||||||
// TODO inject resolveComponent dep to root
|
isComponent ? componentIdentifier! : `"${node.tag}"`
|
||||||
isComponent ? node.tag : `"${node.tag}"`
|
|
||||||
]
|
]
|
||||||
// props
|
// props
|
||||||
if (hasProps) {
|
if (hasProps) {
|
||||||
@ -54,13 +67,13 @@ export const prepareElementForCodegen: NodeTransform = (node, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { loc } = node
|
const { loc } = node
|
||||||
// TODO inject import for `h`
|
context.imports.add(CREATE_ELEMENT)
|
||||||
const vnode = createCallExpression(`h`, args, loc)
|
const vnode = createCallExpression(CREATE_ELEMENT, args, loc)
|
||||||
|
|
||||||
if (runtimeDirectives && runtimeDirectives.length) {
|
if (runtimeDirectives && runtimeDirectives.length) {
|
||||||
// TODO inject import for `applyDirectives`
|
context.imports.add(APPLY_DIRECTIVES)
|
||||||
node.codegenNode = createCallExpression(
|
node.codegenNode = createCallExpression(
|
||||||
`applyDirectives`,
|
APPLY_DIRECTIVES,
|
||||||
[
|
[
|
||||||
vnode,
|
vnode,
|
||||||
createArrayExpression(
|
createArrayExpression(
|
||||||
@ -174,9 +187,16 @@ function createDirectiveArgs(
|
|||||||
dir: DirectiveNode,
|
dir: DirectiveNode,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
): ArrayExpression {
|
): ArrayExpression {
|
||||||
// TODO inject import for `resolveDirective`
|
// inject import for `resolveDirective`
|
||||||
// TODO inject statement for resolving directive
|
context.imports.add(RESOLVE_DIRECTIVE)
|
||||||
const dirArgs: ArrayExpression['elements'] = [dir.name]
|
// inject statement for resolving directive
|
||||||
|
const dirIdentifier = `_directive_${toValidId(dir.name)}`
|
||||||
|
context.statements.push(
|
||||||
|
`const ${dirIdentifier} = _${RESOLVE_DIRECTIVE}(${JSON.stringify(
|
||||||
|
dir.name
|
||||||
|
)})`
|
||||||
|
)
|
||||||
|
const dirArgs: ArrayExpression['elements'] = [dirIdentifier]
|
||||||
const { loc } = dir
|
const { loc } = dir
|
||||||
if (dir.exp) dirArgs.push(dir.exp)
|
if (dir.exp) dirArgs.push(dir.exp)
|
||||||
if (dir.arg) dirArgs.push(dir.arg)
|
if (dir.arg) dirArgs.push(dir.arg)
|
||||||
|
@ -2,18 +2,17 @@ import { createStructuralDirectiveTransform } from '../transform'
|
|||||||
import { NodeTypes, ExpressionNode, createExpression } from '../ast'
|
import { NodeTypes, ExpressionNode, createExpression } from '../ast'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { getInnerRange } from '../utils'
|
import { getInnerRange } from '../utils'
|
||||||
|
import { RENDER_LIST } from '../runtimeConstants'
|
||||||
|
|
||||||
const forAliasRE = /([\s\S]*?)(?:(?<=\))|\s+)(?:in|of)\s+([\s\S]*)/
|
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
|
context.imports.add(RENDER_LIST)
|
||||||
const aliases = parseAliasExpressions(dir.exp.content)
|
const aliases = parseAliasExpressions(dir.exp.content)
|
||||||
|
|
||||||
if (aliases) {
|
if (aliases) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { DirectiveTransform } from '../transform'
|
import { DirectiveTransform } from '../transform'
|
||||||
import { createObjectProperty, createExpression } from '../ast'
|
import { createObjectProperty, createExpression } from '../ast'
|
||||||
import { capitalize } from '@vue/shared'
|
import { capitalize } from '@vue/shared'
|
||||||
|
import { CAPITALIZE } from '../runtimeConstants'
|
||||||
|
|
||||||
// v-on without arg is handled directly in ./element.ts due to it affecting
|
// v-on without arg is handled directly in ./element.ts due to it affecting
|
||||||
// codegen for the entire props object. This transform here is only for v-on
|
// codegen for the entire props object. This transform here is only for v-on
|
||||||
@ -9,8 +10,7 @@ export const transformOn: DirectiveTransform = (dir, context) => {
|
|||||||
const arg = dir.arg!
|
const arg = dir.arg!
|
||||||
const eventName = arg.isStatic
|
const eventName = arg.isStatic
|
||||||
? createExpression(`on${capitalize(arg.content)}`, true, arg.loc)
|
? createExpression(`on${capitalize(arg.content)}`, true, arg.loc)
|
||||||
: // TODO inject capitalize helper
|
: createExpression(`'on' + ${CAPITALIZE}(${arg.content})`, false, arg.loc)
|
||||||
createExpression(`'on' + capitalize(${arg.content})`, false, arg.loc)
|
|
||||||
// TODO .once modifier handling since it is platform agnostic
|
// TODO .once modifier handling since it is platform agnostic
|
||||||
// other modifiers are handled in compiler-dom
|
// other modifiers are handled in compiler-dom
|
||||||
return {
|
return {
|
||||||
|
@ -39,6 +39,7 @@ export {
|
|||||||
export { applyDirectives } from './directives'
|
export { applyDirectives } from './directives'
|
||||||
export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
|
export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
|
||||||
export { renderList } from './helpers/renderList'
|
export { renderList } from './helpers/renderList'
|
||||||
|
export { capitalize } from '@vue/shared'
|
||||||
|
|
||||||
// Internal, for integration with runtime compiler
|
// Internal, for integration with runtime compiler
|
||||||
export { registerRuntimeCompiler } from './component'
|
export { registerRuntimeCompiler } from './component'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user