diff --git a/packages/astro/e2e/error-react-spectrum.test.js b/packages/astro/e2e/error-react-spectrum.test.js
new file mode 100644
index 000000000..c8515cc42
--- /dev/null
+++ b/packages/astro/e2e/error-react-spectrum.test.js
@@ -0,0 +1,24 @@
+import { expect } from '@playwright/test';
+import { testFactory, getErrorOverlayMessage } from './test-utils.js';
+
+const test = testFactory({ root: './fixtures/error-react-spectrum/' });
+
+let devServer;
+
+test.beforeEach(async ({ astro }) => {
+ devServer = await astro.startDevServer();
+});
+
+test.afterEach(async ({ astro }) => {
+ await devServer.stop();
+ astro.resetAllFiles();
+});
+
+test.describe('Error: React Spectrum', () => {
+ test('overlay', async ({ page, astro }) => {
+ await page.goto(astro.resolveUrl('/'));
+
+ const message = await getErrorOverlayMessage(page);
+ expect(message).toMatch('@adobe/react-spectrum is not compatible')
+ });
+});
diff --git a/packages/astro/e2e/error-sass.test.js b/packages/astro/e2e/error-sass.test.js
new file mode 100644
index 000000000..638d5e337
--- /dev/null
+++ b/packages/astro/e2e/error-sass.test.js
@@ -0,0 +1,24 @@
+import { expect } from '@playwright/test';
+import { testFactory, getErrorOverlayMessage } from './test-utils.js';
+
+const test = testFactory({ root: './fixtures/error-sass/' });
+
+let devServer;
+
+test.beforeEach(async ({ astro }) => {
+ devServer = await astro.startDevServer();
+});
+
+test.afterEach(async ({ astro }) => {
+ await devServer.stop();
+ astro.resetAllFiles();
+});
+
+test.describe('Error: Sass', () => {
+ test('overlay', async ({ page, astro }) => {
+ await page.goto(astro.resolveUrl('/'));
+
+ const message = await getErrorOverlayMessage(page);
+ expect(message).toMatch('Undefined variable')
+ });
+});
diff --git a/packages/astro/e2e/errors.test.js b/packages/astro/e2e/errors.test.js
new file mode 100644
index 000000000..e917ce7ba
--- /dev/null
+++ b/packages/astro/e2e/errors.test.js
@@ -0,0 +1,67 @@
+import { expect } from '@playwright/test';
+import { testFactory, getErrorOverlayMessage } from './test-utils.js';
+
+const test = testFactory({ root: './fixtures/errors/' });
+
+let devServer;
+
+test.beforeEach(async ({ astro }) => {
+ devServer = await astro.startDevServer();
+});
+
+test.afterEach(async ({ astro }) => {
+ await devServer.stop();
+ astro.resetAllFiles();
+});
+
+test.describe('Error display', () => {
+ test('detect syntax errors in template', async ({ page, astro }) => {
+ await page.goto(astro.resolveUrl('/astro-syntax-error'));
+
+ const message = await getErrorOverlayMessage(page);
+ expect(message).toMatch('Unexpected "}"')
+
+ await Promise.all([
+ // Wait for page reload
+ page.waitForNavigation(),
+ // Edit the component file
+ await astro.editFile('./src/pages/astro-syntax-error.astro', () => `
No syntax error
`)
+ ]);
+
+ expect(await page.locator('vite-error-overlay').count()).toEqual(0);
+ });
+
+ test('shows useful error when frontmatter import is not found', async ({ page, astro }) => {
+ await page.goto(astro.resolveUrl('/import-not-found'));
+
+ const message = await getErrorOverlayMessage(page);
+ expect(message).toMatch('failed to load module for ssr: ../abc.astro')
+
+ await Promise.all([
+ // Wait for page reload
+ page.waitForNavigation(),
+ // Edit the component file
+ astro.editFile('./src/pages/import-not-found.astro', () => `No import error
`)
+ ]);
+
+ expect(await page.locator('vite-error-overlay').count()).toEqual(0);
+ });
+
+ test('framework errors recover when fixed', async ({ page, astro }) => {
+ await page.goto(astro.resolveUrl('/svelte-syntax-error'));
+
+ const message = await getErrorOverlayMessage(page);
+ expect(message).toMatch(' attempted to close an element that was not open')
+
+ await Promise.all([
+ // Wait for page reload
+ page.waitForNavigation(),
+ // Edit the component file
+ astro.editFile('./src/components/SvelteSyntaxError.svelte', () => `No mismatch
`)
+ ]);
+
+ expect(await page.locator('vite-error-overlay').count()).toEqual(0);
+ });
+
+
+});
diff --git a/packages/astro/test/fixtures/error-react-spectrum/astro.config.mjs b/packages/astro/e2e/fixtures/error-react-spectrum/astro.config.mjs
similarity index 100%
rename from packages/astro/test/fixtures/error-react-spectrum/astro.config.mjs
rename to packages/astro/e2e/fixtures/error-react-spectrum/astro.config.mjs
diff --git a/packages/astro/test/fixtures/error-react-spectrum/package.json b/packages/astro/e2e/fixtures/error-react-spectrum/package.json
similarity index 84%
rename from packages/astro/test/fixtures/error-react-spectrum/package.json
rename to packages/astro/e2e/fixtures/error-react-spectrum/package.json
index e06316aa9..f52e71585 100644
--- a/packages/astro/test/fixtures/error-react-spectrum/package.json
+++ b/packages/astro/e2e/fixtures/error-react-spectrum/package.json
@@ -1,5 +1,5 @@
{
- "name": "@test/error-react-spectrum",
+ "name": "@e2e/error-react-spectrum",
"version": "0.0.0",
"private": true,
"dependencies": {
diff --git a/packages/astro/test/fixtures/error-react-spectrum/src/pages/index.astro b/packages/astro/e2e/fixtures/error-react-spectrum/src/pages/index.astro
similarity index 100%
rename from packages/astro/test/fixtures/error-react-spectrum/src/pages/index.astro
rename to packages/astro/e2e/fixtures/error-react-spectrum/src/pages/index.astro
diff --git a/packages/astro/test/fixtures/sass/package.json b/packages/astro/e2e/fixtures/error-sass/package.json
similarity index 80%
rename from packages/astro/test/fixtures/sass/package.json
rename to packages/astro/e2e/fixtures/error-sass/package.json
index 62074943c..f52820ad2 100644
--- a/packages/astro/test/fixtures/sass/package.json
+++ b/packages/astro/e2e/fixtures/error-sass/package.json
@@ -1,5 +1,5 @@
{
- "name": "@test/sass",
+ "name": "@e2e/error-sass",
"version": "0.0.0",
"private": true,
"dependencies": {
diff --git a/packages/astro/test/fixtures/sass/src/pages/error.astro b/packages/astro/e2e/fixtures/error-sass/src/pages/index.astro
similarity index 100%
rename from packages/astro/test/fixtures/sass/src/pages/error.astro
rename to packages/astro/e2e/fixtures/error-sass/src/pages/index.astro
diff --git a/packages/astro/test/fixtures/errors/astro.config.mjs b/packages/astro/e2e/fixtures/errors/astro.config.mjs
similarity index 100%
rename from packages/astro/test/fixtures/errors/astro.config.mjs
rename to packages/astro/e2e/fixtures/errors/astro.config.mjs
diff --git a/packages/astro/test/fixtures/errors/package.json b/packages/astro/e2e/fixtures/errors/package.json
similarity index 92%
rename from packages/astro/test/fixtures/errors/package.json
rename to packages/astro/e2e/fixtures/errors/package.json
index 3438ca92a..1821e074f 100644
--- a/packages/astro/test/fixtures/errors/package.json
+++ b/packages/astro/e2e/fixtures/errors/package.json
@@ -1,5 +1,5 @@
{
- "name": "@test/errors",
+ "name": "@e2e/errors",
"version": "0.0.0",
"private": true,
"dependencies": {
diff --git a/packages/astro/test/fixtures/errors/src/components/JSRuntimeError.js b/packages/astro/e2e/fixtures/errors/src/components/JSRuntimeError.js
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/JSRuntimeError.js
rename to packages/astro/e2e/fixtures/errors/src/components/JSRuntimeError.js
diff --git a/packages/astro/test/fixtures/errors/src/components/JSSyntaxError.js b/packages/astro/e2e/fixtures/errors/src/components/JSSyntaxError.js
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/JSSyntaxError.js
rename to packages/astro/e2e/fixtures/errors/src/components/JSSyntaxError.js
diff --git a/packages/astro/test/fixtures/errors/src/components/PreactRuntimeError.jsx b/packages/astro/e2e/fixtures/errors/src/components/PreactRuntimeError.jsx
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/PreactRuntimeError.jsx
rename to packages/astro/e2e/fixtures/errors/src/components/PreactRuntimeError.jsx
diff --git a/packages/astro/test/fixtures/errors/src/components/PreactSyntaxError.jsx b/packages/astro/e2e/fixtures/errors/src/components/PreactSyntaxError.jsx
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/PreactSyntaxError.jsx
rename to packages/astro/e2e/fixtures/errors/src/components/PreactSyntaxError.jsx
diff --git a/packages/astro/test/fixtures/errors/src/components/ReactRuntimeError.jsx b/packages/astro/e2e/fixtures/errors/src/components/ReactRuntimeError.jsx
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/ReactRuntimeError.jsx
rename to packages/astro/e2e/fixtures/errors/src/components/ReactRuntimeError.jsx
diff --git a/packages/astro/test/fixtures/errors/src/components/ReactSyntaxError.jsx b/packages/astro/e2e/fixtures/errors/src/components/ReactSyntaxError.jsx
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/ReactSyntaxError.jsx
rename to packages/astro/e2e/fixtures/errors/src/components/ReactSyntaxError.jsx
diff --git a/packages/astro/test/fixtures/errors/src/components/SolidRuntimeError.jsx b/packages/astro/e2e/fixtures/errors/src/components/SolidRuntimeError.jsx
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/SolidRuntimeError.jsx
rename to packages/astro/e2e/fixtures/errors/src/components/SolidRuntimeError.jsx
diff --git a/packages/astro/test/fixtures/errors/src/components/SolidSyntaxError.jsx b/packages/astro/e2e/fixtures/errors/src/components/SolidSyntaxError.jsx
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/SolidSyntaxError.jsx
rename to packages/astro/e2e/fixtures/errors/src/components/SolidSyntaxError.jsx
diff --git a/packages/astro/test/fixtures/errors/src/components/SvelteDirectiveError.svelte b/packages/astro/e2e/fixtures/errors/src/components/SvelteDirectiveError.svelte
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/SvelteDirectiveError.svelte
rename to packages/astro/e2e/fixtures/errors/src/components/SvelteDirectiveError.svelte
diff --git a/packages/astro/test/fixtures/errors/src/components/SvelteRuntimeError.svelte b/packages/astro/e2e/fixtures/errors/src/components/SvelteRuntimeError.svelte
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/SvelteRuntimeError.svelte
rename to packages/astro/e2e/fixtures/errors/src/components/SvelteRuntimeError.svelte
diff --git a/packages/astro/test/fixtures/errors/src/components/SvelteSyntaxError.svelte b/packages/astro/e2e/fixtures/errors/src/components/SvelteSyntaxError.svelte
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/SvelteSyntaxError.svelte
rename to packages/astro/e2e/fixtures/errors/src/components/SvelteSyntaxError.svelte
diff --git a/packages/astro/test/fixtures/errors/src/components/VueRuntimeError.vue b/packages/astro/e2e/fixtures/errors/src/components/VueRuntimeError.vue
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/VueRuntimeError.vue
rename to packages/astro/e2e/fixtures/errors/src/components/VueRuntimeError.vue
diff --git a/packages/astro/test/fixtures/errors/src/components/VueSyntaxError.vue b/packages/astro/e2e/fixtures/errors/src/components/VueSyntaxError.vue
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/components/VueSyntaxError.vue
rename to packages/astro/e2e/fixtures/errors/src/components/VueSyntaxError.vue
diff --git a/packages/astro/test/fixtures/errors/src/pages/astro-client-media-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/astro-client-media-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/astro-client-media-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/astro-client-media-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/astro-frontmatter-syntax-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/astro-frontmatter-syntax-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/astro-frontmatter-syntax-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/astro-frontmatter-syntax-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/astro-hydration-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/astro-hydration-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/astro-hydration-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/astro-hydration-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/astro-runtime-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/astro-runtime-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/astro-runtime-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/astro-runtime-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/astro-syntax-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/astro-syntax-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/astro-syntax-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/astro-syntax-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/import-not-found.astro b/packages/astro/e2e/fixtures/errors/src/pages/import-not-found.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/import-not-found.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/import-not-found.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/js-runtime-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/js-runtime-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/js-runtime-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/js-runtime-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/js-syntax-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/js-syntax-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/js-syntax-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/js-syntax-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/preact-runtime-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/preact-runtime-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/preact-runtime-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/preact-runtime-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/preact-syntax-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/preact-syntax-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/preact-syntax-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/preact-syntax-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/react-runtime-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/react-runtime-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/react-runtime-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/react-runtime-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/react-syntax-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/react-syntax-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/react-syntax-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/react-syntax-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/solid-runtime-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/solid-runtime-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/solid-runtime-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/solid-runtime-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/solid-syntax-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/solid-syntax-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/solid-syntax-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/solid-syntax-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/svelte-runtime-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/svelte-runtime-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/svelte-runtime-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/svelte-runtime-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/svelte-syntax-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/svelte-syntax-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/svelte-syntax-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/svelte-syntax-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/vue-runtime-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/vue-runtime-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/vue-runtime-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/vue-runtime-error.astro
diff --git a/packages/astro/test/fixtures/errors/src/pages/vue-syntax-error.astro b/packages/astro/e2e/fixtures/errors/src/pages/vue-syntax-error.astro
similarity index 100%
rename from packages/astro/test/fixtures/errors/src/pages/vue-syntax-error.astro
rename to packages/astro/e2e/fixtures/errors/src/pages/vue-syntax-error.astro
diff --git a/packages/astro/e2e/fixtures/preact-component/src/pages/mdx.mdx b/packages/astro/e2e/fixtures/preact-component/src/pages/mdx.mdx
index 76952d942..0ba961550 100644
--- a/packages/astro/e2e/fixtures/preact-component/src/pages/mdx.mdx
+++ b/packages/astro/e2e/fixtures/preact-component/src/pages/mdx.mdx
@@ -1,4 +1,4 @@
-
+export { default } from '../components/Layout.astro';
import Counter from '../components/Counter.jsx';
import PreactComponent from '../components/JSXComponent.jsx';
diff --git a/packages/astro/e2e/fixtures/react-component/src/pages/mdx.mdx b/packages/astro/e2e/fixtures/react-component/src/pages/mdx.mdx
index be13d365e..fe6520933 100644
--- a/packages/astro/e2e/fixtures/react-component/src/pages/mdx.mdx
+++ b/packages/astro/e2e/fixtures/react-component/src/pages/mdx.mdx
@@ -1,3 +1,4 @@
+export { default } from '../components/Layout.astro';
import Counter from '../components/Counter.jsx';
import ReactComponent from '../components/JSXComponent.jsx';
diff --git a/packages/astro/e2e/fixtures/solid-component/src/pages/mdx.mdx b/packages/astro/e2e/fixtures/solid-component/src/pages/mdx.mdx
index fcd3e3391..16186ba53 100644
--- a/packages/astro/e2e/fixtures/solid-component/src/pages/mdx.mdx
+++ b/packages/astro/e2e/fixtures/solid-component/src/pages/mdx.mdx
@@ -1,3 +1,4 @@
+export { default } from '../components/Layout.astro';
import Counter from '../components/Counter.jsx';
import SolidComponent from '../components/SolidComponent.jsx';
diff --git a/packages/astro/e2e/fixtures/svelte-component/src/pages/mdx.mdx b/packages/astro/e2e/fixtures/svelte-component/src/pages/mdx.mdx
index 0550f3b9f..60a59a509 100644
--- a/packages/astro/e2e/fixtures/svelte-component/src/pages/mdx.mdx
+++ b/packages/astro/e2e/fixtures/svelte-component/src/pages/mdx.mdx
@@ -1,3 +1,4 @@
+export { default } from '../components/Layout.astro';
import Counter from '../components/Counter.svelte';
import SvelteComponent from '../components/SvelteComponent.svelte';
diff --git a/packages/astro/e2e/fixtures/vue-component/src/pages/mdx.mdx b/packages/astro/e2e/fixtures/vue-component/src/pages/mdx.mdx
index b5d2285e5..5d0a141b0 100644
--- a/packages/astro/e2e/fixtures/vue-component/src/pages/mdx.mdx
+++ b/packages/astro/e2e/fixtures/vue-component/src/pages/mdx.mdx
@@ -1,3 +1,4 @@
+export { default } from '../components/Layout.astro';
import Counter from '../components/Counter.vue';
import VueComponent from '../components/VueComponent.vue';
diff --git a/packages/astro/e2e/preact-compat-component.test.js b/packages/astro/e2e/preact-compat-component.test.js
index db4bc8ae9..cb01983e9 100644
--- a/packages/astro/e2e/preact-compat-component.test.js
+++ b/packages/astro/e2e/preact-compat-component.test.js
@@ -2,18 +2,23 @@ import { prepareTestFactory } from './shared-component-tests.js';
const { test, createTests } = prepareTestFactory({ root: './fixtures/preact-compat-component/' });
+const config = {
+ counterComponentFilePath: './src/components/Counter.jsx',
+ componentFilePath: './src/components/JSXComponent.jsx',
+}
+
test.describe('preact/compat components in Astro files', () => {
createTests({
+ ...config,
pageUrl: '/',
pageSourceFilePath: './src/pages/index.astro',
- componentFilePath: './src/components/JSXComponent.jsx',
});
});
test.describe('preact/compat components in Markdown files', () => {
createTests({
+ ...config,
pageUrl: '/markdown/',
pageSourceFilePath: './src/pages/markdown.md',
- componentFilePath: './src/components/JSXComponent.jsx',
});
});
diff --git a/packages/astro/e2e/preact-component.test.js b/packages/astro/e2e/preact-component.test.js
index 98f723cf8..6d9a0b4b9 100644
--- a/packages/astro/e2e/preact-component.test.js
+++ b/packages/astro/e2e/preact-component.test.js
@@ -2,26 +2,31 @@ import { prepareTestFactory } from './shared-component-tests.js';
const { test, createTests } = prepareTestFactory({ root: './fixtures/preact-component/' });
+const config = {
+ counterComponentFilePath: './src/components/Counter.jsx',
+ componentFilePath: './src/components/JSXComponent.jsx',
+}
+
test.describe('Preact components in Astro files', () => {
createTests({
+ ...config,
pageUrl: '/',
pageSourceFilePath: './src/pages/index.astro',
- componentFilePath: './src/components/JSXComponent.jsx',
});
});
test.describe('Preact components in Markdown files', () => {
createTests({
+ ...config,
pageUrl: '/markdown/',
pageSourceFilePath: './src/pages/markdown.md',
- componentFilePath: './src/components/JSXComponent.jsx',
});
});
test.describe('Preact components in MDX files', () => {
createTests({
+ ...config,
pageUrl: '/mdx/',
pageSourceFilePath: './src/pages/mdx.mdx',
- componentFilePath: './src/components/JSXComponent.jsx',
});
});
diff --git a/packages/astro/e2e/react-component.test.js b/packages/astro/e2e/react-component.test.js
index 7aba75a0c..3442564df 100644
--- a/packages/astro/e2e/react-component.test.js
+++ b/packages/astro/e2e/react-component.test.js
@@ -2,26 +2,31 @@ import { prepareTestFactory } from './shared-component-tests.js';
const { test, createTests } = prepareTestFactory({ root: './fixtures/react-component/' });
+const config = {
+ counterComponentFilePath: './src/components/Counter.jsx',
+ componentFilePath: './src/components/JSXComponent.jsx',
+}
+
test.describe('React components in Astro files', () => {
createTests({
+ ...config,
pageUrl: '/',
pageSourceFilePath: './src/pages/index.astro',
- componentFilePath: './src/components/JSXComponent.jsx',
});
});
test.describe('React components in Markdown files', () => {
createTests({
+ ...config,
pageUrl: '/markdown/',
pageSourceFilePath: './src/pages/markdown.md',
- componentFilePath: './src/components/JSXComponent.jsx',
});
});
test.describe('React components in MDX files', () => {
createTests({
+ ...config,
pageUrl: '/mdx/',
pageSourceFilePath: './src/pages/mdx.mdx',
- componentFilePath: './src/components/JSXComponent.jsx',
});
});
diff --git a/packages/astro/e2e/shared-component-tests.js b/packages/astro/e2e/shared-component-tests.js
index ce66f23b2..3c223d964 100644
--- a/packages/astro/e2e/shared-component-tests.js
+++ b/packages/astro/e2e/shared-component-tests.js
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
-import { testFactory } from './test-utils.js';
+import { testFactory, getErrorOverlayMessage } from './test-utils.js';
export function prepareTestFactory(opts) {
const test = testFactory(opts);
diff --git a/packages/astro/e2e/solid-component.test.js b/packages/astro/e2e/solid-component.test.js
index 033829b04..e1f34587d 100644
--- a/packages/astro/e2e/solid-component.test.js
+++ b/packages/astro/e2e/solid-component.test.js
@@ -2,26 +2,31 @@ import { prepareTestFactory } from './shared-component-tests.js';
const { test, createTests } = prepareTestFactory({ root: './fixtures/solid-component/' });
+const config = {
+ componentFilePath: './src/components/SolidComponent.jsx',
+ counterComponentFilePath: './src/components/Counter.jsx',
+}
+
test.describe('Solid components in Astro files', () => {
createTests({
+ ...config,
pageUrl: '/',
pageSourceFilePath: './src/pages/index.astro',
- componentFilePath: './src/components/SolidComponent.jsx',
});
});
test.describe('Solid components in Markdown files', () => {
createTests({
+ ...config,
pageUrl: '/markdown/',
pageSourceFilePath: './src/pages/markdown.md',
- componentFilePath: './src/components/SolidComponent.jsx',
});
});
test.describe('Solid components in MDX files', () => {
createTests({
+ ...config,
pageUrl: '/mdx/',
pageSourceFilePath: './src/pages/mdx.mdx',
- componentFilePath: './src/components/SolidComponent.jsx',
});
});
diff --git a/packages/astro/e2e/svelte-component.test.js b/packages/astro/e2e/svelte-component.test.js
index 7533d7cb7..637d74270 100644
--- a/packages/astro/e2e/svelte-component.test.js
+++ b/packages/astro/e2e/svelte-component.test.js
@@ -2,29 +2,32 @@ import { prepareTestFactory } from './shared-component-tests.js';
const { test, createTests } = prepareTestFactory({ root: './fixtures/svelte-component/' });
+const config = {
+ componentFilePath: './src/components/SvelteComponent.svelte',
+ counterComponentFilePath: './src/components/Counter.svelte',
+ counterCssFilePath: './src/components/Counter.svelte',
+}
+
test.describe('Svelte components in Astro files', () => {
createTests({
+ ...config,
pageUrl: '/',
pageSourceFilePath: './src/pages/index.astro',
- componentFilePath: './src/components/SvelteComponent.svelte',
- counterCssFilePath: './src/components/Counter.svelte',
});
});
test.describe('Svelte components in Markdown files', () => {
createTests({
+ ...config,
pageUrl: '/markdown/',
pageSourceFilePath: './src/pages/markdown.md',
- componentFilePath: './src/components/SvelteComponent.svelte',
- counterCssFilePath: './src/components/Counter.svelte',
});
});
test.describe('Svelte components in MDX files', () => {
createTests({
+ ...config,
pageUrl: '/mdx/',
pageSourceFilePath: './src/pages/mdx.mdx',
- componentFilePath: './src/components/SvelteComponent.svelte',
- counterCssFilePath: './src/components/Counter.svelte',
});
});
diff --git a/packages/astro/e2e/test-utils.js b/packages/astro/e2e/test-utils.js
index 33edb1e3b..ba5e28053 100644
--- a/packages/astro/e2e/test-utils.js
+++ b/packages/astro/e2e/test-utils.js
@@ -1,4 +1,4 @@
-import { test as testBase } from '@playwright/test';
+import { test as testBase, expect } from '@playwright/test';
import { loadFixture as baseLoadFixture } from '../test/test-utils.js';
export function loadFixture(inlineConfig) {
@@ -29,3 +29,11 @@ export function testFactory(inlineConfig) {
return test;
}
+
+export async function getErrorOverlayMessage(page) {
+ const overlay = await page.waitForSelector('vite-error-overlay', { strict: true, timeout: 10 * 1000 })
+
+ expect(overlay).toBeTruthy()
+
+ return await overlay.$$eval('.message-body', (m) => m[0].textContent)
+}
diff --git a/packages/astro/e2e/vue-component.test.js b/packages/astro/e2e/vue-component.test.js
index d975600dc..ccc18df2b 100644
--- a/packages/astro/e2e/vue-component.test.js
+++ b/packages/astro/e2e/vue-component.test.js
@@ -2,29 +2,32 @@ import { prepareTestFactory } from './shared-component-tests.js';
const { test, createTests } = prepareTestFactory({ root: './fixtures/vue-component/' });
+const config = {
+ componentFilePath: './src/components/VueComponent.vue',
+ counterCssFilePath: './src/components/Counter.vue',
+ counterComponentFilePath: './src/components/Counter.vue',
+}
+
test.describe('Vue components in Astro files', () => {
createTests({
+ ...config,
pageUrl: '/',
pageSourceFilePath: './src/pages/index.astro',
- componentFilePath: './src/components/VueComponent.vue',
- counterCssFilePath: './src/components/Counter.vue',
});
});
test.describe('Vue components in Markdown files', () => {
createTests({
+ ...config,
pageUrl: '/markdown/',
pageSourceFilePath: './src/pages/markdown.md',
- componentFilePath: './src/components/VueComponent.vue',
- counterCssFilePath: './src/components/Counter.vue',
});
});
test.describe('Vue components in MDX files', () => {
createTests({
+ ...config,
pageUrl: '/mdx/',
pageSourceFilePath: './src/pages/mdx.mdx',
- componentFilePath: './src/components/VueComponent.vue',
- counterCssFilePath: './src/components/Counter.vue',
});
});
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 631a12895..a5c7dfd97 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -76,7 +76,7 @@
"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\"",
"benchmark": "node test/benchmark/dev.bench.js && node test/benchmark/build.bench.js",
- "test": "mocha --exit --timeout 20000 --ignore **/lit-element.test.js --ignore **/errors.test.js && mocha --timeout 20000 **/lit-element.test.js && mocha --timeout 20000 **/errors.test.js",
+ "test": "mocha --exit --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js",
"test:match": "mocha --timeout 20000 -g",
"test:e2e": "playwright test",
"test:e2e:match": "playwright test -g"
diff --git a/packages/astro/playwright.config.js b/packages/astro/playwright.config.js
index 205c330d7..6dc2b01f4 100644
--- a/packages/astro/playwright.config.js
+++ b/packages/astro/playwright.config.js
@@ -9,12 +9,12 @@ const config = {
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
- timeout: 5000,
+ timeout: 3000,
},
/* Fail the build on CI if you accidentally left test in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
- retries: process.env.CI ? 5 : 0,
+ retries: process.env.CI ? 3 : 0,
/* Opt out of parallel tests on CI. */
workers: 1,
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
@@ -33,6 +33,7 @@ const config = {
use: {
browserName: 'chromium',
channel: 'chrome',
+ args: ["--use-gl=egl"]
},
},
],
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index 6d0ceb7a5..176471e43 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -4,6 +4,7 @@ import type { LogOptions } from './logger/core';
import fs from 'fs';
import { fileURLToPath } from 'url';
import * as vite from 'vite';
+import { createCustomViteLogger } from './errors.js';
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
import astroViteServerPlugin from '../vite-plugin-astro-server/index.js';
import astroVitePlugin from '../vite-plugin-astro/index.js';
@@ -60,7 +61,7 @@ export async function createVite(
clearScreen: false, // we want to control the output, not Vite
logLevel: 'warn', // log warnings and errors only
optimizeDeps: {
- entries: ['src/**/*'], // Try and scan a user’s project (won’t catch everything),
+ entries: ['src/**/*'],
exclude: ['node-fetch'],
},
plugins: [
@@ -133,6 +134,8 @@ export async function createVite(
sortPlugins(result.plugins);
}
+ result.customLogger = createCustomViteLogger(result.logLevel ?? 'warn');
+
return result;
}
diff --git a/packages/astro/src/core/errors.ts b/packages/astro/src/core/errors.ts
index 793cde1d1..2926ad291 100644
--- a/packages/astro/src/core/errors.ts
+++ b/packages/astro/src/core/errors.ts
@@ -1,8 +1,12 @@
-import eol from 'eol';
import type { BuildResult } from 'esbuild';
-import fs from 'fs';
-import type { ViteDevServer } from 'vite';
+import type { ViteDevServer, ErrorPayload, LogLevel, Logger } from 'vite';
import type { SSRError } from '../@types/astro';
+
+import { fileURLToPath } from 'node:url';
+import { createLogger } from 'vite';
+import eol from 'eol';
+import fs from 'fs';
+import stripAnsi from 'strip-ansi';
import { codeFrame, createSafeError } from './util.js';
export enum AstroErrorCodes {
@@ -34,11 +38,12 @@ export function cleanErrorStack(stack: string) {
return stack
.split(/\n/g)
.filter((l) => /^\s*at/.test(l))
+ .map(l => l.replace(/\/@fs\//g, '/'))
.join('\n');
}
/** Update the error message to correct any vite-isms that we don't want to expose to the user. */
-export function fixViteErrorMessage(_err: unknown, server: ViteDevServer) {
+export function fixViteErrorMessage(_err: unknown, server: ViteDevServer, filePath?: URL) {
const err = createSafeError(_err);
// Vite will give you better stacktraces, using sourcemaps.
server.ssrFixStacktrace(err);
@@ -47,6 +52,18 @@ export function fixViteErrorMessage(_err: unknown, server: ViteDevServer) {
if (err.message === 'import.meta.glob() can only accept string literals.') {
err.message = 'Astro.glob() and import.meta.glob() can only accept string literals.';
}
+ if (filePath && /failed to load module for ssr:/.test(err.message)) {
+ const importName = err.message.split('for ssr:').at(1)?.trim();
+ if (importName) {
+ const content = fs.readFileSync(fileURLToPath(filePath)).toString();
+ const lns = content.split('\n')
+ const line = lns.findIndex(ln => ln.includes(importName));
+ const column = lns[line].indexOf(importName);
+ if (!(err as any).id) {
+ (err as any).id = `${fileURLToPath(filePath)}:${line + 1}:${column + 1}`
+ }
+ }
+ }
return err;
}
@@ -55,7 +72,21 @@ const incompatiblePackages = {
};
const incompatPackageExp = new RegExp(`(${Object.keys(incompatiblePackages).join('|')})`);
-function generateHint(err: ErrorWithMetadata): string | undefined {
+
+export function createCustomViteLogger(logLevel: LogLevel): Logger {
+ const viteLogger = createLogger(logLevel);
+ const logger: Logger = {
+ ...viteLogger,
+ error(msg, options?) {
+ // Silence warnings from incompatible packages (we log better errors for these)
+ if (incompatPackageExp.test(msg)) return;
+ return viteLogger.error(msg, options);
+ },
+ }
+ return logger;
+}
+
+function generateHint(err: ErrorWithMetadata, filePath?: URL): string | undefined {
if (/Unknown file extension \"\.(jsx|vue|svelte|astro|css)\" for /.test(err.message)) {
return 'You likely need to add this package to `vite.ssr.noExternal` in your astro config file.';
} else {
@@ -72,29 +103,54 @@ function generateHint(err: ErrorWithMetadata): string | undefined {
* Takes any error-like object and returns a standardized Error + metadata object.
* Useful for consistent reporting regardless of where the error surfaced from.
*/
-export function collectErrorMetadata(e: any): ErrorWithMetadata {
- // normalize error stack line-endings to \n
+export function collectErrorMetadata(e: any, filePath?: URL): ErrorWithMetadata {
+ const err = e as SSRError;
+
if ((e as any).stack) {
+ // normalize error stack line-endings to \n
(e as any).stack = eol.lf((e as any).stack);
+ // derive error location from stack (if possible)
+ const stackText = stripAnsi(e.stack);
+ // TODO: this could be better, `src` might be something else
+ const possibleFilePath = err.pluginCode || err.id || stackText.split('\n').find(ln => ln.includes('src') || ln.includes('node_modules'));
+ const source = possibleFilePath?.replace(/^[^(]+\(([^)]+).*$/, '$1');
+ const [file, line, column] = source?.split(':') ?? [];
+ if (!err.loc && line && column) {
+ err.loc = {
+ file,
+ line: Number.parseInt(line),
+ column: Number.parseInt(column)
+ }
+ }
+
+ // Derive plugin from stack (if possible)
+ if (!err.plugin) {
+ err.plugin =
+ /withastro\/astro\/packages\/integrations\/([\w-]+)/gmi.exec(stackText)?.at(1) ||
+ /(@astrojs\/[\w-]+)\/(server|client|index)/gmi.exec(stackText)?.at(1) ||
+ undefined;
+ }
+
+ // Normalize stack (remove `/@fs/` urls, etc)
+ err.stack = cleanErrorStack(e.stack)
}
if (e.name === 'YAMLException') {
- const err = e as SSRError;
err.loc = { file: (e as any).id, line: (e as any).mark.line, column: (e as any).mark.column };
err.message = (e as any).reason;
+ }
- if (!err.frame) {
- try {
- const fileContents = fs.readFileSync(err.loc.file!, 'utf8');
- err.frame = codeFrame(fileContents, err.loc);
- } catch {}
- }
+ if (!err.frame && err.loc) {
+ try {
+ const fileContents = fs.readFileSync(err.loc.file!, 'utf8');
+ const frame = codeFrame(fileContents, err.loc);
+ err.frame = frame;
+ } catch {}
}
// Astro error (thrown by esbuild so it needs to be formatted for Vite)
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 };
err.id = err.id || location?.file;
@@ -111,11 +167,28 @@ export function collectErrorMetadata(e: any): ErrorWithMetadata {
if (pluginName) {
err.plugin = pluginName;
}
- err.hint = generateHint(err);
+ err.hint = generateHint(err, filePath);
return err;
}
// Generic error (probably from Vite, and already formatted)
- e.hint = generateHint(e);
- return e;
+ err.hint = generateHint(e, filePath);
+ return err;
+}
+
+export function getViteErrorPayload(err: ErrorWithMetadata): ErrorPayload {
+ let plugin = err.plugin;
+ if (!plugin && err.hint) {
+ plugin = 'astro';
+ }
+ const message = `${err.message}\n\n${err.hint ?? ''}`;
+ return {
+ type: 'error',
+ err: {
+ ...err,
+ plugin,
+ message: message.trim(),
+ stack: err.stack,
+ }
+ }
}
diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts
index 0bbc0a72a..36f445236 100644
--- a/packages/astro/src/core/util.ts
+++ b/packages/astro/src/core/util.ts
@@ -79,7 +79,7 @@ export function createSafeError(err: any): Error {
/** generate code frame from esbuild error */
export function codeFrame(src: string, loc: ErrorPayload['err']['loc']): string {
if (!loc) return '';
- const lines = eol.lf(src).split('\n');
+ const lines = eol.lf(src).split('\n').map(ln => ln.replace(/\t/g, ' '));
// grab 2 lines before, and 3 lines after focused line
const visibleLines = [];
for (let n = -2; n <= 2; n++) {
@@ -98,9 +98,7 @@ export function codeFrame(src: string, loc: ErrorPayload['err']['loc']): string
output += isFocusedLine ? '> ' : ' ';
output += `${lineNo + 1} | ${lines[lineNo]}\n`;
if (isFocusedLine)
- output += `${[...new Array(gutterWidth)].join(' ')} | ${[...new Array(loc.column)].join(
- ' '
- )}^\n`;
+ output += `${Array.from({ length: gutterWidth }).join(' ')} | ${Array.from({ length: loc.column }).join(' ')}^\n`;
}
return output;
}
diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts
index 4a5bbac6e..501dfd828 100644
--- a/packages/astro/src/runtime/server/index.ts
+++ b/packages/astro/src/runtime/server/index.ts
@@ -156,6 +156,7 @@ export function mergeSlots(...slotted: unknown[]) {
}
export const Fragment = Symbol.for('astro:fragment');
+export const Renderer = Symbol.for('astro:renderer');
export const ClientOnlyPlaceholder = 'astro-client-only';
function guessRenderers(componentUrl?: string): string[] {
@@ -182,6 +183,17 @@ function formatList(values: string[]): string {
const rendererAliases = new Map([['solid', 'solid-js']]);
+/** @internal Assosciate JSX components with a specific renderer (see /src/vite-plugin-jsx/tag.ts) */
+export function __astro_tag_component__(Component: unknown, rendererName: string) {
+ if (!Component) return;
+ if (typeof Component !== 'function') return;
+ Object.defineProperty(Component, Renderer, {
+ value: rendererName,
+ enumerable: false,
+ writable: false
+ })
+}
+
export async function renderComponent(
result: SSRResult,
displayName: string,
@@ -270,20 +282,30 @@ Did you mean to add ${formatList(probableRendererNames.map((r) => '`' + r + '`')
// Call the renderers `check` hook to see if any claim this component.
let renderer: SSRLoadedRenderer | undefined;
if (metadata.hydrate !== 'only') {
- let error;
- for (const r of renderers) {
- try {
- if (await r.ssr.check.call({ result }, Component, props, children)) {
- renderer = r;
- break;
- }
- } catch (e) {
- error ??= e;
- }
+ // If this component ran through `__astro_tag_component__`, we already know
+ // which renderer to match to and can skip the usual `check` calls.
+ // This will help us throw most relevant error message for modules with runtime errors
+ if (Component && (Component as any)[Renderer]) {
+ const rendererName = (Component as any)[Renderer];
+ renderer = renderers.find(({ name }) => name === rendererName);
}
- if (error) {
- throw error;
+ if (!renderer) {
+ let error;
+ for (const r of renderers) {
+ try {
+ if (await r.ssr.check.call({ result }, Component, props, children)) {
+ renderer = r;
+ break;
+ }
+ } catch (e) {
+ error ??= e;
+ }
+ }
+
+ if (error) {
+ throw error;
+ }
}
if (!renderer && typeof HTMLElement === 'function' && componentIsHTMLElement(Component)) {
@@ -298,9 +320,9 @@ Did you mean to add ${formatList(probableRendererNames.map((r) => '`' + r + '`')
const rendererName = rendererAliases.has(passedName)
? rendererAliases.get(passedName)
: passedName;
- renderer = renderers.filter(
+ renderer = renderers.find(
({ name }) => name === `@astrojs/${rendererName}` || name === rendererName
- )[0];
+ );
}
// Attempt: user only has a single renderer, default to that
if (!renderer && renderers.length === 1) {
@@ -766,17 +788,21 @@ export async function renderPage(
start(controller) {
async function read() {
let i = 0;
- for await (const chunk of iterable) {
- let html = chunk.toString();
- if (i === 0) {
- if (!/\n'));
+ try {
+ for await (const chunk of iterable) {
+ let html = chunk.toString();
+ if (i === 0) {
+ if (!/\n'));
+ }
}
+ controller.enqueue(encoder.encode(html));
+ i++;
}
- controller.enqueue(encoder.encode(html));
- i++;
+ controller.close();
+ } catch (e) {
+ controller.error(e)
}
- controller.close();
}
read();
},
@@ -851,7 +877,7 @@ export async function* renderAstroComponent(
if (value || value === 0) {
for await (const chunk of _render(value)) {
yield markHTMLString(chunk);
- }
+ }
}
}
}
diff --git a/packages/astro/src/template/5xx.ts b/packages/astro/src/template/5xx.ts
deleted file mode 100644
index 0354fbbfb..000000000
--- a/packages/astro/src/template/5xx.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { encode } from 'html-entities';
-import { baseCSS } from './css.js';
-
-interface ErrorTemplateOptions {
- /** a short description of the error */
- message: string;
- /** information about where the error occurred */
- stack?: string;
- /** HTTP error code */
- statusCode?: number;
- /** HTML */
- tabTitle: string;
- /** page title */
- title: string;
- /** show user a URL for more info or action to take */
- url?: string;
-}
-
-/** Display all errors */
-export default function template({
- title,
- url,
- message,
- stack,
- statusCode,
- tabTitle,
-}: ErrorTemplateOptions): string {
- let error = url ? message.replace(url, '') : message;
- return `
-
-
-
- ${tabTitle}
-
-
-
-
-
-
- ${
- statusCode ? `${statusCode}: ` : ''
- }${title}
-
- ${encode(error)}
- ${url ? `${url}` : ''}
- ${encode(stack)}
-
-
- `;
-}
diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts
index 0b8cfd9b7..d12df2d61 100644
--- a/packages/astro/src/vite-plugin-astro-server/index.ts
+++ b/packages/astro/src/vite-plugin-astro-server/index.ts
@@ -5,9 +5,8 @@ import type { AstroConfig, ManifestData } from '../@types/astro';
import type { SSROptions } from '../core/render/dev/index';
import { Readable } from 'stream';
-import stripAnsi from 'strip-ansi';
import { call as callEndpoint } from '../core/endpoint/dev/index.js';
-import { collectErrorMetadata, fixViteErrorMessage } from '../core/errors.js';
+import { collectErrorMetadata, ErrorWithMetadata, fixViteErrorMessage, getViteErrorPayload } from '../core/errors.js';
import { error, info, LogOptions, warn } from '../core/logger/core.js';
import * as msg from '../core/messages.js';
import { appendForwardSlash } from '../core/path.js';
@@ -18,7 +17,6 @@ import { createRequest } from '../core/request.js';
import { createRouteManifest, matchRoute } from '../core/routing/index.js';
import { createSafeError, isBuildingToSSR, resolvePages } from '../core/util.js';
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
-import serverErrorTemplate from '../template/5xx.js';
interface AstroPluginOptions {
config: AstroConfig;
@@ -151,19 +149,14 @@ async function handle500Response(
origin: string,
req: http.IncomingMessage,
res: http.ServerResponse,
- err: any
+ err: ErrorWithMetadata
) {
- const pathname = decodeURI(new URL('./index.html', origin + req.url).pathname);
- const html = serverErrorTemplate({
- statusCode: 500,
- title: 'Internal Error',
- tabTitle: '500: Error',
- message: stripAnsi(err.hint ?? err.message),
- url: err.url || undefined,
- stack: truncateString(stripAnsi(err.stack), 500),
- });
- const transformedHtml = await viteServer.transformIndexHtml(pathname, html);
- writeHtmlResponse(res, 500, transformedHtml);
+ res.on('close', () => setTimeout(() => viteServer.ws.send(getViteErrorPayload(err)), 200))
+ if (res.headersSent) {
+ res.end()
+ } else {
+ writeHtmlResponse(res, 500, `${err.name}`);
+ }
}
function getCustom404Route(config: AstroConfig, manifest: ManifestData) {
@@ -232,6 +225,7 @@ async function handleRequest(
clientAddress: buildingToSSR ? req.socket.remoteAddress : undefined,
});
+ let filePath: URL|undefined;
try {
if (!pathname.startsWith(devRoot)) {
log404(logging, pathname);
@@ -252,7 +246,7 @@ async function handleRequest(
}
}
- const filePath = new URL(`./${route.component}`, config.root);
+ filePath = new URL(`./${route.component}`, config.root);
const preloadedComponent = await preload({ astroConfig: config, filePath, viteServer });
const [, mod] = preloadedComponent;
// attempt to get static paths
@@ -330,10 +324,10 @@ async function handleRequest(
return await writeSSRResult(result, res);
}
} catch (_err) {
- const err = fixViteErrorMessage(createSafeError(_err), viteServer);
- const errorWithMetadata = collectErrorMetadata(_err);
+ const err = fixViteErrorMessage(createSafeError(_err), viteServer, filePath);
+ const errorWithMetadata = collectErrorMetadata(err);
error(logging, null, msg.formatErrorMessage(errorWithMetadata));
- handle500Response(viteServer, origin, req, res, err);
+ handle500Response(viteServer, origin, req, res, errorWithMetadata);
}
}
diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts
index a08460204..03d2882ad 100644
--- a/packages/astro/src/vite-plugin-jsx/index.ts
+++ b/packages/astro/src/vite-plugin-jsx/index.ts
@@ -5,6 +5,7 @@ import type { LogOptions } from '../core/logger/core.js';
import type { PluginMetadata } from '../vite-plugin-astro/types';
import babel from '@babel/core';
+import tagExportsPlugin from './tag.js';
import * as eslexer from 'es-module-lexer';
import esbuild from 'esbuild';
import * as colors from 'kleur/colors';
@@ -55,7 +56,7 @@ async function transformJSX({
}: TransformJSXOptions): Promise {
const { jsxTransformOptions } = renderer;
const options = await jsxTransformOptions!({ mode, ssr });
- const plugins = [...(options.plugins || [])];
+ const plugins = [...(options.plugins || []), tagExportsPlugin({ rendererName: renderer.name })];
const result = await babel.transformAsync(code, {
presets: options.presets,
plugins,
diff --git a/packages/astro/src/vite-plugin-jsx/tag.ts b/packages/astro/src/vite-plugin-jsx/tag.ts
new file mode 100644
index 000000000..4bb3b2341
--- /dev/null
+++ b/packages/astro/src/vite-plugin-jsx/tag.ts
@@ -0,0 +1,64 @@
+import type { PluginObj } from '@babel/core';
+import * as t from '@babel/types';
+
+/**
+ * This plugin handles every file that runs through our JSX plugin.
+ * Since we statically match every JSX file to an Astro renderer based on import scanning,
+ * it would be helpful to embed some of that metadata at runtime.
+ *
+ * This plugin crawls each export in the file and "tags" each export with a given `rendererName`.
+ * This allows us to automatically match a component to a renderer and skip the usual `check()` calls.
+ */
+export default function tagExportsWithRenderer({ rendererName }: { rendererName: string }): PluginObj {
+ return {
+ visitor: {
+ Program: {
+ // Inject `import { __astro_tag_component__ } from 'astro/server/index.js'`
+ enter(path) {
+ path.node.body.splice(
+ 0,
+ 0,
+ t.importDeclaration(
+ [t.importSpecifier(t.identifier('__astro_tag_component__'), t.identifier('__astro_tag_component__'))],
+ t.stringLiteral('astro/server/index.js')
+ )
+ );
+ },
+ // For each export we found, inject `__astro_tag_component__(exportName, rendererName)`
+ exit(path, state) {
+ const exportedIds = state.get('astro:tags')
+ if (exportedIds) {
+ for (const id of exportedIds) {
+ path.node.body.push(
+ t.expressionStatement(t.callExpression(t.identifier('__astro_tag_component__'), [t.identifier(id), t.stringLiteral(rendererName)]))
+ )
+ }
+ }
+ }
+ },
+ ExportDeclaration(path, state) {
+ const node = path.node;
+ if (node.exportKind === 'type') return;
+ if (node.type === 'ExportAllDeclaration') return;
+
+ if (node.type === 'ExportNamedDeclaration') {
+ if (t.isFunctionDeclaration(node.declaration)) {
+ if (node.declaration.id?.name) {
+ const id = node.declaration.id.name;
+ const tags = state.get('astro:tags') ?? []
+ state.set('astro:tags', [...tags, id])
+ }
+ }
+ } else if (node.type === 'ExportDefaultDeclaration') {
+ if (t.isFunctionDeclaration(node.declaration)) {
+ if (node.declaration.id?.name) {
+ const id = node.declaration.id.name;
+ const tags = state.get('astro:tags') ?? []
+ state.set('astro:tags', [...tags, id])
+ }
+ }
+ }
+ },
+ }
+ };
+}
diff --git a/packages/astro/test/error-react-spectrum.test.js b/packages/astro/test/error-react-spectrum.test.js
deleted file mode 100644
index a16e8632a..000000000
--- a/packages/astro/test/error-react-spectrum.test.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { isWindows, loadFixture } from './test-utils.js';
-import { expect } from 'chai';
-import * as cheerio from 'cheerio';
-
-describe('Error packages: react-spectrum', () => {
- if (isWindows) return;
-
- /** @type {import('./test-utils').Fixture} */
- let fixture;
- /** @type {import('./test-utils').DevServer} */
- let devServer;
-
- before(async () => {
- fixture = await loadFixture({
- root: './fixtures/error-react-spectrum',
- });
- });
- after(async () => {
- devServer && devServer.stop();
- });
-
- it('properly detect syntax errors in template', async () => {
- devServer = await fixture.startDevServer();
- let html = await fixture.fetch('/').then((res) => res.text());
- let $ = cheerio.load(html);
- const msg = $('.error-message').text();
- expect(msg).to.match(/@adobe\/react-spectrum is not compatible/);
- });
-});
diff --git a/packages/astro/test/errors.test.js b/packages/astro/test/errors.test.js
deleted file mode 100644
index 01fe61b9a..000000000
--- a/packages/astro/test/errors.test.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import { isWindows, isLinux, loadFixture } from './test-utils.js';
-import { expect } from 'chai';
-import * as cheerio from 'cheerio';
-
-describe('Error display', () => {
- if (isWindows) return;
-
- /** @type {import('./test-utils').Fixture} */
- let fixture;
-
- before(async () => {
- fixture = await loadFixture({
- root: './fixtures/errors',
- });
- });
-
- /**
- * TODO: Track down reliability issue
- *
- * After fixing a syntax error on one page, the dev server hangs on the hmr.js request.
- * This is specific to a project that has other framework component errors,
- * in this case the fixture has multiple broken pages and components.
- *
- * The issue could be internal to vite, the hmr.js request triggers connect:dispatcher
- * events but vite:load is never actually called.
- */
- describe.skip('Astro template syntax', async () => {
- let devServer;
-
- beforeEach(async () => {
- devServer = await fixture.startDevServer();
- });
-
- afterEach(async () => {
- await devServer.stop();
- });
-
- it('properly detect syntax errors in template', async () => {
- let html = await fixture.fetch('/astro-syntax-error').then((res) => res.text());
-
- // 1. Verify an error message is being shown.
- let $ = cheerio.load(html);
- expect($('.statusMessage').text()).to.equal('Internal Error');
- expect($('.error-message').text()).to.contain('Unexpected "}"');
-
- // 2. Edit the file, fixing the error
- await fixture.editFile('./src/pages/astro-syntax-error.astro', `No syntax error
`);
-
- // 3. Verify that the file is fixed.
- html = await fixture.fetch('/astro-syntax-error').then((res) => res.text());
- $ = cheerio.load(html);
- expect($('h1').text()).to.equal('No syntax error');
- });
-
- it('shows useful error when frontmatter import is not found', async () => {
- let html = await fixture.fetch('/import-not-found').then((res) => res.text());
-
- // 1. Verify an error message is being shown.
- let $ = cheerio.load(html);
- expect($('.statusMessage').text()).to.equal('Internal Error');
- expect($('.error-message').text()).to.equal('failed to load module for ssr: ../abc.astro');
-
- // 2. Edit the file, fixing the error
- await fixture.editFile('./src/pages/import-not-found.astro', 'No import error
');
-
- // 3. Verify that the file is fixed.
- html = await fixture.fetch('/import-not-found').then((res) => res.text());
- $ = cheerio.load(html);
- expect($('h1').text()).to.equal('No import error');
- });
- });
-
- describe('Framework components', function () {
- let devServer;
-
- before(async () => {
- devServer = await fixture.startDevServer();
- });
-
- after(async () => {
- await devServer.stop();
- });
-
- it('Errors recover when fixed', async () => {
- let html = await fixture.fetch('/svelte-syntax-error').then((res) => res.text());
-
- // 1. Verify an error message is being shown.
- let $ = cheerio.load(html);
- expect($('.statusMessage').text()).to.equal('Internal Error');
-
- // 2. Edit the file, fixing the error
- await fixture.editFile('./src/components/SvelteSyntaxError.svelte', `No mismatch
`);
-
- // 3. Verify that the file is fixed.
- html = await fixture.fetch('/svelte-syntax-error').then((res) => res.text());
- $ = cheerio.load(html);
- expect($('h1').text()).to.equal('No mismatch');
- });
- });
-});
diff --git a/packages/astro/test/sass.test.js b/packages/astro/test/sass.test.js
deleted file mode 100644
index 07c1f1499..000000000
--- a/packages/astro/test/sass.test.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { expect } from 'chai';
-import os from 'os';
-import { loadFixture } from './test-utils.js';
-
-// note: many Sass tests live in 0-css.test.js to test within context of a framework.
-// these tests are independent of framework.
-describe('Sass', () => {
- let fixture;
- let devServer;
-
- before(async () => {
- fixture = await loadFixture({ root: './fixtures/sass/' });
- devServer = await fixture.startDevServer();
- });
-
- after(async () => {
- await devServer.stop();
- });
-
- // TODO: Sass cannot be found on macOS for some reason... Vite issue?
- const test = os.platform() === 'darwin' ? it.skip : it;
- test('shows helpful error on failure', async () => {
- const text = await fixture.fetch('/error').then((res) => res.text());
- expect(text).to.include('Undefined variable');
- });
-});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 01ec6c10d..8bcedfd28 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -700,6 +700,48 @@ importers:
'@astrojs/vue': link:../../../../integrations/vue
astro: link:../../..
+ packages/astro/e2e/fixtures/error-react-spectrum:
+ specifiers:
+ '@adobe/react-spectrum': ^3.18.0
+ '@astrojs/react': workspace:*
+ astro: workspace:*
+ react: ^18.1.0
+ react-dom: ^18.1.0
+ dependencies:
+ '@adobe/react-spectrum': 3.19.0_biqbaboplfbrettd7655fr4n2y
+ '@astrojs/react': link:../../../../integrations/react
+ astro: link:../../..
+ react: 18.2.0
+ react-dom: 18.2.0_react@18.2.0
+
+ packages/astro/e2e/fixtures/error-sass:
+ specifiers:
+ astro: workspace:*
+ sass: ^1.52.2
+ dependencies:
+ astro: link:../../..
+ sass: 1.53.0
+
+ packages/astro/e2e/fixtures/errors:
+ specifiers:
+ '@astrojs/preact': workspace:*
+ '@astrojs/react': workspace:*
+ '@astrojs/solid-js': workspace:*
+ '@astrojs/svelte': workspace:*
+ '@astrojs/vue': workspace:*
+ astro: workspace:*
+ react: ^18.1.0
+ react-dom: ^18.1.0
+ dependencies:
+ '@astrojs/preact': link:../../../../integrations/preact
+ '@astrojs/react': link:../../../../integrations/react
+ '@astrojs/solid-js': link:../../../../integrations/solid
+ '@astrojs/svelte': link:../../../../integrations/svelte
+ '@astrojs/vue': link:../../../../integrations/vue
+ astro: link:../../..
+ react: 18.2.0
+ react-dom: 18.2.0_react@18.2.0
+
packages/astro/e2e/fixtures/lit-component:
specifiers:
'@astrojs/lit': workspace:*
@@ -1467,40 +1509,6 @@ importers:
'@astrojs/preact': link:../../../../integrations/preact
astro: link:../../..
- packages/astro/test/fixtures/error-react-spectrum:
- specifiers:
- '@adobe/react-spectrum': ^3.18.0
- '@astrojs/react': workspace:*
- astro: workspace:*
- react: ^18.1.0
- react-dom: ^18.1.0
- dependencies:
- '@adobe/react-spectrum': 3.19.0_biqbaboplfbrettd7655fr4n2y
- '@astrojs/react': link:../../../../integrations/react
- astro: link:../../..
- react: 18.2.0
- react-dom: 18.2.0_react@18.2.0
-
- packages/astro/test/fixtures/errors:
- specifiers:
- '@astrojs/preact': workspace:*
- '@astrojs/react': workspace:*
- '@astrojs/solid-js': workspace:*
- '@astrojs/svelte': workspace:*
- '@astrojs/vue': workspace:*
- astro: workspace:*
- react: ^18.1.0
- react-dom: ^18.1.0
- dependencies:
- '@astrojs/preact': link:../../../../integrations/preact
- '@astrojs/react': link:../../../../integrations/react
- '@astrojs/solid-js': link:../../../../integrations/solid
- '@astrojs/svelte': link:../../../../integrations/svelte
- '@astrojs/vue': link:../../../../integrations/vue
- astro: link:../../..
- react: 18.2.0
- react-dom: 18.2.0_react@18.2.0
-
packages/astro/test/fixtures/fetch:
specifiers:
'@astrojs/preact': workspace:*
@@ -1679,14 +1687,6 @@ importers:
dependencies:
astro: link:../../..
- packages/astro/test/fixtures/sass:
- specifiers:
- astro: workspace:*
- sass: ^1.52.2
- dependencies:
- astro: link:../../..
- sass: 1.53.0
-
packages/astro/test/fixtures/slots-preact:
specifiers:
'@astrojs/preact': workspace:*