Skip to content

Commit 9fb471d

Browse files
author
Luca Forstner
committed
Move wrapping function into loader
1 parent a91da16 commit 9fb471d

File tree

2 files changed

+85
-80
lines changed

2 files changed

+85
-80
lines changed

packages/nextjs/src/config/loaders/proxyLoader.ts

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import commonjs from '@rollup/plugin-commonjs';
2+
import virtual from '@rollup/plugin-virtual';
13
import { stringMatchesSomePattern } from '@sentry/utils';
24
import * as fs from 'fs';
35
import * as path from 'path';
6+
import { rollup } from 'rollup';
47

5-
import { rollupize } from './rollup';
68
import { LoaderThis } from './types';
79

10+
const SENTRY_WRAPPER_MODULE_NAME = 'sentry-wrapper-module';
11+
const WRAPPING_TARGET_MODULE_NAME = '__SENTRY_WRAPEE__.cjs';
12+
813
type LoaderOptions = {
914
pagesDir: string;
1015
pageExtensionRegex: string;
@@ -57,7 +62,7 @@ export default async function proxyLoader(this: LoaderThis<LoaderOptions>, userC
5762
// Run the proxy module code through Rollup, in order to split the `export * from '<wrapped file>'` out into
5863
// individual exports (which nextjs seems to require).
5964
try {
60-
return await rollupize(templateCode, userCode);
65+
return await wrapUserCode(templateCode, userCode);
6166
} catch (err) {
6267
// eslint-disable-next-line no-console
6368
console.warn(
@@ -66,3 +71,81 @@ export default async function proxyLoader(this: LoaderThis<LoaderOptions>, userC
6671
return userCode;
6772
}
6873
}
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+
}

packages/nextjs/src/config/loaders/rollup.ts

Lines changed: 0 additions & 78 deletions
This file was deleted.

0 commit comments

Comments
 (0)