Skip to content

Instantly share code, notes, and snippets.

@sebastianrothbucher
Last active December 7, 2019 12:45
Show Gist options
  • Save sebastianrothbucher/a88c2e72b17516b48f7e46babef636c5 to your computer and use it in GitHub Desktop.
Save sebastianrothbucher/a88c2e72b17516b48f7e46babef636c5 to your computer and use it in GitHub Desktop.
Vue-SSR edge cases
// Step 1: Create a Vue instance
const Vue = require('../vue/dist/vue.runtime.common.dev');
const app = require('./vue/app');
// Step 2: Create a renderer
const rendererFactory = require('../vue/packages/vue-server-renderer/build.dev');
const renderer=rendererFactory.createRenderer();
// Step 3a: Render the Vue instance to HTML
let renderedHtml = null;
let cnt = 0;
const doRender = () => renderer.renderToString(app.create())
/*.then(() => {
console.log(cnt++);
return doRender();
})*/
doRender()
.then(html => {
renderedHtml = html;
console.log((html.length > 100) ? (html.substring(0, 97) + '...') : html);
})
.catch(err => console.error(err));
// Step 3b: actual server
const express = require('express');
const server = express();
const bodyParser = require('body-parser');
const textParser = bodyParser.text();
server.use(express.static('../vue/dist/'));
server.use(express.static('./vue/'));
server.post('/report', (req, res) => {
textParser(req, res, () => {
console.log('> ' + req.body);
res.status(201).end();
});
});
server.get('*', (req, res) => {
if (renderedHtml) {
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>${renderedHtml}</body>
<script src="vue.js"></script>
<script src="app.js"></script>
<script>setTimeout(() => location.reload(), 3000);</script>
</html>
`);
} else {
res.status(500).end('No HTML');
}
});
server.listen(8080);
Old:
1048,
948,
803,
788,
867,
973,
804,
826,
862,
1239,
1256,
954,
832,
761,
786,
839,
779,
791,
847,
999
Min. 1st Qu. Median Mean 3rd Qu. Max.
761.0 800.0 843.0 900.1 958.8 1256.0
New:
1025,
1072,
1834,
1856,
2053,
808,
836,
776,
886,
787,
936,
795,
960,
1173,
794,
891,
835,
862,
874
Min. 1st Qu. Median Mean 3rd Qu. Max.
776.0 821.5 886.0 1055.4 1048.5 2053.0
if (typeof(Vue) === 'undefined') { // server
Vue = require('../../vue/dist/vue.runtime.common.dev');
}
Vue.component('no-ssr', {
name: 'ClientOnly',
functional: true,
props: {
placeholder: String,
placeholderTag: {
type: String,
default: 'div'
}
},
render(h, { parent, slots, props }) {
const { default: defaultSlot = [], placeholder: placeholderSlot } = slots()
if (parent._isMounted) {
return defaultSlot
}
parent.$once('hook:mounted', () => {
parent.$forceUpdate()
})
if (props.placeholderTag && (props.placeholder || placeholderSlot)) {
return h(
props.placeholderTag,
{
class: ['client-only-placeholder']
},
props.placeholder || placeholderSlot
)
}
// Return a placeholder element for each child in the default slot
// Or if no children return a single placeholder
return defaultSlot.length > 0 ? defaultSlot.map(() => h(false)) : h(false)
}
}); // from https://github.com/egoist/vue-client-only/blob/master/src/index.js
Vue.component('only-ssr', {
name: 'ServerOnly',
functional: true,
props: {
placeholder: String,
placeholderTag: {
type: String,
default: 'div'
}
},
render(h, { parent, slots, props }) {
const { default: defaultSlot = [], placeholder: placeholderSlot } = slots()
if (!parent._isMounted) {
return defaultSlot
}
parent.$once('hook:mounted', () => {
parent.$forceUpdate()
})
if (props.placeholderTag && (props.placeholder || placeholderSlot)) {
return h(
props.placeholderTag,
{
class: ['server-only-placeholder']
},
props.placeholder || placeholderSlot
)
}
// Return a placeholder element for each child in the default slot
// Or if no children return a single placeholder
return defaultSlot.length > 0 ? defaultSlot.map(() => h(false)) : h(false)
}
}); // same but other way round
Vue.component('my-component', {
template: `<div><only-ssr><strong>test srv</strong></only-ssr><no-ssr><strong>test clnt</strong></no-ssr> <strong v-if="isServer">test srv II</strong><strong v-if="!isServer">test clnt II</strong> Hello World {{msg}}</div>`,
data() {
return {
msg: null,
isServer: true, // : false - will crash in prodution mode: vue.js:4477 Uncaught DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method - background: assertNodeMatch is not applied in production mode
};
},
mounted() {
this.msg = "from client"; // works b/c text for text
this.$nextTick(() => this.isServer = false); // (only!) this way round works; without $nextTick it's dirty (yet works), with $nextTick hydration is certainly done
},
serverPrefetch() { // !!!
this.msg = "from server"; // works b/c text for text
//this.isServer = true; - see above
},
});
Vue.component('my-load-component', { // performance-test by creating > 10 x 10 x 10 x 10 nodes
template: `<div><span>{{level}}-{{ind}}</span><my-load-component v-for="c in children" :key="c" :level="level+1" :ind="c" /></div>`,
props: {
'level': {
type: Number,
default: 0,
},
'ind': {
type: Number,
default: 0,
},
},
computed: {
children () {
let res = [];
if (this.level < 4) {
for (let i=0; i<10; i++) {
res.push(i);
}
}
return res;
},
},
});
const create = () => new Vue({ // avoid memory leaks when re-using
//template: `<div id="app"><my-component/></div>`,
template: `<div id="app"><my-load-component/></div>`,
});
if (typeof(module) !== 'undefined') { // server
module.exports = {create: create};
} else if (typeof(window) !== 'undefined') { // browser
const startTime = (new Date()).getTime();
create().$mount('#app');
const took = ((new Date()).getTime() - startTime);
console.log('Vue took ' + took + 'ms');
fetch('/report', {
method: 'POST',
headers: {
'Content-type': 'text/plain',
},
body: (took + 'ms'),
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment