Skip to content

Commit

Permalink
feat: create vite runtime error overlay plugin (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa authored Nov 21, 2023
1 parent be85a62 commit 1e2914d
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@hiogawa/tiny-store": "0.0.1-pre.1",
"@hiogawa/tiny-toast": "workspace:*",
"@hiogawa/tiny-transition": "workspace:*",
"@hiogawa/vite-runtime-error-overlay": "workspace:*",
"@hiogawa/unocss-preset-antd": "workspace:*",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
40 changes: 40 additions & 0 deletions packages/app/src/components/stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -956,3 +956,43 @@ export function StoryCubicBezier() {
);
}
}

export function StoryRuntimeErrorOverlay() {
return (
<main className="flex flex-col items-center gap-3 m-2">
<section className="flex flex-col gap-3 w-full max-w-lg border p-3">
<h2 className="text-xl">Runtime Error Overlay</h2>
<span className="text-colorTextSecondary text-sm">
This is enabled only for DEV mode (current mode ={" "}
{import.meta.env.DEV ? "DEV" : "PROD"})
</span>
<div className="flex gap-3">
<button
className="flex-1 antd-btn antd-btn-default"
onClick={() => {
throw new Error("test error");
}}
>
error
</button>
<button
className="flex-1 antd-btn antd-btn-default"
onClick={async () => {
throw new Error("test error (async)");
}}
>
unhandledrejection
</button>
<button
className="flex-1 antd-btn antd-btn-default"
onClick={() => {
throw new Error("test error (filter out)");
}}
>
error (filter-out)
</button>
</div>
</section>
</main>
);
}
4 changes: 4 additions & 0 deletions packages/app/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { execSync } from "node:child_process";
import { themeScriptPlugin } from "@hiogawa/theme-script/dist/vite";
import { vitePluginTinyRefresh } from "@hiogawa/tiny-refresh/dist/vite";
import { viteRuntimeErrorOverlayPlugin } from "@hiogawa/vite-runtime-error-overlay";
import unocss from "unocss/vite";
import { type Plugin, defineConfig } from "vite";

Expand All @@ -12,6 +13,9 @@ export default defineConfig({
unocss(),
unocssDepHmrPlugin([require.resolve("@hiogawa/unocss-preset-antd")]),
vitePluginTinyRefresh(),
viteRuntimeErrorOverlayPlugin({
filter: (error) => !error.message.includes("(filter out)"),
}),
themeScriptPlugin({
storageKey: "unocss-preset-antd-app:theme",
}),
Expand Down
22 changes: 22 additions & 0 deletions packages/vite-runtime-error-overlay/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# vite-runtime-error-overlay

Vite plugin to show client runtime error via builtin error overlay.
Based on the idea from https://github.com/vitejs/vite/pull/6274#issuecomment-1087749460

## usage

```ts
import { defineConfig } from "vite";
import { viteRuntimeErrorOverlayPlugin } from "@hiogawa/vite-runtime-error-overlay";

export default defineConfig({
plugins: [viteRuntimeErrorOverlayPlugin()],
});
```

## development

```sh
pnpm build
pnpm release
```
35 changes: 35 additions & 0 deletions packages/vite-runtime-error-overlay/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@hiogawa/vite-runtime-error-overlay",
"version": "0.0.1",
"homepage": "https://github.com/hi-ogawa/unocss-preset-antd/tree/main/packages/vite-runtime-error-overlay",
"repository": {
"type": "git",
"url": "https://github.com/hi-ogawa/unocss-preset-antd/",
"directory": "packages/vite-runtime-error-overlay"
},
"license": "MIT",
"type": "module",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"release": "pnpm publish --no-git-checks --access public"
},
"devDependencies": {
"vite": "^4.4.9"
},
"peerDependencies": {
"vite": "*"
}
}
84 changes: 84 additions & 0 deletions packages/vite-runtime-error-overlay/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { type Plugin, type WebSocketClient } from "vite";
import { name as packageName } from "../package.json";

// based on the idea in
// https://github.com/vitejs/vite/pull/6274#issuecomment-1087749460
// https://github.com/vitejs/vite/issues/2076

// TODO: the PR has utility to construct "frame"
// getStackLineInformation
// generateErrorPayload
// generateFrame

export function viteRuntimeErrorOverlayPlugin(options?: {
filter?: (error: Error) => boolean;
}): Plugin {
return {
name: packageName,

apply(config, env) {
return env.command === "serve" && !config.ssr;
},

transformIndexHtml() {
return [
{
tag: "script",
attrs: { type: "module" },
children: CLIENT_SCRIPT,
},
];
},

configureServer(server) {
server.ws.on(MESSAGE_TYPE, (data: unknown, client: WebSocketClient) => {
// deserialize error
const error = Object.assign(new Error(), data);

if (options?.filter && !options.filter(error)) {
return;
}

// https://vitejs.dev/guide/api-plugin.html#client-server-communication
// https://github.com/vitejs/vite/blob/5b58eca05939c0667cf9698e83f4f4849f3296f4/packages/vite/src/node/server/middlewares/error.ts#L54-L57
client.send({
type: "error",
err: {
message: error.message,
stack: error.stack ?? "",
},
});
});
},
};
}

const MESSAGE_TYPE = `${packageName}:error`;

const CLIENT_SCRIPT = /* js */ `
import { createHotContext } from "/@vite/client";
// dummy file path to instantiate import.meta.hot
const hot = createHotContext("/__dummy__${packageName}");
function sendError(error) {
if (!(error instanceof Error)) {
error = new Error("(unknown runtime error)");
}
const serialized = {
message: error.message,
stack: error.stack,
};
hot.send("${MESSAGE_TYPE}", serialized);
}
window.addEventListener("error", (evt) => {
sendError(evt.error);
});
window.addEventListener("unhandledrejection", (evt) => {
sendError(evt.reason);
});
`;
4 changes: 4 additions & 0 deletions packages/vite-runtime-error-overlay/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"]
}
7 changes: 7 additions & 0 deletions packages/vite-runtime-error-overlay/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from "tsup";

export default defineConfig({
entry: ["src/index.ts"],
format: ["esm", "cjs"],
dts: true,
});
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

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

0 comments on commit 1e2914d

Please sign in to comment.