فهرست منبع

feat(compiler): handle runtime helper injection

Evan You 6 سال پیش
والد
کامیت
8076ce1f28

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 126 - 0
packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap


+ 2 - 0
packages/compiler-core/src/ast.ts

@@ -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 {

+ 13 - 9
packages/compiler-core/src/codegen.ts

@@ -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) {

+ 2 - 0
packages/compiler-core/src/parse.ts

@@ -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 - 0
packages/compiler-core/src/runtimeConstants.ts

@@ -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`

+ 17 - 5
packages/compiler-core/src/transform.ts

@@ -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)
   }
   }
 }
 }

+ 31 - 11
packages/compiler-core/src/transforms/element.ts

@@ -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`
-        // 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'] = [
       const args: CallExpression['arguments'] = [
-        // TODO inject resolveComponent dep to root
-        isComponent ? node.tag : `"${node.tag}"`
+        isComponent ? componentIdentifier! : `"${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`
-      const vnode = createCallExpression(`h`, args, loc)
+      context.imports.add(CREATE_ELEMENT)
+      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`
-  // 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
   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 - 3
packages/compiler-core/src/transforms/vFor.ts

@@ -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) {

+ 2 - 2
packages/compiler-core/src/transforms/vOn.ts

@@ -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 {

+ 1 - 0
packages/runtime-core/src/index.ts

@@ -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'

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است