Skip to content

fix(nextjs): Stop SentryWebpackPlugin from uploading unnecessary files #3845

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

Merged
merged 7 commits into from
Aug 5, 2021
Merged
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
2 changes: 1 addition & 1 deletion packages/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@sentry/react": "6.10.0",
"@sentry/tracing": "6.10.0",
"@sentry/utils": "6.10.0",
"@sentry/webpack-plugin": "1.15.1",
"@sentry/webpack-plugin": "1.17.1",
"tslib": "^1.9.3"
},
"devDependencies": {
Expand Down
15 changes: 13 additions & 2 deletions packages/nextjs/src/config/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export { SentryCliPluginOptions as SentryWebpackPluginOptions } from '@sentry/webpack-plugin';
import { SentryCliPluginOptions } from '@sentry/webpack-plugin';

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

/**
* Overall Nextjs config
Expand All @@ -9,6 +12,8 @@ export type ExportedNextConfig = NextConfigObject | NextConfigFunction;
export type NextConfigObject = {
// custom webpack options
webpack?: WebpackConfigFunction;
// whether to build serverless functions for all pages, not just API routes
target?: 'server' | 'experimental-serverless-trace';
sentry?: {
disableServerWebpackPlugin?: boolean;
disableClientWebpackPlugin?: boolean;
Expand Down Expand Up @@ -43,7 +48,13 @@ export type WebpackConfigObject = {
};

// Information about the current build environment
export type BuildContext = { dev: boolean; isServer: boolean; buildId: string; dir: string };
export type BuildContext = {
dev: boolean;
isServer: boolean;
buildId: string;
dir: string;
config: Partial<NextConfigObject>;
};

/**
* Webpack `entry` config
Expand Down
90 changes: 64 additions & 26 deletions packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,6 @@ export { SentryWebpackPlugin };
// TODO: merge default SentryWebpackPlugin include with their SentryWebpackPlugin include
// TODO: drop merged keys from override check? `includeDefaults` option?

const defaultSentryWebpackPluginOptions = dropUndefinedKeys({
url: process.env.SENTRY_URL,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
configFile: 'sentry.properties',
stripPrefix: ['webpack://_N_E/'],
urlPrefix: `~/_next`,
include: '.next/',
ignore: ['.next/cache', 'server/ssr-module-cache.js', 'static/*/_ssgManifest.js', 'static/*/_buildManifest.js'],
});

/**
* Construct the function which will be used as the nextjs config's `webpack` value.
*
Expand Down Expand Up @@ -88,18 +76,11 @@ export function constructWebpackConfigFunction(
newConfig.devtool = 'source-map';
}

checkWebpackPluginOverrides(userSentryWebpackPluginOptions);

newConfig.plugins = newConfig.plugins || [];
newConfig.plugins.push(
// @ts-ignore Our types for the plugin are messed up somehow - TS wants this to be `SentryWebpackPlugin.default`,
// but that's not actually a thing
new SentryWebpackPlugin({
dryRun: buildContext.dev,
release: getSentryRelease(buildContext.buildId),
...defaultSentryWebpackPluginOptions,
...userSentryWebpackPluginOptions,
}),
new SentryWebpackPlugin(getWebpackPluginOptions(buildContext, userSentryWebpackPluginOptions)),
);
}

Expand Down Expand Up @@ -136,7 +117,7 @@ async function addSentryToEntryProperty(
: getUserConfigFile(buildContext.dir, 'client');

for (const entryPointName in newEntryProperty) {
if (entryPointName === 'pages/_app' || entryPointName.includes('pages/api')) {
if (shouldAddSentryToEntryPoint(entryPointName)) {
// we need to turn the filename into a path so webpack can find it
addFileToExistingEntryPoint(newEntryProperty, entryPointName, `./${userConfigFile}`);
}
Expand Down Expand Up @@ -221,13 +202,15 @@ function addFileToExistingEntryPoint(
* our default options are getting overridden. (Note: If any of our default values is undefined, it won't be included in
* the warning.)
*
* @param userSentryWebpackPluginOptions The user's SentryWebpackPlugin options
* @param defaultOptions Default SentryWebpackPlugin options
* @param userOptions The user's SentryWebpackPlugin options
*/
function checkWebpackPluginOverrides(userSentryWebpackPluginOptions: Partial<SentryWebpackPluginOptions>): void {
function checkWebpackPluginOverrides(
defaultOptions: SentryWebpackPluginOptions,
userOptions: Partial<SentryWebpackPluginOptions>,
): void {
// warn if any of the default options for the webpack plugin are getting overridden
const sentryWebpackPluginOptionOverrides = Object.keys(defaultSentryWebpackPluginOptions)
.concat('dryrun')
.filter(key => key in userSentryWebpackPluginOptions);
const sentryWebpackPluginOptionOverrides = Object.keys(defaultOptions).filter(key => key in userOptions);
if (sentryWebpackPluginOptionOverrides.length > 0) {
logger.warn(
'[Sentry] You are overriding the following automatically-set SentryWebpackPlugin config options:\n' +
Expand All @@ -236,3 +219,58 @@ function checkWebpackPluginOverrides(userSentryWebpackPluginOptions: Partial<Sen
);
}
}

/**
* Determine if this is an entry point into which both `Sentry.init()` code and the release value should be injected
*
* @param entryPointName The name of the entry point in question
* @returns `true` if sentry code should be injected, and `false` otherwise
*/
function shouldAddSentryToEntryPoint(entryPointName: string): boolean {
return entryPointName === 'pages/_app' || entryPointName.includes('pages/api');
}

/**
* Combine default and user-provided SentryWebpackPlugin options, accounting for whether we're building server files or
* client files.
*
* @param buildContext Nexjs-provided data about the current build
* @param userPluginOptions User-provided SentryWebpackPlugin options
* @returns Final set of combined options
*/
function getWebpackPluginOptions(
buildContext: BuildContext,
userPluginOptions: Partial<SentryWebpackPluginOptions>,
): SentryWebpackPluginOptions {
const { isServer, dir: projectDir, buildId, dev: isDev, config: nextConfig } = buildContext;

const isServerless = nextConfig.target === 'experimental-serverless-trace';
const hasSentryProperties = fs.existsSync(path.resolve(projectDir, 'sentry.properties'));

const serverInclude = isServerless
? [{ paths: ['.next/serverless/'], urlPrefix: '~/_next/serverless' }]
: [
{ paths: ['.next/server/chunks/'], urlPrefix: '~/_next/server/chunks' },
{ paths: ['.next/server/pages/'], urlPrefix: '~/_next/server/pages' },
];
const clientInclude = [{ paths: ['.next/static/chunks/pages'], urlPrefix: '~/_next/static/chunks/pages' }];

const defaultPluginOptions = dropUndefinedKeys({
include: isServer ? serverInclude : clientInclude,
ignore: [],
url: process.env.SENTRY_URL,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
configFile: hasSentryProperties ? 'sentry.properties' : undefined,
stripPrefix: ['webpack://_N_E/'],
urlPrefix: `~/_next`,
entries: shouldAddSentryToEntryPoint,
release: getSentryRelease(buildId),
dryRun: isDev,
});

checkWebpackPluginOverrides(defaultPluginOptions, userPluginOptions);

return { ...defaultPluginOptions, ...userPluginOptions };
}
94 changes: 86 additions & 8 deletions packages/nextjs/test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
EntryPropertyFunction,
ExportedNextConfig,
NextConfigObject,
SentryWebpackPlugin as SentryWebpackPluginType,
SentryWebpackPluginOptions,
WebpackConfigObject,
} from '../src/config/types';
Expand Down Expand Up @@ -44,7 +45,9 @@ const userNextConfig = {
}),
}),
};
const userSentryWebpackPluginConfig = { org: 'squirrelChasers', project: 'simulator', include: './thirdPartyMaps' };
const userSentryWebpackPluginConfig = { org: 'squirrelChasers', project: 'simulator' };
process.env.SENTRY_AUTH_TOKEN = 'dogsarebadatkeepingsecrets';
process.env.SENTRY_RELEASE = 'doGsaREgReaT';

/** Mocks of the arguments passed to the result of `withSentryConfig` (when it's a function). */
const runtimePhase = 'ball-fetching';
Expand Down Expand Up @@ -80,10 +83,12 @@ const clientWebpackConfig = {
target: 'web',
context: '/Users/Maisey/projects/squirrelChasingSimulator',
};

const baseBuildContext = {
dev: false,
buildId: 'doGsaREgReaT',
buildId: 'sItStAyLiEdOwN',
dir: '/Users/Maisey/projects/squirrelChasingSimulator',
config: { target: 'server' as const },
};
const serverBuildContext = { isServer: true, ...baseBuildContext };
const clientBuildContext = { isServer: false, ...baseBuildContext };
Expand All @@ -100,7 +105,7 @@ const clientBuildContext = { isServer: false, ...baseBuildContext };
*/
function materializeFinalNextConfig(
userNextConfig: ExportedNextConfig,
userSentryWebpackPluginConfig?: SentryWebpackPluginOptions,
userSentryWebpackPluginConfig?: Partial<SentryWebpackPluginOptions>,
): NextConfigObject {
const sentrifiedConfig = withSentryConfig(userNextConfig, userSentryWebpackPluginConfig);
let finalConfigValues = sentrifiedConfig;
Expand Down Expand Up @@ -131,7 +136,7 @@ function materializeFinalNextConfig(
*/
async function materializeFinalWebpackConfig(options: {
userNextConfig: ExportedNextConfig;
userSentryWebpackPluginConfig?: SentryWebpackPluginOptions;
userSentryWebpackPluginConfig?: Partial<SentryWebpackPluginOptions>;
incomingWebpackConfig: WebpackConfigObject;
incomingWebpackBuildContext: BuildContext;
}): Promise<WebpackConfigObject> {
Expand Down Expand Up @@ -311,12 +316,40 @@ describe('webpack config', () => {
});

describe('Sentry webpack plugin config', () => {
it('includes expected properties', () => {
// TODO
it('includes expected properties', async () => {
// also, can pull from either env or user config (see notes on specific properties below)
const finalWebpackConfig = await materializeFinalWebpackConfig({
userNextConfig,
userSentryWebpackPluginConfig,
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: serverBuildContext,
});

expect(finalWebpackConfig.plugins?.[0].options).toEqual(
expect.objectContaining({
include: expect.any(Array), // default, tested separately elsewhere
ignore: [], // default
org: 'squirrelChasers', // from user webpack plugin config
project: 'simulator', // from user webpack plugin config
authToken: 'dogsarebadatkeepingsecrets', // picked up from env
stripPrefix: ['webpack://_N_E/'], // default
urlPrefix: `~/_next`, // default
entries: expect.any(Function), // default, tested separately elsewhere
release: 'doGsaREgReaT', // picked up from env
dryRun: false, // based on buildContext.dev being false
}),
);
});

it('preserves unrelated plugin config options', () => {
// TODO
it('preserves unrelated plugin config options', async () => {
const finalWebpackConfig = await materializeFinalWebpackConfig({
userNextConfig,
userSentryWebpackPluginConfig: { ...userSentryWebpackPluginConfig, debug: true },
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: serverBuildContext,
});

expect((finalWebpackConfig.plugins?.[0].options as SentryWebpackPluginOptions).debug).toEqual(true);
});

it('warns when overriding certain default values', () => {
Expand All @@ -327,6 +360,51 @@ describe('Sentry webpack plugin config', () => {
// do we even want to do this?
});

describe('Sentry webpack plugin `include` option', () => {
it('has the correct value when building client bundles', async () => {
const finalWebpackConfig = await materializeFinalWebpackConfig({
userNextConfig,
incomingWebpackConfig: clientWebpackConfig,
incomingWebpackBuildContext: clientBuildContext,
});

const sentryWebpackPlugin = finalWebpackConfig.plugins?.[0] as SentryWebpackPluginType;

expect(sentryWebpackPlugin.options?.include).toEqual([
{ paths: ['.next/static/chunks/pages'], urlPrefix: '~/_next/static/chunks/pages' },
]);
});

it('has the correct value when building serverless server bundles', async () => {
const finalWebpackConfig = await materializeFinalWebpackConfig({
userNextConfig,
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: { ...serverBuildContext, config: { target: 'experimental-serverless-trace' } },
});

const sentryWebpackPlugin = finalWebpackConfig.plugins?.[0] as SentryWebpackPluginType;

expect(sentryWebpackPlugin.options?.include).toEqual([
{ paths: ['.next/serverless/'], urlPrefix: '~/_next/serverless' },
]);
});

it('has the correct value when building serverful server bundles', async () => {
const finalWebpackConfig = await materializeFinalWebpackConfig({
userNextConfig,
incomingWebpackConfig: serverWebpackConfig,
incomingWebpackBuildContext: serverBuildContext,
});

const sentryWebpackPlugin = finalWebpackConfig.plugins?.[0] as SentryWebpackPluginType;

expect(sentryWebpackPlugin.options?.include).toEqual([
{ paths: ['.next/server/chunks/'], urlPrefix: '~/_next/server/chunks' },
{ paths: ['.next/server/pages/'], urlPrefix: '~/_next/server/pages' },
]);
});
});

it('allows SentryWebpackPlugin to be turned off for client code (independent of server code)', () => {
const clientFinalNextConfig = materializeFinalNextConfig({
...userNextConfig,
Expand Down
18 changes: 9 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2624,10 +2624,10 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=

"@sentry/cli@^1.64.1":
version "1.66.0"
resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.66.0.tgz#0526f1bc1c0570ce72ed817190af92f3b63a2e9a"
integrity sha512-2pZ+JHnvKqwyJWcGkKg/gCM/zURYronAnruBNllI+rH2g5IL0N90deMmjB1xcqXS66J222+MPTtWrGEK1Vl0/w==
"@sentry/cli@^1.68.0":
version "1.68.0"
resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.68.0.tgz#2ced8fac67ee01e746a45e8ee45a518d4526937e"
integrity sha512-zc7+cxKDqpHLREGJKRH6KwE8fZW8bnczg3OLibJ0czleXoWPdAuOK1Xm1BTMcOnaXfg3VKAh0rI7S1PTdj+SrQ==
dependencies:
https-proxy-agent "^5.0.0"
mkdirp "^0.5.5"
Expand All @@ -2636,12 +2636,12 @@
progress "^2.0.3"
proxy-from-env "^1.1.0"

"@sentry/webpack-plugin@1.15.1":
version "1.15.1"
resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-1.15.1.tgz#deb014fce8c1b51811100f25ec9050dd03addd9b"
integrity sha512-/Z06MJDXyWcN2+CbeDTMDwVzySjgZWQajOke773TvpkgqdtkeT1eYBsA+pfsje+ZE1prEgrZdlH/L9HdM1odnQ==
"@sentry/webpack-plugin@1.17.1":
version "1.17.1"
resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-1.17.1.tgz#1b3ebbe9991e4d77125ace2b24594059a088268a"
integrity sha512-L47a0hxano4a+9jbvQSBzHCT1Ph8fYAvGGUvFg8qc69yXS9si5lXRNIH/pavN6mqJjhQjAcEsEp+vxgvT4xZDQ==
dependencies:
"@sentry/cli" "^1.64.1"
"@sentry/cli" "^1.68.0"

"@simple-dom/interface@^1.4.0":
version "1.4.0"
Expand Down