React-refresh Overview

Are you familiar with react-refresh?

Yes, I'm familiar with react-refresh. It's the underlying technology that powers Fast Refresh in React development environments—most notably with tools like Vite, Webpack, and Next.js (via plugins or built-in support).

Here’s a quick breakdown:


🚀 What is react-refresh?

react-refresh is a hot module replacement (HMR) system specifically designed for React. It lets you preserve the state of your components while updating their code during development—so when you make changes to your components, the UI updates instantly without a full reload and without losing component state.


🔧 How does it work?

  • It hooks into your bundler (like Webpack or Vite) and watches for code changes.

  • When a change is detected, it swaps out just the changed modules.

  • If the change is non-breaking (e.g., a small tweak in render logic), it preserves the component’s state.

  • If not (e.g., component structure changes), it falls back to reloading the affected components.


🛠️ Typical Setup

With Vite (built-in):

You don't have to do anything—vite handles it with @vitejs/plugin-react.

With Webpack:

You’d use:

bash
npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh

And add it in your webpack.config.js:

js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); module.exports = { // ... plugins: [ isDev && new ReactRefreshWebpackPlugin(), // other plugins ].filter(Boolean), module: { rules: [ { test: /\.(js|jsx|ts|tsx)$/, loader: require.resolve('babel-loader'), options: { plugins: [isDev && require.resolve('react-refresh/babel')].filter(Boolean), }, }, ], }, };

Let me know if you're troubleshooting something specific or want a setup example for a certain framework!

What is the logic behind it?

Great question! The logic behind react-refresh is clever and elegant. It’s designed to preserve React component state across code edits during development, without needing a full reload. Let's break down how it works under the hood:


🧠 Core Logic Behind react-refresh

  1. Runtime Tracking of Components

    • React components are registered with the react-refresh runtime.

    • This happens via the react-refresh/babel plugin, which injects code to register each component function with a unique ID.

    • This ID is typically derived from the file name + export name, so React can track the identity of each component.


  1. Hot Swapping Logic

    • When you edit a component’s code, the module gets recompiled and re-executed.

    • react-refresh compares the new component type with the previous one (i.e., the function or class definition).

    • If the change is considered safe (e.g., you updated JSX or logic inside the function), it replaces the old version with the new one.

    • If the component structure changed significantly (e.g., class to function or changes to hooks structure), it discards the state and re-renders.


  1. State Preservation via Element Types

    • React’s reconciliation logic sees that the “type” of the component is the same (thanks to the registration mechanism).

    • So it can reuse the existing instance and its state.

    • Think of it like this: instead of replacing the whole component tree, it surgically swaps out just the internals of the affected components.


  1. Error Boundaries for Stability

    • If something breaks during the refresh (e.g., a syntax error or hook misusage), react-refresh can fall back gracefully.

    • Most dev setups wrap the app in an error overlay (like the red box in Next.js or Vite) so you can fix the issue without killing the whole app state.


🧪 How Does It Know What’s “Safe” to Refresh?

There’s a concept of refresh boundaries:

  • A file (or module) is considered a Refresh Boundary if all exports are React components or things that support hot-swapping.

  • If a boundary changes in a way that might break the state, the runtime discards the old state and does a remount.

So:

  • ✅ Editing JSX → state preserved

  • ❌ Changing number/order of hooks → state reset

  • ❌ Changing exports in a non-component module → full reload


🔁 React + VDOM Magic

Since React is declarative and virtual-DOM based, it’s especially suited to this kind of fine-grained hot replacement. The key is maintaining the identity of components between edits so React doesn’t think it needs to tear everything down.


If you’re interested, I can show a mini working example with code or even a visual explanation of the lifecycle!

give me both

Absolutely! Let's break it into two parts:


🎯 Part 1: Visual Explanation — "What happens during a refresh?"

🧩 Initial Render

+------------------------+ | MyComponent | |------------------------| | useState: count = 0 | | JSX: <button>Click</>| +------------------------+ User clicks → count = 1

✍️ You Edit the Code:

js
// Change: Text on the button <button>Click me!</button>

🔁 React Refresh Kicks In:

  1. Babel plugin injects registration code.

  2. Hot module replacement runs the new module.

  3. react-refresh runtime checks:

    • Is MyComponent still a valid boundary?

    • Is the shape of the component safe?

  4. If yes → swap in new function, keep old state:

+------------------------+ | MyComponent (updated) | |------------------------| | useState: count = 1 | <-- STATE PRESERVED | JSX: <button>Click me!</> | +------------------------+

User sees updated text — no reload, state intact.


❌ If You Do This:

js
// Change: useEffect → useState useState(...) → useEffect(...)

react-refresh detects change in hook structure → Not safe.

🔥 It invalidates the module and remounts:

Component reloaded → count resets to 0

💻 Part 2: Minimal Working Code Example (with Webpack)

Let’s say you have a React component like this:

jsx
// Counter.jsx import React, { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); }

Now in webpack.config.js, you configure react-refresh:

js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); module.exports = { mode: 'development', entry: './src/index.js', module: { rules: [ { test: /\.(js|jsx)$/, loader: 'babel-loader', options: { plugins: [require.resolve('react-refresh/babel')], }, }, ], }, plugins: [new ReactRefreshWebpackPlugin()], devServer: { hot: true, }, };

Babel config:

json
{ "presets": ["@babel/preset-react"], "plugins": ["react-refresh/babel"] }

Now when you edit Counter.jsx (e.g., change the text), React Refresh updates only that component, keeps the state (like the current count), and the UI reflects the change instantly.


Want me to generate a full working repo template you can run locally or play with in StackBlitz/CodeSandbox?

How does it detect changes in the components?

Great follow-up! The magic of react-refresh detecting changes lies in module-level registration, runtime tracking, and Babel transformations. Let’s break it down step-by-step:


🔍 How react-refresh Detects Component Changes

🧪 1. Code Instrumentation via Babel Plugin

The first step is the Babel plugin: react-refresh/babel.

  • It wraps each React component in a registration call.

  • This tells the runtime:
    🧠 “Hey, this file exports a component named MyComponent — track it.”

Example:

js
// Original code export function MyComponent() { ... } // Transformed code (simplified): const MyComponent = () => { ... }; $RefreshReg$(MyComponent, "MyComponent"); export { MyComponent };

So now every component has:

  • A name

  • A module ID

  • A registration in the refresh runtime


🧠 2. Runtime Keeps Track of “Boundaries”

react-refresh maintains an internal map of modules and their exports:

  • Which modules export React components?

  • What are the “signatures” (structure) of those components?

It stores this info so it can later compare before and after versions.


♻️ 3. When You Save the File…

Your dev server (like Webpack or Vite) notices a file change and triggers HMR.

react-refresh receives the new version of the module.

Now it performs a diff:

  • Has the shape of the module exports changed?

  • Do the component signatures match?


✅ 4. If Everything Matches (Safe Change)

If the export list is the same, and the component’s internal logic looks compatible (e.g., same number/order of hooks), react-refresh:

  • Swaps the old component function with the new one

  • Preserves local state via React’s reconciliation


⚠️ 5. If Something Breaks (Unsafe Change)

If the runtime detects:

  • A new hook added/removed/changed order

  • A changed export list (e.g., removed/renamed component)

  • A switch from class to function, or vice versa

Then:

  • It invalidates the component and forces a remount

  • In some cases, it triggers a full reload of the module

This avoids weird bugs or broken component trees.


🧬 Bonus: “Signature” Checks

react-refresh uses hook signatures to verify safe updates.

  • This is enabled by the react-refresh/babel plugin.

  • It injects metadata about the hook order and type.

  • When you change a hook (e.g., add/remove one), it detects a mismatch in the hook signature and flags it as unsafe.


💡 Summary

Step Action
1️⃣ Babel plugin Tags each component with metadata
2️⃣ Runtime Tracks module exports & component identities
3️⃣ File change Module hot-reloaded
4️⃣ Component diff Checks if update is “safe”
✅ If safe Replaces code, keeps state
❌ If unsafe Reloads component or module

Let me know if you'd like a visual diff demo, or a minimal custom implementation that shows this detection logic in action!

How does it refreshes the component whilst maintaining its internal state?

This is where things get really smart — the key is how React reconciles component trees and how react-refresh leverages that to swap component implementations without touching the instance. Here's the breakdown:


🔄 How react-refresh Refreshes a Component While Preserving State


🧠 First, React’s Own Magic: Reconciliation & Keys

React’s Virtual DOM already knows how to:

  • Compare component types (function Foo() vs function Bar())

  • Re-render only when the type changes

  • Keep internal state (like useState, useReducer, etc.) as long as the component identity stays the same

So if you change the code of a component but keep the same type and props:

jsx
<MyComponent key="same" />

React will reuse the existing instance, including:

  • Its useState() hooks

  • Its useEffect() dependencies

  • Any refs, context, etc.


🔄 What react-refresh Adds to the Equation

Here’s what react-refresh does on top of that to make updates live during development:


1. Swaps the Component Definition

js
// Before: function MyComponent() { const [count, setCount] = useState(0); return <div>{count}</div>; } // After (code edited): function MyComponent() { const [count, setCount] = useState(0); return <div>Count is: {count}</div>; }

The runtime says:

“Hey React, use this new version of MyComponent, but don’t re-render the parent or throw away the existing instance.”

And React says:

“Cool, the type is the same — I’ll keep state.”


2. Injects a Runtime Proxy

react-refresh creates a proxy around your component, so the rendered tree always uses the latest version of the function.

Think of it like this:

js
// This is what React renders: const currentComponent = getCurrentVersion("MyComponent"); return React.createElement(currentComponent, props);

So when the function body changes, it doesn’t remount — it just uses the new logic next render.


3. Preserves Fiber Identity

React uses an internal data structure called the Fiber tree, where each component has a node storing:

  • Its type (component function)

  • Its hooks/state

  • Its children

As long as the identity of the component fiber remains stable (same key, same parent), React won’t destroy the node.

So:

  • react-refresh updates the type

  • React re-renders the same fiber with the new implementation

  • ✅ State is preserved


🧪 Internally, it works roughly like this:

js
// React’s internal fiber: fiber = { type: OldComponent, stateNode: { count: 2 }, // this is preserved alternate: ... } // After refresh: fiber.type = NewComponent; // swapped in React schedules an update

🧩 What’s NOT preserved?

If your edit:

  • Changes the hook order or count

  • Adds/removes useState, useEffect, etc.

  • Changes a function to a class (or vice versa)

Then hook state can't be reused, and React throws a warning or resets the component.


🛠️ Developer Experience Summary

Edit Type State Preserved? Why?
Change JSX Same function type, same hooks
Change logic inside useEffect Hook identity preserved
Add a new useState Hook order changed
Rename export Component identity lost
Change from class to function Component type changed

Would you like a visual example of how React’s fiber tree handles this under the hood? Or maybe a sandbox to experiment with these changes yourself?

So in short it patches a forced updated on the changed component whilst preserving its original state and props?

Exactly — that’s the perfect short version. Here's your summary with a bit more structure for clarity:


✅ In Short: What react-refresh Does

"It patches in the new version of a component and triggers a re-render, while keeping the original state and props intact — as long as the component’s identity and hook signature remain compatible."


🧩 Breakdown:

  • 🔁 Swaps component implementation
    Replaces the function body of the component in memory using a proxy or runtime reference.

  • 🧠 Keeps the component’s identity
    As long as the export name and component type don’t change, React treats it as “the same” component.

  • 💾 Preserves internal state and props
    Since the component instance (the React fiber node) isn’t unmounted, useState, useReducer, and props all stay as-is.

  • 🪛 Forces a re-render
    After swapping the implementation, it schedules a re-render so you see the updated UI right away.


So yes: it's like surgical hot-patching of your component, keeping its memory and lifecycle intact, only changing the logic.

Let me know if you'd like a mental model cheat sheet or a diagram — happy to whip that up!

How does the diffing work to detect changes?

Great question — you're zooming into the real core of react-refresh. While React itself handles diffing for UI updates via the Virtual DOM, react-refresh does its own module-level diffing to decide whether a refresh can happen safely.

Let’s unpack how that diffing works:


🔍 How react-refresh Detects Safe vs Unsafe Component Changes

Unlike React’s UI diff (virtual DOM diffing), react-refresh performs a runtime diff of modules and exports, not of rendered output.

It doesn’t “diff” like Git (line by line). Instead, it checks structural and identity-level consistency in two key layers:


1. 🔢 Module Export Diffing

Each time you save a file, your bundler (like Webpack or Vite) reloads the module. react-refresh does this:

js
Old module: export const MyComponent = () => { ... }; New module: export const MyComponent = () => { ... };

If:

  • The number and names of exports are the same,

  • Each export is a valid React component,

Then the module is marked as a Refresh Boundary — meaning it's eligible for in-place refresh.


2. 🧠 Component Signature Comparison

Beyond export names, react-refresh uses hook signatures to decide whether it's safe to preserve state.

Hook Signature = The structure/order of hook calls

Injected by Babel plugin:

js
function MyComponent() { useState(...); // Line 1 useEffect(...); // Line 2 }

react-refresh tracks:

  • Number of hooks

  • Order of calls

  • Type of hook (useState, useEffect, etc.)

If you:

  • Change hook order

  • Add/remove a hook

  • Change a hook’s type

👉 Signature changes → ❌ Unsafe → Refresh resets state


3. 🧪 What’s Actually Compared?

Checkpoint Purpose
Export count and names Does this module export the same stuff?
Export values Are those exports React components?
Hook signature (via Babel) Are the internals of the component safe?
React type (function, class) Did the component’s definition change?

If all of the above pass → ✅ Safe to swap and preserve state
If any fail → ❌ Invalidate component and re-render from scratch


⚙️ Under the Hood: Fast Refresh Runtime

React Refresh runtime exposes methods like:

js
$RefreshReg$(Component, "ComponentName"); $RefreshSig$();

These are used to:

  • Register components

  • Attach signatures

  • Compare old and new registrations

On reload, it checks:

js
// Can we keep this boundary and just refresh it? isReactRefreshBoundary(currentModule)

🔚 Summary: Diffing Logic

In simple terms:

If the shape of your file’s exports and your components’ internal hooks haven’t changed in a dangerous way, react-refresh will patch in your new code and let React do a seamless re-render.

If not:

It throws out the component and re-renders it fresh.


Would you like to see what a signature comparison looks like in the Babel output, or maybe a code example where you can intentionally break the refresh to see how the diff reacts?

How does it know that the order of the hooks has changed or not?

You're asking the heart of the safety mechanism — and it’s such a cool trick! React doesn’t do this on its own. Instead, react-refresh/babel injects runtime code that tracks hook signatures, which are then used to detect whether the order, number, or type of hooks has changed between refreshes.

Let’s break down exactly how it knows about the hook order:


🧬 Step-by-Step: How Hook Order Changes Are Detected

1. 🧰 Babel Plugin Injects Signature Collector

When you use the react-refresh/babel plugin, it rewrites your component code to include a special function called __REACT_REFRESH_SIGNATURE__.

Here’s what happens:

🧑‍💻 Original code:

js
function MyComponent() { const [count, setCount] = useState(0); useEffect(() => { ... }, []); return <div>{count}</div>; }

🧠 Babel transforms it into:

js
const MyComponent = __REACT_REFRESH_SIGNATURE__()(function MyComponent() { const [count, setCount] = useState(0); useEffect(() => { ... }, []); return <div>{count}</div>; });

The __REACT_REFRESH_SIGNATURE__() function captures a hash representing:

  • The number of hooks

  • The order they are called

  • The type of each hook (useState, useEffect, etc.)


2. 🧠 Signature is Stored and Compared

At runtime, when a component is registered for refresh, its hook signature is:

  • Captured and stored

  • Compared to the previous version's signature (from before your last save)

If the hook signature does not match, react-refresh marks the component as "not refresh-safe", and it gets remounted, losing its state.


🔍 Example of a Hook Signature Diff

Let’s say this is the first version:

js
function MyComponent() { useState(0); // Hook 1 useEffect(() => {}, []); // Hook 2 }

Hook signature might be:

["useState", "useEffect"]

Now you edit it:

js
function MyComponent() { useEffect(() => {}, []); // Hook 1 useState(0); // Hook 2 }

New signature:

["useEffect", "useState"]

Mismatch detected → Not safe to refresh → State is reset


📎 Bonus: What If You Add a Hook?

js
function MyComponent() { useState(0); useEffect(() => {}, []); useRef(null); // New hook added }

New signature:

["useState", "useEffect", "useRef"]

→ Signature changed → Not safe → Full remount.


✅ Safe Edits:

  • Change logic inside hooks

  • Change JSX

  • Rename variables

  • Update props

❌ Unsafe Edits (triggers remount):

  • Add/remove a hook

  • Change hook order

  • Change hook type at a position (e.g., useState to useReducer)


🧪 Summary

react-refresh/babel injects a signature hash that reflects the exact sequence and type of hooks used in your component. At runtime, that signature is compared to the previous version. If it changes, the refresh is considered unsafe, and React remounts the component.

It’s a clever hack layered on top of React’s rules of hooks and designed to prevent weird bugs from hook mismatches.

Would you like to see what the Babel output actually looks like in a real component?

how does the __REACT_REFRESH_SIGNATURE__ track the hooks?

Great — you're now asking the deep internals, and it's a fun one. Let’s unpack how __REACT_REFRESH_SIGNATURE__ works and how it tracks the hooks, even though hooks are just function calls in your component.


🔍 Short Answer:

The __REACT_REFRESH_SIGNATURE__ doesn't literally track the hooks as they run. Instead, it tags the component with metadata during Babel compilation, which acts like a fingerprint (signature) of the hooks’ order and type — not the values or calls themselves.


⚙️ The Key Idea:

It’s static analysis at build time, not runtime execution of hook calls.

So how does this magic happen?


🔧 Step-by-Step Breakdown:

1. Babel Transforms Every Component

When you use react-refresh/babel, it analyzes your component and wraps it like this:

js
const MyComponent = __REACT_REFRESH_SIGNATURE__()(function MyComponent() { const [count, setCount] = useState(0); useEffect(() => {}, []); return <div>{count}</div>; });

But that’s just a wrapper — the real stuff happens behind the scenes:


2. Signature Registration via Babel

The Babel plugin generates something like this (simplified):

js
const $ReactRefreshSig$ = () => { const sig = runtime.createSignatureFunctionForTransform(); sig.__hmr_hook_types__ = ['useState', 'useEffect']; return sig; }; const MyComponent = $ReactRefreshSig$()(function MyComponent() { // your original code });

Now:

  • createSignatureFunctionForTransform() returns a "tracker"

  • It’s annotated with the types and order of hooks as strings

  • This is done at compile time — Babel analyzes your code and records:

    • "The first hook is useState"

    • "The second is useEffect"

    • etc.


3. Runtime Compares Hook Signatures

When you hot-reload the module:

  • react-refresh compares the new __hmr_hook_types__ array to the previous version’s.

  • If they’re identical → ✅ safe update

  • If they differ → ❌ remount the component


🔐 Why This Works

React has strict Rules of Hooks — hooks must:

  • Be called unconditionally

  • Be called in the same order

  • Be called directly in the component body

That makes static analysis (via Babel) actually feasible and reliable!


🎓 Summary

Concept What it does
__REACT_REFRESH_SIGNATURE__ A function injected by Babel that wraps your component
createSignatureFunctionForTransform() Generates an object used to store hook info
__hmr_hook_types__ An array like ["useState", "useEffect"] used to compare old/new
Tracking method Done via static analysis during Babel transformation
Runtime comparison Uses this metadata to decide if the component can be hot-swapped

Would you like to see a real-world example using Babel output or even step through one using a React + Vite + Babel playground?

So does it analyze raw code for hooks?

Yes — exactly! react-refresh/babel analyzes the raw code at build time (not runtime) to extract information about the hooks used in your component, based on how they appear in the code's structure.

It’s a form of static analysis, meaning it reads your component’s source code and determines:

  • Where hooks are called

  • In what order

  • What kind of hooks they are (useState, useEffect, etc.)

All without running the code.

Let’s dig a little deeper:


🔍 Does it "analyze raw code for hooks"?

Yes — using AST (Abstract Syntax Tree) traversal.

Here’s how it works:

1. 🧠 Babel turns your JS code into an AST

Babel parses your source code into a structured tree of nodes, representing:

  • Variables

  • Function calls

  • Import statements

  • etc.

Example:

js
function MyComponent() { const [count, setCount] = useState(0); useEffect(() => {}, []); return <div>{count}</div>; }

Turns into AST nodes like:

js
FunctionDeclaration BlockStatement VariableDeclaration (useState) ExpressionStatement (useEffect) ReturnStatement (JSX)

2. 🔍 The Plugin Traverses the AST

The react-refresh/babel plugin walks the AST to:

  • Find all functions that look like React components

  • Find any top-level calls to functions whose names match use* (like useState, useEffect, useReducer, etc.)

Since React's Rules of Hooks say you can only call hooks:

  • At the top level of your component

  • In the same order every time

…this static scan is enough to build a "hook fingerprint" of your component.


3. 🧬 It Creates the Hook Signature

Once it finds the hooks, it records:

js
['useState', 'useEffect']

And assigns that to:

js
sig.__hmr_hook_types__ = ['useState', 'useEffect'];

Then wraps the component with:

js
const MyComponent = $ReactRefreshSig$()(function MyComponent() { ... });

This is all inserted at build time by Babel.


🚫 It Does Not:

  • Execute your code

  • Evaluate the value of variables

  • Detect conditional hooks (those would break the rules!)

It’s all based on how the hooks appear statically in the code, not what they do or return.


✅ Why This Works Reliably

Because React enforces strict rules on how hooks are used, this static approach is:

  • Lightweight

  • Fast

  • Safe in dev mode


🔚 Summary

Question Answer
Does it analyze raw code? ✅ Yes, using Babel's AST system
What does it extract? The order and type of hooks used
Is it runtime or compile time? 🛠️ Compile-time, during Babel transform
Does it run your code? ❌ No, it's static analysis only

Want to see an actual AST snippet or how you could write a tiny Babel plugin that finds hooks? I can demo that too!

How to write the plugin?

Awesome — let’s build a minimal Babel plugin that detects hooks inside React components! This will help you understand how react-refresh/babel works under the hood.


🛠️ What We’ll Build

A custom Babel plugin that:

  1. Finds all React function components

  2. Detects hook calls inside them

  3. Logs or stores the list of hook types used

We’ll keep it simple: log the names of hooks like useState, useEffect, etc.


🔧 Step-by-Step: A Minimal Hook Tracker Babel Plugin

1. Plugin Boilerplate

Create a file like babel-plugin-track-hooks.js:

js
module.exports = function ({ types: t }) { return { visitor: { FunctionDeclaration(path) { if (isComponent(path)) { const hooks = findHooks(path); console.log(`🔍 ${path.node.id.name}:`, hooks); } }, VariableDeclaration(path) { // Also detect arrow function components const decl = path.node.declarations[0]; if ( decl.init && (t.isArrowFunctionExpression(decl.init) || t.isFunctionExpression(decl.init)) && isComponentFunctionName(decl.id.name) ) { const hooks = findHooks(path.get('declarations.0.init')); console.log(`🔍 ${decl.id.name}:`, hooks); } } } }; function isComponent(path) { const name = path.node.id && path.node.id.name; return isComponentFunctionName(name); } function isComponentFunctionName(name) { return name && /^[A-Z]/.test(name); } function findHooks(fnPath) { const hooks = []; fnPath.traverse({ CallExpression(callPath) { const callee = callPath.get('callee'); if (callee.isIdentifier() && /^use[A-Z0-9]/.test(callee.node.name)) { hooks.push(callee.node.name); } } }); return hooks; } };

2. Use It in Babel

Install Babel CLI if you don’t have it:

bash
npm install --save-dev @babel/core @babel/cli

Create a test file, e.g. App.jsx:

jsx
import React, { useState, useEffect } from 'react'; export default function App() { const [count, setCount] = useState(0); useEffect(() => { console.log('Mounted'); }, []); return <div>{count}</div>; }

Now run Babel with your plugin:

bash
npx babel App.jsx --plugins ./babel-plugin-track-hooks.js

You should see output like:

🔍 App: [ 'useState', 'useEffect' ]

💡 What You Just Did

✅ Parsed React components
✅ Found all top-level hook calls
✅ Matched by name pattern useXyz
✅ Logged the order and types — just like react-refresh does internally


🚀 From Here You Could...

  • Attach __hmr_hook_types__ like react-refresh does

  • Save and compare signatures across builds

  • Invalidate or refresh based on changes

Want me to extend this plugin to simulate real signature injection or show how it fits into a dev toolchain like Vite or Webpack?