-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(sveltekit): Add Sentry Vite Plugin to upload source maps #7811
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
Changes from 2 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
export { sentrySvelteKitPlugin } from './sentrySvelteKitPlugin'; | ||
export { sentryVite } from './sentryVitePlugins'; |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import type { SentryVitePluginOptions } from '@sentry/vite-plugin'; | ||
import type { Plugin } from 'vite'; | ||
|
||
import { makeCustomSentryVitePlugin } from './sourceMaps'; | ||
|
||
type SourceMapsUploadOptions = { | ||
/** | ||
* If this flag is `true`, the Sentry plugins will automatically upload source maps to Sentry. | ||
* Defaults to `true`. | ||
*/ | ||
autoUploadSourceMaps?: boolean; | ||
|
||
/** | ||
* Options for the Sentry Vite plugin to customize and override the release creation and source maps upload process. | ||
* See [Sentry Vite Plugin Options](https://github.com/getsentry/sentry-javascript-bundler-plugins/tree/main/packages/vite-plugin#configuration) for a detailed description. | ||
*/ | ||
sourceMapsUploadOptions?: Partial<SentryVitePluginOptions>; | ||
}; | ||
|
||
export type SentrySvelteKitPluginOptions = { | ||
/** | ||
* If this flag is `true`, the Sentry plugins will log some useful debug information. | ||
* Defaults to `false`. | ||
*/ | ||
debug?: boolean; | ||
} & SourceMapsUploadOptions; | ||
|
||
const DEFAULT_PLUGIN_OPTIONS: SentrySvelteKitPluginOptions = { | ||
autoUploadSourceMaps: true, | ||
debug: false, | ||
}; | ||
|
||
/** | ||
* Vite Plugins for the Sentry SvelteKit SDK, taking care of creating | ||
* Sentry releases and uploading source maps to Sentry. | ||
* | ||
* Sentry adds a few additional properties to your Vite config. | ||
* Make sure, it is registered before the SvelteKit plugin. | ||
*/ | ||
export function sentryVite(options: SentrySvelteKitPluginOptions = {}): Plugin[] { | ||
const mergedOptions = { | ||
...DEFAULT_PLUGIN_OPTIONS, | ||
...options, | ||
}; | ||
|
||
const sentryPlugins = []; | ||
|
||
if (mergedOptions.autoUploadSourceMaps) { | ||
const pluginOptions = { | ||
...mergedOptions.sourceMapsUploadOptions, | ||
debug: mergedOptions.debug, // override the plugin's debug flag with the one from the top-level options | ||
}; | ||
sentryPlugins.push(makeCustomSentryVitePlugin(pluginOptions)); | ||
} | ||
|
||
return sentryPlugins; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import type { SentryVitePluginOptions } from '@sentry/vite-plugin'; | ||
import { sentryVitePlugin } from '@sentry/vite-plugin'; | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
// @ts-ignore -sorcery has no types :( | ||
// eslint-disable-next-line import/default | ||
import * as sorcery from 'sorcery'; | ||
import type { Plugin } from 'vite'; | ||
|
||
const DEFAULT_PLUGIN_OPTIONS: SentryVitePluginOptions = { | ||
// TODO: Read these values from the node adapter somehow as the out dir can be changed in the adapter options | ||
include: ['build/server', 'build/client'], | ||
}; | ||
|
||
// sorcery has no types, so these are some basic type definitions: | ||
type Chain = { | ||
write(): Promise<void>; | ||
apply(): Promise<void>; | ||
}; | ||
type Sorcery = { | ||
load(filepath: string): Promise<Chain>; | ||
}; | ||
|
||
type SentryVitePluginOptionsOptionalInclude = Omit<SentryVitePluginOptions, 'include'> & { | ||
include?: SentryVitePluginOptions['include']; | ||
}; | ||
|
||
/** | ||
* Creates a new Vite plugin that uses the unplugin-based Sentry Vite plugin to create | ||
* releases and upload source maps to Sentry. | ||
* | ||
* Because the unplugin-based Sentry Vite plugin doesn't work ootb with SvelteKit, | ||
* we need to add some additional stuff to make source maps work: | ||
* | ||
* - the `config` hook needs to be added to generate source maps | ||
* - the `configResolved` hook tells us when to upload source maps. | ||
* We only want to upload once at the end, given that SvelteKit makes multiple builds | ||
* - the `closeBundle` hook is used to flatten server source maps, which at the moment is necessary for SvelteKit. | ||
* After the maps are flattened, they're uploaded to Sentry as in the original plugin. | ||
* see: https://github.com/sveltejs/kit/discussions/9608 | ||
* | ||
* @returns the custom Sentry Vite plugin | ||
*/ | ||
export function makeCustomSentryVitePlugin(options?: SentryVitePluginOptionsOptionalInclude): Plugin { | ||
const mergedOptions = { | ||
...DEFAULT_PLUGIN_OPTIONS, | ||
...options, | ||
}; | ||
const sentryPlugin: Plugin = sentryVitePlugin(mergedOptions); | ||
|
||
const { debug } = mergedOptions; | ||
const { buildStart, resolveId, transform, renderChunk } = sentryPlugin; | ||
|
||
let upload = true; | ||
|
||
const customPlugin: Plugin = { | ||
name: 'sentry-vite-plugin-custom', | ||
apply: 'build', // only apply this plugin at build time | ||
enforce: 'post', | ||
|
||
// These hooks are copied from the original Sentry Vite plugin. | ||
// They're mostly responsible for options parsing and release injection. | ||
buildStart, | ||
resolveId, | ||
renderChunk, | ||
transform, | ||
|
||
// Modify the config to generate source maps | ||
config: config => { | ||
// eslint-disable-next-line no-console | ||
debug && console.log('[Source Maps Plugin] Enabeling source map generation'); | ||
return { | ||
...config, | ||
build: { | ||
...config.build, | ||
sourcemap: true, | ||
}, | ||
}; | ||
}, | ||
|
||
configResolved: config => { | ||
// The SvelteKit plugins trigger additional builds within the main (SSR) build. | ||
// We just need a mechanism to upload source maps only once. | ||
// `config.build.ssr` is `true` for that first build and `false` in the other ones. | ||
// Hence we can use it as a switch to upload source maps only once in main build. | ||
if (!config.build.ssr) { | ||
upload = false; | ||
} | ||
}, | ||
|
||
// We need to start uploading source maps later than in the original plugin | ||
// because SvelteKit is still doing some stuff at closeBundle. | ||
closeBundle: () => { | ||
if (!upload) { | ||
return; | ||
} | ||
|
||
// TODO: Read the out dir from the node adapter somehow as it can be changed in the adapter options | ||
const outDir = path.resolve(process.cwd(), 'build'); | ||
|
||
const jsFiles = getFiles(outDir).filter(file => file.endsWith('.js')); | ||
// eslint-disable-next-line no-console | ||
debug && console.log('[Source Maps Plugin] Flattening source maps'); | ||
|
||
jsFiles.forEach(async file => { | ||
try { | ||
await (sorcery as Sorcery).load(file).then(async chain => { | ||
if (!chain) { | ||
// We end up here, if we don't have a source map for the file. | ||
// This is fine, as we're not interested in files w/o source maps. | ||
return; | ||
} | ||
// This flattens the source map | ||
await chain.apply(); | ||
// Write it back to the original file | ||
await chain.write(); | ||
}); | ||
} catch (e) { | ||
// Sometimes sorcery fails to flatten the source map. While this isn't ideal, it seems to be mostly | ||
// happening in Kit-internal files which is fine as they're not in-app. | ||
// This mostly happens when sorcery tries to resolve a source map while flattening that doesn't exist. | ||
const isKnownError = e instanceof Error && e.message.includes('ENOENT: no such file or directory, open'); | ||
if (debug && !isKnownError) { | ||
// eslint-disable-next-line no-console | ||
console.error('[Source Maps Plugin] error while flattening', file, e); | ||
} | ||
} | ||
}); | ||
|
||
// @ts-ignore - this hook exists on the plugin! | ||
sentryPlugin.writeBundle(); | ||
}, | ||
}; | ||
|
||
return customPlugin; | ||
} | ||
|
||
function getFiles(dir: string): string[] { | ||
if (!fs.existsSync(dir)) { | ||
return []; | ||
} | ||
const dirents = fs.readdirSync(dir, { withFileTypes: true }); | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
const files: string[] = dirents.map(dirent => { | ||
const resFileOrDir = path.resolve(dir, dirent.name); | ||
return dirent.isDirectory() ? getFiles(resFileOrDir) : resFileOrDir; | ||
}); | ||
|
||
return Array.prototype.concat(...files); | ||
} |
12 changes: 0 additions & 12 deletions
12
packages/sveltekit/test/vite/sentrySvelteKitPlugin.test.ts
This file was deleted.
Oops, something went wrong.
41 changes: 41 additions & 0 deletions
41
packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { vi } from 'vitest'; | ||
|
||
import { sentryVite } from '../../src/vite/sentryVitePlugins'; | ||
import * as sourceMaps from '../../src/vite/sourceMaps'; | ||
|
||
describe('sentryVite()', () => { | ||
it('returns an array of Vite plugins', () => { | ||
const plugins = sentryVite(); | ||
expect(plugins).toBeInstanceOf(Array); | ||
expect(plugins).toHaveLength(1); | ||
}); | ||
|
||
it('returns the custom sentry source maps plugin by default', () => { | ||
const plugins = sentryVite(); | ||
const plugin = plugins[0]; | ||
expect(plugin.name).toEqual('sentry-vite-plugin-custom'); | ||
}); | ||
|
||
it("doesn't return the custom sentry source maps plugin if autoUploadSourcemaps is `false`", () => { | ||
const plugins = sentryVite({ autoUploadSourceMaps: false }); | ||
expect(plugins).toHaveLength(0); | ||
}); | ||
|
||
it('passes user-specified vite pugin options to the custom sentry source maps plugin', () => { | ||
const makePluginSpy = vi.spyOn(sourceMaps, 'makeCustomSentryVitePlugin'); | ||
const plugins = sentryVite({ | ||
debug: true, | ||
sourceMapsUploadOptions: { | ||
include: ['foo.js'], | ||
ignore: ['bar.js'], | ||
}, | ||
}); | ||
const plugin = plugins[0]; | ||
expect(plugin.name).toEqual('sentry-vite-plugin-custom'); | ||
expect(makePluginSpy).toHaveBeenCalledWith({ | ||
debug: true, | ||
ignore: ['bar.js'], | ||
include: ['foo.js'], | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.