Skip to content

Commit 70f224a

Browse files
authored
feat(vite-plugin-mud): add MUD vite plugin (#3449)
1 parent c681aa6 commit 70f224a

11 files changed

Lines changed: 828 additions & 53 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
"vite-plugin-mud": patch
3+
---
4+
5+
Initial release of Vite plugin for MUD projects.
6+
7+
This will soon be included by default in MUD templates, but you can add to an existing MUD project with:
8+
9+
```
10+
pnpm add -D vite@^6 vite-plugin-mud
11+
```
12+
13+
And use like:
14+
15+
```ts
16+
// vite.config.ts
17+
import { defineConfig } from "vite";
18+
import { mud } from "vite-plugin-mud";
19+
20+
export default defineConfig({
21+
plugins: [mud({ worldsFile: "worlds.json" })],
22+
});
23+
```
24+
25+
```json
26+
// tsconfig.json
27+
{
28+
"compilerOptions": {
29+
"types": ["vite/client", "vite-plugin-mud/env"]
30+
}
31+
}
32+
```

packages/vite-plugin-mud/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# vite-plugin-mud
2+
3+
Vite plugin for MUD projects.
4+
5+
This plugin will use your environment's `VITE_CHAIN_ID` to load the world deploy from your `worlds.json` file and provide it to your app via new environment variables.
6+
7+
| Variable | Type | Description |
8+
| ------------------------------- | --------------------- | ---------------------------------------------------------- |
9+
| `import.meta.env.CHAIN_ID` | `number` | The configured chain ID. |
10+
| `import.meta.env.WORLD_ADDRESS` | `Hex \| undefined` | The world contract address (if available). |
11+
| `import.meta.env.START_BLOCK` | `bigint \| undefined` | The block number the world was deployed at (if available). |
12+
13+
## Installation
14+
15+
```
16+
pnpm add vite-plugin-mud
17+
```
18+
19+
## Usage
20+
21+
```ts
22+
// vite.config.ts
23+
import { defineConfig } from "vite";
24+
import { mud } from "vite-plugin-mud";
25+
26+
export default defineConfig({
27+
plugins: [mud({ worldsFile: "worlds.json" })],
28+
});
29+
```
30+
31+
```json
32+
// tsconfig.json
33+
{
34+
"compilerOptions": {
35+
"types": ["vite/client", "vite-plugin-mud/env"]
36+
}
37+
}
38+
```

packages/vite-plugin-mud/env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
interface ImportMetaEnv {
2+
readonly CHAIN_ID: number;
3+
readonly WORLD_ADDRESS: `0x${string}` | undefined;
4+
readonly START_BLOCK: bigint | undefined;
5+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "vite-plugin-mud",
3+
"version": "2.2.14",
4+
"repository": {
5+
"type": "git",
6+
"url": "https://github.com/latticexyz/mud.git",
7+
"directory": "packages/vite-plugin-mud"
8+
},
9+
"license": "MIT",
10+
"type": "module",
11+
"exports": {
12+
".": {
13+
"types": "./dist/exports/index.d.ts",
14+
"default": "./dist/exports/index.js"
15+
},
16+
"./env": {
17+
"types": "./env.d.ts"
18+
}
19+
},
20+
"typesVersions": {
21+
"*": {
22+
"index": [
23+
"./dist/exports/index.d.ts"
24+
]
25+
}
26+
},
27+
"files": [
28+
"dist",
29+
"env.d.ts"
30+
],
31+
"scripts": {
32+
"build": "pnpm run build:js",
33+
"build:js": "tsup",
34+
"clean": "pnpm run clean:js",
35+
"clean:js": "shx rm -rf dist"
36+
},
37+
"devDependencies": {
38+
"@types/node": "^18.15.11",
39+
"tsup": "^6.7.0",
40+
"vite": "^6.0.7"
41+
},
42+
"peerDependencies": {
43+
"vite": "^6.0.7"
44+
},
45+
"publishConfig": {
46+
"access": "public"
47+
}
48+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { mud } from "../plugin";
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import fs from "node:fs/promises";
2+
3+
export async function isReadable(filename: string) {
4+
try {
5+
await fs.access(filename, fs.constants.R_OK);
6+
return true;
7+
} catch {
8+
return false;
9+
}
10+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Plugin } from "vite";
2+
import { loadEnv } from "vite";
3+
import path from "node:path";
4+
import fs from "node:fs/promises";
5+
import { isReadable } from "./isReadable";
6+
7+
export function mud(opts: { worldsFile: string }): Plugin {
8+
const rootDir = process.cwd();
9+
const worldsFile = path.resolve(rootDir, opts.worldsFile);
10+
11+
return {
12+
name: "vite-plugin-mud",
13+
async config(config, { mode }) {
14+
const env = Object.assign({}, process.env, loadEnv(mode, rootDir));
15+
const chainId = Number(env.VITE_CHAIN_ID) || 31337;
16+
17+
config.define ??= {};
18+
config.define["import.meta.env.CHAIN_ID"] = chainId;
19+
20+
if (!(await isReadable(worldsFile))) {
21+
console.log("no worlds file");
22+
return;
23+
}
24+
25+
const worlds = JSON.parse(await fs.readFile(worldsFile, "utf-8")) as Partial<
26+
Record<string, { address: `0x${string}`; blockNumber?: number }>
27+
>;
28+
29+
const world = worlds[chainId];
30+
if (world) {
31+
config.define["import.meta.env.WORLD_ADDRESS"] = JSON.stringify(world.address);
32+
config.define["import.meta.env.START_BLOCK"] = world.blockNumber != null ? `${world.blockNumber}n` : undefined;
33+
} else {
34+
console.log("no world deploy for chain ID", chainId);
35+
}
36+
},
37+
configureServer(server) {
38+
server.watcher.add(worldsFile);
39+
40+
server.watcher.on("add", handleFileChange);
41+
server.watcher.on("change", handleFileChange);
42+
server.watcher.on("unlink", handleFileChange);
43+
44+
// Wrap server restart in a delay to "debounce" so we
45+
// only restart once after all file change emissions complete.
46+
let timer: ReturnType<typeof setTimeout> | undefined;
47+
function restart() {
48+
clearTimeout(timer);
49+
timer = setTimeout(() => server.restart(), 500);
50+
}
51+
52+
function handleFileChange(filename: string) {
53+
// TODO: check if this works on windows, as paths may have different separators
54+
if (filename === worldsFile) {
55+
restart();
56+
}
57+
}
58+
},
59+
};
60+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist"
5+
},
6+
"include": ["src"]
7+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { defineConfig } from "tsup";
2+
3+
export default defineConfig((opts) => ({
4+
entry: ["src/**"],
5+
target: "esnext",
6+
format: ["esm"],
7+
sourcemap: true,
8+
minify: true,
9+
// don't generate DTS during watch mode because it's slow
10+
// we're likely using TS source in this mode anyway
11+
dts: !opts.watch,
12+
// don't clean during watch mode to avoid removing
13+
// previously-built DTS files, which other build tasks
14+
// depend on
15+
clean: !opts.watch,
16+
}));

0 commit comments

Comments
 (0)