How the charting library keeps map topology out of the bundle that every chart page pays for, and how to measure the result.
The mechanism
Map topology (TopoJSON for US counties, CBSAs, world borders) is by far the largest thing in this library — an order of magnitude bigger than the chart renderers. None of it should load for a bar chart. Two layers of splitting keep it out:
1. Map renderers are lazy. src/lib/controller/ChartBuilder.tsx imports
every map through React.lazy(), so webpack emits each as its own chunk and the
main entry only carries a reference:
const AlbersUSA = lazy(() => import('../charts/maps/AlbersUSA'));
const AlbersUSACounties = lazy(() => import('../charts/maps/AlbersUSACounties'));
const AlbersUSACBSA = lazy(() => import('../charts/maps/AlbersUSACBSA'));
const BlockUSA = lazy(() => import('../charts/maps/BlockUSA'));
const HexUSA = lazy(() => import('../charts/maps/HexUSA'));
const World = lazy(() => import('../charts/maps/World'));
const WorldOrthographic = lazy(() => import('../charts/maps/WorldOrthographic'));
Each is rendered inside a Suspense boundary, so a map shows a loading state
while its chunk is in flight.
2. Topology is a dynamic import behind a Suspense resource. A map renderer
never statically imports its topology — that would pull the JSON straight back
into the map's chunk. Instead it wraps a dynamic import() of the JSON in
createTopologyLoader from @prc/charting-utilities, which returns a
Suspense-compatible reader:
const loadCbsaTopology = createTopologyLoader(() => import('../../data/maps/usa-cbsa/topology.json'));
const loadUsaTopology = createTopologyLoader(() => import('../../data/maps/usa/topology.json'));
Webpack emits each topology as its own chunk, and the loader throws its pending
promise so the surrounding Suspense boundary renders a fallback until the
chunk lands. The resource is created at module scope, so the topology is fetched
once and shared: several maps on one page that use the same topology pay for it
a single time. A counties map therefore costs the entry bundle, plus its
renderer chunk, plus its topology chunk.
Because both entries set output.publicPath: 'auto', chunks resolve relative to
the loading module. This matters on VIP, where the build is not served from the
same origin-relative path in every context — see src/publicPath.js and
class-prc-charting-library.php.
Measuring current sizes
Sizes move with every dependency bump, so measure rather than trusting a number written down here:
npx turbo build --filter=@prc/charting-library
ls -la plugins/prc-charting-library/build/*.js |
awk '{printf "%8.0f KB %s\n", $5/1024, $9}' | sort -rn | head -20
Chunk names are content-derived numbers (855.js, 637.js), and the dual build
emits a parallel view- prefixed set. To find which map a given chunk holds,
grep it for a marker from that topology — a CBSA name, a county name, a country
name.
Snapshot, July 2026 (uncompressed, before transfer encoding):
| Asset | Size | Notes |
|---|---|---|
editor.js | ~481 KB | React editor entry |
view.js | ~589 KB | Preact frontend entry |
| CBSA map chunk | ~1.6 MB | Largest topology by a wide margin |
| Counties map chunk | ~823 KB | |
| Smaller map chunks | ~112 KB | State-level and cartogram maps |
The build prints size warnings for the map chunks. That is expected and acceptable: they are only fetched by pages that actually render that map.
Build configuration
webpack.config.js exports two configs from one source tree — a React
editor build and a Preact Script Module frontend build. See the dual-build table
in the package README for what each emits and how it is
registered with WordPress.
The code splitting itself needs no custom configuration; wp-scripts detects
dynamic import() and emits the chunks. The custom config exists for the dual
build, output.library, and publicPath, not for splitting. The WordPress
enqueue side needs nothing special either — the webpack runtime in the entry
bundle fetches chunks on demand.
If further reduction is needed
- Simplify the topology. More aggressive TopoJSON simplification is the highest-leverage option, since topology dominates every map chunk. The CBSA and counties files are the ones worth attacking.
- Preload for map-heavy pages. Warm the chunk earlier for templates known to render a map, trading idle bandwidth for a shorter visible loading state.
- Split vendor code further. Separating d3 and visx would help pages that render several different chart types.
- Serve topology from a CDN for cross-site caching, at the cost of another origin in the critical path.