Skip to content

Commit 2f5889c

Browse files
committed
fix(tanstackstart-react): Delete only the source maps that Sentry enabled
The default `filesToDeleteAfterUpload: ['./**/*.map']` does not enter dot directories like `.output`, so source maps that Sentry enabled stayed in the client build of Nitro apps. Delete the source maps in the output directory of each environment that only has source maps because Sentry enabled them. Keep source maps that the user enabled with `build.sourcemap`, an environment setting or `build.rolldownOptions.output.sourcemap`. Warn on Vite 7, where Sentry only knows `build.outDir`.
1 parent 27e85c7 commit 2f5889c

2 files changed

Lines changed: 290 additions & 94 deletions

File tree

‎packages/tanstackstart-react/src/vite/sourceMaps.ts‎

Lines changed: 96 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { sentryVitePlugin } from '@sentry/bundler-plugins/vite';
22
import * as path from 'node:path';
3-
import type { Plugin, UserConfig } from 'vite';
3+
import type { BuildEnvironmentOptions, Plugin, ResolvedConfig, UserConfig } from 'vite';
44
import type { SentryTanstackStartOptions } from './sentryTanstackStart';
55

66
type FilesToDeleteAfterUpload = string | string[] | undefined;
77

8+
/**
9+
* Tells the plugin that deletes source maps after the upload whether the source maps of an environment are only
10+
* there because Sentry enabled them.
11+
*/
12+
type SourceMapsEnablerApi = { hasSentryEnabledSourceMaps(environmentName: string): boolean };
13+
814
/** TanStack Router and Start split route files into virtual modules, e.g. `index.tsx?tss-serverfn-split`. */
915
const TANSTACK_SPLIT_QUERY_REGEX = /\?ts[rs]-[^/]*$/;
1016

@@ -62,21 +68,27 @@ export function makeAddSentryVitePlugin(options: SentryTanstackStartOptions): Pl
6268
name: 'sentry-tanstackstart-files-to-delete-after-upload-plugin',
6369
apply: 'build',
6470
enforce: 'post',
65-
config(config) {
66-
const userFilesToDelete = sourcemaps?.filesToDeleteAfterUpload;
67-
68-
// Only auto-delete source maps if the user didn't configure sourcemaps at all
69-
if (typeof userFilesToDelete === 'undefined' && typeof config.build?.sourcemap === 'undefined') {
70-
if (debug) {
71-
// eslint-disable-next-line no-console
72-
console.log(
73-
'[Sentry] Automatically setting `sourcemaps.filesToDeleteAfterUpload: ["./**/*.map"]` to delete generated source maps after they were uploaded to Sentry.',
74-
);
75-
}
76-
resolveFilesToDeleteAfterUpload?.(['./**/*.map']);
77-
} else {
78-
resolveFilesToDeleteAfterUpload?.(userFilesToDelete);
71+
configResolved(config) {
72+
if (sourcemaps?.filesToDeleteAfterUpload !== undefined) {
73+
resolveFilesToDeleteAfterUpload?.(sourcemaps.filesToDeleteAfterUpload);
74+
return;
75+
}
76+
77+
if (!isVite8OrLater(this.meta.viteVersion)) {
78+
// eslint-disable-next-line no-console
79+
console.warn(
80+
`[Sentry] TanStack Start requires Vite 8 or later. With an older Vite, Sentry deletes the source maps it generated only in \`build.outDir\` of each environment, so source maps in other output directories can stay in the build output.`,
81+
);
82+
}
83+
84+
const filesToDelete = getSentryEnabledSourceMapGlobs(config);
85+
if (debug && filesToDelete.length > 0) {
86+
// eslint-disable-next-line no-console
87+
console.log(
88+
`[Sentry] Automatically setting \`sourcemaps.filesToDeleteAfterUpload: ${JSON.stringify(filesToDelete)}\` to delete generated source maps after they were uploaded to Sentry.`,
89+
);
7990
}
91+
resolveFilesToDeleteAfterUpload?.(filesToDelete);
8092
},
8193
};
8294

@@ -116,21 +128,72 @@ export function makeAddSentryVitePlugin(options: SentryTanstackStartOptions): Pl
116128
return [configPlugin, ...sentryPlugins];
117129
}
118130

131+
/**
132+
* Globs for the source maps in the output directories that only have source maps because Sentry enabled them. A glob
133+
* such as `./**\/*.map` does not enter dot directories like `.output`, where Nitro puts the client build.
134+
*/
135+
function getSentryEnabledSourceMapGlobs(config: ResolvedConfig): string[] {
136+
const outDirs = Object.entries(config.environments)
137+
.filter(([name]) => hasSentryEnabledSourceMaps(config, name))
138+
.filter(([, environment]) => !hasOutputSourceMapSetting(environment.build))
139+
.flatMap(([, environment]) => getOutputDirs(config.root, environment.build));
140+
141+
return [...new Set(outDirs)].map(outDir => {
142+
const relativeOutDir = path.relative(process.cwd(), outDir).split(path.sep).join('/');
143+
return `${relativeOutDir || '.'}/**/*.map`;
144+
});
145+
}
146+
147+
/** `output.dir` takes precedence over `build.outDir`. Nitro sets only `output.dir` for its server build. */
148+
function getOutputDirs(root: string, build: ResolvedConfig['environments'][string]['build']): string[] {
149+
// Vite 7 has no `rolldownOptions`.
150+
const outputs = [build.rolldownOptions?.output ?? {}].flat();
151+
return outputs.map(output => path.resolve(root, output.dir ?? build.outDir));
152+
}
153+
154+
/** `output.sourcemap` of the bundler config overrides `build.sourcemap` in the build output. */
155+
function hasOutputSourceMapSetting(build: Pick<BuildEnvironmentOptions, 'rolldownOptions'> | undefined): boolean {
156+
return [build?.rolldownOptions?.output ?? []].flat().some(output => output.sourcemap !== undefined);
157+
}
158+
159+
/** Vite tells plugins its version since Vite 7. */
160+
function isVite8OrLater(viteVersion: string | undefined): boolean {
161+
return Number(viteVersion?.split('.')[0]) >= 8;
162+
}
163+
164+
function hasSentryEnabledSourceMaps(config: ResolvedConfig, environmentName: string): boolean {
165+
return config.plugins.some(plugin =>
166+
(plugin.api as Partial<SourceMapsEnablerApi> | undefined)?.hasSentryEnabledSourceMaps?.(environmentName),
167+
);
168+
}
169+
119170
/**
120171
* A Sentry plugin for TanStack Start React to enable "hidden" source maps if they are unset.
121172
*/
122173
export function makeEnableSourceMapsVitePlugin(options: SentryTanstackStartOptions): Plugin[] {
174+
let enabledSourceMaps = false;
175+
let environmentsWithSourceMapSetting: string[] = [];
176+
177+
const api: SourceMapsEnablerApi = {
178+
hasSentryEnabledSourceMaps: environmentName =>
179+
enabledSourceMaps && !environmentsWithSourceMapSetting.includes(environmentName),
180+
};
181+
123182
return [
124183
{
125184
name: 'sentry-tanstackstart-react-source-maps',
126185
apply: 'build',
127186
enforce: 'post',
187+
api,
128188
config(viteConfig) {
129-
return {
130-
build: {
131-
sourcemap: getUpdatedSourceMapSettings(viteConfig, options),
132-
},
133-
};
189+
const sourcemap = getUpdatedSourceMapSettings(viteConfig, options);
190+
enabledSourceMaps = sourcemap !== viteConfig.build?.sourcemap;
191+
// The setting of an environment overrides the top-level setting, e.g. `nitro/vite` sets it for its environments.
192+
environmentsWithSourceMapSetting = Object.entries(viteConfig.environments ?? {})
193+
.filter(([, environment]) => environment?.build?.sourcemap !== undefined)
194+
.map(([name]) => name);
195+
196+
return { build: { sourcemap } };
134197
},
135198
},
136199
];
@@ -142,19 +205,19 @@ export function makeEnableSourceMapsVitePlugin(options: SentryTanstackStartOptio
142205
* - keep this setting (emit a warning that errors won't be unminified in Sentry)
143206
* - We won't upload anything
144207
*
145-
* 2. Users enabled source map generation (true, 'hidden', 'inline').
208+
* 2. Users enabled source map generation (true, 'hidden', 'inline') or set `build.rolldownOptions.output.sourcemap`.
146209
* - keep this setting (don't do anything - like deletion - besides uploading)
147210
*
148211
* 3. Users didn't set source maps generation
149212
* - we enable 'hidden' source maps generation
150-
* - configure `filesToDeleteAfterUpload` to delete all .map files (we emit a log about this)
213+
* - configure `filesToDeleteAfterUpload` to delete these .map files after the upload (we emit a log about this)
151214
*
152-
* --> only exported for testing
215+
* @internal Only exported for testing.
153216
*/
154217
export function getUpdatedSourceMapSettings(
155218
viteConfig: UserConfig,
156219
sentryPluginOptions?: SentryTanstackStartOptions,
157-
): boolean | 'inline' | 'hidden' {
220+
): boolean | 'inline' | 'hidden' | undefined {
158221
viteConfig.build = viteConfig.build || {};
159222

160223
const viteUserSourceMapSetting = viteConfig.build?.sourcemap;
@@ -183,6 +246,15 @@ export function getUpdatedSourceMapSettings(
183246
}
184247

185248
return viteUserSourceMapSetting;
249+
} else if (hasOutputSourceMapSetting(viteConfig.build)) {
250+
if (debug) {
251+
// eslint-disable-next-line no-console
252+
console.log(
253+
'[Sentry] We discovered `vite.build.rolldownOptions.output.sourcemap`, which overrides `vite.build.sourcemap`. Sentry will keep this source map setting.',
254+
);
255+
}
256+
257+
return undefined;
186258
}
187259

188260
// If the user did not specify a source map setting, we enable 'hidden' by default

0 commit comments

Comments
 (0)