Skip to content
This repository was archived by the owner on Sep 7, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Everything the image builds for itself, plus anything that would only
# invalidate the build cache. node_modules is the big one: it is installed from
# package-lock.json inside the image and must not be copied in from a host with
# a different platform.
node_modules
dist
.git
.github
.claude
*.log
.DS_Store
Dockerfile
.dockerignore
53 changes: 53 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# syntax=docker/dockerfile:1

# Two ways to run this repository in a container:
#
# docker build -t webpack-analyse . # static site behind nginx
# docker build -t webpack-analyse-dev --target dev . # webpack dev server
#
# See the "Docker" section of README.md for the run commands.

# Pinned to the version .nvmrc and `engines.node` ask for. `engine-strict` in
# .npmrc aborts the install on any other one, so this cannot drift silently.
ARG NODE_VERSION=24.20.0
ARG NGINX_VERSION=1.29-alpine

FROM node:${NODE_VERSION}-alpine AS deps
WORKDIR /app
# Only the manifests, so a source edit does not invalidate the install layer.
# .npmrc has to come along: it carries the engine-strict flag the install is
# expected to run under.
COPY package.json package-lock.json .npmrc ./
# `npm ci` rather than `npm install`: it fails on a package-lock.json that does
# not match package.json instead of quietly rewriting it, which is what CI does.
RUN npm ci

# Development: the webpack dev server, for editing the app with the source
# bind-mounted over /app. Not the default target.
FROM deps AS dev
COPY . .
EXPOSE 8080
# --host 0.0.0.0 because the server has to answer on the container's external
# interface; the default 127.0.0.1 is only reachable from inside the container.
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

FROM deps AS build
COPY . .
# The published site is built with long-term caching and the analytics snippet,
# matching the GitHub Pages deploy. A self-hosted copy that should not report to
# webpack's analytics property can drop the second flag:
# docker build --build-arg WEBPACK_ENV=--env\ longTermCaching .
ARG WEBPACK_ENV="--env longTermCaching --env googleAnalytics"
RUN npx webpack --mode production ${WEBPACK_ENV}

# Runtime: the built site is static, so nothing of Node ships in the final
# image. The app routes on the URL hash, so no history-API rewrite is needed
# either and nginx can serve the directory as it is.
FROM nginx:${NGINX_VERSION} AS runtime
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
# 127.0.0.1 rather than localhost, so the check does not depend on how the
# resolver orders IPv4 and IPv6.
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget -q --spider http://127.0.0.1/ || exit 1
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ This project is a lightweight front-end viewer for webpack output generated with
- Node.js 24 (the current Active LTS, "Krypton") — pinned in [`.nvmrc`](.nvmrc)
- npm 11.x (ships with Node 24)
- A webpack project whose build can emit a stats JSON file
- Or Docker on its own, if you would rather not install Node at all — Docker
Desktop on macOS and Windows, Docker Engine on Linux. See [Docker](#docker)

The Node version is enforced: `engines` in `package.json` requires `^24.20.0`,
and `engine-strict=true` in `.npmrc` makes npm abort the install on a mismatched
Expand Down Expand Up @@ -85,6 +87,69 @@ a small hand-written stats file that covers every hint of the hints page and is
loadable in the app as the "hint test cases" example. CI runs this before the
build, so a failing test stops the run and nothing is deployed.

## Docker

The [`Dockerfile`](Dockerfile) builds two things from this repository: the
production site behind nginx, and the webpack dev server for working on the app.

These commands talk to a Docker daemon, so one has to be running first. On macOS
and Windows that means [Docker Desktop][docker-desktop] — installing it is not
enough, it has to be launched, or `docker build` fails with `Cannot connect to
the Docker daemon`. On Linux, Docker Engine with a running `docker` service does
the same job. Check with:

```bash
docker info
```

Serve the built site:

```bash
docker build -t webpack-analyse .
```

```bash
docker run --rm -p 8080:80 webpack-analyse
```

Then open <http://localhost:8080> and upload a `stats.json`. The file is read in
the browser and never reaches the container, so nothing needs to be mounted to
analyze a build. Node only exists in the build stage; the image that runs is
nginx serving the static `dist/`, with the content-hashed bundles marked
immutable and `index.html` and `web.js` marked `no-cache` so a redeploy is never
served half from cache.

Work on the app instead, with the source mounted and live rebuilds:

```bash
docker build -t webpack-analyse-dev --target dev .
```

```bash
docker run --rm -p 8080:8080 -v "$PWD:/app" -v /app/node_modules -e WATCHPACK_POLLING=true webpack-analyse-dev
```

Two details in that command are worth knowing. The bare `-v /app/node_modules`
keeps the dependencies that were installed inside the image, so the mount of the
host directory over `/app` cannot hide them or replace them with binaries built
for a different platform. `WATCHPACK_POLLING=true` makes webpack poll for
changes, because file system events from a bind mount do not reliably reach a
container on macOS or Windows; on Linux it can be dropped.

The image builds the site the same way the GitHub Pages deploy does, analytics
snippet included. For a self-hosted copy that should not report to webpack's
analytics property, build it without that flag:

```bash
docker build --build-arg WEBPACK_ENV="--env longTermCaching" -t webpack-analyse .
```

The Node version is pinned by the `NODE_VERSION` build argument, which tracks
[`.nvmrc`](.nvmrc) and `engines.node`. Because `engine-strict=true` is set in
`.npmrc`, a mismatch fails the install rather than building something untested.

[docker-desktop]: https://www.docker.com/products/docker-desktop/

## Reading the graphs

The module and chunk graphs carry their meaning in colour, size and direction.
Expand Down
32 changes: 32 additions & 0 deletions docker/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Serves the production build from the runtime stage of the Dockerfile.
server {
listen 80;
# Also on IPv6: inside the container "localhost" resolves to ::1 first, so
# an IPv4-only socket makes anything reaching for localhost, the health
# check included, fail with a connection refused.
listen [::]:80;
server_name _;
root /usr/share/nginx/html;

gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;

# Bundle filenames carry a content hash when the site is built with
# --env longTermCaching, so a given file never changes under its name.
location ~ "^/[0-9a-f]{16,}\.js$" {
add_header Cache-Control "public, max-age=31536000, immutable";
}

# index.html and the entry bundle keep their names from one build to the
# next, and index.html names the hashed chunks, so both have to revalidate
# or a new deploy can be served half from cache and fail to boot. A request
# for "/" reaches this through the internal redirect to /index.html.
location ~ "^/(index\.html|web\.js)$" {
add_header Cache-Control "no-cache";
}

location / {
try_files $uri $uri/ =404;
}
}