svelte2tsx-component
v0.0.5
Published
svelte template to react component converter
Downloads
3
Readme
svelte2tsx-component
svelte(lang='ts'
) template to react component converter (PoC)
$ npm install svelte2tsx-component -D
# default css generator is @emotion/css
$ npm install react react-dom @types/react @types/react-dom @emotion/css -D
Concepts
- Generate Component Props Type from
script lang="ts"
, leaving TypeScript type information - Convert svelte's built-in functionality into an idiom on React with similar results
- Import
.svelte
transparently from React
API
import { svelteToTsx } from "svelte2tsx-component";
const code = "<div></div>";
const tsxCode = svelteToTsx(code);
with vite
// vite.config.ts
import { defineConfig } from "vite";
import { plugin as svelteToTsx } from "svelte-to-tsx";
import ts from "typescript";
export default defineConfig({
plugins: [svelteToTsx({
extensions: [".svelte"],
tsCompilerOptions: {
module: ts.ModuleKind.ESNext,
target: ts.ScriptTarget.ESNext,
jsx: ts.JsxEmit.ReactJSX,
}
})],
});
Examples
svelte template
<script lang="ts">
import { onMount } from "svelte";
export let foo: number;
export let bar: number = 1;
const x: number = 1;
let mut = 2;
onMount(() => {
console.log("mounted");
mut = 4;
});
const onClick = () => {
console.log("clicked");
mut = mut + 1;
}
</script>
<div id="x" class="red">
<h1>Nest</h1>
hello, {x}
</div>
<button on:click={onClick}>click</button>
<style>
.red {
color: red;
}
</style>
to tsx component (react
)
import { useEffect, useState } from "react";
import { css } from "@emotion/css";
export default ({ foo, bar = 1 }: { foo: number; bar?: number }) => {
const x: number = 1;
const [mut, set$mut] = useState(2);
useEffect(() => {
console.log("mounted");
set$mut(4);
}, []);
const onClick = () => {
console.log("clicked");
set$mut(mut + 1);
};
return (
<>
<div id="x" className={selector$red}>
<h1>Nest</h1>
hello, {x}
</div>
<button onClick={onClick}>click</button>
</>
);
};
const selector$red = css`
color: red;
`;
So you can use like this.
import React from "react";
import App from "./App.svelte";
import { createRoot } from "react-dom/client";
const root = document.getElementById("root")!;
createRoot(root).render(<App
name="svelte-app"
onMessage={(data) => {
console.log("message received", data)
}
} />);
(put App.svelte.d.ts
manually yet)
Transform Convensions
PropsType with export let
svelte
<script lang="ts">
export let foo: number;
export let bar: number = 1;
</script>
tsx
export default ({ foo, bar = 1 }: { foo: number, bar?: number }) => {
return <></>
}
PropsType with svelte's createEventDispatcher
svelte
<script lang="ts">
import {createEventDispatcher} from "svelte";
// Only support ObjectTypeLiteral (TypeReference not supported)
const dispatch = createEventDispatcher<{
message: {
text: string;
};
}>();
const onClick = () => {
dispatch('message', {
text: 'Hello!'
});
}
</script>
<div on:click={onClick}>
hello
</div>
tsx
export default ({
onMessage,
}: {
onMessage?: (data: { text: string }) => void;
}) => {
const onClick = () => {
onMessage?.({
text: "Hello!",
});
};
return (
<>
<div onClick={onClick}>hello</div>
</>
);
};
Expression in svelte template
<div id="myid"></div>
<div id={expr}></div>
<div id="{expr}"></div>
<div {id}></div>
<div {...params}></div>
onMount / onDestroy / beforeUpdate / afterUpdate
Convert to react's useEffect
Style
<span class="red">text</span>
<style>
.red: {
color: red;
}
</style>
to
// Auto import with style block
import { css } from "@emotion/css";
// in tsx
<span className={style$red}>text</span>
const selector$red = css`
color: red;
`;
Only support single class selector like .red
.
Not Supported these patterns.
/* selector combination */
.foo .bar {}
.foo > .bar {}
/* element selector */
div {}
/* global selector */
:global(div) {}
Unsupported features
- [ ]
style
property with expression- ex.
<div style="color: {color}"></div>
- ex.
<div style={obj}></div>
- ex.
- [ ]
class
property with expression- ex.
<div class="c1 {v}"></div>
- ex.
<div class={expr}></div>
- ex.
- [ ] Await Block
- [ ] Property Bindings
<input bind:value />
<svelte:options />
svelte
'ssetContext
/getContext
/tick
/getAllContexts
svelte/motion
svelte/store
svelte/animation
svelte/transition
svelte/action
<Foo let:prop />
- css:
:global()
(Checkboxed item may be supportted latter)
Currently, the scope is not parsed, so unintended variable conflicts may occur.
Basic Features
- [x] Module:
<script context=module>
- [x] Props Type:
export let foo: number
to{foo}: {foo: number}
- [x] Props Type:
export let bar: number = 1
to{bar = 1}: {bar?: number}
- [x] svelte:
onMount(() => ...)
=>useEffect(() => ..., [])
- [x] svelte:
onDestroy(() => ...)
=>useEffect(() => { return () => ... }, [])
- [x] svelte:
dispatch('foo', data)
=>onFoo?.(data)
- [x] svelte:
beforeUpdate()
=>useEffect
- [x] svelte:
afterUpdate()
=>useEffect
(omit first change) - [x] Let:
let x = 1
=>const [x, set$x] = setState(1)
- [x] Let:
x = 1
=>set$x(1)
; - [x] Computed:
$: added = v + 1;
- [x] Computed:
$: document.title = title
=>useEffect(() => {document.title = title}, [title])
- [x] Computed:
$: { document.title = title }
=>useEffect(() => {document.title = title}, [title])
- [x] Computed:
$: <expr-or-block>
=>useEffect()
- [x] Template:
<div>1</div>
to<><div>1</div></>
- [x] Template:
<div id="x"></div>
to<><div id="x"></div></>
- [x] Template:
<div id={v}></div>
to<><div id={v}></div></>
- [x] Template:
<div on:click={onClick}></div>
to<div onClick={onClick}></div>
- [x] Template:
{#if ...}
- [x] Template:
{:else if ...}
- [x] Template:
{/else}
- [x] Template:
{#each items as item}
- [x] Template:
{#each items as item, idx}
- [x] Template:
{#key <expr>}
- [x] Template: with key
{#each items as item (item.id)}
- [x] Template: Shorthand assignment
{id}
- [x] Template: Spread
{...v}
- [x] SpecialTag: RawMustacheTag
{@html <expr}
- [x] SpecialTag: DebugTag
{@debug "message"}
- [x] SpecialElements: default slot:
<slot>
- [x] SpecialElements:
<svelte:self>
- [x] SpecialElements:
<svelte:component this={currentSelection.component} foo={bar} />
- [x] Template: attribute name converter like
class
=>className
,on:click
=>onClick
- [x] Style:
<style>
tag to@emotion/css
- [x] Style: option for
import {css} from "..."
importer - [x] Plugin: transparent svelte to react loader for rollup or vite
- [x] Inline style property:
<div style="...">
to<div style={{}}>
TODO
- [ ] Template: Await block
{#await <expr>}
- [ ] Computed:
$: ({ name } = person)
- [ ] Directive:
<div contenteditable="true" bind:innerHTML={html}>
- [ ] Directive:
<img bind:naturalWidth bind:naturalHeight></img>
- [ ] Directive:
<div bind:this={element}>
- [ ] Directive:
class:name
- [ ] Directive:
style:property
- [ ] Directive:
use:action
- [ ] SpecialElements:
<svelte:window />
- [ ] SpecialElements:
<svelte:document />
- [ ] SpecialElements:
<svelte:body />
- [ ] SpecialElements:
<svelte:element this={expr} />
- [ ] SpecialTag: ConstTag
{@const v = 1}
- [ ] Directive:
<div on:click|preventDefault={onClick}></div>
- [ ] Directive:
<span bind:prop={}>
- [ ] Directive:
<Foo let:xxx>
- [ ] Directive: event delegation
<Foo on:trigger>
- [ ] SpecialElements:
<svelte:fragment>
- [ ] SpecialElements: named slots:
<slot name="...">
- [ ] SpecialElements:
$$slots
- [ ] Generator:
.d.ts
(<name>.svelte
with<name>.svelte.d.ts
) - [ ] Generator: preact
- [ ] Generator: qwik
- [ ] Generator: solid
- [ ] Generator: vue-tsx
Why?
Svelte templates are not difficult to edit with only HTML and CSS knowledge, but the modern front-end ecosystem revolves around JSX.
However, the modern front-end ecosystem revolves around JSX, and we think we need a converter that transparently treats Svelte templates as React components. I think so.
(This is my personal opinion).
Prior Art
- https://github.com/amen-souissi/svelte-to-react-compiler
LICENSE
MIT