react / react/react-strict-dom

postcss plugin re-transforms every included file on each rebuild under Turbopack (minutes-long HMR in large projects)

Open
#520 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
3.6k
Forks
206
PR merge metrics
No merged PRs in 30d

Description

Summary

In a Next.js 16 app using react-strict-dom/postcss-plugin, every incremental rebuild under Turbopack (the default dev bundler) re-transforms all files matched by the plugin's include globs, not just the changed file. In a large monorepo (~4,000 included source files) this makes a one-line edit take ~80 seconds to rebuild, while the same edit under next dev --webpack rebuilds in ~4 seconds.

Environment

  • react-strict-dom: 0.0.55 (postcss-react-strict-dom 0.0.55)
  • next: 16.2.11 (Turbopack dev)
  • node: 22.x, macOS (M-series)
  • postcss.config.js: react-strict-dom/postcss-plugin with include globs covering the app + shared workspace packages (~4,000 .ts/.tsx files), useLayers: true, and the documented babelConfig with react-strict-dom/babel-preset

Measurements (same machine, same session)

Scenario Time
Cold compile of one route (Turbopack) ~50s
One-line edit to any file matched by include (Turbopack) ~80s
Edit to a file NOT matched by include (Turbopack) 0.35s
One-line edit, next dev --webpack 4.4s

Creating a new untracked file that matches the globs — even one that nothing imports — also triggers the full ~80s rebuild, since the generated CSS has a dir-dependency on the glob roots.

Analysis

The plugin already has an incremental path: postcss-react-strict-dom/src/builder.js keeps a fileModifiedMap of mtimes and skips unchanged files. But that cache lives in the plugin instance's memory:

  • Under webpack, Next keeps the PostCSS config module (and the plugin closure) alive across rebuilds, so only the changed file is re-transformed → ~4s HMR.
  • Under Turbopack, the PostCSS transform runs in a turbopack-node worker whose module state does not survive the invalidation, so fileModifiedMap is empty on every rebuild and all ~4,000 files are re-parsed and babel-transformed serially in a single worker on every edit.

Profiling during a rebuild shows one Node process (the turbopack-node PostCSS transform) pegged at ~100% CPU for the entire rebuild while everything else idles. A native sample of that process lands almost entirely in Array.prototype.includes via postcss's MapGenerator.previous() (previousMaps.includes(map) per AST node — quadratic when the generated CSS has many nodes with distinct input maps), with the repeated Babel transform pass as the underlying volume of work.

Suggested fixes

  1. A persistent (disk-backed) cache for the per-file transform results, keyed by path + mtime/content hash (similar to Tailwind's approach), so extraction stays incremental regardless of the host process lifecycle.
  2. Alternatively/additionally: a note in the Next.js setup docs that large projects should currently prefer next dev --webpack, since Turbopack defeats the plugin's in-memory cache.

Repro

I can't share the affected monorepo, but the behavior scales with the number of files matched by include: generating a few thousand small components using css.create from react-strict-dom and pointing the plugin's include at them reproduces the per-edit full re-extraction under next dev (Turbopack) vs the fast incremental path under next dev --webpack. Happy to put together a synthetic repro repo if useful.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with postcss-react-strict-dom/src/builder.js and its fileModifiedMap, then reproduce the incremental behavior with a synthetic project containing many matched files under next dev using Turbopack and webpack. Compare cache behavior across rebuilds; done means incremental extraction remains effective across Turbopack invalidations, or the documented webpack workaround is added and verified.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, next.js, react
Domain
build-system, performance, tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.