mono-jsx 0.0.0-alpha.13 → 0.0.0-alpha.15

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 CHANGED
@@ -5,7 +5,7 @@
5
5
  mono-jsx is a JSX runtime that renders `<html>` element to a `Response` object in JavaScript runtimes like Node.js, Deno, Bun, Cloudflare Workers, etc.
6
6
 
7
7
  - No build step needed
8
- - Lightweight(7KB gzipped), zero dependencies
8
+ - Lightweight(8KB gzipped), zero dependencies
9
9
  - Minimal state runtime
10
10
  - Streaming rendering
11
11
  - Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.
@@ -36,14 +36,14 @@ bun add mono-jsx
36
36
 
37
37
  ## Setup JSX runtime
38
38
 
39
- To use mono-jsx as the JSX runtime, add the following configuration to your `tsconfig.json`(`deno.json` for Deno):
39
+ To use mono-jsx as JSX runtime, add the following configuration to your `tsconfig.json`(`deno.json` for Deno):
40
40
 
41
41
  ```jsonc
42
42
  {
43
43
  "compilerOptions": {
44
- "allowJs": true, // required for `.jsx` extension in Node.js
45
44
  "jsx": "react-jsx",
46
- "jsxImportSource": "mono-jsx"
45
+ "jsxImportSource": "mono-jsx",
46
+ "allowJs": true // required for `.jsx` extension in Node.js
47
47
  }
48
48
  }
49
49
  ```
@@ -86,6 +86,8 @@ bun run app.jsx
86
86
  **Node.js does not support JSX module and declarative fetch server**, we recommend using mono-jsx with [hono](https://hono.dev).
87
87
 
88
88
  ```jsx
89
+ // app.jsx
90
+
89
91
  import { serve } from "@hono/node-server";
90
92
  import { Hono } from "hono";
91
93
  const app = new Hono();
@@ -214,16 +216,20 @@ function Button() {
214
216
  > [!NOTE]
215
217
  > the event handler would never be called in server-side. Instead it will be serialized to a string and sent to the client-side. **This means you should NOT use any server-side variables or functions in the event handler.**
216
218
 
217
- ```jsx
218
- function Button() {
219
+ ```tsx
220
+ function Button(this: FC, props: { role: string }) {
219
221
  let message = "BOOM!";
222
+ console.log(message); // only print message in server-side
220
223
  return (
221
224
  <button
225
+ role={props.role}
222
226
  onClick={(evt) => {
223
- Deno.exit(0); // ❌ Deno is unavailable in the browser
224
- alert(message); // ❌ message is a server-side variable
225
- document.title = "BOOM!"; // document is a browser API
226
- $state.count++; // ✅ $state is the mono-jsx specific usage
227
+ alert(message); // ❌ `message` is a server-side variable
228
+ console.log(props.role); // ❌ `props` is a server-side variable
229
+ Deno.exit(0); // `Deno` is unavailable in the browser
230
+ document.title = "BOOM!"; // ✅ `document` is a browser API
231
+ console.log(evt.target); // ✅ `evt` is the event object
232
+ this.count++; // ✅ update the state `count`
227
233
  }}
228
234
  >
229
235
  Click Me
@@ -248,79 +254,40 @@ function App() {
248
254
 
249
255
  mono-jsx provides a minimal state runtime that allows you to update view based on state changes in client-side.
250
256
 
251
- ```jsx
252
- function App() {
253
- // Initialize the state 'count' with value `0`
254
- $state.count = 0;
257
+ ```tsx
258
+ function App(
259
+ this: FC<{ count: number }>,
260
+ props: { initialCount?: number },
261
+ ) {
262
+ this.count = props.initialCount ?? 0;
255
263
  return (
256
264
  <div>
257
265
  {/* use the state */}
258
- <span>{$state.count}</span>
259
- {/* computed state */}
260
- <span>doubled: {$computed(() => 2 * $state.count)}</span>
266
+ <span>{this.count}</span>
267
+ {/* use computed state */}
268
+ <span>doubled: {this.computed(() => 2 * this.count)}</span>
261
269
  {/* update the state in event handlers */}
262
- <button onClick={() => $state.count--}>-</button>
263
- <button onClick={() => $state.count++}>+</button>
270
+ <button onClick={() => this.count--}>-</button>
271
+ <button onClick={() => this.count++}>+</button>
264
272
  </div>
265
273
  );
266
274
  }
267
275
  ```
268
276
 
269
- To support type checking in TypeScript, declare the `State` interface in the global scope:
270
-
271
- ```ts
272
- declare global {
273
- interface State {
274
- count: number;
275
- }
276
- }
277
- ```
278
-
279
- > [!NOTE]
280
- > The `$state` and `$computed` are global variables injected by mono-jsx.
281
-
282
- ## Using `$context` Hook
283
-
284
- The `$context` hook allows you to access `data` which is set in the root `<html>` element.
285
-
286
- ```tsx
287
- interface Data {
288
- foo: string;
289
- }
290
-
291
- async function App() {
292
- const { request, data } = $context<Data>();
293
- return (
294
- <p>
295
- {request.method} {request.url} {data.foo}
296
- </p>
297
- );
298
- }
299
-
300
- export default {
301
- fetch: (req) => (
302
- <html request={req} data={{ foo: "bar" }}>
303
- <h1>Hello World!</h1>
304
- <App />
305
- </html>
306
- ),
307
- };
308
- ```
309
-
310
- > [!NOTE]
311
- > If you are using hooks in an async function component, you need to call these hooks before any `await` statement.
277
+ > [!WARNING]
278
+ > The state cannot be used in an arrow function component, you should use a `function` declaration instead.
312
279
 
313
280
  ```jsx
314
- async function AsyncApp() {
315
- const { request } = $context();
316
- const data = await fetchData(new URL(request.url).searchParams.get("id"));
317
- const { request } = $context(); // ❌ request is undefined
281
+ // ❌ `this.count++` won't update the view, please use a function declaration instead
282
+ const App = () => {
283
+ this.count = 0;
318
284
  return (
319
- <p>
320
- {data.title}
321
- </p>
285
+ <div>
286
+ <span>{this.count}</span>
287
+ <button onClick={() => this.count++}>+</button>
288
+ </div>
322
289
  );
323
- }
290
+ };
324
291
  ```
325
292
 
326
293
  ## Built-in Elements
@@ -331,19 +298,19 @@ mono-jsx provides some built-in elements to help you build your app.
331
298
 
332
299
  `<toggle>` element allows you to toggle the visibility of the slotted content.
333
300
 
334
- ```jsx
335
- function App() {
336
- $state.show = false;
301
+ ```tsx
302
+ function App(this: FC<{ show: boolean }>) {
303
+ this.show = false;
337
304
  return (
338
305
  <div>
339
- <toggle value={$state.show}>
306
+ <toggle value={this.show}>
340
307
  <h1>Hello World!</h1>
341
308
  </toggle>
342
- <button onClick={toggle}>{$computed(() => $state.show ? "Hide" : "Show")}</button>
309
+ <button onClick={toggle}>{this.computed(() => this.show ? "Hide" : "Show")}</button>
343
310
  </div>
344
311
  );
345
312
  function toggle() {
346
- $state.show = !$state.show;
313
+ this.show = !this.show;
347
314
  }
348
315
  }
349
316
  ```
@@ -352,26 +319,24 @@ function App() {
352
319
 
353
320
  `<switch>` element allows you to switch the slotted content based on the `value` property. You need to define the `slot` attribute in the slotted content to match the `value`, otherwise, the default slots will be rendered.
354
321
 
355
- ```jsx
356
- function App() {
322
+ ```tsx
323
+ function App(this: FC<{ lang: "en" | "zh" | "emoji" }>) {
324
+ this.lang = "en";
357
325
  return (
358
326
  <div>
359
- <switch value={$state.lang}>
327
+ <switch value={this.lang}>
360
328
  <h1 slot="en">Hello, world!</h1>
361
329
  <h1 slot="zh">你好,世界!</h1>
362
330
  <h1>✋🌎❗️</h1>
363
331
  </switch>
364
- <button onClick={() => $state.lang = "en"}>English</button>
365
- <button onClick={() => $state.lang = "zh"}>中文</button>
332
+ <button onClick={() => this.lang = "en"}>English</button>
333
+ <button onClick={() => this.lang = "zh"}>中文</button>
334
+ <button onClick={() => this.lang = "emoji"}>Emoji</button>
366
335
  </div>
367
336
  );
368
337
  }
369
338
  ```
370
339
 
371
- ### `<cache>` element
372
-
373
- _Work in progress..._
374
-
375
340
  ## Streaming Rendering
376
341
 
377
342
  mono-jsx renders your `<html>` as a readable stream, that allows async function components are rendered asynchrously. You can set a `placeholder` attribute to show a loading state while the async component is loading.
@@ -394,7 +359,7 @@ export default {
394
359
  };
395
360
  ```
396
361
 
397
- You can also set `rendering` attribute to control the rendering strategy of the async component. Currently, only `eager` is supported that renders the async component immediately.
362
+ You can also set `rendering` attribute to "eager" to render the async component eagerly, which means the async component will be rendered as a sync function component and the `placeholder` will be ignored.
398
363
 
399
364
  ```jsx
400
365
  async function Sleep(ms) {
@@ -413,3 +378,49 @@ export default {
413
378
  ),
414
379
  };
415
380
  ```
381
+
382
+ ## Accessing Request Info
383
+
384
+ You can access the request info in a function component by using the `request` property in the `this` context. And you must pass the `request` object to the root `<html>` element to make it work.
385
+
386
+ ```jsx
387
+ function RequestInfo(this: FC) {
388
+ const { request } = this;
389
+ return (
390
+ <div>
391
+ <h1>Request Info</h1>
392
+ <p>{request.method}</p>
393
+ <p>{request.url}</p>
394
+ <p>{request.headers.get("user-agent")}</p>
395
+ </div>
396
+ );
397
+ }
398
+
399
+ export default {
400
+ fetch: (req) => (
401
+ <html request={req}>
402
+ <RequestInfo />
403
+ </html>
404
+ ),
405
+ };
406
+ ```
407
+
408
+ ## Customizing Response
409
+
410
+ You can add `status` or `headers` attribute to the `<html>` element to customize the response.
411
+
412
+ ```jsx
413
+ export default {
414
+ fetch: (req) => (
415
+ <html
416
+ status={404}
417
+ headers={{
418
+ cacheControl: "public, max-age=0, must-revalidate",
419
+ setCookie: "name=value",
420
+ }}
421
+ >
422
+ <h1>Page Not Found</h1>
423
+ </html>
424
+ ),
425
+ };
426
+ ```