From 6e1209380049585fae8e232470ebbab53b804e34 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Thu, 6 Aug 2026 02:37:18 +0200 Subject: [PATCH 1/7] perf: share script attribute copying --- packages/solid-router/src/Asset.tsx | 36 +++++++++++++---------------- packages/vue-router/src/Asset.tsx | 36 +++++++++++++---------------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/packages/solid-router/src/Asset.tsx b/packages/solid-router/src/Asset.tsx index bc6bbe58c3..d71fc66541 100644 --- a/packages/solid-router/src/Asset.tsx +++ b/packages/solid-router/src/Asset.tsx @@ -62,6 +62,20 @@ interface ScriptAttrs { src?: string } +function setScriptAttrs( + script: HTMLScriptElement, + attrs: ScriptAttrs | undefined, +) { + if (!attrs) { + return + } + for (const [key, value] of Object.entries(attrs)) { + if (value !== undefined && value !== false) { + script.setAttribute(key, typeof value === 'boolean' ? '' : String(value)) + } + } +} + function Script({ attrs, children, @@ -97,15 +111,7 @@ function Script({ } const script = document.createElement('script') - - for (const [key, value] of Object.entries(attrs)) { - if (value !== undefined && value !== false) { - script.setAttribute( - key, - typeof value === 'boolean' ? '' : String(value), - ) - } - } + setScriptAttrs(script, attrs) document.head.appendChild(script) @@ -140,17 +146,7 @@ function Script({ const script = document.createElement('script') script.textContent = children - - if (attrs) { - for (const [key, value] of Object.entries(attrs)) { - if (value !== undefined && value !== false) { - script.setAttribute( - key, - typeof value === 'boolean' ? '' : String(value), - ) - } - } - } + setScriptAttrs(script, attrs) document.head.appendChild(script) diff --git a/packages/vue-router/src/Asset.tsx b/packages/vue-router/src/Asset.tsx index fdbf140c79..b51fc03fc2 100644 --- a/packages/vue-router/src/Asset.tsx +++ b/packages/vue-router/src/Asset.tsx @@ -10,6 +10,20 @@ interface ScriptAttrs { src?: string } +function setScriptAttrs( + script: HTMLScriptElement, + attrs: ScriptAttrs | undefined, +) { + if (!attrs) { + return + } + for (const [key, value] of Object.entries(attrs)) { + if (value !== undefined && value !== false) { + script.setAttribute(key, typeof value === 'boolean' ? '' : String(value)) + } + } +} + const Title = Vue.defineComponent({ name: 'Title', props: { @@ -87,15 +101,7 @@ const Script = Vue.defineComponent({ } const script = document.createElement('script') - - for (const [key, value] of Object.entries(attrs)) { - if (value !== undefined && value !== false) { - script.setAttribute( - key, - typeof value === 'boolean' ? '' : String(value), - ) - } - } + setScriptAttrs(script, attrs) document.head.appendChild(script) } else if (typeof children === 'string') { @@ -122,17 +128,7 @@ const Script = Vue.defineComponent({ const script = document.createElement('script') script.textContent = children - - if (attrs) { - for (const [key, value] of Object.entries(attrs)) { - if (value !== undefined && value !== false) { - script.setAttribute( - key, - typeof value === 'boolean' ? '' : String(value), - ) - } - } - } + setScriptAttrs(script, attrs) document.head.appendChild(script) } From b16f2ebff14cf0f3aa959fc2b0a66a5d5303fcb6 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Thu, 6 Aug 2026 02:37:38 +0200 Subject: [PATCH 2/7] perf: scan script elements without temporary arrays --- packages/solid-router/src/Asset.tsx | 28 ++++++++++++---------------- packages/vue-router/src/Asset.tsx | 28 ++++++++++++---------------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/packages/solid-router/src/Asset.tsx b/packages/solid-router/src/Asset.tsx index d71fc66541..dbba0038f9 100644 --- a/packages/solid-router/src/Asset.tsx +++ b/packages/solid-router/src/Asset.tsx @@ -102,12 +102,10 @@ function Script({ return attrs.src } })() - const existingScript = Array.from( - document.querySelectorAll('script[src]'), - ).find((el) => (el as HTMLScriptElement).src === normSrc) - - if (existingScript) { - return + for (const el of document.querySelectorAll('script[src]')) { + if ((el as HTMLScriptElement).src === normSrc) { + return + } } const script = document.createElement('script') @@ -127,21 +125,19 @@ function Script({ typeof attrs?.type === 'string' ? attrs.type : 'text/javascript' const nonceAttr = typeof attrs?.nonce === 'string' ? attrs.nonce : undefined - const existingScript = Array.from( - document.querySelectorAll('script:not([src])'), - ).find((el) => { - if (!(el instanceof HTMLScriptElement)) return false + for (const el of document.querySelectorAll('script:not([src])')) { + if (!(el instanceof HTMLScriptElement)) { + continue + } const sType = el.getAttribute('type') ?? 'text/javascript' const sNonce = el.getAttribute('nonce') ?? undefined - return ( + if ( el.textContent === children && sType === typeAttr && sNonce === nonceAttr - ) - }) - - if (existingScript) { - return + ) { + return + } } const script = document.createElement('script') diff --git a/packages/vue-router/src/Asset.tsx b/packages/vue-router/src/Asset.tsx index b51fc03fc2..610cbee6b0 100644 --- a/packages/vue-router/src/Asset.tsx +++ b/packages/vue-router/src/Asset.tsx @@ -92,12 +92,10 @@ const Script = Vue.defineComponent({ return attrs.src } })() - const existingScript = Array.from( - document.querySelectorAll('script[src]'), - ).find((el) => (el as HTMLScriptElement).src === normSrc) - - if (existingScript) { - return + for (const el of document.querySelectorAll('script[src]')) { + if ((el as HTMLScriptElement).src === normSrc) { + return + } } const script = document.createElement('script') @@ -109,21 +107,19 @@ const Script = Vue.defineComponent({ typeof attrs?.type === 'string' ? attrs.type : 'text/javascript' const nonceAttr = typeof attrs?.nonce === 'string' ? attrs.nonce : undefined - const existingScript = Array.from( - document.querySelectorAll('script:not([src])'), - ).find((el) => { - if (!(el instanceof HTMLScriptElement)) return false + for (const el of document.querySelectorAll('script:not([src])')) { + if (!(el instanceof HTMLScriptElement)) { + continue + } const sType = el.getAttribute('type') ?? 'text/javascript' const sNonce = el.getAttribute('nonce') ?? undefined - return ( + if ( el.textContent === children && sType === typeAttr && sNonce === nonceAttr - ) - }) - - if (existingScript) { - return + ) { + return + } } const script = document.createElement('script') From d674d4cecdca1109d07b54551f2e1c506a9477d4 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Thu, 6 Aug 2026 02:37:56 +0200 Subject: [PATCH 3/7] perf(solid-router): use idempotent script removal --- packages/solid-router/src/Asset.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/solid-router/src/Asset.tsx b/packages/solid-router/src/Asset.tsx index dbba0038f9..4bc01c1041 100644 --- a/packages/solid-router/src/Asset.tsx +++ b/packages/solid-router/src/Asset.tsx @@ -113,11 +113,7 @@ function Script({ document.head.appendChild(script) - onCleanup(() => { - if (script.parentNode) { - script.parentNode.removeChild(script) - } - }) + onCleanup(() => script.remove()) } if (typeof children === 'string') { @@ -146,11 +142,7 @@ function Script({ document.head.appendChild(script) - onCleanup(() => { - if (script.parentNode) { - script.parentNode.removeChild(script) - } - }) + onCleanup(() => script.remove()) } }) From b8ed93f60a057660ab06fe4e3e3f9273cdadd0f1 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Thu, 6 Aug 2026 04:34:17 +0200 Subject: [PATCH 4/7] test: cover compact DOM script handling --- RESULT-optimization-dom-script-passes.md | 111 +++++++++++++++++++ packages/solid-router/tests/Scripts.test.tsx | 71 ++++++++++++ packages/vue-router/tests/Scripts.test.tsx | 74 +++++++++++++ 3 files changed, 256 insertions(+) create mode 100644 RESULT-optimization-dom-script-passes.md diff --git a/RESULT-optimization-dom-script-passes.md b/RESULT-optimization-dom-script-passes.md new file mode 100644 index 0000000000..9d11477c18 --- /dev/null +++ b/RESULT-optimization-dom-script-passes.md @@ -0,0 +1,111 @@ +# DOM script micro-pass results + +## Scope + +Baseline: `697ebb6ddbd433d052b6b4707938a5c595865d58` (`main`) + +Candidate production commits: + +- `6e12093800` — share the private script-attribute copier in Solid and Vue +- `b16f2ebff1` — scan static `querySelectorAll` results directly instead of allocating arrays and callbacks +- `d674d4cecd` — use idempotent `HTMLScriptElement.remove()` in Solid cleanup + +The changes are private to `Asset.tsx`. Public component props, exported types, +rendered attributes, script matching rules, and script lifecycle behavior are +unchanged. React already uses these three implementation shapes. + +## Isolated attribution + +All values are bytes relative to the exact-main control. Gzip is the primary +metric. + +| Hunk | Scenario | Raw | Initial gzip | Gzip | Brotli | +| ------------------------ | ------------------- | --: | -----------: | ---: | -----: | +| Shared attribute copier | `solid-router.full` | -73 | -22 | -21 | -39 | +| Shared attribute copier | `vue-router.full` | -73 | -15 | -14 | +52 | +| Direct static-node scan | `solid-router.full` | -20 | -11 | -9 | -36 | +| Direct static-node scan | `vue-router.full` | -20 | -6 | -6 | +30 | +| Copier + direct scan | `solid-router.full` | -93 | -34 | -32 | -19 | +| Copier + direct scan | `vue-router.full` | -93 | -20 | -21 | -31 | +| Idempotent Solid cleanup | `solid-router.full` | -66 | -10 | -8 | -42 | + +Every independent production hunk improves gzip. The two shared Solid/Vue +hunks also compose better than either hunk's isolated Brotli result in Vue. + +## Final 17-scenario matrix + +Fresh control: `/tmp/dom-script-fresh-control-full.json` + +Candidate: `/tmp/dom-script-final-rerun.json` + +| Scenario | Raw | Initial gzip | Gzip | Brotli | +| ---------------------------------- | ---: | -----------: | ---: | -----: | +| `react-router.minimal` | 0 | 0 | 0 | 0 | +| `react-router.full` | 0 | 0 | 0 | 0 | +| `solid-router.minimal` | 0 | 0 | 0 | 0 | +| `solid-router.full` | -159 | -42 | -40 | -25 | +| `vue-router.minimal` | 0 | 0 | 0 | 0 | +| `vue-router.full` | -93 | -20 | -21 | -31 | +| `react-start.minimal` | 0 | 0 | 0 | 0 | +| `react-start.deferred-hydration` | 0 | 0 | 0 | 0 | +| `react-start.full` | 0 | 0 | 0 | 0 | +| `react-start.rsbuild.minimal` | 0 | 0 | 0 | 0 | +| `react-start.rsbuild.minimal-iife` | 0 | 0 | 0 | 0 | +| `react-start.rsbuild.full` | 0 | 0 | 0 | 0 | +| `solid-start.minimal` | -159 | -30 | -31 | +56 | +| `solid-start.deferred-hydration` | -159 | -31 | -27 | +16 | +| `solid-start.full` | -159 | -28 | -31 | -30 | +| `vue-start.minimal` | -93 | -27 | -27 | +57 | +| `vue-start.full` | -93 | -10 | -10 | +43 | + +Summary: + +- Raw: `-159..0`; 7 improved, 10 neutral, 0 regressed +- Initial gzip: `-42..0`; 7 improved, 10 neutral, 0 regressed +- Gzip: `-40..0`; 7 improved, 10 neutral, 0 regressed +- Brotli: `-31..+57`; 3 improved, 10 neutral, 4 regressed + +The unchanged minimal router bundles confirm that the new private helper does +not leak across tree-shaking boundaries. + +## Runtime and semantics + +- `querySelectorAll` returns a static `NodeList`, so direct iteration visits the + same snapshot in the same order as `Array.from(...).find(...)` while removing + the temporary array and callback. +- `Element.remove()` is idempotent. It has the same result as the previous + guarded `parentNode.removeChild` sequence when the script is attached, + detached, or moved. +- Attribute iteration still uses `Object.entries`, preserves iteration order, + skips `undefined` and `false`, emits an empty attribute for `true`, and + stringifies all other values. +- The direct scan removes an allocation and callback from each lookup; cleanup + removes a branch and property read. The shared copier adds one call only on + the rare DOM script-insertion path, which is dominated by DOM operations and + is the same implementation already used by React Router. + +## Tests + +Focused client tests cover both attribute-copying call sites, both duplicate +scan branches, boolean attribute handling, and attached/detached Solid cleanup. + +- Solid Router client: 55 files, 839 passed, 1 skipped, no type errors +- Solid Router server: 3 files, 3 passed, no type errors +- Vue Router: 54 files, 815 passed, 1 skipped, no type errors +- Solid Router types: TypeScript 5.6, 5.7, 5.8, 5.9, 6.0, and 7.0 passed +- Vue Router types: 17 files, 138 passed, no type errors +- Solid and Vue Router eslint: passed; Vue reported 79 pre-existing warnings + and no errors + +## Rejected nearby variants + +- Sharing Vue preserved-head retention branches saved only 4 gzip bytes in + `vue-router.full`, regressed Brotli by 18 bytes, and mixed update/unmount + cleanup semantics; it was dropped. +- Reusing the default script-type literal across all frameworks regressed + `react-router.full` gzip by 1 byte, so it was not included in this + cross-framework group. +- Reusing a single Vue hydration state across `ScriptOnce`, `Scripts`, and + `Html` saved 25 gzip bytes in `vue-router.full` but regressed Brotli by 31 + bytes and coupled three lifecycle boundaries; it was kept out of this small, + local DOM pass. diff --git a/packages/solid-router/tests/Scripts.test.tsx b/packages/solid-router/tests/Scripts.test.tsx index 29f6553d56..dda7ddaf17 100644 --- a/packages/solid-router/tests/Scripts.test.tsx +++ b/packages/solid-router/tests/Scripts.test.tsx @@ -200,6 +200,77 @@ describe('ssr scripts', () => { ) }) + test('injects client script attributes and removes the script on cleanup', async () => { + const externalScript = { + src: 'solid-client-script.js', + async: true, + defer: false, + crossOrigin: 'anonymous' as const, + } + const inlineScript = { + id: 'solid-client-inline-script', + type: 'module', + children: 'window.__solidClientScript = true', + } + const rootRoute = createRootRoute({ + scripts: () => [ + externalScript, + externalScript, + inlineScript, + inlineScript, + ], + component: () => ( + <> +
+ + + ), + }) + const indexRoute = createRoute({ + path: '/', + getParentRoute: () => rootRoute, + }) + const router = createRouter({ + history: createMemoryHistory({ initialEntries: ['/'] }), + routeTree: rootRoute.addChildren([indexRoute]), + isServer: false, + }) + + await router.load() + const result = render(() => ) + expect( + await screen.findByTestId('solid-client-script-root'), + ).toBeInTheDocument() + + const getScript = () => + document.head.querySelector( + 'script[src="solid-client-script.js"]', + ) + await waitFor(() => expect(getScript()).not.toBeNull()) + expect(getScript()?.hasAttribute('async')).toBe(true) + expect(getScript()?.hasAttribute('defer')).toBe(false) + expect(getScript()?.getAttribute('crossorigin')).toBe('anonymous') + expect( + document.head.querySelectorAll('script[src="solid-client-script.js"]'), + ).toHaveLength(1) + const getInlineScript = () => + document.head.querySelector( + 'script#solid-client-inline-script', + ) + await waitFor(() => expect(getInlineScript()).not.toBeNull()) + expect(getInlineScript()?.textContent).toBe( + 'window.__solidClientScript = true', + ) + expect( + document.head.querySelectorAll('script#solid-client-inline-script'), + ).toHaveLength(1) + + getScript()?.remove() + expect(() => result.unmount()).not.toThrow() + expect(getScript()).toBeNull() + expect(getInlineScript()).toBeNull() + }) + test('keeps manifest stylesheet links mounted across repeated Link navigations', async () => { const history = createTestBrowserHistory() diff --git a/packages/vue-router/tests/Scripts.test.tsx b/packages/vue-router/tests/Scripts.test.tsx index 8d6724076b..0f8e031a09 100644 --- a/packages/vue-router/tests/Scripts.test.tsx +++ b/packages/vue-router/tests/Scripts.test.tsx @@ -51,6 +51,11 @@ afterEach(() => { cleanup() browserHistories.splice(0).forEach((history) => history.destroy()) window.history.replaceState(null, 'root', '/') + document.head + .querySelectorAll( + 'script[src="vue-client-script.js"], script#vue-client-inline-script', + ) + .forEach((script) => script.remove()) delete window.$_TSR }) @@ -149,6 +154,75 @@ describe('ssr scripts', () => { expect(scripts[0]!.getAttribute('src')).toBe('script.js') expect(scripts[1]!.getAttribute('src')).toBe('script3.js') }) + + test('injects client script attributes into the document head', async () => { + const externalScript = { + src: 'vue-client-script.js', + async: true, + defer: false, + crossOrigin: 'anonymous' as const, + } + const inlineScriptOptions = { + id: 'vue-client-inline-script', + type: 'module', + children: 'window.__vueClientScript = true', + } + const rootRoute = createRootRoute({ + scripts: () => [ + externalScript, + externalScript, + inlineScriptOptions, + inlineScriptOptions, + ], + component: () => ( + <> +
+ + + ), + }) + const indexRoute = createRoute({ + path: '/', + getParentRoute: () => rootRoute, + }) + const router = createRouter({ + history: createMemoryHistory({ initialEntries: ['/'] }), + routeTree: rootRoute.addChildren([indexRoute]), + isServer: false, + }) + + await router.load() + render() + expect( + await screen.findByTestId('vue-client-script-root'), + ).toBeInTheDocument() + + const getScript = () => + document.head.querySelector( + 'script[src="vue-client-script.js"]', + ) + await waitFor(() => expect(getScript()).not.toBeNull()) + expect(getScript()?.hasAttribute('async')).toBe(true) + expect(getScript()?.hasAttribute('defer')).toBe(false) + expect(getScript()?.getAttribute('crossorigin')).toBe('anonymous') + expect( + document.head.querySelectorAll('script[src="vue-client-script.js"]'), + ).toHaveLength(1) + const getInlineScript = () => + document.head.querySelector( + 'script#vue-client-inline-script', + ) + await waitFor(() => expect(getInlineScript()).not.toBeNull()) + expect(getInlineScript()?.textContent).toBe( + 'window.__vueClientScript = true', + ) + expect( + document.head.querySelectorAll('script#vue-client-inline-script'), + ).toHaveLength(1) + + getScript()?.remove() + getInlineScript()?.remove() + }) }) describe('ssr HeadContent', () => { From e5d61ae8ec884d509fd41dcd9db5c56431ee86b7 Mon Sep 17 00:00:00 2001 From: "nx-cloud[bot]" <71083854+nx-cloud[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 03:16:33 +0000 Subject: [PATCH 5/7] test: cover compact DOM script handling [Self-Healing CI Rerun] From 6015c737a738cca285f83110dc62f802e051e31b Mon Sep 17 00:00:00 2001 From: Flo Date: Thu, 6 Aug 2026 09:20:44 +0200 Subject: [PATCH 6/7] Delete RESULT-optimization-dom-script-passes.md --- RESULT-optimization-dom-script-passes.md | 111 ----------------------- 1 file changed, 111 deletions(-) delete mode 100644 RESULT-optimization-dom-script-passes.md diff --git a/RESULT-optimization-dom-script-passes.md b/RESULT-optimization-dom-script-passes.md deleted file mode 100644 index 9d11477c18..0000000000 --- a/RESULT-optimization-dom-script-passes.md +++ /dev/null @@ -1,111 +0,0 @@ -# DOM script micro-pass results - -## Scope - -Baseline: `697ebb6ddbd433d052b6b4707938a5c595865d58` (`main`) - -Candidate production commits: - -- `6e12093800` — share the private script-attribute copier in Solid and Vue -- `b16f2ebff1` — scan static `querySelectorAll` results directly instead of allocating arrays and callbacks -- `d674d4cecd` — use idempotent `HTMLScriptElement.remove()` in Solid cleanup - -The changes are private to `Asset.tsx`. Public component props, exported types, -rendered attributes, script matching rules, and script lifecycle behavior are -unchanged. React already uses these three implementation shapes. - -## Isolated attribution - -All values are bytes relative to the exact-main control. Gzip is the primary -metric. - -| Hunk | Scenario | Raw | Initial gzip | Gzip | Brotli | -| ------------------------ | ------------------- | --: | -----------: | ---: | -----: | -| Shared attribute copier | `solid-router.full` | -73 | -22 | -21 | -39 | -| Shared attribute copier | `vue-router.full` | -73 | -15 | -14 | +52 | -| Direct static-node scan | `solid-router.full` | -20 | -11 | -9 | -36 | -| Direct static-node scan | `vue-router.full` | -20 | -6 | -6 | +30 | -| Copier + direct scan | `solid-router.full` | -93 | -34 | -32 | -19 | -| Copier + direct scan | `vue-router.full` | -93 | -20 | -21 | -31 | -| Idempotent Solid cleanup | `solid-router.full` | -66 | -10 | -8 | -42 | - -Every independent production hunk improves gzip. The two shared Solid/Vue -hunks also compose better than either hunk's isolated Brotli result in Vue. - -## Final 17-scenario matrix - -Fresh control: `/tmp/dom-script-fresh-control-full.json` - -Candidate: `/tmp/dom-script-final-rerun.json` - -| Scenario | Raw | Initial gzip | Gzip | Brotli | -| ---------------------------------- | ---: | -----------: | ---: | -----: | -| `react-router.minimal` | 0 | 0 | 0 | 0 | -| `react-router.full` | 0 | 0 | 0 | 0 | -| `solid-router.minimal` | 0 | 0 | 0 | 0 | -| `solid-router.full` | -159 | -42 | -40 | -25 | -| `vue-router.minimal` | 0 | 0 | 0 | 0 | -| `vue-router.full` | -93 | -20 | -21 | -31 | -| `react-start.minimal` | 0 | 0 | 0 | 0 | -| `react-start.deferred-hydration` | 0 | 0 | 0 | 0 | -| `react-start.full` | 0 | 0 | 0 | 0 | -| `react-start.rsbuild.minimal` | 0 | 0 | 0 | 0 | -| `react-start.rsbuild.minimal-iife` | 0 | 0 | 0 | 0 | -| `react-start.rsbuild.full` | 0 | 0 | 0 | 0 | -| `solid-start.minimal` | -159 | -30 | -31 | +56 | -| `solid-start.deferred-hydration` | -159 | -31 | -27 | +16 | -| `solid-start.full` | -159 | -28 | -31 | -30 | -| `vue-start.minimal` | -93 | -27 | -27 | +57 | -| `vue-start.full` | -93 | -10 | -10 | +43 | - -Summary: - -- Raw: `-159..0`; 7 improved, 10 neutral, 0 regressed -- Initial gzip: `-42..0`; 7 improved, 10 neutral, 0 regressed -- Gzip: `-40..0`; 7 improved, 10 neutral, 0 regressed -- Brotli: `-31..+57`; 3 improved, 10 neutral, 4 regressed - -The unchanged minimal router bundles confirm that the new private helper does -not leak across tree-shaking boundaries. - -## Runtime and semantics - -- `querySelectorAll` returns a static `NodeList`, so direct iteration visits the - same snapshot in the same order as `Array.from(...).find(...)` while removing - the temporary array and callback. -- `Element.remove()` is idempotent. It has the same result as the previous - guarded `parentNode.removeChild` sequence when the script is attached, - detached, or moved. -- Attribute iteration still uses `Object.entries`, preserves iteration order, - skips `undefined` and `false`, emits an empty attribute for `true`, and - stringifies all other values. -- The direct scan removes an allocation and callback from each lookup; cleanup - removes a branch and property read. The shared copier adds one call only on - the rare DOM script-insertion path, which is dominated by DOM operations and - is the same implementation already used by React Router. - -## Tests - -Focused client tests cover both attribute-copying call sites, both duplicate -scan branches, boolean attribute handling, and attached/detached Solid cleanup. - -- Solid Router client: 55 files, 839 passed, 1 skipped, no type errors -- Solid Router server: 3 files, 3 passed, no type errors -- Vue Router: 54 files, 815 passed, 1 skipped, no type errors -- Solid Router types: TypeScript 5.6, 5.7, 5.8, 5.9, 6.0, and 7.0 passed -- Vue Router types: 17 files, 138 passed, no type errors -- Solid and Vue Router eslint: passed; Vue reported 79 pre-existing warnings - and no errors - -## Rejected nearby variants - -- Sharing Vue preserved-head retention branches saved only 4 gzip bytes in - `vue-router.full`, regressed Brotli by 18 bytes, and mixed update/unmount - cleanup semantics; it was dropped. -- Reusing the default script-type literal across all frameworks regressed - `react-router.full` gzip by 1 byte, so it was not included in this - cross-framework group. -- Reusing a single Vue hydration state across `ScriptOnce`, `Scripts`, and - `Html` saved 25 gzip bytes in `vue-router.full` but regressed Brotli by 31 - bytes and coupled three lifecycle boundaries; it was kept out of this small, - local DOM pass. From da136c74eb8e0524418fe45ba29a46ace91a5717 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Fri, 7 Aug 2026 14:17:36 +0200 Subject: [PATCH 7/7] changeset --- .changeset/four-cobras-appear.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/four-cobras-appear.md diff --git a/.changeset/four-cobras-appear.md b/.changeset/four-cobras-appear.md new file mode 100644 index 0000000000..3ad5b7e17f --- /dev/null +++ b/.changeset/four-cobras-appear.md @@ -0,0 +1,6 @@ +--- +'@tanstack/solid-router': patch +'@tanstack/vue-router': patch +--- + +compact DOM script handling