@@ -22,8 +22,30 @@ export function componentTrackingPreprocessor(options?: ComponentTrackingInitOpt
22
22
const mergedOptions = { ...defaultComponentTrackingOptions , ...options } ;
23
23
24
24
const visitedFiles = new Set < string > ( ) ;
25
+ const visitedFilesMarkup = new Set < string > ( ) ;
25
26
26
27
const preprocessor : PreprocessorGroup = {
28
+ // This markup hook is called once per .svelte component file, before the `script` hook is called
29
+ // We use it to check if the passed component has a <script> tag. If it doesn't, we add one to inject our
30
+ // code later on, when the `script` hook is executed.
31
+ markup : ( { content, filename } ) => {
32
+ const finalFilename = filename || 'unknown' ;
33
+ const shouldInject = shouldInjectFunction ( mergedOptions . trackComponents , finalFilename , { } , visitedFilesMarkup ) ;
34
+
35
+ if ( shouldInject && ! hasScriptTag ( content ) ) {
36
+ // Insert a <script> tag into the component file where we can later on inject our code.
37
+ // We have to add a placeholder to the script tag because for empty script tags,
38
+ // the `script` preprocessor hook won't be called
39
+ // Note: The space between <script> and </script> is important! Without any content,
40
+ // the `script` hook wouldn't be executed for the added script tag.
41
+ const s = new MagicString ( content ) ;
42
+ s . prepend ( '<script>\n</script>\n' ) ;
43
+ return { code : s . toString ( ) , map : s . generateMap ( ) . toString ( ) } ;
44
+ }
45
+
46
+ return { code : content } ;
47
+ } ,
48
+
27
49
// This script hook is called whenever a Svelte component's <script> content is preprocessed.
28
50
// `content` contains the script code as a string
29
51
script : ( { content, filename, attributes } ) => {
@@ -99,3 +121,18 @@ function getBaseName(filename: string): string {
99
121
const segments = filename . split ( '/' ) ;
100
122
return segments [ segments . length - 1 ] . replace ( '.svelte' , '' ) ;
101
123
}
124
+
125
+ function hasScriptTag ( content : string ) : boolean {
126
+ // This regex is taken from the Svelte compiler code.
127
+ // They use this regex to find matching script tags that are passed to the `script` preprocessor hook:
128
+ // https://github.com/sveltejs/svelte/blob/bb83eddfc623437528f24e9fe210885b446e72fa/src/compiler/preprocess/index.ts#L144
129
+ // However, we remove the first part of the regex to not match HTML comments
130
+ const scriptTagRegex = / < s c r i p t ( \s [ ^ ] * ?) ? (?: > ( [ ^ ] * ?) < \/ s c r i p t > | \/ > ) / gi;
131
+
132
+ // Regex testing is not a super safe way of checking for the presence of a <script> tag in the Svelte
133
+ // component file but I think we can use it as a start.
134
+ // A case that is not covered by regex-testing HTML is e.g. nested <script> tags but I cannot
135
+ // think of why one would do this in Svelte components. For instance, the Svelte compiler errors
136
+ // when there's more than one top-level script tag.
137
+ return scriptTagRegex . test ( content ) ;
138
+ }
0 commit comments