Awesome
hapi-riot
Render Riot Components Server-side in your Hapi.js Web Application.
<small>(supports progressive enhancement on the client so it Works Everywhere All The Time<sup>TM</sup>)</small>
Why?
We love the simplicity of Riot.js.
Riot has the "features" we need and none of the complexity we don't want.
Riot's clean syntax results in components with less code than other
"View Libraries" or "Frameworks" see: http://riotjs.com/compare/
Writing less code means you (the developer/designer) get more done,
have less to maintain and the people using your app
have to download fewer bytes thus saves time/bandwidth.
It's a win-win for everyone.
Why Server-Side Rendering Matters ?
If you render your app on the client/device at least
1% of your people will see a blank page (no app).
The people who won't see your app are your potential users/customers who
for one reason or another don't have the latest device/browser,
don't have the most reliable internet connection
or have dissabled JavaScript in their browser for security reasons.
The Page Loads Faster...
Pages rendered on the server can send the absolute minimum markup to the client. This means the "time to first paint" is always faster than loading a client-side framework and rendering a page on the client. So, your app/site is and feels faster to people.
Why aren't all apps built this way?
Good question! Most developers are lazy. They deny the existence of older browsers/devices as the "minority" convinced that it's "more work" than they need to do.
We are on a quest to change the perception that universal rendering is "more difficult" and help people write code that Works Everywhere All The Time<sup>TM</sup>
Read More
- The 1% figure - https://gds.blog.gov.uk/2013/10/21/how-many-people-are-missing-out-on-javascript-enhancement/
- Why you should support JS disabled: http://www.punkchip.com/why-support-javascript-disabled/
What?
hapi-riot
is a views engine to be used with the Vision Plugin. It provide server-side rendering of Riot Components giving rendered html and attaching scripts to rehydrate tags with javascript if possible.
Note if you are totally new to Hapi.js see: https://github.com/dwyl/learn-hapi
And/or if you are new to Riot.js check out: https://github.com/dwyl/learn-riot
How?
1. Install
npm install hapi vision hapi-riot --save
2. Compile
Right now we have left you to do the compiling of your tags. You can choose to do this as you like but the most simple way is to write the following into your cmd line:
riot path/to/your/views/folder output/file/path.js
For example: riot example/lib/views bundle.js
-> this will compile your
views and the output the results into a file called bundle.js
in the root of
your project. It will be a combination of the contents of all of your .tag
files.
Note for development try adding a watch flag
riot -w example/lib/views bundle.js
to prevent having to compile each time
Right now we expect all tags to be compiled into one file.
3. Set up route to compiled file
hapi-riot
is just a view engine. You'll need to add a route to your server that can handle requests to your
compiled file, made by hapi-riot. Add the following route:
server.route({
method: 'GET',
// this is the same as what you supplied to the view engine in step 4
path: '/your_compiled_file_route.js',
handler: function (request, reply) {
// this is what you specified when compiling in step 2
reply.file(Path.join(__dirname, 'your_compiled_output_file.js'));
}
});
Note above uses inert to serve up static files.
4. Configure Vision Plugin with Hapi-Riot
You can configure the views engine by passing it compileOptions
server.views({ // initialise
engines: {
tag: require('hapi-riot') // file should be .tag
},
relativeTo: __dirname,
path: 'views', // by convention we put our tags in /views dir
compileOptions: { // of this form, see below for meanings
removeCache,
compiledFileRoute, // REQUIRED same as step 3
layoutPath,
layout
}
});
We have added a few features to simplify our own projects but are not meant to be fit for everyone.
compiledFileRoute
REQUIRED default: '/bundle.js'
This is the oath you specified in step 3 to include javascript within your tags. hapi-riot
will then inject a link to your compiled file into your output which makes the
specified methods in your tags available.
removeCache
default: process.env.NODE_ENV === 'production'
While you are developing your app you typically don't want
your views to be cached, however, when you deploy your app
by setting NODE_ENV=production
views will be cached. If for any reason you want to cache your views during development set the value as true;
layoutPath
REQUIRED if layout defined
The place where your layouts are kept.
Note: path does not make use of the relativeTo param given to vision plugin
layout
default: undefined
Can either be set to true
in which case we will look for a layout.html
file or you can specify which ever file you'd like. Can be overridden from reply.view
to have multiple layouts.
Specifying a layout allows you to provide a core html page for your riot content where you can include style sheets, other html content and other base scripts. We will inject the riot content into a place holder <<<RIOT>>>
which much be present in the file.
An example would be
<html>
<head>
<link rel="stylesheet" type="text/css" href="stylesheet.css"/>
<head>
<body>
<<<RIOT>>>
</body>
</html>
5. Use
With vision plugin configured and an index.tag
compiled and place in right directory we should be able to to server side render a page!
<index>
<h1>{opts.title}</h1>
</index>
server.route({
method: 'GET',
path: '/',
handler: (request, reply) => { // render the view:
reply.view('index', { title: 'My Amazing Title!' });
}
});
Examples
Run npm start
to see a basic example.
More/Complete examples are in the /example directory.
If you stuck or need any help just ask!
Lessons Learned & "Gotchas"
1. At compiling step make sure your bundle.js is in .gitignore
Add to your .gitignore
any bundled code in compile step to keep your git history clean.
2. console.log
in your .tag
file ...
(This is fairly obvious, once you think about it)
if you write a console.log('your message')
and render it on the server,
it will log out in your server's stdout
(i.e. your terminal).
3. tag files used as views should be non empty and appropriately wrapped
When you create a new tag add something to it immediately. e.g: views/custom.tag
<custom>
<h1> Content </h1>
</custom>
If you leave a tag empty you will see a strange error when rendering.
To know which tag to mount we perform template.split('>')[0].slice(1)
hence the script mounting your tag on client may be wrong unless you conform to having an appropriate wrapping custom element.
Also good for them to match filename but less important.
4. When writing your layout html make sure to add right place holder
We will be doing a simple find and replace on the characters <<<RIOT>>>
so make sure there are no conflicts and included in the right place. You can obviously add more html above and below if you so choose.
5. If not setting the layout param in compile options make sure you have a layout.html file
If want to use default by using layout: true
make sure you have a file named layout.html
in the layout folder path given
6. Scripts and style stripped out of initial render
Riot.render
removes all js and styles. We will reload the tag instance but initially you may lose any styles created with a <style>
tag or scripts belonging to tag. Issue for styles here .
We therefore recommend to include all static styles into style sheets and link up as normal from you document head.
7. Tag files can't be nested in views folder
When rendering on server we require in all files one level deep from the views folder hence any nesting of files may cause problems. Issue here
8. layoutPath and layout different to top level Vision params
We've mimicked the setup for how other view engines use layouts but were having problems passing them as first order params. Make sure you pass these into compileOptions to avoid bugs especially if you have previously worked with handlebars. Issue here
Questions and Suggestions
We hope you find this module useful! We really want to make the process of server side rendering with progessive enhancement as simple as possible.
If you need something cleared up, have any requests or want to offer any improvements then please create an issue or better yet a PR!
Note We are aware that not all riot/vision features may be supported yet. This module will need a few iterations so please suggest missing features to be implemented as you use it and we can hopefully work together to solve it.