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.
Files changed (106) hide show
  1. package/README.md +61 -2
  2. package/bin/create-acrylblends.js +4 -0
  3. package/dist/index.d.ts +56 -0
  4. package/dist/index.js +274 -0
  5. package/dist/index.js.map +1 -0
  6. package/docs/framework/LICENSE +23 -0
  7. package/docs/framework/README.md +63 -0
  8. package/docs/framework/UPSTREAM.md +3 -0
  9. package/docs/framework/agents/index.md +24 -0
  10. package/docs/framework/api/acryl.md +259 -0
  11. package/docs/framework/api/context.md +527 -0
  12. package/docs/framework/api/exception.md +80 -0
  13. package/docs/framework/api/index.md +12 -0
  14. package/docs/framework/api/presets.md +65 -0
  15. package/docs/framework/api/request.md +396 -0
  16. package/docs/framework/api/routing.md +303 -0
  17. package/docs/framework/api/summary.md +140 -0
  18. package/docs/framework/concepts/benchmarks.md +26 -0
  19. package/docs/framework/concepts/developer-experience.md +5 -0
  20. package/docs/framework/concepts/middleware.md +21 -0
  21. package/docs/framework/concepts/motivation.md +14 -0
  22. package/docs/framework/concepts/routers.md +63 -0
  23. package/docs/framework/concepts/stacks.md +235 -0
  24. package/docs/framework/concepts/web-standard.md +37 -0
  25. package/docs/framework/cordis/acryl-in-a-cordis-app.md +146 -0
  26. package/docs/framework/cordis/concepts.md +105 -0
  27. package/docs/framework/cordis/middleware.md +8 -0
  28. package/docs/framework/cordis/migration-from-0-3.md +111 -0
  29. package/docs/framework/cordis/migration-from-0-4.md +14 -0
  30. package/docs/framework/cordis/migration-from-dsh-webserver.md +109 -0
  31. package/docs/framework/cordis/mounting.md +85 -0
  32. package/docs/framework/cordis/node-server.md +75 -0
  33. package/docs/framework/cordis/plugin-lifecycle.md +111 -0
  34. package/docs/framework/cordis/surfaces.md +71 -0
  35. package/docs/framework/cordis/transport-and-cancellation.md +57 -0
  36. package/docs/framework/cordis/validation.md +135 -0
  37. package/docs/framework/getting-started/basic.md +196 -0
  38. package/docs/framework/getting-started/bun.md +221 -0
  39. package/docs/framework/getting-started/nodejs.md +344 -0
  40. package/docs/framework/getting-started/other-runtimes.md +23 -0
  41. package/docs/framework/guides/best-practices.md +231 -0
  42. package/docs/framework/guides/create-acrylblends.md +79 -0
  43. package/docs/framework/guides/examples.md +3 -0
  44. package/docs/framework/guides/faq.md +19 -0
  45. package/docs/framework/guides/helpers.md +33 -0
  46. package/docs/framework/guides/jsx-dom.md +299 -0
  47. package/docs/framework/guides/jsx.md +440 -0
  48. package/docs/framework/guides/middleware.md +253 -0
  49. package/docs/framework/guides/rpc.md +886 -0
  50. package/docs/framework/guides/testing.md +133 -0
  51. package/docs/framework/guides/validation.md +383 -0
  52. package/docs/framework/helpers/accepts.md +60 -0
  53. package/docs/framework/helpers/adapter.md +87 -0
  54. package/docs/framework/helpers/conninfo.md +112 -0
  55. package/docs/framework/helpers/cookie.md +232 -0
  56. package/docs/framework/helpers/css.md +285 -0
  57. package/docs/framework/helpers/dev.md +67 -0
  58. package/docs/framework/helpers/factory.md +180 -0
  59. package/docs/framework/helpers/html.md +123 -0
  60. package/docs/framework/helpers/jwt.md +184 -0
  61. package/docs/framework/helpers/proxy.md +118 -0
  62. package/docs/framework/helpers/route.md +143 -0
  63. package/docs/framework/helpers/ssg.md +393 -0
  64. package/docs/framework/helpers/streaming.md +119 -0
  65. package/docs/framework/helpers/testing.md +101 -0
  66. package/docs/framework/helpers/websocket.md +215 -0
  67. package/docs/framework/middleware/builtin/basic-auth.md +165 -0
  68. package/docs/framework/middleware/builtin/bearer-auth.md +138 -0
  69. package/docs/framework/middleware/builtin/body-limit.md +71 -0
  70. package/docs/framework/middleware/builtin/cache.md +122 -0
  71. package/docs/framework/middleware/builtin/combine.md +94 -0
  72. package/docs/framework/middleware/builtin/compress.md +59 -0
  73. package/docs/framework/middleware/builtin/context-storage.md +78 -0
  74. package/docs/framework/middleware/builtin/cors.md +135 -0
  75. package/docs/framework/middleware/builtin/csrf.md +105 -0
  76. package/docs/framework/middleware/builtin/etag.md +54 -0
  77. package/docs/framework/middleware/builtin/ip-restriction.md +69 -0
  78. package/docs/framework/middleware/builtin/jsx-renderer.md +286 -0
  79. package/docs/framework/middleware/builtin/jwk.md +192 -0
  80. package/docs/framework/middleware/builtin/jwt.md +145 -0
  81. package/docs/framework/middleware/builtin/language.md +232 -0
  82. package/docs/framework/middleware/builtin/logger.md +74 -0
  83. package/docs/framework/middleware/builtin/loopback-only.md +31 -0
  84. package/docs/framework/middleware/builtin/method-not-allowed.md +48 -0
  85. package/docs/framework/middleware/builtin/method-override.md +80 -0
  86. package/docs/framework/middleware/builtin/mount.md +106 -0
  87. package/docs/framework/middleware/builtin/pretty-json.md +53 -0
  88. package/docs/framework/middleware/builtin/request-id.md +94 -0
  89. package/docs/framework/middleware/builtin/secure-headers.md +247 -0
  90. package/docs/framework/middleware/builtin/timeout.md +98 -0
  91. package/docs/framework/middleware/builtin/timing.md +101 -0
  92. package/docs/framework/middleware/builtin/trailing-slash.md +74 -0
  93. package/docs/framework/middleware/third-party.md +4 -0
  94. package/docs/framework/upstream.json +9 -0
  95. package/package.json +47 -3
  96. package/templates/bun/gitignore +1 -0
  97. package/templates/bun/package.json +19 -0
  98. package/templates/bun/src/index.ts +7 -0
  99. package/templates/bun/test/app.test.ts +8 -0
  100. package/templates/bun/tsconfig.json +15 -0
  101. package/templates/nodejs/gitignore +1 -0
  102. package/templates/nodejs/package.json +22 -0
  103. package/templates/nodejs/src/app.ts +7 -0
  104. package/templates/nodejs/src/index.ts +9 -0
  105. package/templates/nodejs/test/app.test.ts +9 -0
  106. package/templates/nodejs/tsconfig.json +16 -0
@@ -0,0 +1,231 @@
1
+ # Best Practices
2
+
3
+ Acryl is very flexible. You can write your app as you like.
4
+ However, there are best practices that are better to follow.
5
+
6
+ ## Don't make "Controllers" when possible
7
+
8
+ When possible, you should not create "Ruby on Rails-like Controllers".
9
+
10
+ ```ts
11
+ // 🙁
12
+ // A RoR-like Controller
13
+ const booksList = (c: Context) => {
14
+ return c.json('list books')
15
+ }
16
+
17
+ app.get('/books', booksList)
18
+ ```
19
+
20
+ The issue is related to types. For example, the path parameter cannot be inferred in the Controller without writing complex generics.
21
+
22
+ ```ts
23
+ // 🙁
24
+ // A RoR-like Controller
25
+ const bookPermalink = (c: Context) => {
26
+ const id = c.req.param('id') // Can't infer the path param
27
+ return c.json(`get ${id}`)
28
+ }
29
+ ```
30
+
31
+ Therefore, you don't need to create RoR-like controllers and should write handlers directly after path definitions.
32
+
33
+ ```ts
34
+ // 😃
35
+ app.get('/books/:id', (c) => {
36
+ const id = c.req.param('id') // Can infer the path param
37
+ return c.json(`get ${id}`)
38
+ })
39
+ ```
40
+
41
+ ## `factory.createHandlers()` in `acrylblends/factory`
42
+
43
+ If you still want to create a RoR-like Controller, use `factory.createHandlers()` in [`acrylblends/factory`](../helpers/factory.md). If you use this, type inference will work correctly.
44
+
45
+ ```ts
46
+ import { createFactory } from 'acrylblends/factory'
47
+ import { logger } from 'acrylblends/logger'
48
+
49
+ declare module 'acrylblends' {
50
+ interface ContextVariableMap {
51
+ foo: string
52
+ }
53
+ }
54
+
55
+ // ...
56
+
57
+ // 😃
58
+ const factory = createFactory()
59
+
60
+ const middleware = factory.createMiddleware(async (c, next) => {
61
+ c.set('foo', 'bar')
62
+ await next()
63
+ })
64
+
65
+ const handlers = factory.createHandlers(logger(), middleware, (c) => {
66
+ return c.json(c.var.foo)
67
+ })
68
+
69
+ app.get('/api', ...handlers)
70
+ ```
71
+
72
+ ## Building a larger application
73
+
74
+ Use `app.route()` to build a larger application without creating "Ruby on Rails-like Controllers".
75
+
76
+ If your application has `/authors` and `/books` endpoints and you wish to separate files from `index.ts`, create `authors.ts` and `books.ts`.
77
+
78
+ ```ts
79
+ // authors.ts
80
+ import { Acryl } from 'acrylblends'
81
+
82
+ const app = new Acryl()
83
+
84
+ app.get('/', (c) => c.json('list authors'))
85
+ app.post('/', (c) => c.json('create an author', 201))
86
+ app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
87
+
88
+ export default app
89
+ ```
90
+
91
+ ```ts
92
+ // books.ts
93
+ import { Acryl } from 'acrylblends'
94
+
95
+ const app = new Acryl()
96
+
97
+ app.get('/', (c) => c.json('list books'))
98
+ app.post('/', (c) => c.json('create a book', 201))
99
+ app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
100
+
101
+ export default app
102
+ ```
103
+
104
+ Then, import them and mount on the paths `/authors` and `/books` with `app.route()`.
105
+
106
+ ```ts
107
+ // index.ts
108
+ import { Acryl } from 'acrylblends'
109
+ import authors from './authors'
110
+ import books from './books'
111
+
112
+ const app = new Acryl()
113
+
114
+ // 😃
115
+ app.route('/authors', authors)
116
+ app.route('/books', books)
117
+
118
+ export default app
119
+ ```
120
+
121
+ ### If you want to use RPC features
122
+
123
+ The code above works well for normal use cases.
124
+ However, if you want to use the `RPC` feature, you can get the correct type by chaining as follows.
125
+
126
+ ```ts
127
+ // authors.ts
128
+ import { Acryl } from 'acrylblends'
129
+
130
+ const app = new Acryl()
131
+ .get('/', (c) => c.json('list authors'))
132
+ .post('/', (c) => c.json('create an author', 201))
133
+ .get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
134
+
135
+ export default app
136
+ export type AppType = typeof app
137
+ ```
138
+
139
+ If you pass the type of the `app` to `hc`, it will get the correct type.
140
+
141
+ ```ts
142
+ import type { AppType } from './authors'
143
+ import { hc } from 'acrylblends/client'
144
+
145
+ // 😃
146
+ const client = hc<AppType>('http://localhost') // Typed correctly
147
+ ```
148
+
149
+ For more detailed information, please see [the RPC page](rpc.md#using-rpc-with-larger-applications).
150
+
151
+ ## HEAD Request Best Practices
152
+
153
+ ### Understanding Acryl's HEAD Handling
154
+
155
+ Acryl automatically handles HEAD requests by converting them to GET requests and stripping the response body. This behavior is built into the framework's dispatch layer and happens before route matching occurs.
156
+
157
+ ### ✅ Do: Use GET Routes for HEAD Requests
158
+
159
+ ```ts
160
+ // GOOD: This GET route automatically handles HEAD requests
161
+ app.get('/api/users', async (c) => {
162
+ const users = await getUsers()
163
+ c.header('X-Total-Count', users.length.toString())
164
+ return c.json(users)
165
+ })
166
+
167
+ // HEAD /api/users will return:
168
+ // - Same headers as GET (including X-Total-Count)
169
+ // - Status 200
170
+ // - No body (null)
171
+ ```
172
+
173
+ ### ✅ Do: Use Middleware for HEAD-Specific Logic
174
+
175
+ ```ts
176
+ // GOOD: Use middleware when HEAD needs different behavior
177
+ app.use('/api/resource', async (c, next) => {
178
+ await next()
179
+
180
+ // Add HEAD-specific headers after the handler
181
+ if (c.req.method === 'HEAD') {
182
+ c.header('X-HEAD-Processed', 'true')
183
+ // Don't compute expensive body content for HEAD
184
+ c.res = new Response(null, c.res)
185
+ }
186
+ })
187
+ ```
188
+
189
+ ### ❌ Don't: Try to Create Dedicated HEAD Handlers
190
+
191
+ ```ts
192
+ // BAD: This won't work as expected
193
+ app.head('/api/users', (c) => {
194
+ // This handler will NEVER be called
195
+ c.header('X-Custom', 'value')
196
+ return c.text('ignored')
197
+ })
198
+
199
+ // BAD: Using on() also won't work
200
+ app.on('HEAD', '/api/users', (c) => {
201
+ // Still converted to GET before route matching
202
+ })
203
+ ```
204
+
205
+ ### Performance Considerations
206
+
207
+ - **Avoid expensive operations in GET handlers if you expect many HEAD requests**: Use middleware to detect HEAD and skip body generation
208
+ - **Cache headers work identically**: HEAD responses respect the same caching rules as GET
209
+ - **Middleware compatibility**: Most middleware works with HEAD, but body-processing middleware (like compression) automatically skips HEAD requests
210
+
211
+ ### Testing HEAD Requests
212
+
213
+ ```ts
214
+ // Always test both GET and HEAD responses
215
+ it('handles HEAD requests correctly', async () => {
216
+ const getRes = await app.request('/api/users')
217
+ const headRes = await app.request('/api/users', { method: 'HEAD' })
218
+
219
+ expect(headRes.status).toBe(getRes.status)
220
+ expect(headRes.headers.get('X-Total-Count')).toBe(
221
+ getRes.headers.get('X-Total-Count')
222
+ )
223
+ expect(headRes.body).toBe(null)
224
+ })
225
+ ```
226
+
227
+ ### Notes
228
+
229
+ - The automatic HEAD conversion ensures consistent headers between GET and HEAD responses
230
+ - This behavior is consistent across all Acryl runtimes (Node.js, Bun)
231
+ - If you need completely different logic for HEAD vs GET, consider using different endpoints rather than trying to override the framework's HEAD handling
@@ -0,0 +1,79 @@
1
+ # create-acrylblends
2
+
3
+ `create-acrylblends` is the project initializer that runs when you run `npm create acrylblends@latest`, `npx create-acrylblends@latest`, `pnpm create acrylblends@latest`,
4
+ `yarn create acrylblends` or `bun create acrylblends@latest`. It creates a folder with a working Acryl application for the runtime you choose.
5
+
6
+ ## Templates
7
+
8
+ | Template | What you get |
9
+ | --- | --- |
10
+ | `nodejs` | an Acryl application served with `serve()` from `acrylblends/node-server`; run with Node.js 22.19 or newer |
11
+ | `bun` (experimental) | an Acryl application with `export default app`, run with `bun` |
12
+ | `acryl-app` | a Cordis app built from plugins, with an agent that can extend it while it runs (the same app `npx acrylblends init --template agent` creates) |
13
+
14
+ Every template includes `AGENTS.md` and `CLAUDE.md` (which imports `AGENTS.md`) and a copy of these docs in `docs/framework/`, so a coding agent working in the folder reads the
15
+ API from the folder.
16
+
17
+ ## Passing arguments
18
+
19
+ When you use `npm create` (or `npx`), arguments meant for the initializer must be placed **after** `--`. Anything after `--` is forwarded to the initializer.
20
+
21
+ **npm**
22
+
23
+ ```sh
24
+ npm create acrylblends@latest my-app -- --template nodejs
25
+ ```
26
+
27
+ **yarn**
28
+
29
+ ```sh
30
+ yarn create acrylblends my-app --template nodejs
31
+ ```
32
+
33
+ **pnpm**
34
+
35
+ ```sh
36
+ pnpm create acrylblends@latest my-app --template nodejs
37
+ ```
38
+
39
+ **bun**
40
+
41
+ ```sh
42
+ bun create acrylblends@latest my-app --template nodejs
43
+ ```
44
+
45
+ ## Arguments
46
+
47
+ | Argument | Description | Example |
48
+ | :-- | :-- | :-- |
49
+ | `[directory]` | The folder to create. It must not exist or must be empty. Asked for when it is missing. | `my-app` |
50
+ | `--template <template>` | One of `nodejs`, `bun` (experimental), `acryl-app`. Skips the template question. | `--template bun` |
51
+ | `--install` | Install the dependencies with the package manager after creating the project. | `--install` |
52
+ | `--pm <packageManager>` | The package manager for `--install`: `npm`, `pnpm`, `yarn` or `bun`. Defaults to the one that started the initializer. | `--pm pnpm` |
53
+ | `--yes`, `-y` | Ask nothing: use `nodejs` unless `--template` is given, and the directory `acryl-app`. | `-y` |
54
+ | `--help` | Print the arguments. | `--help` |
55
+
56
+ The templates are inside the package, so creating a project needs no network except for `--install`.
57
+
58
+ ## Example flows
59
+
60
+ ### Interactive
61
+
62
+ ```sh
63
+ npm create acrylblends@latest my-app
64
+ ```
65
+
66
+ This asks which template you want.
67
+
68
+ ### Non-interactive
69
+
70
+ ```sh
71
+ npm create acrylblends@latest my-app -- --template nodejs --pm npm --install
72
+ ```
73
+
74
+ This creates `my-app` from the `nodejs` template and installs its dependencies with npm.
75
+
76
+ ## Troubleshooting
77
+
78
+ - If an argument is not recognized, make sure you forward it with `--` when you use `npm create` or `npx`.
79
+ - Run `npm create acrylblends@latest -- --help` to see the arguments of the version you have.
@@ -0,0 +1,3 @@
1
+ # Examples
2
+
3
+ See the Examples section.
@@ -0,0 +1,19 @@
1
+ # Frequently Asked Questions
2
+
3
+ ## Why does `/orders/` return 404 when `/orders` works?
4
+
5
+ Trailing slashes are strict by default: `/orders` and `/orders/` are different paths. Add `trimTrailingSlash()` to redirect, or set the router to ignore the slash. See
6
+ [Mounting and trailing slashes](../cordis/mounting.md#trailing-slashes).
7
+
8
+ ## Why does my validator see `{}` for a JSON body?
9
+
10
+ A `json` validation target reads the body only when the request has a JSON `Content-Type`; without it the validator sees `{}`. See
11
+ [Validation with Standard Schema](../cordis/validation.md).
12
+
13
+ ## Which runtimes are verified?
14
+
15
+ Node.js. Bun is experimental. The others are listed, as not yet verified, on [Other runtimes](../getting-started/other-runtimes.md).
16
+
17
+ ## Where are the parts marked "not yet available"?
18
+
19
+ Their pages are part of the documentation so that the structure is complete; each starts with a note saying so. They are enabled as they are ported.
@@ -0,0 +1,33 @@
1
+ # Helpers
2
+
3
+ Helpers are available to assist in developing your application. Unlike middleware, they don't act as handlers, but rather provide useful functions.
4
+
5
+ For instance, here's how to use the [Cookie helper](../helpers/cookie.md):
6
+
7
+ ```ts
8
+ import { getCookie, setCookie } from 'acrylblends/cookie'
9
+
10
+ const app = new Acryl()
11
+
12
+ app.get('/cookie', (c) => {
13
+ const yummyCookie = getCookie(c, 'yummy_cookie')
14
+ // ...
15
+ setCookie(c, 'delicious_cookie', 'macha')
16
+ //
17
+ })
18
+ ```
19
+
20
+ ## Available Helpers
21
+
22
+ - [Accepts](../helpers/accepts.md)
23
+ - [Adapter](../helpers/adapter.md)
24
+ - [Cookie](../helpers/cookie.md)
25
+ - [css](../helpers/css.md)
26
+ - [Dev](../helpers/dev.md)
27
+ - [Factory](../helpers/factory.md)
28
+ - [html](../helpers/html.md)
29
+ - [JWT](../helpers/jwt.md)
30
+ - [SSG](../helpers/ssg.md)
31
+ - [Streaming](../helpers/streaming.md)
32
+ - [Testing](../helpers/testing.md)
33
+ - [WebSocket](../helpers/websocket.md)
@@ -0,0 +1,299 @@
1
+ # Client Components
2
+
3
+ > **Not yet available.** The client-side JSX package (`acrylblends/jsx/dom`) 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
+ `acrylblends/jsx` supports not only server side but also client side. This means that it is possible to create an interactive UI that runs in the browser. We call it Client Components or `acrylblends/jsx/dom`.
7
+
8
+ It is fast and very small. The counter program in `acrylblends/jsx/dom` is only 2.8KB with Brotli compression, but 47.8KB for React.
9
+
10
+ This section introduces Client Components-specific features.
11
+
12
+ ## Counter example
13
+
14
+ Here is an example of a simple counter, the same code works as in React.
15
+
16
+ ```tsx
17
+ import { useState } from 'acrylblends/jsx'
18
+ import { render } from 'acrylblends/jsx/dom'
19
+
20
+ function Counter() {
21
+ const [count, setCount] = useState(0)
22
+ return (
23
+ <div>
24
+ <p>Count: {count}</p>
25
+ <button onClick={() => setCount(count + 1)}>Increment</button>
26
+ </div>
27
+ )
28
+ }
29
+
30
+ function App() {
31
+ return (
32
+ <html>
33
+ <body>
34
+ <Counter />
35
+ </body>
36
+ </html>
37
+ )
38
+ }
39
+
40
+ const root = document.getElementById('root')
41
+ render(<App />, root)
42
+ ```
43
+
44
+ ## `render()`
45
+
46
+ You can use `render()` to insert JSX components within a specified HTML element.
47
+
48
+ ```tsx
49
+ render(<Component />, container)
50
+ ```
51
+
52
+ You can see full example code here: Counter example.
53
+
54
+ ## Hooks compatible with React
55
+
56
+ acrylblends/jsx/dom has Hooks that are compatible or partially compatible with React. You can learn about these APIs by looking at [the React documentation](https://react.dev/reference/react/hooks).
57
+
58
+ - `useState()`
59
+ - `useEffect()`
60
+ - `useRef()`
61
+ - `useCallback()`
62
+ - `use()`
63
+ - `startTransition()`
64
+ - `useTransition()`
65
+ - `useDeferredValue()`
66
+ - `useMemo()`
67
+ - `useLayoutEffect()`
68
+ - `useReducer()`
69
+ - `useDebugValue()`
70
+ - `createElement()`
71
+ - `memo()`
72
+ - `isValidElement()`
73
+ - `useId()`
74
+ - `createRef()`
75
+ - `forwardRef()`
76
+ - `useImperativeHandle()`
77
+ - `useSyncExternalStore()`
78
+ - `useInsertionEffect()`
79
+ - `useFormStatus()`
80
+ - `useActionState()`
81
+ - `useOptimistic()`
82
+
83
+ ## `startViewTransition()` family
84
+
85
+ The `startViewTransition()` family contains original hooks and functions to handle [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) easily. The examples below demonstrate how to use them.
86
+
87
+ ### 1. A very simple example
88
+
89
+ You can write a transition using the `document.startViewTransition` shortly with the `startViewTransition()`.
90
+
91
+ ```tsx
92
+ import { useState, startViewTransition } from 'acrylblends/jsx'
93
+ import { css, Style } from 'acrylblends/css'
94
+
95
+ export default function App() {
96
+ const [showLargeImage, setShowLargeImage] = useState(false)
97
+ return (
98
+ <>
99
+ <Style />
100
+ <button
101
+ onClick={() =>
102
+ startViewTransition(() =>
103
+ setShowLargeImage((state) => !state)
104
+ )
105
+ }
106
+ >
107
+ Click!
108
+ </button>
109
+ <div>
110
+ {!showLargeImage ? (
111
+ <img src=' />
112
+ ) : (
113
+ <div
114
+ class={css`
115
+ background: url(');
116
+ background-size: contain;
117
+ background-repeat: no-repeat;
118
+ background-position: center;
119
+ width: 600px;
120
+ height: 600px;
121
+ `}
122
+ ></div>
123
+ )}
124
+ </div>
125
+ </>
126
+ )
127
+ }
128
+ ```
129
+
130
+ ### 2. Using `viewTransition()` with `keyframes()`
131
+
132
+ The `viewTransition()` function allows you to get the unique `view-transition-name`.
133
+
134
+ You can use it with the `keyframes()`, The `::view-transition-old()` is converted to `::view-transition-old(${uniqueName))`.
135
+
136
+ ```tsx
137
+ import { useState, startViewTransition } from 'acrylblends/jsx'
138
+ import { viewTransition } from 'acrylblends/jsx/dom/css'
139
+ import { css, keyframes, Style } from 'acrylblends/css'
140
+
141
+ const rotate = keyframes`
142
+ from {
143
+ rotate: 0deg;
144
+ }
145
+ to {
146
+ rotate: 360deg;
147
+ }
148
+ `
149
+
150
+ export default function App() {
151
+ const [showLargeImage, setShowLargeImage] = useState(false)
152
+ const [transitionNameClass] = useState(() =>
153
+ viewTransition(css`
154
+ ::view-transition-old() {
155
+ animation-name: ${rotate};
156
+ }
157
+ ::view-transition-new() {
158
+ animation-name: ${rotate};
159
+ }
160
+ `)
161
+ )
162
+ return (
163
+ <>
164
+ <Style />
165
+ <button
166
+ onClick={() =>
167
+ startViewTransition(() =>
168
+ setShowLargeImage((state) => !state)
169
+ )
170
+ }
171
+ >
172
+ Click!
173
+ </button>
174
+ <div>
175
+ {!showLargeImage ? (
176
+ <img src=' />
177
+ ) : (
178
+ <div
179
+ class={css`
180
+ ${transitionNameClass}
181
+ background: url(');
182
+ background-size: contain;
183
+ background-repeat: no-repeat;
184
+ background-position: center;
185
+ width: 600px;
186
+ height: 600px;
187
+ `}
188
+ ></div>
189
+ )}
190
+ </div>
191
+ </>
192
+ )
193
+ }
194
+ ```
195
+
196
+ ### 3. Using `useViewTransition`
197
+
198
+ If you want to change the style only during the animation. You can use `useViewTransition()`. This hook returns the `[boolean, (callback: () => void) => void]`, and they are the `isUpdating` flag and the `startViewTransition()` function.
199
+
200
+ When this hook is used, the Component is evaluated at the following two times.
201
+
202
+ - Inside the callback of a call to `startViewTransition()`.
203
+ - When [the `finish` promise becomes fulfilled](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition/finished)
204
+
205
+ ```tsx
206
+ import { useState, useViewTransition } from 'acrylblends/jsx'
207
+ import { viewTransition } from 'acrylblends/jsx/dom/css'
208
+ import { css, keyframes, Style } from 'acrylblends/css'
209
+
210
+ const rotate = keyframes`
211
+ from {
212
+ rotate: 0deg;
213
+ }
214
+ to {
215
+ rotate: 360deg;
216
+ }
217
+ `
218
+
219
+ export default function App() {
220
+ const [isUpdating, startViewTransition] = useViewTransition()
221
+ const [showLargeImage, setShowLargeImage] = useState(false)
222
+ const [transitionNameClass] = useState(() =>
223
+ viewTransition(css`
224
+ ::view-transition-old() {
225
+ animation-name: ${rotate};
226
+ }
227
+ ::view-transition-new() {
228
+ animation-name: ${rotate};
229
+ }
230
+ `)
231
+ )
232
+ return (
233
+ <>
234
+ <Style />
235
+ <button
236
+ onClick={() =>
237
+ startViewTransition(() =>
238
+ setShowLargeImage((state) => !state)
239
+ )
240
+ }
241
+ >
242
+ Click!
243
+ </button>
244
+ <div>
245
+ {!showLargeImage ? (
246
+ <img src=' />
247
+ ) : (
248
+ <div
249
+ class={css`
250
+ ${transitionNameClass}
251
+ background: url(');
252
+ background-size: contain;
253
+ background-repeat: no-repeat;
254
+ background-position: center;
255
+ width: 600px;
256
+ height: 600px;
257
+ position: relative;
258
+ ${isUpdating &&
259
+ css`
260
+ &:before {
261
+ content: 'Loading...';
262
+ position: absolute;
263
+ top: 50%;
264
+ left: 50%;
265
+ }
266
+ `}
267
+ `}
268
+ ></div>
269
+ )}
270
+ </div>
271
+ </>
272
+ )
273
+ }
274
+ ```
275
+
276
+ ## The `acrylblends/jsx/dom` runtime
277
+
278
+ There is a small JSX Runtime for Client Components. Using this will result in smaller bundled results than using `acrylblends/jsx`. Specify `acrylblends/jsx/dom` in `tsconfig.json`. For Deno, modify the deno.json.
279
+
280
+ ```json
281
+ {
282
+ "compilerOptions": {
283
+ "jsx": "react-jsx",
284
+ "jsxImportSource": "acrylblends/jsx/dom"
285
+ }
286
+ }
287
+ ```
288
+
289
+ Alternatively, you can specify `acrylblends/jsx/dom` in the esbuild transform options in `vite.config.ts`.
290
+
291
+ ```ts
292
+ import { defineConfig } from 'vite'
293
+
294
+ export default defineConfig({
295
+ esbuild: {
296
+ jsxImportSource: 'acrylblends/jsx/dom',
297
+ },
298
+ })
299
+ ```