diff --git a/.changeset/great-schools-yawn.md b/.changeset/great-schools-yawn.md new file mode 100644 index 000000000..ec53a4dc9 --- /dev/null +++ b/.changeset/great-schools-yawn.md @@ -0,0 +1,5 @@ +--- +'@astrojs/compiler': minor +--- + +Allow preprocessStyle to return an error diff --git a/cmd/astro-wasm/astro-wasm.go b/cmd/astro-wasm/astro-wasm.go index 6db05a792..35e199ba1 100644 --- a/cmd/astro-wasm/astro-wasm.go +++ b/cmd/astro-wasm/astro-wasm.go @@ -162,10 +162,11 @@ type TransformResult struct { Scripts []HoistedScript `js:"scripts"` HydratedComponents []HydratedComponent `js:"hydratedComponents"` ClientOnlyComponents []HydratedComponent `js:"clientOnlyComponents"` + StyleError []string `js:"styleError"` } // This is spawned as a goroutine to preprocess style nodes using an async function passed from JS -func preprocessStyle(i int, style *astro.Node, transformOptions transform.TransformOptions, cb func()) { +func preprocessStyle(i int, style *astro.Node, transformOptions transform.TransformOptions, styleError *[]string, cb func()) { defer cb() if style.FirstChild == nil { return @@ -176,6 +177,14 @@ func preprocessStyle(i int, style *astro.Node, transformOptions transform.Transf if data[0].Equal(js.Undefined()) || data[0].Equal(js.Null()) { return } + // If an error return, override the style's CSS so the compiler doesn't hang + // And return a styleError. The caller will use this to know that style processing failed. + if err := jsString(data[0].Get("error")); err != "" { + style.FirstChild.Data = "" + //*styleError = err + *styleError = append(*styleError, err) + return + } str := jsString(data[0].Get("code")) if str == "" { return @@ -228,6 +237,7 @@ func Transform() interface{} { transformOptions := makeTransformOptions(js.Value(args[1])) transformOptions.Scope = astro.HashFromSourceAndModuleId(source, transformOptions.ModuleId) + styleError := []string{} handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { resolve := args[0] @@ -255,7 +265,7 @@ func Transform() interface{} { for i, style := range doc.Styles { wg.Add(1) i := i - go preprocessStyle(i, style, transformOptions, wg.Done) + go preprocessStyle(i, style, transformOptions, &styleError, wg.Done) } } } @@ -357,11 +367,11 @@ func Transform() interface{} { var value interface{} switch transformOptions.SourceMap { case "external": - value = createExternalSourceMap(source, result, css, &scripts, &hydratedComponents, &clientOnlyComponents, transformOptions) + value = createExternalSourceMap(source, result, css, &scripts, &hydratedComponents, &clientOnlyComponents, &styleError, transformOptions) case "both": - value = createBothSourceMap(source, result, css, &scripts, &hydratedComponents, &clientOnlyComponents, transformOptions) + value = createBothSourceMap(source, result, css, &scripts, &hydratedComponents, &clientOnlyComponents, &styleError, transformOptions) case "inline": - value = createInlineSourceMap(source, result, css, &scripts, &hydratedComponents, &clientOnlyComponents, transformOptions) + value = createInlineSourceMap(source, result, css, &scripts, &hydratedComponents, &clientOnlyComponents, &styleError, transformOptions) default: value = vert.ValueOf(TransformResult{ CSS: css, @@ -371,6 +381,7 @@ func Transform() interface{} { Scripts: scripts, HydratedComponents: hydratedComponents, ClientOnlyComponents: clientOnlyComponents, + StyleError: styleError, }) } @@ -404,7 +415,7 @@ func createSourceMapString(source string, result printer.PrintResult, transformO }`, sourcemap.Sources[0], sourcemap.SourcesContent[0], sourcemap.Mappings) } -func createExternalSourceMap(source string, result printer.PrintResult, css []string, scripts *[]HoistedScript, hydratedComponents *[]HydratedComponent, clientOnlyComponents *[]HydratedComponent, transformOptions transform.TransformOptions) interface{} { +func createExternalSourceMap(source string, result printer.PrintResult, css []string, scripts *[]HoistedScript, hydratedComponents *[]HydratedComponent, clientOnlyComponents *[]HydratedComponent, styleError *[]string, transformOptions transform.TransformOptions) interface{} { return vert.ValueOf(TransformResult{ CSS: css, Code: string(result.Output), @@ -413,10 +424,11 @@ func createExternalSourceMap(source string, result printer.PrintResult, css []st Scripts: *scripts, HydratedComponents: *hydratedComponents, ClientOnlyComponents: *clientOnlyComponents, + StyleError: *styleError, }) } -func createInlineSourceMap(source string, result printer.PrintResult, css []string, scripts *[]HoistedScript, hydratedComponents *[]HydratedComponent, clientOnlyComponents *[]HydratedComponent, transformOptions transform.TransformOptions) interface{} { +func createInlineSourceMap(source string, result printer.PrintResult, css []string, scripts *[]HoistedScript, hydratedComponents *[]HydratedComponent, clientOnlyComponents *[]HydratedComponent, styleError *[]string, transformOptions transform.TransformOptions) interface{} { sourcemapString := createSourceMapString(source, result, transformOptions) inlineSourcemap := `//# sourceMappingURL=data:application/json;charset=utf-8;base64,` + base64.StdEncoding.EncodeToString([]byte(sourcemapString)) return vert.ValueOf(TransformResult{ @@ -427,10 +439,11 @@ func createInlineSourceMap(source string, result printer.PrintResult, css []stri Scripts: *scripts, HydratedComponents: *hydratedComponents, ClientOnlyComponents: *clientOnlyComponents, + StyleError: *styleError, }) } -func createBothSourceMap(source string, result printer.PrintResult, css []string, scripts *[]HoistedScript, hydratedComponents *[]HydratedComponent, clientOnlyComponents *[]HydratedComponent, transformOptions transform.TransformOptions) interface{} { +func createBothSourceMap(source string, result printer.PrintResult, css []string, scripts *[]HoistedScript, hydratedComponents *[]HydratedComponent, clientOnlyComponents *[]HydratedComponent, styleError *[]string, transformOptions transform.TransformOptions) interface{} { sourcemapString := createSourceMapString(source, result, transformOptions) inlineSourcemap := `//# sourceMappingURL=data:application/json;charset=utf-8;base64,` + base64.StdEncoding.EncodeToString([]byte(sourcemapString)) return vert.ValueOf(TransformResult{ @@ -441,5 +454,6 @@ func createBothSourceMap(source string, result printer.PrintResult, css []string Scripts: *scripts, HydratedComponents: *hydratedComponents, ClientOnlyComponents: *clientOnlyComponents, + StyleError: *styleError, }) } diff --git a/packages/compiler/shared/types.ts b/packages/compiler/shared/types.ts index de1887991..e8b37a312 100644 --- a/packages/compiler/shared/types.ts +++ b/packages/compiler/shared/types.ts @@ -6,6 +6,10 @@ export interface PreprocessorResult { map?: string; } +export interface PreprocessorError { + error: string; +} + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ParseOptions { position?: boolean; @@ -24,7 +28,7 @@ export interface TransformOptions { */ as?: 'document' | 'fragment'; projectRoot?: string; - preprocessStyle?: (content: string, attrs: Record) => Promise; + preprocessStyle?: (content: string, attrs: Record) => null | Promise; experimentalStaticExtraction?: boolean; } @@ -54,6 +58,7 @@ export interface TransformResult { code: string; map: string; scope: string; + styleError: string[]; } export interface TSXResult { diff --git a/packages/compiler/test/bad-styles/sass.ts b/packages/compiler/test/bad-styles/sass.ts new file mode 100644 index 000000000..f74854be1 --- /dev/null +++ b/packages/compiler/test/bad-styles/sass.ts @@ -0,0 +1,32 @@ +import { test } from 'uvu'; +import * as assert from 'uvu/assert'; +import { transform } from '@astrojs/compiler'; + +const FIXTURE = ` + + +`; + +test('it works', async () => { + let result = await transform(FIXTURE, { + experimentalStaticExtraction: true, + pathname: '/@fs/users/astro/apps/pacman/src/pages/index.astro', + async preprocessStyle() { + return { + error: new Error('Unable to convert').message, + }; + }, + }); + assert.equal(result.styleError.length, 2); + assert.equal(result.styleError[0], 'Unable to convert'); +}); + +test.run();