Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

staticr

bjoerge654MIT4.0.2

Build predefined routes to static files for faster serving

readme

staticr

Build Status js-standard-style

staticr allows you to define a set of routes that can be either served dynamically by express (e.g. during development) or built to static files on disk (e.g. in a deploy task).

See the example directory for a complete, working example project.

Quick example

All you need to do is define your static routes like this:

// browserify.js

var browserify = require("browserify")
var routes = {
  '/js/main.js': function factory() {
    return browserify("./main.js").bundle();
  }
}
module.exports = routes;

Now you can serve this bundle using express:

var serve = require("staticr/serve");
app.use(serve(require("./browserify.js"));

... or you can compile it to a ./public folder from the command line like this.

staticr --out-dir ./public ./browserify.js

The content of the stream returned from the static route function above is now written to ./public/js/main.js

What's so great about this?

  • It allows you to operate with a flexible set of static routes that can be used in different contexts and environments. You have one central place to define how the static route should be generated depending on environment.

  • You don't have to rely on another layer of indirection between your app and the tools that actually compiles the static resources. No more wrappers like grunt-*, gulp-* or *-middleware - you just use the browserify, node-sass, less packages and their apis directly.

    This again means:

    • You can upgrade to the shiny new browserify version the minute it is released and not have to wait for the maintainers of gulp-browserify, grunt-browserify or browserify-middleware to upgrade their versions of browserify.
    • You will never again be disappointed realizing that a super-useful feature of browserify is not exposed by the wrapper library.
    • You get rid of a whole lot of external dependencies that may contain bugs and break your app.

Usage

Defining routes

The routes are defined as key, value pairs where the keys is the route and value is a factory function that returns a string, a readable stream or accepts a callback, e.g:.

var routes = {
  '/some/route.html': function factory() {
    return "<p>This is some content of some route</p>";
  }
}

If the function takes a callback, this callback must be called with either a string or a readable stream whenever the output is available.

For example, if you had to do something async in order to get the content for the above route, you could instead do:

var routes = {
  '/some/route.html': function factory(callback) {
    doSomethingAsync(function(err, result) {
      if (err) {
        return callback(err);
      }
      callback(null, "<p>Got some async result " + result + "</p>");
    });

  }
}

Note: Any value returned from a route function that expects arguments are ignored silently.

Command line API

Usage: staticr [options] <route file(s) ...>

  Options:

      --out-dir, -o <dir> Build all routes defined in route file(s) and output it to the specified
                          directory.

      --route, -r <route> Route(s) to include in build. If left out, all the
                          defined routes will be included.

             --stdout, -s Pipe a route to stdout instead of writing to output dir folder.
                          This option only works for single routes specified with the --route parameter.

     --require, -m <name> Require the given module. Useful for installing require hooks, e.g babel/register

    --exclude, -e <route> Route(s) to exclude from the build. By default, all the
                          defined routes will be included.

Example: Development vs. production

// browserify-bundles.js

var browserify = require("browserify");
var uglify = require('uglify-stream');

// This bundle will be uglified in production.
var routes = module.exports = {
  "js/bundle.js": function() {

    var bundle = browserify("./js/entry.js", {
        debug: process.env.NODE_ENV === 'development'
      })
      .bundle();

    if (process.env.NODE_ENV === 'production') {
      return bundle.pipe(uglify());
    }
    return bundle;
  }
};

// Dev only route
if (process.env.NODE_ENV == 'development') {
  routes['js/dev.js'] = function() {
    return browserify("./js/dev.js", {debug: true}).bundle();
  }
}

Example: routes returning html

// html-routes.js

var jade = require('jade');

// This bundle will be minified in production.
var routes = module.exports = {
  "/": function() {
    return jade.renderFile("./views/index.jade", {title: "Main"});
  },
  "/about": function() {
    return jade.renderFile("./views/about.jade", {title: "About"});
  }
};

Example: Serve bundles dynamically with express

Routes will be regenerated and written to every response for every request. This is usually the behaviour you want during development.

var express = require("express");

var app = express();

if (process.env.NODE_ENV === 'development') {
  var serve = require("staticr/serve");
  app.use(serve(require("./static-routes/sass-bundles")));
  app.use(serve(require("./static-routes/html-routes")));
  app.use(serve(require("./static-routes/browserify-bundles")));
}

Example: Compile routes to target dir

Routes will be generated once and written to target directory. This can be done right before server startup.

NODE_ENV=production staticr --out-dir ./public \
 ./static-routes/sass-bundles.js \
 ./static-routes/html-routes \
 ./static-routes/browserify-bundles.js

Another cool example!

Track down bloat of a browserified route using disc

staticr --stdout --route /main.js browserify-bundles.js | discify --open

changelog

Changelog

4.0.0

[POSSIBLE BREAKING]: Removed support for passing streams to callback

In staticr < 4.0 you could pass a stream to the callback given to the factory function like this:

var routes = {
  "/foo.js": function(callback) {
    var stream = browserify('./foo.js').bundle()
    callback(null, stream)
  }
}

... or return a promise that resolves to a stream

var routes = {
  "/foo.js": function(callback) {
    var stream = browserify('./foo.js').bundle()
    return Promise.resolve(stream)
  }
}

This will no longer work in 4.0.0 and if you relied on this bug, you should update your route factory functions

Async route resolution

Instead of a {route => factoryFunction} map, a route resolver can now be passed to staticr.

E.g.:

module.exports = function resolveRoutes(callback) {
  setTimeout(function() {
    callback(null, {
      '/foo.js': function() {
        return bundle('../foo.js')
      }
    })
  }, 1000)
}

This opens the possibility for using bundlers where the actual routes are not known up-front, or where multiple static routes are built in a single operation, e.g. with webpack

Webpack + React with HMR example added.

See https://github.com/bjoerge/staticr/tree/master/examples

Added a process.staticr global.

This will have the value build if you are in a build task, or serve if routes are served.

Detect if a route factory never resolves:

Staticr now detects if a route factory never resolves to a value. Previously this route would just cause the build to just exit prematurely in silence, with no errors

var routes = {
  "/foo.txt": function(callback) {
    // oops forgot to call callback()
  }
}

Now, instead an unhandled error will be thrown, causing the process to exit with a failure code (!= 0)

Error: Building static routes ended prematurely. This is most likely due to a promise that never gets resolved 
in the route factory. Please examine the factory function for route "/foo.txt":

function (callback) {
  // oops forgot to call callback()
}

This works for Promises that never resolves too!

Bugfix: last route were emitted twice

Entire test suite is reworked and tap was replaced with mocha.

3.1.1

text/html is now default mime type for extension-less routes

This also means that the extension-less route /foo will be compiled to foo/index.html

3.1.0

Use interop-require for babel 6 compat.

Minor logging improvement

3.0.1

Minor readme tweaks

3.0.0

[BREAKING] Specify output directory using --out-dir instead of first command line argument

Improve error handling and add more tests