Browser-first architecture study
Chunks carry bytes.
Phases decide experience.
LilScript should optimize network placement, execution correctness, progressive enhancement, and cache survival as one delivery problem—without pretending that a critical module can outrun a dependency it synchronously needs.
If Chunk A has a static import from Chunk B, the browser can prioritize A’s request, but it must fetch and evaluate the required B module graph before evaluating A. Priority changes discovery and transport; it does not repeal dependency order.
Your exact scenario
What can run before Module 5?
Switch the dependency strategy. The source behavior stays explicit; the compiler is not allowed to silently make synchronous work asynchronous.
Chunk B
A native ESM entry waits for its static dependency graph. Module 3 being textually inside A does not create an independent execution unit.
- 1
Discover AHTML requests the entry.
- 2
Discover and fetch BA’s static import exposes Module 5.
- 3
Evaluate BDependency initialization completes.
- 4
Evaluate AOnly now can Module 3 execute.
Chunk A
Chunk B
LilScript can emit Module 3 in the critical activation group, call import(B), and activate Modules 1–2 only after Module 5 is available. The type system must expose that asynchronous boundary.
- 1
Fetch and evaluate ANo static edge to B remains.
- 2
Run Module 3Critical UI becomes usable.
- 3
Fetch BImmediately, on intent, visibility, or idle.
- 4
Activate Modules 1–2Typed dependency is now fulfilled.
Critical
Chunk A
Chunk B
Extract Module 3 into a dependency-closed critical entry or inline bootstrap. A and B may preserve ordinary static ESM semantics while critical rendering or event capture proceeds independently.
- 1
HTML rendersUseful markup exists without application JS.
- 2
Critical entry runsModule 3 has no later-phase dependency.
- 3
A and B loadThe browser resolves their normal graph.
- 4
Enhancement attachesExisting UI upgrades without replacement.
Capsule A
Capsule B
Capsule A has no native static edge to B: evaluating its wrapper only registers inert factories. When LilScript can prove or was explicitly told that Module 3 may precede the other effects, the scheduler runs it while B is already in flight, then executes Module 5 → Module 1 → Module 2.
- 1
Discover A + BHTML starts both requests.
- 2
A registersNo source module effect runs yet.
- 3
Execute Module 3Its closure is ready.
- 4
B registers; continueRun the newly ready dependency chain.
Remove the lost round trip
Download early. Register safely. Execute by readiness.
The extra latency is usually a discovery waterfall: B’s URL is learned only after A arrives and runs. Put the generated transfer plan in HTML so both requests begin at t0, without granting B permission to activate early.
B begins one network round trip too late.
Transfer overlaps; semantic execution order remains gated.
<link rel="modulepreload" href="/lilscript/a.cap.js">
<link rel="modulepreload" href="/lilscript/b.cap.js">
<script type="module">
const aReady = import("/a.cap.js");
const bReady = import("/b.cap.js");
const A = await aReady;
A.register(L); // definitions only
await L.run(3); // critical closure ready
const B = await bReady;
B.register(L);
await L.run([1, 2]); // M5 → M1 → M2
</script>
HTML exposes every known URL
Direct modulepreload declarations remove dependency discovery RTT. A can receive high priority while B warms concurrently.
Capsules are inert ESM wrappers
No eval. Importing a capsule creates factory definitions and export cells, but user-visible module effects remain unexecuted.
The scheduler runs ready closures
Module 3 executes as soon as A registers. Modules 1–2 remain blocked until Module 5 exists and the required evaluation order is legal.
Bytes versus latency stays explicit
If B may never be needed, warming it spends bytes. LilScript can choose immediate, intent, visible, idle, or manual transfer separately from activation.
The compiler must first prove that the reordered effects are independent or require an explicit phase declaration; it cannot silently change ordinary module ordering. The runtime must implement live-binding cells, once-only effects, cycle/SCC ordering, failure propagation, cancellation, and perhaps top-level-await state. Capsules should therefore be an opt-in browser delivery backend, with native ESM retained for ordinary graphs. The benchmark must include scheduler and registration-wrapper bytes.
Do not collapse the axes
Four decisions, not one bundle switch
A folder name can inform placement. It cannot safely dictate when code becomes observable.
Placement
Which functions and data share an HTTP response? Automatic, folder-preferred, or manually required groups belong here.
Discovery and fetch
When does the browser learn each URL, and at what priority? HTML, modulepreload, intent, visibility, and idle policy belong here.
Instantiation and evaluation
Static ESM dependencies preserve dependency-first execution. Dynamic imports and generated initializers create explicit later phases.
Activation
When may a feature touch the DOM, install listeners, or expose state? Critical, interactive, visible, idle, and manual activation belong here.
Manual and folder-based bundling
Folders are useful affinity. They are not execution semantics.
My recommendation: support folder-based control, but make it one input to a constrained planner. A critical phase, a physical response, and a source directory must remain different concepts.
delivery browser {
phase critical {
root Module3;
budget brotli <= 8kb;
budget requests <= 2;
}
prefer folder "./checkout" as checkout;
keep "./editor" together;
isolate "./admin" on interaction;
share automatic;
}
Automatic packing
The planner searches placements using exact per-response Brotli/gzip bytes, request depth, reuse, invalidation, and activation scenarios.
prefer folder
Preserve team ownership and cache locality when competitive. The optimizer may cross the folder boundary, but must explain the measured win.
keep / isolate
Honor deliberate product, security, licensing, or rollout boundaries. If semantics make a rule impossible, fail with the exact dependency path.
phase
Defines what must become usable and when. It computes a dependency closure; it does not assume everything beside it in a directory is critical.
Feature folders often import shared state, design systems, and utilities. Blind one-folder/one-chunk output creates tiny requests, duplicate helpers, cache-coupled barrels, or a giant “shared” chunk.
The cost model cannot infer every deployment boundary, business-critical interaction, permission surface, or intentional cache cohort. Manual constraints are essential—not an afterthought.
First build the semantic activation DAG. Then pack its ownership sets into responses under hard rules and soft affinities. Finally print a “why this chunk?” report with rejected alternatives.
Linker operations
“Inject it into A” has four different meanings
Each technique has a different execution and identity contract. LilScript should choose only among transformations it can prove equivalent.
Emit an ESM import
Smallest runtime and real live bindings. Correct for always-needed dependencies, but a static edge makes B part of A’s evaluation prerequisite.
Use when dependency order is intentional.Emit a typed initializer
Keep Module 1’s body behind initModule1(module5). Module 3 can activate first; the initializer runs exactly once after import(B) resolves.
Hoist or clone proven-safe code
Move a required pure function or immutable constant into A, or clone it into both chunks when import scaffolding costs more.
Never clone observable identity or state.Emit a tiny orchestration module
A generated bridge maps activation IDs to dynamic imports, normalizes failures, and wires progressive HTML to features without reimplementing the ESM loader.
Pay only when several phases share it.Non-negotiable invariants
- Mutable state and live exports have one authoritative owner.
- Module side effects execute once and in a semantically valid order.
- Functions, classes, symbols, and singleton objects are not cloned when identity is observable.
- Deferred failures and cancellation remain visible through typed tasks.
- Import/export, bridge, manifest, and loader bytes enter the chunk cost.
Language design sketch
Express user-visible phases, not loader tricks
This is proposed syntax, not a shipped promise. The important part is the compile-time contract behind it.
progressive Checkout {
critical {
render CheckoutShell();
capture intent for "submit";
}
enhance form on interaction
from "./checkout/validation";
enhance totals on visible
from "./checkout/pricing";
prepare recommendations on idle
from "./recommendations";
}
critical
The compiler computes its complete dependency closure and enforces byte/request budgets. A later-phase synchronous dependency is an error unless safely hoisted or cloned.
enhance … on
Declares activation policy: interaction, visibility, idle, intent, or manual. It compiles to a typed dynamic boundary and must upgrade existing HTML.
capture intent
Installs a tiny generated listener before the feature arrives. It records declared, replay-safe intent instead of blindly replaying arbitrary browser events.
Delivery budgets
Critical Brotli bytes, request count, graph depth, and activation latency are build constraints. An annotation without a verified closure is merely a comment.
Generated orchestrator
A tiny phase linker can be worth its bytes
The browser still fetches and caches ordinary ESM capsule files. Inside those files, LilScript can take responsibility for source-module registration and readiness-ordered execution when that removes a measured waterfall.
Semantic HTML and CSS
Content, forms, links, and essential actions work before enhancement. Build/server output owns critical markup and preload declarations.
Per-app phase linker
Factory registry, live-binding cells, dependency readiness, event delegation, import scheduling, cancellation, and failure signals. Tree-shaken to only used semantics.
Typed capsule or ESM chunks
Native ESM for simple graphs; inert registration capsules for critical, interaction, visible, idle, and manual activation groups.
Offline/cache worker
Versioned Service Worker only when offline or background behavior is requested. It is not required for ordinary HTTP caching.
Inline core
Wins: immediate discovery and no request. Costs: repeated HTML bytes, CSP nonce/hash handling, and no independent cache.
External core
Wins: long-lived cache and shared pages. Costs: another discovery edge unless preloaded, plus version coordination.
Generated hybrid
Recommendation: inline the minimal transfer plan and critical scheduler; keep reusable live-binding and cycle machinery in a content-addressed module when the selected graph requires it.
Native ESM backend: nearly zero runtime, best for ordinary static and dynamic graphs. Capsule backend: pays a small phase-linker cost to decouple response arrival from source-module execution and remove critical discovery waterfalls.
Cache architecture
Optimize the second deployment, not only the first visit
A small initial build can still be poor if one leaf change invalidates every parent URL or strands users on deleted chunks.
Revalidate
Short-lived or no-cache. Carries the build ID, current content-addressed URLs, critical preloads, and activation manifest.
Immutable
Long-lived cache headers. Keep older build artifacts during rollout so already-open documents can finish lazy imports.
Indirect
Import maps or manifest-driven dynamic imports can prevent a changed child hash from rewriting otherwise unchanged lazy parents.
Explicitly versioned
Only for offline or advanced policy. CacheStorage is separate from HTTP cache and requires deliberate install, activation, cleanup, and rollback behavior.
The planner needs navigation scenarios
Compression remains per physical response. For every candidate plan, LilScript must re-run name assignment, declaration layout, pooling, import/export aliases, and exact gzip/Brotli measurement per chunk—then score the route graph, not a concatenated global artifact.
JavaScript, C, and native
Shared logic; capability-specific delivery
Browser orchestration should not corrupt the portable language core or pretend that dynamic JavaScript modules have a C ABI.
Portable core
Pure logic, data structures, validation, state machines, and deterministic critical computations continue compiling to JavaScript, C, and native execution.
Browser capability
DOM rendering, visibility, interaction scheduling, module loading, and Service Worker policies require an explicit browser profile or typed extern capability.
Honest diagnostics
A native target either receives a real platform adapter or reports that the delivery primitive is browser-only. Silent stubs would make multi-output claims meaningless.
Recommended direction
Build phase-aware chunking before adding a large runtime
- 01
Model activation groups in IR
Track critical, interactive, visible, idle, and manual closures independently from source folders and output chunks.
- 02
Generalize chunks to arbitrary ownership sets
Permit several modules or selected functions per chunk, while carrying origin, side effects, identity, and cross-phase constraints.
- 03
Emit a delivery manifest for HTML/server use
Critical modulepreloads should be discovered in HTML, not injected only after the entry script begins executing.
- 04
Generate only the orchestration used
No runtime for a static page; native ESM for ordinary graphs; a measured capsule linker for phase-critical graphs; a Service Worker only for explicit offline requirements.
- 05
Benchmark dependency conflicts
Add this exact A→B scenario, route transitions, slow networks, cache-warm revisits, deployment churn, and failure recovery against Vite/Rolldown and Closure chunk output.
LilScript already exposes single, split, and preserve-modules modes and scores emitted chunks with raw, gzip, Brotli, request, depth, preload, and reuse costs.
The current planned-chunk model has one owning source module. It cannot yet represent arbitrary multi-module or function-level activation groups.
ActivationGraph → OwnershipPlan → TransferPlan → Emission, with exact measurement and semantic validation feeding back into the search.
Platform references
Constraints come from the browser, not taste
modulepreloadFetch into the document module map without evaluating the module.
Service Worker cachesScript-managed, separate from HTTP cache, and manually versioned.
Deployment failure recoveryOld documents can request chunks removed by a newer deployment.
Rolldown chunk groupsManual grouping can affect execution order and runtime overhead.