Skip to content

Commit

Permalink
ref(nextjs): Remove sentry field in Next.js config as a means of co…
Browse files Browse the repository at this point in the history
…nfiguration (#10839)
  • Loading branch information
lforst committed Mar 5, 2024
1 parent 8879fd5 commit a818271
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 93 deletions.
39 changes: 39 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,45 @@ The following previously deprecated API has been removed from the `@sentry/nextj
- `IS_BUILD`
- `isBuild`

#### Removal of the `sentry` property in your Next.js options (next.config.js)

With version 8 of the Sentry Next.js SDK, the SDK will no longer support passing Next.js options with a `sentry`
property to `withSentryConfig`. Please use the third argument of `withSentryConfig` to configure the SDK instead:

```ts
// OLD
const nextConfig = {
// Your Next.js options...

sentry: {
// Your Sentry SDK options...
},
};

module.exports = withSentryConfig(nextConfig, {
// Your Sentry Webpack Plugin Options...
});

// NEW
const nextConfig = {
// Your Next.js options...
};

module.exports = withSentryConfig(
nextConfig,
{
// Your Sentry Webpack Plugin Options...
},
{
// Your Sentry SDK options...
},
);
```

The reason for this change is to have one consistent way of defining the SDK options. We hope that this change will
reduce confusion when setting up the SDK, with the upside that the explicit option is properly typed and will therefore
have code completion.

### Astro SDK

#### Removal of `trackHeaders` option for Astro middleware
Expand Down
22 changes: 4 additions & 18 deletions packages/nextjs/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,15 @@ import type { DefinePlugin, WebpackPluginInstance } from 'webpack';

export type SentryWebpackPluginOptions = SentryCliPluginOptions;
export type SentryWebpackPlugin = WebpackPluginInstance & { options: SentryWebpackPluginOptions };

// Export this from here because importing something from Webpack (the library) in `webpack.ts` confuses the heck out of
// madge, which we use for circular dependency checking. We've manually excluded this file from the check (which is
// safe, since it only includes types), so we can import it here without causing madge to fail. See
// https://github.com/pahen/madge/issues/306.
export type { WebpackPluginInstance };

/**
* Overall Nextjs config
*/

// The first argument to `withSentryConfig` (which is the user's next config) may contain a `sentry` key, which we'll
// remove once we've captured it, in order to prevent nextjs from throwing warnings. Since it's only in there
// temporarily, we don't include it in the main `NextConfigObject` or `NextConfigFunction` types.
export type ExportedNextConfig = NextConfigObjectWithSentry | NextConfigFunctionWithSentry;

export type NextConfigObjectWithSentry = NextConfigObject & {
sentry?: UserSentryOptions;
};

export type NextConfigFunctionWithSentry = (
phase: string,
defaults: { defaultConfig: NextConfigObject },
) => NextConfigObjectWithSentry | PromiseLike<NextConfigObjectWithSentry>;
// The first argument to `withSentryConfig` (which is the user's next config).
export type ExportedNextConfig = NextConfigObject | NextConfigFunction;

// Vendored from Next.js (this type is not complete - extend if necessary)
type NextRewrite = {
Expand Down Expand Up @@ -62,7 +48,7 @@ export type NextConfigObject = {
>;
};

export type UserSentryOptions = {
export type SentryBuildtimeOptions = {
/**
* Override the SDK's default decision about whether or not to enable to the Sentry webpack plugin for server files.
* Note that `false` forces the plugin to be enabled, even in situations where it's not recommended.
Expand Down
14 changes: 7 additions & 7 deletions packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import type {
BuildContext,
EntryPropertyObject,
NextConfigObject,
SentryBuildtimeOptions,
SentryWebpackPluginOptions,
UserSentryOptions,
WebpackConfigFunction,
WebpackConfigObject,
WebpackConfigObjectWithModuleRules,
Expand Down Expand Up @@ -61,7 +61,7 @@ let showedMissingGlobalErrorWarningMsg = false;
export function constructWebpackConfigFunction(
userNextConfig: NextConfigObject = {},
userSentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions> = {},
userSentryOptions: UserSentryOptions = {},
userSentryOptions: SentryBuildtimeOptions = {},
): WebpackConfigFunction {
// Will be called by nextjs and passed its default webpack configuration and context data about the build (whether
// we're building server or client, whether we're in dev, what version of webpack we're using, etc). Note that
Expand Down Expand Up @@ -511,7 +511,7 @@ function findTranspilationRules(rules: WebpackModuleRule[] | undefined, projectD
async function addSentryToEntryProperty(
currentEntryProperty: WebpackEntryProperty,
buildContext: BuildContext,
userSentryOptions: UserSentryOptions,
userSentryOptions: SentryBuildtimeOptions,
): Promise<EntryPropertyObject> {
// The `entry` entry in a webpack config can be a string, array of strings, object, or function. By default, nextjs
// sets it to an async function which returns the promise of an object of string arrays. Because we don't know whether
Expand Down Expand Up @@ -725,7 +725,7 @@ function shouldAddSentryToEntryPoint(entryPointName: string, runtime: 'node' | '
export function getWebpackPluginOptions(
buildContext: BuildContext,
userPluginOptions: Partial<SentryWebpackPluginOptions>,
userSentryOptions: UserSentryOptions,
userSentryOptions: SentryBuildtimeOptions,
): SentryWebpackPluginOptions {
const { buildId, isServer, config, dir: projectDir } = buildContext;
const userNextConfig = config as NextConfigObject;
Expand Down Expand Up @@ -884,7 +884,7 @@ export function getWebpackPluginOptions(
}

/** Check various conditions to decide if we should run the plugin */
function shouldEnableWebpackPlugin(buildContext: BuildContext, userSentryOptions: UserSentryOptions): boolean {
function shouldEnableWebpackPlugin(buildContext: BuildContext, userSentryOptions: SentryBuildtimeOptions): boolean {
const { isServer } = buildContext;
const { disableServerWebpackPlugin, disableClientWebpackPlugin } = userSentryOptions;

Expand All @@ -900,7 +900,7 @@ function shouldEnableWebpackPlugin(buildContext: BuildContext, userSentryOptions
/** Handle warning messages about `hideSourceMaps` option. Can be removed in v9 or v10 (or whenever we consider that
* enough people will have upgraded the SDK that the warning about the default in v8 - currently commented out - is
* overkill). */
function handleSourcemapHidingOptionWarning(userSentryOptions: UserSentryOptions, isServer: boolean): void {
function handleSourcemapHidingOptionWarning(userSentryOptions: SentryBuildtimeOptions, isServer: boolean): void {
// This is nextjs's own logging formatting, vendored since it's not exported. See
// https://github.com/vercel/next.js/blob/c3ceeb03abb1b262032bd96457e224497d3bbcef/packages/next/build/output/log.ts#L3-L11
// and
Expand Down Expand Up @@ -969,7 +969,7 @@ function setUpModuleRules(newConfig: WebpackConfigObject): WebpackConfigObjectWi
function addValueInjectionLoader(
newConfig: WebpackConfigObjectWithModuleRules,
userNextConfig: NextConfigObject,
userSentryOptions: UserSentryOptions,
userSentryOptions: SentryBuildtimeOptions,
buildContext: BuildContext,
sentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions>,
): void {
Expand Down
56 changes: 27 additions & 29 deletions packages/nextjs/src/config/withSentryConfig.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,62 @@
import { isThenable } from '@sentry/utils';

import type {
ExportedNextConfig,
ExportedNextConfig as NextConfig,
NextConfigFunction,
NextConfigObject,
NextConfigObjectWithSentry,
SentryBuildtimeOptions,
SentryWebpackPluginOptions,
UserSentryOptions,
} from './types';
import { constructWebpackConfigFunction } from './webpack';

let showedExportModeTunnelWarning = false;

/**
* Add Sentry options to the config to be exported from the user's `next.config.js` file.
* Modifies the passed in Next.js configuration with automatic build-time instrumentation and source map upload.
*
* @param exportedUserNextConfig The existing config to be exported prior to adding Sentry
* @param userSentryWebpackPluginOptions Configuration for SentryWebpackPlugin
* @param sentryOptions Optional additional options to add as alternative to `sentry` property of config
* @param nextConfig A Next.js configuration object, as usually exported in `next.config.js` or `next.config.mjs`.
* @param sentryWebpackPluginOptions Options to configure the automatically included Sentry Webpack Plugin for source maps and release management in Sentry.
* @param sentryBuildtimeOptions Additional options to configure instrumentation and
* @returns The modified config to be exported
*/
export function withSentryConfig(
exportedUserNextConfig: ExportedNextConfig = {},
userSentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions> = {},
sentryOptions?: UserSentryOptions,
nextConfig: NextConfig = {},
sentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions> = {},
sentryBuildtimeOptions: SentryBuildtimeOptions = {},
): NextConfigFunction | NextConfigObject {
if (typeof exportedUserNextConfig === 'function') {
if (typeof nextConfig === 'function') {
return function (this: unknown, ...webpackConfigFunctionArgs: unknown[]): ReturnType<NextConfigFunction> {
const maybeUserNextConfigObject: NextConfigObjectWithSentry = exportedUserNextConfig.apply(
this,
webpackConfigFunctionArgs,
);
const maybePromiseNextConfig: ReturnType<typeof nextConfig> = nextConfig.apply(this, webpackConfigFunctionArgs);

if (isThenable(maybeUserNextConfigObject)) {
return maybeUserNextConfigObject.then(function (userNextConfigObject: NextConfigObjectWithSentry) {
const userSentryOptions = { ...userNextConfigObject.sentry, ...sentryOptions };
return getFinalConfigObject(userNextConfigObject, userSentryOptions, userSentryWebpackPluginOptions);
if (isThenable(maybePromiseNextConfig)) {
return maybePromiseNextConfig.then(promiseResultNextConfig => {
return getFinalConfigObject(promiseResultNextConfig, sentryBuildtimeOptions, sentryWebpackPluginOptions);
});
}

// Reassign for naming-consistency sake.
const userNextConfigObject = maybeUserNextConfigObject;
const userSentryOptions = { ...userNextConfigObject.sentry, ...sentryOptions };
return getFinalConfigObject(userNextConfigObject, userSentryOptions, userSentryWebpackPluginOptions);
return getFinalConfigObject(maybePromiseNextConfig, sentryBuildtimeOptions, sentryWebpackPluginOptions);
};
} else {
const userSentryOptions = { ...exportedUserNextConfig.sentry, ...sentryOptions };
return getFinalConfigObject(exportedUserNextConfig, userSentryOptions, userSentryWebpackPluginOptions);
return getFinalConfigObject(nextConfig, sentryBuildtimeOptions, sentryWebpackPluginOptions);
}
}

// Modify the materialized object form of the user's next config by deleting the `sentry` property and wrapping the
// `webpack` property
function getFinalConfigObject(
incomingUserNextConfigObject: NextConfigObjectWithSentry,
userSentryOptions: UserSentryOptions,
incomingUserNextConfigObject: NextConfigObject,
userSentryOptions: SentryBuildtimeOptions,
userSentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions>,
): NextConfigObject {
// Next 12.2.3+ warns about non-canonical properties on `userNextConfig`.
delete incomingUserNextConfigObject.sentry;
if ('sentry' in incomingUserNextConfigObject) {
// eslint-disable-next-line no-console
console.warn(
'[@sentry/nextjs] Setting a `sentry` property on the Next.js config is no longer supported. Please use the `sentrySDKOptions` argument of `withSentryConfig` instead.',
);

// Next 12.2.3+ warns about non-canonical properties on `userNextConfig`.
delete incomingUserNextConfigObject.sentry;
}

if (userSentryOptions?.tunnelRoute) {
if (incomingUserNextConfigObject.output === 'export') {
Expand Down
3 changes: 1 addition & 2 deletions packages/nextjs/test/config/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {
EntryPropertyFunction,
ExportedNextConfig,
NextConfigObject,
NextConfigObjectWithSentry,
WebpackConfigObject,
} from '../../src/config/types';

Expand All @@ -26,7 +25,7 @@ export const userNextConfig: NextConfigObject = {
};

/** Mocks of the arguments passed to `withSentryConfig` */
export const exportedNextConfig = userNextConfig as NextConfigObjectWithSentry;
export const exportedNextConfig = userNextConfig;
export const userSentryWebpackPluginConfig = { org: 'squirrelChasers', project: 'simulator' };
process.env.SENTRY_AUTH_TOKEN = 'dogsarebadatkeepingsecrets';
process.env.SENTRY_RELEASE = 'doGsaREgReaT';
Expand Down
11 changes: 5 additions & 6 deletions packages/nextjs/test/config/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
EntryPropertyFunction,
ExportedNextConfig,
NextConfigObject,
SentryBuildtimeOptions,
SentryWebpackPluginOptions,
WebpackConfigObject,
WebpackConfigObjectWithModuleRules,
Expand All @@ -28,8 +29,9 @@ export function materializeFinalNextConfig(
exportedNextConfig: ExportedNextConfig,
userSentryWebpackPluginConfig?: Partial<SentryWebpackPluginOptions>,
runtimePhase?: string,
sentryBuildTimeOptions?: SentryBuildtimeOptions,
): NextConfigObject {
const sentrifiedConfig = withSentryConfig(exportedNextConfig, userSentryWebpackPluginConfig);
const sentrifiedConfig = withSentryConfig(exportedNextConfig, userSentryWebpackPluginConfig, sentryBuildTimeOptions);
let finalConfigValues = sentrifiedConfig;

if (typeof sentrifiedConfig === 'function') {
Expand Down Expand Up @@ -59,6 +61,7 @@ export async function materializeFinalWebpackConfig(options: {
userSentryWebpackPluginConfig?: Partial<SentryWebpackPluginOptions>;
incomingWebpackConfig: WebpackConfigObject;
incomingWebpackBuildContext: BuildContext;
sentryBuildTimeOptions?: SentryBuildtimeOptions;
}): Promise<WebpackConfigObjectWithModuleRules> {
const { exportedNextConfig, userSentryWebpackPluginConfig, incomingWebpackConfig, incomingWebpackBuildContext } =
options;
Expand All @@ -69,15 +72,11 @@ export async function materializeFinalWebpackConfig(options: {
? await exportedNextConfig('phase-production-build', defaultsObject)
: exportedNextConfig;

// extract the `sentry` property as we do in `withSentryConfig`
const { sentry: sentryConfig } = materializedUserNextConfig;
delete materializedUserNextConfig.sentry;

// get the webpack config function we'd normally pass back to next
const webpackConfigFunction = constructWebpackConfigFunction(
materializedUserNextConfig,
userSentryWebpackPluginConfig,
sentryConfig,
options.sentryBuildTimeOptions ?? {},
);

// call it to get concrete values for comparison
Expand Down
34 changes: 18 additions & 16 deletions packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,36 +48,38 @@ describe('constructWebpackConfigFunction()', () => {
});

it("doesn't set devtool if webpack plugin is disabled", () => {
const finalNextConfig = materializeFinalNextConfig({
...exportedNextConfig,
webpack: () =>
({
...serverWebpackConfig,
devtool: 'something-besides-source-map',
}) as any,
sentry: { disableServerWebpackPlugin: true },
});
const finalNextConfig = materializeFinalNextConfig(
{
...exportedNextConfig,
webpack: () =>
({
...serverWebpackConfig,
devtool: 'something-besides-source-map',
}) as any,
},
undefined,
undefined,
{ disableServerWebpackPlugin: true },
);

const finalWebpackConfig = finalNextConfig.webpack?.(serverWebpackConfig, serverBuildContext);

expect(finalWebpackConfig?.devtool).not.toEqual('source-map');
});

it('allows for the use of `hidden-source-map` as `devtool` value for client-side builds', async () => {
const exportedNextConfigHiddenSourceMaps = {
...exportedNextConfig,
sentry: { ...exportedNextConfig.sentry, hideSourceMaps: true },
};

const finalClientWebpackConfig = await materializeFinalWebpackConfig({
exportedNextConfig: exportedNextConfigHiddenSourceMaps,
exportedNextConfig: exportedNextConfig,
incomingWebpackConfig: clientWebpackConfig,
incomingWebpackBuildContext: clientBuildContext,
sentryBuildTimeOptions: { hideSourceMaps: true },
});

const finalServerWebpackConfig = await materializeFinalWebpackConfig({
exportedNextConfig: exportedNextConfigHiddenSourceMaps,
exportedNextConfig: exportedNextConfig,
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: serverBuildContext,
sentryBuildTimeOptions: { hideSourceMaps: true },
});

expect(finalClientWebpackConfig.devtool).toEqual('hidden-source-map');
Expand Down
Loading

0 comments on commit a818271

Please sign in to comment.