React and Vue on one page
You'll put a React island and a Vue island in the same document, have them share one piece of state, then navigate between a page whose island is Vue and a page whose island is React while the layout stays exactly where it is. At the end you'll measure what it costs in download size.
Before you start
Install from crates.io. You need no Node and no package manager.
cargo install snapfire_compiler
cargo install snapfire_fsr_cli
cargo install snapfire_vue
fsr --version
Every command and screenshot on this page was captured with fsr 0.x.
Add the second framework#
Start from a React app and give it a second direction. fsr use adds one without touching the other:
One row goes into shop/app/importmap.json, for Vue itself. Everything React is left alone:
snapfirec hands .vue files off to snapfirec-vue, which is the third cargo install in the box above. 200 covers what it does with them.
Share state between them#
Declare the state once, in plain TypeScript that neither framework knows about:
;
;React takes it as state:
;
;
Vue takes it as a reactive holder. Remember it isn't a ref, so you keep the .value:
Click a row in the Vue table and the React masthead re-renders with the new symbol. Neither component imports the other or knows what it is written in. Both are rendered on the server with the same value as a prop, so the first paint already agrees with the store before any script runs.
Place the islands#
Both islands start from the same symbol. The layout's loader returns it and gives the store key its starting value:
;
;The key goes under defaults because the visitor owns it. A store seed is authoritative: every navigation seeds it again and puts ACME back over the row the visitor picked. A defaults value only holds the key while nothing in the browser has written it.
The layout holds the React island:
;
;
;
;
The page's loader returns the rows and the same starting symbol for the first paint:
;
The page holds the Vue island, placed from a React template:
;
;
;
Build it and both adapters turn up in the registry:
;
;
;
A placement carries a module id and the registry says which adapter that id uses. The plan, the payload and the renderer never look past the id, so the registry is the only place the two frameworks differ.
Compare the markup#
The difference shows up in curl:
Both markers carry the markup the server rendered and both frameworks hydrate over it. Each was written the way that framework's own server renderer writes it. React's <!-- --> separates the text from the value. Vue's <!--[--> and <!--]--> bracket the v-for list.
A
.vuecomponent holding something the build can't read, a<Teleport>to a class selector say, arrives as an empty marker and Vue mounts it fresh; the report says which path a component took. 200 covers that. Nothing in this tutorial depends on which path a component takes.
Navigate between them#
Add a /news page whose island is React, under the same layout. Now ask for both payloads and look at the segment tree:
d is a digest of each segment's markup. The shell's is the same on both pages. The layout's moved, because Link marks the current page with aria-current="page" and that moved from Board to News. So when you click News the navigator updates the layout's markup and keeps every island the new markup still places, by its region key. The React masthead keeps its DOM and its state. The page segment underneath is replaced. It goes from a segment holding a Vue island to one holding a React island without consulting either framework. It just applies segments by key and hands each region's props to whatever is mounted there.
Click back to the board and your selected row is still selected, because the store kept its value.
Measure the cost#
Measured from the files the board page loads under RELEASE_ENV=production, gzip at level 9:
| raw | gzip | |
|---|---|---|
| React, with its adapter | 248.9K | 77.8K |
| Vue, with its adapter | 136.5K | 52.6K |
| the fsr client | 92.0K | 31.7K |
| this application | 3.7K | 2.0K |
| everything the page loads | 481.1K | 164.2K |
React and Vue together are about 130K compressed before any of your own code. A page that uses both loads both on every visit.
Mixing frameworks is only worth that cost in these cases:
- A migration. You move off one framework island by island instead of in one jump, with both running until the last one is gone.
- Two teams, one page. One group owns the shell and another owns a panel in it, so neither has to switch frameworks first.
- Zero-runtime pieces. A custom element needs no framework, so dropping one into a React page costs what 210 measures rather than what this table does.
Otherwise use one framework.
Next up: 400. Test a loader.