11import commonjs from '@rollup/plugin-commonjs' ;
2- import virtual from '@rollup/plugin-virtual' ;
32import { stringMatchesSomePattern } from '@sentry/utils' ;
43import * as fs from 'fs' ;
54import * as path from 'path' ;
65import { rollup } from 'rollup' ;
76
8- import { LoaderThis } from './types' ;
7+ import type { LoaderThis } from './types' ;
98
109const apiWrapperTemplatePath = path . resolve ( __dirname , '..' , 'templates' , 'apiWrapperTemplate.js' ) ;
1110const apiWrapperTemplateCode = fs . readFileSync ( apiWrapperTemplatePath , { encoding : 'utf8' } ) ;
@@ -16,8 +15,7 @@ const pageWrapperTemplateCode = fs.readFileSync(pageWrapperTemplatePath, { encod
1615// Just a simple placeholder to make referencing module consistent
1716const SENTRY_WRAPPER_MODULE_NAME = 'sentry-wrapper-module' ;
1817
19- // needs to end in .cjs in order for the `commonjs` plugin to pick it up - unfortunately the plugin has no option to
20- // make this work in combination with the`virtual` plugin
18+ // Needs to end in .cjs in order for the `commonjs` plugin to pick it up
2119const WRAPPING_TARGET_MODULE_NAME = '__SENTRY_WRAPPING_TARGET__.cjs' ;
2220
2321type LoaderOptions = {
@@ -31,7 +29,13 @@ type LoaderOptions = {
3129 * any data-fetching functions (`getInitialProps`, `getStaticProps`, and `getServerSideProps`) or API routes it contains
3230 * are wrapped, and then everything is re-exported.
3331 */
34- export default async function wrappingLoader ( this : LoaderThis < LoaderOptions > , userCode : string ) : Promise < string > {
32+ export default function wrappingLoader (
33+ this : LoaderThis < LoaderOptions > ,
34+ userCode : string ,
35+ userModuleSourceMap : any ,
36+ ) : void {
37+ this . async ( ) ;
38+
3539 // We know one or the other will be defined, depending on the version of webpack being used
3640 const {
3741 pagesDir,
@@ -56,7 +60,8 @@ export default async function wrappingLoader(this: LoaderThis<LoaderOptions>, us
5660
5761 // Skip explicitly-ignored pages
5862 if ( stringMatchesSomePattern ( parameterizedRoute , excludeServerRoutes , true ) ) {
59- return userCode ;
63+ this . callback ( null , userCode , userModuleSourceMap ) ;
64+ return ;
6065 }
6166
6267 let templateCode = parameterizedRoute . startsWith ( '/api' ) ? apiWrapperTemplateCode : pageWrapperTemplateCode ;
@@ -69,15 +74,18 @@ export default async function wrappingLoader(this: LoaderThis<LoaderOptions>, us
6974
7075 // Run the proxy module code through Rollup, in order to split the `export * from '<wrapped file>'` out into
7176 // individual exports (which nextjs seems to require).
72- try {
73- return await wrapUserCode ( templateCode , userCode ) ;
74- } catch ( err ) {
75- // eslint-disable-next-line no-console
76- console . warn (
77- `[@sentry/nextjs] Could not instrument ${ this . resourcePath } . An error occurred while auto-wrapping:\n${ err } ` ,
78- ) ;
79- return userCode ;
80- }
77+ wrapUserCode ( templateCode , userCode , userModuleSourceMap )
78+ . then ( ( { code : wrappedCode , map : wrappedCodeSourceMap } ) => {
79+ this . callback ( null , wrappedCode , wrappedCodeSourceMap ) ;
80+ } )
81+ . catch ( err => {
82+ // eslint-disable-next-line no-console
83+ console . warn (
84+ `[@sentry/nextjs] Could not instrument ${ this . resourcePath } . An error occurred while auto-wrapping:\n${ err } ` ,
85+ ) ;
86+ this . callback ( null , userCode , userModuleSourceMap ) ;
87+ return ;
88+ } ) ;
8189}
8290
8391/**
@@ -92,26 +100,53 @@ export default async function wrappingLoader(this: LoaderThis<LoaderOptions>, us
92100 *
93101 * @param wrapperCode The wrapper module code
94102 * @param userModuleCode The user module code
95- * @returns The wrapped user code
103+ * @returns The wrapped user code and a source map that describes the transformations done by this function
96104 */
97- async function wrapUserCode ( wrapperCode : string , userModuleCode : string ) : Promise < string > {
105+ async function wrapUserCode (
106+ wrapperCode : string ,
107+ userModuleCode : string ,
108+ userModuleSourceMap : any ,
109+ ) : Promise < { code : string ; map ?: any } > {
98110 const rollupBuild = await rollup ( {
99111 input : SENTRY_WRAPPER_MODULE_NAME ,
100112
101113 plugins : [
102- // We're using virtual modules so we don't have to mess around with file paths
103- virtual ( {
104- [ SENTRY_WRAPPER_MODULE_NAME ] : wrapperCode ,
105- [ WRAPPING_TARGET_MODULE_NAME ] : userModuleCode ,
106- } ) ,
114+ // We're using a simple custom plugin that virtualizes our wrapper module and the user module, so we don't have to
115+ // mess around with file paths and so that we can pass the original user module source map to rollup so that
116+ // rollup gives us a bundle with correct source mapping to the original file
117+ {
118+ name : 'virtualize-sentry-wrapper-modules' ,
119+ resolveId : id => {
120+ if ( id === SENTRY_WRAPPER_MODULE_NAME || id === WRAPPING_TARGET_MODULE_NAME ) {
121+ return id ;
122+ } else {
123+ return null ;
124+ }
125+ } ,
126+ load ( id ) {
127+ if ( id === SENTRY_WRAPPER_MODULE_NAME ) {
128+ return wrapperCode ;
129+ } else if ( id === WRAPPING_TARGET_MODULE_NAME ) {
130+ return {
131+ code : userModuleCode ,
132+ map : userModuleSourceMap , // give rollup acces to original user module source map
133+ } ;
134+ } else {
135+ return null ;
136+ }
137+ } ,
138+ } ,
107139
108140 // People may use `module.exports` in their API routes or page files. Next.js allows that and we also need to
109141 // handle that correctly so we let a plugin to take care of bundling cjs exports for us.
110- commonjs ( ) ,
142+ commonjs ( {
143+ transformMixedEsModules : true ,
144+ sourceMap : true ,
145+ } ) ,
111146 ] ,
112147
113148 // We only want to bundle our wrapper module and the wrappee module into one, so we mark everything else as external.
114- external : source => source !== SENTRY_WRAPPER_MODULE_NAME && source !== WRAPPING_TARGET_MODULE_NAME ,
149+ external : sourceId => sourceId !== SENTRY_WRAPPER_MODULE_NAME && sourceId !== WRAPPING_TARGET_MODULE_NAME ,
115150
116151 // Prevent rollup from stressing out about TS's use of global `this` when polyfilling await. (TS will polyfill if the
117152 // user's tsconfig `target` is set to anything before `es2017`. See https://stackoverflow.com/a/72822340 and
@@ -138,22 +173,18 @@ async function wrapUserCode(wrapperCode: string, userModuleCode: string): Promis
138173 // externals entirely, with the result that their paths remain untouched (which is what we want).
139174 makeAbsoluteExternalsRelative : false ,
140175
141- onwarn : ( ) => {
176+ onwarn : ( _warning , _warn ) => {
142177 // Suppress all warnings - we don't want to bother people with this output
143- } ,
144-
145- output : {
146- // TODO
147- interop : 'auto' ,
148- // TODO
149- exports : 'named' ,
178+ // Might be stuff like "you have unused imports"
179+ // _warn(_warning); // uncomment to debug
150180 } ,
151181 } ) ;
152182
153183 const finalBundle = await rollupBuild . generate ( {
154184 format : 'esm' ,
185+ sourcemap : 'hidden' , // put source map data in the bundle but don't generate a source map commment in the output
155186 } ) ;
156187
157188 // The module at index 0 is always the entrypoint, which in this case is the proxy module.
158- return finalBundle . output [ 0 ] . code ;
189+ return finalBundle . output [ 0 ] ;
159190}
0 commit comments