Last active
December 7, 2019 12:45
-
-
Save sebastianrothbucher/a88c2e72b17516b48f7e46babef636c5 to your computer and use it in GitHub Desktop.
Vue-SSR edge cases
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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