Visitar URL original
perf(wasm): Load ICU break dictionaries on demand by MartinZikmund · Pull Request #26055 · unoplatform/uno · GitHub
Skip to content

perf(wasm): Load ICU break dictionaries on demand - #26055

Draft
MartinZikmund wants to merge 4 commits into
masterfrom
dev/mazi/wasm-icu-dict-on-demand
Draft

MartinZikmund wants to merge 4 commits into
masterfrom
dev/mazi/wasm-icu-dict-on-demand

Conversation

@MartinZikmund

@MartinZikmund MartinZikmund commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

GitHub Issue: closes #26049

PR Type:

✨ Feature

What changed? 🚀

uno.icu-wasm embeds a 5.5 MB icudt.dat (2.46 MB brotli) in every WebAssembly app, of which 2.0 MB are the break dictionaries that only Thai, Lao, Khmer, Myanmar and CJK text uses.

  • Build (IcuDataSplitTask + _UnoSplitIcuDictionaries, browserwasm heads only): the head embeds the brkitr rules and *.icu tables (72 KB brotli). The dictionaries are deployed as ms-appx:///Uno.UI/icu/icudt-dict.dat, excluded from the PWA precache, plus a tiny resource naming that URI.
  • Runtime (UnicodeText.ICU.Dictionaries.cs): the first time text in those scripts is laid out, Uno fetches the dictionaries, registers them with udata_setCommonData, drops cached break iterators and invalidates the waiting text through the existing IFontCacheUpdateListener path.
  • Opt-out: UnoWasmIcuDictionariesOnDemand=false.

Blank app: Blank.wasm 2.53 MB → 134 KB brotli. On 4G (9 Mbps / 85 ms) this alone took visual-ready from 11.22 s to 9.42 s.

Validation:

  • Data equivalence: minimal + dictionaries vs the full data give identical boundaries on 1,560 samples (15 scripts × 26 locale forms incl. aliases, keywords, invalid locales × 4 break types). A dictionary package registered after a break iterator already met Thai is picked up by the next iterator.
  • Runtime: a Thai paragraph in a 150 px TextBlock. On 4G the first frame wraps per character, then re-lays out to word boundaries identical to the full-data build; the 2 MB is not fetched for Latin-only apps.
  • Unit tests: Given_IcuDataSplitTask (4), including an up-to-date check so incremental builds don't recompile.

Measurements

Without this PR With this PR Δ
Download (brotli) 8.82 MB 6.42 MB −2.39 MB
App assembly (Blank.wasm, brotli) 2.53 MB 134 KB −2.40 MB
4G visually ready 9086 ms (9082–9091) 6976 ms (6961–6995) −2110 ms (−23%)
Local visually ready 901 ms (890–925) 852 ms (844–868) −49 ms (−5.4%)

On-demand dictionaries: 2.02 MB brotli, fetched only for Thai/Lao/Khmer/Myanmar/CJK text (0 bytes for this app).

How this was measured

Blank dotnet new unoapp (Skia, net10.0-browserwasm, Release publish), built twice from the same tree: with all six startup PRs (#26054–#26059) plus UnoXamlResourcesTrimming, and with only this PR's change removed. Both builds were served by the same static server (brotli q11, immutable caching for fingerprinted files) and loaded cold in headless Chrome, runs interleaved. "Visually ready" is the first CDP screencast frame identical to the settled final frame. 4G = 9 Mbps down, 85 ms RTT (CDP emulation). Values are medians with (min–max). A difference is only claimed where the ranges don't overlap. Rig: Playwright + Node, available on request.

PR Checklist ✅

  • 🧪 Added Runtime tests, UI tests, or a manual test sample (for bug fixes / features, if applicable)
  • 📚 Docs have been added/updated following the documentation template (for bug fixes / features)
  • 🖼️ Validated PR Screenshots Compare Test Run results.
  • ❗ Contains NO breaking changes
  • 👀 Reviewed 2 other open pull requests (optional but appreciated!)

Open for discussion: the long-term home of the split is uno.icu (two data packages built with icupkg). This PR splits the existing package at build time, so it works with the current uno.icu release.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TXMNu2RupRy4QPFGK2cmij

MartinZikmund and others added 4 commits November 4, 2026 19:24
The uno.icu data embedded in every WebAssembly app is 5.5 MB (2.46 MB
brotli), 2.0 MB of which are the break dictionaries that only Thai,
Lao, Khmer, Myanmar and CJK text consults, and 0.4 MB locale data that
break iteration never reads.

The build now splits it: the app embeds the brkitr rules and *.icu
tables (72 KB brotli) and deploys the dictionaries as an app asset.
Uno.UI registers them with udata_setCommonData the first time text in
those scripts is laid out, then invalidates the waiting text. Break
results with both packages are identical to the full data (1560
samples over 26 locales and 4 break types).

Blank app: Blank.wasm 2.53 MB -> 134 KB brotli. Opt out with
UnoWasmIcuDictionariesOnDemand=false.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TXMNu2RupRy4QPFGK2cmij
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TXMNu2RupRy4QPFGK2cmij
The split rewrote its outputs on every build, and a newer embedded
resource recompiles the app. Skip when both outputs are up to date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TXMNu2RupRy4QPFGK2cmij
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TXMNu2RupRy4QPFGK2cmij
@github-actions github-actions Bot added the area/code-generation Categorizes an issue or PR as relevant to code generation label Nov 4, 2026
Comment on lines +48 to +52
catch (Exception e)
{
Log.LogError($"Failed to split the ICU data {SourcePath}: {e.Message}");
return false;
}
Comment on lines +92 to +95
catch (Exception e)
{
typeof(ICU).LogError()?.Error($"Failed to load the ICU break dictionaries from {uri}; Thai, Lao, Khmer, Myanmar and CJK text will break without them.", e);
}
try
{
var (startupPath, dictionariesPath) = RunTask(folder);
var stamp = File.GetLastWriteTimeUtc(Path.Combine(folder.FullName, "icudt.dat")).AddMinutes(1);
new IcuDataSplitTask_v0
{
BuildEngine = new RecordingBuildEngine(),
SourcePath = Path.Combine(folder.FullName, "icudt.dat"),

private static (string Startup, string Dictionaries) RunTask(DirectoryInfo folder)
{
var source = Path.Combine(folder.FullName, "icudt.dat");
{
BuildEngine = new RecordingBuildEngine(),
SourcePath = source,
StartupOutputPath = Path.Combine(folder.FullName, "out", "icudt.dat"),
BuildEngine = new RecordingBuildEngine(),
SourcePath = source,
StartupOutputPath = Path.Combine(folder.FullName, "out", "icudt.dat"),
DictionariesOutputPath = Path.Combine(folder.FullName, "out", "icudt-dict.dat"),
@nventive-devops

Copy link
Copy Markdown
Contributor

The build 243711 found UI Test snapshots differences: skia-linux-screenshots: 2458, skia-windows-screenshots: 2458

Details
  • skia-linux-screenshots: 2458 changed over 2494

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AndroidWindowInsets.png-dark
    • AppBarButtonWithIconTest.png
    • ApplicationViewSizing.png
    • AppWindowFeatures.png
    • AppXamlDefinedResources.png-dark
    • Attributed_text_Simple.png-dark
    • AutoBorderStretchwithbottommargin.png-dark
    • AutoBorderStretchwithtopmargin.png
    • AutomationProperties_StructureAndProps.png
    • AutoSizedPathCentered.png-dark
    • AutoSuggestBox_SoftKeboard.png-dark
    • AutoSuggestBox_SoftKeboard.png
    • Basics_Automated.png-dark
    • Basics_Automated.png
    • BenchmarkDotNet.png-dark
    • BenchmarkDotNet.png
    • BitmapImage_vs_SvgImageSource.png-dark
    • BitmapImage_vs_SvgImageSource.png
    • BorderTopwithmargins.png-dark
    • BorderTopwithmargins.png
  • skia-windows-screenshots: 2458 changed over 2494

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AndroidWindowInsets.png-dark
    • AppBarButtonTest.png-dark
    • AppBar_KeyBoard.png
    • Application_DispatcherShutdownMode.png-dark
    • AppWindowPositionAndSize.png
    • AppXamlDefinedResources.png
    • Attributed_text_FontSize_Changing.png
    • AuthenticationBroker_Cancel.png
    • AutomationPropertiesExtensions_Role.png
    • AutomationProperties_AutomationId.png-dark
    • AutoSuggestBox_Keyboard.png-dark
    • AutoSuggestBox_Keyboard.png
    • BasicAutoSuggestBox.png-dark
    • BasicAutoSuggestBox.png
    • BasicThemeResources.png-dark
    • BasicThemeResources.png
    • BitmapIcon_Foreground.png-dark
    • BitmapIcon_Foreground.png
    • BorderVersusPanelClipping.png-dark
    • BorderVersusPanelClipping.png

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/code-generation Categorizes an issue or PR as relevant to code generation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[WASM] Load ICU break dictionaries on demand instead of embedding them in every app

2 participants