Skip to content

Commit

Permalink
feat: create vite-plugin-simple-hmr (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa authored Feb 9, 2024
1 parent e91a10f commit 230754f
Show file tree
Hide file tree
Showing 21 changed files with 749 additions and 23 deletions.
22 changes: 22 additions & 0 deletions packages/vite-plugin-simple-hmr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# vite-plugin-simple-hmr

Simple HMR to reassign all exports, which can be useful for [SSR HMR](https://github.com/vitejs/vite/pull/12165) to accompany with React plugin's client HMR.

## example

See [./examples/react](./examples/react).

```tsx
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { vitePluginSimpleHmr } from "@hiogawa/vite-plugin-simple-hmr";

export default defineConfig({
plugins: [
react(),
vitePluginSimpleHmr({
include: ["**/*.tsx"],
}),
],
});
```
3 changes: 3 additions & 0 deletions packages/vite-plugin-simple-hmr/examples/react/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```sh
pnpm -C packages/vite-plugin-hmr/examples/react dev
```
12 changes: 12 additions & 0 deletions packages/vite-plugin-simple-hmr/examples/react/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vite-plugin-hmr-ssr-react</title>
</head>
<body>
<div id="root"><!--@INJECT_SSR@--></div>
<script type="module" src="/src/entry-client.tsx"></script>
</body>
</html>
19 changes: 19 additions & 0 deletions packages/vite-plugin-simple-hmr/examples/react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@hiogawa/vite-plugin-hmr-example-react",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@hiogawa/vite-plugin-simple-hmr": "workspace:*",
"@hiogawa/vite-plugin-ssr-middleware": "workspace:*",
"@types/react": "18.2.12",
"@types/react-dom": "18.2.5"
}
}
23 changes: 23 additions & 0 deletions packages/vite-plugin-simple-hmr/examples/react/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useState } from "react";
import { AppDep1, AppDep2 } from "./AppDep";
import { AppDepDep } from "./AppDepDep";

export function App() {
const [count, setCount] = useState(0);

return (
<>
<h1>ssr + hmr + react</h1>
<div>
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
</div>
<div>
<AppDep1 />
<AppDep2 />
<AppDepDep />
</div>
</>
);
}
13 changes: 13 additions & 0 deletions packages/vite-plugin-simple-hmr/examples/react/src/AppDep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { AppDepDep } from "./AppDepDep";

export function AppDep1() {
return <div>AppDep1</div>;
}

export const AppDep2 = () => {
return (
<div>
AppDep2 - <AppDepDep />
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function AppDepDep() {
return <span>AppDepDep</span>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { hydrateRoot } from "react-dom/client";
import { App } from "./App";

function main() {
hydrateRoot(document.getElementById("root")!, <App />);
}

main();
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import fs from "node:fs";
import type http from "node:http";
import { renderToString } from "react-dom/server";
import type { ViteDevServer } from "vite";
import { App } from "./App";

export default async function handler(
req: http.IncomingMessage & { viteDevServer: ViteDevServer },
res: http.ServerResponse
) {
let html = await fs.promises.readFile("./index.html", "utf-8");
html = await req.viteDevServer.transformIndexHtml("/", html);

const ssrHtml = renderToString(<App />);
html = html.replace("<!--@INJECT_SSR@-->", ssrHtml);

res.setHeader("content-type", "text/html").end(html);
}
7 changes: 7 additions & 0 deletions packages/vite-plugin-simple-hmr/examples/react/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"types": ["vite/client"],
"jsx": "react-jsx"
}
}
18 changes: 18 additions & 0 deletions packages/vite-plugin-simple-hmr/examples/react/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { vitePluginSimpleHmr } from "@hiogawa/vite-plugin-simple-hmr";
import { vitePluginSsrMiddleware } from "@hiogawa/vite-plugin-ssr-middleware";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

export default defineConfig({
clearScreen: false,
plugins: [
react(),
vitePluginSimpleHmr({
include: ["**/*.tsx"],
}),
vitePluginSsrMiddleware({
entry: "/src/entry-server.tsx",
mode: "ViteRuntime",
}),
],
});
43 changes: 43 additions & 0 deletions packages/vite-plugin-simple-hmr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@hiogawa/vite-plugin-simple-hmr",
"version": "0.0.1-pre.0",
"homepage": "https://github.com/hi-ogawa/vite-plugins/tree/main/packages/vite-plugin-simple-hmr",
"repository": {
"type": "git",
"url": "https://github.com/hi-ogawa/vite-plugins",
"directory": "packages/vite-plugin-simple-hmr"
},
"license": "MIT",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./runtime": {
"types": "./dist/runtime.d.ts",
"default": "./dist/runtime.js"
}
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"test": "vitest",
"release": "pnpm publish --no-git-checks --access public"
},
"dependencies": {
"magic-string": "^0.30.6"
},
"devDependencies": {
"@types/estree": "^1.0.5",
"vite": "^5.1.0"
},
"peerDependencies": {
"vite": "*"
}
}
183 changes: 183 additions & 0 deletions packages/vite-plugin-simple-hmr/src/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`hmrTransform > 0 - basic > analyzeExports 1`] = `
{
"errors": [],
"exportIds": [
"Test1",
"Test2",
"Test3",
"Test4",
],
}
`;

exports[`hmrTransform > 0 - basic > hmrTransform 1`] = `
{
"code": "
export function Test1() {}
export let Test2 = () => {}
export /*some*/ let /*comment*/ Test3 = () => {}
export default function Test4() {}
if (import.meta.env.SSR && import.meta.hot) {
const $$hmr = await import("@hiogawa/vite-plugin-simple-hmr/runtime");
const $$registry = $$hmr.createRegistry();
$$registry.exports["Test1"] = {
value: Test1,
update: ($$next) => {
Test1 = $$next;
}
};
$$registry.exports["Test2"] = {
value: Test2,
update: ($$next) => {
Test2 = $$next;
}
};
$$registry.exports["Test3"] = {
value: Test3,
update: ($$next) => {
Test3 = $$next;
}
};
$$registry.exports["Test4"] = {
value: Test4,
update: ($$next) => {
Test4 = $$next;
}
};
$$hmr.setupHot(import.meta.hot, $$registry);
import.meta.hot.accept;
}
",
"map": SourceMap {
"file": undefined,
"mappings": "AAAA;AACA;AACA;AACA;AACA;AACA,mBAAqB;AACrB;AACA;",
"names": [],
"sources": [
"",
],
"sourcesContent": undefined,
"version": 3,
},
}
`;

exports[`hmrTransform > 1 - separate named export (unsupported) > analyzeExports 1`] = `
{
"errors": [
"export { SomeNamed };",
],
"exportIds": [],
}
`;

exports[`hmrTransform > 1 - separate named export (unsupported) > hmrTransform 1`] = `
{
"code": "
const SomeNamed = () => {};
export { SomeNamed };
if (import.meta.env.SSR && import.meta.hot) {
import.meta.hot.accept(() => {
import.meta.hot.invalidate("unsupported usage: export { SomeNamed };")
});
}
",
"map": SourceMap {
"file": undefined,
"mappings": "AAAA;AACA;AACA;",
"names": [],
"sources": [
"",
],
"sourcesContent": undefined,
"version": 3,
},
}
`;

exports[`hmrTransform > 2 - separate default export (unsupported) > analyzeExports 1`] = `
{
"errors": [
"export default SomeDefault;",
],
"exportIds": [],
}
`;

exports[`hmrTransform > 2 - separate default export (unsupported) > hmrTransform 1`] = `
{
"code": "
const SomeDeafult = () => {};
export default SomeDefault;
if (import.meta.env.SSR && import.meta.hot) {
import.meta.hot.accept(() => {
import.meta.hot.invalidate("unsupported usage: export default SomeDefault;")
});
}
",
"map": SourceMap {
"file": undefined,
"mappings": "AAAA;AACA;AACA;",
"names": [],
"sources": [
"",
],
"sourcesContent": undefined,
"version": 3,
},
}
`;

exports[`hmrTransform > 3 - anonymous default export (unsupported) > analyzeExports 1`] = `
{
"errors": [
"export default () => {};",
],
"exportIds": [],
}
`;

exports[`hmrTransform > 3 - anonymous default export (unsupported) > hmrTransform 1`] = `
{
"code": "
export default () => {};
if (import.meta.env.SSR && import.meta.hot) {
import.meta.hot.accept(() => {
import.meta.hot.invalidate("unsupported usage: export default () => {};")
});
}
",
"map": SourceMap {
"file": undefined,
"mappings": "AAAA;AACA;",
"names": [],
"sources": [
"",
],
"sourcesContent": undefined,
"version": 3,
},
}
`;
Loading

0 comments on commit 230754f

Please sign in to comment.