feat: start.instrument — a server module awaited to completion before the handler graph loads - #365
Merged
Merged
Conversation
… 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 detectedLatest commit: d893d0b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The last framework-side piece the Solid 2 Sentry adapter's server half needs, and the replacement for the per-host
node --import instrument.mjsstep 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, soimport './instrument'at the top of an entry still runs after@solidjs/weband 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.instrumentA server-only module path. With it set, the handler entry (
virtual:solid-ssr-handler) becomes a thin wrapper:Top-level await sequencing is the one construct that guarantees the order. Exports are re-declared by name because a static
export * fromwould be hoisted like any other static import. The real handler moves under the-implid 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 (theindexservice 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.tsdoes 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 anx-instrumentheader. The middleware and preview modes assertorder[0] === 'instrument(before:0)',done === true,evaluations === 1over dev, prod and preview, plus the wrapper's codegen shape. A static import in place of the await fails thedonecheck — the timer would still be pending when the graph evaluated.Also
componentNamesnote ingetSolidOptionssaid 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.observee2e mode: anobserve: trueproduction build resolvesweb.observeon the client and solid-js'sserver.observeon 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.