diff --git a/.changeset/cyan-chefs-marry.md b/.changeset/cyan-chefs-marry.md
new file mode 100644
index 000000000..ba2f9aa9c
--- /dev/null
+++ b/.changeset/cyan-chefs-marry.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Update vite-jsx-plugin for jsx export
diff --git a/packages/astro/src/vite-plugin-jsx/tag.ts b/packages/astro/src/vite-plugin-jsx/tag.ts
index 12bb3bcdd..bead224e0 100644
--- a/packages/astro/src/vite-plugin-jsx/tag.ts
+++ b/packages/astro/src/vite-plugin-jsx/tag.ts
@@ -1,6 +1,8 @@
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,
@@ -50,29 +52,71 @@ export default function tagExportsWithRenderer({
}
},
},
- ExportDeclaration(path, state) {
- const node = path.node;
- if (node.exportKind === 'type') return;
- if (node.type === 'ExportAllDeclaration') return;
+ ExportDeclaration: {
+ /**
+ * For default anonymous function export, we need to give them a unique name
+ * @param path
+ * @returns
+ */
+ enter(path) {
+ const node = path.node;
+ if (node.type !== 'ExportDefaultDeclaration') 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]);
+ if (node.declaration?.type === 'ArrowFunctionExpression') {
+ const uidIdentifier = path.scope.generateUidIdentifier('_arrow_function');
+ path.insertBefore(
+ t.variableDeclaration('const', [
+ t.variableDeclarator(uidIdentifier, node.declaration),
+ ])
+ );
+ node.declaration = uidIdentifier;
+ } else if (
+ node.declaration?.type === 'FunctionDeclaration' &&
+ !node.declaration.id?.name
+ ) {
+ const uidIdentifier = path.scope.generateUidIdentifier('_function');
+ node.declaration.id = uidIdentifier;
+ }
+ },
+ exit(path, state) {
+ const node = path.node;
+ if (node.exportKind === 'type') return;
+ if (node.type === 'ExportAllDeclaration') return;
+ const addTag = (id: string) => {
+ const tags = state.get('astro:tags') ?? [];
+ state.set('astro:tags', [...tags, id]);
+ };
+ if (node.type === 'ExportNamedDeclaration' || node.type === 'ExportDefaultDeclaration') {
+ if (t.isIdentifier(node.declaration)) {
+ addTag(node.declaration.name);
+ } else if (t.isFunctionDeclaration(node.declaration) && node.declaration.id?.name) {
+ addTag(node.declaration.id.name);
+ } else if (t.isVariableDeclaration(node.declaration)) {
+ node.declaration.declarations?.forEach((declaration) => {
+ if (
+ t.isArrowFunctionExpression(declaration.init) &&
+ t.isIdentifier(declaration.id)
+ ) {
+ addTag(declaration.id.name);
+ }
+ });
+ } else if (t.isObjectExpression(node.declaration)) {
+ node.declaration.properties?.forEach((property) => {
+ if (t.isProperty(property) && t.isIdentifier(property.key)) {
+ addTag(property.key.name);
+ }
+ });
+ } else if (t.isExportNamedDeclaration(node)) {
+ node.specifiers.forEach((specifier) => {
+ if (t.isExportSpecifier(specifier) && t.isIdentifier(specifier.exported)) {
+ addTag(specifier.local.name);
+ }
+ });
}
}
- } 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/fixtures/react-jsx-export/astro.config.mjs b/packages/astro/test/fixtures/react-jsx-export/astro.config.mjs
new file mode 100644
index 000000000..12e5977e9
--- /dev/null
+++ b/packages/astro/test/fixtures/react-jsx-export/astro.config.mjs
@@ -0,0 +1,8 @@
+import { defineConfig } from 'astro/config';
+import react from '@astrojs/react';
+export default defineConfig({
+ integrations: [
+ react()
+ ]
+
+})
diff --git a/packages/astro/test/fixtures/react-jsx-export/package.json b/packages/astro/test/fixtures/react-jsx-export/package.json
new file mode 100644
index 000000000..1e9c0e303
--- /dev/null
+++ b/packages/astro/test/fixtures/react-jsx-export/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "@test/react-jsx-export",
+ "version": "0.0.0",
+ "private": true,
+ "devDependencies": {
+ "astro": "workspace:*",
+ "@astrojs/react": "workspace:*"
+ },
+ "dependencies": {
+ "react": "^18.1.0",
+ "react-dom": "^18.1.0"
+ }
+}
diff --git a/packages/astro/test/fixtures/react-jsx-export/src/components/DeclarationExportTest.jsx b/packages/astro/test/fixtures/react-jsx-export/src/components/DeclarationExportTest.jsx
new file mode 100644
index 000000000..0986da235
--- /dev/null
+++ b/packages/astro/test/fixtures/react-jsx-export/src/components/DeclarationExportTest.jsx
@@ -0,0 +1,16 @@
+import { useState } from "react"
+
+export const ConstDeclarationExport = () => {
+ const [example] = useState('Example')
+ return
{example}
+}
+
+export let LetDeclarationExport = () => {
+ const [example] = useState('Example')
+ return {example}
+}
+
+export function FunctionDeclarationExport() {
+ const [example] = useState('Example')
+ return {example}
+}
\ No newline at end of file
diff --git a/packages/astro/test/fixtures/react-jsx-export/src/components/ListExportTest.jsx b/packages/astro/test/fixtures/react-jsx-export/src/components/ListExportTest.jsx
new file mode 100644
index 000000000..e695821d1
--- /dev/null
+++ b/packages/astro/test/fixtures/react-jsx-export/src/components/ListExportTest.jsx
@@ -0,0 +1,26 @@
+import { useState } from "react"
+
+const ListExport = () => {
+ const [example] = useState('Example')
+ return {example}
+}
+
+export {ListExport}
+
+const OriginListExport = () => {
+ const [example] = useState('Example')
+ return {example}
+}
+
+export {
+ OriginListExport as RenamedListExport
+}
+
+const ListAsDefaultExport = () => {
+ const [example] = useState('Example')
+ return {example}
+}
+
+export {
+ ListAsDefaultExport as default
+}
\ No newline at end of file
diff --git a/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/AnonymousArrowDefaultExport.jsx b/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/AnonymousArrowDefaultExport.jsx
new file mode 100644
index 000000000..7fe5ad1e5
--- /dev/null
+++ b/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/AnonymousArrowDefaultExport.jsx
@@ -0,0 +1,6 @@
+import { useState } from "react"
+
+export default () => {
+ const [example] = useState('Example')
+ return {example}
+}
\ No newline at end of file
diff --git a/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/AnonymousFunctionDefaultExport.jsx b/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/AnonymousFunctionDefaultExport.jsx
new file mode 100644
index 000000000..eed9c6579
--- /dev/null
+++ b/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/AnonymousFunctionDefaultExport.jsx
@@ -0,0 +1,6 @@
+import { useState } from "react"
+
+export default function() {
+ const [example] = useState('Example')
+ return {example}
+}
\ No newline at end of file
diff --git a/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/NamedArrowDefaultExport.jsx b/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/NamedArrowDefaultExport.jsx
new file mode 100644
index 000000000..c859808bc
--- /dev/null
+++ b/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/NamedArrowDefaultExport.jsx
@@ -0,0 +1,8 @@
+import { useState } from "react";
+
+const NamedArrowDefaultExport = () => {
+ const [example] = useState('Example')
+ return {example}
+}
+
+export default NamedArrowDefaultExport;
\ No newline at end of file
diff --git a/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/NamedFunctionDefaultExport.jsx b/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/NamedFunctionDefaultExport.jsx
new file mode 100644
index 000000000..8b3897409
--- /dev/null
+++ b/packages/astro/test/fixtures/react-jsx-export/src/components/defaultExport/NamedFunctionDefaultExport.jsx
@@ -0,0 +1,6 @@
+import { useState } from "react"
+
+export default function NamedFunctionDefaultExport() {
+ const [example] = useState('Example')
+ return {example}
+}
\ No newline at end of file
diff --git a/packages/astro/test/fixtures/react-jsx-export/src/pages/index.astro b/packages/astro/test/fixtures/react-jsx-export/src/pages/index.astro
new file mode 100644
index 000000000..92a3833a4
--- /dev/null
+++ b/packages/astro/test/fixtures/react-jsx-export/src/pages/index.astro
@@ -0,0 +1,23 @@
+---
+import ListAsDefaultExport, {ListExport, RenamedListExport} from '../components/ListExportTest'
+import {ConstDeclarationExport, LetDeclarationExport, FunctionDeclarationExport} from '../components/DeclarationExportTest'
+import AnonymousArrowDefaultExport from '../components/defaultExport/AnonymousArrowDefaultExport'
+import AnonymousFunctionDefaultExport from '../components/defaultExport/AnonymousFunctionDefaultExport'
+import NamedArrowDefaultExport from '../components/defaultExport/NamedArrowDefaultExport'
+import NamedFunctionDefaultExport from '../components/defaultExport/NamedFunctionDefaultExport'
+---
+
+React JSX Export Test
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/astro/test/react-jsx-export.test.js b/packages/astro/test/react-jsx-export.test.js
new file mode 100644
index 000000000..4a5b951b2
--- /dev/null
+++ b/packages/astro/test/react-jsx-export.test.js
@@ -0,0 +1,51 @@
+import { expect } from 'chai';
+import * as cheerio from 'cheerio';
+import { loadFixture } from './test-utils.js';
+
+describe('react-jsx-export', () => {
+ let fixture;
+ let logs = [];
+
+ const ids = [
+ 'anonymous_arrow_default_export',
+ 'anonymous_function_default_export',
+ 'named_arrow_default_export',
+ 'named_Function_default_export',
+ 'export_const_declaration',
+ 'export_let_declaration',
+ 'export_function_declaration',
+ 'default_list_export',
+ 'renamed_list_export',
+ 'list_as_default_export',
+ ];
+
+ const reactInvalidHookWarning =
+ 'Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons';
+ before(async () => {
+ const logging = {
+ dest: {
+ write(chunk) {
+ logs.push(chunk);
+ },
+ },
+ level: 'warn',
+ };
+ fixture = await loadFixture({
+ root: './fixtures/react-jsx-export/',
+ });
+ await fixture.build({ logging });
+ });
+
+ it('Can load all JSX components', async () => {
+ const html = await fixture.readFile('/index.html');
+ const $ = cheerio.load(html);
+
+ ids.forEach((id) => {
+ expect($(`#${id}`).text()).to.equal('Example');
+ });
+ });
+
+ it('Can not output React Invalid Hook warning', async () => {
+ expect(logs.every((log) => log.message.indexOf(reactInvalidHookWarning) === -1)).to.be.true;
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0eb9602f5..6e92f7572 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1799,6 +1799,19 @@ importers:
react-dom: 18.2.0_react@18.2.0
vue: 3.2.39
+ packages/astro/test/fixtures/react-jsx-export:
+ specifiers:
+ '@astrojs/react': workspace:*
+ astro: workspace:*
+ react: ^18.1.0
+ react-dom: ^18.1.0
+ dependencies:
+ react: 18.2.0
+ react-dom: 18.2.0_react@18.2.0
+ devDependencies:
+ '@astrojs/react': link:../../../../integrations/react
+ astro: link:../../..
+
packages/astro/test/fixtures/reexport-astro-containing-client-component:
specifiers:
'@astrojs/preact': 'workspace:'