|
1 |
| -import sucrase from '@rollup/plugin-sucrase'; |
| 1 | +import commonjs from '@rollup/plugin-commonjs'; |
2 | 2 | import virtual from '@rollup/plugin-virtual';
|
3 |
| -import { escapeStringForRegex } from '@sentry/utils'; |
4 |
| -import * as path from 'path'; |
5 |
| -import type { InputOptions as RollupInputOptions, OutputOptions as RollupOutputOptions } from 'rollup'; |
6 | 3 | import { rollup } from 'rollup';
|
7 | 4 |
|
8 |
| -const SENTRY_PROXY_MODULE_NAME = 'sentry-proxy-module'; |
9 |
| - |
10 |
| -const getRollupInputOptions = (templateCode: string, userModulePath: string): RollupInputOptions => ({ |
11 |
| - input: SENTRY_PROXY_MODULE_NAME, |
12 |
| - |
13 |
| - plugins: [ |
14 |
| - virtual({ |
15 |
| - [SENTRY_PROXY_MODULE_NAME]: templateCode, |
16 |
| - }), |
17 |
| - |
18 |
| - sucrase({ |
19 |
| - transforms: ['jsx', 'typescript'], |
20 |
| - }), |
21 |
| - ], |
22 |
| - |
23 |
| - // 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 |
24 |
| - // proxy module (living in the temporary file we've created) and the file we're wrapping not to be external, because |
25 |
| - // otherwise they won't be processed. (We need Rollup to process the former so that we can use the code, and we need |
26 |
| - // it to process the latter so it knows what exports to re-export from the proxy module.) Past that, we don't care, so |
27 |
| - // don't bother to process anything else. |
28 |
| - external: importPath => importPath !== SENTRY_PROXY_MODULE_NAME && importPath !== userModulePath, |
29 |
| - |
30 |
| - // Prevent rollup from stressing out about TS's use of global `this` when polyfilling await. (TS will polyfill if the |
31 |
| - // user's tsconfig `target` is set to anything before `es2017`. See https://stackoverflow.com/a/72822340 and |
32 |
| - // https://stackoverflow.com/a/60347490.) |
33 |
| - context: 'this', |
34 |
| - |
35 |
| - // Rollup's path-resolution logic when handling re-exports can go wrong when wrapping pages which aren't at the root |
36 |
| - // level of the `pages` directory. This may be a bug, as it doesn't match the behavior described in the docs, but what |
37 |
| - // seems to happen is this: |
38 |
| - // |
39 |
| - // - We try to wrap `pages/xyz/userPage.js`, which contains `export { helperFunc } from '../../utils/helper'` |
40 |
| - // - Rollup converts '../../utils/helper' into an absolute path |
41 |
| - // - We mark the helper module as external |
42 |
| - // - Rollup then converts it back to a relative path, but relative to `pages/` rather than `pages/xyz/`. (This is |
43 |
| - // the part which doesn't match the docs. They say that Rollup will use the common ancestor of all modules in the |
44 |
| - // bundle as the basis for the relative path calculation, but both our temporary file and the page being wrapped |
45 |
| - // live in `pages/xyz/`, and they're the only two files in the bundle, so `pages/xyz/`` should be used as the |
46 |
| - // root. Unclear why it's not.) |
47 |
| - // - As a result of the miscalculation, our proxy module will include `export { helperFunc } from '../utils/helper'` |
48 |
| - // rather than the expected `export { helperFunc } from '../../utils/helper'`, thereby causing a build error in |
49 |
| - // nextjs.. |
50 |
| - // |
51 |
| - // Setting `makeAbsoluteExternalsRelative` to `false` prevents all of the above by causing Rollup to ignore imports of |
52 |
| - // externals entirely, with the result that their paths remain untouched (which is what we want). |
53 |
| - makeAbsoluteExternalsRelative: false, |
54 |
| -}); |
55 |
| - |
56 |
| -const rollupOutputOptions: RollupOutputOptions = { |
57 |
| - format: 'esm', |
58 |
| - |
59 |
| - // Don't create a bundle - we just want the transformed entrypoint file |
60 |
| - preserveModules: true, |
61 |
| -}; |
| 5 | +const SENTRY_WRAPPER_MODULE_NAME = 'sentry-wrapper-module'; |
| 6 | +const WRAPEE_MODULE_NAME = '__SENTRY_WRAPEE__.cjs'; |
62 | 7 |
|
63 | 8 | /**
|
64 | 9 | * Use Rollup to process the proxy module code, in order to split its `export * from '<wrapped file>'` call into
|
65 | 10 | * individual exports (which nextjs seems to need).
|
66 | 11 | *
|
67 |
| - * Note: Any errors which occur are handled by the proxy loader which calls this function. |
| 12 | + * Note: This function may throw in case something goes wrong while bundling. |
68 | 13 | *
|
69 |
| - * @param templateCode The proxy module code |
70 |
| - * @param userModulePath The path to the file being wrapped |
| 14 | + * @param templateCode The wrapper module code |
| 15 | + * @param userModuleCode The path to the file being wrapped |
71 | 16 | * @returns The processed proxy module code
|
72 | 17 | */
|
73 |
| -export async function rollupize(templateCode: string, userModulePath: string): Promise<string> { |
74 |
| - const intermediateBundle = await rollup(getRollupInputOptions(templateCode, userModulePath)); |
75 |
| - const finalBundle = await intermediateBundle.generate(rollupOutputOptions); |
| 18 | +export async function rollupize(templateCode: string, userModuleCode: string): Promise<string> { |
| 19 | + const rollupBuild = await rollup({ |
| 20 | + input: SENTRY_WRAPPER_MODULE_NAME, |
76 | 21 |
|
77 |
| - // The module at index 0 is always the entrypoint, which in this case is the proxy module. |
78 |
| - let { code } = finalBundle.output[0]; |
| 22 | + plugins: [ |
| 23 | + // We're using virtual modules so we don't have to mess around with file paths |
| 24 | + virtual({ |
| 25 | + [SENTRY_WRAPPER_MODULE_NAME]: templateCode, |
| 26 | + [WRAPEE_MODULE_NAME]: userModuleCode, |
| 27 | + }), |
| 28 | + |
| 29 | + // People may use `module.exports` in their API routes or page files. Next.js allows that and we also need to |
| 30 | + // handle that correctly so we let a plugin to take care of bundling cjs exports for us. |
| 31 | + commonjs(), |
| 32 | + ], |
| 33 | + |
| 34 | + // We only want to bundle our wrapper module and the wrappee module into one, so we mark everything else as external. |
| 35 | + external: source => source !== SENTRY_WRAPPER_MODULE_NAME && source !== WRAPEE_MODULE_NAME, |
79 | 36 |
|
80 |
| - // In addition to doing the desired work, Rollup also does a few things we *don't* want. Specifically, in messes up |
81 |
| - // the path in both `import * as origModule from '<userModulePath>'` and `export * from '<userModulePath>'`. |
82 |
| - // |
83 |
| - // - It turns the square brackets surrounding each parameterized path segment into underscores. |
84 |
| - // - It always adds `.js` to the end of the filename. |
85 |
| - // - It converts the path from aboslute to relative, which would be fine except that when used with the virual plugin, |
86 |
| - // it uses an incorrect (and not entirely predicable) base for that relative path. |
87 |
| - // |
88 |
| - // To fix this, we overwrite the messed up path with what we know it should be: `./<userModulePathBasename>`. (We can |
89 |
| - // find the value of the messed up path by looking at what `import * as origModule from '<userModulePath>'` becomes. |
90 |
| - // Because it's the first line of the template, it's also the first line of the result, and is therefore easy to |
91 |
| - // find.) |
| 37 | + // Prevent rollup from stressing out about TS's use of global `this` when polyfilling await. (TS will polyfill if the |
| 38 | + // user's tsconfig `target` is set to anything before `es2017`. See https://stackoverflow.com/a/72822340 and |
| 39 | + // https://stackoverflow.com/a/60347490.) |
| 40 | + context: 'this', |
92 | 41 |
|
93 |
| - const importStarStatement = code.split('\n')[0]; |
94 |
| - // This regex should always match (we control both the input and the process which generates it, so we can guarantee |
95 |
| - // the outcome of that processing), but just in case it somehow doesn't, we need it to throw an error so that the |
96 |
| - // proxy loader will know to return the user's code untouched rather than returning proxy module code including a |
97 |
| - // broken path. The non-null assertion asserts that a match has indeed been found. |
98 |
| - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
99 |
| - const messedUpPath = /^import \* as .* from '(.*)';$/.exec(importStarStatement)![1]; |
| 42 | + // Rollup's path-resolution logic when handling re-exports can go wrong when wrapping pages which aren't at the root |
| 43 | + // level of the `pages` directory. This may be a bug, as it doesn't match the behavior described in the docs, but what |
| 44 | + // seems to happen is this: |
| 45 | + // |
| 46 | + // - We try to wrap `pages/xyz/userPage.js`, which contains `export { helperFunc } from '../../utils/helper'` |
| 47 | + // - Rollup converts '../../utils/helper' into an absolute path |
| 48 | + // - We mark the helper module as external |
| 49 | + // - Rollup then converts it back to a relative path, but relative to `pages/` rather than `pages/xyz/`. (This is |
| 50 | + // the part which doesn't match the docs. They say that Rollup will use the common ancestor of all modules in the |
| 51 | + // bundle as the basis for the relative path calculation, but both our temporary file and the page being wrapped |
| 52 | + // live in `pages/xyz/`, and they're the only two files in the bundle, so `pages/xyz/`` should be used as the |
| 53 | + // root. Unclear why it's not.) |
| 54 | + // - As a result of the miscalculation, our proxy module will include `export { helperFunc } from '../utils/helper'` |
| 55 | + // rather than the expected `export { helperFunc } from '../../utils/helper'`, thereby causing a build error in |
| 56 | + // nextjs.. |
| 57 | + // |
| 58 | + // Setting `makeAbsoluteExternalsRelative` to `false` prevents all of the above by causing Rollup to ignore imports of |
| 59 | + // externals entirely, with the result that their paths remain untouched (which is what we want). |
| 60 | + makeAbsoluteExternalsRelative: false, |
100 | 61 |
|
101 |
| - code = code.replace(new RegExp(escapeStringForRegex(messedUpPath), 'g'), `./${path.basename(userModulePath)}`); |
| 62 | + onwarn: () => { |
| 63 | + // Suppress all warnings - we don't want to bother people with this output |
| 64 | + }, |
102 | 65 |
|
103 |
| - return code; |
| 66 | + output: { |
| 67 | + interop: 'auto', |
| 68 | + exports: 'named', |
| 69 | + }, |
| 70 | + }); |
| 71 | + |
| 72 | + const finalBundle = await rollupBuild.generate({ |
| 73 | + format: 'esm', |
| 74 | + }); |
| 75 | + |
| 76 | + // The module at index 0 is always the entrypoint, which in this case is the proxy module. |
| 77 | + return finalBundle.output[0].code; |
104 | 78 | }
|
0 commit comments