create-acrylblends 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +103 -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
- package/README.md +0 -3
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# ConnInfo Helper
|
|
2
|
+
|
|
3
|
+
The ConnInfo Helper helps you to get the connection information. For example, you can get the client's remote address easily.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
Only the Node.js adapter, `acrylblends/node-server`, is available today. The adapters for the other platforms (Cloudflare Workers, Bun, Vercel, AWS Lambda, Netlify and Lambda@Edge) are **not yet available**; their imports are shown for completeness and are not checked by the docs tests.
|
|
8
|
+
|
|
9
|
+
**Cloudflare Workers**
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Acryl } from 'acrylblends'
|
|
13
|
+
import { getConnInfo } from 'acrylblends/cloudflare-workers'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Bun**
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { Acryl } from 'acrylblends'
|
|
20
|
+
import { getConnInfo } from 'acrylblends/bun'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Vercel**
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Acryl } from 'acrylblends'
|
|
27
|
+
import { getConnInfo } from 'acrylblends/vercel'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**AWS Lambda**
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { Acryl } from 'acrylblends'
|
|
34
|
+
import { getConnInfo } from 'acrylblends/aws-lambda'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Netlify**
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { Acryl } from 'acrylblends'
|
|
41
|
+
import { getConnInfo } from 'acrylblends/netlify'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Lambda@Edge**
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { Acryl } from 'acrylblends'
|
|
48
|
+
import { getConnInfo } from 'acrylblends/lambda-edge'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Node.js**
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { Acryl } from 'acrylblends'
|
|
55
|
+
import { getConnInfo } from 'acrylblends/node-server'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const app = new Acryl()
|
|
62
|
+
|
|
63
|
+
app.get('/', (c) => {
|
|
64
|
+
const info = getConnInfo(c) // info is `ConnInfo`
|
|
65
|
+
return c.text(`Your remote address is ${info.remote.address}`)
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Type Definitions
|
|
70
|
+
|
|
71
|
+
The type definitions of the values that you can get from `getConnInfo()` are the following:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
type AddressType = 'IPv6' | 'IPv4' | undefined
|
|
75
|
+
|
|
76
|
+
type NetAddrInfo = {
|
|
77
|
+
/**
|
|
78
|
+
* Transport protocol type
|
|
79
|
+
*/
|
|
80
|
+
transport?: 'tcp' | 'udp'
|
|
81
|
+
/**
|
|
82
|
+
* Transport port number
|
|
83
|
+
*/
|
|
84
|
+
port?: number
|
|
85
|
+
|
|
86
|
+
address?: string
|
|
87
|
+
addressType?: AddressType
|
|
88
|
+
} & (
|
|
89
|
+
| {
|
|
90
|
+
/**
|
|
91
|
+
* Host name such as IP Addr
|
|
92
|
+
*/
|
|
93
|
+
address: string
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Host name type
|
|
97
|
+
*/
|
|
98
|
+
addressType: AddressType
|
|
99
|
+
}
|
|
100
|
+
| {}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* HTTP Connection information
|
|
105
|
+
*/
|
|
106
|
+
interface ConnInfo {
|
|
107
|
+
/**
|
|
108
|
+
* Remote information
|
|
109
|
+
*/
|
|
110
|
+
remote: NetAddrInfo
|
|
111
|
+
}
|
|
112
|
+
```
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Cookie Helper
|
|
2
|
+
|
|
3
|
+
The Cookie Helper provides an easy interface to manage cookies, enabling developers to set, parse, and delete cookies seamlessly.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Acryl } from 'acrylblends'
|
|
9
|
+
import {
|
|
10
|
+
deleteCookie,
|
|
11
|
+
getCookie,
|
|
12
|
+
getSignedCookie,
|
|
13
|
+
setCookie,
|
|
14
|
+
setSignedCookie,
|
|
15
|
+
generateCookie,
|
|
16
|
+
generateSignedCookie,
|
|
17
|
+
} from 'acrylblends/cookie'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Regular cookies
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
app.get('/cookie', (c) => {
|
|
26
|
+
setCookie(c, 'cookie_name', 'cookie_value')
|
|
27
|
+
const yummyCookie = getCookie(c, 'cookie_name')
|
|
28
|
+
deleteCookie(c, 'cookie_name')
|
|
29
|
+
const allCookies = getCookie(c)
|
|
30
|
+
// ...
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Signed cookies
|
|
35
|
+
|
|
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
|
+
|
|
38
|
+
```ts
|
|
39
|
+
app.get('/signed-cookie', (c) => {
|
|
40
|
+
const secret = 'secret' // make sure it's a large enough string to be secure
|
|
41
|
+
|
|
42
|
+
await setSignedCookie(c, 'cookie_name0', 'cookie_value', secret)
|
|
43
|
+
const fortuneCookie = await getSignedCookie(
|
|
44
|
+
c,
|
|
45
|
+
secret,
|
|
46
|
+
'cookie_name0'
|
|
47
|
+
)
|
|
48
|
+
deleteCookie(c, 'cookie_name0')
|
|
49
|
+
// `getSignedCookie` will return `false` for a specified cookie if its signature fails verification
|
|
50
|
+
const allSignedCookies = await getSignedCookie(c, secret)
|
|
51
|
+
// ...
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> [!NOTE]
|
|
56
|
+
> `getSignedCookie` distinguishes two cases. A cookie that has a signature but fails verification returns `false`. A cookie that does not have a valid signature format is treated as not being a signed cookie at all and returns `undefined` - the same as when the cookie is not present. This rule applies both when retrieving a single cookie by name and when retrieving all signed cookies. Since both `false` and `undefined` are falsy, `if (!value)` handles both cases.
|
|
57
|
+
|
|
58
|
+
### Cookie Generation
|
|
59
|
+
|
|
60
|
+
`generateCookie` and `generateSignedCookie` functions allow you to create cookie strings directly without setting them in the response headers.
|
|
61
|
+
|
|
62
|
+
#### `generateCookie`
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// Basic cookie generation
|
|
66
|
+
const cookie = generateCookie('delicious_cookie', 'macha')
|
|
67
|
+
// Returns: 'delicious_cookie=macha; Path=/'
|
|
68
|
+
|
|
69
|
+
// Cookie with options
|
|
70
|
+
const cookie = generateCookie('delicious_cookie', 'macha', {
|
|
71
|
+
path: '/',
|
|
72
|
+
secure: true,
|
|
73
|
+
httpOnly: true,
|
|
74
|
+
domain: 'example.com',
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### `generateSignedCookie`
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// Basic signed cookie generation
|
|
82
|
+
const signedCookie = await generateSignedCookie(
|
|
83
|
+
'delicious_cookie',
|
|
84
|
+
'macha',
|
|
85
|
+
'secret chocolate chips'
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
// Signed cookie with options
|
|
89
|
+
const signedCookie = await generateSignedCookie(
|
|
90
|
+
'delicious_cookie',
|
|
91
|
+
'macha',
|
|
92
|
+
'secret chocolate chips',
|
|
93
|
+
{
|
|
94
|
+
path: '/',
|
|
95
|
+
secure: true,
|
|
96
|
+
httpOnly: true,
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Note**: Unlike `setCookie` and `setSignedCookie`, these functions only generate the cookie strings. You need to manually set them in headers if needed.
|
|
102
|
+
|
|
103
|
+
## Options
|
|
104
|
+
|
|
105
|
+
### `setCookie` & `setSignedCookie`
|
|
106
|
+
|
|
107
|
+
- domain: `string`
|
|
108
|
+
- expires: `Date`
|
|
109
|
+
- httpOnly: `boolean`
|
|
110
|
+
- maxAge: `number`
|
|
111
|
+
- path: `string`
|
|
112
|
+
- secure: `boolean`
|
|
113
|
+
- sameSite: `'Strict'` | `'Lax'` | `'None'`
|
|
114
|
+
- priority: `'Low' | 'Medium' | 'High'`
|
|
115
|
+
- prefix: `secure` | `'host'`
|
|
116
|
+
- partitioned: `boolean`
|
|
117
|
+
|
|
118
|
+
Example:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// Regular cookies
|
|
122
|
+
setCookie(c, 'great_cookie', 'banana', {
|
|
123
|
+
path: '/',
|
|
124
|
+
secure: true,
|
|
125
|
+
domain: 'example.com',
|
|
126
|
+
httpOnly: true,
|
|
127
|
+
maxAge: 1000,
|
|
128
|
+
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
|
|
129
|
+
sameSite: 'Strict',
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// Signed cookies
|
|
133
|
+
await setSignedCookie(
|
|
134
|
+
c,
|
|
135
|
+
'fortune_cookie',
|
|
136
|
+
'lots-of-money',
|
|
137
|
+
'secret ingredient',
|
|
138
|
+
{
|
|
139
|
+
path: '/',
|
|
140
|
+
secure: true,
|
|
141
|
+
domain: 'example.com',
|
|
142
|
+
httpOnly: true,
|
|
143
|
+
maxAge: 1000,
|
|
144
|
+
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
|
|
145
|
+
sameSite: 'Strict',
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### `deleteCookie`
|
|
151
|
+
|
|
152
|
+
- path: `string`
|
|
153
|
+
- secure: `boolean`
|
|
154
|
+
- domain: `string`
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
deleteCookie(c, 'banana', {
|
|
160
|
+
path: '/',
|
|
161
|
+
secure: true,
|
|
162
|
+
domain: 'example.com',
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`deleteCookie` returns the deleted value:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const deletedCookie = deleteCookie(c, 'delicious_cookie')
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## `__Secure-` and `__Host-` prefix
|
|
173
|
+
|
|
174
|
+
The Cookie helper supports `__Secure-` and `__Host-` prefixes for cookie names.
|
|
175
|
+
|
|
176
|
+
If you want to verify if the cookie name has a prefix, specify the prefix option.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const securePrefixCookie = getCookie(c, 'yummy_cookie', 'secure')
|
|
180
|
+
const hostPrefixCookie = getCookie(c, 'yummy_cookie', 'host')
|
|
181
|
+
|
|
182
|
+
const securePrefixSignedCookie = await getSignedCookie(
|
|
183
|
+
c,
|
|
184
|
+
secret,
|
|
185
|
+
'fortune_cookie',
|
|
186
|
+
'secure'
|
|
187
|
+
)
|
|
188
|
+
const hostPrefixSignedCookie = await getSignedCookie(
|
|
189
|
+
c,
|
|
190
|
+
secret,
|
|
191
|
+
'fortune_cookie',
|
|
192
|
+
'host'
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Also, if you wish to specify a prefix when setting the cookie, specify a value for the prefix option.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
setCookie(c, 'delicious_cookie', 'macha', {
|
|
200
|
+
prefix: 'secure', // or `host`
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
await setSignedCookie(
|
|
204
|
+
c,
|
|
205
|
+
'delicious_cookie',
|
|
206
|
+
'macha',
|
|
207
|
+
'secret choco chips',
|
|
208
|
+
{
|
|
209
|
+
prefix: 'secure', // or `host`
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Following the best practices
|
|
215
|
+
|
|
216
|
+
A New Cookie RFC (a.k.a cookie-bis) and CHIPS include some best practices for Cookie settings that developers should follow.
|
|
217
|
+
|
|
218
|
+
- [RFC6265bis-13](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-13)
|
|
219
|
+
- `Max-Age`/`Expires` limitation
|
|
220
|
+
- `__Host-`/`__Secure-` prefix limitation
|
|
221
|
+
- [CHIPS-01](https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html)
|
|
222
|
+
- `Partitioned` limitation
|
|
223
|
+
|
|
224
|
+
Acryl is following the best practices.
|
|
225
|
+
The cookie helper will throw an `Error` when parsing cookies under the following conditions:
|
|
226
|
+
|
|
227
|
+
- The cookie name starts with `__Secure-`, but `secure` option is not set.
|
|
228
|
+
- The cookie name starts with `__Host-`, but `secure` option is not set.
|
|
229
|
+
- The cookie name starts with `__Host-`, but `path` is not `/`.
|
|
230
|
+
- The cookie name starts with `__Host-`, but `domain` is set.
|
|
231
|
+
- The `maxAge` option value is greater than 400 days.
|
|
232
|
+
- The `expires` option value is 400 days later than the current time.
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# css Helper
|
|
2
|
+
|
|
3
|
+
> **Not yet available.** The CSS helper (`acrylblends/css`) 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
|
+
The CSS helper - `acrylblends/css` - is Acryl's built-in CSS in JS(X).
|
|
7
|
+
|
|
8
|
+
You can write CSS in JSX in a JavaScript template literal named `css`. The return value of `css` will be the class name, which is set to the value of the class attribute. The `<Style />` component will then contain the value of the CSS.
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { Acryl } from 'acrylblends'
|
|
14
|
+
import { css, cx, keyframes, Style, createCssContext } from 'acrylblends/css'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## `css` *(Experimental)*
|
|
18
|
+
|
|
19
|
+
You can write CSS in the `css` template literal. In this case, it uses `headerClass` as a value of the `class` attribute. Don't forget to add `<Style />` as it contains the CSS content.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
app.get('/', (c) => {
|
|
23
|
+
const headerClass = css`
|
|
24
|
+
background-color: orange;
|
|
25
|
+
color: white;
|
|
26
|
+
padding: 1rem;
|
|
27
|
+
`
|
|
28
|
+
return c.html(
|
|
29
|
+
<html>
|
|
30
|
+
<head>
|
|
31
|
+
<Style />
|
|
32
|
+
</head>
|
|
33
|
+
<body>
|
|
34
|
+
<h1 class={headerClass}>Hello!</h1>
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You can style pseudo-classes like `:hover` by using the [nesting selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector), `&`:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const buttonClass = css`
|
|
45
|
+
background-color: #fff;
|
|
46
|
+
&:hover {
|
|
47
|
+
background-color: red;
|
|
48
|
+
}
|
|
49
|
+
`
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Extending
|
|
53
|
+
|
|
54
|
+
You can extend the CSS definition by embedding the class name.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
const baseClass = css`
|
|
58
|
+
color: white;
|
|
59
|
+
background-color: blue;
|
|
60
|
+
`
|
|
61
|
+
|
|
62
|
+
const header1Class = css`
|
|
63
|
+
${baseClass}
|
|
64
|
+
font-size: 3rem;
|
|
65
|
+
`
|
|
66
|
+
|
|
67
|
+
const header2Class = css`
|
|
68
|
+
${baseClass}
|
|
69
|
+
font-size: 2rem;
|
|
70
|
+
`
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
In addition, the syntax of `${baseClass} {}` enables nesting classes.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
const headerClass = css`
|
|
77
|
+
color: white;
|
|
78
|
+
background-color: blue;
|
|
79
|
+
`
|
|
80
|
+
const containerClass = css`
|
|
81
|
+
${headerClass} {
|
|
82
|
+
h1 {
|
|
83
|
+
font-size: 3rem;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
`
|
|
87
|
+
return c.render(
|
|
88
|
+
<div class={containerClass}>
|
|
89
|
+
<header class={headerClass}>
|
|
90
|
+
<h1>Hello!</h1>
|
|
91
|
+
</header>
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Global styles
|
|
97
|
+
|
|
98
|
+
A pseudo-selector called `:-acrylblends-global` allows you to define global styles.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
const globalClass = css`
|
|
102
|
+
:-acrylblends-global {
|
|
103
|
+
html {
|
|
104
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
`
|
|
108
|
+
|
|
109
|
+
return c.render(
|
|
110
|
+
<div class={globalClass}>
|
|
111
|
+
<h1>Hello!</h1>
|
|
112
|
+
<p>Today is a good day.</p>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Or you can write CSS in the `<Style />` component with the `css` literal.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
export const renderer = jsxRenderer(({ children, title }) => {
|
|
121
|
+
return (
|
|
122
|
+
<html>
|
|
123
|
+
<head>
|
|
124
|
+
<Style>{css`
|
|
125
|
+
html {
|
|
126
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
127
|
+
}
|
|
128
|
+
`}</Style>
|
|
129
|
+
<title>{title}</title>
|
|
130
|
+
</head>
|
|
131
|
+
<body>
|
|
132
|
+
<div>{children}</div>
|
|
133
|
+
</body>
|
|
134
|
+
</html>
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## `keyframes` *(Experimental)*
|
|
140
|
+
|
|
141
|
+
You can use `keyframes` to write the contents of `@keyframes`. In this case, `fadeInAnimation` will be the name of the animation.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
const fadeInAnimation = keyframes`
|
|
145
|
+
from {
|
|
146
|
+
opacity: 0;
|
|
147
|
+
}
|
|
148
|
+
to {
|
|
149
|
+
opacity: 1;
|
|
150
|
+
}
|
|
151
|
+
`
|
|
152
|
+
const headerClass = css`
|
|
153
|
+
animation-name: ${fadeInAnimation};
|
|
154
|
+
animation-duration: 2s;
|
|
155
|
+
`
|
|
156
|
+
const Header = () => <a class={headerClass}>Hello!</a>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## `cx` *(Experimental)*
|
|
160
|
+
|
|
161
|
+
The `cx` composites the two class names.
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
const buttonClass = css`
|
|
165
|
+
border-radius: 10px;
|
|
166
|
+
`
|
|
167
|
+
const primaryClass = css`
|
|
168
|
+
background: orange;
|
|
169
|
+
`
|
|
170
|
+
const Button = () => (
|
|
171
|
+
<a class={cx(buttonClass, primaryClass)}>Click!</a>
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
It can also compose simple strings.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
const Header = () => <a class={cx('h1', primaryClass)}>Hi</a>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Usage in combination with [Secure Headers](../middleware/builtin/secure-headers.md) middleware
|
|
182
|
+
|
|
183
|
+
If you want to use the CSS helpers in combination with the [Secure Headers](../middleware/builtin/secure-headers.md) middleware, you can add the [`nonce` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce) to the `<Style nonce={c.get('secureHeadersNonce')} />` to avoid Content-Security-Policy caused by the CSS helpers.
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { secureHeaders, NONCE } from 'acrylblends/secure-headers'
|
|
187
|
+
|
|
188
|
+
app.get(
|
|
189
|
+
'*',
|
|
190
|
+
secureHeaders({
|
|
191
|
+
contentSecurityPolicy: {
|
|
192
|
+
// Set the pre-defined nonce value to `styleSrc`:
|
|
193
|
+
styleSrc: [NONCE],
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
app.get('/', (c) => {
|
|
199
|
+
const headerClass = css`
|
|
200
|
+
background-color: orange;
|
|
201
|
+
color: white;
|
|
202
|
+
padding: 1rem;
|
|
203
|
+
`
|
|
204
|
+
return c.html(
|
|
205
|
+
<html>
|
|
206
|
+
<head>
|
|
207
|
+
{/* Set the `nonce` attribute on the CSS helpers `style` and `script` elements */}
|
|
208
|
+
<Style nonce={c.get('secureHeadersNonce')} />
|
|
209
|
+
</head>
|
|
210
|
+
<body>
|
|
211
|
+
<h1 class={headerClass}>Hello!</h1>
|
|
212
|
+
</body>
|
|
213
|
+
</html>
|
|
214
|
+
)
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## `createCssContext` *(Experimental)*
|
|
219
|
+
|
|
220
|
+
`createCssContext` creates CSS helper functions (`css`, `cx`, `keyframes`, `viewTransition`, `Style`) with a custom context. You can use it to customize the style element ID and the generated class names.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { createCssContext } from 'acrylblends/css'
|
|
224
|
+
|
|
225
|
+
const { css, cx, keyframes, Style } = createCssContext({
|
|
226
|
+
id: 'my-app',
|
|
227
|
+
})
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### `classNameSlug`
|
|
231
|
+
|
|
232
|
+
By default, CSS class names are generated in the format `css-1234567890`. You can customize this by passing a `classNameSlug` function.
|
|
233
|
+
|
|
234
|
+
The function receives three arguments:
|
|
235
|
+
|
|
236
|
+
- `hash` - the default generated class name (e.g. `css-1234567890`)
|
|
237
|
+
- `label` - extracted from a `/* comment */` at the start of the CSS template (empty string if none)
|
|
238
|
+
- `css` - the minified CSS string
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const { css, Style } = createCssContext({
|
|
242
|
+
id: 'my-styles',
|
|
243
|
+
classNameSlug: (hash, label) => (label ? `h-${label}` : hash),
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
const heroClass = css`
|
|
247
|
+
/* hero-section */
|
|
248
|
+
background: blue;
|
|
249
|
+
`
|
|
250
|
+
// Generated class name: "h-hero-section"
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### `onInvalidSlug`
|
|
254
|
+
|
|
255
|
+
If the `classNameSlug` function returns an invalid CSS class name, a warning is logged by default. You can customize this behavior with `onInvalidSlug`.
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
const { css, Style } = createCssContext({
|
|
259
|
+
id: 'my-styles',
|
|
260
|
+
classNameSlug: (hash, label) => label || hash,
|
|
261
|
+
onInvalidSlug: (slug) => {
|
|
262
|
+
throw new Error(`Invalid CSS class name: ${slug}`)
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Security
|
|
268
|
+
|
|
269
|
+
The CSS helpers are CSS-authoring APIs: like other CSS-in-JS libraries, interpolated values are inserted as **raw CSS**. They block breaking out into HTML (quotes, backslashes, and `</`), but `{`, `}`, and `;` pass through since they are valid CSS.
|
|
270
|
+
|
|
271
|
+
**Warning.**
|
|
272
|
+
|
|
273
|
+
Treat the CSS helpers like other raw sinks (`html`, `raw`, `rawCssString`): **don't pass untrusted input into them directly.** Doing so allows CSS injection. Validate against an allowlist first.
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
const ALLOWED_COLORS = ['red', 'green', 'blue']
|
|
277
|
+
const color = ALLOWED_COLORS.includes(input) ? input : 'black'
|
|
278
|
+
const headerClass = css`
|
|
279
|
+
color: ${color};
|
|
280
|
+
`
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Tips
|
|
284
|
+
|
|
285
|
+
If you use VS Code, you can use [vscode-styled-components](https://marketplace.visualstudio.com/items?itemName=styled-components.vscode-styled-components) for Syntax highlighting and IntelliSense for CSS tagged literals.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Dev Helper
|
|
2
|
+
|
|
3
|
+
> **Not yet available.** The dev helper (`acrylblends/dev`) 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
|
+
Dev Helper provides useful methods you can use in development.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { Acryl } from 'acrylblends'
|
|
10
|
+
import { getRouterName, showRoutes } from 'acrylblends/dev'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## `getRouterName()`
|
|
14
|
+
|
|
15
|
+
You can get the name of the currently used router with `getRouterName()`.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
const app = new Acryl()
|
|
19
|
+
|
|
20
|
+
// ...
|
|
21
|
+
|
|
22
|
+
console.log(getRouterName(app))
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## `showRoutes()`
|
|
26
|
+
|
|
27
|
+
`showRoutes()` function displays the registered routes in your console.
|
|
28
|
+
|
|
29
|
+
Consider an application like the following:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const app = new Acryl().basePath('/v1')
|
|
33
|
+
|
|
34
|
+
app.get('/posts', (c) => {
|
|
35
|
+
// ...
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
app.get('/posts/:id', (c) => {
|
|
39
|
+
// ...
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
app.post('/posts', (c) => {
|
|
43
|
+
// ...
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
showRoutes(app, {
|
|
47
|
+
verbose: true,
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
When this application starts running, the routes will be shown in your console as follows:
|
|
52
|
+
|
|
53
|
+
```txt
|
|
54
|
+
GET /v1/posts
|
|
55
|
+
GET /v1/posts/:id
|
|
56
|
+
POST /v1/posts
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Options
|
|
60
|
+
|
|
61
|
+
### *(optional)* verbose: `boolean`
|
|
62
|
+
|
|
63
|
+
When set to `true`, it displays verbose information.
|
|
64
|
+
|
|
65
|
+
### *(optional)* colorize: `boolean`
|
|
66
|
+
|
|
67
|
+
When set to `false`, the output will not be colored.
|