Skip to content

tech(core): load the formatjs Intl polyfill from a lazy module - #1644

Merged
thet merged 2 commits into
masterfrom
pr/lazy-intl-polyfill
Sep 17, 2026
Merged

thet merged 2 commits into
masterfrom
pr/lazy-intl-polyfill

Conversation

@MrTango

@MrTango MrTango commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1640.

core/intl-loader.js dynamically imports locale-data/${baseLang}.js from the ~766-file @formatjs/intl-datetimeformat/locale-data directory. Webpack turns that into a context and emits one lazy chunk per file: ~110 MB of chunks plus ~220 MB of source maps, and the production build runs out of the default Node heap.

baseLang never carries a region or script suffix, so the ~450 variant files (pt-BR.js, zh-Hans.js, ...) were never requestable. This PR:

  • constrains the context with webpackInclude to base-language files (de.js, pt.js, ...). Regional variants still resolve through the polyfill's own fallback (pt-BR → pt, de-AT → de), as before. Note: webpack matches webpackInclude against the absolute file path, so the pattern is not anchored at ./.
  • moves the polyfill-force import and the locale-data context into a separate lazy intl-polyfill.js, loaded only after feature detection fails, so the context map is not part of the pat-structure chunk.

Production build, before → after:

master this PR
chunk files 3587 3145
locale-data chunks 767 324
dist size 380 MB 170 MB
bundle.min.js 291 KB 278 KB

Re module federation (@petschki, @thet): MF only avoids loading a shared module twice. It does not change what webpack emits for a dynamic-import context, so the chunk count is only reduced by narrowing the context.

@MrTango
MrTango marked this pull request as ready for review September 16, 2026 09:29
@MrTango
MrTango requested review from erral and petschki September 16, 2026 09:29
@erral

erral commented Sep 16, 2026

Copy link
Copy Markdown
Member

But this will not reduce the plone.staticresources package size issue described in #1640 right?

@petschki

Copy link
Copy Markdown
Member

I do not really understand the need of this "lazy loading" refactorings. I fact, I thought that's what module federation is made for -> to load resources only if they are needed ... or am I missing here something?

/cc @thet

@thet

thet commented Sep 16, 2026

Copy link
Copy Markdown
Member

The module federation machinery only makes sure that the same resource isn't loaded twice from different bundles. As a side effect it also enforces code splitting via the async import in the bundle entry point (import("./bundle.js")).

Loading only when it's needed is only done via async imports (await import....).

However, how this PR solves the issue isn't also clear to me. It looks like it's just async-importing another module where other async imports are done - the same async imports as before.

But I'll give this another thought after I have finished my current thought on a different topic and had some lunch. 🤯

core/intl-loader.js dynamically imports `locale-data/${baseLang}.js` from
the ~766-file @formatjs/intl-datetimeformat/locale-data directory. Webpack
turns that into a context and emits one lazy chunk per file (~110 MB of
chunks plus ~220 MB of source maps), and the production build runs out
of the default Node heap. baseLang never carries a region or script
suffix, so the ~450 variant files were never requestable.
See #1640.

Constrain the context with webpackInclude to base-language files
(de.js, pt.js, ...). Regional variants still resolve through the polyfill's
own fallback (pt-BR -> pt, de-AT -> de), as they did before. The include is
matched against the absolute file path, so it must not be anchored at "./".

Move the polyfill-force import and the locale-data context into a separate
lazy intl-polyfill.js, loaded only after feature detection fails, so the
context map is not part of the pat-structure chunk.

Measured on a production build, 3587 -> 3145 chunk files, 767 -> 324
locale chunks, dist 380 MB -> 170 MB, bundle.min.js -13 KB (webpack's
runtime chunk-hash map shrinks with the chunk count).
@MrTango
MrTango force-pushed the pr/lazy-intl-polyfill branch from c19c35e to 3b8878b Compare September 17, 2026 07:59
@MrTango

MrTango commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

I updated the PR and the description. Still needs a bit of testing.

@thet

thet commented Sep 17, 2026

Copy link
Copy Markdown
Member

I got help reviewing that. The extra async import here improves some webpack build artefact internals - there is a chunk lookup table and that is moved down to the chunk and so not initially polluting the bundle size.

This is the conversation:

Please look at the latest commit and explain why the changes make loading of the intl polyfill cheaper. It was an async lazy import before and is the same afterwards, AFAIK.

You’re right: the polyfill code and individual locale data were already lazy-loaded. Commit 3b8878b improves the surrounding Webpack overhead, rather than making those imports lazy for the first time.

There are two changes:

  1. Generate fewer locale chunks. With import(.../locale-data/${baseLang}.js), Webpack cannot infer that baseLang never contains a region suffix. Previously, it generated chunks for all matching files, including pt-BR.js, even though the code would only request pt.js. The new webpackInclude filter excludes those unreachable variants. This reduces build work, output size, and the chunk metadata in Webpack’s runtime. It does not save downloading hundreds of locale files: browsers weren’t downloading those before.

  2. Lazy-load the locale lookup table itself. A variable import requires a generated “context”: a lookup table mapping filenames to modules/chunks. Previously, that table accompanied intl-loader.js in its containing chunk, although the actual locale data remained lazy. The new import("./intl-polyfill.js") moves that table behind another lazy boundary. Browsers with native locale support now avoid fetching that table too.

So the benefit is substantially cheaper builds and somewhat less JavaScript for browsers that don’t need the polyfill. For browsers that do need it, the actual polyfill and selected locale remain essentially the same payload, with an additional sequential import step.

The commit reports dist shrinking from 380 MB to 170 MB and bundle.min.js shrinking by 13 KB; those are reported build measurements, not measurements I reproduced. The large dist reduction should not be interpreted as equivalent page-load savings.

I also checked the suspected regex escaping issue against the installed Webpack parser: the filter correctly accepts de.js and excludes pt-BR.js.

@thet thet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Also I can reproduce the smaller bundle size - different chunk counts and sizes, but still a big improvement!

@thet
thet merged commit a00b112 into master Sep 17, 2026
3 checks passed
@thet
thet deleted the pr/lazy-intl-polyfill branch September 17, 2026 11:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dynamic Intl locale-data import bloats the Plone bundle by ~770 lazy chunks (~340 MB on disk)

4 participants