ssrComponent.spec.ts 17 KB

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