1
+ import commonjs from '@rollup/plugin-commonjs' ;
2
+ import virtual from '@rollup/plugin-virtual' ;
1
3
import { stringMatchesSomePattern } from '@sentry/utils' ;
2
4
import * as fs from 'fs' ;
3
5
import * as path from 'path' ;
6
+ import { rollup } from 'rollup' ;
4
7
5
- import { rollupize } from './rollup' ;
6
8
import { LoaderThis } from './types' ;
7
9
10
+ const SENTRY_WRAPPER_MODULE_NAME = 'sentry-wrapper-module' ;
11
+ const WRAPPING_TARGET_MODULE_NAME = '__SENTRY_WRAPEE__.cjs' ;
12
+
8
13
type LoaderOptions = {
9
14
pagesDir : string ;
10
15
pageExtensionRegex : string ;
@@ -57,7 +62,7 @@ export default async function proxyLoader(this: LoaderThis<LoaderOptions>, userC
57
62
// Run the proxy module code through Rollup, in order to split the `export * from '<wrapped file>'` out into
58
63
// individual exports (which nextjs seems to require).
59
64
try {
60
- return await rollupize ( templateCode , userCode ) ;
65
+ return await wrapUserCode ( templateCode , userCode ) ;
61
66
} catch ( err ) {
62
67
// eslint-disable-next-line no-console
63
68
console . warn (
@@ -66,3 +71,81 @@ export default async function proxyLoader(this: LoaderThis<LoaderOptions>, userC
66
71
return userCode ;
67
72
}
68
73
}
74
+
75
+ /**
76
+ * Use Rollup to process the proxy module code, in order to split its `export * from '<wrapped file>'` call into
77
+ * individual exports (which nextjs seems to need).
78
+ *
79
+ * Wraps provided user code (located under the import defined via WRAPPING_TARGET_MODULE_NAME) with provided wrapper
80
+ * code. Under the hood, this function uses rollup to bundle the modules together. Rollup is convenient for us because
81
+ * it turns `export * from '<wrapped file>'` (which Next.js doesn't allow) into individual named exports.
82
+ *
83
+ * Note: This function may throw in case something goes wrong while bundling.
84
+ *
85
+ * @param wrapperCode The wrapper module code
86
+ * @param userModuleCode The user module code
87
+ * @returns The wrapped user code
88
+ */
89
+ async function wrapUserCode ( wrapperCode : string , userModuleCode : string ) : Promise < string > {
90
+ const rollupBuild = await rollup ( {
91
+ input : SENTRY_WRAPPER_MODULE_NAME ,
92
+
93
+ plugins : [
94
+ // We're using virtual modules so we don't have to mess around with file paths
95
+ virtual ( {
96
+ [ SENTRY_WRAPPER_MODULE_NAME ] : wrapperCode ,
97
+ [ WRAPPING_TARGET_MODULE_NAME ] : userModuleCode ,
98
+ } ) ,
99
+
100
+ // People may use `module.exports` in their API routes or page files. Next.js allows that and we also need to
101
+ // handle that correctly so we let a plugin to take care of bundling cjs exports for us.
102
+ commonjs ( ) ,
103
+ ] ,
104
+
105
+ // We only want to bundle our wrapper module and the wrappee module into one, so we mark everything else as external.
106
+ external : source => source !== SENTRY_WRAPPER_MODULE_NAME && source !== WRAPPING_TARGET_MODULE_NAME ,
107
+
108
+ // Prevent rollup from stressing out about TS's use of global `this` when polyfilling await. (TS will polyfill if the
109
+ // user's tsconfig `target` is set to anything before `es2017`. See https://stackoverflow.com/a/72822340 and
110
+ // https://stackoverflow.com/a/60347490.)
111
+ context : 'this' ,
112
+
113
+ // Rollup's path-resolution logic when handling re-exports can go wrong when wrapping pages which aren't at the root
114
+ // level of the `pages` directory. This may be a bug, as it doesn't match the behavior described in the docs, but what
115
+ // seems to happen is this:
116
+ //
117
+ // - We try to wrap `pages/xyz/userPage.js`, which contains `export { helperFunc } from '../../utils/helper'`
118
+ // - Rollup converts '../../utils/helper' into an absolute path
119
+ // - We mark the helper module as external
120
+ // - Rollup then converts it back to a relative path, but relative to `pages/` rather than `pages/xyz/`. (This is
121
+ // the part which doesn't match the docs. They say that Rollup will use the common ancestor of all modules in the
122
+ // bundle as the basis for the relative path calculation, but both our temporary file and the page being wrapped
123
+ // live in `pages/xyz/`, and they're the only two files in the bundle, so `pages/xyz/`` should be used as the
124
+ // root. Unclear why it's not.)
125
+ // - As a result of the miscalculation, our proxy module will include `export { helperFunc } from '../utils/helper'`
126
+ // rather than the expected `export { helperFunc } from '../../utils/helper'`, thereby causing a build error in
127
+ // nextjs..
128
+ //
129
+ // Setting `makeAbsoluteExternalsRelative` to `false` prevents all of the above by causing Rollup to ignore imports of
130
+ // externals entirely, with the result that their paths remain untouched (which is what we want).
131
+ makeAbsoluteExternalsRelative : false ,
132
+
133
+ onwarn : ( ) => {
134
+ // Suppress all warnings - we don't want to bother people with this output
135
+ } ,
136
+
137
+ output : {
138
+ // TODO
139
+ interop : 'auto' ,
140
+ // TODO
141
+ exports : 'named' ,
142
+ } ,
143
+ } ) ;
144
+
145
+ const finalBundle = await rollupBuild . generate ( {
146
+ format : 'esm' ,
147
+ } ) ;
148
+
149
+ // The module at index 0 is always the entrypoint, which in this case is the proxy module.
150
+ return finalBundle . output [ 0 ] . code ;
151
+ }
0 commit comments