| | |

Parcel 13 ๐Ÿ“ฆ JSX & React โ€” Automatic JSX transform & Fast Refresh

โฑ๏ธ Estimated read: 10 min | |

Parcel treats React as a first-class target. When it detects that a project depends on React, it automatically enables the JSX transform, and for React 17 and later it enables the modern automatic runtime, which means a component file does not need to import React to use JSX . In development, Parcel also enables React Fast Refresh, which updates the component in the browser without a full page reload and, in most cases, preserves the component’s state . Both behaviors are automatic: the project does not need a Babel configuration, a .babelrc, or a Parcel plugin.

This chapter covers the detection rule that triggers the React integration, the automatic JSX transform and its runtime versions, the Fast Refresh behavior and its constraints, and the dependency conditions that determine whether Fast Refresh actually works.

Key point: The automatic JSX transform is enabled when Parcel detects a React dependency, and the Fast Refresh runtime is enabled in development when the React dependencies are declared in the project’s package.json dependencies. A React installation that is hoisted from a monorepo or a transitive dependency may not activate Fast Refresh, even though the build succeeds.


Why the React integration is automatic

The detection problem. A project can contain .jsx files without depending on React (a different JSX-compatible library, for example), and a project can use React without any .jsx files (using React.createElement directly). Parcel’s default Babel transformer configures the JSX plugin “if file has extension .jsx or if a React like dependency is found as a dependency in package.json” . The detection is based on the dependency declaration, not on the file extension alone.

The runtime problem. React 17 introduced the automatic JSX runtime, which imports the JSX factory from react/jsx-runtime instead of requiring React to be in scope. Parcel’s React documentation states that for React 17 or later, “it also automatically enables the modern JSX transform, which means you don’t even need to import React for JSX to work” . The detection of the React version determines which runtime is used.

The Fast Refresh problem. Fast Refresh is a development-only runtime that integrates with Parcel’s HMR system. The Parcel 2 beta announcement states that SWC replaces Babel by default for transpilation, “as well as for compiling JSX and TypeScript, and React Fast Refresh” . The runtime is injected into the development bundle, and it updates the component module without a page reload.


a. The automatic JSX transform

The transform is enabled when Parcel detects a React dependency. A .jsx file does not need an explicit import React from 'react' when the project uses React 17 or later.

// App.jsx
export function App() {
  return <h1>Hello</h1>
}
// index.js
import { createRoot } from 'react-dom/client'
import { App } from './App.jsx'

createRoot(document.getElementById('app')).render(<App />)

The App.jsx file contains JSX without a React import. Parcel’s automatic transform converts the <h1> element into a call to the jsx function from react/jsx-runtime, which is imported automatically. The index.js file still imports createRoot from react-dom/client, because that is a runtime API, not a JSX transform.

The React documentation lists a tsconfig.json with "jsx": "react-jsx" as the setting that makes the TypeScript language service recognize the automatic runtime. Without it, the editor may report “React refers to a UMD global, but the current file is a module” even though Parcel builds successfully . The tsconfig.json is for the editor’s benefit, not for Parcel’s transform.


b. Fast Refresh and its constraints

Fast Refresh is enabled in development when the project has the React dependencies and the HMR server is running. The documentation describes it as “first-class support for React Fast Refresh, which gives you quick feedback as you edit your code without needing to reload the page” .

The documentation lists four constraints that determine whether the state is preserved :

Function components only. Fast Refresh works with function components and Hooks. A class component is reloaded with a full refresh, and its state is lost.

Components only in the file. If a file exports a mix of React components and other values, the state is reset whenever the file changes. The fix is to move the non-component exports to a separate file.

Named function declarations. An unnamed default export (an arrow function assigned directly to export default) causes the state to be reset when the component changes. A named function, or an arrow function assigned to a variable, preserves the state.

Entry components in their own file. The component that calls createRoot should be in a separate file from the component it renders. If the entry file is edited, the root is remounted, and the state is lost.

A separate GitHub issue reports that Fast Refresh requires react and react-dom to be declared in package.json dependencies, not devDependencies, for the runtime to activate . A monorepo that hoists React to the root package.json may also fail to activate Fast Refresh, because the detection is based on the project’s own dependency declaration .


c. The dependency and version conditions

The Fast Refresh runtime is implemented across two Parcel plugins: @parcel/runtime-react-refresh and @parcel/transformer-react-refresh-wrap. Both depend on the react-refresh package, and they must resolve to the same instance of that package. If npm places two copies of react-refresh in node_modules (because a transitive dependency requires a different version, or because the deduplication fails), Fast Refresh stops working .

The verification is npm ls react-refresh. The output should show a single deduplicated version. If it shows multiple versions, the fix is to use the overrides field in package.json to pin the version, delete node_modules and the lockfile, and reinstall .

A separate issue in Parcel 2.16.0 reports a $RefreshSig$ is not defined error, which is a Fast Refresh runtime error that appears after upgrading. The temporary workaround is --no-hmr, which disables the HMR server and the Fast Refresh runtime .


Complete Example Session

// ============================================
// PART 1: THE REACT COMPONENT
// ============================================
// App.jsx
export function App() {
  return <h1>Hello</h1>
}
// No React import needed for React 17+.
// ============================================
// PART 2: THE ENTRY POINT
// ============================================
// index.js
import { createRoot } from 'react-dom/client'
import { App } from './App.jsx'

createRoot(document.getElementById('app')).render(<App />)
<!-- ============================================
     PART 3: THE HTML ENTRY
     ============================================ -->
<div id="app"></div>
<script type="module" src="./index.js"></script>
# ============================================
# PART 4: THE DEV SERVER
# ============================================
parcel index.html

# Fast Refresh is enabled.
# Editing App.jsx updates the component without a reload.
// ============================================
// PART 5: THE NAMED FUNCTION
// ============================================
// Preserves state on edit
export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
// ============================================
// PART 6: THE UNNAMED DEFAULT EXPORT
// ============================================
// Resets state on edit
export default () => {
  const [count, setCount] = useState(0)
  return <button>{count}</button>
}
// ============================================
// PART 7: THE MIXED EXPORTS
// ============================================
// Resets state on edit
export const API_URL = 'https://api.example.com'
export function Component() { /* ... */ }
# ============================================
# PART 8: THE DEPENDENCY CHECK
# ============================================
npm ls react-refresh

# Deduplicated: Fast Refresh works.
# Multiple versions: Fast Refresh fails.
// ============================================
// PART 9: THE OVERRIDES FIX
// ============================================
{
  "overrides": {
    "react-refresh": "0.14.0"
  }
}
# ============================================
# PART 10: THE TSCONFIG FOR THE EDITOR
# ============================================
# tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx"
  }
}

# The editor recognizes the automatic runtime.

The ten parts covered the React component, the entry point, the HTML entry, the dev server, the named function, the unnamed default export, the mixed exports, the dependency check, the overrides fix, and the tsconfig for the editor.


Quick Reference

React integration

FeatureTriggerConfiguration
JSX transformReact dependency in package.jsonAutomatic
Automatic runtimeReact 17+Automatic
Fast RefreshDevelopment modeAutomatic

Fast Refresh constraints

ConstraintEffect
Function components onlyClass components reset state
Components only in the fileMixed exports reset state
Named function declarationUnnamed default export resets state
Entry component in its own fileEditing the entry remounts the root

Dependency requirements

RequirementDetail
react in dependenciesNot devDependencies
react-dom in dependenciesNot devDependencies
react-refresh deduplicatedSingle version in node_modules

The verification

CommandPurpose
npm ls react-refreshCheck for multiple versions
parcel index.htmlDev server with Fast Refresh
--no-hmrDisable Fast Refresh temporarily

Best Practices

โœ… Do This:

// Use a named function for the component
export function Counter() { ... }                             // โœ…
// Keep the entry component separate from createRoot
// index.js imports App.jsx                                     // โœ…
# Verify the react-refresh deduplication
npm ls react-refresh                                         // โœ…
// Use overrides if multiple versions exist
{ "overrides": { "react-refresh": "0.14.0" } }                // โœ…

โŒ Don’t Do This:

// Use an unnamed default export
export default () => { ... }                                 // โŒ
// Mix components and constants in one file
export const API_URL = '...'
export function Component() { ... }                          // โŒ
# Put react in devDependencies
npm install --save-dev react                                 // โŒ

Common Pitfalls

PitfallWhy It HappensFix
Fast Refresh does not activateReact in devDependencies or hoistedMove to dependencies
State resets on editUnnamed default exportUse a named function
State resets on editMixed exportsMove non-components to another file
State resets on editEntry component in the same file as createRootSeparate the files
$RefreshSig$ is not definedParcel 2.16.0 bugUse --no-hmr or downgrade
Fast Refresh does not apply changesMultiple react-refresh versionsUse overrides to deduplicate

Real-World Examples

1. Automatic JSX

export function App() { return <h1>Hello</h1> }

2. The entry point

import { createRoot } from 'react-dom/client'
import { App } from './App.jsx'

3. The dev server

parcel index.html

4. The named function

export function Counter() { ... }

5. The dependency check

npm ls react-refresh

6. The overrides

{ "overrides": { "react-refresh": "0.14.0" } }

7. The tsconfig

{ "compilerOptions": { "jsx": "react-jsx" } }

8. The Fast Refresh tip

Only export React components from a component file.

9. The entry component separation

index.js (createRoot) + App.jsx (component)

10. The decision

React 17+ โ†’ automatic runtime; dev mode โ†’ Fast Refresh.

Visual

The automatic JSX transform

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  App.jsx                                                     โ”‚
โ”‚    export function App() { return <h1>Hello</h1> }          โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Parcel detects React dependency                            โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Automatic JSX runtime                                       โ”‚
โ”‚    import { jsx } from 'react/jsx-runtime'                  โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  jsx('h1', { children: 'Hello' })                           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Fast Refresh constraints

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  PRESERVES STATE                RESETS STATE                  โ”‚
โ”‚                                                              โ”‚
โ”‚  Function component             Class component              โ”‚
โ”‚  Named function                 Unnamed default export       โ”‚
โ”‚  Components only in file        Mixed exports                โ”‚
โ”‚  Entry in separate file         Entry with createRoot        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The dependency condition

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  package.json                                                โ”‚
โ”‚    dependencies: { react, react-dom }                       โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Fast Refresh runtime injected                              โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  HMR updates component without reload                        โ”‚
โ”‚                                                              โ”‚
โ”‚  devDependencies or hoisted โ†’ Fast Refresh may not activate โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The react-refresh deduplication

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  npm ls react-refresh                                        โ”‚
โ”‚                                                              โ”‚
โ”‚  Single version (deduped) โ†’ Fast Refresh works              โ”‚
โ”‚  Multiple versions โ†’ Fast Refresh fails                      โ”‚
โ”‚                                                              โ”‚
โ”‚  Fix: overrides in package.json                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
JSX transformAutomatic on React detection
Automatic runtimeReact 17+
Fast RefreshDevelopment mode, automatic
Fast Refresh scopeFunction components and Hooks
Dependency requirementReact in dependencies
Deduplicationreact-refresh must be a single version
Verificationnpm ls react-refresh
Workaround for 2.16.0 bug--no-hmr

Key takeaways:

  • Parcel enables the JSX transform automatically when it detects a React dependency. For React 17 and later, the modern automatic runtime is used, which means a component file does not need to import React to use JSX. The detection is based on the package.json dependency declaration, not on the file extension alone .
  • Fast Refresh is enabled in development when the React dependencies are declared in dependencies. A React installation that is hoisted from a monorepo or declared in devDependencies may not activate the runtime, even though the build succeeds .
  • The Fast Refresh state preservation depends on the component structure. Function components with named declarations preserve state. Class components, unnamed default exports, mixed exports, and entry components that call createRoot reset state .
  • The react-refresh package must be deduplicated. The two Parcel plugins that implement Fast Refresh both depend on react-refresh, and they must resolve to the same instance. npm ls react-refresh is the verification, and overrides is the fix .
  • The tsconfig.json jsx option is for the editor. Parcel does not need it to build, but the TypeScript language service uses it to recognize the automatic runtime and stop reporting the “React refers to a UMD global” error .
  • The Parcel 2.16.0 $RefreshSig$ error is a runtime bug. The temporary workaround is --no-hmr, which disables the HMR server and the Fast Refresh runtime. The error is reported in the Parcel issue tracker .

Remember: Parcel’s React integration is automatic. The JSX transform is enabled when React is detected, and the automatic runtime is used for React 17 and later. Fast Refresh is enabled in development, but it depends on the component structure and the dependency declaration. Use named function components, keep the component exports separate from other values, keep the entry component in its own file, and verify that react-refresh is deduplicated. The tsconfig.json is for the editor, not for Parcel.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!