Skip to content

Commit 8cbfa5c

Browse files
Read custom html templates (#447)
* Read custom index.html, fixes #343 * Set .route on server rendered content by default. * tests for custom templates and mounting into custom selector * remove $route.html templating for now; only use index.html. * document custom index.html
1 parent a9cf41f commit 8cbfa5c

File tree

4 files changed

+161
-43
lines changed

4 files changed

+161
-43
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,26 @@ JavaScript, no extra configuration is needed.
221221
}
222222
```
223223

224+
By default, Bankai starts with an empty HTML document, injecting the tags
225+
mentioned [above](#html). You can also create a custom template as `index.html`,
226+
and Bankai will inject tags into it instead.
227+
228+
```js
229+
// app.js
230+
...
231+
module.exports = app.mount('#app')
232+
```
233+
234+
```html
235+
<!-- index.html -->
236+
...
237+
<body>
238+
<div id="app"></div>
239+
<div id="footer">© 2018</div>
240+
</body>
241+
...
242+
```
243+
224244
## HTTP
225245
Bankai can be hooked up directly to an HTTP server, which is useful when
226246
working on full stack code.

lib/graph-document.js

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ var waterfall = require('async-collection/waterfall')
22
var mapLimit = require('async-collection/map-limit')
33
var explain = require('explain-error')
44
var concat = require('concat-stream')
5+
var resolve = require('resolve')
56
var crypto = require('crypto')
67
var pump = require('pump')
78
var path = require('path')
@@ -70,60 +71,77 @@ function node (state, createEdge) {
7071
}
7172
}
7273

74+
function getTemplate (content, done) {
75+
// TODO maybe change this depending on the `content.route`, so you can have different templates for different routes?
76+
var name = 'index'
77+
78+
var dir = path.join(path.dirname(entry), name)
79+
resolve('.', { basedir: dir, extensions: ['.html'] }, function (err, filename) {
80+
if (err) {
81+
done(dir, head(content.language))
82+
} else {
83+
// Only return the filename, documentify will stream it in.
84+
done(filename, null)
85+
}
86+
})
87+
}
88+
7389
function documentifyApp (content, done) {
7490
var base = state.metadata.opts.base
75-
var language = content.language
7691
var route = content.route
7792
var title = content.title
7893
var body = content.body
7994
var selector = content.selector
8095

8196
var hasDynamicScripts = state.scripts.bundle.dynamicBundles.length > 0
8297

83-
var html = head(language)
84-
var d = documentify(entry, html)
85-
var header = [
86-
viewportTag(),
87-
scriptTag({ hash: state.scripts.bundle.hash, base: base }),
88-
hasDynamicScripts && dynamicScriptsTag({
89-
bundleNames: state.scripts.bundle.dynamicBundles,
90-
scripts: state.scripts,
91-
base: base
92-
}),
93-
preloadTag(),
94-
loadFontsTag({ fonts: fonts, base: base }),
95-
manifestTag({ base: base }),
96-
descriptionTag({ description: state.manifest.bundle.description }),
97-
themeColorTag({ color: state.manifest.bundle.color }),
98-
titleTag({ title: title })
99-
].filter(Boolean)
100-
// TODO: twitter
101-
// TODO: facebook
102-
// TODO: apple touch icons
103-
// TODO: favicons
104-
105-
if (state.metadata.reload) {
106-
header.push(reloadTag({ bundle: state.reload.bundle.buffer }))
107-
}
98+
getTemplate(content, ontemplate)
99+
100+
function ontemplate (filename, html) {
101+
var d = documentify(filename, html)
102+
var header = [
103+
viewportTag(),
104+
scriptTag({ hash: state.scripts.bundle.hash, base: base }),
105+
hasDynamicScripts && dynamicScriptsTag({
106+
bundleNames: state.scripts.bundle.dynamicBundles,
107+
scripts: state.scripts,
108+
base: base
109+
}),
110+
preloadTag(),
111+
loadFontsTag({ fonts: fonts, base: base }),
112+
manifestTag({ base: base }),
113+
descriptionTag({ description: state.manifest.bundle.description }),
114+
themeColorTag({ color: state.manifest.bundle.color }),
115+
titleTag({ title: title })
116+
].filter(Boolean)
117+
// TODO: twitter
118+
// TODO: facebook
119+
// TODO: apple touch icons
120+
// TODO: favicons
121+
122+
if (state.metadata.reload) {
123+
header.push(reloadTag({ bundle: state.reload.bundle.buffer }))
124+
}
125+
126+
d.transform(addToHead, header.join(''))
127+
128+
if (state.styles.bundle.buffer.length) {
129+
d.transform(criticalTransform, { css: state.styles.bundle.buffer })
130+
}
131+
132+
d.transform(addToHead, styleTag({ hash: state.styles.bundle.hash, base: base }))
133+
134+
d.transform(insertApp, {
135+
selector: selector,
136+
body: body
137+
})
108138

109-
d.transform(addToHead, header.join(''))
139+
function complete (buf) { done(null, buf) }
110140

111-
if (state.styles.bundle.buffer.length) {
112-
d.transform(criticalTransform, { css: state.styles.bundle.buffer })
141+
pump(d.bundle(), concat({ encoding: 'buffer' }, complete), function (err) {
142+
if (err) return done(explain(err, 'Error in documentify while operating on ' + route))
143+
})
113144
}
114-
115-
d.transform(addToHead, styleTag({ hash: state.styles.bundle.hash, base: base }))
116-
117-
d.transform(insertApp, {
118-
selector: selector,
119-
body: body
120-
})
121-
122-
function complete (buf) { done(null, buf) }
123-
124-
pump(d.bundle(), concat({ encoding: 'buffer' }, complete), function (err) {
125-
if (err) return done(explain(err, 'Error in documentify while operating on ' + route))
126-
})
127145
}
128146
}
129147

ssr/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ module.exports = class ServerRender {
3030

3131
function send (err, res) {
3232
if (err) return done(err)
33-
done(null, Object.assign(self.DEFAULT_RESPONSE, res))
33+
done(null, Object.assign({ route: route }, self.DEFAULT_RESPONSE, res))
3434
}
3535
}
3636

test/document.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ tape('server render choo apps', function (assert) {
143143
})
144144

145145
tape('server render choo apps with root set', function (assert) {
146+
assert.on('end', cleanup)
147+
146148
var expected = `
147149
<!DOCTYPE html>
148150
<html lang="en-US" dir="ltr">
@@ -209,3 +211,81 @@ tape('server render choo apps with root set', function (assert) {
209211
})
210212
})
211213
})
214+
215+
tape('custom index.html template', function (assert) {
216+
assert.on('end', cleanup)
217+
assert.plan(3)
218+
219+
var template = `
220+
<html>
221+
<head>
222+
<meta name="test" content="ok">
223+
</head>
224+
<body>
225+
</body>
226+
</html>
227+
`
228+
var file = `
229+
var html = require('choo/html')
230+
var choo = require('choo')
231+
232+
var app = choo()
233+
app.route('/', function () {
234+
return html\`<body>meow</body>\`
235+
})
236+
module.exports = app.mount('body')
237+
`
238+
239+
var dirname = 'document-pipeline-' + (Math.random() * 1e4).toFixed()
240+
tmpDirname = path.join(__dirname, '../tmp', dirname)
241+
mkdirp.sync(tmpDirname)
242+
fs.writeFileSync(path.join(tmpDirname, 'index.js'), file)
243+
fs.writeFileSync(path.join(tmpDirname, 'index.html'), template)
244+
245+
var compiler = bankai(tmpDirname, { watch: false })
246+
compiler.documents('/', function (err, res) {
247+
assert.error(err, 'no error writing document')
248+
var body = res.buffer.toString('utf8')
249+
assert.notEqual(body.indexOf('<meta name="test" content="ok">'), -1, 'used the custom index.html')
250+
assert.notEqual(body.indexOf('meow'), -1, 'inserted the rendered app')
251+
})
252+
})
253+
254+
tape('mount choo app into given selector', function (assert) {
255+
assert.on('end', cleanup)
256+
assert.plan(3)
257+
258+
var template = `
259+
<html>
260+
<head></head>
261+
<body>
262+
<h1>Some Title!</h1>
263+
<div id="app"></div>
264+
</body>
265+
</html>
266+
`
267+
var file = `
268+
var html = require('choo/html')
269+
var choo = require('choo')
270+
271+
var app = choo()
272+
app.route('/', function () {
273+
return html\`<div>meow</div>\`
274+
})
275+
module.exports = app.mount('#app')
276+
`
277+
278+
var dirname = 'document-pipeline-' + (Math.random() * 1e4).toFixed()
279+
tmpDirname = path.join(__dirname, '../tmp', dirname)
280+
mkdirp.sync(tmpDirname)
281+
fs.writeFileSync(path.join(tmpDirname, 'index.js'), file)
282+
fs.writeFileSync(path.join(tmpDirname, 'index.html'), template)
283+
284+
var compiler = bankai(tmpDirname, { watch: false })
285+
compiler.documents('/', function (err, res) {
286+
assert.error(err, 'no error writing document')
287+
var body = res.buffer.toString('utf8')
288+
assert.notEqual(body.indexOf('<h1>Some Title!</h1>'), -1, 'preserved body contents outside #app selector')
289+
assert.notEqual(body.indexOf('meow'), -1, 'inserted the rendered app')
290+
})
291+
})

0 commit comments

Comments
 (0)