Skip to content

ref(nextjs): Simplify rollupize in proxy loader #6020

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

Merged
merged 4 commits into from
Oct 24, 2022
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
17 changes: 11 additions & 6 deletions packages/nextjs/src/config/loaders/proxyLoader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { escapeStringForRegex } from '@sentry/utils';
import { escapeStringForRegex, logger } from '@sentry/utils';
import * as fs from 'fs';
import * as path from 'path';

Expand Down Expand Up @@ -67,12 +67,17 @@ export default async function proxyLoader(this: LoaderThis<LoaderOptions>, userC

// Run the proxy module code through Rollup, in order to split the `export * from '<wrapped file>'` out into
// individual exports (which nextjs seems to require), then delete the tempoary file.
let proxyCode = await rollupize(tempFilePath, this.resourcePath);
fs.unlinkSync(tempFilePath);

if (!proxyCode) {
// We will already have thrown a warning in `rollupize`, so no need to do it again here
let proxyCode;
try {
proxyCode = await rollupize(tempFilePath, this.resourcePath);
} catch (err) {
__DEBUG_BUILD__ &&
logger.warn(
`Could not wrap ${this.resourcePath}. An error occurred while processing the proxy module template:\n${err}`,
);
return userCode;
} finally {
fs.unlinkSync(tempFilePath);
}

// Add a query string onto all references to the wrapped file, so that webpack will consider it different from the
Expand Down
49 changes: 16 additions & 33 deletions packages/nextjs/src/config/loaders/rollup.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
import type { RollupSucraseOptions } from '@rollup/plugin-sucrase';
import sucrase from '@rollup/plugin-sucrase';
import { logger } from '@sentry/utils';
import * as path from 'path';
import type { InputOptions as RollupInputOptions, OutputOptions as RollupOutputOptions } from 'rollup';
import { rollup } from 'rollup';

const getRollupInputOptions: (proxyPath: string, resourcePath: string) => RollupInputOptions = (
proxyPath,
resourcePath,
) => ({
const getRollupInputOptions = (proxyPath: string, userModulePath: string): RollupInputOptions => ({
input: proxyPath,

plugins: [
// For some reason, even though everything in `RollupSucraseOptions` besides `transforms` is supposed to be
// optional, TS complains that there are a bunch of missing properties (hence the typecast). Similar to
// https://github.com/microsoft/TypeScript/issues/20722, though that's been fixed. (In this case it's an interface
// exporting a `Pick` picking optional properties which is turning them required somehow.)'
sucrase({
transforms: ['jsx', 'typescript'],
} as unknown as RollupSucraseOptions),
}),
],

// We want to process as few files as possible, so as not to slow down the build any more than we have to. We need the
// proxy module (living in the temporary file we've created) and the file we're wrapping not to be external, because
// otherwise they won't be processed. (We need Rollup to process the former so that we can use the code, and we need
// it to process the latter so it knows what exports to re-export from the proxy module.) Past that, we don't care, so
// don't bother to process anything else.
external: importPath => importPath !== proxyPath && importPath !== resourcePath,
external: importPath => importPath !== proxyPath && importPath !== userModulePath,

// Prevent rollup from stressing out about TS's use of global `this` when polyfilling await. (TS will polyfill if the
// user's tsconfig `target` is set to anything before `es2017`. See https://stackoverflow.com/a/72822340 and
Expand All @@ -48,9 +40,8 @@ const getRollupInputOptions: (proxyPath: string, resourcePath: string) => Rollup
// rather than the expected `export { helperFunc } from '../../utils/helper'`, thereby causing a build error in
// nextjs..
//
// It's not 100% clear why, but telling it not to do the conversion back from absolute to relative (by setting
// `makeAbsoluteExternalsRelative` to `false`) seems to also prevent it from going from relative to absolute in the
// first place, with the result that the path remains untouched (which is what we want.)
// Setting `makeAbsoluteExternalsRelative` to `false` prevents all of the above by causing Rollup to ignore imports of
// externals entirely, with the result that their paths remain untouched (which is what we want).
makeAbsoluteExternalsRelative: false,
});

Expand All @@ -65,23 +56,15 @@ const rollupOutputOptions: RollupOutputOptions = {
* Use Rollup to process the proxy module file (located at `tempProxyFilePath`) in order to split its `export * from
* '<wrapped file>'` call into individual exports (which nextjs seems to need).
*
* Note: Any errors which occur are handled by the proxy loader which calls this function.
*
* @param tempProxyFilePath The path to the temporary file containing the proxy module code
* @param resourcePath The path to the file being wrapped
* @returns The processed proxy module code, or undefined if an error occurs
* @param userModulePath The path to the file being wrapped
* @returns The processed proxy module code
*/
export async function rollupize(tempProxyFilePath: string, resourcePath: string): Promise<string | undefined> {
let finalBundle;

try {
const intermediateBundle = await rollup(getRollupInputOptions(tempProxyFilePath, resourcePath));
finalBundle = await intermediateBundle.generate(rollupOutputOptions);
} catch (err) {
__DEBUG_BUILD__ &&
logger.warn(
`Could not wrap ${resourcePath}. An error occurred while processing the proxy module template:\n${err}`,
);
return undefined;
}
export async function rollupize(tempProxyFilePath: string, userModulePath: string): Promise<string> {
const intermediateBundle = await rollup(getRollupInputOptions(tempProxyFilePath, userModulePath));
const finalBundle = await intermediateBundle.generate(rollupOutputOptions);

// The module at index 0 is always the entrypoint, which in this case is the proxy module.
let { code } = finalBundle.output[0];
Expand All @@ -92,12 +75,12 @@ export async function rollupize(tempProxyFilePath: string, resourcePath: string)
// square brackets into underscores. Further, Rollup adds file extensions to bare-path-type import and export sources.
// Because it assumes that everything will have already been processed, it always uses `.js` as the added extension.
// We need to restore the original name and extension so that Webpack will be able to find the wrapped file.
const resourceFilename = path.basename(resourcePath);
const mutatedResourceFilename = resourceFilename
const userModuleFilename = path.basename(userModulePath);
const mutatedUserModuleFilename = userModuleFilename
// `[\\[\\]]` is the character class containing `[` and `]`
.replace(new RegExp('[\\[\\]]', 'g'), '_')
.replace(/(jsx?|tsx?)$/, 'js');
code = code.replace(new RegExp(mutatedResourceFilename, 'g'), resourceFilename);
code = code.replace(new RegExp(mutatedUserModuleFilename, 'g'), userModuleFilename);

return code;
}