@@ -22,8 +22,30 @@ export function componentTrackingPreprocessor(options?: ComponentTrackingInitOpt
2222 const mergedOptions = { ...defaultComponentTrackingOptions , ...options } ;
2323
2424 const visitedFiles = new Set < string > ( ) ;
25+ const visitedFilesMarkup = new Set < string > ( ) ;
2526
2627 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+
2749 // This script hook is called whenever a Svelte component's <script> content is preprocessed.
2850 // `content` contains the script code as a string
2951 script : ( { content, filename, attributes } ) => {
@@ -99,3 +121,18 @@ function getBaseName(filename: string): string {
99121 const segments = filename . split ( '/' ) ;
100122 return segments [ segments . length - 1 ] . replace ( '.svelte' , '' ) ;
101123}
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