ssrComponent.spec.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. import { compile } from '../src'
  2. describe('ssr: components', () => {
  3. test('basic', () => {
  4. expect(compile(`<foo id="a" :prop="b" />`).code).toMatchInlineSnapshot(`
  5. "const { resolveComponent: _resolveComponent, mergeProps: _mergeProps } = require("vue")
  6. const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
  7. return function ssrRender(_ctx, _push, _parent, _attrs) {
  8. const _component_foo = _resolveComponent("foo")
  9. _push(_ssrRenderComponent(_component_foo, _mergeProps({
  10. id: "a",
  11. prop: _ctx.b
  12. }, _attrs), null, _parent))
  13. }"
  14. `)
  15. })
  16. // event listeners should still be passed
  17. test('event listeners', () => {
  18. expect(compile(`<foo @click="bar" />`).code).toMatchInlineSnapshot(`
  19. "const { resolveComponent: _resolveComponent, mergeProps: _mergeProps } = require("vue")
  20. const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
  21. return function ssrRender(_ctx, _push, _parent, _attrs) {
  22. const _component_foo = _resolveComponent("foo")
  23. _push(_ssrRenderComponent(_component_foo, _mergeProps({ onClick: _ctx.bar }, _attrs), null, _parent))
  24. }"
  25. `)
  26. })
  27. test('dynamic component', () => {
  28. expect(compile(`<component is="foo" prop="b" />`).code)
  29. .toMatchInlineSnapshot(`
  30. "const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps, createVNode: _createVNode } = require("vue")
  31. const { ssrRenderVNode: _ssrRenderVNode } = require("vue/server-renderer")
  32. return function ssrRender(_ctx, _push, _parent, _attrs) {
  33. _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("foo"), _mergeProps({ prop: "b" }, _attrs), null), _parent)
  34. }"
  35. `)
  36. expect(compile(`<component :is="foo" prop="b" />`).code)
  37. .toMatchInlineSnapshot(`
  38. "const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps, createVNode: _createVNode } = require("vue")
  39. const { ssrRenderVNode: _ssrRenderVNode } = require("vue/server-renderer")
  40. return function ssrRender(_ctx, _push, _parent, _attrs) {
  41. _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.foo), _mergeProps({ prop: "b" }, _attrs), null), _parent)
  42. }"
  43. `)
  44. })
  45. describe('slots', () => {
  46. test('implicit default slot', () => {
  47. expect(compile(`<foo>hello<div/></foo>`).code).toMatchInlineSnapshot(`
  48. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, createTextVNode: _createTextVNode } = require("vue")
  49. const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
  50. return function ssrRender(_ctx, _push, _parent, _attrs) {
  51. const _component_foo = _resolveComponent("foo")
  52. _push(_ssrRenderComponent(_component_foo, _attrs, {
  53. default: _withCtx((_, _push, _parent, _scopeId) => {
  54. if (_push) {
  55. _push(\`hello<div\${_scopeId}></div>\`)
  56. } else {
  57. return [
  58. _createTextVNode("hello"),
  59. _createVNode("div")
  60. ]
  61. }
  62. }),
  63. _: 1 /* STABLE */
  64. }, _parent))
  65. }"
  66. `)
  67. })
  68. test('explicit default slot', () => {
  69. expect(compile(`<foo v-slot="{ msg }">{{ msg + outer }}</foo>`).code)
  70. .toMatchInlineSnapshot(`
  71. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode } = require("vue")
  72. const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")
  73. return function ssrRender(_ctx, _push, _parent, _attrs) {
  74. const _component_foo = _resolveComponent("foo")
  75. _push(_ssrRenderComponent(_component_foo, _attrs, {
  76. default: _withCtx(({ msg }, _push, _parent, _scopeId) => {
  77. if (_push) {
  78. _push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
  79. } else {
  80. return [
  81. _createTextVNode(_toDisplayString(msg + _ctx.outer), 1 /* TEXT */)
  82. ]
  83. }
  84. }),
  85. _: 1 /* STABLE */
  86. }, _parent))
  87. }"
  88. `)
  89. })
  90. test('slot prop component', () => {
  91. const { code } = compile(`<foo v-slot="{ Foo }"><Foo /></foo>`)
  92. expect(code).toContain(
  93. `_ssrRenderComponent(Foo, null, null, _parent, _scopeId)`,
  94. )
  95. expect(code).toContain(`_createVNode(Foo)`)
  96. expect(code).not.toContain(`_component_Foo`)
  97. expect(code).not.toContain(`_resolveComponent("Foo")`)
  98. })
  99. test('namespaced slot prop component', () => {
  100. const { code } = compile(
  101. `<foo v-slot="slotProps"><slot-props.Foo /></foo>`,
  102. )
  103. expect(code).toContain(
  104. `_ssrRenderComponent(slotProps.Foo, null, null, _parent, _scopeId)`,
  105. )
  106. expect(code).toContain(`_createVNode(slotProps.Foo)`)
  107. expect(code).not.toContain(`_component_slot_props`)
  108. expect(code).not.toContain(`_resolveComponent("slot-props.Foo")`)
  109. })
  110. test('does not resolve v-for binding as slot prop component', () => {
  111. const { code } = compile(
  112. `<template v-for="Foo in list"><Foo :value="Foo" /></template>`,
  113. )
  114. expect(code).toContain(`const _component_Foo = _resolveComponent("Foo")`)
  115. expect(code).toContain(`_ssrRenderComponent(_component_Foo`)
  116. expect(code).toContain(`value: Foo`)
  117. expect(code).not.toContain(`_ssrRenderComponent(Foo,`)
  118. })
  119. test('does not resolve slot prop component when shadowed by v-for', () => {
  120. const { code } = compile(
  121. `<foo v-slot="{ Foo }"><template v-for="Foo in list"><Foo /></template></foo>`,
  122. )
  123. expect(code).toContain(`const _component_Foo = _resolveComponent("Foo")`)
  124. expect(code).toContain(`_ssrRenderComponent(_component_Foo`)
  125. expect(code).toContain(`_createBlock(_component_Foo)`)
  126. expect(code).not.toContain(`_ssrRenderComponent(Foo, null`)
  127. expect(code).not.toContain(`_createVNode(Foo)`)
  128. expect(code).not.toContain(`_createBlock(Foo)`)
  129. })
  130. test('empty attribute should not produce syntax error', () => {
  131. // previously this would produce syntax error `default: _withCtx((, _push, ...)`
  132. expect(compile(`<foo v-slot="">foo</foo>`).code).not.toMatch(`(,`)
  133. })
  134. test('named slots', () => {
  135. expect(
  136. compile(`<foo>
  137. <template v-slot>foo</template>
  138. <template v-slot:named>bar</template>
  139. </foo>`).code,
  140. ).toMatchInlineSnapshot(`
  141. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require("vue")
  142. const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
  143. return function ssrRender(_ctx, _push, _parent, _attrs) {
  144. const _component_foo = _resolveComponent("foo")
  145. _push(_ssrRenderComponent(_component_foo, _attrs, {
  146. default: _withCtx((_, _push, _parent, _scopeId) => {
  147. if (_push) {
  148. _push(\`foo\`)
  149. } else {
  150. return [
  151. _createTextVNode("foo")
  152. ]
  153. }
  154. }),
  155. named: _withCtx((_, _push, _parent, _scopeId) => {
  156. if (_push) {
  157. _push(\`bar\`)
  158. } else {
  159. return [
  160. _createTextVNode("bar")
  161. ]
  162. }
  163. }),
  164. _: 1 /* STABLE */
  165. }, _parent))
  166. }"
  167. `)
  168. })
  169. test('v-if slot', () => {
  170. expect(
  171. compile(`<foo>
  172. <template v-slot:named v-if="ok">foo</template>
  173. </foo>`).code,
  174. ).toMatchInlineSnapshot(`
  175. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode, createSlots: _createSlots } = require("vue")
  176. const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
  177. return function ssrRender(_ctx, _push, _parent, _attrs) {
  178. const _component_foo = _resolveComponent("foo")
  179. _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 /* DYNAMIC */ }, [
  180. (_ctx.ok)
  181. ? {
  182. name: "named",
  183. fn: _withCtx((_, _push, _parent, _scopeId) => {
  184. if (_push) {
  185. _push(\`foo\`)
  186. } else {
  187. return [
  188. _createTextVNode("foo")
  189. ]
  190. }
  191. }),
  192. key: "0"
  193. }
  194. : undefined
  195. ]), _parent))
  196. }"
  197. `)
  198. })
  199. test('v-for slot', () => {
  200. const { code } = compile(`<foo>
  201. <template v-for="(key, index) in names" v-slot:[key]="{ msg }">{{ msg + key + index + bar }}</template>
  202. </foo>`)
  203. expect(code).not.toMatch(`_ctx.msg`)
  204. expect(code).not.toMatch(`_ctx.key`)
  205. expect(code).not.toMatch(`_ctx.index`)
  206. expect(code).toMatch(`_ctx.bar`)
  207. expect(code).toMatchInlineSnapshot(`
  208. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require("vue")
  209. const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")
  210. return function ssrRender(_ctx, _push, _parent, _attrs) {
  211. const _component_foo = _resolveComponent("foo")
  212. _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 /* DYNAMIC */ }, [
  213. _renderList(_ctx.names, (key, index) => {
  214. return {
  215. name: key,
  216. fn: _withCtx(({ msg }, _push, _parent, _scopeId) => {
  217. if (_push) {
  218. _push(\`\${_ssrInterpolate(msg + key + index + _ctx.bar)}\`)
  219. } else {
  220. return [
  221. _createTextVNode(_toDisplayString(msg + key + index + _ctx.bar), 1 /* TEXT */)
  222. ]
  223. }
  224. })
  225. }
  226. })
  227. ]), _parent))
  228. }"
  229. `)
  230. })
  231. test('nested transform scoping in vnode branch', () => {
  232. expect(
  233. compile(`<foo>
  234. <template v-slot:foo="{ list }">
  235. <div v-if="ok">
  236. <span v-for="i in list"></span>
  237. </div>
  238. </template>
  239. <template v-slot:bar="{ ok }">
  240. <div v-if="ok">
  241. <span v-for="i in list"></span>
  242. </div>
  243. </template>
  244. </foo>`).code,
  245. ).toMatchInlineSnapshot(`
  246. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = require("vue")
  247. const { ssrRenderComponent: _ssrRenderComponent, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
  248. return function ssrRender(_ctx, _push, _parent, _attrs) {
  249. const _component_foo = _resolveComponent("foo")
  250. _push(_ssrRenderComponent(_component_foo, _attrs, {
  251. foo: _withCtx(({ list }, _push, _parent, _scopeId) => {
  252. if (_push) {
  253. if (_ctx.ok) {
  254. _push(\`<div\${_scopeId}><!--[-->\`)
  255. _ssrRenderList(list, (i) => {
  256. _push(\`<span\${_scopeId}></span>\`)
  257. })
  258. _push(\`<!--]--></div>\`)
  259. } else {
  260. _push(\`<!---->\`)
  261. }
  262. } else {
  263. return [
  264. (_ctx.ok)
  265. ? (_openBlock(), _createBlock("div", { key: 0 }, [
  266. (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
  267. return (_openBlock(), _createBlock("span"))
  268. }), 256 /* UNKEYED_FRAGMENT */))
  269. ]))
  270. : _createCommentVNode("v-if", true)
  271. ]
  272. }
  273. }),
  274. bar: _withCtx(({ ok }, _push, _parent, _scopeId) => {
  275. if (_push) {
  276. if (ok) {
  277. _push(\`<div\${_scopeId}><!--[-->\`)
  278. _ssrRenderList(_ctx.list, (i) => {
  279. _push(\`<span\${_scopeId}></span>\`)
  280. })
  281. _push(\`<!--]--></div>\`)
  282. } else {
  283. _push(\`<!---->\`)
  284. }
  285. } else {
  286. return [
  287. ok
  288. ? (_openBlock(), _createBlock("div", { key: 0 }, [
  289. (_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (i) => {
  290. return (_openBlock(), _createBlock("span"))
  291. }), 256 /* UNKEYED_FRAGMENT */))
  292. ]))
  293. : _createCommentVNode("v-if", true)
  294. ]
  295. }
  296. }),
  297. _: 1 /* STABLE */
  298. }, _parent))
  299. }"
  300. `)
  301. })
  302. // #7644
  303. test('slot content with v-once', () => {
  304. const { code } = compile(`<foo><bar v-once /></foo>`)
  305. expect(code).not.toMatch(`_cache`)
  306. expect(compile(`<foo><bar v-once /></foo>`).code).toMatchInlineSnapshot(`
  307. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require("vue")
  308. const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
  309. return function ssrRender(_ctx, _push, _parent, _attrs) {
  310. const _component_foo = _resolveComponent("foo")
  311. const _component_bar = _resolveComponent("bar")
  312. _push(_ssrRenderComponent(_component_foo, _attrs, {
  313. default: _withCtx((_, _push, _parent, _scopeId) => {
  314. if (_push) {
  315. _push(_ssrRenderComponent(_component_bar, null, null, _parent, _scopeId))
  316. } else {
  317. return [
  318. _createVNode(_component_bar)
  319. ]
  320. }
  321. }),
  322. _: 1 /* STABLE */
  323. }, _parent))
  324. }"
  325. `)
  326. })
  327. // #13724
  328. test('slot content with v-memo', () => {
  329. const { code } = compile(`<foo><bar v-memo="[]" /></foo>`)
  330. expect(code).not.toMatch(`_cache`)
  331. expect(compile(`<foo><bar v-memo="[]" /></foo>`).code)
  332. .toMatchInlineSnapshot(`
  333. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require("vue")
  334. const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
  335. return function ssrRender(_ctx, _push, _parent, _attrs) {
  336. const _component_foo = _resolveComponent("foo")
  337. const _component_bar = _resolveComponent("bar")
  338. _push(_ssrRenderComponent(_component_foo, _attrs, {
  339. default: _withCtx((_, _push, _parent, _scopeId) => {
  340. if (_push) {
  341. _push(_ssrRenderComponent(_component_bar, null, null, _parent, _scopeId))
  342. } else {
  343. return [
  344. _createVNode(_component_bar)
  345. ]
  346. }
  347. }),
  348. _: 1 /* STABLE */
  349. }, _parent))
  350. }"
  351. `)
  352. })
  353. describe('built-in fallthroughs', () => {
  354. test('transition', () => {
  355. expect(compile(`<transition><div/></transition>`).code)
  356. .toMatchInlineSnapshot(`
  357. "const { ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
  358. return function ssrRender(_ctx, _push, _parent, _attrs) {
  359. _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
  360. }"
  361. `)
  362. })
  363. test('keep-alive', () => {
  364. expect(compile(`<keep-alive><foo/></keep-alive>`).code)
  365. .toMatchInlineSnapshot(`
  366. "const { resolveComponent: _resolveComponent } = require("vue")
  367. const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
  368. return function ssrRender(_ctx, _push, _parent, _attrs) {
  369. const _component_foo = _resolveComponent("foo")
  370. _push(_ssrRenderComponent(_component_foo, _attrs, null, _parent))
  371. }"
  372. `)
  373. })
  374. // #5352
  375. test('should push marker string if is slot root', () => {
  376. expect(
  377. compile(`<foo><transition><div v-if="false"/></transition></foo>`)
  378. .code,
  379. ).toMatchInlineSnapshot(`
  380. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Transition: _Transition, createVNode: _createVNode } = require("vue")
  381. const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
  382. return function ssrRender(_ctx, _push, _parent, _attrs) {
  383. const _component_foo = _resolveComponent("foo")
  384. _push(_ssrRenderComponent(_component_foo, _attrs, {
  385. default: _withCtx((_, _push, _parent, _scopeId) => {
  386. if (_push) {
  387. _push(\`\`)
  388. if (false) {
  389. _push(\`<div\${_scopeId}></div>\`)
  390. } else {
  391. _push(\`<!---->\`)
  392. }
  393. } else {
  394. return [
  395. _createVNode(_Transition, null, {
  396. default: _withCtx(() => [
  397. false
  398. ? (_openBlock(), _createBlock("div", { key: 0 }))
  399. : _createCommentVNode("v-if", true)
  400. ]),
  401. _: 1 /* STABLE */
  402. })
  403. ]
  404. }
  405. }),
  406. _: 1 /* STABLE */
  407. }, _parent))
  408. }"
  409. `)
  410. })
  411. })
  412. })
  413. describe('custom directive', () => {
  414. test('basic', () => {
  415. expect(compile(`<foo v-xxx:x.y="z" />`).code).toMatchInlineSnapshot(`
  416. "const { resolveComponent: _resolveComponent, resolveDirective: _resolveDirective, mergeProps: _mergeProps } = require("vue")
  417. const { ssrGetDirectiveProps: _ssrGetDirectiveProps, ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
  418. return function ssrRender(_ctx, _push, _parent, _attrs) {
  419. const _component_foo = _resolveComponent("foo")
  420. const _directive_xxx = _resolveDirective("xxx")
  421. _push(_ssrRenderComponent(_component_foo, _mergeProps(_attrs, _ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.z, "x", { y: true })), null, _parent))
  422. }"
  423. `)
  424. })
  425. })
  426. })