209 lines
6.3 KiB
JavaScript
209 lines
6.3 KiB
JavaScript
![]() |
import {
|
||
|
transformWithEsbuild
|
||
|
} from "vite";
|
||
|
import babel from "@babel/core";
|
||
|
import * as colors from "kleur/colors";
|
||
|
import path from "node:path";
|
||
|
import { CONTENT_FLAG, PROPAGATED_ASSET_FLAG } from "../content/index.js";
|
||
|
import { astroEntryPrefix } from "../core/build/plugins/plugin-component-entry.js";
|
||
|
import { error } from "../core/logger/core.js";
|
||
|
import { removeQueryString } from "../core/path.js";
|
||
|
import { detectImportSource } from "./import-source.js";
|
||
|
import tagExportsPlugin from "./tag.js";
|
||
|
const JSX_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx", ".mdx"]);
|
||
|
const IMPORT_STATEMENTS = {
|
||
|
react: "import React from 'react'",
|
||
|
preact: "import { h } from 'preact'",
|
||
|
"solid-js": "import 'solid-js'",
|
||
|
astro: "import 'astro/jsx-runtime'"
|
||
|
};
|
||
|
function getEsbuildLoader(filePath) {
|
||
|
const fileExt = path.extname(filePath);
|
||
|
if (fileExt === ".mdx")
|
||
|
return "jsx";
|
||
|
return fileExt.slice(1);
|
||
|
}
|
||
|
function collectJSXRenderers(renderers) {
|
||
|
const renderersWithJSXSupport = renderers.filter((r) => r.jsxImportSource);
|
||
|
return new Map(
|
||
|
renderersWithJSXSupport.map((r) => [r.jsxImportSource, r])
|
||
|
);
|
||
|
}
|
||
|
async function transformJSX({
|
||
|
code,
|
||
|
mode,
|
||
|
id,
|
||
|
ssr,
|
||
|
renderer,
|
||
|
root
|
||
|
}) {
|
||
|
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
|
||
|
});
|
||
|
if (!result)
|
||
|
return null;
|
||
|
if (renderer.name === "astro:jsx") {
|
||
|
const { astro } = result.metadata;
|
||
|
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
|
||
|
};
|
||
|
}
|
||
|
const SPECIAL_QUERY_REGEX = new RegExp(
|
||
|
`[?&](?:worker|sharedworker|raw|url|${CONTENT_FLAG}|${PROPAGATED_ASSET_FLAG})\\b`
|
||
|
);
|
||
|
function jsx({ settings, logging }) {
|
||
|
let viteConfig;
|
||
|
const jsxRenderers = /* @__PURE__ */ new Map();
|
||
|
const jsxRenderersIntegrationOnly = /* @__PURE__ */ new Map();
|
||
|
let astroJSXRenderer;
|
||
|
let defaultJSXRendererEntry;
|
||
|
return {
|
||
|
name: "astro:jsx",
|
||
|
enforce: "pre",
|
||
|
// run transforms before other plugins
|
||
|
async configResolved(resolvedConfig) {
|
||
|
viteConfig = resolvedConfig;
|
||
|
const possibleRenderers = collectJSXRenderers(settings.renderers);
|
||
|
for (const [importSource, renderer] of possibleRenderers) {
|
||
|
jsxRenderers.set(importSource, renderer);
|
||
|
if (importSource === "astro") {
|
||
|
astroJSXRenderer = renderer;
|
||
|
} else {
|
||
|
jsxRenderersIntegrationOnly.set(importSource, renderer);
|
||
|
}
|
||
|
}
|
||
|
defaultJSXRendererEntry = [...jsxRenderersIntegrationOnly.entries()][0];
|
||
|
},
|
||
|
async transform(code, id, opts) {
|
||
|
const ssr = Boolean(opts == null ? void 0 : opts.ssr);
|
||
|
if (SPECIAL_QUERY_REGEX.test(id) || id.startsWith(astroEntryPrefix)) {
|
||
|
return null;
|
||
|
}
|
||
|
id = removeQueryString(id);
|
||
|
if (!JSX_EXTENSIONS.has(path.extname(id))) {
|
||
|
return null;
|
||
|
}
|
||
|
const { mode } = viteConfig;
|
||
|
if (id.endsWith(".mdx")) {
|
||
|
const { code: jsxCode2 } = await transformWithEsbuild(code, id, {
|
||
|
loader: getEsbuildLoader(id),
|
||
|
jsx: "preserve",
|
||
|
sourcemap: "inline",
|
||
|
tsconfigRaw: {
|
||
|
compilerOptions: {
|
||
|
// Ensure client:only imports are treeshaken
|
||
|
// @ts-expect-error anticipate esbuild 0.18 feature
|
||
|
verbatimModuleSyntax: false,
|
||
|
importsNotUsedAsValues: "remove"
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
return transformJSX({
|
||
|
code: jsxCode2,
|
||
|
id,
|
||
|
renderer: astroJSXRenderer,
|
||
|
mode,
|
||
|
ssr,
|
||
|
root: settings.config.root
|
||
|
});
|
||
|
}
|
||
|
if (defaultJSXRendererEntry && jsxRenderersIntegrationOnly.size === 1) {
|
||
|
const { code: jsxCode2 } = await transformWithEsbuild(code, id, {
|
||
|
loader: getEsbuildLoader(id),
|
||
|
jsx: "preserve",
|
||
|
sourcemap: "inline"
|
||
|
});
|
||
|
return transformJSX({
|
||
|
code: jsxCode2,
|
||
|
id,
|
||
|
renderer: defaultJSXRendererEntry[1],
|
||
|
mode,
|
||
|
ssr,
|
||
|
root: settings.config.root
|
||
|
});
|
||
|
}
|
||
|
const importSource = await detectImportSource(code, jsxRenderers, settings.tsConfig);
|
||
|
if (!importSource && defaultJSXRendererEntry) {
|
||
|
const [defaultRendererName] = defaultJSXRendererEntry;
|
||
|
error(
|
||
|
logging,
|
||
|
"renderer",
|
||
|
`${colors.yellow(id)}
|
||
|
Unable to resolve a renderer that handles this file! With more than one renderer enabled, you should include an import or use a pragma comment.
|
||
|
Add ${colors.cyan(
|
||
|
IMPORT_STATEMENTS[defaultRendererName] || `import '${defaultRendererName}';`
|
||
|
)} or ${colors.cyan(`/** @jsxImportSource: ${defaultRendererName} */`)} to this file.
|
||
|
`
|
||
|
);
|
||
|
return null;
|
||
|
} else if (!importSource) {
|
||
|
error(
|
||
|
logging,
|
||
|
"renderer",
|
||
|
`${colors.yellow(id)}
|
||
|
Unable to find a renderer for JSX. Do you have one configured in your Astro config? See this page to learn how:
|
||
|
https://docs.astro.build/en/core-concepts/framework-components/#installing-integrations
|
||
|
`
|
||
|
);
|
||
|
return null;
|
||
|
}
|
||
|
const selectedJsxRenderer = jsxRenderers.get(importSource);
|
||
|
if (!selectedJsxRenderer) {
|
||
|
error(
|
||
|
logging,
|
||
|
"renderer",
|
||
|
`${colors.yellow(
|
||
|
id
|
||
|
)} No renderer installed for ${importSource}. Try adding \`@astrojs/${importSource}\` to your project.`
|
||
|
);
|
||
|
return null;
|
||
|
}
|
||
|
const { code: jsxCode } = await transformWithEsbuild(code, id, {
|
||
|
loader: getEsbuildLoader(id),
|
||
|
jsx: "preserve",
|
||
|
sourcemap: "inline"
|
||
|
});
|
||
|
return await transformJSX({
|
||
|
code: jsxCode,
|
||
|
id,
|
||
|
renderer: selectedJsxRenderer,
|
||
|
mode,
|
||
|
ssr,
|
||
|
root: settings.config.root
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
export {
|
||
|
jsx as default
|
||
|
};
|