Skip to content

Instantly share code, notes, and snippets.

@eunjae-lee
Created April 8, 2020 15:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eunjae-lee/2def69b99034c5b937e49a8bc5ebe614 to your computer and use it in GitHub Desktop.
Save eunjae-lee/2def69b99034c5b937e49a8bc5ebe614 to your computer and use it in GitHub Desktop.
vue ssr testing with serverPrefetch

renderToString in serverPrefetch

// Search.vue

<template>
  <div>
    <h1>Search</h1>
    <search-main :index-name="indexName" />
  </div>
</template>

<script>
export default {
  serverPrefetch() {
    return new Promise((resolve, reject) => {
      const renderer = createRenderer();
      const app = new Vue({
        render: h => h(SearchMain),
      });
      renderer.renderToString(app, (err, html) => {
        if (err) {
          console.log({ err });
          reject(err);
        } else {
          console.log({ html });
          resolve();
        }
      });
    });
  },
  data() {
    return {
      indexName: "instant_search"
    }
  },
}

// SearchMain.vue
<template>
  <div>
    <instant-search :index-name="indexName">
      <configure :hits-per-page="10" :page="1" query="iPhone" />
    </instant-search>
  </div>
</template>

↑ To avoid recursion, I've move all the InstantSearch components to SearchMain.vue. Btw, this InstantSearch component is not the real but just an empty component with the same name.

With the approach above, it almost correctly rendered the whole html. "indexName" is not passed to SearchMain.vue. I tried a few things but didn't work. It's okay for now.

<div data-server-rendered="true">
  <div>
    <p>InstantSearch with index: </p>
    <div>
      <p>this is a Configure widget.</p>
      <p>- hitsPerPage: 10</p>
      <p>- page: 1</p>
      <p>- query: iPhone</p>
    </div>
  </div>
</div>

What's in renderToString?

Apparent it does lots of stuff. The very first rendering begins here. Then it goes to here.

So I mocked the behavior a little bit.

// Search.vue

<script>
export default {
  serverPrefetch() {
    return new Promise(resolve => {
      const app = new Vue({
        render: h => h(SearchMain),
      });
      app.$mount();
      const node = app._render();
      const child = new node.componentOptions.Ctor();
      const childNode = child._render();
      const firstGrandChild = childNode.children[0];
      console.log({ childNode, firstGrandChild });
      resolve();
    });
  }
}
</script>

This printed this:

{
  childNode: VNode {
    tag: 'div',
    data: undefined,
    children: [ [VNode] ],
    text: undefined,
    elm: undefined,
    ns: undefined,
    context: VueComponent {
      _uid: 10,
      _isVue: true,
      '$options': [Object],
      _renderProxy: [Circular],
      _self: [Circular],
      '$parent': undefined,
      '$root': [Circular],
      '$children': [],
      '$refs': {},
      _watcher: null,
      _inactive: null,
      _directInactive: false,
      _isMounted: false,
      _isDestroyed: false,
      _isBeingDestroyed: false,
      _events: [Object: null prototype] {},
      _hasHookEvent: false,
      _vnode: null,
      _staticTrees: null,
      '$vnode': undefined,
      '$slots': {},
      '$scopedSlots': {},
      _c: [Function],
      '$createElement': [Function],
      '$attrs': [Getter/Setter],
      '$listeners': [Getter/Setter],
      _routerRoot: [Circular],
      _watchers: [],
      _props: [Object],
      _data: {}
    },
    fnContext: undefined,
    fnOptions: undefined,
    fnScopeId: undefined,
    key: undefined,
    componentOptions: undefined,
    componentInstance: undefined,
    parent: undefined,
    raw: false,
    isStatic: false,
    isRootInsert: true,
    isComment: false,
    isCloned: false,
    isOnce: false,
    asyncFactory: undefined,
    asyncMeta: undefined,
    isAsyncPlaceholder: false
  },
  firstGrandChild: VNode {
    tag: 'vue-component-7-InstantSearch',
    data: { attrs: {}, on: undefined, hook: [Object] },
    children: undefined,
    text: undefined,
    elm: undefined,
    ns: undefined,
    context: VueComponent {
      _uid: 10,
      _isVue: true,
      '$options': [Object],
      _renderProxy: [Circular],
      _self: [Circular],
      '$parent': undefined,
      '$root': [Circular],
      '$children': [],
      '$refs': {},
      _watcher: null,
      _inactive: null,
      _directInactive: false,
      _isMounted: false,
      _isDestroyed: false,
      _isBeingDestroyed: false,
      _events: [Object: null prototype] {},
      _hasHookEvent: false,
      _vnode: null,
      _staticTrees: null,
      '$vnode': undefined,
      '$slots': {},
      '$scopedSlots': {},
      _c: [Function],
      '$createElement': [Function],
      '$attrs': [Getter/Setter],
      '$listeners': [Getter/Setter],
      _routerRoot: [Circular],
      _watchers: [],
      _props: [Object],
      _data: {}
    },
    fnContext: undefined,
    fnOptions: undefined,
    fnScopeId: undefined,
    key: undefined,
    componentOptions: {
      Ctor: [Function],
      propsData: [Object],
      listeners: undefined,
      tag: 'instant-search',
      children: [Array]
    },
    componentInstance: undefined,
    parent: undefined,
    raw: false,
    isStatic: false,
    isRootInsert: true,
    isComment: false,
    isCloned: false,
    isOnce: false,
    asyncFactory: undefined,
    asyncMeta: undefined,
    isAsyncPlaceholder: false
  }
}

Unclear yet about how far we can go with this approach. For example, will this correctly render all the components to the leaf and let them fire whatever API those components are supposed to fire, just as if it is rendered on client side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment