{"id":29460900,"url":"https://github.com/stipsan/tsdown-react-compiler-repro","last_synced_at":"2025-07-14T02:38:46.766Z","repository":{"id":296923955,"uuid":"995031021","full_name":"stipsan/tsdown-react-compiler-repro","owner":"stipsan","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-02T22:04:59.000Z","size":133,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-03T11:46:07.298Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stipsan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-02T21:28:25.000Z","updated_at":"2025-06-02T22:05:02.000Z","dependencies_parsed_at":"2025-06-03T11:46:13.676Z","dependency_job_id":"1eee5cb8-d013-4d51-a744-6eaeb35040df","html_url":"https://github.com/stipsan/tsdown-react-compiler-repro","commit_stats":null,"previous_names":["stipsan/tsdown-react-compiler-repro"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/stipsan/tsdown-react-compiler-repro","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stipsan%2Ftsdown-react-compiler-repro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stipsan%2Ftsdown-react-compiler-repro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stipsan%2Ftsdown-react-compiler-repro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stipsan%2Ftsdown-react-compiler-repro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stipsan","download_url":"https://codeload.github.com/stipsan/tsdown-react-compiler-repro/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stipsan%2Ftsdown-react-compiler-repro/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265233753,"owners_count":23731825,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-07-14T02:38:45.950Z","updated_at":"2025-07-14T02:38:46.754Z","avatar_url":"https://github.com/stipsan.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Reproduction steps\n\n```bash\npnpm install\npnpm build\n```\n\nNow the output of `./dist/index.js` should be [similar to this](https://playground.react.dev/#N4Igzg9grgTgxgUxALhASwHYBcEwGYCGiABALICeAQlFlhBgAowQAOYxwAOhscVuSwQB+ZMQDkLGGgC2BGOTHcAvt24IAHiwgwsxPFAxwsaemSo06GABTA+AhMSWiK1WvSaswASg7deMBCxYHgAeACMLUzgAGwIwMAA5AmkEAF5OEGlyAFoItwwMgD4s4jzLUX5BDkqEJRCAejL6QuVuECUgA):\n\n```js\nimport { c as _c } from 'react/compiler-runtime'\nimport { jsxs } from 'react/jsx-runtime'\n\n//#region src/MyButton.tsx\nfunction MyButton(t0) {\n  const $ = _c(2)\n  const { type } = t0\n  let t1\n  if ($[0] !== type) {\n    t1 = /* @__PURE__ */ jsxs('button', {\n      className: 'my-button',\n      children: ['my button: type ', type],\n    })\n    $[0] = type\n    $[1] = t1\n  } else {\n    t1 = $[1]\n  }\n  return t1\n}\n\n//#endregion\nexport { MyButton }\n```\n\nBut instead it's erroneously outputting this:\n\n```js\nimport { c } from 'react-compiler-runtime'\nimport { jsxs } from 'react/jsx-runtime'\n\n//#region ../../../../@react-refresh\n/*! Copyright (c) Meta Platforms, Inc. and affiliates. **/\n/**\n * This is simplified pure-js version of https://github.com/facebook/react/blob/main/packages/react-refresh/src/ReactFreshRuntime.js\n * without IE11 compatibility and verbose isDev checks.\n * Some utils are appended at the bottom for HMR integration.\n */\nconst REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref')\nconst REACT_MEMO_TYPE = Symbol.for('react.memo')\nlet allFamiliesByID = /* @__PURE__ */ new Map()\nlet allFamiliesByType = /* @__PURE__ */ new WeakMap()\nlet allSignaturesByType = /* @__PURE__ */ new WeakMap()\nconst updatedFamiliesByType = /* @__PURE__ */ new WeakMap()\nlet pendingUpdates = []\nconst helpersByRendererID = /* @__PURE__ */ new Map()\nconst helpersByRoot = /* @__PURE__ */ new Map()\nconst mountedRoots = /* @__PURE__ */ new Set()\nconst failedRoots = /* @__PURE__ */ new Set()\nlet rootElements = /* @__PURE__ */ new WeakMap()\nlet isPerformingRefresh = false\nfunction computeFullKey(signature) {\n  if (signature.fullKey !== null) return signature.fullKey\n  let fullKey = signature.ownKey\n  let hooks$1\n  try {\n    hooks$1 = signature.getCustomHooks()\n  } catch (err) {\n    signature.forceReset = true\n    signature.fullKey = fullKey\n    return fullKey\n  }\n  for (let i = 0; i \u003c hooks$1.length; i++) {\n    const hook = hooks$1[i]\n    if (typeof hook !== 'function') {\n      signature.forceReset = true\n      signature.fullKey = fullKey\n      return fullKey\n    }\n    const nestedHookSignature = allSignaturesByType.get(hook)\n    if (nestedHookSignature === void 0) continue\n    const nestedHookKey = computeFullKey(nestedHookSignature)\n    if (nestedHookSignature.forceReset) signature.forceReset = true\n    fullKey += '\\n---\\n' + nestedHookKey\n  }\n  signature.fullKey = fullKey\n  return fullKey\n}\nfunction haveEqualSignatures(prevType, nextType) {\n  const prevSignature = allSignaturesByType.get(prevType)\n  const nextSignature = allSignaturesByType.get(nextType)\n  if (prevSignature === void 0 \u0026\u0026 nextSignature === void 0) return true\n  if (prevSignature === void 0 || nextSignature === void 0) return false\n  if (computeFullKey(prevSignature) !== computeFullKey(nextSignature))\n    return false\n  if (nextSignature.forceReset) return false\n  return true\n}\nfunction isReactClass(type) {\n  return type.prototype \u0026\u0026 type.prototype.isReactComponent\n}\nfunction canPreserveStateBetween(prevType, nextType) {\n  if (isReactClass(prevType) || isReactClass(nextType)) return false\n  if (haveEqualSignatures(prevType, nextType)) return true\n  return false\n}\nfunction resolveFamily(type) {\n  return updatedFamiliesByType.get(type)\n}\nfunction getProperty(object, property) {\n  try {\n    return object[property]\n  } catch (err) {\n    return void 0\n  }\n}\nfunction performReactRefresh() {\n  if (pendingUpdates.length === 0) return null\n  if (isPerformingRefresh) return null\n  isPerformingRefresh = true\n  try {\n    const staleFamilies = /* @__PURE__ */ new Set()\n    const updatedFamilies = /* @__PURE__ */ new Set()\n    const updates = pendingUpdates\n    pendingUpdates = []\n    updates.forEach(([family, nextType]) =\u003e {\n      const prevType = family.current\n      updatedFamiliesByType.set(prevType, family)\n      updatedFamiliesByType.set(nextType, family)\n      family.current = nextType\n      if (canPreserveStateBetween(prevType, nextType))\n        updatedFamilies.add(family)\n      else staleFamilies.add(family)\n    })\n    const update = {\n      updatedFamilies,\n      staleFamilies,\n    }\n    helpersByRendererID.forEach((helpers) =\u003e {\n      helpers.setRefreshHandler(resolveFamily)\n    })\n    let didError = false\n    let firstError = null\n    const failedRootsSnapshot = new Set(failedRoots)\n    const mountedRootsSnapshot = new Set(mountedRoots)\n    const helpersByRootSnapshot = new Map(helpersByRoot)\n    failedRootsSnapshot.forEach((root) =\u003e {\n      const helpers = helpersByRootSnapshot.get(root)\n      if (helpers === void 0)\n        throw new Error(\n          'Could not find helpers for a root. This is a bug in React Refresh.',\n        )\n      if (!failedRoots.has(root)) {\n      }\n      if (rootElements === null) return\n      if (!rootElements.has(root)) return\n      const element = rootElements.get(root)\n      try {\n        helpers.scheduleRoot(root, element)\n      } catch (err) {\n        if (!didError) {\n          didError = true\n          firstError = err\n        }\n      }\n    })\n    mountedRootsSnapshot.forEach((root) =\u003e {\n      const helpers = helpersByRootSnapshot.get(root)\n      if (helpers === void 0)\n        throw new Error(\n          'Could not find helpers for a root. This is a bug in React Refresh.',\n        )\n      if (!mountedRoots.has(root)) {\n      }\n      try {\n        helpers.scheduleRefresh(root, update)\n      } catch (err) {\n        if (!didError) {\n          didError = true\n          firstError = err\n        }\n      }\n    })\n    if (didError) throw firstError\n    return update\n  } finally {\n    isPerformingRefresh = false\n  }\n}\nfunction register(type, id) {\n  if (type === null) return\n  if (typeof type !== 'function' \u0026\u0026 typeof type !== 'object') return\n  if (allFamiliesByType.has(type)) return\n  let family = allFamiliesByID.get(id)\n  if (family === void 0) {\n    family = { current: type }\n    allFamiliesByID.set(id, family)\n  } else pendingUpdates.push([family, type])\n  allFamiliesByType.set(type, family)\n  if (typeof type === 'object' \u0026\u0026 type !== null)\n    switch (getProperty(type, '$$typeof')) {\n      case REACT_FORWARD_REF_TYPE:\n        register(type.render, id + '$render')\n        break\n      case REACT_MEMO_TYPE:\n        register(type.type, id + '$type')\n        break\n    }\n}\nfunction setSignature(type, key, forceReset, getCustomHooks) {\n  if (!allSignaturesByType.has(type))\n    allSignaturesByType.set(type, {\n      forceReset,\n      ownKey: key,\n      fullKey: null,\n      getCustomHooks: getCustomHooks || (() =\u003e []),\n    })\n  if (typeof type === 'object' \u0026\u0026 type !== null)\n    switch (getProperty(type, '$$typeof')) {\n      case REACT_FORWARD_REF_TYPE:\n        setSignature(type.render, key, forceReset, getCustomHooks)\n        break\n      case REACT_MEMO_TYPE:\n        setSignature(type.type, key, forceReset, getCustomHooks)\n        break\n    }\n}\nfunction collectCustomHooksForSignature(type) {\n  const signature = allSignaturesByType.get(type)\n  if (signature !== void 0) computeFullKey(signature)\n}\nfunction createSignatureFunctionForTransform() {\n  let savedType\n  let hasCustomHooks\n  let didCollectHooks = false\n  return function (type, key, forceReset, getCustomHooks) {\n    if (typeof key === 'string') {\n      if (!savedType) {\n        savedType = type\n        hasCustomHooks = typeof getCustomHooks === 'function'\n      }\n      if (\n        type != null \u0026\u0026\n        (typeof type === 'function' || typeof type === 'object')\n      )\n        setSignature(type, key, forceReset, getCustomHooks)\n      return type\n    } else if (!didCollectHooks \u0026\u0026 hasCustomHooks) {\n      didCollectHooks = true\n      collectCustomHooksForSignature(savedType)\n    }\n  }\n}\nfunction isLikelyComponentType(type) {\n  switch (typeof type) {\n    case 'function': {\n      if (type.prototype != null) {\n        if (type.prototype.isReactComponent) return true\n        const ownNames = Object.getOwnPropertyNames(type.prototype)\n        if (ownNames.length \u003e 1 || ownNames[0] !== 'constructor') return false\n        if (type.prototype.__proto__ !== Object.prototype) return false\n      }\n      const name = type.name || type.displayName\n      return typeof name === 'string' \u0026\u0026 /^[A-Z]/.test(name)\n    }\n    case 'object': {\n      if (type != null)\n        switch (getProperty(type, '$$typeof')) {\n          case REACT_FORWARD_REF_TYPE:\n          case REACT_MEMO_TYPE:\n            return true\n          default:\n            return false\n        }\n      return false\n    }\n    default:\n      return false\n  }\n}\n/**\n * Plugin utils\n */\nfunction getRefreshReg(filename) {\n  return (type, id) =\u003e register(type, filename + ' ' + id)\n}\nfunction registerExportsForReactRefresh(filename, moduleExports) {\n  for (const key in moduleExports) {\n    if (key === '__esModule') continue\n    const exportValue = moduleExports[key]\n    if (isLikelyComponentType(exportValue))\n      register(exportValue, filename + ' export ' + key)\n  }\n}\nfunction debounce(fn, delay) {\n  let handle\n  return () =\u003e {\n    clearTimeout(handle)\n    handle = setTimeout(fn, delay)\n  }\n}\nconst hooks = []\nwindow.__registerBeforePerformReactRefresh = (cb) =\u003e {\n  hooks.push(cb)\n}\nconst enqueueUpdate = debounce(async () =\u003e {\n  if (hooks.length) await Promise.all(hooks.map((cb) =\u003e cb()))\n  performReactRefresh()\n}, 16)\nfunction validateRefreshBoundaryAndEnqueueUpdate(id, prevExports, nextExports) {\n  const ignoredExports = window.__getReactRefreshIgnoredExports?.({ id }) ?? []\n  if (\n    predicateOnExport(\n      ignoredExports,\n      prevExports,\n      (key) =\u003e key in nextExports,\n    ) !== true\n  )\n    return 'Could not Fast Refresh (export removed)'\n  if (\n    predicateOnExport(\n      ignoredExports,\n      nextExports,\n      (key) =\u003e key in prevExports,\n    ) !== true\n  )\n    return 'Could not Fast Refresh (new export)'\n  let hasExports = false\n  const allExportsAreComponentsOrUnchanged = predicateOnExport(\n    ignoredExports,\n    nextExports,\n    (key, value) =\u003e {\n      hasExports = true\n      if (isLikelyComponentType(value)) return true\n      return prevExports[key] === nextExports[key]\n    },\n  )\n  if (hasExports \u0026\u0026 allExportsAreComponentsOrUnchanged === true) enqueueUpdate()\n  else\n    return `Could not Fast Refresh (\"${allExportsAreComponentsOrUnchanged}\" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports`\n}\nfunction predicateOnExport(ignoredExports, moduleExports, predicate) {\n  for (const key in moduleExports) {\n    if (key === '__esModule') continue\n    if (ignoredExports.includes(key)) continue\n    const desc = Object.getOwnPropertyDescriptor(moduleExports, key)\n    if (desc \u0026\u0026 desc.get) return key\n    if (!predicate(key, moduleExports[key])) return key\n  }\n  return true\n}\nconst __hmr_import = (module) =\u003e\n  import(\n    /* @vite-ignore */\n    module\n  )\n\n//#endregion\n//#region src/MyButton.tsx\nconst inWebWorker =\n  typeof WorkerGlobalScope !== 'undefined' \u0026\u0026 self instanceof WorkerGlobalScope\nlet prevRefreshReg\nlet prevRefreshSig\nif (import.meta.hot \u0026\u0026 !inWebWorker) {\n  if (!window.$RefreshReg$)\n    throw new Error(\n      \"@vitejs/plugin-react can't detect preamble. Something is wrong.\",\n    )\n  prevRefreshReg = window.$RefreshReg$\n  prevRefreshSig = window.$RefreshSig$\n  window.$RefreshReg$ = getRefreshReg('/src/MyButton.tsx')\n  window.$RefreshSig$ = createSignatureFunctionForTransform\n}\nfunction MyButton(t0) {\n  const $ = c(2)\n  const { type } = t0\n  let t1\n  if ($[0] !== type) {\n    t1 = /* @__PURE__ */ jsxs('button', {\n      className: 'my-button',\n      children: ['my button: type ', type],\n    })\n    $[0] = type\n    $[1] = t1\n  } else t1 = $[1]\n  return t1\n}\n_c2 = MyButton\nvar _c2\n$RefreshReg$(_c2, 'MyButton')\nif (import.meta.hot \u0026\u0026 !inWebWorker) {\n  window.$RefreshReg$ = prevRefreshReg\n  window.$RefreshSig$ = prevRefreshSig\n}\nif (import.meta.hot \u0026\u0026 !inWebWorker)\n  __hmr_import(import.meta.url).then((currentExports) =\u003e {\n    registerExportsForReactRefresh('/src/MyButton.tsx', currentExports)\n    import.meta.hot.accept((nextExports) =\u003e {\n      if (!nextExports) return\n      const invalidateMessage = validateRefreshBoundaryAndEnqueueUpdate(\n        '/src/MyButton.tsx',\n        currentExports,\n        nextExports,\n      )\n      if (invalidateMessage) import.meta.hot.invalidate(invalidateMessage)\n    })\n  })\n\n//#endregion\nexport { MyButton }\n//# sourceMappingURL=index.js.map\n```\n\nNote that `pnpm test` and `pnpm playground` both work correctly.\nFor example `pnpm test` will dump to `.vite-node/dump` and a file containing `_src_MyButton_tsx` demonstrates correct output.\nRunning `pnpm playground` also demonstrates the correct output when inspecting `MyButton` and seeing its `Memo✨` label:\n\u003cimg width=\"739\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/22142972-f145-43f7-a27d-e294289f9559\" /\u003e\n\n# Workaround\n\nCheck the [`workaround` branch](https://github.com/stipsan/tsdown-react-compiler-repro/tree/workaround) for a fix.\nIt produces the correct output:\n\n```js\nimport { c } from 'react-compiler-runtime'\nimport { jsxs } from 'react/jsx-runtime'\n\n//#region src/MyButton.tsx\nfunction MyButton(t0) {\n  const $ = c(2)\n  const { type } = t0\n  let t1\n  if ($[0] !== type) {\n    t1 = /* @__PURE__ */ jsxs('button', {\n      className: 'my-button',\n      children: ['my button: type ', type],\n    })\n    $[0] = type\n    $[1] = t1\n  } else t1 = $[1]\n  return t1\n}\n\n//#endregion\nexport { MyButton }\n//# sourceMappingURL=index.js.map\n```\n\nBy patching the `@vitejs/plugin-react` package:\n\n```diff\ndiff --git a/dist/index.cjs b/dist/index.cjs\nindex c3d068fad7c354bedcf3364041b85fe820b11fff..f9365fb338b9bb4de1dad6dd596b48f83e1013ac 100644\n--- a/dist/index.cjs\n+++ b/dist/index.cjs\n@@ -143,7 +143,7 @@ function viteReact(opts = {}) {\n   const jsxImportDevRuntime = `${jsxImportSource}/jsx-dev-runtime`;\n   let isProduction = true;\n   let projectRoot = process.cwd();\n-  let skipFastRefresh = false;\n+  let skipFastRefresh = isProduction;\n   let runPluginOverrides;\n   let staticBabelOptions;\n   const importReactRE = /\\bimport\\s+(?:\\*\\s+as\\s+)?React\\b/;\ndiff --git a/dist/index.mjs b/dist/index.mjs\nindex 5d03436ef54221d059024d8c4d7d2ff25b802ef9..9227cbd94eeceda8075221a43ce3b3adb52571d8 100644\n--- a/dist/index.mjs\n+++ b/dist/index.mjs\n@@ -127,7 +127,7 @@ function viteReact(opts = {}) {\n   const jsxImportDevRuntime = `${jsxImportSource}/jsx-dev-runtime`;\n   let isProduction = true;\n   let projectRoot = process.cwd();\n-  let skipFastRefresh = false;\n+  let skipFastRefresh = isProduction;\n   let runPluginOverrides;\n   let staticBabelOptions;\n   const importReactRE = /\\bimport\\s+(?:\\*\\s+as\\s+)?React\\b/;\n```\n\nThe issue seems to be that the `rolldown` pipeline [doesn't call the `configResolved` hook, which is typically setting `skipFastRefresh` to `true` if `isProduction` is `true`](https://github.com/vitejs/vite-plugin-react/blob/88585dbd60f8ec40b31bed8994ac3a73fc0f1bac/packages/plugin-react/src/index.ts#L167-L170).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstipsan%2Ftsdown-react-compiler-repro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstipsan%2Ftsdown-react-compiler-repro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstipsan%2Ftsdown-react-compiler-repro/lists"}