HS-design: E39 WebWorker plugin
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
150
plans/designs/e39-webworker.md
Normal file
150
plans/designs/e39-webworker.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# E39 — WebWorker plugin
|
||||
|
||||
Cluster 39 of `plans/hs-conformance-to-100.md`. Goal: +1 test.
|
||||
|
||||
## 1. The 1 failing test
|
||||
|
||||
Suite `hs-upstream-worker`, test:
|
||||
|
||||
> `raises a helpful error when the worker plugin is not installed`
|
||||
|
||||
Translated upstream (`test/features/worker.js`) — currently emitted as
|
||||
`SKIP (untranslated)` in `spec/tests/test-hyperscript-behavioral.sx` path
|
||||
`(117 2 2)`:
|
||||
|
||||
```
|
||||
(deftest "raises a helpful error when the worker plugin is not installed"
|
||||
(error "SKIP (untranslated): raises a helpful ..."))
|
||||
```
|
||||
|
||||
Upstream assertion:
|
||||
|
||||
```js
|
||||
const msg = await error("worker MyWorker def noop() end end")
|
||||
expect(msg).toContain('worker plugin')
|
||||
expect(msg).toContain('hyperscript.org/features/worker')
|
||||
```
|
||||
|
||||
Two substring checks on the error produced by compiling the source.
|
||||
|
||||
## 2. Upstream `worker` syntax
|
||||
|
||||
From `https://hyperscript.org/features/worker/`:
|
||||
|
||||
```
|
||||
worker <name>[(<external-script-url>*)]
|
||||
(<def-feature> | <js-block>)+
|
||||
end
|
||||
```
|
||||
|
||||
- Declared at top level alongside `on`, `init`, `def`, `behavior`.
|
||||
- Body restricted to `def` functions and `js ... end` blocks; no DOM / `window` access.
|
||||
- Callers invoke as `WorkerName.fn(args)` from the main thread; calls
|
||||
return promises but are async-transparent.
|
||||
- Stock `_hyperscript` ships only a **stub** in the core bundle — the
|
||||
real implementation is in the `worker` ext. Without the ext loaded,
|
||||
parsing must fail with a message mentioning both `worker plugin` and
|
||||
`hyperscript.org/features/worker`.
|
||||
|
||||
## 3. Proposed SX shape
|
||||
|
||||
We ship the stub only. Full runtime is out of scope for a 1-test cluster.
|
||||
|
||||
### Parser addition (`lib/hyperscript/parser.sx`, `parse-feat`)
|
||||
|
||||
Extend the feature-dispatch `cond` (around line 2620) with one branch
|
||||
**before** the fallthrough to `parse-cmd-list`:
|
||||
|
||||
```
|
||||
((= val "worker") (parse-worker-stub))
|
||||
```
|
||||
|
||||
`parse-worker-stub` raises immediately — it does not consume tokens,
|
||||
does not need to recognise the body grammar. The error string contains
|
||||
both required substrings:
|
||||
|
||||
```
|
||||
"worker plugin is not installed — see https://hyperscript.org/features/worker"
|
||||
```
|
||||
|
||||
`(error ...)` at parser scope is the existing failure channel (used by
|
||||
`Expected 'of' or 'from' at position …` etc.), so this slots in without
|
||||
changes to the error plumbing.
|
||||
|
||||
### Compile output
|
||||
|
||||
None. The stub never returns an AST; the error propagates out of
|
||||
`hs-compile` and through `hs-to-sx-from-source`, surfacing to the test
|
||||
runner as a caught exception.
|
||||
|
||||
## 4. Runtime architecture
|
||||
|
||||
For the 1 test: **no runtime.** Parsing fails, so `runtime.sx` never sees
|
||||
a worker form. No `Worker` class needed in the `hs-run-filtered.js`
|
||||
mock. Nothing touches the DOM shim.
|
||||
|
||||
For a hypothetical full implementation (explicitly out of scope):
|
||||
|
||||
- Server would bind `WorkerName` in the HS top-level env to a record
|
||||
`{:worker-handle H :exports (list ...)}`.
|
||||
- `WorkerName.fn(args)` would compile via the existing property-access
|
||||
path to `(hs-method-call WorkerName "fn" args)`, which would detect
|
||||
the worker handle and dispatch over a `postMessage` channel.
|
||||
- Mock env would need a `Worker` class constructor + a serialisable
|
||||
message loop driving the worker script's own tiny SX interpreter.
|
||||
Deferred until the 7 skipped upstream tests become a target cluster.
|
||||
|
||||
## 5. Test delta estimate
|
||||
|
||||
+1 test. Feasibility: **high**. Two substring checks. Implementation
|
||||
is a single `cond` branch plus a 2-line error string. Generator patch
|
||||
to un-skip the test is mechanical.
|
||||
|
||||
## 6. Risks
|
||||
|
||||
- **Drift from future real plugin** — if we later implement the plugin,
|
||||
the stub must be replaced, not shadowed. Mitigation: the stub lives
|
||||
inline in `parse-feat`; adding the real plugin means deleting the
|
||||
stub branch and replacing it with `(parse-worker-feat)`. Single site.
|
||||
- **In-browser vs mock divergence** — none in this cluster. The stub
|
||||
errors identically in both hosts because it's pure parser logic.
|
||||
- **Message serialisation** — N/A until full plugin.
|
||||
- **Error-message phrasing drift** — the upstream test only checks for
|
||||
two substrings. We must keep `worker plugin` and
|
||||
`hyperscript.org/features/worker` verbatim; change-detector tests
|
||||
should reference this design if the wording is ever touched.
|
||||
- **Generator mis-translation** — the upstream JS uses `await error(src)`
|
||||
which the SX generator currently bails on (`return None`), producing
|
||||
the `SKIP (untranslated)`. We can either (a) teach the generator to
|
||||
translate `await error(src)` + two `toContain` checks, or (b) hand-
|
||||
write the test. Recommend (b) for one test: less generator churn.
|
||||
|
||||
## 7. Implementation checklist (single commit)
|
||||
|
||||
1. `sx_replace_by_pattern` in `lib/hyperscript/parser.sx` — insert the
|
||||
`((= val "worker") ...)` branch inside `parse-feat`'s `cond`. Body:
|
||||
`(error "worker plugin is not installed — see https://hyperscript.org/features/worker")`.
|
||||
2. `sx_replace_node` in `spec/tests/test-hyperscript-behavioral.sx`
|
||||
path `(117 2)` — replace the `SKIP (untranslated)` body with:
|
||||
```
|
||||
(deftest "raises a helpful error when the worker plugin is not installed"
|
||||
(let ((result (guard (e (true (if (string? e) e (str e))))
|
||||
(hs-compile "worker MyWorker def noop() end end")
|
||||
"")))
|
||||
(assert (contains? result "worker plugin"))
|
||||
(assert (contains? result "hyperscript.org/features/worker"))))
|
||||
```
|
||||
3. `sx_validate` on both files.
|
||||
4. Run `node tests/hs-run-filtered.js` with `HS_SUITE=hs-upstream-worker`.
|
||||
Expect 1/1.
|
||||
5. Run full suite smoke (0–195) — expect no regressions (pure additive
|
||||
branch in `parse-feat`; `"worker"` was previously caught by the
|
||||
fallthrough `parse-cmd-list`, which would have errored anyway on
|
||||
an unknown identifier — confirm by checking baseline).
|
||||
6. Commit: `HS: E39 WebWorker plugin stub (+1 test)`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Any worker runtime. Any real `Worker` object. Any message passing.
|
||||
- The 7 sibling `test.skip(...)` cases upstream — they remain skipped.
|
||||
- Generator patches — the test is hand-written per §6 above.
|
||||
Reference in New Issue
Block a user