Skip to content

Using @vitejs/plugin-react always inlines @react-refresh code #294

Closed
vitejs/vite-plugin-react
#513
@stipsan

Description

@stipsan

Reproduction link or steps

See the minimal reproduction here: https://github.com/stipsan/tsdown-react-compiler-repro/tree/main?tab=readme-ov-file#reproduction-steps

There's also a workaround fix here: https://github.com/stipsan/tsdown-react-compiler-repro/tree/main?tab=readme-ov-file#workaround

What is expected?

When using @vitejs/plugin-react to run babel-plugin-react-compiler then the output in ./dist/index.js should be:

import { c as _c } from 'react/compiler-runtime'
import { jsxs } from 'react/jsx-runtime'

//#region src/MyButton.tsx
function MyButton(t0) {
  const $ = _c(2)
  const { type } = t0
  let t1
  if ($[0] !== type) {
    t1 = /* @__PURE__ */ jsxs('button', {
      className: 'my-button',
      children: ['my button: type ', type],
    })
    $[0] = type
    $[1] = t1
  } else {
    t1 = $[1]
  }
  return t1
}

//#endregion
export { MyButton }

What is actually happening?

Instead it inlines @react-refresh, even though this is not a vite dev-type pipeline, it's tsdown with a basic config:

import { c } from 'react-compiler-runtime'
import { jsxs } from 'react/jsx-runtime'

//#region ../../../../@react-refresh
/*! Copyright (c) Meta Platforms, Inc. and affiliates. **/
/**
 * This is simplified pure-js version of https://github.com/facebook/react/blob/main/packages/react-refresh/src/ReactFreshRuntime.js
 * without IE11 compatibility and verbose isDev checks.
 * Some utils are appended at the bottom for HMR integration.
 */
const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref')
const REACT_MEMO_TYPE = Symbol.for('react.memo')
let allFamiliesByID = /* @__PURE__ */ new Map()
let allFamiliesByType = /* @__PURE__ */ new WeakMap()
let allSignaturesByType = /* @__PURE__ */ new WeakMap()
const updatedFamiliesByType = /* @__PURE__ */ new WeakMap()
let pendingUpdates = []
const helpersByRendererID = /* @__PURE__ */ new Map()
const helpersByRoot = /* @__PURE__ */ new Map()
const mountedRoots = /* @__PURE__ */ new Set()
const failedRoots = /* @__PURE__ */ new Set()
let rootElements = /* @__PURE__ */ new WeakMap()
let isPerformingRefresh = false
function computeFullKey(signature) {
  if (signature.fullKey !== null) return signature.fullKey
  let fullKey = signature.ownKey
  let hooks$1
  try {
    hooks$1 = signature.getCustomHooks()
  } catch (err) {
    signature.forceReset = true
    signature.fullKey = fullKey
    return fullKey
  }
  for (let i = 0; i < hooks$1.length; i++) {
    const hook = hooks$1[i]
    if (typeof hook !== 'function') {
      signature.forceReset = true
      signature.fullKey = fullKey
      return fullKey
    }
    const nestedHookSignature = allSignaturesByType.get(hook)
    if (nestedHookSignature === void 0) continue
    const nestedHookKey = computeFullKey(nestedHookSignature)
    if (nestedHookSignature.forceReset) signature.forceReset = true
    fullKey += '\n---\n' + nestedHookKey
  }
  signature.fullKey = fullKey
  return fullKey
}
function haveEqualSignatures(prevType, nextType) {
  const prevSignature = allSignaturesByType.get(prevType)
  const nextSignature = allSignaturesByType.get(nextType)
  if (prevSignature === void 0 && nextSignature === void 0) return true
  if (prevSignature === void 0 || nextSignature === void 0) return false
  if (computeFullKey(prevSignature) !== computeFullKey(nextSignature))
    return false
  if (nextSignature.forceReset) return false
  return true
}
function isReactClass(type) {
  return type.prototype && type.prototype.isReactComponent
}
function canPreserveStateBetween(prevType, nextType) {
  if (isReactClass(prevType) || isReactClass(nextType)) return false
  if (haveEqualSignatures(prevType, nextType)) return true
  return false
}
function resolveFamily(type) {
  return updatedFamiliesByType.get(type)
}
function getProperty(object, property) {
  try {
    return object[property]
  } catch (err) {
    return void 0
  }
}
function performReactRefresh() {
  if (pendingUpdates.length === 0) return null
  if (isPerformingRefresh) return null
  isPerformingRefresh = true
  try {
    const staleFamilies = /* @__PURE__ */ new Set()
    const updatedFamilies = /* @__PURE__ */ new Set()
    const updates = pendingUpdates
    pendingUpdates = []
    updates.forEach(([family, nextType]) => {
      const prevType = family.current
      updatedFamiliesByType.set(prevType, family)
      updatedFamiliesByType.set(nextType, family)
      family.current = nextType
      if (canPreserveStateBetween(prevType, nextType))
        updatedFamilies.add(family)
      else staleFamilies.add(family)
    })
    const update = {
      updatedFamilies,
      staleFamilies,
    }
    helpersByRendererID.forEach((helpers) => {
      helpers.setRefreshHandler(resolveFamily)
    })
    let didError = false
    let firstError = null
    const failedRootsSnapshot = new Set(failedRoots)
    const mountedRootsSnapshot = new Set(mountedRoots)
    const helpersByRootSnapshot = new Map(helpersByRoot)
    failedRootsSnapshot.forEach((root) => {
      const helpers = helpersByRootSnapshot.get(root)
      if (helpers === void 0)
        throw new Error(
          'Could not find helpers for a root. This is a bug in React Refresh.',
        )
      if (!failedRoots.has(root)) {
      }
      if (rootElements === null) return
      if (!rootElements.has(root)) return
      const element = rootElements.get(root)
      try {
        helpers.scheduleRoot(root, element)
      } catch (err) {
        if (!didError) {
          didError = true
          firstError = err
        }
      }
    })
    mountedRootsSnapshot.forEach((root) => {
      const helpers = helpersByRootSnapshot.get(root)
      if (helpers === void 0)
        throw new Error(
          'Could not find helpers for a root. This is a bug in React Refresh.',
        )
      if (!mountedRoots.has(root)) {
      }
      try {
        helpers.scheduleRefresh(root, update)
      } catch (err) {
        if (!didError) {
          didError = true
          firstError = err
        }
      }
    })
    if (didError) throw firstError
    return update
  } finally {
    isPerformingRefresh = false
  }
}
function register(type, id) {
  if (type === null) return
  if (typeof type !== 'function' && typeof type !== 'object') return
  if (allFamiliesByType.has(type)) return
  let family = allFamiliesByID.get(id)
  if (family === void 0) {
    family = { current: type }
    allFamiliesByID.set(id, family)
  } else pendingUpdates.push([family, type])
  allFamiliesByType.set(type, family)
  if (typeof type === 'object' && type !== null)
    switch (getProperty(type, '$$typeof')) {
      case REACT_FORWARD_REF_TYPE:
        register(type.render, id + '$render')
        break
      case REACT_MEMO_TYPE:
        register(type.type, id + '$type')
        break
    }
}
function setSignature(type, key, forceReset, getCustomHooks) {
  if (!allSignaturesByType.has(type))
    allSignaturesByType.set(type, {
      forceReset,
      ownKey: key,
      fullKey: null,
      getCustomHooks: getCustomHooks || (() => []),
    })
  if (typeof type === 'object' && type !== null)
    switch (getProperty(type, '$$typeof')) {
      case REACT_FORWARD_REF_TYPE:
        setSignature(type.render, key, forceReset, getCustomHooks)
        break
      case REACT_MEMO_TYPE:
        setSignature(type.type, key, forceReset, getCustomHooks)
        break
    }
}
function collectCustomHooksForSignature(type) {
  const signature = allSignaturesByType.get(type)
  if (signature !== void 0) computeFullKey(signature)
}
function createSignatureFunctionForTransform() {
  let savedType
  let hasCustomHooks
  let didCollectHooks = false
  return function (type, key, forceReset, getCustomHooks) {
    if (typeof key === 'string') {
      if (!savedType) {
        savedType = type
        hasCustomHooks = typeof getCustomHooks === 'function'
      }
      if (
        type != null &&
        (typeof type === 'function' || typeof type === 'object')
      )
        setSignature(type, key, forceReset, getCustomHooks)
      return type
    } else if (!didCollectHooks && hasCustomHooks) {
      didCollectHooks = true
      collectCustomHooksForSignature(savedType)
    }
  }
}
function isLikelyComponentType(type) {
  switch (typeof type) {
    case 'function': {
      if (type.prototype != null) {
        if (type.prototype.isReactComponent) return true
        const ownNames = Object.getOwnPropertyNames(type.prototype)
        if (ownNames.length > 1 || ownNames[0] !== 'constructor') return false
        if (type.prototype.__proto__ !== Object.prototype) return false
      }
      const name = type.name || type.displayName
      return typeof name === 'string' && /^[A-Z]/.test(name)
    }
    case 'object': {
      if (type != null)
        switch (getProperty(type, '$$typeof')) {
          case REACT_FORWARD_REF_TYPE:
          case REACT_MEMO_TYPE:
            return true
          default:
            return false
        }
      return false
    }
    default:
      return false
  }
}
/**
 * Plugin utils
 */
function getRefreshReg(filename) {
  return (type, id) => register(type, filename + ' ' + id)
}
function registerExportsForReactRefresh(filename, moduleExports) {
  for (const key in moduleExports) {
    if (key === '__esModule') continue
    const exportValue = moduleExports[key]
    if (isLikelyComponentType(exportValue))
      register(exportValue, filename + ' export ' + key)
  }
}
function debounce(fn, delay) {
  let handle
  return () => {
    clearTimeout(handle)
    handle = setTimeout(fn, delay)
  }
}
const hooks = []
window.__registerBeforePerformReactRefresh = (cb) => {
  hooks.push(cb)
}
const enqueueUpdate = debounce(async () => {
  if (hooks.length) await Promise.all(hooks.map((cb) => cb()))
  performReactRefresh()
}, 16)
function validateRefreshBoundaryAndEnqueueUpdate(id, prevExports, nextExports) {
  const ignoredExports = window.__getReactRefreshIgnoredExports?.({ id }) ?? []
  if (
    predicateOnExport(
      ignoredExports,
      prevExports,
      (key) => key in nextExports,
    ) !== true
  )
    return 'Could not Fast Refresh (export removed)'
  if (
    predicateOnExport(
      ignoredExports,
      nextExports,
      (key) => key in prevExports,
    ) !== true
  )
    return 'Could not Fast Refresh (new export)'
  let hasExports = false
  const allExportsAreComponentsOrUnchanged = predicateOnExport(
    ignoredExports,
    nextExports,
    (key, value) => {
      hasExports = true
      if (isLikelyComponentType(value)) return true
      return prevExports[key] === nextExports[key]
    },
  )
  if (hasExports && allExportsAreComponentsOrUnchanged === true) enqueueUpdate()
  else
    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`
}
function predicateOnExport(ignoredExports, moduleExports, predicate) {
  for (const key in moduleExports) {
    if (key === '__esModule') continue
    if (ignoredExports.includes(key)) continue
    const desc = Object.getOwnPropertyDescriptor(moduleExports, key)
    if (desc && desc.get) return key
    if (!predicate(key, moduleExports[key])) return key
  }
  return true
}
const __hmr_import = (module) =>
  import(
    /* @vite-ignore */
    module
  )

//#endregion
//#region src/MyButton.tsx
const inWebWorker =
  typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope
let prevRefreshReg
let prevRefreshSig
if (import.meta.hot && !inWebWorker) {
  if (!window.$RefreshReg$)
    throw new Error(
      "@vitejs/plugin-react can't detect preamble. Something is wrong.",
    )
  prevRefreshReg = window.$RefreshReg$
  prevRefreshSig = window.$RefreshSig$
  window.$RefreshReg$ = getRefreshReg('/src/MyButton.tsx')
  window.$RefreshSig$ = createSignatureFunctionForTransform
}
function MyButton(t0) {
  const $ = c(2)
  const { type } = t0
  let t1
  if ($[0] !== type) {
    t1 = /* @__PURE__ */ jsxs('button', {
      className: 'my-button',
      children: ['my button: type ', type],
    })
    $[0] = type
    $[1] = t1
  } else t1 = $[1]
  return t1
}
_c2 = MyButton
var _c2
$RefreshReg$(_c2, 'MyButton')
if (import.meta.hot && !inWebWorker) {
  window.$RefreshReg$ = prevRefreshReg
  window.$RefreshSig$ = prevRefreshSig
}
if (import.meta.hot && !inWebWorker)
  __hmr_import(import.meta.url).then((currentExports) => {
    registerExportsForReactRefresh('/src/MyButton.tsx', currentExports)
    import.meta.hot.accept((nextExports) => {
      if (!nextExports) return
      const invalidateMessage = validateRefreshBoundaryAndEnqueueUpdate(
        '/src/MyButton.tsx',
        currentExports,
        nextExports,
      )
      if (invalidateMessage) import.meta.hot.invalidate(invalidateMessage)
    })
  })

//#endregion
export { MyButton }
//# sourceMappingURL=index.js.map

Any additional comments?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions