]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(ssr): handle fallthrough attrs in ssr compile output
authorEvan You <yyx990803@gmail.com>
Fri, 26 Jun 2020 18:23:50 +0000 (14:23 -0400)
committerEvan You <yyx990803@gmail.com>
Fri, 26 Jun 2020 20:55:37 +0000 (16:55 -0400)
21 files changed:
packages/compiler-core/src/codegen.ts
packages/compiler-ssr/__tests__/ssrComponent.spec.ts
packages/compiler-ssr/__tests__/ssrElement.spec.ts
packages/compiler-ssr/__tests__/ssrPortal.spec.ts
packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts
packages/compiler-ssr/__tests__/ssrSuspense.spec.ts
packages/compiler-ssr/__tests__/ssrText.spec.ts
packages/compiler-ssr/__tests__/ssrVFor.spec.ts
packages/compiler-ssr/__tests__/ssrVIf.spec.ts
packages/compiler-ssr/__tests__/ssrVModel.spec.ts
packages/compiler-ssr/__tests__/ssrVShow.spec.ts
packages/compiler-ssr/__tests__/utils.ts
packages/compiler-ssr/src/index.ts
packages/compiler-ssr/src/transforms/ssrInjectFallthroughAttrs.ts [new file with mode: 0644]
packages/compiler-ssr/src/transforms/ssrTransformElement.ts
packages/runtime-core/src/componentOptions.ts
packages/runtime-core/src/vnode.ts
packages/server-renderer/__tests__/renderToString.spec.ts
packages/server-renderer/__tests__/ssrAttrFallthrough.spec.ts [new file with mode: 0644]
packages/server-renderer/src/render.ts

index b213eec0ff75f0a8e67298cb59e00bdd654833ae..e71810fc958823a6d37898104c5f725e2ea0cad6 100644 (file)
@@ -210,7 +210,7 @@ export function generate(
   if (!ssr) {
     push(`function render(_ctx, _cache) {`)
   } else {
-    push(`function ssrRender(_ctx, _push, _parent) {`)
+    push(`function ssrRender(_ctx, _push, _parent, _attrs) {`)
   }
   indent()
 
index 8c5fb0d868bb954f79e8050170eb35e06363f88f..4076749321a64cdbf5fb6f8d0a27ff08129b7713 100644 (file)
@@ -3,16 +3,16 @@ import { compile } from '../src'
 describe('ssr: components', () => {
   test('basic', () => {
     expect(compile(`<foo id="a" :prop="b" />`).code).toMatchInlineSnapshot(`
-      "const { resolveComponent: _resolveComponent } = require(\\"vue\\")
+      "const { resolveComponent: _resolveComponent, mergeProps: _mergeProps } = require(\\"vue\\")
       const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
 
-        _push(_ssrRenderComponent(_component_foo, {
+        _push(_ssrRenderComponent(_component_foo, _mergeProps({
           id: \\"a\\",
           prop: _ctx.b
-        }, null, _parent))
+        }, _attrs), null, _parent))
       }"
     `)
   })
@@ -20,21 +20,21 @@ describe('ssr: components', () => {
   test('dynamic component', () => {
     expect(compile(`<component is="foo" prop="b" />`).code)
       .toMatchInlineSnapshot(`
-      "const { resolveDynamicComponent: _resolveDynamicComponent } = require(\\"vue\\")
+      "const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps } = require(\\"vue\\")
       const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(_ssrRenderComponent(_resolveDynamicComponent(\\"foo\\"), { prop: \\"b\\" }, null, _parent))
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(_ssrRenderComponent(_resolveDynamicComponent(\\"foo\\"), _mergeProps({ prop: \\"b\\" }, _attrs), null, _parent))
       }"
     `)
 
     expect(compile(`<component :is="foo" prop="b" />`).code)
       .toMatchInlineSnapshot(`
-      "const { resolveDynamicComponent: _resolveDynamicComponent } = require(\\"vue\\")
+      "const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps } = require(\\"vue\\")
       const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(_ssrRenderComponent(_resolveDynamicComponent(_ctx.foo), { prop: \\"b\\" }, null, _parent))
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(_ssrRenderComponent(_resolveDynamicComponent(_ctx.foo), _mergeProps({ prop: \\"b\\" }, _attrs), null, _parent))
       }"
     `)
   })
@@ -45,10 +45,10 @@ describe('ssr: components', () => {
         "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, createTextVNode: _createTextVNode } = require(\\"vue\\")
         const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
 
-        return function ssrRender(_ctx, _push, _parent) {
+        return function ssrRender(_ctx, _push, _parent, _attrs) {
           const _component_foo = _resolveComponent(\\"foo\\")
 
-          _push(_ssrRenderComponent(_component_foo, null, {
+          _push(_ssrRenderComponent(_component_foo, _attrs, {
             default: _withCtx((_, _push, _parent, _scopeId) => {
               if (_push) {
                 _push(\`hello<div\${_scopeId}></div>\`)
@@ -71,10 +71,10 @@ describe('ssr: components', () => {
         "const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode } = require(\\"vue\\")
         const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
 
-        return function ssrRender(_ctx, _push, _parent) {
+        return function ssrRender(_ctx, _push, _parent, _attrs) {
           const _component_foo = _resolveComponent(\\"foo\\")
 
-          _push(_ssrRenderComponent(_component_foo, null, {
+          _push(_ssrRenderComponent(_component_foo, _attrs, {
             default: _withCtx(({ msg }, _push, _parent, _scopeId) => {
               if (_push) {
                 _push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
@@ -100,10 +100,10 @@ describe('ssr: components', () => {
         "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\")
         const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
 
-        return function ssrRender(_ctx, _push, _parent) {
+        return function ssrRender(_ctx, _push, _parent, _attrs) {
           const _component_foo = _resolveComponent(\\"foo\\")
 
-          _push(_ssrRenderComponent(_component_foo, null, {
+          _push(_ssrRenderComponent(_component_foo, _attrs, {
             default: _withCtx((_, _push, _parent, _scopeId) => {
               if (_push) {
                 _push(\`foo\`)
@@ -137,10 +137,10 @@ describe('ssr: components', () => {
         "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode, createSlots: _createSlots } = require(\\"vue\\")
         const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
 
-        return function ssrRender(_ctx, _push, _parent) {
+        return function ssrRender(_ctx, _push, _parent, _attrs) {
           const _component_foo = _resolveComponent(\\"foo\\")
 
-          _push(_ssrRenderComponent(_component_foo, null, _createSlots({ _: 1 }, [
+          _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 1 }, [
             (_ctx.ok)
               ? {
                   name: \\"named\\",
@@ -169,10 +169,10 @@ describe('ssr: components', () => {
         "const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require(\\"vue\\")
         const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
 
-        return function ssrRender(_ctx, _push, _parent) {
+        return function ssrRender(_ctx, _push, _parent, _attrs) {
           const _component_foo = _resolveComponent(\\"foo\\")
 
-          _push(_ssrRenderComponent(_component_foo, null, _createSlots({ _: 1 }, [
+          _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 1 }, [
             _renderList(_ctx.names, (key) => {
               return {
                 name: key,
@@ -210,10 +210,10 @@ describe('ssr: components', () => {
         "const { resolveComponent: _resolveComponent, withCtx: _withCtx, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = require(\\"vue\\")
         const { ssrRenderComponent: _ssrRenderComponent, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
 
-        return function ssrRender(_ctx, _push, _parent) {
+        return function ssrRender(_ctx, _push, _parent, _attrs) {
           const _component_foo = _resolveComponent(\\"foo\\")
 
-          _push(_ssrRenderComponent(_component_foo, null, {
+          _push(_ssrRenderComponent(_component_foo, _attrs, {
             foo: _withCtx(({ list }, _push, _parent, _scopeId) => {
               if (_push) {
                 if (_ctx.ok) {
@@ -270,7 +270,7 @@ describe('ssr: components', () => {
       expect(compile(`<transition><div/></transition>`).code)
         .toMatchInlineSnapshot(`
         "
-        return function ssrRender(_ctx, _push, _parent) {
+        return function ssrRender(_ctx, _push, _parent, _attrs) {
           _push(\`<div></div>\`)
         }"
       `)
@@ -278,7 +278,7 @@ describe('ssr: components', () => {
       expect(compile(`<transition-group><div/></transition-group>`).code)
         .toMatchInlineSnapshot(`
         "
-        return function ssrRender(_ctx, _push, _parent) {
+        return function ssrRender(_ctx, _push, _parent, _attrs) {
           _push(\`<!--[--><div></div><!--]-->\`)
         }"
       `)
@@ -288,7 +288,7 @@ describe('ssr: components', () => {
         "const { resolveComponent: _resolveComponent } = require(\\"vue\\")
         const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
 
-        return function ssrRender(_ctx, _push, _parent) {
+        return function ssrRender(_ctx, _push, _parent, _attrs) {
           const _component_foo = _resolveComponent(\\"foo\\")
 
           _push(_ssrRenderComponent(_component_foo, null, null, _parent))
index a3181f4e6638f27a571920a0ec636b84561b6188..55eaa50647c587147615bd928365ee217d082ab0 100644 (file)
@@ -23,23 +23,28 @@ describe('ssr: element', () => {
 
   describe('children override', () => {
     test('v-html', () => {
-      expect(getCompiledString(`<div v-html="foo"/>`)).toMatchInlineSnapshot(
-        `"\`<div>\${_ctx.foo}</div>\`"`
-      )
+      expect(getCompiledString(`<div v-html="foo"/>`)).toMatchInlineSnapshot(`
+        "\`<div>\${
+            _ctx.foo
+          }</div>\`"
+      `)
     })
 
     test('v-text', () => {
-      expect(getCompiledString(`<div v-text="foo"/>`)).toMatchInlineSnapshot(
-        `"\`<div>\${_ssrInterpolate(_ctx.foo)}</div>\`"`
-      )
+      expect(getCompiledString(`<div v-text="foo"/>`)).toMatchInlineSnapshot(`
+        "\`<div>\${
+            _ssrInterpolate(_ctx.foo)
+          }</div>\`"
+      `)
     })
 
     test('<textarea> with dynamic value', () => {
-      expect(
-        getCompiledString(`<textarea :value="foo"/>`)
-      ).toMatchInlineSnapshot(
-        `"\`<textarea>\${_ssrInterpolate(_ctx.foo)}</textarea>\`"`
-      )
+      expect(getCompiledString(`<textarea :value="foo"/>`))
+        .toMatchInlineSnapshot(`
+        "\`<textarea>\${
+            _ssrInterpolate(_ctx.foo)
+          }</textarea>\`"
+      `)
     })
 
     test('<textarea> with static value', () => {
@@ -51,13 +56,14 @@ describe('ssr: element', () => {
     test('<textarea> with dynamic v-bind', () => {
       expect(compile(`<textarea v-bind="obj">fallback</textarea>`).code)
         .toMatchInlineSnapshot(`
-        "const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
+        "const { mergeProps: _mergeProps } = require(\\"vue\\")
+        const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
 
-        return function ssrRender(_ctx, _push, _parent) {
+        return function ssrRender(_ctx, _push, _parent, _attrs) {
           let _temp0
 
           _push(\`<textarea\${
-            _ssrRenderAttrs(_temp0 = _ctx.obj, \\"textarea\\")
+            _ssrRenderAttrs(_temp0 = _mergeProps(_ctx.obj, _attrs), \\"textarea\\")
           }>\${
             _ssrInterpolate((\\"value\\" in _temp0) ? _temp0.value : \\"fallback\\")
           }</textarea>\`)
@@ -71,10 +77,11 @@ describe('ssr: element', () => {
           isCustomElement: () => true
         }).code
       ).toMatchInlineSnapshot(`
-        "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+        "const { mergeProps: _mergeProps } = require(\\"vue\\")
+        const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-        return function ssrRender(_ctx, _push, _parent) {
-          _push(\`<my-foo\${_ssrRenderAttrs(_ctx.obj, \\"my-foo\\")}></my-foo>\`)
+        return function ssrRender(_ctx, _push, _parent, _attrs) {
+          _push(\`<my-foo\${_ssrRenderAttrs(_mergeProps(_ctx.obj, _attrs), \\"my-foo\\")}></my-foo>\`)
         }"
       `)
     })
@@ -88,107 +95,126 @@ describe('ssr: element', () => {
     })
 
     test('v-bind:class', () => {
-      expect(
-        getCompiledString(`<div id="foo" :class="bar"></div>`)
-      ).toMatchInlineSnapshot(
-        `"\`<div id=\\"foo\\" class=\\"\${_ssrRenderClass(_ctx.bar)}\\"></div>\`"`
-      )
+      expect(getCompiledString(`<div id="foo" :class="bar"></div>`))
+        .toMatchInlineSnapshot(`
+        "\`<div id=\\"foo\\" class=\\"\${
+            _ssrRenderClass(_ctx.bar)
+          }\\"></div>\`"
+      `)
     })
 
     test('static class + v-bind:class', () => {
-      expect(
-        getCompiledString(`<div class="foo" :class="bar"></div>`)
-      ).toMatchInlineSnapshot(
-        `"\`<div class=\\"\${_ssrRenderClass([_ctx.bar, \\"foo\\"])}\\"></div>\`"`
-      )
+      expect(getCompiledString(`<div class="foo" :class="bar"></div>`))
+        .toMatchInlineSnapshot(`
+        "\`<div class=\\"\${
+            _ssrRenderClass([_ctx.bar, \\"foo\\"])
+          }\\"></div>\`"
+      `)
     })
 
     test('v-bind:style', () => {
-      expect(
-        getCompiledString(`<div id="foo" :style="bar"></div>`)
-      ).toMatchInlineSnapshot(
-        `"\`<div id=\\"foo\\" style=\\"\${_ssrRenderStyle(_ctx.bar)}\\"></div>\`"`
-      )
+      expect(getCompiledString(`<div id="foo" :style="bar"></div>`))
+        .toMatchInlineSnapshot(`
+        "\`<div id=\\"foo\\" style=\\"\${
+            _ssrRenderStyle(_ctx.bar)
+          }\\"></div>\`"
+      `)
     })
 
     test('static style + v-bind:style', () => {
-      expect(
-        getCompiledString(`<div style="color:red;" :style="bar"></div>`)
-      ).toMatchInlineSnapshot(
-        `"\`<div style=\\"\${_ssrRenderStyle([{\\"color\\":\\"red\\"}, _ctx.bar])}\\"></div>\`"`
-      )
+      expect(getCompiledString(`<div style="color:red;" :style="bar"></div>`))
+        .toMatchInlineSnapshot(`
+        "\`<div style=\\"\${
+            _ssrRenderStyle([{\\"color\\":\\"red\\"}, _ctx.bar])
+          }\\"></div>\`"
+      `)
     })
 
     test('v-bind:key (boolean)', () => {
-      expect(
-        getCompiledString(`<input type="checkbox" :checked="checked">`)
-      ).toMatchInlineSnapshot(
-        `"\`<input type=\\"checkbox\\"\${(_ctx.checked) ? \\" checked\\" : \\"\\"}>\`"`
-      )
+      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\${_ssrRenderAttr(\\"id\\", _ctx.id)} class=\\"bar\\"></div>\`"`
-      )
+      expect(getCompiledString(`<div :id="id" class="bar"></div>`))
+        .toMatchInlineSnapshot(`
+        "\`<div\${
+            _ssrRenderAttr(\\"id\\", _ctx.id)
+          } class=\\"bar\\"></div>\`"
+      `)
     })
 
     test('v-bind:[key]', () => {
-      expect(
-        getCompiledString(`<div v-bind:[key]="value"></div>`)
-      ).toMatchInlineSnapshot(
-        `"\`<div\${_ssrRenderAttrs({ [_ctx.key]: _ctx.value })}></div>\`"`
-      )
+      expect(getCompiledString(`<div v-bind:[key]="value"></div>`))
+        .toMatchInlineSnapshot(`
+        "\`<div\${
+            _ssrRenderAttrs({ [_ctx.key]: _ctx.value })
+          }></div>\`"
+      `)
 
       expect(getCompiledString(`<div class="foo" v-bind:[key]="value"></div>`))
         .toMatchInlineSnapshot(`
-        "\`<div\${_ssrRenderAttrs({
-            class: \\"foo\\",
-            [_ctx.key]: _ctx.value
-          })}></div>\`"
+        "\`<div\${
+            _ssrRenderAttrs({
+              class: \\"foo\\",
+              [_ctx.key]: _ctx.value
+            })
+          }></div>\`"
       `)
 
       expect(getCompiledString(`<div :id="id" v-bind:[key]="value"></div>`))
         .toMatchInlineSnapshot(`
-        "\`<div\${_ssrRenderAttrs({
-            id: _ctx.id,
-            [_ctx.key]: _ctx.value
-          })}></div>\`"
+        "\`<div\${
+            _ssrRenderAttrs({
+              id: _ctx.id,
+              [_ctx.key]: _ctx.value
+            })
+          }></div>\`"
       `)
     })
 
     test('v-bind="obj"', () => {
-      expect(
-        getCompiledString(`<div v-bind="obj"></div>`)
-      ).toMatchInlineSnapshot(`"\`<div\${_ssrRenderAttrs(_ctx.obj)}></div>\`"`)
+      expect(getCompiledString(`<div v-bind="obj"></div>`))
+        .toMatchInlineSnapshot(`
+        "\`<div\${
+            _ssrRenderAttrs(_ctx.obj)
+          }></div>\`"
+      `)
 
-      expect(
-        getCompiledString(`<div class="foo" v-bind="obj"></div>`)
-      ).toMatchInlineSnapshot(
-        `"\`<div\${_ssrRenderAttrs(_mergeProps({ class: \\"foo\\" }, _ctx.obj))}></div>\`"`
-      )
+      expect(getCompiledString(`<div class="foo" v-bind="obj"></div>`))
+        .toMatchInlineSnapshot(`
+        "\`<div\${
+            _ssrRenderAttrs(_mergeProps({ class: \\"foo\\" }, _ctx.obj))
+          }></div>\`"
+      `)
 
-      expect(
-        getCompiledString(`<div :id="id" v-bind="obj"></div>`)
-      ).toMatchInlineSnapshot(
-        `"\`<div\${_ssrRenderAttrs(_mergeProps({ id: _ctx.id }, _ctx.obj))}></div>\`"`
-      )
+      expect(getCompiledString(`<div :id="id" v-bind="obj"></div>`))
+        .toMatchInlineSnapshot(`
+        "\`<div\${
+            _ssrRenderAttrs(_mergeProps({ id: _ctx.id }, _ctx.obj))
+          }></div>\`"
+      `)
 
       // dynamic key + v-bind="object"
-      expect(
-        getCompiledString(`<div :[key]="id" v-bind="obj"></div>`)
-      ).toMatchInlineSnapshot(
-        `"\`<div\${_ssrRenderAttrs(_mergeProps({ [_ctx.key]: _ctx.id }, _ctx.obj))}></div>\`"`
-      )
+      expect(getCompiledString(`<div :[key]="id" v-bind="obj"></div>`))
+        .toMatchInlineSnapshot(`
+        "\`<div\${
+            _ssrRenderAttrs(_mergeProps({ [_ctx.key]: _ctx.id }, _ctx.obj))
+          }></div>\`"
+      `)
 
       // should merge class and :class
       expect(getCompiledString(`<div class="a" :class="b" v-bind="obj"></div>`))
         .toMatchInlineSnapshot(`
-        "\`<div\${_ssrRenderAttrs(_mergeProps({
-            class: [\\"a\\", _ctx.b]
-          }, _ctx.obj))}></div>\`"
+        "\`<div\${
+            _ssrRenderAttrs(_mergeProps({
+              class: [\\"a\\", _ctx.b]
+            }, _ctx.obj))
+          }></div>\`"
       `)
 
       // should merge style and :style
@@ -197,9 +223,11 @@ describe('ssr: element', () => {
           `<div style="color:red;" :style="b" v-bind="obj"></div>`
         )
       ).toMatchInlineSnapshot(`
-        "\`<div\${_ssrRenderAttrs(_mergeProps({
-            style: [{\\"color\\":\\"red\\"}, _ctx.b]
-          }, _ctx.obj))}></div>\`"
+        "\`<div\${
+            _ssrRenderAttrs(_mergeProps({
+              style: [{\\"color\\":\\"red\\"}, _ctx.b]
+            }, _ctx.obj))
+          }></div>\`"
       `)
     })
 
@@ -210,9 +238,12 @@ describe('ssr: element', () => {
       expect(
         getCompiledString(`<div id="foo" v-on="bar"/>`)
       ).toMatchInlineSnapshot(`"\`<div id=\\"foo\\"></div>\`"`)
-      expect(
-        getCompiledString(`<div v-bind="foo" v-on="bar"/>`)
-      ).toMatchInlineSnapshot(`"\`<div\${_ssrRenderAttrs(_ctx.foo)}></div>\`"`)
+      expect(getCompiledString(`<div v-bind="foo" v-on="bar"/>`))
+        .toMatchInlineSnapshot(`
+        "\`<div\${
+            _ssrRenderAttrs(_ctx.foo)
+          }></div>\`"
+      `)
     })
   })
 })
index 36d6138d3aa2ec875ae571f42c4954b527419219..e5a6bad1289f7a1d62145cbc157d0974c43959b0 100644 (file)
@@ -6,7 +6,7 @@ describe('ssr compile: teleport', () => {
       .toMatchInlineSnapshot(`
       "const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _ssrRenderTeleport(_push, (_push) => {
           _push(\`<div></div>\`)
         }, _ctx.target, false, _parent)
@@ -18,26 +18,26 @@ describe('ssr compile: teleport', () => {
     expect(
       compile(`<teleport :target="target" disabled><div/></teleport>`).code
     ).toMatchInlineSnapshot(`
-    "const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\")
+      "const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\")
 
-    return function ssrRender(_ctx, _push, _parent) {
-      _ssrRenderTeleport(_push, (_push) => {
-        _push(\`<div></div>\`)
-      }, _ctx.target, true, _parent)
-    }"
-  `)
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _ssrRenderTeleport(_push, (_push) => {
+          _push(\`<div></div>\`)
+        }, _ctx.target, true, _parent)
+      }"
+    `)
 
     expect(
       compile(`<teleport :target="target" :disabled="foo"><div/></teleport>`)
         .code
     ).toMatchInlineSnapshot(`
-    "const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\")
+      "const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\")
 
-    return function ssrRender(_ctx, _push, _parent) {
-      _ssrRenderTeleport(_push, (_push) => {
-        _push(\`<div></div>\`)
-      }, _ctx.target, _ctx.foo, _parent)
-    }"
-  `)
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _ssrRenderTeleport(_push, (_push) => {
+          _push(\`<div></div>\`)
+        }, _ctx.target, _ctx.foo, _parent)
+      }"
+    `)
   })
 })
index 64bb12f116a5c1159293bc46c23bb7bb267636b9..a2a6452a7411f4f096e8f6a0de3f0f1a96a55b49 100644 (file)
@@ -9,9 +9,10 @@ describe('ssr: scopeId', () => {
         scopeId
       }).code
     ).toMatchInlineSnapshot(`
-      "
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<div data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
+      "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
       }"
     `)
   })
@@ -26,10 +27,10 @@ describe('ssr: scopeId', () => {
       "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\")
       const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
 
-        _push(_ssrRenderComponent(_component_foo, null, {
+        _push(_ssrRenderComponent(_component_foo, _attrs, {
           default: _withCtx((_, _push, _parent, _scopeId) => {
             if (_push) {
               _push(\`foo\`)
@@ -54,10 +55,10 @@ describe('ssr: scopeId', () => {
       "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
       const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
 
-        _push(_ssrRenderComponent(_component_foo, null, {
+        _push(_ssrRenderComponent(_component_foo, _attrs, {
           default: _withCtx((_, _push, _parent, _scopeId) => {
             if (_push) {
               _push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
@@ -82,11 +83,11 @@ describe('ssr: scopeId', () => {
       "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
       const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
         const _component_bar = _resolveComponent(\\"bar\\")
 
-        _push(_ssrRenderComponent(_component_foo, null, {
+        _push(_ssrRenderComponent(_component_foo, _attrs, {
           default: _withCtx((_, _push, _parent, _scopeId) => {
             if (_push) {
               _push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
index 4d6d7e7e3be64fc6f8f8886551fef6c8c7b36acd..668a80e878994ed7a8ba2f997fba4f3fd1ed90cc 100644 (file)
@@ -5,7 +5,7 @@ describe('ssr: <slot>', () => {
     expect(compile(`<slot/>`).code).toMatchInlineSnapshot(`
       "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent)
       }"
     `)
@@ -15,7 +15,7 @@ describe('ssr: <slot>', () => {
     expect(compile(`<slot name="foo" />`).code).toMatchInlineSnapshot(`
       "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _ssrRenderSlot(_ctx.$slots, \\"foo\\", {}, null, _push, _parent)
       }"
     `)
@@ -25,7 +25,7 @@ describe('ssr: <slot>', () => {
     expect(compile(`<slot :name="bar.baz" />`).code).toMatchInlineSnapshot(`
       "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _ssrRenderSlot(_ctx.$slots, _ctx.bar.baz, {}, null, _push, _parent)
       }"
     `)
@@ -36,7 +36,7 @@ describe('ssr: <slot>', () => {
       .toMatchInlineSnapshot(`
       "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _ssrRenderSlot(_ctx.$slots, \\"foo\\", {
           p: 1,
           bar: \\"2\\"
@@ -50,7 +50,7 @@ describe('ssr: <slot>', () => {
       .toMatchInlineSnapshot(`
       "const { ssrRenderSlot: _ssrRenderSlot, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, () => {
           _push(\`some \${_ssrInterpolate(_ctx.fallback)} content\`)
         }, _push, _parent)
index db60f5e2321a4fffb9836ecb0fe86f3224523064..e85269f462b43adcf07abecf1d97a27841d4c380 100644 (file)
@@ -6,7 +6,7 @@ describe('ssr compile: suspense', () => {
       "const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require(\\"vue\\")
       const { ssrRenderComponent: _ssrRenderComponent, ssrRenderSuspense: _ssrRenderSuspense } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
 
         _ssrRenderSuspense(_push, {
@@ -33,7 +33,7 @@ describe('ssr compile: suspense', () => {
       "const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require(\\"vue\\")
       const { ssrRenderComponent: _ssrRenderComponent, ssrRenderSuspense: _ssrRenderSuspense } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         const _component_foo = _resolveComponent(\\"foo\\")
 
         _ssrRenderSuspense(_push, {
index 37c9646d2729689b5d4bffa21c478cba4714cc3c..6ecadc6dbb11a6d891ba8b3329d62ee6aaa977a3 100644 (file)
@@ -45,7 +45,7 @@ describe('ssr: text', () => {
     expect(compile(`foo {{ bar }} baz`).code).toMatchInlineSnapshot(`
       "const { ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _push(\`foo \${_ssrInterpolate(_ctx.bar)} baz\`)
       }"
     `)
@@ -56,10 +56,12 @@ describe('ssr: text', () => {
       compile(`<div><span>{{ foo }} bar</span><span>baz {{ qux }}</span></div>`)
         .code
     ).toMatchInlineSnapshot(`
-      "const { ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
+      "const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<div><span>\${
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><span>\${
           _ssrInterpolate(_ctx.foo)
         } bar</span><span>baz \${
           _ssrInterpolate(_ctx.qux)
index d599bc1ae49daf002fda7693238f817872cdebc6..87d0243d735170e6fd69529f2991f48b5eb3e7b1 100644 (file)
@@ -5,7 +5,7 @@ describe('ssr: v-for', () => {
     expect(compile(`<div v-for="i in list" />`).code).toMatchInlineSnapshot(`
       "const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _push(\`<!--[-->\`)
         _ssrRenderList(_ctx.list, (i) => {
           _push(\`<div></div>\`)
@@ -20,7 +20,7 @@ describe('ssr: v-for', () => {
       .toMatchInlineSnapshot(`
       "const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _push(\`<!--[-->\`)
         _ssrRenderList(_ctx.list, (i) => {
           _push(\`<div>foo<span>bar</span></div>\`)
@@ -40,7 +40,7 @@ describe('ssr: v-for', () => {
     ).toMatchInlineSnapshot(`
       "const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _push(\`<!--[-->\`)
         _ssrRenderList(_ctx.list, (row, i) => {
           _push(\`<div><!--[-->\`)
@@ -63,7 +63,7 @@ describe('ssr: v-for', () => {
       .toMatchInlineSnapshot(`
       "const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _push(\`<!--[-->\`)
         _ssrRenderList(_ctx.list, (i) => {
           _push(\`<!--[-->\${_ssrInterpolate(i)}<!--]-->\`)
@@ -80,7 +80,7 @@ describe('ssr: v-for', () => {
     ).toMatchInlineSnapshot(`
       "const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _push(\`<!--[-->\`)
         _ssrRenderList(_ctx.list, (i) => {
           _push(\`<span>\${_ssrInterpolate(i)}</span>\`)
@@ -98,7 +98,7 @@ describe('ssr: v-for', () => {
     ).toMatchInlineSnapshot(`
       "const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _push(\`<!--[-->\`)
         _ssrRenderList(_ctx.list, (i) => {
           _push(\`<!--[--><span>\${
@@ -122,7 +122,7 @@ describe('ssr: v-for', () => {
     expect(code).toMatchInlineSnapshot(`
       "const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         _push(\`<!--[-->\`)
         _ssrRenderList(_ctx.list, ({ foo }, index) => {
           _push(\`<div>\${_ssrInterpolate(foo + _ctx.bar + index)}</div>\`)
index 51a3d78fe6ba564e0473e31d7e7dac0278607bf9..b64df0c21d23b2c0092cd11ea56c5bd80f39fbc0 100644 (file)
@@ -3,10 +3,11 @@ import { compile } from '../src'
 describe('ssr: v-if', () => {
   test('basic', () => {
     expect(compile(`<div v-if="foo"></div>`).code).toMatchInlineSnapshot(`
-      "
-      return function ssrRender(_ctx, _push, _parent) {
+      "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
-          _push(\`<div></div>\`)
+          _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
         } else {
           _push(\`<!---->\`)
         }
@@ -17,10 +18,11 @@ describe('ssr: v-if', () => {
   test('with nested content', () => {
     expect(compile(`<div v-if="foo">hello<span>ok</span></div>`).code)
       .toMatchInlineSnapshot(`
-      "
-      return function ssrRender(_ctx, _push, _parent) {
+      "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
-          _push(\`<div>hello<span>ok</span></div>\`)
+          _push(\`<div\${_ssrRenderAttrs(_attrs)}>hello<span>ok</span></div>\`)
         } else {
           _push(\`<!---->\`)
         }
@@ -31,12 +33,13 @@ describe('ssr: v-if', () => {
   test('v-if + v-else', () => {
     expect(compile(`<div v-if="foo"/><span v-else/>`).code)
       .toMatchInlineSnapshot(`
-      "
-      return function ssrRender(_ctx, _push, _parent) {
+      "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
-          _push(\`<div></div>\`)
+          _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
         } else {
-          _push(\`<span></span>\`)
+          _push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
         }
       }"
     `)
@@ -45,12 +48,13 @@ describe('ssr: v-if', () => {
   test('v-if + v-else-if', () => {
     expect(compile(`<div v-if="foo"/><span v-else-if="bar"/>`).code)
       .toMatchInlineSnapshot(`
-      "
-      return function ssrRender(_ctx, _push, _parent) {
+      "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
-          _push(\`<div></div>\`)
+          _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
         } else if (_ctx.bar) {
-          _push(\`<span></span>\`)
+          _push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
         } else {
           _push(\`<!---->\`)
         }
@@ -61,14 +65,15 @@ describe('ssr: v-if', () => {
   test('v-if + v-else-if + v-else', () => {
     expect(compile(`<div v-if="foo"/><span v-else-if="bar"/><p v-else/>`).code)
       .toMatchInlineSnapshot(`
-      "
-      return function ssrRender(_ctx, _push, _parent) {
+      "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
-          _push(\`<div></div>\`)
+          _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
         } else if (_ctx.bar) {
-          _push(\`<span></span>\`)
+          _push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
         } else {
-          _push(\`<p></p>\`)
+          _push(\`<p\${_ssrRenderAttrs(_attrs)}></p>\`)
         }
       }"
     `)
@@ -78,7 +83,7 @@ describe('ssr: v-if', () => {
     expect(compile(`<template v-if="foo">hello</template>`).code)
       .toMatchInlineSnapshot(`
       "
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
           _push(\`<!--[-->hello<!--]-->\`)
         } else {
@@ -92,10 +97,11 @@ describe('ssr: v-if', () => {
     // single element should not wrap with fragment
     expect(compile(`<template v-if="foo"><div>hi</div></template>`).code)
       .toMatchInlineSnapshot(`
-      "
-      return function ssrRender(_ctx, _push, _parent) {
+      "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
-          _push(\`<div>hi</div>\`)
+          _push(\`<div\${_ssrRenderAttrs(_attrs)}>hi</div>\`)
         } else {
           _push(\`<!---->\`)
         }
@@ -108,7 +114,7 @@ describe('ssr: v-if', () => {
       compile(`<template v-if="foo"><div>hi</div><div>ho</div></template>`).code
     ).toMatchInlineSnapshot(`
       "
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
           _push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`)
         } else {
@@ -124,7 +130,7 @@ describe('ssr: v-if', () => {
     ).toMatchInlineSnapshot(`
       "const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
           _push(\`<!--[-->\`)
           _ssrRenderList(_ctx.list, (i) => {
@@ -144,12 +150,13 @@ describe('ssr: v-if', () => {
         `<template v-if="foo"><div>hi</div><div>ho</div></template><div v-else/>`
       ).code
     ).toMatchInlineSnapshot(`
-      "
-      return function ssrRender(_ctx, _push, _parent) {
+      "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
           _push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`)
         } else {
-          _push(\`<div></div>\`)
+          _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
         }
       }"
     `)
index 151eaa79da86097a634fb79606b3dcca14bb5898..18a03b115f4e4291e73b89331d29f2a03d8374ef 100644 (file)
 import { compile } from '../src'
 
+function compileWithWrapper(src: string) {
+  return compile(`<div>${src}</div>`)
+}
+
 describe('ssr: v-model', () => {
   test('<input> (text types)', () => {
-    expect(compile(`<input v-model="bar">`).code).toMatchInlineSnapshot(`
-      "const { ssrRenderAttr: _ssrRenderAttr } = require(\\"@vue/server-renderer\\")
+    expect(compileWithWrapper(`<input v-model="bar">`).code)
+      .toMatchInlineSnapshot(`
+      "const { ssrRenderAttr: _ssrRenderAttr, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<input\${_ssrRenderAttr(\\"value\\", _ctx.bar)}>\`)
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><input\${
+          _ssrRenderAttr(\\"value\\", _ctx.bar)
+        }></div>\`)
       }"
     `)
 
-    expect(compile(`<input type="email" v-model="bar">`).code)
+    expect(compileWithWrapper(`<input type="email" v-model="bar">`).code)
       .toMatchInlineSnapshot(`
-      "const { ssrRenderAttr: _ssrRenderAttr } = require(\\"@vue/server-renderer\\")
+      "const { ssrRenderAttr: _ssrRenderAttr, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<input type=\\"email\\"\${_ssrRenderAttr(\\"value\\", _ctx.bar)}>\`)
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><input type=\\"email\\"\${
+          _ssrRenderAttr(\\"value\\", _ctx.bar)
+        }></div>\`)
       }"
     `)
   })
 
   test('<input type="radio">', () => {
-    expect(compile(`<input type="radio" value="foo" v-model="bar">`).code)
-      .toMatchInlineSnapshot(`
-      "const { ssrLooseEqual: _ssrLooseEqual } = require(\\"@vue/server-renderer\\")
-
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<input type=\\"radio\\" value=\\"foo\\"\${(_ssrLooseEqual(_ctx.bar, \\"foo\\")) ? \\" checked\\" : \\"\\"}>\`)
+    expect(
+      compileWithWrapper(`<input type="radio" value="foo" v-model="bar">`).code
+    ).toMatchInlineSnapshot(`
+      "const { ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><input type=\\"radio\\" value=\\"foo\\"\${
+          (_ssrLooseEqual(_ctx.bar, \\"foo\\")) ? \\" checked\\" : \\"\\"
+        }></div>\`)
       }"
     `)
   })
 
   test('<input type="checkbox"', () => {
-    expect(compile(`<input type="checkbox" v-model="bar">`).code)
+    expect(compileWithWrapper(`<input type="checkbox" v-model="bar">`).code)
       .toMatchInlineSnapshot(`
-      "const { ssrLooseContain: _ssrLooseContain } = require(\\"@vue/server-renderer\\")
-
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<input type=\\"checkbox\\"\${((Array.isArray(_ctx.bar))
-          ? _ssrLooseContain(_ctx.bar, null)
-          : _ctx.bar) ? \\" checked\\" : \\"\\"}>\`)
+      "const { ssrLooseContain: _ssrLooseContain, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><input type=\\"checkbox\\"\${
+          ((Array.isArray(_ctx.bar))
+            ? _ssrLooseContain(_ctx.bar, null)
+            : _ctx.bar) ? \\" checked\\" : \\"\\"
+        }></div>\`)
       }"
     `)
 
-    expect(compile(`<input type="checkbox" value="foo" v-model="bar">`).code)
-      .toMatchInlineSnapshot(`
-      "const { ssrLooseContain: _ssrLooseContain } = require(\\"@vue/server-renderer\\")
-
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<input type=\\"checkbox\\" value=\\"foo\\"\${((Array.isArray(_ctx.bar))
-          ? _ssrLooseContain(_ctx.bar, \\"foo\\")
-          : _ctx.bar) ? \\" checked\\" : \\"\\"}>\`)
+    expect(
+      compileWithWrapper(`<input type="checkbox" value="foo" v-model="bar">`)
+        .code
+    ).toMatchInlineSnapshot(`
+      "const { ssrLooseContain: _ssrLooseContain, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><input type=\\"checkbox\\" value=\\"foo\\"\${
+          ((Array.isArray(_ctx.bar))
+            ? _ssrLooseContain(_ctx.bar, \\"foo\\")
+            : _ctx.bar) ? \\" checked\\" : \\"\\"
+        }></div>\`)
       }"
     `)
   })
 
   test('<textarea>', () => {
-    expect(compile(`<textarea v-model="foo">bar</textarea>`).code)
+    expect(compileWithWrapper(`<textarea v-model="foo">bar</textarea>`).code)
       .toMatchInlineSnapshot(`
-      "const { ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
-
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<textarea>\${_ssrInterpolate(_ctx.foo)}</textarea>\`)
+      "const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><textarea>\${
+          _ssrInterpolate(_ctx.foo)
+        }</textarea></div>\`)
       }"
     `)
   })
 
   test('<input :type="x">', () => {
-    expect(compile(`<input :type="x" v-model="foo">`).code)
+    expect(compileWithWrapper(`<input :type="x" v-model="foo">`).code)
       .toMatchInlineSnapshot(`
-      "const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel } = require(\\"@vue/server-renderer\\")
+      "const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<input\${
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><input\${
           _ssrRenderAttr(\\"type\\", _ctx.x)
         }\${
           _ssrRenderDynamicModel(_ctx.x, _ctx.foo, null)
-        }>\`)
+        }></div>\`)
       }"
     `)
 
-    expect(compile(`<input :type="x" v-model="foo" value="bar">`).code)
-      .toMatchInlineSnapshot(`
-      "const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel } = require(\\"@vue/server-renderer\\")
+    expect(
+      compileWithWrapper(`<input :type="x" v-model="foo" value="bar">`).code
+    ).toMatchInlineSnapshot(`
+      "const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<input\${
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><input\${
           _ssrRenderAttr(\\"type\\", _ctx.x)
         }\${
           _ssrRenderDynamicModel(_ctx.x, _ctx.foo, \\"bar\\")
-        } value=\\"bar\\">\`)
+        } value=\\"bar\\"></div>\`)
       }"
     `)
 
-    expect(compile(`<input :type="x" v-model="foo" :value="bar">`).code)
-      .toMatchInlineSnapshot(`
-      "const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel } = require(\\"@vue/server-renderer\\")
+    expect(
+      compileWithWrapper(`<input :type="x" v-model="foo" :value="bar">`).code
+    ).toMatchInlineSnapshot(`
+      "const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<input\${
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><input\${
           _ssrRenderAttr(\\"type\\", _ctx.x)
         }\${
           _ssrRenderDynamicModel(_ctx.x, _ctx.foo, _ctx.bar)
         }\${
           _ssrRenderAttr(\\"value\\", _ctx.bar)
-        }>\`)
+        }></div>\`)
       }"
     `)
   })
 
   test('<input v-bind="obj">', () => {
-    expect(compile(`<input v-bind="obj" v-model="foo">`).code)
+    expect(compileWithWrapper(`<input v-bind="obj" v-model="foo">`).code)
       .toMatchInlineSnapshot(`
       "const { mergeProps: _mergeProps } = require(\\"vue\\")
       const { ssrRenderAttrs: _ssrRenderAttrs, ssrGetDynamicModelProps: _ssrGetDynamicModelProps } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         let _temp0
 
-        _push(\`<input\${_ssrRenderAttrs((_temp0 = _ctx.obj, _mergeProps(_temp0, _ssrGetDynamicModelProps(_temp0, _ctx.foo))))}>\`)
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><input\${
+          _ssrRenderAttrs((_temp0 = _ctx.obj, _mergeProps(_temp0, _ssrGetDynamicModelProps(_temp0, _ctx.foo))))
+        }></div>\`)
       }"
     `)
 
-    expect(compile(`<input id="x" v-bind="obj" v-model="foo" class="y">`).code)
-      .toMatchInlineSnapshot(`
+    expect(
+      compileWithWrapper(`<input id="x" v-bind="obj" v-model="foo" class="y">`)
+        .code
+    ).toMatchInlineSnapshot(`
       "const { mergeProps: _mergeProps } = require(\\"vue\\")
       const { ssrRenderAttrs: _ssrRenderAttrs, ssrGetDynamicModelProps: _ssrGetDynamicModelProps } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
         let _temp0
 
-        _push(\`<input\${_ssrRenderAttrs((_temp0 = _mergeProps({ id: \\"x\\" }, _ctx.obj, { class: \\"y\\" }), _mergeProps(_temp0, _ssrGetDynamicModelProps(_temp0, _ctx.foo))))}>\`)
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><input\${
+          _ssrRenderAttrs((_temp0 = _mergeProps({ id: \\"x\\" }, _ctx.obj, { class: \\"y\\" }), _mergeProps(_temp0, _ssrGetDynamicModelProps(_temp0, _ctx.foo))))
+        }></div>\`)
       }"
     `)
   })
index 8a99a52c7d34523cbedd09a8db80d1ff8f3b81e6..e28014d93568aefcb16b79ad7b38cf9cb4dcde8b 100644 (file)
 import { compile } from '../src'
 
+function compileWithWrapper(src: string) {
+  return compile(`<div>${src}</div>`)
+}
+
 describe('ssr: v-show', () => {
-  test('basic', () => {
+  test('basic as root', () => {
     expect(compile(`<div v-show="foo"/>`).code).toMatchInlineSnapshot(`
-      "const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\")
+      "const { mergeProps: _mergeProps } = require(\\"vue\\")
+      const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${_ssrRenderAttrs(_mergeProps({
+          style: (_ctx.foo) ? null : { display: \\"none\\" }
+        }, _attrs))}></div>\`)
+      }"
+    `)
+  })
+
+  test('basic', () => {
+    expect(compileWithWrapper(`<div v-show="foo"/>`).code)
+      .toMatchInlineSnapshot(`
+      "const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<div style=\\"\${_ssrRenderStyle((_ctx.foo) ? null : { display: \\"none\\" })}\\"></div>\`)
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><div style=\\"\${
+          _ssrRenderStyle((_ctx.foo) ? null : { display: \\"none\\" })
+        }\\"></div></div>\`)
       }"
     `)
   })
 
   test('with static style', () => {
-    expect(compile(`<div style="color:red" v-show="foo"/>`).code)
+    expect(compileWithWrapper(`<div style="color:red" v-show="foo"/>`).code)
       .toMatchInlineSnapshot(`
-      "const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\")
+      "const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<div style=\\"\${_ssrRenderStyle([
-          {\\"color\\":\\"red\\"},
-          (_ctx.foo) ? null : { display: \\"none\\" }
-        ])}\\"></div>\`)
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><div style=\\"\${
+          _ssrRenderStyle([
+            {\\"color\\":\\"red\\"},
+            (_ctx.foo) ? null : { display: \\"none\\" }
+          ])
+        }\\"></div></div>\`)
       }"
     `)
   })
 
   test('with dynamic style', () => {
-    expect(compile(`<div :style="{ color: 'red' }" v-show="foo"/>`).code)
-      .toMatchInlineSnapshot(`
-      "const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\")
+    expect(
+      compileWithWrapper(`<div :style="{ color: 'red' }" v-show="foo"/>`).code
+    ).toMatchInlineSnapshot(`
+      "const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<div style=\\"\${_ssrRenderStyle([
-          { color: 'red' },
-          (_ctx.foo) ? null : { display: \\"none\\" }
-        ])}\\"></div>\`)
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><div style=\\"\${
+          _ssrRenderStyle([
+            { color: 'red' },
+            (_ctx.foo) ? null : { display: \\"none\\" }
+          ])
+        }\\"></div></div>\`)
       }"
     `)
   })
 
   test('with static + dynamic style', () => {
     expect(
-      compile(`<div style="color:red" :style="{ fontSize: 14 }" v-show="foo"/>`)
-        .code
+      compileWithWrapper(
+        `<div style="color:red" :style="{ fontSize: 14 }" v-show="foo"/>`
+      ).code
     ).toMatchInlineSnapshot(`
-      "const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\")
+      "const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<div style=\\"\${_ssrRenderStyle([
-          {\\"color\\":\\"red\\"},
-          { fontSize: 14 },
-          (_ctx.foo) ? null : { display: \\"none\\" }
-        ])}\\"></div>\`)
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><div style=\\"\${
+          _ssrRenderStyle([
+            {\\"color\\":\\"red\\"},
+            { fontSize: 14 },
+            (_ctx.foo) ? null : { display: \\"none\\" }
+          ])
+        }\\"></div></div>\`)
       }"
     `)
   })
 
   test('with v-bind', () => {
     expect(
-      compile(
+      compileWithWrapper(
         `<div v-bind="baz" style="color:red" :style="{ fontSize: 14 }" v-show="foo"/>`
       ).code
     ).toMatchInlineSnapshot(`
       "const { mergeProps: _mergeProps } = require(\\"vue\\")
       const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
 
-      return function ssrRender(_ctx, _push, _parent) {
-        _push(\`<div\${_ssrRenderAttrs(_mergeProps(_ctx.baz, {
-          style: [
-            {\\"color\\":\\"red\\"},
-            { fontSize: 14 },
-            (_ctx.foo) ? null : { display: \\"none\\" }
-          ]
-        }))}></div>\`)
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><div\${
+          _ssrRenderAttrs(_mergeProps(_ctx.baz, {
+            style: [
+              {\\"color\\":\\"red\\"},
+              { fontSize: 14 },
+              (_ctx.foo) ? null : { display: \\"none\\" }
+            ]
+          }))
+        }></div></div>\`)
       }"
     `)
   })
index cc3cd45ba28aa13325940927c096e83227205042..08dec4debecb538770ff2ed36e00187bd50b0572 100644 (file)
@@ -1,5 +1,17 @@
 import { compile } from '../src'
 
 export function getCompiledString(src: string): string {
-  return compile(src).code.match(/_push\(([^]*)\)/)![1]
+  // Wrap src template in a root div so that it doesn't get injected
+  // fallthrough attr. This results in less noise in generated snapshots
+  // but also means this util can only be used for non-root cases.
+  const { code } = compile(`<div>${src}</div>`)
+  const match = code.match(
+    /_push\(\`<div\${\s*_ssrRenderAttrs\(_attrs\)\s*}>([^]*)<\/div>\`\)/
+  )
+
+  if (!match) {
+    throw new Error(`Unexpected compile result:\n${code}`)
+  }
+
+  return `\`${match[1]}\``
 }
index 2edb97915d81ffbf42653d53b4c02517ebafab89..02153773b6487473b90e44221e376a9475864228 100644 (file)
@@ -23,6 +23,7 @@ import { ssrTransformIf } from './transforms/ssrVIf'
 import { ssrTransformFor } from './transforms/ssrVFor'
 import { ssrTransformModel } from './transforms/ssrVModel'
 import { ssrTransformShow } from './transforms/ssrVShow'
+import { ssrInjectFallthroughAttrs } from './transforms/ssrInjectFallthroughAttrs'
 
 export function compile(
   template: string,
@@ -55,6 +56,7 @@ export function compile(
       trackVForSlotScopes,
       transformExpression,
       ssrTransformSlotOutlet,
+      ssrInjectFallthroughAttrs,
       ssrTransformElement,
       ssrTransformComponent,
       trackSlotScopes,
diff --git a/packages/compiler-ssr/src/transforms/ssrInjectFallthroughAttrs.ts b/packages/compiler-ssr/src/transforms/ssrInjectFallthroughAttrs.ts
new file mode 100644 (file)
index 0000000..01ca66f
--- /dev/null
@@ -0,0 +1,52 @@
+import {
+  NodeTransform,
+  NodeTypes,
+  ElementTypes,
+  locStub,
+  createSimpleExpression,
+  RootNode,
+  TemplateChildNode,
+  ParentNode,
+  findDir
+} from '@vue/compiler-dom'
+
+const hasSingleChild = (node: ParentNode): boolean =>
+  node.children.filter(n => n.type !== NodeTypes.COMMENT).length === 1
+
+export const ssrInjectFallthroughAttrs: NodeTransform = (node, context) => {
+  // _attrs is provided as a function argument.
+  // mark it as a known identifier so that it doesn't get prefixed by
+  // transformExpression.
+  if (node.type === NodeTypes.ROOT) {
+    context.identifiers._attrs = 1
+  }
+
+  const parent = context.parent
+  if (!parent || parent.type !== NodeTypes.ROOT) {
+    return
+  }
+
+  if (node.type === NodeTypes.IF_BRANCH && hasSingleChild(node)) {
+    injectFallthroughAttrs(node.children[0])
+  } else if (hasSingleChild(parent)) {
+    injectFallthroughAttrs(node)
+  }
+}
+
+function injectFallthroughAttrs(node: RootNode | TemplateChildNode) {
+  if (
+    node.type === NodeTypes.ELEMENT &&
+    (node.tagType === ElementTypes.ELEMENT ||
+      node.tagType === ElementTypes.COMPONENT) &&
+    !findDir(node, 'for')
+  ) {
+    node.props.push({
+      type: NodeTypes.DIRECTIVE,
+      name: 'bind',
+      arg: undefined,
+      exp: createSimpleExpression(`_attrs`, false),
+      modifiers: [],
+      loc: locStub
+    })
+  }
+}
index d56315d4363b08df52a9ba8717b6fc805cdfcecf..21c231e85fca184944f2f5e186de01044c3c2343 100644 (file)
@@ -23,7 +23,8 @@ import {
   hasDynamicKeyVBind,
   MERGE_PROPS,
   isBindKey,
-  createSequenceExpression
+  createSequenceExpression,
+  InterpolationNode
 } from '@vue/compiler-dom'
 import {
   escapeHtml,
@@ -53,30 +54,40 @@ const rawChildrenMap = new WeakMap<
 
 export const ssrTransformElement: NodeTransform = (node, context) => {
   if (
-    node.type === NodeTypes.ELEMENT &&
-    node.tagType === ElementTypes.ELEMENT
+    node.type !== NodeTypes.ELEMENT ||
+    node.tagType !== ElementTypes.ELEMENT
   ) {
-    return function ssrPostTransformElement() {
-      // element
-      // generate the template literal representing the open tag.
-      const openTag: TemplateLiteral['elements'] = [`<${node.tag}`]
-      // some tags need to be pasesd to runtime for special checks
-      const needTagForRuntime =
-        node.tag === 'textarea' || node.tag.indexOf('-') > 0
+    return
+  }
 
-      // 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 = hasDynamicKeyVBind(node)
-      if (hasDynamicVBind) {
-        const { props } = buildProps(node, context, node.props, true /* ssr */)
-        if (props) {
-          const propsExp = createCallExpression(
-            context.helper(SSR_RENDER_ATTRS),
-            [props]
-          )
+  return function ssrPostTransformElement() {
+    // element
+    // generate the template literal representing the open tag.
+    const openTag: TemplateLiteral['elements'] = [`<${node.tag}`]
+    // some tags need to be pasesd to runtime for special checks
+    const needTagForRuntime =
+      node.tag === 'textarea' || node.tag.indexOf('-') > 0
 
-          if (node.tag === 'textarea') {
+    // 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 = hasDynamicKeyVBind(node)
+    if (hasDynamicVBind) {
+      const { props } = buildProps(node, context, node.props, true /* ssr */)
+      if (props) {
+        const propsExp = createCallExpression(
+          context.helper(SSR_RENDER_ATTRS),
+          [props]
+        )
+
+        if (node.tag === 'textarea') {
+          const existingText = node.children[0] as
+            | TextNode
+            | InterpolationNode
+            | undefined
+          // If interpolation, this is dynamic <textarea> content, potentially
+          // injected by v-model and takes higher priority than v-bind value
+          if (!existingText || existingText.type !== NodeTypes.INTERPOLATION) {
             // <textarea> with dynamic v-bind. We don't know if the final props
             // will contain .value, so we will have to do something special:
             // assign the merged props to a temp variable, and check whether
@@ -88,7 +99,6 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
                 props
               )
             ]
-            const existingText = node.children[0] as TextNode | undefined
             rawChildrenMap.set(
               node,
               createCallExpression(context.helper(SSR_INTERPOLATE), [
@@ -103,189 +113,189 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
                 )
               ])
             )
-          } else if (node.tag === 'input') {
-            // <input v-bind="obj" v-model>
-            // we need to determine the props to render for the dynamic v-model
-            // and merge it with the v-bind expression.
-            const vModel = findVModel(node)
-            if (vModel) {
-              // 1. save the props (san v-model) in a temp variable
-              const tempId = `_temp${context.temps++}`
-              const tempExp = createSimpleExpression(tempId, false)
-              propsExp.arguments = [
-                createSequenceExpression([
-                  createAssignmentExpression(tempExp, props),
-                  createCallExpression(context.helper(MERGE_PROPS), [
-                    tempExp,
-                    createCallExpression(
-                      context.helper(SSR_GET_DYNAMIC_MODEL_PROPS),
-                      [
-                        tempExp, // existing props
-                        vModel.exp! // model
-                      ]
-                    )
-                  ])
-                ])
-              ]
-            }
           }
-
-          if (needTagForRuntime) {
-            propsExp.arguments.push(`"${node.tag}"`)
+        } else if (node.tag === 'input') {
+          // <input v-bind="obj" v-model>
+          // we need to determine the props to render for the dynamic v-model
+          // and merge it with the v-bind expression.
+          const vModel = findVModel(node)
+          if (vModel) {
+            // 1. save the props (san v-model) in a temp variable
+            const tempId = `_temp${context.temps++}`
+            const tempExp = createSimpleExpression(tempId, false)
+            propsExp.arguments = [
+              createSequenceExpression([
+                createAssignmentExpression(tempExp, props),
+                createCallExpression(context.helper(MERGE_PROPS), [
+                  tempExp,
+                  createCallExpression(
+                    context.helper(SSR_GET_DYNAMIC_MODEL_PROPS),
+                    [
+                      tempExp, // existing props
+                      vModel.exp! // model
+                    ]
+                  )
+                ])
+              ])
+            ]
           }
+        }
 
-          openTag.push(propsExp)
+        if (needTagForRuntime) {
+          propsExp.arguments.push(`"${node.tag}"`)
         }
+
+        openTag.push(propsExp)
       }
+    }
 
-      // book keeping static/dynamic class merging.
-      let dynamicClassBinding: CallExpression | undefined = undefined
-      let staticClassBinding: string | undefined = undefined
-      // all style bindings are converted to dynamic by transformStyle.
-      // but we need to make sure to merge them.
-      let dynamicStyleBinding: CallExpression | undefined = undefined
+    // book keeping static/dynamic class merging.
+    let dynamicClassBinding: CallExpression | undefined = undefined
+    let staticClassBinding: string | undefined = undefined
+    // all style bindings are converted to dynamic by transformStyle.
+    // but we need to make sure to merge them.
+    let dynamicStyleBinding: CallExpression | undefined = undefined
 
-      for (let i = 0; i < node.props.length; i++) {
-        const prop = node.props[i]
-        // special cases with children override
-        if (prop.type === NodeTypes.DIRECTIVE) {
-          if (prop.name === 'html' && prop.exp) {
-            rawChildrenMap.set(node, prop.exp)
-          } else if (prop.name === 'text' && prop.exp) {
+    for (let i = 0; i < node.props.length; i++) {
+      const prop = node.props[i]
+      // special cases with children override
+      if (prop.type === NodeTypes.DIRECTIVE) {
+        if (prop.name === 'html' && prop.exp) {
+          rawChildrenMap.set(node, prop.exp)
+        } else if (prop.name === 'text' && prop.exp) {
+          node.children = [createInterpolation(prop.exp, prop.loc)]
+        } else if (prop.name === 'slot') {
+          context.onError(
+            createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, prop.loc)
+          )
+        } else if (isTextareaWithValue(node, prop) && prop.exp) {
+          if (!hasDynamicVBind) {
             node.children = [createInterpolation(prop.exp, prop.loc)]
-          } else if (prop.name === 'slot') {
+          }
+        } else {
+          // Directive transforms.
+          const directiveTransform = context.directiveTransforms[prop.name]
+          if (!directiveTransform) {
+            // no corresponding ssr directive transform found.
             context.onError(
-              createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, prop.loc)
+              createSSRCompilerError(
+                SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM,
+                prop.loc
+              )
+            )
+          } else if (!hasDynamicVBind) {
+            const { props, ssrTagParts } = directiveTransform(
+              prop,
+              node,
+              context
             )
-          } else if (isTextareaWithValue(node, prop) && prop.exp) {
-            if (!hasDynamicVBind) {
-              node.children = [createInterpolation(prop.exp, prop.loc)]
+            if (ssrTagParts) {
+              openTag.push(...ssrTagParts)
             }
-          } else {
-            // Directive transforms.
-            const directiveTransform = context.directiveTransforms[prop.name]
-            if (!directiveTransform) {
-              // no corresponding ssr directive transform found.
-              context.onError(
-                createSSRCompilerError(
-                  SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM,
-                  prop.loc
-                )
-              )
-            } else if (!hasDynamicVBind) {
-              const { props, ssrTagParts } = directiveTransform(
-                prop,
-                node,
-                context
-              )
-              if (ssrTagParts) {
-                openTag.push(...ssrTagParts)
-              }
-              for (let j = 0; j < props.length; j++) {
-                const { key, value } = props[j]
-                if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
-                  let attrName = key.content
-                  // static key attr
-                  if (attrName === 'class') {
+            for (let j = 0; j < props.length; j++) {
+              const { key, value } = props[j]
+              if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
+                let attrName = key.content
+                // static key attr
+                if (attrName === 'class') {
+                  openTag.push(
+                    ` class="`,
+                    (dynamicClassBinding = createCallExpression(
+                      context.helper(SSR_RENDER_CLASS),
+                      [value]
+                    )),
+                    `"`
+                  )
+                } else if (attrName === 'style') {
+                  if (dynamicStyleBinding) {
+                    // already has style binding, merge into it.
+                    mergeCall(dynamicStyleBinding, value)
+                  } else {
                     openTag.push(
-                      ` class="`,
-                      (dynamicClassBinding = createCallExpression(
-                        context.helper(SSR_RENDER_CLASS),
+                      ` style="`,
+                      (dynamicStyleBinding = createCallExpression(
+                        context.helper(SSR_RENDER_STYLE),
                         [value]
                       )),
                       `"`
                     )
-                  } else if (attrName === 'style') {
-                    if (dynamicStyleBinding) {
-                      // already has style binding, merge into it.
-                      mergeCall(dynamicStyleBinding, value)
-                    } else {
-                      openTag.push(
-                        ` style="`,
-                        (dynamicStyleBinding = createCallExpression(
-                          context.helper(SSR_RENDER_STYLE),
-                          [value]
-                        )),
-                        `"`
+                  }
+                } else {
+                  attrName =
+                    node.tag.indexOf('-') > 0
+                      ? attrName // preserve raw name on custom elements
+                      : propsToAttrMap[attrName] || attrName.toLowerCase()
+                  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 {
-                    attrName =
-                      node.tag.indexOf('-') > 0
-                        ? attrName // preserve raw name on custom elements
-                        : propsToAttrMap[attrName] || attrName.toLowerCase()
-                    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
-                        )
+                    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
-                  const args: CallExpression['arguments'] = [key, value]
-                  if (needTagForRuntime) {
-                    args.push(`"${node.tag}"`)
-                  }
-                  openTag.push(
-                    createCallExpression(
-                      context.helper(SSR_RENDER_DYNAMIC_ATTR),
-                      args
                     )
-                  )
+                  }
+                }
+              } else {
+                // dynamic key attr
+                // this branch is only encountered for custom directive
+                // transforms that returns properties with dynamic keys
+                const args: CallExpression['arguments'] = [key, value]
+                if (needTagForRuntime) {
+                  args.push(`"${node.tag}"`)
                 }
+                openTag.push(
+                  createCallExpression(
+                    context.helper(SSR_RENDER_DYNAMIC_ATTR),
+                    args
+                  )
+                )
               }
             }
           }
-        } else {
-          // special case: value on <textarea>
-          if (node.tag === 'textarea' && prop.name === 'value' && prop.value) {
-            rawChildrenMap.set(node, escapeHtml(prop.value.content))
-          } else if (!hasDynamicVBind) {
-            // static prop
-            if (prop.name === 'class' && prop.value) {
-              staticClassBinding = JSON.stringify(prop.value.content)
-            }
-            openTag.push(
-              ` ${prop.name}` +
-                (prop.value ? `="${escapeHtml(prop.value.content)}"` : ``)
-            )
+        }
+      } else {
+        // special case: value on <textarea>
+        if (node.tag === 'textarea' && prop.name === 'value' && prop.value) {
+          rawChildrenMap.set(node, escapeHtml(prop.value.content))
+        } else if (!hasDynamicVBind) {
+          // static prop
+          if (prop.name === 'class' && prop.value) {
+            staticClassBinding = JSON.stringify(prop.value.content)
           }
+          openTag.push(
+            ` ${prop.name}` +
+              (prop.value ? `="${escapeHtml(prop.value.content)}"` : ``)
+          )
         }
       }
+    }
 
-      // handle co-existence of dynamic + static class bindings
-      if (dynamicClassBinding && staticClassBinding) {
-        mergeCall(dynamicClassBinding, staticClassBinding)
-        removeStaticBinding(openTag, 'class')
-      }
-
-      if (context.scopeId) {
-        openTag.push(` ${context.scopeId}`)
-      }
+    // handle co-existence of dynamic + static class bindings
+    if (dynamicClassBinding && staticClassBinding) {
+      mergeCall(dynamicClassBinding, staticClassBinding)
+      removeStaticBinding(openTag, 'class')
+    }
 
-      node.ssrCodegenNode = createTemplateLiteral(openTag)
+    if (context.scopeId) {
+      openTag.push(` ${context.scopeId}`)
     }
+
+    node.ssrCodegenNode = createTemplateLiteral(openTag)
   }
 }
 
index 54be5467a270d9b39ffdf280d719a1982a835927..1fb5d3f747abf6d18072268727640eec847fc076 100644 (file)
@@ -119,7 +119,8 @@ export interface ComponentOptionsBase<
   ssrRender?: (
     ctx: any,
     push: (item: any) => void,
-    parentInstance: ComponentInternalInstance
+    parentInstance: ComponentInternalInstance,
+    attrs?: Data
   ) => void
 
   /**
index 83d401783f12878308e67850ea5c7d2bfcb4aa57..35b48b6899e35786ce88581ac394a3eede42c23b 100644 (file)
@@ -554,8 +554,7 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
 const handlersRE = /^on|^vnode/
 
 export function mergeProps(...args: (Data & VNodeProps)[]) {
-  const ret: Data = {}
-  extend(ret, args[0])
+  const ret = extend({}, args[0])
   for (let i = 1; i < args.length; i++) {
     const toMerge = args[i]
     for (const key in toMerge) {
index f628d99183438b6dc2d8685b4f02412aa1b820dc..ab210c621ac44df81d59e91b8bf33b9bf3971fe1 100644 (file)
@@ -65,7 +65,7 @@ describe('ssr: renderToString', () => {
       expect(
         await renderToString(
           createApp(
-            defineComponent((props: {}) => {
+            defineComponent(() => {
               const msg = ref('hello')
               return () => h('div', msg.value)
             })
@@ -89,31 +89,6 @@ describe('ssr: renderToString', () => {
       ).toBe(`<div>hello</div>`)
     })
 
-    describe('template components', () => {
-      test('render', async () => {
-        expect(
-          await renderToString(
-            createApp({
-              data() {
-                return { msg: 'hello' }
-              },
-              template: `<div>{{ msg }}</div>`
-            })
-          )
-        ).toBe(`<div>hello</div>`)
-      })
-
-      test('handle compiler errors', async () => {
-        await renderToString(createApp({ template: `<` }))
-
-        expect(
-          'Template compilation error: Unexpected EOF in tag.\n' +
-            '1  |  <\n' +
-            '   |   ^'
-        ).toHaveBeenWarned()
-      })
-    })
-
     test('nested vnode components', async () => {
       const Child = {
         props: ['msg'],
@@ -247,7 +222,7 @@ describe('ssr: renderToString', () => {
                   { msg: 'hello' },
                   {
                     // optimized slot using string push
-                    default: ({ msg }: any, push: any, p: any) => {
+                    default: ({ msg }: any, push: any, _p: any) => {
                       push(`<span>${msg}</span>`)
                     },
                     // important to avoid slots being normalized
@@ -583,4 +558,29 @@ describe('ssr: renderToString', () => {
       )
     })
   })
+
+  describe('integration w/ compiled template', () => {
+    test('render', async () => {
+      expect(
+        await renderToString(
+          createApp({
+            data() {
+              return { msg: 'hello' }
+            },
+            template: `<div>{{ msg }}</div>`
+          })
+        )
+      ).toBe(`<div>hello</div>`)
+    })
+
+    test('handle compiler errors', async () => {
+      await renderToString(createApp({ template: `<` }))
+
+      expect(
+        'Template compilation error: Unexpected EOF in tag.\n' +
+          '1  |  <\n' +
+          '   |   ^'
+      ).toHaveBeenWarned()
+    })
+  })
 })
diff --git a/packages/server-renderer/__tests__/ssrAttrFallthrough.spec.ts b/packages/server-renderer/__tests__/ssrAttrFallthrough.spec.ts
new file mode 100644 (file)
index 0000000..fb974f9
--- /dev/null
@@ -0,0 +1,78 @@
+import { createApp } from 'vue'
+import { renderToString } from '../src/renderToString'
+
+describe('ssr: attr fallthrough', () => {
+  test('basic', async () => {
+    const Child = {
+      template: `<div class="foo" />`
+    }
+    const Parent = {
+      components: { Child },
+      template: `<child class="bar"/>`
+    }
+    const app = createApp(Parent)
+    expect(await renderToString(app)).toBe(`<div class="foo bar"></div>`)
+  })
+
+  test('with v-if', async () => {
+    const Child = {
+      props: ['ok'],
+      template: `<div v-if="ok" class="foo" /><span v-else />`
+    }
+    const Parent = {
+      props: ['ok'],
+      components: { Child },
+      template: `<child :ok="ok" class="bar"/>`
+    }
+    expect(await renderToString(createApp(Parent, { ok: true }))).toBe(
+      `<div class="foo bar"></div>`
+    )
+    expect(await renderToString(createApp(Parent, { ok: false }))).toBe(
+      `<span class="bar"></span>`
+    )
+  })
+
+  test('with v-model', async () => {
+    const Child = {
+      props: ['text'],
+      template: `<input v-model="text">`
+    }
+    const Parent = {
+      components: { Child },
+      template: `<child text="hello" class="bar"/>`
+    }
+    expect(await renderToString(createApp(Parent))).toBe(
+      `<input class="bar" value="hello">`
+    )
+  })
+
+  test('with v-bind', async () => {
+    const Child = {
+      props: ['obj'],
+      template: `<div v-bind="obj" />`
+    }
+    const Parent = {
+      components: { Child },
+      template: `<child :obj="{ class: 'foo' }" class="bar"/>`
+    }
+    expect(await renderToString(createApp(Parent))).toBe(
+      `<div class="foo bar"></div>`
+    )
+  })
+
+  test('nested fallthrough', async () => {
+    const Child = {
+      props: ['id'],
+      template: `<div :id="id"></div>`
+    }
+    const Parent = {
+      components: { Child },
+      template: `<child id="foo" class="bar"/>`
+    }
+    // pass to parent, fallthrough to child and merge
+    const app = createApp(Parent, { class: 'baz' })
+    expect(await renderToString(app)).toBe(
+      `<div id="foo" class="bar baz"></div>`
+    )
+  })
+})
index 9f166aa6edf7ee128c7ba37a1cb4cdd02491fa22..1846bffceccd22cac22619463ebe0b18a05eb068 100644 (file)
@@ -111,7 +111,10 @@ function renderComponentSubTree(
       // optimized
       // set current rendering instance for asset resolution
       setCurrentRenderingInstance(instance)
-      comp.ssrRender(instance.proxy, push, instance)
+      // fallthrough attrs
+      const attrs =
+        instance.type.inheritAttrs !== false ? instance.attrs : undefined
+      comp.ssrRender(instance.proxy, push, instance, attrs)
       setCurrentRenderingInstance(null)
     } else if (instance.render) {
       renderVNode(push, renderComponentRoot(instance), instance)