Koa Normalize
koa-normalize
is a koa middleware that
integrates your entire frontend workflow into your app
using normalize.io
so that you don't have to worry about things like:
- Any sort of CLI command or build process
- Dependency installations or management
- Concatenation and source maps as files are individually SPDY pushed
- Deployment distributions
- Asset fingerprinting
Instead of building and serving bundles,
this middleware will serve each file individually in development.
Currently, for JS, these are CommonJS-wrapped files.
This is ideal in development as builds are incremental
without any concatenation step,
and it leverages HTTP caching (ETags) so that page loads in are fast.
For a solution integrated into a framework, look at koala.
Overview
This uses normalize.
In other words, you type your dependencies like:
import emitter from 'component/emitter@1'
@import 'https://github.com/necolas/normalize/3/index.css';
Then just run the server as it is the build process!
All the dependencies will be downloaded automatically and re-served from your server locally.
There's no bower_components/
or vendors/
folders to manage.
There's no npm install
, bower install
, browserify
, gulp
,
grunt
, or
any other command to run except for npm start
to start and/or deploy your server
(except for maybe tar
).
Development
You need to have a .nlzrc
file in your app.
This library also only supports one server per process.
Suppose you have an .nlzrc
file:
{
"entrypoints": ["client/index.js"]
}
Install this middleware somewhere at the top of your app:
app.use(require('koa-normalize')(app))
Now, in every middleware below koa-normalize
,
you'll have a this.entrypoints
property.
It'll look like this:
app.use(require('koa-normalize')())
app.use(function* (next) {
{
"client/index.js": {
urls: [
"/.nlz/require.js",
"/index.js",
"/dependency.js"
],
html: '<script src="/.nlz/require.js"></script>'
+ '<script src="/index.js"></script>'
+ '<script src="/dependency.js"></script>'
+ '<script src="/.nlz/github/component/emitter/1.1.2/index.js"></script>'
+ '<script>require("/dependency.js");</script>'
}
}
})
The urls
are the list of local files used in the build.
This is primarily used for debugging and you shouldn't need to use them
unless you want to do something crazy.
Note that normalize-specific files, including the require()
implementation,
as well as any remote dependencies,
are prefixed with .nlz/
to distinguish files from your app's routes.
Also note that all files are prefixed with /
.
The server assumes that it is served from the root.
This shouldn't conflict with your app's actual routes.
Now, where ever you would typically do <script src="client/index.js"></script>
,
you would instead insert the .html
.
If you look at nlz-build(1)
s equivalent output,
this is essentially how builds are split up.
For example, in your template, you would do something like:
<!DOCTYPE html>
<html>
<head>
{{ this.entrypoints['client/index.css'].html }}
</head>
<body>
<div id="container"></div>
{{ this.entrypoints['client/index.js'].html }}
</body>
</html>
Now, all your scripts are available as separate single files
and you don't have to worry about dependency management.
However, even in development, this is still pretty slow since you
could potentially be doing thousands of HTTP requests.
Thus, koa-normalize
allows you to SPDY push everything.
app.use(function* (next) {
this.normalize.push()
this.normalize.push('client/index.js')
})
Now, in development, your network will look something like this:
To actually use SPDY push,
you have to enable SPDY on your dev server.
If you GET /client/index.js
,
you'll see that koa-normalize
is an HTTP file server as well.
Staging
NOT IMPLEMENTED
During staging, all the bundles will be concatenated,
minimized, gzipped, and fingerprinted.
To enable staging, set NODE_ENV=stage
.
Thus, the script tag would look something like this:
<script src="/client/index.a0f2a05b.js"></script>
As defined in entrypoints['client/index.js'].html
.
Thus, you don't have to do anything between development and staging,
and you can test whether the bundling system works.
Staging is also slow as it gzips all files using node-zopfli and saves them to disk
every time a file changes.
What you'll notice is that your build/
folder would look like this:
build/
client/index.a0f2a05b.js
client/index.a0f2a05b.js.gz
.manifest.json
The build/
folder is a folder of assets that you must include in production.
All these files are created automatically during staging and none are
created during development.
The manifest.json
saves data such as Last-Modified
and ETag
dates.
Read more on how production works.
Production
NOT IMPLEMENTED
In production, the server will simply look for a build/manifest.json
and try to serve assets from that folder and file.
This file must exist, otherwise the process will crash.
Thus, you need to stage before deploying to production.
This is ideal in production as there are no runtime
ETag calculations, gzip compressions, or dependency resolution.
However, files will still be served from disk because
storing all that in memory is a little too ridiculous.
ES6
NOT IMPLEMENTED
This entire build system is a precursor to ES6 module systems.
However, until all supported browsers support ES6 modules,
all the JS will be compiled down to CommonJS.
There are two ways to serve ES6 modules.
The first is as individual files like how development works.
If this is the case, a lot of SPDY pushes would have to occur during
production, but essentially nothing would change
between development and production except caching.
It's the easiest, and it'll be as simple as:
<module>import '/client/index';</module>
The other solution is to load all the modules into bundles ourselves.
This is a little more complicated, especially since
no body even knows how ES6 modules will even work yet,
but will be nicer as it won't necessitate excessive SPDY push calls
during production.
API
app.use(require('koa-normalize')(app, [options]))
Use this middleware somewhere at the top of your app.
There are no options
yet.
this.entrypoints
As shown above, this is a list of entry points you've defined above.
The format is:
this.entrypoints[<name>] = {
urls: [urls...],
html: '<html...>'
}
this.normalize.push([entrypoint])
SPDY push the entrypoint
and all of its dependencies if SPDY is enabled.
If entrypoint
is not defined,
then all entrypoints will be SPDY pushed.
Example
To run an example, clone this repository, install the dependencies, and run the server:
git clone git:
cd koa.js
npm install
npm start
Open up the HTTP server at http://127.0.0.1:3333
and the HTTPS/SPDY server at https://127.0.0.1:3334
.
Note that the SSL certificate is self-signed.
Then, feel free to edit the client assets in the example/
folder.