Skip to content

fix(vite,devtools-kit): Open in Editor for JSX/TSX components - #1122

Open
NikhilVerma wants to merge 4 commits into
vuejs:mainfrom
NikhilVerma:fix/tsx-open-in-editor
Open

fix(vite,devtools-kit): Open in Editor for JSX/TSX components#1122
NikhilVerma wants to merge 4 commits into
vuejs:mainfrom
NikhilVerma:fix/tsx-open-in-editor

Conversation

@NikhilVerma

Copy link
Copy Markdown

Summary

"Open in Editor" never appears for JSX/TSX components. DevTools resolves a
component's source path from instance.type.__file, and nothing sets it for
JSX/TSX — so activeTreeNodeFilePath is empty and the button is never rendered.

@vitejs/plugin-vue attaches __file to every SFC during serve, with the
comment "expose filename during serve for devtools to pickup":

if (devToolsEnabled || (devServer && !isProduction)) {
  attachedProps.push([`__file`, JSON.stringify(isProduction ? path.basename(filename) : filename)])
}

@vitejs/plugin-vue-jsx has no equivalent. It emits only __hmrId, which is a
sha256 hash with no path in it.

Reproduction

https://github.com/NikhilVerma/vue-devtools-tsx-repro

pnpm install && pnpm verify
  module                            plugin                    __file
  ------------------------------------------------------------------------
  /src/components/SfcButton.vue     @vitejs/plugin-vue        yes
  /src/components/TsxButton.tsx     @vitejs/plugin-vue-jsx    NO
  /src/components/PlainTsxButton.tsxplain function            NO

pnpm verify boots the dev server and greps each transformed module — no
browser needed. pnpm dev reproduces it in the UI: select <SfcButton> and the
launch icon is there, select either TSX component and it is gone.

Changes

packages/vite — attach __file to JSX/TSX components in serve mode
(src/jsx-file-injection.ts). Two cases:

  1. Components plugin-vue-jsx already tagged with __hmrId (the
    defineComponent pattern) — stamp the same local binding.
  2. Plain function/arrow components, which get no __hmrId at all, matched by
    the convention of naming the component after its file. This only fires when
    the module declares that binding itself, so a re-exported import is never
    attributed to the wrong file, and the assignment is narrowed to functions and
    objects so a same-named non-component binding cannot throw under ESM strict
    mode.

packages/devtools-kitopenInEditor now prefers __VUE_INSPECTOR__
whenever vitePluginDetected is true, regardless of host. The
chrome-extension branch was bypassing the already-configured
launch-editor-middleware and falling back to a raw fetch that failed silently.
The fetch fallback is kept for when no Vite plugin is present, with a clearer
error.

packages/devtools-kitgetComponentFileName now handles .jsx/.tsx.
Once __file exists, basename(file, '.vue') still cannot read it:
lastIndexOf('.vue') on Foo.tsx is -1 and substring(0, -1) clamps to '',
so an anonymous defineComponent({}) in a .tsx file falls through to
"Anonymous Component" instead of its filename. Also extends the existing
index.vue suppression to index.jsx / index.tsx.

Relationship to vitejs/vite-plugin-vue#784

I also opened vitejs/vite-plugin-vue#784, which fixes
this in plugin-vue-jsx via the Babel AST. That is the better home for it —
it works for anyone using JSX, including people on the browser extension who
never install this plugin.

These aren't redundant. The upstream fix only reaches components
plugin-vue-jsx tracks for HMR, so plain function components stay uncovered;
and it only helps once released. The change here works today and covers both
shapes. If both land they assign __file to the same value, last write wins,
and the result is identical.

Tests

  • packages/vite/__tests__/jsx-file-injection.spec.ts — 10 tests covering both
    injection paths, the imported-binding case, double-stamping, non-JSX files,
    query-suffixed ids, and lowercase filenames
  • packages/devtools-kit/__tests__/component/utils.test.ts — 15 tests for
    getComponentName / getInstanceName against .jsx and .tsx files

Verified manually

Against the reproduction, with this branch built and installed:

  • Every component carries its path in the component tree
  • The launch icon appears for both TSX components
  • Clicking it requests /__open-in-editor?file=/…/TsxButton.tsx:0:0 — the same
    shape as the SFC — which returns 200 and opens the file
  • With the released plugin restored, all of the above goes back to failing

Supersedes #1102

#1102 was pushed from the wrong branch: it contained a component-highlighter
change rather than any of this, which is why it looked like it was fixing
nothing. Closing it in favour of this. The highlighter change (DOM walking for
content the renderer never created, e.g. v-html / innerHTML) is a separate
issue and I'll raise it on its own if it's wanted.

NikhilVerma and others added 4 commits August 10, 2026 15:17
@vitejs/plugin-vue-jsx marks every Vue component it finds with __hmrId
but never sets __file, so the devtools "Open in Editor" button was hidden
for all JSX/TSX components (the button is gated on instance.type.__file).

Add a post-enforce Vite transform that piggybacks on those __hmrId
assignments: for each `LocalName.__hmrId = ...` the plugin has already
injected, we prepend `LocalName.__file = "/abs/path/to/file.tsx"`.
This makes JSX/TSX components behave identically to SFCs in the
component tree panel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two follow-up fixes for JSX/TSX component support:

1. Open in Editor did nothing when clicking the icon (devtools-kit)
   When the Vite plugin is active, __VUE_INSPECTOR__ is already set up with
   the correct launch-editor-middleware. The chrome-extension branch was
   bypassing it and falling back to a raw fetch that often failed silently.
   Now __VUE_INSPECTOR__ is preferred whenever vitePluginDetected is true,
   regardless of the devtools host. The fetch fallback is kept for cases where
   no Vite plugin is present, with a clearer console error message.

2. Plain function/arrow TSX components had no file icon (vite)
   The previous __hmrId piggyback only worked for defineComponent-based
   components. Plain exports like `export function HomeKpiGrid()` don't get
   __hmrId from @vitejs/plugin-vue-jsx. Added a second injection that derives
   the component name from the file name (PascalCase convention) and injects
   __file on it as a safe guard expression.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Once a build tool sets `__file` on a JSX/TSX component, as
@vitejs/plugin-vue already does for SFCs (see vitejs/vite-plugin-vue#784),
`getComponentFileName` still cannot read it: `basename(file, '.vue')`
calls `lastIndexOf('.vue')` on `Foo.tsx`, gets -1, and `substring(0, -1)`
clamps to an empty string. An anonymous `defineComponent({})` in a .tsx
file therefore falls through to "Anonymous Component" instead of its
filename.

Handle .jsx and .tsx alongside .vue, and extend the existing index.vue
suppression to index.jsx / index.tsx barrel files.
Move the injection out of the plugin factory into its own module so it can
be unit tested, and fix two problems in the plain-function branch:

- It stamped any binding whose name matched the file name, including one the
  module merely imported. `Button.tsx` re-exporting a `Button` from
  elsewhere would attribute that component to the wrong file. It now only
  stamps bindings the module declares itself.
- Property assignment on a non-object binding throws under ESM strict mode,
  so the assignment is now narrowed to functions and objects.

Also derive the basename from the normalized path so the behaviour does not
depend on the host OS separator.
@netlify

netlify Bot commented Aug 10, 2026

Copy link
Copy Markdown

Deploy Preview for vue-devtools-docs canceled.

Name Link
🔨 Latest commit 2f0123e
🔍 Latest deploy log https://app.netlify.com/projects/vue-devtools-docs/deploys/6a79af82ddab0800083e2846

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant