Skip to content

feat: start.instrument — a server module awaited to completion before the handler graph loads - #365

Merged
ryansolid merged 2 commits into
nextfrom
early-server-import
Sep 18, 2026
Merged

ryansolid merged 2 commits into
nextfrom
early-server-import

Conversation

@ryansolid

Copy link
Copy Markdown
Member

The last framework-side piece the Solid 2 Sentry adapter's server half needs, and the replacement for the per-host node --import instrument.mjs step now that there is no SolidStart to own the server entry.

The problem

An APM's OpenTelemetry setup (Sentry.init(), NodeSDK.start()) has to run before the modules it patches are loaded. ESM import order cannot provide that: static imports are hoisted and evaluated in dependency order, so import './instrument' at the top of an entry still runs after @solidjs/web and everything else the entry imports. Sentry's own fix for this (autoInjectServerSentry) is to wrap the server entry in a dynamic import — which only the thing that generates the entry can do. This plugin generates it.

start.instrument

A server-only module path. With it set, the handler entry (virtual:solid-ssr-handler) becomes a thin wrapper:

await import("<instrument>");
const handler = await import("virtual:solid-ssr-handler-impl");
export const handleRequest = handler.handleRequest;
export default handler.default;

Top-level await sequencing is the one construct that guarantees the order. Exports are re-declared by name because a static export * from would be hoisted like any other static import. The real handler moves under the -impl id unchanged; without the option nothing changes (the id resolves to the same code as before). The module may be async and needs no exports. Honored on every surface — vite dev (the runner imports the same wrapper), vite build, vite preview, and a host consuming the handler entry (the index service entry is the wrapper). The server build must keep code splitting on, the default; inlining dynamic imports would hoist the handler graph back above the instrument.

Proving it without an APM

examples/start-ssr/src/instrument.ts does real async work (a 20 ms timer) and records into a register: how many modules had run before it (must be none), whether it finished (done), and how many times it evaluated. middleware.ts — which imports @solidjs/web, so it evaluates as part of the handler graph — snapshots that register at its own module top level and reports it in an x-instrument header. The middleware and preview modes assert order[0] === 'instrument(before:0)', done === true, evaluations === 1 over dev, prod and preview, plus the wrapper's codegen shape. A static import in place of the await fails the done check — the timer would still be pending when the graph evaluated.

Also

  • The componentNames note in getSolidOptions said the labels were DOM-only; the SSR generate emits them from the compilers that carry feat(compiler,babel-plugin,solid): componentNames for SSR output; ssrScope swaps the id-bearing owner solid#3441 (2.0.0-rc.9). The code already passed the option for both postures.
  • A new observe e2e mode: an observe: true production build resolves web.observe on the client and solid-js's server.observe on the server, and client components carry their source labels through minification. Two further checks — SSR component labels and @solidjs/web's server observe build in the bundle — record why they are not yet assertable on the installed rc.8 (both landed in Solid after it) and become real assertions when the workspace rides rc.9. Confirmed on the way: on rc.8 an observe build's server half is the production web runtime, so the server side of the observability story is rc.9-gated in practice.

Modes run: middleware 75/75, preview 31/31, observe 5/5, dev 67/67, prod 58/58, external 3/3.

… the handler graph loads

The seam APM/OpenTelemetry setup needs: `Sentry.init()` must run before the
modules it patches are loaded, and ESM import order cannot provide that —
static imports are hoisted and evaluated in dependency order, so
`import './instrument'` at the top of an entry still runs after everything
the entry imports. With `start.instrument` the plugin hands out the handler
entry as `await import(instrument); await import(handler)`, exports
re-declared by name, on every surface (dev, build, preview, a host
consuming the entry). Replaces the per-host `node --import instrument.mjs`
dance.

The start-ssr suite proves the contract without an APM: an instrument
module with real async work records that nothing from the graph had run
when it started and that it had finished by the time middleware.ts (which
imports @solidjs/web) evaluated — asserted over dev, prod and preview, plus
the wrapper's codegen shape.

Also: the componentNames note in getSolidOptions no longer calls the labels
DOM-only (the SSR generate emits them from the compilers that carry
solidjs/solid#3441, 2.0.0-rc.9), and a new `observe` e2e mode asserts an
`observe: true` build resolves the observe artifacts and carries client
component labels — the SSR-label and @solidjs/web server-observe checks
record why they are not yet assertable on rc.8 and turn real on rc.9.

Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Sep 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d893d0b

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

This PR includes changesets to release 1 package
Name Type
@solidjs/vite-plugin 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

@pkg-pr-new

pkg-pr-new Bot commented Sep 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@solidjs/vite-plugin@365

commit: d893d0b

@ryansolid
ryansolid merged commit 8ec02f5 into next Sep 18, 2026
6 checks passed
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