@rshono/create 1.0.0-rc.2 → 1.0.0-rc.20

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 CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@rshono/create",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.20",
4
4
  "description": "Scaffold a new rshono app — Hono + Rspack + React Server Components",
5
5
  "author": "Lasse <lasse@lassetange.com> (https://www.lassetange.com)",
6
- "license": "ISC",
6
+ "license": "MIT",
7
7
  "keywords": [
8
8
  "create",
9
9
  "scaffold",
@@ -36,24 +36,23 @@
36
36
  "templates"
37
37
  ],
38
38
  "engines": {
39
- "node": ">=22.1.0"
39
+ "node": ">=22.18.0"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public"
43
43
  },
44
+ "devDependencies": {
45
+ "@clack/prompts": "^1.7.0",
46
+ "@rspack/core": "2.2.2",
47
+ "@types/node": "^26.4.0",
48
+ "typescript": "^7.0.2",
49
+ "@rshono/core": "1.0.0-rc.20"
50
+ },
44
51
  "scripts": {
45
52
  "build": "node scripts/codegen.mjs && node scripts/build.mjs",
46
53
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
47
54
  "codegen": "node scripts/codegen.mjs",
48
55
  "typecheck": "node scripts/codegen.mjs && tsc --noEmit",
49
- "test": "npm run build && node --test --test-concurrency=1 \"test/*.test.mjs\"",
50
- "prepack": "npm run build"
51
- },
52
- "devDependencies": {
53
- "@clack/prompts": "^1.7.0",
54
- "@rspack/core": "2.1.5",
55
- "@types/node": "^26.1.1",
56
- "@rshono/core": "workspace:*",
57
- "typescript": "^7.0.2"
56
+ "test": "npm run build && node --test --test-concurrency=1 \"test/*.test.mjs\""
58
57
  }
59
- }
58
+ }
@@ -0,0 +1,5 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ An [rshono](https://www.rshono.com) app — Hono, Rspack and React Server Components.
4
+
5
+ Read the framework's documentation before guessing at a convention: <https://www.rshono.com/llms.txt>.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -1,11 +1,7 @@
1
1
  # {{PROJECT_NAME}}
2
2
 
3
- A [rshono](https://github.com/rshono/rshono) app — [Hono](https://hono.dev) + [Rspack](https://rspack.rs) + [React Server Components](https://react.dev/reference/rsc/server-components).
4
-
5
3
  ```bash
6
- {{PM_RUN}} dev # dev server with HMR, http://localhost:3000
7
- {{PM_RUN}} build # production build for {{DEPLOY_TARGET}}
8
- {{PM_RUN}} typecheck # tsc --noEmit
4
+ {{SCRIPT_TABLE}}
9
5
  ```
10
6
 
11
7
  `package.json` has the rest, including whatever your formatter and linter added.
@@ -18,9 +14,7 @@ public/ served verbatim at the web root (favicon.svg → /favicon.svg
18
14
  src/
19
15
  routes.ts the route table — the one file rshono requires
20
16
  server.ts a Hono app: middleware, API routes, redirects, error reporting
21
- actions.ts 'use server' functions the browser can call
22
17
  components/ pages and components
23
- lib/ everything else
24
18
  styles.css imported by the layout, so it loads with the page
25
19
  ```
26
20
 
@@ -31,12 +25,20 @@ Interactive parts are `'use client'` components a page imports — only those sh
31
25
 
32
26
  `.env` holds committed defaults; `.env.local` overrides it and is gitignored. Only `PUBLIC_`-prefixed
33
27
  variables reach the browser — everything else is server-only, and a stray read of it in client code
34
- compiles to `undefined` rather than shipping. `src/lib/env.ts` is where both sides are read.
28
+ compiles to `undefined` rather than shipping. `src/components/layout.tsx` reads `PUBLIC_APP_NAME` that way.
29
+
30
+ Both are read **once, at startup**, and the `PUBLIC_` view of them is compiled into the bundle — so editing
31
+ either while `pnpm dev` is running changes nothing until you restart it. The dev server says so when it
32
+ happens; the same is true of `rshono.config.ts`.
35
33
 
36
34
  ## Deploying
37
35
 
38
- This app is built for `{{DEPLOY_TARGET}}`: after `{{PM_RUN}} build`, {{DEPLOY_HINT}}.
36
+ This app is built for `{{DEPLOY_TARGET}}`. {{DEPLOY_STEP}}
37
+
38
+ {{PLATFORM_SETUP}}
39
39
 
40
40
  Change `deploy` in `rshono.config.ts` to target somewhere else, or build for one place without editing the
41
41
  file: `rshono build --deploy vercel`, or `RSHONO_DEPLOY=vercel` in CI. `dev` always runs the Node dev
42
42
  server, whatever the target — it is a property of the build, not of developing.
43
+
44
+ Every deployment target, and what each one needs: <https://www.rshono.com/docs/deployment>
@@ -1,7 +1,4 @@
1
- # Committed defaults safe to share, and the values a fresh clone starts with.
2
- #
3
- # Secrets go in `.env.local`, which is gitignored and loaded first, so a value there wins over this
4
- # file. The real environment wins over both.
1
+ # Every variable here is on `process.env` the layout reads `process.env.PUBLIC_APP_NAME`.
5
2
  #
6
3
  # Only `PUBLIC_`-prefixed variables are compiled into the browser bundle. Anything else is server-only:
7
4
  # a stray read of it in client code compiles to `undefined` rather than shipping.
@@ -1,5 +1,6 @@
1
1
  node_modules/
2
2
  dist/
3
+ .rshono/
3
4
  *.tsbuildinfo
4
5
  .DS_Store
5
6
 
@@ -1 +1,2 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><title>Lightning bolt</title><text y=".9em" font-size="90">⚡</text></svg>
1
+ <!-- The rshono mark, as a placeholder — replace it with your own and the tab is yours. -->
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><title>rshono</title><path fill="#087EA4" d="M12.4365 0.2520325c0.062375 -0.0080215 0.11745 0.0077075 0.16515 0.0471875 1.755075 2.142025 3.40665 4.359855 4.954725 6.65348 1.14615 1.725625 2.121325 3.550175 2.92565 5.4738 1.2845 3.426025 0.616 6.40675 -2.005475 8.9421 -2.293725 1.938575 -4.936225 2.693575 -7.927575 2.265025 -3.57555 -0.729025 -6.00575 -2.7974 -7.290525 -6.205225 -0.33465 -1.109425 -0.44475 -2.241925 -0.3303 -3.397525 0.19055 -1.9891 0.662425 -3.9081 1.415625 -5.7569 0.31385 -0.75435 0.722825 -1.4464 1.2269 -2.076275 0.411225 0.4898 0.80445 0.993175 1.179675 1.510025 0.17375 0.181625 0.354625 0.35465 0.542675 0.51905C8.728325 5.378325 10.44285 2.7201 12.4365 0.2520325Z"/><path fill="#61DAFB" d="M12.10625 4.07425c1.73145 2.008325 3.296525 4.1475 4.695175 6.41755 0.438525 0.75115 0.800275 1.537625 1.085325 2.3594 0.593825 2.336175 -0.043225 4.26305 -1.9111 5.7805 -1.80655 1.2712 -3.788425 1.6487 -5.945675 1.132525 -2.326325 -0.721875 -3.671175 -2.286975 -4.034575 -4.6952 -0.088175 -0.7593 -0.009525 -1.4986 0.23595 -2.217825 0.35005 -0.888875 0.774725 -1.73825 1.274075 -2.54815 0.471875 -0.6921 0.94375 -1.38415 1.415625 -2.07625 1.071925 -1.378375 2.13365 -2.762525 3.1852 -4.15255Z"/></svg>
@@ -1,35 +1,5 @@
1
1
  import { defineConfig } from '@rshono/core';
2
2
 
3
- /**
4
- * Every field is optional — delete this file to accept all the defaults. The commented lines are the
5
- * defaults, kept as documentation of what is there to change.
6
- *
7
- * The framework settings are compiled into the server bundle at build time, so changing one means a
8
- * rebuild; there is no env-var interface for them. `--port`/`PORT` and `HOST` are the two exceptions,
9
- * and they win over what is written here.
10
- */
11
3
  export default defineConfig({
12
- /** Where `build` targets. Overridable per build with `--deploy` or `RSHONO_DEPLOY`. */
13
4
  deploy: '{{DEPLOY_TARGET}}',
14
-
15
- // The public origin, baked into prerendered pages' absolute URLs. Set it if you use `render: 'static'`
16
- // and build canonical tags, `og:url` or absolute links — there is no request to read a Host from.
17
- // siteUrl: 'https://example.com',
18
-
19
- // port: 3000, // default for dev/start
20
- // host: '0.0.0.0', // bind address for start
21
-
22
- // trustProxy: false, // honour X-Forwarded-Host/-Proto — only behind a proxy you control
23
- // checkOrigin: true, // CSRF origin check on server-action POSTs
24
- // allowedOrigins: [], // extra origins allowed to post actions
25
- // csp: false, // strict per-request-nonce Content-Security-Policy
26
- // cspDirectives: {}, // widen it, e.g. { 'img-src': "'self' https://cdn.example.com" }
27
- // bodySizeLimit: '1mb',// request body cap before a 413; false disables it
28
- // renderTimeout: 10_000, // ms deadline for a request (action + flight + SSR)
29
- // compress: true, // gzip; turn off behind a proxy that already does it
30
-
31
- // Escape hatch: mutate the generated Rspack config just before it compiles. Called once per compiler.
32
- // rspack(config, { isServer, isDev }) {
33
- // return config;
34
- // },
35
5
  });
@@ -1,7 +1,6 @@
1
1
  import type { PageProps } from '@rshono/core';
2
2
  import { Layout } from './layout';
3
3
 
4
- /** Declared as `notFound` in routes.ts. Answers unmatched paths, and any `notFound()` call, with a 404. */
5
4
  export default function NotFound({ url }: PageProps) {
6
5
  return (
7
6
  <Layout title="Not found">
@@ -1,12 +1,6 @@
1
1
  import type { ErrorPageProps } from '@rshono/core';
2
2
  import { Layout } from './layout';
3
3
 
4
- /**
5
- * Declared as `error` in routes.ts. Answers a request that threw, with a 500.
6
- *
7
- * `error.message` is the real message in development and a generic `'Internal Server Error'` in
8
- * production, where `error.stack` is absent — so this component can show it without leaking anything.
9
- */
10
4
  export default function ServerError({ error }: ErrorPageProps) {
11
5
  return (
12
6
  <Layout title="Something went wrong">
@@ -1,29 +1,14 @@
1
1
  import type { PageProps } from '@rshono/core';
2
- import { publicEnv } from '../lib/env';
3
- import type { AppEnv } from '../server';
4
- import { GreetForm } from './greet-form';
5
- import { Layout } from './layout';
2
+ import { appName, Layout } from './layout';
6
3
 
7
- /**
8
- * A page is a React **server component**: it runs on the server only, may be `async`, and can await data
9
- * directly — no loaders, no client bundle for any of this.
10
- *
11
- * The `AppEnv` type argument is what types `ctx.var` key by key; without it `ctx.var` is an open record.
12
- */
13
- export default function Home({ url, ctx }: PageProps<'/', AppEnv>) {
4
+ export default function Home({ url }: PageProps<'/'>) {
14
5
  return (
15
6
  <Layout description="A new rshono app.">
16
- <h1>{publicEnv.appName}</h1>
7
+ <h1>{appName}</h1>
17
8
  <p>
18
- Edit <code>src/components/home.tsx</code> and save. The page re-renders in place — the form below keeps whatever you have typed in it.
9
+ Edit <code>src/components/home.tsx</code> and save the page re-renders in place.
19
10
  </p>
20
11
 
21
- <h2>Server actions</h2>
22
- <p>
23
- This form calls a <code>'use server'</code> function in <code>src/actions.ts</code>. It works before hydration and with JavaScript disabled.
24
- </p>
25
- <GreetForm />
26
-
27
12
  <h2>Where things are</h2>
28
13
  <ul>
29
14
  <li>
@@ -33,7 +18,7 @@ export default function Home({ url, ctx }: PageProps<'/', AppEnv>) {
33
18
  <code>src/server.ts</code> — a Hono app for middleware and API routes, mounted ahead of the pages
34
19
  </li>
35
20
  <li>
36
- <code>src/components/</code> — pages and components; <code>src/lib/</code> — everything else
21
+ <code>src/components/</code> — pages and components
37
22
  </li>
38
23
  <li>
39
24
  <code>rshono.config.ts</code> — deploy target, security and build settings
@@ -41,9 +26,7 @@ export default function Home({ url, ctx }: PageProps<'/', AppEnv>) {
41
26
  </ul>
42
27
 
43
28
  <p>
44
- {/* `ctx` is the request context — cookies, headers, env, middleware variables — handed to the page as a
45
- prop, so reading it needs no import. It is server-only and never crosses into a client component. */}
46
- Rendered on the server for <code>{url.pathname}</code>, request <code>{ctx.var.requestId}</code>.
29
+ Rendered on the server for <code>{url.pathname}</code>.
47
30
  </p>
48
31
  </Layout>
49
32
  );
@@ -1,14 +1,10 @@
1
- import { NavigationProgress } from '@rshono/core/client';
2
1
  import type { ReactNode } from 'react';
3
- import { publicEnv } from '../lib/env';
4
2
  import '../styles.css';
5
3
 
6
- /**
7
- * A page renders the whole document, so the shell lives in one component every page wraps its content
8
- * in. Importing the stylesheet here is what attaches it to each of those pages.
9
- */
4
+ export const appName = process.env.PUBLIC_APP_NAME ?? '{{PROJECT_NAME}}';
5
+
10
6
  export function Layout({ title, description, children }: { title?: string; description?: string; children: ReactNode }) {
11
- const heading = title ? `${title} · ${publicEnv.appName}` : publicEnv.appName;
7
+ const heading = title ? `${title} · ${appName}` : appName;
12
8
 
13
9
  return (
14
10
  <html lang="en">
@@ -20,14 +16,11 @@ export function Layout({ title, description, children }: { title?: string; descr
20
16
  <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
21
17
  </head>
22
18
  <body>
23
- {/* Paints during a soft navigation, so a slow page still feels answered. */}
24
- <NavigationProgress />
25
19
  <header>
26
20
  <nav>
27
21
  <a href="/">
28
- <strong>{publicEnv.appName}</strong>
22
+ <strong>{appName}</strong>
29
23
  </a>
30
- {/* `data-prefetch` warms a page on hover; `data-native` opts a link out of soft navigation. */}
31
24
  <a href="/api/health" data-native>
32
25
  /api/health
33
26
  </a>
@@ -1,32 +1,7 @@
1
1
  import { defineRoutes } from '@rshono/core';
2
2
 
3
- /**
4
- * The one file rshono requires. It only ever runs on the server, so importing server-only modules from
5
- * it — inside `staticPaths`, say — is safe.
6
- *
7
- * Write each page as the inline `component: () => import('…')` thunk you see below: the build detects
8
- * that exact form and attaches the page's own JS and CSS to it, which is what makes assets code-split
9
- * per route.
10
- */
11
3
  export const routes = defineRoutes({
12
- routes: [
13
- { path: '/', component: () => import('./components/home') },
14
-
15
- // A page with params. `PageProps<'/posts/:slug'>` types `params.slug` for the component.
16
- // { path: '/posts/:slug', component: () => import('./components/post') },
17
-
18
- // Prerendered at build time, one file per param set, served from disk.
19
- // {
20
- // path: '/docs/:slug',
21
- // render: 'static',
22
- // component: () => import('./components/doc'),
23
- // staticPaths: async () => [{ slug: 'getting-started' }],
24
- // },
25
-
26
- // A raw Hono handler instead of a page — for anything that isn't HTML. The other place for these
27
- // is src/server.ts, which is the better home for a group of them.
28
- // { type: 'endpoint', path: '/api/posts', server: () => import('./api/posts') },
29
- ],
4
+ routes: [{ path: '/', component: () => import('./components/home') }],
30
5
  notFound: { component: () => import('./components/404') },
31
6
  error: { component: () => import('./components/500') },
32
7
  });
@@ -1,59 +1,44 @@
1
- import { onServerError } from '@rshono/core/server';
1
+ import { onServerError, publicUrl } from '@rshono/core/server';
2
2
  import { Hono } from 'hono';
3
+ import { bodyLimit } from 'hono/body-limit';
4
+ import { csrf } from 'hono/csrf';
3
5
  import { trimTrailingSlash } from 'hono/trailing-slash';
4
6
 
5
7
  /**
6
- * What this app's middleware puts on the Hono context. Pass it to `PageProps<path, AppEnv>` (or
7
- * `getContext<AppEnv>()`) and `ctx.var` is typed key by key instead of an open record see
8
- * `components/home.tsx`.
8
+ * Mounted at `/` ahead of the page routes, so middleware registered here wraps page requests too — auth,
9
+ * logging, headers. The flip side: a terminal handler at a page's path shadows the page.
9
10
  */
10
- export type AppEnv = { Variables: { requestId: string } };
11
+ const server = new Hono();
11
12
 
12
- const server = new Hono<AppEnv>();
13
- const startedAt = Date.now();
14
-
15
- /**
16
- * Where an error tracker goes. Registered at module load — this file is imported as the server starts —
17
- * so every error the framework catches reaches one place: a thrown action, a failed render, SSR falling
18
- * over. Swap the log for `Sentry.captureException(error)` or whatever you use.
19
- */
13
+ /** Every error the framework catches lands here: a thrown action, a failed render, SSR falling over. */
20
14
  onServerError((error, { source, request }) => {
21
15
  const message = error instanceof Error ? error.message : String(error);
22
16
  console.error(`[error] ${source} ${new URL(request.url).pathname}: ${message}`);
23
17
  });
24
18
 
19
+ /** Caps every request body before anything downstream buffers it. Raise it where you accept uploads. */
20
+ server.use(bodyLimit({ maxSize: 1024 * 1024 }));
21
+
22
+ /** Cross-origin hosts allowed to post server actions, alongside this app's own. */
23
+ const ALLOWED_ORIGINS: string[] = [];
24
+
25
+ /** Rejects a cross-origin POST before it reaches a server action. `publicUrl(c)`, not `c.req.url`: behind a proxy those differ. */
26
+ server.use(csrf({ origin: (origin, c) => origin === publicUrl(c).origin || ALLOWED_ORIGINS.includes(origin) }));
27
+
25
28
  /** `/about/` and `/about` should not be two pages. */
26
29
  server.use(trimTrailingSlash({ alwaysRedirect: true }));
27
30
 
28
- /**
29
- * This sub-app is mounted at `/` *ahead of* the page routes, so middleware registered here wraps page
30
- * requests too — auth, logging, headers. The flip side: a terminal handler at the same path as a page
31
- * route shadows the page.
32
- */
33
- server.use('*', async (c, next) => {
34
- c.set('requestId', crypto.randomUUID());
35
- const start = performance.now();
36
- await next();
37
- c.res.headers.set('X-Response-Time', `${(performance.now() - start).toFixed(1)} ms`);
38
- });
39
-
40
31
  /** Old paths that should keep working. One place to add to, rather than a handler each. */
41
- const REDIRECTS: Record<string, string> = {
42
- '/home': '/',
43
- };
32
+ const REDIRECTS: Record<string, string> = { '/home': '/' };
44
33
 
45
34
  for (const [from, to] of Object.entries(REDIRECTS)) {
46
35
  server.get(from, (c) => c.redirect(to, 301));
47
36
  }
48
37
 
49
- server.get('/api/health', (c) => {
50
- return c.json({ status: 'ok', uptime: (Date.now() - startedAt) / 1000, requestId: c.var.requestId });
51
- });
38
+ /** A JSON API route, with no page involved. The layout links to it. */
39
+ server.get('/api/health', (c) => c.json({ status: 'ok' }));
52
40
 
53
41
  export default server;
54
42
 
55
- /**
56
- * End-to-end types for a client that calls this app: `hc<AppType>('/')` from `hono/client` gives typed
57
- * paths, params and responses, checked against the handlers above.
58
- */
43
+ /** `hc<AppType>('/')` from `hono/client` gives paths, params and responses typed against the handlers above. */
59
44
  export type AppType = typeof server;
@@ -1,12 +1,3 @@
1
- /*
2
- * Deliberately element-level: nothing here names a class, so the markup in src/components stays plain
3
- * HTML you can restyle or replace wholesale without hunting for selectors.
4
- *
5
- * Rspack compiles this natively — there is no PostCSS anywhere in the framework. If you want a plugin
6
- * chain, add the loader in `rshono.config.ts`'s `rspack` hook; that, plus the packages, is all the
7
- * Tailwind setup is.
8
- */
9
-
10
1
  *,
11
2
  *::before,
12
3
  *::after {
@@ -38,12 +29,7 @@
38
29
  }
39
30
 
40
31
  body {
41
- font-family:
42
- ui-sans-serif,
43
- system-ui,
44
- -apple-system,
45
- 'Segoe UI',
46
- sans-serif;
32
+ font-family: ui-sans-serif, system-ui, 'Segoe UI', sans-serif;
47
33
  line-height: 1.6;
48
34
  color: var(--text);
49
35
  background: var(--bg);
@@ -127,52 +113,3 @@ pre {
127
113
  background: var(--surface);
128
114
  font-size: 0.8125rem;
129
115
  }
130
-
131
- form {
132
- display: flex;
133
- flex-wrap: wrap;
134
- align-items: center;
135
- gap: 0.5rem;
136
- padding: 1rem;
137
- border: 1px solid var(--border);
138
- border-radius: var(--radius);
139
- background: var(--surface);
140
- }
141
-
142
- label {
143
- flex-basis: 100%;
144
- font-size: 0.875rem;
145
- color: var(--muted);
146
- }
147
-
148
- input {
149
- flex: 1 1 12rem;
150
- padding: 0.5rem 0.75rem;
151
- border: 1px solid var(--border);
152
- border-radius: 6px;
153
- background: var(--bg);
154
- color: inherit;
155
- font: inherit;
156
- }
157
-
158
- button {
159
- padding: 0.5rem 1rem;
160
- border: 0;
161
- border-radius: 6px;
162
- background: var(--accent);
163
- color: #fff;
164
- font: inherit;
165
- font-weight: 500;
166
- cursor: pointer;
167
- }
168
-
169
- button:disabled {
170
- opacity: 0.6;
171
- cursor: progress;
172
- }
173
-
174
- output {
175
- flex-basis: 100%;
176
- font-size: 0.9375rem;
177
- color: var(--muted);
178
- }
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "$schema": "https://biomejs.dev/schemas/2.5.6/schema.json",
3
- "files": { "includes": ["**", "!dist", "!.wrangler", "!.vercel", "!.netlify", "!wrangler.jsonc"] },
3
+ "files": { "includes": ["**", "!dist", "!.rshono", "!.wrangler", "!.vercel", "!.netlify", "!wrangler.jsonc"] },
4
4
  "formatter": { "indentStyle": "space", "indentWidth": 2, "lineWidth": 140 },
5
5
  "javascript": { "formatter": { "quoteStyle": "single", "jsxQuoteStyle": "double" } },
6
+ "css": { "formatter": { "quoteStyle": "single" } },
6
7
  "linter": { "enabled": true, "rules": { "preset": "recommended" } }
7
8
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://biomejs.dev/schemas/2.5.6/schema.json",
3
- "files": { "includes": ["**", "!dist", "!.wrangler", "!.vercel", "!.netlify", "!wrangler.jsonc", "!**/*.css"] },
3
+ "files": { "includes": ["**", "!dist", "!.rshono", "!.wrangler", "!.vercel", "!.netlify", "!wrangler.jsonc", "!**/*.css"] },
4
4
  "formatter": { "indentStyle": "space", "indentWidth": 2, "lineWidth": 140 },
5
5
  "javascript": { "formatter": { "quoteStyle": "single", "jsxQuoteStyle": "double" } },
6
6
  "linter": { "enabled": true, "rules": { "preset": "recommended" } }
@@ -1,31 +1,31 @@
1
+ /**
2
+ * ESLint with type-aware rules, which is the reason to reach for it over a linter that only reads syntax.
3
+ *
4
+ * **This app's `typescript` is pinned below the one rshono is built with.** typescript-eslint reads the
5
+ * compiler API directly and accepts nothing from TypeScript 6.1 up, so `package.json` holds the newest
6
+ * version it takes. Your editor and `tsc` are unaffected — rshono's declarations compile the same under
7
+ * either — but the rules below reason about your program through the *older* compiler.
8
+ *
9
+ * The practical consequence: `lint:fix` can rewrite code that `tsc` then rejects. Run `typecheck` after it,
10
+ * not instead of it. When typescript-eslint widens its peer range, drop the `typescript` pin from
11
+ * `package.json` and this note with it.
12
+ */
1
13
  import js from '@eslint/js';
2
14
  import reactHooks from 'eslint-plugin-react-hooks';
3
15
  import tseslint from 'typescript-eslint';
4
16
 
5
- /**
6
- * Flat config, with type-aware rules — the reason to run ESLint over a syntax-only linter. `projectService`
7
- * builds the same program `tsc` does from tsconfig.json, so a rule can ask what a value actually *is*:
8
- * an unawaited promise, a `catch` that swallows an error, a `String()` around something that is not one.
9
- *
10
- * The cost is that ESLint needs TypeScript to answer, which is why this app pins TypeScript 6 —
11
- * typescript-eslint reads the compiler API directly and accepts nothing newer. `npm run typecheck` is
12
- * still the thing that decides whether the app compiles; these rules only see what it sees.
13
- */
14
17
  export default tseslint.config(
15
- // ESLint's own default ignores cover node_modules and nothing else, so the build output and whatever
16
- // the deploy target leaves beside it would otherwise be linted as if you had written it.
17
- { ignores: ['dist/**', '.wrangler/**', '.vercel/**', '.netlify/**'] },
18
+ // Build output, all of it generated: `dist/` from `rshono build`, `.rshono/` from `rshono dev`, and
19
+ // whatever the deploy target assembles beside them. Without this, linting after a dev run reports on
20
+ // bundles rather than on anything you wrote.
21
+ { ignores: ['dist/**', '.rshono/**', '.wrangler/**', '.vercel/**', '.netlify/**'] },
18
22
  js.configs.recommended,
19
23
  tseslint.configs.recommendedTypeChecked,
20
- // The rules of hooks: the one class of React mistake no type checker catches, and the reason a React
21
- // app wants a linter at all.
22
24
  reactHooks.configs.flat['recommended-latest'],
23
25
  {
24
26
  languageOptions: {
25
27
  parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname },
26
28
  },
27
29
  },
28
- // This file and any other plain JavaScript sits outside the TypeScript program, so the type-aware rules
29
- // have nothing to run against and would report every file as unconfigured.
30
30
  { files: ['**/*.{js,mjs,cjs}'], extends: [tseslint.configs.disableTypeChecked] },
31
31
  );
@@ -3,5 +3,5 @@
3
3
  "printWidth": 140,
4
4
  "singleQuote": true,
5
5
  "trailingComma": "all",
6
- "ignorePatterns": ["dist", ".wrangler", ".vercel", ".netlify", "wrangler.jsonc"]
6
+ "ignorePatterns": ["dist", ".wrangler", ".vercel", ".netlify", "wrangler.jsonc", "package.json"]
7
7
  }
@@ -1,7 +1,3 @@
1
- /**
2
- * The plugin list. postcss-loader finds this file on its own; what puts postcss-loader in front of
3
- * Rspack's CSS parser in the first place is the rule in `rshono.config.ts`.
4
- */
5
1
  export default {
6
2
  plugins: {
7
3
  '@tailwindcss/postcss': {},
@@ -1,44 +1,8 @@
1
1
  import { defineConfig } from '@rshono/core';
2
2
 
3
- /**
4
- * Every field is optional — delete this file to accept all the defaults. The commented lines are the
5
- * defaults, kept as documentation of what is there to change.
6
- *
7
- * The framework settings are compiled into the server bundle at build time, so changing one means a
8
- * rebuild; there is no env-var interface for them. `--port`/`PORT` and `HOST` are the two exceptions,
9
- * and they win over what is written here.
10
- */
11
3
  export default defineConfig({
12
- /** Where `build` targets. Overridable per build with `--deploy` or `RSHONO_DEPLOY`. */
13
4
  deploy: '{{DEPLOY_TARGET}}',
14
-
15
- // The public origin, baked into prerendered pages' absolute URLs. Set it if you use `render: 'static'`
16
- // and build canonical tags, `og:url` or absolute links — there is no request to read a Host from.
17
- // siteUrl: 'https://example.com',
18
-
19
- // port: 3000, // default for dev/start
20
- // host: '0.0.0.0', // bind address for start
21
-
22
- // trustProxy: false, // honour X-Forwarded-Host/-Proto — only behind a proxy you control
23
- // checkOrigin: true, // CSRF origin check on server-action POSTs
24
- // allowedOrigins: [], // extra origins allowed to post actions
25
- // csp: false, // strict per-request-nonce Content-Security-Policy
26
- // cspDirectives: {}, // widen it, e.g. { 'img-src': "'self' https://cdn.example.com" }
27
- // bodySizeLimit: '1mb',// request body cap before a 413; false disables it
28
- // renderTimeout: 10_000, // ms deadline for a request (action + flight + SSR)
29
- // compress: true, // gzip; turn off behind a proxy that already does it
30
-
31
- /**
32
- * Tailwind, and the only thing the build needs to know about it.
33
- *
34
- * Rspack compiles CSS natively, which is fast and is all a plain stylesheet needs — but that parser
35
- * reads *finished* CSS, and `@import 'tailwindcss'`, `@theme` and `@apply` are not that. Tailwind is a
36
- * PostCSS plugin, so it has to run in front of the parser, which is what this rule does. The plugin
37
- * list itself is in `postcss.config.mjs`, where postcss-loader looks for it.
38
- *
39
- * The hook is called once per compiler, so the rule reaches the client and the server graph both.
40
- * Delete all of this, and the four Tailwind packages, to go back to plain CSS.
41
- */
5
+ /** Tailwind is a PostCSS plugin, so postcss-loader has to run ahead of Rspack's native CSS parser. */
42
6
  rspack(config) {
43
7
  config.module!.rules!.push({ test: /\.css$/i, use: ['postcss-loader'], type: 'css/auto' });
44
8
  },