@f0x52/budo-express
v1.1.0
Published
A small wrapper to integrate Budo with an Express application
Downloads
5
Readme
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
intopublic/js/bundle.js
- transforms JSX and ES6+ using Babel
- automatically reloads the browser when any
.css
,.html
,.js
, or.svg
files in thepublic/
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
- Filesystem paths, ie. paths of folders/files on your computer
- URL paths, ie. the
/some/path
part inhttps://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
ordevelopmentMode: 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.1.0 (June 8th, 2022)
- Feature: budoExpress now returns the budo/express server or bundler, so you can attach your own event listeners
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.