feat(lynx): merge @rozenite/lynx-dev in and inject the device runtime - #489
Merged
Merged
Conversation
Setting Rozenite up for Lynx cost two installs and a hand-written import in the app's entry point. That import had to be `__DEV__`-guarded or the dispatcher shipped to production, and the README offered the unguarded form as an equally valid alternative. `@rozenite/lynx` now carries both halves: `.` stays the device runtime, `./rspeedy` is the plugin formerly published as `@rozenite/lynx-dev`. The plugin injects the runtime itself via `source.preEntry`, from inside a `setup` that `apply: 'serve'` already keeps out of `rspeedy build` — so the production leak is structurally impossible rather than documented against. `@rozenite/lynx-dev` stays as a deprecated re-export shim. Closes #488
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.
Description
Rozenite for Lynx is now one package, and the device runtime is injected rather than hand-imported.
@rozenite/lynxgains a./rspeedyexport carrying the plugin previously published as@rozenite/lynx-dev. Its.export — the device runtime — is unchanged.source.preEntry, so there is nothing to add tosrc/index.tsxany more.@rozenite/lynx-devbecomes a deprecated re-export shim over@rozenite/lynx/rspeedy. Existing imports keep working.Setup drops from two installs plus a hand-written entry-point import to one install and one line in
lynx.config.ts.Related Issue
Closes #488
Context
The hand-written import was the thing worth removing. It had to be
__DEV__-guarded or the dispatcher shipped to production, andpackages/lynx/README.mdoffered the unguardedimport '@rozenite/lynx';as an equally valid alternative — which it was not. Measured onapps/playground-lynxbefore this change: unguarded 424.1 kB,__DEV__-guarded 420.6 kB, no import at all 420.6 kB. The guarded build was byte-identical to no import, so the guard worked; without it ~3.5 kB shipped andObject.defineProperty(globalThis, '__FUSEBOX_REACT_DEVTOOLS_DISPATCHER__', …)actually ran in the production app.Injection removes that failure mode structurally rather than documenting against it. The injection sits inside the plugin's
setup, andapply: 'serve'already keepssetupfrom running duringrspeedy build— Rsbuild's plugin initializer mapsaction: 'build'to an expectedapplyof'build'and skips anything that does not match. There is no code path from the injection site to a production bundle.apply: 'serve'and theenableddefault are unchanged; this change depends on both.Three things were verified rather than assumed before the design was settled:
source.preEntrysurvives rspeedy's config pipeline and reaches the bundle.__BACKGROUND__dead-code elimination, despite apreEntrymodule having no issuer for the layer-matched loader rules to key off. This was the one thing that could have sunk the approach, so it was probed directly.preEntrymodules ahead of the app's entry, which satisfies the "must run before any plugin'suseRozeniteDevToolsClient" ordering the docs previously pushed onto the user..stays the device runtime rather than becoming the plugin. Inverting them would break every existingimport '@rozenite/lynx', and keeping.as the runtime matches the convention already in the repo, where a plugin exports.for device code and a subpath for its bundler half.The merge makes
express,wsand@lynx-js/debug-router-connectordependencies of the package an app installs. They never reach the bundle — nothing reachable from.imports them — so the cost is install size only.@rozenite/lynx's README no longer claims zero runtime dependencies.RUNTIME_ENTRYresolves by package self-reference through the package's ownexportsmap, so it needs nonode_moduleslookup and behaves the same symlinked or installed. It resolves therequirecondition, yieldingdist/index.cjs; that is fine, since the value is only ever handed to Rspack as an entry path.Two
vite-plugin-dtslimitations surfaced while giving one package two entries with different build shapes, and are documented inline inpackages/lynx/vite.rspeedy.config.ts:lib.fileNameis ignored underssr: true, androllupTypes: truetakes its output path frompackage.json's top-leveltypesfield rather than the build's own entry, so it silently emits nothing for a second entry. The rspeedy entry emits un-rolled per-module declarations instead.Testing
pnpm checks:affected— 96/96 tasks passed across 27 affected packages.pnpm test:affected— 56/56 tasks passed.@rozenite/lynxruns 8 test files / 99 tests, which includes all 7 suites moved over from@rozenite/lynx-dev(bridge ×4, server ×2, transport ×1); none were dropped from the run.pnpm release:plan— changeset registers.apps/playground-lynx:NODE_ENV=production npx rspeedy build→ 420.6 kB with 6 occurrences ofFUSEBOX_REACT_DEVTOOLS_DISPATCHERand 0 ofsetupRozenite— an exact match for the no-Rozenite baseline, confirming the plugin injects nothing into a production build.apps/playground-lynx: startednpx rspeedy dev, fetchedmain.lynx.bundle, and confirmed the runtime is present and installed —setupRozenite×2 (one per ReactLynx layer), and the dispatcher install site appears asvar r = ()=>{ globalThis.__FUSEBOX_REACT_DEVTOOLS_DISPATCHER__ == null && Object.defineProperty(...) }; r();, i.e. executing at module scope.__BACKGROUND__does not survive into the output, confirming per-layer DCE ran on the injected module.