@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.
- package/README.md +75 -0
- package/index.js +96 -0
- 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
|
+
}
|