foundry-vtt-react 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -108
- package/dist/{chunk-U6WX7YSY.mjs → chunk-7ZJUEWDX.mjs} +1 -1
- package/dist/chunk-7ZJUEWDX.mjs.map +1 -0
- package/dist/index.mjs +1 -1
- package/dist/util/dev-setup.d.mts +4 -0
- package/dist/util/dev-setup.mjs +1 -1
- package/dist/vite/index.d.mts +38 -0
- package/dist/vite/index.mjs +93 -0
- package/dist/vite/index.mjs.map +1 -0
- package/package.json +20 -3
- package/dist/chunk-U6WX7YSY.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# Foundry VTT React
|
|
2
2
|
|
|
3
|
-
This package provides extensions of various Foundry VTT classes to support building React applications inside Foundry VTT.
|
|
4
|
-
|
|
5
|
-
It is a **library**, consumed by module developers — not a Foundry module itself.
|
|
6
|
-
|
|
7
|
-
This is a bit experimental, but enables a modern JS workflow and if you know and enjoy React workflows with HMR/fast-refresh, this should have you flying in no time!
|
|
3
|
+
This package provides a development and build harness for creating React applications within Foundry. It also includes extensions of various Foundry VTT classes to support building React applications inside Foundry VTT. This is a bit experimental, but enables a modern JS workflow and if you know and enjoy React workflows with HMR/fast-refresh, this should have you flying in no time.
|
|
8
4
|
|
|
9
5
|
**Disclaimers:**
|
|
10
|
-
-
|
|
11
|
-
-
|
|
6
|
+
- Only tested against Foundry v13
|
|
7
|
+
- Not a Foundry module, but a build dependency to add to your module's build workflow and dev setup
|
|
8
|
+
- Relies on Vitejs building.
|
|
9
|
+
- TypeScript friendly!
|
|
12
10
|
|
|
13
11
|
## Installation
|
|
14
12
|
|
|
@@ -159,143 +157,96 @@ const off = contextConnector.onUpdate(handleUpdate);
|
|
|
159
157
|
|
|
160
158
|
## Development setup with Vite
|
|
161
159
|
|
|
162
|
-
For a fast dev loop with React Fast Refresh
|
|
163
|
-
|
|
164
|
-
- `src/main.ts` — your **real app** entry (registers hooks, sheets, etc.). This is what gets bundled for production.
|
|
165
|
-
- `src/main.js` — a tiny **dev-only shim** that calls `devSetup`, which injects the React Refresh runtime and then loads `src/main.ts` over HMR.
|
|
166
|
-
|
|
167
|
-
The trick that ties it together: your manifest's `esmodules` points at `dist/main.js`. In a production build that's the bundled app. In development, the Vite dev server is configured so that the same `/modules/<id>/dist/main.js` URL is served from `src/main.js` (the shim) — so **the manifest entry resolves to the dev shim while you develop, and to the real bundle once built.**
|
|
168
|
-
|
|
169
|
-
### Directory structure
|
|
160
|
+
For a fast dev loop with React Fast Refresh inside Foundry, add the `foundry-vtt-react/vite` plugin. Your manifest's `esmodules` points at `dist/main.js`: in production that's your built bundle, and in dev the plugin's middleware serves that same URL with the Fast Refresh preamble + a dynamic import of your real entry — so there's no shim file and no hand-written Vite config to maintain.
|
|
170
161
|
|
|
171
162
|
```text
|
|
172
163
|
my-module/
|
|
173
|
-
├─ module.json #
|
|
174
|
-
├─ package.json
|
|
164
|
+
├─ module.json # esmodules: ["dist/main.js"]
|
|
175
165
|
├─ vite.config.ts
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
│ └─ MySheetApp.tsx # your React component(s)
|
|
180
|
-
└─ dist/ # build output (gitignored); created by `vite build`
|
|
166
|
+
└─ src/
|
|
167
|
+
├─ main.ts # real entry: Hooks, registerSheet, … (build input)
|
|
168
|
+
└─ MySheetApp.tsx # your React component(s)
|
|
181
169
|
```
|
|
182
170
|
|
|
183
|
-
|
|
171
|
+
**1. Add the plugin** — it derives everything from your `module.json` `id`:
|
|
184
172
|
|
|
185
|
-
|
|
173
|
+
```ts
|
|
174
|
+
// vite.config.ts
|
|
175
|
+
import { defineConfig } from "vite";
|
|
176
|
+
import react from "@vitejs/plugin-react";
|
|
177
|
+
import foundryReact from "foundry-vtt-react/vite";
|
|
186
178
|
|
|
187
|
-
|
|
188
|
-
// module.json
|
|
189
|
-
{
|
|
190
|
-
"id": "my-module",
|
|
191
|
-
"esmodules": ["dist/main.js"],
|
|
192
|
-
"styles": ["dist/main.css"],
|
|
193
|
-
"compatibility": { "minimum": "13", "verified": "13" }
|
|
194
|
-
}
|
|
179
|
+
export default defineConfig({ plugins: [react(), foundryReact()] });
|
|
195
180
|
```
|
|
196
181
|
|
|
197
|
-
**2.
|
|
182
|
+
**2. Add scripts** — `"dev": "vite"` for the HMR server, `"build": "vite build"` for production.
|
|
198
183
|
|
|
199
|
-
|
|
200
|
-
import MyActorSheet from "./MyActorSheet";
|
|
184
|
+
**3. Make the module visible to Foundry** — symlink (or copy) your module folder into Foundry's `Data/modules/`. With Docker, mount it instead:
|
|
201
185
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
```
|
|
186
|
+
> ```yaml
|
|
187
|
+
> foundry:
|
|
188
|
+
> image: felddy/foundryvtt:13
|
|
189
|
+
> volumes:
|
|
190
|
+
> - /path/to/my-module:/data/Data/modules/my-module
|
|
191
|
+
> ```
|
|
209
192
|
|
|
210
|
-
**
|
|
193
|
+
**4. Run it** — start Foundry (`:30000`), then `npm run dev` and open the Vite server (`:30001`). Edits to your components hot-reload. For production, `npm run build` ships `dist/`; the dev middleware isn't involved.
|
|
211
194
|
|
|
212
|
-
|
|
213
|
-
import { id as APP_ID } from "../module.json";
|
|
214
|
-
import { devSetup } from "foundry-vtt-react";
|
|
195
|
+
`vite` and `@vitejs/plugin-react` are **optional peer dependencies** — needed only for this plugin, not the runtime classes (you already have them for any React + Vite setup).
|
|
215
196
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
197
|
+
### Plugin options
|
|
198
|
+
|
|
199
|
+
All options are optional:
|
|
200
|
+
|
|
201
|
+
| Option | Default | Description |
|
|
202
|
+
| --------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
203
|
+
| `appId` | the `id` read from `./module.json` | Your module's `id`. Used to build served paths and the proxy rule. |
|
|
204
|
+
| `entry` | `"src/main.ts"` | Your real app entry / build input, relative to the project root. |
|
|
205
|
+
| `foundryUrl` | `"http://localhost:30000"` | The local Foundry server that non-bundle requests are proxied to. |
|
|
206
|
+
| `port` | `30001` | The Vite dev server port. |
|
|
207
|
+
| `manifestEntry` | basename of `module.json` `esmodules[0]`, else `"main.js"` | The bundle filename Foundry requests (where the dev preamble is served). |
|
|
219
208
|
|
|
220
|
-
|
|
209
|
+
### What it expands to
|
|
210
|
+
|
|
211
|
+
For a module whose `id` is `my-module`, `foundryReact()` contributes the Vite config you'd otherwise hand-write — each value applied only when you haven't set it yourself, so your own config always wins:
|
|
221
212
|
|
|
222
213
|
```ts
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
export default defineConfig({
|
|
230
|
-
plugins: [react()],
|
|
231
|
-
root: "src/",
|
|
232
|
-
// Serve src/ under the path the manifest references, so
|
|
233
|
-
// /modules/<id>/dist/main.js resolves to src/main.js (the shim),
|
|
234
|
-
// and /modules/<id>/dist/main.ts resolves to src/main.ts (the app).
|
|
235
|
-
base: `/modules/${APP_ID}/dist`,
|
|
214
|
+
{
|
|
215
|
+
base: "/modules/my-module/dist",
|
|
216
|
+
root: "src",
|
|
217
|
+
// react/react-dom forced to a single copy — without this, a linked or git-installed
|
|
218
|
+
// foundry-vtt-react pulls a second React and every hook throws "Invalid hook call".
|
|
219
|
+
resolve: { dedupe: ["react", "react-dom"] },
|
|
236
220
|
server: {
|
|
237
221
|
port: 30001,
|
|
238
222
|
proxy: {
|
|
239
|
-
|
|
240
|
-
[`^(?!/modules/${APP_ID}/dist)`]: "http://localhost:30000/",
|
|
223
|
+
"^(?!/modules/my-module/dist)": "http://localhost:30000", // non-bundle requests → Foundry
|
|
241
224
|
"/socket.io": { target: "ws://localhost:30000", ws: true },
|
|
242
225
|
},
|
|
243
226
|
},
|
|
244
227
|
build: {
|
|
245
|
-
outDir:
|
|
228
|
+
outDir: "<root>/dist",
|
|
246
229
|
emptyOutDir: true,
|
|
247
230
|
rollupOptions: {
|
|
248
|
-
input:
|
|
231
|
+
input: "<root>/src/main.ts", // your `entry`
|
|
249
232
|
output: { entryFileNames: "[name].js", assetFileNames: "[name].[ext]", format: "es" },
|
|
250
233
|
},
|
|
251
234
|
},
|
|
252
|
-
});
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
**5. Scripts (**`package.json`**):**
|
|
256
|
-
|
|
257
|
-
```jsonc
|
|
258
|
-
{
|
|
259
|
-
"scripts": {
|
|
260
|
-
"dev": "vite", // dev server with HMR
|
|
261
|
-
"build": "vite build" // produces dist/main.js for production
|
|
262
|
-
}
|
|
263
235
|
}
|
|
264
236
|
```
|
|
265
237
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
> If you run foundry in docker like me, you can simply mount your local module volume into the docker foundry's modules folder like:
|
|
269
|
-
>
|
|
270
|
-
> ```
|
|
271
|
-
> foundry:
|
|
272
|
-
> image: felddy/foundryvtt:13
|
|
273
|
-
> hostname: localhost
|
|
274
|
-
> volumes:
|
|
275
|
-
> - /path/to/my-module:/data/Data/modules/my-module
|
|
276
|
-
> ```
|
|
277
|
-
|
|
278
|
-
**7. Run it.** Start Foundry (port `30000`), then `npm run dev` and open the Vite server (e.g. `http://localhost:30001`). Foundry loads your module; the manifest's `dist/main.js` is served from `src/main.js`, `devSetup` boots React Refresh, and edits to your components hot-reload.
|
|
238
|
+
In dev it also serves the manifest URL (`/modules/my-module/dist/main.js`) with the module that replaces the old `src/main.js` shim:
|
|
279
239
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
240
|
+
```js
|
|
241
|
+
// Fast Refresh preamble (reused from @vitejs/plugin-react; base derived from your config)
|
|
242
|
+
import { injectIntoGlobalHook } from "/modules/my-module/dist/@react-refresh";
|
|
243
|
+
injectIntoGlobalHook(window);
|
|
244
|
+
window.$RefreshReg$ = () => {};
|
|
245
|
+
window.$RefreshSig$ = () => (type) => type;
|
|
246
|
+
import("/modules/my-module/dist/main.ts"); // dynamic, so the preamble runs first
|
|
286
247
|
```
|
|
287
248
|
|
|
288
|
-
|
|
289
|
-
| Parameter | Description |
|
|
290
|
-
| ------------ | ---------------------------------------------------------------------------------------------------------------------- |
|
|
291
|
-
| `appId` | Your module's `id` (from `module.json`). Used to build the served paths and to namespace the injected `<script>` tags. |
|
|
292
|
-
| `entrypoint` | Path to your real app entry, relative to the module root (e.g. `dist/main.ts`), as served by the Vite dev server. |
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
It injects (idempotently — safe to call more than once):
|
|
296
|
-
|
|
297
|
-
- A `<script type="module">` preamble that wires up the React Refresh global hooks, loading `@react-refresh` from `/modules/<appId>/dist/@react-refresh`.
|
|
298
|
-
- A `<script type="module" src="/modules/<appId>/<entrypoint>">` that loads your app.
|
|
249
|
+
> **Migrating from `devSetup`:** `devSetup` is **deprecated**. Delete your `src/main.js` shim and the hand-written `base`/`server`/`build` config, then add `foundryReact()` — it reproduces the same behavior, deriving the preamble and paths from your resolved Vite config.
|
|
299
250
|
|
|
300
251
|
## Exports
|
|
301
252
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../lib/util/logger.ts","../lib/util/dev-setup.ts"],"sourcesContent":["const logger =\n (namespace: string): ((message: string) => void) =>\n (message: string) => {\n console.log(`%c[foundry-vtt-react][${namespace}]`, \"color: tomato;\", message);\n };\n\nexport default logger;\n","import logger from \"../util/logger.js\";\nconst log = logger(\"dev-setup\");\n\n/**\n * devSetup.js\n *\n * Utility function to set up development environment for React applications in Foundry VTT. Used in conjunction\n * with a Vite development server, this function injects necessary scripts to enable React Fast Refresh and loads\n * the React application entrypoint.\n *\n * @deprecated Use the `foundry-vtt-react/vite` plugin instead. It derives the refresh preamble and paths\n * from your Vite config (no hand-maintained paths) and serves the dev entry via dev-server middleware, so\n * the `src/main.js` shim that calls `devSetup` is no longer needed. This function will be removed in a future major.\n *\n * @param {string} appId - The application ID used to construct module paths, typically the `id` value in `module.json`.\n * @param {string} entrypoint - The path to the React application entrypoint script relative to the module root. This should match\n * the entrypoint defined in the Vite configuration.\n */\nexport function devSetup(appId: string, entrypoint: string) {\n const refreshScriptId = `foundry-react-refresh-script-${appId}`;\n\n if (document.getElementById(refreshScriptId)) {\n log(\"Script tag already exists, not adding again\");\n return;\n } else {\n log(\"Adding script tag for react refresh\");\n\n const scriptInner = `\n import { injectIntoGlobalHook } from \"/modules/${appId}/dist/@react-refresh\";\n injectIntoGlobalHook(window);\n window.$RefreshReg$ = () => {};\n window.$RefreshSig$ = () => (type) => type;\n `;\n\n const tag = document.createElement(\"script\");\n tag.type = \"module\";\n tag.id = refreshScriptId;\n tag.innerHTML = scriptInner;\n document.head.prepend(tag);\n }\n\n const devEntrypointId = `foundry-react-dev-entrypoint-${appId}`;\n if (document.getElementById(devEntrypointId)) {\n log(\"Dev entrypoint script tag already exists, not adding again\");\n return;\n } else {\n const mainScript = document.createElement(\"script\");\n mainScript.type = \"module\";\n mainScript.src = `/modules/${appId}${entrypoint.startsWith(\"/\") ? \"\" : \"/\"}${entrypoint}`;\n document.body.appendChild(mainScript);\n }\n}\n"],"mappings":";AAAA,IAAM,SACJ,CAAC,cACD,CAAC,YAAoB;AACnB,UAAQ,IAAI,yBAAyB,SAAS,KAAK,kBAAkB,OAAO;AAC9E;AAEF,IAAO,iBAAQ;;;ACLf,IAAM,MAAM,eAAO,WAAW;AAiBvB,SAAS,SAAS,OAAe,YAAoB;AAC1D,QAAM,kBAAkB,gCAAgC,KAAK;AAE7D,MAAI,SAAS,eAAe,eAAe,GAAG;AAC5C,QAAI,6CAA6C;AACjD;AAAA,EACF,OAAO;AACL,QAAI,qCAAqC;AAEzC,UAAM,cAAc;AAAA,uDAC+B,KAAK;AAAA;AAAA;AAAA;AAAA;AAMxD,UAAM,MAAM,SAAS,cAAc,QAAQ;AAC3C,QAAI,OAAO;AACX,QAAI,KAAK;AACT,QAAI,YAAY;AAChB,aAAS,KAAK,QAAQ,GAAG;AAAA,EAC3B;AAEA,QAAM,kBAAkB,gCAAgC,KAAK;AAC7D,MAAI,SAAS,eAAe,eAAe,GAAG;AAC5C,QAAI,4DAA4D;AAChE;AAAA,EACF,OAAO;AACL,UAAM,aAAa,SAAS,cAAc,QAAQ;AAClD,eAAW,OAAO;AAClB,eAAW,MAAM,YAAY,KAAK,GAAG,WAAW,WAAW,GAAG,IAAI,KAAK,GAAG,GAAG,UAAU;AACvF,aAAS,KAAK,YAAY,UAAU;AAAA,EACtC;AACF;","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* with a Vite development server, this function injects necessary scripts to enable React Fast Refresh and loads
|
|
6
6
|
* the React application entrypoint.
|
|
7
7
|
*
|
|
8
|
+
* @deprecated Use the `foundry-vtt-react/vite` plugin instead. It derives the refresh preamble and paths
|
|
9
|
+
* from your Vite config (no hand-maintained paths) and serves the dev entry via dev-server middleware, so
|
|
10
|
+
* the `src/main.js` shim that calls `devSetup` is no longer needed. This function will be removed in a future major.
|
|
11
|
+
*
|
|
8
12
|
* @param {string} appId - The application ID used to construct module paths, typically the `id` value in `module.json`.
|
|
9
13
|
* @param {string} entrypoint - The path to the React application entrypoint script relative to the module root. This should match
|
|
10
14
|
* the entrypoint defined in the Vite configuration.
|
package/dist/util/dev-setup.mjs
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/** Options for the `foundry-vtt-react` Vite plugin. All optional; Foundry-friendly defaults fill in. */
|
|
4
|
+
interface FoundryReactOptions {
|
|
5
|
+
/** Module `id`. Defaults to the `id` in `./module.json`. */
|
|
6
|
+
appId?: string;
|
|
7
|
+
/** App entry / build input, relative to the project root. Default `"src/main.ts"`. */
|
|
8
|
+
entry?: string;
|
|
9
|
+
/** Foundry server to proxy non-bundle requests to. Default `"http://localhost:30000"`. */
|
|
10
|
+
foundryUrl?: string;
|
|
11
|
+
/** Dev server port. Default `30001`. */
|
|
12
|
+
port?: number;
|
|
13
|
+
/** Bundle filename Foundry requests. Defaults to the basename of `esmodules[0]`, else `"main.js"`. */
|
|
14
|
+
manifestEntry?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Read the Fast Refresh preamble template from `@vitejs/plugin-react` (its source of truth).
|
|
18
|
+
* Lazy import, never static: a static one couples us to plugin-react's internal-API version.
|
|
19
|
+
* plugin-react is required for the dev server, so a missing dep throws naturally here. `??` covers
|
|
20
|
+
* both export shapes (named pre-v6, default-export property since).
|
|
21
|
+
*/
|
|
22
|
+
declare function loadPreambleTemplate(): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Vite plugin wiring a React module into a local Foundry VTT instance: owns the Foundry-specific
|
|
25
|
+
* config (`base`, `root`, `server.proxy`, `build`, react dedupe — only where you haven't set it)
|
|
26
|
+
* and serves a Fast Refresh preamble at the manifest URL so Foundry boots with HMR, no shim file.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* export default defineConfig({ plugins: [react(), foundryReact()] });
|
|
30
|
+
*/
|
|
31
|
+
declare function foundryReact(options?: FoundryReactOptions): Plugin;
|
|
32
|
+
/**
|
|
33
|
+
* Preamble (base-substituted) + a **dynamic** import of the real entry. Dynamic is required: a
|
|
34
|
+
* static `import` hoists above `injectIntoGlobalHook(window)` and breaks Fast Refresh.
|
|
35
|
+
*/
|
|
36
|
+
declare function buildDevModule(base: string, entryUrl: string, preambleTemplate: string): string;
|
|
37
|
+
|
|
38
|
+
export { type FoundryReactOptions, buildDevModule, foundryReact as default, loadPreambleTemplate };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// lib/vite/index.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { basename, resolve } from "path";
|
|
4
|
+
var PLUGIN_NAME = "vite-plugin-foundry-react";
|
|
5
|
+
async function loadPreambleTemplate() {
|
|
6
|
+
const mod = await import("@vitejs/plugin-react");
|
|
7
|
+
return mod.default?.preambleCode ?? mod.preambleCode;
|
|
8
|
+
}
|
|
9
|
+
function readManifest(root) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(readFileSync(resolve(root, "module.json"), "utf-8"));
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function foundryReact(options = {}) {
|
|
17
|
+
let base = "";
|
|
18
|
+
let entryUrl = "";
|
|
19
|
+
let manifestEntry = "";
|
|
20
|
+
let foundryUrl = "";
|
|
21
|
+
return {
|
|
22
|
+
name: PLUGIN_NAME,
|
|
23
|
+
config(userConfig) {
|
|
24
|
+
const root = userConfig.root ? resolve(process.cwd(), userConfig.root) : process.cwd();
|
|
25
|
+
const manifest = readManifest(process.cwd());
|
|
26
|
+
const appId = options.appId ?? manifest?.id;
|
|
27
|
+
if (!appId) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`[${PLUGIN_NAME}] Could not determine appId. Pass { appId } to the plugin, or run from a directory containing a module.json with an "id" field.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const entry = options.entry ?? "src/main.ts";
|
|
33
|
+
foundryUrl = options.foundryUrl ?? "http://localhost:30000";
|
|
34
|
+
const port = options.port ?? 30001;
|
|
35
|
+
manifestEntry = options.manifestEntry ?? (manifest?.esmodules?.[0] ? basename(manifest.esmodules[0]) : "main.js");
|
|
36
|
+
base = `/modules/${appId}/dist`;
|
|
37
|
+
entryUrl = `${base}/${basename(entry)}`;
|
|
38
|
+
const proxy = {
|
|
39
|
+
[`^(?!${base})`]: foundryUrl,
|
|
40
|
+
// anything but the dev bundle → Foundry
|
|
41
|
+
"/socket.io": { target: foundryUrl.replace(/^http/, "ws"), ws: true }
|
|
42
|
+
};
|
|
43
|
+
const next = {};
|
|
44
|
+
if (userConfig.base == null) next.base = base;
|
|
45
|
+
if (userConfig.root == null) next.root = "src";
|
|
46
|
+
next.server = {
|
|
47
|
+
port: userConfig.server?.port ?? port,
|
|
48
|
+
proxy: { ...proxy, ...userConfig.server?.proxy }
|
|
49
|
+
};
|
|
50
|
+
next.build = {
|
|
51
|
+
outDir: userConfig.build?.outDir ?? resolve(root, "dist"),
|
|
52
|
+
emptyOutDir: userConfig.build?.emptyOutDir ?? true,
|
|
53
|
+
rollupOptions: {
|
|
54
|
+
input: userConfig.build?.rollupOptions?.input ?? resolve(root, entry),
|
|
55
|
+
output: userConfig.build?.rollupOptions?.output ?? {
|
|
56
|
+
entryFileNames: "[name].js",
|
|
57
|
+
assetFileNames: "[name].[ext]",
|
|
58
|
+
format: "es"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const dedupe = ["react", "react-dom"].filter((d) => !userConfig.resolve?.dedupe?.includes(d));
|
|
63
|
+
if (dedupe.length) next.resolve = { dedupe };
|
|
64
|
+
return next;
|
|
65
|
+
},
|
|
66
|
+
async configureServer(server) {
|
|
67
|
+
const manifestUrl = `${base}/${manifestEntry}`;
|
|
68
|
+
const preambleTemplate = await loadPreambleTemplate();
|
|
69
|
+
const body = buildDevModule(base, entryUrl, preambleTemplate);
|
|
70
|
+
server.middlewares.use((req, res, next) => {
|
|
71
|
+
const url = req.url?.split("?")[0];
|
|
72
|
+
if (url !== manifestUrl) return next();
|
|
73
|
+
res.setHeader("Content-Type", "text/javascript");
|
|
74
|
+
res.end(body);
|
|
75
|
+
});
|
|
76
|
+
server.config.logger.info(
|
|
77
|
+
` ${PLUGIN_NAME} serving dev entry at ${manifestUrl} (proxying to ${foundryUrl})`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function buildDevModule(base, entryUrl, preambleTemplate) {
|
|
83
|
+
const baseWithSlash = base.endsWith("/") ? base : `${base}/`;
|
|
84
|
+
return `${preambleTemplate.replace("__BASE__", baseWithSlash)}
|
|
85
|
+
import(${JSON.stringify(entryUrl)});
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
buildDevModule,
|
|
90
|
+
foundryReact as default,
|
|
91
|
+
loadPreambleTemplate
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../lib/vite/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { basename, resolve } from \"node:path\";\nimport type { Plugin, ProxyOptions, UserConfig } from \"vite\";\n\n/** Options for the `foundry-vtt-react` Vite plugin. All optional; Foundry-friendly defaults fill in. */\nexport interface FoundryReactOptions {\n /** Module `id`. Defaults to the `id` in `./module.json`. */\n appId?: string;\n /** App entry / build input, relative to the project root. Default `\"src/main.ts\"`. */\n entry?: string;\n /** Foundry server to proxy non-bundle requests to. Default `\"http://localhost:30000\"`. */\n foundryUrl?: string;\n /** Dev server port. Default `30001`. */\n port?: number;\n /** Bundle filename Foundry requests. Defaults to the basename of `esmodules[0]`, else `\"main.js\"`. */\n manifestEntry?: string;\n}\n\nconst PLUGIN_NAME = \"vite-plugin-foundry-react\";\n\n/**\n * Read the Fast Refresh preamble template from `@vitejs/plugin-react` (its source of truth).\n * Lazy import, never static: a static one couples us to plugin-react's internal-API version.\n * plugin-react is required for the dev server, so a missing dep throws naturally here. `??` covers\n * both export shapes (named pre-v6, default-export property since).\n */\nexport async function loadPreambleTemplate(): Promise<string> {\n const mod: any = await import(\"@vitejs/plugin-react\");\n return mod.default?.preambleCode ?? mod.preambleCode;\n}\n\nfunction readManifest(root: string): { id?: string; esmodules?: string[] } | null {\n try {\n return JSON.parse(readFileSync(resolve(root, \"module.json\"), \"utf-8\"));\n } catch {\n return null;\n }\n}\n\n/**\n * Vite plugin wiring a React module into a local Foundry VTT instance: owns the Foundry-specific\n * config (`base`, `root`, `server.proxy`, `build`, react dedupe — only where you haven't set it)\n * and serves a Fast Refresh preamble at the manifest URL so Foundry boots with HMR, no shim file.\n *\n * @example\n * export default defineConfig({ plugins: [react(), foundryReact()] });\n */\nexport default function foundryReact(options: FoundryReactOptions = {}): Plugin {\n let base = \"\";\n let entryUrl = \"\";\n let manifestEntry = \"\";\n let foundryUrl = \"\";\n\n return {\n name: PLUGIN_NAME,\n\n config(userConfig): UserConfig {\n const root = userConfig.root ? resolve(process.cwd(), userConfig.root) : process.cwd();\n const manifest = readManifest(process.cwd());\n\n const appId = options.appId ?? manifest?.id;\n if (!appId) {\n throw new Error(\n `[${PLUGIN_NAME}] Could not determine appId. Pass { appId } to the plugin, ` +\n `or run from a directory containing a module.json with an \"id\" field.`,\n );\n }\n\n const entry = options.entry ?? \"src/main.ts\";\n foundryUrl = options.foundryUrl ?? \"http://localhost:30000\";\n const port = options.port ?? 30001;\n manifestEntry =\n options.manifestEntry ?? (manifest?.esmodules?.[0] ? basename(manifest.esmodules[0]) : \"main.js\");\n\n base = `/modules/${appId}/dist`;\n entryUrl = `${base}/${basename(entry)}`;\n\n const proxy: Record<string, string | ProxyOptions> = {\n [`^(?!${base})`]: foundryUrl, // anything but the dev bundle → Foundry\n \"/socket.io\": { target: foundryUrl.replace(/^http/, \"ws\"), ws: true },\n };\n\n // Only fill what the user hasn't set, so their config always wins.\n const next: UserConfig = {};\n if (userConfig.base == null) next.base = base;\n if (userConfig.root == null) next.root = \"src\";\n next.server = {\n port: userConfig.server?.port ?? port,\n proxy: { ...proxy, ...userConfig.server?.proxy },\n };\n next.build = {\n outDir: userConfig.build?.outDir ?? resolve(root, \"dist\"),\n emptyOutDir: userConfig.build?.emptyOutDir ?? true,\n rollupOptions: {\n input: userConfig.build?.rollupOptions?.input ?? resolve(root, entry),\n output: userConfig.build?.rollupOptions?.output ?? {\n entryFileNames: \"[name].js\",\n assetFileNames: \"[name].[ext]\",\n format: \"es\",\n },\n },\n };\n\n // Force a single react/react-dom — a linked/git-installed copy otherwise duplicates React\n // (\"Invalid hook call\"). Append only what the user hasn't (Vite concatenates dedupe lists).\n const dedupe = [\"react\", \"react-dom\"].filter((d) => !userConfig.resolve?.dedupe?.includes(d));\n if (dedupe.length) next.resolve = { dedupe };\n\n return next;\n },\n\n async configureServer(server) {\n const manifestUrl = `${base}/${manifestEntry}`;\n const preambleTemplate = await loadPreambleTemplate();\n const body = buildDevModule(base, entryUrl, preambleTemplate);\n\n // Serve the preamble + dynamic entry import at the manifest URL (replaces the dev shim file).\n server.middlewares.use((req, res, next) => {\n const url = req.url?.split(\"?\")[0];\n if (url !== manifestUrl) return next();\n\n res.setHeader(\"Content-Type\", \"text/javascript\");\n res.end(body);\n });\n\n server.config.logger.info(\n ` ${PLUGIN_NAME} serving dev entry at ${manifestUrl} (proxying to ${foundryUrl})`,\n );\n },\n };\n}\n\n/**\n * Preamble (base-substituted) + a **dynamic** import of the real entry. Dynamic is required: a\n * static `import` hoists above `injectIntoGlobalHook(window)` and breaks Fast Refresh.\n */\nexport function buildDevModule(base: string, entryUrl: string, preambleTemplate: string): string {\n // plugin-react's `__BASE__` expects Vite's normalized base (trailing slash).\n const baseWithSlash = base.endsWith(\"/\") ? base : `${base}/`;\n return `${preambleTemplate.replace(\"__BASE__\", baseWithSlash)}\\nimport(${JSON.stringify(entryUrl)});\\n`;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foundry-vtt-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Extensions of various FoundryVTT classes to support React applications.",
|
|
5
5
|
"packageManager": "pnpm@10.30.3",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"./dev-setup": {
|
|
14
14
|
"types": "./dist/util/dev-setup.d.mts",
|
|
15
15
|
"import": "./dist/util/dev-setup.mjs"
|
|
16
|
+
},
|
|
17
|
+
"./vite": {
|
|
18
|
+
"types": "./dist/vite/index.d.mts",
|
|
19
|
+
"import": "./dist/vite/index.mjs"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"files": [
|
|
@@ -21,6 +25,7 @@
|
|
|
21
25
|
"scripts": {
|
|
22
26
|
"build": "tsup",
|
|
23
27
|
"build:watch": "tsup --watch",
|
|
28
|
+
"prepare": "tsup",
|
|
24
29
|
"prepublishOnly": "pnpm build"
|
|
25
30
|
},
|
|
26
31
|
"keywords": [],
|
|
@@ -35,13 +40,25 @@
|
|
|
35
40
|
"url": "https://github.com/tasandberg/foundry-vtt-react/issues"
|
|
36
41
|
},
|
|
37
42
|
"devDependencies": {
|
|
43
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
38
44
|
"fvtt-types": "^13",
|
|
39
45
|
"tsup": "^8.5.1",
|
|
40
|
-
"typescript": "^5.9.3"
|
|
46
|
+
"typescript": "^5.9.3",
|
|
47
|
+
"vite": "^8.0.16"
|
|
41
48
|
},
|
|
42
49
|
"peerDependencies": {
|
|
50
|
+
"@vitejs/plugin-react": ">=4",
|
|
43
51
|
"react": "^19.2.0",
|
|
44
|
-
"react-dom": "^19.2.0"
|
|
52
|
+
"react-dom": "^19.2.0",
|
|
53
|
+
"vite": ">=5"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"@vitejs/plugin-react": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"vite": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
45
62
|
},
|
|
46
63
|
"dependencies": {
|
|
47
64
|
"@types/react": "^19.2.5",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../lib/util/logger.ts","../lib/util/dev-setup.ts"],"sourcesContent":["const logger =\n (namespace: string): ((message: string) => void) =>\n (message: string) => {\n console.log(`%c[foundry-vtt-react][${namespace}]`, \"color: tomato;\", message);\n };\n\nexport default logger;\n","import logger from \"../util/logger.js\";\nconst log = logger(\"dev-setup\");\n\n/**\n * devSetup.js\n *\n * Utility function to set up development environment for React applications in Foundry VTT. Used in conjunction\n * with a Vite development server, this function injects necessary scripts to enable React Fast Refresh and loads\n * the React application entrypoint.\n *\n * @param {string} appId - The application ID used to construct module paths, typically the `id` value in `module.json`.\n * @param {string} entrypoint - The path to the React application entrypoint script relative to the module root. This should match\n * the entrypoint defined in the Vite configuration.\n */\nexport function devSetup(appId: string, entrypoint: string) {\n const refreshScriptId = `foundry-react-refresh-script-${appId}`;\n\n if (document.getElementById(refreshScriptId)) {\n log(\"Script tag already exists, not adding again\");\n return;\n } else {\n log(\"Adding script tag for react refresh\");\n\n const scriptInner = `\n import { injectIntoGlobalHook } from \"/modules/${appId}/dist/@react-refresh\";\n injectIntoGlobalHook(window);\n window.$RefreshReg$ = () => {};\n window.$RefreshSig$ = () => (type) => type;\n `;\n\n const tag = document.createElement(\"script\");\n tag.type = \"module\";\n tag.id = refreshScriptId;\n tag.innerHTML = scriptInner;\n document.head.prepend(tag);\n }\n\n const devEntrypointId = `foundry-react-dev-entrypoint-${appId}`;\n if (document.getElementById(devEntrypointId)) {\n log(\"Dev entrypoint script tag already exists, not adding again\");\n return;\n } else {\n const mainScript = document.createElement(\"script\");\n mainScript.type = \"module\";\n mainScript.src = `/modules/${appId}${entrypoint.startsWith(\"/\") ? \"\" : \"/\"}${entrypoint}`;\n document.body.appendChild(mainScript);\n }\n}\n"],"mappings":";AAAA,IAAM,SACJ,CAAC,cACD,CAAC,YAAoB;AACnB,UAAQ,IAAI,yBAAyB,SAAS,KAAK,kBAAkB,OAAO;AAC9E;AAEF,IAAO,iBAAQ;;;ACLf,IAAM,MAAM,eAAO,WAAW;AAavB,SAAS,SAAS,OAAe,YAAoB;AAC1D,QAAM,kBAAkB,gCAAgC,KAAK;AAE7D,MAAI,SAAS,eAAe,eAAe,GAAG;AAC5C,QAAI,6CAA6C;AACjD;AAAA,EACF,OAAO;AACL,QAAI,qCAAqC;AAEzC,UAAM,cAAc;AAAA,uDAC+B,KAAK;AAAA;AAAA;AAAA;AAAA;AAMxD,UAAM,MAAM,SAAS,cAAc,QAAQ;AAC3C,QAAI,OAAO;AACX,QAAI,KAAK;AACT,QAAI,YAAY;AAChB,aAAS,KAAK,QAAQ,GAAG;AAAA,EAC3B;AAEA,QAAM,kBAAkB,gCAAgC,KAAK;AAC7D,MAAI,SAAS,eAAe,eAAe,GAAG;AAC5C,QAAI,4DAA4D;AAChE;AAAA,EACF,OAAO;AACL,UAAM,aAAa,SAAS,cAAc,QAAQ;AAClD,eAAW,OAAO;AAClB,eAAW,MAAM,YAAY,KAAK,GAAG,WAAW,WAAW,GAAG,IAAI,KAAK,GAAG,GAAG,UAAU;AACvF,aAAS,KAAK,YAAY,UAAU;AAAA,EACtC;AACF;","names":[]}
|