tosijs-ui 1.6.20 → 1.6.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/code-editor-cm.d.ts +18 -0
- package/dist/code-editor-cm.js +94 -7
- package/dist/code-editor.d.ts +7 -0
- package/dist/code-editor.js +22 -0
- package/dist/doc-browser.js +13 -5
- package/dist/doc-system/site/dev-server.js +4 -4
- package/dist/doc-system/site/index.d.ts +1 -1
- package/dist/doc-system/site/make-llms-txt.js +16 -0
- package/dist/doc-system/site/orchestrator.js +41 -5
- package/dist/doc-system/site/site-config.d.ts +28 -0
- package/dist/iife.js +136 -120
- package/dist/iife.js.map +17 -12
- package/dist/live-example/code-transform.js +1 -1
- package/dist/live-example/component.js +185 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +8 -1
- package/package.json +3 -3
- package/tls/create-dev-certs.sh +25 -9
|
@@ -61,7 +61,7 @@ export async function executeCode(code, context, transform) {
|
|
|
61
61
|
// self-contained) and is loaded only for `ts` examples; from-ts in turn fetches
|
|
62
62
|
// the TypeScript compiler lazily at runtime, so tsc is never pulled in until a TS
|
|
63
63
|
// example actually transforms. Pinned to match the dev dep.
|
|
64
|
-
const TJS_VERSION = '0.
|
|
64
|
+
const TJS_VERSION = '0.9.0';
|
|
65
65
|
// Where to fetch a tjs-lang browser bundle from, in priority order:
|
|
66
66
|
// 1. SAME-ORIGIN — the doc-site build copies the bundles next to the iife and
|
|
67
67
|
// sets `__TJS_LOCAL_BASE`, so the transpiler ships in lockstep with the page,
|
|
@@ -33,6 +33,38 @@ preview.append('Try editing some code and hitting refresh…')
|
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
## How examples run: one shared page (read this)
|
|
37
|
+
|
|
38
|
+
By default every example on a page runs **inline — in the page's own document and
|
|
39
|
+
JavaScript realm**, not in a sandbox. Each example gets its own `preview` element
|
|
40
|
+
(and its `css` is scoped to that preview), but they all share the one `tosijs`
|
|
41
|
+
module, so **`tosi()` state singletons are shared across every example on the page**.
|
|
42
|
+
|
|
43
|
+
This is a deliberate choice, and it buys two things:
|
|
44
|
+
|
|
45
|
+
1. It's a live demonstration of how clean tosi's isolation is — many independent
|
|
46
|
+
components mount into one page and coexist without stepping on each other's DOM.
|
|
47
|
+
2. You can drive any demo from the console (or from another example) through the same
|
|
48
|
+
global singleton — the state is right there, not walled off in a frame.
|
|
49
|
+
|
|
50
|
+
The trade-off is the gotcha to know about: **examples can see and clobber each
|
|
51
|
+
other's state.** Two examples that both do `tosi({ app: … })` bind to the *same*
|
|
52
|
+
`app` singleton (the tosi registry is keyed by the top-level name), so one overwrites
|
|
53
|
+
the other — and you get "impossible" bugs where an example misbehaves only when some
|
|
54
|
+
*other* example happens to be on the same page. Worth it, but plan for it:
|
|
55
|
+
|
|
56
|
+
- **Namespace your state.** Give each example a unique top-level key
|
|
57
|
+
(`tosi({ ratingDemo: … })`, not a generic `tosi({ app: … })`).
|
|
58
|
+
- Prefer local variables and `preview`-scoped DOM over shared singletons when a demo
|
|
59
|
+
doesn't need to be globally reachable.
|
|
60
|
+
- In `test` blocks, assert with **counts / deltas**, not presence/absence — other
|
|
61
|
+
examples may have left their elements in the DOM.
|
|
62
|
+
|
|
63
|
+
For real isolation of the **DOM and CSS**, add the `iframe` attribute (below). Note
|
|
64
|
+
it does **not** isolate tosijs *state*: the `tosijs`/`tosijs-ui` handed to an iframe
|
|
65
|
+
example are still the host page's module instances, so `tosi()` singletons stay
|
|
66
|
+
shared. Namespacing is the fix for state; `iframe` is the fix for DOM/CSS bleed.
|
|
67
|
+
|
|
36
68
|
## Source dialects: `js`, `tjs`, `ts`
|
|
37
69
|
|
|
38
70
|
The executable block's fence language picks how the source is compiled before it
|
|
@@ -76,9 +108,157 @@ example the equivalent is written inside a comment (so it survives `tsc`) — bu
|
|
|
76
108
|
that comment form can't appear inside a doc comment like this one, since the
|
|
77
109
|
test's own closing delimiter would end the doc comment.
|
|
78
110
|
|
|
111
|
+
## Inline WebAssembly (SIMD)
|
|
112
|
+
|
|
113
|
+
A `tjs` example can drop a hot loop into **WebAssembly** with a `wasm { … } fallback
|
|
114
|
+
{ … }` block — compiled to bytecode *at transpile time*, embedded as base64, and run
|
|
115
|
+
in your browser. No Emscripten, no `.wasm` file, no build step, no server.
|
|
116
|
+
|
|
117
|
+
WASM earns its keep with **SIMD**: scalar WASM roughly ties the JS JIT, but a
|
|
118
|
+
branchless `f32x4` kernel does four values per instruction. Below, a field of a few
|
|
119
|
+
thousand particles is held on a grid by springs and pushed aside by your pointer —
|
|
120
|
+
every particle's position updated four at a time in WASM SIMD. **Click the button to
|
|
121
|
+
toggle WASM ↔ JavaScript** and watch the per-step time — the same code, one path
|
|
122
|
+
compiled to SIMD bytecode:
|
|
123
|
+
|
|
124
|
+
```tjs
|
|
125
|
+
// One physics step for the whole field, 4 particles per iteration with f32x4 SIMD:
|
|
126
|
+
// a spring pulls each particle to its home cell, and an inverse-square push shoves
|
|
127
|
+
// it away from the pointer. Pure arithmetic — no per-lane branches (tjs's f32x4 has
|
|
128
|
+
// no compare/select yet), which is exactly what SIMD wants. `fallback { }` is the
|
|
129
|
+
// scalar-JS twin.
|
|
130
|
+
function step(! px: Float32Array, py: Float32Array, vx: Float32Array, vy: Float32Array, hx: Float32Array, hy: Float32Array, n: 0, tx: 0.0, ty: 0.0) {
|
|
131
|
+
wasm {
|
|
132
|
+
let txv = f32x4_splat(tx)
|
|
133
|
+
let tyv = f32x4_splat(ty)
|
|
134
|
+
let spring = f32x4_splat(0.03)
|
|
135
|
+
let damp = f32x4_splat(0.86)
|
|
136
|
+
let repel = f32x4_splat(1600.0)
|
|
137
|
+
let soft = f32x4_splat(140.0)
|
|
138
|
+
for (let i = 0; i < n; i = i + 4) {
|
|
139
|
+
let off = i * 4
|
|
140
|
+
let x = f32x4_load(px, off)
|
|
141
|
+
let y = f32x4_load(py, off)
|
|
142
|
+
let sx = f32x4_mul(f32x4_sub(f32x4_load(hx, off), x), spring)
|
|
143
|
+
let sy = f32x4_mul(f32x4_sub(f32x4_load(hy, off), y), spring)
|
|
144
|
+
let rx = f32x4_sub(x, txv)
|
|
145
|
+
let ry = f32x4_sub(y, tyv)
|
|
146
|
+
let push = f32x4_div(repel, f32x4_add(f32x4_add(f32x4_mul(rx, rx), f32x4_mul(ry, ry)), soft))
|
|
147
|
+
let nvx = f32x4_mul(f32x4_add(f32x4_load(vx, off), f32x4_add(sx, f32x4_mul(rx, push))), damp)
|
|
148
|
+
let nvy = f32x4_mul(f32x4_add(f32x4_load(vy, off), f32x4_add(sy, f32x4_mul(ry, push))), damp)
|
|
149
|
+
f32x4_store(vx, off, nvx)
|
|
150
|
+
f32x4_store(vy, off, nvy)
|
|
151
|
+
f32x4_store(px, off, f32x4_add(x, nvx))
|
|
152
|
+
f32x4_store(py, off, f32x4_add(y, nvy))
|
|
153
|
+
}
|
|
154
|
+
} fallback {
|
|
155
|
+
for (let i = 0; i < n; i++) {
|
|
156
|
+
const sx = (hx[i] - px[i]) * 0.03, sy = (hy[i] - py[i]) * 0.03
|
|
157
|
+
const rx = px[i] - tx, ry = py[i] - ty
|
|
158
|
+
const push = 1600.0 / (rx * rx + ry * ry + 140.0)
|
|
159
|
+
const nvx = (vx[i] + sx + rx * push) * 0.86, nvy = (vy[i] + sy + ry * push) * 0.86
|
|
160
|
+
vx[i] = nvx; vy[i] = nvy; px[i] += nvx; py[i] += nvy
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const W = 480, H = 300, cols = 100, rows = 60, N = cols * rows
|
|
166
|
+
const px = new Float32Array(N), py = new Float32Array(N)
|
|
167
|
+
const vx = new Float32Array(N), vy = new Float32Array(N)
|
|
168
|
+
const hx = new Float32Array(N), hy = new Float32Array(N)
|
|
169
|
+
for (let i = 0; i < N; i++) {
|
|
170
|
+
const c = i % cols, r = (i / cols) | 0
|
|
171
|
+
hx[i] = (c + 0.5) * W / cols
|
|
172
|
+
hy[i] = (r + 0.5) * H / rows
|
|
173
|
+
px[i] = hx[i]
|
|
174
|
+
py[i] = hy[i]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const canvas = document.createElement('canvas')
|
|
178
|
+
canvas.width = W
|
|
179
|
+
canvas.height = H
|
|
180
|
+
canvas.style.cssText = 'width:100%;max-width:480px;border-radius:8px;display:block;background:#0b0e14;touch-action:none;cursor:crosshair'
|
|
181
|
+
const ctx = canvas.getContext('2d')
|
|
182
|
+
|
|
183
|
+
let tx = W / 2, ty = H / 2, idle = 0, frame = 0
|
|
184
|
+
canvas.addEventListener('pointermove', (e) => {
|
|
185
|
+
const r = canvas.getBoundingClientRect()
|
|
186
|
+
tx = (e.clientX - r.left) / r.width * W
|
|
187
|
+
ty = (e.clientY - r.top) / r.height * H
|
|
188
|
+
idle = 0
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// tjs registers the compiled function as globalThis.__tjs_wasm_0; nulling it makes
|
|
192
|
+
// step() fall to its JS twin. (A public WASM on/off toggle would be tidier — noted
|
|
193
|
+
// in TJS-FEEDBACK.md.)
|
|
194
|
+
let useWasm = true, savedWasm = null
|
|
195
|
+
const btn = document.createElement('button')
|
|
196
|
+
btn.style.cssText = 'margin:8px 0;padding:6px 12px;border-radius:6px;cursor:pointer'
|
|
197
|
+
const readout = document.createElement('p')
|
|
198
|
+
function applyMode() {
|
|
199
|
+
if (useWasm && savedWasm) { globalThis.__tjs_wasm_0 = savedWasm; savedWasm = null }
|
|
200
|
+
if (!useWasm && globalThis.__tjs_wasm_0) { savedWasm = globalThis.__tjs_wasm_0; globalThis.__tjs_wasm_0 = null }
|
|
201
|
+
btn.textContent = useWasm ? '⚡ WebAssembly SIMD — click for JavaScript' : '🐢 JavaScript — click for WASM SIMD'
|
|
202
|
+
}
|
|
203
|
+
btn.onclick = () => { useWasm = !useWasm; applyMode() }
|
|
204
|
+
|
|
205
|
+
let acc = 0, frames = 0
|
|
206
|
+
function loop() {
|
|
207
|
+
if (!document.body.contains(canvas)) return // example removed → stop
|
|
208
|
+
frame++
|
|
209
|
+
idle++
|
|
210
|
+
if (idle > 45) {
|
|
211
|
+
tx = W / 2 + Math.cos(frame * 0.02) * W * 0.32
|
|
212
|
+
ty = H / 2 + Math.sin(frame * 0.031) * H * 0.32
|
|
213
|
+
}
|
|
214
|
+
const t0 = performance.now()
|
|
215
|
+
step(px, py, vx, vy, hx, hy, N, tx, ty)
|
|
216
|
+
acc += performance.now() - t0
|
|
217
|
+
frames++
|
|
218
|
+
ctx.fillStyle = 'rgba(11,14,20,0.3)'
|
|
219
|
+
ctx.fillRect(0, 0, W, H)
|
|
220
|
+
ctx.fillStyle = useWasm ? '#4fd1c5' : '#f6ad55'
|
|
221
|
+
for (let i = 0; i < N; i++) ctx.fillRect(px[i], py[i], 1.6, 1.6)
|
|
222
|
+
if (frames >= 20) {
|
|
223
|
+
readout.textContent = N.toLocaleString() + ' particles · ' + (acc / frames).toFixed(3) +
|
|
224
|
+
' ms/step (' + (useWasm ? 'WASM SIMD' : 'JS') + ') · move the pointer'
|
|
225
|
+
acc = 0
|
|
226
|
+
frames = 0
|
|
227
|
+
}
|
|
228
|
+
requestAnimationFrame(loop)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
;(async () => {
|
|
232
|
+
// The wasm bootstrap is async — wait for the compiled function before starting,
|
|
233
|
+
// so the first frames run in WASM rather than the fallback.
|
|
234
|
+
const t0 = performance.now()
|
|
235
|
+
while (!globalThis.__tjs_wasm_0 && performance.now() - t0 < 3000) {
|
|
236
|
+
await new Promise((r) => setTimeout(r, 0))
|
|
237
|
+
}
|
|
238
|
+
applyMode()
|
|
239
|
+
requestAnimationFrame(loop)
|
|
240
|
+
})()
|
|
241
|
+
|
|
242
|
+
preview.append(canvas, btn, readout)
|
|
243
|
+
```
|
|
244
|
+
|
|
79
245
|
## CSS Isolation with `iframe`
|
|
80
246
|
|
|
81
|
-
Add the `iframe` attribute to render the preview inside an iframe
|
|
247
|
+
Add the `iframe` attribute to render the preview inside an iframe, giving the example
|
|
248
|
+
its own document, CSS scope, and custom-element registry. Reach for it when a demo's
|
|
249
|
+
styles would otherwise leak into (or get leaked on by) the rest of the page.
|
|
250
|
+
|
|
251
|
+
It isolates **DOM and CSS, not state.** The `tosijs`/`tosijs-ui` modules injected
|
|
252
|
+
into the iframe are the host page's own instances, so `tosi()` singletons remain
|
|
253
|
+
shared across every example (see "How examples run" above). Namespace your state to
|
|
254
|
+
keep examples from stomping each other; use `iframe` when you need visual/DOM
|
|
255
|
+
isolation.
|
|
256
|
+
|
|
257
|
+
*Fully-isolated examples (a separate module realm, so even `tosi()` state and
|
|
258
|
+
imported dependencies are sandboxed — the way tjs-lang's playgrounds do it with a
|
|
259
|
+
service worker intercepting imports) are a possible future option. For **actual
|
|
260
|
+
examples** the shared-page default is usually the nicer behavior: it shows off tosi's
|
|
261
|
+
isolation and lets you poke demos through the live global state.*
|
|
82
262
|
|
|
83
263
|
## Test Blocks
|
|
84
264
|
|
|
@@ -418,8 +598,11 @@ export class LiveExample extends Component {
|
|
|
418
598
|
this.productTabsReady = true;
|
|
419
599
|
const { editors } = this.parts;
|
|
420
600
|
// Relabel the source tab from "js" to the actual dialect (the `part` stays
|
|
421
|
-
// `js`, so everything referencing this.parts.js / this.js is unaffected)
|
|
601
|
+
// `js`, so everything referencing this.parts.js / this.js is unaffected), and
|
|
602
|
+
// put the source editor in the dialect's mode — so a `tjs` example gets
|
|
603
|
+
// first-class tjs editing (highlighting + autocomplete), `ts` gets TypeScript.
|
|
422
604
|
this.parts.js.setAttribute('name', this.dialect);
|
|
605
|
+
this.parts.js.mode = this.dialect;
|
|
423
606
|
this.jsOutEditor = codeEditor({
|
|
424
607
|
name: 'JS',
|
|
425
608
|
mode: 'javascript',
|