Skip to content

Commit 3d5f5b8

Browse files
committed
feat: comptime evaluation
`comptime` statements/imports and `-` prefixed tags (`<-if>`, `<-for>`, `<-const>`, `<-log>`, `<-debug>`) evaluate while compiling and leave only their expanded output. Values reach runtime through a resume-style AST serializer; primitive reads fold into eagerly escaped static output. Design doc: comptime.md.
1 parent 00bf1ab commit 3d5f5b8

202 files changed

Lines changed: 5876 additions & 5 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/comptime-evaluation.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@marko/runtime-tags": minor
3+
"@marko/compiler": minor
4+
---
5+
6+
Add compile time evaluation: `comptime` statements/imports and the `<-if>`, `<-for>`, `<-const>`, `<-log>`, and `<-debug>` tags run while compiling and leave only their expanded output. Values reach runtime through a resume-style AST serializer, and primitive reads fold into eagerly escaped static output. The new `marko/comptime` module ships `inert`/`isInert`, an airlock that snapshots untrusted data into frozen null-prototype trees safe to hand to comptime evaluation. `<-define>`, `<-return>`, and userland comptime tags are reserved, as are `$ct`-prefixed names in comptime code.

agent-feedback/bugs.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,19 @@ CSR is a runtime-only fix: push `() => childScope[AccessorProp.StartNode]` throu
157157
`packages/runtime-tags/src/html/assets.ts:135` | 2026-07-15 | impact:med | effort:med
158158

159159
`addAsset` deduplicates solely by asset id and silently ignores the triggers on every later registration. The existing `lazy-tag-shared-parent` shape proves separate parent modules can wrap the same child asset independently; if one imports it with `visible` and another with `idle`/an event, whichever parent renders first becomes the only trigger and the other condition can never load the shared module. Detect incompatible registrations before the first flush and combine their triggers, or emit a compile/debug error as the existing TODO suggests; do not let render order choose behavior.
160+
161+
## Concise `--` without following whitespace silently swallows one text character
162+
163+
`htmljs-parser@5.12.1 dist/index.mjs (BEGIN_DELIMITED_HTML_BLOCK)` | 2026-07-09 | impact:low | effort:low
164+
165+
Upstream htmljs-parser (exercised via `packages/compiler/src/babel-plugin/parser.js`):
166+
in concise mode, a text line whose `--` delimiter is not followed by whitespace
167+
drops the first content character with no error. Verified against the installed
168+
parser with a bare `createParser({ onText })`: `"--foo"` emits text `"oo"`,
169+
`"--foo bar"` emits `"oo bar"`, and `"---foo"` emits `"oo"`, while `"-- foo"`
170+
correctly emits `"foo"`. The delimiter scanner appears to unconditionally skip
171+
one character after the run of hyphens instead of requiring whitespace. Either
172+
the missing-whitespace form should be a parse error or the character should be
173+
kept; silent one-character data loss is the worst of the options. Fix belongs in
174+
marko-js/htmljs-parser (`BEGIN_DELIMITED_HTML_BLOCK` state) with regression
175+
tests for `--foo` / `---foo`.

agent-feedback/dx.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
Friction in builds, tests, tooling, or repo workflows. Format and rules: [README.md](README.md).
44

5+
## `stripTypes` silently unregisters scriptlet import bindings until the transform-exit crawl
6+
7+
`packages/compiler/src/babel-plugin/index.js:516` | 2026-07-09 | impact:med | effort:low
8+
9+
`stripTypes` temporarily hoists `ImportDeclaration`s out of static scriptlets so the TypeScript transform sees them, then puts them back with a raw `scriptlet.body.unshift(importNode)`. The hoist uses `path.remove()`, which unregisters the import's scope bindings (babel's `_removeFromScope`), and the raw re-insert never re-registers them — so from stripTypes until the crawl at transform **exit**, `scope.getBinding()` misses every import that lives inside a static/comptime scriptlet, while `const` bindings in the same scriptlet still resolve (they never went through the remove/re-add dance). Nothing in the tree looks wrong, which makes this a confusing trap for any pass that classifies identifiers during the transform stage (the comptime pass hit it and now re-crawls defensively at `packages/runtime-tags/src/translator/comptime/index.ts:111`). Fix by re-registering the binding when the import is moved back (e.g. `scriptletPath.scope.getProgramParent().registerDeclaration(...)` via a real path op instead of the raw unshift), then the defensive crawl can go.
10+
511
## `c8` coverage crashes generating lcov when the wrapped process loads `~ts`
612

713
`scripts/test-parallel.js:10` | 2026-07-02 | impact:med | effort:med

agent-feedback/perf.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,18 @@ event regardless of interop, and `compat.onFlush` permanently patches
219219
Marko 6 chunk flush with a `writersByGlobal.get` miss once the class-compat
220220
module is loaded. Each is minor individually; worth gating the resolver loop on a
221221
"has bridged events" flag and scoping the patches.
222+
223+
## Prune statically-confident `<if>` branches like `<show>` does
224+
225+
`packages/runtime-tags/src/translator/core/if.ts:583` | 2026-07-09 | impact:med | effort:med
226+
227+
`<show>` consults `evaluate()` and, when `.confident`, collapses to plain markup
228+
(`packages/runtime-tags/src/translator/core/show.ts:80`), but `core/if.ts` never
229+
calls the evaluator: `<if=false><p>never</p></if><else><p>always</p></else>`
230+
compiles (optimized html output) to a literal `if (false) {...} else {...}` with
231+
both branches' `_html` strings, scope ids, and analysis metadata emitted. A
232+
confident condition could instead select the surviving branch in `getBranches`
233+
(if.ts:583) before analysis, dropping the dead branch from both output targets.
234+
Care needed with branch chains (a confident-false head still leaves a live
235+
`else-if`/`else` chain) and with tag variables/attribute tags inside dropped
236+
branches.

0 commit comments

Comments
 (0)