Skip to content

Commit

Permalink
feat: support HMR for class components
Browse files Browse the repository at this point in the history
  • Loading branch information
ArnaudBarre committed May 8, 2024
1 parent 21eef9e commit 640ea48
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 6 deletions.
24 changes: 24 additions & 0 deletions playground/class-components/__tests__/class-components.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { expect, test } from "@playwright/test";
import { setupDevServer, setupWaitForLogs } from "../../utils.ts";

test("Class component HMR", async ({ page }) => {
const { testUrl, server, editFile } =
await setupDevServer("class-components");
const waitForLogs = await setupWaitForLogs(page);
await page.goto(testUrl);

await expect(page.locator("body")).toHaveText("Hello World");
editFile("src/App.tsx", ["World", "class components"]);
await waitForLogs("[vite] hot updated: /src/App.tsx");
await expect(page.locator("body")).toHaveText("Hello class components");

editFile("src/utils.tsx", ["Hello", "Hi"]);
await waitForLogs("[vite] hot updated: /src/App.tsx");
await expect(page.locator("body")).toHaveText("Hi class components");

editFile("src/index.tsx", ["App mounted", "App mounted!"]);
await waitForLogs("[vite] page reload: src/index.tsx");
await expect(page.locator("body")).toHaveText("Hi class components");

await server.close();
});
13 changes: 13 additions & 0 deletions playground/class-components/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + class components</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
19 changes: 19 additions & 0 deletions playground/class-components/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "class-components",
"type": "module",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@vitejs/plugin-react-swc": "../../dist"
}
}
1 change: 1 addition & 0 deletions playground/class-components/public/vite.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions playground/class-components/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Component } from "react";
import { getGetting } from "./utils.tsx";

export class App extends Component {
render() {
return <span>{getGetting()} World</span>;
}
}
9 changes: 9 additions & 0 deletions playground/class-components/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App.tsx";

createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);
1 change: 1 addition & 0 deletions playground/class-components/src/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const getGetting = () => <span>Hello</span>
23 changes: 23 additions & 0 deletions playground/class-components/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"include": ["src", "vite.config.ts"],
"compilerOptions": {
"module": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"target": "ESNext",
"jsx": "react-jsx",
"types": ["vite/client"],
"noEmit": true,
"isolatedModules": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true
}
}
6 changes: 6 additions & 0 deletions playground/class-components/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";

export default defineConfig({
plugins: [react()],
});
Binary file not shown.
19 changes: 19 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 16 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const _dirname =
const resolve = createRequire(
typeof __filename !== "undefined" ? __filename : import.meta.url,
).resolve;
const reactCompRE = /extends\s+(?:React\.)?(?:Pure)?Component/;
const refreshContentRE = /\$Refresh(?:Reg|Sig)\$\(/;

type Options = {
Expand Down Expand Up @@ -141,14 +142,21 @@ const react = (_options?: Options): PluginOption[] => {
},
);
if (!result) return;
if (!refresh) return result;

if (!refresh || !refreshContentRE.test(result.code)) {
return result;
}
const hasRefresh = refreshContentRE.test(result.code);
if (!hasRefresh && !reactCompRE.test(result.code)) return result;

const sourceMap: SourceMapPayload = JSON.parse(result.map!);
sourceMap.mappings = ";;" + sourceMap.mappings;

result.code = `import * as RefreshRuntime from "${runtimePublicPath}";
if (!window.$RefreshReg$) throw new Error("React refresh preamble was not loaded. Something is wrong.");
${result.code}`;

if (hasRefresh) {
sourceMap.mappings = ";;;;;;" + sourceMap.mappings;
result.code = `if (!window.$RefreshReg$) throw new Error("React refresh preamble was not loaded. Something is wrong.");
const prevRefreshReg = window.$RefreshReg$;
const prevRefreshSig = window.$RefreshSig$;
window.$RefreshReg$ = RefreshRuntime.getRefreshReg("${id}");
Expand All @@ -158,6 +166,10 @@ ${result.code}
window.$RefreshReg$ = prevRefreshReg;
window.$RefreshSig$ = prevRefreshSig;
`;
}

result.code += `
RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
RefreshRuntime.registerExportsForReactRefresh("${id}", currentExports);
import.meta.hot.accept((nextExports) => {
Expand All @@ -168,8 +180,6 @@ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
});
`;

const sourceMap: SourceMapPayload = JSON.parse(result.map!);
sourceMap.mappings = ";;;;;;;;" + sourceMap.mappings;
return { code: result.code, map: sourceMap };
},
},
Expand Down

0 comments on commit 640ea48

Please sign in to comment.