summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Bjorn Lu <bjornlu.dev@gmail.com> 2022-12-01 00:01:37 +0800
committerGravatar GitHub <noreply@github.com> 2022-11-30 11:01:37 -0500
commit1a3923da780288e6435fa79ee8fb61e420af344c (patch)
tree54419f4605d8b1ff593405bf79271f58ae52ae49
parentca01a71eb0937eae3ddc34d48396df8161e830db (diff)
downloadastro-1a3923da780288e6435fa79ee8fb61e420af344c.tar.gz
astro-1a3923da780288e6435fa79ee8fb61e420af344c.tar.zst
astro-1a3923da780288e6435fa79ee8fb61e420af344c.zip
Optimize JSX import source detection (#5498)
* Optimize JSX import source detection * Skip type import check
-rw-r--r--.changeset/wicked-dolphins-design.md5
-rw-r--r--packages/astro/src/vite-plugin-jsx/import-source.ts59
-rw-r--r--packages/astro/src/vite-plugin-jsx/index.ts70
3 files changed, 66 insertions, 68 deletions
diff --git a/.changeset/wicked-dolphins-design.md b/.changeset/wicked-dolphins-design.md
new file mode 100644
index 000000000..96236ebb4
--- /dev/null
+++ b/.changeset/wicked-dolphins-design.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Optimize JSX import source detection
diff --git a/packages/astro/src/vite-plugin-jsx/import-source.ts b/packages/astro/src/vite-plugin-jsx/import-source.ts
new file mode 100644
index 000000000..f21d2ba02
--- /dev/null
+++ b/packages/astro/src/vite-plugin-jsx/import-source.ts
@@ -0,0 +1,59 @@
+import { TsConfigJson } from 'tsconfig-resolver';
+import { AstroRenderer } from '../@types/astro';
+import { parseNpmName } from '../core/util.js';
+
+export async function detectImportSource(
+ code: string,
+ jsxRenderers: Map<string, AstroRenderer>,
+ tsConfig?: TsConfigJson
+): Promise<string | undefined> {
+ let importSource = detectImportSourceFromComments(code);
+ if (!importSource && /import/.test(code)) {
+ importSource = await detectImportSourceFromImports(code, jsxRenderers);
+ }
+ if (!importSource && tsConfig) {
+ importSource = tsConfig.compilerOptions?.jsxImportSource;
+ }
+ return importSource;
+}
+
+// Matches import statements and dynamic imports. Captures import specifiers only.
+// Adapted from: https://github.com/vitejs/vite/blob/97f8b4df3c9eb817ab2669e5c10b700802eec900/packages/vite/src/node/optimizer/scan.ts#L47-L48
+const importsRE =
+ /(?<!\/\/.*)(?<=^|;|\*\/)\s*(?:import(?!\s+type)(?:[\w*{}\n\r\t, ]+from)?\s*("[^"]+"|'[^']+')\s*(?=$|;|\/\/|\/\*)|import\s*\(\s*("[^"]+"|'[^']+')\s*\))/gm;
+
+/**
+ * Scan a file's imports to detect which renderer it may need.
+ * ex: if the file imports "preact", it's safe to assume the
+ * component should be built as a Preact component.
+ * If no relevant imports found, return undefined.
+ */
+async function detectImportSourceFromImports(
+ code: string,
+ jsxRenderers: Map<string, AstroRenderer>
+): Promise<string | undefined> {
+ let m;
+ importsRE.lastIndex = 0;
+ while ((m = importsRE.exec(code)) != null) {
+ const spec = (m[1] || m[2]).slice(1, -1);
+ const pkg = parseNpmName(spec);
+ if (pkg && jsxRenderers.has(pkg.name)) {
+ return pkg.name;
+ }
+ }
+}
+
+/**
+ * Scan a file for an explicit @jsxImportSource comment.
+ * If one is found, return it's value. Otherwise, return undefined.
+ */
+function detectImportSourceFromComments(code: string): string | undefined {
+ // if no imports were found, look for @jsxImportSource comment
+ const multiline = code.match(/\/\*\*?[\S\s]*\*\//gm) || [];
+ for (const comment of multiline) {
+ const [_, lib] = comment.slice(0, -2).match(/@jsxImportSource\s*(\S+)/) || [];
+ if (lib) {
+ return lib.trim();
+ }
+ }
+}
diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts
index 0a3e64344..39be6db6e 100644
--- a/packages/astro/src/vite-plugin-jsx/index.ts
+++ b/packages/astro/src/vite-plugin-jsx/index.ts
@@ -1,23 +1,17 @@
import type { TransformResult } from 'rollup';
-import type { TsConfigJson } from 'tsconfig-resolver';
import type { Plugin, ResolvedConfig } from 'vite';
import type { AstroRenderer, AstroSettings } from '../@types/astro';
import type { LogOptions } from '../core/logger/core.js';
import type { PluginMetadata } from '../vite-plugin-astro/types';
import babel from '@babel/core';
-import * as eslexer from 'es-module-lexer';
import esbuild from 'esbuild';
import * as colors from 'kleur/colors';
import path from 'path';
import { error } from '../core/logger/core.js';
import { removeQueryString } from '../core/path.js';
-import { parseNpmName } from '../core/util.js';
import tagExportsPlugin from './tag.js';
-
-type FixedCompilerOptions = TsConfigJson.CompilerOptions & {
- jsxImportSource?: string;
-};
+import { detectImportSource } from './import-source.js';
const JSX_EXTENSIONS = new Set(['.jsx', '.tsx', '.mdx']);
const IMPORT_STATEMENTS: Record<string, string> = {
@@ -27,10 +21,6 @@ const IMPORT_STATEMENTS: Record<string, string> = {
astro: "import 'astro/jsx-runtime'",
};
-// A code snippet to inject into JS files to prevent esbuild reference bugs.
-// The `tsx` loader in esbuild will remove unused imports, so we need to
-// be careful about esbuild not treating h, React, Fragment, etc. as unused.
-const PREVENT_UNUSED_IMPORTS = ';;(React,Fragment,h);';
// A fast check regex for the import keyword. False positives are okay.
const IMPORT_KEYWORD_REGEX = /import/;
@@ -46,53 +36,6 @@ function collectJSXRenderers(renderers: AstroRenderer[]): Map<string, AstroRende
);
}
-/**
- * Scan a file for an explicit @jsxImportSource comment.
- * If one is found, return it's value. Otherwise, return undefined.
- */
-function detectImportSourceFromComments(code: string): string | undefined {
- // if no imports were found, look for @jsxImportSource comment
- const multiline = code.match(/\/\*\*?[\S\s]*\*\//gm) || [];
- for (const comment of multiline) {
- const [_, lib] = comment.slice(0, -2).match(/@jsxImportSource\s*(\S+)/) || [];
- if (lib) {
- return lib.trim();
- }
- }
-}
-
-/**
- * Scan a file's imports to detect which renderer it may need.
- * ex: if the file imports "preact", it's safe to assume the
- * component should be built as a Preact component.
- * If no relevant imports found, return undefined.
- */
-async function detectImportSourceFromImports(
- code: string,
- id: string,
- jsxRenderers: Map<string, AstroRenderer>
-) {
- // We need valid JS to scan for imports.
- // NOTE: Because we only need imports, it is okay to use `h` and `Fragment` as placeholders.
- const { code: jsCode } = await esbuild.transform(code + PREVENT_UNUSED_IMPORTS, {
- loader: getEsbuildLoader(path.extname(id)) as esbuild.Loader,
- jsx: 'transform',
- jsxFactory: 'h',
- jsxFragment: 'Fragment',
- sourcefile: id,
- sourcemap: 'inline',
- });
- const [imports] = eslexer.parse(jsCode);
- if (imports.length > 0) {
- for (let { n: spec } of imports) {
- const pkg = spec && parseNpmName(spec);
- if (!pkg) continue;
- if (jsxRenderers.has(pkg.name)) {
- return pkg.name;
- }
- }
- }
-}
interface TransformJSXOptions {
code: string;
id: string;
@@ -229,16 +172,7 @@ export default function jsx({ settings, logging }: AstroPluginJSXOptions): Plugi
});
}
- let importSource = detectImportSourceFromComments(code);
- if (!importSource && IMPORT_KEYWORD_REGEX.test(code)) {
- importSource = await detectImportSourceFromImports(code, id, jsxRenderers);
- }
-
- // Check the tsconfig
- if (!importSource) {
- const compilerOptions = settings.tsConfig?.compilerOptions;
- importSource = (compilerOptions as FixedCompilerOptions | undefined)?.jsxImportSource;
- }
+ const importSource = await detectImportSource(code, jsxRenderers, settings.tsConfig);
// if we still can’t tell the import source, now is the time to throw an error.
if (!importSource && defaultJSXRendererEntry) {