summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/dirty-guests-wash.md5
-rw-r--r--packages/astro/src/vite-plugin-astro/index.ts26
-rw-r--r--packages/astro/test/errors.test.js119
-rw-r--r--packages/astro/test/fixtures/errors/src/pages/astro-frontmatter-syntax-error.astro4
4 files changed, 44 insertions, 110 deletions
diff --git a/.changeset/dirty-guests-wash.md b/.changeset/dirty-guests-wash.md
new file mode 100644
index 000000000..c47deb09a
--- /dev/null
+++ b/.changeset/dirty-guests-wash.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Improve error message on bad JS/TS frontmatter
diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts
index 6ec16db2f..e77389ff6 100644
--- a/packages/astro/src/vite-plugin-astro/index.ts
+++ b/packages/astro/src/vite-plugin-astro/index.ts
@@ -10,6 +10,7 @@ import { transform } from '@astrojs/compiler';
import { AstroDevServer } from '../core/dev/index.js';
import { getViteTransform, TransformHook, transformWithVite } from './styles.js';
+const FRONTMATTER_PARSE_REGEXP = /^\-\-\-(.*)^\-\-\-/ms;
interface AstroPluginOptions {
config: AstroConfig;
devServer?: AstroDevServer;
@@ -87,7 +88,8 @@ export default function astro({ config, devServer }: AstroPluginOptions): vite.P
// throw CSS transform errors here if encountered
if (cssTransformError) throw cssTransformError;
- // Compile `.ts` to `.js`
+ // Compile all TypeScript to JavaScript.
+ // Also, catches invalid JS/TS in the compiled output before returning.
const { code, map } = await esbuild.transform(tsResult.code, { loader: 'ts', sourcemap: 'external', sourcefile: id });
return {
@@ -95,6 +97,28 @@ export default function astro({ config, devServer }: AstroPluginOptions): vite.P
map,
};
} catch (err: any) {
+ // Verify frontmatter: a common reason that this plugin fails is that
+ // the user provided invalid JS/TS in the component frontmatter.
+ // If the frontmatter is invalid, the `err` object may be a compiler
+ // panic or some other vague/confusing compiled error message.
+ //
+ // Before throwing, it is better to verify the frontmatter here, and
+ // let esbuild throw a more specific exception if the code is invalid.
+ // If frontmatter is valid or cannot be parsed, then continue.
+ const scannedFrontmatter = FRONTMATTER_PARSE_REGEXP.exec(source);
+ if (scannedFrontmatter) {
+ try {
+ await esbuild.transform(scannedFrontmatter[1], { loader: 'ts', sourcemap: false, sourcefile: id });
+ } catch (frontmatterErr: any) {
+ // Improve the error by replacing the phrase "unexpected end of file"
+ // with "unexpected end of frontmatter" in the esbuild error message.
+ if (frontmatterErr && frontmatterErr.message) {
+ frontmatterErr.message = frontmatterErr.message.replace('end of file', 'end of frontmatter');
+ }
+ throw frontmatterErr;
+ }
+ }
+
// improve compiler errors
if (err.stack.includes('wasm-function')) {
const search = new URLSearchParams({
diff --git a/packages/astro/test/errors.test.js b/packages/astro/test/errors.test.js
index 43b43df5f..e283a7a45 100644
--- a/packages/astro/test/errors.test.js
+++ b/packages/astro/test/errors.test.js
@@ -1,10 +1,6 @@
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;
@@ -21,90 +17,56 @@ before(async () => {
describe('Error display', () => {
describe('Astro', () => {
- // This test is redundant w/ runtime error since it no longer produces an Astro syntax error
- it.skip('syntax error', async () => {
- if (isMacOS) return;
-
+ it('syntax error in template', async () => {
const res = await fixture.fetch('/astro-syntax-error');
-
- // 500 returned
expect(res.status).to.equal(500);
+ const body = await res.text();
+ console.log(res.body);
+ expect(body).to.include('Unexpected "}"');
+ });
- // error message includes "unrecoverable error"
+ it('syntax error in frontmatter', async () => {
+ const res = await fixture.fetch('/astro-frontmatter-syntax-error');
+ expect(res.status).to.equal(500);
const body = await res.text();
console.log(res.body);
- expect(body).to.include('unrecoverable error');
+ expect(body).to.include('Unexpected end of frontmatter');
});
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
+ // TODO: improve and test stacktrace
});
it('hydration error', async () => {
- if (isMacOS) return;
-
const res = await fixture.fetch('/astro-hydration-error');
-
- // 500 returned
expect(res.status).to.equal(500);
-
- // error message contains error
const body = await res.text();
-
- // error message contains error
expect(body).to.include('Error: invalid hydration directive');
});
it('client:media error', async () => {
- if (isMacOS) return;
-
const res = await fixture.fetch('/astro-client-media-error');
-
- // 500 returned
expect(res.status).to.equal(500);
-
- // error message contains error
const body = await res.text();
-
- // error message contains error
expect(body).to.include('Error: Media query must be provided');
});
});
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');
});
@@ -112,27 +74,15 @@ describe('Error display', () => {
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');
});
@@ -140,27 +90,15 @@ describe('Error display', () => {
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');
});
@@ -168,27 +106,15 @@ describe('Error display', () => {
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');
});
@@ -196,27 +122,15 @@ describe('Error display', () => {
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');
});
@@ -224,28 +138,15 @@ describe('Error display', () => {
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
});
diff --git a/packages/astro/test/fixtures/errors/src/pages/astro-frontmatter-syntax-error.astro b/packages/astro/test/fixtures/errors/src/pages/astro-frontmatter-syntax-error.astro
new file mode 100644
index 000000000..6bb22998c
--- /dev/null
+++ b/packages/astro/test/fixtures/errors/src/pages/astro-frontmatter-syntax-error.astro
@@ -0,0 +1,4 @@
+---
+{
+---
+<h1>Testing bad JS in frontmatter</h1> \ No newline at end of file