What actually fixed WooCommerce build performance

Run pnpm watch:build on the WooCommerce monorepo as it stood a few months ago and Node would quietly eat 24.4 GB of RAM before it was even ready to watch files. Christopher Allford wrote up how that got fixed on the WooCommerce developer blog, and the headline numbers are worth reading twice: cold build time down 60 percent, watch ready time down 75 percent, watch memory down 84 percent, all measured on an M4 Max with 48 GB of RAM. What I take from it is that the compiler was never the problem. The problem was how the build was put together.

Where the 24 GB went

The watch command ran 128 processes: separate builds for ESM, separate builds for CommonJS, and the webpack builds for Admin and Blocks, each wrapped in a wireit monitor and a pnpm kickoff process. The webpack builds only consumed the ESM output, so every CJS build running on a developer machine was work that nothing ever used.

The memory went into the fan-out: dozens of Node processes, each with its own copy of the dependency graph. When a build feels heavy, count processes before you blame the compiler. Running ps aux | grep node | wc -l while the watch is going tells you more than most profiling sessions.

The four changes

The pull requests landed in a specific order, and the order matters: each one removed a prerequisite for the next.

Emit CommonJS only at publish time

WooCommerce distributes both ESM and CommonJS, but the internal bundler only consumed the ESM build, so the CJS pass on every dev build was wasted work. PR #64876 moved the dual-format build into a prepack script that runs at publish time. Cold build went from 96s to 59s and watch memory from 24.4 GB to 18.8 GB.

A smaller version of this shows up in plugin work constantly: builds that emit minified and unminified bundles nobody loads, source maps for scripts nothing debugs, admin and frontend built together on every save. Look at what your build writes and ask who reads each file.

Move type-checking to lint

tsc was doing two jobs, checking types and emitting JavaScript, and the build only needed the second one. PR #65168 moved checking to a lint step and emission to a publish step. That split sounds like bureaucracy until you see what it buys:

{
  "scripts": {
    "build": "node build.mjs",
    "build:publish": "node build.mjs --formats esm,cjs",
    "lint:types": "tsc --noEmit",
    "prepack": "npm run lint:types && npm run build:publish"
  }
}

A build that doesn’t type-check is a build you can hand to a faster tool. That’s the reason the split exists.

Replace tsc with esbuild

PR #65210 moved package builds to esbuild, which strips types and emits output without checking anything, exactly what you want once lint owns the checking. Cold build dropped to 12.8s and watch memory to 7.3 GB. A follow-up, #65422, centralized the build scripts into a new @woocommerce/internal-build package. Migrations leave debris, and each package had a nearly identical build.mjs copied in. Fixing that in its own pull request instead of living with the copies is the part most teams skip.

Let webpack bundle the packages from source

PR #65254 was the real fix and the original goal. The Admin and Blocks webpack builds used to consume already-transpiled ESM instead of bundling from source, and that one decision is why there were 128 processes: webpack needed a finished input, so every package needed its own build and watcher. Once the packages compile inside the admin webpack build, the fan-out disappears. Watch memory landed at 3.9 GB and watch ready time at 33s, down from 132s.

The regression they shipped anyway

Cold build time went up on that last pull request, from 12.8s to 37.1s, because Babel now transpiles the TypeScript inside webpack and Babel is slower at that than esbuild. The wireit caching layer also went away, and the E2E CI jobs take an extra minute to build.

They shipped it anyway, and I think that’s the right call. The cost they accepted is paid in CI, where a filesystem cache doesn’t help, while the cost they removed was paid by every developer all day. The trade works because of webpack’s own filesystem cache: warm builds sit at 4.9s, faster than the old baseline warm build of 6.3s. A regression that only shows up on cold builds is a good swap for one that showed up every time you saved a file.

The follow-up plans mention a CI problem too. Their job matrix sharding tends to exhaust the available GitHub Actions workers, so waiting 20+ minutes for a free worker makes per-job tuning meaningless. Before you spend a week shaving a minute off one job, check whether the queue is the bottleneck instead of the job.

If your project is one plugin with one webpack config, most of this doesn’t map. You don’t have 128 processes; you have maybe four, and a 24 GB problem isn’t hiding in them. Two pieces are worth copying at any size: check whether your build emits files that nothing consumes, and turn on webpack’s filesystem cache if it isn’t already on, since that cache is what made the regression above survivable. The same kind of check applies further down the stack, like deciding whether custom media queries need a build step at all.

If your plugin or client project has a build pipeline that’s slower than the work it supports, or one that emits things nobody ships, that’s the kind of cleanup I do, and I’m happy to look at it with you.

Before you touch anything, measure cold build, warm build, watch ready time and watch memory on one machine, and write the numbers down. Allford’s table works because the baseline was recorded before the first pull request. Without a recorded baseline, you can’t tell whether you improved anything.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.

Leave a Comment