create-acrylblends 0.0.0-stage → 0.1.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 +61 -2
- package/bin/create-acrylblends.js +4 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +274 -0
- package/dist/index.js.map +1 -0
- package/docs/framework/LICENSE +23 -0
- package/docs/framework/README.md +63 -0
- package/docs/framework/UPSTREAM.md +3 -0
- package/docs/framework/agents/index.md +24 -0
- package/docs/framework/api/acryl.md +259 -0
- package/docs/framework/api/context.md +527 -0
- package/docs/framework/api/exception.md +80 -0
- package/docs/framework/api/index.md +12 -0
- package/docs/framework/api/presets.md +65 -0
- package/docs/framework/api/request.md +396 -0
- package/docs/framework/api/routing.md +303 -0
- package/docs/framework/api/summary.md +140 -0
- package/docs/framework/concepts/benchmarks.md +26 -0
- package/docs/framework/concepts/developer-experience.md +5 -0
- package/docs/framework/concepts/middleware.md +21 -0
- package/docs/framework/concepts/motivation.md +14 -0
- package/docs/framework/concepts/routers.md +63 -0
- package/docs/framework/concepts/stacks.md +235 -0
- package/docs/framework/concepts/web-standard.md +37 -0
- package/docs/framework/cordis/acryl-in-a-cordis-app.md +146 -0
- package/docs/framework/cordis/concepts.md +105 -0
- package/docs/framework/cordis/middleware.md +8 -0
- package/docs/framework/cordis/migration-from-0-3.md +111 -0
- package/docs/framework/cordis/migration-from-0-4.md +14 -0
- package/docs/framework/cordis/migration-from-dsh-webserver.md +109 -0
- package/docs/framework/cordis/mounting.md +85 -0
- package/docs/framework/cordis/node-server.md +75 -0
- package/docs/framework/cordis/plugin-lifecycle.md +111 -0
- package/docs/framework/cordis/surfaces.md +71 -0
- package/docs/framework/cordis/transport-and-cancellation.md +57 -0
- package/docs/framework/cordis/validation.md +135 -0
- package/docs/framework/getting-started/basic.md +196 -0
- package/docs/framework/getting-started/bun.md +221 -0
- package/docs/framework/getting-started/nodejs.md +344 -0
- package/docs/framework/getting-started/other-runtimes.md +23 -0
- package/docs/framework/guides/best-practices.md +231 -0
- package/docs/framework/guides/create-acrylblends.md +79 -0
- package/docs/framework/guides/examples.md +3 -0
- package/docs/framework/guides/faq.md +19 -0
- package/docs/framework/guides/helpers.md +33 -0
- package/docs/framework/guides/jsx-dom.md +299 -0
- package/docs/framework/guides/jsx.md +440 -0
- package/docs/framework/guides/middleware.md +253 -0
- package/docs/framework/guides/rpc.md +886 -0
- package/docs/framework/guides/testing.md +133 -0
- package/docs/framework/guides/validation.md +383 -0
- package/docs/framework/helpers/accepts.md +60 -0
- package/docs/framework/helpers/adapter.md +87 -0
- package/docs/framework/helpers/conninfo.md +112 -0
- package/docs/framework/helpers/cookie.md +232 -0
- package/docs/framework/helpers/css.md +285 -0
- package/docs/framework/helpers/dev.md +67 -0
- package/docs/framework/helpers/factory.md +180 -0
- package/docs/framework/helpers/html.md +123 -0
- package/docs/framework/helpers/jwt.md +184 -0
- package/docs/framework/helpers/proxy.md +118 -0
- package/docs/framework/helpers/route.md +143 -0
- package/docs/framework/helpers/ssg.md +393 -0
- package/docs/framework/helpers/streaming.md +119 -0
- package/docs/framework/helpers/testing.md +101 -0
- package/docs/framework/helpers/websocket.md +215 -0
- package/docs/framework/middleware/builtin/basic-auth.md +165 -0
- package/docs/framework/middleware/builtin/bearer-auth.md +138 -0
- package/docs/framework/middleware/builtin/body-limit.md +71 -0
- package/docs/framework/middleware/builtin/cache.md +122 -0
- package/docs/framework/middleware/builtin/combine.md +94 -0
- package/docs/framework/middleware/builtin/compress.md +59 -0
- package/docs/framework/middleware/builtin/context-storage.md +78 -0
- package/docs/framework/middleware/builtin/cors.md +135 -0
- package/docs/framework/middleware/builtin/csrf.md +105 -0
- package/docs/framework/middleware/builtin/etag.md +54 -0
- package/docs/framework/middleware/builtin/ip-restriction.md +69 -0
- package/docs/framework/middleware/builtin/jsx-renderer.md +286 -0
- package/docs/framework/middleware/builtin/jwk.md +192 -0
- package/docs/framework/middleware/builtin/jwt.md +145 -0
- package/docs/framework/middleware/builtin/language.md +232 -0
- package/docs/framework/middleware/builtin/logger.md +74 -0
- package/docs/framework/middleware/builtin/loopback-only.md +31 -0
- package/docs/framework/middleware/builtin/method-not-allowed.md +48 -0
- package/docs/framework/middleware/builtin/method-override.md +80 -0
- package/docs/framework/middleware/builtin/mount.md +106 -0
- package/docs/framework/middleware/builtin/pretty-json.md +53 -0
- package/docs/framework/middleware/builtin/request-id.md +94 -0
- package/docs/framework/middleware/builtin/secure-headers.md +247 -0
- package/docs/framework/middleware/builtin/timeout.md +98 -0
- package/docs/framework/middleware/builtin/timing.md +101 -0
- package/docs/framework/middleware/builtin/trailing-slash.md +74 -0
- package/docs/framework/middleware/third-party.md +4 -0
- package/docs/framework/upstream.json +9 -0
- package/package.json +47 -3
- package/templates/bun/gitignore +1 -0
- package/templates/bun/package.json +19 -0
- package/templates/bun/src/index.ts +7 -0
- package/templates/bun/test/app.test.ts +8 -0
- package/templates/bun/tsconfig.json +15 -0
- package/templates/nodejs/gitignore +1 -0
- package/templates/nodejs/package.json +22 -0
- package/templates/nodejs/src/app.ts +7 -0
- package/templates/nodejs/src/index.ts +9 -0
- package/templates/nodejs/test/app.test.ts +9 -0
- package/templates/nodejs/tsconfig.json +16 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
# JSX
|
|
2
|
+
|
|
3
|
+
> **Not yet available.** The JSX packages (`acrylblends/jsx` and its subpaths) is documented here so the page structure matches the rest of the framework, and is not part of acrylblends yet. The code on this page is not checked by the docs tests.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
You can write HTML with JSX syntax with `acrylblends/jsx`.
|
|
7
|
+
|
|
8
|
+
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.
|
|
9
|
+
|
|
10
|
+
## Settings
|
|
11
|
+
|
|
12
|
+
To use JSX, modify the `tsconfig.json`:
|
|
13
|
+
|
|
14
|
+
`tsconfig.json`:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"compilerOptions": {
|
|
19
|
+
"jsx": "react-jsx",
|
|
20
|
+
"jsxImportSource": "acrylblends/jsx"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Alternatively, use the pragma directives:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
/** @jsx jsx */
|
|
29
|
+
/** @jsxImportSource acrylblends/jsx */
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
> **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. You should additionally modify the `package.json` (or `deno.json` if you are using Deno) to reflect that change (e.g. instead of having `bun run --hot src/index.ts` in dev script, you should have `bun run --hot src/index.tsx`).
|
|
35
|
+
|
|
36
|
+
`index.tsx`:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { Acryl } from 'acrylblends'
|
|
40
|
+
import type { FC } from 'acrylblends/jsx'
|
|
41
|
+
|
|
42
|
+
const app = new Acryl()
|
|
43
|
+
|
|
44
|
+
const Layout: FC = (props) => {
|
|
45
|
+
return (
|
|
46
|
+
<html>
|
|
47
|
+
<body>{props.children}</body>
|
|
48
|
+
</html>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const Top: FC<{ messages: string[] }> = (props: {
|
|
53
|
+
messages: string[]
|
|
54
|
+
}) => {
|
|
55
|
+
return (
|
|
56
|
+
<Layout>
|
|
57
|
+
<h1>Hello Acryl!</h1>
|
|
58
|
+
<ul>
|
|
59
|
+
{props.messages.map((message) => {
|
|
60
|
+
return <li>{message}!!</li>
|
|
61
|
+
})}
|
|
62
|
+
</ul>
|
|
63
|
+
</Layout>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
app.get('/', (c) => {
|
|
68
|
+
const messages = ['Good Morning', 'Good Evening', 'Good Night']
|
|
69
|
+
return c.html(<Top messages={messages} />)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
export default app
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Metadata hoisting
|
|
76
|
+
|
|
77
|
+
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.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { Acryl } from 'acrylblends'
|
|
81
|
+
|
|
82
|
+
const app = new Acryl()
|
|
83
|
+
|
|
84
|
+
app.use('*', async (c, next) => {
|
|
85
|
+
c.setRenderer((content) => {
|
|
86
|
+
return c.html(
|
|
87
|
+
<html>
|
|
88
|
+
<head></head>
|
|
89
|
+
<body>{content}</body>
|
|
90
|
+
</html>
|
|
91
|
+
)
|
|
92
|
+
})
|
|
93
|
+
await next()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
app.get('/about', (c) => {
|
|
97
|
+
return c.render(
|
|
98
|
+
<>
|
|
99
|
+
<title>About Page</title>
|
|
100
|
+
<meta name='description' content='This is the about page.' />
|
|
101
|
+
about page content
|
|
102
|
+
</>
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
export default app
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
> **Note.** When hoisting occurs, existing elements are not removed. Elements appearing later are added to the end. For example, if you have `<title>Default</title>` in your `<head>` and a component renders `<title>Page Title</title>`, both titles will appear in the head.
|
|
110
|
+
|
|
111
|
+
## Fragment
|
|
112
|
+
|
|
113
|
+
Use Fragment to group multiple elements without adding extra nodes:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { Fragment } from 'acrylblends/jsx'
|
|
117
|
+
|
|
118
|
+
const List = () => (
|
|
119
|
+
<Fragment>
|
|
120
|
+
<p>first child</p>
|
|
121
|
+
<p>second child</p>
|
|
122
|
+
<p>third child</p>
|
|
123
|
+
</Fragment>
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Or you can write it with `<></>` if it sets up properly.
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
const List = () => (
|
|
131
|
+
<>
|
|
132
|
+
<p>first child</p>
|
|
133
|
+
<p>second child</p>
|
|
134
|
+
<p>third child</p>
|
|
135
|
+
</>
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## `PropsWithChildren`
|
|
140
|
+
|
|
141
|
+
You can use `PropsWithChildren` to correctly infer a child element in a function component.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { PropsWithChildren } from 'acrylblends/jsx'
|
|
145
|
+
|
|
146
|
+
type Post = {
|
|
147
|
+
id: number
|
|
148
|
+
title: string
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function Component({ title, children }: PropsWithChildren<Post>) {
|
|
152
|
+
return (
|
|
153
|
+
<div>
|
|
154
|
+
<h1>{title}</h1>
|
|
155
|
+
{children}
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Inserting Raw HTML
|
|
162
|
+
|
|
163
|
+
To directly insert HTML, use `dangerouslySetInnerHTML`:
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
app.get('/foo', (c) => {
|
|
167
|
+
const inner = { __html: 'JSX · SSR' }
|
|
168
|
+
const Div = <div dangerouslySetInnerHTML={inner} />
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Memoization
|
|
173
|
+
|
|
174
|
+
Optimize your components by memoizing computed strings using `memo`:
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { memo } from 'acrylblends/jsx'
|
|
178
|
+
|
|
179
|
+
const Header = memo(() => <header>Welcome to Acryl</header>)
|
|
180
|
+
const Footer = memo(() => <footer>Powered by Acryl</footer>)
|
|
181
|
+
const Layout = (
|
|
182
|
+
<div>
|
|
183
|
+
<Header />
|
|
184
|
+
<p>Acryl is cool!</p>
|
|
185
|
+
<Footer />
|
|
186
|
+
</div>
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Context
|
|
191
|
+
|
|
192
|
+
By using `useContext`, you can share data globally across any level of the Component tree without passing values through props.
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import type { FC } from 'acrylblends/jsx'
|
|
196
|
+
import { createContext, useContext } from 'acrylblends/jsx'
|
|
197
|
+
|
|
198
|
+
const themes = {
|
|
199
|
+
light: {
|
|
200
|
+
color: '#000000',
|
|
201
|
+
background: '#eeeeee',
|
|
202
|
+
},
|
|
203
|
+
dark: {
|
|
204
|
+
color: '#ffffff',
|
|
205
|
+
background: '#222222',
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const ThemeContext = createContext(themes.light)
|
|
210
|
+
|
|
211
|
+
const Button: FC = () => {
|
|
212
|
+
const theme = useContext(ThemeContext)
|
|
213
|
+
return <button style={theme}>Push!</button>
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const Toolbar: FC = () => {
|
|
217
|
+
return (
|
|
218
|
+
<div>
|
|
219
|
+
<Button />
|
|
220
|
+
</div>
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ...
|
|
225
|
+
|
|
226
|
+
app.get('/', (c) => {
|
|
227
|
+
return c.html(
|
|
228
|
+
<div>
|
|
229
|
+
<ThemeContext.Provider value={themes.dark}>
|
|
230
|
+
<Toolbar />
|
|
231
|
+
</ThemeContext.Provider>
|
|
232
|
+
</div>
|
|
233
|
+
)
|
|
234
|
+
})
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Async Component
|
|
238
|
+
|
|
239
|
+
`acrylblends/jsx` supports an Async Component, so you can use `async`/`await` in your component.
|
|
240
|
+
If you render it with `c.html()`, it will await automatically.
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
const AsyncComponent = async () => {
|
|
244
|
+
await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
|
|
245
|
+
return <div>Done!</div>
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
app.get('/', (c) => {
|
|
249
|
+
return c.html(
|
|
250
|
+
<html>
|
|
251
|
+
<body>
|
|
252
|
+
<AsyncComponent />
|
|
253
|
+
</body>
|
|
254
|
+
</html>
|
|
255
|
+
)
|
|
256
|
+
})
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Suspense *(Experimental)*
|
|
260
|
+
|
|
261
|
+
The React-like `Suspense` feature is available.
|
|
262
|
+
If you wrap the async component with `Suspense`, the content in the fallback will be rendered first, and once the Promise is resolved, the awaited content will be displayed.
|
|
263
|
+
You can use it with `renderToReadableStream()`.
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import { renderToReadableStream, Suspense } from 'acrylblends/jsx/streaming'
|
|
267
|
+
|
|
268
|
+
//...
|
|
269
|
+
|
|
270
|
+
app.get('/', (c) => {
|
|
271
|
+
const stream = renderToReadableStream(
|
|
272
|
+
<html>
|
|
273
|
+
<body>
|
|
274
|
+
<Suspense fallback={<div>loading...</div>}>
|
|
275
|
+
<Component />
|
|
276
|
+
</Suspense>
|
|
277
|
+
</body>
|
|
278
|
+
</html>
|
|
279
|
+
)
|
|
280
|
+
return c.body(stream, {
|
|
281
|
+
headers: {
|
|
282
|
+
'Content-Type': 'text/html; charset=UTF-8',
|
|
283
|
+
'Transfer-Encoding': 'chunked',
|
|
284
|
+
},
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## ErrorBoundary *(Experimental)*
|
|
290
|
+
|
|
291
|
+
You can catch errors in child components using `ErrorBoundary`.
|
|
292
|
+
|
|
293
|
+
In the example below, it will show the content specified in `fallback` if an error occurs.
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
function SyncComponent() {
|
|
297
|
+
throw new Error('Error')
|
|
298
|
+
return <div>Hello</div>
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
app.get('/sync', async (c) => {
|
|
302
|
+
return c.html(
|
|
303
|
+
<html>
|
|
304
|
+
<body>
|
|
305
|
+
<ErrorBoundary fallback={<div>Out of Service</div>}>
|
|
306
|
+
<SyncComponent />
|
|
307
|
+
</ErrorBoundary>
|
|
308
|
+
</body>
|
|
309
|
+
</html>
|
|
310
|
+
)
|
|
311
|
+
})
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`ErrorBoundary` can also be used with async components and `Suspense`.
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
async function AsyncComponent() {
|
|
318
|
+
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
319
|
+
throw new Error('Error')
|
|
320
|
+
return <div>Hello</div>
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
app.get('/with-suspense', async (c) => {
|
|
324
|
+
return c.html(
|
|
325
|
+
<html>
|
|
326
|
+
<body>
|
|
327
|
+
<ErrorBoundary fallback={<div>Out of Service</div>}>
|
|
328
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
329
|
+
<AsyncComponent />
|
|
330
|
+
</Suspense>
|
|
331
|
+
</ErrorBoundary>
|
|
332
|
+
</body>
|
|
333
|
+
</html>
|
|
334
|
+
)
|
|
335
|
+
})
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## StreamingContext *(Experimental)*
|
|
339
|
+
|
|
340
|
+
You can use `StreamingContext` to provide configuration for streaming components like `Suspense` and `ErrorBoundary`. This is useful for adding nonce values to script tags generated by these components for Content Security Policy (CSP).
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
import { Suspense, StreamingContext } from 'acrylblends/jsx/streaming'
|
|
344
|
+
|
|
345
|
+
// ...
|
|
346
|
+
|
|
347
|
+
app.get('/', (c) => {
|
|
348
|
+
const stream = renderToReadableStream(
|
|
349
|
+
<html>
|
|
350
|
+
<body>
|
|
351
|
+
<StreamingContext
|
|
352
|
+
value={{ scriptNonce: 'random-nonce-value' }}
|
|
353
|
+
>
|
|
354
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
355
|
+
<AsyncComponent />
|
|
356
|
+
</Suspense>
|
|
357
|
+
</StreamingContext>
|
|
358
|
+
</body>
|
|
359
|
+
</html>
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
return c.body(stream, {
|
|
363
|
+
headers: {
|
|
364
|
+
'Content-Type': 'text/html; charset=UTF-8',
|
|
365
|
+
'Transfer-Encoding': 'chunked',
|
|
366
|
+
'Content-Security-Policy':
|
|
367
|
+
"script-src 'nonce-random-nonce-value'",
|
|
368
|
+
},
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The `scriptNonce` value will be automatically added to any `<script>` tags generated by `Suspense` and `ErrorBoundary` components.
|
|
374
|
+
|
|
375
|
+
## Integration with html Middleware
|
|
376
|
+
|
|
377
|
+
Combine the JSX and HTML middlewares for powerful templating.
|
|
378
|
+
For in-depth details, consult the [HTML middleware documentation](../helpers/html.md).
|
|
379
|
+
|
|
380
|
+
```tsx
|
|
381
|
+
import { Acryl } from 'acrylblends'
|
|
382
|
+
import { html } from 'acrylblends/html'
|
|
383
|
+
|
|
384
|
+
const app = new Acryl()
|
|
385
|
+
|
|
386
|
+
interface SiteData {
|
|
387
|
+
title: string
|
|
388
|
+
children?: any
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const Layout = (props: SiteData) =>
|
|
392
|
+
html`<!doctype html>
|
|
393
|
+
<html>
|
|
394
|
+
<head>
|
|
395
|
+
<title>${props.title}</title>
|
|
396
|
+
</head>
|
|
397
|
+
<body>
|
|
398
|
+
${props.children}
|
|
399
|
+
</body>
|
|
400
|
+
</html>`
|
|
401
|
+
|
|
402
|
+
const Content = (props: { siteData: SiteData; name: string }) => (
|
|
403
|
+
<Layout {...props.siteData}>
|
|
404
|
+
<h1>Hello {props.name}</h1>
|
|
405
|
+
</Layout>
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
app.get('/:name', (c) => {
|
|
409
|
+
const { name } = c.req.param()
|
|
410
|
+
const props = {
|
|
411
|
+
name: name,
|
|
412
|
+
siteData: {
|
|
413
|
+
title: 'JSX with html sample',
|
|
414
|
+
},
|
|
415
|
+
}
|
|
416
|
+
return c.html(<Content {...props} />)
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
export default app
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## With JSX Renderer Middleware
|
|
423
|
+
|
|
424
|
+
The [JSX Renderer Middleware](../middleware/builtin/jsx-renderer.md) allows you to create HTML pages more easily with the JSX.
|
|
425
|
+
|
|
426
|
+
## Override type definitions
|
|
427
|
+
|
|
428
|
+
You can override the type definition to add your custom elements and attributes.
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
declare module 'acrylblends/jsx' {
|
|
432
|
+
namespace JSX {
|
|
433
|
+
interface IntrinsicElements {
|
|
434
|
+
'my-custom-element': HTMLAttributes & {
|
|
435
|
+
'x-event'?: 'click' | 'scroll'
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
```
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Middleware
|
|
2
|
+
|
|
3
|
+
Middleware works before/after the endpoint `Handler`. We can get the `Request` before dispatching or manipulate the `Response` after dispatching.
|
|
4
|
+
|
|
5
|
+
## Definition of Middleware
|
|
6
|
+
|
|
7
|
+
- Handler - should return `Response` object. Only one handler will be called.
|
|
8
|
+
- Middleware - should `await next()` and return nothing to call the next Middleware, **or** return a `Response` to early-exit.
|
|
9
|
+
|
|
10
|
+
The user can register middleware using `app.use` or using `app.HTTP_METHOD` as well as the handlers. For this feature, it's easy to specify the path and the method.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// match any method, all routes
|
|
14
|
+
app.use(logger())
|
|
15
|
+
|
|
16
|
+
// specify path
|
|
17
|
+
app.use('/posts/*', cors())
|
|
18
|
+
|
|
19
|
+
// specify method and path
|
|
20
|
+
app.post('/posts/*', basicAuth())
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If the handler returns `Response`, it will be used for the end-user and will stop processing.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
app.post('/posts', (c) => c.text('Created!', 201))
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
In this case, four middleware are processed before dispatching like this:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
logger() -> cors() -> basicAuth() -> *handler*
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Execution order
|
|
36
|
+
|
|
37
|
+
The order in which Middleware is executed is determined by the order in which it is registered.
|
|
38
|
+
The process before the `next` of the first registered Middleware is executed first,
|
|
39
|
+
and the process after the `next` is executed last.
|
|
40
|
+
See below.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
app.use(async (_, next) => {
|
|
44
|
+
console.log('middleware 1 start')
|
|
45
|
+
await next()
|
|
46
|
+
console.log('middleware 1 end')
|
|
47
|
+
})
|
|
48
|
+
app.use(async (_, next) => {
|
|
49
|
+
console.log('middleware 2 start')
|
|
50
|
+
await next()
|
|
51
|
+
console.log('middleware 2 end')
|
|
52
|
+
})
|
|
53
|
+
app.use(async (_, next) => {
|
|
54
|
+
console.log('middleware 3 start')
|
|
55
|
+
await next()
|
|
56
|
+
console.log('middleware 3 end')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
app.get('/', (c) => {
|
|
60
|
+
console.log('handler')
|
|
61
|
+
return c.text('Hello!')
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Result is the following.
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
middleware 1 start
|
|
69
|
+
middleware 2 start
|
|
70
|
+
middleware 3 start
|
|
71
|
+
handler
|
|
72
|
+
middleware 3 end
|
|
73
|
+
middleware 2 end
|
|
74
|
+
middleware 1 end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Note that if the handler or any middleware throws, acrylblends will catch it and either pass it to [your app.onError() callback](../api/acryl.md#error-handling) or automatically convert it to a 500 response before returning it up the chain of middleware. This means that next() will never throw, so there is no need to wrap it in a try/catch/finally.
|
|
78
|
+
|
|
79
|
+
## Built-in Middleware
|
|
80
|
+
|
|
81
|
+
Acryl has built-in middleware.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { Acryl } from 'acrylblends'
|
|
85
|
+
import { poweredBy } from 'acrylblends/powered-by'
|
|
86
|
+
import { logger } from 'acrylblends/logger'
|
|
87
|
+
import { basicAuth } from 'acrylblends/basic-auth'
|
|
88
|
+
|
|
89
|
+
const app = new Acryl()
|
|
90
|
+
|
|
91
|
+
app.use(poweredBy())
|
|
92
|
+
app.use(logger())
|
|
93
|
+
|
|
94
|
+
app.use(
|
|
95
|
+
'/auth/*',
|
|
96
|
+
basicAuth({
|
|
97
|
+
username: 'acrylblends',
|
|
98
|
+
password: 'acoolproject',
|
|
99
|
+
})
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Warning.**
|
|
104
|
+
|
|
105
|
+
For example, this code is not working because the version is different.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { Acryl } from 'npm:acrylblends'
|
|
109
|
+
import { upgradeWebSocket } from 'npm:acrylblends'
|
|
110
|
+
|
|
111
|
+
const app = new Acryl()
|
|
112
|
+
|
|
113
|
+
app.get(
|
|
114
|
+
'/ws',
|
|
115
|
+
upgradeWebSocket(() => ({
|
|
116
|
+
// ...
|
|
117
|
+
}))
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Custom Middleware
|
|
122
|
+
|
|
123
|
+
You can write your own middleware directly inside `app.use()`:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// Custom logger
|
|
127
|
+
app.use(async (c, next) => {
|
|
128
|
+
console.log(`[${c.req.method}] ${c.req.url}`)
|
|
129
|
+
await next()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// Add a custom header
|
|
133
|
+
app.use('/message/*', async (c, next) => {
|
|
134
|
+
await next()
|
|
135
|
+
c.header('x-message', 'This is middleware!')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
app.get('/message/hello', (c) => c.text('Hello Middleware!'))
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
However, embedding middleware directly within `app.use()` can limit its reusability. Therefore, we can separate our middleware into different files.
|
|
142
|
+
|
|
143
|
+
To ensure we don't lose type definitions for `context` and `next`, when separating middleware, we can use [`createMiddleware()`](../helpers/factory.md#createmiddleware) from Acryl's factory. This also allows us to type-safely [access data we've `set` in `Context`](../api/context.md#set-get) from downstream handlers.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { createMiddleware } from 'acrylblends/factory'
|
|
147
|
+
|
|
148
|
+
const logger = createMiddleware(async (c, next) => {
|
|
149
|
+
console.log(`[${c.req.method}] ${c.req.url}`)
|
|
150
|
+
await next()
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Note.**
|
|
155
|
+
|
|
156
|
+
Type generics can be used with `createMiddleware`:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
createMiddleware<{Bindings: Bindings}>(async (c, next) =>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Modify the Response After Next
|
|
163
|
+
|
|
164
|
+
Additionally, middleware can be designed to modify responses if necessary:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const stripRes = createMiddleware(async (c, next) => {
|
|
168
|
+
await next()
|
|
169
|
+
c.res = undefined
|
|
170
|
+
c.res = new Response('New Response')
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Context access inside Middleware arguments
|
|
175
|
+
|
|
176
|
+
To access the context inside middleware arguments, directly use the context parameter provided by `app.use`. See the example below for clarification.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { cors } from 'acrylblends/cors'
|
|
180
|
+
|
|
181
|
+
app.use('*', async (c, next) => {
|
|
182
|
+
const middleware = cors({
|
|
183
|
+
origin: c.env.CORS_ORIGIN,
|
|
184
|
+
})
|
|
185
|
+
return middleware(c, next)
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Extending the Context in Middleware
|
|
190
|
+
|
|
191
|
+
To extend the context inside middleware, use `c.set`. You can make this type-safe by passing a `{ Variables: { yourVariable: YourVariableType } }` generic argument to the `createMiddleware` function.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { createMiddleware } from 'acrylblends/factory'
|
|
195
|
+
|
|
196
|
+
const echoMiddleware = createMiddleware<{
|
|
197
|
+
Variables: {
|
|
198
|
+
echo: (str: string) => string
|
|
199
|
+
}
|
|
200
|
+
}>(async (c, next) => {
|
|
201
|
+
c.set('echo', (str) => str)
|
|
202
|
+
await next()
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
app.get('/echo', echoMiddleware, (c) => {
|
|
206
|
+
return c.text(c.var.echo('Hello!'))
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Type Inference Across Chained Middleware
|
|
211
|
+
|
|
212
|
+
When you chain multiple middleware using `.use()`, Acryl automatically accumulates the `Variables` types. Route handlers that follow the middleware chain can access all variables from every preceding middleware in a type-safe way:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { createMiddleware } from 'acrylblends/factory'
|
|
216
|
+
|
|
217
|
+
const authMiddleware = createMiddleware<{
|
|
218
|
+
Variables: { user: { id: string; name: string } }
|
|
219
|
+
}>(async (c, next) => {
|
|
220
|
+
c.set('user', { id: '123', name: 'Alice' })
|
|
221
|
+
await next()
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
const dbMiddleware = createMiddleware<{
|
|
225
|
+
Variables: { db: { query: (sql: string) => Promise<unknown> } }
|
|
226
|
+
}>(async (c, next) => {
|
|
227
|
+
c.set('db', {
|
|
228
|
+
query: async (sql) => {
|
|
229
|
+
/* ... */
|
|
230
|
+
},
|
|
231
|
+
})
|
|
232
|
+
await next()
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
const app = new Acryl()
|
|
236
|
+
.use(authMiddleware)
|
|
237
|
+
.use(dbMiddleware)
|
|
238
|
+
.get('/', (c) => {
|
|
239
|
+
// Both `user` and `db` are available and type-safe
|
|
240
|
+
const user = c.var.user // { id: string; name: string }
|
|
241
|
+
const db = c.var.db // { query: (sql: string) => Promise<unknown> }
|
|
242
|
+
return c.json({ user })
|
|
243
|
+
})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
This works because each `.use()` call returns a new Acryl instance with the merged type, so the type grows as middleware is chained. This eliminates the need to manually declare a combined `Env` type upfront for most use cases.
|
|
247
|
+
|
|
248
|
+
## Third-party Middleware
|
|
249
|
+
|
|
250
|
+
Built-in middleware does not depend on external modules, but third-party middleware can depend on third-party libraries. So with them, we may make a more complex application.
|
|
251
|
+
|
|
252
|
+
We can explore a variety of third-party middleware.
|
|
253
|
+
For example, we have GraphQL Server Middleware, Sentry Middleware, Firebase Auth Middleware, and others.
|