From 5bf1f092957a1d7a87f84aa6d2a8f51e425bca4e Mon Sep 17 00:00:00 2001 From: shulandmimi Date: Sat, 12 Oct 2024 15:31:49 +0800 Subject: [PATCH] fix: class component hmr --- .changeset/red-kids-trade.md | 5 + e2e/utils.ts | 18 ++++ examples/react-fast-refresh/README.md | 37 +++++++ examples/react-fast-refresh/e2e.spec.ts | 69 ++++++++++++ examples/react-fast-refresh/farm.config.ts | 5 + examples/react-fast-refresh/index.html | 14 +++ examples/react-fast-refresh/package.json | 24 +++++ .../react-fast-refresh/public/favicon.ico | Bin 0 -> 4154 bytes .../react-fast-refresh/src/assets/logo.png | Bin 0 -> 16859 bytes .../react-fast-refresh/src/assets/react.svg | 1 + .../src/components/ClassC.tsx | 3 + .../react-fast-refresh/src/components/FnC.tsx | 3 + examples/react-fast-refresh/src/index.css | 69 ++++++++++++ examples/react-fast-refresh/src/index.tsx | 8 ++ examples/react-fast-refresh/src/main.css | 42 ++++++++ examples/react-fast-refresh/src/main.tsx | 36 +++++++ examples/react-fast-refresh/src/typings.d.ts | 3 + examples/react-fast-refresh/tsconfig.json | 25 +++++ .../react-fast-refresh/tsconfig.node.json | 11 ++ pnpm-lock.yaml | 31 ++++++ rust-plugins/react/.eslintrc.json | 7 -- .../react/src/is_react_refresh_boundary.ts | 101 +++++++++++++----- rust-plugins/react/src/react_refresh.rs | 26 +++-- 23 files changed, 494 insertions(+), 44 deletions(-) create mode 100644 .changeset/red-kids-trade.md create mode 100644 examples/react-fast-refresh/README.md create mode 100644 examples/react-fast-refresh/e2e.spec.ts create mode 100644 examples/react-fast-refresh/farm.config.ts create mode 100644 examples/react-fast-refresh/index.html create mode 100644 examples/react-fast-refresh/package.json create mode 100644 examples/react-fast-refresh/public/favicon.ico create mode 100644 examples/react-fast-refresh/src/assets/logo.png create mode 100644 examples/react-fast-refresh/src/assets/react.svg create mode 100644 examples/react-fast-refresh/src/components/ClassC.tsx create mode 100644 examples/react-fast-refresh/src/components/FnC.tsx create mode 100644 examples/react-fast-refresh/src/index.css create mode 100644 examples/react-fast-refresh/src/index.tsx create mode 100644 examples/react-fast-refresh/src/main.css create mode 100644 examples/react-fast-refresh/src/main.tsx create mode 100644 examples/react-fast-refresh/src/typings.d.ts create mode 100644 examples/react-fast-refresh/tsconfig.json create mode 100644 examples/react-fast-refresh/tsconfig.node.json delete mode 100644 rust-plugins/react/.eslintrc.json diff --git a/.changeset/red-kids-trade.md b/.changeset/red-kids-trade.md new file mode 100644 index 000000000..e9b574662 --- /dev/null +++ b/.changeset/red-kids-trade.md @@ -0,0 +1,5 @@ +--- +"@farmfe/plugin-react": patch +--- + +fix class component hmr diff --git a/e2e/utils.ts b/e2e/utils.ts index 1fb9f45e9..404dedbd4 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -1,3 +1,5 @@ +import fs from 'node:fs/promises'; +import { isAbsolute, join } from 'node:path'; export type SimpleUnwrapArray = T extends ReadonlyArray ? P : T; export function logger(msg: any, { title = 'FARM INFO', color = 'green' } = {}) { @@ -92,3 +94,19 @@ export const concurrentMap = < >(arr: Arr, maxConcurrent: number, cb: F) => arr.map( concurrentify(maxConcurrent, cb) as any, ) as ReturnType[]; + + +export async function editFile(_filename: string, matched: string | RegExp, to: string): Promise Promise)> { + const filename = isAbsolute(_filename) ? _filename : join(process.cwd(), _filename); + const content = await fs.readFile(filename, 'utf-8') + + let newContent = content.replaceAll(matched, to); + + if(content.length !== newContent.length || content !== newContent) { + await fs.writeFile(filename, newContent); + + return async () => { + await fs.writeFile(filename, content) + } + } +} \ No newline at end of file diff --git a/examples/react-fast-refresh/README.md b/examples/react-fast-refresh/README.md new file mode 100644 index 000000000..5802e857f --- /dev/null +++ b/examples/react-fast-refresh/README.md @@ -0,0 +1,37 @@ +# Farm + React + +This template should help you start developing using React and TypeScript in Farm. + +## Setup + +Install the dependencies: + +```bash +pnpm install +``` + +## Get Started + +Start the dev server: + +```bash +pnpm start +``` + +Build the app for production: + +```bash +pnpm build +``` + +Preview the Production build product: + +```bash +pnpm preview +``` + +Clear persistent cache local files + +```bash +pnpm clean +``` diff --git a/examples/react-fast-refresh/e2e.spec.ts b/examples/react-fast-refresh/e2e.spec.ts new file mode 100644 index 000000000..2998e905a --- /dev/null +++ b/examples/react-fast-refresh/e2e.spec.ts @@ -0,0 +1,69 @@ +import { test, expect, describe } from 'vitest'; +import { startProjectAndTest } from '../../e2e/vitestSetup'; +import path, { basename, dirname, normalize } from 'path'; +import { fileURLToPath } from 'url'; +import { editFile } from '../../e2e/utils'; +import { ConsoleMessage, ElementHandle, Page } from 'playwright-chromium'; + +const name = basename(import.meta.url); +const projectPath = dirname(fileURLToPath(import.meta.url)); + +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +const waitMatchConsole = (page: Page, text: string) => new Promise((resolve) => { + let handler = (message: ConsoleMessage) => { + console.log({ message }) + if(message.text().includes(text)) { + resolve(undefined); + }; + + page.off('console', handler); + }; + page.on('console', handler); +}) + +async function testFileHmr(page: Page, element: ElementHandle, filename: string, originText: string, afterText: string) { + + const matchUpdateMessage = `[Farm HMR] ${normalize(filename)} updated`; + + let waitClassUpdate = waitMatchConsole(page, matchUpdateMessage); + + const recover = await editFile(path.join(projectPath, filename), originText, afterText); + + try { + await waitClassUpdate; + await delay(300); + expect((await element.textContent())).toContain(afterText); + } finally { + await recover?.() + } + await delay(300); +} + +describe(`e2e tests - ${name}`, async () => { + const runTest = (command: 'start' = 'start') => + startProjectAndTest( + projectPath, + async (page) => { + const root = (await page.$('#root'))!; + + expect(root).not.toBeNull(); + + const content = await root?.textContent(); + + expect(content).toContain('class component'); + + expect(content).toContain('function component'); + + await testFileHmr(page, root, './src/components/ClassC.tsx', 'class component', 'class component update'); + + await testFileHmr(page, root, './src/components/FnC.tsx', 'function component', 'function component update'); + }, + command + ); + + test(`exmaples ${name} run start`, async () => { + await runTest(); + }) + +}); diff --git a/examples/react-fast-refresh/farm.config.ts b/examples/react-fast-refresh/farm.config.ts new file mode 100644 index 000000000..477cb13e4 --- /dev/null +++ b/examples/react-fast-refresh/farm.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from '@farmfe/core'; + +export default defineConfig({ + plugins: ['@farmfe/plugin-react'] +}); diff --git a/examples/react-fast-refresh/index.html b/examples/react-fast-refresh/index.html new file mode 100644 index 000000000..56d5a1bdb --- /dev/null +++ b/examples/react-fast-refresh/index.html @@ -0,0 +1,14 @@ + + + + + + + + Farm + React + TS + + +
+ + + \ No newline at end of file diff --git a/examples/react-fast-refresh/package.json b/examples/react-fast-refresh/package.json new file mode 100644 index 000000000..d993f6b07 --- /dev/null +++ b/examples/react-fast-refresh/package.json @@ -0,0 +1,24 @@ +{ + "name": "react-fast-refresh", + "version": "1.0.0", + "scripts": { + "dev": "farm start", + "start": "farm start", + "build": "farm build", + "preview": "farm preview", + "clean": "farm clean" + }, + "dependencies": { + "react": "18", + "react-dom": "18" + }, + "devDependencies": { + "@farmfe/cli": "workspace:", + "@farmfe/core": "workspace:", + "@farmfe/plugin-react": "workspace:", + "@types/react": "18", + "core-js": "^3.36.1", + "@types/react-dom": "18", + "react-refresh": "^0.14.0" + } +} \ No newline at end of file diff --git a/examples/react-fast-refresh/public/favicon.ico b/examples/react-fast-refresh/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..80465dedc03cfd08b0e3b118db6e765f65e3adc3 GIT binary patch literal 4154 zcmb`~duWw)9LMqBoO)WB8>rdNSy794a8L80HKCYy;X53Ll~aB<(pZM`qE!25qV^T{4`B6-myS?o2hN82+<+U< zgU>Js#Y@ls0rgpHaWfVd>OhcuLiH?%JvX{-jp-L?TuqIfpde{Z+6RpMT(1M2a zNgW#BR8$vQhXMP8dvl>UUXQDxF|NSvPbf6_&zLFD zH5>=EtG%cFqj(pZ5A8>dbr{yJ+S~!fc|+tT()+LzipxT%okH!;)YStw?b>8VB6{p_in}7AeAdaJ^{r}^?eMB-Gk({ zrayu9w#~ow!{$co^?m3pP+TWG|G2Mpr`Uyk4031DEWT^Zs#|q!fzAf4HC z@HD383zV1%YP(h6O6-MVF$0><`LHpo%n?h&yyCS6;aV%P*?jSIU3mWM_tJK}3hkK- z(TTZGyGg9VBE;t=>{Gt7qs&mJ>d|=ip#xfr=c5gZ$yw07U$FsIX?|Ok>qC96J=cd; z@;YC2-m6XRg(lYaG*Z2nG~YT0)YowAdafLws6MUp<@g2%pfgBwk;0cy``Y{OLgf^v zvdn?TV0Do;U>(}g2+jRrsC}dJR{Q2sg!{9Maj?GBP`Bpc6{z<{_vLJy;6Olit;eS4G)6KtfV<)|&@?~GFW7k{s0_}^bcdli`x%y$}s)w9QNY*W`%sMACqBL=U`#(}{kZI}9O!ob|625;;!v7E?e72>_ YXKTD4qPpQwz4tCb{gqHVI7FV$f0MB}F8}}l literal 0 HcmV?d00001 diff --git a/examples/react-fast-refresh/src/assets/logo.png b/examples/react-fast-refresh/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0caeb4381267cff0c3adf3020077c55bac612a20 GIT binary patch literal 16859 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 literal 0 HcmV?d00001 diff --git a/examples/react-fast-refresh/src/assets/react.svg b/examples/react-fast-refresh/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/examples/react-fast-refresh/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/react-fast-refresh/src/components/ClassC.tsx b/examples/react-fast-refresh/src/components/ClassC.tsx new file mode 100644 index 000000000..44d14ca3c --- /dev/null +++ b/examples/react-fast-refresh/src/components/ClassC.tsx @@ -0,0 +1,3 @@ +export function ClassC() { + return
hello class component
+} \ No newline at end of file diff --git a/examples/react-fast-refresh/src/components/FnC.tsx b/examples/react-fast-refresh/src/components/FnC.tsx new file mode 100644 index 000000000..5641b3f66 --- /dev/null +++ b/examples/react-fast-refresh/src/components/FnC.tsx @@ -0,0 +1,3 @@ +export function FnC() { + return
hello function component
; +} diff --git a/examples/react-fast-refresh/src/index.css b/examples/react-fast-refresh/src/index.css new file mode 100644 index 000000000..6cc4daf98 --- /dev/null +++ b/examples/react-fast-refresh/src/index.css @@ -0,0 +1,69 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #9f1a8f; + text-decoration: inherit; +} +a:hover { + color: #9f1a8f; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #9f1a8f; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #9F1A8F; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/react-fast-refresh/src/index.tsx b/examples/react-fast-refresh/src/index.tsx new file mode 100644 index 000000000..952e71d91 --- /dev/null +++ b/examples/react-fast-refresh/src/index.tsx @@ -0,0 +1,8 @@ +import { createRoot } from "react-dom/client"; +import { Main } from "./main"; +import "./index.css"; + +const container = document.querySelector("#root"); +const root = createRoot(container!); + +root.render(
); diff --git a/examples/react-fast-refresh/src/main.css b/examples/react-fast-refresh/src/main.css new file mode 100644 index 000000000..ff29d4128 --- /dev/null +++ b/examples/react-fast-refresh/src/main.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #9F1A8Faa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/examples/react-fast-refresh/src/main.tsx b/examples/react-fast-refresh/src/main.tsx new file mode 100644 index 000000000..b2dad9a45 --- /dev/null +++ b/examples/react-fast-refresh/src/main.tsx @@ -0,0 +1,36 @@ +import { useState } from "react"; +import "./main.css"; +import reactLogo from "./assets/react.svg"; +import FarmLogo from "./assets/logo.png"; +import { ClassC } from "./components/ClassC"; +import { FnC } from "./components/FnC"; +export function Main() { + const [count, setCount] = useState(0); + + return ( + <> + +

Farm + React

+ + +
+ +

+ Edit src/main.tsx and save to test HMR +

+
+

+ Click on the Farm and React logos to learn more +

+ + ); +} diff --git a/examples/react-fast-refresh/src/typings.d.ts b/examples/react-fast-refresh/src/typings.d.ts new file mode 100644 index 000000000..fa0a2da54 --- /dev/null +++ b/examples/react-fast-refresh/src/typings.d.ts @@ -0,0 +1,3 @@ +declare module '*.svg'; +declare module '*.png'; +declare module '*.css'; diff --git a/examples/react-fast-refresh/tsconfig.json b/examples/react-fast-refresh/tsconfig.json new file mode 100644 index 000000000..7a7611e4a --- /dev/null +++ b/examples/react-fast-refresh/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/examples/react-fast-refresh/tsconfig.node.json b/examples/react-fast-refresh/tsconfig.node.json new file mode 100644 index 000000000..8d4232518 --- /dev/null +++ b/examples/react-fast-refresh/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["farm.config.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83a8adc39..b8d05d7f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1015,6 +1015,37 @@ importers: specifier: ^0.14.0 version: 0.14.0 + examples/react-fast-refresh: + dependencies: + react: + specifier: '18' + version: 18.2.0 + react-dom: + specifier: '18' + version: 18.2.0(react@18.2.0) + devDependencies: + '@farmfe/cli': + specifier: 'workspace:' + version: link:../../packages/cli + '@farmfe/core': + specifier: workspace:* + version: link:../../packages/core + '@farmfe/plugin-react': + specifier: 'workspace:' + version: link:../../rust-plugins/react + '@types/react': + specifier: '18' + version: 18.2.35 + '@types/react-dom': + specifier: '18' + version: 18.2.14 + core-js: + specifier: ^3.36.1 + version: 3.37.1 + react-refresh: + specifier: ^0.14.0 + version: 0.14.2 + examples/react-query: dependencies: '@tanstack/react-query': diff --git a/rust-plugins/react/.eslintrc.json b/rust-plugins/react/.eslintrc.json deleted file mode 100644 index 2c3c080c7..000000000 --- a/rust-plugins/react/.eslintrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "root": true, - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - } -} diff --git a/rust-plugins/react/src/is_react_refresh_boundary.ts b/rust-plugins/react/src/is_react_refresh_boundary.ts index 80687d34c..c9782114b 100644 --- a/rust-plugins/react/src/is_react_refresh_boundary.ts +++ b/rust-plugins/react/src/is_react_refresh_boundary.ts @@ -1,43 +1,92 @@ // @ts-ignore -import React from 'react'; +import Refresh from 'react-refresh'; -function isClassComponent(v: any): boolean { -// class component can not be hot-updated, so skip it and find its parent - if(v && v.prototype instanceof React.Component) { - return true; +type ModExport = Record; + +// from: https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/src/refreshUtils.js + +// @ts-ignore +export function validateRefreshBoundaryAndEnqueueUpdate(prevExports: ModExport, nextExports: ModExport) { + const ignoredExports = [] + 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)' } - return false; -} + let hasExports = false + const allExportsAreComponentsOrUnchanged = predicateOnExport( + ignoredExports, + nextExports, + (key, value) => { + hasExports = true + if (Refresh.isLikelyComponentType(value)) return true + return prevExports[key] === nextExports[key] + }, + ) -export default function isReactRefreshBoundary(Refresh: any, moduleExports: any): boolean { + if (hasExports && allExportsAreComponentsOrUnchanged === true) { + enqueueUpdate() + } else { + return `Could not Fast Refresh ("${allExportsAreComponentsOrUnchanged}" export is incompatible).` + } +} - if (Refresh.isLikelyComponentType(moduleExports)) { - if(isClassComponent(moduleExports)) return false; - return true; +function predicateOnExport(ignoredExports: string[], moduleExports: ModExport, predicate: (key: string, value: any) => boolean): string | true { + for (const key in moduleExports) { + if (key === '__esModule') continue + if (ignoredExports.includes(key)) continue + if (!predicate(key, moduleExports[key])) return key } + return true +} +function debounce(func: (...args: Args) => void, wait: number, immediate?: boolean) { + var timeout: any; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +}; + +const _enqueueUpdate = () => { + Refresh.performReactRefresh() +} + +export var registerExportsForReactRefresh = (moduleExports: ModExport, moduleID: string) => { if (moduleExports == null || typeof moduleExports !== 'object') { // Exit if we can't iterate over exports. - return false; + // (This is important for legacy environments.) + return; } - let hasExports = false; - let areAllExportsComponents = true; - for (const key in moduleExports) { - hasExports = true; - if (key === '__esModule') { - continue; - } - const exportValue = moduleExports[key]; - if(Refresh.isLikelyComponentType(exportValue)) { - if(isClassComponent(exportValue)) return false; - }else { - areAllExportsComponents = false; + const typeID = moduleID + ' exports ' + key; + Refresh.register(exportValue, typeID); } } - return hasExports && areAllExportsComponents; -}; \ No newline at end of file +}; + +export const enqueueUpdate = debounce(_enqueueUpdate, 30); \ No newline at end of file diff --git a/rust-plugins/react/src/react_refresh.rs b/rust-plugins/react/src/react_refresh.rs index 1ef939ec8..5ae6417a6 100644 --- a/rust-plugins/react/src/react_refresh.rs +++ b/rust-plugins/react/src/react_refresh.rs @@ -24,7 +24,7 @@ lazy_static! { prevRefreshReg = window.$RefreshReg$; prevRefreshSig = window.$RefreshSig$; window.$RefreshReg$ = (type, id) => {{ - RefreshRuntime.register(type, {FARM_MODULE}.id + id); + RefreshRuntime.register(type, {FARM_MODULE}.id + " " + id); }}; window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform; "# @@ -35,16 +35,19 @@ lazy_static! { const POST_CODE: &str = r#" window.$RefreshReg$ = prevRefreshReg; window.$RefreshSig$ = prevRefreshSig; - if (import.meta.hot) { - import.meta.hot.accept(mod => { - if (!isReactRefreshBoundary(RefreshRuntime, mod)) { - import.meta.hot.invalidate(`Not all exports of ${module.id} are react components`); - } - }); -} + ReactRefreshUtil.registerExportsForReactRefresh(module.exports, module.id); -RefreshRuntime.performReactRefresh(); + import.meta.hot.accept(nextExport => { + const message = ReactRefreshUtil.validateRefreshBoundaryAndEnqueueUpdate(module.exports, nextExport); + + if (message) { + import.meta.hot.invalidate(message); + } + }); + + ReactRefreshUtil.enqueueUpdate(); +} "#; fn inject_runtime_import(lib: &Library, ast: &mut SwcModule) { @@ -71,9 +74,9 @@ fn inject_runtime_import(lib: &Library, ast: &mut SwcModule) { // inject react boundary detection let react_boundary_import = - format!("import isReactRefreshBoundary from '{IS_REACT_REFRESH_BOUNDARY}'"); + format!("import * as ReactRefreshUtil from '{IS_REACT_REFRESH_BOUNDARY}'"); let react_boundary_import_decl = - parse_import_decl("isReactRefreshBoundary", &react_boundary_import); + parse_import_decl("ReactRefreshUtil", &react_boundary_import); ast.body.insert( 0, @@ -112,6 +115,7 @@ fn inject_post_code(lib: &Library, ast: &mut SwcModule) { ast.body.extend(module.body); } +// [react fast refresh](https://github.com/facebook/react/issues/16604#issuecomment-528663101) pub fn inject_react_refresh(lib: &Library, ast: &mut SwcModule) { inject_runtime_import(lib, ast); inject_pre_code(lib, ast);