Skip to content

Commit d23214d

Browse files
authored
feat(sveltekit): Add sentrySvelteKitPlugin (#7788)
Add the `sentrySvelteKitPlugin` vite plugin to the SvelteKit SDK which will replace the `withSentryViteConfig` wrapper. Currently, this plugin does exactly what the wrapper is doing, namely, adding the injectInitPlugin. In the future, this plugin will add the source maps plugin.
1 parent c3a42f3 commit d23214d

11 files changed

+200
-29
lines changed

packages/sveltekit/src/config/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export * from './server';
2-
export * from './config';
2+
export * from './vite';

packages/sveltekit/src/index.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// Some of the exports collide, which is not allowed, unless we redifine the colliding
55
// exports in this file - which we do below.
66
export * from './client';
7-
export * from './config';
7+
export * from './vite';
88
export * from './server';
99

1010
import type { Integration, Options, StackParser } from '@sentry/types';

packages/sveltekit/src/vite/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { withSentryViteConfig } from './withSentryViteConfig';
2+
export { sentrySvelteKitPlugin } from './sentrySvelteKitPlugin';

packages/sveltekit/src/config/vitePlugins.ts renamed to packages/sveltekit/src/vite/injectInitPlugin.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { logger } from '@sentry/utils';
2-
import * as fs from 'fs';
32
import MagicString from 'magic-string';
43
import * as path from 'path';
54
import type { Plugin, TransformResult } from 'vite';
65

6+
import { getUserConfigFile } from './utils';
7+
78
const serverIndexFilePath = path.join('@sveltejs', 'kit', 'src', 'runtime', 'server', 'index.js');
89
const devClientAppFilePath = path.join('generated', 'client', 'app.js');
910
const prodClientAppFilePath = path.join('generated', 'client-optimized', 'app.js');
@@ -59,19 +60,3 @@ function addSentryConfigFileImport(
5960

6061
return { code: ms.toString(), map: ms.generateMap() };
6162
}
62-
63-
/**
64-
* Looks up the sentry.{@param platform}.config.(ts|js) file
65-
* @returns the file path to the file or undefined if it doesn't exist
66-
*/
67-
export function getUserConfigFile(projectDir: string, platform: 'server' | 'client'): string | undefined {
68-
const possibilities = [`sentry.${platform}.config.ts`, `sentry.${platform}.config.js`];
69-
70-
for (const filename of possibilities) {
71-
if (fs.existsSync(path.resolve(projectDir, filename))) {
72-
return filename;
73-
}
74-
}
75-
76-
return undefined;
77-
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { Plugin, UserConfig } from 'vite';
2+
3+
import { injectSentryInitPlugin } from './injectInitPlugin';
4+
import { hasSentryInitFiles } from './utils';
5+
6+
/**
7+
* Vite Plugin for the Sentry SvelteKit SDK, taking care of:
8+
*
9+
* - Creating Sentry releases and uploading source maps to Sentry
10+
* - Injecting Sentry.init calls if you use dedicated `sentry.(client|server).config.ts` files
11+
*
12+
* This plugin adds a few additional properties to your Vite config.
13+
* Make sure, it is registered before the SvelteKit plugin.
14+
*/
15+
export function sentrySvelteKitPlugin(): Plugin {
16+
return {
17+
name: 'sentry-sveltekit',
18+
enforce: 'pre', // we want this plugin to run early enough
19+
config: originalConfig => {
20+
return addSentryConfig(originalConfig);
21+
},
22+
};
23+
}
24+
25+
function addSentryConfig(originalConfig: UserConfig): UserConfig {
26+
const sentryPlugins = [];
27+
28+
const shouldAddInjectInitPlugin = hasSentryInitFiles();
29+
30+
if (shouldAddInjectInitPlugin) {
31+
sentryPlugins.push(injectSentryInitPlugin);
32+
}
33+
34+
const config = {
35+
...originalConfig,
36+
plugins: originalConfig.plugins ? [...sentryPlugins, ...originalConfig.plugins] : [...sentryPlugins],
37+
};
38+
39+
const mergedDevServerFileSystemConfig: UserConfig['server'] = shouldAddInjectInitPlugin
40+
? {
41+
fs: {
42+
...(config.server && config.server.fs),
43+
allow: [...((config.server && config.server.fs && config.server.fs.allow) || []), '.'],
44+
},
45+
}
46+
: {};
47+
48+
config.server = {
49+
...config.server,
50+
...mergedDevServerFileSystemConfig,
51+
};
52+
53+
return config;
54+
}

packages/sveltekit/src/vite/utils.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
/**
5+
* Checks if the user has a Sentry init file in the root of their project.
6+
* @returns true if the user has a Sentry init file, false otherwise.
7+
*/
8+
export function hasSentryInitFiles(): boolean {
9+
const hasSentryServerInit = !!getUserConfigFile(process.cwd(), 'server');
10+
const hasSentryClientInit = !!getUserConfigFile(process.cwd(), 'client');
11+
return hasSentryServerInit || hasSentryClientInit;
12+
}
13+
14+
/**
15+
* Looks up the sentry.{@param platform}.config.(ts|js) file
16+
* @returns the file path to the file or undefined if it doesn't exist
17+
*/
18+
export function getUserConfigFile(projectDir: string, platform: 'server' | 'client'): string | undefined {
19+
const possibilities = [`sentry.${platform}.config.ts`, `sentry.${platform}.config.js`];
20+
21+
for (const filename of possibilities) {
22+
if (fs.existsSync(path.resolve(projectDir, filename))) {
23+
return filename;
24+
}
25+
}
26+
27+
return undefined;
28+
}

packages/sveltekit/src/config/withSentryViteConfig.ts renamed to packages/sveltekit/src/vite/withSentryViteConfig.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { UserConfig, UserConfigExport } from 'vite';
22

3-
import { getUserConfigFile, injectSentryInitPlugin } from './vitePlugins';
3+
import { injectSentryInitPlugin } from './injectInitPlugin';
4+
import { hasSentryInitFiles } from './utils';
45

56
/**
67
* This function adds Sentry-specific configuration to your Vite config.
@@ -60,9 +61,3 @@ function addSentryConfig(originalConfig: UserConfig): UserConfig {
6061

6162
return config;
6263
}
63-
64-
function hasSentryInitFiles(): boolean {
65-
const hasSentryServerInit = !!getUserConfigFile(process.cwd(), 'server');
66-
const hasSentryClientInit = !!getUserConfigFile(process.cwd(), 'client');
67-
return hasSentryServerInit || hasSentryClientInit;
68-
}

packages/sveltekit/test/config/vitePlugins.test.ts renamed to packages/sveltekit/test/vite/injectInitPlugin.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type * as fs from 'fs';
22
import { vi } from 'vitest';
33

4-
import { injectSentryInitPlugin } from '../../src/config/vitePlugins';
4+
import { injectSentryInitPlugin } from '../../src/vite/injectInitPlugin';
55

66
vi.mock('fs', async () => {
77
const original = await vi.importActual<typeof fs>('fs');
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { vi } from 'vitest';
2+
3+
import { sentrySvelteKitPlugin } from './../../src/vite/sentrySvelteKitPlugin';
4+
import * as utils from './../../src/vite/utils';
5+
6+
describe('sentrySvelteKitPlugin', () => {
7+
it('returns a Vite plugin with name, enforce, and config hook', () => {
8+
const plugin = sentrySvelteKitPlugin();
9+
expect(plugin).toHaveProperty('name');
10+
expect(plugin).toHaveProperty('enforce');
11+
expect(plugin).toHaveProperty('config');
12+
expect(plugin.name).toEqual('sentry-sveltekit');
13+
expect(plugin.enforce).toEqual('pre');
14+
});
15+
16+
describe('config hook', () => {
17+
const hasSentryInitFilesSpy = vi.spyOn(utils, 'hasSentryInitFiles').mockReturnValue(true);
18+
19+
beforeEach(() => {
20+
hasSentryInitFilesSpy.mockClear();
21+
});
22+
23+
it('adds the injectInitPlugin and adjusts the dev server config if init config files exist', () => {
24+
const plugin = sentrySvelteKitPlugin();
25+
const originalConfig = {};
26+
27+
// @ts-ignore - plugin.config exists and is callable
28+
const modifiedConfig = plugin.config(originalConfig);
29+
30+
expect(modifiedConfig).toEqual({
31+
plugins: [
32+
{
33+
enforce: 'pre',
34+
name: 'sentry-init-injection-plugin',
35+
transform: expect.any(Function),
36+
},
37+
],
38+
server: {
39+
fs: {
40+
allow: ['.'],
41+
},
42+
},
43+
});
44+
expect(hasSentryInitFilesSpy).toHaveBeenCalledTimes(1);
45+
});
46+
47+
it('merges user-defined options with Sentry-specifc ones', () => {
48+
const plugin = sentrySvelteKitPlugin();
49+
const originalConfig = {
50+
test: {
51+
include: ['src/**/*.{test,spec}.{js,ts}'],
52+
},
53+
build: {
54+
sourcemap: 'css',
55+
},
56+
plugins: [{ name: 'some plugin' }],
57+
server: {
58+
fs: {
59+
allow: ['./build/**/*.{js}'],
60+
},
61+
},
62+
};
63+
64+
// @ts-ignore - plugin.config exists and is callable
65+
const modifiedConfig = plugin.config(originalConfig);
66+
67+
expect(modifiedConfig).toEqual({
68+
test: {
69+
include: ['src/**/*.{test,spec}.{js,ts}'],
70+
},
71+
build: {
72+
sourcemap: 'css',
73+
},
74+
plugins: [
75+
{
76+
enforce: 'pre',
77+
name: 'sentry-init-injection-plugin',
78+
transform: expect.any(Function),
79+
},
80+
{ name: 'some plugin' },
81+
],
82+
server: {
83+
fs: {
84+
allow: ['./build/**/*.{js}', '.'],
85+
},
86+
},
87+
});
88+
expect(hasSentryInitFilesSpy).toHaveBeenCalledTimes(1);
89+
});
90+
91+
it("doesn't add the injectInitPlugin if init config files don't exist", () => {
92+
hasSentryInitFilesSpy.mockReturnValue(false);
93+
const plugin = sentrySvelteKitPlugin();
94+
const originalConfig = {
95+
plugins: [{ name: 'some plugin' }],
96+
};
97+
98+
// @ts-ignore - plugin.config exists and is callable
99+
const modifiedConfig = plugin.config(originalConfig);
100+
101+
expect(modifiedConfig).toEqual({
102+
plugins: [{ name: 'some plugin' }],
103+
server: {},
104+
});
105+
expect(hasSentryInitFilesSpy).toHaveBeenCalledTimes(1);
106+
});
107+
});
108+
});

packages/sveltekit/test/config/withSentryViteConfig.test.ts renamed to packages/sveltekit/test/vite/withSentryViteConfig.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type fs from 'fs';
22
import type { Plugin, UserConfig } from 'vite';
33
import { vi } from 'vitest';
44

5-
import { withSentryViteConfig } from '../../src/config/withSentryViteConfig';
5+
import { withSentryViteConfig } from '../../src/vite/withSentryViteConfig';
66

77
let existsFile = true;
88
vi.mock('fs', async () => {

0 commit comments

Comments
 (0)