Repository navigation
perf(build): free 2,972 gzip bytes in main.js for the v3 redesign - #439
Merged
Merged
Conversation
Svelte 5's runtime uses private fields and methods. At the es2020 target the bundler lowers each one to WeakMap helpers and every class field to a defineProperty call. Emitting ES2022 keeps them native and frees 1,351 gzip bytes (90,884 to 89,533). Saving settings already needs structuredClone (Chromium 98, Safari 15.4), which postdates ES2022 class fields, private methods and logical assignment, the only syntax this adds. Desktop installers from 1.1.9 run Electron 21, and the iOS app requires iOS 15.6.
PodNotes mounts its components and never hydrates server-rendered markup, so Svelte's hydrating flag never becomes true. Svelte restores the flag from values it read earlier, which the minifier cannot prove are false, so every hydration branch shipped. A build plugin pins the flag to a constant and fails the build if Svelte changes its shape. Frees 1,194 gzip bytes (89,533 to 88,339). Svelte 5.57.1 audit: hydrate() is the only unconditional set_hydrating(true), and the bundle does not include it. Every other call restores a value read from the flag, or runs inside if (hydrating).
Svelte adds its major version to window.__svelte.v so tools can see which versions a page runs. Nothing in Obsidian reads it. Frees 27 gzip bytes (88,339 to 88,312).
Every scoped element and selector carries the component's CSS hash class, 324 times in the bundle. A pn- prefix instead of svelte- saves 4 bytes each and keeps PodNotes' style ids apart from other Svelte plugins'. Frees 70 gzip bytes (88,312 to 88,242).
Terser inlines single-use functions and variables that Oxc keeps, and Oxc then mangles and compresses its output. Either minifier alone gives about the same size; together they free 1,712 gzip bytes (88,242 to 86,530). pure_getters stays off because reading a Svelte state property subscribes to it. Terser's own mangling is off because Oxc mangles afterwards, and mangling twice compressed slightly worse. package-lock.json gains only terser and its five dependencies; npm 10.9 dropped unrelated libc fields, which were restored.
Safari 15 mis-scopes a class field initializer that references an outer variable when the initializer contains a parenthesized expression (WebKit bug 236843, fixed in Safari 16). Obsidian evaluates plugins inside a function, so every class in main.js is affected, and the iOS app still supports iOS 15.6. Today's bundle has no such initializer, but an es2022 target would emit one silently after any future change. It would also keep class static blocks, which need Safari 16.4. Targeting Chromium 98 and Safari 15.6 lets the compiler lower class fields and private members for Safari 15. Saving settings already needs structuredClone, so Chromium 98 is the Android floor. This gives back 1,163 gzip bytes (88,881 to 90,044 after merging master). The hydration transform now runs only in production builds, because it returns no source map and development builds keep theirs.
Vite drops legal comments once build.minify is set, so running terser removed the Fuse.js Apache-2.0 notice that master ships. Keeping legal comments restores it for 135 gzip bytes (90,044 to 90,179). The hydration plugin now fails the build when Svelte moves its hydration module, instead of silently shipping the hydration code again.
Lowering class fields for Safari 15 depends on Oxc's compat table, so a toolchain bump or an es2022 target could ship them to the iOS 15.6 app again. The build now parses main.js as ES2021 with acorn, already in the lockfile, and fails before writing it otherwise. The Oxc minifier shares the build target, so neither producer can raise the syntax. The Vite banner is gone: it is a plain comment, so production builds never shipped it. The production bundle is byte-identical.
Deploying podnotes with
|
| Latest commit: |
d438bf7
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://ea9579de.podnotes.pages.dev |
| Branch Preview URL: | https://perf-bundle-room.podnotes.pages.dev |
Safari 15 also lacks regular expression lookbehind, which compilers cannot lower, and an ES2021 parse accepts it. The floor check now rejects it and names Safari 15 in its error. A tooling test builds a class field through Vite and checks that the build fails before main.js is written, so moving the check to a later hook fails the test. It lives with the other tooling tests because Vite reads window while loading, which the unit suite forbids.
Obsidian's Android app sets no WebView minimum, and its own code parses as ES2020. The chrome98 target emitted ES2021 logical assignment, which V8 8.4 rejects, so PodNotes would fail to load where master loads. The structuredClone dependency only breaks saving, not startup, so it is no floor. The target is es2020 again, which also keeps class fields lowered for Safari 15. The syntax floor check parses main.js as ES2020 and still rejects regex lookbehind. This gives back 107 gzip bytes.
This was referenced Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PodNotes users see no change. The production
main.jsshrinks from 96,076 to 93,104 gzip bytes, and from 301,276 to 292,929 raw bytes, against master921be0d(npm run size, gzip level 9). Master sits 3,924 bytes under the 100,000-byte gate, and the UI0 shell (#434) alone adds about 5,600, so the v3 redesign needs this room.Only build configuration changes. The output syntax stays ES2020, as on master. Each row shows what removing that cut alone from the final configuration costs.
hydratingoutsidehydrate()set_hydrating(true)in Svelte 5.57.1; the build fails if the module changes shape or movespn-scoped CSS prefix instead ofsvelte-svelte-<hash>classes, which change with every style edit anywaywindow.__svelteversion registrationTerser alone and Oxc alone produce almost the same size. Run in sequence, terser inlines single-use functions and variables that Oxc keeps, and Oxc then mangles and compresses the result.
pure_gettersstays off, because reading a Svelte state property subscribes to it. Vite drops legal comments oncebuild.minifyis set, solegal: truekeeps the Fuse.js Apache-2.0 notice that master ships.PodNotes mounts its components and never hydrates server-rendered markup. Svelte restores its
hydratingflag from values it read earlier, so the minifier cannot prove the flag is always false.scripts/svelte-without-hydration.tspins it in production builds, and fails the build if Svelte moves or reshapes the flag.New dev dependencies:
terser, andacorn, which the lockfile already had.package-lock.jsongains only terser and its five dependencies. The unused Vite banner is gone; production bundles never carried it.Syntax floor
The target stays
es2020, now shared with the Oxc minifier so it cannot raise the syntax either. A new build check,scripts/syntax-floor.ts, parses the finalmain.jsas ES2020 with acorn, rejects regular expression lookbehind, and fails before writing the file otherwise. A tooling test builds a class field through Vite and checks that nomain.jsis written.app.jsparses as ES2020. Master and this branch'smain.jsboth parse on V8 8.4 (Node 14.21.3). An ES2021 target emitted logical assignment (??=), which V8 8.4 rejects, so this branch keeps ES2020.Composition against master
921be0dRaw bytes and marginal gzip bytes (the gzip size of the whole bundle minus the bundle without that part).
srcoutsidesrc/uisrc/uisrc/ui)Room left for later
legacy_mode_flagbecomes false and its branches can go.Verification
Node 22.23.2, npm 10.9.8, Linux. Head
d438bf7, merged with master921be0d.npm run lint,npm run format:check,npm run typecheck,npm run build,npm run test(107 files, 1,980 tests),npm run test:tooling(11 tests),npm run docs:build: all pass.npm run size: raw 292,929 of 330,000; gzip 93,104 of 100,000.npm run test:e2eon Obsidian 1.14.4: 13 files, 89 tests passed. An earlier head of this branch also passed on Obsidian 1.13.7, theminAppVersionfloor.??=when the target is raised, and its tests fail when it parses at ES2021.b11c8cb, against mastere3b90c1), in isolated Obsidian 1.14.4 on fresh vaults with the fixture feed:obsidian://podnotesURI): results identical.app.emulateMobile(true): PodNotes loads, its settings tab opens, the episode plays and pauses, and the results match master.dev:errorsstayed empty in every run.edb9447, 4.0 and 3.5 ms (8 rounds, p = 0.32). No measurable change. Master's own cold load rose from about 4 ms to about 12 ms when fix(feeds): keep private subscriptions working across devices #428 merged, which this PR does not touch.Release impact
None. No settings, storage, API, or URI change.
Docs at release
None.
Note
Shrink
main.jsby 2,972 gzip bytes with Terser, hydration removal, and ES2020 syntax floorfalseand no-ops its setter, so minification can drop hydration branches; the build fails if Svelte's module shape changesgenerateBundleplugin that fails the build when any chunk uses syntax above the ES2020 / Safari 15 floor (e.g. class fields, regex lookbehind)build.minify), remove console calls, mangle names, disable Svelte version disclosure, and change Svelte CSS hash prefixes fromsvelte-topn-acornandterserare now declared dependenciespn-prefix instead ofsvelte-, the custom bundle banner is gone, and production hydration code is removed; any out-of-tree code matching onsvelte-class names or the banner breaksMacroscope summarized d438bf7.