Repository navigation
Dependency scanner ignores oxc.jsxInject imports until first serve #23751
Description
Describe the bug
oxc.jsxInject imports are missing from the initial dependency scan and are discovered only when a JSX module is served. The documented oxc option and its deprecated esbuild compatibility equivalent produce different initial dependency sets.
Expected: both configurations discover jsx-helper during the initial scan. Actual on Vite 8.3.4: oxc.jsxInject initially optimizes only manual-dep; transforming the TSX entry adds jsx-helper and replaces the optimizer publication. esbuild.jsxInject discovers both dependencies initially and keeps that publication.
This reproduces with Vite alone, without Vite plugins, framework plugins or ttsc.
Reproduction
https://github.com/samchon/vite-oxc-jsx-inject-repro
Steps to reproduce
git clone https://github.com/samchon/vite-oxc-jsx-inject-repro.git
cd vite-oxc-jsx-inject-repro
npm install
npm run reproThe script creates two cold temporary projects and their two small dependencies, waits up to 15 seconds for each observed optimizer publication, prints the result, then closes each server and removes its project.
Complete repro.mjs
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { createServer, version } from 'vite';
for (const option of ['oxc', 'esbuild']) {
const root = fs.realpathSync.native(
fs.mkdtempSync(path.join(os.tmpdir(), 'vite-jsx-inject-')),
);
let server;
try {
fs.mkdirSync(path.join(root, 'src'), { recursive: true });
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ type: 'module' }));
fs.writeFileSync(path.join(root, 'src/main.tsx'), 'export const view = "view";\n');
for (const name of ['manual-dep', 'jsx-helper']) {
const dependency = path.join(root, 'node_modules', name);
fs.mkdirSync(dependency, { recursive: true });
fs.writeFileSync(path.join(dependency, 'package.json'), JSON.stringify({
name, type: 'module', exports: './index.js',
}));
fs.writeFileSync(path.join(dependency, 'index.js'), 'export const value = 1;\n');
}
server = await createServer({
root,
configFile: false,
appType: 'custom',
logLevel: 'silent',
[option]: { jsxInject: 'import "jsx-helper";' },
optimizeDeps: {
entries: ['src/main.tsx'],
include: ['manual-dep'],
holdUntilCrawlEnd: false,
},
server: { host: '127.0.0.1', port: 0 },
});
await server.listen();
const metadataFile = path.join(server.config.cacheDir, 'deps/_metadata.json');
const readMetadata = () => JSON.parse(fs.readFileSync(metadataFile, 'utf8'));
const waitFor = async (ready) => {
const deadline = Date.now() + 15000;
while (!ready()) {
if (Date.now() >= deadline) throw new Error('Optimizer metadata timed out');
await new Promise((resolve) => setTimeout(resolve, 25));
}
};
await waitFor(() => fs.existsSync(metadataFile));
const initial = readMetadata();
const delivered = await server.transformRequest('/src/main.tsx');
await waitFor(() => 'jsx-helper' in readMetadata().optimized);
const after = readMetadata();
console.log({
option,
version,
initial: Object.keys(initial.optimized).sort(),
servedHasInjectedImport: delivered.code.includes('jsx-helper'),
after: Object.keys(after.optimized).sort(),
publicationChanged: initial.browserHash !== after.browserHash,
});
} finally {
await server?.close();
fs.rmSync(root, { recursive: true });
}
}System Info
Collected with npx envinfo --system --npmPackages "{vite,@vitejs/*,rollup,rolldown}" --binaries --browsers:
System:
OS: Windows 11 10.0.26300
CPU: (32) x64 AMD RYZEN AI MAX+ 395 w/ Radeon 8060S
Memory: 20.77 GB / 59.65 GB
Binaries:
Node: 24.18.0 - C:\Program Files\nodejs\node.EXE
npm: 11.16.0 - C:\Program Files\nodejs\npm.CMD
pnpm: 11.17.0 - C:\Users\SamchonNam\AppData\Roaming\npm\pnpm.CMD
Browsers:
Chrome: 155.0.8059.40
Edge: Chromium (154.0.4258.37)
npmPackages:
vite: 8.3.4 => 8.3.4
Used Package Manager
npm
Logs
oxc: initial=[manual-dep], after=[jsx-helper, manual-dep], servedHasInjectedImport=true, publicationChanged=true
esbuild: initial=[jsx-helper, manual-dep], after=[jsx-helper, manual-dep], servedHasInjectedImport=true, publicationChanged=false
The Oxc serve transform applies oxc.jsxInject, but the JSX injection step in the dependency scanner still reads environment.config.esbuild.jsxInject.
Related but different: #22340 and #22342 concern JSX runtime options during import.meta.glob transformation; this reproduction has no glob and uses jsxInject.
Validations
- Follow the project conduct policy.
- Reviewed the contributor instructions.
- Read the applicable Vite documentation.
- Checked open and closed issues and pull requests for duplicates.
- Reproduced with Vite alone, without framework or third-party plugins.
- This describes repeatable faulty behavior rather than a support question.
- The linked repository contains the complete minimal reproduction.
This reproducible report was prepared and executed by Codex at the request of the ttsc maintainer.
Activity
fix(optimizer): scan imports from oxc jsxInject