Skip to content

feat: refactor SSR#2205

Merged
PupilTong merged 25 commits intolynx-family:mainfrom
PupilTong:p/hw/rust-refactor-ssr
Feb 28, 2026
Merged

feat: refactor SSR#2205
PupilTong merged 25 commits intolynx-family:mainfrom
PupilTong:p/hw/rust-refactor-ssr

Conversation

@PupilTong
Copy link
Copy Markdown
Collaborator

@PupilTong PupilTong commented Feb 9, 2026

Summary by CodeRabbit

  • New Features

    • Full server-side rendering runtime and middleware for generating HTML/CSS on Node; new server APIs to decode and execute packaged templates.
    • Unified style-resource model accessible to both client and server.
  • Tests

    • Extensive SSR, server-compatibility, snapshot, and No‑JS end-to-end tests added.
  • Chores

    • Test/tooling updates (Vitest, Playwright tweaks), build target for server, decoding utility, and runtime polyfills.
  • Documentation

    • Expanded Server Runtime and testing guidance.

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).

@PupilTong PupilTong self-assigned this Feb 9, 2026
@PupilTong PupilTong requested a review from Sherry-hue as a code owner February 9, 2026 12:20
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 9, 2026

🦋 Changeset detected

Latest commit: d8dac79

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@lynx-js/web-core-wasm Patch
@lynx-js/web-elements Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds server-side rendering support and APIs across Rust, WASM bindings, and TypeScript: new MainThreadServerContext and server style manager, StyleSheetResource refactor, template decode/execute (deploy), SSR dev middleware, server TS element APIs, updated d.ts wasm bindings, tests (Vitest & Playwright), and E2E infra.

Changes

Cohort / File(s) Summary
Server TS runtime & deploy
packages/web-platform/web-core-wasm/ts/server/*, packages/web-platform/web-core-wasm/ts/types/DecodedTemplate.ts
New server modules: decode.ts, deploy.ts, createServerLynx.ts, wasm re-export and DecodedTemplate now may include StyleSheetResource.
Server element APIs & shims
packages/web-platform/web-core-wasm/ts/server/elementAPIs/*
Adds SSR element API implementations and PAPI shims: createElementAPI, pureElementAPIs, SSR binding types and large server-side element surface.
Rust server main-thread & style manager
packages/web-platform/web-core-wasm/src/main_thread/server/*
Introduces MainThreadServerContext (HTML generation, attributes, templates) and StyleManagerServer (global CSS buffer, per-unique-id CSS rules).
WASM bindings & d.ts surfaces
packages/web-platform/web-core-wasm/binary/...
Client bindings rename TemplateManagerStyleSheetResource and change push_style_sheet signature; adds comprehensive server wasm d.ts/bg bindings.
Rust feature-gating & API reshuffle
packages/web-platform/web-core-wasm/src/main_thread/*, src/lib.rs, mod.rs
Restructures modules behind client/server cfgs; moves client code under client; removes many per-method wasm_bindgen exposures.
Template & StyleSheetResource changes (Rust & TS)
src/template/..., ts/client/TemplateManager.ts, ts/client/wasm.ts
Removes TemplateManager module; adds StyleSheetResource (client DOM + server string fields), updates TS TemplateManager to store/get StyleSheetResource, removes exported templateManagerWasm.
Element data & serialization
packages/web-platform/web-core-wasm/src/main_thread/element_data.rs
Adds rkyv archival derives, server-only fields (tag_name, attributes, children) and server helper methods for element manipulation.
Style transformer gating
packages/web-platform/web-core-wasm/src/style_transformer/*
Expands cfg to include server for inline style functions, tokenizer/transformer imports and re-exports.
Client runtime adjustments
packages/web-platform/web-core-wasm/ts/client/*
Switches style flow to resource-based StyleSheetResource, adds TemplateManager.getStyleSheet, introduces requestIdleCallback polyfill, updates LynxViewInstance lifecycle and decode.worker blob construction.
Server e2e, tests & vitest
packages/web-platform/web-core-wasm-e2e/*, packages/web-platform/web-core-wasm/tests/*, vitest.config.ts
Adds Vitest server snapshot tests, Playwright SSR No-JS test, e2e package files, rsbuild config wiring, and many server-side unit/e2e tests.
SSR middleware & dev shell
packages/web-platform/web-core-wasm-e2e/shell-project/devMiddleware.ts, .../ssr.html
New ssrMiddleware endpoint that reads bundles, executes templates via executeTemplate, injects SSR output into ssr.html.
Playwright fixtures, coverage & CI shards
packages/web-platform/playwright-fixtures/src/*, .github/workflows/test.yml
Adds testMatch pattern, adjusts coverage guard to skip SSR No-JS coverage, and changes Playwright shard counts in CI.
Utilities & small additions
packages/web-platform/web-core-wasm/ts/common/decodeUtils.ts, .../requestIdleCallback.ts
Adds decodeBinaryMap util, idle-callback polyfill, and other small helpers.
Package exports & build
packages/web-platform/web-core-wasm/package.json, .../scripts/build.js, tsconfig.json
Adds ./server export, updates client asset export paths, adds server build invocation, and enables "composite": true in e2e tsconfig.
Misc & config
.changeset/lemon-pigs-teach.md, .vscode/settings.json, AGENTS.md, web-elements/src/compat/*
Adds changeset, toggles typescript.experimental.useTsgo to false, updates AGENTS doc, removes a CSS import.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Sherry-hue

Poem

🐇 I hop through bundles, bytes, and thread,
I plant the SSR seeds in headless bed,
StyleSheetResource snug as a quilt,
Templates bloom where code was built,
A rabbit cheers — the server's fed!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: refactor SSR' accurately describes the main change—a comprehensive refactor of server-side rendering with new WASM bindings, TypeScript APIs, and infrastructure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/web-platform/web-core-wasm/AGENTS.md (1)

16-16: ⚠️ Potential issue | 🟡 Minor

Architecture overview on Line 16 doesn't mention the new Server Runtime layer.

Line 16 still lists only three layers: "Rust Core (src), the Client Runtime (ts/client), and the Encoder (ts/encode)." Since section 3 now introduces Server Runtime (ts/server), this line should be updated to include it.

📝 Suggested fix
-The codebase is structured into three main layers: the **Rust Core** (`src`), the **Client Runtime** (`ts/client`), and the **Encoder** (`ts/encode`).
+The codebase is structured into four main layers: the **Rust Core** (`src`), the **Client Runtime** (`ts/client`), the **Server Runtime** (`ts/server`), and the **Encoder** (`ts/encode`).
🤖 Fix all issues with AI agents
In `@packages/web-platform/web-core-wasm-e2e/shell-project/devMiddleware.ts`:
- Around line 48-58: The code in devMiddleware.ts builds viewAttributes by
interpolating url.searchParams values into attribute strings (see
url.searchParams.forEach, the attributes Map, and the viewAttributes
concatenation), which allows injection via unescaped quotes; fix by
HTML-escaping attribute values before concatenation — at minimum replace or
encode double-quotes (") to &quot; and also escape &, <, > (and optionally
single-quote) when reading values from attributes (or during the for...of
attributes loop) so the generated key="value" pairs cannot break out of the
attribute context.
- Around line 83-86: The catch block that handles SSR errors currently logs the
error and sets res.statusCode = 500 but never finalizes the response, leaving
the request hanging; update the catch (err: any) handler to call res.end() (or
res.end('Internal Server Error') for a body) after setting the status code and
logging, ensuring the response is completed; modify the same catch block where
console.error('SSR Error:', err) and res.statusCode = 500 are used so that
res.end() is invoked in all error paths.
- Around line 25-30: Validate the caseName extracted from the request query
before using it to construct bundlePath: in devMiddleware.ts (where caseName and
hasdir are used to build bundlePath) add an early guard that checks if caseName
is null/empty/invalid and either respond with a 400 error (or set a safe
default) instead of letting path.join receive null; ensure the guard runs before
computing bundlePath and references the caseName and hasdir variables so you do
not pass null into path.join.

In `@packages/web-platform/web-core-wasm/src/lib.rs`:
- Around line 35-36: Add the missing benchmark helper module by declaring the
bench_support module in lib.rs using the exact cfg attributes: #[cfg(all(feature
= "client", feature = "encode", target_arch = "wasm32"))] and #[path =
"../benches/support/bench_support.rs"]] mod bench_support; (refer to symbol
bench_support) and create the corresponding file
benches/support/bench_support.rs containing the shared benchmark utilities /
support structure used by tests/benches (name the file’s module contents to
match bench_support so imports resolve).

In
`@packages/web-platform/web-core-wasm/src/main_thread/client/main_thread_context.rs`:
- Around line 63-69: Add a unit/integration test in tests/element-apis.spec.ts
that exercises the JS-facing API which triggers the Rust
MainThreadContext::push_style_sheet path: construct a StyleSheetResource-shaped
object (matching the Rust/TS type used by LynxViewInstance/createElementAPI),
call the exposed pushStyleSheet (or equivalent) method on the element
API/LynxViewInstance, and assert expected JS-side effects (e.g., the style
manager received/registered the stylesheet, a style entry exists under the
provided entry_name, or the DOM/style registry was updated). Name the test to
indicate it covers push_style_sheet and ensure it passes the StyleSheetResource
and optional entry_name to replicate the production call sites
(LynxViewInstance.ts / createElementAPI.ts).

In `@packages/web-platform/web-core-wasm/src/main_thread/client/style_manager.rs`:
- Around line 103-105: Add JS-side tests in tests/element-apis.spec.ts that
exercise the exported wasm function MainThreadWasmContext.push_style_sheet:
instantiate or obtain MainThreadWasmContext from the wasm module, construct or
mock a StyleSheetResource matching the Rust type shape, call push_style_sheet
from JS, and assert the expected DOM/host-side effect (e.g., a new <style>
element is created, the sheet count or stylesheet text contains the provided
rules, or the resource is referenced). Ensure tests cover success and at least
one error/edge case, and import the wasm module so the wasm_bindgen export for
push_style_sheet is exercised.

In `@packages/web-platform/web-core-wasm/src/main_thread/element_data.rs`:
- Around line 161-171: The set_style function currently panics on empty values
which can crash the SSR process; update set_style (in element_data::set_style)
to avoid panicking by returning early when value.is_empty() (no-op) or change
the signature to return a Result and return an Err for unsupported remove-style
attempts; ensure the attributes map manipulation
(self.attributes.entry("style"...).or_default() and subsequent push_str/push
calls) only runs when value is non-empty and, if you choose Result, propagate a
clear error variant instead of calling panic!.

In
`@packages/web-platform/web-core-wasm/src/main_thread/server/main_thread_server_context.rs`:
- Around line 213-241: The test fails to compile because
MainThreadServerContext::new requires two args (templates: js_sys::Object,
view_attributes: String) but the test only passes templates; update the test to
call MainThreadServerContext::new with a suitable second argument (e.g., an
empty or sample String for view_attributes) and gate the whole test with
#[cfg(target_arch = "wasm32")] so it only compiles on the wasm32 target (wrap or
annotate the test function test_html_generation), ensuring any use of
js_sys::Object stays behind the wasm-only cfg.
- Around line 155-165: The attribute values in render_element are not escaped
(see element.attributes loop pushing value into buffer), causing XSS; implement
an HTML attribute-escaping helper (e.g., escape_attribute_value or
escape_html_attribute) that replaces &, <, > and " (and optionally ') with their
corresponding HTML entities and use that helper when appending the attribute
value to buffer instead of pushing raw value; update the loop in render_element
to call the new escape function for each value before buffer.push_str so all SSR
attribute output is properly escaped.

In `@packages/web-platform/web-core-wasm/tests/decode.spec.ts`:
- Around line 184-195: The test fails because decodeTemplate (see decode.ts
handling of TemplateSectionLabel.CustomSections) decodes custom section bytes as
UTF-16LE and JSON.parses them, so the test should supply a UTF-16LE-encoded JSON
string and assert on the parsed object rather than raw Uint8Array; update the
test to encode a JSON value (e.g., an object or array) as a UTF-16LE byte
sequence for the custom section and expect result.customSections toEqual the
parsed JS value returned by decodeTemplate (referencing the test's use of
TemplateSectionLabel.CustomSections and decodeTemplate).

In `@packages/web-platform/web-core-wasm/tests/server-ssr.spec.ts`:
- Around line 13-15: The test initializes SSRBinding and config with missing
required fields causing strict TS errors; update the SSRBinding initialization
to include the required ssrResult property (e.g., set ssrResult: '' or
appropriate fixture) and supply the required config properties expected by
createElementAPI—add defaultOverflowVisible and defaultDisplayLinear with
suitable boolean/string values—so the variables match the SSRBinding type and
the createElementAPI signature (look for SSRBinding and createElementAPI in the
file to update their initializers).

In `@packages/web-platform/web-core-wasm/ts/client/mainthread/TemplateManager.ts`:
- Around line 203-213: The code creates a StyleSheetResource unconditionally in
the TemplateSectionLabel.StyleInfo case but then may find no template via
this.#templates.get(url), leaking the resource; fix by guarding so you only
construct the new StyleSheetResource if a template exists (check template =
this.#templates.get(url) first) or, if you must construct it earlier, ensure you
call the resource's cleanup/dispose/free method when template is undefined
before calling instance.onStyleInfoReady(url); update references to
StyleSheetResource, this.#templates.get(url), template.styleSheet and
instance.onStyleInfoReady accordingly.

In `@packages/web-platform/web-core-wasm/ts/server/decode.ts`:
- Around line 25-27: The check in decode.ts compares `magic` to
`BigInt(MagicHeader)`, but `MagicHeader` is defined as a Number constant and
loses precision for the 64-bit value; update the constant definition in
constants.ts to be a BigInt literal (e.g., change `0x464F525741524453` to
`0x464F525741524453n`) so `MagicHeader` is already a BigInt and the comparison
in `decode.ts` (using `magic !== MagicHeader` or `magic !==
BigInt(MagicHeader)`) will be accurate against Rust-encoded templates; locate
the `MagicHeader` symbol in constants.ts and replace its numeric literal with
the `n` BigInt suffix.

In `@packages/web-platform/web-core-wasm/ts/server/deploy.ts`:
- Around line 59-72: The promise can hang if renderPageFunction is never set;
update the deploy flow to detect failures and reject the render promise: wrap
the VM execution that sets renderPageFunction (the call that populates sandbox
where renderPage is expected) in a try/catch and on catch call the same
rejection path as resolveRender (rejectRender or equivalent), and add a clear
timeout fallback that rejects after a configurable timeout if renderPageFunction
(the setter logic for renderPageFunction/queueMicrotask) was never invoked;
ensure you reference and use the existing symbols renderPageFunction,
queueMicrotask, resolveRender (or its reject counterpart),
sandbox['processData'], and binding.ssrResult so the rejection path mirrors the
success path.
- Around line 83-107: The code currently skips assigning globalThis.renderPage
when result.lepusCode['root'] is undefined, causing renderPromise returned by
executeTemplate to never settle; update executeTemplate to detect missing root
(check result.lepusCode['root'] / rootCodeBuf) and immediately reject (or
resolve with a clear error) the renderPromise instead of skipping
execution—ensure the branch that currently contains vm.runInContext(...) (and
references wrappedCode, context, and renderPage) calls the promise rejector with
a descriptive Error so callers do not hang.

In
`@packages/web-platform/web-core-wasm/ts/server/elementAPIs/createElementAPI.ts`:
- Around line 209-221: The split on clsVal can produce [''] when classNames is
null, so update the call sites around wasmContext.update_css_og_style to pass a
cleaned array: after computing clsVal (from classNames || ''), split on /\s+/
then filter out empty strings (or use trim() and conditional) so you never pass
[''] to WASM; modify the occurrences that call wasmContext.update_css_og_style
(use uniqueIdSymbol, cssIdAttribute, lynxEntryNameAttribute,
setAttributeInternal/getAttributeInternal context) and ensure the same filtering
is applied at the similar call around line 239.
🧹 Nitpick comments (24)
packages/web-platform/web-core-wasm/ts/client/mainthread/utils/requestIdleCallback.ts (1)

1-5: Polyfill type signature doesn't match requestIdleCallback.

The fallback's signature (callback: () => void) => ... drops both the IdleDeadline parameter and the optional options argument that native requestIdleCallback accepts. This is fine for the single current call site (which uses neither), but if another caller passes { timeout: ... } or reads deadline.timeRemaining(), it will silently misbehave.

Consider aligning the fallback signature so it's a drop-in replacement:

Suggested improvement
 // Safari doesn't support requestIdleCallback
 export const requestIdleCallbackImpl =
   typeof requestIdleCallback === 'undefined'
-    ? (callback: () => void) => setTimeout(callback, 16)
+    ? (callback: IdleRequestCallback, _options?: IdleRequestOptions): number =>
+        setTimeout(() => callback(Object.freeze({ didTimeout: false, timeRemaining: () => 0 }) as IdleDeadline), 16) as unknown as number
     : requestIdleCallback;

Also note that Safari has supported requestIdleCallback since version 16.4 (March 2023), so the polyfill is only needed for fairly old Safari versions. The comment is still helpful though.

packages/web-platform/web-core-wasm/ts/client/mainthread/TemplateManager.ts (2)

265-268: #removeTemplate uses createTemplate for side-effect cleanup.

Calling createTemplate (which also re-inserts a new empty template) just to leverage its cleanup logic, then immediately deleting, is a bit roundabout. Consider extracting the cleanup logic into a dedicated private method (e.g., #clearTemplateResources) that both createTemplate and #removeTemplate can call.


304-307: getStyleSheet return type is any.

The StyleSheetResource type is available from the wasm typings. Using any weakens type safety for all downstream consumers. Consider importing and using the concrete type (or at minimum StyleSheetResource | undefined).

packages/web-platform/web-core-wasm/ts/server/createServerLynx.ts (2)

10-13: Parameters are required but defensively accessed as if optional.

globalProps and customSections are both required in the signature, yet line 31 uses globalProps ?? {} and line 33 uses customSections?.[key]. Either mark the parameters as optional (e.g., globalProps?: Cloneable) or drop the defensive fallbacks for consistency.

Proposed fix (make parameters optional)
 export function createServerLynx(
-  globalProps: Cloneable,
-  customSections: Record<string, Cloneable>,
+  globalProps?: Cloneable,
+  customSections?: Record<string, Cloneable>,
 ): MainThreadLynx {

19-27: Consider trimming the development-note comments before merging.

Lines 20-24 read like internal reasoning rather than documentation. A single concise comment (e.g., // Use setTimeout as a no-op stand-in for rAF in SSR) would suffice.

packages/web-platform/web-core-wasm/src/template/template_sections/style_info/style_sheet_resource.rs (2)

49-58: Shadowed variable name style_content_element inside the font-face block is misleading.

Line 51 declares let style_content_element = document.create_element("style")... inside the block that creates the font face element. This shadows the outer style_content_element from line 40 and reads as though it's building style content rather than font-face content.

📝 Suggested rename
-      let style_content_element = document.create_element("style").map_err(|e| {
+      let font_face_style_element = document.create_element("style").map_err(|e| {
           wasm_bindgen::JsError::new(&format!("Failed to create style element: {e:?}"))
         })?;
-        style_content_element.set_text_content(Some(font_face_content));
-        Some(style_content_element)
+        font_face_style_element.set_text_content(Some(font_face_content));
+        Some(font_face_style_element)

62-66: Remove unnecessary .clone() calls on the server feature path — move values instead.

On the server feature path, decoded_style_data.style_content and decoded_style_data.font_face_content are owned values that are only used once. After line 65, the only subsequent use of decoded_style_data accesses a different field (css_og_css_id_to_class_selector_name_to_declarations_map), so a partial move is safe and avoids unnecessary allocations.

♻️ Suggested change
     #[cfg(feature = "server")]
     let (style_content_str, font_face_content_str) = (
-      decoded_style_data.style_content.clone(),
-      decoded_style_data.font_face_content.clone(),
+      decoded_style_data.style_content,
+      decoded_style_data.font_face_content,
     );
packages/web-platform/web-core-wasm-e2e/server-tests/server-e2e.test.ts (1)

7-23: Consider using test.each to reduce boilerplate.

All 17 tests follow the identical pattern of calling runSnapshotTest with a bundle name. This can be simplified with Vitest's test.each:

♻️ Proposed refactor using test.each
+const bundles = [
+  'basic-pink-rect.web.bundle',
+  'config-css-default-display-linear-false.web.bundle',
+  'config-css-remove-scope-false-display-linear.web.bundle',
+  'config-css-selector-false-remove-css-and-style-collapsed.web.bundle',
+  'basic-element-text-baseline.web.bundle',
+  'basic-scroll-view.web.bundle',
+  'basic-element-x-audio-tt-play.web.bundle',
+  'basic-element-image-src.web.bundle',
+  'basic-element-x-input-value.web.bundle',
+  'basic-element-list-basic.web.bundle',
+  'basic-element-x-overlay-ng-demo.web.bundle',
+  'basic-element-x-refresh-view-demo.web.bundle',
+  'basic-element-svg-with-css.web.bundle',
+  'basic-element-x-swiper-autoplay.web.bundle',
+  'basic-element-text-color.web.bundle',
+  'basic-element-x-textarea-placeholder.web.bundle',
+  'basic-element-x-viewpager-ng-bindchange.web.bundle',
+];
+
+test.each(bundles)(
+  'executeTemplate should run lepusCode.root from %s',
+  async (bundleName) => {
+    await runSnapshotTest(bundleName);
+  },
+);
packages/web-platform/web-core-wasm/tests/server-compat.spec.ts (2)

13-15: Unused variable wasmCtx in all three tests.

wasmCtx is declared but never referenced in any of the three tests (lines 15, 34, 58). The MainThreadServerContext import on line 9 also becomes unused.

🧹 Proposed fix

Remove the unused declarations from each test and the unused import:

-import { MainThreadServerContext } from '../ts/server/wasm.js';

And in each test body:

-    const wasmCtx = binding.wasmContext as MainThreadServerContext;

66-78: Remove investigation/exploration comments before merging.

Lines 66–78 read like working notes from exploring the API (e.g., "Wait, createElementAPI maps…", "Let's check createElementAPI.ts implementation details."). These should be cleaned up or replaced with a concise comment explaining the intent.

packages/web-platform/web-core-wasm/ts/client/decodeWorker/decode.worker.ts (2)

280-286: Same unnecessary cast on Manifest code.

Same as above — Uint8Array is a valid BlobPart.

-            code as unknown as BlobPart,
+            code,

241-252: Remove unnecessary double cast on code.

Uint8Array already satisfies the BlobPart type (it's a BufferSource), so as unknown as BlobPart can be removed. The Blob constructor will accept it directly.

🧹 Suggested simplification
-            code as unknown as BlobPart,
+            code,
packages/web-platform/web-core-wasm/ts/server/deploy.ts (1)

62-62: Remove leftover refactoring comment.

// Removed: capturedRenderPage = true; is a development artifact.

packages/web-platform/web-core-wasm/tests/server-ssr.spec.ts (2)

36-36: Remove console.log debug statements from tests.

Lines 36 and 62 contain debug logging that should be removed before merging.

-    console.log('Generated HTML:', html);
-    console.log('Image HTML:', html);

49-51: Same type issues in second and third tests.

Lines 49 and 71 use binding: any which bypasses type checking entirely. Consider using the properly typed SSRBinding with the correct initialization as suggested for the first test.

packages/web-platform/web-core-wasm/tests/decode.spec.ts (1)

59-64: Unnecessary @ts-ignore.

callbacks.magic is typed bigint | undefined, and BigInt(MagicHeader) returns bigint. The ternary expression type-checks cleanly without the suppression.

Proposed fix
   // Magic Header
   const magicBuf = new Uint8Array(8);
-  // `@ts-ignore`
   const magicVal = callbacks.magic !== undefined
     ? callbacks.magic
     : BigInt(MagicHeader);
packages/web-platform/web-core-wasm-e2e/shell-project/devMiddleware.ts (1)

33-33: Synchronous file reads inside an async handler block the event loop.

fs.readFileSync is used twice in an async function. Since this is dev-only middleware the impact is limited, but switching to fs.promises.readFile would be more consistent with the async pattern and avoid blocking the server during concurrent requests.

Also applies to: 70-73

packages/web-platform/web-core-wasm/ts/server/decode.ts (1)

76-83: Variable buffer shadows the function parameter.

The const buffer on line 77 shadows the outer buffer parameter (line 13), reducing readability. Consider renaming to styleInfoBuffer or decodedStyleInfo.

Also, the inline comment on line 79 reads like a thinking-out-loud note rather than a code comment — consider cleaning it up before merging.

Proposed fix
       case TemplateSectionLabel.StyleInfo: {
-        const buffer = decode_style_info(
+        const decodedStyleInfo = decode_style_info(
           content,
-          config['isLazy'] === 'true' ? '' : undefined, // URL is not available in synchronous decode usually, or passed as arg? The user req says "uint8array as params decode directly". Assuming URL is empty or unneeded for sync server decode unless specified.
+          config['isLazy'] === 'true' ? '' : undefined,
           config['enableCSSSelector'] === 'true',
         );
-        styleInfo = buffer;
+        styleInfo = decodedStyleInfo;
         break;
       }
packages/web-platform/web-core-wasm/src/main_thread/server/style_manager_server.rs (1)

17-24: Consider deriving Default for StyleManagerServer.

Since new() is just default initialization, you could #[derive(Default)] on the struct (or implement Default) and let callers use StyleManagerServer::default(). This is idiomatic Rust and also silences the clippy::new_without_default lint.

packages/web-platform/web-core-wasm/ts/server/elementAPIs/createElementAPI.ts (2)

97-104: SSRBinding type is very loosely defined.

SSRBinding only has a ssrResult: string property, but on Line 137 the code attaches wasmContext via an as any cast. Consider extending the type to include an optional wasmContext property so downstream consumers can access it type-safely and avoid the any escape hatch.

♻️ Suggested type improvement
 export type SSRBinding = {
   ssrResult: string;
+  wasmContext?: MainThreadServerContext;
 };

Then on line 137:

-  (mtsBinding as any).wasmContext = wasmContext;
+  mtsBinding.wasmContext = wasmContext;

458-462: __FlushElementTree silently does nothing when pageElementId is undefined.

If __CreatePage was never called before __FlushElementTree, the ssrResult remains unset. This could be confusing to consumers. Consider logging a warning or throwing if pageElementId is undefined at flush time, since it almost certainly indicates a programming error.

♻️ Add a guard for missing page element
     __FlushElementTree: (() => {
       if (pageElementId !== undefined) {
         mtsBinding.ssrResult = wasmContext.generate_html(pageElementId);
+      } else {
+        console.warn('[SSR] __FlushElementTree called before __CreatePage');
       }
     }),
packages/web-platform/web-core-wasm/src/main_thread/client/main_thread_context.rs (1)

146-151: Remove or track the commented-out gc method.

This has been commented out with the old logic referencing get_dom(). If GC is planned for a future iteration, a TODO/issue reference would be better than leaving dead code.

packages/web-platform/web-core-wasm/src/main_thread/server/main_thread_server_context.rs (1)

73-78: create_element uses a Vec with index-based lookup — no bounds validation in append_child, set_attribute, etc.

The element ID is the index in self.elements. The helper methods (Lines 80-109) use self.elements.get_mut(element_id) which safely returns None on out-of-bounds access. However, the silent failure (no error, no log) when an invalid ID is passed could mask bugs during SSR rendering. Consider at least a debug assertion.

packages/web-platform/web-core-wasm/src/main_thread/element_data.rs (1)

177-238: Large block of commented-out code should be removed.

Lines 177-238 contain multiple commented-out methods (clone_node, should_enable_exposure_event, add_event_listener_with_js_function, remove_js_function_event_listener). These appear to be leftover from the client-only implementation. Since this is a WIP PR, consider cleaning these up before merge or adding a tracking TODO.

Comment thread packages/web-platform/web-core-wasm/src/lib.rs
Comment thread packages/web-platform/web-core-wasm/ts/server/decode.ts Outdated
Comment thread packages/web-platform/web-core-wasm/ts/server/deploy.ts Outdated
Comment thread packages/web-platform/web-core-wasm/ts/server/deploy.ts Outdated
Comment thread packages/web-platform/web-core-wasm/ts/server/elementAPIs/createElementAPI.ts Outdated
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Feb 9, 2026

Merging this PR will degrade performance by 14.47%

⚡ 1 improved benchmark
❌ 1 regressed benchmark
✅ 61 untouched benchmarks
🆕 9 new benchmarks
⏩ 3 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
🆕 basic-performance-image-100 N/A 11.1 ms N/A
🆕 basic-performance-small-css N/A 5.9 ms N/A
🆕 basic-performance-large-css N/A 16.1 ms N/A
🆕 basic-performance-nest-level-100 N/A 5.2 ms N/A
🆕 basic-performance-scroll-view-100 N/A 8.7 ms N/A
🆕 basic-performance-text-200 N/A 11.6 ms N/A
basic-performance-small-css 8.2 ms 7.7 ms +6.19%
🆕 basic-performance-div-100 N/A 5.6 ms N/A
transform 1000 view elements 40.5 ms 47.3 ms -14.47%
🆕 basic-performance-div-10000 N/A 258.2 ms N/A
🆕 basic-performance-div-1000 N/A 28.6 ms N/A

Comparing PupilTong:p/hw/rust-refactor-ssr (d8dac79) with main (09929fb)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Feb 9, 2026

Web Explorer

#7839 Bundle Size — 383.64KiB (-0.03%).

d8dac79(current) vs 09929fb main#7815(baseline)

Bundle metrics  Change 3 changes Improvement 1 improvement
                 Current
#7839
     Baseline
#7815
Improvement  Initial JS 154.78KiB(-0.07%) 154.88KiB
No change  Initial CSS 35.06KiB 35.06KiB
Change  Cache Invalidation 40.36% 0%
No change  Chunks 8 8
No change  Assets 8 8
Change  Modules 239(+1.27%) 236
No change  Duplicate Modules 16 16
No change  Duplicate Code 2.99% 2.99%
No change  Packages 4 4
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Improvement 1 improvement
                 Current
#7839
     Baseline
#7815
Improvement  JS 252.72KiB (-0.04%) 252.83KiB
No change  Other 95.85KiB 95.85KiB
No change  CSS 35.06KiB 35.06KiB

Bundle analysis reportBranch PupilTong:p/hw/rust-refactor-ssrProject dashboard


Generated by RelativeCIDocumentationReport issue

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/test.yml (1)

138-155: ⚠️ Potential issue | 🟠 Major

web-core-wasm-e2e: SSR missing from render matrix, and job excluded from done gate.

Two confirmed issues:

  1. SSR not in render matrix (Line 141): The matrix only includes [CSR], but this PR adds SSR E2E tests (ssr-no-js.spec.ts). The conditional if [ "${{ matrix.render }}" = "SSR" ] exists in the script but is unreachable, meaning SSR tests won't run in CI.

  2. Not listed in done job (Lines 287–300): The web-core-wasm-e2e job is not included in the done job's needs list, so its failure won't block the PR status check.

🤖 Fix all issues with AI agents
In `@packages/web-platform/playwright-fixtures/src/playwright.common.ts`:
- Line 45: The testMatch glob currently set in testMatch should be tightened to
only pick up actual test files and nested tests: change the pattern from
'**/tests/*' to a glob that filters by test filename conventions and extensions
(e.g., include .ts/.tsx/.js/.jsx and typical test/spec suffixes) and allow
recursive matching under tests/ so files like common.css or jsdom.ts are
excluded; update the testMatch value used in the Playwright fixture
configuration to the new glob.

Comment thread packages/web-platform/playwright-fixtures/src/playwright.common.ts
@PupilTong PupilTong force-pushed the p/hw/rust-refactor-ssr branch from 1882359 to fabeb16 Compare February 10, 2026 06:15
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@packages/web-platform/web-core-wasm-e2e/server-tests/server-e2e.test.ts`:
- Around line 7-22: The test uses __dirname inside runSnapshotTest but the
project is ESM, so define __dirname from import.meta.url before it's used:
import fileURLToPath and derive __dirname via
path.dirname(fileURLToPath(import.meta.url)), placing that definition near the
top of the file after imports so runSnapshotTest and the bundlePath computation
can use it without runtime errors.

In
`@packages/web-platform/web-core-wasm/src/main_thread/server/main_thread_server_context.rs`:
- Around line 111-124: The generate_html function injects self.view_attributes
raw into the <lynx-view ...> tag (in generate_html) which allows
tag-breaking/XSS; fix by validating or escaping view_attributes before
rendering: either parse/normalize view_attributes into a structured map at
construction and render each key/value with proper HTML attribute escaping, or
at minimum sanitize/validate the string in generate_html to disallow or
percent-escape unsafe chars (e.g. '"', '>', '<', and '`') and reject/trim
invalid input; update the code paths that set/construct view_attributes so the
invariant (safe escaped attributes) is maintained and keep
render_element/style_manager usage unchanged.
- Around line 73-78: The server-side create_element
(main_thread_server_context.rs::create_element) always constructs
LynxElementData with css_id 0; replicate the client-side inheritance from
create_element_common (client/main_thread_context.rs::create_element_common
lines ~88-96) so the server method inherits the parent's css_id when a parent is
present (lookup parent in self.elements and use its css_id unless an explicit
css_id is provided), update the call to LynxElementData::new_with_tag_name
accordingly, and add a test in tests/element-apis.spec.ts that mounts a parent
with a nonzero css_id then creates a child via the server path and asserts the
child receives the inherited css_id (or adjust if SSR intentionally differs).

In `@packages/web-platform/web-core-wasm/tests/server-ssr.spec.ts`:
- Line 30: The test calls api.__FlushElementTree with two arguments but the
function is defined with zero parameters (see createElementAPI.ts), causing a
TypeScript error; update the call in server-ssr.spec.ts to invoke
api.__FlushElementTree with no arguments (remove page and {}), ensuring the call
matches the zero-argument signature of __FlushElementTree.
- Line 9: The test incorrectly imports the TypeScript type SSRBinding from the
WASM re-export; update the imports so MainThreadServerContext continues to be
imported from wasm.js but SSRBinding is imported from the module that actually
declares it (the createElementAPI TypeScript module that exports SSRBinding).
Remove SSRBinding from the wasm import list, add a separate import for
SSRBinding from the createElementAPI module (use the module name where
SSRBinding is exported), and run a quick search for "export.*SSRBinding" to
confirm the correct export surface before committing.

In `@packages/web-platform/web-core-wasm/ts/client/decodeWorker/decode.worker.ts`:
- Around line 241-252: The JSON handling path should include the same '//#
allFunctionsCalledOnLoad\n' hint as the binary blob path so
behavior/optimization is consistent; update the handleJSON branch in
decode.worker.ts to prepend that exact string before the wrapped code (matching
how the binary path builds the Blob using isLazy and code) or, if the hint is
only meaningful for binary bundles, add a brief inline comment in handleJSON
explaining why it is intentionally omitted to avoid confusion for future
maintainers.

In
`@packages/web-platform/web-core-wasm/ts/server/elementAPIs/createElementAPI.ts`:
- Around line 116-136: The new MainThreadServerContext and StyleSheetResource
(wasmContext and resource) are allocated but never disposed; after calling
__FlushElementTree() (or before returning element APIs) explicitly call their
disposal APIs (resource.free() or resource[Symbol.dispose]() and
wasmContext.free() or wasmContext[Symbol.dispose]()) or instead return a cleanup
function/closer that callers can invoke to free wasmContext and resource when
done; update createElementAPI to ensure disposal runs in all code paths
(including error paths) referencing MainThreadServerContext, StyleSheetResource,
wasmContext, resource, and __FlushElementTree.
- Around line 395-408: The server-side __GetTag implementation returns the raw
HTML tag (e.g., 'div') instead of the Lynx tag ('page'); update the server
__GetTag to reverse-map the stored tagName through HTML_TAG_TO_LYNX_TAG_MAP (the
same logic used client-side) and return the Lynx tag when a mapping exists,
otherwise fall back to the original tagName; locate the __GetTag function in the
same module that uses __CreatePage and apply the reverse lookup using
HTML_TAG_TO_LYNX_TAG_MAP so page elements return 'page' on the server as they do
on the client.
🧹 Nitpick comments (19)
packages/web-platform/web-core-wasm/src/main_thread/element_data.rs (2)

177-238: Remove commented-out dead code.

This is a ~60-line block of commented-out methods (clone_node, should_enable_exposure_event, add_event_listener_with_js_function, remove_js_function_event_listener). Since this is tracked in version control, leaving large dead code blocks hurts readability. Remove them now and recover from git history if needed later.


166-171: set_style allocates a new "style" String on every call.

"style".to_string() on line 166 allocates a new heap String each time set_style is invoked. Consider using a constant or Cow to avoid repeated allocations, especially if styles are set frequently during SSR rendering.

♻️ Minor optimization
+const STYLE_ATTR: &str = "style";
+
   pub(crate) fn set_style(&mut self, key: String, value: String) {
     if value.is_empty() {
       // In SSR we do not support remove style
       panic!("Remove style is not supported in SSR");
     }
-    let style = self.attributes.entry("style".to_string()).or_default();
+    let style = self.attributes.entry(STYLE_ATTR.to_string()).or_default();

Alternatively, if FnvHashMap were keyed by &'static str or Cow<'static, str>, the allocation could be avoided entirely, but that would be a larger refactor.

packages/web-platform/web-core-wasm/ts/common/decodeUtils.ts (1)

47-47: subarray returns a view, not a copy — callers must not detach the underlying buffer.

buffer.subarray(offset, offset + valLen) shares the same ArrayBuffer. If any caller transfers or detaches buffer.buffer (e.g., via postMessage with a transfer list), all returned Uint8Array values become unusable. Current usage in the worker (Blob construction) appears safe, but this is a subtle contract worth documenting or defensively switching to slice if the buffer lifecycle isn't guaranteed.

packages/web-platform/web-core-wasm/ts/client/mainthread/LynxViewInstance.ts (2)

28-28: Module-level side effect: loadAllWebElements() fires at import time.

This means web element preloading starts even if no LynxViewInstance is ever constructed. If that's the intended warm-up behaviour, a brief comment would help future readers understand the intent.


172-193: webElementsLoadingPromises is populated in loadUnknownElement but never consumed.

onMTSScriptsExecuted immediately clears the array (line 174) without awaiting any promises. Meanwhile loadUnknownElement (line 217) still pushes to it. If the new preload path (loadAllWebElementsPromise) fully supersedes this mechanism, consider removing the field and loadUnknownElement's push to avoid dead-code confusion. Otherwise, if some elements can only be discovered at render time, the promises need to be awaited somewhere.

packages/web-platform/web-core-wasm/src/main_thread/server/style_manager_server.rs (3)

26-44: Unnecessary clone() and avoidable allocation for entry_name.

On line 31, entry_name.clone().unwrap_or_else(…) clones the Option<String> for no reason — entry_name is already an owned Option<String> and is never used after this line. Additionally, push_style_sheet takes resource by reference only to .clone() it on line 42. If callers always yield ownership, accepting resource: StyleSheetResource avoids the extra clone.

Proposed fix
   pub fn push_style_sheet(
     &mut self,
-    resource: &StyleSheetResource,
+    resource: StyleSheetResource,
     entry_name: Option<String>,
   ) -> Result<(), String> {
-    let entry_key = entry_name.clone().unwrap_or_else(|| "__Card__".to_string());
+    let entry_key = entry_name.unwrap_or_else(|| "__Card__".to_string());
 
     if let Some(content) = &resource.style_content_str {
       self.global_style_buffer.push_str(content);
@@ ..
     self
       .css_query_map_by_entry_name
-      .insert(entry_key, resource.clone());
+      .insert(entry_key, resource);
     Ok(())
   }

31-31: Duplicated magic string "__Card__".

The default entry name "__Card__" appears on both line 31 and line 53. Extract it into a const to keep the two call sites consistent and to make renaming painless.

Proposed fix
+const DEFAULT_ENTRY_NAME: &str = "__Card__";
+
 impl StyleManagerServer {
   ...
-    let entry_key = entry_name.clone().unwrap_or_else(|| "__Card__".to_string());
+    let entry_key = entry_name.unwrap_or_else(|| DEFAULT_ENTRY_NAME.to_string());
   ...
-    let entry_name = entry_name.as_deref().unwrap_or("__Card__");
+    let entry_name = entry_name.as_deref().unwrap_or(DEFAULT_ENTRY_NAME);

Also applies to: 53-53


74-80: get_css_string clones the entire global_style_buffer.

For large stylesheets in SSR, this allocates a full copy. If get_css_string is the terminal call (i.e., the server context is consumed afterwards), consider a variant that takes self by value (fn into_css_string(self) -> String) to avoid the clone. Not critical, but worth noting for performance-sensitive SSR paths.

packages/web-platform/web-core-wasm-e2e/server-tests/server-e2e.test.ts (1)

25-95: Consider parameterized tests to reduce boilerplate.

All 17 test cases follow the identical pattern of calling runSnapshotTest with a bundle name. Vitest's test.each (or it.each) would collapse this to a single declaration with a table of bundle names, making additions trivial and the file much shorter.

Example
-test('executeTemplate should run lepusCode.root from basic-pink-rect.web.bundle', async () => {
-  await runSnapshotTest('basic-pink-rect.web.bundle');
-});
-// ... 16 more copies
+const bundles = [
+  'basic-pink-rect.web.bundle',
+  'config-css-default-display-linear-false.web.bundle',
+  'config-css-remove-scope-false-display-linear.web.bundle',
+  'config-css-selector-false-remove-css-and-style-collapsed.web.bundle',
+  'basic-element-text-baseline.web.bundle',
+  'basic-scroll-view.web.bundle',
+  'basic-element-x-audio-tt-play.web.bundle',
+  'basic-element-image-src.web.bundle',
+  'basic-element-x-input-value.web.bundle',
+  'basic-element-list-basic.web.bundle',
+  'basic-element-x-overlay-ng-demo.web.bundle',
+  'basic-element-x-refresh-view-demo.web.bundle',
+  'basic-element-svg-with-css.web.bundle',
+  'basic-element-x-swiper-autoplay.web.bundle',
+  'basic-element-text-color.web.bundle',
+  'basic-element-x-textarea-placeholder.web.bundle',
+  'basic-element-x-viewpager-ng-bindchange.web.bundle',
+];
+
+test.each(bundles)(
+  'executeTemplate should run lepusCode.root from %s',
+  async (bundle) => {
+    await runSnapshotTest(bundle);
+  },
+);
packages/web-platform/web-core-wasm/tests/server-ssr.spec.ts (1)

36-36: Remove console.log debug statements from tests.

Lines 36 and 62 contain console.log calls that pollute test output. In a CI environment, this noise makes it harder to spot real problems.

Also applies to: 62-62

packages/web-platform/web-core-wasm/ts/client/mainthread/TemplateManager.ts (2)

304-307: getStyleSheet returns any — loses type safety.

The StyleSheetResource class has a well-defined shape (with free(), [Symbol.dispose](), etc. per the .d.ts files). Returning any discards all that information for callers. Consider a narrower return type, even if the exact WASM-generated type isn't directly importable at this level.

Proposed fix
-  public getStyleSheet(url: string): any {
+  public getStyleSheet(url: string): DecodedTemplate['styleSheet'] {
     return this.#templates.get(url)?.styleSheet;
   }

(Assumes DecodedTemplate is updated to include a typed styleSheet property — if not, at minimum use unknown instead of any.)


265-268: #removeTemplate creates an empty template only to immediately delete it.

createTemplate(url) on line 266 cleans up the old entry (which is good) but also inserts a fresh empty {} into the map (line 262). The very next line then deletes it. This is correct but wasteful and confusing to readers.

Consider extracting the cleanup logic from createTemplate into a private helper (e.g., #cleanupTemplate) that both methods can call, so #removeTemplate doesn't perform the unnecessary insert.

packages/web-platform/web-core-wasm/tests/decode.spec.ts (1)

59-64: Unnecessary @ts-ignore on line 60.

The ternary on lines 61-63 should type-check without suppression — callbacks.magic is bigint | undefined, and the ternary produces a bigint. Consider removing the @ts-ignore.

Suggested fix
-  // `@ts-ignore`
   const magicVal = callbacks.magic !== undefined
     ? callbacks.magic
     : BigInt(MagicHeader);
packages/web-platform/web-core-wasm/ts/server/decode.ts (2)

76-83: buffer variable shadows the function parameter.

The const buffer on line 77 shadows the outer buffer parameter. While it doesn't cause a bug in this specific case block, it's confusing and error-prone if the code is later refactored.

Also, the comment on line 79 reads like internal development notes — consider cleaning it up or removing it.

Suggested fix
       case TemplateSectionLabel.StyleInfo: {
-        const buffer = decode_style_info(
+        const decodedStyleInfo = decode_style_info(
           content,
-          config['isLazy'] === 'true' ? '' : undefined, // URL is not available in synchronous decode usually, or passed as arg? The user req says "uint8array as params decode directly". Assuming URL is empty or unneeded for sync server decode unless specified.
+          config['isLazy'] === 'true' ? '' : undefined,
           config['enableCSSSelector'] === 'true',
         );
-        styleInfo = buffer;
+        styleInfo = decodedStyleInfo;
         break;
       }

47-49: Inconsistent handling of partial label read vs. partial length read.

When there are fewer than 4 bytes remaining for the label (line 48), the code silently breaks out of the loop. But if there are fewer than 4 bytes for the section length (line 54), it throws. A partial label is likely a truncated stream too — consider throwing in both cases or documenting why the asymmetry is intentional.

packages/web-platform/web-core-wasm/ts/server/elementAPIs/createElementAPI.ts (1)

88-96: Wrapping user code in an IIFE with shadowed globals — document is not shadowed.

The wrapped code shadows navigator, postMessage, and window, but document is not shadowed. If template code references document, it could leak into the host process. If this is server-side SSR code that should never touch the DOM directly, consider also shadowing document.

Suggested addition in deploy.ts
           const navigator = undefined;
           const postMessage = undefined;
           const window = undefined;
+          const document = undefined;
packages/web-platform/web-core-wasm/src/main_thread/client/main_thread_context.rs (1)

146-151: Consider removing commented-out gc method.

This dead code has been commented out. If it's not planned for near-term use, removing it keeps the file cleaner. Version control preserves history if needed later.

packages/web-platform/web-core-wasm/src/main_thread/server/main_thread_server_context.rs (2)

12-12: Remove commented-out import.

-// use crate::constants;

80-84: No validation that child_id refers to a valid element or isn't already parented.

append_child silently does nothing if parent_id is invalid, but doesn't check if child_id actually exists in self.elements. If an invalid child_id is appended, render_element will silently skip it (line 152 checks self.elements.get(element_id)), so there's no crash, but it's a silent data integrity issue.

This is acceptable for a WIP, but worth noting for robustness.

Comment thread packages/web-platform/web-core-wasm/tests/server-ssr.spec.ts Outdated
Comment thread packages/web-platform/web-core-wasm/tests/server-ssr.spec.ts Outdated
Comment thread packages/web-platform/web-core-wasm/ts/server/elementAPIs/createElementAPI.ts Outdated
Comment thread packages/web-platform/web-core-wasm/ts/server/elementAPIs/createElementAPI.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/web-platform/web-core-wasm/ts/client/mainthread/LynxViewInstance.ts (1)

128-136: ⚠️ Potential issue | 🟠 Major

Multiple renderPage assignments will trigger duplicate onMTSScriptsExecuted calls.

Every assignment to renderPage queues a new microtask that invokes onMTSScriptsExecuted, which starts the web worker and calls renderPage. If the setter is invoked more than once (even by the same frame script re-assigning it), you'll get duplicate worker starts and render calls.

Consider guarding with a flag or replacing repeated queueMicrotask calls:

Proposed guard
+  `#mtsScriptsExecutedScheduled` = false;
+
   // inside the setter:
        this.#renderPageFunction = v;
-       queueMicrotask(() => {
-         this.onMTSScriptsExecuted();
-       });
+       if (!this.#mtsScriptsExecutedScheduled) {
+         this.#mtsScriptsExecutedScheduled = true;
+         queueMicrotask(() => {
+           this.onMTSScriptsExecuted();
+         });
+       }
🤖 Fix all issues with AI agents
In
`@packages/web-platform/web-core-wasm/ts/client/mainthread/LynxViewInstance.ts`:
- Line 28: The module-level fire-and-forget call to loadAllWebElements() can
produce unhandled promise rejections if the dynamic import fails; update the
call site to handle errors (for example, append .catch(err => { /* log error */
}) or invoke it from an async init function wrapped with try/catch) so any
failure is logged; reference the existing loadAllWebElements invocation in
LynxViewInstance.ts and ensure the catch logs the error (console.error or the
project's logger) to avoid unhandled rejections.
🧹 Nitpick comments (1)
packages/web-platform/web-elements/src/elements/XSwiper/XSwiperIndicator.ts (1)

138-149: Prefer parseInt (or a bitwise floor) over parseFloat for a child-element index.

parseFloat can yield a fractional number (e.g. "1.5"1.5), and HTMLCollection indexed by a non-integer returns undefined, silently skipping the highlight. Using parseInt(…, 10) (or Number(…) | 0) makes the integer intent explicit and handles the same NaN-to-undefined fallback via ?..

Proposed fix
-      const firstPaintIndex = parseFloat(
-        this.#dom.getAttribute('current') ?? '0',
-      );
+      const firstPaintIndex = parseInt(
+        this.#dom.getAttribute('current') ?? '0',
+        10,
+      );

Comment thread packages/web-platform/web-core-wasm/ts/client/mainthread/LynxViewInstance.ts Outdated
Copilot AI review requested due to automatic review settings February 25, 2026 08:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a server-side rendering (SSR) runtime for web-core-wasm, adds a Rust web_elements crate to mirror web-elements shadow DOM templates for SSR, and expands SSR-focused test/bench infrastructure.

Changes:

  • Add ts/server SSR runtime (decode + VM execution + server ElementPAPIs) and a new server Wasm target.
  • Introduce packages/web-platform/web-elements Rust crate mirroring htmlTemplates.ts, with a parity test.
  • Refactor style-sheet handling to use StyleSheetResource across client/server paths and add SSR/e2e tests & snapshots.

Reviewed changes

Copilot reviewed 74 out of 80 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds new dev deps (e.g., Prettier/Vitest/CodSpeed) for the e2e package.
packages/web-platform/web-elements/tests/template_sync.rs Rust test to enforce TS/Rust template parity via Node execution.
packages/web-platform/web-elements/src/template.rs Rust mirror of htmlTemplates.ts templates and helpers.
packages/web-platform/web-elements/src/lib.rs Exposes the template module for the new Rust crate.
packages/web-platform/web-elements/src/elements/htmlTemplates.ts Adds synchronization notice for mirrored Rust templates.
packages/web-platform/web-elements/src/elements/common/constants.ts Removes UA-derived exports, keeps useScrollEnd and symbols.
packages/web-platform/web-elements/src/elements/XSwiper/XSwiperIndicator.ts Updates indicator initial paint logic (removes microtask usage).
packages/web-platform/web-elements/src/compat/LinearContainer/LinearContainer.ts Removes direct CSS import (now handled via global CSS imports).
packages/web-platform/web-elements/Cargo.toml Adds new web_elements Rust crate manifest.
packages/web-platform/web-elements/AGENTS.md Documents TS/Rust template synchronization + test location.
packages/web-platform/web-core-wasm/ts/types/DecodedTemplate.ts Extends decoded template type with styleSheet.
packages/web-platform/web-core-wasm/ts/server/wasm.ts Exposes server wasm bindings via a server entrypoint.
packages/web-platform/web-core-wasm/ts/server/index.ts Adds public SSR exports (deploy/decode/ElementAPIs/wasm).
packages/web-platform/web-core-wasm/ts/server/elementAPIs/pureElementAPIs.ts Introduces SSR-safe/stub element APIs for server runtime.
packages/web-platform/web-core-wasm/ts/server/elementAPIs/createElementAPI.ts Implements server ElementPAPIs backed by MainThreadServerContext.
packages/web-platform/web-core-wasm/ts/server/deploy.ts Executes bundled templates in a Node vm sandbox to produce SSR HTML.
packages/web-platform/web-core-wasm/ts/server/decode.ts Adds synchronous server-side template bundle decoder.
packages/web-platform/web-core-wasm/ts/server/createServerLynx.ts Adds SSR lynx global mock implementation for template execution.
packages/web-platform/web-core-wasm/ts/common/decodeUtils.ts Extracts shared decodeBinaryMap utility (used by worker/server).
packages/web-platform/web-core-wasm/ts/client/wasm.ts Removes TemplateManager wasm singleton usage from client path.
packages/web-platform/web-core-wasm/ts/client/mainthread/utils/requestIdleCallback.ts Adds requestIdleCallback fallback helper for timing dispatch.
packages/web-platform/web-core-wasm/ts/client/mainthread/elementAPIs/createElementAPI.ts Switches timing dispatch from rAF to requestIdleCallback fallback.
packages/web-platform/web-core-wasm/ts/client/mainthread/TemplateManager.ts Refactors style-info handling to store/free StyleSheetResource directly.
packages/web-platform/web-core-wasm/ts/client/mainthread/LynxViewInstance.ts Updates style push path to use TemplateManager-stored StyleSheetResource.
packages/web-platform/web-core-wasm/ts/client/mainthread/Background.ts Minor cleanup in RPC initialization.
packages/web-platform/web-core-wasm/ts/client/decodeWorker/decode.worker.ts Reuses shared decodeBinaryMap; adjusts blob sourceURL generation.
packages/web-platform/web-core-wasm/tests/server-ssr.spec.ts Adds vitest SSR unit tests for server ElementPAPIs/HTML generation.
packages/web-platform/web-core-wasm/tests/server-compat.spec.ts Adds snapshot-based SSR compatibility/perf-style tests.
packages/web-platform/web-core-wasm/tests/decode.spec.ts Adds tests for decodeTemplate server decoder.
packages/web-platform/web-core-wasm/tests/snapshots/server-compat.spec.ts.snap New snapshots for server compat tests.
packages/web-platform/web-core-wasm/src/template/template_sections/style_info/style_sheet_resource.rs Makes StyleSheetResource usable for both client/server; adds server string fields.
packages/web-platform/web-core-wasm/src/template/template_sections/style_info/style_info_decoder.rs Minor test variable rename cleanup.
packages/web-platform/web-core-wasm/src/template/template_sections/style_info/raw_style_info.rs Removes wasm_bindgen annotations from some methods (feature-gated encode stays).
packages/web-platform/web-core-wasm/src/template/template_sections/style_info/mod.rs Exposes style resource module for both client and server features.
packages/web-platform/web-core-wasm/src/template/template_manager.rs Removes TemplateManager implementation (no longer used).
packages/web-platform/web-core-wasm/src/template/mod.rs Removes TemplateManager export and module include.
packages/web-platform/web-core-wasm/src/style_transformer/transformer.rs Enables tokenizer usage for server feature as well.
packages/web-platform/web-core-wasm/src/style_transformer/mod.rs Adjusts exports/feature-gates for server usage.
packages/web-platform/web-core-wasm/src/style_transformer/inline_style.rs Feature-gates key-value transform to client; keeps string transform for server.
packages/web-platform/web-core-wasm/src/main_thread/server/style_manager_server.rs Adds server-side style manager for SSR CSS string generation and CSSOG rules.
packages/web-platform/web-core-wasm/src/main_thread/server/mod.rs Introduces server main_thread modules.
packages/web-platform/web-core-wasm/src/main_thread/server/main_thread_server_context.rs Adds SSR element tree store + HTML renderer using web_elements templates.
packages/web-platform/web-core-wasm/src/main_thread/mod.rs Splits main_thread into client/server modules with feature gating.
packages/web-platform/web-core-wasm/src/main_thread/element_data.rs Adds server-specific element data fields (tag/attributes/children) + style setters.
packages/web-platform/web-core-wasm/src/main_thread/client/style_manager.rs Refactors to store cloned StyleSheetResource (no Rc in map).
packages/web-platform/web-core-wasm/src/main_thread/client/mod.rs Adds client module root after main_thread refactor.
packages/web-platform/web-core-wasm/src/main_thread/client/main_thread_context.rs Updates push_style_sheet signature to accept StyleSheetResource directly.
packages/web-platform/web-core-wasm/src/main_thread/client/element_apis/style_apis.rs Removes redundant wasm_bindgen annotations after refactor.
packages/web-platform/web-core-wasm/src/main_thread/client/element_apis/mod.rs Removes old element_data module wiring.
packages/web-platform/web-core-wasm/src/main_thread/client/element_apis/event_apis.rs Removes redundant wasm_bindgen annotations.
packages/web-platform/web-core-wasm/src/main_thread/client/element_apis/dataset_apis.rs Removes redundant wasm_bindgen annotations.
packages/web-platform/web-core-wasm/src/main_thread/client/element_apis/component_apis.rs Removes redundant wasm_bindgen annotations.
packages/web-platform/web-core-wasm/src/lib.rs Updates exports for new client module structure; removes TemplateManager export.
packages/web-platform/web-core-wasm/src/constants.rs Makes LYNX_UNIQUE_ID_ATTRIBUTE available for server feature.
packages/web-platform/web-core-wasm/scripts/build.js Adds server wasm build target.
packages/web-platform/web-core-wasm/package.json Adds ./server export and updates prod asset export paths.
packages/web-platform/web-core-wasm/binary/server/server_bg.wasm.d.ts Adds generated server wasm typings.
packages/web-platform/web-core-wasm/binary/server/server.d.ts Adds generated server JS typings (SSR APIs + StyleSheetResource).
packages/web-platform/web-core-wasm/binary/client/client_bg.wasm.d.ts Updates generated client wasm typings after TemplateManager removal.
packages/web-platform/web-core-wasm/binary/client/client.d.ts Updates generated client JS typings (push_style_sheet signature; StyleSheetResource).
packages/web-platform/web-core-wasm/Cargo.toml Adds dependency on new web_elements crate.
packages/web-platform/web-core-wasm/AGENTS.md Documents new ts/server runtime and updated build variants.
packages/web-platform/web-core-wasm-e2e/vitest.config.ts Adds vitest config, aliases, and optional CodSpeed plugin for SSR e2e/bench.
packages/web-platform/web-core-wasm-e2e/tsconfig.json Enables composite builds for the e2e project.
packages/web-platform/web-core-wasm-e2e/tests/web-core.test.ts Updates e2e tests to flush without relying on renderPage assignment.
packages/web-platform/web-core-wasm-e2e/tests/ssr-no-js.spec.ts Adds Playwright “No JS” SSR page validation test.
packages/web-platform/web-core-wasm-e2e/shell-project/ssr.html Adds SSR shell page that injects server-rendered output.
packages/web-platform/web-core-wasm-e2e/shell-project/devMiddleware.ts Adds dev SSR middleware using executeTemplate.
packages/web-platform/web-core-wasm-e2e/server-tests/server-e2e.test.ts Adds snapshot-driven SSR execution tests against built bundles.
packages/web-platform/web-core-wasm-e2e/server-tests/snapshots/server-e2e.test.ts.snap Adds SSR output snapshots for many bundle scenarios.
packages/web-platform/web-core-wasm-e2e/rsbuild.config.ts Registers SSR middleware in dev server pipeline.
packages/web-platform/web-core-wasm-e2e/package.json Adds bench script and dev deps (vitest/prettier/codspeed).
packages/web-platform/web-core-wasm-e2e/bench/server.bench.vitest.spec.ts Adds server-side SSR performance benchmarks.
packages/web-platform/playwright-fixtures/src/playwright.common.ts Changes default Playwright testMatch pattern.
packages/web-platform/playwright-fixtures/src/coverage-fixture.ts Skips JS coverage collection for the No-JS SSR Playwright suite.
Cargo.toml Adds packages/web-platform/web-elements to the Rust workspace.
Cargo.lock Locks new workspace crate dependencies (web_elements).
.vscode/settings.json Disables typescript.experimental.useTsgo.
.github/workflows/test.yml Adjusts Playwright sharding across jobs.
.changeset/lemon-pigs-teach.md Adds a changeset file (currently empty/invalid).
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (2)

packages/web-platform/web-core-wasm/ts/server/decode.ts:82

  • For lazy templates, the client passes the template url into decode_style_info, but the server decoder passes an empty string. Passing "" is not equivalent to undefined and can produce incorrectly-scoped CSS (e.g., selectors keyed by an empty entry name). Consider accepting a template identifier/url as a parameter to decodeTemplate (or executeTemplate) and pass it through here; otherwise keep behavior consistent with the client by using undefined when no url is available.
    packages/web-platform/web-core-wasm/ts/client/mainthread/utils/requestIdleCallback.ts:5
  • The exported requestIdleCallbackImpl ends up with an imprecise union type between the real requestIdleCallback signature (callback receives an IdleDeadline) and the fallback (callback: () => void). This can cause type errors at call sites. Consider typing it explicitly (e.g., const requestIdleCallbackImpl: typeof requestIdleCallback = ...) and adapting the fallback to accept the IdleDeadline parameter (even if ignored).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/web-platform/web-core-wasm/ts/server/elementAPIs/createElementAPI.ts Outdated
Comment thread .changeset/lemon-pigs-teach.md
Comment thread packages/web-platform/web-core-wasm/tests/server-ssr.spec.ts
Comment thread packages/web-platform/web-core-wasm/tests/server-compat.spec.ts
Comment thread packages/web-platform/web-core-wasm/tests/server-ssr.spec.ts Outdated
Comment thread packages/web-platform/web-core-wasm/tests/server-ssr.spec.ts Outdated
Comment thread packages/web-platform/web-core-wasm/tests/server-compat.spec.ts Outdated
@PupilTong PupilTong force-pushed the p/hw/rust-refactor-ssr branch from ce97e9a to 2254b4d Compare February 25, 2026 08:59
@PupilTong PupilTong force-pushed the p/hw/rust-refactor-ssr branch 3 times, most recently from d7b1ab7 to 28deb51 Compare February 26, 2026 08:43
… and `__AddDataset` no-ops in SSR, and update e2e snapshots.
@PupilTong PupilTong force-pushed the p/hw/rust-refactor-ssr branch from 28deb51 to d964453 Compare February 26, 2026 09:06
@PupilTong PupilTong changed the title WIP: refactor SSR feat: refactor SSR Feb 28, 2026
@PupilTong PupilTong merged commit 94e5779 into lynx-family:main Feb 28, 2026
78 of 82 checks passed
colinaaa pushed a commit that referenced this pull request Mar 2, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @lynx-js/react@0.116.4

### Patch Changes

- Support `ReactLynx::hooks::setState` trace for function components.
([#2198](#2198))

- fix: properly cleanup `__DestroyLifetime` listeners and listCallbacks
in `snapshotDestroyList`.
([#2224](#2224))

## @lynx-js/qrcode-rsbuild-plugin@0.4.6

### Patch Changes

- Print all entries with all schema URLs in non-TTY environments instead
of only showing the first entry's QR code.
([#2227](#2227))

## @lynx-js/react-rsbuild-plugin@0.12.9

### Patch Changes

- Add alias for `use-sync-external-store/with-selector.js` and
`use-sync-external-store/shim/with-selector.js` pointing to
@lynx-js/use-sync-external-store.
([#2200](#2200))

- Updated dependencies
\[[`9033e2d`](9033e2d)]:
    -   @lynx-js/template-webpack-plugin@0.10.4
    -   @lynx-js/react-alias-rsbuild-plugin@0.12.9
    -   @lynx-js/use-sync-external-store@1.5.0
    -   @lynx-js/react-refresh-webpack-plugin@0.3.4
    -   @lynx-js/react-webpack-plugin@0.7.4
    -   @lynx-js/css-extract-webpack-plugin@0.7.0

## @lynx-js/css-serializer@0.1.4

### Patch Changes

- Move `cssChunksToMap` implementation from
`@lynx-js/template-webpack-plugin` to `@lynx-js/css-serializer` for
future reuse.
([#2269](#2269))

## @lynx-js/web-core-wasm@0.0.4

### Patch Changes

- Refactor web element templates and server-side rendering logic
([#2205](#2205))

- Updated dependencies
\[[`94e5779`](94e5779),
[`9033e2d`](9033e2d)]:
    -   @lynx-js/web-elements@0.11.3
    -   @lynx-js/css-serializer@0.1.4

## @lynx-js/web-elements@0.11.3

### Patch Changes

- fix: firefox 147+ layout issue
([#2205](#2205))

## @lynx-js/template-webpack-plugin@0.10.4

### Patch Changes

- Move `cssChunksToMap` implementation from
`@lynx-js/template-webpack-plugin` to `@lynx-js/css-serializer` for
future reuse.
([#2269](#2269))

- Updated dependencies
\[[`9033e2d`](9033e2d)]:
    -   @lynx-js/css-serializer@0.1.4

## @lynx-js/react-alias-rsbuild-plugin@0.12.9

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@coderabbitai coderabbitai Bot mentioned this pull request Mar 24, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants