diff --git a/.changeset/slow-frogs-hope.md b/.changeset/slow-frogs-hope.md new file mode 100644 index 000000000000..b8214a57fc81 --- /dev/null +++ b/.changeset/slow-frogs-hope.md @@ -0,0 +1,6 @@ +--- +'astro': patch +'@astrojs/mdx': patch +--- + +Allow automatic JSX runtime with MDX diff --git a/packages/astro/src/jsx/babel.ts b/packages/astro/src/jsx/babel.ts index 8850dadc1ac3..ce1587a0bfad 100644 --- a/packages/astro/src/jsx/babel.ts +++ b/packages/astro/src/jsx/babel.ts @@ -136,6 +136,7 @@ export default function astroJSX(): PluginObj { visitor: { Program: { enter(path, state) { + const { file } = state; if (!(state.file.metadata as PluginMetadata).astro) { (state.file.metadata as PluginMetadata).astro = { clientOnlyComponents: [], @@ -143,6 +144,13 @@ export default function astroJSX(): PluginObj { scripts: [], }; } + if(!file.ast.comments) { + file.ast.comments = []; + } + file.ast.comments?.push({ + type: 'CommentBlock', + value: '* @jsxImportSource astro ' + }); path.node.body.splice( 0, 0, diff --git a/packages/astro/src/jsx/renderer.ts b/packages/astro/src/jsx/renderer.ts index 3aee8520fc64..d40c5200e0b6 100644 --- a/packages/astro/src/jsx/renderer.ts +++ b/packages/astro/src/jsx/renderer.ts @@ -1,7 +1,6 @@ const renderer = { name: 'astro:jsx', serverEntrypoint: 'astro/jsx/server.js', - jsxImportSource: 'astro', jsxTransformOptions: async () => { const { default: { default: jsx }, @@ -11,7 +10,7 @@ const renderer = { return { plugins: [ astroJSX(), - jsx({}, { throwIfNamespace: false, runtime: 'automatic', importSource: 'astro' }), + jsx({}, { throwIfNamespace: false, runtime: 'automatic' }), ], }; }, diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts index 56005413e326..a217fcbb97b6 100644 --- a/packages/astro/src/vite-plugin-jsx/index.ts +++ b/packages/astro/src/vite-plugin-jsx/index.ts @@ -14,6 +14,7 @@ import { parseNpmName } from '../core/util.js'; import tagExportsPlugin from './tag.js'; const JSX_RENDERER_CACHE = new WeakMap>(); +const JSX_RENDERER_WITH_AUTO_CACHE = new WeakMap(); const JSX_EXTENSIONS = new Set(['.jsx', '.tsx', '.mdx']); const IMPORT_STATEMENTS: Record = { react: "import React from 'react'", @@ -33,7 +34,7 @@ function getEsbuildLoader(fileExt: string): string { } function collectJSXRenderers(renderers: AstroRenderer[]): Map { - const renderersWithJSXSupport = renderers.filter((r) => r.jsxImportSource); + const renderersWithJSXSupport = renderers.filter((r) => r.jsxImportSource || r.jsxTransformOptions); return new Map( renderersWithJSXSupport.map((r) => [r.jsxImportSource, r] as [string, AstroRenderer]) ); @@ -69,6 +70,7 @@ async function transformJSX({ babelrc: false, inputSourceMap: options.inputSourceMap, }); + // TODO: Be more strict about bad return values here. // Should we throw an error instead? Should we never return `{code: ""}`? if (!result) return null; @@ -118,6 +120,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin const { mode } = viteConfig; let jsxRenderers = JSX_RENDERER_CACHE.get(config); + let jsxAutoRenderers = JSX_RENDERER_WITH_AUTO_CACHE.get(config); // load renderers (on first run only) if (!jsxRenderers) { @@ -136,10 +139,19 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin } JSX_RENDERER_CACHE.set(config, jsxRenderers); } + if(!jsxAutoRenderers) { + jsxAutoRenderers = []; + for(const [,renderer] of jsxRenderers) { + if(renderer.jsxImportSource) { + jsxAutoRenderers.push(renderer); + } + } + } // Attempt: Single JSX renderer // If we only have one renderer, we can skip a bunch of work! - if (jsxRenderers.size === 1) { + if (jsxAutoRenderers.length === 1 || jsxRenderers.size === 1) { + const renderer = jsxAutoRenderers.length ? jsxAutoRenderers[0] : Array.from(jsxRenderers.values())[0]; // downlevel any non-standard syntax, but preserve JSX const { code: jsxCode } = await esbuild.transform(code, { loader: getEsbuildLoader(path.extname(id)) as esbuild.Loader, @@ -150,7 +162,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin return transformJSX({ code: jsxCode, id, - renderer: [...jsxRenderers.values()][0], + renderer, mode, ssr, }); diff --git a/packages/astro/src/vite-plugin-jsx/tag.ts b/packages/astro/src/vite-plugin-jsx/tag.ts index 12bb3bcdd5b7..5a0040d20eb9 100644 --- a/packages/astro/src/vite-plugin-jsx/tag.ts +++ b/packages/astro/src/vite-plugin-jsx/tag.ts @@ -20,7 +20,7 @@ export default function tagExportsWithRenderer({ // Inject `import { __astro_tag_component__ } from 'astro/server/index.js'` enter(path) { path.node.body.splice( - 0, + 1, 0, t.importDeclaration( [ diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react/astro.config.mjs b/packages/integrations/mdx/test/fixtures/mdx-plus-react/astro.config.mjs new file mode 100644 index 000000000000..4671227d3ea1 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react/astro.config.mjs @@ -0,0 +1,6 @@ +import mdx from '@astrojs/mdx'; +import react from '@astrojs/react'; + +export default { + integrations: [react(), mdx()] +} diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react/package.json b/packages/integrations/mdx/test/fixtures/mdx-plus-react/package.json new file mode 100644 index 000000000000..982f4c685486 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/mdx-plus-react", + "dependencies": { + "astro": "workspace:*", + "@astrojs/mdx": "workspace:*", + "@astrojs/react": "workspace:*" + } +} diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/components/Component.jsx b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/components/Component.jsx new file mode 100644 index 000000000000..53f5dad3f108 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/components/Component.jsx @@ -0,0 +1,5 @@ +const Component = () => { + return

Hello world

; +}; + +export default Component; diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/index.astro b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/index.astro new file mode 100644 index 000000000000..2486e78342be --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react/src/pages/index.astro @@ -0,0 +1,11 @@ +--- +import Component from "../components/Component.jsx"; +--- + + + Testing + + + + + diff --git a/packages/integrations/mdx/test/mdx-plus-react.test.js b/packages/integrations/mdx/test/mdx-plus-react.test.js new file mode 100644 index 000000000000..49c25d558dde --- /dev/null +++ b/packages/integrations/mdx/test/mdx-plus-react.test.js @@ -0,0 +1,25 @@ +import mdx from '@astrojs/mdx'; + +import { expect } from 'chai'; +import { parseHTML } from 'linkedom'; +import { loadFixture } from '../../../astro/test/test-utils.js'; + +describe('MDX and React', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/mdx-plus-react/', import.meta.url), + }); + await fixture.build(); + }); + + it('can be used in the same project', async () => { + const html = await fixture.readFile('/index.html'); + const { document } = parseHTML(html); + + const p = document.querySelector('p'); + + expect(p.textContent).to.equal('Hello world'); + }); +}); diff --git a/packages/integrations/react/src/index.ts b/packages/integrations/react/src/index.ts index a283938c343f..4ae6ea77fc7b 100644 --- a/packages/integrations/react/src/index.ts +++ b/packages/integrations/react/src/index.ts @@ -12,10 +12,9 @@ function getRenderer() { : '@astrojs/react/server-v17.js', jsxImportSource: 'react', jsxTransformOptions: async () => { - const { - default: { default: jsx }, - // @ts-expect-error types not found - } = await import('@babel/plugin-transform-react-jsx'); + // @ts-expect-error types not found + const babelPluginTransformReactJsxModule = await import('@babel/plugin-transform-react-jsx'); + const jsx = babelPluginTransformReactJsxModule?.default?.default ?? babelPluginTransformReactJsxModule?.default; return { plugins: [ jsx( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20db5c7de63e..bf0f3729aa5b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2188,6 +2188,16 @@ importers: '@astrojs/mdx': link:../../.. astro: link:../../../../../astro + packages/integrations/mdx/test/fixtures/mdx-plus-react: + specifiers: + '@astrojs/mdx': workspace:* + '@astrojs/react': workspace:* + astro: workspace:* + dependencies: + '@astrojs/mdx': link:../../.. + '@astrojs/react': link:../../../../react + astro: link:../../../../../astro + packages/integrations/netlify: specifiers: '@astrojs/webapi': ^0.12.0