From a5fc020e6a616d6a39940f1d616547a911b07781 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Sun, 2 Jun 2024 11:51:31 -0400 Subject: [PATCH] fix(@angular/build): watch all related files during a Sass error When `dart-sass` returns an error, the provided location span may not be the exact location of the error but can instead be the location of the `@error` call within a Sass function. The generated stack trace string does contain all files related to the error including the usage site in the case of the function `@error` case. To ensure all related files for the error are watched and allow for a rebuild that may correct the error, the stack trace is parsed and found files are added to the watch list. Unfortunately, `dart-sass` does not provide the list directly. If that changes in the future, the stack trace parsing could be removed. --- .../esbuild/stylesheets/sass-language.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts b/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts index c0fb7c7397fb..313b7ccaa662 100644 --- a/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts +++ b/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts @@ -174,6 +174,11 @@ async function compileString( if (isSassException(error)) { const fileWithError = error.span.url ? fileURLToPath(error.span.url) : undefined; + const watchFiles = [filePath, ...extractFilesFromStack(error.sassStack)]; + if (fileWithError) { + watchFiles.push(fileWithError); + } + return { loader: 'css', errors: [ @@ -182,7 +187,7 @@ async function compileString( }, ], warnings, - watchFiles: fileWithError ? [filePath, fileWithError] : [filePath], + watchFiles, }; } @@ -202,3 +207,29 @@ function sourceMapToUrlComment( return `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${urlSourceMap} */`; } + +function* extractFilesFromStack(stack: string): Iterable { + const lines = stack.split('\n'); + const cwd = process.cwd(); + + // Stack line has format of " " + for (const line of lines) { + const segments = line.split(' '); + if (segments.length < 3) { + break; + } + + // Extract path from stack line. + // Paths may contain spaces. All segments before location are part of the file path. + let path = ''; + let index = 0; + while (!segments[index].match(/\d+:\d+/)) { + path += segments[index++]; + } + + if (path) { + // Stack paths from dart-sass are relative to the current working directory (not input file or workspace root) + yield join(cwd, path); + } + } +}