Skip to content

Commit

Permalink
Refactor CSS preprocessing handling (#5236)
Browse files Browse the repository at this point in the history
  • Loading branch information
bluwy authored Nov 2, 2022
1 parent 0bab357 commit 1cc0670
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 260 deletions.
5 changes: 5 additions & 0 deletions .changeset/tall-keys-hunt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Refactor CSS preprocessing handling
29 changes: 17 additions & 12 deletions packages/astro/src/core/compile/compile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TransformResult } from '@astrojs/compiler';
import type { ResolvedConfig } from 'vite';
import type { AstroConfig } from '../../@types/astro';
import type { TransformStyle } from './types';

import { transform } from '@astrojs/compiler';
import { AstroErrorCodes } from '../errors/codes.js';
Expand All @@ -18,17 +18,17 @@ type CompileResult = TransformResult & {
const configCache = new WeakMap<AstroConfig, CompilationCache>();

export interface CompileProps {
config: AstroConfig;
astroConfig: AstroConfig;
viteConfig: ResolvedConfig;
filename: string;
source: string;
transformStyle: TransformStyle;
}

async function compile({
config,
astroConfig,
viteConfig,
filename,
source,
transformStyle,
}: CompileProps): Promise<CompileResult> {
let cssDeps = new Set<string>();
let cssTransformErrors: AstroError[] = [];
Expand All @@ -38,16 +38,21 @@ async function compile({
// result passed to esbuild, but also available in the catch handler.
const transformResult = await transform(source, {
pathname: filename,
projectRoot: config.root.toString(),
site: config.site?.toString(),
projectRoot: astroConfig.root.toString(),
site: astroConfig.site?.toString(),
sourcefile: filename,
sourcemap: 'both',
internalURL: `/@fs${prependForwardSlash(
viteID(new URL('../../runtime/server/index.js', import.meta.url))
)}`,
// TODO: baseline flag
experimentalStaticExtraction: true,
preprocessStyle: createStylePreprocessor(transformStyle, cssDeps, cssTransformErrors),
preprocessStyle: createStylePreprocessor({
filename,
viteConfig,
cssDeps,
cssTransformErrors,
}),
async resolvePath(specifier) {
return resolvePath(specifier, filename);
},
Expand Down Expand Up @@ -132,13 +137,13 @@ export function invalidateCompilation(config: AstroConfig, filename: string) {
}

export async function cachedCompilation(props: CompileProps): Promise<CompileResult> {
const { config, filename } = props;
const { astroConfig, filename } = props;
let cache: CompilationCache;
if (!configCache.has(config)) {
if (!configCache.has(astroConfig)) {
cache = new Map();
configCache.set(config, cache);
configCache.set(astroConfig, cache);
} else {
cache = configCache.get(config)!;
cache = configCache.get(astroConfig)!;
}
if (cache.has(filename)) {
return cache.get(filename)!;
Expand Down
103 changes: 80 additions & 23 deletions packages/astro/src/core/compile/style.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import fs from 'fs';
import type { TransformOptions } from '@astrojs/compiler';
import type { SourceMapInput } from 'rollup';
import type { TransformStyle } from './types';
import { preprocessCSS, ResolvedConfig } from 'vite';
import { AstroErrorCodes } from '../errors/codes.js';
import { CSSError } from '../errors/errors.js';
import { positionAt } from '../errors/index.js';

type PreprocessStyle = TransformOptions['preprocessStyle'];

export function createStylePreprocessor(
transformStyle: TransformStyle,
cssDeps: Set<string>,
errors: Error[]
): PreprocessStyle {
const preprocessStyle: PreprocessStyle = async (value: string, attrs: Record<string, string>) => {
export function createStylePreprocessor({
filename,
viteConfig,
cssDeps,
cssTransformErrors,
}: {
filename: string;
viteConfig: ResolvedConfig;
cssDeps: Set<string>;
cssTransformErrors: Error[];
}): TransformOptions['preprocessStyle'] {
return async (content, attrs) => {
const lang = `.${attrs?.lang || 'css'}`.toLowerCase();

const id = `${filename}?astro&type=style&lang${lang}`;
try {
const result = await transformStyle(value, lang);

if (!result) return null as any; // TODO: add type in compiler to fix "any"
const result = await preprocessCSS(content, id, viteConfig);

for (const dep of result.deps) {
result.deps?.forEach((dep) => {
cssDeps.add(dep);
}
});

let map: SourceMapInput | undefined;
let map: string | undefined;
if (result.map) {
if (typeof result.map === 'string') {
map = result.map;
Expand All @@ -31,13 +36,65 @@ export function createStylePreprocessor(
}

return { code: result.code, map };
} catch (err) {
errors.push(err as unknown as Error);
return {
error: err + '',
};
} catch (err: any) {
try {
err = enhanceCSSError(err, filename);
} catch {}
cssTransformErrors.push(err);
return { error: err + '' };
}
};
}

function enhanceCSSError(err: any, filename: string) {
const fileContent = fs.readFileSync(filename).toString();
const styleTagBeginning = fileContent.indexOf(err.input?.source ?? err.code);

// PostCSS Syntax Error
if (err.name === 'CssSyntaxError') {
const errorLine = positionAt(styleTagBeginning, fileContent).line + (err.line ?? 0);

// Vite will handle creating the frame for us with proper line numbers, no need to create one

return new CSSError({
errorCode: AstroErrorCodes.CssSyntaxError,
message: err.reason,
location: {
file: filename,
line: errorLine,
column: err.column,
},
});
}

// Some CSS processor will return a line and a column, so let's try to show a pretty error
if (err.line && err.column) {
const errorLine = positionAt(styleTagBeginning, fileContent).line + (err.line ?? 0);

return new CSSError({
errorCode: AstroErrorCodes.CssUnknownError,
message: err.message,
location: {
file: filename,
line: errorLine,
column: err.column,
},
frame: err.frame,
});
}

// For other errors we'll just point to the beginning of the style tag
const errorPosition = positionAt(styleTagBeginning, fileContent);
errorPosition.line += 1;

return preprocessStyle;
return new CSSError({
errorCode: AstroErrorCodes.CssUnknownError,
message: err.message,
location: {
file: filename,
line: errorPosition.line,
column: 0,
},
frame: err.frame,
});
}
24 changes: 6 additions & 18 deletions packages/astro/src/vite-plugin-astro/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { PluginContext, SourceDescription } from 'rollup';
import type * as vite from 'vite';
import type { AstroSettings } from '../@types/astro';
import type { LogOptions } from '../core/logger/core.js';
import type { ViteStyleTransformer } from '../vite-style-transform';
import type { PluginMetadata as AstroPluginMetadata } from './types';

import ancestor from 'common-ancestor-path';
Expand All @@ -13,10 +12,6 @@ import { cachedCompilation, CompileProps, getCachedSource } from '../core/compil
import { isRelativePath, prependForwardSlash, startsWithForwardSlash } from '../core/path.js';
import { viteID } from '../core/util.js';
import { getFileInfo } from '../vite-plugin-utils/index.js';
import {
createTransformStyles,
createViteStyleTransformer,
} from '../vite-style-transform/index.js';
import { handleHotUpdate } from './hmr.js';
import { parseAstroRequest, ParsedRequestResult } from './query.js';

Expand Down Expand Up @@ -44,8 +39,6 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P
}

let resolvedConfig: vite.ResolvedConfig;
let styleTransformer: ViteStyleTransformer;
let viteDevServer: vite.ViteDevServer | undefined;

// Variables for determining if an id starts with /src...
const srcRootWeb = config.srcDir.pathname.slice(config.root.pathname.length - 1);
Expand All @@ -68,11 +61,6 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P
enforce: 'pre', // run transforms before other plugins can
configResolved(_resolvedConfig) {
resolvedConfig = _resolvedConfig;
styleTransformer = createViteStyleTransformer(_resolvedConfig);
},
configureServer(server) {
viteDevServer = server;
styleTransformer.viteDevServer = server;
},
// note: don’t claim .astro files with resolveId() — it prevents Vite from transpiling the final JS (import.meta.glob, etc.)
async resolveId(id, from, opts) {
Expand Down Expand Up @@ -125,10 +113,10 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P
}

const compileProps: CompileProps = {
config,
astroConfig: config,
viteConfig: resolvedConfig,
filename,
source,
transformStyle: createTransformStyles(styleTransformer, filename, Boolean(opts?.ssr), this),
};

switch (query.type) {
Expand Down Expand Up @@ -220,10 +208,10 @@ export default function astro({ settings, logging }: AstroPluginOptions): vite.P

const filename = normalizeFilename(parsedId.filename);
const compileProps: CompileProps = {
config,
astroConfig: config,
viteConfig: resolvedConfig,
filename,
source,
transformStyle: createTransformStyles(styleTransformer, filename, Boolean(opts?.ssr), this),
};

try {
Expand Down Expand Up @@ -342,10 +330,10 @@ ${source}
async handleHotUpdate(context) {
if (context.server.config.isProduction) return;
const compileProps: CompileProps = {
config,
astroConfig: config,
viteConfig: resolvedConfig,
filename: context.file,
source: await context.read(),
transformStyle: createTransformStyles(styleTransformer, context.file, true),
};
const compile = () => cachedCompilation(compileProps);
return handleHotUpdate(context, {
Expand Down
25 changes: 4 additions & 21 deletions packages/astro/src/vite-plugin-markdown-legacy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import esbuild from 'esbuild';
import fs from 'fs';
import matter from 'gray-matter';
import { fileURLToPath } from 'url';
import type { Plugin, ViteDevServer } from 'vite';
import type { Plugin, ResolvedConfig } from 'vite';
import type { AstroSettings } from '../@types/astro';
import { pagesVirtualModuleId } from '../core/app/index.js';
import { cachedCompilation, CompileProps } from '../core/compile/index.js';
Expand All @@ -13,11 +13,6 @@ import type { LogOptions } from '../core/logger/core.js';
import { isMarkdownFile } from '../core/util.js';
import type { PluginMetadata as AstroPluginMetadata } from '../vite-plugin-astro/types';
import { getFileInfo } from '../vite-plugin-utils/index.js';
import {
createTransformStyles,
createViteStyleTransformer,
ViteStyleTransformer,
} from '../vite-style-transform/index.js';

interface AstroPluginOptions {
settings: AstroSettings;
Expand Down Expand Up @@ -86,18 +81,11 @@ export default function markdown({ settings }: AstroPluginOptions): Plugin {
return false;
}

let styleTransformer: ViteStyleTransformer;
let viteDevServer: ViteDevServer | undefined;
let resolvedConfig: ResolvedConfig;

return {
name: 'astro:markdown',
enforce: 'pre',
configResolved(_resolvedConfig) {
styleTransformer = createViteStyleTransformer(_resolvedConfig);
},
configureServer(server) {
styleTransformer.viteDevServer = server;
},
async resolveId(id, importer, options) {
// Resolve any .md (or alternative extensions of markdown files like .markdown) files with the `?content` cache buster. This should only come from
// an already-resolved JS module wrapper. Needed to prevent infinite loops in Vite.
Expand Down Expand Up @@ -226,15 +214,10 @@ ${setup}`.trim();

// Transform from `.astro` to valid `.ts`
const compileProps: CompileProps = {
config,
astroConfig: config,
viteConfig: resolvedConfig,
filename,
source: astroResult,
transformStyle: createTransformStyles(
styleTransformer,
filename,
Boolean(opts?.ssr),
this
),
};

let transformResult = await cachedCompilation(compileProps);
Expand Down
2 changes: 0 additions & 2 deletions packages/astro/src/vite-style-transform/index.ts

This file was deleted.

Loading

0 comments on commit 1cc0670

Please sign in to comment.