mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
9c37b0217c
* fix(cli): compile runes in .svelte.ts / .svelte.js modules The svelte plugin only ran on `/\.svelte$/`, so a rune module like `lib.svelte.ts` was bundled as plain TypeScript: the types were stripped and `$state(0)` survived as a call to an undefined global, blowing up at runtime with "ReferenceError: $state is not defined". Route those files through `compileModule`. It parses with plain acorn and chokes on TypeScript, so types come off first via esbuild's transform — which is what vite-plugin-svelte gets for free by running after Vite's own esbuild transform. `wmill app dev` picks this up too; watch mode builds its plugin list with the same `createFrameworkPlugins`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: pin ui_builder to 1ffb28e Picks up the matching rune-module fix in the in-editor builder (windmill-labs/windmill-code-ui-builder#24), so `.svelte.ts` modules compile in the editor as well as through the CLI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): compile raw apps with the app's own svelte compiler Svelte 5.52.0 moved delegated event handlers off `element.__click` onto a Symbol-keyed map. A raw app supplies its own Svelte *runtime* via package.json, but `import("svelte/compiler")` resolves against the CLI, whose own svelte floats independently — so the two can land on opposite sides of that change and the app builds, renders, and has every onclick/oninput silently dead. Resolve the compiler from the app's node_modules instead, so compiler and runtime are the same install by construction, and raise the CLI's own floor past the break for the fallback path. Also pin ui_builder to 013bf67, which carries the matching fix for the in-editor builder (windmill-labs/windmill-code-ui-builder#25), and move the Svelte raw-app template onto the same range. Those two go together: the new builder rejects a runtime that sits on the far side of the ABI break from its compiler. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
/**
|
|
* `lib.svelte.ts` / `lib.svelte.js` modules are plain modules that may use
|
|
* runes. They need `svelte.compileModule`; without it esbuild happily bundles
|
|
* `$state(...)` as an ordinary call and the app dies at runtime with
|
|
* "ReferenceError: $state is not defined".
|
|
*/
|
|
|
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
import * as fs from "node:fs";
|
|
import * as path from "node:path";
|
|
import * as os from "node:os";
|
|
import { createBundle } from "../src/commands/app/bundle.ts";
|
|
|
|
let tempDir: string;
|
|
let originalCwd: string;
|
|
|
|
// Runes used as bare calls, ignoring the `$state(...)` mentions inside Svelte's
|
|
// own warning message template literals.
|
|
function bareRuneCalls(js: string): string[] {
|
|
return [...js.matchAll(/(^|[^.\w$`])\$(state|derived|effect|props)\s*\(/g)].map(
|
|
(m) => m[0]
|
|
);
|
|
}
|
|
|
|
function writeApp(files: Record<string, string>) {
|
|
for (const [name, content] of Object.entries(files)) {
|
|
fs.writeFileSync(path.join(tempDir, name), content, "utf-8");
|
|
}
|
|
}
|
|
|
|
async function bundle(entry: string): Promise<string> {
|
|
const { js } = await createBundle({
|
|
entryPoint: path.join(tempDir, entry),
|
|
minify: false,
|
|
sourcemap: false,
|
|
});
|
|
return js;
|
|
}
|
|
|
|
beforeAll(() => {
|
|
originalCwd = process.cwd();
|
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "raw-app-svelte-module-"));
|
|
fs.writeFileSync(
|
|
path.join(tempDir, "package.json"),
|
|
JSON.stringify({ name: "app", private: true, dependencies: { svelte: "*" } }),
|
|
"utf-8"
|
|
);
|
|
// Reuse the CLI's own svelte install instead of paying for an npm install;
|
|
// `ensureNodeModules` only checks that the directory is there.
|
|
fs.symlinkSync(
|
|
path.join(originalCwd, "node_modules"),
|
|
path.join(tempDir, "node_modules")
|
|
);
|
|
process.chdir(tempDir);
|
|
});
|
|
|
|
afterAll(() => {
|
|
process.chdir(originalCwd);
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe("svelte plugin: .svelte.ts modules", () => {
|
|
test("compiles runes in a TypeScript rune module and the bundle runs", async () => {
|
|
writeApp({
|
|
"lib.svelte.ts": `export class Cycle<T> {
|
|
#options: T[] = [];
|
|
#index = $state(0);
|
|
current = $derived(this.#options[this.#index]);
|
|
|
|
constructor(options: T[]) {
|
|
this.#options = options;
|
|
}
|
|
|
|
next() {
|
|
this.#index = (this.#index + 1) % this.#options.length;
|
|
}
|
|
}
|
|
`,
|
|
"ts_entry.ts": `import { Cycle } from './lib.svelte';
|
|
|
|
const cycle = new Cycle(['a', 'b', 'c']);
|
|
cycle.next();
|
|
(globalThis as any).__cycleResult = cycle.current;
|
|
`,
|
|
});
|
|
|
|
const js = await bundle("ts_entry.ts");
|
|
|
|
expect(bareRuneCalls(js)).toEqual([]);
|
|
// Runtime is the real check: unfixed, this throws
|
|
// "ReferenceError: $state is not defined".
|
|
new Function(js)();
|
|
expect((globalThis as any).__cycleResult).toBe("b");
|
|
});
|
|
|
|
test("compiles runes in a JavaScript rune module", async () => {
|
|
writeApp({
|
|
"counter.svelte.js": `export const counter = $state({ n: 0 });
|
|
|
|
export function bump() {
|
|
counter.n += 1;
|
|
}
|
|
`,
|
|
"js_entry.ts": `import { counter, bump } from './counter.svelte.js';
|
|
|
|
bump();
|
|
bump();
|
|
(globalThis as any).__counterResult = counter.n;
|
|
`,
|
|
});
|
|
|
|
const js = await bundle("js_entry.ts");
|
|
|
|
expect(bareRuneCalls(js)).toEqual([]);
|
|
new Function(js)();
|
|
expect((globalThis as any).__counterResult).toBe(2);
|
|
});
|
|
});
|