Przeglądaj źródła

wip(ssr): v-bind basic usage

Evan You 6 lat temu
rodzic
commit
6a5ed49ea9

+ 26 - 0
packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts

@@ -0,0 +1,26 @@
+import {
+  baseParse as parse,
+  transform,
+  ElementNode,
+  CallExpression,
+  noopDirectiveTransform
+} from '../../src'
+import { transformElement } from '../../src/transforms/transformElement'
+
+describe('compiler: noop directive transform', () => {
+  test('should add no props to DOM', () => {
+    const ast = parse(`<div v-noop/>`)
+    transform(ast, {
+      nodeTransforms: [transformElement],
+      directiveTransforms: {
+        noop: noopDirectiveTransform
+      }
+    })
+    const node = ast.children[0] as ElementNode
+    const codegenArgs = (node.codegenNode as CallExpression).arguments
+
+    // As v-noop adds no properties the codegen should be identical to
+    // rendering a div with no props or reactive data (so just the tag as the arg)
+    expect(codegenArgs.length).toBe(1)
+  })
+})

+ 1 - 2
packages/compiler-core/__tests__/transforms/transformElement.spec.ts

@@ -405,8 +405,7 @@ describe('compiler: element transform', () => {
         foo(dir) {
           _dir = dir
           return {
-            props: [createObjectProperty(dir.arg!, dir.exp!)],
-            needRuntime: false
+            props: [createObjectProperty(dir.arg!, dir.exp!)]
           }
         }
       }

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

@@ -26,6 +26,7 @@ export {
 export * from './ast'
 export * from './utils'
 export { registerRuntimeHelpers } from './runtimeHelpers'
+export { noopDirectiveTransform } from './transforms/noopDirectiveTransform'
 
 // expose transforms so higher-order compilers can import and extend them
 export { transformModel } from './transforms/vModel'

+ 2 - 1
packages/compiler-core/src/options.ts

@@ -28,7 +28,8 @@ export interface ParserOptions {
 
 export interface TransformOptions {
   nodeTransforms?: NodeTransform[]
-  directiveTransforms?: { [name: string]: DirectiveTransform | undefined }
+  directiveTransforms?: Record<string, DirectiveTransform | undefined>
+  ssrDirectiveTransforms?: Record<string, DirectiveTransform | undefined>
   isBuiltInComponent?: (tag: string) => symbol | void
   // Transform expressions like {{ foo }} to `_ctx.foo`.
   // If this option is false, the generated code will be wrapped in a

+ 3 - 1
packages/compiler-core/src/transform.ts

@@ -61,7 +61,7 @@ export type DirectiveTransform = (
 
 export interface DirectiveTransformResult {
   props: Property[]
-  needRuntime: boolean | symbol
+  needRuntime?: boolean | symbol
 }
 
 // A structural directive transform is a technically a NodeTransform;
@@ -114,6 +114,7 @@ function createTransformContext(
     cacheHandlers = false,
     nodeTransforms = [],
     directiveTransforms = {},
+    ssrDirectiveTransforms = {},
     isBuiltInComponent = NOOP,
     ssr = false,
     onError = defaultOnError
@@ -126,6 +127,7 @@ function createTransformContext(
     cacheHandlers,
     nodeTransforms,
     directiveTransforms,
+    ssrDirectiveTransforms,
     isBuiltInComponent,
     ssr,
     onError,

+ 3 - 0
packages/compiler-core/src/transforms/noopDirectiveTransform.ts

@@ -0,0 +1,3 @@
+import { DirectiveTransform } from '../transform'
+
+export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })

+ 1 - 2
packages/compiler-core/src/transforms/vBind.ts

@@ -30,7 +30,6 @@ export const transformBind: DirectiveTransform = (dir, node, context) => {
   return {
     props: [
       createObjectProperty(arg!, exp || createSimpleExpression('', true, loc))
-    ],
-    needRuntime: false
+    ]
   }
 }

+ 1 - 1
packages/compiler-core/src/transforms/vModel.ts

@@ -101,5 +101,5 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
 }
 
 function createTransformProps(props: Property[] = []) {
-  return { props, needRuntime: false }
+  return { props }
 }

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

@@ -99,8 +99,7 @@ export const transformOn: DirectiveTransform = (
         eventName,
         exp || createSimpleExpression(`() => {}`, false, loc)
       )
-    ],
-    needRuntime: false
+    ]
   }
 
   // apply extended compiler augmentor

+ 0 - 30
packages/compiler-dom/__tests__/transforms/vCloak.spec.ts

@@ -1,30 +0,0 @@
-import {
-  baseParse as parse,
-  transform,
-  ElementNode,
-  CallExpression
-} from '@vue/compiler-core'
-import { transformCloak } from '../../src/transforms/vCloak'
-import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
-
-function transformWithCloak(template: string) {
-  const ast = parse(template)
-  transform(ast, {
-    nodeTransforms: [transformElement],
-    directiveTransforms: {
-      cloak: transformCloak
-    }
-  })
-  return ast.children[0] as ElementNode
-}
-
-describe('compiler: v-cloak transform', () => {
-  test('should add no props to DOM', () => {
-    const node = transformWithCloak(`<div v-cloak/>`)
-    const codegenArgs = (node.codegenNode as CallExpression).arguments
-
-    // As v-cloak adds no properties the codegen should be identical to
-    // rendering a div with no props or reactive data (so just the tag as the arg)
-    expect(codegenArgs.length).toBe(1)
-  })
-})

+ 2 - 1
packages/compiler-dom/src/errors.ts

@@ -28,7 +28,8 @@ export const enum DOMErrorCodes {
   X_V_MODEL_ON_INVALID_ELEMENT,
   X_V_MODEL_ARG_ON_ELEMENT,
   X_V_MODEL_ON_FILE_INPUT_ELEMENT,
-  X_V_SHOW_NO_EXPRESSION
+  X_V_SHOW_NO_EXPRESSION,
+  __EXTEND_POINT__
 }
 
 export const DOMErrorMessages: { [code: number]: string } = {

+ 4 - 3
packages/compiler-dom/src/index.ts

@@ -5,12 +5,12 @@ import {
   CodegenResult,
   isBuiltInType,
   ParserOptions,
-  RootNode
+  RootNode,
+  noopDirectiveTransform
 } from '@vue/compiler-core'
 import { parserOptionsMinimal } from './parserOptionsMinimal'
 import { parserOptionsStandard } from './parserOptionsStandard'
 import { transformStyle } from './transforms/transformStyle'
-import { transformCloak } from './transforms/vCloak'
 import { transformVHtml } from './transforms/vHtml'
 import { transformVText } from './transforms/vText'
 import { transformModel } from './transforms/vModel'
@@ -31,7 +31,7 @@ export function compile(
     ...options,
     nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])],
     directiveTransforms: {
-      cloak: transformCloak,
+      cloak: noopDirectiveTransform,
       html: transformVHtml,
       text: transformVText,
       model: transformModel, // override compiler-core
@@ -56,4 +56,5 @@ export function parse(template: string, options: ParserOptions = {}): RootNode {
   })
 }
 
+export { DOMErrorCodes } from './errors'
 export * from '@vue/compiler-core'

+ 0 - 5
packages/compiler-dom/src/transforms/vCloak.ts

@@ -1,5 +0,0 @@
-import { DirectiveTransform } from '@vue/compiler-core'
-
-export const transformCloak: DirectiveTransform = () => {
-  return { props: [], needRuntime: false }
-}

+ 1 - 2
packages/compiler-dom/src/transforms/vHtml.ts

@@ -24,7 +24,6 @@ export const transformVHtml: DirectiveTransform = (dir, node, context) => {
         createSimpleExpression(`innerHTML`, true, loc),
         exp || createSimpleExpression('', true)
       )
-    ],
-    needRuntime: false
+    ]
   }
 }

+ 1 - 2
packages/compiler-dom/src/transforms/vOn.ts

@@ -102,8 +102,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
     }
 
     return {
-      props: [createObjectProperty(key, handlerExp)],
-      needRuntime: false
+      props: [createObjectProperty(key, handlerExp)]
     }
   })
 }

+ 1 - 2
packages/compiler-dom/src/transforms/vText.ts

@@ -24,7 +24,6 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
         createSimpleExpression(`textContent`, true, loc),
         exp || createSimpleExpression('', true)
       )
-    ],
-    needRuntime: false
+    ]
   }
 }

+ 13 - 0
packages/compiler-ssr/__tests__/ssrVBind.spec.ts

@@ -0,0 +1,13 @@
+import { compile } from '../src'
+
+describe('ssr: v-bind', () => {
+  test('basic', () => {
+    expect(compile(`<div :id="id"/>`).code).toMatchInlineSnapshot(`
+      "const { _renderAttr } = require(\\"vue\\")
+
+      return function ssrRender(_ctx, _push, _parent) {
+        _push(\`<div\${_renderAttr(\\"id\\", _ctx.id)}></div>\`)
+      }"
+    `)
+  })
+})

+ 25 - 0
packages/compiler-ssr/src/errors.ts

@@ -0,0 +1,25 @@
+import {
+  SourceLocation,
+  CompilerError,
+  createCompilerError,
+  DOMErrorCodes
+} from '@vue/compiler-dom'
+
+export interface SSRCompilerError extends CompilerError {
+  code: SSRErrorCodes
+}
+
+export function createSSRCompilerError(
+  code: SSRErrorCodes,
+  loc?: SourceLocation
+): SSRCompilerError {
+  return createCompilerError(code, loc, SSRErrorMessages)
+}
+
+export const enum SSRErrorCodes {
+  X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM = DOMErrorCodes.__EXTEND_POINT__
+}
+
+export const SSRErrorMessages: { [code: number]: string } = {
+  [SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM]: `Custom directive is missing corresponding SSR transform and will be ignored.`
+}

+ 15 - 9
packages/compiler-ssr/src/index.ts

@@ -7,20 +7,22 @@ import {
   CompilerOptions,
   transformExpression,
   trackVForSlotScopes,
-  trackSlotScopes
+  trackSlotScopes,
+  noopDirectiveTransform
 } from '@vue/compiler-dom'
 import { ssrCodegenTransform } from './ssrCodegenTransform'
-import { ssrTransformIf } from './transforms/ssrVIf'
-import { ssrTransformFor } from './transforms/ssrVFor'
 import { ssrTransformElement } from './transforms/ssrTransformElement'
 import { ssrTransformComponent } from './transforms/ssrTransformComponent'
 import { ssrTransformSlotOutlet } from './transforms/ssrTransformSlotOutlet'
-
-export interface SSRCompilerOptions extends CompilerOptions {}
+import { ssrTransformIf } from './transforms/ssrVIf'
+import { ssrTransformFor } from './transforms/ssrVFor'
+import { ssrVBind } from './transforms/ssrVBind'
+import { ssrVModel } from './transforms/ssrVModel'
+import { ssrVShow } from './transforms/ssrVShow'
 
 export function compile(
   template: string,
-  options: SSRCompilerOptions = {}
+  options: CompilerOptions = {}
 ): CodegenResult {
   options = {
     mode: 'cjs',
@@ -50,9 +52,13 @@ export function compile(
       trackSlotScopes,
       ...(options.nodeTransforms || []) // user transforms
     ],
-    directiveTransforms: {
-      // TODO server-side directive transforms
-      ...(options.directiveTransforms || {}) // user transforms
+    ssrDirectiveTransforms: {
+      on: noopDirectiveTransform,
+      cloak: noopDirectiveTransform,
+      bind: ssrVBind,
+      model: ssrVModel,
+      show: ssrVShow,
+      ...(options.ssrDirectiveTransforms || {}) // user transforms
     }
   })
 

+ 40 - 7
packages/compiler-ssr/src/transforms/ssrTransformElement.ts

@@ -4,9 +4,12 @@ import {
   ElementTypes,
   TemplateLiteral,
   createTemplateLiteral,
-  createInterpolation
+  createInterpolation,
+  createCallExpression
 } from '@vue/compiler-dom'
 import { escapeHtml } from '@vue/shared'
+import { createSSRCompilerError, SSRErrorCodes } from '../errors'
+import { SSR_RENDER_ATTR } from '../runtimeHelpers'
 
 export const ssrTransformElement: NodeTransform = (node, context) => {
   if (
@@ -19,10 +22,25 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
       const openTag: TemplateLiteral['elements'] = [`<${node.tag}`]
       let rawChildren
 
+      // v-bind="obj" or v-bind:[key] can potentially overwrite other static
+      // attrs and can affect final rendering result, so when they are present
+      // we need to bail out to full `renderAttrs`
+      const hasDynamicVBind = node.props.some(
+        p =>
+          p.type === NodeTypes.DIRECTIVE &&
+          p.name === 'bind' &&
+          (!p.arg || // v-bind="obj"
+          p.arg.type !== NodeTypes.SIMPLE_EXPRESSION || // v-bind:[_ctx.foo]
+            !p.arg.isStatic) // v-bind:[foo]
+      )
+
+      if (hasDynamicVBind) {
+      }
+
       for (let i = 0; i < node.props.length; i++) {
         const prop = node.props[i]
+        // special cases with children override
         if (prop.type === NodeTypes.DIRECTIVE) {
-          // special cases with children override
           if (prop.name === 'html' && prop.exp) {
             node.children = []
             rawChildren = prop.exp
@@ -40,13 +58,28 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
           ) {
             node.children = [createInterpolation(prop.exp, prop.loc)]
             // TODO handle <textrea> with dynamic v-bind
-          } else {
-            const directiveTransform = context.directiveTransforms[prop.name]
+          } else if (!hasDynamicVBind) {
+            // Directive transforms.
+            const directiveTransform = context.ssrDirectiveTransforms[prop.name]
             if (directiveTransform) {
-              // TODO directive transforms
+              const { props } = directiveTransform(prop, node, context)
+              for (let j = 0; j < props.length; j++) {
+                const { key, value } = props[i]
+                openTag.push(
+                  createCallExpression(context.helper(SSR_RENDER_ATTR), [
+                    key,
+                    value
+                  ])
+                )
+              }
             } else {
               // no corresponding ssr directive transform found.
-              // TODO emit error
+              context.onError(
+                createSSRCompilerError(
+                  SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM,
+                  prop.loc
+                )
+              )
             }
           }
         } else {
@@ -54,7 +87,7 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
           if (node.tag === 'textarea' && prop.name === 'value' && prop.value) {
             node.children = []
             rawChildren = escapeHtml(prop.value.content)
-          } else {
+          } else if (!hasDynamicVBind) {
             // static prop
             openTag.push(
               ` ${prop.name}` +

+ 18 - 1
packages/compiler-ssr/src/transforms/ssrVBind.ts

@@ -1 +1,18 @@
-// TODO
+import { DirectiveTransform, createObjectProperty } from '@vue/compiler-dom'
+
+export const ssrVBind: DirectiveTransform = (dir, node, context) => {
+  if (!dir.exp) {
+    // error
+    return { props: [] }
+  } else {
+    // TODO modifiers
+    return {
+      props: [
+        createObjectProperty(
+          dir.arg!, // v-bind="obj" is handled separately
+          dir.exp
+        )
+      ]
+    }
+  }
+}

+ 0 - 1
packages/compiler-ssr/src/transforms/ssrVCloak.ts

@@ -1 +0,0 @@
-// TODO

+ 7 - 1
packages/compiler-ssr/src/transforms/ssrVModel.ts

@@ -1 +1,7 @@
-// TODO
+import { DirectiveTransform } from '@vue/compiler-dom'
+
+export const ssrVModel: DirectiveTransform = (dir, node, context) => {
+  return {
+    props: []
+  }
+}

+ 0 - 1
packages/compiler-ssr/src/transforms/ssrVOn.ts

@@ -1 +0,0 @@
-// TODO

+ 7 - 1
packages/compiler-ssr/src/transforms/ssrVShow.ts

@@ -1 +1,7 @@
-// TODO
+import { DirectiveTransform } from '@vue/compiler-dom'
+
+export const ssrVShow: DirectiveTransform = (dir, node, context) => {
+  return {
+    props: []
+  }
+}