gesso-vite-plugin 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # gesso-vite-plugin
2
+
3
+ ## 0.1.0
4
+
5
+ First public release.
6
+
7
+ `gesso()` finds an application's worker entries, writes the constructions and
8
+ the hot-replacement wiring, and mounts the development error overlay.
9
+
10
+ It is optional by design: nothing in `gesso-core` or `gesso-framework`
11
+ mentions Vite, and the literal `new Worker(new URL(...))` construction stays
12
+ the documented fallback. The plugin's factories go in first, so your own
13
+ options spread over them.
14
+
15
+ It also names the one failure that looks like a bug in the framework: a save
16
+ that reloads the page instead of replacing a module, because a module is
17
+ reached from the main thread as well as from the render worker.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kevin Baker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # gesso-vite-plugin
2
+
3
+ Finds a Gesso application's worker entries, writes the constructions and the hot-replacement wiring, and says when a save will reload the page.
4
+
5
+ ```bash
6
+ npm install --save-dev gesso-vite-plugin
7
+ ```
8
+
9
+ ```ts
10
+ // vite.config.ts
11
+ import { gesso } from 'gesso-vite-plugin';
12
+
13
+ export default defineConfig({
14
+ plugins: [gesso()]
15
+ });
16
+ ```
17
+
18
+ Then the worker boilerplate goes away:
19
+
20
+ ```ts
21
+ // main.ts
22
+ createApp({ history: { mode: 'path' } }).mount('#app');
23
+
24
+ // RenderWorker.ts
25
+ renderRoot(AppRoot).useService(Feed);
26
+ ```
27
+
28
+ ## It is optional, deliberately
29
+
30
+ Nothing in `gesso-core` or `gesso-framework` mentions Vite, and no bundler is imported into the framework. Everything this plugin writes, you can write by hand: the literal `new Worker(new URL(...))` construction stays the documented fallback, and the plugin's factories go in first so your own options spread over them.
31
+
32
+ ## The diagnostic that is worth the install
33
+
34
+ One failure looks exactly like a bug in the framework: a save that reloads the whole page instead of replacing a module. The cause is a module reached from the main thread as well as from the render worker, which Vite cannot hot-replace because it propagates the invalidation to a main-thread importer that does not accept it. The plugin says so, and names the module, instead of leaving you to guess.
35
+
36
+ ## Documentation
37
+
38
+ [The Vite plugin](https://github.com/kevinpbaker/gesso/blob/main/apps/docs/tooling/vite-plugin.md)
39
+
40
+ MIT (c) Kevin Baker
@@ -0,0 +1,246 @@
1
+ import { Plugin } from "vite";
2
+ //#region src/render.d.ts
3
+ /** What the plugin found in a render worker entry, and could wire. */
4
+ interface RenderWiring {
5
+ /** The rewritten module, or null when there was nothing to wire. */
6
+ readonly code: string | null;
7
+ /**
8
+ * Why nothing was wired, when nothing was. A sentence for the
9
+ * developer, not an error: an entry the plugin cannot wire still
10
+ * runs, it just full-reloads on a save.
11
+ */
12
+ readonly skipped: string | null;
13
+ }
14
+ /**
15
+ * Writes the two lines an author would otherwise write by hand.
16
+ *
17
+ * The example below is the specification:
18
+ *
19
+ * const app = renderRoot(LiveApp).useService(LiveFeed);
20
+ * import.meta.hot?.accept('./LiveExampleApp', module => {
21
+ * app.reload(module.LiveApp, [module.LiveFeed]);
22
+ * });
23
+ *
24
+ * Three things have to be found for that to be written mechanically:
25
+ * the handle the chain returns, the module the root component came
26
+ * from, and the modules the services came from. The handle is taken by
27
+ * routing the call through a wrapper of the plugin's own, which keeps
28
+ * the chain that follows it intact whether or not the author assigned
29
+ * it. The modules come from the entry's own import statements, which is
30
+ * exactly the question `importSources` answers.
31
+ *
32
+ * A service declared in a module of its own is accepted alongside the
33
+ * root, because a registry keyed by class object cannot tell a
34
+ * replacement from a stranger and `reload` is where that is repaired.
35
+ * What is deliberately *not* wired is `useRoutes`: routes are read once
36
+ * when the runtime is built, so a change to the route table is a full
37
+ * reload and saying so is better than replacing a tree that still
38
+ * resolves the old routes.
39
+ */
40
+ declare function transformRenderWorker(code: string): RenderWiring;
41
+ //#endregion
42
+ //#region src/source.d.ts
43
+ /**
44
+ * Just enough of a JavaScript scanner to find a call and to say which
45
+ * module a name was imported from.
46
+ *
47
+ * Written here rather than taken from npm for the reason the rest of
48
+ * this repository's tooling gives (`packages/devtools/src/sourceMap.ts`,
49
+ * `scripts/gen-layout-fixtures.ts`): the whole of what the plugin needs
50
+ * is "where does `createApp(` open and close" and "where did `SegueApp`
51
+ * come from", and a parser is a dependency, a TypeScript dialect
52
+ * question and a version to keep in step, in exchange for two
53
+ * questions a scanner answers.
54
+ *
55
+ * The trick that makes it safe is `blankLiterals`: every string,
56
+ * template and comment is replaced by spaces of the same length, so the
57
+ * positions found in the blanked copy are positions in the original,
58
+ * and no brace inside a string can be mistaken for code. Everything
59
+ * below searches the blank copy and slices the original.
60
+ *
61
+ * What it does not do is understand scope. A `createApp` that a module
62
+ * defined for itself would be found the same way an imported one is,
63
+ * which is why the plugin checks the imports first and only then looks
64
+ * for the call.
65
+ */
66
+ /** Where a call expression starts, where its arguments are, where it ends. */
67
+ interface CallSite {
68
+ /** Index of the first character of the callee name. */
69
+ readonly start: number;
70
+ /** Index just after the opening parenthesis. */
71
+ readonly argsStart: number;
72
+ /** Index of the closing parenthesis. */
73
+ readonly argsEnd: number;
74
+ /** Index just after the closing parenthesis. */
75
+ readonly end: number;
76
+ }
77
+ /**
78
+ * A copy of `code` in which every string, template literal, regular
79
+ * expression body and comment is spaces, and everything else is
80
+ * untouched.
81
+ *
82
+ * Same length as the input, and newlines are kept, so an index into
83
+ * the result is an index into the original and a line number is still
84
+ * a line number. String and template delimiters are kept as well, so a
85
+ * specifier can be found by its quotes here and read from there.
86
+ */
87
+ declare function blankLiterals(code: string): string;
88
+ /**
89
+ * Every call of `name` in the module, in source order.
90
+ *
91
+ * A bare name has to stand alone: `renderRoot(` matches, `myRenderRoot(`
92
+ * and `thing.renderRoot(` do not, because a method of that name on some
93
+ * other object is not the framework's function. A name written with a
94
+ * leading dot is the opposite and matches only the method call:
95
+ * `.useService(` is how a builder chain reads and the only way it is
96
+ * ever written.
97
+ */
98
+ declare function findCalls(code: string, name: string, blank?: string): CallSite[];
99
+ /** The first call of `name`, or null. */
100
+ declare function findCall(code: string, name: string, blank?: string): CallSite | null;
101
+ /**
102
+ * The identifier a call's first argument is, or null when the argument
103
+ * is anything more complicated than a name.
104
+ *
105
+ * `renderRoot(SegueApp)` is the shape every entry in this repository
106
+ * writes and the only one that can be hot-replaced, because a
107
+ * replacement has to be looked up by name in the module that changed.
108
+ * `renderRoot(<App x={1} />)` is legal and gets no HMR wiring, which
109
+ * the plugin says rather than guessing.
110
+ */
111
+ declare function firstArgumentName(code: string, call: CallSite, blank?: string): string | null;
112
+ /**
113
+ * Which module each imported name came from: local name to specifier,
114
+ * exactly as the import wrote it.
115
+ *
116
+ * Default and namespace imports are recorded under their local names
117
+ * too, so `import App from './App'` and `import * as app from './App'`
118
+ * both answer. What the map does not carry is whether the binding is a
119
+ * type, because `import type` is stripped before this runs in every
120
+ * configuration that matters and a type never reaches a call.
121
+ */
122
+ declare function importSources(code: string, blank?: string): Map<string, string>;
123
+ //#endregion
124
+ //#region src/shell.d.ts
125
+ /** Where the plugin found the two worker entries, as the shell imports them. */
126
+ interface WorkerEntries {
127
+ /** A specifier relative to the shell module, e.g. `./RenderWorker.ts`. */
128
+ readonly renderWorker: string;
129
+ /** The same for the application worker, or null when there is none. */
130
+ readonly appLogicWorker: string | null;
131
+ }
132
+ /** A `createApp` call the plugin could write for, and what it still needs. */
133
+ interface ShellCall {
134
+ readonly call: CallSite;
135
+ /**
136
+ * False when the author wrote `renderWorker` themselves, which is the
137
+ * documented fallback and the case where the plugin has no worker to
138
+ * find. It still wires the overlay in.
139
+ */
140
+ readonly needsWorkers: boolean;
141
+ }
142
+ interface ShellTransformOptions {
143
+ /** The entries to construct, or null when the author named them. */
144
+ readonly entries: WorkerEntries | null;
145
+ /**
146
+ * Whether to wire the error overlay in as well. True while the dev
147
+ * server is running and never in a build, so a production bundle
148
+ * carries no reference to `gesso-devtools`.
149
+ */
150
+ readonly overlay: boolean;
151
+ }
152
+ /**
153
+ * The `createApp` call in a module, when the module has one that came
154
+ * from the framework.
155
+ *
156
+ * Separate from the transform because finding the worker entries is an
157
+ * asynchronous resolution against the bundler and worth doing only for
158
+ * a module that turns out to be a shell, which is one module in an
159
+ * application.
160
+ */
161
+ declare function findShellCall(code: string, blank?: string): ShellCall | null;
162
+ /**
163
+ * Rewrites the shell module so that it names no worker.
164
+ *
165
+ * The one edit inside the author's own code is the call itself:
166
+ * `createApp(...)` becomes `createApp(__gessoOptions(...))`, on the
167
+ * same line, so every line number in the module is the one it was.
168
+ * Everything else is appended below, as function declarations, which
169
+ * hoist and are therefore usable from the line above them.
170
+ *
171
+ * The merge order is what keeps the promise that the
172
+ * literal construction stays the documented fallback: the plugin's
173
+ * factories go in first and the author's options spread over them, so
174
+ * a `renderWorker` written by hand still wins.
175
+ *
176
+ * Returns null when there is nothing to do, which is the common case:
177
+ * most modules do not call `createApp`.
178
+ */
179
+ declare function transformShell(code: string, options: ShellTransformOptions): string | null;
180
+ //#endregion
181
+ //#region src/index.d.ts
182
+ /**
183
+ * `gesso-vite-plugin` — the plumbing between saving a file and seeing
184
+ * the result.
185
+ *
186
+ * A Gesso application is three files either side of a thread barrier
187
+ *, and until now the barrier cost the author four
188
+ * incantations: `new Worker(new URL('./RenderWorker.ts',
189
+ * import.meta.url), { type: 'module' })` twice, because only a literal
190
+ * gets a chunk; `import.meta.hot.accept` with the root module named by
191
+ * hand; and `mountErrorOverlay` wired to `onError`, without which a
192
+ * worker exception reaches a console nobody has selected. None of them
193
+ * is a decision. All of them are written the same way in every
194
+ * application, and getting one wrong fails silently.
195
+ *
196
+ * The three-file shape stays. What this removes is the incantations,
197
+ * and what it adds is the one diagnostic that cannot be read off the
198
+ * screen: a save that reloads the page rather than replacing a module,
199
+ * which traces to the root module having a
200
+ * main-thread importer and which otherwise looks like HMR simply not
201
+ * working.
202
+ *
203
+ * // vite.config.ts
204
+ * import { gesso } from 'gesso-vite-plugin';
205
+ * export default defineConfig({ plugins: [gesso()] });
206
+ *
207
+ * // main.ts
208
+ * createApp({ history: { mode: 'path' } }).mount('#app');
209
+ *
210
+ * The literal construction stays supported and stays documented: the
211
+ * plugin merges its factories *under* the options the author wrote, so
212
+ * a hand-written `renderWorker` wins and an application that would
213
+ * rather say it out loud can.
214
+ */
215
+ interface GessoPluginOptions {
216
+ /**
217
+ * The render worker entry, as a specifier the shell module could
218
+ * import. By default the plugin tries the names below, in order, and
219
+ * uses the first that resolves beside the shell.
220
+ */
221
+ readonly renderWorker?: string;
222
+ /**
223
+ * The application worker entry, or false for an application that has
224
+ * none. By default the plugin looks for it and leaves it out when
225
+ * there is nothing to find, which is the single-worker shape the
226
+ * scaffold produces.
227
+ */
228
+ readonly appLogicWorker?: string | false;
229
+ /**
230
+ * Wire `gesso-devtools`'s error overlay into `onError` (default
231
+ * true). Only ever in a dev server: a build carries no reference to
232
+ * the package.
233
+ */
234
+ readonly overlay?: boolean;
235
+ /** Emit the `import.meta.hot.accept` wiring (default true). */
236
+ readonly hmr?: boolean;
237
+ /**
238
+ * Report a save that will reload the page instead of replacing a
239
+ * module (default true).
240
+ */
241
+ readonly diagnostics?: boolean;
242
+ }
243
+ declare function gesso(options?: GessoPluginOptions): Plugin;
244
+ //#endregion
245
+ export { type CallSite, GessoPluginOptions, type RenderWiring, type ShellCall, type ShellTransformOptions, type WorkerEntries, blankLiterals, gesso as default, gesso, findCall, findCalls, findShellCall, firstArgumentName, importSources, transformRenderWorker, transformShell };
246
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,584 @@
1
+ //#region src/source.ts
2
+ const IDENTIFIER = /[A-Za-z0-9_$]/;
3
+ /**
4
+ * A copy of `code` in which every string, template literal, regular
5
+ * expression body and comment is spaces, and everything else is
6
+ * untouched.
7
+ *
8
+ * Same length as the input, and newlines are kept, so an index into
9
+ * the result is an index into the original and a line number is still
10
+ * a line number. String and template delimiters are kept as well, so a
11
+ * specifier can be found by its quotes here and read from there.
12
+ */
13
+ function blankLiterals(code) {
14
+ const out = code.split("");
15
+ let index = 0;
16
+ let regexAllowed = true;
17
+ const blank = (from, to) => {
18
+ for (let i = from; i < to; i++) if (out[i] !== "\n") out[i] = " ";
19
+ };
20
+ while (index < code.length) {
21
+ const char = code[index];
22
+ if (char === "/" && code[index + 1] === "/") {
23
+ const end = code.indexOf("\n", index);
24
+ blank(index, end === -1 ? code.length : end);
25
+ index = end === -1 ? code.length : end;
26
+ continue;
27
+ }
28
+ if (char === "/" && code[index + 1] === "*") {
29
+ const end = code.indexOf("*/", index + 2);
30
+ const stop = end === -1 ? code.length : end + 2;
31
+ blank(index, stop);
32
+ index = stop;
33
+ continue;
34
+ }
35
+ if (char === "\"" || char === "'") {
36
+ const end = closingQuote(code, index, char);
37
+ blank(index + 1, end);
38
+ index = end + 1;
39
+ regexAllowed = false;
40
+ continue;
41
+ }
42
+ if (char === "`") {
43
+ const end = closingQuote(code, index, "`");
44
+ blank(index + 1, end);
45
+ index = end + 1;
46
+ regexAllowed = false;
47
+ continue;
48
+ }
49
+ if (char === "/" && regexAllowed) {
50
+ const end = closingRegex(code, index);
51
+ if (end !== -1) {
52
+ blank(index + 1, end);
53
+ index = end + 1;
54
+ regexAllowed = false;
55
+ continue;
56
+ }
57
+ }
58
+ if (!/\s/.test(char)) regexAllowed = !IDENTIFIER.test(char) && char !== ")" && char !== "]";
59
+ index++;
60
+ }
61
+ return out.join("");
62
+ }
63
+ /** The index of the quote that closes the one at `open`, or the end. */
64
+ function closingQuote(code, open, quote) {
65
+ for (let i = open + 1; i < code.length; i++) {
66
+ if (code[i] === "\\") {
67
+ i++;
68
+ continue;
69
+ }
70
+ if (code[i] === quote) return i;
71
+ }
72
+ return code.length;
73
+ }
74
+ /**
75
+ * The index of the slash closing a regular expression, or -1 when the
76
+ * slash was division after all.
77
+ *
78
+ * A newline inside means it was not a regular expression, which is the
79
+ * cheap test that keeps `a / b` and `c / d` on one line from swallowing
80
+ * everything between them.
81
+ */
82
+ function closingRegex(code, open) {
83
+ let inClass = false;
84
+ for (let i = open + 1; i < code.length; i++) {
85
+ const char = code[i];
86
+ if (char === "\\") {
87
+ i++;
88
+ continue;
89
+ }
90
+ if (char === "\n") return -1;
91
+ if (char === "[") {
92
+ inClass = true;
93
+ continue;
94
+ }
95
+ if (char === "]") {
96
+ inClass = false;
97
+ continue;
98
+ }
99
+ if (char === "/" && !inClass) return i;
100
+ }
101
+ return -1;
102
+ }
103
+ /**
104
+ * Every call of `name` in the module, in source order.
105
+ *
106
+ * A bare name has to stand alone: `renderRoot(` matches, `myRenderRoot(`
107
+ * and `thing.renderRoot(` do not, because a method of that name on some
108
+ * other object is not the framework's function. A name written with a
109
+ * leading dot is the opposite and matches only the method call:
110
+ * `.useService(` is how a builder chain reads and the only way it is
111
+ * ever written.
112
+ */
113
+ function findCalls(code, name, blank = blankLiterals(code)) {
114
+ const member = name.startsWith(".");
115
+ const bare = member ? name.slice(1) : name;
116
+ const sites = [];
117
+ let from = 0;
118
+ for (;;) {
119
+ const at = blank.indexOf(bare, from);
120
+ if (at === -1) return sites;
121
+ from = at + bare.length;
122
+ const before = at === 0 ? "" : blank[at - 1];
123
+ if (member ? before !== "." : before !== "" && (IDENTIFIER.test(before) || before === ".")) continue;
124
+ let cursor = from;
125
+ while (cursor < blank.length && /\s/.test(blank[cursor])) cursor++;
126
+ if (blank[cursor] !== "(") continue;
127
+ const argsEnd = matchingBracket(blank, cursor);
128
+ if (argsEnd === -1) continue;
129
+ sites.push({
130
+ start: at,
131
+ argsStart: cursor + 1,
132
+ argsEnd,
133
+ end: argsEnd + 1
134
+ });
135
+ from = argsEnd + 1;
136
+ }
137
+ }
138
+ /** The first call of `name`, or null. */
139
+ function findCall(code, name, blank = blankLiterals(code)) {
140
+ return findCalls(code, name, blank)[0] ?? null;
141
+ }
142
+ /** The index of the bracket closing the one at `open`, or -1. */
143
+ function matchingBracket(blank, open) {
144
+ const stack = [];
145
+ for (let i = open; i < blank.length; i++) {
146
+ const char = blank[i];
147
+ if (char === "(" || char === "[" || char === "{") {
148
+ stack.push(char);
149
+ continue;
150
+ }
151
+ if (char === ")" || char === "]" || char === "}") {
152
+ stack.pop();
153
+ if (stack.length === 0) return i;
154
+ }
155
+ }
156
+ return -1;
157
+ }
158
+ /**
159
+ * The identifier a call's first argument is, or null when the argument
160
+ * is anything more complicated than a name.
161
+ *
162
+ * `renderRoot(SegueApp)` is the shape every entry in this repository
163
+ * writes and the only one that can be hot-replaced, because a
164
+ * replacement has to be looked up by name in the module that changed.
165
+ * `renderRoot(<App x={1} />)` is legal and gets no HMR wiring, which
166
+ * the plugin says rather than guessing.
167
+ */
168
+ function firstArgumentName(code, call, blank = blankLiterals(code)) {
169
+ const first = (splitTopLevel(blank.slice(call.argsStart, call.argsEnd))[0] ?? "").trim();
170
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(first) ? first : null;
171
+ }
172
+ /** Splits an argument list on its top-level commas. */
173
+ function splitTopLevel(text) {
174
+ const parts = [];
175
+ let depth = 0;
176
+ let start = 0;
177
+ for (let i = 0; i < text.length; i++) {
178
+ const char = text[i];
179
+ if (char === "(" || char === "[" || char === "{") depth++;
180
+ else if (char === ")" || char === "]" || char === "}") depth--;
181
+ else if (char === "," && depth === 0) {
182
+ parts.push(text.slice(start, i));
183
+ start = i + 1;
184
+ }
185
+ }
186
+ parts.push(text.slice(start));
187
+ return parts;
188
+ }
189
+ /**
190
+ * Which module each imported name came from: local name to specifier,
191
+ * exactly as the import wrote it.
192
+ *
193
+ * Default and namespace imports are recorded under their local names
194
+ * too, so `import App from './App'` and `import * as app from './App'`
195
+ * both answer. What the map does not carry is whether the binding is a
196
+ * type, because `import type` is stripped before this runs in every
197
+ * configuration that matters and a type never reaches a call.
198
+ */
199
+ function importSources(code, blank = blankLiterals(code)) {
200
+ const sources = /* @__PURE__ */ new Map();
201
+ const statement = /\bimport\b([\s\S]*?)\bfrom\b\s*(['"])/g;
202
+ for (;;) {
203
+ const match = statement.exec(blank);
204
+ if (match === null) return sources;
205
+ const quote = match[2];
206
+ const open = match.index + match[0].length;
207
+ const close = code.indexOf(quote, open);
208
+ if (close === -1) continue;
209
+ const specifier = code.slice(open, close);
210
+ for (const local of importedNames(match[1])) sources.set(local, specifier);
211
+ statement.lastIndex = close + 1;
212
+ }
213
+ }
214
+ /** The local names an import clause binds. */
215
+ function importedNames(clause) {
216
+ const names = [];
217
+ const braces = clause.indexOf("{");
218
+ const head = (braces === -1 ? clause : clause.slice(0, braces)).replace(/\btype\b/g, "");
219
+ for (const part of head.split(",")) {
220
+ const named = /^\s*(?:\*\s*as\s*)?([A-Za-z_$][A-Za-z0-9_$]*)\s*$/.exec(part);
221
+ if (named !== null) names.push(named[1]);
222
+ }
223
+ if (braces !== -1) {
224
+ const close = clause.lastIndexOf("}");
225
+ const list = clause.slice(braces + 1, close === -1 ? clause.length : close);
226
+ for (const part of list.split(",")) {
227
+ const aliased = /^\s*(?:type\s+)?[A-Za-z_$][A-Za-z0-9_$]*\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*$/.exec(part);
228
+ const plain = /^\s*(?:type\s+)?([A-Za-z_$][A-Za-z0-9_$]*)\s*$/.exec(part);
229
+ const name = aliased?.[1] ?? plain?.[1];
230
+ if (name !== void 0) names.push(name);
231
+ }
232
+ }
233
+ return names;
234
+ }
235
+ //#endregion
236
+ //#region src/render.ts
237
+ const MARKER$1 = "/* gesso-vite-plugin: hot replacement */";
238
+ /**
239
+ * Writes the two lines an author would otherwise write by hand.
240
+ *
241
+ * The example below is the specification:
242
+ *
243
+ * const app = renderRoot(LiveApp).useService(LiveFeed);
244
+ * import.meta.hot?.accept('./LiveExampleApp', module => {
245
+ * app.reload(module.LiveApp, [module.LiveFeed]);
246
+ * });
247
+ *
248
+ * Three things have to be found for that to be written mechanically:
249
+ * the handle the chain returns, the module the root component came
250
+ * from, and the modules the services came from. The handle is taken by
251
+ * routing the call through a wrapper of the plugin's own, which keeps
252
+ * the chain that follows it intact whether or not the author assigned
253
+ * it. The modules come from the entry's own import statements, which is
254
+ * exactly the question `importSources` answers.
255
+ *
256
+ * A service declared in a module of its own is accepted alongside the
257
+ * root, because a registry keyed by class object cannot tell a
258
+ * replacement from a stranger and `reload` is where that is repaired.
259
+ * What is deliberately *not* wired is `useRoutes`: routes are read once
260
+ * when the runtime is built, so a change to the route table is a full
261
+ * reload and saying so is better than replacing a tree that still
262
+ * resolves the old routes.
263
+ */
264
+ function transformRenderWorker(code) {
265
+ if (code.includes(MARKER$1)) return {
266
+ code: null,
267
+ skipped: null
268
+ };
269
+ const blank = blankLiterals(code);
270
+ const imports = importSources(code, blank);
271
+ if (imports.get("renderRoot") !== "gesso-framework") return {
272
+ code: null,
273
+ skipped: null
274
+ };
275
+ const call = findCall(code, "renderRoot", blank);
276
+ if (call === null) return {
277
+ code: null,
278
+ skipped: null
279
+ };
280
+ const root = firstArgumentName(code, call, blank);
281
+ if (root === null) return {
282
+ code: null,
283
+ skipped: "renderRoot() is not called with the name of a component, so there is nothing to look up in a replaced module."
284
+ };
285
+ const rootSource = imports.get(root);
286
+ if (rootSource === void 0) return {
287
+ code: null,
288
+ skipped: `${root} is declared in the worker entry itself rather than imported, so replacing it would replace the entry and reload the page.`
289
+ };
290
+ const services = [];
291
+ for (const site of findCalls(code, ".useService", blank)) {
292
+ const name = firstArgumentName(code, site, blank);
293
+ const source = name === null ? void 0 : imports.get(name);
294
+ if (name !== null && source !== void 0) services.push({
295
+ name,
296
+ source
297
+ });
298
+ }
299
+ const sources = [rootSource, ...services.map((service) => service.source)].filter((source, index, all) => all.indexOf(source) === index);
300
+ const namespace = (source) => `__gessoModule${sources.indexOf(source)}`;
301
+ return {
302
+ code: `${`${code.slice(0, call.start)}__gessoRenderRoot${code.slice(call.start + 10)}`}\n${[
303
+ MARKER$1,
304
+ ...sources.map((source, index) => `import * as __gessoImported${index} from ${JSON.stringify(source)};`),
305
+ "function __gessoRenderRoot(root) {",
306
+ " __gessoRenderRoot.app = renderRoot(root);",
307
+ " return __gessoRenderRoot.app;",
308
+ "}",
309
+ `const __gessoLatest = [${sources.map((_, index) => `__gessoImported${index}`).join(", ")}];`,
310
+ "if (import.meta.hot) {",
311
+ ` import.meta.hot.accept([${sources.map((source) => JSON.stringify(source)).join(", ")}], replaced => {`,
312
+ " replaced.forEach((module, index) => {",
313
+ " if (module !== undefined) {",
314
+ " __gessoLatest[index] = module;",
315
+ " }",
316
+ " });",
317
+ ` __gessoRenderRoot.app.reload(${namespaceRead(namespace(rootSource), root)}, [${services.map((service) => namespaceRead(namespace(service.source), service.name)).join(", ")}]);`,
318
+ " });",
319
+ "}"
320
+ ].join("\n")}\n`,
321
+ skipped: null
322
+ };
323
+ }
324
+ /** `__gessoLatest[n].Name`, written through the array so a replacement wins. */
325
+ function namespaceRead(namespaceName, exported) {
326
+ return `__gessoLatest[${namespaceName.slice(13)}].${exported}`;
327
+ }
328
+ //#endregion
329
+ //#region src/shell.ts
330
+ /** Marks a module the plugin has already rewritten, so it is not done twice. */
331
+ const MARKER = "/* gesso-vite-plugin */";
332
+ /**
333
+ * The `createApp` call in a module, when the module has one that came
334
+ * from the framework.
335
+ *
336
+ * Separate from the transform because finding the worker entries is an
337
+ * asynchronous resolution against the bundler and worth doing only for
338
+ * a module that turns out to be a shell, which is one module in an
339
+ * application.
340
+ */
341
+ function findShellCall(code, blank = blankLiterals(code)) {
342
+ if (code.includes(MARKER)) return null;
343
+ if (importSources(code, blank).get("createApp") !== "gesso-framework") return null;
344
+ const call = findCall(code, "createApp", blank);
345
+ if (call === null) return null;
346
+ return {
347
+ call,
348
+ needsWorkers: !blank.slice(call.argsStart, call.argsEnd).includes("renderWorker")
349
+ };
350
+ }
351
+ /**
352
+ * Rewrites the shell module so that it names no worker.
353
+ *
354
+ * The one edit inside the author's own code is the call itself:
355
+ * `createApp(...)` becomes `createApp(__gessoOptions(...))`, on the
356
+ * same line, so every line number in the module is the one it was.
357
+ * Everything else is appended below, as function declarations, which
358
+ * hoist and are therefore usable from the line above them.
359
+ *
360
+ * The merge order is what keeps the promise that the
361
+ * literal construction stays the documented fallback: the plugin's
362
+ * factories go in first and the author's options spread over them, so
363
+ * a `renderWorker` written by hand still wins.
364
+ *
365
+ * Returns null when there is nothing to do, which is the common case:
366
+ * most modules do not call `createApp`.
367
+ */
368
+ function transformShell(code, options) {
369
+ const shell = findShellCall(code, blankLiterals(code));
370
+ if (shell === null) return null;
371
+ if (options.entries === null && !options.overlay) return null;
372
+ const { call } = shell;
373
+ const args = code.slice(call.argsStart, call.argsEnd).trim();
374
+ return `${`${code.slice(0, call.argsStart)}__gessoOptions(${args === "" ? "{}" : args})${code.slice(call.argsEnd)}`}\n${prelude(options)}`;
375
+ }
376
+ /**
377
+ * The appended half: the worker construction, and in development the
378
+ * error overlay.
379
+ *
380
+ * `new Worker(new URL(...), { type: 'module', ... })` is written out
381
+ * literally because that is the only shape a bundler splits a worker
382
+ * out of, which is the whole reason this text has to be emitted into
383
+ * the author's module rather than living inside the framework. `name`
384
+ * is passed as a member whose value may be undefined, which a
385
+ * dictionary conversion treats as absent, so one construction serves
386
+ * both the named and the unnamed case.
387
+ */
388
+ function prelude(options) {
389
+ const entries = options.entries;
390
+ const lines = [
391
+ MARKER,
392
+ "function __gessoOptions(options = {}) {",
393
+ " return {",
394
+ ...entries === null ? [] : [` renderWorker: () => new Worker(new URL(${JSON.stringify(entries.renderWorker)}, import.meta.url), { type: 'module', name: options.workerName }),`],
395
+ ...entries?.appLogicWorker == null ? [] : [` appLogicWorker: () => new Worker(new URL(${JSON.stringify(entries.appLogicWorker)}, import.meta.url), { type: 'module', name: options.workerName }),`],
396
+ ...options.overlay ? [" onError: __gessoReportError,"] : [],
397
+ " ...options",
398
+ " };",
399
+ "}"
400
+ ];
401
+ return options.overlay ? `${lines.join("\n")}\n${OVERLAY}` : `${lines.join("\n")}\n`;
402
+ }
403
+ /**
404
+ * The development error overlay, loaded the first time something
405
+ * throws and not before.
406
+ *
407
+ * Lazy because it costs nothing until it is needed: a page that never
408
+ * throws never fetches `gesso-devtools`, and a page that does gets
409
+ * the overlay within a network round trip of the error rather than
410
+ * paying for the package on every start.
411
+ *
412
+ * The host is the canvas's parent, which is the element the
413
+ * application was mounted into: the overlay covers the application it
414
+ * is reporting on rather than the viewport, and a Gesso app is not
415
+ * necessarily the whole page. `document.body` is the fallback for an
416
+ * error thrown before there is a canvas at all.
417
+ *
418
+ * State is kept on the function object rather than in module-level
419
+ * `let` bindings, because this block is appended below the code that
420
+ * calls it and a `let` read from above it would be in its temporal
421
+ * dead zone.
422
+ */
423
+ const OVERLAY = `function __gessoReportError(message, stack, source) {
424
+ const state = (__gessoReportError.state ??= { pending: [], overlay: null, loading: false });
425
+ state.pending.push([message, stack, source]);
426
+ if (state.overlay !== null) {
427
+ __gessoFlushErrors();
428
+ return;
429
+ }
430
+ if (state.loading) {
431
+ return;
432
+ }
433
+ state.loading = true;
434
+ void import('gesso-devtools').then(({ mountErrorOverlay }) => {
435
+ const canvas = document.querySelector('canvas');
436
+ state.overlay = mountErrorOverlay(canvas?.parentElement ?? document.body);
437
+ __gessoFlushErrors();
438
+ });
439
+ }
440
+ function __gessoFlushErrors() {
441
+ const state = __gessoReportError.state;
442
+ for (const [message, stack, source] of state.pending.splice(0)) {
443
+ state.overlay.report(message, stack, source);
444
+ }
445
+ }
446
+ addEventListener('error', event => {
447
+ const error = event.error;
448
+ __gessoReportError(error?.message ?? event.message, error?.stack, 'window');
449
+ });
450
+ addEventListener('unhandledrejection', event => {
451
+ const reason = event.reason;
452
+ __gessoReportError(reason?.message ?? String(reason), reason?.stack, 'window');
453
+ });
454
+ `;
455
+ //#endregion
456
+ //#region src/index.ts
457
+ /**
458
+ * Where a render worker entry is looked for, beside the shell.
459
+ *
460
+ * Ordered so that a name that says what the file is beats the generic
461
+ * one: an application with both `RenderWorker.ts` and `worker.ts` means
462
+ * the first, and the scaffold, which has only `worker.ts`, still
463
+ * resolves.
464
+ */
465
+ const RENDER_WORKER_NAMES = [
466
+ "./RenderWorker.ts",
467
+ "./RenderWorker.tsx",
468
+ "./render.worker.ts",
469
+ "./app.render.worker.ts",
470
+ "./worker.ts"
471
+ ];
472
+ /** Where an application worker entry is looked for, beside the shell. */
473
+ const APP_WORKER_NAMES = [
474
+ "./AppWorker.ts",
475
+ "./AppWorker.tsx",
476
+ "./app.worker.ts",
477
+ "./app.logic.worker.ts"
478
+ ];
479
+ /** Modules the plugin will read: the application's own source, not its dependencies. */
480
+ const SOURCE = /\.(?:[cm]?[jt]sx?)(?:$|\?)/;
481
+ function gesso(options = {}) {
482
+ let serving = false;
483
+ /** The shell module, once one has been transformed. */
484
+ let shellId = null;
485
+ /** The render worker entry, once the shell has named it. */
486
+ let renderWorkerId = null;
487
+ /** Files already reported as reloading, so a save is not a stream of warnings. */
488
+ const reported = /* @__PURE__ */ new Set();
489
+ return {
490
+ name: "gesso",
491
+ enforce: "pre",
492
+ configResolved(config) {
493
+ serving = config.command === "serve";
494
+ },
495
+ async transform(code, id) {
496
+ if (!SOURCE.test(id) || id.includes("/node_modules/")) return null;
497
+ if (serving && options.hmr !== false) {
498
+ const wiring = transformRenderWorker(code);
499
+ if (wiring.skipped !== null) this.warn(`${wiring.skipped} Saving it will reload the page rather than replace the tree.`);
500
+ if (wiring.code !== null) {
501
+ renderWorkerId = id;
502
+ return {
503
+ code: wiring.code,
504
+ map: null
505
+ };
506
+ }
507
+ }
508
+ const call = findShellCall(code);
509
+ if (call === null) return null;
510
+ shellId = id;
511
+ const shell = transformShell(code, {
512
+ entries: call.needsWorkers ? await resolveEntries(this, id, options) : null,
513
+ overlay: serving && options.overlay !== false
514
+ });
515
+ return shell === null ? null : {
516
+ code: shell,
517
+ map: null
518
+ };
519
+ },
520
+ /**
521
+ * The failure that looks like a bug in the framework.
522
+ *
523
+ * A module reached from the main thread as well
524
+ * as from the render worker cannot be hot-replaced, because Vite
525
+ * propagates the invalidation to a main-thread importer that does
526
+ * not accept and reloads the page. The screen blinks, the app
527
+ * restarts, and nothing anywhere says why. Both ends of the module
528
+ * graph are known here, so the answer is one walk of the importers.
529
+ */
530
+ hotUpdate({ modules, server }) {
531
+ if (options.diagnostics === false || shellId === null || renderWorkerId === null) return;
532
+ for (const module of modules) {
533
+ if (module.id === null || reported.has(module.id)) continue;
534
+ const importers = importerClosure(module);
535
+ if (!importers.has(shellId) || !importers.has(renderWorkerId)) continue;
536
+ reported.add(module.id);
537
+ server.config.logger.warn(`[gesso] ${short(module.id, server.config.root)} is imported by the main thread as well as by the render worker, so saving it reloads the page instead of replacing the tree. Reach it only from the render worker's own graph to get hot replacement back.`);
538
+ }
539
+ }
540
+ };
541
+ }
542
+ /** Every module that transitively imports this one, plus this one. */
543
+ function importerClosure(module) {
544
+ const seen = /* @__PURE__ */ new Set();
545
+ const queue = [module];
546
+ while (queue.length > 0) {
547
+ const next = queue.pop();
548
+ if (next.id === null || seen.has(next.id)) continue;
549
+ seen.add(next.id);
550
+ queue.push(...next.importers);
551
+ }
552
+ return seen;
553
+ }
554
+ /** A path relative to the project, for a message a person reads. */
555
+ function short(id, root) {
556
+ return id.startsWith(root) ? id.slice(root.length + 1) : id;
557
+ }
558
+ /**
559
+ * Finds the worker entries beside a shell module.
560
+ *
561
+ * The names are tried against the bundler's own resolver rather than
562
+ * read off the disk: it already has the answer cached, it applies the
563
+ * project's aliases and extensions, and it is the only thing that can
564
+ * say whether the specifier the plugin is about to write would have
565
+ * resolved. Nothing here touches the filesystem, which is also why the
566
+ * package needs no node types.
567
+ */
568
+ async function resolveEntries(context, id, options) {
569
+ const renderWorker = await firstResolved(context, id, options.renderWorker === void 0 ? RENDER_WORKER_NAMES : [options.renderWorker]);
570
+ if (renderWorker === null) throw new Error(`gesso-vite-plugin found no render worker beside ${id}. It looked for ${RENDER_WORKER_NAMES.join(", ")}. Name it with the plugin's \`renderWorker\` option, or write \`renderWorker\` in createApp() yourself.`);
571
+ return {
572
+ renderWorker,
573
+ appLogicWorker: options.appLogicWorker === false ? null : await firstResolved(context, id, options.appLogicWorker === void 0 ? APP_WORKER_NAMES : [options.appLogicWorker])
574
+ };
575
+ }
576
+ /** The first specifier that resolves beside `importer`, as written. */
577
+ async function firstResolved(context, importer, names) {
578
+ for (const name of names) if (await context.resolve(name, importer) !== null) return name;
579
+ return null;
580
+ }
581
+ //#endregion
582
+ export { blankLiterals, gesso as default, gesso, findCall, findCalls, findShellCall, firstArgumentName, importSources, transformRenderWorker, transformShell };
583
+
584
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["MARKER"],"sources":["../src/source.ts","../src/render.ts","../src/shell.ts","../src/index.ts"],"sourcesContent":["/**\n * Just enough of a JavaScript scanner to find a call and to say which\n * module a name was imported from.\n *\n * Written here rather than taken from npm for the reason the rest of\n * this repository's tooling gives (`packages/devtools/src/sourceMap.ts`,\n * `scripts/gen-layout-fixtures.ts`): the whole of what the plugin needs\n * is \"where does `createApp(` open and close\" and \"where did `SegueApp`\n * come from\", and a parser is a dependency, a TypeScript dialect\n * question and a version to keep in step, in exchange for two\n * questions a scanner answers.\n *\n * The trick that makes it safe is `blankLiterals`: every string,\n * template and comment is replaced by spaces of the same length, so the\n * positions found in the blanked copy are positions in the original,\n * and no brace inside a string can be mistaken for code. Everything\n * below searches the blank copy and slices the original.\n *\n * What it does not do is understand scope. A `createApp` that a module\n * defined for itself would be found the same way an imported one is,\n * which is why the plugin checks the imports first and only then looks\n * for the call.\n */\n\n/** Where a call expression starts, where its arguments are, where it ends. */\nexport interface CallSite {\n /** Index of the first character of the callee name. */\n readonly start: number;\n /** Index just after the opening parenthesis. */\n readonly argsStart: number;\n /** Index of the closing parenthesis. */\n readonly argsEnd: number;\n /** Index just after the closing parenthesis. */\n readonly end: number;\n}\n\nconst IDENTIFIER = /[A-Za-z0-9_$]/;\n\n/**\n * A copy of `code` in which every string, template literal, regular\n * expression body and comment is spaces, and everything else is\n * untouched.\n *\n * Same length as the input, and newlines are kept, so an index into\n * the result is an index into the original and a line number is still\n * a line number. String and template delimiters are kept as well, so a\n * specifier can be found by its quotes here and read from there.\n */\nexport function blankLiterals(code: string): string {\n const out = code.split('');\n let index = 0;\n // What may legally precede a regular expression: an operator, an\n // opening bracket, or the start of the file. After a name, a number\n // or a closing bracket, a slash is division.\n let regexAllowed = true;\n\n const blank = (from: number, to: number): void => {\n for (let i = from; i < to; i++) {\n if (out[i] !== '\\n') {\n out[i] = ' ';\n }\n }\n };\n\n while (index < code.length) {\n const char = code[index];\n if (char === '/' && code[index + 1] === '/') {\n const end = code.indexOf('\\n', index);\n blank(index, end === -1 ? code.length : end);\n index = end === -1 ? code.length : end;\n continue;\n }\n if (char === '/' && code[index + 1] === '*') {\n const end = code.indexOf('*/', index + 2);\n const stop = end === -1 ? code.length : end + 2;\n blank(index, stop);\n index = stop;\n continue;\n }\n if (char === '\"' || char === \"'\") {\n const end = closingQuote(code, index, char);\n blank(index + 1, end);\n index = end + 1;\n regexAllowed = false;\n continue;\n }\n if (char === '`') {\n const end = closingQuote(code, index, '`');\n blank(index + 1, end);\n index = end + 1;\n regexAllowed = false;\n continue;\n }\n if (char === '/' && regexAllowed) {\n const end = closingRegex(code, index);\n if (end !== -1) {\n blank(index + 1, end);\n index = end + 1;\n regexAllowed = false;\n continue;\n }\n }\n if (!/\\s/.test(char)) {\n regexAllowed = !IDENTIFIER.test(char) && char !== ')' && char !== ']';\n }\n index++;\n }\n return out.join('');\n}\n\n/** The index of the quote that closes the one at `open`, or the end. */\nfunction closingQuote(code: string, open: number, quote: string): number {\n for (let i = open + 1; i < code.length; i++) {\n if (code[i] === '\\\\') {\n i++;\n continue;\n }\n if (code[i] === quote) {\n return i;\n }\n }\n return code.length;\n}\n\n/**\n * The index of the slash closing a regular expression, or -1 when the\n * slash was division after all.\n *\n * A newline inside means it was not a regular expression, which is the\n * cheap test that keeps `a / b` and `c / d` on one line from swallowing\n * everything between them.\n */\nfunction closingRegex(code: string, open: number): number {\n let inClass = false;\n for (let i = open + 1; i < code.length; i++) {\n const char = code[i];\n if (char === '\\\\') {\n i++;\n continue;\n }\n if (char === '\\n') {\n return -1;\n }\n if (char === '[') {\n inClass = true;\n continue;\n }\n if (char === ']') {\n inClass = false;\n continue;\n }\n if (char === '/' && !inClass) {\n return i;\n }\n }\n return -1;\n}\n\n/**\n * Every call of `name` in the module, in source order.\n *\n * A bare name has to stand alone: `renderRoot(` matches, `myRenderRoot(`\n * and `thing.renderRoot(` do not, because a method of that name on some\n * other object is not the framework's function. A name written with a\n * leading dot is the opposite and matches only the method call:\n * `.useService(` is how a builder chain reads and the only way it is\n * ever written.\n */\nexport function findCalls(code: string, name: string, blank = blankLiterals(code)): CallSite[] {\n const member = name.startsWith('.');\n const bare = member ? name.slice(1) : name;\n const sites: CallSite[] = [];\n let from = 0;\n for (;;) {\n const at = blank.indexOf(bare, from);\n if (at === -1) {\n return sites;\n }\n from = at + bare.length;\n const before = at === 0 ? '' : blank[at - 1];\n if (member ? before !== '.' : before !== '' && (IDENTIFIER.test(before) || before === '.')) {\n continue;\n }\n let cursor = from;\n while (cursor < blank.length && /\\s/.test(blank[cursor])) {\n cursor++;\n }\n if (blank[cursor] !== '(') {\n continue;\n }\n const argsEnd = matchingBracket(blank, cursor);\n if (argsEnd === -1) {\n continue;\n }\n sites.push({ start: at, argsStart: cursor + 1, argsEnd, end: argsEnd + 1 });\n from = argsEnd + 1;\n }\n}\n\n/** The first call of `name`, or null. */\nexport function findCall(code: string, name: string, blank = blankLiterals(code)): CallSite | null {\n return findCalls(code, name, blank)[0] ?? null;\n}\n\n/** The index of the bracket closing the one at `open`, or -1. */\nexport function matchingBracket(blank: string, open: number): number {\n const stack: string[] = [];\n for (let i = open; i < blank.length; i++) {\n const char = blank[i];\n if (char === '(' || char === '[' || char === '{') {\n stack.push(char);\n continue;\n }\n if (char === ')' || char === ']' || char === '}') {\n stack.pop();\n if (stack.length === 0) {\n return i;\n }\n }\n }\n return -1;\n}\n\n/**\n * The identifier a call's first argument is, or null when the argument\n * is anything more complicated than a name.\n *\n * `renderRoot(SegueApp)` is the shape every entry in this repository\n * writes and the only one that can be hot-replaced, because a\n * replacement has to be looked up by name in the module that changed.\n * `renderRoot(<App x={1} />)` is legal and gets no HMR wiring, which\n * the plugin says rather than guessing.\n */\nexport function firstArgumentName(code: string, call: CallSite, blank = blankLiterals(code)): string | null {\n const first = (splitTopLevel(blank.slice(call.argsStart, call.argsEnd))[0] ?? '').trim();\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(first) ? first : null;\n}\n\n/** Splits an argument list on its top-level commas. */\nfunction splitTopLevel(text: string): string[] {\n const parts: string[] = [];\n let depth = 0;\n let start = 0;\n for (let i = 0; i < text.length; i++) {\n const char = text[i];\n if (char === '(' || char === '[' || char === '{') {\n depth++;\n } else if (char === ')' || char === ']' || char === '}') {\n depth--;\n } else if (char === ',' && depth === 0) {\n parts.push(text.slice(start, i));\n start = i + 1;\n }\n }\n parts.push(text.slice(start));\n return parts;\n}\n\n/**\n * Which module each imported name came from: local name to specifier,\n * exactly as the import wrote it.\n *\n * Default and namespace imports are recorded under their local names\n * too, so `import App from './App'` and `import * as app from './App'`\n * both answer. What the map does not carry is whether the binding is a\n * type, because `import type` is stripped before this runs in every\n * configuration that matters and a type never reaches a call.\n */\nexport function importSources(code: string, blank = blankLiterals(code)): Map<string, string> {\n const sources = new Map<string, string>();\n const statement = /\\bimport\\b([\\s\\S]*?)\\bfrom\\b\\s*(['\"])/g;\n for (;;) {\n const match = statement.exec(blank);\n if (match === null) {\n return sources;\n }\n const quote = match[2];\n const open = match.index + match[0].length;\n const close = code.indexOf(quote, open);\n if (close === -1) {\n continue;\n }\n const specifier = code.slice(open, close);\n for (const local of importedNames(match[1])) {\n sources.set(local, specifier);\n }\n statement.lastIndex = close + 1;\n }\n}\n\n/** The local names an import clause binds. */\nfunction importedNames(clause: string): string[] {\n const names: string[] = [];\n const braces = clause.indexOf('{');\n const head = (braces === -1 ? clause : clause.slice(0, braces)).replace(/\\btype\\b/g, '');\n for (const part of head.split(',')) {\n const named = /^\\s*(?:\\*\\s*as\\s*)?([A-Za-z_$][A-Za-z0-9_$]*)\\s*$/.exec(part);\n if (named !== null) {\n names.push(named[1]);\n }\n }\n if (braces !== -1) {\n const close = clause.lastIndexOf('}');\n const list = clause.slice(braces + 1, close === -1 ? clause.length : close);\n for (const part of list.split(',')) {\n const aliased = /^\\s*(?:type\\s+)?[A-Za-z_$][A-Za-z0-9_$]*\\s+as\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s*$/.exec(part);\n const plain = /^\\s*(?:type\\s+)?([A-Za-z_$][A-Za-z0-9_$]*)\\s*$/.exec(part);\n const name = aliased?.[1] ?? plain?.[1];\n if (name !== undefined) {\n names.push(name);\n }\n }\n }\n return names;\n}\n","import { blankLiterals, findCall, findCalls, firstArgumentName, importSources } from './source.ts';\n\n/** What the plugin found in a render worker entry, and could wire. */\nexport interface RenderWiring {\n /** The rewritten module, or null when there was nothing to wire. */\n readonly code: string | null;\n /**\n * Why nothing was wired, when nothing was. A sentence for the\n * developer, not an error: an entry the plugin cannot wire still\n * runs, it just full-reloads on a save.\n */\n readonly skipped: string | null;\n}\n\nconst MARKER = '/* gesso-vite-plugin: hot replacement */';\n\n/**\n * Writes the two lines an author would otherwise write by hand.\n *\n * The example below is the specification:\n *\n * const app = renderRoot(LiveApp).useService(LiveFeed);\n * import.meta.hot?.accept('./LiveExampleApp', module => {\n * app.reload(module.LiveApp, [module.LiveFeed]);\n * });\n *\n * Three things have to be found for that to be written mechanically:\n * the handle the chain returns, the module the root component came\n * from, and the modules the services came from. The handle is taken by\n * routing the call through a wrapper of the plugin's own, which keeps\n * the chain that follows it intact whether or not the author assigned\n * it. The modules come from the entry's own import statements, which is\n * exactly the question `importSources` answers.\n *\n * A service declared in a module of its own is accepted alongside the\n * root, because a registry keyed by class object cannot tell a\n * replacement from a stranger and `reload` is where that is repaired.\n * What is deliberately *not* wired is `useRoutes`: routes are read once\n * when the runtime is built, so a change to the route table is a full\n * reload and saying so is better than replacing a tree that still\n * resolves the old routes.\n */\nexport function transformRenderWorker(code: string): RenderWiring {\n if (code.includes(MARKER)) {\n return { code: null, skipped: null };\n }\n const blank = blankLiterals(code);\n const imports = importSources(code, blank);\n if (imports.get('renderRoot') !== 'gesso-framework') {\n return { code: null, skipped: null };\n }\n const call = findCall(code, 'renderRoot', blank);\n if (call === null) {\n return { code: null, skipped: null };\n }\n\n const root = firstArgumentName(code, call, blank);\n if (root === null) {\n return {\n code: null,\n skipped:\n 'renderRoot() is not called with the name of a component, so there is nothing to look up in a replaced module.'\n };\n }\n const rootSource = imports.get(root);\n if (rootSource === undefined) {\n return {\n code: null,\n skipped: `${root} is declared in the worker entry itself rather than imported, so replacing it would replace the entry and reload the page.`\n };\n }\n\n // Every service the chain registers, paired with the module it came\n // from. One declared beside the root is the common case and needs no\n // second dependency; one in a module of its own adds one.\n const services: { name: string; source: string }[] = [];\n for (const site of findCalls(code, '.useService', blank)) {\n const name = firstArgumentName(code, site, blank);\n const source = name === null ? undefined : imports.get(name);\n if (name !== null && source !== undefined) {\n services.push({ name, source });\n }\n }\n\n const sources = [rootSource, ...services.map(service => service.source)].filter(\n (source, index, all) => all.indexOf(source) === index\n );\n const namespace = (source: string): string => `__gessoModule${sources.indexOf(source)}`;\n const rewritten = `${code.slice(0, call.start)}__gessoRenderRoot${code.slice(call.start + 'renderRoot'.length)}`;\n\n const lines = [\n MARKER,\n ...sources.map((source, index) => `import * as __gessoImported${index} from ${JSON.stringify(source)};`),\n 'function __gessoRenderRoot(root) {',\n ' __gessoRenderRoot.app = renderRoot(root);',\n ' return __gessoRenderRoot.app;',\n '}',\n `const __gessoLatest = [${sources.map((_, index) => `__gessoImported${index}`).join(', ')}];`,\n 'if (import.meta.hot) {',\n ` import.meta.hot.accept([${sources.map(source => JSON.stringify(source)).join(', ')}], replaced => {`,\n ' replaced.forEach((module, index) => {',\n ' if (module !== undefined) {',\n ' __gessoLatest[index] = module;',\n ' }',\n ' });',\n ` __gessoRenderRoot.app.reload(${namespaceRead(namespace(rootSource), root)}, [${services\n .map(service => namespaceRead(namespace(service.source), service.name))\n .join(', ')}]);`,\n ' });',\n '}'\n ];\n return { code: `${rewritten}\\n${lines.join('\\n')}\\n`, skipped: null };\n}\n\n/** `__gessoLatest[n].Name`, written through the array so a replacement wins. */\nfunction namespaceRead(namespaceName: string, exported: string): string {\n const index = namespaceName.slice('__gessoModule'.length);\n return `__gessoLatest[${index}].${exported}`;\n}\n","import { blankLiterals, findCall, importSources, type CallSite } from './source.ts';\n\n/** Where the plugin found the two worker entries, as the shell imports them. */\nexport interface WorkerEntries {\n /** A specifier relative to the shell module, e.g. `./RenderWorker.ts`. */\n readonly renderWorker: string;\n /** The same for the application worker, or null when there is none. */\n readonly appLogicWorker: string | null;\n}\n\n/** A `createApp` call the plugin could write for, and what it still needs. */\nexport interface ShellCall {\n readonly call: CallSite;\n /**\n * False when the author wrote `renderWorker` themselves, which is the\n * documented fallback and the case where the plugin has no worker to\n * find. It still wires the overlay in.\n */\n readonly needsWorkers: boolean;\n}\n\nexport interface ShellTransformOptions {\n /** The entries to construct, or null when the author named them. */\n readonly entries: WorkerEntries | null;\n /**\n * Whether to wire the error overlay in as well. True while the dev\n * server is running and never in a build, so a production bundle\n * carries no reference to `gesso-devtools`.\n */\n readonly overlay: boolean;\n}\n\n/** Marks a module the plugin has already rewritten, so it is not done twice. */\nconst MARKER = '/* gesso-vite-plugin */';\n\n/**\n * The `createApp` call in a module, when the module has one that came\n * from the framework.\n *\n * Separate from the transform because finding the worker entries is an\n * asynchronous resolution against the bundler and worth doing only for\n * a module that turns out to be a shell, which is one module in an\n * application.\n */\nexport function findShellCall(code: string, blank = blankLiterals(code)): ShellCall | null {\n if (code.includes(MARKER)) {\n return null;\n }\n if (importSources(code, blank).get('createApp') !== 'gesso-framework') {\n return null;\n }\n const call = findCall(code, 'createApp', blank);\n if (call === null) {\n return null;\n }\n return { call, needsWorkers: !blank.slice(call.argsStart, call.argsEnd).includes('renderWorker') };\n}\n\n/**\n * Rewrites the shell module so that it names no worker.\n *\n * The one edit inside the author's own code is the call itself:\n * `createApp(...)` becomes `createApp(__gessoOptions(...))`, on the\n * same line, so every line number in the module is the one it was.\n * Everything else is appended below, as function declarations, which\n * hoist and are therefore usable from the line above them.\n *\n * The merge order is what keeps the promise that the\n * literal construction stays the documented fallback: the plugin's\n * factories go in first and the author's options spread over them, so\n * a `renderWorker` written by hand still wins.\n *\n * Returns null when there is nothing to do, which is the common case:\n * most modules do not call `createApp`.\n */\nexport function transformShell(code: string, options: ShellTransformOptions): string | null {\n const blank = blankLiterals(code);\n const shell = findShellCall(code, blank);\n if (shell === null) {\n return null;\n }\n if (options.entries === null && !options.overlay) {\n return null;\n }\n const { call } = shell;\n const args = code.slice(call.argsStart, call.argsEnd).trim();\n const rewritten = `${code.slice(0, call.argsStart)}__gessoOptions(${args === '' ? '{}' : args})${code.slice(call.argsEnd)}`;\n return `${rewritten}\\n${prelude(options)}`;\n}\n\n/**\n * The appended half: the worker construction, and in development the\n * error overlay.\n *\n * `new Worker(new URL(...), { type: 'module', ... })` is written out\n * literally because that is the only shape a bundler splits a worker\n * out of, which is the whole reason this text has to be emitted into\n * the author's module rather than living inside the framework. `name`\n * is passed as a member whose value may be undefined, which a\n * dictionary conversion treats as absent, so one construction serves\n * both the named and the unnamed case.\n */\nfunction prelude(options: ShellTransformOptions): string {\n const entries = options.entries;\n const lines = [\n MARKER,\n 'function __gessoOptions(options = {}) {',\n ' return {',\n ...(entries === null\n ? []\n : [\n ` renderWorker: () => new Worker(new URL(${JSON.stringify(entries.renderWorker)}, import.meta.url), { type: 'module', name: options.workerName }),`\n ]),\n ...(entries?.appLogicWorker == null\n ? []\n : [\n ` appLogicWorker: () => new Worker(new URL(${JSON.stringify(entries.appLogicWorker)}, import.meta.url), { type: 'module', name: options.workerName }),`\n ]),\n ...(options.overlay ? [' onError: __gessoReportError,'] : []),\n ' ...options',\n ' };',\n '}'\n ];\n return options.overlay ? `${lines.join('\\n')}\\n${OVERLAY}` : `${lines.join('\\n')}\\n`;\n}\n\n/**\n * The development error overlay, loaded the first time something\n * throws and not before.\n *\n * Lazy because it costs nothing until it is needed: a page that never\n * throws never fetches `gesso-devtools`, and a page that does gets\n * the overlay within a network round trip of the error rather than\n * paying for the package on every start.\n *\n * The host is the canvas's parent, which is the element the\n * application was mounted into: the overlay covers the application it\n * is reporting on rather than the viewport, and a Gesso app is not\n * necessarily the whole page. `document.body` is the fallback for an\n * error thrown before there is a canvas at all.\n *\n * State is kept on the function object rather than in module-level\n * `let` bindings, because this block is appended below the code that\n * calls it and a `let` read from above it would be in its temporal\n * dead zone.\n */\nconst OVERLAY = `function __gessoReportError(message, stack, source) {\n const state = (__gessoReportError.state ??= { pending: [], overlay: null, loading: false });\n state.pending.push([message, stack, source]);\n if (state.overlay !== null) {\n __gessoFlushErrors();\n return;\n }\n if (state.loading) {\n return;\n }\n state.loading = true;\n void import('gesso-devtools').then(({ mountErrorOverlay }) => {\n const canvas = document.querySelector('canvas');\n state.overlay = mountErrorOverlay(canvas?.parentElement ?? document.body);\n __gessoFlushErrors();\n });\n}\nfunction __gessoFlushErrors() {\n const state = __gessoReportError.state;\n for (const [message, stack, source] of state.pending.splice(0)) {\n state.overlay.report(message, stack, source);\n }\n}\naddEventListener('error', event => {\n const error = event.error;\n __gessoReportError(error?.message ?? event.message, error?.stack, 'window');\n});\naddEventListener('unhandledrejection', event => {\n const reason = event.reason;\n __gessoReportError(reason?.message ?? String(reason), reason?.stack, 'window');\n});\n`;\n","import type { EnvironmentModuleNode, Plugin } from 'vite';\n\nimport { transformRenderWorker } from './render.ts';\nimport { findShellCall, transformShell, type WorkerEntries } from './shell.ts';\n\nexport { transformRenderWorker, type RenderWiring } from './render.ts';\nexport {\n findShellCall,\n transformShell,\n type ShellCall,\n type ShellTransformOptions,\n type WorkerEntries\n} from './shell.ts';\nexport { blankLiterals, findCall, findCalls, firstArgumentName, importSources, type CallSite } from './source.ts';\n\n/**\n * `gesso-vite-plugin` — the plumbing between saving a file and seeing\n * the result.\n *\n * A Gesso application is three files either side of a thread barrier\n *, and until now the barrier cost the author four\n * incantations: `new Worker(new URL('./RenderWorker.ts',\n * import.meta.url), { type: 'module' })` twice, because only a literal\n * gets a chunk; `import.meta.hot.accept` with the root module named by\n * hand; and `mountErrorOverlay` wired to `onError`, without which a\n * worker exception reaches a console nobody has selected. None of them\n * is a decision. All of them are written the same way in every\n * application, and getting one wrong fails silently.\n *\n * The three-file shape stays. What this removes is the incantations,\n * and what it adds is the one diagnostic that cannot be read off the\n * screen: a save that reloads the page rather than replacing a module,\n * which traces to the root module having a\n * main-thread importer and which otherwise looks like HMR simply not\n * working.\n *\n * // vite.config.ts\n * import { gesso } from 'gesso-vite-plugin';\n * export default defineConfig({ plugins: [gesso()] });\n *\n * // main.ts\n * createApp({ history: { mode: 'path' } }).mount('#app');\n *\n * The literal construction stays supported and stays documented: the\n * plugin merges its factories *under* the options the author wrote, so\n * a hand-written `renderWorker` wins and an application that would\n * rather say it out loud can.\n */\nexport interface GessoPluginOptions {\n /**\n * The render worker entry, as a specifier the shell module could\n * import. By default the plugin tries the names below, in order, and\n * uses the first that resolves beside the shell.\n */\n readonly renderWorker?: string;\n /**\n * The application worker entry, or false for an application that has\n * none. By default the plugin looks for it and leaves it out when\n * there is nothing to find, which is the single-worker shape the\n * scaffold produces.\n */\n readonly appLogicWorker?: string | false;\n /**\n * Wire `gesso-devtools`'s error overlay into `onError` (default\n * true). Only ever in a dev server: a build carries no reference to\n * the package.\n */\n readonly overlay?: boolean;\n /** Emit the `import.meta.hot.accept` wiring (default true). */\n readonly hmr?: boolean;\n /**\n * Report a save that will reload the page instead of replacing a\n * module (default true).\n */\n readonly diagnostics?: boolean;\n}\n\n/**\n * Where a render worker entry is looked for, beside the shell.\n *\n * Ordered so that a name that says what the file is beats the generic\n * one: an application with both `RenderWorker.ts` and `worker.ts` means\n * the first, and the scaffold, which has only `worker.ts`, still\n * resolves.\n */\nconst RENDER_WORKER_NAMES = [\n './RenderWorker.ts',\n './RenderWorker.tsx',\n './render.worker.ts',\n './app.render.worker.ts',\n './worker.ts'\n];\n\n/** Where an application worker entry is looked for, beside the shell. */\nconst APP_WORKER_NAMES = ['./AppWorker.ts', './AppWorker.tsx', './app.worker.ts', './app.logic.worker.ts'];\n\n/** Modules the plugin will read: the application's own source, not its dependencies. */\nconst SOURCE = /\\.(?:[cm]?[jt]sx?)(?:$|\\?)/;\n\nexport function gesso(options: GessoPluginOptions = {}): Plugin {\n let serving = false;\n /** The shell module, once one has been transformed. */\n let shellId: string | null = null;\n /** The render worker entry, once the shell has named it. */\n let renderWorkerId: string | null = null;\n /** Files already reported as reloading, so a save is not a stream of warnings. */\n const reported = new Set<string>();\n\n return {\n name: 'gesso',\n // Before Vite's own worker and asset plugins, which are what turn\n // the emitted `new Worker(new URL(...))` into a chunk, and before\n // the TypeScript transform, so the module still reads as it was\n // written.\n enforce: 'pre',\n\n configResolved(config) {\n serving = config.command === 'serve';\n },\n\n async transform(code, id) {\n if (!SOURCE.test(id) || id.includes('/node_modules/')) {\n return null;\n }\n\n if (serving && options.hmr !== false) {\n const wiring = transformRenderWorker(code);\n if (wiring.skipped !== null) {\n this.warn(`${wiring.skipped} Saving it will reload the page rather than replace the tree.`);\n }\n if (wiring.code !== null) {\n renderWorkerId = id;\n return { code: wiring.code, map: null };\n }\n }\n\n const call = findShellCall(code);\n if (call === null) {\n return null;\n }\n shellId = id;\n const entries = call.needsWorkers ? await resolveEntries(this, id, options) : null;\n const shell = transformShell(code, { entries, overlay: serving && options.overlay !== false });\n return shell === null ? null : { code: shell, map: null };\n },\n\n /**\n * The failure that looks like a bug in the framework.\n *\n * A module reached from the main thread as well\n * as from the render worker cannot be hot-replaced, because Vite\n * propagates the invalidation to a main-thread importer that does\n * not accept and reloads the page. The screen blinks, the app\n * restarts, and nothing anywhere says why. Both ends of the module\n * graph are known here, so the answer is one walk of the importers.\n */\n hotUpdate({ modules, server }) {\n if (options.diagnostics === false || shellId === null || renderWorkerId === null) {\n return;\n }\n for (const module of modules) {\n if (module.id === null || reported.has(module.id)) {\n continue;\n }\n const importers = importerClosure(module);\n if (!importers.has(shellId) || !importers.has(renderWorkerId)) {\n continue;\n }\n reported.add(module.id);\n server.config.logger.warn(\n `[gesso] ${short(module.id, server.config.root)} is imported by the main thread as well as by the render ` +\n 'worker, so saving it reloads the page instead of replacing the tree. Reach it only from the render ' +\n \"worker's own graph to get hot replacement back.\"\n );\n }\n }\n };\n}\n\nexport default gesso;\n\n/** Every module that transitively imports this one, plus this one. */\nfunction importerClosure(module: EnvironmentModuleNode): Set<string> {\n const seen = new Set<string>();\n const queue = [module];\n while (queue.length > 0) {\n const next = queue.pop()!;\n if (next.id === null || seen.has(next.id)) {\n continue;\n }\n seen.add(next.id);\n queue.push(...next.importers);\n }\n return seen;\n}\n\n/** A path relative to the project, for a message a person reads. */\nfunction short(id: string, root: string): string {\n return id.startsWith(root) ? id.slice(root.length + 1) : id;\n}\n\n/** What the plugin resolves against, so a spec can hand it a fake. */\ninterface Resolver {\n resolve(source: string, importer: string): Promise<{ id: string } | null>;\n}\n\n/**\n * Finds the worker entries beside a shell module.\n *\n * The names are tried against the bundler's own resolver rather than\n * read off the disk: it already has the answer cached, it applies the\n * project's aliases and extensions, and it is the only thing that can\n * say whether the specifier the plugin is about to write would have\n * resolved. Nothing here touches the filesystem, which is also why the\n * package needs no node types.\n */\nasync function resolveEntries(context: Resolver, id: string, options: GessoPluginOptions): Promise<WorkerEntries> {\n const renderWorker = await firstResolved(\n context,\n id,\n options.renderWorker === undefined ? RENDER_WORKER_NAMES : [options.renderWorker]\n );\n if (renderWorker === null) {\n throw new Error(\n `gesso-vite-plugin found no render worker beside ${id}. It looked for ${RENDER_WORKER_NAMES.join(', ')}. ` +\n \"Name it with the plugin's `renderWorker` option, or write `renderWorker` in createApp() yourself.\"\n );\n }\n const appLogicWorker =\n options.appLogicWorker === false\n ? null\n : await firstResolved(\n context,\n id,\n options.appLogicWorker === undefined ? APP_WORKER_NAMES : [options.appLogicWorker]\n );\n return { renderWorker, appLogicWorker };\n}\n\n/** The first specifier that resolves beside `importer`, as written. */\nasync function firstResolved(context: Resolver, importer: string, names: readonly string[]): Promise<string | null> {\n for (const name of names) {\n if ((await context.resolve(name, importer)) !== null) {\n return name;\n }\n }\n return null;\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "gesso-vite-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Finds a Gesso application's worker entries, writes the constructions and the hot-replacement wiring, and says when a save will reload the page.",
5
+ "license": "MIT",
6
+ "author": "Kevin Baker",
7
+ "homepage": "https://github.com/kevinpbaker/gesso#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kevinpbaker/gesso.git",
11
+ "directory": "packages/vite-plugin"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/kevinpbaker/gesso/issues"
15
+ },
16
+ "keywords": [
17
+ "gesso",
18
+ "canvas",
19
+ "ui",
20
+ "typescript",
21
+ "vite",
22
+ "vite-plugin",
23
+ "hmr",
24
+ "web-worker",
25
+ "build-tool"
26
+ ],
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "default": "./dist/index.js"
33
+ }
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "files": [
39
+ "README.md",
40
+ "CHANGELOG.md",
41
+ "dist",
42
+ "LICENSE"
43
+ ],
44
+ "peerDependencies": {
45
+ "vite": "^8.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "vite": "^8.2.0"
49
+ },
50
+ "scripts": {
51
+ "build": "tsdown"
52
+ }
53
+ }