Minor Changes
-
Support
lynx.createIntersectionObserverin Lynx for Web. (#3383) -
Load a hand-written Lynx XML markup card in the browser, by compiling it into a (#3404)
.web.bundlethere and then loading that.A markup card is not a kind of artifact. The decode worker already dispatches on
the eight header bytes it reads -{for a.jsontemplate, the magic header for
a bundle - and a markup card is what neither of those claims, so it costs the two
shapes that stream nothing to reach. From there it is handed toencodeLynxXML,
the same function@lynx-js/web-core/encodegives a build, which returns real
bundle bytes: magic header, version, and the five sections in the encoder's order.
Those bytes go straight back tohandleStream, so every section is read by the
reader that already existed. There is no markup decoding anywhere - not in the
worker, not on the main thread - and a markup card is not merely equivalent to a
built card, it is one by the time anything decodes it.Compiling in the browser is what #3589 made possible:
binary/encode's glue used
to load its wasm throughnode:fs, and is now generated with
wasm-bindgen --target bundler, which a bundler resolves on either platform.What this costs, measured
Both sides rebuilt from source -
npm run build:wasmthenrsbuild build, with
--forceso neither figure is a cache replay - becausebinary/is gitignored
and survivesgit switch, which has produced wrong numbers here before. Raw /
gzip -9.artifact origin/mainthis change delta binary/client/client_bg.wasm227,536 / 82,883 byte-identical 0 binary/client_legacy/…_bg.wasm183,444 / 74,949 byte-identical 0 eager client.js45,287 / 14,388 byte-identical 0 web-core-main-chunk.js159,621 / 33,074 byte-identical 0 web-core-worker-chunk.js15,135 / 6,001 byte-identical 0 worker chunk ( …-loader-thread.js)33,257 / 9,912 34,755 / 10,357 +1,498 / +445 web-core-markup-encoder.js(new)– 227,811 / 64,809 new, lazy encode wasm asset (new) – 167,689 / 55,689 new, lazy So a card that was built ahead of time pays +1,498 B raw / +445 B gzip, all of
it in the worker chunk, and nothing at all in the eager entry or the main chunk.
The four byte-identical rows are sha256 comparisons, not size comparisons.The laziness is load bearing rather than tidy:
TemplateManagerrequests the
worker withwebpackPrefetch,webpackPreloadandfetchPriority: "high", so a
static import would eagerly fetch all 395 kB for every card. Verified positively
and with a negative control onorigin/main, counting occurrences (a minified
chunk is one line, so a line count cannot tell 1 from 60):marker eager client.jsworker chunk main chunk markup chunk encode wasm asset name 0 0 0 1 css-treetoken names0 / 0 0 / 0 0 / 0 2 / 2 the XML parser's own message 0 0 0 2 All of these are 0 everywhere on
origin/main, including in the chunk that does
not exist there.encode_legacy_json_generated_raw_style_inforeads 2 in the
eager entry and 3 in the worker chunk on both sides - it is the client wasm's
own export, used bycssLoaderfor.jsonartifacts, and is not this change.Compiling itself is work that moved from a build into the browser, medians of 41
interleaved rounds: a 72 B stylesheet takes 0.42 ms, 872 B takes 0.89 ms, 9.1 kB
takes 7.4 ms and 26 kB takes 23.5 ms. Only markup cards pay it.Reviewer decisions this change deliberately leaves open
encodeLynxXMLwarns on the console unconditionally, and now does so at
runtime. It reports each at-rule the Lynx style format cannot carry. That was
written when the only caller was a build, which has no production runtime to
stay quiet for; the same code now runs in a browser. It is left exactly as it is
onorigin/mainso thatts/encode/keeps a zero diff, but gating it on a dev
build, or deduplicating it per at-rule name, are both reasonable and neither is
done here.- The published tarball grows by 397 kB, being the new markup chunk plus the
encode wasm, which rspack now also emits underdist/client_prod/static/wasm/.
That wasm is consequently present three times in the package - there, under
dist/encode_prod/static/wasm/since #3589, and underbinary/encode/. Nothing
here makes that worse than the pattern already in place for the client wasm, and
reclaiming it is afileschange that would alter what deep importers can
reach, so it is left out of this change. - If #3390 lands first, the two chunk figures above need re-measuring. It
reachescss-treedirectly where this change reaches it through
@lynx-js/css-serializer; the spec resolves to a singlecss-tree@3.2.1, so
rspack would either duplicate it into both lazy chunks or hoist it into a shared
one. Nobody has built the union yet, so no combined figure is quoted here.
Other limits worth knowing
@lynx-js/css-serializerbecomes a realdependencyrather than the optional
peer it was.dist/clientships as unbundled ESM, so theimportthe compiler
chunk performs is resolved by the consumer, and an optional peer nobody installs
would make a markup card fail to load in exactly the packaging that looks fine
on disk.- A corrupted bundle now reaches the markup path, because markup is what is
left when a response is neither a bundle nor JSON. Handing those bytes to the XML
parser would answerexpected '<lynx version="...">' root element, which points
the reader at a markup bug in a file that is not markup, so the two are told
apart first - before the compiler chunk is even fetched - on whether the content
begins a tag at all. Bytes that do not keep the diagnosis they always had,
Invalid Magic Header, now carrying the eight header bytes that failed to match;
a document that does gets the XML parser's own message and offset. A response
shorter than 8 bytes is still rejected by the header read, exactly as before. @media,@supportsand@layerare dropped, as they already were when
building a markup card into a bundle: Lynx's style format has no rule kind for a
conditional group, so they are not Lynx features on any platform. Same for
@importwith a URL.- The
handleMarkuprecursion is one level deep and cannot loop:encodewrites
the magic header at offset 0 unconditionally, so the secondhandleStreamtakes
the binary branch. Were that untrue, the bytes would fail the "begins a tag"
check and the recursion would end in a thrown error rather than a cycle.
Patch Changes
-
Build
binary/encodewithwasm-bindgen --target bundlerand publish@lynx-js/web-core/encodeas an rslib bundle, so one set of artifacts serves both Node and the browser. (#3589)Why
binary/encodeused to be generated with--target experimental-nodejs-module, whose glue callsreadFileSync('node:fs')at module scope. That made@lynx-js/web-core/encodeimportable from Node only. Thebundlertarget instead emits an "async wasm module" glue (import * as wasm from './encode_bg.wasm'), which a bundler can resolve for either platform.What changed in the published package
./encodenow resolves todist/encode_prod/index.js(bundled by rslib, like./serveralready was) instead of the unbundleddist/encode/index.jsemitted bytsc. The wasm is emitted as a build asset underdist/encode_prod/static/wasm/.- Bundling is what keeps the import clean: consuming the
bundlerglue directly from Node would work only on Node 22 or newer, and would printExperimentalWarning: Importing WebAssembly module instanceson every build. Letting rslib resolve the wasm at web-core build time avoids both. encode_bg.wasmis byte-for-byte unchanged; only the JavaScript glue differs. Thebinary/encode/*.d.tstype declarations are identical under both targets.
Compatibility
- No API change.
encode()andencodeCSS(cssMap): Uint8Arraykeep their synchronous signatures. The wasm initialization becomes a single module-levelawaitinside the bundle, which is already accommodated by theawait import('@lynx-js/web-core/encode')that consumers use. Encoded output is byte-identical to the previous release. @lynx-js/css-serializerstays an external dependency of the bundle and is not inlined.- Note on Node versions:
@lynx-js/web-core/encodedoes not work on Node 20 and did not work there before this change either. Theencodewasm is optimized withwasm-opt --all-features, and Node 20's engine rejects it withCompileError: Unknown heap type -14regardless of which glue is used. This is a pre-existing limitation that this change neither introduces nor fixes; the effective floor for this entry point is Node 22. - The tarball grows by roughly the size of the encode wasm, because
binary/encode/encode_bg.wasmis still shipped alongside the copy that rslib emits intodist/encode_prod/.
-
Prevent event dispatch failures from escaping through the WebAssembly boundary. (#3356)
-
Updated dependencies [
9c2be3e,d671851,d671851]:- @lynx-js/css-serializer@0.1.9
- @lynx-js/web-elements@0.12.9
- @lynx-js/web-worker-rpc@0.25.0