Skip to content

Commit b152e97

Browse files
committed
(WIP) Experimental SSR
1 parent bf2a867 commit b152e97

File tree

5 files changed

+177
-4
lines changed

5 files changed

+177
-4
lines changed

packages/vue-ssr/compile-html.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
Plugin.registerCompiler({
2+
extensions: ['html'],
3+
archMatching: 'web',
4+
isTemplate: true
5+
}, () => new CachingHtmlCompiler("static-html", TemplatingTools.scanHtmlForTags, compileTagsToStaticHtml));
6+
7+
// Same API as TutorialTools.compileTagsWithSpacebars, but instead of compiling
8+
// with Spacebars, it just returns static HTML
9+
function compileTagsToStaticHtml(tags) {
10+
var handler = new StaticHtmlTagHandler();
11+
12+
tags.forEach((tag) => {
13+
handler.addTagToResults(tag);
14+
});
15+
16+
return handler.getResults();
17+
};
18+
19+
class StaticHtmlTagHandler {
20+
constructor() {
21+
this.results = {
22+
head: '',
23+
body: '',
24+
js: '',
25+
bodyAttrs: {}
26+
};
27+
}
28+
29+
getResults() {
30+
return this.results;
31+
}
32+
33+
addTagToResults(tag) {
34+
this.tag = tag;
35+
36+
// do we have 1 or more attributes?
37+
const hasAttribs = ! _.isEmpty(this.tag.attribs);
38+
39+
if (this.tag.tagName === "head") {
40+
if (hasAttribs) {
41+
this.throwCompileError("Attributes on <head> not supported");
42+
}
43+
44+
this.results.head += this.tag.contents;
45+
return;
46+
}
47+
48+
49+
// <body> or <template>
50+
51+
try {
52+
if (this.tag.tagName === "body") {
53+
this.addBodyAttrs(this.tag.attribs);
54+
55+
// We may be one of many `<body>` tags.
56+
this.results.body += this.tag.contents;
57+
} else {
58+
this.throwCompileError("Expected <head> or <body> tag", this.tag.tagStartIndex);
59+
}
60+
} catch (e) {
61+
if (e.scanner) {
62+
// The error came from Spacebars
63+
this.throwCompileError(e.message, this.tag.contentsStartIndex + e.offset);
64+
} else {
65+
throw e;
66+
}
67+
}
68+
}
69+
70+
addBodyAttrs(attrs) {
71+
Object.keys(attrs).forEach((attr) => {
72+
const val = attrs[attr];
73+
74+
// This check is for conflicting body attributes in the same file;
75+
// we check across multiple files in caching-html-compiler using the
76+
// attributes on results.bodyAttrs
77+
if (this.results.bodyAttrs.hasOwnProperty(attr) && this.results.bodyAttrs[attr] !== val) {
78+
this.throwCompileError(
79+
`<body> declarations have conflicting values for the '${attr}' attribute.`);
80+
}
81+
82+
this.results.bodyAttrs[attr] = val;
83+
});
84+
}
85+
86+
throwCompileError(message, overrideIndex) {
87+
TemplatingTools.throwCompileError(this.tag, message, overrideIndex);
88+
}
89+
}

packages/vue-ssr/index.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/vue-ssr/npm.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"dependencies": {
3-
"vue": "^2.2.1",
3+
"vue": "2.2.4",
44
"vue-router": "^2.3.0"
55
}
66
}

packages/vue-ssr/package.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,29 @@ Package.describe({
66
documentation: 'README.md'
77
});
88

9+
10+
Package.registerBuildPlugin({
11+
name: "compileStaticHtmlBatch",
12+
use: [
13+
'ecmascript@0.6.3',
14+
'underscore@1.0.10',
15+
'caching-html-compiler@1.1.1',
16+
'templating-tools@1.1.1'
17+
],
18+
sources: [
19+
'compile-html.js'
20+
]
21+
});
22+
923
Package.onUse(function(api) {
1024
api.versionsFrom('1.4.3.1');
11-
api.use('akryum:npm-check@0.0.2');
25+
api.use('akryum:npm-check@0.0.3');
26+
api.use('isobuild:compiler-plugin@1.0.0');
1227
api.use('ecmascript');
13-
api.mainModule('index.js', 'client');
28+
api.mainModule('server/index.js', 'server');
29+
api.export('VueSSR', 'server');
1430
});
31+
32+
Npm.depends({
33+
'vue-server-renderer': '2.2.4',
34+
})

packages/vue-ssr/server/index.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import Vue from 'vue'
4+
import VueServerRenderer from 'vue-server-renderer/build.js'
5+
import { WebApp } from 'meteor/webapp'
6+
7+
VueSSR = {
8+
createApp () {
9+
return new Vue({
10+
template: `<div id="not-the-app" style="font-family: sans-serif;">
11+
<h1>This is not what you expected</h1>
12+
<p>
13+
You need to tell <code>vue-ssr</code> how to create your app by setting the <code>VueSSR.createApp</code> function. It should return a new Vue instance.
14+
</p>
15+
<p>
16+
Here is an example of server-side code:
17+
</p>
18+
<pre style="background: #ddd; padding: 12px; border-radius: 3px; font-family: monospace;">import Vue from 'vue'
19+
import { VueSSR } from 'meteor/akryum:vue-ssr'
20+
21+
function createApp () {
22+
return new Vue({
23+
render: h => h('div', 'Hello world'),
24+
})
25+
}
26+
27+
VueSSR.createApp = createApp</pre>
28+
</div>`,
29+
})
30+
}
31+
}
32+
33+
const renderer = VueServerRenderer.createRenderer()
34+
35+
function writeServerError (res) {
36+
res.writeHead(500)
37+
res.end('Server Error')
38+
}
39+
40+
WebApp.connectHandlers.use((req, res, next) => {
41+
let asyncResult
42+
const result = VueSSR.createApp({ url: req.url })
43+
if (result && typeof result.then === 'function') {
44+
asyncResult = result
45+
} else {
46+
asyncResult = Promise.resolve(result)
47+
}
48+
asyncResult.then(app => {
49+
renderer.renderToString(
50+
app,
51+
(error, html) => {
52+
if (error) {
53+
console.error(error)
54+
writeServerError(res)
55+
return
56+
}
57+
req.dynamicBody = html
58+
next()
59+
},
60+
)
61+
}).catch(e => {
62+
console.error(e)
63+
writeServerError(res)
64+
})
65+
})

0 commit comments

Comments
 (0)