Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
123 changes: 106 additions & 17 deletions packages/nextjs/src/config/getBuildPluginOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,125 @@ import * as path from 'path';
import type { SentryBuildOptions } from './types';

/**
* Get Sentry Build Plugin options for the runAfterProductionCompile hook.
* Get Sentry Build Plugin options for both webpack and turbopack builds.
* These options can be used in two ways:
* 1. The build can be done in a single operation after the production build completes
* 2. The build can be done in multiple operations, one for each webpack build
*/
export function getBuildPluginOptions({
sentryBuildOptions,
releaseName,
distDirAbsPath,
buildTool,
useRunAfterProductionCompileHook,
}: {
sentryBuildOptions: SentryBuildOptions;
releaseName: string | undefined;
distDirAbsPath: string;
buildTool:
| 'webpack-client'
| 'webpack-nodejs'
| 'webpack-edge'
| 'after-production-compile-webpack'
| 'after-production-compile-turbopack';
useRunAfterProductionCompileHook?: boolean; // Whether the user has opted into using the experimental hook
}): SentryBuildPluginOptions {
const sourcemapUploadAssets: string[] = [];
const sourcemapUploadIgnore: string[] = [];

const filesToDeleteAfterUpload: string[] = [];

// We need to convert paths to posix because Glob patterns use `\` to escape
// glob characters. This clashes with Windows path separators.
// See: https://www.npmjs.com/package/glob
const normalizedDistDirAbsPath = distDirAbsPath.replace(/\\/g, '/');

sourcemapUploadAssets.push(
path.posix.join(normalizedDistDirAbsPath, '**'), // Next.js build output
);
if (sentryBuildOptions.sourcemaps?.deleteSourcemapsAfterUpload) {
filesToDeleteAfterUpload.push(
path.posix.join(normalizedDistDirAbsPath, '**', '*.js.map'),
path.posix.join(normalizedDistDirAbsPath, '**', '*.mjs.map'),
path.posix.join(normalizedDistDirAbsPath, '**', '*.cjs.map'),
const loggerPrefix = {
'webpack-nodejs': '[@sentry/nextjs - Node.js]',
'webpack-edge': '[@sentry/nextjs - Edge]',
'webpack-client': '[@sentry/nextjs - Client]',
'after-production-compile-webpack': '[@sentry/nextjs - After Production Compile (Webpack)]',
'after-production-compile-turbopack': '[@sentry/nextjs - After Production Compile (Turbopack)]',
}[buildTool];

if (buildTool.startsWith('after-production-compile')) {
sourcemapUploadAssets.push(
path.posix.join(normalizedDistDirAbsPath, 'server', '**'), // Standard output location for server builds
path.posix.join(normalizedDistDirAbsPath, 'serverless', '**'), // Legacy output location for serverless Next.js
);

if (buildTool === 'after-production-compile-turbopack') {
sourcemapUploadAssets.push(path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', '**'));
} else {
// Webpack client builds
if (sentryBuildOptions.widenClientFileUpload) {
sourcemapUploadAssets.push(path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', '**'));
} else {
sourcemapUploadAssets.push(
path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'pages', '**'),
path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'app', '**'),
);
}
}

if (sentryBuildOptions.sourcemaps?.deleteSourcemapsAfterUpload) {
filesToDeleteAfterUpload.push(
// We only care to delete client bundle source maps because they would be the ones being served.
// Removing the server source maps crashes Vercel builds for (thus far) unknown reasons:
// https://github.com/getsentry/sentry-javascript/issues/13099
path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.js.map'),
path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.mjs.map'),
path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.cjs.map'),
);
}
} else {
if (buildTool === 'webpack-nodejs' || buildTool === 'webpack-edge') {
// Webpack server builds
sourcemapUploadAssets.push(
path.posix.join(normalizedDistDirAbsPath, 'server', '**'), // Standard output location for server builds
path.posix.join(normalizedDistDirAbsPath, 'serverless', '**'), // Legacy output location for serverless Next.js
);
} else {
// Webpack client builds
if (sentryBuildOptions.widenClientFileUpload) {
sourcemapUploadAssets.push(path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', '**'));
} else {
sourcemapUploadAssets.push(
path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'pages', '**'),
path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'app', '**'),
);
}

// File deletion for webpack client builds
// If the user has opted into using the experimental hook, we delete the source maps in the hook instead
if (sentryBuildOptions.sourcemaps?.deleteSourcemapsAfterUpload && !useRunAfterProductionCompileHook) {
filesToDeleteAfterUpload.push(
// We only care to delete client bundle source maps because they would be the ones being served.
// Removing the server source maps crashes Vercel builds for (thus far) unknown reasons:
// https://github.com/getsentry/sentry-javascript/issues/13099
path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.js.map'),
path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.mjs.map'),
path.posix.join(normalizedDistDirAbsPath, 'static', '**', '*.cjs.map'),
);
}
}
}

// We want to include main-* files if widenClientFileUpload is true as they have proven to be useful
if (!sentryBuildOptions.widenClientFileUpload) {
sourcemapUploadIgnore.push(path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'main-*'));
}

// Always ignore framework, polyfills, and webpack files
sourcemapUploadIgnore.push(
path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'framework-*'),
path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'framework.*'),
path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'polyfills-*'),
path.posix.join(normalizedDistDirAbsPath, 'static', 'chunks', 'webpack-*'),
);

// If the user has opted into using the experimental hook, we skip sourcemap uploads in the plugin
const shouldSkipSourcemapsUpload = useRunAfterProductionCompileHook && buildTool.startsWith('webpack');

return {
authToken: sentryBuildOptions.authToken,
headers: sentryBuildOptions.headers,
Expand All @@ -43,14 +130,16 @@ export function getBuildPluginOptions({
telemetry: sentryBuildOptions.telemetry,
debug: sentryBuildOptions.debug,
errorHandler: sentryBuildOptions.errorHandler,
reactComponentAnnotation: {
...sentryBuildOptions.reactComponentAnnotation,
...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.reactComponentAnnotation,
},
reactComponentAnnotation: buildTool.startsWith('after-production-compile')
? undefined
: {
...sentryBuildOptions.reactComponentAnnotation,
...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.reactComponentAnnotation,
},
silent: sentryBuildOptions.silent,
url: sentryBuildOptions.sentryUrl,
sourcemaps: {
disable: sentryBuildOptions.sourcemaps?.disable,
disable: shouldSkipSourcemapsUpload ? true : (sentryBuildOptions.sourcemaps?.disable ?? false),
rewriteSources(source) {
if (source.startsWith('webpack://_N_E/')) {
return source.replace('webpack://_N_E/', '');
Expand All @@ -62,7 +151,7 @@ export function getBuildPluginOptions({
},
assets: sentryBuildOptions.sourcemaps?.assets ?? sourcemapUploadAssets,
ignore: sentryBuildOptions.sourcemaps?.ignore ?? sourcemapUploadIgnore,
filesToDeleteAfterUpload,
filesToDeleteAfterUpload: filesToDeleteAfterUpload.length > 0 ? filesToDeleteAfterUpload : undefined,
...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.sourcemaps,
},
release:
Expand All @@ -87,7 +176,7 @@ export function getBuildPluginOptions({
...sentryBuildOptions.bundleSizeOptimizations,
},
_metaOptions: {
loggerPrefixOverride: '[@sentry/nextjs]',
loggerPrefixOverride: loggerPrefix,
telemetry: {
metaFramework: 'nextjs',
},
Expand Down
28 changes: 11 additions & 17 deletions packages/nextjs/src/config/handleRunAfterProductionCompile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ export async function handleRunAfterProductionCompile(
{ releaseName, distDir, buildTool }: { releaseName?: string; distDir: string; buildTool: 'webpack' | 'turbopack' },
sentryBuildOptions: SentryBuildOptions,
): Promise<void> {
// We don't want to do anything for webpack at this point because the plugin already handles this
// TODO: Actually implement this for webpack as well
if (buildTool === 'webpack') {
return;
}

if (sentryBuildOptions.debug) {
// eslint-disable-next-line no-console
console.debug('[@sentry/nextjs] Running runAfterProductionCompile logic.');
Expand All @@ -36,17 +30,17 @@ export async function handleRunAfterProductionCompile(
return;
}

const sentryBuildPluginManager = createSentryBuildPluginManager(
getBuildPluginOptions({
sentryBuildOptions,
releaseName,
distDirAbsPath: distDir,
}),
{
buildTool,
loggerPrefix: '[@sentry/nextjs]',
},
);
const options = getBuildPluginOptions({
sentryBuildOptions,
releaseName,
distDirAbsPath: distDir,
buildTool: `after-production-compile-${buildTool}`,
});

const sentryBuildPluginManager = createSentryBuildPluginManager(options, {
buildTool,
loggerPrefix: '[@sentry/nextjs - After Production Compile]',
});

await sentryBuildPluginManager.telemetry.emitBundlerPluginExecutionSignal();
await sentryBuildPluginManager.createRelease();
Expand Down
39 changes: 30 additions & 9 deletions packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as fs from 'fs';
import * as path from 'path';
import { sync as resolveSync } from 'resolve';
import type { VercelCronsConfig } from '../common/types';
import { getBuildPluginOptions } from './getBuildPluginOptions';
import type { RouteManifest } from './manifest/types';
// Note: If you need to import a type from Webpack, do it in `types.ts` and export it from there. Otherwise, our
// circular dependency check thinks this file is importing from itself. See https://github.com/pahen/madge/issues/306.
Expand All @@ -22,7 +23,6 @@ import type {
WebpackEntryProperty,
} from './types';
import { getNextjsVersion } from './util';
import { getWebpackPluginOptions } from './webpackPluginOptions';

// Next.js runs webpack 3 times, once for the client, the server, and for edge. Because we don't want to print certain
// warnings 3 times, we keep track of them here.
Expand All @@ -40,13 +40,21 @@ let showedMissingGlobalErrorWarningMsg = false;
* @param userSentryOptions The user's SentryWebpackPlugin config, as passed to `withSentryConfig`
* @returns The function to set as the nextjs config's `webpack` value
*/
export function constructWebpackConfigFunction(
userNextConfig: NextConfigObject = {},
userSentryOptions: SentryBuildOptions = {},
releaseName: string | undefined,
routeManifest: RouteManifest | undefined,
nextJsVersion: string | undefined,
): WebpackConfigFunction {
export function constructWebpackConfigFunction({
userNextConfig = {},
userSentryOptions = {},
releaseName,
routeManifest,
nextJsVersion,
useRunAfterProductionCompileHook,
}: {
userNextConfig: NextConfigObject;
userSentryOptions: SentryBuildOptions;
releaseName: string | undefined;
routeManifest: RouteManifest | undefined;
nextJsVersion: string | undefined;
useRunAfterProductionCompileHook: boolean | undefined;
}): 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
// `incomingConfig` and `buildContext` are referred to as `config` and `options` in the nextjs docs.
Expand Down Expand Up @@ -408,9 +416,22 @@ export function constructWebpackConfigFunction(
}

newConfig.plugins = newConfig.plugins || [];
const { config: userNextConfig, dir, nextRuntime } = buildContext;
const buildTool = isServer ? (nextRuntime === 'edge' ? 'webpack-edge' : 'webpack-nodejs') : 'webpack-client';
const projectDir = dir.replace(/\\/g, '/');
const distDir = (userNextConfig as NextConfigObject).distDir?.replace(/\\/g, '/') ?? '.next';
const distDirAbsPath = path.posix.join(projectDir, distDir);

const sentryWebpackPluginInstance = sentryWebpackPlugin(
getWebpackPluginOptions(buildContext, userSentryOptions, releaseName),
getBuildPluginOptions({
sentryBuildOptions: userSentryOptions,
releaseName,
distDirAbsPath,
buildTool,
useRunAfterProductionCompileHook,
}),
);

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
sentryWebpackPluginInstance._name = 'sentry-webpack-plugin'; // For tests and debugging. Serves no other purpose.
newConfig.plugins.push(sentryWebpackPluginInstance);
Expand Down
Loading
Loading