acrylblends 0.6.4 → 0.7.1
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/dist/index.js +34 -3
- package/dist/index.js.map +1 -1
- package/docs/framework/README.md +1 -1
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/api/summary.md +1 -1
- package/docs/framework/getting-started/desktop.md +1 -1
- package/docs/framework/guides/forms-and-sessions.md +231 -0
- package/docs/framework/guides/jsx-dom.md +6 -6
- package/docs/framework/guides/jsx.md +51 -25
- package/docs/framework/guides/validation.md +1 -1
- package/docs/framework/helpers/cookie.md +1 -1
- package/docs/framework/upstream.json +3 -3
- 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/plugin.json +1 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/main.ts +3 -1
- package/plugins/desktop-web/plugin.json +1 -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
|
@@ -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.
|
|
@@ -36,7 +36,7 @@ app.get('/cookie', (c) => {
|
|
|
36
36
|
**NOTE**: Setting and retrieving signed cookies returns a Promise due to the async nature of the WebCrypto API, which is used to create HMAC SHA-256 signatures.
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
-
app.get('/signed-cookie', (c) => {
|
|
39
|
+
app.get('/signed-cookie', async (c) => {
|
|
40
40
|
const secret = process.env.COOKIE_SECRET! // a long random string, kept out of the source
|
|
41
41
|
|
|
42
42
|
await setSignedCookie(c, 'cookie_name0', 'cookie_value', secret)
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"source": {
|
|
3
3
|
"repo": "acryldev/acryl",
|
|
4
4
|
"path": "docs/framework",
|
|
5
|
-
"commit": "
|
|
5
|
+
"commit": "524406d2033cdc5ca5e01207f9ce29543bbf221b"
|
|
6
6
|
},
|
|
7
|
-
"files":
|
|
8
|
-
"digest": "sha256:
|
|
7
|
+
"files": 89,
|
|
8
|
+
"digest": "sha256:fe2da945c3286e316a0412b2d3cfccc4dbf8d1c082e283b77606b45e27b778aa"
|
|
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.1",
|
|
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": "524406d2033cdc5ca5e01207f9ce29543bbf221b"
|
|
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:c830697a6c09e4f9137f5589cffb56e55d07a06044f22c4087206bcfc3e62ea6",
|
|
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": "524406d2033cdc5ca5e01207f9ce29543bbf221b"
|
|
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": "524406d2033cdc5ca5e01207f9ce29543bbf221b"
|
|
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`.
|