From 56307baa0c88458fc85f4996d514c8bd60dac24e Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Tue, 26 Jul 2022 16:58:24 -0700 Subject: [PATCH 1/5] expand `module.rules` type --- packages/nextjs/src/config/types.ts | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index e0c60e4484e8..41fc6eeedbf0 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -57,13 +57,7 @@ export type WebpackConfigObject = { alias?: { [key: string]: string | boolean }; }; module?: { - rules: Array<{ - test: string | RegExp; - use: Array<{ - loader: string; - options: Record; - }>; - }>; + rules: Array; }; } & { // other webpack options @@ -98,3 +92,20 @@ export type EntryPropertyFunction = () => Promise; // listed under the key `import`. export type EntryPointValue = string | Array | EntryPointObject; export type EntryPointObject = { import: string | Array }; + +/** + * Webpack `module.rules` entry + */ + +export type WebpackModuleRule = { + test?: string | RegExp; + include?: Array | RegExp; + exclude?: (filepath: string) => boolean; + use?: ModuleRuleUseProperty | Array; + oneOf?: Array; +}; + +export type ModuleRuleUseProperty = { + loader?: string; + options?: Record; +}; From 7b0b82caef6bab31ae87ec97bfd549baf2ef5571 Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Tue, 26 Jul 2022 17:19:17 -0700 Subject: [PATCH 2/5] add `transpileClientSDK` option --- packages/nextjs/src/config/types.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 41fc6eeedbf0..28e023590285 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -22,6 +22,9 @@ export type NextConfigObject = { disableClientWebpackPlugin?: boolean; hideSourceMaps?: boolean; + // Force webpack to apply the same transpilation rules to the SDK code as apply to user code. Helpful when targeting + // older browsers which don't support ES6 (and ES6+ features like object spread). + transpileClientSDK?: boolean; // Upload files from `/static/chunks` rather than `/static/chunks/pages`. Usually files outside of // `pages/` only contain third-party code, but in cases where they contain user code, restricting the webpack // plugin's upload breaks sourcemaps for those user-code-containing files, because it keeps them from being From 7c993b54955c2cfc12ef55037656ebe151e6aa9e Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Tue, 26 Jul 2022 17:21:56 -0700 Subject: [PATCH 3/5] add functions for finding rules to modify --- packages/nextjs/src/config/webpack.ts | 67 +++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 5f98421b8d1b..62bf67b3df7e 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -13,6 +13,7 @@ import { WebpackConfigFunction, WebpackConfigObject, WebpackEntryProperty, + WebpackModuleRule, } from './types'; export { SentryWebpackPlugin }; @@ -124,6 +125,72 @@ export function constructWebpackConfigFunction( return newWebpackFunction; } +/** + * Determine if this `module.rules` entry is one which will transpile user code + * + * @param rule The rule to check + * @param projectDir The path to the user's project directory + * @returns True if the rule transpiles user code, and false otherwise + */ +function isMatchingRule(rule: WebpackModuleRule, projectDir: string): boolean { + // We want to run our SDK code through the same transformations the user's code will go through, so we test against a + // sample user code path + const samplePagePath = path.resolve(projectDir, 'pageFile.js'); + if (rule.test && rule.test instanceof RegExp && !rule.test.test(samplePagePath)) { + return false; + } + if (Array.isArray(rule.include) && !rule.include.includes(projectDir)) { + return false; + } + + // `rule.use` can be an object or an array of objects. For simplicity, force it to be an array. + const useEntries = Array.isArray(rule.use) ? rule.use : [rule.use]; + + // Depending on the version of nextjs we're talking about, the loader which does the transpiling is either + // + // 'next-babel-loader' (next 10), + // '/abs/path/to/node_modules/next/more/path/babel/even/more/path/loader/yet/more/path/index.js' (next 11), or + // 'next-swc-loader' (next 12). + // + // The next 11 option is ugly, but thankfully 'next', 'babel', and 'loader' do appear in it in the same order as in + // 'next-babel-loader', so we can use the same regex to test for both. + if (!useEntries.some(entry => entry?.loader && new RegExp('next.*(babel|swc).*loader').test(entry.loader))) { + return false; + } + + return true; +} + +/** + * Find all rules in `module.rules` which transpile user code. + * + * @param rules The `module.rules` value + * @param projectDir The path to the user's project directory + * @returns An array of matching rules + */ +function findTranspilationRules(rules: WebpackModuleRule[] | undefined, projectDir: string): WebpackModuleRule[] { + if (!rules) { + return []; + } + + const matchingRules: WebpackModuleRule[] = []; + + // Each entry in `module.rules` is either a rule in and of itself or an object with a `oneOf` property, whose value is + // an array of rules + rules.forEach(rule => { + // if (rule.oneOf) { + if (isMatchingRule(rule, projectDir)) { + matchingRules.push(rule); + } else if (rule.oneOf) { + const matchingOneOfRules = rule.oneOf.filter(oneOfRule => isMatchingRule(oneOfRule, projectDir)); + matchingRules.push(...matchingOneOfRules); + // } else if (isMatchingRule(rule, projectDir)) { + } + }); + + return matchingRules; +} + /** * Modify the webpack `entry` property so that the code in `sentry.server.config.js` and `sentry.client.config.js` is * included in the the necessary bundles. From f19559eaedd6e4a6f186eeb652ef6a4cb6943148 Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Tue, 26 Jul 2022 17:23:01 -0700 Subject: [PATCH 4/5] force transpilation of SDK files --- packages/nextjs/src/config/webpack.ts | 30 ++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 62bf67b3df7e..972fbf75351c 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -42,7 +42,7 @@ export function constructWebpackConfigFunction( // we're building server or client, whether we're in dev, what version of webpack we're using, etc). Note that // `incomingConfig` and `buildContext` are referred to as `config` and `options` in the nextjs docs. const newWebpackFunction = (incomingConfig: WebpackConfigObject, buildContext: BuildContext): WebpackConfigObject => { - const { isServer, dev: isDev } = buildContext; + const { isServer, dev: isDev, dir: projectDir } = buildContext; let newConfig = { ...incomingConfig }; // if user has custom webpack config (which always takes the form of a function), run it so we have actual values to @@ -74,6 +74,34 @@ export function constructWebpackConfigFunction( }; } + // The SDK uses syntax (ES6 and ES6+ features like object spread) which isn't supported by older browsers. For users + // who want to support such browsers, `transpileClientSDK` allows them to force the SDK code to go through the same + // transpilation that their code goes through. We don't turn this on by default because it increases bundle size + // fairly massively. + if (!isServer && userNextConfig.sentry?.transpileClientSDK) { + // Find all loaders which apply transpilation to user code + const transpilationRules = findTranspilationRules(newConfig.module?.rules, projectDir); + + // For each matching rule, wrap its `exclude` function so that it won't exclude SDK files, even though they're in + // `node_modules` (which is otherwise excluded) + transpilationRules.forEach(rule => { + // All matching rules will necessarily have an `exclude` property, but this keeps TS happy + if (rule.exclude && typeof rule.exclude === 'function') { + const origExclude = rule.exclude; + + const newExclude = (filepath: string): boolean => { + if (filepath.includes('@sentry')) { + // `false` in this case means "don't exclude it" + return false; + } + return origExclude(filepath); + }; + + rule.exclude = newExclude; + } + }); + } + // Tell webpack to inject user config files (containing the two `Sentry.init()` calls) into the appropriate output // bundles. Store a separate reference to the original `entry` value to avoid an infinite loop. (If we don't do // this, we'll have a statement of the form `x.y = () => f(x.y)`, where one of the things `f` does is call `x.y`. From 652f766a39e132dae73ac2cd6a24382812de4301 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 27 Jul 2022 10:05:04 +0200 Subject: [PATCH 5/5] Update packages/nextjs/src/config/types.ts --- packages/nextjs/src/config/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 28e023590285..8c0c39f1ef04 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -23,7 +23,7 @@ export type NextConfigObject = { hideSourceMaps?: boolean; // Force webpack to apply the same transpilation rules to the SDK code as apply to user code. Helpful when targeting - // older browsers which don't support ES6 (and ES6+ features like object spread). + // older browsers which don't support ES6 (or ES6+ features like object spread). transpileClientSDK?: boolean; // Upload files from `/static/chunks` rather than `/static/chunks/pages`. Usually files outside of // `pages/` only contain third-party code, but in cases where they contain user code, restricting the webpack