mirror of
https://github.com/withastro/astro.git
synced 2025-01-27 22:19:04 -05:00
Refactor MDX transformJSX handling (#10688)
This commit is contained in:
parent
4ea042c388
commit
799f6f3f29
10 changed files with 199 additions and 217 deletions
5
.changeset/four-pants-juggle.md
Normal file
5
.changeset/four-pants-juggle.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"astro": patch
|
||||
---
|
||||
|
||||
Marks renderer `jsxImportSource` and `jsxTransformOptions` options as deprecated as they are no longer used since Astro 3.0
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -137,7 +137,7 @@ export async function createVite(
|
|||
envVitePlugin({ settings }),
|
||||
markdownVitePlugin({ settings, logger }),
|
||||
htmlVitePlugin(),
|
||||
mdxVitePlugin({ settings, logger }),
|
||||
mdxVitePlugin(),
|
||||
astroPostprocessVitePlugin(),
|
||||
astroIntegrationsContainerPlugin({ settings, logger }),
|
||||
astroScriptsPageSSRPlugin({ settings }),
|
||||
|
|
|
@ -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;
|
||||
|
|
14
packages/astro/src/jsx/transform-options.ts
Normal file
14
packages/astro/src/jsx/transform-options.ts
Normal file
|
@ -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' }),
|
||||
],
|
||||
};
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,13 +12,7 @@ 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 {
|
||||
export const tagExportsPlugin: PluginObj = {
|
||||
visitor: {
|
||||
Program: {
|
||||
// Inject `import { __astro_tag_component__ } from 'astro/runtime/server/index.js'`
|
||||
|
@ -61,18 +58,13 @@ export default async function tagExportsWithRenderer({
|
|||
const node = path.node;
|
||||
if (!t.isExportDefaultDeclaration(node)) return;
|
||||
|
||||
if (
|
||||
t.isArrowFunctionExpression(node.declaration) ||
|
||||
t.isCallExpression(node.declaration)
|
||||
) {
|
||||
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),
|
||||
])
|
||||
t.variableDeclaration('const', [t.variableDeclarator(uidIdentifier, node.declaration)])
|
||||
);
|
||||
node.declaration = uidIdentifier;
|
||||
} else if (t.isFunctionDeclaration(node.declaration) && !node.declaration.id?.name) {
|
||||
|
@ -95,10 +87,7 @@ export default async function tagExportsWithRenderer({
|
|||
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)
|
||||
) {
|
||||
if (t.isArrowFunctionExpression(declaration.init) && t.isIdentifier(declaration.id)) {
|
||||
addTag(declaration.id.name);
|
||||
}
|
||||
});
|
||||
|
@ -120,4 +109,3 @@ export default async function tagExportsWithRenderer({
|
|||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
69
packages/astro/src/vite-plugin-mdx/transform-jsx.ts
Normal file
69
packages/astro/src/vite-plugin-mdx/transform-jsx.ts
Normal file
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue