← Back to Blog

How we checked every bundler: the verification matrix

·8 min read

The bundler wiring rests on a specific claim: tsconfig paths is enough for TypeScript and webpack, but not for Vite, SvelteKit, or Next.js Turbopack. Before shipping the alias injection we had to prove that claim, not just assert it. This post is the proof: how we checked each tool, what the test suite pins down, and why the wiring is per client project rather than per framework.

The claim, as a matrix

The deciding variable is not the bundler alone, it is the bundler and where the import originates. A project’s own source files get path-rewritten almost everywhere. The interesting row is the last one: an import issued from inside node_modules, which is exactly where the adapters live.

                 import from YOUR source          import from node_modules
                                                 (the adapter packages)

TypeScript       ✓ paths honored                 ✓ paths honored, then erased
                                                 (type-only, no runtime resolution)

webpack-based    ✓ paths honored                 ✓ paths honored
(Next webpack    (No alias needed)               (Next webpack mode, CRA)
 mode, CRA)

Vite             ✓ paths honored                 ✗ skipped → resolve.alias
SvelteKit        ✓ paths honored                 ✗ skipped → resolve.alias (Vite)
Next Turbopack   ✓ paths honored                 ✗ skipped → resolveAlias

The webpack row is the one that reads counter-intuitively. Vite and Turbopack deliberately treat node_modules as opaque, pre-resolved code and skip tsconfig rewriting there for performance and predictability. Webpack-based tooling applies the paths mapping uniformly, including to imports issued from inside dependencies, so Next.js in webpack mode and Create React App need no alias at all.

How we checked the mechanics: the unit suite

The matrix is a design claim. The splice itself is a mechanical claim: given this config file text, the injection inserts exactly the alias, leaves everything else intact, and is a no-op on re-run. That is what test_bundlers.py pins down, one failure mode per test:

test_no_config_file_is_a_noop                 → True, nothing written
test_vite_defineconfig_gets_alias             → alias inserted, plugins preserved
test_vite_config_with_braces_in_strings       → strings never mistaken for objects
test_vite_already_aliased_is_idempotent       → re-run leaves the file untouched
test_vite_without_defineconfig_returns_false  → warning path, file untouched
test_next_export_default_gets_alias           → resolveAlias into nextConfig
test_next_const_object_gets_alias             → const nextConfig = { ... } form too
test_next_module_exports_object_returns_false → unsupported shape, warning path
test_next_already_aliased_is_idempotent       → re-run is a no-op
test_ts_config_takes_precedence_over_js       → next.config.ts wins over .js

Three of these are worth calling out. The braces-in-strings test feeds a config containing {"x": 1} and a proxy string, and asserts the mini tokenizer never mistakes a brace inside a string for the config object, the exact bug class the parser post digs into. The idempotency tests assert that regenerating a hundred times produces the same file as the first time. And the .ts-over-.js precedence test mirrors what Next.js itself does when both config files exist.

How we checked the matrix: the example apps

Unit tests prove the splice; they do not prove the matrix. For that, the repo carries twelve example applications, FastAPI, Django, and Flask, each paired with Next.js, React (Vite), Vue (Vite), and SvelteKit:

server         client
----------------------------------------------
FastAPI        Next.js · React · Vue · Svelte
Django         Next.js · React · Vue · Svelte
Flask          Next.js · React · Vue · Svelte

Each example is a real client directory. pyrpc dev generates __pyrpc.ts, writes the tsconfig paths entry, and injects the bundler alias where the matrix says one is needed. Building each client is then a live check of one matrix cell: the Vite/SvelteKit/Turbopack projects only link if the alias landed, and the Next.js webpack-mode build only works if the absence of an alias is harmless.

We did it for the adapters too, because it had to be

The wiring is not a per-framework special case. It is per client project, and the reason is where the unresolved import actually lives. The adapter packages (@pyrpc/react, @pyrpc/next, @pyrpc/vue, @pyrpc/svelte) sit inside node_modules and import @pyrpc/types as a value. Every consumer project (vanilla, React, Vue, SvelteKit, or Next.js) is a client_dir that goes through the same code path in cli.py: generate, configure tsconfig, configure bundler. So the four adapter projects in the matrix above are not extra work; they are the same loop running on four different config shapes (which is exactly why the loop exists.

The safety net under the whole check

The matrix has a fourth row hidden underneath: unconfigured. If no known config file exists, or a known one cannot be edited, configure_bundler returns False and the CLI prints an exact remediation hint instead of failing silently, the fail-loud contract described in fail loud when unconfigurable. The verification matrix is not the only thing standing between a developer and a broken build; it is the thing that makes the failure message trustworthy.

Further reading