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 {
|
||||
type: NodeTypes.ROOT
|
||||
children: ChildNode[]
|
||||
imports: string[]
|
||||
statements: string[]
|
||||
}
|
||||
|
||||
export interface ElementNode extends Node {
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||
import { advancePositionWithMutation, assert } from './utils'
|
||||
import { isString, isArray } from '@vue/shared'
|
||||
import { RENDER_LIST_HELPER } from './transforms/vFor'
|
||||
import { RENDER_LIST } from './runtimeConstants'
|
||||
|
||||
type CodegenNode = ChildNode | JSChildNode
|
||||
|
||||
@ -43,8 +43,6 @@ export interface CodegenContext extends Required<CodegenOptions> {
|
||||
column: number
|
||||
offset: number
|
||||
indentLevel: number
|
||||
imports: Set<string>
|
||||
knownIdentifiers: Set<string>
|
||||
map?: SourceMapGenerator
|
||||
push(code: string, node?: CodegenNode): void
|
||||
indent(): void
|
||||
@ -70,8 +68,6 @@ function createCodegenContext(
|
||||
line: 1,
|
||||
offset: 0,
|
||||
indentLevel: 0,
|
||||
imports: new Set(),
|
||||
knownIdentifiers: new Set(),
|
||||
|
||||
// lazy require source-map implementation, only in non-browser builds!
|
||||
map: __BROWSER__
|
||||
@ -123,16 +119,24 @@ export function generate(
|
||||
options: CodegenOptions = {}
|
||||
): CodegenResult {
|
||||
const context = createCodegenContext(ast, options)
|
||||
// TODO handle different output for module mode and IIFE mode
|
||||
const { mode, push, useWith, indent, deindent } = context
|
||||
const imports = ast.imports.join(', ')
|
||||
if (mode === 'function') {
|
||||
// TODO generate const declarations for helpers
|
||||
// generate const declarations for helpers
|
||||
if (imports) {
|
||||
push(`const { ${imports} } = Vue\n\n`)
|
||||
}
|
||||
push(`return `)
|
||||
} 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(`function render() {`)
|
||||
// generate asset resolution statements
|
||||
ast.statements.forEach(s => push(s + `\n`))
|
||||
if (useWith) {
|
||||
indent()
|
||||
push(`with (this) {`)
|
||||
@ -317,7 +321,7 @@ function genIfBranch(
|
||||
function genFor(node: ForNode, context: CodegenContext) {
|
||||
const { push } = context
|
||||
const { source, keyAlias, valueAlias, objectIndexAlias, children } = node
|
||||
push(`${RENDER_LIST_HELPER}(`, node)
|
||||
push(`${RENDER_LIST}(`, node)
|
||||
genExpression(source, context)
|
||||
push(`, (`)
|
||||
if (valueAlias) {
|
||||
|
@ -82,6 +82,8 @@ export function parse(content: string, options: ParserOptions = {}): RootNode {
|
||||
return {
|
||||
type: NodeTypes.ROOT,
|
||||
children: parseChildren(context, TextModes.DATA, []),
|
||||
imports: [],
|
||||
statements: [],
|
||||
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> {
|
||||
imports: Set<string>
|
||||
statements: string[]
|
||||
identifiers: { [name: string]: true }
|
||||
parent: ParentNode
|
||||
ancestors: ParentNode[]
|
||||
childIndex: number
|
||||
@ -52,16 +55,14 @@ export interface TransformContext extends Required<TransformOptions> {
|
||||
onNodeRemoved: () => void
|
||||
}
|
||||
|
||||
export function transform(root: RootNode, options: TransformOptions) {
|
||||
const context = createTransformContext(root, options)
|
||||
traverseChildren(root, context)
|
||||
}
|
||||
|
||||
function createTransformContext(
|
||||
root: RootNode,
|
||||
options: TransformOptions
|
||||
): TransformContext {
|
||||
const context: TransformContext = {
|
||||
imports: new Set(),
|
||||
statements: [],
|
||||
identifiers: {},
|
||||
nodeTransforms: options.nodeTransforms || [],
|
||||
directiveTransforms: options.directiveTransforms || {},
|
||||
onError: options.onError || defaultOnError,
|
||||
@ -103,11 +104,21 @@ function createTransformContext(
|
||||
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(
|
||||
parent: ParentNode,
|
||||
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 identifiers = context.identifiers
|
||||
let i = 0
|
||||
const nodeRemoved = () => {
|
||||
i--
|
||||
@ -117,6 +128,7 @@ export function traverseChildren(
|
||||
context.ancestors = ancestors
|
||||
context.childIndex = i
|
||||
context.onNodeRemoved = nodeRemoved
|
||||
context.identifiers = identifiers
|
||||
traverseNode((context.currentNode = parent.children[i]), context)
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,14 @@ import {
|
||||
} from '../ast'
|
||||
import { isArray } from '@vue/shared'
|
||||
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
|
||||
export const prepareElementForCodegen: NodeTransform = (node, context) => {
|
||||
@ -28,15 +36,20 @@ export const prepareElementForCodegen: NodeTransform = (node, context) => {
|
||||
const hasProps = node.props.length > 0
|
||||
const hasChildren = node.children.length > 0
|
||||
let runtimeDirectives: DirectiveNode[] | undefined
|
||||
let componentIdentifier: string | undefined
|
||||
|
||||
if (isComponent) {
|
||||
// TODO inject import for `resolveComponent`
|
||||
// TODO inject statement for resolving component
|
||||
context.imports.add(RESOLVE_COMPONENT)
|
||||
componentIdentifier = `_component_${toValidId(node.tag)}`
|
||||
context.statements.push(
|
||||
`const ${componentIdentifier} = ${RESOLVE_COMPONENT}(${JSON.stringify(
|
||||
node.tag
|
||||
)})`
|
||||
)
|
||||
}
|
||||
|
||||
const args: CallExpression['arguments'] = [
|
||||
// TODO inject resolveComponent dep to root
|
||||
isComponent ? node.tag : `"${node.tag}"`
|
||||
isComponent ? componentIdentifier! : `"${node.tag}"`
|
||||
]
|
||||
// props
|
||||
if (hasProps) {
|
||||
@ -54,13 +67,13 @@ export const prepareElementForCodegen: NodeTransform = (node, context) => {
|
||||
}
|
||||
|
||||
const { loc } = node
|
||||
// TODO inject import for `h`
|
||||
const vnode = createCallExpression(`h`, args, loc)
|
||||
context.imports.add(CREATE_ELEMENT)
|
||||
const vnode = createCallExpression(CREATE_ELEMENT, args, loc)
|
||||
|
||||
if (runtimeDirectives && runtimeDirectives.length) {
|
||||
// TODO inject import for `applyDirectives`
|
||||
context.imports.add(APPLY_DIRECTIVES)
|
||||
node.codegenNode = createCallExpression(
|
||||
`applyDirectives`,
|
||||
APPLY_DIRECTIVES,
|
||||
[
|
||||
vnode,
|
||||
createArrayExpression(
|
||||
@ -174,9 +187,16 @@ function createDirectiveArgs(
|
||||
dir: DirectiveNode,
|
||||
context: TransformContext
|
||||
): ArrayExpression {
|
||||
// TODO inject import for `resolveDirective`
|
||||
// TODO inject statement for resolving directive
|
||||
const dirArgs: ArrayExpression['elements'] = [dir.name]
|
||||
// inject import for `resolveDirective`
|
||||
context.imports.add(RESOLVE_DIRECTIVE)
|
||||
// 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
|
||||
if (dir.exp) dirArgs.push(dir.exp)
|
||||
if (dir.arg) dirArgs.push(dir.arg)
|
||||
|
@ -2,18 +2,17 @@ import { createStructuralDirectiveTransform } from '../transform'
|
||||
import { NodeTypes, ExpressionNode, createExpression } from '../ast'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import { getInnerRange } from '../utils'
|
||||
import { RENDER_LIST } from '../runtimeConstants'
|
||||
|
||||
const forAliasRE = /([\s\S]*?)(?:(?<=\))|\s+)(?:in|of)\s+([\s\S]*)/
|
||||
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
||||
const stripParensRE = /^\(|\)$/g
|
||||
|
||||
export const RENDER_LIST_HELPER = `renderList`
|
||||
|
||||
export const transformFor = createStructuralDirectiveTransform(
|
||||
'for',
|
||||
(node, dir, context) => {
|
||||
if (dir.exp) {
|
||||
// TODO inject helper import
|
||||
context.imports.add(RENDER_LIST)
|
||||
const aliases = parseAliasExpressions(dir.exp.content)
|
||||
|
||||
if (aliases) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DirectiveTransform } from '../transform'
|
||||
import { createObjectProperty, createExpression } from '../ast'
|
||||
import { capitalize } from '@vue/shared'
|
||||
import { CAPITALIZE } from '../runtimeConstants'
|
||||
|
||||
// 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
|
||||
@ -9,8 +10,7 @@ export const transformOn: DirectiveTransform = (dir, context) => {
|
||||
const arg = dir.arg!
|
||||
const eventName = arg.isStatic
|
||||
? 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
|
||||
// other modifiers are handled in compiler-dom
|
||||
return {
|
||||
|
@ -39,6 +39,7 @@ export {
|
||||
export { applyDirectives } from './directives'
|
||||
export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
|
||||
export { renderList } from './helpers/renderList'
|
||||
export { capitalize } from '@vue/shared'
|
||||
|
||||
// Internal, for integration with runtime compiler
|
||||
export { registerRuntimeCompiler } from './component'
|
||||
|
Loading…
x
Reference in New Issue
Block a user