Unverified Commit 8cbfa5c3 authored by Renée Kooi's avatar Renée Kooi Committed by GitHub
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
parent a9cf41fa
......@@ -221,6 +221,26 @@ JavaScript, no extra configuration is needed.
}
```
By default, Bankai starts with an empty HTML document, injecting the tags
mentioned [above](#html). You can also create a custom template as `index.html`,
and Bankai will inject tags into it instead.
```js
// app.js
...
module.exports = app.mount('#app')
```
```html
<!-- index.html -->
...
<body>
<div id="app"></div>
<div id="footer">© 2018</div>
</body>
...
```
## HTTP
Bankai can be hooked up directly to an HTTP server, which is useful when
working on full stack code.
......
......@@ -2,6 +2,7 @@ var waterfall = require('async-collection/waterfall')
var mapLimit = require('async-collection/map-limit')
var explain = require('explain-error')
var concat = require('concat-stream')
var resolve = require('resolve')
var crypto = require('crypto')
var pump = require('pump')
var path = require('path')
......@@ -70,9 +71,23 @@ function node (state, createEdge) {
}
}
function getTemplate (content, done) {
// TODO maybe change this depending on the `content.route`, so you can have different templates for different routes?
var name = 'index'
var dir = path.join(path.dirname(entry), name)
resolve('.', { basedir: dir, extensions: ['.html'] }, function (err, filename) {
if (err) {
done(dir, head(content.language))
} else {
// Only return the filename, documentify will stream it in.
done(filename, null)
}
})
}
function documentifyApp (content, done) {
var base = state.metadata.opts.base
var language = content.language
var route = content.route
var title = content.title
var body = content.body
......@@ -80,50 +95,53 @@ function node (state, createEdge) {
var hasDynamicScripts = state.scripts.bundle.dynamicBundles.length > 0
var html = head(language)
var d = documentify(entry, html)
var header = [
viewportTag(),
scriptTag({ hash: state.scripts.bundle.hash, base: base }),
hasDynamicScripts && dynamicScriptsTag({
bundleNames: state.scripts.bundle.dynamicBundles,
scripts: state.scripts,
base: base
}),
preloadTag(),
loadFontsTag({ fonts: fonts, base: base }),
manifestTag({ base: base }),
descriptionTag({ description: state.manifest.bundle.description }),
themeColorTag({ color: state.manifest.bundle.color }),
titleTag({ title: title })
].filter(Boolean)
// TODO: twitter
// TODO: facebook
// TODO: apple touch icons
// TODO: favicons
if (state.metadata.reload) {
header.push(reloadTag({ bundle: state.reload.bundle.buffer }))
}
getTemplate(content, ontemplate)
function ontemplate (filename, html) {
var d = documentify(filename, html)
var header = [
viewportTag(),
scriptTag({ hash: state.scripts.bundle.hash, base: base }),
hasDynamicScripts && dynamicScriptsTag({
bundleNames: state.scripts.bundle.dynamicBundles,
scripts: state.scripts,
base: base
}),
preloadTag(),
loadFontsTag({ fonts: fonts, base: base }),
manifestTag({ base: base }),
descriptionTag({ description: state.manifest.bundle.description }),
themeColorTag({ color: state.manifest.bundle.color }),
titleTag({ title: title })
].filter(Boolean)
// TODO: twitter
// TODO: facebook
// TODO: apple touch icons
// TODO: favicons
if (state.metadata.reload) {
header.push(reloadTag({ bundle: state.reload.bundle.buffer }))
}
d.transform(addToHead, header.join(''))
if (state.styles.bundle.buffer.length) {
d.transform(criticalTransform, { css: state.styles.bundle.buffer })
}
d.transform(addToHead, styleTag({ hash: state.styles.bundle.hash, base: base }))
d.transform(insertApp, {
selector: selector,
body: body
})
d.transform(addToHead, header.join(''))
function complete (buf) { done(null, buf) }
if (state.styles.bundle.buffer.length) {
d.transform(criticalTransform, { css: state.styles.bundle.buffer })
pump(d.bundle(), concat({ encoding: 'buffer' }, complete), function (err) {
if (err) return done(explain(err, 'Error in documentify while operating on ' + route))
})
}
d.transform(addToHead, styleTag({ hash: state.styles.bundle.hash, base: base }))
d.transform(insertApp, {
selector: selector,
body: body
})
function complete (buf) { done(null, buf) }
pump(d.bundle(), concat({ encoding: 'buffer' }, complete), function (err) {
if (err) return done(explain(err, 'Error in documentify while operating on ' + route))
})
}
}
......
......@@ -30,7 +30,7 @@ module.exports = class ServerRender {
function send (err, res) {
if (err) return done(err)
done(null, Object.assign(self.DEFAULT_RESPONSE, res))
done(null, Object.assign({ route: route }, self.DEFAULT_RESPONSE, res))
}
}
......
......@@ -143,6 +143,8 @@ tape('server render choo apps', function (assert) {
})
tape('server render choo apps with root set', function (assert) {
assert.on('end', cleanup)
var expected = `
<!DOCTYPE html>
<html lang="en-US" dir="ltr">
......@@ -209,3 +211,81 @@ tape('server render choo apps with root set', function (assert) {
})
})
})
tape('custom index.html template', function (assert) {
assert.on('end', cleanup)
assert.plan(3)
var template = `
<html>
<head>
<meta name="test" content="ok">
</head>
<body>
</body>
</html>
`
var file = `
var html = require('choo/html')
var choo = require('choo')
var app = choo()
app.route('/', function () {
return html\`<body>meow</body>\`
})
module.exports = app.mount('body')
`
var dirname = 'document-pipeline-' + (Math.random() * 1e4).toFixed()
tmpDirname = path.join(__dirname, '../tmp', dirname)
mkdirp.sync(tmpDirname)
fs.writeFileSync(path.join(tmpDirname, 'index.js'), file)
fs.writeFileSync(path.join(tmpDirname, 'index.html'), template)
var compiler = bankai(tmpDirname, { watch: false })
compiler.documents('/', function (err, res) {
assert.error(err, 'no error writing document')
var body = res.buffer.toString('utf8')
assert.notEqual(body.indexOf('<meta name="test" content="ok">'), -1, 'used the custom index.html')
assert.notEqual(body.indexOf('meow'), -1, 'inserted the rendered app')
})
})
tape('mount choo app into given selector', function (assert) {
assert.on('end', cleanup)
assert.plan(3)
var template = `
<html>
<head></head>
<body>
<h1>Some Title!</h1>
<div id="app"></div>
</body>
</html>
`
var file = `
var html = require('choo/html')
var choo = require('choo')
var app = choo()
app.route('/', function () {
return html\`<div>meow</div>\`
})
module.exports = app.mount('#app')
`
var dirname = 'document-pipeline-' + (Math.random() * 1e4).toFixed()
tmpDirname = path.join(__dirname, '../tmp', dirname)
mkdirp.sync(tmpDirname)
fs.writeFileSync(path.join(tmpDirname, 'index.js'), file)
fs.writeFileSync(path.join(tmpDirname, 'index.html'), template)
var compiler = bankai(tmpDirname, { watch: false })
compiler.documents('/', function (err, res) {
assert.error(err, 'no error writing document')
var body = res.buffer.toString('utf8')
assert.notEqual(body.indexOf('<h1>Some Title!</h1>'), -1, 'preserved body contents outside #app selector')
assert.notEqual(body.indexOf('meow'), -1, 'inserted the rendered app')
})
})
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment