From d7c6e9d2b73add717a99a467471cd66e0a0bf8e0 Mon Sep 17 00:00:00 2001 From: Tomas Cerskus Date: Sun, 20 Nov 2022 18:30:33 +0000 Subject: [PATCH 1/3] fix(nextjs): handle assetPrefix --- packages/nextjs/src/config/types.ts | 2 + packages/nextjs/src/config/webpack.ts | 10 ++- .../webpack/sentryWebpackPlugin.test.ts | 83 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 26279d19271c..c3f54023347b 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -30,6 +30,8 @@ export type NextConfigObject = { distDir?: string; // The root at which the nextjs app will be served (defaults to "/") basePath?: string; + // The asset prefix (pathname or full URL) if assets will not be stored at the default Next.js location + assetPrefix?: string; // Config which will be available at runtime publicRuntimeConfig?: { [key: string]: unknown }; // File extensions that count as pages in the `pages/` directory diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 81e38cee6007..a3dfafe69e8b 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -5,6 +5,7 @@ import { default as SentryWebpackPlugin } from '@sentry/webpack-plugin'; import * as chalk from 'chalk'; import * as fs from 'fs'; import * as path from 'path'; +import * as url from 'url'; import { BuildContext, @@ -457,7 +458,14 @@ export function getWebpackPluginOptions( const isWebpack5 = webpack.version.startsWith('5'); const isServerless = userNextConfig.target === 'experimental-serverless-trace'; const hasSentryProperties = fs.existsSync(path.resolve(projectDir, 'sentry.properties')); - const urlPrefix = userNextConfig.basePath ? `~${userNextConfig.basePath}/_next` : '~/_next'; + + let assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || ''; + if (assetPrefix) { + const assertPrefixUrl = url.parse(assetPrefix); + assetPrefix = assertPrefixUrl.pathname || ''; + assetPrefix = assetPrefix.endsWith('/') ? assetPrefix.slice(0, -1) : assetPrefix; + } + const urlPrefix = `~${assetPrefix}/_next`; const serverInclude = isServerless ? [{ paths: [`${distDirAbsPath}/serverless/`], urlPrefix: `${urlPrefix}/serverless` }] diff --git a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts index e1e16a0e7ed1..5f292154e685 100644 --- a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts +++ b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts @@ -295,6 +295,89 @@ describe('Sentry webpack plugin config', () => { }); }); + describe('Sentry webpack plugin `urlPrefix` option with assetPrefix set', () => { + it('has the correct value given a path', async () => { + const exportedNextConfigWithAssetPrefix = { + ...exportedNextConfig, + assetPrefix: '/asset-prefix', + }; + const buildContext = getBuildContext('client', exportedNextConfigWithAssetPrefix); + const finalWebpackConfig = await materializeFinalWebpackConfig({ + exportedNextConfig: exportedNextConfigWithAssetPrefix, + incomingWebpackConfig: clientWebpackConfig, + incomingWebpackBuildContext: buildContext, + }); + + const sentryWebpackPluginInstance = findWebpackPlugin( + finalWebpackConfig, + 'SentryCliPlugin', + ) as SentryWebpackPlugin; + + expect(sentryWebpackPluginInstance.options.urlPrefix).toEqual('~/asset-prefix/_next'); + }); + + it('has the correct value given a path with a leading slash', async () => { + const exportedNextConfigWithAssetPrefix = { + ...exportedNextConfig, + assetPrefix: '/asset-prefix/', + }; + const buildContext = getBuildContext('client', exportedNextConfigWithAssetPrefix); + const finalWebpackConfig = await materializeFinalWebpackConfig({ + exportedNextConfig: exportedNextConfigWithAssetPrefix, + incomingWebpackConfig: clientWebpackConfig, + incomingWebpackBuildContext: buildContext, + }); + + const sentryWebpackPluginInstance = findWebpackPlugin( + finalWebpackConfig, + 'SentryCliPlugin', + ) as SentryWebpackPlugin; + + expect(sentryWebpackPluginInstance.options.urlPrefix).toEqual('~/asset-prefix/_next'); + }); + + it('has the correct value when given a full URL', async () => { + const exportedNextConfigWithAssetPrefix = { + ...exportedNextConfig, + assetPrefix: 'https://cdn.mydomain.com/asset-prefix', + }; + const buildContext = getBuildContext('client', exportedNextConfigWithAssetPrefix); + const finalWebpackConfig = await materializeFinalWebpackConfig({ + exportedNextConfig: exportedNextConfigWithAssetPrefix, + incomingWebpackConfig: clientWebpackConfig, + incomingWebpackBuildContext: buildContext, + }); + + const sentryWebpackPluginInstance = findWebpackPlugin( + finalWebpackConfig, + 'SentryCliPlugin', + ) as SentryWebpackPlugin; + + expect(sentryWebpackPluginInstance.options.urlPrefix).toEqual('~/asset-prefix/_next'); + }); + + it('takes priority over basePath ', async () => { + const exportedNextConfigWithAssetPrefix = { + ...exportedNextConfig, + assetPrefix: '/asset-prefix', + basePath: '/base-path', + }; + const buildContext = getBuildContext('client', exportedNextConfigWithAssetPrefix); + const finalWebpackConfig = await materializeFinalWebpackConfig({ + exportedNextConfig: exportedNextConfigWithAssetPrefix, + incomingWebpackConfig: clientWebpackConfig, + incomingWebpackBuildContext: buildContext, + }); + + const sentryWebpackPluginInstance = findWebpackPlugin( + finalWebpackConfig, + 'SentryCliPlugin', + ) as SentryWebpackPlugin; + + expect(sentryWebpackPluginInstance.options.urlPrefix).toEqual('~/asset-prefix/_next'); + }); + }); + describe('SentryWebpackPlugin enablement', () => { let processEnvBackup: typeof process.env; From 460fa36530622569b00b28f16fb9a1d22d231c29 Mon Sep 17 00:00:00 2001 From: Tomas Cerskus Date: Sun, 20 Nov 2022 18:55:07 +0000 Subject: [PATCH 2/3] Add a test for URL without a pathname --- .../webpack/sentryWebpackPlugin.test.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts index 5f292154e685..17f153d3a03f 100644 --- a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts +++ b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts @@ -337,6 +337,26 @@ describe('Sentry webpack plugin config', () => { }); it('has the correct value when given a full URL', async () => { + const exportedNextConfigWithAssetPrefix = { + ...exportedNextConfig, + assetPrefix: 'https://cdn.mydomain.com', + }; + const buildContext = getBuildContext('client', exportedNextConfigWithAssetPrefix); + const finalWebpackConfig = await materializeFinalWebpackConfig({ + exportedNextConfig: exportedNextConfigWithAssetPrefix, + incomingWebpackConfig: clientWebpackConfig, + incomingWebpackBuildContext: buildContext, + }); + + const sentryWebpackPluginInstance = findWebpackPlugin( + finalWebpackConfig, + 'SentryCliPlugin', + ) as SentryWebpackPlugin; + + expect(sentryWebpackPluginInstance.options.urlPrefix).toEqual('~/_next'); + }); + + it('has the correct value when given a full URL with a path', async () => { const exportedNextConfigWithAssetPrefix = { ...exportedNextConfig, assetPrefix: 'https://cdn.mydomain.com/asset-prefix', From e1372947423da9004d644eb71b0c2c6f9c255597 Mon Sep 17 00:00:00 2001 From: Tomas Cerskus Date: Sun, 20 Nov 2022 20:17:10 +0000 Subject: [PATCH 3/3] Don't apply assetPrefix to server files --- packages/nextjs/src/config/webpack.ts | 21 ++++-- .../webpack/sentryWebpackPlugin.test.ts | 67 ++++++++++++++++--- 2 files changed, 73 insertions(+), 15 deletions(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index a3dfafe69e8b..ed9dbb1cdd18 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -459,23 +459,31 @@ export function getWebpackPluginOptions( const isServerless = userNextConfig.target === 'experimental-serverless-trace'; const hasSentryProperties = fs.existsSync(path.resolve(projectDir, 'sentry.properties')); + let basePath = userNextConfig.basePath || ''; + if (basePath) { + basePath = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath; + } + const serverUrlPrefix = `~${basePath}/_next`; + let assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || ''; if (assetPrefix) { const assertPrefixUrl = url.parse(assetPrefix); assetPrefix = assertPrefixUrl.pathname || ''; assetPrefix = assetPrefix.endsWith('/') ? assetPrefix.slice(0, -1) : assetPrefix; } - const urlPrefix = `~${assetPrefix}/_next`; + const clientUrlPrefix = `~${assetPrefix}/_next`; const serverInclude = isServerless - ? [{ paths: [`${distDirAbsPath}/serverless/`], urlPrefix: `${urlPrefix}/serverless` }] - : [{ paths: [`${distDirAbsPath}/server/pages/`], urlPrefix: `${urlPrefix}/server/pages` }].concat( - isWebpack5 ? [{ paths: [`${distDirAbsPath}/server/chunks/`], urlPrefix: `${urlPrefix}/server/chunks` }] : [], + ? [{ paths: [`${distDirAbsPath}/serverless/`], urlPrefix: `${serverUrlPrefix}/serverless` }] + : [{ paths: [`${distDirAbsPath}/server/pages/`], urlPrefix: `${serverUrlPrefix}/server/pages` }].concat( + isWebpack5 + ? [{ paths: [`${distDirAbsPath}/server/chunks/`], urlPrefix: `${serverUrlPrefix}/server/chunks` }] + : [], ); const clientInclude = userSentryOptions.widenClientFileUpload - ? [{ paths: [`${distDirAbsPath}/static/chunks`], urlPrefix: `${urlPrefix}/static/chunks` }] - : [{ paths: [`${distDirAbsPath}/static/chunks/pages`], urlPrefix: `${urlPrefix}/static/chunks/pages` }]; + ? [{ paths: [`${distDirAbsPath}/static/chunks`], urlPrefix: `${clientUrlPrefix}/static/chunks` }] + : [{ paths: [`${distDirAbsPath}/static/chunks/pages`], urlPrefix: `${clientUrlPrefix}/static/chunks/pages` }]; const defaultPluginOptions = dropUndefinedKeys({ include: isServer ? serverInclude : clientInclude, @@ -492,7 +500,6 @@ export function getWebpackPluginOptions( authToken: process.env.SENTRY_AUTH_TOKEN, configFile: hasSentryProperties ? 'sentry.properties' : undefined, stripPrefix: ['webpack://_N_E/'], - urlPrefix, entries: (entryPointName: string) => shouldAddSentryToEntryPoint(entryPointName, isServer, userSentryOptions.excludeServerRoutes), release: getSentryRelease(buildId), diff --git a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts index 17f153d3a03f..63ba018cef55 100644 --- a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts +++ b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts @@ -35,7 +35,6 @@ describe('Sentry 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 @@ -295,7 +294,34 @@ describe('Sentry webpack plugin config', () => { }); }); - describe('Sentry webpack plugin `urlPrefix` option with assetPrefix set', () => { + describe('Sentry webpack plugin `includes` option with assetPrefix set', () => { + it('does not affect server build', async () => { + const exportedNextConfigWithAssetPrefix = { + ...exportedNextConfig, + assetPrefix: '/asset-prefix', + }; + const serverBuildContextWebpack4 = getBuildContext('server', exportedNextConfigWithAssetPrefix); + serverBuildContextWebpack4.webpack.version = '4.15.13'; + + const finalWebpackConfig = await materializeFinalWebpackConfig({ + exportedNextConfig: exportedNextConfigWithAssetPrefix, + incomingWebpackConfig: serverWebpackConfig, + incomingWebpackBuildContext: serverBuildContextWebpack4, + }); + + const sentryWebpackPluginInstance = findWebpackPlugin( + finalWebpackConfig, + 'SentryCliPlugin', + ) as SentryWebpackPlugin; + + expect(sentryWebpackPluginInstance.options.include).toEqual([ + { + paths: [`${serverBuildContextWebpack4.dir}/.next/server/pages/`], + urlPrefix: '~/_next/server/pages', + }, + ]); + }); + it('has the correct value given a path', async () => { const exportedNextConfigWithAssetPrefix = { ...exportedNextConfig, @@ -313,7 +339,12 @@ describe('Sentry webpack plugin config', () => { 'SentryCliPlugin', ) as SentryWebpackPlugin; - expect(sentryWebpackPluginInstance.options.urlPrefix).toEqual('~/asset-prefix/_next'); + expect(sentryWebpackPluginInstance.options.include).toEqual([ + { + paths: [`${buildContext.dir}/.next/static/chunks/pages`], + urlPrefix: '~/asset-prefix/_next/static/chunks/pages', + }, + ]); }); it('has the correct value given a path with a leading slash', async () => { @@ -333,10 +364,15 @@ describe('Sentry webpack plugin config', () => { 'SentryCliPlugin', ) as SentryWebpackPlugin; - expect(sentryWebpackPluginInstance.options.urlPrefix).toEqual('~/asset-prefix/_next'); + expect(sentryWebpackPluginInstance.options.include).toEqual([ + { + paths: [`${buildContext.dir}/.next/static/chunks/pages`], + urlPrefix: '~/asset-prefix/_next/static/chunks/pages', + }, + ]); }); - it('has the correct value when given a full URL', async () => { + it('has the correct value when given a full URL with no path', async () => { const exportedNextConfigWithAssetPrefix = { ...exportedNextConfig, assetPrefix: 'https://cdn.mydomain.com', @@ -353,7 +389,12 @@ describe('Sentry webpack plugin config', () => { 'SentryCliPlugin', ) as SentryWebpackPlugin; - expect(sentryWebpackPluginInstance.options.urlPrefix).toEqual('~/_next'); + expect(sentryWebpackPluginInstance.options.include).toEqual([ + { + paths: [`${buildContext.dir}/.next/static/chunks/pages`], + urlPrefix: '~/_next/static/chunks/pages', + }, + ]); }); it('has the correct value when given a full URL with a path', async () => { @@ -373,7 +414,12 @@ describe('Sentry webpack plugin config', () => { 'SentryCliPlugin', ) as SentryWebpackPlugin; - expect(sentryWebpackPluginInstance.options.urlPrefix).toEqual('~/asset-prefix/_next'); + expect(sentryWebpackPluginInstance.options.include).toEqual([ + { + paths: [`${buildContext.dir}/.next/static/chunks/pages`], + urlPrefix: '~/asset-prefix/_next/static/chunks/pages', + }, + ]); }); it('takes priority over basePath ', async () => { @@ -394,7 +440,12 @@ describe('Sentry webpack plugin config', () => { 'SentryCliPlugin', ) as SentryWebpackPlugin; - expect(sentryWebpackPluginInstance.options.urlPrefix).toEqual('~/asset-prefix/_next'); + expect(sentryWebpackPluginInstance.options.include).toEqual([ + { + paths: [`${buildContext.dir}/.next/static/chunks/pages`], + urlPrefix: '~/asset-prefix/_next/static/chunks/pages', + }, + ]); }); });