|
1 | 1 | import * as fs from 'fs'; |
2 | 2 | import { createResolver } from '@nuxt/kit'; |
3 | 3 | import type { Nuxt } from '@nuxt/schema'; |
4 | | -import { wrapServerEntryWithDynamicImport } from '@sentry-internal/nitro-utils'; |
5 | 4 | import { consoleSandbox } from '@sentry/core'; |
6 | 5 | import type { Nitro } from 'nitropack'; |
| 6 | +import type { InputPluginOption } from 'rollup'; |
7 | 7 | import type { SentryNuxtModuleOptions } from '../common/types'; |
| 8 | +import { |
| 9 | + QUERY_END_INDICATOR, |
| 10 | + SENTRY_REEXPORTED_FUNCTIONS, |
| 11 | + SENTRY_WRAPPED_ENTRY, |
| 12 | + SENTRY_WRAPPED_FUNCTIONS, |
| 13 | + constructFunctionReExport, |
| 14 | + constructWrappedFunctionExportQuery, |
| 15 | + removeSentryQueryFromPath, |
| 16 | +} from './utils'; |
8 | 17 |
|
9 | 18 | const SERVER_CONFIG_FILENAME = 'sentry.server.config'; |
10 | 19 |
|
@@ -92,13 +101,91 @@ export function addDynamicImportEntryFileWrapper( |
92 | 101 | } |
93 | 102 |
|
94 | 103 | nitro.options.rollupConfig.plugins.push( |
95 | | - wrapServerEntryWithDynamicImport({ |
96 | | - serverEntrypointFileName: moduleOptions.serverEntrypointFileName || nitro.options.preset, |
97 | | - serverConfigFileName: SERVER_CONFIG_FILENAME, |
98 | | - resolvedServerConfigPath: createResolver(nitro.options.srcDir).resolve(`/${serverConfigFile}`), |
| 104 | + wrapEntryWithDynamicImport({ |
| 105 | + resolvedSentryConfigPath: createResolver(nitro.options.srcDir).resolve(`/${serverConfigFile}`), |
99 | 106 | entrypointWrappedFunctions: moduleOptions.entrypointWrappedFunctions, |
100 | | - additionalImports: ['import-in-the-middle/hook.mjs'], |
101 | | - debug: moduleOptions.debug, |
102 | 107 | }), |
103 | 108 | ); |
104 | 109 | } |
| 110 | + |
| 111 | + |
| 112 | +/** |
| 113 | + * A Rollup plugin which wraps the server entry with a dynamic `import()`. This makes it possible to initialize Sentry first |
| 114 | + * by using a regular `import` and load the server after that. |
| 115 | + * This also works with serverless `handler` functions, as it re-exports the `handler`. |
| 116 | + */ |
| 117 | +function wrapEntryWithDynamicImport({ |
| 118 | + resolvedSentryConfigPath, |
| 119 | + entrypointWrappedFunctions, |
| 120 | + debug, |
| 121 | + }: { resolvedSentryConfigPath: string; entrypointWrappedFunctions: string[]; debug?: boolean }): InputPluginOption { |
| 122 | + // In order to correctly import the server config file |
| 123 | + // and dynamically import the nitro runtime, we need to |
| 124 | + // mark the resolutionId with '\0raw' to fall into the |
| 125 | + // raw chunk group, c.f. https://github.com/nitrojs/nitro/commit/8b4a408231bdc222569a32ce109796a41eac4aa6#diff-e58102d2230f95ddeef2662957b48d847a6e891e354cfd0ae6e2e03ce848d1a2R142 |
| 126 | + const resolutionIdPrefix = '\0raw'; |
| 127 | + |
| 128 | + return { |
| 129 | + name: 'sentry-wrap-entry-with-dynamic-import', |
| 130 | + async resolveId(source, importer, options) { |
| 131 | + if (source.includes(`/${SERVER_CONFIG_FILENAME}`)) { |
| 132 | + return { id: source, moduleSideEffects: true }; |
| 133 | + } |
| 134 | + |
| 135 | + if (source === 'import-in-the-middle/hook.mjs') { |
| 136 | + // We are importing "import-in-the-middle" in the returned code of the `load()` function below |
| 137 | + // By setting `moduleSideEffects` to `true`, the import is added to the bundle, although nothing is imported from it |
| 138 | + // By importing "import-in-the-middle/hook.mjs", we can make sure this file is included, as not all node builders are including files imported with `module.register()`. |
| 139 | + // Prevents the error "Failed to register ESM hook Error: Cannot find module 'import-in-the-middle/hook.mjs'" |
| 140 | + return { id: source, moduleSideEffects: true, external: true }; |
| 141 | + } |
| 142 | + |
| 143 | + if (options.isEntry && source.includes('.mjs') && !source.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) { |
| 144 | + const resolution = await this.resolve(source, importer, options); |
| 145 | + |
| 146 | + // If it cannot be resolved or is external, just return it so that Rollup can display an error |
| 147 | + if (!resolution || resolution?.external) return resolution; |
| 148 | + |
| 149 | + const moduleInfo = await this.load(resolution); |
| 150 | + |
| 151 | + moduleInfo.moduleSideEffects = true; |
| 152 | + |
| 153 | + // The enclosing `if` already checks for the suffix in `source`, but a check in `resolution.id` is needed as well to prevent multiple attachment of the suffix |
| 154 | + return resolution.id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`) |
| 155 | + ? resolution.id |
| 156 | + : `${resolutionIdPrefix}${resolution.id |
| 157 | + // Concatenates the query params to mark the file (also attaches names of re-exports - this is needed for serverless functions to re-export the handler) |
| 158 | + .concat(SENTRY_WRAPPED_ENTRY) |
| 159 | + .concat( |
| 160 | + constructWrappedFunctionExportQuery(moduleInfo.exportedBindings, entrypointWrappedFunctions, debug), |
| 161 | + ) |
| 162 | + .concat(QUERY_END_INDICATOR)}`; |
| 163 | + } |
| 164 | + return null; |
| 165 | + }, |
| 166 | + load(id: string) { |
| 167 | + if (id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) { |
| 168 | + const entryId = removeSentryQueryFromPath(id).slice(resolutionIdPrefix.length); |
| 169 | + |
| 170 | + // Mostly useful for serverless `handler` functions |
| 171 | + const reExportedFunctions = |
| 172 | + id.includes(SENTRY_WRAPPED_FUNCTIONS) || id.includes(SENTRY_REEXPORTED_FUNCTIONS) |
| 173 | + ? constructFunctionReExport(id, entryId) |
| 174 | + : ''; |
| 175 | + |
| 176 | + return ( |
| 177 | + // Regular `import` of the Sentry config |
| 178 | + `import ${JSON.stringify(resolvedSentryConfigPath)};\n` + |
| 179 | + // Dynamic `import()` for the previous, actual entry point. |
| 180 | + // `import()` can be used for any code that should be run after the hooks are registered (https://nodejs.org/api/module.html#enabling) |
| 181 | + `import(${JSON.stringify(entryId)});\n` + |
| 182 | + // By importing "import-in-the-middle/hook.mjs", we can make sure this file wil be included, as not all node builders are including files imported with `module.register()`. |
| 183 | + "import 'import-in-the-middle/hook.mjs';\n" + |
| 184 | + `${reExportedFunctions}\n` |
| 185 | + ); |
| 186 | + } |
| 187 | + |
| 188 | + return null; |
| 189 | + }, |
| 190 | + }; |
| 191 | +} |
0 commit comments