summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/four-pants-juggle.md5
-rw-r--r--packages/astro/src/@types/astro.ts4
-rw-r--r--packages/astro/src/core/create-vite.ts2
-rw-r--r--packages/astro/src/jsx/renderer.ts18
-rw-r--r--packages/astro/src/jsx/transform-options.ts14
-rw-r--r--packages/astro/src/vite-plugin-mdx/README.md4
-rw-r--r--packages/astro/src/vite-plugin-mdx/index.ts97
-rw-r--r--packages/astro/src/vite-plugin-mdx/tag.ts200
-rw-r--r--packages/astro/src/vite-plugin-mdx/transform-jsx.ts69
-rw-r--r--packages/astro/test/units/render/jsx.test.js1
10 files changed, 198 insertions, 216 deletions
diff --git a/.changeset/four-pants-juggle.md b/.changeset/four-pants-juggle.md
new file mode 100644
index 000000000..e10cb5019
--- /dev/null
+++ b/.changeset/four-pants-juggle.md
@@ -0,0 +1,5 @@
+---
+"astro": patch
+---
+
+Marks renderer `jsxImportSource` and `jsxTransformOptions` options as deprecated as they are no longer used since Astro 3.0
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 010815695..e39689a25 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -2577,9 +2577,9 @@ export interface AstroRenderer {
clientEntrypoint?: string;
/** Import entrypoint for the server/build/ssr renderer. */
serverEntrypoint: string;
- /** JSX identifier (e.g. 'react' or 'solid-js') */
+ /** @deprecated Vite plugins should transform the JSX instead */
jsxImportSource?: string;
- /** Babel transform options */
+ /** @deprecated Vite plugins should transform the JSX instead */
jsxTransformOptions?: JSXTransformFn;
}
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index 56092bd32..cc32fb6f1 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -137,7 +137,7 @@ export async function createVite(
envVitePlugin({ settings }),
markdownVitePlugin({ settings, logger }),
htmlVitePlugin(),
- mdxVitePlugin({ settings, logger }),
+ mdxVitePlugin(),
astroPostprocessVitePlugin(),
astroIntegrationsContainerPlugin({ settings, logger }),
astroScriptsPageSSRPlugin({ settings }),
diff --git a/packages/astro/src/jsx/renderer.ts b/packages/astro/src/jsx/renderer.ts
index 39d7f5adb..413257faa 100644
--- a/packages/astro/src/jsx/renderer.ts
+++ b/packages/astro/src/jsx/renderer.ts
@@ -1,19 +1,11 @@
-const renderer = {
+import type { AstroRenderer } from '../@types/astro.js';
+import { jsxTransformOptions } from './transform-options.js';
+
+const renderer: AstroRenderer = {
name: 'astro:jsx',
serverEntrypoint: 'astro/jsx/server.js',
jsxImportSource: 'astro',
- jsxTransformOptions: async () => {
- // @ts-expect-error types not found
- const plugin = await import('@babel/plugin-transform-react-jsx');
- const jsx = plugin.default?.default ?? plugin.default;
- const { default: astroJSX } = await import('./babel.js');
- return {
- plugins: [
- astroJSX(),
- jsx({}, { throwIfNamespace: false, runtime: 'automatic', importSource: 'astro' }),
- ],
- };
- },
+ jsxTransformOptions,
};
export default renderer;
diff --git a/packages/astro/src/jsx/transform-options.ts b/packages/astro/src/jsx/transform-options.ts
new file mode 100644
index 000000000..4b51d85b8
--- /dev/null
+++ b/packages/astro/src/jsx/transform-options.ts
@@ -0,0 +1,14 @@
+import type { JSXTransformConfig } from '../@types/astro.js';
+
+export async function jsxTransformOptions(): Promise<JSXTransformConfig> {
+ // @ts-expect-error types not found
+ const plugin = await import('@babel/plugin-transform-react-jsx');
+ const jsx = plugin.default?.default ?? plugin.default;
+ const { default: astroJSX } = await import('./babel.js');
+ return {
+ plugins: [
+ astroJSX(),
+ jsx({}, { throwIfNamespace: false, runtime: 'automatic', importSource: 'astro' }),
+ ],
+ };
+}
diff --git a/packages/astro/src/vite-plugin-mdx/README.md b/packages/astro/src/vite-plugin-mdx/README.md
index 554651869..fc962ad4e 100644
--- a/packages/astro/src/vite-plugin-mdx/README.md
+++ b/packages/astro/src/vite-plugin-mdx/README.md
@@ -1,3 +1,3 @@
-# vite-plugin-jsx
+# vite-plugin-mdx
-Modifies Vite’s built-in JSX behavior to allow for React, Preact, and Solid.js to coexist and all use `.jsx` and `.tsx` extensions.
+Handles transforming MDX via the `astro:jsx` renderer.
diff --git a/packages/astro/src/vite-plugin-mdx/index.ts b/packages/astro/src/vite-plugin-mdx/index.ts
index 94fef2783..7e86aed28 100644
--- a/packages/astro/src/vite-plugin-mdx/index.ts
+++ b/packages/astro/src/vite-plugin-mdx/index.ts
@@ -1,99 +1,19 @@
-import type { TransformResult } from 'rollup';
-import { type Plugin, type ResolvedConfig, transformWithEsbuild } from 'vite';
-import type { AstroRenderer, AstroSettings } from '../@types/astro.js';
-import type { Logger } from '../core/logger/core.js';
-import type { PluginMetadata } from '../vite-plugin-astro/types.js';
-
-import babel from '@babel/core';
+import { type Plugin, transformWithEsbuild } from 'vite';
import { CONTENT_FLAG, PROPAGATED_ASSET_FLAG } from '../content/index.js';
import { astroEntryPrefix } from '../core/build/plugins/plugin-component-entry.js';
import { removeQueryString } from '../core/path.js';
-import tagExportsPlugin from './tag.js';
-
-interface TransformJSXOptions {
- code: string;
- id: string;
- mode: string;
- renderer: AstroRenderer;
- ssr: boolean;
- root: URL;
-}
-
-async function transformJSX({
- code,
- mode,
- id,
- ssr,
- renderer,
- root,
-}: TransformJSXOptions): Promise<TransformResult> {
- const { jsxTransformOptions } = renderer;
- const options = await jsxTransformOptions!({ mode, ssr });
- const plugins = [...(options.plugins || [])];
- if (ssr) {
- plugins.push(await tagExportsPlugin({ rendererName: renderer.name, root }));
- }
- const result = await babel.transformAsync(code, {
- presets: options.presets,
- plugins,
- cwd: process.cwd(),
- filename: id,
- ast: false,
- compact: false,
- sourceMaps: true,
- configFile: false,
- 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;
-
- if (renderer.name === 'astro:jsx') {
- const { astro } = result.metadata as unknown as PluginMetadata;
- return {
- code: result.code || '',
- map: result.map,
- meta: {
- astro,
- vite: {
- // Setting this vite metadata to `ts` causes Vite to resolve .js
- // extensions to .ts files.
- lang: 'ts',
- },
- },
- };
- }
-
- return {
- code: result.code || '',
- map: result.map,
- };
-}
-
-interface AstroPluginJSXOptions {
- settings: AstroSettings;
- logger: Logger;
-}
+import { transformJSX } from './transform-jsx.js';
// Format inspired by https://github.com/vitejs/vite/blob/main/packages/vite/src/node/constants.ts#L54
const SPECIAL_QUERY_REGEX = new RegExp(
`[?&](?:worker|sharedworker|raw|url|${CONTENT_FLAG}|${PROPAGATED_ASSET_FLAG})\\b`
);
-/** Use Astro config to allow for alternate or multiple JSX renderers (by default Vite will assume React) */
-export default function mdxVitePlugin({ settings }: AstroPluginJSXOptions): Plugin {
- let viteConfig: ResolvedConfig;
- // A reference to Astro's internal JSX renderer.
- let astroJSXRenderer: AstroRenderer;
-
+// TODO: Move this Vite plugin into `@astrojs/mdx` in Astro 5
+export default function mdxVitePlugin(): Plugin {
return {
name: 'astro:jsx',
enforce: 'pre', // run transforms before other plugins
- async configResolved(resolvedConfig) {
- viteConfig = resolvedConfig;
- astroJSXRenderer = settings.renderers.find((r) => r.jsxImportSource === 'astro')!;
- },
async transform(code, id, opts) {
// Skip special queries and astro entries. We skip astro entries here as we know it doesn't contain
// JSX code, and also because we can't detect the import source to apply JSX transforms.
@@ -117,14 +37,7 @@ export default function mdxVitePlugin({ settings }: AstroPluginJSXOptions): Plug
},
},
});
- return transformJSX({
- code: jsxCode,
- id,
- renderer: astroJSXRenderer,
- mode: viteConfig.mode,
- ssr: Boolean(opts?.ssr),
- root: settings.config.root,
- });
+ return await transformJSX(jsxCode, id, opts?.ssr);
},
};
}
diff --git a/packages/astro/src/vite-plugin-mdx/tag.ts b/packages/astro/src/vite-plugin-mdx/tag.ts
index b7ae1f2c4..3b774a0a2 100644
--- a/packages/astro/src/vite-plugin-mdx/tag.ts
+++ b/packages/astro/src/vite-plugin-mdx/tag.ts
@@ -1,5 +1,8 @@
import type { PluginObj } from '@babel/core';
import * as t from '@babel/types';
+import astroJsxRenderer from '../jsx/renderer.js';
+
+const rendererName = astroJsxRenderer.name;
/**
* This plugin handles every file that runs through our JSX plugin.
@@ -9,115 +12,100 @@ import * as t from '@babel/types';
* 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 async function tagExportsWithRenderer({
- rendererName,
-}: {
- rendererName: string;
- root: URL;
-}): Promise<PluginObj> {
- return {
- visitor: {
- Program: {
- // Inject `import { __astro_tag_component__ } from 'astro/runtime/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/runtime/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),
- ])
- )
- );
- }
- }
- },
+export const tagExportsPlugin: PluginObj = {
+ visitor: {
+ Program: {
+ // Inject `import { __astro_tag_component__ } from 'astro/runtime/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/runtime/server/index.js')
+ )
+ );
},
- ExportDeclaration: {
- /**
- * For default anonymous function export, we need to give them a unique name
- * @param path
- * @returns
- */
- enter(path) {
- const node = path.node;
- if (!t.isExportDefaultDeclaration(node)) return;
-
- if (
- t.isArrowFunctionExpression(node.declaration) ||
- t.isCallExpression(node.declaration)
- ) {
- const varName = t.isArrowFunctionExpression(node.declaration)
- ? '_arrow_function'
- : '_hoc_function';
- const uidIdentifier = path.scope.generateUidIdentifier(varName);
- path.insertBefore(
- t.variableDeclaration('const', [
- t.variableDeclarator(uidIdentifier, node.declaration),
- ])
+ // 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),
+ ])
+ )
);
- node.declaration = uidIdentifier;
- } else if (t.isFunctionDeclaration(node.declaration) && !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 (t.isExportAllDeclaration(node)) return;
- const addTag = (id: string) => {
- const tags = state.get('astro:tags') ?? [];
- state.set('astro:tags', [...tags, id]);
- };
- if (t.isExportNamedDeclaration(node) || t.isExportDefaultDeclaration(node)) {
- 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.source) {
- node.specifiers.forEach((specifier) => {
- if (t.isExportSpecifier(specifier) && t.isIdentifier(specifier.exported)) {
- addTag(specifier.local.name);
- }
- });
- }
+ }
+ },
+ },
+ ExportDeclaration: {
+ /**
+ * For default anonymous function export, we need to give them a unique name
+ * @param path
+ * @returns
+ */
+ enter(path) {
+ const node = path.node;
+ if (!t.isExportDefaultDeclaration(node)) return;
+
+ if (t.isArrowFunctionExpression(node.declaration) || t.isCallExpression(node.declaration)) {
+ const varName = t.isArrowFunctionExpression(node.declaration)
+ ? '_arrow_function'
+ : '_hoc_function';
+ const uidIdentifier = path.scope.generateUidIdentifier(varName);
+ path.insertBefore(
+ t.variableDeclaration('const', [t.variableDeclarator(uidIdentifier, node.declaration)])
+ );
+ node.declaration = uidIdentifier;
+ } else if (t.isFunctionDeclaration(node.declaration) && !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 (t.isExportAllDeclaration(node)) return;
+ const addTag = (id: string) => {
+ const tags = state.get('astro:tags') ?? [];
+ state.set('astro:tags', [...tags, id]);
+ };
+ if (t.isExportNamedDeclaration(node) || t.isExportDefaultDeclaration(node)) {
+ 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.source) {
+ node.specifiers.forEach((specifier) => {
+ if (t.isExportSpecifier(specifier) && t.isIdentifier(specifier.exported)) {
+ addTag(specifier.local.name);
+ }
+ });
}
- },
+ }
},
},
- };
-}
+ },
+};
diff --git a/packages/astro/src/vite-plugin-mdx/transform-jsx.ts b/packages/astro/src/vite-plugin-mdx/transform-jsx.ts
new file mode 100644
index 000000000..07eb87d04
--- /dev/null
+++ b/packages/astro/src/vite-plugin-mdx/transform-jsx.ts
@@ -0,0 +1,69 @@
+import babel from '@babel/core';
+import type { TransformResult } from 'rollup';
+import type { JSXTransformConfig } from '../@types/astro.js';
+import { jsxTransformOptions } from '../jsx/transform-options.js';
+import type { PluginMetadata } from '../vite-plugin-astro/types.js';
+import { tagExportsPlugin } from './tag.js';
+
+export async function transformJSX(
+ code: string,
+ id: string,
+ ssr?: boolean
+): Promise<TransformResult> {
+ const options = await getJsxTransformOptions();
+ const plugins = ssr ? [...(options.plugins ?? []), tagExportsPlugin] : options.plugins;
+
+ const result = await babel.transformAsync(code, {
+ presets: options.presets,
+ plugins,
+ cwd: process.cwd(),
+ filename: id,
+ ast: false,
+ compact: false,
+ sourceMaps: true,
+ configFile: false,
+ babelrc: false,
+ browserslistConfigFile: 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;
+
+ const { astro } = result.metadata as unknown as PluginMetadata;
+ return {
+ code: result.code || '',
+ map: result.map,
+ meta: {
+ astro,
+ vite: {
+ // Setting this vite metadata to `ts` causes Vite to resolve .js
+ // extensions to .ts files.
+ lang: 'ts',
+ },
+ },
+ };
+}
+
+let cachedJsxTransformOptions: Promise<JSXTransformConfig> | JSXTransformConfig | undefined;
+
+/**
+ * Get the `jsxTransformOptions` with caching
+ */
+async function getJsxTransformOptions(): Promise<JSXTransformConfig> {
+ if (cachedJsxTransformOptions) {
+ return cachedJsxTransformOptions;
+ }
+
+ const options = jsxTransformOptions();
+
+ // Cache the promise
+ cachedJsxTransformOptions = options;
+ // After the promise is resolved, cache the final resolved options
+ options.then((resolvedOptions) => {
+ cachedJsxTransformOptions = resolvedOptions;
+ });
+
+ return options;
+}
diff --git a/packages/astro/test/units/render/jsx.test.js b/packages/astro/test/units/render/jsx.test.js
index 0f91ccfc9..3e1b01b23 100644
--- a/packages/astro/test/units/render/jsx.test.js
+++ b/packages/astro/test/units/render/jsx.test.js
@@ -15,6 +15,7 @@ import { createBasicPipeline } from '../test-utils.js';
const createAstroModule = (AstroComponent) => ({ default: AstroComponent });
const loadJSXRenderer = () => loadRenderer(jsxRenderer, { import: (s) => import(s) });
+// NOTE: This test may be testing an outdated JSX setup
describe('core/render', () => {
describe('Astro JSX components', () => {
let pipeline;