Skip to content

refactor(server): extract embedded JS/CSS/HTML blobs into separate files #4017

@iduartgomez

Description

@iduartgomez

Problem

Several server-side Rust files embed large JS/CSS/HTML payloads as raw-string consts (r#"..."# / r##"..."##). PR #4016 ("fix: allow http: URLs in shell open_url handler") surfaced the cost: the two functional code changes (one-line scheme allowlist + IPv6 loopback fix) were buried inside a 692-line JS string literal in crates/core/src/server/path_handlers.rs. gh pr view and reviewers see "+150 lines, mostly tests" and miss the actual semantic delta. See #4016 (comment) for the dig-out comment.

Concrete pain points:

  • No syntax highlighting, no JS LSP, no ESLint, no prettier — every JS edit is blind.
  • Diffs are unreadable. A scheme-check change looks identical in git diff to a comment tweak.
  • Grep/IDE search lands inside Rust string literals instead of .js files.
  • ~850 of 3332 lines in path_handlers.rs are payload, not Rust.
  • CI cannot lint embedded JS for syntax/style/security regressions.

Precedent: crates/core/src/bin/commands/setup_wizard/ui.rs:17 already does the right thing — const SETUP_HTML: &str = include_str!("setup.html");.

Scope (inventory)

Static blobs to extract via include_str!. Asset files placed next to the consuming .rs file (matches setup_wizard/ precedent).

crates/core/src/server/path_handlers.rs

  • SHELL_BRIDGE_JS (lines 612–1304, ~692 lines) → path_handlers/assets/shell_bridge.js
  • WEBSOCKET_SHIM_JS (lines 1312–1402, ~90 lines) → path_handlers/assets/websocket_shim.js
  • NAVIGATION_INTERCEPTOR_JS (lines 1424–1489, ~65 lines) → path_handlers/assets/navigation_interceptor.js
  • Shell HTML scaffold (lines 464–482, has format! placeholders {favicon}, {iframe_src}, {auth_token}, {SHELL_BRIDGE_JS}) → path_handlers/assets/shell.html. Use format!(include_str!("assets/shell.html"), ...)include_str! expands to a string literal, accepted as format!'s first arg.

crates/core/src/server/home_page.rs

  • CSS (lines 732–1200, ~468 lines) → home_page/assets/style.css
  • JS (lines 1208–1293, ~85 lines) → home_page/assets/dashboard.js
  • PEER_CSS (lines 2289–2435, ~146 lines) → home_page/assets/peer.css
  • Two <!DOCTYPE html> page scaffolds (lines 1308 and 1633, with {CSS}{PEER_CSS}{JS} interpolation) → home_page/assets/{home,peer}.html via format!(include_str!(...)).

crates/core/src/server/client_api/permission_prompts.rs

  • Permission-prompt HTML template (lines 315–~395, includes embedded <style> and <script>) → split into permission_prompts/assets/prompt.html, prompt.css, prompt.js.
  • Permission-result HTML template (lines 524–~600) → permission_prompts/assets/result.html (+ extracted css/js if non-trivial).

crates/core/src/server/errors.rs

  • connecting_page() HTML (lines 102–129) → errors/assets/connecting.html returned via include_str!(...).to_string().

Out of scope (intentional)

  • home_page.rs per-row/per-card format!(r#"<div...">"#) snippets — runtime templating with row-level interpolation, not static blobs. Templating-engine adoption is a separate question.
  • service.rs plist/systemd/launchd templates — platform-specific config, not web assets.
  • RABBIT_SVG_PATH — single path string, not worth a file.
  • path_handlers.rs test-fixture r#"<!DOCTYPE..."# literals (lines 1782, 1829, 1854, 1877, 2178, 3116, 3150) — test inputs, kept inline for locality.

Approach

Mechanism: include_str! only. No build script, no asset bundling, no runtime I/O, no new dependency. Compiler tracks the included file and rebuilds on change.

Layout (assets next to consumer):

crates/core/src/server/
  path_handlers.rs
  path_handlers/assets/
    shell_bridge.js
    websocket_shim.js
    navigation_interceptor.js
    shell.html
  home_page.rs
  home_page/assets/
    style.css
    peer.css
    dashboard.js
    home.html
    peer.html
  errors.rs
  errors/assets/connecting.html
  client_api/
    permission_prompts.rs
    permission_prompts/assets/
      prompt.html prompt.css prompt.js
      result.html

include_str! resolves paths relative to the calling source file and does NOT require the directory to be a module — path_handlers/ and home_page/ are asset-only directories, no mod.rs needed.

Format-string scaffolds: format!(include_str!("assets/foo.html"), name = value, ...) works because include_str! expands to a & 'static str literal at compile time, satisfying format!'s "first arg must be a string literal" requirement. Verify with a small spike before the first PR commits.

Constraints / things that must NOT regress

  1. All existing SHELL_BRIDGE_JS.contains("...") test assertions in path_handlers.rs (~40 sites at lines 2343–2557) keep working unchanged — include_str! produces the same & 'static str value.
  2. Wire/protocol-relevant strings (u.protocol !== 'https:', u.protocol !== 'http:', '::1', MAX_CONNECTIONS, CONTRACT_PREFIX_RE, etc.) must be byte-identical post-extraction. The extraction PR is a pure move; no behavioral changes.
  3. cargo fmt && cargo clippy -- -D warnings && cargo test clean.
  4. No new runtime cost: include_str! is compile-time inlining.

CI lint (part of this issue)

After extraction, add CI step running prettier (or deno fmt) and ESLint over crates/core/src/**/assets/*.{js,css,html}. Raises the floor on future JS edits — basic syntax, unused vars, implicit globals.

Minimum CI bar:

  • prettier --check on **/assets/*.{js,css,html}
  • eslint with a minimal config (recommended ruleset) on **/assets/*.js
  • runs in existing lint job, no new workflow

Sequencing (single issue, separate PRs)

  • PR 1: Extract path_handlers.rs blobs (biggest payoff, directly addresses fix(server): allow http: URLs in shell open_url handler #4016 pain). Pure code-move + include_str!.
  • PR 2: Extract home_page.rs CSS/JS/HTML scaffolds.
  • PR 3: Extract permission_prompts.rs and errors.rs.
  • PR 4: Add prettier + eslint CI step over extracted assets.

Each PR is a refactor: and must be byte-for-byte behavior-preserving (verifiable by running existing test suite, including the regression test added in #4016).

Acceptance criteria

  • All blobs in inventory live in *.{js,css,html} files next to consumer .rs.
  • No raw-string r#"..."# block longer than ~30 lines remains in the four target files (ad-hoc small snippets in format! row templates excepted).
  • cargo test -p freenet --lib server:: passes including all *.contains(...) assertions.
  • PR fix(server): allow http: URLs in shell open_url handler #4016's regression test (shell_open_url_handler_accepts_http_and_https_but_blocks_localhost) still passes.
  • CI runs prettier + eslint over crates/core/src/**/assets/* and is green.
  • Future PRs that change shell/dashboard JS show readable diffs in gh pr view (manual verification on first follow-up PR).

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-developer-xpArea: developer experienceE-mediumExperience needed to fix/implement: Medium / intermediateT-enhancementType: Improvement to existing functionality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions