create-nowaki 0.11.0 → 0.13.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/package.json +1 -1
- package/templates/_shared/AGENTS.md +39 -5
- package/templates/_shared/README.md +12 -0
- package/templates/_shared/_gitignore +2 -0
- package/templates/_shared/nowaki-env.d.ts +29 -6
- package/templates/_shared/package.json +2 -2
- package/templates/_shared/tsconfig.json +1 -0
- package/templates/basics/routes/index.tsx +2 -3
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ npm run dev # nowaki dev — dev server with hot reload (http://localho
|
|
|
12
12
|
npm run build # nowaki build — production build (dist/client + dist/server)
|
|
13
13
|
npm run start # nowaki start — serve the production build
|
|
14
14
|
npm run prerender # nowaki prerender — static export
|
|
15
|
+
npx nowaki upgrade # update nowaki (CLI) + @nowaki-dev/runtime to latest
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
## Project layout
|
|
@@ -38,16 +39,23 @@ nowaki.config.mjs plugins (optional)
|
|
|
38
39
|
`onClick`/hooks in route or `components/` files.
|
|
39
40
|
3. **Use explicit file extensions in relative imports**:
|
|
40
41
|
`import Counter from "../islands/Counter.tsx"` (not `"../islands/Counter"`).
|
|
41
|
-
4. **Routes** are components with an optional server-only `loader` and `action
|
|
42
|
+
4. **Routes** are components with an optional server-only `loader` and `action`.
|
|
43
|
+
Optional types come from `@nowaki-dev/runtime` — `PageProps<typeof loader>`
|
|
44
|
+
infers the page's `data`, `LoaderContext` types `params`/cookies/headers:
|
|
42
45
|
```tsx
|
|
43
46
|
// routes/blog/[slug].tsx
|
|
44
|
-
|
|
45
|
-
export async
|
|
47
|
+
import type { LoaderContext, PageProps } from "@nowaki-dev/runtime";
|
|
48
|
+
export const loader = async ({ params }: LoaderContext) => ({ post: await db.post(params.slug) }); // server-only
|
|
49
|
+
export async function action(ctx: LoaderContext) { // runs on non-GET requests
|
|
46
50
|
const form = await ctx.formData();
|
|
47
51
|
return ctx.redirect("/blog"); // Post/Redirect/Get
|
|
48
52
|
}
|
|
49
|
-
export default function Post({ data }) { return <article><h1>{data.post.title}</h1></article>; }
|
|
53
|
+
export default function Post({ data }: PageProps<typeof loader>) { return <article><h1>{data.post.title}</h1></article>; }
|
|
50
54
|
```
|
|
55
|
+
A catch-all segment `routes/files/[...path].tsx` matches `/files/a/b/c`;
|
|
56
|
+
`params.path` is the array `["a","b","c"]`. Add `export const revalidate = 60`
|
|
57
|
+
(seconds) to a route for ISR: the HTML is cached and regenerated in the
|
|
58
|
+
background when stale (don't put per-user data on an ISR page).
|
|
51
59
|
5. **API routes** are `routes/api/*.ts` with per-method exports; return a value
|
|
52
60
|
(JSON-encoded) or a `Response`:
|
|
53
61
|
```ts
|
|
@@ -63,13 +71,24 @@ nowaki.config.mjs plugins (optional)
|
|
|
63
71
|
return <button onClick={() => setN(n + 1)}>count: {n}</button>;
|
|
64
72
|
}
|
|
65
73
|
```
|
|
74
|
+
**Lazy hydration** (Astro-style `client:*` directives) controls *when* an
|
|
75
|
+
island hydrates — add a directive where you use it:
|
|
76
|
+
```tsx
|
|
77
|
+
<Counter client:load /> // default — hydrate on load
|
|
78
|
+
<Counter client:idle /> // hydrate on requestIdleCallback
|
|
79
|
+
<Counter client:visible /> // hydrate when scrolled into view
|
|
80
|
+
<Chart client:media="(min-width: 768px)" /> // hydrate when the query matches
|
|
81
|
+
<Map client:only /> // skip SSR, render only on the client
|
|
82
|
+
```
|
|
83
|
+
Prefer `client:visible` / `client:idle` for below-the-fold or non-critical
|
|
84
|
+
islands to cut work on first load. The directives are typed (no prop leaks).
|
|
66
85
|
7. **Server functions** — a module with a top-of-file `"use server"` directive
|
|
67
86
|
becomes RPC; the client gets a tiny fetch proxy, the implementation stays on
|
|
68
87
|
the server. Validate arguments; read auth via `getContext()`:
|
|
69
88
|
```ts
|
|
70
89
|
// actions/todos.ts
|
|
71
90
|
"use server";
|
|
72
|
-
import { getContext } from "@nowaki-dev/runtime
|
|
91
|
+
import { getContext } from "@nowaki-dev/runtime";
|
|
73
92
|
export async function addTodo(text: string) { /* runs on the server */ }
|
|
74
93
|
```
|
|
75
94
|
Call it from an island like a normal async function:
|
|
@@ -77,6 +96,21 @@ nowaki.config.mjs plugins (optional)
|
|
|
77
96
|
8. **Jetstream islands** (server-reactive, zero client JS) — give an island
|
|
78
97
|
`export const live = { state, on }`; buttons use `data-live="handler"` instead
|
|
79
98
|
of `onClick`. State lives on the server; the server pushes HTML patches.
|
|
99
|
+
9. **Loading / error UI** (client navigation) — `routes/loading.tsx` renders
|
|
100
|
+
while a navigation is in flight; `routes/error.tsx` (props `{ error, reset }`,
|
|
101
|
+
type `ErrorPageProps`) renders if it fails. Both nest by directory. In
|
|
102
|
+
`error.tsx`, mark the message element `data-nowaki-error` and the retry button
|
|
103
|
+
`data-nowaki-reset`. Visited pages are kept in a ~30s Router Cache, so
|
|
104
|
+
back/forward is instant. These are server components, not islands.
|
|
105
|
+
10. **Typed navigation** — `dev`/`build` generate `.nowaki/types.d.ts` from your
|
|
106
|
+
routes (run `nowaki typegen` to refresh manually). Build links type-safely
|
|
107
|
+
from `@nowaki-dev/runtime/navigation`:
|
|
108
|
+
```tsx
|
|
109
|
+
import { route, Link } from "@nowaki-dev/runtime/navigation";
|
|
110
|
+
<Link href="/about">About</Link> // static path, checked
|
|
111
|
+
<a href={route("/blog/[slug]", { slug })}>Post</a> // dynamic, params typed
|
|
112
|
+
```
|
|
113
|
+
Unknown paths, missing params, and wrong param keys are compile errors.
|
|
80
114
|
|
|
81
115
|
## Don't
|
|
82
116
|
|
|
@@ -15,6 +15,18 @@ Other scripts: `npm run build`, `npm run start`, `npm run prerender`.
|
|
|
15
15
|
Pass `--host` to expose dev on your LAN, `--open` to open the browser:
|
|
16
16
|
`npm run dev -- --host --open`.
|
|
17
17
|
|
|
18
|
+
## Update
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx nowaki upgrade # bumps nowaki (CLI) and @nowaki-dev/runtime to latest
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`nowaki upgrade` detects your package manager and updates both packages,
|
|
25
|
+
rewriting the `package.json` ranges. (A plain `npm update` won't cross a
|
|
26
|
+
`0.x` minor, so prefer `nowaki upgrade` — or install `@latest` by hand.)
|
|
27
|
+
Pin a version with `--to`, e.g. `npx nowaki upgrade --to 0.11.0`. Pre-1.0,
|
|
28
|
+
check the release notes before upgrading; minors can include breaking changes.
|
|
29
|
+
|
|
18
30
|
## Structure
|
|
19
31
|
|
|
20
32
|
```
|
|
@@ -32,11 +32,34 @@ declare module "*.pdf" { const url: string; export default url; }
|
|
|
32
32
|
// プラグインの仮想モジュール(nowaki.config の resolveId/load)。任意の export を許す。
|
|
33
33
|
declare module "virtual:*";
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
// 遅延ハイドレーション指令(Astro 互換の client:*)を任意のコンポーネントに付けられるよう、
|
|
36
|
+
// preact の JSX.IntrinsicAttributes を拡張する。<Counter client:visible /> 等が型チェックを通る。
|
|
37
|
+
// (この import で本ファイルはモジュールになる=下のグローバル型は declare global で公開する。)
|
|
38
|
+
import "preact";
|
|
39
|
+
declare module "preact" {
|
|
40
|
+
namespace JSX {
|
|
41
|
+
interface IntrinsicAttributes {
|
|
42
|
+
/** ページ読み込み時にハイドレート(既定)。 */
|
|
43
|
+
"client:load"?: boolean;
|
|
44
|
+
/** requestIdleCallback でアイドル時にハイドレート。 */
|
|
45
|
+
"client:idle"?: boolean;
|
|
46
|
+
/** IntersectionObserver で可視になったらハイドレート。 */
|
|
47
|
+
"client:visible"?: boolean;
|
|
48
|
+
/** 指定メディアクエリが一致したらハイドレート。例: client:media="(min-width: 768px)" */
|
|
49
|
+
"client:media"?: string;
|
|
50
|
+
/** SSR せず、クライアントだけで描画(ブラウザ専用 API を使う島向け)。 */
|
|
51
|
+
"client:only"?: boolean;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
39
54
|
}
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
|
|
56
|
+
declare global {
|
|
57
|
+
// import.meta.env(PUBLIC_* と MODE がビルド時に inline される)。
|
|
58
|
+
interface ImportMetaEnv {
|
|
59
|
+
readonly MODE: string;
|
|
60
|
+
readonly [key: `PUBLIC_${string}`]: string | undefined;
|
|
61
|
+
}
|
|
62
|
+
interface ImportMeta {
|
|
63
|
+
readonly env: ImportMetaEnv;
|
|
64
|
+
}
|
|
42
65
|
}
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"prerender": "nowaki prerender"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@nowaki-dev/runtime": "^0.
|
|
12
|
+
"@nowaki-dev/runtime": "^0.11.0",
|
|
13
13
|
"preact": "^10.25.4",
|
|
14
14
|
"preact-render-to-string": "^6.5.13"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"nowaki": "^0.
|
|
17
|
+
"nowaki": "^0.12.0"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PageProps } from "@nowaki-dev/runtime";
|
|
1
2
|
import Counter from "../islands/Counter.tsx";
|
|
2
3
|
|
|
3
4
|
const CSS = `
|
|
@@ -32,9 +33,7 @@ export const head = `<style>${CSS}</style>`;
|
|
|
32
33
|
|
|
33
34
|
export const loader = async () => ({ greeting: "Welcome to Nowaki" });
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export default function Home({ data }: { data: Data }) {
|
|
36
|
+
export default function Home({ data }: PageProps<typeof loader>) {
|
|
38
37
|
return (
|
|
39
38
|
<main class="wrap">
|
|
40
39
|
<div class="brand">
|