]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(ssr): v-bind basic usage
authorEvan You <yyx990803@gmail.com>
Tue, 4 Feb 2020 17:20:51 +0000 (12:20 -0500)
committerEvan You <yyx990803@gmail.com>
Tue, 4 Feb 2020 17:21:04 +0000 (12:21 -0500)
25 files changed:
packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts [new file with mode: 0644]
packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/options.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/noopDirectiveTransform.ts [new file with mode: 0644]
packages/compiler-core/src/transforms/vBind.ts
packages/compiler-core/src/transforms/vModel.ts
packages/compiler-core/src/transforms/vOn.ts
packages/compiler-dom/__tests__/transforms/vCloak.spec.ts [deleted file]
packages/compiler-dom/src/errors.ts
packages/compiler-dom/src/index.ts
packages/compiler-dom/src/transforms/vCloak.ts [deleted file]
packages/compiler-dom/src/transforms/vHtml.ts
packages/compiler-dom/src/transforms/vOn.ts
packages/compiler-dom/src/transforms/vText.ts
packages/compiler-ssr/__tests__/ssrVBind.spec.ts [new file with mode: 0644]
packages/compiler-ssr/src/errors.ts [new file with mode: 0644]
packages/compiler-ssr/src/index.ts
packages/compiler-ssr/src/transforms/ssrTransformElement.ts
packages/compiler-ssr/src/transforms/ssrVBind.ts
packages/compiler-ssr/src/transforms/ssrVCloak.ts [deleted file]
packages/compiler-ssr/src/transforms/ssrVModel.ts
packages/compiler-ssr/src/transforms/ssrVOn.ts [deleted file]
packages/compiler-ssr/src/transforms/ssrVShow.ts

diff --git a/packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts b/packages/compiler-core/__tests__/transforms/noopDirectiveTransform.spec.ts
new file mode 100644 (file)
index 0000000..f48d3e8
--- /dev/null
@@ -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)
+  })
+})
index f411f4ff8ec9fa020f9eb164e5172072f5cdf5b1..dc581f0f09de1d1b7d78a72488ef4415a0ee26f0 100644 (file)
@@ -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!)]
           }
         }
       }
index c9c80e5737249ef5548769b7214d74a599cc5ffe..bb4a23d400f973003dd5adeacd745c160ce33959 100644 (file)
@@ -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'
index f6dde0fa64dd2a66b03f921f02e29334f4ae9f14..6ec92e7f80e4032c33b100f5645372a332bf9e82 100644 (file)
@@ -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
index 35c81e8be6f10588e39e6d9a01475148d7ae6412..940e7fe4eb763e90908581fadd54f9c6d90bc999 100644 (file)
@@ -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,
diff --git a/packages/compiler-core/src/transforms/noopDirectiveTransform.ts b/packages/compiler-core/src/transforms/noopDirectiveTransform.ts
new file mode 100644 (file)
index 0000000..5975069
--- /dev/null
@@ -0,0 +1,3 @@
+import { DirectiveTransform } from '../transform'
+
+export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })
index 39582af5ccd66039d956ff5958393819e4f864bc..1e5d6a98dd393d7b9c66d71d6ad3d0acceb409f1 100644 (file)
@@ -30,7 +30,6 @@ export const transformBind: DirectiveTransform = (dir, node, context) => {
   return {
     props: [
       createObjectProperty(arg!, exp || createSimpleExpression('', true, loc))
-    ],
-    needRuntime: false
+    ]
   }
 }
index 0434894911e8a1c2a62dc888b7b736d59bdf530b..8e13cc10a3976540c1cebb382cddc8cf6dd9df1e 100644 (file)
@@ -101,5 +101,5 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
 }
 
 function createTransformProps(props: Property[] = []) {
-  return { props, needRuntime: false }
+  return { props }
 }
index a43f5cbe8583f4d4a4703714a95c7b5a553fa860..d6ecd14587e510f3b0bad6c74adb6249b338ddc8 100644 (file)
@@ -99,8 +99,7 @@ export const transformOn: DirectiveTransform = (
         eventName,
         exp || createSimpleExpression(`() => {}`, false, loc)
       )
-    ],
-    needRuntime: false
+    ]
   }
 
   // apply extended compiler augmentor
diff --git a/packages/compiler-dom/__tests__/transforms/vCloak.spec.ts b/packages/compiler-dom/__tests__/transforms/vCloak.spec.ts
deleted file mode 100644 (file)
index 03d7f71..0000000
+++ /dev/null
@@ -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)
-  })
-})
index 7dd83fce3d6735dadb6bcfc009795d50ff324903..87579a8cdbd88e28ae597fafde82371b3784f637 100644 (file)
@@ -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 } = {
index ba9aa8727b8c5b47b9553bad47c7f793c4b13bf9..6013ba931d1e76a255f259f5082865824a98eda2 100644 (file)
@@ -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'
diff --git a/packages/compiler-dom/src/transforms/vCloak.ts b/packages/compiler-dom/src/transforms/vCloak.ts
deleted file mode 100644 (file)
index 3a552b1..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-import { DirectiveTransform } from '@vue/compiler-core'
-
-export const transformCloak: DirectiveTransform = () => {
-  return { props: [], needRuntime: false }
-}
index 9f77539daa64694eaf6977d752115a5de999f214..e42451a58c288205de665fcc145a177a75cddab6 100644 (file)
@@ -24,7 +24,6 @@ export const transformVHtml: DirectiveTransform = (dir, node, context) => {
         createSimpleExpression(`innerHTML`, true, loc),
         exp || createSimpleExpression('', true)
       )
-    ],
-    needRuntime: false
+    ]
   }
 }
index 8c4972e0afa48819e1398450fbf063d80a7627cb..dd6696b824aa90fa27a273420bfe06e49fbe6da4 100644 (file)
@@ -102,8 +102,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
     }
 
     return {
-      props: [createObjectProperty(key, handlerExp)],
-      needRuntime: false
+      props: [createObjectProperty(key, handlerExp)]
     }
   })
 }
index e28ca9a8ed6cfab3f340aa81e088fd3f3a4d6df9..0969ce95ecc53fafc206ec03767180a1ac5cc09f 100644 (file)
@@ -24,7 +24,6 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
         createSimpleExpression(`textContent`, true, loc),
         exp || createSimpleExpression('', true)
       )
-    ],
-    needRuntime: false
+    ]
   }
 }
diff --git a/packages/compiler-ssr/__tests__/ssrVBind.spec.ts b/packages/compiler-ssr/__tests__/ssrVBind.spec.ts
new file mode 100644 (file)
index 0000000..83c049f
--- /dev/null
@@ -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>\`)
+      }"
+    `)
+  })
+})
diff --git a/packages/compiler-ssr/src/errors.ts b/packages/compiler-ssr/src/errors.ts
new file mode 100644 (file)
index 0000000..375bea6
--- /dev/null
@@ -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.`
+}
index 3d4f5d95fb8498927a6b5be502dadafb775619af..b26053f8e23e4b7e7eaa6002cc12b444d876e48c 100644 (file)
@@ -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
     }
   })
 
index 2f37023b53df2bccb7f73ebafa9acbf0d4d2758e..d80860cae18123236dd7f0d6e48340e11e639ff6 100644 (file)
@@ -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}` +
index 70b786d12ed055a08b57f5cf47f717bf6a266301..072d54c3db6fe388d4fc05e1a27c4bd0e4d10211 100644 (file)
@@ -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
+        )
+      ]
+    }
+  }
+}
diff --git a/packages/compiler-ssr/src/transforms/ssrVCloak.ts b/packages/compiler-ssr/src/transforms/ssrVCloak.ts
deleted file mode 100644 (file)
index 70b786d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-// TODO
index 70b786d12ed055a08b57f5cf47f717bf6a266301..b7e59be7f43ddcdaecd35babfc5f39ead07ae142 100644 (file)
@@ -1 +1,7 @@
-// TODO
+import { DirectiveTransform } from '@vue/compiler-dom'
+
+export const ssrVModel: DirectiveTransform = (dir, node, context) => {
+  return {
+    props: []
+  }
+}
diff --git a/packages/compiler-ssr/src/transforms/ssrVOn.ts b/packages/compiler-ssr/src/transforms/ssrVOn.ts
deleted file mode 100644 (file)
index 70b786d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-// TODO
index 70b786d12ed055a08b57f5cf47f717bf6a266301..eced51154c5c9e7667d3039243adb28363b208d3 100644 (file)
@@ -1 +1,7 @@
-// TODO
+import { DirectiveTransform } from '@vue/compiler-dom'
+
+export const ssrVShow: DirectiveTransform = (dir, node, context) => {
+  return {
+    props: []
+  }
+}