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.

252 lines
12 KiB
Markdown

# 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](https://github.com/mattdesl/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](http://www.wtfpl.net/txt/copying/) or [CC0](https://creativecommons.org/publicdomain/zero/1.0/), 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](mailto:admin@cryto.net) 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](http://cryto.net/~joepie91/donate.html).
## 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:
```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"],
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:
```js
"use strict";
const express = require("express");
let app = express();
// ... your Express app stuff goes here ...
module.exports = app;
```
Now you simply run:
```sh
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`](https://nodemon.io/) installed:
```sh
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](https://www.npmjs.com/package/browserify#browserifyfiles--opts).
So for example, if we want to add support for CSS modules and nested CSS, we just install [`icssify`](https://www.npmjs.com/package/icssify) and [`postcss-nested`](https://www.npmjs.com/package/postcss-nested), and do this in our `bin/server.js`:
```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:
```sh
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`](https://nodejs.org/api/path.html#path_path_join_paths) with [`__dirname`](https://nodejs.org/api/modules.html#modules_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](https://www.npmjs.com/package/picomatch#globbing-features) - 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](https://www.npmjs.com/package/browserify#browserifyfiles--opts) 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](https://www.html5rocks.com/en/tutorials/developertools/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.
3 years ago
### 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!
3 years ago
### 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
4 years ago
### v1.0.1 (February 16, 2020)
- __Documentation:__ Added license/donation boilerplate to README
### v1.0.0 (February 16, 2020)
Initial release.