@tishlang/vite-plugin-tish 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.
Files changed (3) hide show
  1. package/README.md +75 -0
  2. package/index.js +96 -0
  3. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # @tishlang/vite-plugin-tish
2
+
3
+ Official [Vite](https://vitejs.dev) plugin for [Tish](https://tishlang.com). It compiles each
4
+ `.tish` file directly into Vite's module graph, so editing a module hot-swaps in place (HMR) instead
5
+ of triggering a full page reload, and dev builds carry source maps back to the original `.tish`.
6
+
7
+ This replaces the common out-of-band shim that ran `tish build` over the whole program and sent
8
+ `{ type: 'full-reload' }` on every change.
9
+
10
+ ## Requirements
11
+
12
+ - Vite 5 or newer.
13
+ - The `tish` CLI on your `PATH` (from `@tishlang/tish`), or pass `tishPath`.
14
+ - Tish module output (`tish build --target js --format esm`) — the same per-module ESM emit the
15
+ plugin relies on. See [the JS target docs](https://tishlang.com/docs/reference/js-target).
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install -D @tishlang/vite-plugin-tish
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```js
26
+ // vite.config.js
27
+ import { defineConfig } from "vite";
28
+ import tish from "@tishlang/vite-plugin-tish";
29
+
30
+ export default defineConfig({
31
+ plugins: [tish()],
32
+ });
33
+ ```
34
+
35
+ Import `.tish` modules as normal ES modules:
36
+
37
+ ```html
38
+ <script type="module" src="/src/main.tish"></script>
39
+ ```
40
+
41
+ ```tish
42
+ // src/main.tish
43
+ import { makeCounter } from "./counter.tish"
44
+
45
+ let next = makeCounter(0)
46
+ console.log(next())
47
+ ```
48
+
49
+ Editing `counter.tish` updates the running app without a full reload, and runtime errors point back
50
+ to the `.tish` source in Vite's overlay and the browser debugger.
51
+
52
+ ## Options
53
+
54
+ | Option | Type | Default | Description |
55
+ |--------|------|---------|-------------|
56
+ | `tishPath` | `string` | `$TISH_PATH` or `tish` | Path to the `tish` binary. |
57
+ | `projectRoot` | `string` | Vite `root` | Root for resolving bare specifiers / `node_modules`. |
58
+ | `mode` | `"hmr" \| "full-reload"` | `"hmr"` | `hmr` hot-swaps modules; `full-reload` reloads the whole page on any `.tish` change. |
59
+
60
+ ### When to use `full-reload`
61
+
62
+ For apps compiled to `--target bytecode` (the wasm VM owns all engine state), per-module HMR does not
63
+ apply — use `mode: "full-reload"` as the documented fallback.
64
+
65
+ ## How it works
66
+
67
+ The plugin registers `resolveId` + `load` so each `.tish` import becomes one module in Vite's graph,
68
+ compiled via `tish compile-module --target js --format esm --vite-dev`. Relative `.tish` specifiers
69
+ are preserved so Vite resolves dependencies through the plugin per module; a self-accepting
70
+ `import.meta.hot.accept()` boundary is injected so leaf edits hot-swap. `handleHotUpdate` returns the
71
+ changed module node(s) to Vite for per-module invalidation instead of a full reload.
72
+
73
+ ## License
74
+
75
+ [PIF](https://payitforwardlicense.com/)
package/index.js ADDED
@@ -0,0 +1,96 @@
1
+ // Vite plugin for Tish (#284). Compiles each `.tish` file into Vite's module graph one module at a
2
+ // time via `tish compile-module`, so editing a leaf module hot-swaps without a full page reload —
3
+ // instead of the older out-of-band "compile the whole bundle and full-reload on every change" shim.
4
+ //
5
+ // Dev compiles request a source map (`tish compile-module --source-map`) so Vite's error overlay
6
+ // and the browser debugger resolve back to the original `.tish`.
7
+
8
+ import { execFileSync } from "node:child_process";
9
+ import { existsSync } from "node:fs";
10
+ import path from "node:path";
11
+
12
+ const TISH_EXT = ".tish";
13
+
14
+ /**
15
+ * @param {object} [opts]
16
+ * @param {string} [opts.tishPath] Path to the `tish` binary (default: $TISH_PATH or `tish` on PATH).
17
+ * @param {string} [opts.projectRoot] Root for resolving bare specifiers / node_modules (default: Vite root).
18
+ * @param {"hmr"|"full-reload"} [opts.mode] `hmr` (default) hot-swaps modules; `full-reload` reloads the
19
+ * page on any `.tish` change (the documented fallback for `--target bytecode` apps).
20
+ */
21
+ export default function tishPlugin(opts = {}) {
22
+ const tishPath = opts.tishPath ?? process.env.TISH_PATH ?? "tish";
23
+ const mode = opts.mode ?? "hmr";
24
+ let projectRoot = opts.projectRoot;
25
+
26
+ // Compile a single `.tish` file to one ES module. In `hmr` mode we request a source map and parse
27
+ // the `{ js, map }` envelope; otherwise we take raw JS on stdout.
28
+ function compileModule(file) {
29
+ const args = [
30
+ "compile-module",
31
+ file,
32
+ "--target",
33
+ "js",
34
+ "--format",
35
+ "esm",
36
+ "--vite-dev",
37
+ ];
38
+ args.push(mode === "hmr" ? "--source-map" : "--no-source-map");
39
+ if (projectRoot) args.push("--project-root", projectRoot);
40
+ const stdout = execFileSync(tishPath, args, {
41
+ encoding: "utf8",
42
+ maxBuffer: 64 * 1024 * 1024,
43
+ });
44
+ if (mode === "hmr") {
45
+ const { js, map } = JSON.parse(stdout);
46
+ return { code: js, map };
47
+ }
48
+ return { code: stdout, map: null };
49
+ }
50
+
51
+ return {
52
+ name: "vite-plugin-tish",
53
+ enforce: "pre",
54
+
55
+ configResolved(config) {
56
+ if (!projectRoot) projectRoot = config.root;
57
+ },
58
+
59
+ // Bring `.tish` imports into Vite's module graph: resolve relative `.tish` specifiers to absolute
60
+ // file paths so Vite addresses each module and can invalidate it individually. Root-relative URLs
61
+ // (`/src/x.tish`) and bare specifiers are left to Vite's resolver, which maps them to real files.
62
+ resolveId(source, importer) {
63
+ if (!source.endsWith(TISH_EXT)) return null;
64
+ if (source.startsWith(".") && importer) {
65
+ return path.resolve(path.dirname(importer.split("?")[0]), source);
66
+ }
67
+ if (path.isAbsolute(source) && existsSync(source)) {
68
+ return source;
69
+ }
70
+ return null;
71
+ },
72
+
73
+ load(id) {
74
+ const file = id.split("?")[0];
75
+ if (!file.endsWith(TISH_EXT)) return null;
76
+ const { code, map } = compileModule(file);
77
+ if (mode !== "hmr") return { code, map };
78
+ // Self-accepting boundary: editing this module re-runs it without reloading the page. Frameworks
79
+ // (e.g. Lattish) can register their own `import.meta.hot.accept` handlers on top.
80
+ const withBoundary = `${code}\nif (import.meta.hot) { import.meta.hot.accept(); }\n`;
81
+ return { code: withBoundary, map };
82
+ },
83
+
84
+ handleHotUpdate(ctx) {
85
+ const { file, server } = ctx;
86
+ if (!file.endsWith(TISH_EXT)) return;
87
+ if (mode === "full-reload") {
88
+ server.ws.send({ type: "full-reload" });
89
+ return [];
90
+ }
91
+ // Hand Vite the changed module node(s) so it performs per-module HMR rather than a full reload.
92
+ const mods = server.moduleGraph.getModulesByFile(file);
93
+ return mods ? [...mods] : undefined;
94
+ },
95
+ };
96
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@tishlang/vite-plugin-tish",
3
+ "version": "0.1.0",
4
+ "description": "Vite plugin for Tish: in-graph .tish compilation with HMR and source maps for dev.",
5
+ "license": "PIF",
6
+ "type": "module",
7
+ "main": "./index.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./index.js",
11
+ "default": "./index.js"
12
+ }
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/tishlang/tish.git",
17
+ "directory": "npm/vite-plugin-tish"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "scripts": {
23
+ "test": "vitest run"
24
+ },
25
+ "files": ["index.js", "README.md"],
26
+ "peerDependencies": {
27
+ "vite": ">=5",
28
+ "@tishlang/tish": "*"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "@tishlang/tish": {
32
+ "optional": true
33
+ }
34
+ },
35
+ "devDependencies": {
36
+ "vite": "^7",
37
+ "vitest": "^3"
38
+ },
39
+ "engines": {
40
+ "node": ">=22"
41
+ },
42
+ "keywords": ["tish", "vite", "vite-plugin", "hmr", "lattish"]
43
+ }