Skip to content

Dependency scanner ignores oxc.jsxInject imports until first serve #23751

Closed

Description

@samchon

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 repro

The 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

  1. added a commit that references this issue on Oct 13, 2026
    0e5216c
  2. added theissue type on Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feat: deps optimizerDependencies Optimizationp2-edge-caseBug, but has workaround or limited in scope (priority)

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions