Skip to content

Commit 3d35ed3

Browse files
committed
[demos] Svelte hello world
1 parent 7a1163e commit 3d35ed3

7 files changed

Lines changed: 342 additions & 46 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<nav><ul><li><a href="/">TinyBase</a></li><li><a href="/demos/">Demos</a></li><li><a href="/demos/hello-world/">Hello World</a></li><li><a href="/demos/hello-world/hello-world-v5/">Hello World v5</a></li></ul></nav><section class="s1" id="/demos/hello-world/hello-world-v5/" data-id="HW6"><h1>Hello World v5</h1><iframe srcdoc="&lt;html&gt;&lt;head&gt;&lt;script type=&quot;importmap&quot;&gt;{
2+
&quot;imports&quot;: {
3+
&quot;tinybase&quot;: &quot;https://esm.sh/tinybase@8.1.0-beta.5&quot;,
4+
&quot;tinybase/ui-svelte&quot;: &quot;https://esm.sh/tinybase@8.1.0-beta.5/ui-svelte&quot;,
5+
&quot;svelte&quot;: &quot;https://esm.sh/svelte@&quot;
6+
}
7+
}&lt;/script&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;script type=&quot;module&quot;&gt;const files = {&quot;src/App.svelte&quot;:&quot;\u003cscript&gt;\n import {CellView, Provider} from &#x27;tinybase/ui-svelte&#x27;;\n\n let {store} = $props();\n\u003c/script&gt;\n\n\u003cProvider {store}&gt;\n \u003cCellView tableId=\&quot;t1\&quot; rowId=\&quot;r1\&quot; cellId=\&quot;c1\&quot; /&gt;\n\u003c/Provider&gt;\n&quot;,&quot;index.html&quot;:&quot;\n\u003cscript type=\&quot;importmap\&quot;&gt;\n {\n \&quot;imports\&quot;: {\n \&quot;tinybase\&quot;: \&quot;https://esm.sh/tinybase@8.1.0-beta.5\&quot;,\n \&quot;tinybase/ui-svelte\&quot;: \&quot;https://esm.sh/tinybase@8.1.0-beta.5/ui-svelte\&quot;,\n \&quot;svelte\&quot;: \&quot;https://esm.sh/svelte@\&quot;\n }\n }\n\u003c/script&gt;\n&quot;,&quot;src/index.less&quot;:&quot;\n@font-face {\n font-family: Inter;\n src: url(https://tinybase.org/fonts/inter.woff2) format(&#x27;woff2&#x27;);\n}\n\nbody {\n align-items: center;\n display: flex;\n font-family: Inter, sans-serif;\n letter-spacing: -0.04rem;\n height: 100vh;\n justify-content: center;\n margin: 0;\n}\n&quot;,&quot;src/main.js&quot;:&quot;import { createStore } from \&quot;tinybase\&quot;;\nimport { mount } from \&quot;svelte\&quot;;\nimport App from \&quot;./App.svelte\&quot;;\nconst store = createStore();\nconst setTime = () =&gt; {\n store.setCell(\&quot;t1\&quot;, \&quot;r1\&quot;, \&quot;c1\&quot;, (new Date()).toLocaleTimeString());\n};\nsetTime();\nmount(App, { target: document.body, props: { store } });\nsetInterval(setTime, 1e3);&quot;};
8+
const svelteUrl = &quot;https://esm.sh/svelte@&quot;;
9+
const css = &quot;@font-face{font-family:Inter;src:url(https://tinybase.org/fonts/inter.woff2) format(&#x27;woff2&#x27;)}body{align-items:center;display:flex;font-family:Inter,sans-serif;letter-spacing:-0.04rem;height:100vh;justify-content:center;margin:0}&quot;;
10+
const cssPaths = new Set();
11+
const moduleUrls = new Map();
12+
const style = document.createElement(&#x27;style&#x27;);
13+
style.textContent = css;
14+
document.head.appendChild(style);
15+
16+
const getSvelteUrl = (specifier) =&gt; {
17+
const match = svelteUrl.match(/^https:\/\/esm\.sh\/svelte@([^/?#]+)/);
18+
return match == null
19+
? &#x27;https://esm.sh/&#x27; + specifier
20+
: &#x27;https://esm.sh/&#x27; + specifier + &#x27;@&#x27; + match[1];
21+
};
22+
23+
const resolvePath = (from, relative) =&gt;
24+
new URL(relative, &#x27;https://demo.local/&#x27; + from).pathname.slice(1);
25+
26+
const rewriteImports = async (code, from) =&gt; {
27+
let rewritten = &#x27;&#x27;;
28+
let lastIndex = 0;
29+
const regex = /(?:\bfrom\s*|\bimport\s*)([&#x27;&quot;])([^&#x27;&quot;]+)\1/g;
30+
for (const match of code.matchAll(regex)) {
31+
const index = match.index ?? 0;
32+
const [full, , specifier] = match;
33+
let replacement = specifier;
34+
if (specifier.startsWith(&#x27;./&#x27;) || specifier.startsWith(&#x27;../&#x27;)) {
35+
replacement = await buildModule(resolvePath(from, specifier));
36+
} else if (specifier == &#x27;svelte&#x27; || specifier.startsWith(&#x27;svelte/&#x27;)) {
37+
replacement = getSvelteUrl(specifier);
38+
}
39+
rewritten +=
40+
code.slice(lastIndex, index) + full.replace(specifier, replacement);
41+
lastIndex = index + full.length;
42+
}
43+
return rewritten + code.slice(lastIndex);
44+
};
45+
46+
const compilerPromise = import(getSvelteUrl(&#x27;svelte/compiler&#x27;));
47+
48+
const buildModule = async (path) =&gt; {
49+
const existing = moduleUrls.get(path);
50+
if (existing != null) {
51+
return existing;
52+
}
53+
const promise = (async () =&gt; {
54+
let code = files[path];
55+
if (code == null) {
56+
throw new Error(&#x27;Missing file: &#x27; + path);
57+
}
58+
if (/\.svelte$/.test(path)) {
59+
const {compile} = await compilerPromise;
60+
const compiled = compile(code, {filename: path, generate: &#x27;client&#x27;});
61+
if (compiled.css?.code != null &amp;&amp; !cssPaths.has(path)) {
62+
style.textContent += compiled.css.code;
63+
cssPaths.add(path);
64+
}
65+
code = compiled.js.code;
66+
}
67+
code = await rewriteImports(code, path);
68+
return URL.createObjectURL(
69+
new Blob([code], {type: &#x27;text/javascript&#x27;}),
70+
);
71+
})();
72+
moduleUrls.set(path, promise);
73+
return promise;
74+
};
75+
76+
buildModule(&quot;src/main.js&quot;)
77+
.then((url) =&gt; import(url))
78+
.catch((error) =&gt; {
79+
document.body.innerHTML =
80+
&#x27;&lt;pre&gt;&#x27; +
81+
String(error?.stack ?? error)
82+
.replace(/&amp;/g, &#x27;&amp;amp;&#x27;)
83+
.replace(/&lt;/g, &#x27;&amp;lt;&#x27;)
84+
.replace(/&gt;/g, &#x27;&amp;gt;&#x27;) +
85+
&#x27;&lt;/pre&gt;&#x27;;
86+
});&lt;/script&gt;&lt;/html&gt;"></iframe><p>In this demo, we use Svelte to render data in the Store object and then change the Cell to see the display update. We&#x27;re making changes to the <a href="/demos/hello-world/hello-world-v4/">Hello World v4</a> demo.</p><p>We swap the React imports for Svelte and the TinyBase Svelte module:</p><pre><code><span class="unchanged"><span class="prefix"> </span><span class="tag"><span class="tag"><span class="punctuation">&lt;</span>script</span> <span class="attr-name">type</span><span class="attr-value"><span class="punctuation">=</span><span class="punctuation">"</span>importmap<span class="punctuation">"</span></span><span class="punctuation">></span></span>
87+
<span class="prefix"> </span> {
88+
<span class="prefix"> </span> "imports": {
89+
<span class="prefix"> </span> "tinybase": "https://esm.sh/tinybase@8.1.0-beta.5",
90+
</span><span class="deleted-sign"><span class="prefix">-</span> "tinybase/ui-react": "https://esm.sh/tinybase@8.1.0-beta.5/ui-react",
91+
<span class="prefix">-</span> "tinybase/ui-react-inspector": "https://esm.sh/tinybase@8.1.0-beta.5/ui-react-inspector",
92+
<span class="prefix">-</span> "react": "https://esm.sh/react@^19.2.4",
93+
<span class="prefix">-</span> "react/jsx-runtime": "https://esm.sh/react@^19.2.4/jsx-runtime",
94+
<span class="prefix">-</span> "react-dom/client": "https://esm.sh/react-dom@^19.2.4/client"
95+
</span><span class="inserted-sign"><span class="prefix">+</span> "tinybase/ui-svelte": "https://esm.sh/tinybase@8.1.0-beta.5/ui-svelte",
96+
<span class="prefix">+</span> "svelte": "https://esm.sh/svelte@"
97+
</span><span class="unchanged"><span class="prefix"> </span> }
98+
<span class="prefix"> </span> }
99+
<span class="prefix"> </span><span class="tag"><span class="tag"><span class="punctuation">&lt;/</span>script</span><span class="punctuation">></span></span>
100+
</span></code></pre><p>The main entry point no longer needs the React imports:</p><pre><code><span class="unchanged"><span class="prefix"> </span><span class="keyword">import</span> <span class="punctuation">{</span>createStore<span class="punctuation">}</span> <span class="keyword">from</span> <span class="string">'tinybase'</span><span class="punctuation">;</span>
101+
</span><span class="deleted-sign"><span class="prefix">-</span><span class="keyword">import</span> <span class="punctuation">{</span>CellView<span class="punctuation">,</span> Provider<span class="punctuation">}</span> <span class="keyword">from</span> <span class="string">'tinybase/ui-react'</span><span class="punctuation">;</span>
102+
<span class="prefix">-</span><span class="keyword">import</span> <span class="punctuation">{</span>Inspector<span class="punctuation">}</span> <span class="keyword">from</span> <span class="string">'tinybase/ui-react-inspector'</span><span class="punctuation">;</span>
103+
<span class="prefix">-</span><span class="keyword">import</span> <span class="punctuation">{</span>createRoot<span class="punctuation">}</span> <span class="keyword">from</span> <span class="string">'react-dom/client'</span><span class="punctuation">;</span>
104+
<span class="prefix">-</span><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span><span class="punctuation">;</span>
105+
</span><span class="inserted-sign"><span class="prefix">+</span><span class="keyword">import</span> <span class="punctuation">{</span>mount<span class="punctuation">}</span> <span class="keyword">from</span> <span class="string">'svelte'</span><span class="punctuation">;</span>
106+
<span class="prefix">+</span><span class="keyword">import</span> App <span class="keyword">from</span> <span class="string">'./App.svelte'</span><span class="punctuation">;</span>
107+
</span></code></pre><p>Instead of rendering a React tree, we mount a Svelte component and pass the Store in as a prop:</p><pre><code><span class="deleted-sign"><span class="prefix">-</span><span class="function">createRoot</span><span class="punctuation">(</span>document<span class="punctuation">.</span>body<span class="punctuation">)</span><span class="punctuation">.</span><span class="function">render</span><span class="punctuation">(</span>
108+
<span class="prefix">-</span> <span class="tag"><span class="tag"><span class="punctuation">&lt;</span><span class="class-name">Provider</span></span> <span class="attr-name">store</span><span class="script"><span class="script-punctuation">=</span><span class="punctuation">{</span>store<span class="punctuation">}</span></span><span class="punctuation">></span></span><span class="plain-text">
109+
<span class="prefix">-</span> </span><span class="tag"><span class="tag"><span class="punctuation">&lt;</span><span class="class-name">CellView</span></span> <span class="attr-name">tableId</span><span class="attr-value"><span class="punctuation">=</span><span class="punctuation">"</span>t1<span class="punctuation">"</span></span> <span class="attr-name">rowId</span><span class="attr-value"><span class="punctuation">=</span><span class="punctuation">"</span>r1<span class="punctuation">"</span></span> <span class="attr-name">cellId</span><span class="attr-value"><span class="punctuation">=</span><span class="punctuation">"</span>c1<span class="punctuation">"</span></span> <span class="punctuation">/></span></span><span class="plain-text">
110+
<span class="prefix">-</span> </span><span class="tag"><span class="tag"><span class="punctuation">&lt;</span><span class="class-name">Inspector</span></span> <span class="punctuation">/></span></span><span class="plain-text">
111+
<span class="prefix">-</span> </span><span class="tag"><span class="tag"><span class="punctuation">&lt;/</span><span class="class-name">Provider</span></span><span class="punctuation">></span></span><span class="punctuation">,</span>
112+
<span class="prefix">-</span><span class="punctuation">)</span><span class="punctuation">;</span>
113+
</span><span class="inserted-sign"><span class="prefix">+</span><span class="function">mount</span><span class="punctuation">(</span>App<span class="punctuation">,</span> <span class="punctuation">{</span><span class="literal-property">target</span><span class="operator">:</span> document<span class="punctuation">.</span>body<span class="punctuation">,</span> <span class="literal-property">props</span><span class="operator">:</span> <span class="punctuation">{</span>store<span class="punctuation">}</span><span class="punctuation">}</span><span class="punctuation">)</span><span class="punctuation">;</span>
114+
</span></code></pre><p>The Svelte component uses the Provider and CellView components from the ui-svelte module:</p><pre><code><span class="tag"><span class="tag"><span class="punctuation">&lt;</span>script</span><span class="punctuation">></span></span><span class="script"><span class="language-javascript">
115+
<span class="keyword">import</span> <span class="punctuation">{</span>CellView<span class="punctuation">,</span> Provider<span class="punctuation">}</span> <span class="keyword">from</span> <span class="string">'tinybase/ui-svelte'</span><span class="punctuation">;</span>
116+
117+
<span class="keyword">let</span> <span class="punctuation">{</span>store<span class="punctuation">}</span> <span class="operator">=</span> <span class="function">$props</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
118+
</span></span><span class="tag"><span class="tag"><span class="punctuation">&lt;/</span>script</span><span class="punctuation">></span></span>
119+
120+
<span class="tag"><span class="tag"><span class="punctuation">&lt;</span>Provider</span> <span class="language-javascript"><span class="punctuation">{</span>store<span class="punctuation">}</span></span><span class="punctuation">></span></span>
121+
<span class="tag"><span class="tag"><span class="punctuation">&lt;</span>CellView</span> <span class="attr-name">tableId</span><span class="attr-value"><span class="punctuation">=</span><span class="punctuation">"</span>t1<span class="punctuation">"</span></span> <span class="attr-name">rowId</span><span class="attr-value"><span class="punctuation">=</span><span class="punctuation">"</span>r1<span class="punctuation">"</span></span> <span class="attr-name">cellId</span><span class="attr-value"><span class="punctuation">=</span><span class="punctuation">"</span>c1<span class="punctuation">"</span></span> <span class="punctuation">/></span></span>
122+
<span class="tag"><span class="tag"><span class="punctuation">&lt;/</span>Provider</span><span class="punctuation">></span></span>
123+
</code></pre><p>Unlike the React version, there is not yet a Svelte Inspector component, so this demo just focuses on the live-updating Cell itself.</p><p>Next, we will use a Metrics object to keep a count (and a rolling average) of the values in each Cell in a Store. Please continue to the <a href="/demos/rolling-dice/averaging-dice-rolls/">Averaging Dice Rolls</a> demo.</p></section>

0 commit comments

Comments
 (0)