🌳 perf: make the library actually tree-shakable
Importing one decorator pulled in the message and validator of all 68 — 4,909 bytes instead of 1,837 through esbuild, 4,823 instead of 1,818 through webpack. Nothing failed and nothing warned. The library was simply about three times heavier than it needed to be in every consumer's bundle. Every rule is a top-level call: `export const IsString = rule(...)`. rollup proves such a call side-effect-free by reading the factory, which is why it was already emitting 1,823 bytes — and why a single-bundler measurement would have shown no problem at all. esbuild and webpack do not do that analysis and keep the call. Thirty declarations now carry /*#__PURE__*/, which tsc preserves into the ESM emit, and all three bundlers now land within 20 bytes of each other. Measured, minified, esbuild / rollup / webpack: flattenErrors 287 / 292 / 291 one decorator 1837 / 1823 / 1818 validateSync 3722 / 3554 / 3823 toPlainSync 7744 / 7769 / 7832 toInstanceSync 7900 / 7942 / 7956 a typical DTO 10395 / 10402 / 10372 everything 26266 / 25671 / 26879 The serializer and deserializer drop independently — read JSON and you do not pay for writing it. Both mapping entry points keep the validator, because `validate` defaults to true and that is a real reference rather than a missed optimisation. The annotations are a promise to the bundler, so I checked the three factories they cover: rule, pattern and affix each return a closure and touch nothing outside themselves. A false promise here would mean silent deletion in someone else's production build. src/treeshake.test.ts pins the property. It asserts content rather than only bytes — it names the rules that must not appear — and one case asserts everything IS present when everything is used, so a "shaken" result cannot come from a bundle that failed to build. That mattered: two earlier passes at this measurement reported a clean sweep of shaken symbols because rollup had failed to resolve its entry and grep was reading missing files as absence. Strip the annotations and the test fails with `"must be a latitude" should have been shaken out`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
This commit is contained in:
+6
-4
@@ -329,10 +329,12 @@ await esbuild.build({
|
||||
## A single file, no bundler
|
||||
|
||||
`cereale/min` is the whole library flattened into one minified ES module (25.5 KB, 8.6 KB
|
||||
gzipped) for import maps, `<script type="module">`, Deno and Workers. Bundler users should keep
|
||||
the default entry point: measured through esbuild and through rollup + terser, the two produce
|
||||
consumer bundles within a couple of hundred bytes of each other and tree-shake identically, and
|
||||
the per-module build keeps readable stack traces.
|
||||
gzipped) for import maps, `<script type="module">`, Deno and Workers.
|
||||
|
||||
**If you are using a bundler, do not use it.** It is the whole library in one file, so nothing
|
||||
can be dropped from it. The default entry point tree-shakes — one decorator costs about 1.8 KB
|
||||
against 26 KB for everything — and produces a smaller result in any real application. See
|
||||
[Bundle size](README.md#bundle-size).
|
||||
|
||||
```html
|
||||
<script type="importmap">
|
||||
|
||||
Reference in New Issue
Block a user