From 230754fd1fe5276f4a3dbfeea7673d37eb8916e9 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 9 Feb 2024 11:38:11 +0900 Subject: [PATCH] feat: create `vite-plugin-simple-hmr` (#157) --- packages/vite-plugin-simple-hmr/README.md | 22 +++ .../examples/react/README.md | 3 + .../examples/react/index.html | 12 ++ .../examples/react/package.json | 19 ++ .../examples/react/src/App.tsx | 23 +++ .../examples/react/src/AppDep.tsx | 13 ++ .../examples/react/src/AppDepDep.tsx | 3 + .../examples/react/src/entry-client.tsx | 8 + .../examples/react/src/entry-server.tsx | 18 ++ .../examples/react/tsconfig.json | 7 + .../examples/react/vite.config.ts | 18 ++ packages/vite-plugin-simple-hmr/package.json | 43 ++++ .../src/__snapshots__/index.test.ts.snap | 183 ++++++++++++++++++ .../vite-plugin-simple-hmr/src/index.test.ts | 61 ++++++ packages/vite-plugin-simple-hmr/src/index.ts | 1 + packages/vite-plugin-simple-hmr/src/plugin.ts | 24 +++ .../vite-plugin-simple-hmr/src/runtime.ts | 66 +++++++ .../vite-plugin-simple-hmr/src/transform.ts | 150 ++++++++++++++ packages/vite-plugin-simple-hmr/tsconfig.json | 7 + .../vite-plugin-simple-hmr/tsup.config.ts | 8 + pnpm-lock.yaml | 83 +++++--- 21 files changed, 749 insertions(+), 23 deletions(-) create mode 100644 packages/vite-plugin-simple-hmr/README.md create mode 100644 packages/vite-plugin-simple-hmr/examples/react/README.md create mode 100644 packages/vite-plugin-simple-hmr/examples/react/index.html create mode 100644 packages/vite-plugin-simple-hmr/examples/react/package.json create mode 100644 packages/vite-plugin-simple-hmr/examples/react/src/App.tsx create mode 100644 packages/vite-plugin-simple-hmr/examples/react/src/AppDep.tsx create mode 100644 packages/vite-plugin-simple-hmr/examples/react/src/AppDepDep.tsx create mode 100644 packages/vite-plugin-simple-hmr/examples/react/src/entry-client.tsx create mode 100644 packages/vite-plugin-simple-hmr/examples/react/src/entry-server.tsx create mode 100644 packages/vite-plugin-simple-hmr/examples/react/tsconfig.json create mode 100644 packages/vite-plugin-simple-hmr/examples/react/vite.config.ts create mode 100644 packages/vite-plugin-simple-hmr/package.json create mode 100644 packages/vite-plugin-simple-hmr/src/__snapshots__/index.test.ts.snap create mode 100644 packages/vite-plugin-simple-hmr/src/index.test.ts create mode 100644 packages/vite-plugin-simple-hmr/src/index.ts create mode 100644 packages/vite-plugin-simple-hmr/src/plugin.ts create mode 100644 packages/vite-plugin-simple-hmr/src/runtime.ts create mode 100644 packages/vite-plugin-simple-hmr/src/transform.ts create mode 100644 packages/vite-plugin-simple-hmr/tsconfig.json create mode 100644 packages/vite-plugin-simple-hmr/tsup.config.ts diff --git a/packages/vite-plugin-simple-hmr/README.md b/packages/vite-plugin-simple-hmr/README.md new file mode 100644 index 000000000..56196bf31 --- /dev/null +++ b/packages/vite-plugin-simple-hmr/README.md @@ -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"], + }), + ], +}); +``` diff --git a/packages/vite-plugin-simple-hmr/examples/react/README.md b/packages/vite-plugin-simple-hmr/examples/react/README.md new file mode 100644 index 000000000..2a578ecec --- /dev/null +++ b/packages/vite-plugin-simple-hmr/examples/react/README.md @@ -0,0 +1,3 @@ +```sh +pnpm -C packages/vite-plugin-hmr/examples/react dev +``` diff --git a/packages/vite-plugin-simple-hmr/examples/react/index.html b/packages/vite-plugin-simple-hmr/examples/react/index.html new file mode 100644 index 000000000..b11e3bffd --- /dev/null +++ b/packages/vite-plugin-simple-hmr/examples/react/index.html @@ -0,0 +1,12 @@ + + + + + + vite-plugin-hmr-ssr-react + + +
+ + + diff --git a/packages/vite-plugin-simple-hmr/examples/react/package.json b/packages/vite-plugin-simple-hmr/examples/react/package.json new file mode 100644 index 000000000..f324b5914 --- /dev/null +++ b/packages/vite-plugin-simple-hmr/examples/react/package.json @@ -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" + } +} diff --git a/packages/vite-plugin-simple-hmr/examples/react/src/App.tsx b/packages/vite-plugin-simple-hmr/examples/react/src/App.tsx new file mode 100644 index 000000000..bc4a4f104 --- /dev/null +++ b/packages/vite-plugin-simple-hmr/examples/react/src/App.tsx @@ -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 ( + <> +

ssr + hmr + react

+
+ +
+
+ + + +
+ + ); +} diff --git a/packages/vite-plugin-simple-hmr/examples/react/src/AppDep.tsx b/packages/vite-plugin-simple-hmr/examples/react/src/AppDep.tsx new file mode 100644 index 000000000..4886cfbc4 --- /dev/null +++ b/packages/vite-plugin-simple-hmr/examples/react/src/AppDep.tsx @@ -0,0 +1,13 @@ +import { AppDepDep } from "./AppDepDep"; + +export function AppDep1() { + return
AppDep1
; +} + +export const AppDep2 = () => { + return ( +
+ AppDep2 - +
+ ); +}; diff --git a/packages/vite-plugin-simple-hmr/examples/react/src/AppDepDep.tsx b/packages/vite-plugin-simple-hmr/examples/react/src/AppDepDep.tsx new file mode 100644 index 000000000..0a8e60172 --- /dev/null +++ b/packages/vite-plugin-simple-hmr/examples/react/src/AppDepDep.tsx @@ -0,0 +1,3 @@ +export function AppDepDep() { + return AppDepDep; +} diff --git a/packages/vite-plugin-simple-hmr/examples/react/src/entry-client.tsx b/packages/vite-plugin-simple-hmr/examples/react/src/entry-client.tsx new file mode 100644 index 000000000..e7f5c997d --- /dev/null +++ b/packages/vite-plugin-simple-hmr/examples/react/src/entry-client.tsx @@ -0,0 +1,8 @@ +import { hydrateRoot } from "react-dom/client"; +import { App } from "./App"; + +function main() { + hydrateRoot(document.getElementById("root")!, ); +} + +main(); diff --git a/packages/vite-plugin-simple-hmr/examples/react/src/entry-server.tsx b/packages/vite-plugin-simple-hmr/examples/react/src/entry-server.tsx new file mode 100644 index 000000000..dc8124e39 --- /dev/null +++ b/packages/vite-plugin-simple-hmr/examples/react/src/entry-server.tsx @@ -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(); + html = html.replace("", ssrHtml); + + res.setHeader("content-type", "text/html").end(html); +} diff --git a/packages/vite-plugin-simple-hmr/examples/react/tsconfig.json b/packages/vite-plugin-simple-hmr/examples/react/tsconfig.json new file mode 100644 index 000000000..389c44df1 --- /dev/null +++ b/packages/vite-plugin-simple-hmr/examples/react/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "types": ["vite/client"], + "jsx": "react-jsx" + } +} diff --git a/packages/vite-plugin-simple-hmr/examples/react/vite.config.ts b/packages/vite-plugin-simple-hmr/examples/react/vite.config.ts new file mode 100644 index 000000000..24e58e177 --- /dev/null +++ b/packages/vite-plugin-simple-hmr/examples/react/vite.config.ts @@ -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", + }), + ], +}); diff --git a/packages/vite-plugin-simple-hmr/package.json b/packages/vite-plugin-simple-hmr/package.json new file mode 100644 index 000000000..c4ee97a63 --- /dev/null +++ b/packages/vite-plugin-simple-hmr/package.json @@ -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": "*" + } +} diff --git a/packages/vite-plugin-simple-hmr/src/__snapshots__/index.test.ts.snap b/packages/vite-plugin-simple-hmr/src/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000..44cef6fbe --- /dev/null +++ b/packages/vite-plugin-simple-hmr/src/__snapshots__/index.test.ts.snap @@ -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, + }, +} +`; diff --git a/packages/vite-plugin-simple-hmr/src/index.test.ts b/packages/vite-plugin-simple-hmr/src/index.test.ts new file mode 100644 index 000000000..b7b84ae0d --- /dev/null +++ b/packages/vite-plugin-simple-hmr/src/index.test.ts @@ -0,0 +1,61 @@ +import MagicString from "magic-string"; +import { parseAstAsync } from "vite"; +import { describe, expect, it } from "vitest"; +import { analyzeExports, hmrTransform } from "./transform"; + +describe(hmrTransform, () => { + const examples = [ + [ + "basic", + ` +export function Test1() {} + +export let Test2 = () => {} + +export /*some*/ const /*comment*/ Test3 = () => {} + +export default function Test4() {} +`, + ], + [ + "separate named export (unsupported)", + ` +const SomeNamed = () => {}; +export { SomeNamed }; +`, + ], + [ + "separate default export (unsupported)", + ` +const SomeDeafult = () => {}; +export default SomeDefault; +`, + ], + [ + "anonymous default export (unsupported)", + ` +export default () => {}; +`, + ], + ] as const; + + examples.forEach(([title, code], i) => { + describe(`${i} - ${title}`, () => { + it(analyzeExports, async () => { + const ast = await parseAstAsync(code); + const { exportIds, errors } = analyzeExports( + new MagicString(code), + ast as any + ); + expect({ + exportIds, + errors: errors.map((e) => code.slice(e.node.start, e.node.end)), + }).toMatchSnapshot(); + }); + + it(hmrTransform, async () => { + expect(await hmrTransform(code)).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/packages/vite-plugin-simple-hmr/src/index.ts b/packages/vite-plugin-simple-hmr/src/index.ts new file mode 100644 index 000000000..4c151932c --- /dev/null +++ b/packages/vite-plugin-simple-hmr/src/index.ts @@ -0,0 +1 @@ +export { vitePluginSimpleHmr } from "./plugin"; diff --git a/packages/vite-plugin-simple-hmr/src/plugin.ts b/packages/vite-plugin-simple-hmr/src/plugin.ts new file mode 100644 index 000000000..97cae966b --- /dev/null +++ b/packages/vite-plugin-simple-hmr/src/plugin.ts @@ -0,0 +1,24 @@ +import { type FilterPattern, type Plugin, createFilter } from "vite"; +import { name as packageName } from "../package.json"; +import { hmrTransform } from "./transform"; + +export function vitePluginSimpleHmr(pluginOpts: { + include?: FilterPattern; + exclude?: FilterPattern; +}): Plugin { + const filter = createFilter( + pluginOpts.include, + pluginOpts.exclude ?? ["**/node_modules/**"] + ); + + return { + name: packageName, + apply: "serve", + transform(code, id, options) { + if (options?.ssr && filter(id)) { + return hmrTransform(code); + } + return; + }, + }; +} diff --git a/packages/vite-plugin-simple-hmr/src/runtime.ts b/packages/vite-plugin-simple-hmr/src/runtime.ts new file mode 100644 index 000000000..cc5839eb6 --- /dev/null +++ b/packages/vite-plugin-simple-hmr/src/runtime.ts @@ -0,0 +1,66 @@ +import { name as packageName } from "../package.json"; + +const REGISTRY_KEY = Symbol.for(`${packageName}:registry`); + +export interface ViteHot { + data: { + [REGISTRY_KEY]?: Registry; + }; + accept: (cb: (exports?: unknown) => void) => void; + invalidate: (message?: string) => void; +} + +interface Export { + value: unknown; + update: (next: unknown) => void; +} + +interface Registry { + exports: Record; + // keep track of all exports of hot update history + // since currently "writer" is responsible to keep old module up-to-date + // where each old export could be used by other modules at any point in time + // but this approach obviously leaks memory indefinitely + // (alternative is to let "reader" be responsible for looking up latest module using proxy) + history: Record[]; +} + +export function createRegistry(): Registry { + const exports = {}; + return { exports, history: [exports] }; +} + +function patchRegistry(current: Registry, next: Registry): boolean { + // replace all exports in history or full reload + const keys = [ + ...new Set([...Object.keys(current.exports), ...Object.keys(next.exports)]), + ]; + const mismatches = keys.filter( + (key) => !(key in current.exports && key in next.exports) + ); + if (mismatches.length > 0) { + console.log("[simple-hmr] mismatch: ", mismatches.join(", ")); + return false; + } + for (const key of keys) { + console.log("[simple-hmr]", key); + for (const e of current.history) { + e[key]!.update(next.exports[key]!.value); + } + } + next.history = current.history; + next.history.push(next.exports); + return true; +} + +export function setupHot(hot: ViteHot, registry: Registry) { + hot.data[REGISTRY_KEY] = registry; + + hot.accept((newExports) => { + const next = hot.data[REGISTRY_KEY]; + const ok = newExports && next && patchRegistry(registry, next); + if (!ok) { + hot.invalidate(); + } + }); +} diff --git a/packages/vite-plugin-simple-hmr/src/transform.ts b/packages/vite-plugin-simple-hmr/src/transform.ts new file mode 100644 index 000000000..b53bafd64 --- /dev/null +++ b/packages/vite-plugin-simple-hmr/src/transform.ts @@ -0,0 +1,150 @@ +import type { Node, Program } from "estree"; +import MagicString from "magic-string"; +import { parseAstAsync } from "vite"; +import { name as packageName } from "../package.json"; + +export async function hmrTransform(code: string) { + const magic = new MagicString(code); + + const ast = await parseAstAsync(code); + const result = analyzeExports(magic, ast as any); + + if (result.errors.length > 0) { + const node = result.errors[0]!.node; + const message = "unsupported usage: " + code.slice(node.start, node.end); + magic.append("\n" + generateFooterUnsupported(message)); + } else { + magic.append("\n" + generateFooter(result.exportIds)); + } + + return { + code: magic.toString(), + map: magic.generateMap(), + }; +} + +function generateFooter(names: string[]) { + const parts = names.map( + (name) => ` + $$registry.exports["${name}"] = { + value: ${name}, + update: ($$next) => { + ${name} = $$next; + } + }; +` + ); + + // requires dummy "hot.accept" for vite to detect + return ` +if (import.meta.env.SSR && import.meta.hot) { + const $$hmr = await import("${packageName}/runtime"); + const $$registry = $$hmr.createRegistry(); + +${parts.join("\n")} + + $$hmr.setupHot(import.meta.hot, $$registry); + import.meta.hot.accept; +} +`; +} + +// always invalidate on unsupported usage +function generateFooterUnsupported(message: string) { + return ` +if (import.meta.env.SSR && import.meta.hot) { + import.meta.hot.accept(() => { + import.meta.hot.invalidate(${JSON.stringify(message)}) + }); +} +`; +} + +// traverse export declaration statements based on +// https://github.com/vitejs/vite/blob/fc2bceb09fb65cc6dc843462f51506586251a703/packages/vite/src/node/ssr/ssrTransform.ts#L172 + +declare module "estree" { + interface BaseNode { + start: number; + end: number; + } +} + +export function analyzeExports(code: MagicString, ast: Program) { + // extract exported top-level identifiers + const exportIds: string[] = []; + + // unsupported export usage + const errors: { node: Node }[] = []; + + for (const node of ast.body) { + // named exports + if (node.type === "ExportNamedDeclaration") { + if (node.declaration) { + if ( + node.declaration.type === "FunctionDeclaration" || + node.declaration.type === "ClassDeclaration" + ) { + /** + * export function foo() {} + */ + exportIds.push(node.declaration.id.name); + } else if (node.declaration.type === "VariableDeclaration") { + /** + * export const foo = 1, bar = 2 + */ + if (node.declaration.kind === "const") { + // rewrite from "const" to "let" + code.remove(node.declaration.start, node.declaration.start + 5); + code.appendLeft(node.declaration.start, "let"); + } + for (const decl of node.declaration.declarations) { + if (decl.id.type === "Identifier") { + exportIds.push(decl.id.name); + } else { + errors.push({ node: decl }); + } + } + } + } else { + if (node.source) { + /** + * export { foo, bar } from './foo' + */ + } else { + /** + * export { foo, bar } + */ + // TODO: support by analyzing scope? or just rewrite all top level `const` into `let`? + errors.push({ node }); + } + } + } + + // default export + if (node.type === "ExportDefaultDeclaration") { + if ( + (node.declaration.type === "FunctionDeclaration" || + node.declaration.type === "ClassExpression") && + node.declaration.id + ) { + /** + * export default function foo() {} + * export default class A {} + */ + exportIds.push(node.declaration.id.name); + } else { + // anonymous default exports + errors.push({ node }); + } + } + + /** + * export * from './foo' + */ + if (node.type === "ExportAllDeclaration") { + } + } + + return { exportIds, errors }; +} diff --git a/packages/vite-plugin-simple-hmr/tsconfig.json b/packages/vite-plugin-simple-hmr/tsconfig.json new file mode 100644 index 000000000..b42d82b9a --- /dev/null +++ b/packages/vite-plugin-simple-hmr/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "jsx": "react-jsx" + } +} diff --git a/packages/vite-plugin-simple-hmr/tsup.config.ts b/packages/vite-plugin-simple-hmr/tsup.config.ts new file mode 100644 index 000000000..65c51f39b --- /dev/null +++ b/packages/vite-plugin-simple-hmr/tsup.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts", "src/runtime.ts"], + format: ["esm"], + dts: true, + splitting: false, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46e20d38b..d8ef7079e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -423,6 +423,41 @@ importers: specifier: ^5.1.0 version: 5.1.0(@types/node@18.16.18) + packages/vite-plugin-simple-hmr: + dependencies: + magic-string: + specifier: ^0.30.6 + version: 0.30.7 + devDependencies: + '@types/estree': + specifier: ^1.0.5 + version: 1.0.5 + vite: + specifier: ^5.1.0 + version: 5.1.0(@types/node@18.16.18) + + packages/vite-plugin-simple-hmr/examples/react: + dependencies: + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + devDependencies: + '@hiogawa/vite-plugin-simple-hmr': + specifier: workspace:* + version: link:../.. + '@hiogawa/vite-plugin-ssr-middleware': + specifier: workspace:* + version: link:../../../vite-plugin-ssr-middleware + '@types/react': + specifier: 18.2.12 + version: 18.2.12 + '@types/react-dom': + specifier: 18.2.5 + version: 18.2.5 + packages/vite-plugin-ssr-middleware: devDependencies: vite: @@ -2576,7 +2611,7 @@ packages: rollup: optional: true dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 dev: true @@ -2711,7 +2746,7 @@ packages: /@types/acorn@4.0.6: resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 dev: true /@types/babel__core@7.20.5: @@ -2768,11 +2803,7 @@ packages: /@types/estree-jsx@1.0.3: resolution: {integrity: sha512-pvQ+TKeRHeiUGRhvYwRrQ/ISnohKkSJR14fT2yqyZ4e9K5vqc7hrtY2Y1Dw0ZwAzQ6DQsxsaCUuSIIi8v0Cq6w==} dependencies: - '@types/estree': 1.0.1 - dev: true - - /@types/estree@1.0.1: - resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} + '@types/estree': 1.0.5 dev: true /@types/estree@1.0.5: @@ -2937,7 +2968,7 @@ packages: colorette: 2.0.20 consola: 3.2.3 fast-glob: 3.3.2 - magic-string: 0.30.5 + magic-string: 0.30.7 pathe: 1.1.1 perfect-debounce: 1.0.0 transitivePeerDependencies: @@ -2992,7 +3023,7 @@ packages: '@unocss/rule-utils': 0.57.7 css-tree: 2.3.1 fast-glob: 3.3.2 - magic-string: 0.30.5 + magic-string: 0.30.7 postcss: 8.4.35 dev: true @@ -3095,7 +3126,7 @@ packages: engines: {node: '>=14'} dependencies: '@unocss/core': 0.57.7 - magic-string: 0.30.5 + magic-string: 0.30.7 dev: true /@unocss/rule-utils@0.58.0: @@ -3103,7 +3134,7 @@ packages: engines: {node: '>=14'} dependencies: '@unocss/core': 0.58.0 - magic-string: 0.30.5 + magic-string: 0.30.7 dev: true /@unocss/scope@0.57.7: @@ -3161,7 +3192,7 @@ packages: '@unocss/transformer-directives': 0.57.7 chokidar: 3.5.3 fast-glob: 3.3.2 - magic-string: 0.30.5 + magic-string: 0.30.7 vite: 5.1.0(@types/node@18.16.18) transitivePeerDependencies: - rollup @@ -3256,7 +3287,7 @@ packages: /@vitest/snapshot@1.0.4: resolution: {integrity: sha512-vkfXUrNyNRA/Gzsp2lpyJxh94vU2OHT1amoD6WuvUAA12n32xeVZQ0KjjQIf8F6u7bcq2A2k969fMVxEsxeKYA==} dependencies: - magic-string: 0.30.5 + magic-string: 0.30.7 pathe: 1.1.1 pretty-format: 29.7.0 dev: true @@ -4323,7 +4354,7 @@ packages: /estree-util-attach-comments@2.1.1: resolution: {integrity: sha512-+5Ba/xGGS6mnwFbXIuQiDPTbuTxuMCooq3arVv7gPZtYpjp+VXH/NkHAP35OOefPhNG/UGqU3vt/LTABwcHX0w==} dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 dev: true /estree-util-build-jsx@2.2.2: @@ -4371,7 +4402,7 @@ packages: /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 dev: true /etag@1.8.1: @@ -4866,7 +4897,7 @@ packages: /hast-util-to-estree@2.3.3: resolution: {integrity: sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==} dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 '@types/estree-jsx': 1.0.3 '@types/hast': 2.3.8 '@types/unist': 2.0.10 @@ -5152,7 +5183,7 @@ packages: /is-reference@3.0.2: resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 dev: true /is-regex@1.1.4: @@ -5474,6 +5505,12 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /magic-string@0.30.7: + resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} @@ -5701,7 +5738,7 @@ packages: /micromark-extension-mdx-expression@1.0.8: resolution: {integrity: sha512-zZpeQtc5wfWKdzDsHRBY003H2Smg+PUi2REhqgIhdzAa5xonhP03FcXxqFSerFiNUr5AWmHpaNPQTBVOS4lrXw==} dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 micromark-factory-mdx-expression: 1.0.9 micromark-factory-space: 1.1.0 micromark-util-character: 1.2.0 @@ -5715,7 +5752,7 @@ packages: resolution: {integrity: sha512-gPH+9ZdmDflbu19Xkb8+gheqEDqkSpdCEubQyxuz/Hn8DOXiXvrXeikOoBA71+e8Pfi0/UYmU3wW3H58kr7akA==} dependencies: '@types/acorn': 4.0.6 - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 estree-util-is-identifier-name: 2.1.0 micromark-factory-mdx-expression: 1.0.9 micromark-factory-space: 1.1.0 @@ -5735,7 +5772,7 @@ packages: /micromark-extension-mdxjs-esm@1.0.5: resolution: {integrity: sha512-xNRBw4aoURcyz/S69B19WnZAkWJMxHMT5hE36GtDAyhoyn/8TuAeqjFJQlwk+MKQsUD7b3l7kFX+vlfVWgcX1w==} dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 micromark-core-commonmark: 1.1.0 micromark-util-character: 1.2.0 micromark-util-events-to-acorn: 1.2.3 @@ -5779,7 +5816,7 @@ packages: /micromark-factory-mdx-expression@1.0.9: resolution: {integrity: sha512-jGIWzSmNfdnkJq05c7b0+Wv0Kfz3NJ3N4cBjnbO4zjXIlxJr+f8lk+5ZmwFvqdAbUy2q6B5rCY//g0QAAaXDWA==} dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 micromark-util-character: 1.2.0 micromark-util-events-to-acorn: 1.2.3 micromark-util-symbol: 1.1.0 @@ -5865,7 +5902,7 @@ packages: resolution: {integrity: sha512-ij4X7Wuc4fED6UoLWkmo0xJQhsktfNh1J0m8g4PbIMPlx+ek/4YdW5mvbye8z/aZvAPUoxgXHrwVlXAPKMRp1w==} dependencies: '@types/acorn': 4.0.6 - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 '@types/unist': 2.0.10 estree-util-visit: 1.2.1 micromark-util-symbol: 1.1.0 @@ -6469,7 +6506,7 @@ packages: /periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} dependencies: - '@types/estree': 1.0.1 + '@types/estree': 1.0.5 estree-walker: 3.0.3 is-reference: 3.0.2 dev: true