diff --git a/.changeset/context-core-tag.md b/.changeset/context-core-tag.md new file mode 100644 index 00000000000..114ed8fe24c --- /dev/null +++ b/.changeset/context-core-tag.md @@ -0,0 +1,62 @@ +--- +"marko": minor +--- + +Add the `` core tag: a provider/consumer mechanism resolved +through the render tree (dynamic extent), not lexical authoring structure, +so a provider works correctly even when consumers are reached through +passed content. + +```marko + +
// sees settings +
// sees settings + +``` + +```marko + +``` + +A provider whose value is server-only (literals, module/`static` values, +`$global`-derived expressions) contributes zero bytes to the client +bundle: no provider import, no registration, no subscription machinery. + +Mutable providers (values derived from `let`/`input` state) fan changes +out to consumers fine-grained: each consumer re-runs individually when +the provided value changes, resumed pages reattach subscriptions without +re-rendering, and consumers created client-side (eg inside an `` +toggled after resume) resolve the provider through its serialized branch +entry. Subscription wiring is only serialized for consumers that can +observe the value client-side (an unread consumer costs nothing), and +same-scope consumers of one provider share a single composed fan-out +entry. The bind shorthand makes a provider writable: + +```marko + + + // may assign to its consumed `cart` variable + +``` + +A writable consumer's assignment invokes the provider's change handler, +so the write lands in provider state and fans back out to every +consumer. + +A template may consume its own context (`from=` resolving to the file +itself): the consume resolves the nearest instance in the render tree, +which enables recursive components and consume-inside-provide patterns. +A self-consume of a server-only value stays in the zero-bytes tier. + +A server-only context consumed by content that first renders in the +browser (a toggled ``, appended `` items, re-rendered dynamic +content) still works: compiler reach analysis spots client-re-renderable +regions that may consume it and lazily serializes the provider box (the +server-computed value plus a resolution link) only on those pages, so +every other page serializes nothing. A provider created in the browser +evaluates its value there (under the usual client `$global` rules), so +full client rendering works end to end. + +Also fixes a serializer slot-encoding bug (surfaced by root providers +with `serializedGlobals`): two partials for the same slot now encode a +signed delta instead of silently shifting every later scope by one. diff --git a/agent-feedback/dx.md b/agent-feedback/dx.md index 1d892db38ec..36c1f53a558 100644 --- a/agent-feedback/dx.md +++ b/agent-feedback/dx.md @@ -25,3 +25,9 @@ The dependency upgrade took everything to latest except two majors that are true `package.json:9` | 2026-07-07 | impact:low | effort:low Bare `npm audit` shows 3 advisories (`serialize-javascript` high, `js-yaml`/mocha moderate, `diff` low), all transitively under `mocha` and `@changesets/cli` — dev tooling that never ships. They can't be resolved by version bumps: the fixes live in higher majors than mocha's ranges allow (`serialize-javascript ^6`→fix in 7.x, `diff ^7`→8.x, `js-yaml ^4`→5.x), mocha 11.7.6 is the newest stable, and the latest `@changesets/parse` still pins `js-yaml ^4.1.1`. Rather than pin them via `overrides`, the repo audits production deps only: **`npm run audit`** (`npm audit --omit=dev`) is the gate and returns 0 — that's what consumers of the published packages actually receive. Revisit and drop the distinction once mocha/changesets update their transitive deps upstream. + +## Optimize-mode resumed behavior is never cross-checked against debug + +`packages/runtime-tags/src/__tests__/main.test.ts:127` | 2026-07-09 | impact:med | effort:med + +CSR is skipped in optimize mode (`skipCSR = optimize || ...`), and optimize ssr render logs snapshot to `render.md` independently of debug's `render.debug.md`, so nothing compares optimize-mode resumed interactions against known-good behavior. A mode-conditional runtime bug therefore gets immortalized by `npm run test:update` instead of caught: an argument-arity mismatch in `_context_link` (truthy in debug, undefined in optimize) shipped a `render.md` where update steps produced zero DOM mutations, and the suite stayed green because each mode only compares against its own snapshot. Suggested direction: after both modes run, assert the optimize ssr render log equals the debug one (modulo debug-only output), or enable the `equivalent` csr/ssr comparison in optimize mode. diff --git a/agent-feedback/perf.md b/agent-feedback/perf.md index 2461b3b96b2..dd6bc1c4739 100644 --- a/agent-feedback/perf.md +++ b/agent-feedback/perf.md @@ -49,3 +49,15 @@ Existing TODO: `<${input.component}/>` style dynamic tags always compile against `scripts/test-parallel.js:40` | 2026-07-02 | impact:low | effort:med `npm run test:parallel` runs one mocha process per core, but each fixture bundle already drives rolldown's own multi-threaded build (`packages/runtime-tags/src/__tests__/utils/bundle.ts`), so even a serial run uses ~1.4 cores. On a 4-core box the whole suite lands at ~87s vs ~238s serial (2.7×), short of the ~4× the core count implies, because the workers contend for the same native threads. `RAYON_NUM_THREADS=1` in the worker env made no measurable difference, so rolldown isn't honoring it. If rolldown (or its native binding) exposes a per-build thread cap, pinning workers to 1 bundler thread each — so N workers ≈ N threads total — could recover much of the lost efficiency and let the runner scale closer to linearly on higher core counts. + +## `` write-only consumers ship more wiring than the link they need + +`packages/runtime-tags/src/translator/core/context.ts` (`consumerNeedsWiring`) | 2026-07-09 | impact:low | effort:low + +A writable consumer that only assigns (no reads anywhere, so the binding prunes) still serializes full wiring: link, registered fan-out signal (an empty composed arrow), and fan-out `Set` membership. `_context_write` only needs the `ContextLink` entry on the consumer scope; the signal and `Set` membership are dead. Splitting `consumerNeedsWiring` into link-only vs full wiring would save the registered-signal bytes and a registry id for this (rare) shape. + +## Mutable `` provider double-serializes primitive values + +`packages/runtime-tags/src/html/context.ts` (`_context_provide`) | 2026-07-09 | impact:low | effort:med + +A mutable provider serializes its value into the provider scope's `ContextValue` slot for fan-out comparison, alongside the backing state binding's own slot (eg `I: "light"` and the ``'s `g: "light"` in `context-mutable-inert`'s `writes.html`). Objects dedupe by serializer identity; primitives ship twice. When the provide value is a bare state-binding reference the `ContextValue` slot could alias the binding's accessor instead of re-serializing, at the cost of the dom runtime knowing which accessor to compare against. diff --git a/agent-feedback/unclear.md b/agent-feedback/unclear.md index d7f73f280de..216623c19bb 100644 --- a/agent-feedback/unclear.md +++ b/agent-feedback/unclear.md @@ -7,3 +7,15 @@ Things that were hard to understand, and what would have clarified them. Format `packages/runtime-tags/src/translator/util/runtime.ts:21` | 2026-07-02 | impact:low | effort:low `pureDOMFunctions` includes `_template`, `_await_promise`, `_await_content`, `_load_template`, and `_load_setup`, yet those factories have observable side effects at call time: `_template` calls `_resume(id, renderer)` (`packages/runtime-tags/src/dom/template.ts:42`) and the await/load factories call `_enable_catch()`/`enableBranches()` latches. The annotations are sound only because of a non-obvious invariant: registration is needed exactly when the value can be referenced by a serialized register id, which requires the value to be reachable in the client module graph anyway, and the enable latches are re-triggered by whichever construct survives tree-shaking. Two independent reviews flagged these as possibly-unsound; a comment on `pureDOMFunctions` stating the invariant would prevent repeated re-derivation. + +## Nested same-template `` providers need an intervening resumable branch + +`packages/runtime-tags/src/dom/context.ts` (`_context_branch`) / `html/context.ts` | 2026-07-09 | impact:low | effort:high + +Same-template provider instances nested through content that never resumes (no state/closures between them) collide on one branch scope and hit the debug "share the same branch" error, because the caller-side `kBranchSerializeReason` set for a mutable provider's section does not thread through the content/dynamic-tag runtime guard (`_serialize_guard($scope_reason, holeIndex)`) the way ``/`` read it directly. A static parent-side `addSerializeReason(section.parent, true, sectionAccessor.binding)` in `finalizeReferences` was tried and does not reach the child's hole reason. Real recursive components carry per-instance state (which resumes the branch and resolves this naturally -- see the `context-self` fixture), so the gap is narrow; closing it is the cross-template serialize-reason edge the persisted-pages reach analysis owns. + +## `` reserve reach analysis has two conservative escape hatches + +`packages/runtime-tags/src/translator/core/context.ts` (`buildContextReserve`) | 2026-07-09 | impact:low | effort:high + +Server-only context boxes serialize when a client-re-renderable region may consume them: ``/`` sites compile `_context_reserve(...ids)` from statically known subtree sets, and the html `_dynamic_tag`/`_attr_content` runtimes reserve every open box when a resumable hole actually renders. Two shapes fall through both nets and hit the `_context_read` debug error instead: (1) a consumer reached only through a dynamic renderer value (passed content/component) inside a branch that never rendered on the server, and (2) consumers inside mutually recursive templates (self-recursion resolves via the program exit fixed-point; mutual cycles contribute nothing). ``/`` bodies also emit no compiled reserve (their rendered content is covered by the runtime holes). Closing these needs consumption metadata carried on content/renderer values plus a cycle fixed-point across templates. Note also that client-side resolution is branch granular where the server is extent granular: an inner instance of a provider that shares a branch with trailing consumers of the outer instance resolves for them too (see `context-shadowing`, csr skipped) -- the documented rule, enforced by the debug same-branch guard where detectable, is to wrap each instance in its own control flow. diff --git a/cspell.json b/cspell.json index ba44ddae471..b7cdddac13f 100644 --- a/cspell.json +++ b/cspell.json @@ -382,7 +382,8 @@ "vmax", "cqmin", "cqmax", - "whib" + "whib", + "reparenting" ], "ignoreRegExpList": [], "files": ["*"], diff --git a/packages/runtime-class/test/taglib-lookup/fixtures/getTagsSorted/expected.json b/packages/runtime-class/test/taglib-lookup/fixtures/getTagsSorted/expected.json index 65d12582f5a..7a3b14bdc22 100644 --- a/packages/runtime-class/test/taglib-lookup/fixtures/getTagsSorted/expected.json +++ b/packages/runtime-class/test/taglib-lookup/fixtures/getTagsSorted/expected.json @@ -40,6 +40,7 @@ "col", "colgroup", "const", + "context", "data", "datalist", "dd", diff --git a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-basic-class-to-tags/sizes.json b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-basic-class-to-tags/sizes.json index 619fa6371f1..6c6a927abcd 100644 --- a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-basic-class-to-tags/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-basic-class-to-tags/sizes.json @@ -2,8 +2,8 @@ "dom": { "template.marko.page.mjs": { "total": { - "min": 65847, - "brotli": 20208 + "min": 65835, + "brotli": 20207 }, "files": { "components/tags-counter.marko": 681, diff --git a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-class-to-tags-import/sizes.json b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-class-to-tags-import/sizes.json index 619fa6371f1..6c6a927abcd 100644 --- a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-class-to-tags-import/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-class-to-tags-import/sizes.json @@ -2,8 +2,8 @@ "dom": { "template.marko.page.mjs": { "total": { - "min": 65847, - "brotli": 20208 + "min": 65835, + "brotli": 20207 }, "files": { "components/tags-counter.marko": 681, diff --git a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-dynamic-tag-class-to-tags/sizes.json b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-dynamic-tag-class-to-tags/sizes.json index 185dec996a5..3bb1daecbc4 100644 --- a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-dynamic-tag-class-to-tags/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-dynamic-tag-class-to-tags/sizes.json @@ -2,8 +2,8 @@ "dom": { "template.marko.page.mjs": { "total": { - "min": 65745, - "brotli": 20176 + "min": 65733, + "brotli": 20160 }, "files": { "components/tags-child.marko": 509, diff --git a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-dynamic-tag-conditional-class-to-tags/sizes.json b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-dynamic-tag-conditional-class-to-tags/sizes.json index c2e0f79d1a3..4629f6e1bd5 100644 --- a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-dynamic-tag-conditional-class-to-tags/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-dynamic-tag-conditional-class-to-tags/sizes.json @@ -2,8 +2,8 @@ "dom": { "template.marko.page.mjs": { "total": { - "min": 65786, - "brotli": 20187 + "min": 65774, + "brotli": 20200 }, "files": { "components/tags-child.marko": 509, diff --git a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-nested-attr-tags-class-to-tags/sizes.json b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-nested-attr-tags-class-to-tags/sizes.json index c2c9712939e..cc08a5e6cd0 100644 --- a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-nested-attr-tags-class-to-tags/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-nested-attr-tags-class-to-tags/sizes.json @@ -2,8 +2,8 @@ "dom": { "template.marko.page.mjs": { "total": { - "min": 66152, - "brotli": 20390 + "min": 66140, + "brotli": 20358 }, "files": { "components/tags-layout.marko": 838, diff --git a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-nested-class-to-tags/sizes.json b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-nested-class-to-tags/sizes.json index 7f0f22ba1b2..a2bc99236d0 100644 --- a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-nested-class-to-tags/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-nested-class-to-tags/sizes.json @@ -2,8 +2,8 @@ "dom": { "template.marko.page.mjs": { "total": { - "min": 65824, - "brotli": 20220 + "min": 65812, + "brotli": 20211 }, "files": { "components/tags-layout.marko": 696, diff --git a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-tag-params-class-to-tags/sizes.json b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-tag-params-class-to-tags/sizes.json index f128d9a7f48..1c6208c8be6 100644 --- a/packages/runtime-tags/src/__tests__/fixtures-interop/interop-tag-params-class-to-tags/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures-interop/interop-tag-params-class-to-tags/sizes.json @@ -2,8 +2,8 @@ "dom": { "template.marko.page.mjs": { "total": { - "min": 66203, - "brotli": 20371 + "min": 66191, + "brotli": 20397 }, "files": { "components/tags-layout.marko": 912, diff --git a/packages/runtime-tags/src/__tests__/fixtures/branch-closure-if-only-child/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/branch-closure-if-only-child/sizes.json index 994f77146eb..a93cb9d6c5e 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/branch-closure-if-only-child/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/branch-closure-if-only-child/sizes.json @@ -1,8 +1,8 @@ { "dom": { "template.marko.page.mjs": { - "min": 10131, - "brotli": 3983 + "min": 10334, + "brotli": 4070 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..7d4b906a5f2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,64 @@ +// settings.marko +const $template$3 = ""; +const $walks$3 = "b%c"; +const $input_content_direct$1 = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +function $setup$3($scope) { + $scope["#ContextValue"] = "dark"; + _context_branch($scope, "__tests__/settings.marko"); +} +const $dynamicTag$1 = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content$1 = $dynamicTag$1; +const $input$1 = ($scope, input) => $input_content$1($scope, input.content); +var settings_default = /*@__PURE__*/ _template("__tests__/settings.marko", $template$3, "b%c", $setup$3, $input$1); + +// tags/cart-size.marko +const $template$2 = " items"; +const $walks$2 = "bD%l"; +const $items = ($scope, items) => $items_length($scope, items?.length); +const $items_length = ($scope, items_length) => _text($scope["#text/0"], items_length); +function $setup$2($scope) { + $items($scope, _context_read($scope, "items", "__tests__/tags/cart-provider.marko", "")); +} +var cart_size_default = /*@__PURE__*/ _template("__tests__/tags/cart-size.marko", $template$2, $walks$2, $setup$2); + +// tags/cart-provider.marko +const $template$1 = ""; +const $walks$1 = "b%c"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +function $setup$1($scope) { + $scope["#ContextValue"] = [ + "a", + "b", + "c" + ]; + _context_branch($scope, "__tests__/tags/cart-provider.marko"); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +var cart_provider_default = /*@__PURE__*/ _template("__tests__/tags/cart-provider.marko", $template$1, "b%c", $setup$1, $input); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)("b%c"); +const $Settings_content__items = /*@__PURE__*/ _closure_get("items", ($scope) => _text($scope["#text/0"], $scope._.items.join(","))); +const $Settings_content__theme = ($scope, theme) => _text($scope["#text/1"], theme); +const $Settings_content__setup = ($scope) => { + $Settings_content__items($scope); + $Settings_content__theme($scope, _context_read($scope, "theme", "__tests__/settings.marko", "./settings.marko")); +}; +const $Settings_content = /*@__PURE__*/ _content("__tests__/template.marko_2_content", "
-
", "bD%c%l", $Settings_content__setup); +const $cartprovider_content__items__closure = /*@__PURE__*/ _closure($Settings_content__items); +const $cartprovider_content__items = /*@__PURE__*/ _const("items", $cartprovider_content__items__closure); +const $cartprovider_content__setup = ($scope) => { + $setup$2($scope["#childScope/0"]); + $setup$3($scope["#childScope/1"]); + $input_content_direct$1($scope["#childScope/1"], $Settings_content($scope)); + $cartprovider_content__items($scope, _context_read($scope, "items", "__tests__/tags/cart-provider.marko", "")); +}; +const $cartprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", /*@__PURE__*/ ((_w0, _w1) => `${_w0}${_w1}`)($template$2, $template$3), /*@__PURE__*/ ((_w0, _w1) => `b/${_w0}&/${_w1}&b`)($walks$2, "b%c"), $cartprovider_content__setup); +function $setup($scope) { + $setup$1($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $cartprovider_content($scope)); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..5bb833f147f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/html.bundle.debug.js @@ -0,0 +1,54 @@ +// settings.marko +var settings_default = _template("__tests__/settings.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("__tests__/settings.marko", "dark", () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}, "__tests__/settings.marko", 0); +}); + +// tags/cart-size.marko +var cart_size_default = _template("__tests__/tags/cart-size.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + const items = _context_get("__tests__/tags/cart-provider.marko", ""); + _html(`${_escape(items.length)}${_el_resume($scope0_id, "#text/0")} items`); + writeScope($scope0_id, {}, "__tests__/tags/cart-size.marko", 0); +}); + +// tags/cart-provider.marko +var cart_provider_default = _template("__tests__/tags/cart-provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("__tests__/tags/cart-provider.marko", [ + "a", + "b", + "c" + ], () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}, "__tests__/tags/cart-provider.marko", 0); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + cart_provider_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + const $cartprovider_content__items__closures = new Set(); + const items = _context_get("__tests__/tags/cart-provider.marko", ""); + cart_size_default({}); + settings_default({ content: _content("__tests__/template.marko_2_content", () => { + _scope_reason(); + const $scope2_id = _scope_id(); + const theme = _context_get("__tests__/settings.marko", "./settings.marko"); + _html(`
${_escape(items.join(","))}${_el_resume($scope2_id, "#text/0")}-${_escape(theme)}${_el_resume($scope2_id, "#text/1")}
`); + _subscribe($cartprovider_content__items__closures, writeScope($scope2_id, { _: _scope_with_id($scope1_id) }, "__tests__/template.marko", "6:4")); + _resume_branch($scope2_id); + }) }); + writeScope($scope1_id, { "ClosureScopes:items": $cartprovider_content__items__closures }, "__tests__/template.marko", "3:2"); + }) }); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..769f7318b4a --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/html.bundle.js @@ -0,0 +1,53 @@ +// settings.marko +var settings_default = _template("a", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("a", "dark", () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}); +}); + +// tags/cart-size.marko +var cart_size_default = _template("d", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + _html(`${_escape(_context_get("c").length)}${_el_resume($scope0_id, "a")} items`); + writeScope($scope0_id, {}); +}); + +// tags/cart-provider.marko +var cart_provider_default = _template("c", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("c", [ + "a", + "b", + "c" + ], () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}); +}); + +// template.marko +var template_default = _template("b", (input) => { + _scope_reason(); + _scope_id(); + cart_provider_default({ content: _content("b1", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + const $cartprovider_content__items__closures = /* @__PURE__ */ new Set(); + const items = _context_get("c"); + cart_size_default({}); + settings_default({ content: _content("b0", () => { + _scope_reason(); + const $scope2_id = _scope_id(); + const theme = _context_get("a"); + _html(`
${_escape(items.join(","))}${_el_resume($scope2_id, "a")}-${_escape(theme)}${_el_resume($scope2_id, "b")}
`); + _subscribe($cartprovider_content__items__closures, writeScope($scope2_id, { _: _scope_with_id($scope1_id) })); + _resume_branch($scope2_id); + }) }); + writeScope($scope1_id, { Bc: $cartprovider_content__items__closures }); + }) }); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.debug.md new file mode 100644 index 00000000000..d423b2cccb6 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.debug.md @@ -0,0 +1,9 @@ +# Render +```html + + 3 items + +
+ a,b,c-dark +
+``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.md new file mode 100644 index 00000000000..d423b2cccb6 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/render.md @@ -0,0 +1,9 @@ +# Render +```html + + 3 items + +
+ a,b,c-dark +
+``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..d5f8db108e1 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.debug.html @@ -0,0 +1,10 @@ +3 items +
a,b,c-dark
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.html new file mode 100644 index 00000000000..19f5f779b99 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/__snapshots__/writes.html @@ -0,0 +1,10 @@ +3 items +
a,b,c-dark
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/settings.marko b/packages/runtime-tags/src/__tests__/fixtures/context-basics/settings.marko new file mode 100644 index 00000000000..f398f5740e0 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/settings.marko @@ -0,0 +1,5 @@ +// A non-taglib template addressable only via a relative `from=` path (e.g. +// a `+layout.marko`-shaped provider). + + <${input.content}/> +
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-basics/sizes.json new file mode 100644 index 00000000000..10059a89a06 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/sizes.json @@ -0,0 +1,7 @@ +{ + "dom": {}, + "html": { + "min": 402, + "brotli": 282 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-provider.marko new file mode 100644 index 00000000000..331f5cfe4ca --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-provider.marko @@ -0,0 +1,5 @@ +// Taglib-discoverable provider wrapping passed content (dynamic-extent +// resolution); the literal value classifies server-only. + + <${input.content}/> +
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-size.marko b/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-size.marko new file mode 100644 index 00000000000..049b60414b5 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/tags/cart-size.marko @@ -0,0 +1,3 @@ +// A second, distinct consumer template of the same provider. + +${items.length} items diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-basics/template.marko new file mode 100644 index 00000000000..fea10970490 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/template.marko @@ -0,0 +1,10 @@ +import Settings from "./settings.marko" + + + + + + +
${items.join(",")}-${theme}
+
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-basics/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-basics/test.ts new file mode 100644 index 00000000000..53375135d86 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-basics/test.ts @@ -0,0 +1,5 @@ +import type { TestConfig } from "../../main.test"; + +export const config: TestConfig = { + steps: [{}], +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..4fca45f573f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,49 @@ +// counter.js +let n = 0; +function next() { + return "value-" + ++n; +} + +// provider.marko +const $template$2 = ""; +const $walks$2 = "b%c"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +function $setup$2($scope) { + $scope["#ContextValue"] = next(); + _context_branch($scope, "__tests__/provider.marko"); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +var provider_default = /*@__PURE__*/ _template("__tests__/provider.marko", $template$2, "b%c", $setup$2, $input); + +// consumer.marko +const $template$1 = " "; +const $walks$1 = "bD l"; +const $label = ($scope, label) => _text($scope["#text/0"], label); +function $setup$1($scope) { + $label($scope, _context_read($scope, "label", "__tests__/provider.marko", "./provider.marko")); +} +var consumer_default = /*@__PURE__*/ _template("__tests__/consumer.marko", $template$1, $walks$1, $setup$1); + +// template.marko +const $template = /*@__PURE__*/ ((_w0, _w1) => `${_w0}${_w1}`)($template$2, $template$2); +const $walks = /*@__PURE__*/ ((_w0, _w1) => `b/${_w0}&/${_w1}&b`)("b%c", "b%c"); +const $Provider_content2 = /*@__PURE__*/ _content("__tests__/template.marko_3_content", "
sibling
", "b"); +const $await_content__setup = ($scope) => { + $setup$1($scope["#childScope/0"]); +}; +const $await_content = /*@__PURE__*/ _await_content("#text/0", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$1), $await_content__setup); +const $Provider_content__await_promise = /*@__PURE__*/ _await_promise("#text/0"); +const $Provider_content__setup = ($scope) => { + $await_content($scope); + $Provider_content__await_promise($scope, resolveAfter(1)); +}; +const $Provider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "", "b%c", $Provider_content__setup); +function $setup($scope) { + $setup$2($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $Provider_content($scope)); + $setup$2($scope["#childScope/1"]); + $input_content_direct($scope["#childScope/1"], $Provider_content2($scope)); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..c3c2f7cf7ea --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.debug.js @@ -0,0 +1,43 @@ +// counter.js +let n = 0; +function next() { + return "value-" + ++n; +} + +// provider.marko +var provider_default = _template("__tests__/provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("__tests__/provider.marko", next(), () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}, "__tests__/provider.marko", 0); +}); + +// consumer.marko +var consumer_default = _template("__tests__/consumer.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + const label = _context_get("__tests__/provider.marko", "./provider.marko"); + _html(`${_escape(label)}${_el_resume($scope0_id, "#text/0")}`); + writeScope($scope0_id, {}, "__tests__/consumer.marko", 0); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + provider_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + _await($scope1_id, "#text/0", resolveAfter(1), (_ignored) => { + const $scope2_id = _scope_id(); + consumer_default({}); + }, 0); + }) }); + provider_default({ content: _content("__tests__/template.marko_3_content", () => { + _scope_reason(); + const $scope3_id = _scope_id(); + _html("
sibling
"); + }) }); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..ed9767cde32 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/html.bundle.js @@ -0,0 +1,41 @@ +// counter.js +let n = 0; +function next() { + return "value-" + ++n; +} + +// provider.marko +var provider_default = _template("b", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("b", next(), () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}); +}); + +// consumer.marko +var consumer_default = _template("a", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + _html(`${_escape(_context_get("b"))}${_el_resume($scope0_id, "a")}`); + writeScope($scope0_id, {}); +}); + +// template.marko +var template_default = _template("c", (input) => { + _scope_reason(); + _scope_id(); + provider_default({ content: _content("c0", () => { + _scope_reason(); + _await(_scope_id(), "a", resolveAfter(1), (_ignored) => { + _scope_id(); + consumer_default({}); + }, 0); + }) }); + provider_default({ content: _content("c1", () => { + _scope_reason(); + _scope_id(); + _html("
sibling
"); + }) }); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.debug.md new file mode 100644 index 00000000000..6a0517d6784 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.debug.md @@ -0,0 +1,18 @@ +# Render + +# Update +```html + + value-1 + +
+ sibling +
+``` +## Change +``` +INSERT: span +INSERT: span::text("value-1") +INSERT: span + div +INSERT: div::text("sibling") +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.md new file mode 100644 index 00000000000..6a0517d6784 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/render.md @@ -0,0 +1,18 @@ +# Render + +# Update +```html + + value-1 + +
+ sibling +
+``` +## Change +``` +INSERT: span +INSERT: span::text("value-1") +INSERT: span + div +INSERT: div::text("sibling") +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..3bbbd2f26e5 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.debug.html @@ -0,0 +1,7 @@ + + +value-1 +
sibling
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.html new file mode 100644 index 00000000000..a79ec7f304c --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/__snapshots__/writes.html @@ -0,0 +1,7 @@ + + +value-1 +
sibling
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/consumer.marko b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/consumer.marko new file mode 100644 index 00000000000..552da2e324c --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/consumer.marko @@ -0,0 +1,2 @@ + +${label} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/counter.js b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/counter.js new file mode 100644 index 00000000000..6e59b8613eb --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/counter.js @@ -0,0 +1,5 @@ +// Distinct value per provider instance while classifying immutable. +let n = 0; +export default function next() { + return "value-" + ++n; +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/provider.marko new file mode 100644 index 00000000000..904f4c0a0d3 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/provider.marko @@ -0,0 +1,5 @@ +import next from "./counter.js" + + + <${input.content}/> + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/sizes.json new file mode 100644 index 00000000000..2fa87af1622 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/sizes.json @@ -0,0 +1,7 @@ +{ + "dom": {}, + "html": { + "min": 323, + "brotli": 235 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/template.marko new file mode 100644 index 00000000000..b3e907f7646 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/template.marko @@ -0,0 +1,14 @@ +import { resolveAfter } from "../../utils/resolve" +import Provider from "./provider.marko" +import Consumer from "./consumer.marko" + +// The consumer inside the deferred `` body must read the value +// captured at defer time, not the sibling provider's later push. + + + + + + +
sibling
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/test.ts new file mode 100644 index 00000000000..8678ab208e3 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-deferred-await/test.ts @@ -0,0 +1,9 @@ +import type { TestConfig } from "../../main.test"; +import { flush, wait } from "../../utils/resolve"; + +export const config: TestConfig = { + steps: [{}, flush, wait], + // Two sibling instances of one provider share the root branch, which + // client-side (branch based) resolution cannot distinguish. + skip_csr: true, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.debug.txt new file mode 100644 index 00000000000..e24b63045ce --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.debug.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko:2:14 + 1 | + > 2 | + | ^^^^^^^^^^^^ The `from=` attribute must be a static string literal (eg `from=""` or `from="./provider.marko"`). + 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.txt new file mode 100644 index 00000000000..e24b63045ce --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-dom.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko:2:14 + 1 | + > 2 | + | ^^^^^^^^^^^^ The `from=` attribute must be a static string literal (eg `from=""` or `from="./provider.marko"`). + 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.debug.txt new file mode 100644 index 00000000000..e24b63045ce --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.debug.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko:2:14 + 1 | + > 2 | + | ^^^^^^^^^^^^ The `from=` attribute must be a static string literal (eg `from=""` or `from="./provider.marko"`). + 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.txt new file mode 100644 index 00000000000..e24b63045ce --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/__snapshots__/error-compile-html.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko:2:14 + 1 | + > 2 | + | ^^^^^^^^^^^^ The `from=` attribute must be a static string literal (eg `from=""` or `from="./provider.marko"`). + 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/provider.marko new file mode 100644 index 00000000000..0c279465187 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/provider.marko @@ -0,0 +1,3 @@ + +
provided
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko new file mode 100644 index 00000000000..be4ce17ad94 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/template.marko @@ -0,0 +1,2 @@ + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/test.ts new file mode 100644 index 00000000000..87d511a1f39 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-dynamic-from/test.ts @@ -0,0 +1,5 @@ +import type { TestConfig } from "../../main.test"; + +export const config: TestConfig = { + error_compiler: true, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.debug.txt new file mode 100644 index 00000000000..ce3347b6589 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.debug.txt @@ -0,0 +1,5 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko:1:14 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The template "./not-a-provider.marko" does not provide a value (it has no provide-mode `` tag). + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.txt new file mode 100644 index 00000000000..ce3347b6589 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-dom.txt @@ -0,0 +1,5 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko:1:14 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The template "./not-a-provider.marko" does not provide a value (it has no provide-mode `` tag). + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.debug.txt new file mode 100644 index 00000000000..ce3347b6589 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.debug.txt @@ -0,0 +1,5 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko:1:14 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The template "./not-a-provider.marko" does not provide a value (it has no provide-mode `` tag). + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.txt new file mode 100644 index 00000000000..ce3347b6589 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/__snapshots__/error-compile-html.txt @@ -0,0 +1,5 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko:1:14 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The template "./not-a-provider.marko" does not provide a value (it has no provide-mode `` tag). + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/not-a-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/not-a-provider.marko new file mode 100644 index 00000000000..018e857be3f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/not-a-provider.marko @@ -0,0 +1 @@ +
I do not provide a context value.
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko new file mode 100644 index 00000000000..79075d22b07 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/template.marko @@ -0,0 +1 @@ + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/test.ts new file mode 100644 index 00000000000..87d511a1f39 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-not-provider/test.ts @@ -0,0 +1,5 @@ +import type { TestConfig } from "../../main.test"; + +export const config: TestConfig = { + error_compiler: true, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.debug.txt new file mode 100644 index 00000000000..0dbb550afd2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.debug.txt @@ -0,0 +1,5 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko:1:2 + > 1 | + | ^^^^^^^ A [`` provider](https://markojs.com/docs/reference/core-tag#context) requires [body content](https://markojs.com/docs/reference/language#tag-content); wrap the tags that should see the provided value. + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.txt new file mode 100644 index 00000000000..0dbb550afd2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-dom.txt @@ -0,0 +1,5 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko:1:2 + > 1 | + | ^^^^^^^ A [`` provider](https://markojs.com/docs/reference/core-tag#context) requires [body content](https://markojs.com/docs/reference/language#tag-content); wrap the tags that should see the provided value. + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.debug.txt new file mode 100644 index 00000000000..0dbb550afd2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.debug.txt @@ -0,0 +1,5 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko:1:2 + > 1 | + | ^^^^^^^ A [`` provider](https://markojs.com/docs/reference/core-tag#context) requires [body content](https://markojs.com/docs/reference/language#tag-content); wrap the tags that should see the provided value. + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.txt new file mode 100644 index 00000000000..0dbb550afd2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/__snapshots__/error-compile-html.txt @@ -0,0 +1,5 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko:1:2 + > 1 | + | ^^^^^^^ A [`` provider](https://markojs.com/docs/reference/core-tag#context) requires [body content](https://markojs.com/docs/reference/language#tag-content); wrap the tags that should see the provided value. + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko new file mode 100644 index 00000000000..577202e9cf2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/template.marko @@ -0,0 +1 @@ + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/test.ts new file mode 100644 index 00000000000..87d511a1f39 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-provide-no-body/test.ts @@ -0,0 +1,5 @@ +import type { TestConfig } from "../../main.test"; + +export const config: TestConfig = { + error_compiler: true, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.debug.txt new file mode 100644 index 00000000000..4e23c980b6f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.debug.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko:2:21 + 1 | + > 2 | + | ^^^^^^^ Cannot assign to `val`; the provider for "./provider.marko" is read-only. Add `:=` to the provider's default attribute to make it writable. + 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.txt new file mode 100644 index 00000000000..4e23c980b6f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-dom.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko:2:21 + 1 | + > 2 | + | ^^^^^^^ Cannot assign to `val`; the provider for "./provider.marko" is read-only. Add `:=` to the provider's default attribute to make it writable. + 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.debug.txt new file mode 100644 index 00000000000..4e23c980b6f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.debug.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko:2:21 + 1 | + > 2 | + | ^^^^^^^ Cannot assign to `val`; the provider for "./provider.marko" is read-only. Add `:=` to the provider's default attribute to make it writable. + 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.txt new file mode 100644 index 00000000000..4e23c980b6f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/__snapshots__/error-compile-html.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko:2:21 + 1 | + > 2 | + | ^^^^^^^ Cannot assign to `val`; the provider for "./provider.marko" is read-only. Add `:=` to the provider's default attribute to make it writable. + 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/provider.marko new file mode 100644 index 00000000000..0c279465187 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/provider.marko @@ -0,0 +1,3 @@ + +
provided
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko new file mode 100644 index 00000000000..d997ed4410b --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/template.marko @@ -0,0 +1,2 @@ + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/test.ts new file mode 100644 index 00000000000..87d511a1f39 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-readonly-assign/test.ts @@ -0,0 +1,5 @@ +import type { TestConfig } from "../../main.test"; + +export const config: TestConfig = { + error_compiler: true, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.debug.txt new file mode 100644 index 00000000000..5f294099b9d --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.debug.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko:1:12 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^ The template "./template.marko" does not provide a value (it has no provide-mode `` tag). + 2 |
${x}
+ 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.txt new file mode 100644 index 00000000000..5f294099b9d --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-dom.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko:1:12 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^ The template "./template.marko" does not provide a value (it has no provide-mode `` tag). + 2 |
${x}
+ 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.debug.txt new file mode 100644 index 00000000000..5f294099b9d --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.debug.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko:1:12 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^ The template "./template.marko" does not provide a value (it has no provide-mode `` tag). + 2 |
${x}
+ 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.txt new file mode 100644 index 00000000000..5f294099b9d --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/__snapshots__/error-compile-html.txt @@ -0,0 +1,6 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko:1:12 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^ The template "./template.marko" does not provide a value (it has no provide-mode `` tag). + 2 |
${x}
+ 3 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko new file mode 100644 index 00000000000..3872e3ebe8c --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/template.marko @@ -0,0 +1,2 @@ + +
${x}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/test.ts new file mode 100644 index 00000000000..87d511a1f39 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-self-not-provider/test.ts @@ -0,0 +1,5 @@ +import type { TestConfig } from "../../main.test"; + +export const config: TestConfig = { + error_compiler: true, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.debug.txt new file mode 100644 index 00000000000..071a3bfce20 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.debug.txt @@ -0,0 +1,9 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko:4:2 + 2 |
first
+ 3 |
+ > 4 | + | ^^^^^^^ A template may only have a single provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context); more than one makes the template's identity as a context provider ambiguous. + 5 |
second
+ 6 |
+ 7 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.txt new file mode 100644 index 00000000000..071a3bfce20 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-dom.txt @@ -0,0 +1,9 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko:4:2 + 2 |
first
+ 3 |
+ > 4 | + | ^^^^^^^ A template may only have a single provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context); more than one makes the template's identity as a context provider ambiguous. + 5 |
second
+ 6 |
+ 7 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.debug.txt new file mode 100644 index 00000000000..071a3bfce20 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.debug.txt @@ -0,0 +1,9 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko:4:2 + 2 |
first
+ 3 |
+ > 4 | + | ^^^^^^^ A template may only have a single provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context); more than one makes the template's identity as a context provider ambiguous. + 5 |
second
+ 6 |
+ 7 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.txt new file mode 100644 index 00000000000..071a3bfce20 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/__snapshots__/error-compile-html.txt @@ -0,0 +1,9 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko:4:2 + 2 |
first
+ 3 |
+ > 4 | + | ^^^^^^^ A template may only have a single provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context); more than one makes the template's identity as a context provider ambiguous. + 5 |
second
+ 6 |
+ 7 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko new file mode 100644 index 00000000000..9d6de2f5593 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/template.marko @@ -0,0 +1,6 @@ + +
first
+
+ +
second
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/test.ts new file mode 100644 index 00000000000..87d511a1f39 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-two-provides/test.ts @@ -0,0 +1,5 @@ +import type { TestConfig } from "../../main.test"; + +export const config: TestConfig = { + error_compiler: true, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.debug.txt new file mode 100644 index 00000000000..4e5f3f1fe71 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.debug.txt @@ -0,0 +1,9 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:14 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^ Unable to resolve the `from=` template "": + at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:1 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unable to find entry point for custom tag . + 2 | + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.txt new file mode 100644 index 00000000000..4e5f3f1fe71 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-dom.txt @@ -0,0 +1,9 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:14 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^ Unable to resolve the `from=` template "": + at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:1 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unable to find entry point for custom tag . + 2 | + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.debug.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.debug.txt new file mode 100644 index 00000000000..4e5f3f1fe71 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.debug.txt @@ -0,0 +1,9 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:14 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^ Unable to resolve the `from=` template "": + at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:1 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unable to find entry point for custom tag . + 2 | + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.txt b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.txt new file mode 100644 index 00000000000..4e5f3f1fe71 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/__snapshots__/error-compile-html.txt @@ -0,0 +1,9 @@ + + at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:14 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^ Unable to resolve the `from=` template "": + at packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko:1:1 + > 1 | + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unable to find entry point for custom tag . + 2 | + 2 | \ No newline at end of file diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko new file mode 100644 index 00000000000..8bcb8cd06d6 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/template.marko @@ -0,0 +1 @@ + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/test.ts new file mode 100644 index 00000000000..87d511a1f39 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-error-unknown-from/test.ts @@ -0,0 +1,5 @@ +import type { TestConfig } from "../../main.test"; + +export const config: TestConfig = { + error_compiler: true, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..69fbf03e0a4 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,48 @@ +// tags/theme-display.marko +const $template$2 = " "; +const $walks$2 = "bD l"; +const $context_theme = _context_closure("__tests__/tags/theme-provider.marko", ($scope2, $provider) => $theme$1($scope2, $provider["#ContextValue"]), "__tests__/tags/theme-display.marko_0_theme/context", ""); +const $theme$1 = /*@__PURE__*/ _const("theme", ($scope) => _text($scope["#text/0"], $scope.theme)); +const $setup$2 = $context_theme; +var theme_display_default = /*@__PURE__*/ _template("__tests__/tags/theme-display.marko", $template$2, $walks$2, $setup$2); + +// tags/theme-provider.marko +const $template$1 = ""; +const $walks$1 = "b%b b"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +const $theme = /*@__PURE__*/ _let("theme/5", ($scope) => _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko")); +const $setup__script = _script("__tests__/tags/theme-provider.marko_0", ($scope) => _on($scope["#button/1"], "click", function() { + $theme($scope, "dark"); +})); +function $setup$1($scope) { + _context_branch($scope, "__tests__/tags/theme-provider.marko"); + $theme($scope, "light"); + $setup__script($scope); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1); +_enable_catch(); +const $await_content__setup = ($scope) => { + $setup$2($scope["#childScope/0"]); +}; +const $placeholder_content = _content_resume("__tests__/template.marko_3_content", "loading", "b"); +const $await_content = /*@__PURE__*/ _await_content("#text/0", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$2), $await_content__setup); +const $try_content__await_promise = /*@__PURE__*/ _await_promise("#text/0"); +const $try_content__setup = ($scope) => { + $await_content($scope); + $try_content__await_promise($scope, resolveAfter(0, 1)); +}; +const $themeprovider_content__try = /*@__PURE__*/ _try("#text/0", "", "b%c", $try_content__setup); +const $themeprovider_content__setup = ($scope) => $themeprovider_content__try($scope, { placeholder: attrTag({ content: $placeholder_content($scope) }) }); +const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "", "b%c", $themeprovider_content__setup); +function $setup($scope) { + $setup$1($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope)); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..02baa5eaddf --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/dom.bundle.js @@ -0,0 +1,13 @@ +// tags/theme-display.marko +const $context_theme = _context_closure("c", ($scope2, $provider) => $theme$1($scope2, $provider["I"]), "b0"); +const $theme$1 = /*@__PURE__*/ _const(1, ($scope) => _text($scope.a, $scope.b)); + +// tags/theme-provider.marko +const $theme = /*@__PURE__*/ _let(5, ($scope) => _context_value($scope, $scope.f, "c")); +const $setup__script = _script("c0", ($scope) => _on($scope.b, "click", function() { + $theme($scope, "dark"); +})); + +// template.marko +_enable_catch(); +const $placeholder_content = _content_resume("a0", "loading", "b"); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..0a268a25dcf --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.debug.js @@ -0,0 +1,46 @@ +// tags/theme-display.marko +var theme_display_default = _template("__tests__/tags/theme-display.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + const theme = _context_link($scope0_id, "__tests__/tags/theme-provider.marko", "__tests__/tags/theme-display.marko_0_theme/context", ""); + _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")}`); + writeScope($scope0_id, {}, "__tests__/tags/theme-display.marko", 0); + _resume_branch($scope0_id); +}); + +// tags/theme-provider.marko +var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let theme = "light"; + _context_provide("__tests__/tags/theme-provider.marko", theme, () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "#button/1")}`); + _script($scope0_id, "__tests__/tags/theme-provider.marko_0"); + writeScope($scope0_id, {}, "__tests__/tags/theme-provider.marko", 0); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + theme_provider_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + _try($scope1_id, "#text/0", _content_resume("__tests__/template.marko_2_content", () => { + const $scope2_id = _scope_id(); + _scope_reason(); + _await($scope2_id, "#text/0", resolveAfter(0, 1), (_ignored) => { + const $scope4_id = _scope_id(); + theme_display_default({}); + }, 0); + }, $scope1_id), { placeholder: attrTag({ content: _content_resume("__tests__/template.marko_3_content", () => { + _scope_reason(); + const $scope3_id = _scope_id(); + _html("loading"); + }, $scope1_id) }) }); + }) }); + writeScope($scope0_id, {}, "__tests__/template.marko", 0); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..cf1754fa059 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/html.bundle.js @@ -0,0 +1,44 @@ +// tags/theme-display.marko +var theme_display_default = _template("b", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + _html(`${_escape(_context_link($scope0_id, "c", "b0"))}${_el_resume($scope0_id, "a")}`); + writeScope($scope0_id, {}); + _resume_branch($scope0_id); +}); + +// tags/theme-provider.marko +var theme_provider_default = _template("c", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("c", "light", () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "b")}`); + _script($scope0_id, "c0"); + writeScope($scope0_id, {}); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("a", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + theme_provider_default({ content: _content("a2", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + _try($scope1_id, "a", _content_resume("a1", () => { + const $scope2_id = _scope_id(); + _scope_reason(); + _await($scope2_id, "a", resolveAfter(0, 1), (_ignored) => { + _scope_id(); + theme_display_default({}); + }, 0); + }, $scope1_id), { placeholder: attrTag({ content: _content_resume("a0", () => { + _scope_reason(); + _scope_id(); + _html("loading"); + }, $scope1_id) }) }); + }) }); + writeScope($scope0_id, {}); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-csr.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-csr.debug.md new file mode 100644 index 00000000000..65a8223afa9 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-csr.debug.md @@ -0,0 +1,63 @@ +# Render +```html + +``` + +# Update +```html +loading + +``` +## Change +``` +INSERT: ::text("loading") +``` + +# Update +```html + + light + + +``` +## Change +``` +INSERT: .display +REMOVE: .display + ::text("loading") +UPDATE: .display::text " " => "light" +``` + +# Update +```js +container.querySelector("button.change").click(); +``` +```html + + dark + + +``` +## Change +``` +UPDATE: .display::text "light" => "dark" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.debug.md new file mode 100644 index 00000000000..f3146a775b5 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.debug.md @@ -0,0 +1,34 @@ +# Render +```html +loading + +``` + +# Update +```html + + light + + +``` +## Change +``` +INSERT: .display::text("light") +REMOVE: ::text("loading") +INSERT: .display +``` + +# Update +```js +container.querySelector("button.change").click(); +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.md new file mode 100644 index 00000000000..f3146a775b5 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/render-ssr.md @@ -0,0 +1,34 @@ +# Render +```html +loading + +``` + +# Update +```html + + light + + +``` +## Change +``` +INSERT: .display::text("light") +REMOVE: ::text("loading") +INSERT: .display +``` + +# Update +```js +container.querySelector("button.change").click(); +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..45e092f2ba5 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.debug.html @@ -0,0 +1,37 @@ +loading + + + + + + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.html new file mode 100644 index 00000000000..d4c29eb985b --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/__snapshots__/writes.html @@ -0,0 +1,28 @@ +loading + + + + + + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/sizes.json new file mode 100644 index 00000000000..5fe8d6d9309 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/sizes.json @@ -0,0 +1,19 @@ +{ + "dom": { + "template.marko.page.mjs": { + "total": { + "min": 7776, + "brotli": 3408 + }, + "files": { + "tags/theme-display.marko": 240, + "tags/theme-provider.marko": 253, + "template.marko": 123 + } + } + }, + "html": { + "min": 1078, + "brotli": 619 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-display.marko new file mode 100644 index 00000000000..60b9ab808db --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-display.marko @@ -0,0 +1,2 @@ + +${theme} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-provider.marko new file mode 100644 index 00000000000..d9808943a47 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/tags/theme-provider.marko @@ -0,0 +1,5 @@ + + + <${input.content}/> + +change diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/template.marko new file mode 100644 index 00000000000..321c29f382a --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/template.marko @@ -0,0 +1,12 @@ +import { resolveAfter } from "../../utils/resolve" + +// A mutable consumer inside a deferred `` body subscribes from a +// later flush; a provider change after resume must still reach it. + + + <@placeholder>loading + + + + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/test.ts new file mode 100644 index 00000000000..3e0946ba3d1 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-await/test.ts @@ -0,0 +1,12 @@ +import type { TestConfig } from "../../main.test"; +import { after, flush } from "../../utils/resolve"; + +function change(container: Element) { + container.querySelector("button.change")!.click(); +} + +export const config: TestConfig = { + // csr and ssr render logs legitimately differ for awaited content. + equivalent: false, + steps: [{}, after(1), flush, change], +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..1aa15b3f354 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,39 @@ +// tags/inert-log.marko +const $template$2 = "
inert
"; +const $walks$2 = "c"; +const $setup$2 = () => {}; +var inert_log_default = /*@__PURE__*/ _template("__tests__/tags/inert-log.marko", $template$2, "c", $setup$2); + +// tags/theme-provider.marko +const $template$1 = " "; +const $walks$1 = "bD l%b b"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/1"); +const $theme = /*@__PURE__*/ _let("theme/6", ($scope) => { + _text($scope["#text/0"], $scope.theme); + _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko"); +}); +const $setup__script = _script("__tests__/tags/theme-provider.marko_0", ($scope) => _on($scope["#button/2"], "click", function() { + $theme($scope, $scope.theme === "light" ? "dark" : "light"); +})); +function $setup$1($scope) { + _context_branch($scope, "__tests__/tags/theme-provider.marko"); + $theme($scope, "light"); + $setup__script($scope); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/1"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1); +const $themeprovider_content__setup = ($scope) => { + /* @__PURE__ */ $setup$2($scope["#childScope/0"]); +}; +const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)("c"), $themeprovider_content__setup); +function $setup($scope) { + $setup$1($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope)); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..ea25da16a9f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/dom.bundle.js @@ -0,0 +1,8 @@ +// tags/theme-provider.marko +const $theme = /*@__PURE__*/ _let(6, ($scope) => { + _text($scope.a, $scope.g); + _context_value($scope, $scope.g, "c"); +}); +const $setup__script = _script("c0", ($scope) => _on($scope.c, "click", function() { + $theme($scope, $scope.g === "light" ? "dark" : "light"); +})); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..341ecd70863 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.debug.js @@ -0,0 +1,34 @@ +// tags/inert-log.marko +var inert_log_default = _template("__tests__/tags/inert-log.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + const theme = _context_get("__tests__/tags/theme-provider.marko", ""); + _html("
inert
"); +}); + +// tags/theme-provider.marko +var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let theme = "light"; + _context_provide("__tests__/tags/theme-provider.marko", theme, () => { + _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")}`); + _dynamic_tag($scope0_id, "#text/1", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "#button/2")}`); + _script($scope0_id, "__tests__/tags/theme-provider.marko_0"); + writeScope($scope0_id, { theme }, "__tests__/tags/theme-provider.marko", 0, { theme: "2:6" }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + theme_provider_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + inert_log_default({}); + }) }); + writeScope($scope0_id, {}, "__tests__/template.marko", 0); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..77dd3e42b1c --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/html.bundle.js @@ -0,0 +1,34 @@ +// tags/inert-log.marko +var inert_log_default = _template("b", (input) => { + _scope_reason(); + _scope_id(); + _context_get("c"); + _html("
inert
"); +}); + +// tags/theme-provider.marko +var theme_provider_default = _template("c", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let theme = "light"; + _context_provide("c", theme, () => { + _html(`${_escape(theme)}${_el_resume($scope0_id, "a")}`); + _dynamic_tag($scope0_id, "b", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "c")}`); + _script($scope0_id, "c0"); + writeScope($scope0_id, { g: theme }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("a", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + theme_provider_default({ content: _content("a0", () => { + _scope_reason(); + _scope_id(); + inert_log_default({}); + }) }); + writeScope($scope0_id, {}); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.debug.md new file mode 100644 index 00000000000..fd2edca2c76 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.debug.md @@ -0,0 +1,36 @@ +# Render +```html + + light + +
+ inert +
+ +``` + +# Update +```js +container.querySelector("button.toggle").click(); +``` +```html + + dark + +
+ inert +
+ +``` +## Change +``` +UPDATE: span::text "light" => "dark" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.md new file mode 100644 index 00000000000..fd2edca2c76 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/render.md @@ -0,0 +1,36 @@ +# Render +```html + + light + +
+ inert +
+ +``` + +# Update +```js +container.querySelector("button.toggle").click(); +``` +```html + + dark + +
+ inert +
+ +``` +## Change +``` +UPDATE: span::text "light" => "dark" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..1ce641e4925 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.debug.html @@ -0,0 +1,16 @@ +light +
inert
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.html new file mode 100644 index 00000000000..6ac936f28a9 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/__snapshots__/writes.html @@ -0,0 +1,13 @@ +light +
inert
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/sizes.json new file mode 100644 index 00000000000..44c34ceceed --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/sizes.json @@ -0,0 +1,12 @@ +{ + "dom": { + "template.marko.page.mjs": { + "min": 2776, + "brotli": 1405 + } + }, + "html": { + "min": 442, + "brotli": 302 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/inert-log.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/inert-log.marko new file mode 100644 index 00000000000..f5751a0d369 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/inert-log.marko @@ -0,0 +1,4 @@ +// This consumer never observes the value client-side, so no wiring +// serializes and no fan-out signal reaches the bundle (`bundle_excludes`). + +
inert
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/theme-provider.marko new file mode 100644 index 00000000000..2a223538945 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/tags/theme-provider.marko @@ -0,0 +1,9 @@ +// Mutable provider: ``-derived, so consumers subscribe. + + + ${theme} + <${input.content}/> +
+ + toggle + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/template.marko new file mode 100644 index 00000000000..ac87bdf513f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/template.marko @@ -0,0 +1,3 @@ + + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/test.ts new file mode 100644 index 00000000000..4e2068c3504 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-inert/test.ts @@ -0,0 +1,12 @@ +import type { TestConfig } from "../../main.test"; + +function toggle(container: Element) { + container.querySelector("button.toggle")!.click(); +} + +export const config: TestConfig = { + steps: [{}, toggle], + // Even with a mutable provider, an unobserved consumer serializes no + // wiring and registers no fan-out signal. + bundle_excludes: { dom: ["_context_closure"] }, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..3bd312a2a99 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,31 @@ +// tags/theme-provider.marko +const $template$1 = ""; +const $walks$1 = "b%b b"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +const $theme = /*@__PURE__*/ _let("theme/5", ($scope) => _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko")); +const $setup__script = _script("__tests__/tags/theme-provider.marko_0", ($scope) => _on($scope["#button/1"], "click", function() { + $theme($scope, $scope.theme === "light" ? "dark" : "light"); +})); +function $setup$1($scope) { + _context_branch($scope, "__tests__/tags/theme-provider.marko"); + $theme($scope, "light"); + $setup__script($scope); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1); +const $context_theme = _context_closure("__tests__/tags/theme-provider.marko", ($scope2, $provider) => ($themeprovider_content__theme($scope2, $provider["#ContextValue"]), $themeprovider_content__accent($scope2, $provider["#ContextValue"])), "__tests__/template.marko_1_theme/context", ""); +const $themeprovider_content__theme = /*@__PURE__*/ _const("theme", ($scope) => _text($scope["#text/0"], $scope.theme)); +const $themeprovider_content__accent = /*@__PURE__*/ _const("accent", ($scope) => _text($scope["#text/1"], $scope.accent === "light" ? "gold" : "silver")); +const $themeprovider_content__setup = $context_theme; +const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", " ", "bD lD l", $themeprovider_content__setup); +function $setup($scope) { + $setup$1($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope)); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..74668e68c22 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/dom.bundle.js @@ -0,0 +1,10 @@ +// tags/theme-provider.marko +const $theme = /*@__PURE__*/ _let(5, ($scope) => _context_value($scope, $scope.f, "b")); +const $setup__script = _script("b0", ($scope) => _on($scope.b, "click", function() { + $theme($scope, $scope.f === "light" ? "dark" : "light"); +})); + +// template.marko +const $context_theme = _context_closure("b", ($scope2, $provider) => ($themeprovider_content__theme($scope2, $provider["I"]), $themeprovider_content__accent($scope2, $provider["I"])), "a0"); +const $themeprovider_content__theme = /*@__PURE__*/ _const(2, ($scope) => _text($scope.a, $scope.c)); +const $themeprovider_content__accent = /*@__PURE__*/ _const(3, ($scope) => _text($scope.b, $scope.d === "light" ? "gold" : "silver")); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..468e35d1ba3 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.debug.js @@ -0,0 +1,29 @@ +// tags/theme-provider.marko +var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let theme = "light"; + _context_provide("__tests__/tags/theme-provider.marko", theme, () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "#button/1")}`); + _script($scope0_id, "__tests__/tags/theme-provider.marko_0"); + writeScope($scope0_id, { theme }, "__tests__/tags/theme-provider.marko", 0, { theme: "2:6" }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + theme_provider_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + const theme = _context_link($scope1_id, "__tests__/tags/theme-provider.marko", "__tests__/template.marko_1_theme/context", ""); + const accent = _context_get("__tests__/tags/theme-provider.marko", ""); + _html(`${_escape(theme)}${_el_resume($scope1_id, "#text/0")}${accent === "light" ? "gold" : "silver"}${_el_resume($scope1_id, "#text/1")}`); + writeScope($scope1_id, {}, "__tests__/template.marko", "3:2"); + _resume_branch($scope1_id); + }) }); + writeScope($scope0_id, {}, "__tests__/template.marko", 0); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..980c993f7b5 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/html.bundle.js @@ -0,0 +1,29 @@ +// tags/theme-provider.marko +var theme_provider_default = _template("b", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let theme = "light"; + _context_provide("b", theme, () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "b")}`); + _script($scope0_id, "b0"); + writeScope($scope0_id, { f: theme }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("a", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + theme_provider_default({ content: _content("a1", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + const theme = _context_link($scope1_id, "b", "a0"); + const accent = _context_get("b"); + _html(`${_escape(theme)}${_el_resume($scope1_id, "a")}${accent === "light" ? "gold" : "silver"}${_el_resume($scope1_id, "b")}`); + writeScope($scope1_id, {}); + _resume_branch($scope1_id); + }) }); + writeScope($scope0_id, {}); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.debug.md new file mode 100644 index 00000000000..72723f4ae3b --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.debug.md @@ -0,0 +1,60 @@ +# Render +```html + + light + + + gold + + +``` + +# Update +```js +container.querySelector("button.toggle").click(); +``` +```html + + dark + + + silver + + +``` +## Change +``` +UPDATE: span::text "light" => "dark" +UPDATE: em::text "gold" => "silver" +``` + +# Update +```js +container.querySelector("button.toggle").click(); +``` +```html + + light + + + gold + + +``` +## Change +``` +UPDATE: span::text "dark" => "light" +UPDATE: em::text "silver" => "gold" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.md new file mode 100644 index 00000000000..72723f4ae3b --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/render.md @@ -0,0 +1,60 @@ +# Render +```html + + light + + + gold + + +``` + +# Update +```js +container.querySelector("button.toggle").click(); +``` +```html + + dark + + + silver + + +``` +## Change +``` +UPDATE: span::text "light" => "dark" +UPDATE: em::text "gold" => "silver" +``` + +# Update +```js +container.querySelector("button.toggle").click(); +``` +```html + + light + + + gold + + +``` +## Change +``` +UPDATE: span::text "dark" => "light" +UPDATE: em::text "silver" => "gold" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..c6d3a55ec62 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.debug.html @@ -0,0 +1,23 @@ +lightgold + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.html new file mode 100644 index 00000000000..085418c7396 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/__snapshots__/writes.html @@ -0,0 +1,16 @@ +lightgold + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/sizes.json new file mode 100644 index 00000000000..83f04ac218a --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/sizes.json @@ -0,0 +1,18 @@ +{ + "dom": { + "template.marko.page.mjs": { + "total": { + "min": 3352, + "brotli": 1659 + }, + "files": { + "tags/theme-provider.marko": 286, + "template.marko": 466 + } + } + }, + "html": { + "min": 480, + "brotli": 316 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/tags/theme-provider.marko new file mode 100644 index 00000000000..3efeca46d5d --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/tags/theme-provider.marko @@ -0,0 +1,8 @@ +// Mutable provider: ``-derived, so consumers subscribe. + + + <${input.content}/> +
+ + toggle + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/template.marko new file mode 100644 index 00000000000..03ff9e26cb4 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/template.marko @@ -0,0 +1,8 @@ +// Same-scope consumers of one provider compose into a single fan-out entry; +// an update must reach both. + + + + ${theme} + ${accent === "light" ? "gold" : "silver"} + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/test.ts new file mode 100644 index 00000000000..a99c7830772 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-multi/test.ts @@ -0,0 +1,9 @@ +import type { TestConfig } from "../../main.test"; + +function toggle(container: Element) { + container.querySelector("button.toggle")!.click(); +} + +export const config: TestConfig = { + steps: [{}, toggle, toggle], +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..a9d03b577f2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,61 @@ +// tags/theme-display.marko +const $template$2 = " "; +const $walks$2 = "bD l"; +const $context_theme = _context_closure("__tests__/tags/theme-provider.marko", ($scope2, $provider) => $theme$1($scope2, $provider["#ContextValue"]), "__tests__/tags/theme-display.marko_0_theme/context", ""); +const $theme$1 = /*@__PURE__*/ _const("theme", ($scope) => _text($scope["#text/0"], $scope.theme)); +const $setup$2 = $context_theme; +var theme_display_default = /*@__PURE__*/ _template("__tests__/tags/theme-display.marko", $template$2, $walks$2, $setup$2); + +// tags/theme-provider.marko +const $template$1 = ""; +const $walks$1 = "b%b b"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +const $theme = /*@__PURE__*/ _let("theme/6", ($scope) => _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko")); +const $input_id__script = _script("__tests__/tags/theme-provider.marko_0_input_id", ($scope) => _on($scope["#button/1"], "click", function() { + $theme($scope, `dark-${$scope.input_id}`); +})); +const $input_id = /*@__PURE__*/ _const("input_id", ($scope) => { + _attr_class($scope["#button/1"], `change-${$scope.input_id}`); + $theme($scope, `light-${$scope.input_id}`); + $input_id__script($scope); +}); +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content = $dynamicTag; +function $setup$1($scope) { + _context_branch($scope, "__tests__/tags/theme-provider.marko"); +} +const $input = ($scope, input) => { + $input_id($scope, input.id); + $input_content($scope, input.content); +}; +var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input); + +// template.marko +const $template = ""; +const $walks = "b%c"; +const $if_content__setup = ($scope) => { + $setup$2($scope["#childScope/0"]); +}; +const $themeprovider_content__id = /*@__PURE__*/ _closure_get("id", ($scope) => _attr_class($scope["#button/0"], `show-${$scope._.id}`)); +const $themeprovider_content__if = /*@__PURE__*/ _if("#text/1", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$2), $if_content__setup); +const $themeprovider_content__show = /*@__PURE__*/ _let("show/2", ($scope) => $themeprovider_content__if($scope, $scope.show ? 0 : 1)); +const $themeprovider_content__setup__script = _script("__tests__/template.marko_2", ($scope) => _on($scope["#button/0"], "click", function() { + $themeprovider_content__show($scope, true); +})); +const $themeprovider_content__setup = ($scope) => { + $themeprovider_content__id($scope); + $themeprovider_content__show($scope, false); + $themeprovider_content__setup__script($scope); +}; +const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_2_content", "", " b%c", $themeprovider_content__setup); +const $for_content__setup = ($scope) => { + $setup$1($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope)); +}; +const $for_content__id = /*@__PURE__*/ _const("id", ($scope) => $input_id($scope["#childScope/0"], $scope.id)); +const $for_content__$params = ($scope, $params2) => $for_content__id($scope, $params2[0]); +const $for = /*@__PURE__*/ _for_of("#text/0", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1), $for_content__setup, $for_content__$params); +function $setup($scope) { + $for($scope, [[0, 1], (id) => id]); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, "b%c", $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..5552e787800 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/dom.bundle.js @@ -0,0 +1,22 @@ +// tags/theme-display.marko +const $template = " "; +const $walks = "bD l"; +const $context_theme = _context_closure("c", ($scope2, $provider) => $theme$1($scope2, $provider["I"]), "b0"); +const $theme$1 = /*@__PURE__*/ _const(1, ($scope) => _text($scope.a, $scope.b)); +const $setup = $context_theme; + +// tags/theme-provider.marko +const $theme = /*@__PURE__*/ _let(6, ($scope) => _context_value($scope, $scope.g, "c")); +const $input_id__script = _script("c0", ($scope) => _on($scope.b, "click", function() { + $theme($scope, `dark-${$scope.e}`); +})); + +// template.marko +const $if_content__setup = ($scope) => { + $setup($scope.a); +}; +const $themeprovider_content__if = /*@__PURE__*/ _if(1, /*@__PURE__*/ ((_w0) => `${_w0}`)($template), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks), $if_content__setup); +const $themeprovider_content__show = /*@__PURE__*/ _let(2, ($scope) => $themeprovider_content__if($scope, $scope.c ? 0 : 1)); +const $themeprovider_content__setup__script = _script("a0", ($scope) => _on($scope.a, "click", function() { + $themeprovider_content__show($scope, true); +})); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..a846173e864 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.debug.js @@ -0,0 +1,53 @@ +// tags/theme-display.marko +var theme_display_default = _template("__tests__/tags/theme-display.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + const theme = _context_link($scope0_id, "__tests__/tags/theme-provider.marko", "__tests__/tags/theme-display.marko_0_theme/context", ""); + _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")}`); + writeScope($scope0_id, {}, "__tests__/tags/theme-display.marko", 0); + _resume_branch($scope0_id); +}); + +// tags/theme-provider.marko +var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let theme = `light-${input.id}`; + _context_provide("__tests__/tags/theme-provider.marko", theme, () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`change${_el_resume($scope0_id, "#button/1")}`); + _script($scope0_id, "__tests__/tags/theme-provider.marko_0_input_id"); + writeScope($scope0_id, { input_id: input.id }, "__tests__/tags/theme-provider.marko", 0, { input_id: ["input.id"] }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + _for_of([0, 1], (id) => { + const $scope1_id = _scope_id(); + theme_provider_default({ + id, + content: _content("__tests__/template.marko_2_content", () => { + _scope_reason(); + const $scope2_id = _scope_id(); + let show = false; + _html(`show${_el_resume($scope2_id, "#button/0")}`); + _if(() => { + if (show) { + const $scope3_id = _scope_id(); + theme_display_default({}); + writeScope($scope3_id, {}, "__tests__/template.marko", "7:6"); + return 0; + } + }, $scope2_id, "#text/1"); + _script($scope2_id, "__tests__/template.marko_2"); + writeScope($scope2_id, {}, "__tests__/template.marko", "4:4"); + _resume_branch($scope2_id); + }) + }); + writeScope($scope1_id, {}, "__tests__/template.marko", "3:2"); + }, (id) => id, $scope0_id, "#text/0", 1, 0, 0); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..fdd99f5084f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/html.bundle.js @@ -0,0 +1,42 @@ +// tags/theme-display.marko +var theme_display_default = _template("b", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + _html(`${_escape(_context_link($scope0_id, "c", "b0"))}${_el_resume($scope0_id, "a")}`); + writeScope($scope0_id, {}); + _resume_branch($scope0_id); +}); + +// tags/theme-provider.marko +var theme_provider_default = _template("c", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("c", `light-${input.id}`, () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`change${_el_resume($scope0_id, "b")}`); + _script($scope0_id, "c0"); + writeScope($scope0_id, { e: input.id }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("a", (input) => { + _scope_reason(); + _for_of([0, 1], (id) => { + const $scope1_id = _scope_id(); + theme_provider_default({ + id, + content: _content("a1", () => { + _scope_reason(); + const $scope2_id = _scope_id(); + _html(`show${_el_resume($scope2_id, "a")}`); + _if(() => {}, $scope2_id, "b"); + _script($scope2_id, "a0"); + writeScope($scope2_id, {}); + _resume_branch($scope2_id); + }) + }); + writeScope($scope1_id, {}); + }, (id) => id, _scope_id(), "a", 1, 0, 0); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.debug.md new file mode 100644 index 00000000000..5a169660786 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.debug.md @@ -0,0 +1,143 @@ +# Render +```html + + + + +``` + +# Update +```js +container.querySelector(sel).click(); +``` +```html + + + light-0 + + + + +``` +## Change +``` +INSERT: .show-0 + .display +UPDATE: .display::text " " => "light-0" +``` + +# Update +```js +container.querySelector(sel).click(); +``` +```html + + + light-0 + + + + + light-1 + + +``` +## Change +``` +INSERT: .show-1 + span +UPDATE: span:nth-of-type(2)::text " " => "light-1" +``` + +# Update +```js +container.querySelector(sel).click(); +``` +```html + + + light-0 + + + + + dark-1 + + +``` +## Change +``` +UPDATE: span:nth-of-type(2)::text "light-1" => "dark-1" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.md new file mode 100644 index 00000000000..5a169660786 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/render.md @@ -0,0 +1,143 @@ +# Render +```html + + + + +``` + +# Update +```js +container.querySelector(sel).click(); +``` +```html + + + light-0 + + + + +``` +## Change +``` +INSERT: .show-0 + .display +UPDATE: .display::text " " => "light-0" +``` + +# Update +```js +container.querySelector(sel).click(); +``` +```html + + + light-0 + + + + + light-1 + + +``` +## Change +``` +INSERT: .show-1 + span +UPDATE: span:nth-of-type(2)::text " " => "light-1" +``` + +# Update +```js +container.querySelector(sel).click(); +``` +```html + + + light-0 + + + + + dark-1 + + +``` +## Change +``` +UPDATE: span:nth-of-type(2)::text "light-1" => "dark-1" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..9cf66d122c9 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.debug.html @@ -0,0 +1,34 @@ + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.html new file mode 100644 index 00000000000..b2aca21478c --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/__snapshots__/writes.html @@ -0,0 +1,29 @@ + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/sizes.json new file mode 100644 index 00000000000..3f6bbc31e8e --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/sizes.json @@ -0,0 +1,19 @@ +{ + "dom": { + "template.marko.page.mjs": { + "total": { + "min": 6857, + "brotli": 3090 + }, + "files": { + "tags/theme-display.marko": 347, + "tags/theme-provider.marko": 268, + "template.marko": 557 + } + } + }, + "html": { + "min": 672, + "brotli": 359 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-display.marko new file mode 100644 index 00000000000..60b9ab808db --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-display.marko @@ -0,0 +1,2 @@ + +${theme} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-provider.marko new file mode 100644 index 00000000000..7bb3a9abc53 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/tags/theme-provider.marko @@ -0,0 +1,8 @@ +// Mutable provider: ``-derived, so consumers subscribe. + + + <${input.content}/> + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/template.marko new file mode 100644 index 00000000000..4686eeee04b --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/template.marko @@ -0,0 +1,11 @@ +// Providers inside keyed `` iterations: each iteration's branch carries +// its own link entry, resolved by client-created consumers within it. + id> + + + + + +
+ + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/test.ts new file mode 100644 index 00000000000..2196da52861 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-nested/test.ts @@ -0,0 +1,10 @@ +import type { TestConfig } from "../../main.test"; + +function click(sel: string) { + return (container: Element) => + container.querySelector(sel)!.click(); +} + +export const config: TestConfig = { + steps: [{}, click(".show-0"), click(".show-1"), click(".change-1")], +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..d69b996f791 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,47 @@ +// tags/theme-display.marko +const $template$2 = " "; +const $walks$2 = "bD l"; +const $context_theme = _context_closure("__tests__/tags/theme-provider.marko", ($scope2, $provider) => $theme$1($scope2, $provider["#ContextValue"]), "__tests__/tags/theme-display.marko_0_theme/context", ""); +const $theme$1 = /*@__PURE__*/ _const("theme", ($scope) => _text($scope["#text/0"], $scope.theme)); +const $setup$2 = $context_theme; +var theme_display_default = /*@__PURE__*/ _template("__tests__/tags/theme-display.marko", $template$2, $walks$2, $setup$2); + +// tags/theme-provider.marko +const $template$1 = ""; +const $walks$1 = "b%b b"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +const $theme = /*@__PURE__*/ _let("theme/5", ($scope) => _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko")); +const $setup__script = _script("__tests__/tags/theme-provider.marko_0", ($scope) => _on($scope["#button/1"], "click", function() { + $theme($scope, "dark"); +})); +function $setup$1($scope) { + _context_branch($scope, "__tests__/tags/theme-provider.marko"); + $theme($scope, "light"); + $setup__script($scope); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1); +const $if_content__setup = ($scope) => { + $setup$2($scope["#childScope/0"]); +}; +const $themeprovider_content__if = /*@__PURE__*/ _if("#text/1", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$2), $if_content__setup); +const $themeprovider_content__show = /*@__PURE__*/ _let("show/2", ($scope) => $themeprovider_content__if($scope, $scope.show ? 0 : 1)); +const $themeprovider_content__setup__script = _script("__tests__/template.marko_1", ($scope) => _on($scope["#button/0"], "click", function() { + $themeprovider_content__show($scope, true); +})); +const $themeprovider_content__setup = ($scope) => { + $themeprovider_content__show($scope, false); + $themeprovider_content__setup__script($scope); +}; +const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "", " b%c", $themeprovider_content__setup); +function $setup($scope) { + $setup$1($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope)); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..915f42576c6 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/dom.bundle.js @@ -0,0 +1,22 @@ +// tags/theme-display.marko +const $template = " "; +const $walks = "bD l"; +const $context_theme = _context_closure("c", ($scope2, $provider) => $theme$1($scope2, $provider["I"]), "b0"); +const $theme$1 = /*@__PURE__*/ _const(1, ($scope) => _text($scope.a, $scope.b)); +const $setup = $context_theme; + +// tags/theme-provider.marko +const $theme = /*@__PURE__*/ _let(5, ($scope) => _context_value($scope, $scope.f, "c")); +const $setup__script = _script("c0", ($scope) => _on($scope.b, "click", function() { + $theme($scope, "dark"); +})); + +// template.marko +const $if_content__setup = ($scope) => { + $setup($scope.a); +}; +const $themeprovider_content__if = /*@__PURE__*/ _if(1, /*@__PURE__*/ ((_w0) => `${_w0}`)($template), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks), $if_content__setup); +const $themeprovider_content__show = /*@__PURE__*/ _let(2, ($scope) => $themeprovider_content__if($scope, $scope.c ? 0 : 1)); +const $themeprovider_content__setup__script = _script("a0", ($scope) => _on($scope.a, "click", function() { + $themeprovider_content__show($scope, true); +})); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..1a01ed615b5 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.debug.js @@ -0,0 +1,47 @@ +// tags/theme-display.marko +var theme_display_default = _template("__tests__/tags/theme-display.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + const theme = _context_link($scope0_id, "__tests__/tags/theme-provider.marko", "__tests__/tags/theme-display.marko_0_theme/context", ""); + _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")}`); + writeScope($scope0_id, {}, "__tests__/tags/theme-display.marko", 0); + _resume_branch($scope0_id); +}); + +// tags/theme-provider.marko +var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let theme = "light"; + _context_provide("__tests__/tags/theme-provider.marko", theme, () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "#button/1")}`); + _script($scope0_id, "__tests__/tags/theme-provider.marko_0"); + writeScope($scope0_id, {}, "__tests__/tags/theme-provider.marko", 0); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + theme_provider_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + let show = false; + _html(`${_el_resume($scope1_id, "#button/0")}`); + _if(() => { + if (show) { + const $scope2_id = _scope_id(); + theme_display_default({}); + writeScope($scope2_id, {}, "__tests__/template.marko", "6:4"); + return 0; + } + }, $scope1_id, "#text/1"); + _script($scope1_id, "__tests__/template.marko_1"); + writeScope($scope1_id, {}, "__tests__/template.marko", "3:2"); + _resume_branch($scope1_id); + }) }); + writeScope($scope0_id, {}, "__tests__/template.marko", 0); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..c9706ce1918 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/html.bundle.js @@ -0,0 +1,37 @@ +// tags/theme-display.marko +var theme_display_default = _template("b", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + _html(`${_escape(_context_link($scope0_id, "c", "b0"))}${_el_resume($scope0_id, "a")}`); + writeScope($scope0_id, {}); + _resume_branch($scope0_id); +}); + +// tags/theme-provider.marko +var theme_provider_default = _template("c", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("c", "light", () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "b")}`); + _script($scope0_id, "c0"); + writeScope($scope0_id, {}); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("a", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + theme_provider_default({ content: _content("a1", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + _html(`${_el_resume($scope1_id, "a")}`); + _if(() => {}, $scope1_id, "b"); + _script($scope1_id, "a0"); + writeScope($scope1_id, {}); + _resume_branch($scope1_id); + }) }); + writeScope($scope0_id, {}); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.debug.md new file mode 100644 index 00000000000..034b1318153 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.debug.md @@ -0,0 +1,66 @@ +# Render +```html + + +``` + +# Update +```js +container.querySelector("button.show").click(); +``` +```html + + + light + + +``` +## Change +``` +INSERT: .show + .display +UPDATE: .display::text " " => "light" +``` + +# Update +```js +container.querySelector("button.change").click(); +``` +```html + + + dark + + +``` +## Change +``` +UPDATE: .display::text "light" => "dark" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.md new file mode 100644 index 00000000000..034b1318153 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/render.md @@ -0,0 +1,66 @@ +# Render +```html + + +``` + +# Update +```js +container.querySelector("button.show").click(); +``` +```html + + + light + + +``` +## Change +``` +INSERT: .show + .display +UPDATE: .display::text " " => "light" +``` + +# Update +```js +container.querySelector("button.change").click(); +``` +```html + + + dark + + +``` +## Change +``` +UPDATE: .display::text "light" => "dark" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..6cb5ddf5af0 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.debug.html @@ -0,0 +1,16 @@ + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.html new file mode 100644 index 00000000000..1a1fcf50eda --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/__snapshots__/writes.html @@ -0,0 +1,12 @@ + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/sizes.json new file mode 100644 index 00000000000..163681278b2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/sizes.json @@ -0,0 +1,19 @@ +{ + "dom": { + "template.marko.page.mjs": { + "total": { + "min": 6850, + "brotli": 3086 + }, + "files": { + "tags/theme-display.marko": 347, + "tags/theme-provider.marko": 253, + "template.marko": 557 + } + } + }, + "html": { + "min": 458, + "brotli": 299 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-display.marko new file mode 100644 index 00000000000..60b9ab808db --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-display.marko @@ -0,0 +1,2 @@ + +${theme} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-provider.marko new file mode 100644 index 00000000000..d9808943a47 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/tags/theme-provider.marko @@ -0,0 +1,5 @@ + + + <${input.content}/> + +change diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/template.marko new file mode 100644 index 00000000000..9abf3edb3db --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/template.marko @@ -0,0 +1,9 @@ +// SSR renders the `` off; the first click creates the consumer branch +// client-side (resolved via the serialized entry), the second updates it. + + + show + + + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/test.ts new file mode 100644 index 00000000000..6e8dde4ad9e --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable-toggle/test.ts @@ -0,0 +1,13 @@ +import type { TestConfig } from "../../main.test"; + +function show(container: Element) { + container.querySelector("button.show")!.click(); +} + +function change(container: Element) { + container.querySelector("button.change")!.click(); +} + +export const config: TestConfig = { + steps: [{}, show, change], +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..a0b82da4719 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,38 @@ +// tags/theme-display.marko +const $template$2 = " "; +const $walks$2 = "bD l"; +const $context_theme = _context_closure("__tests__/tags/theme-provider.marko", ($scope2, $provider) => $theme$1($scope2, $provider["#ContextValue"]), "__tests__/tags/theme-display.marko_0_theme/context", ""); +const $theme$1 = /*@__PURE__*/ _const("theme", ($scope) => _text($scope["#text/0"], $scope.theme)); +const $setup$2 = $context_theme; +var theme_display_default = /*@__PURE__*/ _template("__tests__/tags/theme-display.marko", $template$2, $walks$2, $setup$2); + +// tags/theme-provider.marko +const $template$1 = ""; +const $walks$1 = "b%b b"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +const $theme = /*@__PURE__*/ _let("theme/5", ($scope) => _context_value($scope, $scope.theme, "__tests__/tags/theme-provider.marko")); +const $setup__script = _script("__tests__/tags/theme-provider.marko_0", ($scope) => _on($scope["#button/1"], "click", function() { + $theme($scope, $scope.theme === "light" ? "dark" : "light"); +})); +function $setup$1($scope) { + _context_branch($scope, "__tests__/tags/theme-provider.marko"); + $theme($scope, "light"); + $setup__script($scope); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +var theme_provider_default = /*@__PURE__*/ _template("__tests__/tags/theme-provider.marko", $template$1, $walks$1, $setup$1, $input); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1); +const $themeprovider_content__setup = ($scope) => { + $setup$2($scope["#childScope/0"]); +}; +const $themeprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", /*@__PURE__*/ ((_w0) => `${_w0}
static
`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$2), $themeprovider_content__setup); +function $setup($scope) { + $setup$1($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $themeprovider_content($scope)); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..2f5203cf1e9 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/dom.bundle.js @@ -0,0 +1,9 @@ +// tags/theme-display.marko +const $context_theme = _context_closure("c", ($scope2, $provider) => $theme$1($scope2, $provider["I"]), "b0"); +const $theme$1 = /*@__PURE__*/ _const(1, ($scope) => _text($scope.a, $scope.b)); + +// tags/theme-provider.marko +const $theme = /*@__PURE__*/ _let(5, ($scope) => _context_value($scope, $scope.f, "c")); +const $setup__script = _script("c0", ($scope) => _on($scope.b, "click", function() { + $theme($scope, $scope.f === "light" ? "dark" : "light"); +})); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..8b2070a4056 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.debug.js @@ -0,0 +1,36 @@ +// tags/theme-display.marko +var theme_display_default = _template("__tests__/tags/theme-display.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + const theme = _context_link($scope0_id, "__tests__/tags/theme-provider.marko", "__tests__/tags/theme-display.marko_0_theme/context", ""); + _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")}`); + writeScope($scope0_id, {}, "__tests__/tags/theme-display.marko", 0); + _resume_branch($scope0_id); +}); + +// tags/theme-provider.marko +var theme_provider_default = _template("__tests__/tags/theme-provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let theme = "light"; + _context_provide("__tests__/tags/theme-provider.marko", theme, () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "#button/1")}`); + _script($scope0_id, "__tests__/tags/theme-provider.marko_0"); + writeScope($scope0_id, { theme }, "__tests__/tags/theme-provider.marko", 0, { theme: "2:6" }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + theme_provider_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + theme_display_default({}); + _html("
static
"); + }) }); + writeScope($scope0_id, {}, "__tests__/template.marko", 0); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..b78e6a20d96 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/html.bundle.js @@ -0,0 +1,35 @@ +// tags/theme-display.marko +var theme_display_default = _template("b", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + _html(`${_escape(_context_link($scope0_id, "c", "b0"))}${_el_resume($scope0_id, "a")}`); + writeScope($scope0_id, {}); + _resume_branch($scope0_id); +}); + +// tags/theme-provider.marko +var theme_provider_default = _template("c", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let theme = "light"; + _context_provide("c", theme, () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "b")}`); + _script($scope0_id, "c0"); + writeScope($scope0_id, { f: theme }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("a", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + theme_provider_default({ content: _content("a0", () => { + _scope_reason(); + _scope_id(); + theme_display_default({}); + _html("
static
"); + }) }); + writeScope($scope0_id, {}); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.debug.md new file mode 100644 index 00000000000..1dd8dc8ce5e --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.debug.md @@ -0,0 +1,58 @@ +# Render +```html + + light + +
+ static +
+ +``` + +# Update +```js +container.querySelector("button.toggle").click(); +``` +```html + + dark + +
+ static +
+ +``` +## Change +``` +UPDATE: span::text "light" => "dark" +``` + +# Update +```js +container.querySelector("button.toggle").click(); +``` +```html + + light + +
+ static +
+ +``` +## Change +``` +UPDATE: span::text "dark" => "light" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.md new file mode 100644 index 00000000000..1dd8dc8ce5e --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/render.md @@ -0,0 +1,58 @@ +# Render +```html + + light + +
+ static +
+ +``` + +# Update +```js +container.querySelector("button.toggle").click(); +``` +```html + + dark + +
+ static +
+ +``` +## Change +``` +UPDATE: span::text "light" => "dark" +``` + +# Update +```js +container.querySelector("button.toggle").click(); +``` +```html + + light + +
+ static +
+ +``` +## Change +``` +UPDATE: span::text "dark" => "light" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..5fb2db51dca --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.debug.html @@ -0,0 +1,23 @@ +light +
static
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.html new file mode 100644 index 00000000000..0568bdef5be --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/__snapshots__/writes.html @@ -0,0 +1,16 @@ +light +
static
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/sizes.json new file mode 100644 index 00000000000..ac3fdec49ee --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/sizes.json @@ -0,0 +1,18 @@ +{ + "dom": { + "template.marko.page.mjs": { + "total": { + "min": 3288, + "brotli": 1641 + }, + "files": { + "tags/theme-display.marko": 240, + "tags/theme-provider.marko": 286 + } + } + }, + "html": { + "min": 473, + "brotli": 319 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-display.marko new file mode 100644 index 00000000000..f7293b73a54 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-display.marko @@ -0,0 +1,2 @@ + +${theme} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-provider.marko new file mode 100644 index 00000000000..3efeca46d5d --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/tags/theme-provider.marko @@ -0,0 +1,8 @@ +// Mutable provider: ``-derived, so consumers subscribe. + + + <${input.content}/> + + + toggle + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/template.marko new file mode 100644 index 00000000000..25e08a918ef --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/template.marko @@ -0,0 +1,4 @@ + + +
static
+
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-mutable/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/test.ts new file mode 100644 index 00000000000..a99c7830772 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-mutable/test.ts @@ -0,0 +1,9 @@ +import type { TestConfig } from "../../main.test"; + +function toggle(container: Element) { + container.querySelector("button.toggle")!.click(); +} + +export const config: TestConfig = { + steps: [{}, toggle, toggle], +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..ee53d2758a9 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,35 @@ +// resolve-label.js +function resolveLabel($global, _marker) { + return $global.label; +} + +// labeled-region.marko +const $template$1 = " "; +const $walks$1 = "bD l%c"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/1"); +const $label = /*@__PURE__*/ _const("label", ($scope) => _text($scope["#text/0"], $scope.label)); +function $setup$1($scope) { + $scope["#ContextValue"] = resolveLabel($scope.$global, "self_context_sentinel"); + _context_branch($scope, "__tests__/labeled-region.marko"); + $label($scope, _context_read($scope, "label", "__tests__/labeled-region.marko", "./labeled-region.marko")); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/1"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +var labeled_region_default = /*@__PURE__*/ _template("__tests__/labeled-region.marko", $template$1, $walks$1, $setup$1, $input); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}& D l`)($walks$1); +const $LabeledRegion_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "
static
", "b"); +const $count = /*@__PURE__*/ _let("count/3", ($scope) => _text($scope["#text/2"], $scope.count)); +const $setup__script = _script("__tests__/template.marko_0", ($scope) => _on($scope["#button/1"], "click", function() { + $count($scope, $scope.count + 1); +})); +function $setup($scope) { + $setup$1($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $LabeledRegion_content($scope)); + $count($scope, 0); + $setup__script($scope); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..51ecbe37266 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/dom.bundle.js @@ -0,0 +1,5 @@ +// template.marko +const $count = /*@__PURE__*/ _let(3, ($scope) => _text($scope.c, $scope.d)); +const $setup__script = _script("b1", ($scope) => _on($scope.b, "click", function() { + $count($scope, $scope.d + 1); +})); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..b113a2e0f04 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.debug.js @@ -0,0 +1,33 @@ +// resolve-label.js +function resolveLabel($global, _marker) { + return $global.label; +} + +// labeled-region.marko +var labeled_region_default = _template("__tests__/labeled-region.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("__tests__/labeled-region.marko", resolveLabel($global(), "self_context_sentinel"), () => { + const label = _context_get("__tests__/labeled-region.marko", "./labeled-region.marko"); + _html(`${_escape(label)}${_el_resume($scope0_id, "#text/0")}`); + _dynamic_tag($scope0_id, "#text/1", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + writeScope($scope0_id, {}, "__tests__/labeled-region.marko", 0); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + labeled_region_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + _html("
static
"); + }) }); + let count = 0; + _html(`${_el_resume($scope0_id, "#button/1")}`); + _script($scope0_id, "__tests__/template.marko_0"); + writeScope($scope0_id, { count }, "__tests__/template.marko", 0, { count: "9:6" }); + _resume_branch($scope0_id); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..f464cc40f98 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/html.bundle.js @@ -0,0 +1,32 @@ +// resolve-label.js +function resolveLabel($global, _marker) { + return $global.label; +} + +// labeled-region.marko +var labeled_region_default = _template("a", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("a", resolveLabel($global(), "self_context_sentinel"), () => { + _html(`${_escape(_context_get("a"))}${_el_resume($scope0_id, "a")}`); + _dynamic_tag($scope0_id, "b", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + writeScope($scope0_id, {}); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("b", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + labeled_region_default({ content: _content("b0", () => { + _scope_reason(); + _scope_id(); + _html("
static
"); + }) }); + let count = 0; + _html(`${_el_resume($scope0_id, "b")}`); + _script($scope0_id, "b1"); + writeScope($scope0_id, { d: count }); + _resume_branch($scope0_id); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.debug.md new file mode 100644 index 00000000000..7637e013399 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.debug.md @@ -0,0 +1,36 @@ +# Render `{"$global":{"label":"intro"}}` +```html + + intro + +
+ static +
+ +``` + +# Update +```js +container.querySelector("button.inc").click(); +``` +```html + + intro + +
+ static +
+ +``` +## Change +``` +UPDATE: .inc::text "0" => "1" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.md new file mode 100644 index 00000000000..7637e013399 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/render.md @@ -0,0 +1,36 @@ +# Render `{"$global":{"label":"intro"}}` +```html + + intro + +
+ static +
+ +``` + +# Update +```js +container.querySelector("button.inc").click(); +``` +```html + + intro + +
+ static +
+ +``` +## Change +``` +UPDATE: .inc::text "0" => "1" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..103a1e952b8 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.debug.html @@ -0,0 +1,12 @@ +intro +
static
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.html new file mode 100644 index 00000000000..1080a7a22d8 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/__snapshots__/writes.html @@ -0,0 +1,9 @@ +intro +
static
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/labeled-region.marko b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/labeled-region.marko new file mode 100644 index 00000000000..171c1ea0c9f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/labeled-region.marko @@ -0,0 +1,9 @@ +import resolveLabel from "./resolve-label.js" + +// A server-only value consumed by the providing template itself: the whole +// context (provide and self-consume) compiles to zero client bytes. + + + ${label} + <${input.content}/> + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/resolve-label.js b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/resolve-label.js new file mode 100644 index 00000000000..2906baba699 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/resolve-label.js @@ -0,0 +1,5 @@ +// Sentinel reachable only through the provider's context value expression; +// the dom bundle pins that this module tree-shakes out. +export default function resolveLabel($global, _marker) { + return $global.label; +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/sizes.json new file mode 100644 index 00000000000..4a62ed4b982 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/sizes.json @@ -0,0 +1,12 @@ +{ + "dom": { + "template.marko.page.mjs": { + "min": 2618, + "brotli": 1330 + } + }, + "html": { + "min": 406, + "brotli": 272 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/template.marko new file mode 100644 index 00000000000..0c30ab83ac2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/template.marko @@ -0,0 +1,10 @@ +import LabeledRegion from "./labeled-region.marko" + + +
static
+
+ +// Unrelated interactivity keeps the client bundle nonempty, so the +// `bundle_excludes` assertion is meaningful. + +${count} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/test.ts new file mode 100644 index 00000000000..1beb153f57c --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self-server-only/test.ts @@ -0,0 +1,12 @@ +import type { TestConfig } from "../../main.test"; + +function inc(container: Element) { + container.querySelector("button.inc")!.click(); +} + +export const config: TestConfig = { + steps: [{ $global: { label: "intro" } }, inc], + // The self-consume drops to the immutable tier once the provide side + // classifies server-only: no context runtime may reach the client. + bundle_excludes: { dom: ["self_context_sentinel", "_context"] }, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..ebab8d69328 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,48 @@ +// tags/labeled-region.marko +const $template$1 = ""; +const $walks$1 = "b b%c"; +const $context_label = _context_closure("__tests__/tags/labeled-region.marko", ($scope2, $provider) => $if_content__label($scope2, $provider["#ContextValue"]), "__tests__/tags/labeled-region.marko_1_label/context", ""); +const $if_content__dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $if_content__input_content = /*@__PURE__*/ _if_closure("#text/1", 0, ($scope) => $if_content__dynamicTag($scope, $scope._.input_content)); +const $if_content__setup = ($scope) => { + $if_content__input_content._($scope); + $context_label($scope); +}; +const $if_content__label = /*@__PURE__*/ _const("label", ($scope) => _text($scope["#text/1"], $scope.label)); +const $if = /*@__PURE__*/ _if("#text/1", "
", "b%bD l", $if_content__setup); +const $open = /*@__PURE__*/ _let("open/6", ($scope) => $if($scope, $scope.open ? 0 : 1)); +const $setup__script = _script("__tests__/tags/labeled-region.marko_0", ($scope) => _on($scope["#button/0"], "click", function() { + $open($scope, !$scope.open); +})); +function $setup$1($scope) { + _context_branch($scope, "__tests__/tags/labeled-region.marko"); + $open($scope, true); + $setup__script($scope); +} +const $input_label = /*@__PURE__*/ _const("input_label", ($scope) => { + _attr_class($scope["#button/0"], `toggle-${$scope.input_label}`); + _context_value($scope, $scope.input_label, "__tests__/tags/labeled-region.marko"); +}); +const $input = ($scope, input) => { + $input_label($scope, input.label); + $input_content($scope, input.content); +}; +const $input_content = /*@__PURE__*/ _const("input_content", $if_content__input_content); +var labeled_region_default = /*@__PURE__*/ _template("__tests__/tags/labeled-region.marko", $template$1, $walks$1, $setup$1, $input); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$1); +const $labeledregion_content2 = _content_resume("__tests__/template.marko_2_content", "content", "b"); +const $labeledregion_content__setup = ($scope) => { + $setup$1($scope["#childScope/0"]); + $input_content($scope["#childScope/0"], $labeledregion_content2($scope)); + $input_label($scope["#childScope/0"], "inner"); +}; +const $labeledregion_content = _content_resume("__tests__/template.marko_1_content", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$1), $labeledregion_content__setup); +function $setup($scope) { + $setup$1($scope["#childScope/0"]); + $input_content($scope["#childScope/0"], $labeledregion_content($scope)); + $input_label($scope["#childScope/0"], "outer"); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..d79c6301bed --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/dom.bundle.js @@ -0,0 +1,35 @@ +// tags/labeled-region.marko +const $template = ""; +const $walks = "b b%c"; +const $context_label = _context_closure("b", ($scope2, $provider) => $if_content__label($scope2, $provider["I"]), "b0"); +const $if_content__dynamicTag = /*@__PURE__*/ _dynamic_tag(0); +const $if_content__input_content = /*@__PURE__*/ _if_closure(1, 0, ($scope) => $if_content__dynamicTag($scope, $scope._.f)); +const $if_content__setup = ($scope) => { + $if_content__input_content._($scope); + $context_label($scope); +}; +const $if_content__label = /*@__PURE__*/ _const(2, ($scope) => _text($scope.b, $scope.c)); +const $if = /*@__PURE__*/ _if(1, "
", "b%bD l", $if_content__setup); +const $open = /*@__PURE__*/ _let(6, ($scope) => $if($scope, $scope.g ? 0 : 1)); +const $setup__script = _script("b1", ($scope) => _on($scope.a, "click", function() { + $open($scope, !$scope.g); +})); +function $setup($scope) { + _context_branch($scope, "b"); + $open($scope, true); + $setup__script($scope); +} +const $input_label = /*@__PURE__*/ _const(4, ($scope) => { + _attr_class($scope.a, `toggle-${$scope.e}`); + _context_value($scope, $scope.e, "b"); +}); +const $input_content = /*@__PURE__*/ _const(5, $if_content__input_content); + +// template.marko +const $labeledregion_content2 = _content_resume("a0", "content", "b"); +const $labeledregion_content__setup = ($scope) => { + $setup($scope.a); + $input_content($scope.a, $labeledregion_content2($scope)); + $input_label($scope.a, "inner"); +}; +const $labeledregion_content = _content_resume("a1", /*@__PURE__*/ ((_w0) => `${_w0}`)($template), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks), $labeledregion_content__setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..24dba71eaa3 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.debug.js @@ -0,0 +1,51 @@ +// tags/labeled-region.marko +var labeled_region_default = _template("__tests__/tags/labeled-region.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let open = true; + _context_provide("__tests__/tags/labeled-region.marko", input.label, () => { + _html(`toggle${_el_resume($scope0_id, "#button/0")}`); + _if(() => { + if (open) { + const $scope1_id = _scope_id(); + _dynamic_tag($scope1_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + const label = _context_link($scope1_id, "__tests__/tags/labeled-region.marko", "__tests__/tags/labeled-region.marko_1_label/context", ""); + _html(`
${_escape(label)}${_el_resume($scope1_id, "#text/1")}
`); + writeScope($scope1_id, {}, "__tests__/tags/labeled-region.marko", "10:4"); + return 0; + } + }, $scope0_id, "#text/1"); + }, $scope0_id); + _script($scope0_id, "__tests__/tags/labeled-region.marko_0"); + writeScope($scope0_id, { + input_content: input.content, + open + }, "__tests__/tags/labeled-region.marko", 0, { + input_content: ["input.content"], + open: "5:6" + }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + labeled_region_default({ + label: "outer", + content: _content_resume("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + labeled_region_default({ + label: "inner", + content: _content_resume("__tests__/template.marko_2_content", () => { + _scope_reason(); + const $scope2_id = _scope_id(); + _html("content"); + }, $scope1_id) + }); + writeScope($scope1_id, {}, "__tests__/template.marko", "1:2"); + }, $scope0_id) + }); + writeScope($scope0_id, {}, "__tests__/template.marko", 0); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..41e11a51d4f --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/html.bundle.js @@ -0,0 +1,47 @@ +// tags/labeled-region.marko +var labeled_region_default = _template("b", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let open = true; + _context_provide("b", input.label, () => { + _html(`toggle${_el_resume($scope0_id, "a")}`); + _if(() => { + { + const $scope1_id = _scope_id(); + _dynamic_tag($scope1_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + _html(`
${_escape(_context_link($scope1_id, "b", "b0"))}${_el_resume($scope1_id, "b")}
`); + writeScope($scope1_id, {}); + return 0; + } + }, $scope0_id, "b"); + }, $scope0_id); + _script($scope0_id, "b1"); + writeScope($scope0_id, { + f: input.content, + g: open + }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("a", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + labeled_region_default({ + label: "outer", + content: _content_resume("a1", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + labeled_region_default({ + label: "inner", + content: _content_resume("a0", () => { + _scope_reason(); + _scope_id(); + _html("content"); + }, $scope1_id) + }); + writeScope($scope1_id, {}); + }, $scope0_id) + }); + writeScope($scope0_id, {}); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.debug.md new file mode 100644 index 00000000000..b309e42f683 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.debug.md @@ -0,0 +1,85 @@ +# Render +```html + + + + content + +
+ inner +
+
+ outer +
+``` + +# Update +```js +container.querySelector(`button.toggle-${label}`).click(); +``` +```html + +``` +## Change +``` +REMOVE: .toggle-outer + button +REMOVE: .toggle-outer + span +REMOVE: .toggle-outer + div +REMOVE: .toggle-outer + div +``` + +# Update +```js +container.querySelector(`button.toggle-${label}`).click(); +``` +```html + + + + content + +
+ inner +
+
+ outer +
+``` +## Change +``` +INSERT: .toggle-outer + div +INSERT: .toggle-outer + .toggle-inner +UPDATE: div:nth-of-type(2)::text " " => "outer" +INSERT: .toggle-inner + div +UPDATE: .toggle-inner[class] null => "toggle-inner" +INSERT: .toggle-inner + span +UPDATE: div:nth-of-type(1)::text " " => "inner" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.md new file mode 100644 index 00000000000..b309e42f683 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/render.md @@ -0,0 +1,85 @@ +# Render +```html + + + + content + +
+ inner +
+
+ outer +
+``` + +# Update +```js +container.querySelector(`button.toggle-${label}`).click(); +``` +```html + +``` +## Change +``` +REMOVE: .toggle-outer + button +REMOVE: .toggle-outer + span +REMOVE: .toggle-outer + div +REMOVE: .toggle-outer + div +``` + +# Update +```js +container.querySelector(`button.toggle-${label}`).click(); +``` +```html + + + + content + +
+ inner +
+
+ outer +
+``` +## Change +``` +INSERT: .toggle-outer + div +INSERT: .toggle-outer + .toggle-inner +UPDATE: div:nth-of-type(2)::text " " => "outer" +INSERT: .toggle-inner + div +UPDATE: .toggle-inner[class] null => "toggle-inner" +INSERT: .toggle-inner + span +UPDATE: div:nth-of-type(1)::text " " => "inner" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..0b1b886f1a4 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.debug.html @@ -0,0 +1,43 @@ +content +
inner
+
outer
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.html new file mode 100644 index 00000000000..cc9ba7e0ced --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/__snapshots__/writes.html @@ -0,0 +1,28 @@ +content +
inner
+
outer
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-self/sizes.json new file mode 100644 index 00000000000..5b60ae65e08 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/sizes.json @@ -0,0 +1,18 @@ +{ + "dom": { + "template.marko.page.mjs": { + "total": { + "min": 14438, + "brotli": 5574 + }, + "files": { + "tags/labeled-region.marko": 1268, + "template.marko": 474 + } + } + }, + "html": { + "min": 732, + "brotli": 394 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/tags/labeled-region.marko b/packages/runtime-tags/src/__tests__/fixtures/context-self/tags/labeled-region.marko new file mode 100644 index 00000000000..5429bc32a69 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/tags/labeled-region.marko @@ -0,0 +1,15 @@ +// A self-referencing consume (inside the provide's own body) resolves this +// instance; nested instances shadow per dynamic extent. The `` +// gives each instance its own resumable branch (nested same-template +// providers sharing one branch are rejected -- see `_context_branch`). + + + + + <${input.content}/> + +
${label}
+ +
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-self/template.marko new file mode 100644 index 00000000000..a0ba6eb862c --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/template.marko @@ -0,0 +1,5 @@ + + + content + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-self/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-self/test.ts new file mode 100644 index 00000000000..733a3d3dbfa --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-self/test.ts @@ -0,0 +1,12 @@ +import type { TestConfig } from "../../main.test"; + +function toggle(label: string) { + return (container: Element) => + container + .querySelector(`button.toggle-${label}`)! + .click(); +} + +export const config: TestConfig = { + steps: [{}, toggle("outer"), toggle("outer")], +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..aa8b04ba57d --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,36 @@ +// tags/counter.marko +const $template2 = " "; +const $walks2 = "b bD l"; +const $context_count = _context_closure("__tests__/template.marko", ($scope2, $provider) => $count$1($scope2, $provider["#ContextValue"]), "__tests__/tags/counter.marko_0_count/context", "../template.marko"); +const $count$1 = /*@__PURE__*/ _const("count", ($scope) => _text($scope["#text/1"], $scope.count)); +const $setup2__script = _script("__tests__/tags/counter.marko_0", ($scope) => _on($scope["#button/0"], "click", function() { + _context_write($scope, "__tests__/template.marko", $scope.count + 1); +})); +function $setup2($scope) { + $context_count($scope); + $setup2__script($scope); +} +var counter_default = /*@__PURE__*/ _template("__tests__/tags/counter.marko", $template2, $walks2, $setup2); + +// template.marko +const $template = ""; +const $walks = "b%b b"; +const $count = /*@__PURE__*/ _let("count/2", ($scope) => _context_value($scope, $scope.count, "__tests__/template.marko")); +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $setup__script = _script("__tests__/template.marko_0", ($scope) => _on($scope["#button/1"], "click", function() { + console.log($scope.$global.settings.message); +})); +function $setup($scope) { + _context_branch($scope, "__tests__/template.marko"); + _context_change($scope, $valueChange($scope)); + $count($scope, 0); + $dynamicTag($scope, counter_default); + $setup__script($scope); +} +function $valueChange($scope) { + return (_new_count) => { + $count($scope, _new_count); + }; +} +_resume("__tests__/template.marko_0/valueChange", $valueChange); +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..d7a63180867 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/dom.bundle.js @@ -0,0 +1,18 @@ +// tags/counter.marko +const $context_count = _context_closure("a", ($scope2, $provider) => $count$1($scope2, $provider["I"]), "b0"); +const $count$1 = /*@__PURE__*/ _const(2, ($scope) => _text($scope.b, $scope.c)); +const $setup2__script = _script("b1", ($scope) => _on($scope.a, "click", function() { + _context_write($scope, "a", $scope.c + 1); +})); + +// template.marko +const $count = /*@__PURE__*/ _let(2, ($scope) => _context_value($scope, $scope.c, "a")); +const $setup__script = _script("a1", ($scope) => _on($scope.b, "click", function() { + console.log($scope.$.settings.message); +})); +function $valueChange($scope) { + return (_new_count) => { + $count($scope, _new_count); + }; +} +_resume("a0", $valueChange); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..097ea48105a --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.debug.js @@ -0,0 +1,26 @@ +// tags/counter.marko +var counter_default = _template("__tests__/tags/counter.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + let count = _context_link($scope0_id, "__tests__/template.marko", "__tests__/tags/counter.marko_0_count/context", "../template.marko"); + _html(`${_el_resume($scope0_id, "#button/0")}${_escape(count)}${_el_resume($scope0_id, "#text/1")}`); + _script($scope0_id, "__tests__/tags/counter.marko_0"); + writeScope($scope0_id, { count }, "__tests__/tags/counter.marko", 0, { count: "1:10" }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + let count = 0; + _context_provide("__tests__/template.marko", count, () => { + _dynamic_tag($scope0_id, "#text/0", counter_default, {}, 0, 0, 0); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "#button/1")}`); + _script($scope0_id, "__tests__/template.marko_0"); + writeScope($scope0_id, { "TagVariableChange:#ContextValue": _resume((_new_count) => { + count = _new_count; + }, "__tests__/template.marko_0/valueChange", $scope0_id) || void 0 }, "__tests__/template.marko", 0); + _resume_branch($scope0_id); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..4e623d217a0 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/html.bundle.js @@ -0,0 +1,26 @@ +// tags/counter.marko +var counter_default = _template("b", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + let count = _context_link($scope0_id, "a", "b0"); + _html(`${_el_resume($scope0_id, "a")}${_escape(count)}${_el_resume($scope0_id, "b")}`); + _script($scope0_id, "b1"); + writeScope($scope0_id, { c: count }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("a", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + let count = 0; + _context_provide("a", count, () => { + _dynamic_tag($scope0_id, "a", counter_default, {}, 0, 0, 0); + }, $scope0_id); + _html(`${_el_resume($scope0_id, "b")}`); + _script($scope0_id, "a1"); + writeScope($scope0_id, { MI: _resume((_new_count) => { + count = _new_count; + }, "a0", $scope0_id) || void 0 }); + _resume_branch($scope0_id); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.debug.md new file mode 100644 index 00000000000..54754384386 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.debug.md @@ -0,0 +1,44 @@ +# Render `{"$global":{"settings":{"message":"a globals message long enough to dedup"},"serializedGlobals":["settings"]}}` +```html + + + 0 + + +``` + +# Update +```js +container.querySelector("button.add").click(); +``` +```html + + + 1 + + +``` +## Change +``` +UPDATE: .count::text "0" => "1" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.md new file mode 100644 index 00000000000..54754384386 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/render.md @@ -0,0 +1,44 @@ +# Render `{"$global":{"settings":{"message":"a globals message long enough to dedup"},"serializedGlobals":["settings"]}}` +```html + + + 0 + + +``` + +# Update +```js +container.querySelector("button.add").click(); +``` +```html + + + 1 + + +``` +## Change +``` +UPDATE: .count::text "0" => "1" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..72a401508e0 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.debug.html @@ -0,0 +1,31 @@ +0 + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.html new file mode 100644 index 00000000000..0d6104083fe --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/__snapshots__/writes.html @@ -0,0 +1,21 @@ +0 + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/sizes.json new file mode 100644 index 00000000000..03b98712d29 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/sizes.json @@ -0,0 +1,18 @@ +{ + "dom": { + "template.marko.page.mjs": { + "total": { + "min": 3435, + "brotli": 1683 + }, + "files": { + "tags/counter.marko": 369, + "template.marko": 381 + } + } + }, + "html": { + "min": 568, + "brotli": 348 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/tags/counter.marko b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/tags/counter.marko new file mode 100644 index 00000000000..8b494280a84 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/tags/counter.marko @@ -0,0 +1,3 @@ + +add +${count} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/template.marko new file mode 100644 index 00000000000..e52f87e2a4d --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/template.marko @@ -0,0 +1,8 @@ +// A root writable provider stamps its branch link on scope 0, which must +// coexist with the serialized-globals partial for the same slot (the +// serializer's signed slot deltas) for post-resume writes to work. + + + + +log diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/test.ts new file mode 100644 index 00000000000..151112e4e82 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-serialized-globals/test.ts @@ -0,0 +1,17 @@ +import type { TestConfig } from "../../main.test"; + +function add(container: Element) { + container.querySelector("button.add")!.click(); +} + +export const config: TestConfig = { + steps: [ + { + $global: { + settings: { message: "a globals message long enough to dedup" }, + serializedGlobals: ["settings"], + }, + }, + add, + ], +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..3f069cef5a4 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,59 @@ +// tags/info-display.marko +const $template$2 = " "; +const $walks$2 = "bD l"; +const $theme = ($scope, theme) => _text($scope["#text/0"], theme); +function $setup$2($scope) { + $theme($scope, _context_read($scope, "theme", "__tests__/tags/info-provider.marko", "")); +} +var info_display_default = /*@__PURE__*/ _template("__tests__/tags/info-display.marko", $template$2, $walks$2, $setup$2); + +// resolve-info.js +function resolveInfo($global, _marker) { + return $global.theme; +} + +// tags/info-provider.marko +const $template$1 = ""; +const $walks$1 = "b%c"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +function $setup$1($scope) { + $scope["#ContextValue"] = resolveInfo($scope.$global, "toggle_context_sentinel"); + _context_branch($scope, "__tests__/tags/info-provider.marko"); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +var info_provider_default = /*@__PURE__*/ _template("__tests__/tags/info-provider.marko", $template$1, "b%c", $setup$1, $input); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)("b%c"); +const $for_content__setup = ($scope) => { + $setup$2($scope["#childScope/0"]); +}; +const $if_content__setup = ($scope) => { + $setup$2($scope["#childScope/0"]); +}; +const $infoprovider_content__if = /*@__PURE__*/ _if("#text/2", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$2), $if_content__setup); +const $infoprovider_content__show = /*@__PURE__*/ _let("show/4", ($scope) => $infoprovider_content__if($scope, $scope.show ? 0 : 1)); +const $infoprovider_content__for = /*@__PURE__*/ _for_of("#text/3", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$2), $for_content__setup); +const $infoprovider_content__items = /*@__PURE__*/ _let("items/5", ($scope) => $infoprovider_content__for($scope, [$scope.items, (x) => x])); +const $infoprovider_content__setup__script = _script("__tests__/template.marko_1", ($scope) => { + _on($scope["#button/0"], "click", function() { + $infoprovider_content__show($scope, !$scope.show); + }); + _on($scope["#button/1"], "click", function() { + $infoprovider_content__items($scope, [...$scope.items, $scope.items?.length]); + }); +}); +const $infoprovider_content__setup = ($scope) => { + $infoprovider_content__show($scope, false); + $infoprovider_content__items($scope, []); + $infoprovider_content__setup__script($scope); +}; +const $infoprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "", " b b%b%c", $infoprovider_content__setup); +function $setup($scope) { + $setup$1($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $infoprovider_content($scope)); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..5388be1de76 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/dom.bundle.js @@ -0,0 +1,27 @@ +// tags/info-display.marko +const $template = " "; +const $walks = "bD l"; +const $theme = ($scope, theme) => _text($scope.a, theme); +function $setup($scope) { + $theme($scope, _context_read($scope, 1, "c")); +} + +// template.marko +const $for_content__setup = ($scope) => { + $setup($scope.a); +}; +const $if_content__setup = ($scope) => { + $setup($scope.a); +}; +const $infoprovider_content__if = /*@__PURE__*/ _if(2, /*@__PURE__*/ ((_w0) => `${_w0}`)($template), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks), $if_content__setup); +const $infoprovider_content__show = /*@__PURE__*/ _let(4, ($scope) => $infoprovider_content__if($scope, $scope.e ? 0 : 1)); +const $infoprovider_content__for = /*@__PURE__*/ _for_of(3, /*@__PURE__*/ ((_w0) => `${_w0}`)($template), /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks), $for_content__setup); +const $infoprovider_content__items = /*@__PURE__*/ _let(5, ($scope) => $infoprovider_content__for($scope, [$scope.f, (x) => x])); +const $infoprovider_content__setup__script = _script("a0", ($scope) => { + _on($scope.a, "click", function() { + $infoprovider_content__show($scope, !$scope.e); + }); + _on($scope.b, "click", function() { + $infoprovider_content__items($scope, [...$scope.f, $scope.f?.length]); + }); +}); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..d8da5907593 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.debug.js @@ -0,0 +1,60 @@ +// tags/info-display.marko +var info_display_default = _template("__tests__/tags/info-display.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + const theme = _context_get("__tests__/tags/info-provider.marko", ""); + _html(`${_escape(theme)}${_el_resume($scope0_id, "#text/0")}`); + writeScope($scope0_id, {}, "__tests__/tags/info-display.marko", 0); +}); + +// resolve-info.js +function resolveInfo($global, _marker) { + return $global.theme; +} + +// tags/info-provider.marko +var info_provider_default = _template("__tests__/tags/info-provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("__tests__/tags/info-provider.marko", resolveInfo($global(), "toggle_context_sentinel"), () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}, "__tests__/tags/info-provider.marko", 0); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + info_provider_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + let show = false; + let items = []; + _html(`${_el_resume($scope1_id, "#button/0")}${_el_resume($scope1_id, "#button/1")}`); + _context_reserve("__tests__/tags/info-provider.marko"); + _if(() => { + if (show) { + const $scope2_id = _scope_id(); + info_display_default({}); + writeScope($scope2_id, {}, "__tests__/template.marko", "8:4"); + return 0; + } + }, $scope1_id, "#text/2"); + _context_reserve("__tests__/tags/info-provider.marko"); + _for_of(items, (i) => { + const $scope3_id = _scope_id(); + info_display_default({}); + writeScope($scope3_id, {}, "__tests__/template.marko", "11:4"); + }, (x) => x, $scope1_id, "#text/3"); + _script($scope1_id, "__tests__/template.marko_1"); + writeScope($scope1_id, { + show, + items + }, "__tests__/template.marko", "3:2", { + show: "4:8", + items: "5:8" + }); + _resume_branch($scope1_id); + }) }); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..ddcd97b9ef1 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/html.bundle.js @@ -0,0 +1,49 @@ +// tags/info-display.marko +var info_display_default = _template("b", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + _html(`${_escape(_context_get("c"))}${_el_resume($scope0_id, "a")}`); + writeScope($scope0_id, {}); +}); + +// resolve-info.js +function resolveInfo($global, _marker) { + return $global.theme; +} + +// tags/info-provider.marko +var info_provider_default = _template("c", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("c", resolveInfo($global(), "toggle_context_sentinel"), () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}); +}); + +// template.marko +var template_default = _template("a", (input) => { + _scope_reason(); + _scope_id(); + info_provider_default({ content: _content("a1", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + let show = false; + let items = []; + _html(`${_el_resume($scope1_id, "a")}${_el_resume($scope1_id, "b")}`); + _context_reserve("c"); + _if(() => {}, $scope1_id, "c"); + _context_reserve("c"); + _for_of(items, (i) => { + const $scope3_id = _scope_id(); + info_display_default({}); + writeScope($scope3_id, {}); + }, (x) => x, $scope1_id, "d"); + _script($scope1_id, "a0"); + writeScope($scope1_id, { + e: show, + f: items + }); + _resume_branch($scope1_id); + }) }); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.debug.md new file mode 100644 index 00000000000..48e2a6f8c43 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.debug.md @@ -0,0 +1,143 @@ +# Render `{"$global":{"theme":"light"}}` +```html + + +``` + +# Update +```js +container.querySelector(selector).click(); +``` +```html + + + + light + +``` +## Change +``` +INSERT: .add + span +UPDATE: span::text " " => "light" +``` + +# Update +```js +container.querySelector(selector).click(); +``` +```html + + +``` +## Change +``` +REMOVE: .add + span +``` + +# Update +```js +container.querySelector(selector).click(); +``` +```html + + + + light + +``` +## Change +``` +INSERT: .add + span +UPDATE: span::text " " => "light" +``` + +# Update +```js +container.querySelector(selector).click(); +``` +```html + + + + light + + + light + +``` +## Change +``` +INSERT: span:nth-of-type(1) + span +UPDATE: span:nth-of-type(2)::text " " => "light" +``` + +# Update +```js +container.querySelector(selector).click(); +``` +```html + + + + light + + + light + + + light + +``` +## Change +``` +INSERT: span:nth-of-type(2) + span +UPDATE: span:nth-of-type(3)::text " " => "light" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.md new file mode 100644 index 00000000000..48e2a6f8c43 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/render.md @@ -0,0 +1,143 @@ +# Render `{"$global":{"theme":"light"}}` +```html + + +``` + +# Update +```js +container.querySelector(selector).click(); +``` +```html + + + + light + +``` +## Change +``` +INSERT: .add + span +UPDATE: span::text " " => "light" +``` + +# Update +```js +container.querySelector(selector).click(); +``` +```html + + +``` +## Change +``` +REMOVE: .add + span +``` + +# Update +```js +container.querySelector(selector).click(); +``` +```html + + + + light + +``` +## Change +``` +INSERT: .add + span +UPDATE: span::text " " => "light" +``` + +# Update +```js +container.querySelector(selector).click(); +``` +```html + + + + light + + + light + +``` +## Change +``` +INSERT: span:nth-of-type(1) + span +UPDATE: span:nth-of-type(2)::text " " => "light" +``` + +# Update +```js +container.querySelector(selector).click(); +``` +```html + + + + light + + + light + + + light + +``` +## Change +``` +INSERT: span:nth-of-type(2) + span +UPDATE: span:nth-of-type(3)::text " " => "light" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..4c1996a2f49 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.debug.html @@ -0,0 +1,17 @@ + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.html new file mode 100644 index 00000000000..322eed43ba5 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/__snapshots__/writes.html @@ -0,0 +1,14 @@ + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/resolve-info.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/resolve-info.js new file mode 100644 index 00000000000..14342d276cf --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/resolve-info.js @@ -0,0 +1,6 @@ +// Sentinel reachable only through the provider's context value expression; +// the optimize dom bundle pins that this module tree-shakes out even though +// consumers of the context render client-side. +export default function resolveInfo($global, _marker) { + return $global.theme; +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/sizes.json new file mode 100644 index 00000000000..4a07e12f722 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/sizes.json @@ -0,0 +1,18 @@ +{ + "dom": { + "template.marko.page.mjs": { + "total": { + "min": 7851, + "brotli": 3558 + }, + "files": { + "tags/info-display.marko": 243, + "template.marko": 1051 + } + } + }, + "html": { + "min": 462, + "brotli": 293 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-display.marko new file mode 100644 index 00000000000..4e9d0fc55d2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-display.marko @@ -0,0 +1,2 @@ + +${theme} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-provider.marko new file mode 100644 index 00000000000..df3fcd96d9e --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/tags/info-provider.marko @@ -0,0 +1,5 @@ +import resolveInfo from "../resolve-info.js" + + + <${input.content}/> + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/template.marko new file mode 100644 index 00000000000..dc711c6ccce --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/template.marko @@ -0,0 +1,14 @@ +// Consumers of a server-only context created client-side (a toggled `` +// and appended `` items) resolve the reserved provider box. + + + + show + add + + + + x> + + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/test.ts new file mode 100644 index 00000000000..099f06eabae --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only-toggle/test.ts @@ -0,0 +1,20 @@ +import type { TestConfig } from "../../main.test"; + +function click(selector: string) { + return (container: Element) => + container.querySelector(selector)!.click(); +} + +export const config: TestConfig = { + steps: [ + { $global: { theme: "light" } }, + click("button.show"), + click("button.show"), + click("button.show"), + click("button.add"), + click("button.add"), + ], + // The provider's value expression stays server-side; only its computed + // value reaches the client (in the reserved box). + bundle_excludes: { dom: ["toggle_context_sentinel"] }, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..753b8286a8c --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,37 @@ +// resolve-theme.js +function resolveTheme($global, _marker) { + return $global.theme; +} + +// provider.marko +const $template$1 = ""; +const $walks$1 = "b%c"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +function $setup$1($scope) { + $scope["#ContextValue"] = resolveTheme($scope.$global, "context_value_sentinel"); + _context_branch($scope, "__tests__/provider.marko"); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +var provider_default = /*@__PURE__*/ _template("__tests__/provider.marko", $template$1, "b%c", $setup$1, $input); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)("b%c"); +const $Provider_content__theme = ($scope, theme) => _text($scope["#text/0"], theme); +const $Provider_content__loggedTheme = /*@__PURE__*/ _const("loggedTheme"); +const $Provider_content__setup__script = _script("__tests__/template.marko_1", ($scope) => _on($scope["#button/1"], "click", function() { + console.log($scope.loggedTheme); +})); +const $Provider_content__setup = ($scope) => { + $Provider_content__theme($scope, _context_read($scope, "theme", "__tests__/provider.marko", "./provider.marko")); + $Provider_content__loggedTheme($scope, _context_read($scope, "loggedTheme", "__tests__/provider.marko", "./provider.marko")); + $Provider_content__setup__script($scope); +}; +const $Provider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", "
", "bD l b", $Provider_content__setup); +function $setup($scope) { + $setup$1($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $Provider_content($scope)); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..d59ca1abca4 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/dom.bundle.js @@ -0,0 +1,4 @@ +// template.marko +const $Provider_content__setup__script = _script("b0", ($scope) => _on($scope.b, "click", function() { + console.log($scope.d); +})); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..484843ace42 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.debug.js @@ -0,0 +1,29 @@ +// resolve-theme.js +function resolveTheme($global, _marker) { + return $global.theme; +} + +// provider.marko +var provider_default = _template("__tests__/provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("__tests__/provider.marko", resolveTheme($global(), "context_value_sentinel"), () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}, "__tests__/provider.marko", 0); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + provider_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + const theme = _context_get("__tests__/provider.marko", "./provider.marko"); + const loggedTheme = _context_get("__tests__/provider.marko", "./provider.marko"); + _html(`
${_escape(theme)}${_el_resume($scope1_id, "#text/0")}
${_el_resume($scope1_id, "#button/1")}`); + _script($scope1_id, "__tests__/template.marko_1"); + writeScope($scope1_id, { loggedTheme }, "__tests__/template.marko", "3:2", { loggedTheme: "9:12" }); + }) }); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..7a87752766d --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/html.bundle.js @@ -0,0 +1,29 @@ +// resolve-theme.js +function resolveTheme($global, _marker) { + return $global.theme; +} + +// provider.marko +var provider_default = _template("a", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("a", resolveTheme($global(), "context_value_sentinel"), () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}); +}); + +// template.marko +var template_default = _template("b", (input) => { + _scope_reason(); + _scope_id(); + provider_default({ content: _content("b1", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + const theme = _context_get("a"); + const loggedTheme = _context_get("a"); + _html(`
${_escape(theme)}${_el_resume($scope1_id, "a")}
${_el_resume($scope1_id, "b")}`); + _script($scope1_id, "b0"); + writeScope($scope1_id, { d: loggedTheme }); + }) }); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.debug.md new file mode 100644 index 00000000000..49979dee439 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.debug.md @@ -0,0 +1,9 @@ +# Render `{"$global":{"theme":"light"}}` +```html +
+ light +
+ +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.md new file mode 100644 index 00000000000..49979dee439 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/render.md @@ -0,0 +1,9 @@ +# Render `{"$global":{"theme":"light"}}` +```html +
+ light +
+ +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..343379e3479 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.debug.html @@ -0,0 +1,11 @@ +
light
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.html new file mode 100644 index 00000000000..ae1429288c3 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/__snapshots__/writes.html @@ -0,0 +1,8 @@ +
light
+ diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/provider.marko new file mode 100644 index 00000000000..5217b9c1cbb --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/provider.marko @@ -0,0 +1,7 @@ +import resolveTheme from "./resolve-theme.js" + +// `$global` is not client-reactive, so this classifies server-only; the +// use-site sentinel is checked by the fixture's `bundle_excludes`. + + <${input.content}/> + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/resolve-theme.js b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/resolve-theme.js new file mode 100644 index 00000000000..40dd36594f8 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/resolve-theme.js @@ -0,0 +1,5 @@ +// Sentinel reachable only through the provider's context value expression; +// the optimize dom bundle pins that this module tree-shakes out. +export default function resolveTheme($global, _marker) { + return $global.theme; +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/sizes.json new file mode 100644 index 00000000000..f9905293459 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/sizes.json @@ -0,0 +1,12 @@ +{ + "dom": { + "template.marko.page.mjs": { + "min": 1891, + "brotli": 976 + } + }, + "html": { + "min": 382, + "brotli": 262 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/template.marko new file mode 100644 index 00000000000..c17d9f27d42 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/template.marko @@ -0,0 +1,11 @@ +import Provider from "./provider.marko" + + + // Static-only consumer: never serializes or ships client behavior. + +
${theme}
+ + // Handler-read consumer: the value must serialize for hydration. + + +
diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-server-only/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/test.ts new file mode 100644 index 00000000000..a82afcb490d --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-server-only/test.ts @@ -0,0 +1,8 @@ +import type { TestConfig } from "../../main.test"; + +export const config: TestConfig = { + steps: [{ $global: { theme: "light" } }], + // Zero-bundle, made executable: no provide-value sentinel and no context + // runtime helper may reach the client output. + bundle_excludes: { dom: ["context_value_sentinel", "_context"] }, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..3f4ee8ba510 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,61 @@ +// counter.js +let n = 0; +function next() { + return "p" + ++n; +} + +// provider.marko +const $template$2 = ""; +const $walks$2 = "b%c"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +function $setup$2($scope) { + $scope["#ContextValue"] = next(); + _context_branch($scope, "__tests__/provider.marko"); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +var provider_default = /*@__PURE__*/ _template("__tests__/provider.marko", $template$2, "b%c", $setup$2, $input); + +// consumer.marko +const $template$1 = "
  • "; +const $walks$1 = "bD l"; +const $label = ($scope, label) => _text($scope["#text/0"], label); +function $setup$1($scope) { + $label($scope, _context_read($scope, "label", "__tests__/provider.marko", "./provider.marko")); +} +var consumer_default = /*@__PURE__*/ _template("__tests__/consumer.marko", $template$1, $walks$1, $setup$1); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&%c`)("b%c"); +const $Provider_content3__setup = ($scope) => { + $setup$1($scope["#childScope/0"]); +}; +const $Provider_content3 = /*@__PURE__*/ _content("__tests__/template.marko_4_content", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$1), $Provider_content3__setup); +const $for_content__setup = ($scope) => { + $setup$2($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $Provider_content3($scope)); +}; +const $Provider_content2__setup = ($scope) => { + $setup$1($scope["#childScope/0"]); +}; +const $Provider_content2 = /*@__PURE__*/ _content("__tests__/template.marko_2_content", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)($walks$1), $Provider_content2__setup); +const $Provider_content__setup = ($scope) => { + $setup$1($scope["#childScope/0"]); + $setup$2($scope["#childScope/1"]); + $input_content_direct($scope["#childScope/1"], $Provider_content2($scope)); + $setup$1($scope["#childScope/2"]); +}; +const $Provider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", /*@__PURE__*/ ((_w0, _w1, _w2) => `${_w0}${_w1}${_w2}`)($template$1, $template$2, $template$1), /*@__PURE__*/ ((_w0, _w1, _w2) => `b/${_w0}&/${_w1}&/${_w2}&b`)($walks$1, "b%c", $walks$1), $Provider_content__setup); +const $for = /*@__PURE__*/ _for_of("#text/1", /*@__PURE__*/ ((_w0) => `${_w0}`)($template$2), /*@__PURE__*/ ((_w0) => `b/${_w0}&b`)("b%c"), $for_content__setup); +function $setup($scope) { + $setup$2($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $Provider_content($scope)); + $for($scope, [[ + "x", + "y", + "z" + ], (x) => x]); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..88b5124b46b --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.debug.js @@ -0,0 +1,53 @@ +// counter.js +let n = 0; +function next() { + return "p" + ++n; +} + +// provider.marko +var provider_default = _template("__tests__/provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("__tests__/provider.marko", next(), () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}, "__tests__/provider.marko", 0); +}); + +// consumer.marko +var consumer_default = _template("__tests__/consumer.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + const label = _context_get("__tests__/provider.marko", "./provider.marko"); + _html(`
  • ${_escape(label)}${_el_resume($scope0_id, "#text/0")}
  • `); + writeScope($scope0_id, {}, "__tests__/consumer.marko", 0); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + provider_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + consumer_default({}); + provider_default({ content: _content("__tests__/template.marko_2_content", () => { + _scope_reason(); + const $scope2_id = _scope_id(); + consumer_default({}); + }) }); + consumer_default({}); + }) }); + forOf([ + "x", + "y", + "z" + ], (item) => { + const $scope3_id = _scope_id(); + provider_default({ content: _content("__tests__/template.marko_4_content", () => { + _scope_reason(); + const $scope4_id = _scope_id(); + consumer_default({}); + }) }); + }); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..fea9015bafe --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/html.bundle.js @@ -0,0 +1,52 @@ +// counter.js +let n = 0; +function next() { + return "p" + ++n; +} + +// provider.marko +var provider_default = _template("b", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + _context_provide("b", next(), () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }); + _serialize_if($scope0_reason, 0) && writeScope($scope0_id, {}); +}); + +// consumer.marko +var consumer_default = _template("a", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + _html(`
  • ${_escape(_context_get("b"))}${_el_resume($scope0_id, "a")}
  • `); + writeScope($scope0_id, {}); +}); + +// template.marko +var template_default = _template("c", (input) => { + _scope_reason(); + _scope_id(); + provider_default({ content: _content("c1", () => { + _scope_reason(); + _scope_id(); + consumer_default({}); + provider_default({ content: _content("c0", () => { + _scope_reason(); + _scope_id(); + consumer_default({}); + }) }); + consumer_default({}); + }) }); + forOf([ + "x", + "y", + "z" + ], (item) => { + _scope_id(); + provider_default({ content: _content("c2", () => { + _scope_reason(); + _scope_id(); + consumer_default({}); + }) }); + }); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.debug.md new file mode 100644 index 00000000000..26bb8d5075e --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.debug.md @@ -0,0 +1,21 @@ +# Render +```html +
  • + p1 +
  • +
  • + p2 +
  • +
  • + p1 +
  • +
  • + p3 +
  • +
  • + p4 +
  • +
  • + p5 +
  • +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.md new file mode 100644 index 00000000000..26bb8d5075e --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/render.md @@ -0,0 +1,21 @@ +# Render +```html +
  • + p1 +
  • +
  • + p2 +
  • +
  • + p1 +
  • +
  • + p3 +
  • +
  • + p4 +
  • +
  • + p5 +
  • +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..5d789e5f8f7 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.debug.html @@ -0,0 +1,9 @@ +
  • p1
  • +
  • p2
  • +
  • p1
  • +
  • p3
  • +
  • p4
  • +
  • p5
  • + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.html new file mode 100644 index 00000000000..46133219ff2 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/__snapshots__/writes.html @@ -0,0 +1,9 @@ +
  • p1
  • +
  • p2
  • +
  • p1
  • +
  • p3
  • +
  • p4
  • +
  • p5
  • + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/consumer.marko b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/consumer.marko new file mode 100644 index 00000000000..cee302a0d16 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/consumer.marko @@ -0,0 +1,2 @@ + +
  • ${label}
  • diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/counter.js b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/counter.js new file mode 100644 index 00000000000..e94bcd104eb --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/counter.js @@ -0,0 +1,6 @@ +// Distinct value per provider instance while classifying immutable (no +// `let`/`input` reference). +let n = 0; +export default function next() { + return "p" + ++n; +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/provider.marko new file mode 100644 index 00000000000..904f4c0a0d3 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/provider.marko @@ -0,0 +1,5 @@ +import next from "./counter.js" + + + <${input.content}/> +
    diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/sizes.json new file mode 100644 index 00000000000..9d2102c848b --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/sizes.json @@ -0,0 +1,7 @@ +{ + "dom": {}, + "html": { + "min": 419, + "brotli": 244 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/template.marko new file mode 100644 index 00000000000..1e4b07bd6b4 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/template.marko @@ -0,0 +1,19 @@ +import Provider from "./provider.marko" +import Consumer from "./consumer.marko" + +// The inner provider shadows the outer; after its block closes the outer +// instance is visible again. + + + + + + + + +// A provider inside a keyed `` is one instance per iteration. + x> + + + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/test.ts new file mode 100644 index 00000000000..838290b7932 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-shadowing/test.ts @@ -0,0 +1,10 @@ +import type { TestConfig } from "../../main.test"; + +export const config: TestConfig = { + steps: [{}], + // The inner sibling instance shares its branch with the trailing + // consumer, which client-side (branch based) resolution cannot + // distinguish; the keyed `` instances below are the supported + // client-side shape. + skip_csr: true, +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.debug.js new file mode 100644 index 00000000000..af613d0275d --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.debug.js @@ -0,0 +1,59 @@ +// tags/add-button.marko +const $template$3 = ""; +const $walks$3 = "b b"; +const $context_count$1 = _context_closure("__tests__/tags/count-provider.marko", ($scope2, $provider) => $count$2($scope2, $provider["#ContextValue"]), "__tests__/tags/add-button.marko_0_count/context", ""); +const $count$2 = /*@__PURE__*/ _const("count"); +const $setup__script = _script("__tests__/tags/add-button.marko_0", ($scope) => _on($scope["#button/0"], "click", function() { + _context_write($scope, "__tests__/tags/count-provider.marko", $scope.count + 1); +})); +function $setup$3($scope) { + $context_count$1($scope); + $setup__script($scope); +} +var add_button_default = /*@__PURE__*/ _template("__tests__/tags/add-button.marko", $template$3, "b b", $setup$3); + +// tags/count-display.marko +const $template$2 = " "; +const $walks$2 = "bD l"; +const $context_count = _context_closure("__tests__/tags/count-provider.marko", ($scope2, $provider) => $count$1($scope2, $provider["#ContextValue"]), "__tests__/tags/count-display.marko_0_count/context", ""); +const $count$1 = /*@__PURE__*/ _const("count", ($scope) => _text($scope["#text/0"], $scope.count)); +const $setup$2 = $context_count; +var count_display_default = /*@__PURE__*/ _template("__tests__/tags/count-display.marko", $template$2, $walks$2, $setup$2); + +// tags/count-provider.marko +const $template$1 = "
    "; +const $walks$1 = "b%bD l"; +const $input_content_direct = /*@__PURE__*/ _dynamic_tag_content("#text/0"); +const $count = /*@__PURE__*/ _let("count/5", ($scope) => { + _context_value($scope, $scope.count, "__tests__/tags/count-provider.marko"); + _text($scope["#text/1"], $scope.count); +}); +function $setup$1($scope) { + _context_branch($scope, "__tests__/tags/count-provider.marko"); + _context_change($scope, $valueChange($scope)); + $count($scope, 0); +} +const $dynamicTag = /*@__PURE__*/ _dynamic_tag("#text/0"); +const $input_content = $dynamicTag; +const $input = ($scope, input) => $input_content($scope, input.content); +function $valueChange($scope) { + return (_new_count) => { + $count($scope, _new_count); + }; +} +_resume("__tests__/tags/count-provider.marko_0/valueChange", $valueChange); +var count_provider_default = /*@__PURE__*/ _template("__tests__/tags/count-provider.marko", $template$1, $walks$1, $setup$1, $input); + +// template.marko +const $template = /*@__PURE__*/ ((_w0) => `${_w0}`)($template$1); +const $walks = /*@__PURE__*/ ((_w0) => `b/${_w0}&`)($walks$1); +const $countprovider_content__setup = ($scope) => { + $setup$3($scope["#childScope/0"]); + $setup$2($scope["#childScope/1"]); +}; +const $countprovider_content = /*@__PURE__*/ _content("__tests__/template.marko_1_content", /*@__PURE__*/ ((_w0, _w1) => `${_w0}${_w1}`)($template$3, $template$2), /*@__PURE__*/ ((_w0, _w1) => `b/${_w0}&/${_w1}&`)("b b", $walks$2), $countprovider_content__setup); +function $setup($scope) { + $setup$1($scope["#childScope/0"]); + $input_content_direct($scope["#childScope/0"], $countprovider_content($scope)); +} +var template_default = /*@__PURE__*/ _template("__tests__/template.marko", $template, $walks, $setup); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.js new file mode 100644 index 00000000000..6a1ba555b7a --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/dom.bundle.js @@ -0,0 +1,22 @@ +// tags/add-button.marko +const $context_count$1 = _context_closure("d", ($scope2, $provider) => $count$2($scope2, $provider["I"]), "b0"); +const $count$2 = /*@__PURE__*/ _const(1); +const $setup__script = _script("b1", ($scope) => _on($scope.a, "click", function() { + _context_write($scope, "d", $scope.b + 1); +})); + +// tags/count-display.marko +const $context_count = _context_closure("d", ($scope2, $provider) => $count$1($scope2, $provider["I"]), "c0"); +const $count$1 = /*@__PURE__*/ _const(1, ($scope) => _text($scope.a, $scope.b)); + +// tags/count-provider.marko +const $count = /*@__PURE__*/ _let(5, ($scope) => { + _context_value($scope, $scope.f, "d"); + _text($scope.b, $scope.f); +}); +function $valueChange($scope) { + return (_new_count) => { + $count($scope, _new_count); + }; +} +_resume("d0", $valueChange); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.debug.js b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.debug.js new file mode 100644 index 00000000000..67d21ee7871 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.debug.js @@ -0,0 +1,48 @@ +// tags/add-button.marko +var add_button_default = _template("__tests__/tags/add-button.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + let count = _context_link($scope0_id, "__tests__/tags/count-provider.marko", "__tests__/tags/add-button.marko_0_count/context", ""); + _html(`${_el_resume($scope0_id, "#button/0")}`); + _script($scope0_id, "__tests__/tags/add-button.marko_0"); + writeScope($scope0_id, { count }, "__tests__/tags/add-button.marko", 0, { count: "1:10" }); + _resume_branch($scope0_id); +}); + +// tags/count-display.marko +var count_display_default = _template("__tests__/tags/count-display.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + let count = _context_link($scope0_id, "__tests__/tags/count-provider.marko", "__tests__/tags/count-display.marko_0_count/context", ""); + _html(`${_escape(count)}${_el_resume($scope0_id, "#text/0")}`); + writeScope($scope0_id, {}, "__tests__/tags/count-display.marko", 0); + _resume_branch($scope0_id); +}); + +// tags/count-provider.marko +var count_provider_default = _template("__tests__/tags/count-provider.marko", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let count = 0; + _context_provide("__tests__/tags/count-provider.marko", count, () => { + _dynamic_tag($scope0_id, "#text/0", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`
    ${_escape(count)}${_el_resume($scope0_id, "#text/1")}
    `); + writeScope($scope0_id, { "TagVariableChange:#ContextValue": _resume((_new_count) => { + count = _new_count; + }, "__tests__/tags/count-provider.marko_0/valueChange", $scope0_id) || void 0 }, "__tests__/tags/count-provider.marko", 0); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("__tests__/template.marko", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + count_provider_default({ content: _content("__tests__/template.marko_1_content", () => { + _scope_reason(); + const $scope1_id = _scope_id(); + add_button_default({}); + count_display_default({}); + }) }); + writeScope($scope0_id, {}, "__tests__/template.marko", 0); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.js b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.js new file mode 100644 index 00000000000..4f4d2840345 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/html.bundle.js @@ -0,0 +1,47 @@ +// tags/add-button.marko +var add_button_default = _template("b", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + let count = _context_link($scope0_id, "d", "b0"); + _html(`${_el_resume($scope0_id, "a")}`); + _script($scope0_id, "b1"); + writeScope($scope0_id, { b: count }); + _resume_branch($scope0_id); +}); + +// tags/count-display.marko +var count_display_default = _template("c", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + _html(`${_escape(_context_link($scope0_id, "d", "c0"))}${_el_resume($scope0_id, "a")}`); + writeScope($scope0_id, {}); + _resume_branch($scope0_id); +}); + +// tags/count-provider.marko +var count_provider_default = _template("d", (input) => { + const $scope0_reason = _scope_reason(); + const $scope0_id = _scope_id(); + let count = 0; + _context_provide("d", count, () => { + _dynamic_tag($scope0_id, "a", input.content, {}, 0, 0, _serialize_guard($scope0_reason, 0)); + }, $scope0_id); + _html(`
    ${_escape(count)}${_el_resume($scope0_id, "b")}
    `); + writeScope($scope0_id, { MI: _resume((_new_count) => { + count = _new_count; + }, "d0", $scope0_id) || void 0 }); + _resume_branch($scope0_id); +}); + +// template.marko +var template_default = _template("a", (input) => { + _scope_reason(); + const $scope0_id = _scope_id(); + count_provider_default({ content: _content("a0", () => { + _scope_reason(); + _scope_id(); + add_button_default({}); + count_display_default({}); + }) }); + writeScope($scope0_id, {}); +}, 1); diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.debug.md b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.debug.md new file mode 100644 index 00000000000..5c81b665cbe --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.debug.md @@ -0,0 +1,72 @@ +# Render +```html + + + 0 + +
    + 0 +
    +``` + +# Update +```js +container.querySelector("button.add").click(); +``` +```html + + + 1 + +
    + 1 +
    +``` +## Change +``` +UPDATE: .provider-count::text "0" => "1" +UPDATE: .display::text "0" => "1" +``` + +# Update +```js +container.querySelector("button.add").click(); +``` +```html + + + 2 + +
    + 2 +
    +``` +## Change +``` +UPDATE: .provider-count::text "1" => "2" +UPDATE: .display::text "1" => "2" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.md b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.md new file mode 100644 index 00000000000..5c81b665cbe --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/render.md @@ -0,0 +1,72 @@ +# Render +```html + + + 0 + +
    + 0 +
    +``` + +# Update +```js +container.querySelector("button.add").click(); +``` +```html + + + 1 + +
    + 1 +
    +``` +## Change +``` +UPDATE: .provider-count::text "0" => "1" +UPDATE: .display::text "0" => "1" +``` + +# Update +```js +container.querySelector("button.add").click(); +``` +```html + + + 2 + +
    + 2 +
    +``` +## Change +``` +UPDATE: .provider-count::text "1" => "2" +UPDATE: .display::text "1" => "2" +``` diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.debug.html b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.debug.html new file mode 100644 index 00000000000..44d0e35d211 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.debug.html @@ -0,0 +1,34 @@ +0 +
    0
    + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.html b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.html new file mode 100644 index 00000000000..229e8ce94f7 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/__snapshots__/writes.html @@ -0,0 +1,21 @@ +0 +
    0
    + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/context-writable/sizes.json new file mode 100644 index 00000000000..7baa5c2c3ca --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/sizes.json @@ -0,0 +1,19 @@ +{ + "dom": { + "template.marko.page.mjs": { + "total": { + "min": 3411, + "brotli": 1680 + }, + "files": { + "tags/add-button.marko": 334, + "tags/count-display.marko": 240, + "tags/count-provider.marko": 295 + } + } + }, + "html": { + "min": 532, + "brotli": 340 + } +} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/add-button.marko b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/add-button.marko new file mode 100644 index 00000000000..c7665f1b277 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/add-button.marko @@ -0,0 +1,2 @@ + +add diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-display.marko b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-display.marko new file mode 100644 index 00000000000..82aa3e9cdb7 --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-display.marko @@ -0,0 +1,2 @@ + +${count} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-provider.marko b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-provider.marko new file mode 100644 index 00000000000..9d63c42ca1a --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/tags/count-provider.marko @@ -0,0 +1,6 @@ +// Writable provider: consumer assignments land here and fan back out. + + + <${input.content}/> + +${count} diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/template.marko b/packages/runtime-tags/src/__tests__/fixtures/context-writable/template.marko new file mode 100644 index 00000000000..725787f3bab --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/template.marko @@ -0,0 +1,4 @@ + + + + diff --git a/packages/runtime-tags/src/__tests__/fixtures/context-writable/test.ts b/packages/runtime-tags/src/__tests__/fixtures/context-writable/test.ts new file mode 100644 index 00000000000..0e3954256bc --- /dev/null +++ b/packages/runtime-tags/src/__tests__/fixtures/context-writable/test.ts @@ -0,0 +1,9 @@ +import type { TestConfig } from "../../main.test"; + +function add(container: Element) { + container.querySelector("button.add")!.click(); +} + +export const config: TestConfig = { + steps: [{}, add, add], +}; diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-chained/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-chained/sizes.json index 0aecd76db2e..3bb828eee42 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-chained/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-chained/sizes.json @@ -1,8 +1,8 @@ { "dom": { "child.mjs": { - "min": 129, - "brotli": 104 + "min": 127, + "brotli": 105 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-in-child/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-in-child/sizes.json index 8ebfc801927..108d5318385 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-in-child/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-async-in-child/sizes.json @@ -1,8 +1,8 @@ { "dom": { "child.mjs": { - "min": 817, - "brotli": 366 + "min": 808, + "brotli": 363 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-attrs-update/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-attrs-update/sizes.json index 070c25f3a2c..af891033cc5 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-attrs-update/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-attrs-update/sizes.json @@ -1,11 +1,11 @@ { "dom": { "template.marko.page.mjs": { - "min": 362, - "brotli": 247 + "min": 212, + "brotli": 161 }, "child.mjs": { - "min": 122, + "min": 120, "brotli": 100 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-inert/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-inert/sizes.json index f63a4fac554..edb19a2e844 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-inert/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-inert/sizes.json @@ -1,16 +1,16 @@ { "dom": { "template.marko.page.mjs": { - "min": 1114, - "brotli": 616 + "min": 435, + "brotli": 246 }, "child.mjs": { - "min": 162, - "brotli": 127 + "min": 198, + "brotli": 138 }, "v:child.marko.setup.mjs": { - "min": 77, - "brotli": 80 + "min": 95, + "brotli": 81 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-state/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-state/sizes.json index b7457abd2cf..f1bc12adc7d 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-state/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-conditional-child-state/sizes.json @@ -1,11 +1,11 @@ { "dom": { "template.marko.page.mjs": { - "min": 1118, - "brotli": 615 + "min": 439, + "brotli": 246 }, "child.mjs": { - "min": 296, + "min": 287, "brotli": 193 }, "v:child.marko.setup.mjs": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-placeholder/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-placeholder/sizes.json index 1d17874b0c7..86fb9ccbec7 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-placeholder/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-placeholder/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 1111, - "brotli": 616 + "min": 413, + "brotli": 253 }, "child.mjs": { - "min": 221, - "brotli": 152 + "min": 189, + "brotli": 137 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-unmount-before-load/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-unmount-before-load/sizes.json index b182f9f44d3..771ad6ba25c 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-unmount-before-load/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic-unmount-before-load/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 1680, - "brotli": 802 + "min": 977, + "brotli": 453 }, "child.mjs": { - "min": 223, - "brotli": 158 + "min": 186, + "brotli": 166 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic/sizes.json index 71666c0e9fa..9aebff66e6f 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-dynamic/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 1032, - "brotli": 570 + "min": 327, + "brotli": 206 }, "child.mjs": { - "min": 221, - "brotli": 152 + "min": 189, + "brotli": 156 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-attrs-update/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-attrs-update/sizes.json index e4e090ab364..7161b4d6a2f 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-attrs-update/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-attrs-update/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 545, - "brotli": 323 + "min": 247, + "brotli": 165 }, - "v:child.marko.input_value.mjs": { - "min": 71, - "brotli": 67 + "child.mjs": { + "min": 80, + "brotli": 82 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-class/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-class/sizes.json index a99e7958cc3..440144710ad 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-class/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-class/sizes.json @@ -1,7 +1,7 @@ { "dom": { "child.mjs": { - "min": 65, + "min": 63, "brotli": 63 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-id/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-id/sizes.json index 7c64c330ca2..cf73aaaae0e 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-id/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-selector-id/sizes.json @@ -1,7 +1,7 @@ { "dom": { "child.mjs": { - "min": 65, + "min": 63, "brotli": 63 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-unmount-before-load/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-unmount-before-load/sizes.json index a6314ac649c..6f7accd963c 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-unmount-before-load/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event-unmount-before-load/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 1279, - "brotli": 691 + "min": 442, + "brotli": 260 }, "child.mjs": { - "min": 177, - "brotli": 124 + "min": 168, + "brotli": 120 }, "v:child.marko.setup.mjs": { "min": 87, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event/sizes.json index fcc262a297c..1cff759d0c3 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-event/sizes.json @@ -1,7 +1,7 @@ { "dom": { "child.mjs": { - "min": 65, + "min": 63, "brotli": 63 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-attrs-update/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-attrs-update/sizes.json index a2cef4d9df3..114204d081e 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-attrs-update/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-attrs-update/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 479, - "brotli": 298 + "min": 242, + "brotli": 168 }, - "v:child.marko.input_value.mjs": { - "min": 71, - "brotli": 67 + "child.mjs": { + "min": 80, + "brotli": 82 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-unmount-before-load/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-unmount-before-load/sizes.json index b6aeb52a030..843d4930813 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-unmount-before-load/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle-unmount-before-load/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 1216, - "brotli": 662 + "min": 440, + "brotli": 258 }, "child.mjs": { - "min": 177, - "brotli": 124 + "min": 168, + "brotli": 120 }, "v:child.marko.setup.mjs": { "min": 87, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle/sizes.json index 3c81cf93757..a2eed697c44 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-idle/sizes.json @@ -1,7 +1,7 @@ { "dom": { "child.mjs": { - "min": 65, + "min": 63, "brotli": 63 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-input-event-handler/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-input-event-handler/sizes.json index da1dcaadeeb..fefd758cbae 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-input-event-handler/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-input-event-handler/sizes.json @@ -1,11 +1,11 @@ { "dom": { "template.marko.page.mjs": { - "min": 247, + "min": 245, "brotli": 164 }, "child.mjs": { - "min": 147, + "min": 145, "brotli": 114 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-load-error/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-load-error/sizes.json index adcabc18ef4..fc9acc488d3 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-load-error/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-load-error/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 1170, - "brotli": 657 + "min": 513, + "brotli": 298 }, "child.mjs": { - "min": 145, - "brotli": 107 + "min": 136, + "brotli": 103 }, "v:child.marko.setup.mjs": { "min": 87, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-media/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-media/sizes.json index 88ba521d056..a1113d782c7 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-media/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-media/sizes.json @@ -1,7 +1,7 @@ { "dom": { "child.mjs": { - "min": 65, + "min": 63, "brotli": 63 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-mixed-known-and-dynamic/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-mixed-known-and-dynamic/sizes.json index 4a578943eb1..0be54e35541 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-mixed-known-and-dynamic/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-mixed-known-and-dynamic/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 1102, - "brotli": 599 + "min": 404, + "brotli": 249 }, "child.mjs": { - "min": 199, - "brotli": 140 + "min": 167, + "brotli": 141 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-attrs-update/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-attrs-update/sizes.json index b707f6203bc..d333d6e6a6b 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-attrs-update/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-attrs-update/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 837, - "brotli": 426 + "min": 274, + "brotli": 188 }, - "v:child.marko.input_value.mjs": { - "min": 71, - "brotli": 67 + "child.mjs": { + "min": 80, + "brotli": 82 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-visible-wins/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-visible-wins/sizes.json index ee6a96c9b98..bdcab3a498a 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-visible-wins/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger-visible-wins/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 825, - "brotli": 435 + "min": 269, + "brotli": 179 }, - "v:child.marko.input_value.mjs": { - "min": 71, - "brotli": 67 + "child.mjs": { + "min": 80, + "brotli": 82 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger/sizes.json index 78e1f5fffc1..ae8e57c5417 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multi-trigger/sizes.json @@ -1,7 +1,7 @@ { "dom": { "child.mjs": { - "min": 65, + "min": 63, "brotli": 63 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multiple/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multiple/sizes.json index 2e59aa842ef..0044da62404 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multiple/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-multiple/sizes.json @@ -1,15 +1,15 @@ { "dom": { "template.marko.page.mjs": { - "min": 430, - "brotli": 262 + "min": 280, + "brotli": 177 }, "child-a.mjs": { - "min": 124, - "brotli": 102 + "min": 122, + "brotli": 103 }, "child-b.mjs": { - "min": 126, + "min": 124, "brotli": 103 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-async/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-async/sizes.json index 86ea5b28ab3..401db2518d8 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-async/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-async/sizes.json @@ -1,12 +1,12 @@ { "dom": { "child.mjs": { - "min": 416, - "brotli": 273 + "min": 259, + "brotli": 177 }, "grand-child.mjs": { - "min": 211, - "brotli": 149 + "min": 202, + "brotli": 142 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared-reversed/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared-reversed/sizes.json index 8474d5221ba..fa8276ef521 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared-reversed/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared-reversed/sizes.json @@ -1,12 +1,12 @@ { "dom": { "child.mjs": { - "min": 402, - "brotli": 278 + "min": 255, + "brotli": 182 }, "grand-child.mjs": { - "min": 211, - "brotli": 160 + "min": 204, + "brotli": 152 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared/sizes.json index 8474d5221ba..fa8276ef521 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-shared/sizes.json @@ -1,12 +1,12 @@ { "dom": { "child.mjs": { - "min": 402, - "brotli": 278 + "min": 255, + "brotli": 182 }, "grand-child.mjs": { - "min": 211, - "brotli": 160 + "min": 204, + "brotli": 152 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-visible-parent/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-visible-parent/sizes.json index fad3b619b04..aaf3a7d0051 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-visible-parent/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested-visible-parent/sizes.json @@ -1,12 +1,12 @@ { "dom": { "child.mjs": { - "min": 402, - "brotli": 278 + "min": 255, + "brotli": 182 }, "grand-child.mjs": { - "min": 211, - "brotli": 160 + "min": 204, + "brotli": 152 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested/sizes.json index 584555c2f13..f69512b4eeb 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-nested/sizes.json @@ -1,8 +1,8 @@ { "dom": { "grand-child.mjs": { - "min": 65, - "brotli": 63 + "min": 63, + "brotli": 62 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-parent-scope-child-state/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-parent-scope-child-state/sizes.json index 7058bc15296..0cf56d2d80b 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-parent-scope-child-state/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-parent-scope-child-state/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 384, - "brotli": 261 + "min": 232, + "brotli": 172 }, "child.mjs": { - "min": 182, - "brotli": 147 + "min": 180, + "brotli": 137 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder-script-runs-after-insert/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder-script-runs-after-insert/sizes.json index b2596c005da..47911520b6c 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder-script-runs-after-insert/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder-script-runs-after-insert/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 110, - "brotli": 89 + "min": 101, + "brotli": 84 }, "child.mjs": { - "min": 107, - "brotli": 87 + "min": 98, + "brotli": 102 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder/sizes.json index f22427a1cdd..635f655f286 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-placeholder/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 428, - "brotli": 288 + "min": 279, + "brotli": 192 }, - "v:child.marko.input_value.mjs": { - "min": 78, - "brotli": 74 + "child.mjs": { + "min": 80, + "brotli": 82 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-reorder-stream-order/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-reorder-stream-order/sizes.json index 1bd2e7b27dc..78e580f5f93 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-reorder-stream-order/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-reorder-stream-order/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 750, - "brotli": 331 + "min": 741, + "brotli": 329 }, "child.mjs": { - "min": 143, - "brotli": 112 + "min": 134, + "brotli": 111 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-runtime-id/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-runtime-id/sizes.json index eeb5e72faed..946144251e3 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-runtime-id/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-runtime-id/sizes.json @@ -1,8 +1,8 @@ { "dom": { "child.mjs": { - "min": 129, - "brotli": 102 + "min": 127, + "brotli": 104 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-script-runs-after-insert/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-script-runs-after-insert/sizes.json index a9b3f5e603f..798c89f8704 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-script-runs-after-insert/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-script-runs-after-insert/sizes.json @@ -1,7 +1,7 @@ { "dom": { "child.mjs": { - "min": 107, + "min": 105, "brotli": 93 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialize-promise/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialize-promise/sizes.json index de69894f1db..cb49746bdcd 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialize-promise/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialize-promise/sizes.json @@ -1,8 +1,8 @@ { "dom": { "child.mjs": { - "min": 111, - "brotli": 85 + "min": 109, + "brotli": 88 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialized-globals/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialized-globals/sizes.json index 21eb2c7decc..13a35d23097 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialized-globals/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-serialized-globals/sizes.json @@ -1,8 +1,8 @@ { "dom": { "child.mjs": { - "min": 143, - "brotli": 109 + "min": 141, + "brotli": 110 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-shared-parent/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-shared-parent/sizes.json index 5b9df0cbf09..3a94f04036c 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-shared-parent/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-shared-parent/sizes.json @@ -2,8 +2,8 @@ "dom": { "template.marko.page.mjs": { "total": { - "min": 474, - "brotli": 279 + "min": 324, + "brotli": 185 }, "files": { "tags/parent-a.marko": 258, @@ -12,7 +12,7 @@ } }, "child.mjs": { - "min": 122, + "min": 120, "brotli": 100 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-sibling-binding/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-sibling-binding/sizes.json index 98a32947b72..1ebeb348fb9 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-sibling-binding/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-sibling-binding/sizes.json @@ -1,15 +1,15 @@ { "dom": { "template.marko.page.mjs": { - "min": 324, - "brotli": 194 + "min": 322, + "brotli": 185 }, "child-b.mjs": { - "min": 140, + "min": 138, "brotli": 108 }, "child-s.mjs": { - "min": 140, + "min": 138, "brotli": 108 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-twice/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-twice/sizes.json index ddfc3adf4f4..ed94aa8b34a 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-twice/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-twice/sizes.json @@ -1,8 +1,8 @@ { "dom": { "child.mjs": { - "min": 129, - "brotli": 104 + "min": 127, + "brotli": 105 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-unmount-before-load/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-unmount-before-load/sizes.json index baadd04c91e..3f2504f7f09 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-unmount-before-load/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-unmount-before-load/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 2003, - "brotli": 953 + "min": 1237, + "brotli": 567 }, "child.mjs": { - "min": 177, - "brotli": 124 + "min": 168, + "brotli": 120 }, "v:child.marko.setup.mjs": { "min": 87, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-attrs-update/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-attrs-update/sizes.json index e1e9c7cd214..67855c40551 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-attrs-update/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-attrs-update/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 600, - "brotli": 359 + "min": 235, + "brotli": 161 }, - "v:child.marko.input_value.mjs": { - "min": 71, - "brotli": 67 + "child.mjs": { + "min": 80, + "brotli": 82 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-parent-resume/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-parent-resume/sizes.json index f397d124f5b..26f5e7b3dae 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-parent-resume/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible-parent-resume/sizes.json @@ -1,12 +1,12 @@ { "dom": { "template.marko.page.mjs": { - "min": 159, - "brotli": 121 + "min": 157, + "brotli": 141 }, "child.mjs": { - "min": 129, - "brotli": 102 + "min": 127, + "brotli": 104 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible/sizes.json index c174b7cac32..d1edd8740c2 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag-visible/sizes.json @@ -1,7 +1,7 @@ { "dom": { "child.mjs": { - "min": 65, + "min": 63, "brotli": 63 } }, diff --git a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag/sizes.json index 39b0beb9a90..07fb3b24a04 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/lazy-tag/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/lazy-tag/sizes.json @@ -1,8 +1,8 @@ { "dom": { "child.mjs": { - "min": 129, - "brotli": 103 + "min": 127, + "brotli": 105 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholder-static-multiple/sizes.json b/packages/runtime-tags/src/__tests__/fixtures/placeholder-static-multiple/sizes.json index ba5d5d6bb83..e69a6e1e0d9 100644 --- a/packages/runtime-tags/src/__tests__/fixtures/placeholder-static-multiple/sizes.json +++ b/packages/runtime-tags/src/__tests__/fixtures/placeholder-static-multiple/sizes.json @@ -1,8 +1,8 @@ { "dom": { "template.marko.page.mjs": { - "min": 9848, - "brotli": 3880 + "min": 10051, + "brotli": 3955 } }, "html": { diff --git a/packages/runtime-tags/src/__tests__/main.test.ts b/packages/runtime-tags/src/__tests__/main.test.ts index 7c5c2c76474..d785b58f413 100644 --- a/packages/runtime-tags/src/__tests__/main.test.ts +++ b/packages/runtime-tags/src/__tests__/main.test.ts @@ -52,6 +52,9 @@ export type TestConfig = { error_compiler?: true | string[]; /** Compiles the fixture with a custom `runtimeId` compiler option. */ runtime_id?: string; + /** Strings that must not appear in the given output's optimize bundle + * snapshot (debug bundles skip tree-shaking). */ + bundle_excludes?: { dom?: string[]; html?: string[] }; }; // `scripts/test-parallel` fans the fixtures across CPU cores by giving each @@ -218,7 +221,19 @@ function testFixtures(interop?: true) { const runner = await ssrRunner(); const { snapshot, sizes } = await runner[`${output}Bundle`](); if (optimize && sizes) stats.dom = sizes; - return stripFixtureDir(snapshot); + const result = await stripFixtureDir(snapshot); + if (optimize) { + // Debug bundles skip tree-shaking, so exclusion is only a + // meaningful guarantee for the optimized artifact. + for (const exclude of config.bundle_excludes?.[output] || []) { + if (result.includes(exclude)) { + throw new Error( + `Expected the ${output} bundle to not contain ${JSON.stringify(exclude)} (bundle_excludes).`, + ); + } + } + } + return result; }, `${output}.bundle.js`); }; diff --git a/packages/runtime-tags/src/common/accessor.debug.ts b/packages/runtime-tags/src/common/accessor.debug.ts index 29250bc8218..7350d1f6df3 100644 --- a/packages/runtime-tags/src/common/accessor.debug.ts +++ b/packages/runtime-tags/src/common/accessor.debug.ts @@ -3,6 +3,8 @@ export enum AccessorPrefix { ClosureScopes = "ClosureScopes:", ClosureSignalIndex = "ClosureSignalIndex:", ConditionalRenderer = "ConditionalRenderer:", + ContextLink = "ContextLink:", + ContextSignal = "ContextSignal:", ControlledHandler = "ControlledHandler:", ControlledType = "ControlledType:", ControlledValue = "ControlledValue:", @@ -26,6 +28,8 @@ export enum AccessorProp { CatchContent = "#CatchContent", ClosestBranch = "#ClosestBranch", ClosestBranchId = "#ClosestBranchId", + ContextScopes = "#ContextScopes", + ContextValue = "#ContextValue", Gen = "#Gen", DetachedAwait = "#DetachedAwait", EndNode = "#EndNode", diff --git a/packages/runtime-tags/src/common/accessor.ts b/packages/runtime-tags/src/common/accessor.ts index d26ffde0852..d2ab8fca060 100644 --- a/packages/runtime-tags/src/common/accessor.ts +++ b/packages/runtime-tags/src/common/accessor.ts @@ -3,6 +3,8 @@ export enum AccessorPrefix { ClosureScopes = "B", ClosureSignalIndex = "C", ConditionalRenderer = "D", + ContextLink = "V", + ContextSignal = "W", ControlledHandler = "E", ControlledType = "F", ControlledValue = "G", @@ -26,6 +28,8 @@ export enum AccessorProp { CatchContent = "E", ClosestBranch = "F", ClosestBranchId = "G", + ContextScopes = "Z", + ContextValue = "I", Gen = "H", DetachedAwait = "V", EndNode = "K", diff --git a/packages/runtime-tags/src/dom.ts b/packages/runtime-tags/src/dom.ts index 9872fe29744..4cf3bdf760c 100644 --- a/packages/runtime-tags/src/dom.ts +++ b/packages/runtime-tags/src/dom.ts @@ -4,6 +4,14 @@ export { forIn, forOf, forTo, forUntil } from "./common/for"; export { _call } from "./common/helpers"; export { $signal, $signalReset } from "./dom/abort-signal"; export { compat } from "./dom/compat"; +export { + _context_branch, + _context_change, + _context_closure, + _context_read, + _context_value, + _context_write, +} from "./dom/context"; export { _await_content, _await_promise, diff --git a/packages/runtime-tags/src/dom/context.ts b/packages/runtime-tags/src/dom/context.ts new file mode 100644 index 00000000000..011cef6af31 --- /dev/null +++ b/packages/runtime-tags/src/dom/context.ts @@ -0,0 +1,137 @@ +import { AccessorPrefix, AccessorProp, type Scope } from "../common/types"; +import { queueRender, runId } from "./queue"; +import { _resume } from "./resume"; +import { findBranchWithKey } from "./scope"; +import { type SignalFn, subscribeToScopeSet } from "./signals"; + +// `` client runtime; server-rendered immutable consumers read a +// serialized slot and pull nothing from this module. + +// Immutable consume: the serialized slot when present (server-rendered), +// otherwise resolve the nearest provider (a reserved box or a +// client-created instance); the value signal populates the slot. +export function _context_read( + scope: Scope, + accessor: string, + providerId: string, + debugName?: string, +) { + if (accessor in scope) return scope[accessor]; + + const linkAccessor = AccessorPrefix.ContextLink + providerId; + const provider = (findBranchWithKey(scope, linkAccessor)?.[linkAccessor] || + scope[AccessorProp.Global][linkAccessor]) as Scope | undefined; + if (MARKO_DEBUG && !provider) { + throw new Error( + `No provider found for ${JSON.stringify(debugName || providerId)}. Render a \`\` (or \`\`) tag above this consumer in the render tree.`, + ); + } + return provider && provider[AccessorProp.ContextValue]; +} + +// Consumer fan-out signal: resolves the provider (serialized link, branch +// entry, or root $global), applies the value, and subscribes. `fn` always +// receives a provider scope (an empty one when missing in production, so +// consumers compile plain member reads); debug throws instead. +export function _context_closure( + providerId: string, + fn: (scope: Scope, provider: Scope) => void, + resumeId: string, + debugName?: string, +) { + const linkAccessor = AccessorPrefix.ContextLink + providerId; + const signal: SignalFn = (scope) => { + let provider = (scope[linkAccessor] ||= + findBranchWithKey(scope, linkAccessor)?.[linkAccessor] || + scope[AccessorProp.Global][linkAccessor]) as Scope | undefined; + if (provider) { + scope[AccessorPrefix.ContextSignal + providerId] = signal; + subscribeToScopeSet(provider, AccessorProp.ContextScopes, scope); + } else if (MARKO_DEBUG) { + throw new Error( + `No provider found for ${JSON.stringify(debugName || providerId)}. Render a \`\` (or \`\`) tag above this consumer in the render tree.`, + ); + } else { + provider = {} as Scope; + } + fn(scope, provider); + }; + return _resume(resumeId, signal); +} + +// Provider value write + fan-out, re-run whenever the value can change. +export function _context_value( + scope: Scope, + value: unknown, + providerId: string, +) { + if ( + scope[AccessorProp.ContextValue] !== value || + !(AccessorProp.ContextValue in scope) + ) { + scope[AccessorProp.ContextValue] = value; + const subscribers = scope[AccessorProp.ContextScopes] as + Set | undefined; + if (subscribers) { + const signalAccessor = AccessorPrefix.ContextSignal + providerId; + for (const subscriber of subscribers) { + // Skip destroyed scopes and scopes rendering this pass (same + // liveness guard as `_closure`). + if ( + subscriber[AccessorProp.Gen] > 0 && + subscriber[AccessorProp.Gen] < runId + ) { + queueRender(subscriber, subscriber[signalAccessor] as SignalFn, -1); + } + } + } + } +} + +// Branch entry so client-created consumers below resolve this provider +// (root providers stamp $global); mirrors the serialized entry for resume. +export function _context_branch(scope: Scope, providerId: string) { + const target = + scope[AccessorProp.ClosestBranch] || scope[AccessorProp.Global]; + const linkAccessor = AccessorPrefix.ContextLink + providerId; + if (MARKO_DEBUG) { + const existing = target[linkAccessor] as Scope | undefined; + if (existing && existing !== scope && existing[AccessorProp.Gen] > 0) { + throw new Error( + `Multiple live instances of the provider ${JSON.stringify(providerId)} share the same branch; consumers created client-side cannot tell them apart. Wrap each provider instance in its own control flow (eg a keyed \`\` iteration or an \`\`).`, + ); + } + } + target[linkAccessor] = scope; +} + +// The change handler rides the tag-variable-change prefix keyed by the +// context value prop, which no real binding accessor can collide with. +const changeAccessor = + AccessorPrefix.TagVariableChange + AccessorProp.ContextValue; + +// Installs the writable provider's change handler for consumer writes. +export function _context_change( + scope: Scope, + changeHandler: (value: unknown) => void, +) { + scope[changeAccessor] = changeHandler; +} + +// Writable consumer assignment: the write lands in provider state and fans +// back out; the local value updates then, not synchronously. +export function _context_write( + scope: Scope, + providerId: string, + value: T, +): T { + const provider = scope[AccessorPrefix.ContextLink + providerId] as + Scope | undefined; + if (MARKO_DEBUG && !provider?.[changeAccessor]) { + throw new Error( + `Cannot write to the value for ${JSON.stringify(providerId)}; no writable provider was found.`, + ); + } + provider?.[changeAccessor]?.(value); + return value; +} diff --git a/packages/runtime-tags/src/html.ts b/packages/runtime-tags/src/html.ts index 52367140091..d9d94dfc782 100644 --- a/packages/runtime-tags/src/html.ts +++ b/packages/runtime-tags/src/html.ts @@ -37,6 +37,12 @@ export { _to_text, _unescaped, } from "./html/content"; +export { + _context_get, + _context_link, + _context_provide, + _context_reserve, +} from "./html/context"; export { _content, _content_resume, _dynamic_tag } from "./html/dynamic-tag"; export { forIn, diff --git a/packages/runtime-tags/src/html/context.ts b/packages/runtime-tags/src/html/context.ts new file mode 100644 index 00000000000..2c004616c19 --- /dev/null +++ b/packages/runtime-tags/src/html/context.ts @@ -0,0 +1,150 @@ +import { AccessorPrefix, AccessorProp } from "../common/types"; +import { + _resume, + _scope, + _scope_id, + _scope_with_id, + getBranchId, + getContext, + withContext, +} from "./writer"; + +// Boxed so "no provider above" is distinguishable from an explicitly +// provided `undefined`. +interface ContextBox { + v: unknown; + /** The provider's scope id; assigned lazily for immutable providers. */ + s?: number; + /** Immutable only: provider id, containing branch, and open-chain link. */ + k?: string; + b?: number; + p?: ContextBox; +} + +// Chain of open immutable boxes, for reserve-all at dynamic content holes. +const kOpenBoxes = Symbol(); + +export function _context_provide( + key: string, + value: unknown, + cb: () => T, + scopeId?: number, +): T { + const box: ContextBox = { v: value }; + if (scopeId === undefined) { + // Immutable: serialized only if a `_context_reserve` names it. + box.k = key; + box.b = getBranchId() ?? 0; + box.p = getContext(kOpenBoxes as never) as ContextBox | undefined; + return withContext(key, box, () => withContext(kOpenBoxes, box, cb)); + } + { + // Mutable providers serialize their value, fan-out set, and a branch + // entry (scope 0 for root providers) for client-created consumers. + box.s = scopeId; + const branchId = getBranchId() ?? 0; + const providerScope = _scope(scopeId, { + [AccessorProp.ContextValue]: value, + [AccessorProp.ContextScopes]: + (_scope_with_id(scopeId) as Record)[ + AccessorProp.ContextScopes + ] || new Set(), + }); + if (MARKO_DEBUG) { + assertSoleBranchInstance(branchId, key, providerScope); + } + _scope(branchId, { + [AccessorPrefix.ContextLink + key]: providerScope, + }); + } + return withContext(key, box, cb); +} + +// Compiler-emitted where client-re-renderable content may consume an +// immutable context: lazily serializes the named provider boxes (or every +// open one when called bare) so client-created consumers can resolve them. +export function _context_reserve(...keys: string[]) { + if (keys.length) { + for (const key of keys) { + reserveBox(getContext(key) as ContextBox | undefined); + } + } else { + for ( + let box = getContext(kOpenBoxes as never) as ContextBox | undefined; + box; + box = box.p + ) { + reserveBox(box); + } + } +} + +function reserveBox(box: ContextBox | undefined) { + if (box && box.k && box.s === undefined) { + const providerScope = _scope((box.s = _scope_id()), { + [AccessorProp.ContextValue]: box.v, + }); + if (MARKO_DEBUG) { + assertSoleBranchInstance(box.b!, box.k, providerScope); + } + _scope(box.b!, { + [AccessorPrefix.ContextLink + box.k]: providerScope, + }); + } +} + +function assertSoleBranchInstance( + branchId: number, + key: string, + providerScope: unknown, +) { + const existing = (_scope_with_id(branchId) as Record)[ + AccessorPrefix.ContextLink + key + ]; + if (existing && existing !== providerScope) { + throw new Error( + `Multiple instances of the provider ${JSON.stringify(key)} share the same branch; consumers created client-side cannot tell them apart. Wrap each provider instance in its own control flow (eg a keyed \`\` iteration or an \`\`).`, + ); + } +} + +export function _context_get(key: string, debugName?: string) { + const box = getContext(key) as ContextBox | undefined; + if (MARKO_DEBUG && !box) { + throw missingProviderError(debugName ?? key); + } + + return box && box.v; +} + +// Mutable-provider consume: returns the value and serializes the link, +// registered fan-out signal, and fan-out set membership. +export function _context_link( + scopeId: number, + key: string, + registryId: string, + debugName?: string, +) { + const box = getContext(key) as ContextBox | undefined; + if (!box) { + if (MARKO_DEBUG) { + throw missingProviderError(debugName ?? key); + } + return undefined; + } + + const providerScope = _scope_with_id(box.s!) as Record; + (providerScope[AccessorProp.ContextScopes] as Set).add( + _scope(scopeId, { + [AccessorPrefix.ContextLink + key]: providerScope, + [AccessorPrefix.ContextSignal + key]: _resume({}, registryId), + }), + ); + return box.v; +} + +function missingProviderError(name: string) { + return new Error( + `No provider found for ${JSON.stringify(name)}. Render a \`\` (or \`\`) tag above this consumer in the render tree.`, + ); +} diff --git a/packages/runtime-tags/src/html/dynamic-tag.ts b/packages/runtime-tags/src/html/dynamic-tag.ts index 7b970d08b43..3a4a79d6741 100644 --- a/packages/runtime-tags/src/html/dynamic-tag.ts +++ b/packages/runtime-tags/src/html/dynamic-tag.ts @@ -9,6 +9,7 @@ import { ResumeSymbol, } from "../common/types"; import { _attr_select_value, _attr_textarea_value, _attrs } from "./attrs"; +import { _context_reserve } from "./context"; import type { ServerRenderer } from "./template"; import { _html, @@ -49,6 +50,12 @@ export let _dynamic_tag = ( let rendered: boolean; let result: unknown; + if (shouldResume) { + // Statically unknowable content that can render client-side may + // consume any open server-only context. + _context_reserve(); + } + if (typeof renderer === "string") { if (MARKO_DEBUG) { assertValidTagName(renderer); diff --git a/packages/runtime-tags/src/html/serializer.ts b/packages/runtime-tags/src/html/serializer.ts index a4d0682974d..4097b3010fd 100644 --- a/packages/runtime-tags/src/html/serializer.ts +++ b/packages/runtime-tags/src/html/serializer.ts @@ -424,10 +424,15 @@ function writeScopesRoot(state: State, flushes: ScopeFlush[]) { // skip count (the browser creates scopes on demand). const openIndex = buf.push("") - 1; if (writeObjectProps(state, flush[2], ref)) { + // The delta may be negative: a payload can carry two partials for the + // same slot under different identities (the serialized globals object + // and scope 0's own record both flush as slot 0 when a root mutable + // `` provider stamps its branch link), and the browser's + // `applyScopes` sums deltas signed. buf[openIndex] = nextSlotId === -1 ? "[" + scopeId + ",{" - : (scopeId > nextSlotId ? "," + (scopeId - nextSlotId) : "") + ",{"; + : (scopeId !== nextSlotId ? "," + (scopeId - nextSlotId) : "") + ",{"; if (fillIndex === -1) fillIndex = openIndex; nextSlotId = scopeId + 1; buf.push("}"); diff --git a/packages/runtime-tags/src/html/writer.ts b/packages/runtime-tags/src/html/writer.ts index 98924919408..397a85b73d0 100644 --- a/packages/runtime-tags/src/html/writer.ts +++ b/packages/runtime-tags/src/html/writer.ts @@ -17,6 +17,7 @@ import { } from "../common/types"; import { RendererProp } from "../common/types"; import { attrAssignment } from "./attrs"; +import { _context_reserve } from "./context"; import { forInBy, forOfBy, forStepBy } from "./for"; import { REORDER_RUNTIME_CODE, @@ -135,6 +136,9 @@ export function _attr_content( const branchId = _peek_scope_id(); if (render) { if (shouldResume) { + // Statically unknowable content that can render client-side may + // consume any open server-only context. + _context_reserve(); withBranchId(branchId, render); } else { render(); @@ -331,6 +335,10 @@ export function isInResumedBranch() { return $chunk?.context?.[kBranchId] !== undefined; } +export function getBranchId() { + return $chunk.context?.[kBranchId] as number | undefined; +} + export function withBranchId(branchId: number, cb: () => T): T { return withContext(kBranchId, branchId, cb); } diff --git a/packages/runtime-tags/src/translator/core/context.ts b/packages/runtime-tags/src/translator/core/context.ts new file mode 100644 index 00000000000..b24ae674a2e --- /dev/null +++ b/packages/runtime-tags/src/translator/core/context.ts @@ -0,0 +1,801 @@ +import { types as t } from "@marko/compiler"; +import { + assertNoArgs, + assertNoParams, + getProgram, + loadFileForImport, + type Tag, +} from "@marko/compiler/babel-utils"; + +import type { AccessorProp } from "../../common/types"; +import { assertNoBodyContent, assertNoSpreadAttrs } from "../util/assert"; +import { generateUidIdentifier } from "../util/generate-uid"; +import { getAccessorPrefix, getAccessorProp } from "../util/get-accessor-char"; +import { isOptimize } from "../util/marko-config"; +import { + type Binding, + BindingType, + getScopeAccessorLiteral, + mergeReferences, + pruneBinding, + trackVarReferences, +} from "../util/references"; +import { callRuntime } from "../util/runtime"; +import runtimeInfo from "../util/runtime-info"; +import { getScopeExpression } from "../util/scope-read"; +import { + getOrCreateSection, + getScopeIdIdentifier, + getSection, + type Section, +} from "../util/sections"; +import { getExprIfSerialized } from "../util/serialize-guard"; +import { + addSerializeReason, + type SerializeReason, +} from "../util/serialize-reasons"; +import { addSetupStatement } from "../util/setup-statements"; +import { + addStatement, + addValue, + getResumeRegisterId, + initValue, + setSectionSerializedValue, +} from "../util/signals"; +import { createSectionState } from "../util/state"; +import translateVar from "../util/translate-var"; +import { translateByTarget } from "../util/visitors"; +import * as writer from "../util/writer"; +import { scopeIdentifier } from "../visitors/program"; + +// Cross-template flags, set once at the provider's own analyze exit; a +// mid-analysis reentrant read sees them unset and uses conservative defaults. +declare module "@marko/compiler/dist/types" { + export interface ProgramExtra { + providesContext?: true; + contextMutable?: boolean; + contextWritable?: true; + [kProvideInfo]?: { + writable: boolean; + valueExtra: t.NodeExtra; + section: Section; + }; + /** Statically known server-only provider ids consumed anywhere under + * this template (content rendered through dynamic holes is instead + * reserved at render time by the html content runtimes). */ + consumedContexts?: Set; + [kSelfConsumes]?: Section[]; + [kRecursiveRefs]?: Section[]; + } + + export interface MarkoTagExtra { + contextProviderId?: string; + contextProviderRequest?: string; + contextProviderMutable?: boolean; + contextProviderWritable?: boolean; + /** Shared by same-section consumers of one provider (a shared scope): + * they compose into a single wiring entry (one link + fan-out signal). */ + contextConsumers?: Binding[]; + } +} + +export const kProvideInfo: unique symbol = Symbol("context provide info"); +export const kSelfConsumes: unique symbol = Symbol("context self consumes"); +export const kRecursiveRefs: unique symbol = Symbol("context recursive refs"); + +// Server-only context ids consumed beneath each section (bubbled to +// ancestors); a client-re-renderable branch serializes the boxes its +// subtree may consume (`_context_reserve`), keeping every other page free +// of context payload bytes. +const sectionConsumedContexts = new WeakMap>(); + +export function addConsumedContext( + section: Section | undefined, + ids: Set | string | undefined, +) { + if (!ids || (typeof ids === "object" && !ids.size)) return; + for (let cur = section; cur; cur = cur.parent) { + let set = sectionConsumedContexts.get(cur); + if (!set) { + sectionConsumedContexts.set(cur, (set = new Set())); + } + if (typeof ids === "string") { + set.add(ids); + } else { + for (const id of ids) set.add(id); + } + } +} + +export function getConsumedContexts(section: Section) { + return sectionConsumedContexts.get(section); +} + +// Statement for a client-re-renderable branch site whose subtree consumes +// statically known server-only contexts; `undefined` when the site cannot +// re-render client-side or nothing known is consumed. Unknowable subtrees +// (dynamic holes) are instead reserved at render time by the content +// runtimes, so branches never server-rendered cover only known ids. +export function buildContextReserve( + section: Section, + rerenderReason: SerializeReason | undefined, + bodySections: (Section | undefined)[], +): t.Statement | undefined { + if (!rerenderReason) return; + let consumed: Set | undefined; + for (const body of bodySections) { + const cur = body && sectionConsumedContexts.get(body); + if (cur) { + if (consumed) { + for (const id of cur) consumed.add(id); + } else { + consumed = new Set(cur); + } + } + } + if (!consumed) return; + + const gated = getExprIfSerialized( + section, + rerenderReason, + callRuntime( + "_context_reserve", + ...[...consumed].sort().map((id) => t.stringLiteral(id)), + ), + ); + return gated && t.expressionStatement(gated); +} + +// Groups same-section consumers per provider during analyze (phase-local; +// the group crosses to translate via each tag's `contextConsumers` extra). +const [getConsumerGroups] = createSectionState>( + "contextConsumers", + () => new Map(), +); + +export default { + analyze(tag) { + assertNoArgs(tag); + assertNoParams(tag); + assertNoSpreadAttrs(tag); + + const { node } = tag; + let valueAttr: t.MarkoAttribute | undefined; + let valueChangeAttr: t.MarkoAttribute | undefined; + let fromAttr: t.MarkoAttribute | undefined; + + for (const attr of node.attributes) { + const markoAttr = attr as t.MarkoAttribute; + if (markoAttr.name === "from") { + fromAttr = markoAttr; + } else if (markoAttr.name === "valueChange") { + valueChangeAttr = markoAttr; + } else if (markoAttr.default || markoAttr.name === "value") { + valueAttr = markoAttr; + } else { + throw buildAttrError( + tag, + markoAttr, + "The [`` tag](https://markojs.com/docs/reference/core-tag#context) only supports the `value=` (default/bind) attribute, `valueChange=`, and `from=`.", + ); + } + } + + if (fromAttr) { + analyzeConsume(tag, fromAttr, valueAttr, valueChangeAttr); + } else if (valueAttr) { + analyzeProvide(tag, valueAttr, valueChangeAttr); + } else if (node.var) { + throw tag + .get("name") + .buildCodeFrameError( + "The [`` tag](https://markojs.com/docs/reference/core-tag#context) requires a `from=` attribute when consuming a context with a tag variable.", + ); + } else { + throw tag + .get("name") + .buildCodeFrameError( + "The [`` tag](https://markojs.com/docs/reference/core-tag#context) requires either a provided value (eg ``) or a `from=` attribute to consume a context.", + ); + } + }, + translate: translateByTarget({ + html: { + enter(tag) { + if (isProvide(tag)) { + writer.flushBefore(tag); + } + }, + exit(tag) { + if (isProvide(tag)) { + translateProvideHTML(tag); + } else { + translateConsumeHTML(tag); + } + }, + }, + dom: { + enter(tag) { + if (isProvide(tag)) { + // Emitted at enter so a client-created provider's value and + // branch stamp precede any same-section consumer's read. + translateProvideDOMStatements(tag); + } + }, + exit(tag) { + if (isProvide(tag)) { + translateProvideDOM(tag); + } else { + translateConsumeDOM(tag); + } + }, + }, + }), + attributes: {}, + autocomplete: [ + { + displayText: "context=", + description: "Provides a value to its body content.", + snippet: "context=${1:value}", + descriptionMoreURL: "https://markojs.com/docs/reference/core-tag#context", + }, + { + displayText: 'context/ from=""', + description: "Consumes a value provided by another template.", + descriptionMoreURL: "https://markojs.com/docs/reference/core-tag#context", + }, + ], + types: runtimeInfo.name + "/tags/context.d.marko", +} as Tag; + +function isProvide(tag: t.NodePath) { + return !tag.node.extra?.contextProviderId; +} + +function analyzeProvide( + tag: t.NodePath, + valueAttr: t.MarkoAttribute, + valueChangeAttr: t.MarkoAttribute | undefined, +) { + const { node } = tag; + + if (node.var) { + throw tag + .get("var") + .buildCodeFrameError( + "A [`` provider](https://markojs.com/docs/reference/core-tag#context) does not take a tag variable; use `from=` on a separate `` tag to consume it.", + ); + } + + if (node.body.attributeTags) { + throw tag + .get("name") + .buildCodeFrameError( + "The [`` tag](https://markojs.com/docs/reference/core-tag#context) does not support attribute tags.", + ); + } + + const program = getProgram(); + const programExtra = (program.node.extra ??= {}); + if (programExtra[kProvideInfo]) { + throw tag + .get("name") + .buildCodeFrameError( + "A template may only have a single provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context); more than one makes the template's identity as a context provider ambiguous.", + ); + } + + if (!node.body.body.length) { + throw tag + .get("name") + .buildCodeFrameError( + "A [`` provider](https://markojs.com/docs/reference/core-tag#context) requires [body content](https://markojs.com/docs/reference/language#tag-content); wrap the tags that should see the provided value.", + ); + } + + const section = getOrCreateSection(tag); + const tagExtra = mergeReferences(section, node, [valueAttr.value]); + + if (valueChangeAttr) { + // The change handler serializes (registered) so resumed consumers can + // invoke it through the link. + (valueChangeAttr.value.extra ??= {}).isEffect = true; + addSerializeReason(section, true, contextChangeAccessor()); + } + + programExtra[kProvideInfo] = { + writable: !!valueChangeAttr, + valueExtra: tagExtra, + section, + }; +} + +function analyzeConsume( + tag: t.NodePath, + fromAttr: t.MarkoAttribute, + valueAttr: t.MarkoAttribute | undefined, + valueChangeAttr: t.MarkoAttribute | undefined, +) { + const { node } = tag; + assertNoBodyContent(tag); + + if (valueAttr) { + throw buildAttrError( + tag, + valueAttr, + "A [`` tag](https://markojs.com/docs/reference/core-tag#context) cannot both provide a value and consume one via `from=`.", + ); + } + + if (valueChangeAttr) { + throw buildAttrError( + tag, + valueChangeAttr, + "The `valueChange=` attribute is only supported on a provide-mode [`` tag](https://markojs.com/docs/reference/core-tag#context).", + ); + } + + if (!node.var) { + throw tag + .get("name") + .buildCodeFrameError( + "The [`` tag](https://markojs.com/docs/reference/core-tag#context) requires a [tag variable](https://markojs.com/docs/reference/language#tag-variables) to consume a context.", + ); + } + + if (!t.isIdentifier(node.var)) { + throw tag + .get("var") + .buildCodeFrameError( + "The [`` tag](https://markojs.com/docs/reference/core-tag#context) consumer variable cannot be destructured.", + ); + } + + if (!t.isStringLiteral(fromAttr.value)) { + throw buildAttrError( + tag, + fromAttr, + 'The `from=` attribute must be a static string literal (eg `from=""` or `from="./provider.marko"`).', + ); + } + + const request = fromAttr.value.value; + let providerFile; + + try { + providerFile = loadFileForImport(tag.hub.file, request); + } catch (err) { + throw buildAttrError( + tag, + fromAttr, + `Unable to resolve the \`from=\` template ${JSON.stringify(request)}: ${ + (err as Error).message + }`, + ); + } + + if (!providerFile) { + throw buildAttrError( + tag, + fromAttr, + `Unable to resolve the \`from=\` template ${JSON.stringify(request)}.`, + ); + } + + const providerExtra = providerFile.ast.program.extra ?? {}; + // A self-referencing `from=` resolves to this same mid-analysis file, whose + // flags are not final yet -- answer from its AST and force the mutable tier + // (the program-exit hook makes the provide side match). + const self = providerFile === tag.hub.file; + const selfProvide = self + ? findProvideTag(providerFile.ast.program) + : undefined; + + if (self ? !selfProvide : !providerExtra.providesContext) { + throw buildAttrError( + tag, + fromAttr, + `The template ${JSON.stringify(request)} does not provide a value (it has no provide-mode \`\` tag).`, + ); + } + + // Conservative defaults where flags are not final yet: a reentrant cycle + // is treated as read-only and mutable, and a self-consume as mutable (its + // tier is refined at translate once the provide side is classified). + const writable = self + ? hasValueChangeAttr(selfProvide!) + : !!providerExtra.contextWritable; + const mutable = self || (providerExtra.contextMutable ?? true); + + // A mutable context's consumer variable changes client-side (fan-out), so + // it is tracked like a `` for serialize reasons and the update graph. + const varBinding = trackVarReferences( + tag, + mutable ? BindingType.let : BindingType.derived, + )!; + const tagExtra = (node.extra ??= {}); + const section = getOrCreateSection(tag); + + // Conservative: translate skips emission for pruned consumers, but that is + // only known after this analyze pass. + addSetupStatement(section); + + if (mutable) { + const providerId = providerFile.metadata.marko.id; + const group = getConsumerGroups(section); + let members = group.get(providerId); + if (members) { + members.push(varBinding); + } else { + group.set(providerId, (members = [varBinding])); + } + tagExtra.contextConsumers = members; + if (self) { + // A self-consume may drop to the immutable tier at the program exit + // hook; its section joins the consumed-context sets there. + ((getProgram().node.extra ??= {})[kSelfConsumes] ??= []).push(section); + } + } else { + addConsumedContext(section, providerFile.metadata.marko.id); + } + + if (varBinding.assignmentSections && !writable) { + const babelBinding = tag.scope.getBinding(node.var.name)!; + const violation = babelBinding.constantViolations[0]; + throw (violation ?? tag.get("var")).buildCodeFrameError( + `Cannot assign to \`${node.var.name}\`; the provider for ${JSON.stringify(request)} is read-only. Add \`:=\` to the provider's default attribute to make it writable.`, + ); + } + + tagExtra.contextProviderId = providerFile.metadata.marko.id; + tagExtra.contextProviderRequest = request; + tagExtra.contextProviderMutable = mutable; + tagExtra.contextProviderWritable = writable; +} + +// Provides compile inline into the parent section (`isNativeNode`, like +// ``): the body runs once in place; `withContext` only needs a closure. +function translateProvideHTML(tag: t.NodePath) { + const { node } = tag; + const { valueAttr, valueChangeAttr } = getContextAttrs(node); + const programExtra = getProgram().node.extra!; + const mutable = !!programExtra.contextMutable; + const section = getSection(tag); + writer.flushInto(tag); + + if (valueChangeAttr) { + setSectionSerializedValue( + section, + contextChangeAccessor(), + t.logicalExpression( + "||", + valueChangeAttr.value, + t.unaryExpression("void", t.numericLiteral(0)), + ), + ); + } + + const bodyStatements = node.body.body as unknown as t.Statement[]; + const providerId = tag.hub.file.metadata.marko.id; + + tag + .replaceWith( + t.expressionStatement( + callRuntime( + "_context_provide", + t.stringLiteral(providerId), + valueAttr!.value, + t.arrowFunctionExpression([], t.blockStatement(bodyStatements)), + mutable ? getScopeIdIdentifier(section) : undefined, + ), + ), + )[0] + .skip(); +} + +function translateConsumeHTML(tag: t.NodePath) { + const { node } = tag; + const tagExtra = node.extra!; + const providerId = tagExtra.contextProviderId!; + const varBinding = (node.var as t.Identifier).extra!.binding!; + const debugName = + !isOptimize() && t.stringLiteral(tagExtra.contextProviderRequest!); + let read: t.Expression; + + if (isMutableTierConsume(tag) && getWiringLead(tag) === varBinding) { + const section = getSection(tag); + read = callRuntime( + "_context_link", + getScopeIdIdentifier(section), + t.stringLiteral(providerId), + t.stringLiteral(getResumeRegisterId(section, varBinding, "context")), + debugName, + ); + } else { + read = callRuntime("_context_get", t.stringLiteral(providerId), debugName); + } + + // `let` matches ``'s server semantics for the writable form: an + // SSR-time assignment mutates the local for the remainder of the render. + translateVar(tag, read, tagExtra.contextProviderWritable ? "let" : "const"); + tag.remove(); +} + +function translateProvideDOM(tag: t.NodePath) { + const { node } = tag; + const programExtra = getProgram().node.extra!; + const section = getSection(tag); + const tagExtra = node.extra!; + const { valueAttr, valueChangeAttr } = getContextAttrs(node); + const providerId = t.stringLiteral(tag.hub.file.metadata.marko.id); + + if (programExtra.contextMutable) { + addStatement( + "render", + section, + tagExtra.referencedBindings, + t.expressionStatement( + callRuntime( + "_context_value", + scopeIdentifier, + valueAttr!.value, + providerId, + ), + ), + ); + addStatement( + "render", + section, + undefined, + t.expressionStatement( + callRuntime("_context_branch", scopeIdentifier, providerId), + ), + ); + + if (valueChangeAttr) { + addStatement( + "render", + section, + valueChangeAttr.value.extra?.referencedBindings, + t.expressionStatement( + callRuntime( + "_context_change", + scopeIdentifier, + valueChangeAttr.value, + ), + ), + ); + } + } + + // Both tiers splice the body inline. + const bodyStatements = node.body.body as unknown as t.Statement[]; + for (const replacement of tag.replaceWithMultiple(bodyStatements)) { + replacement.skip(); + } +} + +// A client-created immutable provider stamps its branch and value like the +// serialized box (evaluating the value under client `$global` rules); the +// tree-shaken bundle drops this when the provider only renders server-side. +function translateProvideDOMStatements(tag: t.NodePath) { + const { node } = tag; + if (getProgram().node.extra!.contextMutable) return; + const section = getSection(tag); + const { valueAttr } = getContextAttrs(node); + + addStatement( + "render", + section, + node.extra!.referencedBindings, + t.expressionStatement( + t.assignmentExpression( + "=", + t.memberExpression( + scopeIdentifier, + t.stringLiteral(getAccessorProp().ContextValue), + true, + ), + valueAttr!.value, + ), + ), + ); + addStatement( + "render", + section, + undefined, + t.expressionStatement( + callRuntime( + "_context_branch", + scopeIdentifier, + t.stringLiteral(tag.hub.file.metadata.marko.id), + ), + ), + ); +} + +// Immutable: the serialized slot, or resolve the reserved box when created +// client-side. Mutable: a registered fan-out signal from setup. +function translateConsumeDOM(tag: t.NodePath) { + const { node } = tag; + const tagExtra = node.extra!; + const section = getSection(tag); + const varBinding = (node.var as t.Identifier).extra!.binding!; + + if (isMutableTierConsume(tag)) { + const providerId = tagExtra.contextProviderId!; + if (!consumerNeedsWiring(varBinding)) { + tag.remove(); + return; + } + + if (tagExtra.contextProviderWritable) { + initValue(varBinding).buildAssignment = (valueSection, value) => + callRuntime( + "_context_write", + getScopeExpression(valueSection, section), + t.stringLiteral(providerId), + value, + ); + } + + if (getWiringLead(tag) !== varBinding) { + tag.remove(); + return; + } + + const scopeParam = generateUidIdentifier("scope"); + const providerParam = generateUidIdentifier("provider"); + const wrapperId = generateUidIdentifier( + `context_${(node.var as t.Identifier).name}`, + ); + const memberCalls = tagExtra + .contextConsumers!.filter((member) => !pruneBinding(member)) + .map((member) => + t.callExpression(initValue(member).identifier, [ + t.identifier(scopeParam.name), + t.memberExpression( + t.identifier(providerParam.name), + t.stringLiteral(getAccessorProp().ContextValue), + true, + ), + ]), + ); + + getProgram().node.body.push( + t.variableDeclaration("const", [ + t.variableDeclarator( + wrapperId, + callRuntime( + "_context_closure", + t.stringLiteral(providerId), + t.arrowFunctionExpression( + [scopeParam, providerParam], + memberCalls.length > 1 + ? t.sequenceExpression(memberCalls) + : (memberCalls[0] ?? t.blockStatement([])), + ), + t.stringLiteral( + getResumeRegisterId(section, varBinding, "context"), + ), + !isOptimize() && t.stringLiteral(tagExtra.contextProviderRequest!), + ), + ), + ]), + ); + + addStatement( + "render", + section, + undefined, + t.expressionStatement(t.callExpression(wrapperId, [scopeIdentifier])), + ); + } else { + const signal = initValue(varBinding); + + // Slot when server-rendered, resolved from the reserved box when the + // consumer is created client-side. + addValue( + section, + undefined, + signal, + callRuntime( + "_context_read", + scopeIdentifier, + getScopeAccessorLiteral(varBinding, true), + t.stringLiteral(tagExtra.contextProviderId!), + !isOptimize() && t.stringLiteral(tagExtra.contextProviderRequest!), + ), + ); + } + + tag.remove(); +} + +// Wiring matters only when the value is observable client-side: any read +// keeps the binding un-pruned; assignments need the link for writes. +function consumerNeedsWiring(binding: Binding) { + return !pruneBinding(binding) || !!binding.assignmentSections; +} + +// A self-consume is conservatively mutable at analyze (the provide side is +// classified later); by translate the program flag is final, so a +// server-only self-consume drops to the immutable tier. +function isMutableTierConsume(tag: t.NodePath) { + const tagExtra = tag.node.extra!; + return ( + !!tagExtra.contextProviderMutable && + (tagExtra.contextProviderId !== tag.hub.file.metadata.marko.id || + getProgram().node.extra!.contextMutable !== false) + ); +} + +// The first group member needing wiring carries the group's single link and +// composed fan-out signal; `undefined` means the group serializes nothing. +function getWiringLead(tag: t.NodePath) { + return tag.node.extra!.contextConsumers!.find(consumerNeedsWiring); +} + +function getContextAttrs(node: t.MarkoTag) { + let valueAttr: t.MarkoAttribute | undefined; + let valueChangeAttr: t.MarkoAttribute | undefined; + for (const attr of node.attributes) { + const markoAttr = attr as t.MarkoAttribute; + if (markoAttr.name === "valueChange") { + valueChangeAttr = markoAttr; + } else if (markoAttr.default || markoAttr.name === "value") { + valueAttr = markoAttr; + } + } + return { valueAttr, valueChangeAttr }; +} + +// Syntactic scan for a provide-mode `` (used for self-referencing +// `from=`, where the program's own flags are not final yet). +function findProvideTag(node: t.Node): t.MarkoTag | undefined { + if (t.isMarkoTag(node)) { + if ( + t.isStringLiteral(node.name, { value: "context" }) && + !node.attributes.some( + (attr) => t.isMarkoAttribute(attr) && attr.name === "from", + ) && + node.attributes.some( + (attr) => + t.isMarkoAttribute(attr) && (attr.default || attr.name === "value"), + ) + ) { + return node; + } + } + + for (const child of t.isProgram(node) || t.isMarkoTagBody(node) + ? node.body + : t.isMarkoTag(node) + ? node.body.body + : []) { + const found = findProvideTag(child); + if (found) return found; + } +} + +function hasValueChangeAttr(tag: t.MarkoTag) { + return tag.attributes.some( + (attr) => t.isMarkoAttribute(attr) && attr.name === "valueChange", + ); +} + +// Mirrors `changeAccessor` in dom/context.ts. +function contextChangeAccessor() { + return (getAccessorPrefix().TagVariableChange + + getAccessorProp().ContextValue) as AccessorProp; +} + +function buildAttrError( + tag: t.NodePath, + attr: t.MarkoAttribute, + message: string, +) { + const attrPath = tag.get("attributes").find((a) => a.node === attr) as + t.NodePath | undefined; + return (attrPath ?? tag.get("name")).buildCodeFrameError(message); +} diff --git a/packages/runtime-tags/src/translator/core/for.ts b/packages/runtime-tags/src/translator/core/for.ts index e249296b069..7a6b73e17e5 100644 --- a/packages/runtime-tags/src/translator/core/for.ts +++ b/packages/runtime-tags/src/translator/core/for.ts @@ -59,6 +59,7 @@ import { translateByTarget } from "../util/visitors"; import * as walks from "../util/walks"; import * as writer from "../util/writer"; import { kSkipEndTag } from "../visitors/tag/native-tag"; +import { buildContextReserve } from "./context"; type ForType = "in" | "of" | "to" | "until"; const kStatefulReason = Symbol(" stateful reason"); @@ -282,6 +283,17 @@ export default { } } + // A client-changing loop input can create fresh iteration branches; + // server-only contexts consumed beneath must serialize their box. + const reserveStatement = buildContextReserve( + tagSection, + statefulSerializeReason, + [bodySection], + ); + if (reserveStatement) { + statements.push(reserveStatement); + } + statements.push( t.expressionStatement(callRuntime(forTagHTMLRuntime, ...forTagArgs)), ); diff --git a/packages/runtime-tags/src/translator/core/if.ts b/packages/runtime-tags/src/translator/core/if.ts index 51a7f723508..fd204015035 100644 --- a/packages/runtime-tags/src/translator/core/if.ts +++ b/packages/runtime-tags/src/translator/core/if.ts @@ -61,6 +61,7 @@ import { translateByTarget } from "../util/visitors"; import * as walks from "../util/walks"; import * as writer from "../util/writer"; import { kSkipEndTag } from "../visitors/tag/native-tag"; +import { buildContextReserve } from "./context"; const kStatefulReason = Symbol(" stateful reason"); const BRANCHES_LOOKUP = new WeakMap< @@ -279,6 +280,17 @@ export const IfTag = { ); } + // A stateful condition can create branch content client-side; any + // server-only contexts consumed beneath must serialize their box. + const reserveStatement = buildContextReserve( + ifTagSection, + getSerializeReason(ifTagSection, kStatefulReason), + branches.map(([, branchBody]) => branchBody), + ); + if (reserveStatement) { + nextTag.insertBefore(reserveStatement); + } + nextTag.insertBefore(statement!); } }, diff --git a/packages/runtime-tags/src/translator/core/index.ts b/packages/runtime-tags/src/translator/core/index.ts index 6ed1119b7b8..c21ff8332d8 100644 --- a/packages/runtime-tags/src/translator/core/index.ts +++ b/packages/runtime-tags/src/translator/core/index.ts @@ -3,6 +3,7 @@ import AttrsTag from "./attrs"; import AwaitTag from "./await"; import ClientTag from "./client"; import ConstTag from "./const"; +import ContextTag from "./context"; import DebugTag from "./debug"; import DefineTag from "./define"; import EffectTag from "./effect"; @@ -31,6 +32,7 @@ export default { "": AwaitTag, "": ClientTag, "": ConstTag, + "": ContextTag, "": DebugTag, "": DefineTag, "": EffectTag, diff --git a/packages/runtime-tags/src/translator/util/sections.ts b/packages/runtime-tags/src/translator/util/sections.ts index a5a80ea7c12..6ed5c0a9610 100644 --- a/packages/runtime-tags/src/translator/util/sections.ts +++ b/packages/runtime-tags/src/translator/util/sections.ts @@ -290,6 +290,9 @@ export function getNodeContentType( case "await": case "try": case "show": + case "context": + // The body renders inline exactly once; Dynamic is always safe + // (a boundary marker only at section edges). return ContentType.Dynamic; default: return null; @@ -494,6 +497,7 @@ function isNativeNode(tag: t.NodePath) { case "html-script": case "html-style": case "show": + case "context": return true; case "style": return tag.node.body.body.some((child) => t.isMarkoPlaceholder(child)); diff --git a/packages/runtime-tags/src/translator/visitors/program/index.ts b/packages/runtime-tags/src/translator/visitors/program/index.ts index d493c56000d..fced8abb3e7 100644 --- a/packages/runtime-tags/src/translator/visitors/program/index.ts +++ b/packages/runtime-tags/src/translator/visitors/program/index.ts @@ -5,6 +5,13 @@ import { resolveRelativePath, } from "@marko/compiler/babel-utils"; +import { + addConsumedContext, + getConsumedContexts, + kProvideInfo, + kRecursiveRefs, + kSelfConsumes, +} from "../../core/context"; import { addAssetImport } from "../../util/asset-imports"; import { type BindingPropTree, @@ -22,12 +29,20 @@ import { import { BindingType, finalizeReferences, + kBranchSerializeReason, trackParamsReferences, } from "../../util/references"; import { resolveRelativeToEntry } from "../../util/resolve-relative-to-entry"; import { getCompatRuntimeFile, getRuntimePath } from "../../util/runtime"; import { startSection } from "../../util/sections"; -import { sectionHasSetupStatements } from "../../util/setup-statements"; +import { + addSerializeReason, + getSerializeSourcesForExpr, +} from "../../util/serialize-reasons"; +import { + addSetupStatement, + sectionHasSetupStatements, +} from "../../util/setup-statements"; import type { TemplateVisitor } from "../../util/visitors"; import programDOM from "./dom"; import programHTML from "./html"; @@ -99,7 +114,45 @@ export default { programExtra.domExports!.params = getBindingPropTree(paramsBinding); } + // Publish `` cross-template flags once, after + // `finalizeReferences()` (mutability reads finalized `sources`). + // Self-consumes are typed conservatively at analyze and pick their + // tier from this flag at translate. + const contextProvide = programExtra[kProvideInfo]; + if (contextProvide) { + programExtra.providesContext = true; + programExtra.contextMutable = + contextProvide.writable || + !!getSerializeSourcesForExpr(contextProvide.valueExtra); + if (contextProvide.writable) { + programExtra.contextWritable = true; + } + // Both tiers emit dom setup statements (a client-created provider + // stamps its branch and value). + addSetupStatement(contextProvide.section); + if (programExtra.contextMutable) { + // The mutable provider's runtime branch stamp needs the + // containing branch serialized. + addSerializeReason( + contextProvide.section, + true, + kBranchSerializeReason, + ); + } else { + // Immutable self-consumes join the consumed-context reach sets + // now that the tier is final. + for (const selfSection of programExtra[kSelfConsumes] || []) { + addConsumedContext(selfSection, program.hub.file.metadata.marko.id); + } + } + } + const section = programExtra.section!; + // Self-recursive render sites see the template's own final set. + for (const recursiveSection of programExtra[kRecursiveRefs] || []) { + addConsumedContext(recursiveSection, getConsumedContexts(section)); + } + programExtra.consumedContexts = getConsumedContexts(section) || new Set(); if (!section.hoistedTo && !sectionHasSetupStatements(section)) { // The setup export will be a noop, letting parent templates skip // importing and calling it (checked when this template translates). diff --git a/packages/runtime-tags/src/translator/visitors/tag/custom-tag.ts b/packages/runtime-tags/src/translator/visitors/tag/custom-tag.ts index c84978b943f..82859174ac0 100644 --- a/packages/runtime-tags/src/translator/visitors/tag/custom-tag.ts +++ b/packages/runtime-tags/src/translator/visitors/tag/custom-tag.ts @@ -15,6 +15,7 @@ import path from "path"; import { WalkCode } from "../../../common/types"; import type { LoadTrigger } from "../../../html/assets"; +import { addConsumedContext, kRecursiveRefs } from "../../core/context"; import { getBindingPropTree } from "../../util/binding-prop-tree"; import { generateUidIdentifier } from "../../util/generate-uid"; import { getTagName } from "../../util/get-tag-name"; @@ -29,10 +30,12 @@ import { BindingType, createBinding, getScopeAccessorLiteral, + kBranchSerializeReason, } from "../../util/references"; import { callRuntime } from "../../util/runtime"; import { createScopeReadExpression } from "../../util/scope-read"; import { getOrCreateSection } from "../../util/sections"; +import { addSerializeReason } from "../../util/serialize-reasons"; import { addSetupStatement } from "../../util/setup-statements"; import { addStatement, getSignal } from "../../util/signals"; import { createProgramState } from "../../util/state"; @@ -93,6 +96,29 @@ export default { ); } + if (childExtra.providesContext && childExtra.contextMutable !== false) { + // A mutable provider stamps its containing branch's scope at + // runtime, which the reason analysis cannot otherwise see. + addSerializeReason( + getOrCreateSection(tag), + true, + kBranchSerializeReason, + ); + } + + // Server-only contexts consumed under this tag, for branch reach + // analysis. A mid-analysis child means recursion: a self reference + // resolves to this template's own set at the program exit hook (a + // mutual cycle is unknowable, like other dynamic content). + if (childExtra.consumedContexts !== undefined) { + addConsumedContext( + getOrCreateSection(tag), + childExtra.consumedContexts, + ); + } else if (childProgram === getProgram().node) { + (programExtra[kRecursiveRefs] ??= []).push(getOrCreateSection(tag)); + } + if (tagExtra.tagNameLoad || !childExtra.domExports?.setupEmpty) { // The child's setup call lands in this section's setup unless the // child template proved its setup export is a noop (a mid analysis diff --git a/packages/runtime-tags/tags/context.d.marko b/packages/runtime-tags/tags/context.d.marko new file mode 100644 index 00000000000..9514d9bbce1 --- /dev/null +++ b/packages/runtime-tags/tags/context.d.marko @@ -0,0 +1,7 @@ +/** File for types only, not actual implementation **/ + +export interface Input { + value?: T; + valueChange?: ((newValue: T) => void) | false | null; + from?: string; +}