Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix support for scss in static build #2522

Merged
merged 3 commits into from
Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
},
};
}