diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index a7a03ea67c59..dfb144b5946d 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -116,13 +116,13 @@ async function addSentryToEntryProperty( // we know is that it won't have gotten *simpler* in form, so we only need to worry about the object and function // options. See https://webpack.js.org/configuration/entry-context/#entry. + const { isServer, dir: projectDir, dev: isDev, config: userNextConfig } = buildContext; + const newEntryProperty = typeof currentEntryProperty === 'function' ? await currentEntryProperty() : { ...currentEntryProperty }; // `sentry.server.config.js` or `sentry.client.config.js` (or their TS equivalents) - const userConfigFile = buildContext.isServer - ? getUserConfigFile(buildContext.dir, 'server') - : getUserConfigFile(buildContext.dir, 'client'); + const userConfigFile = isServer ? getUserConfigFile(projectDir, 'server') : getUserConfigFile(projectDir, 'client'); // we need to turn the filename into a path so webpack can find it const filesToInject = [`./${userConfigFile}`]; @@ -131,12 +131,12 @@ async function addSentryToEntryProperty( // server SDK's default `RewriteFrames` instance (which needs it at runtime). Doesn't work when using the dev server // because it somehow tricks the file watcher into thinking that compilation itself is a file change, triggering an // infinite recompiling loop. (This should be fine because we don't upload sourcemaps in dev in any case.) - if (buildContext.isServer && !buildContext.dev) { + if (isServer && !isDev) { const rewriteFramesHelper = path.resolve( fs.mkdtempSync(path.resolve(os.tmpdir(), 'sentry-')), 'rewriteFramesHelper.js', ); - fs.writeFileSync(rewriteFramesHelper, `global.__rewriteFramesDistDir__ = '${buildContext.config.distDir}';\n`); + fs.writeFileSync(rewriteFramesHelper, `global.__rewriteFramesDistDir__ = '${userNextConfig.distDir}';\n`); // stick our helper file ahead of the user's config file so the value is in the global namespace *before* // `Sentry.init()` is called filesToInject.unshift(rewriteFramesHelper); @@ -269,13 +269,13 @@ export function getWebpackPluginOptions( buildContext: BuildContext, userPluginOptions: Partial, ): SentryWebpackPluginOptions { - const { isServer, dir: projectDir, buildId, dev: isDev, config: nextConfig, webpack } = buildContext; - const distDir = nextConfig.distDir ?? '.next'; // `.next` is the default directory + const { buildId, isServer, webpack, config: userNextConfig, dev: isDev, dir: projectDir } = buildContext; + const distDir = userNextConfig.distDir ?? '.next'; // `.next` is the default directory const isWebpack5 = webpack.version.startsWith('5'); - const isServerless = nextConfig.target === 'experimental-serverless-trace'; + const isServerless = userNextConfig.target === 'experimental-serverless-trace'; const hasSentryProperties = fs.existsSync(path.resolve(projectDir, 'sentry.properties')); - const urlPrefix = nextConfig.basePath ? `~${nextConfig.basePath}/_next` : '~/_next'; + const urlPrefix = userNextConfig.basePath ? `~${userNextConfig.basePath}/_next` : '~/_next'; const serverInclude = isServerless ? [{ paths: [`${distDir}/serverless/`], urlPrefix: `${urlPrefix}/serverless` }] diff --git a/packages/nextjs/test/config.test.ts b/packages/nextjs/test/config.test.ts index 76b9af438c0c..f9aa4018bdd9 100644 --- a/packages/nextjs/test/config.test.ts +++ b/packages/nextjs/test/config.test.ts @@ -315,11 +315,11 @@ describe('webpack config', () => { expect(finalWebpackConfig.entry).toEqual( expect.objectContaining({ - // original entry point value is a string + // original entrypoint value is a string // (was 'private-next-pages/api/dogs/[name].js') 'pages/api/dogs/[name]': [rewriteFramesHelper, serverConfigFilePath, 'private-next-pages/api/dogs/[name].js'], - // original entry point value is a string array + // original entrypoint value is a string array // (was ['./node_modules/smellOVision/index.js', 'private-next-pages/_app.js']) 'pages/_app': [ rewriteFramesHelper, @@ -328,14 +328,14 @@ describe('webpack config', () => { 'private-next-pages/_app.js', ], - // original entry point value is an object containing a string `import` value - // (`import` was 'private-next-pages/api/simulator/dogStats/[name].js') + // original entrypoint value is an object containing a string `import` value + // (was { import: 'private-next-pages/api/simulator/dogStats/[name].js' }) 'pages/api/simulator/dogStats/[name]': { import: [rewriteFramesHelper, serverConfigFilePath, 'private-next-pages/api/simulator/dogStats/[name].js'], }, - // original entry point value is an object containing a string array `import` value - // (`import` was ['./node_modules/dogPoints/converter.js', 'private-next-pages/api/simulator/leaderboard.js']) + // original entrypoint value is an object containing a string array `import` value + // (was { import: ['./node_modules/dogPoints/converter.js', 'private-next-pages/api/simulator/leaderboard.js'] }) 'pages/api/simulator/leaderboard': { import: [ rewriteFramesHelper, @@ -345,16 +345,64 @@ describe('webpack config', () => { ], }, - // original entry point value is an object containg properties besides `import` - // (`dependOn` remains untouched) + // original entrypoint value is an object containg properties besides `import` + // (was { import: 'private-next-pages/api/tricks/[trickName].js', dependOn: 'treats', }) 'pages/api/tricks/[trickName]': { import: [rewriteFramesHelper, serverConfigFilePath, 'private-next-pages/api/tricks/[trickName].js'], - dependOn: 'treats', + dependOn: 'treats', // untouched }, }), ); }); + it('injects user config file into `_app` in both server and client bundles', async () => { + const finalServerWebpackConfig = await materializeFinalWebpackConfig({ + userNextConfig, + incomingWebpackConfig: serverWebpackConfig, + incomingWebpackBuildContext: serverBuildContext, + }); + const finalClientWebpackConfig = await materializeFinalWebpackConfig({ + userNextConfig, + incomingWebpackConfig: clientWebpackConfig, + incomingWebpackBuildContext: clientBuildContext, + }); + + expect(finalServerWebpackConfig.entry).toEqual( + expect.objectContaining({ + 'pages/_app': expect.arrayContaining([serverConfigFilePath]), + }), + ); + expect(finalClientWebpackConfig.entry).toEqual( + expect.objectContaining({ + 'pages/_app': expect.arrayContaining([clientConfigFilePath]), + }), + ); + }); + + it('injects user config file into API routes', async () => { + const finalWebpackConfig = await materializeFinalWebpackConfig({ + userNextConfig, + incomingWebpackConfig: serverWebpackConfig, + incomingWebpackBuildContext: serverBuildContext, + }); + + expect(finalWebpackConfig.entry).toEqual( + expect.objectContaining({ + 'pages/api/simulator/dogStats/[name]': { + import: expect.arrayContaining([serverConfigFilePath]), + }, + + 'pages/api/simulator/leaderboard': { + import: expect.arrayContaining([serverConfigFilePath]), + }, + + 'pages/api/tricks/[trickName]': expect.objectContaining({ + import: expect.arrayContaining([serverConfigFilePath]), + }), + }), + ); + }); + it('does not inject anything into non-_app, non-API routes', async () => { const finalWebpackConfig = await materializeFinalWebpackConfig({ userNextConfig,