From c91ce966ccbecce7a8a05bb827409d8bc70d644d Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 12 Nov 2026 11:17:37 +0000 Subject: [PATCH] feat(gatsby): Delete source maps through the bundler plugin instead of a glob The SDK now passes `deleteSourcemapsAfterUpload` to the webpack plugin instead of the `./public/**/*.map` glob. The user's own option wins, and `true` is the fallback when the SDK turned on hidden source maps. When it is set, the SDK also sets `sdkEnabledSourceMaps`, so the plugin removes all maps of the build, also CSS maps and maps of chunks outside the upload, and also when no upload runs. Both options are only passed in the `build-javascript` stage, because Gatsby reads the `build-html` maps in `.cache` to map HTML render errors. --- packages/gatsby/gatsby-node.js | 16 ++- packages/gatsby/test/gatsby-node.test.ts | 160 +++++++++++------------ 2 files changed, 85 insertions(+), 91 deletions(-) diff --git a/packages/gatsby/gatsby-node.js b/packages/gatsby/gatsby-node.js index f6fb559f68d8..02e547716d3a 100644 --- a/packages/gatsby/gatsby-node.js +++ b/packages/gatsby/gatsby-node.js @@ -4,7 +4,7 @@ const { sentryWebpackPlugin } = require('@sentry/bundler-plugins/webpack'); const SENTRY_USER_CONFIG = ['./sentry.config.js', './sentry.config.ts']; -exports.onCreateWebpackConfig = ({ getConfig, actions }, options) => { +exports.onCreateWebpackConfig = ({ getConfig, actions, stage }, options) => { const enableClientWebpackPlugin = options.enableClientWebpackPlugin !== false; if (process.env.NODE_ENV === 'production' && enableClientWebpackPlugin) { const prevSourceMapSetting = getConfig() && 'devtool' in getConfig() ? getConfig().devtool : undefined; @@ -18,10 +18,13 @@ exports.onCreateWebpackConfig = ({ getConfig, actions }, options) => { ); } - // Delete source maps per default or when this is explicitly set to `true` (`deleteSourceMapsAfterUpload: true` can override the default behavior) + // Gatsby reads the `build-html` maps in `.cache` to map HTML render errors. const deleteSourcemapsAfterUpload = - options.deleteSourcemapsAfterUpload || - (options.deleteSourcemapsAfterUpload !== false && shouldAutomaticallyEnableSourceMaps); + stage === 'build-javascript' && + (options.deleteSourcemapsAfterUpload || + (options.deleteSourcemapsAfterUpload !== false && shouldAutomaticallyEnableSourceMaps)) + ? true + : undefined; actions.setWebpackConfig({ devtool: shouldAutomaticallyEnableSourceMaps ? 'hidden-source-map' : prevSourceMapSetting, @@ -30,8 +33,9 @@ exports.onCreateWebpackConfig = ({ getConfig, actions }, options) => { sourcemaps: { // Only include files from the build output directory assets: ['./public/**'], - // Delete source files after uploading - filesToDeleteAfterUpload: deleteSourcemapsAfterUpload ? ['./public/**/*.map'] : undefined, + deleteSourcemapsAfterUpload, + // Also removes CSS maps and maps of chunks that are not uploaded. + sdkEnabledSourceMaps: deleteSourcemapsAfterUpload, // Ignore files that aren't users' source code related ignore: [ 'polyfill-*', // related to polyfills diff --git a/packages/gatsby/test/gatsby-node.test.ts b/packages/gatsby/test/gatsby-node.test.ts index cb1a8f406b33..ef9027b09136 100644 --- a/packages/gatsby/test/gatsby-node.test.ts +++ b/packages/gatsby/test/gatsby-node.test.ts @@ -1,4 +1,4 @@ -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; import { onCreateWebpackConfig } from '../gatsby-node'; vi.hoisted( @@ -106,121 +106,111 @@ describe('onCreateWebpackConfig', () => { }); }); - describe('delete source maps after upload', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - const actions = { - setWebpackConfig: vi.fn(), - }; - - const getConfig = vi.fn(); - - it('sets sourceMapFilesToDeleteAfterUpload when provided in options', () => { - const actions = { - setWebpackConfig: vi.fn(), - }; - - const getConfig = vi.fn().mockReturnValue({ devtool: 'source-map' }); - - onCreateWebpackConfig({ actions, getConfig }, { deleteSourcemapsAfterUpload: true }); - - expect(actions.setWebpackConfig).toHaveBeenCalledTimes(1); - - expect(sentryWebpackPlugin).toHaveBeenCalledWith( - expect.objectContaining({ - sourcemaps: expect.objectContaining({ - filesToDeleteAfterUpload: ['./public/**/*.map'], - }), - }), - ); - }); - + describe('source maps', () => { it.each([ { - name: 'without provided options: sets hidden source maps and deletes source maps', + name: 'build-javascript, devtool unset: enables hidden source maps and passes delete options', + stage: 'build-javascript', initialConfig: undefined, options: {}, - expected: { - devtool: 'hidden-source-map', - deleteSourceMaps: true, - }, + expected: { devtool: 'hidden-source-map', deleteOptions: true }, }, { - name: "preserves enabled source-map and doesn't delete", - initialConfig: { devtool: 'source-map' }, + name: 'build-javascript, devtool false: enables hidden source maps and passes delete options', + stage: 'build-javascript', + initialConfig: { devtool: false }, options: {}, - expected: { - devtool: 'source-map', - deleteSourceMaps: false, - }, + expected: { devtool: 'hidden-source-map', deleteOptions: true }, }, { - name: "preserves enabled hidden-source-map and doesn't delete", - initialConfig: { devtool: 'hidden-source-map' }, + name: 'build-javascript, devtool eval: enables hidden source maps and passes delete options', + stage: 'build-javascript', + initialConfig: { devtool: 'eval' }, options: {}, - expected: { - devtool: 'hidden-source-map', - deleteSourceMaps: false, - }, + expected: { devtool: 'hidden-source-map', deleteOptions: true }, }, { - name: 'deletes source maps, when user explicitly sets it', - initialConfig: { devtool: 'eval' }, + name: 'build-javascript, source-map: keeps it and passes no delete options', + stage: 'build-javascript', + initialConfig: { devtool: 'source-map' }, options: {}, - expected: { - devtool: 'hidden-source-map', - deleteSourceMaps: true, - }, + expected: { devtool: 'source-map', deleteOptions: undefined }, }, { - name: 'replaces disabled source maps with hidden ones and deletes them', - initialConfig: { devtool: false }, + name: 'build-javascript, hidden-source-map: keeps it and passes no delete options', + stage: 'build-javascript', + initialConfig: { devtool: 'hidden-source-map' }, options: {}, - expected: { - devtool: 'hidden-source-map', - deleteSourceMaps: true, - }, + expected: { devtool: 'hidden-source-map', deleteOptions: undefined }, }, { - name: 'explicit deleteSourcemapsAfterUpload true', + name: 'build-javascript, devtool false, user false: enables hidden source maps and passes no delete options', + stage: 'build-javascript', + initialConfig: { devtool: false }, + options: { deleteSourcemapsAfterUpload: false }, + expected: { devtool: 'hidden-source-map', deleteOptions: undefined }, + }, + { + name: 'build-javascript, source-map, user true: passes delete options', + stage: 'build-javascript', initialConfig: { devtool: 'source-map' }, options: { deleteSourcemapsAfterUpload: true }, - expected: { - devtool: 'source-map', - deleteSourceMaps: true, - }, + expected: { devtool: 'source-map', deleteOptions: true }, }, { - name: 'explicit deleteSourcemapsAfterUpload false', + name: 'build-javascript, hidden-source-map, user true: passes delete options', + stage: 'build-javascript', + initialConfig: { devtool: 'hidden-source-map' }, + options: { deleteSourcemapsAfterUpload: true }, + expected: { devtool: 'hidden-source-map', deleteOptions: true }, + }, + { + name: 'build-javascript, source-map, user truthy value: passes delete options as true', + stage: 'build-javascript', + initialConfig: { devtool: 'source-map' }, + options: { deleteSourcemapsAfterUpload: 'true' }, + expected: { devtool: 'source-map', deleteOptions: true }, + }, + { + name: 'build-javascript, hidden-source-map, user false: passes no delete options', + stage: 'build-javascript', initialConfig: { devtool: 'hidden-source-map' }, options: { deleteSourcemapsAfterUpload: false }, - expected: { - devtool: 'hidden-source-map', - deleteSourceMaps: false, - }, + expected: { devtool: 'hidden-source-map', deleteOptions: undefined }, + }, + { + name: 'build-html, devtool false: enables hidden source maps and passes no delete options', + stage: 'build-html', + initialConfig: { devtool: false }, + options: {}, + expected: { devtool: 'hidden-source-map', deleteOptions: undefined }, + }, + { + name: 'build-html, source-map, user true: passes no delete options', + stage: 'build-html', + initialConfig: { devtool: 'source-map' }, + options: { deleteSourcemapsAfterUpload: true }, + expected: { devtool: 'source-map', deleteOptions: undefined }, }, - ])('$name', ({ initialConfig, options, expected }) => { - getConfig.mockReturnValue(initialConfig); + ])('$name', ({ stage, initialConfig, options, expected }) => { + const actions = { setWebpackConfig: vi.fn() }; + const getConfig = vi.fn().mockReturnValue(initialConfig); - onCreateWebpackConfig({ actions: actions, getConfig: getConfig }, options); + onCreateWebpackConfig({ actions, getConfig, stage }, options); expect(actions.setWebpackConfig).toHaveBeenCalledTimes(1); - - expect(actions.setWebpackConfig).toHaveBeenCalledWith( - expect.objectContaining({ - devtool: expected.devtool, - plugins: expect.arrayContaining([expect.any(Object)]), - }), - ); - + expect(actions.setWebpackConfig).toHaveBeenCalledWith({ + devtool: expected.devtool, + plugins: [expect.any(Object)], + }); expect(sentryWebpackPlugin).toHaveBeenCalledWith( expect.objectContaining({ - sourcemaps: expect.objectContaining({ + sourcemaps: { assets: ['./public/**'], - filesToDeleteAfterUpload: expected.deleteSourceMaps ? ['./public/**/*.map'] : undefined, - }), + deleteSourcemapsAfterUpload: expected.deleteOptions, + sdkEnabledSourceMaps: expected.deleteOptions, + ignore: ['polyfill-*', 'framework-*', 'webpack-runtime-*'], + }, }), ); });