-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(tanstackstart-react): Auto-instrument global middleware #18844
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
nicohrubec
wants to merge
47
commits into
develop
Choose a base branch
from
nh/automatic-middleware-instrumentation
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+473
−38
Open
Changes from all commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
704a781
Add placeholder vite config wrapper
nicohrubec 6cc5784
align usage with solidstart
nicohrubec b4a0d83
Add placeholder to add plugins
nicohrubec b6ddbff
Add sentry vite plugin and enable source maps plugins automatically
nicohrubec 8c6f0fa
?
nicohrubec 207a96a
add unit tests
nicohrubec 53e9099
add vite wrapper to e2e tests
nicohrubec b9c51e9
simplify unit tests
nicohrubec 6eb1c8b
use buildTimeOptionsBase instead of defining my own type
nicohrubec 4dcdabf
Merge branch 'develop' into nh/tss-vite-config-wrapper
nicohrubec 9aaabd9
.
nicohrubec bfa238a
switch to sentry vite plugin
nicohrubec 086b9f6
add changelog entry and pass down all options
nicohrubec 05e08f1
clean
nicohrubec bc0acdd
always add sentry vite plugin and pass down disable option
nicohrubec 8e60de8
Merge branch 'develop' into nh/tss-vite-config-wrapper
nicohrubec 9bbd0b0
readability
nicohrubec 283a545
update tests
nicohrubec 1e736b4
Merge branch 'develop' into nh/tss-vite-config-wrapper
nicohrubec 841a311
bump bundler plugins
nicohrubec 9ad7a8d
update
nicohrubec 286f624
update bundler plugins fr this time
nicohrubec 505c92c
Revert "update bundler plugins fr this time"
nicohrubec 07d5e77
Merge branch 'develop' into nh/tss-vite-config-wrapper
nicohrubec 073e352
Merge branch 'develop' into nh/tss-vite-config-wrapper
nicohrubec 8ce5358
update sentry vite plugin
nicohrubec 76c746c
address some pr comments
nicohrubec bbf7be4
use post for config plugin
nicohrubec 63a6660
fix files to delete after upload settings
nicohrubec c99e1a1
Merge branch 'develop' into nh/tss-vite-config-wrapper
nicohrubec e2bf4b0
make global middleware auto-wrapping work
nicohrubec b7d35ad
update unit tests for sentryTanstackStart
nicohrubec c6d7624
do not transform files with manul middleware wrapping
nicohrubec 8142b20
clean
nicohrubec 463d7f0
clean
nicohrubec 1ed2308
.
nicohrubec 2706bc0
Add changelog entry
nicohrubec bdb9ac5
Merge branch 'develop' into nh/automatic-middleware-instrumentation
nicohrubec a39e88f
handle use directive edge case and improve tests
nicohrubec 00fbbbd
warn users if stuff goes wrong
nicohrubec 993ba3f
deduplicate middleware entries
nicohrubec aa7adc5
yarn fix
nicohrubec 65a1a3e
early return
nicohrubec 7137d00
remove duplicate test
nicohrubec b5ed7e2
formatting
nicohrubec b0c6045
be more precise about what files to instrument
nicohrubec e0c1428
Merge branch 'develop' into nh/automatic-middleware-instrumentation
nicohrubec File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
7 changes: 4 additions & 3 deletions
7
dev-packages/e2e-tests/test-applications/tanstackstart-react/src/start.ts
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,10 @@ | ||
| import { createStart } from '@tanstack/react-start'; | ||
| import { wrappedGlobalRequestMiddleware, wrappedGlobalFunctionMiddleware } from './middleware'; | ||
| // NOTE: These are NOT wrapped - auto-instrumentation via the Vite plugin will wrap them | ||
| import { globalRequestMiddleware, globalFunctionMiddleware } from './middleware'; | ||
|
|
||
| export const startInstance = createStart(() => { | ||
| return { | ||
| requestMiddleware: [wrappedGlobalRequestMiddleware], | ||
| functionMiddleware: [wrappedGlobalFunctionMiddleware], | ||
| requestMiddleware: [globalRequestMiddleware], | ||
| functionMiddleware: [globalFunctionMiddleware], | ||
| }; | ||
| }); |
117 changes: 117 additions & 0 deletions
117
packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import type { Plugin } from 'vite'; | ||
|
|
||
| type AutoInstrumentMiddlewareOptions = { | ||
| enabled?: boolean; | ||
| debug?: boolean; | ||
| }; | ||
|
|
||
| /** | ||
| * A Vite plugin that automatically instruments TanStack Start middlewares | ||
| * by wrapping `requestMiddleware` and `functionMiddleware` arrays in `createStart()`. | ||
| */ | ||
| export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin { | ||
| const { enabled = true, debug = false } = options; | ||
|
|
||
| return { | ||
| name: 'sentry-tanstack-middleware-auto-instrument', | ||
| enforce: 'pre', | ||
|
|
||
| transform(code, id) { | ||
| if (!enabled) { | ||
| return null; | ||
| } | ||
|
|
||
| // Skip if not a TS/JS file | ||
| if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) { | ||
| return null; | ||
| } | ||
|
|
||
| // Only wrap requestMiddleware and functionMiddleware in createStart() | ||
| if (!code.includes('createStart(')) { | ||
| return null; | ||
| } | ||
|
|
||
| // Skip if the user already did some manual wrapping | ||
| if (code.includes('wrapMiddlewaresWithSentry')) { | ||
| return null; | ||
| } | ||
|
|
||
| let transformed = code; | ||
| let needsImport = false; | ||
| const skippedMiddlewares: string[] = []; | ||
|
|
||
| transformed = transformed.replace( | ||
| /(requestMiddleware|functionMiddleware)\s*:\s*\[([^\]]*)\]/g, | ||
| (match: string, key: string, contents: string) => { | ||
| const objContents = arrayToObjectShorthand(contents); | ||
| if (objContents) { | ||
| needsImport = true; | ||
| if (debug) { | ||
| // eslint-disable-next-line no-console | ||
| console.log(`[Sentry] Auto-wrapping ${key} in ${id}`); | ||
| } | ||
| return `${key}: wrapMiddlewaresWithSentry(${objContents})`; | ||
| } | ||
| // Track middlewares that couldn't be auto-wrapped | ||
| if (contents.trim()) { | ||
| skippedMiddlewares.push(key); | ||
| } | ||
| return match; | ||
| }, | ||
| ); | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Warn about middlewares that couldn't be auto-wrapped | ||
| if (skippedMiddlewares.length > 0) { | ||
| // eslint-disable-next-line no-console | ||
| console.warn( | ||
| `[Sentry] Could not auto-instrument ${skippedMiddlewares.join(' and ')} in ${id}. ` + | ||
| 'To instrument these middlewares, use wrapMiddlewaresWithSentry() manually. ', | ||
| ); | ||
| } | ||
|
|
||
| // We didn't wrap any middlewares, so we don't need to import the wrapMiddlewaresWithSentry function | ||
| if (!needsImport) { | ||
| return null; | ||
| } | ||
|
|
||
| const sentryImport = "import { wrapMiddlewaresWithSentry } from '@sentry/tanstackstart-react';\n"; | ||
|
|
||
| // Check for 'use server' or 'use client' directives, these need to be before any imports | ||
| const directiveMatch = transformed.match(/^(['"])use (client|server)\1;?\s*\n?/); | ||
| if (directiveMatch) { | ||
| // Insert import after the directive | ||
| const directive = directiveMatch[0]; | ||
| transformed = directive + sentryImport + transformed.slice(directive.length); | ||
| } else { | ||
| transformed = sentryImport + transformed; | ||
| } | ||
|
|
||
| return { code: transformed, map: null }; | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Convert array contents to object shorthand syntax. | ||
| * e.g., "foo, bar, baz" → "{ foo, bar, baz }" | ||
| * | ||
| * Returns null if contents contain non-identifier expressions (function calls, etc.) | ||
| * which cannot be converted to object shorthand. | ||
| */ | ||
| export function arrayToObjectShorthand(contents: string): string | null { | ||
| const items = contents | ||
| .split(',') | ||
| .map(s => s.trim()) | ||
| .filter(Boolean); | ||
|
|
||
| // Only convert if all items are valid identifiers (no complex expressions) | ||
| const allIdentifiers = items.every(item => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(item)); | ||
| if (!allIdentifiers || items.length === 0) { | ||
| return null; | ||
| } | ||
|
|
||
| // Deduplicate to avoid invalid syntax like { foo, foo } | ||
| const uniqueItems = [...new Set(items)]; | ||
|
|
||
| return `{ ${uniqueItems.join(', ')} }`; | ||
| } | ||
nicohrubec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| export { sentryTanstackStart } from './sentryTanstackStart'; | ||
| export type { SentryTanstackStartOptions } from './sentryTanstackStart'; |
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
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,26 @@ | ||||||||
| import type { BuildTimeOptionsBase } from '@sentry/core'; | ||||||||
| import type { Plugin } from 'vite'; | ||||||||
| import { makeAutoInstrumentMiddlewarePlugin } from './autoInstrumentMiddleware'; | ||||||||
| import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourceMaps'; | ||||||||
|
|
||||||||
| /** | ||||||||
| * Build-time options for the Sentry TanStack Start SDK. | ||||||||
| */ | ||||||||
| export interface SentryTanstackStartOptions extends BuildTimeOptionsBase { | ||||||||
| /** | ||||||||
| * If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares. | ||||||||
| * | ||||||||
| * This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry | ||||||||
| * instrumentation to capture performance data. | ||||||||
| * | ||||||||
| * Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually | ||||||||
| * using `wrapMiddlewaresWithSentry`. | ||||||||
| * | ||||||||
| * @default true | ||||||||
| */ | ||||||||
| autoInstrumentMiddleware?: boolean; | ||||||||
| } | ||||||||
|
|
||||||||
| /** | ||||||||
| * Vite plugins for the Sentry TanStack Start SDK. | ||||||||
| * | ||||||||
|
|
@@ -26,14 +45,21 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc | |||||||
| * @param options - Options to configure the Sentry Vite plugins | ||||||||
| * @returns An array of Vite plugins | ||||||||
| */ | ||||||||
| export function sentryTanstackStart(options: BuildTimeOptionsBase = {}): Plugin[] { | ||||||||
| // Only add plugins in production builds | ||||||||
| export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): Plugin[] { | ||||||||
| // only add plugins in production builds | ||||||||
| if (process.env.NODE_ENV === 'development') { | ||||||||
| return []; | ||||||||
| } | ||||||||
|
|
||||||||
| const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)]; | ||||||||
|
|
||||||||
| // middleware auto-instrumentation | ||||||||
| const autoInstrumentMiddleware = options.autoInstrumentMiddleware !== false; | ||||||||
| if (autoInstrumentMiddleware) { | ||||||||
|
Comment on lines
+57
to
+58
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This value is just used once and it's not too complicated to understand what the condition is without having this extra variable :)
Suggested change
|
||||||||
| plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug })); | ||||||||
| } | ||||||||
|
|
||||||||
| // source maps | ||||||||
| const sourceMapsDisabled = options.sourcemaps?.disable === true || options.sourcemaps?.disable === 'disable-upload'; | ||||||||
| if (!sourceMapsDisabled) { | ||||||||
| plugins.push(...makeEnableSourceMapsVitePlugin(options)); | ||||||||
|
|
||||||||
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: why do we trim the contents here? To make sure it's not an empty file? Just curious