ssrComponent.spec.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  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('named slots', () => {
  91. expect(
  92. compile(`<foo>
  93. <template v-slot>foo</template>
  94. <template v-slot:named>bar</template>
  95. </foo>`).code
  96. ).toMatchInlineSnapshot(`
  97. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\")
  98. const { ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
  99. return function ssrRender(_ctx, _push, _parent, _attrs) {
  100. const _component_foo = _resolveComponent(\\"foo\\")
  101. _push(_ssrRenderComponent(_component_foo, _attrs, {
  102. default: _withCtx((_, _push, _parent, _scopeId) => {
  103. if (_push) {
  104. _push(\`foo\`)
  105. } else {
  106. return [
  107. _createTextVNode(\\"foo\\")
  108. ]
  109. }
  110. }),
  111. named: _withCtx((_, _push, _parent, _scopeId) => {
  112. if (_push) {
  113. _push(\`bar\`)
  114. } else {
  115. return [
  116. _createTextVNode(\\"bar\\")
  117. ]
  118. }
  119. }),
  120. _: 1 /* STABLE */
  121. }, _parent))
  122. }"
  123. `)
  124. })
  125. test('v-if slot', () => {
  126. expect(
  127. compile(`<foo>
  128. <template v-slot:named v-if="ok">foo</template>
  129. </foo>`).code
  130. ).toMatchInlineSnapshot(`
  131. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode, createSlots: _createSlots } = require(\\"vue\\")
  132. const { ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
  133. return function ssrRender(_ctx, _push, _parent, _attrs) {
  134. const _component_foo = _resolveComponent(\\"foo\\")
  135. _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 /* DYNAMIC */ }, [
  136. (_ctx.ok)
  137. ? {
  138. name: \\"named\\",
  139. fn: _withCtx((_, _push, _parent, _scopeId) => {
  140. if (_push) {
  141. _push(\`foo\`)
  142. } else {
  143. return [
  144. _createTextVNode(\\"foo\\")
  145. ]
  146. }
  147. })
  148. }
  149. : undefined
  150. ]), _parent))
  151. }"
  152. `)
  153. })
  154. test('v-for slot', () => {
  155. expect(
  156. compile(`<foo>
  157. <template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template>
  158. </foo>`).code
  159. ).toMatchInlineSnapshot(`
  160. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require(\\"vue\\")
  161. const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"vue/server-renderer\\")
  162. return function ssrRender(_ctx, _push, _parent, _attrs) {
  163. const _component_foo = _resolveComponent(\\"foo\\")
  164. _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 2 /* DYNAMIC */ }, [
  165. _renderList(_ctx.names, (key) => {
  166. return {
  167. name: key,
  168. fn: _withCtx(({ msg }, _push, _parent, _scopeId) => {
  169. if (_push) {
  170. _push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
  171. } else {
  172. return [
  173. _createTextVNode(_toDisplayString(msg + _ctx.key + _ctx.bar), 1 /* TEXT */)
  174. ]
  175. }
  176. })
  177. }
  178. })
  179. ]), _parent))
  180. }"
  181. `)
  182. })
  183. test('nested transform scoping in vnode branch', () => {
  184. expect(
  185. compile(`<foo>
  186. <template v-slot:foo="{ list }">
  187. <div v-if="ok">
  188. <span v-for="i in list"></span>
  189. </div>
  190. </template>
  191. <template v-slot:bar="{ ok }">
  192. <div v-if="ok">
  193. <span v-for="i in list"></span>
  194. </div>
  195. </template>
  196. </foo>`).code
  197. ).toMatchInlineSnapshot(`
  198. "const { resolveComponent: _resolveComponent, withCtx: _withCtx, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode } = require(\\"vue\\")
  199. const { ssrRenderComponent: _ssrRenderComponent, ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
  200. return function ssrRender(_ctx, _push, _parent, _attrs) {
  201. const _component_foo = _resolveComponent(\\"foo\\")
  202. _push(_ssrRenderComponent(_component_foo, _attrs, {
  203. foo: _withCtx(({ list }, _push, _parent, _scopeId) => {
  204. if (_push) {
  205. if (_ctx.ok) {
  206. _push(\`<div\${_scopeId}><!--[-->\`)
  207. _ssrRenderList(list, (i) => {
  208. _push(\`<span\${_scopeId}></span>\`)
  209. })
  210. _push(\`<!--]--></div>\`)
  211. } else {
  212. _push(\`<!---->\`)
  213. }
  214. } else {
  215. return [
  216. (_ctx.ok)
  217. ? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, [
  218. (_openBlock(true), _createBlock(_Fragment, null, _renderList(list, (i) => {
  219. return (_openBlock(), _createBlock(\\"span\\"))
  220. }), 256 /* UNKEYED_FRAGMENT */))
  221. ]))
  222. : _createCommentVNode(\\"v-if\\", true)
  223. ]
  224. }
  225. }),
  226. bar: _withCtx(({ ok }, _push, _parent, _scopeId) => {
  227. if (_push) {
  228. if (ok) {
  229. _push(\`<div\${_scopeId}><!--[-->\`)
  230. _ssrRenderList(_ctx.list, (i) => {
  231. _push(\`<span\${_scopeId}></span>\`)
  232. })
  233. _push(\`<!--]--></div>\`)
  234. } else {
  235. _push(\`<!---->\`)
  236. }
  237. } else {
  238. return [
  239. ok
  240. ? (_openBlock(), _createBlock(\\"div\\", { key: 0 }, [
  241. (_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (i) => {
  242. return (_openBlock(), _createBlock(\\"span\\"))
  243. }), 256 /* UNKEYED_FRAGMENT */))
  244. ]))
  245. : _createCommentVNode(\\"v-if\\", true)
  246. ]
  247. }
  248. }),
  249. _: 1 /* STABLE */
  250. }, _parent))
  251. }"
  252. `)
  253. })
  254. test('built-in fallthroughs', () => {
  255. expect(compile(`<transition><div/></transition>`).code)
  256. .toMatchInlineSnapshot(`
  257. "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
  258. return function ssrRender(_ctx, _push, _parent, _attrs) {
  259. _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
  260. }"
  261. `)
  262. // should inject attrs if root with coomments
  263. expect(compile(`<!--root--><transition><div/></transition>`).code)
  264. .toMatchInlineSnapshot(`
  265. "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
  266. return function ssrRender(_ctx, _push, _parent, _attrs) {
  267. _push(\`<!--[--><!--root--><div\${_ssrRenderAttrs(_attrs)}></div><!--]-->\`)
  268. }"
  269. `)
  270. // should not inject attrs if not root
  271. expect(compile(`<div/><transition><div/></transition>`).code)
  272. .toMatchInlineSnapshot(`
  273. "
  274. return function ssrRender(_ctx, _push, _parent, _attrs) {
  275. _push(\`<!--[--><div></div><div></div><!--]-->\`)
  276. }"
  277. `)
  278. expect(compile(`<keep-alive><foo/></keep-alive>`).code)
  279. .toMatchInlineSnapshot(`
  280. "const { resolveComponent: _resolveComponent } = require(\\"vue\\")
  281. const { ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
  282. return function ssrRender(_ctx, _push, _parent, _attrs) {
  283. const _component_foo = _resolveComponent(\\"foo\\")
  284. _push(_ssrRenderComponent(_component_foo, _attrs, null, _parent))
  285. }"
  286. `)
  287. })
  288. // transition-group should flatten and concat its children fragments into
  289. // a single one
  290. describe('transition-group', () => {
  291. test('basic', () => {
  292. expect(
  293. compile(
  294. `<transition-group><div v-for="i in list"/></transition-group>`
  295. ).code
  296. ).toMatchInlineSnapshot(`
  297. "const { ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
  298. return function ssrRender(_ctx, _push, _parent, _attrs) {
  299. _push(\`<!--[-->\`)
  300. _ssrRenderList(_ctx.list, (i) => {
  301. _push(\`<div></div>\`)
  302. })
  303. _push(\`<!--]-->\`)
  304. }"
  305. `)
  306. })
  307. test('with static tag', () => {
  308. expect(
  309. compile(
  310. `<transition-group tag="ul"><div v-for="i in list"/></transition-group>`
  311. ).code
  312. ).toMatchInlineSnapshot(`
  313. "const { ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
  314. return function ssrRender(_ctx, _push, _parent, _attrs) {
  315. _push(\`<ul>\`)
  316. _ssrRenderList(_ctx.list, (i) => {
  317. _push(\`<div></div>\`)
  318. })
  319. _push(\`</ul>\`)
  320. }"
  321. `)
  322. })
  323. test('with dynamic tag', () => {
  324. expect(
  325. compile(
  326. `<transition-group :tag="someTag"><div v-for="i in list"/></transition-group>`
  327. ).code
  328. ).toMatchInlineSnapshot(`
  329. "const { ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
  330. return function ssrRender(_ctx, _push, _parent, _attrs) {
  331. _push(\`<\${_ctx.someTag}>\`)
  332. _ssrRenderList(_ctx.list, (i) => {
  333. _push(\`<div></div>\`)
  334. })
  335. _push(\`</\${_ctx.someTag}>\`)
  336. }"
  337. `)
  338. })
  339. test('with multi fragments children', () => {
  340. expect(
  341. compile(
  342. `<transition-group>
  343. <div v-for="i in 10"/>
  344. <div v-for="i in 10"/>
  345. <template v-if="ok"><div>ok</div></template>
  346. </transition-group>`
  347. ).code
  348. ).toMatchInlineSnapshot(`
  349. "const { ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
  350. return function ssrRender(_ctx, _push, _parent, _attrs) {
  351. _push(\`<!--[-->\`)
  352. _ssrRenderList(10, (i) => {
  353. _push(\`<div></div>\`)
  354. })
  355. _ssrRenderList(10, (i) => {
  356. _push(\`<div></div>\`)
  357. })
  358. if (_ctx.ok) {
  359. _push(\`<div>ok</div>\`)
  360. } else {
  361. _push(\`<!---->\`)
  362. }
  363. _push(\`<!--]-->\`)
  364. }"
  365. `)
  366. })
  367. })
  368. })
  369. describe('custom directive', () => {
  370. test('basic', () => {
  371. expect(compile(`<foo v-xxx:x.y="z" />`).code).toMatchInlineSnapshot(`
  372. "const { resolveComponent: _resolveComponent, resolveDirective: _resolveDirective, mergeProps: _mergeProps } = require(\\"vue\\")
  373. const { ssrGetDirectiveProps: _ssrGetDirectiveProps, ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
  374. return function ssrRender(_ctx, _push, _parent, _attrs) {
  375. const _component_foo = _resolveComponent(\\"foo\\")
  376. const _directive_xxx = _resolveDirective(\\"xxx\\")
  377. _push(_ssrRenderComponent(_component_foo, _mergeProps(_attrs, _ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.z, \\"x\\", { y: true })), null, _parent))
  378. }"
  379. `)
  380. })
  381. })
  382. })