acrylblends 0.6.4 → 0.7.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 +2 -2
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/guides/jsx.md +51 -25
- package/docs/framework/upstream.json +2 -2
- package/lib/{base-ddiVmiE7.js → base-NSN_SNv-.js} +1 -0
- package/lib/{components-DdI7-ns7.js → components-JXVm45kK.js} +1 -1
- package/lib/{components-0o0CBYNv.js → components-cEW3ovK0.js} +3 -3
- package/lib/{hooks-BMw6BAyP.js → hooks-w72iLeFR.js} +1 -1
- package/lib/jsx/dom/client.js +1 -1
- package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
- package/lib/jsx/dom/jsx-runtime.js +1 -1
- package/lib/jsx/dom/server.js +2 -2
- package/lib/jsx/dom.js +4 -4
- package/lib/jsx/jsx-dev-runtime.js +1 -1
- package/lib/jsx/jsx-runtime.js +1 -1
- package/lib/jsx/streaming.js +1 -1
- package/lib/{jsx-dev-runtime-D18Y35TI.js → jsx-dev-runtime-CWLRA2F3.js} +2 -2
- package/lib/jsx-renderer.js +2 -2
- package/lib/jsx.js +4 -4
- package/lib/node-server/register.d.ts +1 -0
- package/lib/node-server/register.js +73 -0
- package/package.json +5 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-node-server/src/register.ts +86 -0
- package/plugins/acryl-node-server/upstream.json +5 -3
- package/plugins/acryl-router/upstream.json +1 -1
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/desktop/main.ts +4 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/main.ts +3 -1
- package/plugins/desktop-web/upstream.json +1 -1
- package/shared/AGENTS.core.md +1 -2
- package/templates/agent/package.json +7 -3
- package/templates/agent/tsconfig.json +3 -1
- package/templates/blank/package.json +7 -3
- package/templates/blank/src/about.tsx +14 -0
- package/templates/blank/src/routes.ts +2 -0
- package/templates/blank/test/app.test.ts +15 -0
- package/templates/blank/tsconfig.json +3 -1
- package/templates/todo/package.json +7 -3
- package/templates/todo/tsconfig.json +3 -1
- package/templates/todo/upstream.json +1 -1
package/README.md
CHANGED
|
@@ -275,10 +275,10 @@ See [Node.js](https://github.com/acryldev/acryl/blob/main/docs/framework/getting
|
|
|
275
275
|
| `acrylblends/cache`, `acrylblends/mount` | the cache middleware and mounting another framework's fetch handler |
|
|
276
276
|
| `acrylblends/ssg` | static site generation: `toSSG` writes every route as a file ([SSG helper](https://github.com/acryldev/acryl/blob/main/docs/framework/helpers/ssg.md)) |
|
|
277
277
|
| `acrylblends/proxy`, `acrylblends/dev` | the proxy helper and the dev helpers (`showRoutes`, `inspectRoutes`, `getRouterName`) |
|
|
278
|
-
| `acrylblends/jsx` | JSX on the server: `jsxImportSource: 'acrylblends/jsx'`, with `jsx/streaming`, and `acrylblends/jsx-renderer` for a layout behind `c.render` ([JSX guide](https://github.com/acryldev/acryl/blob/main/docs/framework/guides/jsx.md)) |
|
|
278
|
+
| `acrylblends/jsx` | JSX on the server: `jsxImportSource: 'acrylblends/jsx'`, `.tsx` files run on Node with `node --import acrylblends/node-server/register`, with `jsx/streaming`, and `acrylblends/jsx-renderer` for a layout behind `c.render` ([JSX guide](https://github.com/acryldev/acryl/blob/main/docs/framework/guides/jsx.md)) |
|
|
279
279
|
| `acrylblends/jsx/dom` | JSX in the browser: hooks and `render` (needs the DOM library) ([client components](https://github.com/acryldev/acryl/blob/main/docs/framework/guides/jsx-dom.md)) |
|
|
280
280
|
| `acrylblends/css` | CSS in JSX: `css`, `cx`, `keyframes`, `Style` ([CSS helper](https://github.com/acryldev/acryl/blob/main/docs/framework/helpers/css.md)) |
|
|
281
|
-
| `acrylblends/node-server` | `serve`, `createAdaptorServer`, `getRequestListener`; with `node-server/serve-static` and `node-server/
|
|
281
|
+
| `acrylblends/node-server` | `serve`, `createAdaptorServer`, `getRequestListener`; with `node-server/serve-static`, `node-server/conninfo`, and `node-server/register` (compiles `.tsx` with the app's `typescript`) |
|
|
282
282
|
|
|
283
283
|
Every subpath is listed in the package's `exports`. The adapters of other runtimes are being ported. The docs mark them "not yet available" until they ship
|
|
284
284
|
([FAQ](https://github.com/acryldev/acryl/blob/main/docs/framework/guides/faq.md)).
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
# Generated copy
|
|
2
2
|
|
|
3
|
-
This folder is a copy of `docs/framework` of acryldev/acryl at commit `
|
|
3
|
+
This folder is a copy of `docs/framework` of acryldev/acryl at commit `7bba3ed0420d87e61d8256fa60614d22af734c8f`, made by `scripts/sync-framework.mjs`. Do not edit it here: change the text in ACRYL and sync again. `pnpm run verify:framework` fails when a page differs from the recorded digest.
|
|
@@ -4,30 +4,6 @@ You can write HTML with JSX syntax with `acrylblends/jsx`.
|
|
|
4
4
|
|
|
5
5
|
Although `acrylblends/jsx` works on the client, you will probably use it most often when rendering content on the server side. Here are some things related to JSX that are common to both server and client.
|
|
6
6
|
|
|
7
|
-
## Node.js runs `jsx()`, not JSX syntax
|
|
8
|
-
|
|
9
|
-
The apps `npm create acrylblends@latest` makes run their TypeScript on Node.js by stripping the types (no build step). That does **not** compile JSX: a `.tsx` file does not load. Either compile the file first (`tsc`
|
|
10
|
-
or a bundler, with the settings below), or write the call that JSX compiles to. `jsx(tag, props, ...children)` takes the children after the props, and a node renders with `toString()`:
|
|
11
|
-
|
|
12
|
-
```ts
|
|
13
|
-
import { Acryl } from 'acrylblends'
|
|
14
|
-
import { jsx } from 'acrylblends/jsx'
|
|
15
|
-
import type { PropsWithChildren } from 'acrylblends/jsx'
|
|
16
|
-
|
|
17
|
-
const Layout = (props: PropsWithChildren<{ title: string }>) =>
|
|
18
|
-
jsx('html', { lang: 'en' }, jsx('head', null, jsx('title', null, props.title)), jsx('body', null, props.children))
|
|
19
|
-
|
|
20
|
-
const About = () =>
|
|
21
|
-
jsx(Layout, { title: 'About' }, jsx('h1', null, 'About'), jsx('p', { class: 'lead' }, 'Rendered on the server.'))
|
|
22
|
-
|
|
23
|
-
const app = new Acryl()
|
|
24
|
-
app.get('/about', (c) => c.html(jsx(About, null).toString()))
|
|
25
|
-
|
|
26
|
-
export default app
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Pass the children as arguments after the props. A `children` entry inside the props object is not rendered by this form.
|
|
30
|
-
|
|
31
7
|
## Settings
|
|
32
8
|
|
|
33
9
|
To use JSX, modify the `tsconfig.json`:
|
|
@@ -50,9 +26,34 @@ Alternatively, use the pragma directives:
|
|
|
50
26
|
/** @jsxImportSource acrylblends/jsx */
|
|
51
27
|
```
|
|
52
28
|
|
|
29
|
+
## Run `.tsx` on Node.js
|
|
30
|
+
|
|
31
|
+
Node.js strips TypeScript's types but does not compile JSX, so a `.tsx` file needs one flag: `--import acrylblends/node-server/register`.
|
|
32
|
+
It compiles each `.tsx` (and `.jsx`) module as Node.js loads it, with your app's own `typescript` (a development dependency) and the
|
|
33
|
+
`jsxImportSource` of your `tsconfig.json` (`acrylblends/jsx` without one); every other module runs exactly as before. Stack traces point at
|
|
34
|
+
the `.tsx` lines.
|
|
35
|
+
|
|
36
|
+
`package.json`:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"scripts": {
|
|
41
|
+
"dev": "node --watch --import acrylblends/node-server/register src/index.tsx",
|
|
42
|
+
"start": "node --import acrylblends/node-server/register src/index.tsx",
|
|
43
|
+
"test": "node --import acrylblends/node-server/register --test \"src/**/*.test.{ts,tsx}\""
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"typescript": "^6.0.3"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`node --test` looks only for `.js` and `.ts` test files on its own, so the `test` script names the `.tsx` ones with a pattern. Without `typescript` in the app, the first `.tsx` module stops with a message that says to install it. Bun compiles JSX itself and needs
|
|
52
|
+
no flag.
|
|
53
|
+
|
|
53
54
|
## Usage
|
|
54
55
|
|
|
55
|
-
> **Note.** If you are coming straight from the [Quick Start](../README.md#quick-start), the main file has a `.ts` extension - you need to change it to `.tsx` - otherwise you will not be able to run the application at all.
|
|
56
|
+
> **Note.** If you are coming straight from the [Quick Start](../README.md#quick-start), the main file has a `.ts` extension - you need to change it to `.tsx` - otherwise you will not be able to run the application at all. Change the scripts in `package.json` to match: on Node.js, as [above](#run-tsx-on-nodejs); on Bun, `bun run --hot src/index.tsx` instead of `bun run --hot src/index.ts`.
|
|
56
57
|
|
|
57
58
|
`index.tsx`:
|
|
58
59
|
|
|
@@ -93,6 +94,31 @@ app.get('/', (c) => {
|
|
|
93
94
|
export default app
|
|
94
95
|
```
|
|
95
96
|
|
|
97
|
+
## Without compiling: `jsx()`
|
|
98
|
+
|
|
99
|
+
A `.ts` file can build the same nodes without JSX syntax and without the register flag: write the call that JSX compiles to.
|
|
100
|
+
`jsx(tag, props, ...children)` takes the children after the props, and a node renders with `toString()`. The `` html`...` `` template
|
|
101
|
+
from `acrylblends/html` is the other build-free choice.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { Acryl } from 'acrylblends'
|
|
105
|
+
import { jsx } from 'acrylblends/jsx'
|
|
106
|
+
import type { PropsWithChildren } from 'acrylblends/jsx'
|
|
107
|
+
|
|
108
|
+
const Layout = (props: PropsWithChildren<{ title: string }>) =>
|
|
109
|
+
jsx('html', { lang: 'en' }, jsx('head', null, jsx('title', null, props.title)), jsx('body', null, props.children))
|
|
110
|
+
|
|
111
|
+
const About = () =>
|
|
112
|
+
jsx(Layout, { title: 'About' }, jsx('h1', null, 'About'), jsx('p', { class: 'lead' }, 'Rendered on the server.'))
|
|
113
|
+
|
|
114
|
+
const app = new Acryl()
|
|
115
|
+
app.get('/about', (c) => c.html(jsx(About, null).toString()))
|
|
116
|
+
|
|
117
|
+
export default app
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Pass the children as arguments after the props. A `children` entry inside the props object is not rendered by this form.
|
|
121
|
+
|
|
96
122
|
## Metadata hoisting
|
|
97
123
|
|
|
98
124
|
You can write document metadata tags such as `<title>`, `<link>`, and `<meta>` directly inside your components. These tags will be automatically hoisted to the `<head>` section of the document. This is especially useful when the `<head>` element is rendered far from the component that determines the appropriate metadata.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"source": {
|
|
3
3
|
"repo": "acryldev/acryl",
|
|
4
4
|
"path": "docs/framework",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "7bba3ed0420d87e61d8256fa60614d22af734c8f"
|
|
6
6
|
},
|
|
7
7
|
"files": 88,
|
|
8
|
-
"digest": "sha256:
|
|
8
|
+
"digest": "sha256:1f7e213ee1fd287a98b5bead17a0b197311fcd75013ddf5912ca4a476657cece"
|
|
9
9
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { escapeToBuffer, raw, resolveCallbackSync, stringBufferToString } from "./utils/html.js";
|
|
2
2
|
import { i as DOM_RENDERER, n as DOM_INTERNAL_TAG, o as PERMALINK, r as DOM_MEMO, t as DOM_ERROR_HANDLER } from "./constants-BR9jM9BC.js";
|
|
3
|
+
import "node:module";
|
|
3
4
|
//#region \0rolldown/runtime.js
|
|
4
5
|
var __defProp = Object.defineProperty;
|
|
5
6
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as DOM_ERROR_HANDLER } from "./constants-BR9jM9BC.js";
|
|
2
|
-
import { t as Fragment } from "./jsx-dev-runtime-
|
|
2
|
+
import { t as Fragment } from "./jsx-dev-runtime-CWLRA2F3.js";
|
|
3
3
|
import "./jsx/dom/jsx-runtime.js";
|
|
4
4
|
//#region ../../plugins/acryl-router/src/core/jsx/dom/components.ts
|
|
5
5
|
const ErrorBoundary = (({ children, fallback, fallbackRender, onError }) => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { C as captureRenderContext, E as useContext, d as renderUntrustedObject, i as isUntrustedObject, n as JSXNode, o as jsx, t as Fragment, u as renderChildren, w as createContext } from "./base-
|
|
1
|
+
import { C as captureRenderContext, E as useContext, d as renderUntrustedObject, i as isUntrustedObject, n as JSXNode, o as jsx, t as Fragment, u as renderChildren, w as createContext } from "./base-NSN_SNv-.js";
|
|
2
2
|
import { HtmlEscapedCallbackPhase, raw, resolveCallback } from "./utils/html.js";
|
|
3
3
|
import { a as DOM_STASH, i as DOM_RENDERER } from "./constants-BR9jM9BC.js";
|
|
4
|
-
import { x as buildDataStack } from "./hooks-
|
|
5
|
-
import { n as Suspense$1, t as ErrorBoundary$1 } from "./components-
|
|
4
|
+
import { x as buildDataStack } from "./hooks-w72iLeFR.js";
|
|
5
|
+
import { n as Suspense$1, t as ErrorBoundary$1 } from "./components-JXVm45kK.js";
|
|
6
6
|
//#region ../../plugins/acryl-router/src/core/jsx/streaming.ts
|
|
7
7
|
/**
|
|
8
8
|
* @module
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as createContext, E as useContext, T as globalContexts, _ as toArray, h as styleObjectForEach, m as normalizeIntrinsicElementKey } from "./base-
|
|
1
|
+
import { D as createContext, E as useContext, T as globalContexts, _ as toArray, h as styleObjectForEach, m as normalizeIntrinsicElementKey } from "./base-NSN_SNv-.js";
|
|
2
2
|
import { a as DOM_STASH, i as DOM_RENDERER, n as DOM_INTERNAL_TAG, r as DOM_MEMO, t as DOM_ERROR_HANDLER } from "./constants-BR9jM9BC.js";
|
|
3
3
|
//#region ../../plugins/acryl-router/src/core/jsx/dom/render.ts
|
|
4
4
|
const ACRYL_PORTAL_ELEMENT = "_hp";
|
package/lib/jsx/dom/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as renderNode, S as buildNode, _ as useState } from "../../hooks-
|
|
1
|
+
import { D as renderNode, S as buildNode, _ as useState } from "../../hooks-w72iLeFR.js";
|
|
2
2
|
//#region ../../plugins/acryl-router/src/core/jsx/dom/client.ts
|
|
3
3
|
/**
|
|
4
4
|
* Create a root object for rendering
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as jsxDEV, t as Fragment } from "../../jsx-dev-runtime-
|
|
1
|
+
import { n as jsxDEV, t as Fragment } from "../../jsx-dev-runtime-CWLRA2F3.js";
|
|
2
2
|
export { Fragment, jsxDEV };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as jsxDEV, t as Fragment } from "../../jsx-dev-runtime-
|
|
1
|
+
import { n as jsxDEV, t as Fragment } from "../../jsx-dev-runtime-CWLRA2F3.js";
|
|
2
2
|
export { Fragment, jsxDEV as jsx, jsxDEV as jsxs };
|
package/lib/jsx/dom/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { u as renderChildren } from "../../base-
|
|
2
|
-
import { i as renderToReadableStream$1 } from "../../components-
|
|
1
|
+
import { u as renderChildren } from "../../base-NSN_SNv-.js";
|
|
2
|
+
import { i as renderToReadableStream$1 } from "../../components-cEW3ovK0.js";
|
|
3
3
|
import dom_default from "../dom.js";
|
|
4
4
|
//#region ../../plugins/acryl-router/src/core/jsx/dom/server.ts
|
|
5
5
|
const prepareRoot = (element) => typeof element === "string" || Array.isArray(element) ? renderChildren([element]) : element;
|
package/lib/jsx/dom.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { D as createContext, E as useContext, a as isValidElement, f as shallowEqual, g as Children, l as reactAPICompatVersion } from "../base-
|
|
1
|
+
import { D as createContext, E as useContext, a as isValidElement, f as shallowEqual, g as Children, l as reactAPICompatVersion } from "../base-NSN_SNv-.js";
|
|
2
2
|
import { r as DOM_MEMO } from "../constants-BR9jM9BC.js";
|
|
3
|
-
import { C as createPortal, E as render, _ as useState, a as use, b as useViewTransition, c as useDeferredValue, d as useImperativeHandle, f as useInsertionEffect, g as useRef, h as useReducer, i as startViewTransition, l as useEffect, m as useMemo, n as forwardRef, o as useCallback, p as useLayoutEffect, r as startTransition, s as useDebugValue, t as createRef, u as useId, v as useSyncExternalStore, w as flushSync, y as useTransition } from "../hooks-
|
|
4
|
-
import { a as useOptimistic, i as useFormStatus, n as jsxDEV, r as useActionState, t as Fragment } from "../jsx-dev-runtime-
|
|
3
|
+
import { C as createPortal, E as render, _ as useState, a as use, b as useViewTransition, c as useDeferredValue, d as useImperativeHandle, f as useInsertionEffect, g as useRef, h as useReducer, i as startViewTransition, l as useEffect, m as useMemo, n as forwardRef, o as useCallback, p as useLayoutEffect, r as startTransition, s as useDebugValue, t as createRef, u as useId, v as useSyncExternalStore, w as flushSync, y as useTransition } from "../hooks-w72iLeFR.js";
|
|
4
|
+
import { a as useOptimistic, i as useFormStatus, n as jsxDEV, r as useActionState, t as Fragment } from "../jsx-dev-runtime-CWLRA2F3.js";
|
|
5
5
|
import "./dom/jsx-runtime.js";
|
|
6
|
-
import { n as Suspense, t as ErrorBoundary } from "../components-
|
|
6
|
+
import { n as Suspense, t as ErrorBoundary } from "../components-JXVm45kK.js";
|
|
7
7
|
//#region ../../plugins/acryl-router/src/core/jsx/dom/index.ts
|
|
8
8
|
/**
|
|
9
9
|
* @module
|
package/lib/jsx/jsx-runtime.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h as styleObjectForEach, p as isValidAttributeName, t as Fragment } from "../base-
|
|
1
|
+
import { h as styleObjectForEach, p as isValidAttributeName, t as Fragment } from "../base-NSN_SNv-.js";
|
|
2
2
|
import { escapeToBuffer, raw, stringBufferToString } from "../utils/html.js";
|
|
3
3
|
import { html } from "../html.js";
|
|
4
4
|
import { jsxDEV } from "./jsx-dev-runtime.js";
|
package/lib/jsx/streaming.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as renderToReadableStream, n as StreamingContext, r as Suspense } from "../components-
|
|
1
|
+
import { i as renderToReadableStream, n as StreamingContext, r as Suspense } from "../components-cEW3ovK0.js";
|
|
2
2
|
export { StreamingContext, Suspense, renderToReadableStream };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { D as createContext, E as useContext, O as __exportAll, S as shouldDeDupeByKey, b as domRenderers, v as dataPrecedenceAttr, x as isStylesheetLinkWithPrecedence, y as deDupeKeyMap } from "./base-
|
|
1
|
+
import { D as createContext, E as useContext, O as __exportAll, S as shouldDeDupeByKey, b as domRenderers, v as dataPrecedenceAttr, x as isStylesheetLinkWithPrecedence, y as deDupeKeyMap } from "./base-NSN_SNv-.js";
|
|
2
2
|
import { o as PERMALINK } from "./constants-BR9jM9BC.js";
|
|
3
|
-
import { C as createPortal, T as getNameSpaceContext, _ as useState, a as use, m as useMemo, o as useCallback } from "./hooks-
|
|
3
|
+
import { C as createPortal, T as getNameSpaceContext, _ as useState, a as use, m as useMemo, o as useCallback } from "./hooks-w72iLeFR.js";
|
|
4
4
|
//#region ../../plugins/acryl-router/src/core/jsx/dom/hooks/index.ts
|
|
5
5
|
/**
|
|
6
6
|
* Provide hooks used only in jsx/dom
|
package/lib/jsx-renderer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { E as useContext, o as jsx, t as Fragment, w as createContext } from "./base-
|
|
1
|
+
import { E as useContext, o as jsx, t as Fragment, w as createContext } from "./base-NSN_SNv-.js";
|
|
2
2
|
import { raw } from "./utils/html.js";
|
|
3
3
|
import { html } from "./html.js";
|
|
4
|
-
import { i as renderToReadableStream } from "./components-
|
|
4
|
+
import { i as renderToReadableStream } from "./components-cEW3ovK0.js";
|
|
5
5
|
import "./jsx.js";
|
|
6
6
|
//#region ../../plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts
|
|
7
7
|
const RequestContext = createContext(null);
|
package/lib/jsx.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { E as useContext, a as isValidElement, c as memo, g as Children, l as reactAPICompatVersion, o as jsx, r as cloneElement, t as Fragment, w as createContext } from "./base-
|
|
2
|
-
import { _ as useState, a as use, b as useViewTransition, c as useDeferredValue, d as useImperativeHandle, f as useInsertionEffect, g as useRef, h as useReducer, i as startViewTransition, l as useEffect, m as useMemo, n as forwardRef, o as useCallback, p as useLayoutEffect, r as startTransition, s as useDebugValue, t as createRef, u as useId, v as useSyncExternalStore, y as useTransition } from "./hooks-
|
|
3
|
-
import { a as useOptimistic, r as useActionState } from "./jsx-dev-runtime-
|
|
4
|
-
import { r as Suspense, t as ErrorBoundary } from "./components-
|
|
1
|
+
import { E as useContext, a as isValidElement, c as memo, g as Children, l as reactAPICompatVersion, o as jsx, r as cloneElement, t as Fragment, w as createContext } from "./base-NSN_SNv-.js";
|
|
2
|
+
import { _ as useState, a as use, b as useViewTransition, c as useDeferredValue, d as useImperativeHandle, f as useInsertionEffect, g as useRef, h as useReducer, i as startViewTransition, l as useEffect, m as useMemo, n as forwardRef, o as useCallback, p as useLayoutEffect, r as startTransition, s as useDebugValue, t as createRef, u as useId, v as useSyncExternalStore, y as useTransition } from "./hooks-w72iLeFR.js";
|
|
3
|
+
import { a as useOptimistic, r as useActionState } from "./jsx-dev-runtime-CWLRA2F3.js";
|
|
4
|
+
import { r as Suspense, t as ErrorBoundary } from "./components-cEW3ovK0.js";
|
|
5
5
|
//#region ../../plugins/acryl-router/src/core/jsx/index.ts
|
|
6
6
|
/**
|
|
7
7
|
* @module
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createRequire, registerHooks } from "node:module";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
//#region ../../plugins/acryl-node-server/src/register.ts
|
|
6
|
+
/**
|
|
7
|
+
* JSX files on Node: `node --import acrylblends/node-server/register src/index.ts`. Node strips TypeScript's types but does not compile JSX, so a
|
|
8
|
+
* `.tsx` (or `.jsx`) module does not load on its own. This module registers a load hook that compiles exactly those files with the app's own
|
|
9
|
+
* `typescript` (react-jsx, the `jsxImportSource` of the app's nearest tsconfig.json, `acrylblends/jsx` without one) and leaves every other
|
|
10
|
+
* module to Node untouched. The output carries an inline source map and source maps are switched on, so a stack trace names the `.tsx` line.
|
|
11
|
+
*
|
|
12
|
+
* `typescript` is loaded from the app, on the first JSX file only; the framework itself has no dependencies.
|
|
13
|
+
*/
|
|
14
|
+
/** The JSX runtime the docs set up; an app's tsconfig.json (or a file's own `@jsxImportSource` pragma) chooses another. */
|
|
15
|
+
const DEFAULT_IMPORT_SOURCE = "acrylblends/jsx";
|
|
16
|
+
const JSX_FILE = /\.[jt]sx$/u;
|
|
17
|
+
const compilers = /* @__PURE__ */ new Map();
|
|
18
|
+
const importSources = /* @__PURE__ */ new Map();
|
|
19
|
+
/** The app's own `typescript`, resolved from the JSX file, so each app compiles with the version it pinned. */
|
|
20
|
+
function compilerFor(file) {
|
|
21
|
+
let path;
|
|
22
|
+
try {
|
|
23
|
+
path = createRequire(file).resolve("typescript");
|
|
24
|
+
} catch {
|
|
25
|
+
throw new Error(`acrylblends: ${file} contains JSX, and compiling it needs the typescript package in this app: npm install --save-dev typescript`);
|
|
26
|
+
}
|
|
27
|
+
let compiler = compilers.get(path);
|
|
28
|
+
if (compiler === void 0) {
|
|
29
|
+
compiler = createRequire(path)(path);
|
|
30
|
+
compilers.set(path, compiler);
|
|
31
|
+
}
|
|
32
|
+
return compiler;
|
|
33
|
+
}
|
|
34
|
+
/** `jsxImportSource` of the tsconfig.json nearest to the file (following `extends`), read once per config. */
|
|
35
|
+
function importSourceFor(file, ts) {
|
|
36
|
+
const config = ts.findConfigFile(dirname(file), (path) => existsSync(path));
|
|
37
|
+
if (config === void 0) return DEFAULT_IMPORT_SOURCE;
|
|
38
|
+
let source = importSources.get(config);
|
|
39
|
+
if (source === void 0) {
|
|
40
|
+
const { config: json } = ts.readConfigFile(config, (path) => ts.sys.readFile(path));
|
|
41
|
+
source = ts.parseJsonConfigFileContent(json ?? {}, ts.sys, dirname(config)).options.jsxImportSource ?? DEFAULT_IMPORT_SOURCE;
|
|
42
|
+
importSources.set(config, source);
|
|
43
|
+
}
|
|
44
|
+
return source;
|
|
45
|
+
}
|
|
46
|
+
/** A JSX module compiled to JavaScript, with its source map inline. */
|
|
47
|
+
function compileJsx(file, text) {
|
|
48
|
+
const ts = compilerFor(file);
|
|
49
|
+
return ts.transpileModule(text, {
|
|
50
|
+
fileName: file,
|
|
51
|
+
compilerOptions: {
|
|
52
|
+
module: "esnext",
|
|
53
|
+
target: "es2022",
|
|
54
|
+
jsx: "react-jsx",
|
|
55
|
+
jsxImportSource: importSourceFor(file, ts),
|
|
56
|
+
allowImportingTsExtensions: true,
|
|
57
|
+
inlineSourceMap: true,
|
|
58
|
+
inlineSources: true
|
|
59
|
+
}
|
|
60
|
+
}).outputText;
|
|
61
|
+
}
|
|
62
|
+
process.setSourceMapsEnabled(true);
|
|
63
|
+
registerHooks({ load(url, context, nextLoad) {
|
|
64
|
+
if (!url.startsWith("file:") || !JSX_FILE.test(new URL(url).pathname)) return nextLoad(url, context);
|
|
65
|
+
const file = fileURLToPath(url);
|
|
66
|
+
return {
|
|
67
|
+
format: "module",
|
|
68
|
+
source: compileJsx(file, readFileSync(file, "utf8")),
|
|
69
|
+
shortCircuit: true
|
|
70
|
+
};
|
|
71
|
+
} });
|
|
72
|
+
//#endregion
|
|
73
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "acrylblends",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "The Acryl web framework (import { Acryl } from \"acrylblends\") and the acrylblends command that scaffolds Cordis apps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -382,6 +382,10 @@
|
|
|
382
382
|
"types": "./lib/node-server/conninfo.d.ts",
|
|
383
383
|
"default": "./lib/node-server/conninfo.js"
|
|
384
384
|
},
|
|
385
|
+
"./node-server/register": {
|
|
386
|
+
"types": "./lib/node-server/register.d.ts",
|
|
387
|
+
"default": "./lib/node-server/register.js"
|
|
388
|
+
},
|
|
385
389
|
"./package.json": "./package.json",
|
|
386
390
|
"./cli": {
|
|
387
391
|
"types": "./dist/index.d.ts",
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSX files on Node: `node --import acrylblends/node-server/register src/index.ts`. Node strips TypeScript's types but does not compile JSX, so a
|
|
3
|
+
* `.tsx` (or `.jsx`) module does not load on its own. This module registers a load hook that compiles exactly those files with the app's own
|
|
4
|
+
* `typescript` (react-jsx, the `jsxImportSource` of the app's nearest tsconfig.json, `acrylblends/jsx` without one) and leaves every other
|
|
5
|
+
* module to Node untouched. The output carries an inline source map and source maps are switched on, so a stack trace names the `.tsx` line.
|
|
6
|
+
*
|
|
7
|
+
* `typescript` is loaded from the app, on the first JSX file only; the framework itself has no dependencies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createRequire, registerHooks } from 'node:module'
|
|
11
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
12
|
+
import { dirname } from 'node:path'
|
|
13
|
+
import { fileURLToPath } from 'node:url'
|
|
14
|
+
|
|
15
|
+
/** The JSX runtime the docs set up; an app's tsconfig.json (or a file's own `@jsxImportSource` pragma) chooses another. */
|
|
16
|
+
const DEFAULT_IMPORT_SOURCE = 'acrylblends/jsx'
|
|
17
|
+
const JSX_FILE = /\.[jt]sx$/u
|
|
18
|
+
|
|
19
|
+
/** The part of the TypeScript compiler API the hook uses. */
|
|
20
|
+
interface Compiler {
|
|
21
|
+
transpileModule(input: string, options: { fileName: string, compilerOptions: Record<string, unknown> }): { outputText: string }
|
|
22
|
+
findConfigFile(searchPath: string, fileExists: (path: string) => boolean): string | undefined
|
|
23
|
+
readConfigFile(path: string, readFile: (path: string) => string | undefined): { config?: unknown }
|
|
24
|
+
parseJsonConfigFileContent(json: unknown, host: unknown, basePath: string): { options: { jsxImportSource?: string } }
|
|
25
|
+
sys: { fileExists(path: string): boolean, readFile(path: string): string | undefined }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const compilers = new Map<string, Compiler>()
|
|
29
|
+
const importSources = new Map<string, string>()
|
|
30
|
+
|
|
31
|
+
/** The app's own `typescript`, resolved from the JSX file, so each app compiles with the version it pinned. */
|
|
32
|
+
function compilerFor(file: string): Compiler {
|
|
33
|
+
let path: string
|
|
34
|
+
try {
|
|
35
|
+
path = createRequire(file).resolve('typescript')
|
|
36
|
+
} catch {
|
|
37
|
+
throw new Error(`acrylblends: ${file} contains JSX, and compiling it needs the typescript package in this app: npm install --save-dev typescript`)
|
|
38
|
+
}
|
|
39
|
+
let compiler = compilers.get(path)
|
|
40
|
+
if (compiler === undefined) {
|
|
41
|
+
compiler = createRequire(path)(path) as Compiler
|
|
42
|
+
compilers.set(path, compiler)
|
|
43
|
+
}
|
|
44
|
+
return compiler
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** `jsxImportSource` of the tsconfig.json nearest to the file (following `extends`), read once per config. */
|
|
48
|
+
function importSourceFor(file: string, ts: Compiler): string {
|
|
49
|
+
const config = ts.findConfigFile(dirname(file), path => existsSync(path))
|
|
50
|
+
if (config === undefined) return DEFAULT_IMPORT_SOURCE
|
|
51
|
+
let source = importSources.get(config)
|
|
52
|
+
if (source === undefined) {
|
|
53
|
+
const { config: json } = ts.readConfigFile(config, path => ts.sys.readFile(path))
|
|
54
|
+
source = ts.parseJsonConfigFileContent(json ?? {}, ts.sys, dirname(config)).options.jsxImportSource ?? DEFAULT_IMPORT_SOURCE
|
|
55
|
+
importSources.set(config, source)
|
|
56
|
+
}
|
|
57
|
+
return source
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A JSX module compiled to JavaScript, with its source map inline. */
|
|
61
|
+
function compileJsx(file: string, text: string): string {
|
|
62
|
+
const ts = compilerFor(file)
|
|
63
|
+
return ts.transpileModule(text, {
|
|
64
|
+
fileName: file,
|
|
65
|
+
compilerOptions: {
|
|
66
|
+
module: 'esnext',
|
|
67
|
+
target: 'es2022',
|
|
68
|
+
jsx: 'react-jsx',
|
|
69
|
+
jsxImportSource: importSourceFor(file, ts),
|
|
70
|
+
allowImportingTsExtensions: true,
|
|
71
|
+
inlineSourceMap: true,
|
|
72
|
+
inlineSources: true,
|
|
73
|
+
},
|
|
74
|
+
}).outputText
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
process.setSourceMapsEnabled(true)
|
|
78
|
+
|
|
79
|
+
registerHooks({
|
|
80
|
+
load(url, context, nextLoad) {
|
|
81
|
+
// Every other module goes straight to Node: the hook's whole cost for them is this test.
|
|
82
|
+
if (!url.startsWith('file:') || !JSX_FILE.test(new URL(url).pathname)) return nextLoad(url, context)
|
|
83
|
+
const file = fileURLToPath(url)
|
|
84
|
+
return { format: 'module', source: compileJsx(file, readFileSync(file, 'utf8')), shortCircuit: true }
|
|
85
|
+
},
|
|
86
|
+
})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"source": {
|
|
3
3
|
"repo": "acryldev/acryl",
|
|
4
4
|
"path": "plugins/acryl-node-server",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "7bba3ed0420d87e61d8256fa60614d22af734c8f"
|
|
6
6
|
},
|
|
7
7
|
"vendored": [
|
|
8
8
|
"src",
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
"LICENSE"
|
|
11
11
|
],
|
|
12
12
|
"rewrittenSpecifiers": 28,
|
|
13
|
-
"digest": "sha256:
|
|
14
|
-
"testsNotRun": {
|
|
13
|
+
"digest": "sha256:9dc4cca51f5a3d8416750c0392135128a0787947f473cff2e00bdb12fe5a63e1",
|
|
14
|
+
"testsNotRun": {
|
|
15
|
+
"tests/register.spec.ts": "links typescript from the plugin's own node_modules (ACRYL's workspace layout); the hook is exercised here by the templates' own .tsx tests and verify:scaffolder"
|
|
16
|
+
},
|
|
15
17
|
"notice": [
|
|
16
18
|
"LICENSE"
|
|
17
19
|
]
|
package/plugins/desktop/main.ts
CHANGED
|
@@ -10,7 +10,8 @@ import { mkdirSync, readFileSync } from 'node:fs'
|
|
|
10
10
|
import { join } from 'node:path'
|
|
11
11
|
import { fileURLToPath } from 'node:url'
|
|
12
12
|
import { app, BrowserWindow, shell } from 'electron'
|
|
13
|
-
import {
|
|
13
|
+
import type { App } from '../../app.ts'
|
|
14
|
+
import '../acryl-node-server/src/register.ts'
|
|
14
15
|
import { selectInstance } from '../instance/index.ts'
|
|
15
16
|
import { createDesktopShutdown, installShutdownRequests } from './shutdown.ts'
|
|
16
17
|
import { resolveDesktopUserDataOverride } from './user-data.ts'
|
|
@@ -63,6 +64,8 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
63
64
|
})
|
|
64
65
|
|
|
65
66
|
void app.whenReady().then(async () => {
|
|
67
|
+
// The hook above is registered before the app's modules are loaded, so the app may have .tsx pages: they are loaded here, not as a static import.
|
|
68
|
+
const { createApp } = await import('../../app.ts')
|
|
66
69
|
running = await createApp(chosen)
|
|
67
70
|
const origin = new URL(running.url).origin
|
|
68
71
|
window = new BrowserWindow(windowOptions({ title: chosen.name }))
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"apps/acryl-desktop/src/shell/desktop-user-data.ts": "user-data.ts",
|
|
6
6
|
"apps/acryl-desktop/src/shell/shutdown.ts": "shutdown.ts"
|
|
7
7
|
},
|
|
8
|
-
"commit": "
|
|
8
|
+
"commit": "7bba3ed0420d87e61d8256fa60614d22af734c8f"
|
|
9
9
|
},
|
|
10
10
|
"rewrittenSpecifiers": 0,
|
|
11
11
|
"digest": "sha256:7dfe960a8bbb476c35540d2202a7aaa62b33492c2ac3e34314e2ecfca45f32f2"
|
|
@@ -12,7 +12,7 @@ import { join } from 'node:path'
|
|
|
12
12
|
import { fileURLToPath } from 'node:url'
|
|
13
13
|
import { serve } from 'acrylblends/node-server'
|
|
14
14
|
import { app, BrowserWindow, shell } from 'electron'
|
|
15
|
-
import
|
|
15
|
+
import 'acrylblends/node-server/register'
|
|
16
16
|
import { createDesktopShutdown, installShutdownRequests } from './shutdown.ts'
|
|
17
17
|
import { resolveDesktopUserDataOverride } from './user-data.ts'
|
|
18
18
|
import { windowOptions } from './window.ts'
|
|
@@ -66,6 +66,8 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
66
66
|
})
|
|
67
67
|
|
|
68
68
|
void app.whenReady().then(async () => {
|
|
69
|
+
// The hook above is registered before the app's modules are loaded, so the app may have .tsx pages: they are loaded here, not as a static import.
|
|
70
|
+
const { default: acrylApp } = await import('../../app.ts')
|
|
69
71
|
const port = await new Promise<number>((resolve) => {
|
|
70
72
|
server = serve(
|
|
71
73
|
{ fetch: acrylApp.fetch, port: 0, hostname: '127.0.0.1' },
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"apps/acryl-desktop/src/shell/desktop-user-data.ts": "user-data.ts",
|
|
6
6
|
"apps/acryl-desktop/src/shell/shutdown.ts": "shutdown.ts"
|
|
7
7
|
},
|
|
8
|
-
"commit": "
|
|
8
|
+
"commit": "7bba3ed0420d87e61d8256fa60614d22af734c8f"
|
|
9
9
|
},
|
|
10
10
|
"rewrittenSpecifiers": 0,
|
|
11
11
|
"digest": "sha256:7dfe960a8bbb476c35540d2202a7aaa62b33492c2ac3e34314e2ecfca45f32f2"
|
package/shared/AGENTS.core.md
CHANGED
|
@@ -17,8 +17,7 @@ The docs write `import { Acryl } from 'acrylblends'` and `from 'acrylblends/<sub
|
|
|
17
17
|
- `./plugins/acryl-router/src/helpers/index.ts` for the helpers and validators (`streamSSE`, `schemaValidator`, `validator`, cookies, `testClient`, ...).
|
|
18
18
|
- `./plugins/acryl-router/src/core/jsx/index.ts` for `acrylblends/jsx` (`jsx`, `Fragment`, the types), and `.../core/jsx/streaming.ts` for `acrylblends/jsx/streaming`.
|
|
19
19
|
|
|
20
|
-
**JSX
|
|
21
|
-
(`jsx(tag, props, ...children)`); render with `.toString()` and return it with `c.html(...)`. A component is a function from props to a `jsx(...)` node.
|
|
20
|
+
**JSX: write `.tsx`.** The scripts in `package.json` run Node.js with `--import ./src/plugins/acryl-node-server/src/register.ts`, which compiles `.tsx` files with this app's own `typescript`; `tsconfig.json` sets `jsx: react-jsx` and `jsxImportSource: #acryl-jsx`, which `package.json`'s `imports` maps to the vendored runtime in `src/plugins/acryl-router`. See `src/about.tsx` (blank template) for a page and `test/app.test.ts` for how it is tested. Render a component with `.toString()` and return it with `c.html(...)`; import `jsx`, `Fragment` and the types from `./plugins/acryl-router/src/core/jsx/index.ts` when you need them. A command that starts the app without those scripts (a bare `node src/main.ts`) cannot load a `.tsx` file. Without a build step you can write the call JSX compiles to instead: `jsx('h1', { class: 'title' }, 'Hello')`, children after the props.
|
|
22
21
|
|
|
23
22
|
A plugin written into `extensions/` while the app runs may use the docs' own names instead: `acrylblends` and `acrylblends/<subpath>` resolve to the same files. `App` is the
|
|
24
23
|
deprecated name of `Acryl`.
|
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
+
"imports": {
|
|
7
|
+
"#acryl-jsx/jsx-runtime": "./src/plugins/acryl-router/src/core/jsx/jsx-runtime.ts",
|
|
8
|
+
"#acryl-jsx/jsx-dev-runtime": "./src/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts"
|
|
9
|
+
},
|
|
6
10
|
"engines": {
|
|
7
11
|
"node": ">=22.19.0"
|
|
8
12
|
},
|
|
9
13
|
"scripts": {
|
|
10
|
-
"start": "node src/main.ts",
|
|
11
|
-
"dev": "node --watch src/main.ts",
|
|
12
|
-
"test": "node --test",
|
|
14
|
+
"start": "node --import ./src/plugins/acryl-node-server/src/register.ts src/main.ts",
|
|
15
|
+
"dev": "node --watch --import ./src/plugins/acryl-node-server/src/register.ts src/main.ts",
|
|
16
|
+
"test": "node --import ./src/plugins/acryl-node-server/src/register.ts --test \"test/**/*.test.{ts,tsx}\"",
|
|
13
17
|
"check": "tsc --noEmit",
|
|
14
18
|
"lock": "node src/plugins/manifest/lock.ts",
|
|
15
19
|
"save": "node src/plugins/save/cli.ts",
|
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"./src/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts"
|
|
63
63
|
],
|
|
64
64
|
"acrylblends/jsx/streaming": ["./src/plugins/acryl-router/src/core/jsx/streaming.ts"]
|
|
65
|
-
}
|
|
65
|
+
},
|
|
66
|
+
"jsx": "react-jsx",
|
|
67
|
+
"jsxImportSource": "#acryl-jsx"
|
|
66
68
|
},
|
|
67
69
|
"include": ["src", "test", "extensions"]
|
|
68
70
|
}
|
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
+
"imports": {
|
|
7
|
+
"#acryl-jsx/jsx-runtime": "./src/plugins/acryl-router/src/core/jsx/jsx-runtime.ts",
|
|
8
|
+
"#acryl-jsx/jsx-dev-runtime": "./src/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts"
|
|
9
|
+
},
|
|
6
10
|
"engines": {
|
|
7
11
|
"node": ">=22.19.0"
|
|
8
12
|
},
|
|
9
13
|
"scripts": {
|
|
10
|
-
"start": "node src/main.ts",
|
|
11
|
-
"dev": "node --watch src/main.ts",
|
|
12
|
-
"test": "node --test",
|
|
14
|
+
"start": "node --import ./src/plugins/acryl-node-server/src/register.ts src/main.ts",
|
|
15
|
+
"dev": "node --watch --import ./src/plugins/acryl-node-server/src/register.ts src/main.ts",
|
|
16
|
+
"test": "node --import ./src/plugins/acryl-node-server/src/register.ts --test \"test/**/*.test.{ts,tsx}\"",
|
|
13
17
|
"check": "tsc --noEmit",
|
|
14
18
|
"lock": "node src/plugins/manifest/lock.ts",
|
|
15
19
|
"save": "node src/plugins/save/cli.ts",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** A page written in JSX: the scripts in package.json run Node.js with the vendored register hook (src/plugins/acryl-node-server/src/register.ts), which compiles .tsx with this app's own typescript. */
|
|
2
|
+
const About = () => (
|
|
3
|
+
<main>
|
|
4
|
+
<h1>About</h1>
|
|
5
|
+
<p>Rendered on the server with the JSX runtime in src/plugins/acryl-router.</p>
|
|
6
|
+
</main>
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
export const aboutPage = (): string => (<About />).toString()
|
|
10
|
+
|
|
11
|
+
/** Throws on purpose: the template's test checks that the stack names this file, so a failure in your own pages points at the .tsx line. */
|
|
12
|
+
export const failingPage = (): string => {
|
|
13
|
+
throw new Error('a page failed')
|
|
14
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis'
|
|
2
|
+
import { aboutPage } from './about.tsx'
|
|
2
3
|
import { Acryl } from './plugins/acryl-router/src/index.ts'
|
|
3
4
|
|
|
4
5
|
export const APP_NAME = '__APP_NAME__'
|
|
@@ -11,6 +12,7 @@ export const routes = {
|
|
|
11
12
|
const app = new Acryl()
|
|
12
13
|
.get('/', (c) => c.json({ app: APP_NAME, ok: true }))
|
|
13
14
|
.get('/health', (c) => c.json({ ok: true }))
|
|
15
|
+
.get('/about', (c) => c.html(aboutPage()))
|
|
14
16
|
ctx.router.route('/', app)
|
|
15
17
|
},
|
|
16
18
|
}
|
|
@@ -29,6 +29,21 @@ test('serves the index route', () =>
|
|
|
29
29
|
assert.deepEqual(await response.json(), { app: APP_NAME, ok: true })
|
|
30
30
|
}))
|
|
31
31
|
|
|
32
|
+
test('renders a .tsx page, and an error in it names the .tsx line', () =>
|
|
33
|
+
withApp(async (app) => {
|
|
34
|
+
const response = await fetch(`${app.url}/about`)
|
|
35
|
+
assert.equal(response.status, 200)
|
|
36
|
+
assert.match(await response.text(), /<main><h1>About<\/h1>/)
|
|
37
|
+
const { failingPage } = await import('../src/about.tsx')
|
|
38
|
+
let stack = ''
|
|
39
|
+
try {
|
|
40
|
+
failingPage()
|
|
41
|
+
} catch (error) {
|
|
42
|
+
stack = String((error as Error).stack)
|
|
43
|
+
}
|
|
44
|
+
assert.match(stack, /about\.tsx:\d+/)
|
|
45
|
+
}))
|
|
46
|
+
|
|
32
47
|
test('answers health checks and 404 for unknown paths', () =>
|
|
33
48
|
withApp(async (app) => {
|
|
34
49
|
assert.equal((await fetch(`${app.url}/health`)).status, 200)
|
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
+
"imports": {
|
|
7
|
+
"#acryl-jsx/jsx-runtime": "./src/plugins/acryl-router/src/core/jsx/jsx-runtime.ts",
|
|
8
|
+
"#acryl-jsx/jsx-dev-runtime": "./src/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts"
|
|
9
|
+
},
|
|
6
10
|
"engines": {
|
|
7
11
|
"node": ">=22.19.0"
|
|
8
12
|
},
|
|
9
13
|
"scripts": {
|
|
10
|
-
"start": "node src/main.ts",
|
|
11
|
-
"dev": "node --watch src/main.ts",
|
|
12
|
-
"test": "node --test",
|
|
14
|
+
"start": "node --import ./src/plugins/acryl-node-server/src/register.ts src/main.ts",
|
|
15
|
+
"dev": "node --watch --import ./src/plugins/acryl-node-server/src/register.ts src/main.ts",
|
|
16
|
+
"test": "node --import ./src/plugins/acryl-node-server/src/register.ts --test \"test/**/*.test.{ts,tsx}\"",
|
|
13
17
|
"check": "tsc --noEmit",
|
|
14
18
|
"lock": "node src/plugins/manifest/lock.ts",
|
|
15
19
|
"save": "node src/plugins/save/cli.ts",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"examples/acryl-todo/src/index.ts": "src/todo.ts",
|
|
6
6
|
"examples/acryl-todo/src/page.ts": "src/page.ts"
|
|
7
7
|
},
|
|
8
|
-
"commit": "
|
|
8
|
+
"commit": "7bba3ed0420d87e61d8256fa60614d22af734c8f"
|
|
9
9
|
},
|
|
10
10
|
"rewrittenSpecifiers": 4,
|
|
11
11
|
"digest": "sha256:9093704776563001f7f472f94cc5c2fdd79206342c1cb59c11fce62aa74df3ed"
|