diff --git a/packages/nextjs/rollup.npm.config.js b/packages/nextjs/rollup.npm.config.js index f9c498c8f39e..ce15b951235e 100644 --- a/packages/nextjs/rollup.npm.config.js +++ b/packages/nextjs/rollup.npm.config.js @@ -24,11 +24,12 @@ export default [ ...makeNPMConfigVariants( makeBaseNPMConfig({ entrypoints: [ - 'src/config/templates/pageWrapperTemplate.ts', 'src/config/templates/apiWrapperTemplate.ts', 'src/config/templates/middlewareWrapperTemplate.ts', - 'src/config/templates/serverComponentWrapperTemplate.ts', + 'src/config/templates/pageWrapperTemplate.ts', 'src/config/templates/requestAsyncStorageShim.ts', + 'src/config/templates/sentryInitWrapperTemplate.ts', + 'src/config/templates/serverComponentWrapperTemplate.ts', ], packageSpecificConfig: { @@ -47,6 +48,7 @@ export default [ external: [ '@sentry/nextjs', 'next/dist/client/components/request-async-storage', + '__SENTRY_CONFIG_IMPORT_PATH__', '__SENTRY_WRAPPING_TARGET_FILE__', '__SENTRY_NEXTJS_REQUEST_ASYNC_STORAGE_SHIM__', ], diff --git a/packages/nextjs/src/config/loaders/wrappingLoader.ts b/packages/nextjs/src/config/loaders/wrappingLoader.ts index e4d58c579420..f2e07ab6537d 100644 --- a/packages/nextjs/src/config/loaders/wrappingLoader.ts +++ b/packages/nextjs/src/config/loaders/wrappingLoader.ts @@ -30,6 +30,9 @@ const requestAsyncStorageShimPath = path.resolve(__dirname, '..', 'templates', ' const requestAsyncStorageModuleExists = moduleExists(NEXTJS_REQUEST_ASYNC_STORAGE_MODULE_PATH); let showedMissingAsyncStorageModuleWarning = false; +const sentryInitWrapperTemplatePath = path.resolve(__dirname, '..', 'templates', 'sentryInitWrapperTemplate.js'); +const sentryInitWrapperTemplateCode = fs.readFileSync(sentryInitWrapperTemplatePath, { encoding: 'utf8' }); + const serverComponentWrapperTemplatePath = path.resolve( __dirname, '..', @@ -43,7 +46,7 @@ type LoaderOptions = { appDir: string; pageExtensionRegex: string; excludeServerRoutes: Array; - wrappingTargetKind: 'page' | 'api-route' | 'middleware' | 'server-component'; + wrappingTargetKind: 'page' | 'api-route' | 'middleware' | 'server-component' | 'sentry-init'; sentryConfigFilePath?: string; vercelCronsConfig?: VercelCronsConfig; }; @@ -83,7 +86,23 @@ export default function wrappingLoader( let templateCode: string; - if (wrappingTargetKind === 'page' || wrappingTargetKind === 'api-route') { + if (wrappingTargetKind === 'sentry-init') { + templateCode = sentryInitWrapperTemplateCode; + + // Absolute paths to the sentry config do not work with Windows: https://github.com/getsentry/sentry-javascript/issues/8133 + // Se we need check whether `this.resourcePath` is absolute because there is no contract by webpack that says it is absolute. + // Examples where `this.resourcePath` could possibly be non-absolute are virtual modules. + if (sentryConfigFilePath && path.isAbsolute(this.resourcePath)) { + const sentryConfigImportPath = path + .relative(path.dirname(this.resourcePath), sentryConfigFilePath) + .replace(/\\/g, '/'); + templateCode = templateCode.replace(/__SENTRY_CONFIG_IMPORT_PATH__/g, sentryConfigImportPath); + } else { + // Bail without doing any wrapping + this.callback(null, userCode, userModuleSourceMap); + return; + } + } else if (wrappingTargetKind === 'page' || wrappingTargetKind === 'api-route') { // Get the parameterized route name from this page's filepath const parameterizedPagesRoute = path.posix .normalize( @@ -207,15 +226,6 @@ export default function wrappingLoader( throw new Error(`Invariant: Could not get template code of unknown kind "${wrappingTargetKind}"`); } - // We check whether `this.resourcePath` is absolute because there is no contract by webpack that says it is absolute, - // however we can only create relative paths to the sentry config from absolute paths.Examples where this could possibly be non - absolute are virtual modules. - if (sentryConfigFilePath && path.isAbsolute(this.resourcePath)) { - const sentryConfigImportPath = path - .relative(path.dirname(this.resourcePath), sentryConfigFilePath) // Absolute paths do not work with Windows: https://github.com/getsentry/sentry-javascript/issues/8133 - .replace(/\\/g, '/'); - templateCode = `import "${sentryConfigImportPath}";\n`.concat(templateCode); - } - // Replace the import path of the wrapping target in the template with a path that the `wrapUserCode` function will understand. templateCode = templateCode.replace(/__SENTRY_WRAPPING_TARGET_FILE__/g, WRAPPING_TARGET_MODULE_NAME); diff --git a/packages/nextjs/src/config/templates/sentryInitWrapperTemplate.ts b/packages/nextjs/src/config/templates/sentryInitWrapperTemplate.ts new file mode 100644 index 000000000000..1720c3b62672 --- /dev/null +++ b/packages/nextjs/src/config/templates/sentryInitWrapperTemplate.ts @@ -0,0 +1,11 @@ +// @ts-ignore This will be replaced with the user's sentry config gile +// eslint-disable-next-line import/no-unresolved +import '__SENTRY_CONFIG_IMPORT_PATH__'; + +// @ts-ignore This is the file we're wrapping +// eslint-disable-next-line import/no-unresolved +export * from '__SENTRY_WRAPPING_TARGET_FILE__'; + +// @ts-ignore This is the file we're wrapping +// eslint-disable-next-line import/no-unresolved +export { default } from '__SENTRY_WRAPPING_TARGET_FILE__'; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 49f450bb27a4..d711fef5c14f 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -140,19 +140,45 @@ export function constructWebpackConfigFunction( return path.normalize(absoluteResourcePath); }; + const isPageResource = (resourcePath: string): boolean => { + const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath); + return ( + normalizedAbsoluteResourcePath.startsWith(pagesDirPath + path.sep) && + !normalizedAbsoluteResourcePath.startsWith(apiRoutesPath + path.sep) && + dotPrefixedPageExtensions.some(ext => normalizedAbsoluteResourcePath.endsWith(ext)) + ); + }; + + const isApiRouteResource = (resourcePath: string): boolean => { + const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath); + return ( + normalizedAbsoluteResourcePath.startsWith(apiRoutesPath + path.sep) && + dotPrefixedPageExtensions.some(ext => normalizedAbsoluteResourcePath.endsWith(ext)) + ); + }; + + const isMiddlewareResource = (resourcePath: string): boolean => { + const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath); + return normalizedAbsoluteResourcePath === middlewareJsPath || normalizedAbsoluteResourcePath === middlewareTsPath; + }; + + const isServerComponentResource = (resourcePath: string): boolean => { + const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath); + + // ".js, .jsx, or .tsx file extensions can be used for Pages" + // https://beta.nextjs.org/docs/routing/pages-and-layouts#pages:~:text=.js%2C%20.jsx%2C%20or%20.tsx%20file%20extensions%20can%20be%20used%20for%20Pages. + return ( + normalizedAbsoluteResourcePath.startsWith(appDirPath + path.sep) && + !!normalizedAbsoluteResourcePath.match(/[\\/](page|layout|loading|head|not-found)\.(js|jsx|tsx)$/) + ); + }; + if (isServer && userSentryOptions.autoInstrumentServerFunctions !== false) { // It is very important that we insert our loaders at the beginning of the array because we expect any sort of transformations/transpilations (e.g. TS -> JS) to already have happened. // Wrap pages newConfig.module.rules.unshift({ - test: resourcePath => { - const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath); - return ( - normalizedAbsoluteResourcePath.startsWith(pagesDirPath + path.sep) && - !normalizedAbsoluteResourcePath.startsWith(apiRoutesPath + path.sep) && - dotPrefixedPageExtensions.some(ext => normalizedAbsoluteResourcePath.endsWith(ext)) - ); - }, + test: isPageResource, use: [ { loader: path.resolve(__dirname, 'loaders', 'wrappingLoader.js'), @@ -190,13 +216,7 @@ export function constructWebpackConfigFunction( // Wrap api routes newConfig.module.rules.unshift({ - test: resourcePath => { - const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath); - return ( - normalizedAbsoluteResourcePath.startsWith(apiRoutesPath + path.sep) && - dotPrefixedPageExtensions.some(ext => normalizedAbsoluteResourcePath.endsWith(ext)) - ); - }, + test: isApiRouteResource, use: [ { loader: path.resolve(__dirname, 'loaders', 'wrappingLoader.js'), @@ -211,12 +231,7 @@ export function constructWebpackConfigFunction( // Wrap middleware newConfig.module.rules.unshift({ - test: resourcePath => { - const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath); - return ( - normalizedAbsoluteResourcePath === middlewareJsPath || normalizedAbsoluteResourcePath === middlewareTsPath - ); - }, + test: isMiddlewareResource, use: [ { loader: path.resolve(__dirname, 'loaders', 'wrappingLoader.js'), @@ -232,14 +247,28 @@ export function constructWebpackConfigFunction( if (isServer && userSentryOptions.autoInstrumentAppDirectory !== false) { // Wrap page server components newConfig.module.rules.unshift({ - test: resourcePath => { - const normalizedAbsoluteResourcePath = normalizeLoaderResourcePath(resourcePath); + test: isServerComponentResource, + use: [ + { + loader: path.resolve(__dirname, 'loaders', 'wrappingLoader.js'), + options: { + ...staticWrappingLoaderOptions, + wrappingTargetKind: 'server-component', + }, + }, + ], + }); + } - // ".js, .jsx, or .tsx file extensions can be used for Pages" - // https://beta.nextjs.org/docs/routing/pages-and-layouts#pages:~:text=.js%2C%20.jsx%2C%20or%20.tsx%20file%20extensions%20can%20be%20used%20for%20Pages. + if (isServer) { + // Import the Sentry config in every user file + newConfig.module.rules.unshift({ + test: resourcePath => { return ( - normalizedAbsoluteResourcePath.startsWith(appDirPath + path.sep) && - !!normalizedAbsoluteResourcePath.match(/[\\/](page|layout|loading|head|not-found)\.(js|jsx|tsx)$/) + isPageResource(resourcePath) || + isApiRouteResource(resourcePath) || + isMiddlewareResource(resourcePath) || + isServerComponentResource(resourcePath) ); }, use: [ @@ -247,7 +276,7 @@ export function constructWebpackConfigFunction( loader: path.resolve(__dirname, 'loaders', 'wrappingLoader.js'), options: { ...staticWrappingLoaderOptions, - wrappingTargetKind: 'server-component', + wrappingTargetKind: 'sentry-init', }, }, ],