-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
preprocessors.ts
101 lines (81 loc) · 3.43 KB
/
preprocessors.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import MagicString from 'magic-string';
import { PreprocessorGroup } from 'svelte/types/compiler/preprocess';
import { ComponentTrackingInitOptions, SentryPreprocessorGroup, TrackComponentOptions } from './types';
export const defaultComponentTrackingOptions: Required<ComponentTrackingInitOptions> = {
trackComponents: true,
trackInit: true,
trackUpdates: true,
};
export const FIRST_PASS_COMPONENT_TRACKING_PREPROC_ID = 'FIRST_PASS_COMPONENT_TRACKING_PREPROCESSOR';
/**
* Svelte Preprocessor to inject Sentry performance monitoring related code
* into Svelte components.
*
* @deprecated Use `withSentryConfig` which is the new way of making compile-time modifications
* to Svelte apps going forward.
*/
export function componentTrackingPreprocessor(options?: ComponentTrackingInitOptions): PreprocessorGroup {
const mergedOptions = { ...defaultComponentTrackingOptions, ...options };
const visitedFiles = new Set<string>();
const preprocessor: PreprocessorGroup = {
// This script hook is called whenever a Svelte component's <script> content is preprocessed.
// `content` contains the script code as a string
script: ({ content, filename, attributes }) => {
// TODO: Not sure when a filename could be undefined. Using this 'unknown' fallback for the time being
const finalFilename = filename || 'unknown';
if (!shouldInjectFunction(mergedOptions.trackComponents, finalFilename, attributes, visitedFiles)) {
return { code: content };
}
const { trackInit, trackUpdates } = mergedOptions;
const trackComponentOptions: TrackComponentOptions = {
trackInit,
trackUpdates,
componentName: getBaseName(finalFilename),
};
const importStmt = 'import { trackComponent } from "@sentry/svelte";\n';
const functionCall = `trackComponent(${JSON.stringify(trackComponentOptions)});\n`;
const s = new MagicString(content);
s.prepend(functionCall).prepend(importStmt);
const updatedCode = s.toString();
const updatedSourceMap = s.generateMap().toString();
return { code: updatedCode, map: updatedSourceMap };
},
};
const sentryPreprocessor: SentryPreprocessorGroup = {
...preprocessor,
id: FIRST_PASS_COMPONENT_TRACKING_PREPROC_ID,
};
return sentryPreprocessor;
}
function shouldInjectFunction(
trackComponents: Required<ComponentTrackingInitOptions['trackComponents']>,
filename: string,
attributes: Record<string, string | boolean>,
visitedFiles: Set<string>,
): boolean {
// We do cannot inject our function multiple times into the same component
// This can happen when a component has multiple <script> blocks
if (visitedFiles.has(filename)) {
return false;
}
visitedFiles.add(filename);
// We can't inject our function call into <script context="module"> blocks
// because the code inside is not executed when the component is instantiated but
// when the module is first imported.
// see: https://svelte.dev/docs#component-format-script-context-module
if (attributes.context === 'module') {
return false;
}
if (!trackComponents) {
return false;
}
if (Array.isArray(trackComponents)) {
const componentName = getBaseName(filename);
return trackComponents.some(allowed => allowed === componentName);
}
return true;
}
function getBaseName(filename: string): string {
const segments = filename.split('/');
return segments[segments.length - 1].replace('.svelte', '');
}