create-zoijs 0.1.2 → 0.1.4
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 +34 -0
- package/README.md +13 -4
- package/bin/create-zoijs.js +7 -5
- package/package.json +2 -2
- package/templates/app/_vscode/extensions.json +7 -0
- package/templates/app/jsconfig.json +16 -0
- package/templates/basic/_vscode/extensions.json +7 -0
- package/templates/basic/jsconfig.json +16 -0
- package/templates/library/README.md +48 -0
- package/templates/library/_gitignore +1 -0
- package/templates/library/_vscode/extensions.json +7 -0
- package/templates/library/package.json +38 -0
- package/templates/library/src/index.d.ts +24 -0
- package/templates/library/src/index.js +30 -0
- package/templates/library/tests/index.test.js +29 -0
- package/templates/library/tsconfig.json +14 -0
- package/templates/minimal/README.md +34 -0
- package/templates/minimal/app.js +18 -0
- package/templates/minimal/index.html +17 -0
- package/templates/typescript/README.md +57 -0
- package/templates/typescript/_gitignore +1 -0
- package/templates/typescript/_vscode/extensions.json +7 -0
- package/templates/typescript/dev-server.mjs +74 -0
- package/templates/typescript/index.html +22 -0
- package/templates/typescript/package.json +16 -0
- package/templates/typescript/src/app.js +64 -0
- package/templates/typescript/src/style.css +33 -0
- package/templates/typescript/tsconfig.json +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `create-zoijs` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.1.4 — 2026-06-27
|
|
6
|
+
|
|
7
|
+
Scaffolded apps now come with editor config for a great out-of-the-box experience —
|
|
8
|
+
no toolchain required. The CLI API and the app code are unchanged.
|
|
9
|
+
|
|
10
|
+
- **`.vscode/extensions.json`** (templates `app`, `basic`, `typescript`, `library`) —
|
|
11
|
+
recommends a highlighter for `` html`` `` templates (`bierner.lit-html`), the ESLint
|
|
12
|
+
extension, and Prettier. VS Code suggests them when you open the project.
|
|
13
|
+
- **`jsconfig.json`** (templates `app`, `basic`) — IntelliSense (autocomplete, hover,
|
|
14
|
+
go-to-definition) from the types every Zoijs package ships, with no build step. Flip
|
|
15
|
+
`checkJs` on to also type-check your JavaScript. (The `typescript` and `library`
|
|
16
|
+
templates already carry a `tsconfig.json`.)
|
|
17
|
+
- See the [Editor Setup guide](https://zoijs.dev/editor-setup).
|
|
18
|
+
|
|
19
|
+
## 0.1.3 — 2026-06-26
|
|
20
|
+
|
|
21
|
+
Three new templates (now five total). No change to existing templates or the CLI
|
|
22
|
+
API; `app` stays the default.
|
|
23
|
+
|
|
24
|
+
- **`typescript`** — the counter as **type-checked JavaScript**: `// @ts-check` +
|
|
25
|
+
a strict `tsconfig.json` + `npm run typecheck` (`tsc --noEmit`) give full
|
|
26
|
+
TypeScript safety using `@zoijs/core`'s shipped types, with **no build step** (the
|
|
27
|
+
browser runs the `.js` as-is). Keeps the dev server on 7310.
|
|
28
|
+
- **`minimal`** — the smallest scaffold: two flat files (`index.html` + `app.js`)
|
|
29
|
+
loading `@zoijs/core` from a CDN. No `package.json`, no install, no dev server —
|
|
30
|
+
run with `npx serve . -l 7310`.
|
|
31
|
+
- **`library`** — a starter for authoring a Zoijs-based package: `src/index.js` +
|
|
32
|
+
hand-written `index.d.ts`, an `exports`/`types` `package.json` with a peer
|
|
33
|
+
dependency on `@zoijs/core`, and a `node:test` suite — the shape the official
|
|
34
|
+
optional packages use.
|
|
35
|
+
- The scaffolder now treats `.ts`/`.d.ts` as text (token substitution), and the
|
|
36
|
+
CLI's own `npm test` targets its own test file so it no longer runs the
|
|
37
|
+
templates' bundled test files.
|
|
38
|
+
|
|
5
39
|
## 0.1.2 — 2026-06-26
|
|
6
40
|
|
|
7
41
|
Dev-server output polish. No CLI API changes; same zero-dependency server on the
|
package/README.md
CHANGED
|
@@ -56,15 +56,24 @@ one.
|
|
|
56
56
|
## Templates
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
npm create zoijs@latest my-app
|
|
60
|
-
npm create zoijs@latest my-app --template
|
|
61
|
-
npm create zoijs@latest my-app --template
|
|
59
|
+
npm create zoijs@latest my-app # default: the "app" template
|
|
60
|
+
npm create zoijs@latest my-app --template app # a polished project dashboard
|
|
61
|
+
npm create zoijs@latest my-app --template basic # a basic counter
|
|
62
|
+
npm create zoijs@latest my-app --template typescript # type-checked JS, no build step
|
|
63
|
+
npm create zoijs@latest my-app --template minimal # two flat files, CDN, no install
|
|
64
|
+
npm create zoijs@latest my-app --template library # author a Zoijs-based package
|
|
62
65
|
```
|
|
63
66
|
|
|
64
67
|
- **`app`** (default) — a polished project dashboard (hero, stat cards, filters,
|
|
65
68
|
empty state) showing `html`, `mount`, `createState`, `computed`, `each`,
|
|
66
69
|
conditional rendering, and parent⇄child component communication.
|
|
67
|
-
- **`basic`** — a
|
|
70
|
+
- **`basic`** — a basic counter, a small local app with a dev server.
|
|
71
|
+
- **`typescript`** — the counter as type-checked JavaScript (`// @ts-check` +
|
|
72
|
+
strict `tsconfig.json` + `npm run typecheck`). Full TS safety, **no build step**.
|
|
73
|
+
- **`minimal`** — the smallest scaffold: two flat files loading `@zoijs/core` from a
|
|
74
|
+
CDN. No install, no dev server — run with `npx serve . -l 7310`.
|
|
75
|
+
- **`library`** — a starter for authoring a Zoijs-based package: `src` + `.d.ts`,
|
|
76
|
+
`exports`/`types`, a peer dep on `@zoijs/core`, and a `node:test` suite.
|
|
68
77
|
|
|
69
78
|
## What it does
|
|
70
79
|
|
package/bin/create-zoijs.js
CHANGED
|
@@ -17,7 +17,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
17
17
|
|
|
18
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
19
|
|
|
20
|
-
export const TEMPLATES = ["basic", "app"];
|
|
20
|
+
export const TEMPLATES = ["basic", "app", "typescript", "minimal", "library"];
|
|
21
21
|
export const DEFAULT_TEMPLATE = "app";
|
|
22
22
|
const TEMPLATES_DIR = path.join(__dirname, "..", "templates");
|
|
23
23
|
|
|
@@ -75,9 +75,13 @@ export function ensureTargetAvailable(targetDir) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
const TEXT_EXT = new Set([".js", ".mjs", ".html", ".css", ".json", ".md", ".txt", ".svg"]);
|
|
78
|
+
const TEXT_EXT = new Set([".js", ".ts", ".mjs", ".html", ".css", ".json", ".md", ".txt", ".svg"]);
|
|
79
79
|
const isText = (file) => file === "_gitignore" || TEXT_EXT.has(path.extname(file));
|
|
80
80
|
|
|
81
|
+
// npm strips/renames files and dirs that start with a dot from a published package,
|
|
82
|
+
// so templates ship them with a "_" prefix and we restore the dot name on copy.
|
|
83
|
+
const RENAME = { _gitignore: ".gitignore", _vscode: ".vscode" };
|
|
84
|
+
|
|
81
85
|
function applyTokens(content, tokens) {
|
|
82
86
|
return content.replaceAll("{{APP_NAME}}", tokens.APP_NAME).replaceAll("{{APP_TITLE}}", tokens.APP_TITLE);
|
|
83
87
|
}
|
|
@@ -86,9 +90,7 @@ function copyDir(srcDir, destDir, tokens, written) {
|
|
|
86
90
|
fs.mkdirSync(destDir, { recursive: true });
|
|
87
91
|
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
88
92
|
const srcPath = path.join(srcDir, entry.name);
|
|
89
|
-
|
|
90
|
-
// ship it as "_gitignore" and we rename it on copy.
|
|
91
|
-
const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
|
|
93
|
+
const destName = RENAME[entry.name] ?? entry.name; // restore dot-prefixed names
|
|
92
94
|
const destPath = path.join(destDir, destName);
|
|
93
95
|
if (entry.isDirectory()) {
|
|
94
96
|
copyDir(srcPath, destPath, tokens, written);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-zoijs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Create a new Zoijs app with one command. Plain HTML, CSS, and JavaScript — no build step.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,6 +38,6 @@
|
|
|
38
38
|
"no-jsx"
|
|
39
39
|
],
|
|
40
40
|
"scripts": {
|
|
41
|
-
"test": "node --test"
|
|
41
|
+
"test": "node --test tests/create.test.js"
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
// jsconfig.json — gives your editor IntelliSense (autocomplete, hover, go-to-
|
|
3
|
+
// definition) for Zoijs and the DOM, from the types every package ships. No build
|
|
4
|
+
// step, no TypeScript migration. Flip "checkJs" to true to also TYPE-CHECK your
|
|
5
|
+
// JavaScript against those types (or add `// @ts-check` to a single file).
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"target": "ES2022",
|
|
10
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
11
|
+
"checkJs": false,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"skipLibCheck": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
// jsconfig.json — gives your editor IntelliSense (autocomplete, hover, go-to-
|
|
3
|
+
// definition) for Zoijs and the DOM, from the types every package ships. No build
|
|
4
|
+
// step, no TypeScript migration. Flip "checkJs" to true to also TYPE-CHECK your
|
|
5
|
+
// JavaScript against those types (or add `// @ts-check` to a single file).
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"target": "ES2022",
|
|
10
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
11
|
+
"checkJs": false,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"skipLibCheck": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# {{APP_NAME}}
|
|
2
|
+
|
|
3
|
+
A small library built on [Zoijs](https://zoijs.dev) — plain JavaScript, types
|
|
4
|
+
shipped as `.d.ts`, **no build step.**
|
|
5
|
+
|
|
6
|
+
This is the same shape the official optional packages (`@zoijs/forms`,
|
|
7
|
+
`@zoijs/storage`, …) use: one tiny module that imports **only** the core's public
|
|
8
|
+
API, with hand-written type definitions and a test suite.
|
|
9
|
+
|
|
10
|
+
## Develop
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install
|
|
14
|
+
npm test # behavior (node --test)
|
|
15
|
+
npm run test:types # type-check src against the .d.ts (tsc --noEmit)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
There's nothing to compile — consumers import your `src/index.js` directly via an
|
|
19
|
+
import map or a bundler of their choosing.
|
|
20
|
+
|
|
21
|
+
## Layout
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
{{APP_NAME}}/
|
|
25
|
+
package.json name, exports, types, peer dep on @zoijs/core
|
|
26
|
+
tsconfig.json strict, checkJs, noEmit (type-check only)
|
|
27
|
+
src/
|
|
28
|
+
index.js your library (imports only @zoijs/core's public API)
|
|
29
|
+
index.d.ts hand-written types (imports core types so they never drift)
|
|
30
|
+
tests/
|
|
31
|
+
index.test.js node:test
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## What's inside
|
|
35
|
+
|
|
36
|
+
`src/index.js` ships one example helper — `counter(initial)` — to show the
|
|
37
|
+
pattern: a factory that returns reader methods plus the raw reactive cell.
|
|
38
|
+
**Replace it with your own.** Keep the package small and single-purpose; depend
|
|
39
|
+
only on `@zoijs/core`.
|
|
40
|
+
|
|
41
|
+
## Publishing
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm publish --access public
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Add a `LICENSE`, fill in the `description`/`keywords` in `package.json`, and you're
|
|
48
|
+
done. Learn more at [zoijs.dev](https://zoijs.dev).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
node_modules
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{APP_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A small library built on @zoijs/core.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"types": "src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "node --test",
|
|
25
|
+
"test:types": "tsc --noEmit -p tsconfig.json"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"zoijs"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@zoijs/core": "^1.2.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@zoijs/core": "^1.2.0",
|
|
36
|
+
"typescript": "^5.9.3"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Type definitions for {{APP_NAME}}.
|
|
2
|
+
//
|
|
3
|
+
// Authored in plain JavaScript; these declarations add editor autocomplete and
|
|
4
|
+
// optional type-checking without requiring TypeScript. Core types are imported
|
|
5
|
+
// from @zoijs/core so shapes never drift.
|
|
6
|
+
|
|
7
|
+
import type { State } from "@zoijs/core";
|
|
8
|
+
|
|
9
|
+
/** A reactive counter created by {@link counter}. */
|
|
10
|
+
export interface Counter {
|
|
11
|
+
/** The current value (reactive inside a binding). */
|
|
12
|
+
value(): number;
|
|
13
|
+
/** Add `by` (default 1). */
|
|
14
|
+
increment(by?: number): void;
|
|
15
|
+
/** Subtract `by` (default 1). */
|
|
16
|
+
decrement(by?: number): void;
|
|
17
|
+
/** Reset to the initial value. */
|
|
18
|
+
reset(): void;
|
|
19
|
+
/** The underlying reactive cell — advanced / direct access. */
|
|
20
|
+
state: State<number>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Create a reactive counter built on `@zoijs/core`. */
|
|
24
|
+
export function counter(initial?: number): Counter;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// {{APP_NAME}} — a small library built on @zoijs/core.
|
|
2
|
+
//
|
|
3
|
+
// Optional Zoijs packages are tiny and import ONLY the core's public API — never
|
|
4
|
+
// each other's internals. This example helper does the same; replace it with your
|
|
5
|
+
// own. Run `npm test` (behavior) and `npm run test:types` (the .d.ts) as you go.
|
|
6
|
+
|
|
7
|
+
import { createState } from "@zoijs/core";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A reactive counter built on `createState`. A small, illustrative helper showing
|
|
11
|
+
* the shape an optional package takes: a factory that returns reader methods plus
|
|
12
|
+
* the raw reactive cell for advanced use.
|
|
13
|
+
*
|
|
14
|
+
* @param {number} [initial]
|
|
15
|
+
*/
|
|
16
|
+
export function counter(initial = 0) {
|
|
17
|
+
const state = createState(initial);
|
|
18
|
+
return {
|
|
19
|
+
/** The current value (reactive inside a binding). */
|
|
20
|
+
value: () => state.get(),
|
|
21
|
+
/** Add `by` (default 1). */
|
|
22
|
+
increment: (by = 1) => state.set(state.get() + by),
|
|
23
|
+
/** Subtract `by` (default 1). */
|
|
24
|
+
decrement: (by = 1) => state.set(state.get() - by),
|
|
25
|
+
/** Reset to the initial value. */
|
|
26
|
+
reset: () => state.set(initial),
|
|
27
|
+
/** The underlying reactive cell — advanced / direct access. */
|
|
28
|
+
state,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Tests for {{APP_NAME}}. Pure logic — no DOM needed. Run with `npm test`.
|
|
2
|
+
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { counter } from "../src/index.js";
|
|
6
|
+
|
|
7
|
+
test("counts up and down", () => {
|
|
8
|
+
const c = counter(0);
|
|
9
|
+
assert.equal(c.value(), 0);
|
|
10
|
+
c.increment();
|
|
11
|
+
assert.equal(c.value(), 1);
|
|
12
|
+
c.increment(5);
|
|
13
|
+
assert.equal(c.value(), 6);
|
|
14
|
+
c.decrement(2);
|
|
15
|
+
assert.equal(c.value(), 4);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("reset restores the initial value", () => {
|
|
19
|
+
const c = counter(10);
|
|
20
|
+
c.increment();
|
|
21
|
+
c.reset();
|
|
22
|
+
assert.equal(c.value(), 10);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("exposes the raw reactive cell", () => {
|
|
26
|
+
const c = counter(3);
|
|
27
|
+
assert.equal(typeof c.state.get, "function");
|
|
28
|
+
assert.equal(c.state.peek(), 3);
|
|
29
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"noEmit": true,
|
|
4
|
+
"checkJs": true,
|
|
5
|
+
"strict": true,
|
|
6
|
+
"target": "ES2020",
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"lib": ["ES2020", "DOM"],
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"types": []
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# {{APP_TITLE}}
|
|
2
|
+
|
|
3
|
+
The smallest possible [Zoijs](https://zoijs.dev) app — **two files, no install, no
|
|
4
|
+
build step.** A blank canvas.
|
|
5
|
+
|
|
6
|
+
## Run it
|
|
7
|
+
|
|
8
|
+
Any static file server works. The simplest:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx serve . -l 7310
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Then open <http://localhost:7310>.
|
|
15
|
+
|
|
16
|
+
## What's here
|
|
17
|
+
|
|
18
|
+
- `index.html` — loads `@zoijs/core` from a CDN (pinned to the `v1` major) and
|
|
19
|
+
mounts `app.js`.
|
|
20
|
+
- `app.js` — a counter: state, markup, and one `mount` call.
|
|
21
|
+
|
|
22
|
+
That's the whole project. Edit `app.js` and reload — there is nothing to build.
|
|
23
|
+
|
|
24
|
+
## Growing up
|
|
25
|
+
|
|
26
|
+
When you want local packages instead of the CDN, or a few components to organize,
|
|
27
|
+
scaffold a fuller starter:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm create zoijs@latest my-app # the app dashboard
|
|
31
|
+
npm create zoijs@latest my-app --template basic # a basic counter
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Learn more at [zoijs.dev](https://zoijs.dev).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { html, mount, createState } from "@zoijs/core";
|
|
2
|
+
|
|
3
|
+
// A complete Zoijs app in one file. No install, no build step.
|
|
4
|
+
function App() {
|
|
5
|
+
const count = createState(0);
|
|
6
|
+
|
|
7
|
+
return html`
|
|
8
|
+
<main>
|
|
9
|
+
<h1>{{APP_TITLE}}</h1>
|
|
10
|
+
<p>Edit <code>app.js</code> and reload.</p>
|
|
11
|
+
<button onclick=${() => count.set(count.get() + 1)}>
|
|
12
|
+
Clicked ${() => count.get()} times
|
|
13
|
+
</button>
|
|
14
|
+
</main>
|
|
15
|
+
`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
mount(App, "#app");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{APP_TITLE}}</title>
|
|
7
|
+
<!-- Zero install, zero build: load @zoijs/core straight from a CDN.
|
|
8
|
+
Pin the major version (@1) so your app never breaks unexpectedly. -->
|
|
9
|
+
<script type="importmap">
|
|
10
|
+
{ "imports": { "@zoijs/core": "https://esm.sh/@zoijs/core@1" } }
|
|
11
|
+
</script>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="app"></div>
|
|
15
|
+
<script type="module" src="./app.js"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# {{APP_TITLE}}
|
|
2
|
+
|
|
3
|
+
A [Zoijs](https://zoijs.dev) app with **TypeScript-grade safety and no build step.**
|
|
4
|
+
|
|
5
|
+
The source is plain JavaScript the browser runs as-is — but every file starts with
|
|
6
|
+
`// @ts-check`, and together with the type definitions shipped in `@zoijs/core` you
|
|
7
|
+
get full type-checking, autocomplete, and inline errors. There is **no compile
|
|
8
|
+
step**: you never turn `.ts` into `.js`, because there's no `.ts` to begin with.
|
|
9
|
+
|
|
10
|
+
## Develop
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install
|
|
14
|
+
npm run dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
Zoijs dev server
|
|
19
|
+
|
|
20
|
+
- Local: http://localhost:7310
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Open the printed URL (it falls back to 7311–7313 if the port is busy).
|
|
24
|
+
|
|
25
|
+
## Type-check
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run typecheck
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Runs `tsc --noEmit` against `src/**/*.js` using `@zoijs/core`'s types — no output
|
|
32
|
+
files, just type errors if any. Wire it into CI or your editor.
|
|
33
|
+
|
|
34
|
+
## How the typing works
|
|
35
|
+
|
|
36
|
+
- `// @ts-check` at the top of a file turns on type-checking for that file.
|
|
37
|
+
- `@typedef` / `@type` JSDoc comments add your own types (see `src/app.js`).
|
|
38
|
+
- `@zoijs/core` ships `.d.ts`, so `createState`, `html`, `each`, etc. are fully
|
|
39
|
+
typed — e.g. `createState<Todo[]>(…)` gives you a typed list.
|
|
40
|
+
|
|
41
|
+
Prefer authoring real `.ts`? You can, but it would require adding a `tsc` compile
|
|
42
|
+
step — the one thing Zoijs avoids. This template keeps the no-build promise.
|
|
43
|
+
|
|
44
|
+
## Project layout
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
{{APP_NAME}}/
|
|
48
|
+
index.html import map + #app mount point
|
|
49
|
+
tsconfig.json strict, checkJs, noEmit (type-check only)
|
|
50
|
+
dev-server.mjs tiny zero-dependency static server (npm run dev)
|
|
51
|
+
src/
|
|
52
|
+
app.js your app, typed with @ts-check
|
|
53
|
+
style.css plain CSS
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
No bundler, no build step. Open the files and read them. Learn more at
|
|
57
|
+
[zoijs.dev](https://zoijs.dev).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
node_modules
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// dev-server.mjs — a tiny static file server for this Zoijs app.
|
|
2
|
+
//
|
|
3
|
+
// Zero dependencies (Node built-ins only), no build step, no bundler. It serves
|
|
4
|
+
// the project files so the import map in index.html can load @zoijs/core from
|
|
5
|
+
// node_modules. Tries port 7310, then 7311 / 7312 / 7313 if one is busy.
|
|
6
|
+
//
|
|
7
|
+
// node dev-server.mjs (this is what `npm run dev` runs)
|
|
8
|
+
|
|
9
|
+
import { createServer } from "node:http";
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
import { extname, join, normalize } from "node:path";
|
|
12
|
+
|
|
13
|
+
const PORTS = [7310, 7311, 7312, 7313];
|
|
14
|
+
const ROOT = process.cwd();
|
|
15
|
+
|
|
16
|
+
const TYPES = {
|
|
17
|
+
".html": "text/html; charset=utf-8",
|
|
18
|
+
".js": "text/javascript; charset=utf-8",
|
|
19
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
20
|
+
".css": "text/css; charset=utf-8",
|
|
21
|
+
".json": "application/json; charset=utf-8",
|
|
22
|
+
".svg": "image/svg+xml",
|
|
23
|
+
".ico": "image/x-icon",
|
|
24
|
+
".png": "image/png",
|
|
25
|
+
".woff2": "font/woff2",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const server = createServer(async (req, res) => {
|
|
29
|
+
let pathname = decodeURIComponent(new URL(req.url, "http://localhost").pathname);
|
|
30
|
+
if (pathname === "/") pathname = "/index.html";
|
|
31
|
+
|
|
32
|
+
// Resolve inside ROOT and refuse anything that climbs out of it.
|
|
33
|
+
const filePath = join(ROOT, normalize(pathname));
|
|
34
|
+
if (!filePath.startsWith(ROOT)) {
|
|
35
|
+
res.writeHead(403).end("Forbidden");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const body = await readFile(filePath);
|
|
40
|
+
res.writeHead(200, { "Content-Type": TYPES[extname(filePath)] || "application/octet-stream" });
|
|
41
|
+
res.end(body);
|
|
42
|
+
} catch {
|
|
43
|
+
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }).end("Not found");
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
function start(i = 0) {
|
|
48
|
+
if (i >= PORTS.length) {
|
|
49
|
+
console.error(`\nAll dev ports are busy (${PORTS.join(", ")}). Free one and try again.\n`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
// Paired once-listeners: each attempt removes its sibling so a failed bind
|
|
53
|
+
// can't leave a stale "listening" handler that prints the wrong port later.
|
|
54
|
+
const onError = (err) => {
|
|
55
|
+
server.removeListener("listening", onListening);
|
|
56
|
+
if (err.code === "EADDRINUSE") start(i + 1);
|
|
57
|
+
else {
|
|
58
|
+
console.error(err);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const onListening = () => {
|
|
63
|
+
server.removeListener("error", onError);
|
|
64
|
+
const lines = ["", " Zoijs dev server", "", ` - Local: http://localhost:${PORTS[i]}`];
|
|
65
|
+
if (i > 0) lines.push(` (ports ${PORTS.slice(0, i).join(", ")} were busy)`);
|
|
66
|
+
lines.push("");
|
|
67
|
+
console.log(lines.join("\n"));
|
|
68
|
+
};
|
|
69
|
+
server.once("error", onError);
|
|
70
|
+
server.once("listening", onListening);
|
|
71
|
+
server.listen(PORTS[i]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
start();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{APP_TITLE}}</title>
|
|
7
|
+
<link rel="stylesheet" href="./src/style.css" />
|
|
8
|
+
<!-- No build step: an import map points the @zoijs/core name at the package
|
|
9
|
+
installed in node_modules. `npm install` puts it there. -->
|
|
10
|
+
<script type="importmap">
|
|
11
|
+
{
|
|
12
|
+
"imports": {
|
|
13
|
+
"@zoijs/core": "./node_modules/@zoijs/core/src/index.js"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<div id="app"></div>
|
|
20
|
+
<script type="module" src="./src/app.js"></script>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{APP_NAME}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "node dev-server.mjs",
|
|
8
|
+
"typecheck": "tsc --noEmit"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@zoijs/core": "^1.2.0"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"typescript": "^5.9.3"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
// TypeScript-grade safety with NO build step. This is plain JavaScript the
|
|
3
|
+
// browser runs as-is — but `@ts-check` + the shipped `@zoijs/core` types give you
|
|
4
|
+
// full type-checking and autocomplete. Run `npm run typecheck` (tsc --noEmit).
|
|
5
|
+
import { html, mount, createState, each } from "@zoijs/core";
|
|
6
|
+
|
|
7
|
+
/** @typedef {{ id: number; text: string; done: boolean }} Todo */
|
|
8
|
+
|
|
9
|
+
function App() {
|
|
10
|
+
/** @type {import("@zoijs/core").State<Todo[]>} */
|
|
11
|
+
const todos = createState([
|
|
12
|
+
{ id: 1, text: "Edit src/app.js", done: false },
|
|
13
|
+
{ id: 2, text: "Run npm run typecheck", done: false },
|
|
14
|
+
]);
|
|
15
|
+
const draft = createState("");
|
|
16
|
+
let nextId = 3;
|
|
17
|
+
|
|
18
|
+
/** @param {Event} e */
|
|
19
|
+
const add = (e) => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
const text = draft.get().trim();
|
|
22
|
+
if (!text) return;
|
|
23
|
+
todos.set([...todos.get(), { id: nextId++, text, done: false }]);
|
|
24
|
+
draft.set("");
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** @param {number} id */
|
|
28
|
+
const toggle = (id) =>
|
|
29
|
+
todos.set(todos.get().map((t) => (t.id === id ? { ...t, done: !t.done } : t)));
|
|
30
|
+
|
|
31
|
+
return html`
|
|
32
|
+
<main>
|
|
33
|
+
<h1>{{APP_TITLE}}</h1>
|
|
34
|
+
<p>Typed with <code>@ts-check</code> — no build step. Run <code>npm run typecheck</code>.</p>
|
|
35
|
+
|
|
36
|
+
<form onsubmit=${add}>
|
|
37
|
+
<input
|
|
38
|
+
aria-label="New todo"
|
|
39
|
+
placeholder="Add a todo…"
|
|
40
|
+
value=${() => draft.get()}
|
|
41
|
+
oninput=${(/** @type {Event} */ e) =>
|
|
42
|
+
draft.set(/** @type {HTMLInputElement} */ (e.target).value)} />
|
|
43
|
+
<button type="submit">Add</button>
|
|
44
|
+
</form>
|
|
45
|
+
|
|
46
|
+
<ul>
|
|
47
|
+
${each(
|
|
48
|
+
() => todos.get(),
|
|
49
|
+
(t) => t.id,
|
|
50
|
+
(t) => html`
|
|
51
|
+
<li>
|
|
52
|
+
<label>
|
|
53
|
+
<input type="checkbox" checked=${() => t.done} onchange=${() => toggle(t.id)} />
|
|
54
|
+
${() => t.text}
|
|
55
|
+
</label>
|
|
56
|
+
</li>
|
|
57
|
+
`
|
|
58
|
+
)}
|
|
59
|
+
</ul>
|
|
60
|
+
</main>
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
mount(App, "#app");
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light dark;
|
|
3
|
+
font-family: system-ui, sans-serif;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
display: grid;
|
|
9
|
+
place-items: center;
|
|
10
|
+
min-height: 100vh;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
main {
|
|
14
|
+
text-align: center;
|
|
15
|
+
padding: 2rem;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
h1 {
|
|
19
|
+
margin: 0 0 0.5rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
button {
|
|
23
|
+
font: inherit;
|
|
24
|
+
padding: 0.6rem 1.2rem;
|
|
25
|
+
border: 1px solid currentColor;
|
|
26
|
+
border-radius: 8px;
|
|
27
|
+
background: transparent;
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
button:hover {
|
|
32
|
+
background: rgba(127, 127, 127, 0.12);
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"noEmit": true,
|
|
4
|
+
"checkJs": true,
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"strict": true,
|
|
7
|
+
"target": "ES2020",
|
|
8
|
+
"module": "ESNext",
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"types": []
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*.js"]
|
|
15
|
+
}
|