Skip to content

perf: speed up client startup - #4009

Merged
afonsojramos merged 14 commits into
v3-betafrom
perf/startup
Oct 1, 2026
Merged

afonsojramos merged 14 commits into
v3-betafrom
perf/startup

Conversation

@afonsojramos

@afonsojramos afonsojramos commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Summary

Cold startup does much less waiting before modules run.

  • Webpack capture. The capture registers its chunk and then always slept 500 ms before checking the result. The runtime runs the chunk callback inside push, so the capture now returns as soon as it succeeds.
  • Scrolling fix. The workaround is meant for Spotify 1.2.56 and older, but its check (version[1] >= 2 && version[2] >= 57) let it run on 1.3.0 to 1.3.56. On every DOM mutation it called getComputedStyle on every element, about 400 ms of main-thread time per boot on 1.3.3, and it kept forcing compositor layers on scrollable elements. A component-wise isVersionBefore helper now limits it to the versions it was written for.
  • Theme first. The boot theme and its dependencies load before other modules, still in dependency order, so the client renders already themed. Theme stylesheets are kept after every other module stylesheet, so themes still override extension styles at equal specificity whatever order modules load in.
  • Color schemes only for themes. Every module with CSS used to fetch /modules/<id>/color.ini before its load, one module at a time (about 35 ms each). Only themes ship a color.ini, so extensions no longer look for one. That removes about 450 ms of serial requests with 12 styled extensions.
  • Prefetch. After mixins, the loader adds modulepreload for each staged module's JS and starts fetching its CSS, using the same skip rules as runLoads. Apply now records every other script of a multi-file module in the manifest (preload, skipping tests, specs and tool configs), so a tree like stdlib's 119 files arrives in one batch instead of five or six dependent import waves. Nothing is evaluated early. Unused prefetched CSS is dropped after boot, and a failed CSS prefetch is logged before the loader fetches again.
  • waitForClient polls every 25 ms instead of 200 ms.

Results

Spotify 1.3.3.264, macOS arm64, 19 installed modules with the Text theme. These are 12 interleaved cold renderer boots per setup, measured over CDP with the machine under heavy unrelated load (load average about 8). Median, with the 10th to 90th percentile in brackets:

Milestone Released CLI + stdlib 1.13.1 This PR + spicetify/modules#48
Theme applied 2206 ms [2058–2327] 572 ms [520–615]
All modules loaded 2297 ms [2273–2378] 1412 ms [1328–1624]
First home content 849 ms [814–971] 978 ms [894–1118]

First home content arrives about 130 ms later because the home page now renders with the theme already applied. Before, it rendered unthemed and then restyled when the theme arrived about 2.2 s in, so the themed home now appears more than a second earlier. An A/B with and without the preload list, 12 boots each, showed the list is not the cause: home content came at 931 ms with it and 985 ms without, and all modules loaded at 1458 ms against 1511 ms.

Validation

  • All 191 src/jsHelper tests and all 166 spicetify library tests pass (4 ignored, as on v3-beta). cargo clippy -p spicetify --release -- -D warnings is clean.
  • New tests cover:
    • synchronous capture and the version helper
    • theme-first ordering and scheme lookup only for themes
    • prefetch selection: disabled and blocked dependencies, other themes, mixin modules, local installs and preload lists
    • stylesheet ordering against happy-dom's adoptedStyleSheets
    • the manifest script list
  • Live, on a client applied with this branch: the scrolling fix no longer tags elements on 1.3.3, every module entry and all of stdlib's scripts are fetched within the first 70 ms, and no extension requests a color.ini.

Not verified: Windows and Linux, and the Go CLI's use of the shared wrapper on Spotify versions before 1.2.57.

A related bug is left for a separate change. The CosmosAsync proxy in platform.js uses the same broken check (version[1] >= 2 && version[2] < 31), so on 1.3.0 to 1.3.30 it returns Spotify's unwrapped fetch. Fixing it changes request handling for every module on 1.3, so it needs its own testing.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: spicetify/cli/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 91aa0b1b-30ad-4c2c-a349-f68085f6ebf1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

@afonsojramos
afonsojramos merged commit 8d5ab98 into v3-beta Oct 1, 2026
7 checks passed
@afonsojramos
afonsojramos deleted the perf/startup branch October 1, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant