Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/nextjs/src/config/getBuildPluginOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ function createReleaseConfig(
vcsRemote: sentryBuildOptions.release?.vcsRemote,
setCommits: sentryBuildOptions.release?.setCommits,
deploy: sentryBuildOptions.release?.deploy,
...sentryBuildOptions.webpack?.unstable_sentryWebpackPluginOptions?.release,
};
}

Expand Down Expand Up @@ -333,7 +332,6 @@ export function getBuildPluginOptions({
? undefined
: {
...sentryBuildOptions.webpack?.reactComponentAnnotation,
...sentryBuildOptions.webpack?.unstable_sentryWebpackPluginOptions?.reactComponentAnnotation,
},
silent: sentryBuildOptions.silent,
url: sentryBuildOptions.sentryUrl,
Expand All @@ -343,18 +341,18 @@ export function getBuildPluginOptions({
assets: sentryBuildOptions.sourcemaps?.assets ?? sourcemapUploadAssets,
ignore: finalIgnorePatterns,
filesToDeleteAfterUpload,
...sentryBuildOptions.webpack?.unstable_sentryWebpackPluginOptions?.sourcemaps,
resolveSourceMap: sentryBuildOptions.sourcemaps?.resolveSourceMap,
},
release: createReleaseConfig(releaseName, sentryBuildOptions),
bundleSizeOptimizations: {
...sentryBuildOptions.bundleSizeOptimizations,
},
moduleMetadata: sentryBuildOptions.moduleMetadata,
_metaOptions: {
loggerPrefixOverride: loggerPrefix,
telemetry: {
metaFramework: 'nextjs',
},
},
...sentryBuildOptions.webpack?.unstable_sentryWebpackPluginOptions,
};
}
57 changes: 27 additions & 30 deletions packages/nextjs/src/config/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { GLOBAL_OBJ } from '@sentry/core';
import type { SentryWebpackPluginOptions } from '@sentry/bundler-plugins/webpack';
import type {
GLOBAL_OBJ,
ModuleMetadata,
ModuleMetadataCallback,
ReactComponentAnnotationOptions,
ResolveSourceMapHook,
} from '@sentry/core';

// The first argument to `withSentryConfig` (which is the user's next config).
export type ExportedNextConfig = NextConfigObject | NextConfigFunction;
Expand Down Expand Up @@ -146,32 +151,14 @@ export type SentryBuildWebpackOptions = {
excludeReplayCompressionWorker?: boolean;
};

/**
* Options to be passed directly to the Sentry Webpack Plugin (`@sentry/bundler-plugins/webpack`) that ships with the Sentry SDK.
* You can use this option to override any options the SDK passes to the Webpack plugin.
*
* Please note that this option is unstable and may change in a breaking way in any release.
*/
unstable_sentryWebpackPluginOptions?: SentryWebpackPluginOptions;

/**
* Options related to react component name annotations.
* Disabled by default, unless a value is set for this option.
* When enabled, your app's DOM will automatically be annotated during build-time with their respective component names.
* This will unlock the capability to search for Replays in Sentry by component name, as well as see component names in breadcrumbs and performance monitoring.
* Please note that this feature is not currently supported by the esbuild bundler plugins, and will only annotate React components
*/
reactComponentAnnotation?: {
/**
* Whether the component name annotate plugin should be enabled or not.
*/
enabled?: boolean;

/**
* A list of strings representing the names of components to ignore. The plugin will not apply `data-sentry` annotations on the DOM element for these components.
*/
ignoredComponents?: string[];
};
reactComponentAnnotation?: ReactComponentAnnotationOptions;
};

export type SentryBuildOptions = {
Expand Down Expand Up @@ -311,6 +298,16 @@ export type SentryBuildOptions = {
*/
// oxlint-disable-next-line typescript-eslint/no-explicit-any -- matches the bundler plugin's RewriteSourcesHook type
rewriteSources?: (source: string, map: any, context?: { mapDir: string }) => string;

/**
* Hook to customize source map file resolution.
*
* Mostly helpful for complex builds with custom source map generation. For example, if source maps
* are written to a separate directory and the `//# sourceMappingURL=` comment is rewritten to
* something other than a relative path, Sentry is unable to locate the source map for a given
* build artifact. This hook lets you implement the resolution process yourself.
*/
resolveSourceMap?: ResolveSourceMapHook;
};

/**
Expand Down Expand Up @@ -487,6 +484,15 @@ export type SentryBuildOptions = {
*/
applicationKey?: string;

/**
* Metadata that should be associated with the built application.
*
* The metadata is serialized and can be looked up at runtime from within the SDK (for example in
* `beforeSend`, event processors, or the transport), allowing for custom event filtering logic or
* routing of events. Read it at runtime via `moduleMetadataIntegration`.
*/
moduleMetadata?: ModuleMetadata | ModuleMetadataCallback;

/**
* Options to configure various bundle size optimizations related to the Sentry SDK.
*/
Expand Down Expand Up @@ -552,15 +558,6 @@ export type SentryBuildOptions = {
ignoredComponents?: string[];
}; // TODO(v11): remove this option

/**
* Options to be passed directly to the Sentry Webpack Plugin (`@sentry/bundler-plugins/webpack`) that ships with the Sentry Next.js SDK.
* You can use this option to override any options the SDK passes to the webpack plugin.
*
* Please note that this option is unstable and may change in a breaking way in any release.
* @deprecated Use `webpack.unstable_sentryWebpackPluginOptions` instead.
*/
unstable_sentryWebpackPluginOptions?: SentryWebpackPluginOptions; // TODO(v11): remove this option

/**
* Include Next.js-internal code and code from dependencies when uploading source maps.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,6 @@ export function migrateDeprecatedWebpackOptions(userSentryOptions: SentryBuildOp
deprecatedMessage('excludeServerRoutes', 'webpack.excludeServerRoutes'),
);

webpack.unstable_sentryWebpackPluginOptions = withDeprecatedFallback(
webpack.unstable_sentryWebpackPluginOptions,
userSentryOptions.unstable_sentryWebpackPluginOptions,
deprecatedMessage('unstable_sentryWebpackPluginOptions', 'webpack.unstable_sentryWebpackPluginOptions'),
);

webpack.disableSentryConfig = withDeprecatedFallback(
webpack.disableSentryConfig,
userSentryOptions.disableSentryWebpackConfig,
Expand Down
5 changes: 4 additions & 1 deletion packages/nextjs/src/config/withSentryConfig/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isThenable } from '@sentry/core';
import { isThenable, warnOnRemovedBuildOptions } from '@sentry/core';
import type { ExportedNextConfig as NextConfig, NextConfigFunction, SentryBuildOptions } from '../types';
import { DEFAULT_SERVER_EXTERNAL_PACKAGES } from './constants';
import { getFinalConfigObject } from './getFinalConfigObject';
Expand All @@ -15,6 +15,9 @@ export { DEFAULT_SERVER_EXTERNAL_PACKAGES };
* @returns The wrapped Next.js config (same shape as the input)
*/
export function withSentryConfig<C>(nextConfig?: C, sentryBuildOptions: SentryBuildOptions = {}): C {
warnOnRemovedBuildOptions(sentryBuildOptions, ['unstable_sentryWebpackPluginOptions']);
warnOnRemovedBuildOptions(sentryBuildOptions.webpack, ['unstable_sentryWebpackPluginOptions']);

const castNextConfig = (nextConfig as NextConfig) || {};
if (typeof castNextConfig === 'function') {
return function (this: unknown, ...webpackConfigFunctionArgs: unknown[]): ReturnType<NextConfigFunction> {
Expand Down
49 changes: 20 additions & 29 deletions packages/nextjs/test/config/getBuildPluginOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,24 +813,18 @@ describe('getBuildPluginOptions', () => {
});
});

it('merges webpack plugin release options correctly', () => {
it('passes release options through to the plugin', () => {
const sentryBuildOptions: SentryBuildOptions = {
org: 'test-org',
project: 'test-project',
release: {
create: true,
vcsRemote: 'origin',
},
webpack: {
unstable_sentryWebpackPluginOptions: {
release: {
setCommits: {
auto: true,
},
deploy: {
env: 'production',
},
},
setCommits: {
auto: true,
},
deploy: {
env: 'production',
},
},
};
Expand All @@ -842,25 +836,21 @@ describe('getBuildPluginOptions', () => {
buildTool: 'webpack-client',
});

// The webpack.unstable_sentryWebpackPluginOptions.release is spread at the end and may override base properties
expect(result.release).toHaveProperty('setCommits.auto', true);
expect(result.release).toHaveProperty('deploy.env', 'production');
expect(result.release).toHaveProperty('vcsRemote', 'origin');
});
});

describe('react component annotation', () => {
it('merges react component annotation options correctly for webpack builds', () => {
it('passes react component annotation options through for webpack builds', () => {
const sentryBuildOptions: SentryBuildOptions = {
org: 'test-org',
project: 'test-project',
webpack: {
reactComponentAnnotation: {
enabled: true,
},
unstable_sentryWebpackPluginOptions: {
reactComponentAnnotation: {
enabled: false, // This will override the base setting
},
ignoredComponents: ['MyComponent'],
},
},
};
Expand All @@ -872,8 +862,8 @@ describe('getBuildPluginOptions', () => {
buildTool: 'webpack-client',
});

// The unstable options override the base options - in this case enabled should be false
expect(result.reactComponentAnnotation).toHaveProperty('enabled', false);
expect(result.reactComponentAnnotation).toHaveProperty('enabled', true);
expect(result.reactComponentAnnotation).toHaveProperty('ignoredComponents', ['MyComponent']);
});

it('sets react component annotation to undefined for after-production-compile builds', () => {
Expand Down Expand Up @@ -943,17 +933,16 @@ describe('getBuildPluginOptions', () => {
});
});

it('merges unstable webpack plugin options correctly', () => {
it('passes top-level applicationKey, moduleMetadata and sourcemaps through to the plugin', () => {
const resolveSourceMap = (artifactPath: string): string => `${artifactPath}.map`;
const sentryBuildOptions: SentryBuildOptions = {
org: 'test-org',
project: 'test-project',
webpack: {
unstable_sentryWebpackPluginOptions: {
applicationKey: 'test-app-key',
sourcemaps: {
disable: false,
},
},
applicationKey: 'test-app-key',
moduleMetadata: { team: 'sdk' },
sourcemaps: {
disable: false,
resolveSourceMap,
},
};

Expand All @@ -966,8 +955,10 @@ describe('getBuildPluginOptions', () => {

expect(result).toMatchObject({
applicationKey: 'test-app-key',
moduleMetadata: { team: 'sdk' },
sourcemaps: expect.objectContaining({
disable: false,
resolveSourceMap,
}),
});
});
Expand Down
28 changes: 28 additions & 0 deletions packages/nextjs/test/config/withSentryConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,34 @@ const EXPECTED_DEFAULT_EXTERNALS = [
];

describe('withSentryConfig', () => {
// `next.config.js` / `next.config.mjs` get no type checking, so this warning is the only signal
// those users receive that the option is gone.
describe('removed `unstable_sentryWebpackPluginOptions`', () => {
it.each([
['top-level', { unstable_sentryWebpackPluginOptions: { applicationKey: 'my-app' } }],
['nested under `webpack`', { webpack: { unstable_sentryWebpackPluginOptions: { applicationKey: 'my-app' } } }],
])('warns when set %s', (_name, sentryBuildOptions) => {
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

// @ts-expect-error - removed in v11, but JS configs get no type checking
materializeFinalNextConfig(exportedNextConfig, undefined, sentryBuildOptions);

expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('unstable_sentryWebpackPluginOptions'));

consoleWarnSpy.mockRestore();
});

it('does not warn for a config without removed options', () => {
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

materializeFinalNextConfig(exportedNextConfig);

expect(consoleWarnSpy).not.toHaveBeenCalledWith(expect.stringContaining('unstable_'));

consoleWarnSpy.mockRestore();
});
});

it('includes expected properties', () => {
const finalConfig = materializeFinalNextConfig(exportedNextConfig);

Expand Down