Use Svelte 4 components in React.
There are two ways to use svelte2react:
-
Manual: Turn Svelte components to React by using
Wrap.import { Wrap } from '@baykar/svelte2react'; import SvelteButton from './SvelteButton.svelte'; const WrappedSvelteButton = Wrap(SvelteButton); function render() { const [name, setName] = useState('Mark'); return <SvelteButtonX name={name} onclick={() => setName('Jack')} />; }
-
Automatic (π§WIPπ§): Automatically generate React versions of all Svelte compunents using unplugin-svelte2react and ts-plugin-svelte2react.
// β¨ Auto-generated React component! β¨ import { SvelteButtonX } from './SvelteButton.svelte'; function render() { const [name, setName] = useState('Mark'); return <SvelteButtonX name={name} onclick={() => setName('Jack')} />; }
Some Svelte and React features obviously won't make sense or won't work. To name some:
-
β Children
<WrappedSvelteComponent> <SvelteOrReactComponent /> </WrappedSvelteComponent>
-
β Svelte bindings
<script lang="ts"> let { value = $bindable() } = $props(); </script>
npm i @baykar/svelte2reactGiven an example Svelte component such as;
<!-- SvelteButton.svelte -->
<script lang="ts">
let { name = '' } = $props();
let count = $state(0);
console.log('Svelte component initialized');
</script>
<button onclick={() => count++}>
Hi {name}! You clicked me {count} times!
</button>You can use it in a React component like:
// App.tsx
import { useState } from 'react';
import { Wrap } from '@baykar/svelte2react';
import SvelteButton from './SvelteButton.svelte';
const WrappedSvelteButton = Wrap(SvelteButton);
export default function App() {
const [name, setName] = useState('Mark');
return (
<>
<label>
Your name:
<input value={name} onChange={(e) => setName(e.target.value)} />
</label>
My Svelte component:
<WrappedSvelteButton name={name} />
</>
);
}Now the rendered SvelteButton will react to changes to name (without re-initializing everytime name changes).
Important
Using svelte2react this way doesn't support tsc, svelte-check, or an alternative to these yet. Because of this, the automatic method is work-in-progress. Although type-checking while editing is supported.
npm i @baykar/svelte2react
npm i -D @baykar/unplugin-svelte2react @baykar/ts-plugin-svelte2reactAnd now setup your bundler and TypeScript configuration:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import svelte2react from '@baykar/unplugin-svelte2react/vite';
export default defineConfig({
plugins: [react(), svelte(), svelte2react()]
});(For other bundlers see unplugin-svelte2react README.)
Now in order to get ts-plugin-svelte2react to work, we need to do some editor specific configuration. The following part describes the steps for Visual Studio Code. Firstly, install TypeScript into your node_modules:
npm i -D typescriptThen set your workspace version of TypeScript the the instance in your node_modules:
// .vscode/settings.json
{
"js/ts.tsdk.path": "node_modules\\typescript\\lib",
"js/ts.tsdk.promptToUseWorkspaceVersion": true
}Make sure VS Code uses the TypeScript instance in your node_modules via: Ctrl+Shift+P β >TypeScript: Select TypeScript Version. (You may need to open a .js or .ts file first for this command to appear in the Command Palette.)
Important
Any contributor for your project should be told to configure their editor to use the project's TypeScript version in node_modules, when setting up their development environment for the first time (e.g., when they first clone your project repository) using the method above.
Given an example Svelte component such as: (same example as in Way 1)
<!-- SvelteButton.svelte -->
<script lang="ts">
let { name = '' } = $props();
let count = $state(0);
console.log('Svelte component initialized');
</script>
<button onclick={() => count++}>
Hi {name}! You clicked me {count} times!
</button>You can use it in a React component like:
import { useState } from 'react';
// β¨ Auto-generated React component! β¨
import { SvelteButtonX } from './SvelteButton.svelte';
export default function App() {
const [name, setName] = useState('Mark');
return (
<>
<label>
Your name:
<input value={name} onChange={(e) => setName(e.target.value)} />
</label>
My Svelte component:
<SvelteButtonX name={name} />
</>
);
}Make sure both your build and dev scripts work, otherwise the troubleshooting section may have a quick fix.
If you run into any issues, try these:
- Remove
svelte-check(ortsc) from build script. They aren't supported and there isn't an alternative for them yet. - Change plugin order in your bundler configuration, the
svelte2reactplugin should run aftersvelte. - Try setting
enforce: 'post'in your svelte2react bundler options. - Restart your editor and terminals.
Ctrl+Shift+Pβ>TypeScript: Select TypeScript Versionβ Select your workspace version.
There are two places to configure svelte2react:
- In your bundler configuration (
vite.config.js,webpack.config.js, etc...) - In your TypeScript configuration (
tsconfig.json)
// vite.config.ts
// Imports...
export default defineConfig({
plugins: [
react(),
svelte(),
svelte2react({
// π svelte2react bundler config here
})
]
});| Option | Type | Default | Description |
|---|---|---|---|
skipDependencyCheck |
boolean |
false |
Skips checking whether @baykar/svelte2react is listed in package.json. Safe to disable if it is already installed. |
include |
(string | RegExp) | (string | RegExp)[] |
[/\.svelte$/] |
Files to include. Accepts a glob string, RegExp, or an array of them. This filter is applied before internal .svelte filtering. |
exclude |
(string | RegExp) | (string | RegExp)[] |
[/node_modules/] |
Files to exclude. Accepts a glob string, RegExp, or an array of them. |
enforce |
'pre' | 'post' | undefined |
undefined |
Controls plugin execution order in supported bundlers (e.g. Vite). |
tsconfig |
string |
undefined |
Path to a custom tsconfig.json file. Useful for non-standard setups or when config is not in the project root. |
Note
TS configuration also affects bundler behaviour. Some options had to be placed there so that the TS Plugin can also reach them.
Note
You don't need to set include and exclude to only process .svelte files as that check is done later anyway. But setting them up so that non-.svelte files are filtered-out may improve bundler performance.
These are used as unplugin hook filters for file id, see Rolldown hook filter documentation (even if you aren't using Rolldown) for technical details.
// tsconfig.json
{
"compilerOptions": {
"plugins": [
{
"name": "@baykar/ts-plugin-svelte2react"
// π svelte2react TS config here
}
]
}
}| Option | Type | Default | Description |
|---|---|---|---|
prefix |
string |
'' |
Prefix added to generated export names. |
suffix |
string |
'X' |
Suffix added to generated export names. |
constant |
string |
undefined |
Overrides the export name entirely (ignores prefix and suffix). |
When prefix: '_' and suffix: 'ABC', a React component can be exported from comp.svelte like:
import { _CompABC } from './comp.svelte';When constant: 'Wrapped', that becomes:
import { Wrapped } from './comp.svelte';The Svelte component given to Wrap is wrapped in a Svelte component (defined in Wrapper.svelte) and a React component (defined in index.ts). These two communicate via a Svelte store pStoreRef.current that stores your components props.
In the example above, once setName is called:
-
Calling
setNametriggers a re-render ofApp. -
WrappedSvelteButtonis re-rendered because its parent (App) re-rendered -
pStoreRef.current.set(p)is called in index.ts.pStoreRef.currentis a Svelte store that stores the props forSvelteButton. -
pStoreRef.currentcalles its only subscriber, which is an instance of the Svelte componentWrapper, defined in Wrapper.svelte. -
Wrapperupdates the props passed to your component due to{...$pState}in Wrapper.svelte. ($pStateis whyWrapperautomatically subscribes topStoreRef.currentpreviously.)