summaryrefslogtreecommitdiff
path: root/src/frontend/render/vue.ts
blob: 57c3c82762e52806aec3cb4bfcb6c29b51ea5aeb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import type { ComponentRenderer } from '../../@types/renderer';
import type { Component as VueComponent } from 'vue';
import { renderToString } from '@vue/server-renderer';
import { defineComponent, createSSRApp, h as createElement } from 'vue';
import { createRenderer } from './renderer';

// This prevents tree-shaking of render.
Function.prototype(renderToString);

/**
 * Users might attempt to use :vueAttribute syntax to pass primitive values.
 * If so, try to JSON.parse them to get the primitives
 */
function cleanPropsForVue(obj: Record<string, any>) {
  let cleaned = {} as any;
  for (let [key, value] of Object.entries(obj)) {
    if (key.startsWith(':')) {
      key = key.slice(1);
      if (typeof value === 'string') {
        try {
          value = JSON.parse(value);
        } catch (e) {}
      }
    }
    cleaned[key] = value;
  }
  return cleaned;
}

const Vue: ComponentRenderer<VueComponent> = {
  jsxPragma: createElement,
  jsxPragmaName: 'createElement',
  renderStatic(Component) {
    return async (props, ...children) => {
      const App = defineComponent({
        components: {
          Component,
        },
        data() {
          return { props };
        },
        template: `<Component v-bind="props">${children.join('\n')}</Component>`,
      });

      const app = createSSRApp(App);
      const html = await renderToString(app);
      return html;
    };
  },
  imports: {
    vue: ['createApp', 'h: createElement'],
  },
  render({ Component, root, props, children }) {
    const vueProps = cleanPropsForVue(JSON.parse(props));
    return `const App = { render: () => createElement(${Component}, ${JSON.stringify(vueProps)}, { default: () => ${children} }) };
createApp(App).mount(${root});`;
  },
};

const renderer = createRenderer(Vue);

export const __vue_static = renderer.static;
export const __vue_load = renderer.load;
export const __vue_idle = renderer.idle;
export const __vue_visible = renderer.visible;