Skip to content

Commit

Permalink
Fix support for scss in static build (#2522)
Browse files Browse the repository at this point in the history
* Fix support for scss in static build

* Adds a changeset

* Pass the normalizedID to transformWithVite
  • Loading branch information
matthewp authored Feb 2, 2022
1 parent 9e9567c commit 3e8844f
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/brown-dancers-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fix for CSS superset support and HMR in the static build
4 changes: 2 additions & 2 deletions examples/fast-build/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import ExternalHoisted from '../components/ExternalHoisted.astro';
}
</style>
<style lang="scss">
$color: purple;
@import "../styles/_global.scss";
h2 {
color: purple;
color: $color;
}
</style>
<style define:vars={{ color: 'blue' }}>
Expand Down
1 change: 1 addition & 0 deletions examples/fast-build/src/styles/_global.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$color: tan;
44 changes: 28 additions & 16 deletions packages/astro/src/vite-plugin-astro/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { fileURLToPath } from 'url';
import { transform } from '@astrojs/compiler';
import { transformWithVite } from './styles.js';

type CompilationCache = Map<string, TransformResult>;
type CompilationCache = Map<string, CompileResult>;

const configCache = new WeakMap<AstroConfig, CompilationCache>();

Expand All @@ -26,14 +26,17 @@ function isSSR(options: undefined | boolean | { ssr: boolean }): boolean {
return false;
}

async function compile(config: AstroConfig, filename: string, source: string, viteTransform: TransformHook, opts: boolean | undefined) {
type CompileResult = TransformResult & { rawCSSDeps: Set<string> };

async function compile(config: AstroConfig, filename: string, source: string, viteTransform: TransformHook, opts: boolean | undefined): Promise<CompileResult> {
// pages and layouts should be transformed as full documents (implicit <head> <body> etc)
// everything else is treated as a fragment
const filenameURL = new URL(`file://${filename}`);
const normalizedID = fileURLToPath(filenameURL);
const isPage = normalizedID.startsWith(fileURLToPath(config.pages)) || normalizedID.startsWith(fileURLToPath(config.layouts));
const pathname = filenameURL.pathname.substr(config.projectRoot.pathname.length - 1);

let rawCSSDeps = new Set<string>();
let cssTransformError: Error | undefined;

// Transform from `.astro` to valid `.ts`
Expand All @@ -51,21 +54,20 @@ async function compile(config: AstroConfig, filename: string, source: string, vi
// TODO add experimental flag here
preprocessStyle: async (value: string, attrs: Record<string, string>) => {
const lang = `.${attrs?.lang || 'css'}`.toLowerCase();

try {
let prefix = '';
// In the static build, strip away at-imports so that they can be resolved
// by the pseudo-module that gets created.
// In the static build, grab any @import as CSS dependencies for HMR.
if (config.buildOptions.experimentalStaticBuild) {
value = value.replace(/(?:@import)\s(?:url\()?\s?["\'](.*?)["\']\s?\)?(?:[^;]*);?/gi, (match) => {
prefix += match;
// Replace with an empty string of the same length, to preserve source maps.
return new Array(match.length).fill(' ').join('');
value.replace(/(?:@import)\s(?:url\()?\s?["\'](.*?)["\']\s?\)?(?:[^;]*);?/gi, (match, spec) => {
rawCSSDeps.add(spec);
return match;
});
}

const result = await transformWithVite({
value,
lang,
id: filename,
id: normalizedID,
transformHook: viteTransform,
ssr: isSSR(opts),
});
Expand All @@ -79,7 +81,7 @@ async function compile(config: AstroConfig, filename: string, source: string, vi
map = result.map.toString();
}
}
const code = (prefix += result.code);
const code = result.code;
return { code, map };
} catch (err) {
// save error to throw in plugin context
Expand All @@ -92,7 +94,17 @@ async function compile(config: AstroConfig, filename: string, source: string, vi
// throw CSS transform errors here if encountered
if (cssTransformError) throw cssTransformError;

return transformResult;
const compileResult: CompileResult = Object.create(transformResult, {
rawCSSDeps: {
value: rawCSSDeps
}
});

return compileResult;
}

export function isCached(config: AstroConfig, filename: string) {
return configCache.has(config) && (configCache.get(config)!).has(filename);
}

export function invalidateCompilation(config: AstroConfig, filename: string) {
Expand All @@ -102,7 +114,7 @@ export function invalidateCompilation(config: AstroConfig, filename: string) {
}
}

export async function cachedCompilation(config: AstroConfig, filename: string, source: string | null, viteTransform: TransformHook, opts: boolean | undefined) {
export async function cachedCompilation(config: AstroConfig, filename: string, source: string | null, viteTransform: TransformHook, opts: boolean | undefined): Promise<CompileResult> {
let cache: CompilationCache;
if (!configCache.has(config)) {
cache = new Map();
Expand All @@ -118,7 +130,7 @@ export async function cachedCompilation(config: AstroConfig, filename: string, s
const fileUrl = new URL(`file://${filename}`);
source = await fs.promises.readFile(fileUrl, 'utf-8');
}
const transformResult = await compile(config, filename, source, viteTransform, opts);
cache.set(filename, transformResult);
return transformResult;
const compileResult = await compile(config, filename, source, viteTransform, opts);
cache.set(filename, compileResult);
return compileResult;
}
68 changes: 68 additions & 0 deletions packages/astro/src/vite-plugin-astro/hmr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { AstroConfig } from '../@types/astro';
import type { ViteDevServer, ModuleNode, HmrContext } from '../core/vite';
import type { PluginContext as RollupPluginContext, ResolvedId } from 'rollup';
import { cachedCompilation, invalidateCompilation, isCached } from './compile.js';

interface TrackCSSDependenciesOptions {
viteDevServer: ViteDevServer | null;
filename: string;
id: string;
deps: Set<string>;
}

export async function trackCSSDependencies(this: RollupPluginContext, opts: TrackCSSDependenciesOptions): Promise<void> {
const { viteDevServer, filename, deps, id } = opts;
// Dev, register CSS dependencies for HMR.
if(viteDevServer) {
const mod = viteDevServer.moduleGraph.getModuleById(id);
if(mod) {
const cssDeps = (await Promise.all(Array.from(deps).map((spec) => {
return this.resolve(spec, id);
}))).filter(Boolean).map(dep => (dep as ResolvedId).id);

const { moduleGraph } = viteDevServer;
// record deps in the module graph so edits to @import css can trigger
// main import to hot update
const depModules = new Set(mod.importedModules);
for (const dep of cssDeps) {
depModules.add(moduleGraph.createFileOnlyEntry(dep))
}

// Update the module graph, telling it about our CSS deps.
moduleGraph.updateModuleInfo(mod, depModules, new Set(), true);
for (const dep of cssDeps) {
this.addWatchFile(dep);
}
}
}
}

export function handleHotUpdate(ctx: HmrContext, config: AstroConfig) {
// Invalidate the compilation cache so it recompiles
invalidateCompilation(config, ctx.file);

// go through each of these modules importers and invalidate any .astro compilation
// that needs to be rerun.
const filtered = new Set<ModuleNode>();
const files = new Set<string>();
for(const mod of ctx.modules) {
if(mod.file && isCached(config, mod.file)) {
filtered.add(mod);
files.add(mod.file);
}
for(const imp of mod.importers) {
if(imp.file && isCached(config, imp.file)) {
filtered.add(imp);
files.add(imp.file);
}
}
}

// Invalidate happens as a separate step because a single .astro file
// produces multiple CSS modules and we want to return all of those.
for(const file of files) {
invalidateCompilation(config, file);
}

return Array.from(filtered);
}
12 changes: 10 additions & 2 deletions packages/astro/src/vite-plugin-astro/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getViteTransform, TransformHook } from './styles.js';
import { parseAstroRequest } from './query.js';
import { cachedCompilation, invalidateCompilation } from './compile.js';
import ancestor from 'common-ancestor-path';
import { trackCSSDependencies, handleHotUpdate } from './hmr.js';

const FRONTMATTER_PARSE_REGEXP = /^\-\-\-(.*)^\-\-\-/ms;
interface AstroPluginOptions {
Expand All @@ -28,6 +29,7 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
}

let viteTransform: TransformHook;
let viteDevServer: vite.ViteDevServer | null = null;

// Variables for determing if an id starts with /src...
const srcRootWeb = config.src.pathname.slice(config.projectRoot.pathname.length - 1);
Expand All @@ -39,6 +41,9 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
configResolved(resolvedConfig) {
viteTransform = getViteTransform(resolvedConfig);
},
configureServer(server) {
viteDevServer = server;
},
// note: don’t claim .astro files with resolveId() — it prevents Vite from transpiling the final JS (import.meta.globEager, etc.)
async resolveId(id) {
// serve sub-part requests (*?astro) as virtual modules
Expand All @@ -64,6 +69,10 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
}

const transformResult = await cachedCompilation(config, normalizeFilename(filename), null, viteTransform, opts);

// Track any CSS dependencies so that HMR is triggered when they change.
await trackCSSDependencies.call(this, { viteDevServer, id, filename, deps: transformResult.rawCSSDeps });

const csses = transformResult.css;
const code = csses[query.index];

Expand Down Expand Up @@ -166,8 +175,7 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
}
},
async handleHotUpdate(context) {
// Invalidate the compilation cache so it recompiles
invalidateCompilation(config, context.file);
return handleHotUpdate(context, config);
},
};
}

0 comments on commit 3e8844f

Please sign in to comment.