Read size workflow (mrdoob/three.js)
The Read size workflow from mrdoob/three.js, explained and optimized by Latchkey.
CI health: C - fair
Point runs-on at Latchkey and get run de-duplication, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Read size workflow from the mrdoob/three.js repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: Read size
on:
pull_request:
paths:
- 'src/**'
- 'package.json'
- 'utils/build/**'
# This workflow runs in a read-only environment. We can safely checkout
# the PR code here.
# Reference:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
permissions:
contents: read
jobs:
read-size:
name: Tree-shaking
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Git checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 24
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: === Test tree-shaking ===
run: npm run test-treeshake
- name: Read bundle sizes
id: read-size
run: |
# minify the builds to measure their size
npx terser build/three.module.js --module --compress --mangle --output build/three.module.min.js
npx terser build/three.webgpu.js --module --compress --mangle --output build/three.webgpu.min.js
npx terser build/three.webgpu.nodes.js --module --compress --mangle --output build/three.webgpu.nodes.min.js
WEBGL_FILESIZE=$(stat --format=%s build/three.module.min.js)
gzip -k build/three.module.min.js
WEBGL_FILESIZE_GZIP=$(stat --format=%s build/three.module.min.js.gz)
WEBGL_TREESHAKEN=$(stat --format=%s test/treeshake/index.bundle.min.js)
gzip -k test/treeshake/index.bundle.min.js
WEBGL_TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.bundle.min.js.gz)
WEBGPU_FILESIZE=$(stat --format=%s build/three.webgpu.min.js)
gzip -k build/three.webgpu.min.js
WEBGPU_FILESIZE_GZIP=$(stat --format=%s build/three.webgpu.min.js.gz)
WEBGPU_TREESHAKEN=$(stat --format=%s test/treeshake/index.webgpu.bundle.min.js)
gzip -k test/treeshake/index.webgpu.bundle.min.js
WEBGPU_TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.webgpu.bundle.min.js.gz)
WEBGPU_NODES_FILESIZE=$(stat --format=%s build/three.webgpu.nodes.min.js)
gzip -k build/three.webgpu.nodes.min.js
WEBGPU_NODES_FILESIZE_GZIP=$(stat --format=%s build/three.webgpu.nodes.min.js.gz)
WEBGPU_NODES_TREESHAKEN=$(stat --format=%s test/treeshake/index.webgpu.nodes.bundle.min.js)
gzip -k test/treeshake/index.webgpu.nodes.bundle.min.js
WEBGPU_NODES_TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.webgpu.nodes.bundle.min.js.gz)
PR=${{ github.event.pull_request.number }}
# write the output in a json file to upload it as artifact
node -pe "JSON.stringify({ filesize: $WEBGL_FILESIZE, gzip: $WEBGL_FILESIZE_GZIP, treeshaken: $WEBGL_TREESHAKEN, treeshakenGzip: $WEBGL_TREESHAKEN_GZIP, filesize2: $WEBGPU_FILESIZE, gzip2: $WEBGPU_FILESIZE_GZIP, treeshaken2: $WEBGPU_TREESHAKEN, treeshakenGzip2: $WEBGPU_TREESHAKEN_GZIP, filesize3: $WEBGPU_NODES_FILESIZE, gzip3: $WEBGPU_NODES_FILESIZE_GZIP, treeshaken3: $WEBGPU_NODES_TREESHAKEN, treeshakenGzip3: $WEBGPU_NODES_TREESHAKEN_GZIP, pr: $PR })" > sizes.json
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: sizes
path: sizes.json
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: Read size on: pull_request: paths: - 'src/**' - 'package.json' - 'utils/build/**' # This workflow runs in a read-only environment. We can safely checkout # the PR code here. # Reference: # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: read-size: timeout-minutes: 30 name: Tree-shaking runs-on: latchkey-small permissions: contents: read steps: - name: Git checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Install Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: 24 cache: 'npm' - name: Install dependencies run: npm ci - name: Build run: npm run build - name: === Test tree-shaking === run: npm run test-treeshake - name: Read bundle sizes id: read-size run: | # minify the builds to measure their size npx terser build/three.module.js --module --compress --mangle --output build/three.module.min.js npx terser build/three.webgpu.js --module --compress --mangle --output build/three.webgpu.min.js npx terser build/three.webgpu.nodes.js --module --compress --mangle --output build/three.webgpu.nodes.min.js WEBGL_FILESIZE=$(stat --format=%s build/three.module.min.js) gzip -k build/three.module.min.js WEBGL_FILESIZE_GZIP=$(stat --format=%s build/three.module.min.js.gz) WEBGL_TREESHAKEN=$(stat --format=%s test/treeshake/index.bundle.min.js) gzip -k test/treeshake/index.bundle.min.js WEBGL_TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.bundle.min.js.gz) WEBGPU_FILESIZE=$(stat --format=%s build/three.webgpu.min.js) gzip -k build/three.webgpu.min.js WEBGPU_FILESIZE_GZIP=$(stat --format=%s build/three.webgpu.min.js.gz) WEBGPU_TREESHAKEN=$(stat --format=%s test/treeshake/index.webgpu.bundle.min.js) gzip -k test/treeshake/index.webgpu.bundle.min.js WEBGPU_TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.webgpu.bundle.min.js.gz) WEBGPU_NODES_FILESIZE=$(stat --format=%s build/three.webgpu.nodes.min.js) gzip -k build/three.webgpu.nodes.min.js WEBGPU_NODES_FILESIZE_GZIP=$(stat --format=%s build/three.webgpu.nodes.min.js.gz) WEBGPU_NODES_TREESHAKEN=$(stat --format=%s test/treeshake/index.webgpu.nodes.bundle.min.js) gzip -k test/treeshake/index.webgpu.nodes.bundle.min.js WEBGPU_NODES_TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.webgpu.nodes.bundle.min.js.gz) PR=${{ github.event.pull_request.number }} # write the output in a json file to upload it as artifact node -pe "JSON.stringify({ filesize: $WEBGL_FILESIZE, gzip: $WEBGL_FILESIZE_GZIP, treeshaken: $WEBGL_TREESHAKEN, treeshakenGzip: $WEBGL_TREESHAKEN_GZIP, filesize2: $WEBGPU_FILESIZE, gzip2: $WEBGPU_FILESIZE_GZIP, treeshaken2: $WEBGPU_TREESHAKEN, treeshakenGzip2: $WEBGPU_TREESHAKEN_GZIP, filesize3: $WEBGPU_NODES_FILESIZE, gzip3: $WEBGPU_NODES_FILESIZE_GZIP, treeshaken3: $WEBGPU_NODES_TREESHAKEN, treeshakenGzip3: $WEBGPU_NODES_TREESHAKEN_GZIP, pr: $PR })" > sizes.json - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: sizes path: sizes.json
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Add a job timeout so a hung step cannot burn hours of runner time.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.