]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(compiler-ssr): v-bind with static keys
authorEvan You <yyx990803@gmail.com>
Tue, 4 Feb 2020 21:47:12 +0000 (16:47 -0500)
committerEvan You <yyx990803@gmail.com>
Tue, 4 Feb 2020 21:47:12 +0000 (16:47 -0500)
18 files changed:
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/index.ts
packages/compiler-ssr/__tests__/ssrElement.spec.ts
packages/compiler-ssr/__tests__/ssrText.spec.ts
packages/compiler-ssr/__tests__/ssrVBind.spec.ts [deleted file]
packages/compiler-ssr/__tests__/ssrVFor.spec.ts
packages/compiler-ssr/__tests__/utils.ts
packages/compiler-ssr/src/errors.ts
packages/compiler-ssr/src/index.ts
packages/compiler-ssr/src/runtimeHelpers.ts
packages/compiler-ssr/src/transforms/ssrTransformElement.ts
packages/compiler-ssr/src/transforms/ssrVBind.ts [deleted file]
packages/compiler-ssr/src/transforms/ssrVModel.ts
packages/compiler-ssr/src/transforms/ssrVShow.ts
packages/server-renderer/__tests__/renderAttrs.spec.ts [moved from packages/server-renderer/__tests__/renderProps.spec.ts with 100% similarity]
packages/server-renderer/src/helpers/renderAttrs.ts
packages/server-renderer/src/index.ts

index d35530d6e1aef6bf0ae833ad32608d071125fe52..eb824a0035f09d3e01e3ed9d5edee8cf3c3f590b 100644 (file)
@@ -301,9 +301,10 @@ export interface SequenceExpression extends Node {
 
 export interface ConditionalExpression extends Node {
   type: NodeTypes.JS_CONDITIONAL_EXPRESSION
-  test: ExpressionNode
+  test: JSChildNode
   consequent: JSChildNode
   alternate: JSChildNode
+  newline: boolean
 }
 
 export interface CacheExpression extends Node {
@@ -648,13 +649,15 @@ export function createSequenceExpression(
 export function createConditionalExpression(
   test: ConditionalExpression['test'],
   consequent: ConditionalExpression['consequent'],
-  alternate: ConditionalExpression['alternate']
+  alternate: ConditionalExpression['alternate'],
+  newline = true
 ): ConditionalExpression {
   return {
     type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
     test,
     consequent,
     alternate,
+    newline,
     loc: locStub
   }
 }
index e609541e2d5dcd17db76420d507bbd408e93c542..b0acb69dae6b867abc25436b8e05df48bedc9b53 100644 (file)
@@ -685,7 +685,7 @@ function genConditionalExpression(
   node: ConditionalExpression,
   context: CodegenContext
 ) {
-  const { test, consequent, alternate } = node
+  const { test, consequent, alternate, newline: needNewline } = node
   const { push, indent, deindent, newline } = context
   if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
     const needsParens = !isSimpleIdentifier(test.content)
@@ -694,15 +694,15 @@ function genConditionalExpression(
     needsParens && push(`)`)
   } else {
     push(`(`)
-    genCompoundExpression(test, context)
+    genNode(test, context)
     push(`)`)
   }
-  indent()
+  needNewline && indent()
   context.indentLevel++
   push(`? `)
   genNode(consequent, context)
   context.indentLevel--
-  newline()
+  needNewline && newline()
   push(`: `)
   const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
   if (!isNested) {
@@ -712,7 +712,7 @@ function genConditionalExpression(
   if (!isNested) {
     context.indentLevel--
   }
-  deindent(true /* without newline */)
+  needNewline && deindent(true /* without newline */)
 }
 
 function genSequenceExpression(
@@ -748,15 +748,17 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
 function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
   const { push, indent, deindent } = context
   push('`')
-  for (let i = 0; i < node.elements.length; i++) {
+  const l = node.elements.length
+  const multilines = l > 3
+  for (let i = 0; i < l; i++) {
     const e = node.elements[i]
     if (isString(e)) {
       push(e.replace(/`/g, '\\`'))
     } else {
       push('${')
-      indent()
+      if (multilines) indent()
       genNode(e, context)
-      deindent()
+      if (multilines) deindent()
       push('}')
     }
   }
index bb4a23d400f973003dd5adeacd745c160ce33959..07d1a4052038c0e1a80696e21aca4ced25e912af 100644 (file)
@@ -31,6 +31,7 @@ export { noopDirectiveTransform } from './transforms/noopDirectiveTransform'
 // expose transforms so higher-order compilers can import and extend them
 export { transformModel } from './transforms/vModel'
 export { transformOn } from './transforms/vOn'
+export { transformBind } from './transforms/vBind'
 
 // exported for compiler-ssr
 export { processIfBranches } from './transforms/vIf'
index 521f1531f67fdfa9be54d7c868bb069646c518aa..994fbeb8d17f63427750370cb28e1df2916f051e 100644 (file)
@@ -10,12 +10,6 @@ describe('ssr: element', () => {
     )
   })
 
-  test('static attrs', () => {
-    expect(
-      getCompiledString(`<div id="foo" class="bar"></div>`)
-    ).toMatchInlineSnapshot(`"\`<div id=\\"foo\\" class=\\"bar\\"></div>\`"`)
-  })
-
   test('nested elements', () => {
     expect(
       getCompiledString(`<div><span></span><span></span></div>`)
@@ -26,27 +20,71 @@ describe('ssr: element', () => {
     expect(getCompiledString(`<input>`)).toMatchInlineSnapshot(`"\`<input>\`"`)
   })
 
-  test('v-html', () => {
-    expect(getCompiledString(`<div v-html="foo"/>`)).toMatchInlineSnapshot(
-      `"\`<div>\${_ctx.foo}</div>\`"`
-    )
-  })
+  describe('children override', () => {
+    test('v-html', () => {
+      expect(getCompiledString(`<div v-html="foo"/>`)).toMatchInlineSnapshot(
+        `"\`<div>\${_ctx.foo}</div>\`"`
+      )
+    })
 
-  test('v-text', () => {
-    expect(getCompiledString(`<div v-text="foo"/>`)).toMatchInlineSnapshot(
-      `"\`<div>\${_interpolate(_ctx.foo)}</div>\`"`
-    )
-  })
+    test('v-text', () => {
+      expect(getCompiledString(`<div v-text="foo"/>`)).toMatchInlineSnapshot(
+        `"\`<div>\${_interpolate(_ctx.foo)}</div>\`"`
+      )
+    })
 
-  test('<textarea> with dynamic value', () => {
-    expect(getCompiledString(`<textarea :value="foo"/>`)).toMatchInlineSnapshot(
-      `"\`<textarea>\${_interpolate(_ctx.foo)}</textarea>\`"`
-    )
+    test('<textarea> with dynamic value', () => {
+      expect(
+        getCompiledString(`<textarea :value="foo"/>`)
+      ).toMatchInlineSnapshot(
+        `"\`<textarea>\${_interpolate(_ctx.foo)}</textarea>\`"`
+      )
+    })
+
+    test('<textarea> with static value', () => {
+      expect(
+        getCompiledString(`<textarea value="fo&gt;o"/>`)
+      ).toMatchInlineSnapshot(`"\`<textarea>fo&gt;o</textarea>\`"`)
+    })
   })
 
-  test('<textarea> with static value', () => {
-    expect(
-      getCompiledString(`<textarea value="fo&gt;o"/>`)
-    ).toMatchInlineSnapshot(`"\`<textarea>fo&gt;o</textarea>\`"`)
+  describe('attrs', () => {
+    test('static attrs', () => {
+      expect(
+        getCompiledString(`<div id="foo" class="bar"></div>`)
+      ).toMatchInlineSnapshot(`"\`<div id=\\"foo\\" class=\\"bar\\"></div>\`"`)
+    })
+
+    test('v-bind:class', () => {
+      expect(
+        getCompiledString(`<div id="foo" :class="bar"></div>`)
+      ).toMatchInlineSnapshot(
+        `"\`<div id=\\"foo\\"\${_renderClass(_ctx.bar)}></div>\`"`
+      )
+    })
+
+    test('v-bind:style', () => {
+      expect(
+        getCompiledString(`<div id="foo" :style="bar"></div>`)
+      ).toMatchInlineSnapshot(
+        `"\`<div id=\\"foo\\"\${_renderStyle(_ctx.bar)}></div>\`"`
+      )
+    })
+
+    test('v-bind:key (boolean)', () => {
+      expect(
+        getCompiledString(`<input type="checkbox" :checked="checked">`)
+      ).toMatchInlineSnapshot(
+        `"\`<input type=\\"checkbox\\"\${(_ctx.checked)? \\" checked\\": \\"\\"}>\`"`
+      )
+    })
+
+    test('v-bind:key (non-boolean)', () => {
+      expect(
+        getCompiledString(`<div :id="id" class="bar"></div>`)
+      ).toMatchInlineSnapshot(
+        `"\`<div\${_renderAttr(\\"id\\", _ctx.id)} class=\\"bar\\"></div>\`"`
+      )
+    })
   })
 })
index 7ca7cded9c6f5d7b43949d04649a4ede9f15b87a..5626a6302a24bea104ed17f8bcb1870cde758d45 100644 (file)
@@ -38,7 +38,11 @@ describe('ssr: text', () => {
       "const { _interpolate } = require(\\"@vue/server-renderer\\")
 
       return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<div><span>\${_interpolate(_ctx.foo)} bar</span><span>baz \${_interpolate(_ctx.qux)}</span></div>\`)
+        _push(\`<div><span>\${
+          _interpolate(_ctx.foo)
+        } bar</span><span>baz \${
+          _interpolate(_ctx.qux)
+        }</span></div>\`)
       }"
     `)
   })
diff --git a/packages/compiler-ssr/__tests__/ssrVBind.spec.ts b/packages/compiler-ssr/__tests__/ssrVBind.spec.ts
deleted file mode 100644 (file)
index 83c049f..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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>\`)
-      }"
-    `)
-  })
-})
index ac11021ea4b4e204bf463fc46421534158433d80..064abd06650a2ca7305be0d830d13c041487ea70 100644 (file)
@@ -45,7 +45,11 @@ describe('ssr: v-for', () => {
         _renderList(_ctx.list, (row, i) => {
           _push(\`<div><!---->\`)
           _renderList(row, (j) => {
-            _push(\`<div>\${_interpolate(i)},\${_interpolate(j)}</div>\`)
+            _push(\`<div>\${
+              _interpolate(i)
+            },\${
+              _interpolate(j)
+            }</div>\`)
           })
           _push(\`<!----></div>\`)
         })
@@ -97,7 +101,11 @@ describe('ssr: v-for', () => {
       return function ssrRender(_ctx, _push, _parent) {
         _push(\`<!---->\`)
         _renderList(_ctx.list, (i) => {
-          _push(\`<!----><span>\${_interpolate(i)}</span><span>\${_interpolate(i + 1)}</span><!---->\`)
+          _push(\`<!----><span>\${
+            _interpolate(i)
+          }</span><span>\${
+            _interpolate(i + 1)
+          }</span><!---->\`)
         })
         _push(\`<!---->\`)
       }"
index 333b1e1cfe69085c6a78b36979e664bd9da29aca..cc3cd45ba28aa13325940927c096e83227205042 100644 (file)
@@ -1,5 +1,5 @@
 import { compile } from '../src'
 
 export function getCompiledString(src: string): string {
-  return compile(src).code.match(/_push\((.*)\)/)![1]
+  return compile(src).code.match(/_push\(([^]*)\)/)![1]
 }
index 375bea6856b1083f6744c03c17aceb1ad4703611..30431fb0818142f6ca9d7787e443e915c513423d 100644 (file)
@@ -17,9 +17,11 @@ export function createSSRCompilerError(
 }
 
 export const enum SSRErrorCodes {
-  X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM = DOMErrorCodes.__EXTEND_POINT__
+  X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM = DOMErrorCodes.__EXTEND_POINT__,
+  X_SSR_UNSAFE_ATTR_NAME
 }
 
 export const SSRErrorMessages: { [code: number]: string } = {
-  [SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM]: `Custom directive is missing corresponding SSR transform and will be ignored.`
+  [SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM]: `Custom directive is missing corresponding SSR transform and will be ignored.`,
+  [SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME]: `Unsafe attribute name for SSR.`
 }
index b8833e079b7088755d5fd5f7916382181a5a3c93..295c923ad59cd97cf0e806d0b527cae42d980543 100644 (file)
@@ -8,7 +8,8 @@ import {
   transformExpression,
   trackVForSlotScopes,
   trackSlotScopes,
-  noopDirectiveTransform
+  noopDirectiveTransform,
+  transformBind
 } from '@vue/compiler-dom'
 import { ssrCodegenTransform } from './ssrCodegenTransform'
 import { ssrTransformElement } from './transforms/ssrTransformElement'
@@ -16,9 +17,8 @@ import { ssrTransformComponent } from './transforms/ssrTransformComponent'
 import { ssrTransformSlotOutlet } from './transforms/ssrTransformSlotOutlet'
 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'
+import { ssrTransformModel } from './transforms/ssrVModel'
+import { ssrTransformShow } from './transforms/ssrVShow'
 
 export function compile(
   template: string,
@@ -54,9 +54,9 @@ export function compile(
     ssrDirectiveTransforms: {
       on: noopDirectiveTransform,
       cloak: noopDirectiveTransform,
-      bind: ssrVBind,
-      model: ssrVModel,
-      show: ssrVShow,
+      bind: transformBind, // reusing core v-bind
+      model: ssrTransformModel,
+      show: ssrTransformShow,
       ...(options.ssrDirectiveTransforms || {}) // user transforms
     }
   })
index 06df6b251847ccb4a6767966a0108900952e9b35..6c98e3ce10b1864c6c4db95e53770219132f616e 100644 (file)
@@ -7,6 +7,7 @@ export const SSR_RENDER_CLASS = Symbol(`renderClass`)
 export const SSR_RENDER_STYLE = Symbol(`renderStyle`)
 export const SSR_RENDER_ATTRS = Symbol(`renderAttrs`)
 export const SSR_RENDER_ATTR = Symbol(`renderAttr`)
+export const SSR_RENDER_DYNAMIC_ATTR = Symbol(`renderDynamicAttr`)
 export const SSR_RENDER_LIST = Symbol(`renderList`)
 
 // Note: these are helpers imported from @vue/server-renderer
@@ -19,5 +20,6 @@ registerRuntimeHelpers({
   [SSR_RENDER_STYLE]: `_renderStyle`,
   [SSR_RENDER_ATTRS]: `_renderAttrs`,
   [SSR_RENDER_ATTR]: `_renderAttr`,
+  [SSR_RENDER_DYNAMIC_ATTR]: `_renderDynamicAttr`,
   [SSR_RENDER_LIST]: `_renderList`
 })
index 6d89ed62ad53865499ab77f5a808566fecaf7369..7d86478283edd08228b098ccc4ff48239760a0a2 100644 (file)
@@ -5,11 +5,18 @@ import {
   TemplateLiteral,
   createTemplateLiteral,
   createInterpolation,
-  createCallExpression
+  createCallExpression,
+  createConditionalExpression,
+  createSimpleExpression
 } from '@vue/compiler-dom'
-import { escapeHtml } from '@vue/shared'
+import { escapeHtml, isBooleanAttr, isSSRSafeAttrName } from '@vue/shared'
 import { createSSRCompilerError, SSRErrorCodes } from '../errors'
-import { SSR_RENDER_ATTR } from '../runtimeHelpers'
+import {
+  SSR_RENDER_ATTR,
+  SSR_RENDER_CLASS,
+  SSR_RENDER_STYLE,
+  SSR_RENDER_DYNAMIC_ATTR
+} from '../runtimeHelpers'
 
 export const ssrTransformElement: NodeTransform = (node, context) => {
   if (
@@ -66,12 +73,58 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
               const { props } = directiveTransform(prop, node, context)
               for (let j = 0; j < props.length; j++) {
                 const { key, value } = props[j]
-                openTag.push(
-                  createCallExpression(context.helper(SSR_RENDER_ATTR), [
-                    key,
-                    value
-                  ])
-                )
+                if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
+                  const attrName = key.content
+                  // static key attr
+                  if (attrName === 'class') {
+                    openTag.push(
+                      createCallExpression(context.helper(SSR_RENDER_CLASS), [
+                        value
+                      ])
+                    )
+                  } else if (attrName === 'style') {
+                    openTag.push(
+                      createCallExpression(context.helper(SSR_RENDER_STYLE), [
+                        value
+                      ])
+                    )
+                  } else if (isBooleanAttr(attrName)) {
+                    openTag.push(
+                      createConditionalExpression(
+                        value,
+                        createSimpleExpression(' ' + attrName, true),
+                        createSimpleExpression('', true),
+                        false /* no newline */
+                      )
+                    )
+                  } else {
+                    if (isSSRSafeAttrName(attrName)) {
+                      openTag.push(
+                        createCallExpression(context.helper(SSR_RENDER_ATTR), [
+                          key,
+                          value
+                        ])
+                      )
+                    } else {
+                      context.onError(
+                        createSSRCompilerError(
+                          SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME,
+                          key.loc
+                        )
+                      )
+                    }
+                  }
+                } else {
+                  // dynamic key attr
+                  // this branch is only encountered for custom directive
+                  // transforms that returns properties with dynamic keys
+                  openTag.push(
+                    createCallExpression(
+                      context.helper(SSR_RENDER_DYNAMIC_ATTR),
+                      [key, value]
+                    )
+                  )
+                }
               }
             } else {
               // no corresponding ssr directive transform found.
diff --git a/packages/compiler-ssr/src/transforms/ssrVBind.ts b/packages/compiler-ssr/src/transforms/ssrVBind.ts
deleted file mode 100644 (file)
index 072d54c..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-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
-        )
-      ]
-    }
-  }
-}
index b7e59be7f43ddcdaecd35babfc5f39ead07ae142..077991d830f3d598234d32da0cb9db829450a9e5 100644 (file)
@@ -1,6 +1,6 @@
 import { DirectiveTransform } from '@vue/compiler-dom'
 
-export const ssrVModel: DirectiveTransform = (dir, node, context) => {
+export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
   return {
     props: []
   }
index eced51154c5c9e7667d3039243adb28363b208d3..b5671b51ac604c043d1fef1c8d8a7263beb69122 100644 (file)
@@ -1,6 +1,6 @@
 import { DirectiveTransform } from '@vue/compiler-dom'
 
-export const ssrVShow: DirectiveTransform = (dir, node, context) => {
+export const ssrTransformShow: DirectiveTransform = (dir, node, context) => {
   return {
     props: []
   }
index 4549903cb1a393698844cd92e21d7b52f978dd04..eaeb9e9bd1effe34cd91c6a09ca0798168e4dc7d 100644 (file)
@@ -33,13 +33,18 @@ export function renderAttrs(
     } else if (key === 'style') {
       ret += ` style="${renderStyle(value)}"`
     } else {
-      ret += renderAttr(key, value, tag)
+      ret += renderDynamicAttr(key, value, tag)
     }
   }
   return ret
 }
 
-export function renderAttr(key: string, value: unknown, tag?: string): string {
+// render an attr with dynamic (unknown) key.
+export function renderDynamicAttr(
+  key: string,
+  value: unknown,
+  tag?: string
+): string {
   if (value == null) {
     return ``
   }
@@ -56,6 +61,15 @@ export function renderAttr(key: string, value: unknown, tag?: string): string {
   }
 }
 
+// Render a v-bind attr with static key. The key is pre-processed at compile
+// time and we only need to check and escape value.
+export function renderAttr(key: string, value: unknown): string {
+  if (value == null) {
+    return ``
+  }
+  return ` ${key}="${escapeHtml(value)}"`
+}
+
 export function renderClass(raw: unknown): string {
   return escapeHtml(normalizeClass(raw))
 }
index 2d4c2884a7e6d371335181c78860656e7eb6a6a9..39e5c3e33088f610f2ec1be9007ca5d2ea822e76 100644 (file)
@@ -10,7 +10,8 @@ export {
   renderClass as _renderClass,
   renderStyle as _renderStyle,
   renderAttrs as _renderAttrs,
-  renderAttr as _renderAttr
+  renderAttr as _renderAttr,
+  renderDynamicAttr as _renderDynamicAttr
 } from './helpers/renderAttrs'
 export { interpolate as _interpolate } from './helpers/interpolate'
 export { renderList as _renderList } from './helpers/renderList'