Repository navigation
Conversation
Switch `gzipInline` to `brotliInline`, using maximum quality and the largest window so long-range repetition (e.g. in `Names/index.mjs`) is exploited. Generated modules now import `brotliDecompressSync` from `node:zlib`. For Unicode 18.0.0 this shrinks the package from 700 KB to 590 KB packed (1.96 MB to 1.79 MB unpacked); `Names/index.mjs` goes from 228 KB to 158 KB. Assisted-By: Claude Fable 5.1
Each `regex.mjs` used to contain a regex literal produced by Regenerate, and for astral ranges that expands into long surrogate-pair alternations (470 KB per package, 190 KB of it surrogates). The data is already in `ranges.mjs`, so `regex.mjs` now imports it and calls the new `ranges-to-regex.mjs`, a port of Regenerate's serialization logic. The resulting `source` and `flags` are identical to the previous literals; a new test asserts this for every property against Regenerate itself, and the port was checked against all 19,610 regexes previously generated across the 35 Unicode versions. For Unicode 18.0.0 this shrinks the package from 590 KB to 444 KB packed (1.79 MB to 1.47 MB unpacked). Assisted-By: Claude Fable 5.1
For Case_Folding, Simple_Case_Mapping, Special_Casing and Decomposition_Mapping, `symbols.mjs` shipped a second compressed copy of the same data as `code-points.mjs`, differing only in that every code point was replaced by its string. It now imports `code-points.mjs` and derives the map through the new `mapping-to-symbols.mjs`. Small mappings (fewer than 10 entries) keep their inline literals. The resulting maps are identical, including entry order; a new test checks this for every mapping directory. For Unicode 18.0.0 this shrinks the package from 444 KB to 410 KB packed (1.47 MB to 1.43 MB unpacked). Assisted-By: Claude Fable 5.1
`Names/index.mjs` stored every character name as a JSON string inside the run-length array shared with the other property maps. Names are sorted by code point and neighbouring names share long prefixes (e.g. `LATIN SMALL LETTER A WITH …`), so the table is now written as text: a first line with the `[gap, length]` run pairs as JSON, followed by one front-coded name per line (a base64 digit for the number of characters shared with the previous name, then the remainder). The new `decode-names.mjs` rebuilds the same `Map<number, string>`, with the same entries in the same order; this was verified against all 35 versions, and a new test checks representative lookups. For Unicode 18.0.0 `Names/index.mjs` shrinks from 158 KB to 123 KB, and the package from 410 KB to 390 KB packed (1.43 MB to 1.39 MB unpacked). Assisted-By: Claude Fable 5.1
The `code-points.mjs` of Case_Folding, Simple_Case_Mapping, Special_Casing and Decomposition_Mapping stored the map as compressed JSON `[from, to]` pairs. Mapped code points are almost always close to their keys, so the map is now written as a stream of variable-length integers using the same base64 scheme as `ranges.mjs`: per entry, the zigzag-encoded difference between its key and the previous key, a count, and the zigzag-encoded differences between consecutive mapped code points. A count of 0 marks an entry mapping to a single code point, so files mixing scalar and array values (e.g. Unicode 3.0.1 Case_Folding/E) round-trip exactly. The stream is brotli-compressed as before and decoded by the new `decode-mapping.mjs`; `decode-ranges.mjs` now also exports `decodeDeltas` for it. All 1,699 maps across the 35 versions are identical to the previous output, entry order included. For Unicode 18.0.0, `Simple_Case_Mapping/Uppercase/code-points.mjs` shrinks from 4.1 KB to 1.1 KB, and the package from 390 KB to 378 KB packed (1.39 MB to 1.38 MB unpacked). Assisted-By: Claude Fable 5.1
Every property directory shipped four identical declaration files (`ranges.d.mts`, `regex.d.mts`, `code-points.d.mts`, `symbols.d.mts`), about 4,000 files per package. They are now written once under `types/` and selected through a generated `typesVersions` map in package.json. Mapping directories (Case_Folding, Simple_Case_Mapping, Special_Casing, Decomposition_Mapping) keep their own `code-points`/`symbols` declarations, since their value types differ per directory; more specific patterns route them there. The build removes per-directory declarations left over from earlier runs. `decode-ranges.d.mts` now exports the `UnicodeRange` type at module level; previously `ranges.d.mts` imported a name that was only declared inside a namespace, so TypeScript consumers saw an error. The README now documents TypeScript 4.7 as the minimum, which is what `.d.mts` requires. Verified with TypeScript 5.9 (node16, bundler and node10 resolution) and TypeScript 4.7: imports resolve to the same types as before. A new test checks the declaration layout and the typesVersions targets. For Unicode 18.0.0 this shrinks the package from 378 KB to 312 KB packed (1.38 MB to 1.08 MB unpacked) and from 8,352 to 4,320 files. Assisted-By: Claude Fable 5.1
`index.mjs` has a single default export, an object mapping each property type to its list of directories, but `index.d.mts` declared named exports instead, so `import dirs from '@unicode/unicode-X'` failed to type-check. The declaration now describes the default export with one `readonly <Type>: string[]` property per type. A test checks that the declared keys match the object exported at runtime. Assisted-By: Claude Fable 5.1
|
As for the Node.js floor, I suggest we explicitly specify the engines requirement in |
|
Thanks for your work on this! I’m considering merging this except for the runtime regexes (7fb6a67). This is the only change that I struggle to get behind. I like that it’s easy to look up the regular expressions for a given property/value pair just by checking e.g. https://github.com/node-unicode/unicode-18.0.0/blob/main/Script_Extensions/Adlam/regex.mjs, and that was one of the original motivations of this project (as the README change in that comment indeed suggests). I don’t like that |
Switch `gzipInline` to `brotliInline`, using maximum quality and the largest window so long-range repetition (e.g. in `Names/index.mjs`) is exploited. Generated modules now import `brotliDecompressSync` from `node:zlib`. For Unicode 18.0.0 this shrinks the package from 700 KB to 590 KB packed (1.96 MB to 1.79 MB unpacked); `Names/index.mjs` goes from 228 KB to 158 KB. Assisted-By: Claude Fable 5.1 Issue: #109
For Case_Folding, Simple_Case_Mapping, Special_Casing and Decomposition_Mapping, `symbols.mjs` shipped a second compressed copy of the same data as `code-points.mjs`, differing only in that every code point was replaced by its string. It now imports `code-points.mjs` and derives the map through the new `mapping-to-symbols.mjs`. Small mappings (fewer than 10 entries) keep their inline literals. The resulting maps are identical, including entry order; a new test checks this for every mapping directory. For Unicode 18.0.0 this shrinks the package from 444 KB to 410 KB packed (1.47 MB to 1.43 MB unpacked). Assisted-By: Claude Fable 5.1 Issue: #109
`Names/index.mjs` stored every character name as a JSON string inside the run-length array shared with the other property maps. Names are sorted by code point and neighbouring names share long prefixes (e.g. `LATIN SMALL LETTER A WITH …`), so the table is now written as text: a first line with the `[gap, length]` run pairs as JSON, followed by one front-coded name per line (a base64 digit for the number of characters shared with the previous name, then the remainder). The new `decode-names.mjs` rebuilds the same `Map<number, string>`, with the same entries in the same order; this was verified against all 35 versions, and a new test checks representative lookups. For Unicode 18.0.0 `Names/index.mjs` shrinks from 158 KB to 123 KB, and the package from 410 KB to 390 KB packed (1.43 MB to 1.39 MB unpacked). Assisted-By: Claude Fable 5.1 Issue: #109
The `code-points.mjs` of Case_Folding, Simple_Case_Mapping, Special_Casing and Decomposition_Mapping stored the map as compressed JSON `[from, to]` pairs. Mapped code points are almost always close to their keys, so the map is now written as a stream of variable-length integers using the same base64 scheme as `ranges.mjs`: per entry, the zigzag-encoded difference between its key and the previous key, a count, and the zigzag-encoded differences between consecutive mapped code points. A count of 0 marks an entry mapping to a single code point, so files mixing scalar and array values (e.g. Unicode 3.0.1 Case_Folding/E) round-trip exactly. The stream is brotli-compressed as before and decoded by the new `decode-mapping.mjs`; `decode-ranges.mjs` now also exports `decodeDeltas` for it. All 1,699 maps across the 35 versions are identical to the previous output, entry order included. For Unicode 18.0.0, `Simple_Case_Mapping/Uppercase/code-points.mjs` shrinks from 4.1 KB to 1.1 KB, and the package from 390 KB to 378 KB packed (1.39 MB to 1.38 MB unpacked). Assisted-By: Claude Fable 5.1 Issue: #109
Every property directory shipped four identical declaration files (`ranges.d.mts`, `regex.d.mts`, `code-points.d.mts`, `symbols.d.mts`), about 4,000 files per package. They are now written once under `types/` and selected through a generated `typesVersions` map in package.json. Mapping directories (Case_Folding, Simple_Case_Mapping, Special_Casing, Decomposition_Mapping) keep their own `code-points`/`symbols` declarations, since their value types differ per directory; more specific patterns route them there. The build removes per-directory declarations left over from earlier runs. `decode-ranges.d.mts` now exports the `UnicodeRange` type at module level; previously `ranges.d.mts` imported a name that was only declared inside a namespace, so TypeScript consumers saw an error. The README now documents TypeScript 4.7 as the minimum, which is what `.d.mts` requires. Verified with TypeScript 5.9 (node16, bundler and node10 resolution) and TypeScript 4.7: imports resolve to the same types as before. A new test checks the declaration layout and the typesVersions targets. For Unicode 18.0.0 this shrinks the package from 378 KB to 312 KB packed (1.38 MB to 1.08 MB unpacked) and from 8,352 to 4,320 files. Assisted-By: Claude Fable 5.1 Issue: #109
`index.mjs` has a single default export, an object mapping each property type to its list of directories, but `index.d.mts` declared named exports instead, so `import dirs from '@unicode/unicode-X'` failed to type-check. The declaration now describes the default export with one `readonly <Type>: string[]` property per type. A test checks that the declared keys match the object exported at runtime. Assisted-By: Claude Fable 5.1 Issue: #109
|
I’ve merged the changes except for the runtime regex patch. Thanks a lot!
Agreed. |
The generated modules use `brotliDecompressSync` (Node.js 11.7+) and `Object.fromEntries` (Node.js 12+). Issue: #109
In this PR we shrink the output size by more than 50% with the help of LLM:
Reduces the published
@unicode/unicode-18.0.0packages from 700 KB to 312 KB packed, from 1.96 MB to 1.08 MB unpacked, and from 8,344 to 4,320 files. No module path, export shape, regex source, or map entry order changes; every step was verified against the previously generated output for all 35 Unicode versions.Changes
regex.mjsat runtime fromranges.mjssymbols.mjsfromcode-points.mjstypesVersionsBrotli.
gzipInlinebecomesbrotliInlineat maximum quality and window size. Generated modules importbrotliDecompressSyncfromnode:zlib.Runtime regexes. Each
regex.mjsheld a regex literal from Regenerate; astral ranges expand into long surrogate-pair alternations (470 KB per package).regex.mjsnow importsranges.mjsand calls the newranges-to-regex.mjs, a port of Regenerate's serializer.sourceandflagsare byte-identical to the previous literals.Derived symbols. For the four mapping types,
symbols.mjscarried a second compressed copy ofcode-points.mjs. It now derives the map at runtime throughmapping-to-symbols.mjs. Mappings with fewer than 10 entries keep their inline literals.Front-coded Names.
Names/index.mjsstores one name per line, front-coded against the previous name, with the run pairs as a JSON first line.decode-names.mjsrebuilds the sameMap<number, string>. 158 KB → 123 KB.Delta-coded mappings. Case and decomposition
code-points.mjsfiles store zigzag deltas as variable-length integers in the same base64 scheme asranges.mjs, decoded bydecode-mapping.mjs. A count of 0 marks a scalar value, so files that mix scalar and array values (Unicode 3.0.1Case_Folding/E) round-trip exactly.Shared declarations. The four identical per-directory
.d.mtsfiles are written once undertypes/and selected through a generatedtypesVersionsmap. Mapping directories keep their owncode-points/symbolsdeclarations because their value types differ; more specific patterns route them there. The build removes stale per-directory declarations from earlier runs.Fixes found along the way
ranges.d.mtsimported aUnicodeRangetype thatdecode-ranges.d.mtsonly declared inside a namespace, so TypeScript consumers got an error on everyranges.mjsimport. The type is now exported at module level.index.d.mtsdeclared named exports whileindex.mjsonly has a default export, soimport dirs from '@unicode/unicode-X'did not type-check. It now declares the default export with onereadonly <Type>: string[]per property type.Things to be aware of
brotliDecompressSyncrequires Node.js 11.7 or later. The previous output needed onlygunzipSync. Note that although we have not exactly specified the Node.js support, we already useObject.fromEntriesindecode-ranges.mjs, which requires Node.js 12.0.0, so the switch fromgziptobrotlishould be safe..d.mtsfiles already required TypeScript 4.7; the generated README now says so explicitly.typesVersionsitself has existed since TypeScript 3.1.regex.mjsand mappingsymbols.mjsnow do a little work on import instead of shipping precomputed data.Names/index.mjsbuilds its map on import, as before.output/unicode-*trees were regenerated locally and need to be committed and tagged in their own repositories as usual.Verification
npm testpasses (21 tests). New tests assert that everyregex.mjsmatches Regenerate's output for the same ranges, that every mappingsymbols.mjsmatches itscode-points.mjs, that representative Names lookups are correct, that the declaration layout andtypesVersionstargets are consistent, and thatindex.d.mtsmatches the keys exported byindex.mjs.symbols.mjsmaps identical, 1,699 mappingcode-points.mjsmaps identical (entry order included), and all 35 Names maps identical by hash.node16,bundlerandnode10resolution, and with TypeScript 4.7: imports ofregex.mjs,ranges.mjs,code-points.mjs,symbols.mjs, a mappingcode-points.mjs,Names/index.mjsand the package root resolve to the same types as before.