You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
Sven Slootweg 1e005793e5 1.0.8 3 years ago
operations Fix integration error 3 years ago
validation Initial commit; 1.0.0 4 years ago
.eslintrc Initial commit; 1.0.0 4 years ago
.gitignore Initial commit; 1.0.0 4 years ago
README.md Fix integration error 3 years ago
index.js Initial commit; 1.0.0 4 years ago
package.json 1.0.8 3 years ago
yarn.lock Use inject-lr-script instead of custom monkypatching for livereload injection 3 years ago

README.md

budo-express - Auto-reload superpowers for Express!

Get client-side code bundling and automagical live-reloading working in your Express app, in 5 minutes!

This library integrates a Budo development server into your Express application. It uses Browserify for bundling, and LiveReload for page reloading. It combines really well with nodemon, for auto-restarting your server process too!

Because this package doesn't rely on any generators or boilerplates, you can easily integrate it into your existing Express application.

What about Webpack?

This library uses Browserify, which is a bundler just like Webpack - however, Browserify is much simpler to understand and configure. It can do the same things as Webpack can, and you won't need Webpack when using Browserify. It's actually been around for longer than Webpack!

License, donations, and other boilerplate

Licensed under either the WTFPL or CC0, at your choice. In practice, that means it's more or less public domain, and you can do whatever you want with it. Giving credit is not required, but still very much appreciated! I'd love to hear from you if this module was useful to you.

Creating and maintaining open-source modules is a lot of work. A donation is also not required, but much appreciated! You can donate here.

Usage

Because budo-express is meant to integrate into your own Express app, it's exposed as a library rather than a command-line utility.

You simply start by creating your own "binary", something like bin/server.js, and then call budo-express from there, handing it your Express app as an argument:

"use strict";

const budoExpress = require("budo-express");
const path = require("path");

budoExpress({
    port: 3000,
    expressApp: require("../src/server/app"),
    basePath: path.join(__dirname, ".."),
    entryFiles: "src/client/index.jsx",
    staticPath: "public",
    bundlePath: "js/bundle.js",
    livereloadPattern: "**/*.{css,html,js,svg}",
    browserify: {
        extensions: [".jsx"],
        transform: [
            ["babelify", {
                presets: ["@babel/preset-env", "@babel/preset-react"],
            }]
        ]
    }
});

(Don't worry about all those options for now - they're all explained below!)

And in your src/server/app.js -- note that you never call app.listen, and you export your app instead:

"use strict";

const express = require("express");

let app = express();

// ... your Express app stuff goes here ...

module.exports = app;

Now you simply run:

NODE_ENV=development node bin/server.js

... and that's it! You now have a fully-functioning development server, that:

  • bundles src/client/index.jsx into public/js/bundle.js
  • transforms JSX and ES6+ using Babel
  • automatically reloads the browser when any .css, .html, .js, or .svg files in the public/ folder change.

There's only one thing missing; your server process won't auto-restart when your server code changes. That's easy to fix, when you have nodemon installed:

NODE_ENV=development nodemon bin/server.js

(Note how we've just changed node to nodemon, and that's it!)

More complex bundling configurations (eg. CSS modules)

The browserify option accepts any kind of valid Browserify configuration.

So for example, if we want to add support for CSS modules and nested CSS, we just install icssify and postcss-nested, and do this in our bin/server.js:

"use strict";

const budoExpress = require("budo-express");
const path = require("path");

budoExpress({
    port: 3000,
    expressApp: require("../src/server/app"),
    basePath: path.join(__dirname, ".."),
    entryFiles: "src/client/index.jsx",
    staticPath: "public",
    bundlePath: "js/bundle.js",
    livereloadPattern: "**/*.{css,html,js,svg}",
    browserify: {
        extensions: [".jsx"],
        plugin: [
            ["icssify", {
                before: [ require("postcss-nested")() ]
            }]
        ],
        transform: [
            ["babelify", {
                presets: ["@babel/preset-env", "@babel/preset-react"],
            }]
        ]
    }
});

Note the added plugin list with icssify; that's the only difference.

Running in production

Of course, we don't want to run a development server when we're really deploying our app. And when we're running it in production, we need a 'real' generated bundle, not just one that the development server generates on-the-fly.

There's only two steps needed to run your app in production. Step one is to generate the bundle:

BUDO_BUILD=1 node bin/server.js

Note how we're just running the same 'binary' again, but with a different environment variable. This tells budo-express that we want to generate a bundle instead. This doesn't require any extra configuration; it'll use the configuration we've specified in bin/server.js!

The second step is to actually run your application, but without the development server sitting in front of it:

node bin/server.js

That's it! We just leave off the environment variables, and it will default to production mode. Nothing will be auto-reloaded, budo-express will get out of the way, and all you're left with is your own Express application.

About all those options...

In the example above, there were quite a few options - especially path-related options. This is because we're actually dealing with two different kinds of paths

  1. Filesystem paths, ie. paths of folders/files on your computer
  2. URL paths, ie. the /some/path part in https://example.com/some/path.

... and we need to deal with a number of different files; the input files, the output bundle, static files, and so on.

Here's a rundown of what all the path-related options mean:

basePath: This is the filesystem path for your "project root", ie. where your package.json is. It must be an absolute path. The easiest way to generate this is to use path.join with __dirname and the right amount of ../.. entries.

For example, in the example above, our bin/server.js is in bin, which is one folder deeper than the root of the project; so we need to combine it with a single .. to get the project root.

entryFiles: These are the files - relative to the basePath (project root) - that the bundler should start with; basically, the files that contain the code that should be executed right when the bundle loads. The bundler will then follow all the require(...) statements starting from those files, collecting everything together. Note that this can be either a single path, or an array of them.

staticPath: This is the filesystem path - again, relative to the basePath - where your static files (CSS stylesheets, images...) are stored.

bundlePath: This is the filesystem path - relative to both staticPath and the URL root of your static files (see the staticPrefix option below) - where your bundle should be saved. So if staticPath is "public/", and bundlePath is "js/bundle.js", the final path for your generated bundle will be "public/js/bundle.js" (relative to the project root).

livereloadPattern: This is the glob pattern - relative to staticPath - that defines which files should be live-reloaded when they changed. Anything in the static files folder that matches this pattern, will be reloaded; anything that does not, will not.

And then there's a bunch of required non-path options:

port: The port that your server should run on, both in development and production mode.

expressApp: The actual Express application that the development server should be integrated into (or, in production mode, the application that should be run).

browserify: The Browserify configuration to use for bundling.

Optional extras

Finally, there's a handful of optional options:

staticPrefix: The URL path, relative to the root of your domain/hostname, where your static file folder is publicly exposed. If you're exposing it at the root (the default result of doing app.use(express.static(...)) without specifying a middleware prefix), leave this undefined.

host: The host to listen on in production mode. Defaults to all network interfaces ("::").

allowUnsafeHost: By default, in development mode, the development server will only listen on localhost, so that noone can access it from outside your computer; this is for security reasons. If you want to disable this and use the host specified in host instead, you can set this option to true -- however, only do this if you fully understand the risks!

sourceMaps: Whether to enable Browserify's sourcemaps in development mode. Defaults to true.

developmentMode: Whether to run in development mode (true), production mode (false), or auto-detect it based on the NODE_ENV environment variable like in the examples ("auto"). Defaults to auto-detection.

middleware: Custom Connect/Express-style middleware to run in development mode only, before a request reaches your application. Don't use this for normal application middleware! You should almost never need this option.

livereloadPort: The port that the LiveReload server should listen on. Changing this will almost certainly break any LiveReload browser extensions. Defaults to 35729.

stream: A Writable stream to log ndjson output from Budo to. This defaults to process.stdout (ie. your terminal).

Altogether, the various paths are composed like this:

Path Calculated as
Static files filesystem path basePath + staticPath
Static files URL path root of your domain + staticPrefix
Bundle filesystem path basePath + staticPath + bundlePath
Bundle URL path root of your domain + staticPrefix + bundlePath
Pattern for livereloaded file matching basePath + staticPath + livereloadPattern
Entry files filesystem path basePath + entryFiles

API

expressBudo(options)

Depending on whether BUDO_BUILD=1 is specified:

  • If yes, starts an instance of your Express application; with, if development mode is enabled (NODE_ENV=development or developmentMode: true), a development server integrated into it.
  • If no, generates and saves a bundle based on your configuration.

Arguments:

  • options: The options, as documented above.

Changelog

v1.0.8 (May 8, 2020)

  • Patch: The previous release included a small error that broke Express integration; this is now fixed.

v1.0.7 (May 8, 2020)

  • Patch: Now uses inject-lr for LiveReload script injection instead of a custom implementation, to make it more robust in edge cases.
  • Patch: Budo-served resources now bypass the Express app entirely, and therefore it should now be possible to have a catch-all 404 handler in your Express app without breaking express-budo.
  • Patch: Now waits with sending out a LiveReload event for a bit, so that when budo-express is used with an external restarter such as Nodemon, there's no longer a race condition between the browser reloading and the server process having restarted (which would result in an error page). Currently uses a fixed timeout, which is not ideal; if you're still seeing this problem, please file an issue!

v1.0.6 (December 7, 2020)

  • Patch: No longer crashes when headers have already been sent; though in some cases this may break auto-reloading. If you can find a better solution for livereload injection, please file a PR!

v1.0.4 (October 23, 2020)

  • Patch: No longer breaks in development mode when a string is sent as the response (rather than a Buffer).
  • Patch: No longer breaks in development mode when a Content-Type header is omitted entirely.

v1.0.3 (October 4, 2020)

  • Patch: Fixed monkeypatching logic in development mode (for livereload tag injection) to also work on newer Node versions.

v1.0.2 (February 17, 2020)

  • Misc: Fixed repository URL in package.json

v1.0.1 (February 16, 2020)

  • Documentation: Added license/donation boilerplate to README

v1.0.0 (February 16, 2020)

Initial release.