Prevent locking up when encountering invalid CSS (#4675)
* Prevent locking up when encountering invalid CSS * Add a changeset * Move the unit test to the test folder * ponyfill aggregateerror * Use the exported type * keep original errors * fix
This commit is contained in:
parent
b5c436cfd8
commit
63e49c3b64
15 changed files with 229 additions and 106 deletions
5
.changeset/eleven-goats-confess.md
Normal file
5
.changeset/eleven-goats-confess.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Prevent locking up when encountering invalid CSS
|
|
@ -88,13 +88,14 @@
|
||||||
"dev": "astro-scripts dev --prebuild \"src/runtime/server/astro-island.ts\" --prebuild \"src/runtime/client/{idle,load,media,only,visible}.ts\" \"src/**/*.ts\"",
|
"dev": "astro-scripts dev --prebuild \"src/runtime/server/astro-island.ts\" --prebuild \"src/runtime/client/{idle,load,media,only,visible}.ts\" \"src/**/*.ts\"",
|
||||||
"postbuild": "astro-scripts copy \"src/**/*.astro\"",
|
"postbuild": "astro-scripts copy \"src/**/*.astro\"",
|
||||||
"benchmark": "node test/benchmark/dev.bench.js && node test/benchmark/build.bench.js",
|
"benchmark": "node test/benchmark/dev.bench.js && node test/benchmark/build.bench.js",
|
||||||
"test": "mocha --exit --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js",
|
"test:unit": "mocha --exit --timeout 2000 ./test/units/**/*.test.js",
|
||||||
|
"test": "pnpm run test:unit && mocha --exit --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js",
|
||||||
"test:match": "mocha --timeout 20000 -g",
|
"test:match": "mocha --timeout 20000 -g",
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
"test:e2e:match": "playwright test -g"
|
"test:e2e:match": "playwright test -g"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/compiler": "^0.23.5",
|
"@astrojs/compiler": "^0.24.0",
|
||||||
"@astrojs/language-server": "^0.23.0",
|
"@astrojs/language-server": "^0.23.0",
|
||||||
"@astrojs/markdown-remark": "^1.1.1",
|
"@astrojs/markdown-remark": "^1.1.1",
|
||||||
"@astrojs/telemetry": "^1.0.0",
|
"@astrojs/telemetry": "^1.0.0",
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import type { TransformResult } from '@astrojs/compiler';
|
import type { TransformResult } from '@astrojs/compiler';
|
||||||
import type { PluginContext, SourceMapInput } from 'rollup';
|
import type { AstroConfig } from '../../@types/astro';
|
||||||
import type { ViteDevServer } from 'vite';
|
import type { TransformStyle } from './types';
|
||||||
import type { AstroConfig } from '../@types/astro';
|
|
||||||
import type { TransformStyleWithVite } from './styles';
|
|
||||||
|
|
||||||
import { transform } from '@astrojs/compiler';
|
import { transform } from '@astrojs/compiler';
|
||||||
import { fileURLToPath } from 'url';
|
import { AstroErrorCodes } from '../errors.js';
|
||||||
import { AstroErrorCodes } from '../core/errors.js';
|
import { prependForwardSlash } from '../path.js';
|
||||||
import { prependForwardSlash } from '../core/path.js';
|
import { viteID, AggregateError } from '../util.js';
|
||||||
import { viteID } from '../core/util.js';
|
import { createStylePreprocessor } from './style.js';
|
||||||
|
|
||||||
type CompilationCache = Map<string, CompileResult>;
|
type CompilationCache = Map<string, CompileResult>;
|
||||||
type CompileResult = TransformResult & {
|
type CompileResult = TransformResult & {
|
||||||
|
@ -23,20 +21,7 @@ export interface CompileProps {
|
||||||
filename: string;
|
filename: string;
|
||||||
moduleId: string;
|
moduleId: string;
|
||||||
source: string;
|
source: string;
|
||||||
ssr: boolean;
|
transformStyle: TransformStyle;
|
||||||
transformStyleWithVite: TransformStyleWithVite;
|
|
||||||
viteDevServer?: ViteDevServer;
|
|
||||||
pluginContext: PluginContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNormalizedID(filename: string): string {
|
|
||||||
try {
|
|
||||||
const filenameURL = new URL(`file://${filename}`);
|
|
||||||
return fileURLToPath(filenameURL);
|
|
||||||
} catch (err) {
|
|
||||||
// Not a real file, so just use the provided filename as the normalized id
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function compile({
|
async function compile({
|
||||||
|
@ -44,19 +29,10 @@ async function compile({
|
||||||
filename,
|
filename,
|
||||||
moduleId,
|
moduleId,
|
||||||
source,
|
source,
|
||||||
ssr,
|
transformStyle,
|
||||||
transformStyleWithVite,
|
|
||||||
viteDevServer,
|
|
||||||
pluginContext,
|
|
||||||
}: CompileProps): Promise<CompileResult> {
|
}: CompileProps): Promise<CompileResult> {
|
||||||
const normalizedID = getNormalizedID(filename);
|
|
||||||
let cssDeps = new Set<string>();
|
let cssDeps = new Set<string>();
|
||||||
let cssTransformError: Error | undefined;
|
let cssTransformErrors: Error[] = [];
|
||||||
|
|
||||||
// handleHotUpdate doesn't have `addWatchFile` used by transformStyleWithVite.
|
|
||||||
if (!pluginContext.addWatchFile) {
|
|
||||||
pluginContext.addWatchFile = () => {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform from `.astro` to valid `.ts`
|
// Transform from `.astro` to valid `.ts`
|
||||||
// use `sourcemap: "both"` so that sourcemap is included in the code
|
// use `sourcemap: "both"` so that sourcemap is included in the code
|
||||||
|
@ -69,44 +45,11 @@ async function compile({
|
||||||
sourcefile: filename,
|
sourcefile: filename,
|
||||||
sourcemap: 'both',
|
sourcemap: 'both',
|
||||||
internalURL: `/@fs${prependForwardSlash(
|
internalURL: `/@fs${prependForwardSlash(
|
||||||
viteID(new URL('../runtime/server/index.js', import.meta.url))
|
viteID(new URL('../../runtime/server/index.js', import.meta.url))
|
||||||
)}`,
|
)}`,
|
||||||
// TODO: baseline flag
|
// TODO: baseline flag
|
||||||
experimentalStaticExtraction: true,
|
experimentalStaticExtraction: true,
|
||||||
preprocessStyle: async (value: string, attrs: Record<string, string>) => {
|
preprocessStyle: createStylePreprocessor(transformStyle, cssDeps, cssTransformErrors),
|
||||||
const lang = `.${attrs?.lang || 'css'}`.toLowerCase();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await transformStyleWithVite.call(pluginContext, {
|
|
||||||
id: normalizedID,
|
|
||||||
source: value,
|
|
||||||
lang,
|
|
||||||
ssr,
|
|
||||||
viteDevServer,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result) return null as any; // TODO: add type in compiler to fix "any"
|
|
||||||
|
|
||||||
for (const dep of result.deps) {
|
|
||||||
cssDeps.add(dep);
|
|
||||||
}
|
|
||||||
|
|
||||||
let map: SourceMapInput | undefined;
|
|
||||||
if (result.map) {
|
|
||||||
if (typeof result.map === 'string') {
|
|
||||||
map = result.map;
|
|
||||||
} else if (result.map.mappings) {
|
|
||||||
map = result.map.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { code: result.code, map };
|
|
||||||
} catch (err) {
|
|
||||||
// save error to throw in plugin context
|
|
||||||
cssTransformError = err as any;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
// throw compiler errors here if encountered
|
// throw compiler errors here if encountered
|
||||||
|
@ -114,13 +57,21 @@ async function compile({
|
||||||
throw err;
|
throw err;
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
// throw CSS transform errors here if encountered
|
switch(cssTransformErrors.length) {
|
||||||
if (cssTransformError) {
|
case 0: return result;
|
||||||
(cssTransformError as any).code =
|
case 1: {
|
||||||
(cssTransformError as any).code || AstroErrorCodes.UnknownCompilerCSSError;
|
let error = cssTransformErrors[0];
|
||||||
throw cssTransformError;
|
if(!(error as any).code) {
|
||||||
|
(error as any).code = AstroErrorCodes.UnknownCompilerCSSError;
|
||||||
|
}
|
||||||
|
throw cssTransformErrors[0];
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
const aggregateError = new AggregateError(cssTransformErrors);
|
||||||
|
(aggregateError as any).code = AstroErrorCodes.UnknownCompilerCSSError;
|
||||||
|
throw aggregateError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const compileResult: CompileResult = Object.create(transformResult, {
|
const compileResult: CompileResult = Object.create(transformResult, {
|
13
packages/astro/src/core/compile/index.ts
Normal file
13
packages/astro/src/core/compile/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
export type {
|
||||||
|
CompileProps
|
||||||
|
} from './compile';
|
||||||
|
export type {
|
||||||
|
TransformStyle
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
export {
|
||||||
|
cachedCompilation,
|
||||||
|
invalidateCompilation,
|
||||||
|
isCached,
|
||||||
|
getCachedSource
|
||||||
|
} from './compile.js';
|
39
packages/astro/src/core/compile/style.ts
Normal file
39
packages/astro/src/core/compile/style.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import type { TransformOptions } from '@astrojs/compiler';
|
||||||
|
import type { TransformStyle } from './types';
|
||||||
|
import type { SourceMapInput } from 'rollup';
|
||||||
|
|
||||||
|
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>) => {
|
||||||
|
const lang = `.${attrs?.lang || 'css'}`.toLowerCase();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await transformStyle(value, lang);
|
||||||
|
|
||||||
|
if (!result) return null as any; // TODO: add type in compiler to fix "any"
|
||||||
|
|
||||||
|
for (const dep of result.deps) {
|
||||||
|
cssDeps.add(dep);
|
||||||
|
}
|
||||||
|
|
||||||
|
let map: SourceMapInput | undefined;
|
||||||
|
if (result.map) {
|
||||||
|
if (typeof result.map === 'string') {
|
||||||
|
map = result.map;
|
||||||
|
} else if (result.map.mappings) {
|
||||||
|
map = result.map.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { code: result.code, map };
|
||||||
|
} catch (err) {
|
||||||
|
errors.push(err as unknown as Error);
|
||||||
|
return {
|
||||||
|
error: err + ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return preprocessStyle;
|
||||||
|
};
|
9
packages/astro/src/core/compile/types.ts
Normal file
9
packages/astro/src/core/compile/types.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import type { SourceMap } from 'rollup';
|
||||||
|
|
||||||
|
export type TransformStyleResult = null | {
|
||||||
|
code: string;
|
||||||
|
map: SourceMap | null;
|
||||||
|
deps: Set<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TransformStyle = (source: string, lang: string) => TransformStyleResult | Promise<TransformStyleResult>;
|
|
@ -226,3 +226,11 @@ export async function resolveIdToUrl(viteServer: ViteDevServer, id: string) {
|
||||||
}
|
}
|
||||||
return VALID_ID_PREFIX + result.id;
|
return VALID_ID_PREFIX + result.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const AggregateError = typeof globalThis.AggregateError !== 'undefined' ? globalThis.AggregateError : class extends Error {
|
||||||
|
errors: Array<any> = [];
|
||||||
|
constructor( errors: Iterable<any>, message?: string | undefined) {
|
||||||
|
super(message);
|
||||||
|
this.errors = Array.from(errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import type { AstroConfig } from '../@types/astro';
|
||||||
import type { LogOptions } from '../core/logger/core.js';
|
import type { LogOptions } from '../core/logger/core.js';
|
||||||
import { info } from '../core/logger/core.js';
|
import { info } from '../core/logger/core.js';
|
||||||
import * as msg from '../core/messages.js';
|
import * as msg from '../core/messages.js';
|
||||||
import { cachedCompilation, invalidateCompilation, isCached } from './compile.js';
|
import { cachedCompilation, invalidateCompilation, isCached } from '../core/compile/index.js';
|
||||||
import { isAstroScript } from './query.js';
|
import { isAstroScript } from './query.js';
|
||||||
|
|
||||||
const PKG_PREFIX = new URL('../../', import.meta.url);
|
const PKG_PREFIX = new URL('../../', import.meta.url);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type * as vite from 'vite';
|
||||||
import type { AstroConfig } from '../@types/astro';
|
import type { AstroConfig } from '../@types/astro';
|
||||||
import type { LogOptions } from '../core/logger/core.js';
|
import type { LogOptions } from '../core/logger/core.js';
|
||||||
import type { PluginMetadata as AstroPluginMetadata } from './types';
|
import type { PluginMetadata as AstroPluginMetadata } from './types';
|
||||||
|
import type { ViteStyleTransformer } from '../vite-style-transform';
|
||||||
|
|
||||||
import ancestor from 'common-ancestor-path';
|
import ancestor from 'common-ancestor-path';
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
|
@ -10,10 +11,14 @@ import slash from 'slash';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { isRelativePath, startsWithForwardSlash } from '../core/path.js';
|
import { isRelativePath, startsWithForwardSlash } from '../core/path.js';
|
||||||
import { getFileInfo } from '../vite-plugin-utils/index.js';
|
import { getFileInfo } from '../vite-plugin-utils/index.js';
|
||||||
import { cachedCompilation, CompileProps, getCachedSource } from './compile.js';
|
import {
|
||||||
|
cachedCompilation,
|
||||||
|
CompileProps,
|
||||||
|
getCachedSource
|
||||||
|
} from '../core/compile/index.js';
|
||||||
import { handleHotUpdate } from './hmr.js';
|
import { handleHotUpdate } from './hmr.js';
|
||||||
import { parseAstroRequest, ParsedRequestResult } from './query.js';
|
import { parseAstroRequest, ParsedRequestResult } from './query.js';
|
||||||
import { createTransformStyleWithViteFn, TransformStyleWithVite } from './styles.js';
|
import { createViteStyleTransformer, createTransformStyles } from '../vite-style-transform/index.js';
|
||||||
|
|
||||||
const FRONTMATTER_PARSE_REGEXP = /^\-\-\-(.*)^\-\-\-/ms;
|
const FRONTMATTER_PARSE_REGEXP = /^\-\-\-(.*)^\-\-\-/ms;
|
||||||
interface AstroPluginOptions {
|
interface AstroPluginOptions {
|
||||||
|
@ -38,7 +43,7 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
||||||
}
|
}
|
||||||
|
|
||||||
let resolvedConfig: vite.ResolvedConfig;
|
let resolvedConfig: vite.ResolvedConfig;
|
||||||
let transformStyleWithVite: TransformStyleWithVite;
|
let styleTransformer: ViteStyleTransformer;
|
||||||
let viteDevServer: vite.ViteDevServer | undefined;
|
let viteDevServer: vite.ViteDevServer | undefined;
|
||||||
|
|
||||||
// Variables for determining if an id starts with /src...
|
// Variables for determining if an id starts with /src...
|
||||||
|
@ -60,10 +65,11 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
||||||
enforce: 'pre', // run transforms before other plugins can
|
enforce: 'pre', // run transforms before other plugins can
|
||||||
configResolved(_resolvedConfig) {
|
configResolved(_resolvedConfig) {
|
||||||
resolvedConfig = _resolvedConfig;
|
resolvedConfig = _resolvedConfig;
|
||||||
transformStyleWithVite = createTransformStyleWithViteFn(_resolvedConfig);
|
styleTransformer = createViteStyleTransformer(_resolvedConfig);
|
||||||
},
|
},
|
||||||
configureServer(server) {
|
configureServer(server) {
|
||||||
viteDevServer = 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.)
|
// 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) {
|
async resolveId(id, from, opts) {
|
||||||
|
@ -117,10 +123,7 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
||||||
filename,
|
filename,
|
||||||
moduleId: id,
|
moduleId: id,
|
||||||
source,
|
source,
|
||||||
ssr: Boolean(opts?.ssr),
|
transformStyle: createTransformStyles(styleTransformer, filename, Boolean(opts?.ssr), this),
|
||||||
transformStyleWithVite,
|
|
||||||
viteDevServer,
|
|
||||||
pluginContext: this,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (query.type) {
|
switch (query.type) {
|
||||||
|
@ -216,10 +219,7 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
||||||
filename,
|
filename,
|
||||||
moduleId: id,
|
moduleId: id,
|
||||||
source,
|
source,
|
||||||
ssr: Boolean(opts?.ssr),
|
transformStyle: createTransformStyles(styleTransformer, filename, Boolean(opts?.ssr), this),
|
||||||
transformStyleWithVite,
|
|
||||||
viteDevServer,
|
|
||||||
pluginContext: this,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -352,10 +352,7 @@ ${source}
|
||||||
filename: context.file,
|
filename: context.file,
|
||||||
moduleId: context.file,
|
moduleId: context.file,
|
||||||
source: await context.read(),
|
source: await context.read(),
|
||||||
ssr: true,
|
transformStyle: createTransformStyles(styleTransformer, context.file, true, this),
|
||||||
transformStyleWithVite,
|
|
||||||
viteDevServer,
|
|
||||||
pluginContext: this,
|
|
||||||
};
|
};
|
||||||
const compile = () => cachedCompilation(compileProps);
|
const compile = () => cachedCompilation(compileProps);
|
||||||
return handleHotUpdate.call(this, context, {
|
return handleHotUpdate.call(this, context, {
|
||||||
|
|
|
@ -9,11 +9,8 @@ import type { AstroConfig } from '../@types/astro';
|
||||||
import { pagesVirtualModuleId } from '../core/app/index.js';
|
import { pagesVirtualModuleId } from '../core/app/index.js';
|
||||||
import { collectErrorMetadata } from '../core/errors.js';
|
import { collectErrorMetadata } from '../core/errors.js';
|
||||||
import type { LogOptions } from '../core/logger/core.js';
|
import type { LogOptions } from '../core/logger/core.js';
|
||||||
import { cachedCompilation, CompileProps } from '../vite-plugin-astro/compile.js';
|
import { cachedCompilation, CompileProps } from '../core/compile/index.js';
|
||||||
import {
|
import { ViteStyleTransformer, createViteStyleTransformer, createTransformStyles } from '../vite-style-transform/index.js';
|
||||||
createTransformStyleWithViteFn,
|
|
||||||
TransformStyleWithVite,
|
|
||||||
} from '../vite-plugin-astro/styles.js';
|
|
||||||
import type { PluginMetadata as AstroPluginMetadata } from '../vite-plugin-astro/types';
|
import type { PluginMetadata as AstroPluginMetadata } from '../vite-plugin-astro/types';
|
||||||
import { getFileInfo } from '../vite-plugin-utils/index.js';
|
import { getFileInfo } from '../vite-plugin-utils/index.js';
|
||||||
|
|
||||||
|
@ -64,14 +61,17 @@ export default function markdown({ config, logging }: AstroPluginOptions): Plugi
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let transformStyleWithVite: TransformStyleWithVite;
|
let styleTransformer: ViteStyleTransformer;
|
||||||
let viteDevServer: ViteDevServer | undefined;
|
let viteDevServer: ViteDevServer | undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'astro:markdown',
|
name: 'astro:markdown',
|
||||||
enforce: 'pre',
|
enforce: 'pre',
|
||||||
configResolved(_resolvedConfig) {
|
configResolved(_resolvedConfig) {
|
||||||
transformStyleWithVite = createTransformStyleWithViteFn(_resolvedConfig);
|
styleTransformer = createViteStyleTransformer(_resolvedConfig);
|
||||||
|
},
|
||||||
|
configureServer(server) {
|
||||||
|
styleTransformer.viteDevServer = server;
|
||||||
},
|
},
|
||||||
async resolveId(id, importer, options) {
|
async resolveId(id, importer, options) {
|
||||||
// Resolve any .md files with the `?content` cache buster. This should only come from
|
// Resolve any .md files with the `?content` cache buster. This should only come from
|
||||||
|
@ -208,10 +208,7 @@ ${setup}`.trim();
|
||||||
filename,
|
filename,
|
||||||
moduleId: id,
|
moduleId: id,
|
||||||
source: astroResult,
|
source: astroResult,
|
||||||
ssr: Boolean(opts?.ssr),
|
transformStyle: createTransformStyles(styleTransformer, filename, Boolean(opts?.ssr), this),
|
||||||
transformStyleWithVite,
|
|
||||||
viteDevServer,
|
|
||||||
pluginContext: this,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let transformResult = await cachedCompilation(compileProps);
|
let transformResult = await cachedCompilation(compileProps);
|
||||||
|
|
7
packages/astro/src/vite-style-transform/index.ts
Normal file
7
packages/astro/src/vite-style-transform/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export type {
|
||||||
|
ViteStyleTransformer
|
||||||
|
} from './style-transform';
|
||||||
|
export {
|
||||||
|
createViteStyleTransformer,
|
||||||
|
createTransformStyles
|
||||||
|
} from './style-transform.js';
|
49
packages/astro/src/vite-style-transform/style-transform.ts
Normal file
49
packages/astro/src/vite-style-transform/style-transform.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import type { PluginContext } from 'rollup';
|
||||||
|
import type { TransformStyle } from '../core/compile/index';
|
||||||
|
import { TransformStyleWithVite, createTransformStyleWithViteFn } from './transform-with-vite.js';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
import type * as vite from 'vite';
|
||||||
|
|
||||||
|
export type ViteStyleTransformer = {
|
||||||
|
viteDevServer?: vite.ViteDevServer;
|
||||||
|
transformStyleWithVite: TransformStyleWithVite;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createViteStyleTransformer(viteConfig: vite.ResolvedConfig): ViteStyleTransformer {
|
||||||
|
return {
|
||||||
|
transformStyleWithVite: createTransformStyleWithViteFn(viteConfig),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNormalizedIDForPostCSS(filename: string): string {
|
||||||
|
try {
|
||||||
|
const filenameURL = new URL(`file://${filename}`);
|
||||||
|
return fileURLToPath(filenameURL);
|
||||||
|
} catch (err) {
|
||||||
|
// Not a real file, so just use the provided filename as the normalized id
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTransformStyles(viteStyleTransformer: ViteStyleTransformer, filename: string, ssr: boolean, pluginContext: PluginContext): TransformStyle {
|
||||||
|
// handleHotUpdate doesn't have `addWatchFile` used by transformStyleWithVite.
|
||||||
|
// TODO, refactor, why is this happening *here* ?
|
||||||
|
if (!pluginContext.addWatchFile) {
|
||||||
|
pluginContext.addWatchFile = () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedID = getNormalizedIDForPostCSS(filename);
|
||||||
|
|
||||||
|
return async function(styleSource, lang) {
|
||||||
|
const result = await viteStyleTransformer.transformStyleWithVite.call(pluginContext, {
|
||||||
|
id: normalizedID,
|
||||||
|
source: styleSource,
|
||||||
|
lang,
|
||||||
|
ssr,
|
||||||
|
viteDevServer: viteStyleTransformer.viteDevServer,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
43
packages/astro/test/units/compile/invalid-css.test.js
Normal file
43
packages/astro/test/units/compile/invalid-css.test.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { cachedCompilation } from '../../../dist/core/compile/index.js';
|
||||||
|
import { AggregateError } from '../../../dist/core/util.js';
|
||||||
|
|
||||||
|
describe('astro/src/core/compile', () => {
|
||||||
|
describe('Invalid CSS', () => {
|
||||||
|
it('throws an aggregate error with the errors', async () => {
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
let r = await cachedCompilation({
|
||||||
|
config: /** @type {any} */({
|
||||||
|
root: '/'
|
||||||
|
}),
|
||||||
|
filename: '/src/pages/index.astro',
|
||||||
|
moduleId: '/src/pages/index.astro',
|
||||||
|
source: `
|
||||||
|
---
|
||||||
|
---
|
||||||
|
<style lang="scss">
|
||||||
|
article:global(:is(h1, h2, h3, h4, h5, h6):hover {
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
article:is(h1, h2, h3, h4, h5, h6:hover {
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`,
|
||||||
|
transformStyle(source, lang) {
|
||||||
|
throw new Error('Invalid css');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).to.be.an.instanceOf(AggregateError);
|
||||||
|
expect(error.errors[0].message).to.contain('Invalid css');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -338,7 +338,7 @@ importers:
|
||||||
|
|
||||||
packages/astro:
|
packages/astro:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@astrojs/compiler': ^0.23.5
|
'@astrojs/compiler': ^0.24.0
|
||||||
'@astrojs/language-server': ^0.23.0
|
'@astrojs/language-server': ^0.23.0
|
||||||
'@astrojs/markdown-remark': ^1.1.1
|
'@astrojs/markdown-remark': ^1.1.1
|
||||||
'@astrojs/telemetry': ^1.0.0
|
'@astrojs/telemetry': ^1.0.0
|
||||||
|
@ -421,7 +421,7 @@ importers:
|
||||||
yargs-parser: ^21.0.1
|
yargs-parser: ^21.0.1
|
||||||
zod: ^3.17.3
|
zod: ^3.17.3
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/compiler': 0.23.5
|
'@astrojs/compiler': 0.24.0
|
||||||
'@astrojs/language-server': 0.23.3
|
'@astrojs/language-server': 0.23.3
|
||||||
'@astrojs/markdown-remark': link:../markdown/remark
|
'@astrojs/markdown-remark': link:../markdown/remark
|
||||||
'@astrojs/telemetry': link:../telemetry
|
'@astrojs/telemetry': link:../telemetry
|
||||||
|
@ -3232,6 +3232,10 @@ packages:
|
||||||
resolution: {integrity: sha512-vBMPy9ok4iLapSyCCT1qsZ9dK7LkVFl9mObtLEmWiec9myGHS9h2kQY2xzPeFNJiWXUf9O6tSyQpQTy5As/p3g==}
|
resolution: {integrity: sha512-vBMPy9ok4iLapSyCCT1qsZ9dK7LkVFl9mObtLEmWiec9myGHS9h2kQY2xzPeFNJiWXUf9O6tSyQpQTy5As/p3g==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@astrojs/compiler/0.24.0:
|
||||||
|
resolution: {integrity: sha512-xZ81C/oMfExdF18I1Tyd2BKKzBqO+qYYctSy4iCwH4UWSo/4Y8A8MAzV1hG67uuE7hFRourSl6H5KUbhyChv/A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@astrojs/language-server/0.23.3:
|
/@astrojs/language-server/0.23.3:
|
||||||
resolution: {integrity: sha512-ROoMKo37NZ76pE/A2xHfjDlgfsNnFmkhL4+Wifs0L855n73SUCbnXz7ZaQktIGAq2Te2TpSjAawiOx0q9L5qeg==}
|
resolution: {integrity: sha512-ROoMKo37NZ76pE/A2xHfjDlgfsNnFmkhL4+Wifs0L855n73SUCbnXz7ZaQktIGAq2Te2TpSjAawiOx0q9L5qeg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
Loading…
Reference in a new issue