wgsl-edit 0.0.24 → 0.0.26

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 CHANGED
@@ -10,7 +10,8 @@ Web component for editing WESL/WGSL with CodeMirror 6.
10
10
  <wgsl-edit></wgsl-edit>
11
11
  ```
12
12
 
13
- Features syntax highlighting, linting, multi-file tabs, and light/dark themes out of the box.
13
+ Features syntax highlighting, linting, multi-file tabs, and light/dark themes
14
+ out of the box.
14
15
 
15
16
  ### Inline source
16
17
 
@@ -74,19 +75,24 @@ editor.project = { // load a full project
74
75
  | `line-numbers` | `true` `false` | `false` | Show line numbers |
75
76
  | `fetch-libs` | `true` `false` | `true` | Auto-fetch missing libraries from npm |
76
77
  | `shader-root` | string | - | Root path for shader imports |
78
+ | `autosave` | `true` `false` | `false` | Persist edits to disk via dev server (use `wgsl-edit/autosave` plugin) |
77
79
 
78
80
  ### Properties
79
81
 
80
82
  - `source: string` - Get/set active file content
81
- - `conditions: Record<string, boolean>` - Get/set conditions for conditional compilation (`@if`/`@elif`/`@else`)
82
- - `project: WeslProject` - Get/set full project (weslSrc, conditions, constants, packageName, libs)
83
+ - `conditions: Record<string, boolean>` - Get/set conditions for conditional
84
+ compilation (`@if`/`@elif`/`@else`)
85
+ - `project: WeslProject` - Get/set full project (weslSrc, conditions, constants,
86
+ packageName, libs)
83
87
  - `activeFile: string` - Get/set active file name
84
88
  - `fileNames: string[]` - List all file names
85
- - `theme`, `tabs`, `lint`, `lineNumbers`, `readonly`, `shaderRoot`, `fetchLibs` - Mirror attributes
89
+ - `theme`, `tabs`, `lint`, `lineNumbers`, `readonly`, `shaderRoot`, `fetchLibs`
90
+ - Mirror attributes
86
91
 
87
92
  ### Methods
88
93
 
89
- - `link(options?): Promise<string>` - Compile WESL sources into WGSL, returns the linked output
94
+ - `link(options?): Promise<string>` - Compile WESL sources into WGSL, returns
95
+ the linked output
90
96
  - `addFile(name, content?)` - Add a new file
91
97
  - `removeFile(name)` - Remove a file
92
98
  - `renameFile(oldName, newName)` - Rename a file
@@ -98,8 +104,8 @@ editor.project = { // load a full project
98
104
 
99
105
  ## Using with wesl-plugin
100
106
 
101
- For full project support (libraries, conditional compilation, constants),
102
- use [wesl-plugin](https://github.com/wgsl-tooling-wg/wesl-js/tree/main/packages/wesl-plugin)
107
+ For full project support (libraries, conditional compilation, constants), use
108
+ [wesl-plugin](https://github.com/webgpu-tools/wesl-js/tree/main/packages/wesl-plugin)
103
109
  to assemble shaders at build time and pass them to the editor via `project`.
104
110
 
105
111
  ```typescript
@@ -122,8 +128,42 @@ editor.project = {
122
128
  };
123
129
  ```
124
130
 
125
- The `?link` import provides `weslSrc`, `libs`, `rootModuleName`, and `packageName`.
126
- The editor's linter uses these to validate imports, conditions, and constants as you type.
131
+ The `?link` import provides `weslSrc`, `libs`, `rootModuleName`, and
132
+ `packageName`. The editor's linter uses these to validate imports, conditions,
133
+ and constants as you type.
134
+
135
+ ## Autosave (dev mode)
136
+
137
+ When using wesl-plugin, edits can be persisted back to the source file on disk
138
+ during Vite development. Add the `wgslEditAutosave()` plugin alongside
139
+ wesl-plugin and set `autosave` on the editor:
140
+
141
+ ```typescript
142
+ // vite.config.ts
143
+ import wgslEditAutosave from "wgsl-edit/autosave";
144
+ import { linkBuildExtension } from "wesl-plugin";
145
+ import viteWesl from "wesl-plugin/vite";
146
+
147
+ export default {
148
+ plugins: [
149
+ viteWesl({ extensions: [linkBuildExtension] }),
150
+ wgslEditAutosave(),
151
+ ],
152
+ };
153
+ ```
154
+
155
+ ```typescript
156
+ // app.ts
157
+ import shaderConfig from "./shader.wesl?link";
158
+
159
+ const editor = document.querySelector("wgsl-edit");
160
+ editor.project = shaderConfig;
161
+ editor.autosave = true;
162
+ ```
163
+
164
+ `shader-root` is picked up automatically from the `?link` import, so edits land
165
+ in the right file on disk. Production builds never include the middleware, so
166
+ the same code is safe to ship.
127
167
 
128
168
  ## Styling
129
169
 
@@ -0,0 +1,5 @@
1
+ //#region src/SaveEndpoint.d.ts
2
+ /** Shared between client (WgslEdit) and server (SaveMiddleware) so they can't drift. */
3
+ declare const saveEndpoint = "/__wgsl-edit/save";
4
+ //#endregion
5
+ export { saveEndpoint };
@@ -0,0 +1,5 @@
1
+ //#region src/SaveEndpoint.ts
2
+ /** Shared between client (WgslEdit) and server (SaveMiddleware) so they can't drift. */
3
+ const saveEndpoint = "/__wgsl-edit/save";
4
+ //#endregion
5
+ export { saveEndpoint };
@@ -0,0 +1,9 @@
1
+ import { IncomingMessage, ServerResponse } from "node:http";
2
+
3
+ //#region src/SaveMiddleware.d.ts
4
+ /** Paths recently written by autosave; consumed by hotUpdate to suppress reloads. */
5
+ declare const pendingSaves: Set<string>;
6
+ /** Connect-style middleware that handles POST <saveEndpoint> to write files to disk. */
7
+ declare function weslSaveMiddleware(projectRoot: string): (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
8
+ //#endregion
9
+ export { pendingSaves, weslSaveMiddleware };
@@ -0,0 +1,49 @@
1
+ import "./SaveEndpoint.mjs";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ //#region src/SaveMiddleware.ts
5
+ /** Paths recently written by autosave; consumed by hotUpdate to suppress reloads. */
6
+ const pendingSaves = /* @__PURE__ */ new Set();
7
+ /** Connect-style middleware that handles POST <saveEndpoint> to write files to disk. */
8
+ function weslSaveMiddleware(projectRoot) {
9
+ return (req, res, next) => {
10
+ if (req.url !== "/__wgsl-edit/save") return next();
11
+ if (req.method !== "POST") return next();
12
+ readBody(req).then((body) => handleSave(body, projectRoot, res), next);
13
+ };
14
+ }
15
+ async function handleSave(raw, projectRoot, res) {
16
+ let body;
17
+ try {
18
+ body = JSON.parse(raw);
19
+ } catch {
20
+ return respond(res, 400, "invalid JSON");
21
+ }
22
+ const { root, file, content } = body;
23
+ if (!file || typeof content !== "string") return respond(res, 400, "missing file or content");
24
+ const resolved = path.resolve(projectRoot, root ?? ".", file);
25
+ if (!resolved.startsWith(projectRoot + path.sep)) return respond(res, 403, "path outside project root");
26
+ try {
27
+ await fs.mkdir(path.dirname(resolved), { recursive: true });
28
+ await fs.writeFile(resolved, content);
29
+ pendingSaves.add(resolved);
30
+ respond(res, 200, "ok");
31
+ } catch (e) {
32
+ console.error("[wgsl-edit] save failed:", e.message);
33
+ respond(res, 500, e.message);
34
+ }
35
+ }
36
+ function readBody(req) {
37
+ return new Promise((resolve, reject) => {
38
+ const chunks = [];
39
+ req.on("data", (c) => chunks.push(c));
40
+ req.on("end", () => resolve(Buffer.concat(chunks).toString()));
41
+ req.on("error", reject);
42
+ });
43
+ }
44
+ function respond(res, status, msg) {
45
+ res.writeHead(status, { "Content-Type": "text/plain" });
46
+ res.end(msg);
47
+ }
48
+ //#endregion
49
+ export { pendingSaves, weslSaveMiddleware };