@fizker/serve-prepare

Tool for preparing a site for being hosted by @fizker/serve

Usage no npm install needed!

<script type="module">
  import fizkerServePrepare from 'https://cdn.skypack.dev/@fizker/serve-prepare';
</script>

README

@fizker/serve-prepare

Tool for preparing a site for being statically hosted by @fizker/serve.

Like @fizker/serve, this is meant to be used in conjunction with Docker, and feature decisions are meant to utilize the cache mechanisms that Docker provides.

How to use

The optimal way to use it is to make use of multi-stage builds. The build needs at least three stages:

  1. Build stage. This is where the project does its own stuff, like webpack, content preparation and minification.
  2. Preparation stage. This is where @fizker/serve-prepare runs.
  3. Serve stage. This is the final stage that is sent to the server farm. It contains only @fizker/serve and the static files.

To start the build, run docker build -t <your docker user>/<your project name> ..

To start the server, see the documentation for @fizker/serve for details.

Optimizing for development

Per default, all files are also compressed. This is good for production, but takes longer and thus makes the turn-around time worse during development. For this reason, compression can be disabled.

There are two ways to do this:

  1. Add --skip-compression to npx serve-prepare build command
  2. Set SERVE_SKIP_COMPRESSION=true env variable before running npx serve-prepare build command.

The sample Dockerfile sets up the env variable by using a Docker build-arg. Then to skip compression, run Docker with the --build-arg skip_compression=true flag like so: docker build --build-arg skip_compression=true -t <your docker user>/<your project name> ..

For a way to listen to changes instead of a full rebuild, see the “Listening to changes during development” section

Sample .dockerignore file

See the sample .dockerignore file for a full example.

Running npx flow serve-prepare create-dockerfile will create a sample .dockerignore and Dockerfile in the current directory.

The dockerignore should include everything that is not necessary for building, since that makes the docker build faster and utilizes the docker cache better.

Common entries includes:

  • .git - There is a lot of data here, and stuff like branches and tags should not affect the docker build.
  • node_modules - Modules might need to be compiled for the platform, so it is better to include the package.json and package-lock.json and do npm install than to copy the local node_modules. The docker caching will ensure that npm install is not rerun until either package*.json file changes.
  • tests - The docker image is for deployment. Tests are not needed.
  • build artifacts - The docker image will regenerate the build artifacts anyway.

Sample Dockerfile

See the sample Dockerfile file for a full example.

Running npx flow serve-prepare create-dockerfile will create a sample .dockerignore and Dockerfile in the current directory.

The sample file includes comments explaining the parts. Some parts might be different depending on the specific project. These are marked with ###.

The following are of particular note:

  • There is an expectation that the raw files are created by running npx webpack. This should be changed if necessary.
  • There is an expectation that the raw files are placed in a folder called /static. This should be changed as necessary.
  • If private NPM repositories are in play, add authentication to an .npmrc file and copy that in before running npm install.
  • HTTPS/HTTP2 support is done by copying certificates into the container. This can be done at build-time, but the recommended way is to copy them in using volumes, as this allows the image to be reused in different setups and different URLs. After the files have been copied in, two environment vars, HTTPS_KEY and HTTPS_CERT, should be set with the internal path to the files. Whether it works or not, @fizker/serve will log appropriately.
  • The server will use ports 80 and 443 internally. This makes it convenient to know which ports to rebind. If desired, the ports can be overridden by changing the PORT env for HTTP and the HTTPS_PORT for HTTPS.

sample serve-setup-request.json

See the sample serve-setup-request file for a full example.

Running npx flow serve-prepare create-setup-request will create a sample setup-request file in the current directory.

This file contains any settings that needs to be considered for serving the content later. It contains the following segments:

  • catchAllFile - The file that will be served if a client requests a path that does not match an existing file. The file must exist among the files to host. This can be left as null, in which case a standard 404 message is returned for unknown files. See the next segment for how to format this object.
  • files - A list of overrides for files. If any of these refer to a non-existing file, it is simply ignored. See the next segment for how to format this object.
  • aliases - A list of aliases to resolve. This allows the same physical file to be served for multiple paths. An Alias object must conform to the following JSON structure: { "from": "/path/to/alias", "to": "/path/to/file/or/alias" }.
  • globalHeaders - A string-string object of headers to add to all outgoing requests, unless overridden by the specific path.

The File object used by the files or catchAllFile segments must adhere to the following JSON structure:

{
    "path": "/path/to/file",
    "mime": "plain/text",
    "statusCode": 200,
    "headers": {
        "header": "value"
    }
}
  • path - This is the only required property. It must start with /. It should not be URI encoded.
  • mime - The mime-type to return for this file. If omitted, an attempt will be made to auto-detect this from the extension of the file. If that fails, the docker build command will fail.
  • statusCode - The HTTP status code to return for the file. If omitted, it will default to 200. This is mostly useful for custom 404 errors.
  • headers - A string-string object of headers to add for the file. Entries here will override any matching values from the globalHeaders. This allows easily setting default headers and override on individual files, like cache headers for everything but the index.html file.

Listening to changes during development

Instead of rebuilding the image from scratch after every change, it is more convenient and efficient to build incremental changes, especially for big webpack builds.

To achieve this, a dev Docker image can be used to great effect. Please note that this is not nearly as performant as the production build, so it should only be used for development.

Instead of building a Docker image with the local files, it is easier to simply use the fizker/serve-prepare docker image directly.

If HTTPS is required, it functions the exact same way as for the regular Dockerfile; simply copy in the key and certificate, and set up the env vars for where the files are located.

To run it, two steps are required:

First, run the build tool for your project

If webpack is used, this is most likely npx webpack --watch. The build tool will then update the build output whenever files are changed, and the development server will serve any files as they are.

This step is first, since the server will not start if the folder containing the build output is missing.

Second, start the dev server

docker run --rm \
    --publish 8080:80 \
    -v "$(pwd)"/local/serve-setup-request.json:/root/serve-setup-request.json:ro \
    --mount type=bind,source="$(pwd)"/static,destination=/root/target,consistency=cached \
    fizker/serve-prepare

We bind the port to whatever we prefer.

The --rm flag means that the container is automatically deleted when the server stops. Since the server contains no data of interest, keeping it around just makes it harder to start with the same command, and leaves unnecessary house-keeping.

This commands starts the server in attached mode and it closes with ctrl-c. If detached-mode is preferred, add --detach (or -d for short) flag and consider adding a --name <name> for easier reference. In detached mode, the server is stopped with docker stop <name> or docker stop <sha>, if no --name was given.

The -v simply links to the static serve-setup-request.json file that is also used by the production build.

The magic lies in the --mount part. It creates a link between the folder on the host machine that contains the build output and the container hosting the files. If the build output is not located in $pwd/static from where the command is being run, alter the source= parameter accordingly.

To include HTTPS, do the following command instead:

docker run --rm \
    --publish 8080:80 \
    -v "$(pwd)"/local/serve-setup-request.json:/root/serve-setup-request.json:ro \
    --mount type=bind,source="$(pwd)"/static,destination=/root/target,consistency=cached \
    --publish 80443:443 \
    -v "$(pwd)"/local/key.pem:/root/key.pem:ro --env HTTPS_KEY=/root/key.pem \
    -v "$(pwd)"/local/cert.pem:/root/cert.pem:ro --env HTTPS_CERT=/root/cert.pem \
    fizker/serve-prepare

The only difference is the additional port bind and the two lines mounting and then referencing key.pem and cert.pem. The first path, prefixed with $(pwd)/local/, refers to the local path; update this as necessary. The second part is not important, it just have to match the value for the env-variable.