diff --git a/.changeset/lovely-lies-dress.md b/.changeset/lovely-lies-dress.md new file mode 100644 index 000000000..9e982c8ce --- /dev/null +++ b/.changeset/lovely-lies-dress.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +fix astro scoping of "@import" inside of style tags diff --git a/examples/fast-build/package.json b/examples/fast-build/package.json index 26cc49b9b..a769fd1c7 100644 --- a/examples/fast-build/package.json +++ b/examples/fast-build/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "astro dev --experimental-static-build", - "start": "astro dev", + "start": "astro dev --experimental-static-build", "build": "astro build --experimental-static-build", "scan-build": "astro build", "preview": "astro preview" diff --git a/packages/astro/src/vite-plugin-astro/compile.ts b/packages/astro/src/vite-plugin-astro/compile.ts index 7eb0eddc4..569392ceb 100644 --- a/packages/astro/src/vite-plugin-astro/compile.ts +++ b/packages/astro/src/vite-plugin-astro/compile.ts @@ -9,11 +9,29 @@ import { transform } from '@astrojs/compiler'; import { transformWithVite } from './styles.js'; type CompilationCache = Map; +type CompileResult = TransformResult & { rawCSSDeps: Set }; + +/** + * Note: this is currently needed because Astro is directly using a Vite internal CSS transform. This gives us + * some nice features out of the box, but at the expense of also running Vite's CSS postprocessing build step, + * which does some things that we don't like, like resolving/handling `@import` too early. This function pulls + * out the `@import` tags to be added back later, and then finally handled correctly by Vite. + * + * In the future, we should remove this workaround and most likely implement our own Astro style handling without + * having to hook into Vite's internals. + */ +function createImportPlaceholder(spec: string) { + // Note: We keep this small so that we can attempt to exactly match the # of characters in the original @import. + // This keeps sourcemaps accurate (to the best of our ability) at the intermediate step where this appears. + // -> `@import '${spec}';`; + return `/*IMPORT:${spec}*/`; +} +function safelyReplaceImportPlaceholder(code: string) { + return code.replace(/\/\*IMPORT\:(.*?)\*\//g, `@import '$1';`); +} const configCache = new WeakMap(); -type CompileResult = TransformResult & { rawCSSDeps: Set }; - async function compile(config: AstroConfig, filename: string, source: string, viteTransform: TransformHook, opts: { ssr: boolean }): Promise { // pages and layouts should be transformed as full documents (implicit etc) // everything else is treated as a fragment @@ -44,9 +62,15 @@ async function compile(config: AstroConfig, filename: string, source: string, vi try { // In the static build, grab any @import as CSS dependencies for HMR. if (config.buildOptions.experimentalStaticBuild) { - value.replace(/(?:@import)\s(?:url\()?\s?["\'](.*?)["\']\s?\)?(?:[^;]*);?/gi, (match, spec) => { + value = value.replace(/(?:@import)\s(?:url\()?\s?["\'](.*?)["\']\s?\)?(?:[^;]*);?/gi, (match, spec) => { rawCSSDeps.add(spec); - return match; + // If the language is CSS: prevent `@import` inlining to prevent scoping of imports. + // Otherwise: Sass, etc. need to see imports for variables, so leave in for their compiler to handle. + if (lang === '.css') { + return createImportPlaceholder(spec); + } else { + return match; + } }); } @@ -67,7 +91,7 @@ async function compile(config: AstroConfig, filename: string, source: string, vi map = result.map.toString(); } } - const code = result.code; + const code = safelyReplaceImportPlaceholder(result.code); return { code, map }; } catch (err) { // save error to throw in plugin context