Skip to content
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

fix(nextjs): Execute sentry config independently of autoInstrumentServerFunctions and autoInstrumentAppDirectory #8781

Merged
merged 6 commits into from
Aug 10, 2023
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
6 changes: 4 additions & 2 deletions packages/nextjs/rollup.npm.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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__',
],
Expand Down
32 changes: 21 additions & 11 deletions packages/nextjs/src/config/loaders/wrappingLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
'..',
Expand All @@ -43,7 +46,7 @@ type LoaderOptions = {
appDir: string;
pageExtensionRegex: string;
excludeServerRoutes: Array<RegExp | string>;
wrappingTargetKind: 'page' | 'api-route' | 'middleware' | 'server-component';
wrappingTargetKind: 'page' | 'api-route' | 'middleware' | 'server-component' | 'sentry-init';
sentryConfigFilePath?: string;
vercelCronsConfig?: VercelCronsConfig;
};
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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);

Expand Down
11 changes: 11 additions & 0 deletions packages/nextjs/src/config/templates/sentryInitWrapperTemplate.ts
Original file line number Diff line number Diff line change
@@ -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__';
85 changes: 57 additions & 28 deletions packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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'),
Expand All @@ -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'),
Expand All @@ -232,22 +247,36 @@ 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: [
{
loader: path.resolve(__dirname, 'loaders', 'wrappingLoader.js'),
options: {
...staticWrappingLoaderOptions,
wrappingTargetKind: 'server-component',
wrappingTargetKind: 'sentry-init',
},
},
],
Expand Down