|
1 |
| -import fs from 'fs' |
2 |
| -import path from 'path' |
3 |
| -import HoneybadgerSourceMapPlugin from '@honeybadger-io/webpack' |
4 |
| -import type { WebpackConfigContext } from 'next/dist/server/config-shared' |
5 |
| -import { HoneybadgerNextJsConfig, NextJsRuntime, HoneybadgerWebpackPluginOptions } from './types' |
6 |
| - |
7 |
| -const URL_DOCS_SOURCE_MAPS_UPLOAD = 'https://docs.honeybadger.io/lib/javascript/integration/nextjs/#source-map-upload-and-tracking-deploys' |
8 |
| -let _silent = true |
9 |
| -function log(type: 'error' | 'warn' | 'debug', msg: string): void { |
10 |
| - if (['error', 'warn'].includes(type) || !_silent) { |
11 |
| - console[type]('[HoneybadgerNextJs]', msg) |
12 |
| - } |
13 |
| -} |
14 |
| - |
15 |
| -function shouldUploadSourceMaps(honeybadgerNextJsConfig: HoneybadgerNextJsConfig, context: WebpackConfigContext): boolean { |
16 |
| - const { dev } = context |
17 |
| - |
18 |
| - if (honeybadgerNextJsConfig.disableSourceMapUpload) { |
19 |
| - return false |
20 |
| - } |
21 |
| - |
22 |
| - if (!honeybadgerNextJsConfig.webpackPluginOptions || !honeybadgerNextJsConfig.webpackPluginOptions.apiKey) { |
23 |
| - log('warn', `skipping source map upload; here's how to enable: ${URL_DOCS_SOURCE_MAPS_UPLOAD}`) |
24 |
| - return false |
25 |
| - } |
26 |
| - |
27 |
| - if (dev || process.env.NODE_ENV === 'development') { |
28 |
| - return false |
29 |
| - } |
30 |
| - |
31 |
| - return true |
32 |
| -} |
33 |
| - |
34 |
| -function mergeWithExistingWebpackConfig(nextJsWebpackConfig, honeybadgerNextJsConfig: HoneybadgerNextJsConfig) { |
35 |
| - return function webpackFunctionMergedWithHb(webpackConfig, context: WebpackConfigContext) { |
36 |
| - |
37 |
| - const { isServer, dir: projectDir, nextRuntime } = context |
38 |
| - const configType = isServer ? (nextRuntime === 'edge' ? 'edge' : 'server') : 'browser' |
39 |
| - log('debug', `reached webpackFunctionMergedWithHb isServer[${isServer}] configType[${configType}]`) |
40 |
| - |
41 |
| - let result = { ...webpackConfig } |
42 |
| - if (typeof nextJsWebpackConfig === 'function') { |
43 |
| - result = nextJsWebpackConfig(result, context) |
44 |
| - } |
45 |
| - |
46 |
| - const originalEntry = result.entry |
47 |
| - result.entry = async () => injectHoneybadgerConfigToEntry(originalEntry, projectDir, configType) |
48 |
| - |
49 |
| - if (shouldUploadSourceMaps(honeybadgerNextJsConfig, context)) { |
50 |
| - // `result.devtool` must be 'hidden-source-map' or 'source-map' to properly pass sourcemaps. |
51 |
| - // Next.js uses regular `source-map` which doesnt pass its sourcemaps to Webpack. |
52 |
| - // https://github.com/vercel/next.js/blob/89ec21ed686dd79a5770b5c669abaff8f55d8fef/packages/next/build/webpack/config/blocks/base.ts#L40 |
53 |
| - // Use the hidden-source-map option when you don't want the source maps to be |
54 |
| - // publicly available on the servers, only to the error reporting |
55 |
| - result.devtool = 'hidden-source-map' |
56 |
| - if (!result.plugins) { |
57 |
| - result.plugins = [] |
58 |
| - } |
59 |
| - const options = getWebpackPluginOptions(honeybadgerNextJsConfig) |
60 |
| - if (options) { |
61 |
| - result.plugins.push(new HoneybadgerSourceMapPlugin(options)) |
62 |
| - } |
63 |
| - } |
64 |
| - |
65 |
| - return result |
66 |
| - } |
67 |
| -} |
68 |
| - |
69 |
| -async function injectHoneybadgerConfigToEntry(originalEntry, projectDir: string, configType: NextJsRuntime) { |
70 |
| - const result = typeof originalEntry === 'function' ? await originalEntry() : { ...originalEntry } |
71 |
| - const hbConfigFile = getHoneybadgerConfigFile(projectDir, configType) |
72 |
| - if (!hbConfigFile) { |
73 |
| - return result |
74 |
| - } |
75 |
| - |
76 |
| - const hbConfigFileRelativePath = `./${hbConfigFile}` |
77 |
| - if (!Object.keys(result).length) { |
78 |
| - log('debug', `no entry points for configType[${configType}]`) |
79 |
| - } |
80 |
| - for (const entryName in result) { |
81 |
| - addHoneybadgerConfigToEntry(result, entryName, hbConfigFileRelativePath, configType) |
82 |
| - } |
83 |
| - |
84 |
| - return result |
85 |
| -} |
86 |
| - |
87 |
| -function addHoneybadgerConfigToEntry(entry, entryName: string, hbConfigFile: string, configType: NextJsRuntime) { |
88 |
| - |
89 |
| - log('debug', `adding entry[${entryName}] to configType[${configType}]`) |
90 |
| - |
91 |
| - switch (configType) { |
92 |
| - case 'server': |
93 |
| - if (!entryName.startsWith('pages/')) { |
94 |
| - return |
95 |
| - } |
96 |
| - |
97 |
| - break |
98 |
| - case 'browser': |
99 |
| - if (!['pages/_app', 'main-app'].includes(entryName)) { |
100 |
| - return |
101 |
| - } |
102 |
| - |
103 |
| - break |
104 |
| - case 'edge': |
105 |
| - // nothing? |
106 |
| - |
107 |
| - break |
108 |
| - } |
109 |
| - |
110 |
| - const currentEntryPoint = entry[entryName] |
111 |
| - let newEntryPoint = currentEntryPoint |
112 |
| - |
113 |
| - if (typeof currentEntryPoint === 'string') { |
114 |
| - newEntryPoint = [hbConfigFile, currentEntryPoint] |
115 |
| - } else if (Array.isArray(currentEntryPoint)) { |
116 |
| - newEntryPoint = [hbConfigFile, ...currentEntryPoint] |
117 |
| - } // descriptor object (webpack 5+) |
118 |
| - else if (typeof currentEntryPoint === 'object' && currentEntryPoint && 'import' in currentEntryPoint) { |
119 |
| - const currentImportValue = currentEntryPoint['import'] |
120 |
| - const newImportValue = [hbConfigFile] |
121 |
| - if (typeof currentImportValue === 'string') { |
122 |
| - newImportValue.push(currentImportValue) |
123 |
| - } else { |
124 |
| - newImportValue.push(...(currentImportValue)) |
125 |
| - } |
126 |
| - newEntryPoint = { |
127 |
| - ...currentEntryPoint, |
128 |
| - import: newImportValue, |
129 |
| - }; |
130 |
| - } else { |
131 |
| - log('error', 'Could not inject Honeybadger config to entry point: ' + JSON.stringify(currentEntryPoint, null, 2)) |
132 |
| - } |
133 |
| - |
134 |
| - entry[entryName] = newEntryPoint |
135 |
| -} |
136 |
| - |
137 |
| -function getHoneybadgerConfigFile(projectDir: string, configType: NextJsRuntime): string | null { |
138 |
| - const possibilities = [`honeybadger.${configType}.config.ts`, `honeybadger.${configType}.config.js`] |
139 |
| - |
140 |
| - for (const filename of possibilities) { |
141 |
| - if (fs.existsSync(path.resolve(projectDir, filename))) { |
142 |
| - return filename; |
143 |
| - } |
144 |
| - } |
145 |
| - |
146 |
| - log('debug', `could not find config file in ${projectDir} for ${configType}`) |
147 |
| - return null |
148 |
| -} |
149 |
| - |
150 |
| -function getWebpackPluginOptions(honeybadgerNextJsConfig: HoneybadgerNextJsConfig): HoneybadgerWebpackPluginOptions | null { |
151 |
| - const apiKey = honeybadgerNextJsConfig.webpackPluginOptions?.apiKey || process.env.NEXT_PUBLIC_HONEYBADGER_API_KEY |
152 |
| - const assetsUrl = honeybadgerNextJsConfig.webpackPluginOptions?.assetsUrl || process.env.NEXT_PUBLIC_HONEYBADGER_ASSETS_URL |
153 |
| - if (!apiKey || !assetsUrl) { |
154 |
| - log('error', 'Missing Honeybadger required configuration for webpack plugin. Source maps will not be uploaded to Honeybadger.') |
155 |
| - |
156 |
| - return null |
157 |
| - } |
158 |
| - |
159 |
| - return { |
160 |
| - ...honeybadgerNextJsConfig.webpackPluginOptions, |
161 |
| - apiKey, |
162 |
| - assetsUrl, |
163 |
| - revision: honeybadgerNextJsConfig.webpackPluginOptions?.revision || process.env.NEXT_PUBLIC_HONEYBADGER_REVISION, |
164 |
| - silent: _silent, |
165 |
| - } |
166 |
| -} |
167 |
| - |
168 |
| -function getNextJsVersionInstalled(): [major: string, minor: string, patch: string] | null { |
169 |
| - try { |
170 |
| - return require('next/package.json').version?.split('.') |
171 |
| - } catch (e) { |
172 |
| - return null |
173 |
| - } |
174 |
| -} |
175 |
| - |
176 |
| -/** |
177 |
| - * NextJs will report a warning if the `serverExternalPackages` option is not present. |
178 |
| - * This is because @honeybadger-io/js will try to require configuration files dynamically (https://github.com/honeybadger-io/honeybadger-js/pull/1268). |
179 |
| - * |
180 |
| - * First reported here: https://github.com/honeybadger-io/honeybadger-js/issues/1351 |
181 |
| - */ |
182 |
| -function addServerExternalPackagesOption(config) { |
183 |
| - // this should be available in the upcoming version of Next.js (14.3.0) |
184 |
| - if (config.serverExternalPackages && Array.isArray(config.serverExternalPackages)) { |
185 |
| - log('debug', 'adding @honeybadger-io/js to serverExternalPackages') |
186 |
| - config.serverExternalPackages.push('@honeybadger-io/js') |
187 |
| - return |
188 |
| - } |
189 |
| - |
190 |
| - if (config.experimental?.serverComponentsExternalPackages && Array.isArray(config.experimental?.serverComponentsExternalPackages)) { |
191 |
| - log('debug', 'adding @honeybadger-io/js to experimental.serverComponentsExternalPackages') |
192 |
| - config.experimental.serverComponentsExternalPackages.push('@honeybadger-io/js') |
193 |
| - return |
194 |
| - } |
195 |
| - |
196 |
| - const nextJsVersion = getNextJsVersionInstalled(); |
197 |
| - if (nextJsVersion) { |
198 |
| - if ((+nextJsVersion[0] === 14 && +nextJsVersion[1] >= 3) || +nextJsVersion[0] > 14) { |
199 |
| - log('debug', 'adding serverExternalPackages option with value ["@honeybadger-io/js"]') |
200 |
| - config.serverExternalPackages = ['@honeybadger-io/js'] |
201 |
| - } |
202 |
| - else { |
203 |
| - log('debug', 'adding experimental.serverComponentsExternalPackages option with value ["@honeybadger-io/js"]') |
204 |
| - if (!config.experimental) { |
205 |
| - config.experimental = {} |
206 |
| - } |
207 |
| - config.experimental.serverComponentsExternalPackages = ['@honeybadger-io/js'] |
208 |
| - } |
209 |
| - } |
210 |
| -} |
211 |
| - |
212 |
| -export function setupHoneybadger(config, honeybadgerNextJsConfig?: HoneybadgerNextJsConfig) { |
213 |
| - if (!honeybadgerNextJsConfig) { |
214 |
| - honeybadgerNextJsConfig = { |
215 |
| - silent: true, |
216 |
| - disableSourceMapUpload: false, |
217 |
| - } |
218 |
| - } |
219 |
| - |
220 |
| - _silent = honeybadgerNextJsConfig.silent ?? true |
221 |
| - |
222 |
| - addServerExternalPackagesOption(config) |
223 |
| - |
224 |
| - return { |
225 |
| - ...config, |
226 |
| - webpack: mergeWithExistingWebpackConfig(config.webpack, honeybadgerNextJsConfig) |
227 |
| - } |
228 |
| -} |
| 1 | +export * from './webpack' |
| 2 | +export * from './with-honeybadger' |
0 commit comments