koa-normalize

0.2.2 • Public • Published

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) {
  /* this.entrypoints === */ {
    "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"><!-- all your html --></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) {
  // SPDY push everything
  this.normalize.push()
 
  // SPDY push only a specific file and its dependencies
  this.normalize.push('client/index.js')
})

Now, in development, your network will look something like this:

Chrome Dev Tools

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://github.com/normalize/koa.js
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.

Readme

Keywords

none

Package Sidebar

Install

npm i koa-normalize

Weekly Downloads

8

Version

0.2.2

License

MIT

Last publish

Collaborators

  • jongleberry
  • swatinem