Improve error messages (#1875)

* Fix error handling in correct scope

Also improve Vite IDs for better module graph lookups

* Improve code frame

* Add changeset

* maybeLoc can be undefined

* Add tests

Co-authored-by: Matthew Phillips <matthew@skypack.dev>
This commit is contained in:
Drew Powers 2021-11-18 17:42:07 -07:00 committed by GitHub
parent 78b3371adb
commit 8986d33bfc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 449 additions and 62 deletions

View file

@ -0,0 +1,6 @@
---
'astro': patch
'@astrojs/renderer-vue': patch
---
Improve error display

View file

@ -104,7 +104,7 @@ class AstroBuilder {
return routes;
})
.catch((err) => {
debug(logging, 'build', `├── ${colors.bold(colors.red(' '))} ${route.component}`);
debug(logging, 'build', `├── ${colors.bold(colors.red(''))} ${route.component}`);
throw err;
}),
};

View file

@ -1,8 +1,7 @@
import type vite from '../vite';
import { fileURLToPath } from 'url';
import path from 'path';
import slash from 'slash';
import { viteifyURL } from '../util.js';
// https://vitejs.dev/guide/features.html#css-pre-processors
export const STYLE_EXTENSIONS = new Set(['.css', '.pcss', '.scss', '.sass', '.styl', '.stylus', '.less']);
@ -11,7 +10,7 @@ export const STYLE_EXTENSIONS = new Set(['.css', '.pcss', '.scss', '.sass', '.st
export function getStylesForURL(filePath: URL, viteServer: vite.ViteDevServer): Set<string> {
const css = new Set<string>();
const { idToModuleMap } = viteServer.moduleGraph;
const rootID = slash(fileURLToPath(filePath)); // Vite fix: Windows URLs must have forward slashes
const rootID = viteifyURL(filePath);
const moduleGraph = idToModuleMap.get(rootID);
if (!moduleGraph) return css;

View file

@ -18,12 +18,12 @@ import type {
} from '../../@types/astro';
import type { LogOptions } from '../logger';
import eol from 'eol';
import fs from 'fs';
import path from 'path';
import slash from 'slash';
import { fileURLToPath } from 'url';
import { renderPage, renderSlot } from '../../runtime/server/index.js';
import { canonicalURL as getCanonicalURL, codeFrame, resolveDependency, viteifyPath } from '../util.js';
import { canonicalURL as getCanonicalURL, codeFrame, resolveDependency, viteifyURL } from '../util.js';
import { getStylesForURL } from './css.js';
import { injectTags } from './html.js';
import { generatePaginateFunction } from './paginate.js';
@ -88,22 +88,34 @@ async function resolveRenderers(viteServer: vite.ViteDevServer, astroConfig: Ast
return renderers;
}
async function errorHandler(e: unknown, viteServer: vite.ViteDevServer, filePath: URL) {
interface ErrorHandlerOptions {
filePath: URL;
viteServer: vite.ViteDevServer;
}
async function errorHandler(e: unknown, { viteServer, filePath }: ErrorHandlerOptions) {
// normalize error stack line-endings to \n
if ((e as any).stack) {
(e as any).stack = eol.lf((e as any).stack);
}
// fix stack trace with Vite (this searches its module graph for matches)
if (e instanceof Error) {
viteServer.ssrFixStacktrace(e);
}
// Astro error (thrown by esbuild so it needs to be formatted for Vite)
const anyError = e as any;
if (anyError.errors) {
if (Array.isArray((e as any).errors)) {
const { location, pluginName, text } = (e as BuildResult).errors[0];
const err = e as SSRError;
if (location) err.loc = { file: location.file, line: location.line, column: location.column };
const frame = codeFrame(await fs.promises.readFile(filePath, 'utf8'), err.loc);
err.frame = frame;
let src = err.pluginCode;
if (!src && err.id && fs.existsSync(err.id)) src = await fs.promises.readFile(err.id, 'utf8');
if (!src) src = await fs.promises.readFile(filePath, 'utf8');
err.frame = codeFrame(src, err.loc);
err.id = location?.file;
err.message = `${location?.file}: ${text}
${frame}
${err.frame}
`;
if (pluginName) err.plugin = pluginName;
throw err;
@ -119,8 +131,7 @@ export async function preload({ astroConfig, filePath, viteServer }: SSROptions)
// Important: This needs to happen first, in case a renderer provides polyfills.
const renderers = await resolveRenderers(viteServer, astroConfig);
// Load the module from the Vite SSR Runtime.
const viteFriendlyURL = viteifyPath(filePath.pathname);
const mod = (await viteServer.ssrLoadModule(viteFriendlyURL)) as ComponentInstance;
const mod = (await viteServer.ssrLoadModule(viteifyURL(filePath))) as ComponentInstance;
return [renderers, mod];
}
@ -250,8 +261,7 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
// run transformIndexHtml() in dev to run Vite dev transformations
if (mode === 'development') {
const viteFilePath = slash(fileURLToPath(filePath)); // Vite Windows fix: URLs on Windows have forward slashes (not .pathname, which has a leading '/' on Windows)
html = await viteServer.transformIndexHtml(viteFilePath, html, pathname);
html = await viteServer.transformIndexHtml(viteifyURL(filePath), html, pathname);
}
return html;
@ -269,9 +279,9 @@ async function getHmrScript() {
export async function ssr(ssrOpts: SSROptions): Promise<string> {
try {
const [renderers, mod] = await preload(ssrOpts);
return render(renderers, mod, ssrOpts);
return await render(renderers, mod, ssrOpts); // note(drew): without "await", errors wont get caught by errorHandler()
} catch (e: unknown) {
await errorHandler(e, ssrOpts.viteServer, ssrOpts.filePath);
await errorHandler(e, { viteServer: ssrOpts.viteServer, filePath: ssrOpts.filePath });
throw e;
}
}

View file

@ -2,6 +2,7 @@ import type { AstroConfig } from '../@types/astro';
import type { ErrorPayload } from 'vite';
import eol from 'eol';
import path from 'path';
import slash from 'slash';
import { fileURLToPath, pathToFileURL } from 'url';
import resolve from 'resolve';
@ -72,6 +73,13 @@ export function resolveDependency(dep: string, astroConfig: AstroConfig) {
return pathToFileURL(resolved).toString();
}
export function viteifyPath(pathname: string): string {
return `/@fs/${pathname.replace(/^\//, '')}`;
/**
* Vite-ify URL
* Given a file URL, return an ID that matches Vites module graph. Needed for resolution and stack trace fixing.
* Must match the following format:
* Linux/Mac: /Users/astro/code/my-project/src/pages/index.astro
* Windows: C:/Users/astro/code/my-project/src/pages/index.astro
*/
export function viteifyURL(filePath: URL): string {
return slash(fileURLToPath(filePath));
}

View file

@ -8,7 +8,6 @@ import fs from 'fs';
import { fileURLToPath } from 'url';
import os from 'os';
import { transform } from '@astrojs/compiler';
import { decode } from 'sourcemap-codec';
import { AstroDevServer } from '../core/dev/index.js';
import { getViteTransform, TransformHook, transformWithVite } from './styles.js';
@ -121,16 +120,6 @@ ${err.url}`;
err.stack = ` at ${id}`;
}
// improve esbuild errors
if (err.errors && tsResult?.map) {
const json = JSON.parse(tsResult.map);
const mappings = decode(json.mappings);
const focusMapping = mappings[err.errors[0].location.line + 1];
if (Array.isArray(focusMapping) && focusMapping.length) {
err.sourceLoc = { file: id, line: (focusMapping[0][2] || 0) + 1, column: (focusMapping[0][3] || 0) + 1 };
}
}
throw err;
}
},

View file

@ -8,13 +8,10 @@ import srcsetParse from 'srcset-parse';
import * as npath from 'path';
import { promises as fs } from 'fs';
import { getAttribute, hasAttribute, getTagName, insertBefore, remove, createScript, createElement, setAttribute } from '@web/parse5-utils';
import slash from 'slash';
import { fileURLToPath } from 'url';
import { addRollupInput } from './add-rollup-input.js';
import { findAssets, findExternalScripts, findInlineScripts, findInlineStyles, getTextContent, isStylesheetLink } from './extract-assets.js';
import { render as ssrRender } from '../core/ssr/index.js';
import { getAstroStyleId, getAstroPageStyleId } from '../vite-plugin-build-css/index.js';
import { viteifyPath } from '../core/util.js';
// This package isn't real ESM, so have to coerce it
const matchSrcset: typeof srcsetParse = (srcsetParse as any).default;
@ -140,8 +137,7 @@ export function rollupPluginAstroBuildHTML(options: PluginOptions): VitePlugin {
for (let node of findAssets(document)) {
if (isBuildableLink(node, srcRoot)) {
const href = getAttribute(node, 'href')!;
const linkId = viteifyPath(href);
assetImports.push(linkId);
assetImports.push(href);
}
if (isBuildableImage(node, srcRoot)) {

View file

@ -137,6 +137,8 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
const { code: jsxCode } = await esbuild.transform(code, {
loader: getEsbuildLoader(path.extname(id)) as esbuild.Loader,
jsx: 'preserve',
sourcefile: id,
sourcemap: 'inline',
});
return transformJSX({ code: jsxCode, id, renderer: [...jsxRenderers.values()][0], mode, ssr });
}
@ -148,6 +150,8 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
jsx: 'transform',
jsxFactory: 'h',
jsxFragment: 'Fragment',
sourcefile: id,
sourcemap: 'inline',
});
let imports: eslexer.ImportSpecifier[] = [];
@ -191,8 +195,10 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
const { code: jsxCode } = await esbuild.transform(code, {
loader: getEsbuildLoader(path.extname(id)) as esbuild.Loader,
jsx: 'preserve',
sourcefile: id,
sourcemap: 'inline',
});
return transformJSX({ code: jsxCode, id, renderer: jsxRenderers.get(importSource) as Renderer, mode, ssr });
return await transformJSX({ code: jsxCode, id, renderer: jsxRenderers.get(importSource) as Renderer, mode, ssr });
}
// if we still cant tell, throw error

View file

@ -2,25 +2,25 @@ import { expect } from 'chai';
import cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';
let fixture;
let index$;
let bundledCSS;
before(async () => {
fixture = await loadFixture({
projectRoot: './fixtures/astro-styles-ssr/',
renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'],
});
await fixture.build();
// get bundled CSS (will be hashed, hence DOM query)
const html = await fixture.readFile('/index.html');
index$ = cheerio.load(html);
const bundledCSSHREF = index$('link[rel=stylesheet][href^=assets/]').attr('href');
bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
});
describe('Styles SSR', function () {
let fixture;
let index$;
let bundledCSS;
before(async () => {
fixture = await loadFixture({
projectRoot: './fixtures/astro-styles-ssr/',
renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'],
});
await fixture.build();
// get bundled CSS (will be hashed, hence DOM query)
const html = await fixture.readFile('/index.html');
index$ = cheerio.load(html);
const bundledCSSHREF = index$('link[rel=stylesheet][href^=assets/]').attr('href');
bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
});
describe('Astro styles', () => {
it('HTML and CSS scoped correctly', async () => {
const $ = index$;

View file

@ -0,0 +1,225 @@
import { expect } from 'chai';
import os from 'os';
import { loadFixture } from './test-utils.js';
// TODO: fix these tests on macOS
const isMacOS = os.platform() === 'darwin';
let fixture;
let devServer;
before(async () => {
fixture = await loadFixture({
projectRoot: './fixtures/errors',
renderers: ['@astrojs/renderer-preact', '@astrojs/renderer-react', '@astrojs/renderer-solid', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'],
vite: {
optimizeDeps: false, // necessary to prevent Vite throwing on bad files
},
});
devServer = await fixture.startDevServer();
});
describe('Error display', () => {
describe('Astro', () => {
it('syntax error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/astro-syntax-error');
// 500 returned
expect(res.status).to.equal(500);
// error message includes "unrecoverable error"
const body = await res.text();
expect(body).to.include('unrecoverable error');
});
it('runtime error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/astro-runtime-error');
// 500 returned
expect(res.status).to.equal(500);
// error message contains error
const body = await res.text();
expect(body).to.include('ReferenceError: title is not defined');
// TODO: improve stacktrace
});
});
describe('JS', () => {
it('syntax error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/js-syntax-error');
// 500 returnd
expect(res.status).to.equal(500);
// error message is helpful
const body = await res.text();
expect(body).to.include('Parse failure');
});
it('runtime error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/js-runtime-error');
// 500 returnd
expect(res.status).to.equal(500);
// error message is helpful
const body = await res.text();
expect(body).to.include('ReferenceError: undefinedvar is not defined');
});
});
describe('Preact', () => {
it('syntax error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/preact-syntax-error');
// 500 returned
expect(res.status).to.equal(500);
// error message is helpful
const body = await res.text();
expect(body).to.include('Syntax error');
});
it('runtime error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/preact-runtime-error');
// 500 returned
expect(res.status).to.equal(500);
// error message is helpful
const body = await res.text();
expect(body).to.include('Error: PreactRuntimeError');
});
});
describe('React', () => {
it('syntax error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/react-syntax-error');
// 500 returned
expect(res.status).to.equal(500);
// error message is helpful
const body = await res.text();
expect(body).to.include('Syntax error');
});
it('runtime error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/react-runtime-error');
// 500 returned
expect(res.status).to.equal(500);
// error message is helpful
const body = await res.text();
expect(body).to.include('Error: ReactRuntimeError');
});
});
describe('Solid', () => {
it('syntax error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/solid-syntax-error');
// 500 returned
expect(res.status).to.equal(500);
// error message is helpful
const body = await res.text();
expect(body).to.include('Syntax error');
});
it('runtime error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/solid-runtime-error');
// 500 returned
expect(res.status).to.equal(500);
// error message is helpful
const body = await res.text();
expect(body).to.include('Error: SolidRuntimeError');
});
});
describe('Svelte', () => {
it('syntax error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/svelte-syntax-error');
// 500 returned
expect(res.status).to.equal(500);
// error message is helpful
const body = await res.text();
expect(body).to.include('ParseError');
});
it('runtime error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/svelte-runtime-error');
// 500 returned
expect(res.status).to.equal(500);
// error message is helpful
const body = await res.text();
expect(body).to.include('Error: SvelteRuntimeError');
});
});
describe('Vue', () => {
it('syntax error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/vue-syntax-error');
const body = await res.text();
// 500 returned
expect(res.status).to.equal(500);
// error message is helpful
expect(body).to.include('Parse failure');
});
it('runtime error', async () => {
if (isMacOS) return;
const res = await fixture.fetch('/vue-runtime-error');
// 500 returned
expect(res.status).to.equal(500);
// error message is helpful
const body = await res.text();
expect(body).to.match(/Cannot read.*undefined/); // note: error differs slightly between Node versions
});
});
});
after(async () => {
await devServer.stop();
});

View file

@ -0,0 +1,3 @@
export default function Error() {
return undefinedvar;
}

View file

@ -0,0 +1,4 @@
export default function Error() {
const 1badvar = true;
return 1badvar;
}

View file

@ -0,0 +1,6 @@
import { h } from 'preact';
export default function PreactRuntimeError({shouldThrow = true}) {
if (shouldThrow) throw new Error('PreactRuntimeError')
return <div>I shouldnt be here</div>;
}

View file

@ -0,0 +1,5 @@
import { h } from 'preact';
export default function PreactSyntaxError() {
return (<div></div></div>);
}

View file

@ -0,0 +1,6 @@
import React from 'react';
export default function ReactRuntimeError({shouldThrow = true}) {
if (shouldThrow) throw new Error('ReactRuntimeError')
return <div>I shouldnt be here</div>;
}

View file

@ -0,0 +1,5 @@
import React from 'react';
export default function ReactSyntaxError() {
return (<div></div></div>);
}

View file

@ -0,0 +1,6 @@
import { h } from 'solid-js/web';
export default function SolidRuntimeError({shouldThrow = true}) {
if (shouldThrow) throw new Error('SolidRuntimeError')
return <div>I shouldnt be here</div>;
}

View file

@ -0,0 +1,5 @@
import { h } from 'solid-js/web'
export default function ReactSyntaxError() {
return (<div></div></div>);
}

View file

@ -0,0 +1,9 @@
<script>
export let shouldThrow = true;
if (shouldThrow) {
throw new Error('SvelteRuntimeError');
}
</script>
<h1>I shouldnt be here</h1>

View file

@ -0,0 +1 @@
<h1>Tag mismatch</div>

View file

@ -0,0 +1,3 @@
<template>
<h1>Wanna see something undefined? {{ not.here }}</h1>
</template>

View file

@ -0,0 +1,3 @@
<p>This needs a template</p>
<p>But alas, this is lacking</p>
<p>Look 5 syllables</p>

View file

@ -0,0 +1 @@
<h1>{title}</h1>

View file

@ -0,0 +1 @@
<h1>{// comment

View file

@ -0,0 +1,4 @@
---
import Error from '../components/JSRuntimeError';
---
<div>{Error()}</div>

View file

@ -0,0 +1,4 @@
---
const Error = await import( '../components/JSSyntaxError.js');
---
<h1>{Error()}</h1>

View file

@ -0,0 +1,7 @@
---
import PreactRuntimeError from '../components/PreactRuntimeError.jsx';
---
<div>
<PreactRuntimeError />
</div>

View file

@ -0,0 +1,7 @@
---
import PreactSyntaxError from '../components/PreactSyntaxError.jsx';
---
<div>
<PreactSyntaxError />
</div>

View file

@ -0,0 +1,7 @@
---
import ReactRuntimeError from '../components/ReactRuntimeError.jsx';
---
<div>
<ReactRuntimeError />
</div>

View file

@ -0,0 +1,7 @@
---
import ReactSyntaxError from '../components/ReactSyntaxError.jsx';
---
<div>
<ReactSyntaxError />
</div>

View file

@ -0,0 +1,7 @@
---
import SolidRuntimeError from '../components/SolidRuntimeError.jsx';
---
<div>
<SolidRuntimeError />
</div>

View file

@ -0,0 +1,7 @@
---
import SolidSyntaxError from '../components/SolidSyntaxError.jsx';
---
<div>
<SolidSyntaxError />
</div>

View file

@ -0,0 +1,7 @@
---
import SvelteRuntimeError from '../components/SvelteRuntimeError.svelte';
---
<div>
<SvelteRuntimeError />
</div>

View file

@ -0,0 +1,7 @@
---
import SvelteSyntaxError from '../components/SvelteSyntaxError.svelte';
---
<div>
<SvelteSyntaxError />
</div>

View file

@ -0,0 +1,7 @@
---
import VueRuntimeError from '../components/VueRuntimeError.vue';
---
<div>
<VueRuntimeError />
</div>

View file

@ -0,0 +1,7 @@
---
import VueSyntaxError from '../components/VueSyntaxError.vue';
---
<div>
<VueSyntaxError />
</div>

View file

@ -11,8 +11,6 @@
},
"dependencies": {
"@vitejs/plugin-vue": "^1.9.4",
"@vue/compiler-sfc": "^3.2.22",
"@vue/server-renderer": "^3.2.22",
"vue": "^3.2.22"
},
"engines": {

View file

@ -1,5 +1,5 @@
import { renderToString } from '@vue/server-renderer';
import { h, createSSRApp } from 'vue';
import { renderToString } from 'vue/server-renderer';
import StaticHtml from './static-html.js';
function check(Component) {

View file

@ -351,7 +351,12 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.12.7", "@babel/parser@^7.15.0", "@babel/parser@^7.16.0", "@babel/parser@^7.16.3", "@babel/parser@^7.4.5":
"@babel/parser@^7.1.0", "@babel/parser@^7.12.7", "@babel/parser@^7.4.5":
version "7.15.7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.7.tgz#0c3ed4a2eb07b165dfa85b3cc45c727334c4edae"
integrity sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==
"@babel/parser@^7.15.0", "@babel/parser@^7.16.0", "@babel/parser@^7.16.3":
version "7.16.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e"
integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==
@ -2095,7 +2100,7 @@
"@vue/compiler-core" "3.2.22"
"@vue/shared" "3.2.22"
"@vue/compiler-sfc@3.2.22", "@vue/compiler-sfc@^3.2.22":
"@vue/compiler-sfc@3.2.22":
version "3.2.22"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.22.tgz#ffd0e5e35479b6ade18d12fefec369cbaf2f7718"
integrity sha512-tWRQ5ge1tsTDhUwHgueicKJ8rYm6WUVAPTaIpFW3GSwZKcOEJ2rXdfkHFShNVGupeRALz2ET2H84OL0GeRxY0A==
@ -2154,7 +2159,7 @@
"@vue/shared" "3.2.22"
csstype "^2.6.8"
"@vue/server-renderer@3.2.22", "@vue/server-renderer@^3.2.22":
"@vue/server-renderer@3.2.22":
version "3.2.22"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.22.tgz#049c91a495cb0fcdac02dec485c31cb99410885f"
integrity sha512-jCwbQgKPXiXoH9VS9F7K+gyEvEMrjutannwEZD1R8fQ9szmOTqC+RRbIY3Uf2ibQjZtZ8DV9a4FjxICvd9zZlQ==
@ -8450,7 +8455,7 @@ postcss-value-parser@^4.1.0:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
postcss@^8.1.10, postcss@^8.1.6, postcss@^8.2.1, postcss@^8.3.8:
postcss@^8.1.10, postcss@^8.3.8:
version "8.3.11"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.11.tgz#c3beca7ea811cd5e1c4a3ec6d2e7599ef1f8f858"
integrity sha512-hCmlUAIlUiav8Xdqw3Io4LcpA1DOt7h3LSTAC4G6JGHFFaWzI6qvFt9oilvl8BmkbBRX1IhM90ZAmpk68zccQA==
@ -8459,6 +8464,15 @@ postcss@^8.1.10, postcss@^8.1.6, postcss@^8.2.1, postcss@^8.3.8:
picocolors "^1.0.0"
source-map-js "^0.6.2"
postcss@^8.1.6, postcss@^8.2.1:
version "8.3.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea"
integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.23"
source-map-js "^0.6.2"
preact-render-to-string@^5.1.19:
version "5.1.19"
resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-5.1.19.tgz#ffae7c3bd1680be5ecf5991d41fe3023b3051e0e"