remix 3.0.0-beta.1 → 3.0.0-beta.2
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/package.json +38 -43
- package/src/file-storage/README.md +10 -4
- package/src/file-storage-s3/README.md +1 -1
- package/src/form-data-parser/README.md +5 -8
- package/src/headers/README.md +23 -1
- package/src/node-fetch-server/README.md +0 -4
- package/src/ui/README.md +9 -7
- package/src/ui/animation/README.md +7 -5
- package/src/ui/combobox/README.md +1 -1
- package/src/ui/server/README.md +4 -2
- package/dist/node-serve.d.ts +0 -2
- package/dist/node-serve.d.ts.map +0 -1
- package/dist/node-serve.js +0 -2
- package/src/node-serve/README.md +0 -253
- package/src/node-serve.ts +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remix",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.2",
|
|
4
4
|
"description": "The Remix web framework",
|
|
5
5
|
"author": "Michael Jackson <mjijackson@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -261,10 +261,6 @@
|
|
|
261
261
|
"types": "./dist/node-fetch-server/test.d.ts",
|
|
262
262
|
"default": "./dist/node-fetch-server/test.js"
|
|
263
263
|
},
|
|
264
|
-
"./node-serve": {
|
|
265
|
-
"types": "./dist/node-serve.d.ts",
|
|
266
|
-
"default": "./dist/node-serve.js"
|
|
267
|
-
},
|
|
268
264
|
"./node-tsx": {
|
|
269
265
|
"types": "./dist/node-tsx.d.ts",
|
|
270
266
|
"default": "./dist/node-tsx.js"
|
|
@@ -473,51 +469,50 @@
|
|
|
473
469
|
"typescript": "^5.9.3"
|
|
474
470
|
},
|
|
475
471
|
"dependencies": {
|
|
476
|
-
"@remix-run/
|
|
477
|
-
"@remix-run/
|
|
478
|
-
"@remix-run/auth
|
|
479
|
-
"@remix-run/
|
|
480
|
-
"@remix-run/
|
|
481
|
-
"@remix-run/compression-middleware": "^0.1.
|
|
482
|
-
"@remix-run/
|
|
483
|
-
"@remix-run/
|
|
484
|
-
"@remix-run/
|
|
472
|
+
"@remix-run/assets": "^0.4.1",
|
|
473
|
+
"@remix-run/async-context-middleware": "^0.3.1",
|
|
474
|
+
"@remix-run/auth": "^0.2.3",
|
|
475
|
+
"@remix-run/ui": "^0.2.0",
|
|
476
|
+
"@remix-run/cop-middleware": "^0.1.4",
|
|
477
|
+
"@remix-run/compression-middleware": "^0.1.9",
|
|
478
|
+
"@remix-run/cookie": "^0.5.3",
|
|
479
|
+
"@remix-run/auth-middleware": "^0.2.1",
|
|
480
|
+
"@remix-run/cors-middleware": "^0.1.4",
|
|
481
|
+
"@remix-run/csrf-middleware": "^0.1.4",
|
|
485
482
|
"@remix-run/data-schema": "^0.3.0",
|
|
486
|
-
"@remix-run/csrf-middleware": "^0.1.3",
|
|
487
483
|
"@remix-run/data-table": "^0.3.0",
|
|
488
484
|
"@remix-run/data-table-mysql": "^0.4.0",
|
|
489
485
|
"@remix-run/data-table-postgres": "^0.4.0",
|
|
490
486
|
"@remix-run/data-table-sqlite": "^0.5.0",
|
|
491
|
-
"@remix-run/fetch-proxy": "^0.8.
|
|
492
|
-
"@remix-run/
|
|
493
|
-
"@remix-run/file-storage
|
|
494
|
-
"@remix-run/
|
|
495
|
-
"@remix-run/form-data-parser": "^0.17.
|
|
496
|
-
"@remix-run/
|
|
497
|
-
"@remix-run/
|
|
498
|
-
"@remix-run/
|
|
499
|
-
"@remix-run/
|
|
500
|
-
"@remix-run/lazy-file": "^5.0.
|
|
487
|
+
"@remix-run/fetch-proxy": "^0.8.2",
|
|
488
|
+
"@remix-run/fetch-router": "^0.19.1",
|
|
489
|
+
"@remix-run/file-storage": "^0.13.5",
|
|
490
|
+
"@remix-run/form-data-middleware": "^0.3.1",
|
|
491
|
+
"@remix-run/form-data-parser": "^0.17.2",
|
|
492
|
+
"@remix-run/file-storage-s3": "^0.1.2",
|
|
493
|
+
"@remix-run/fs": "^0.4.4",
|
|
494
|
+
"@remix-run/html-template": "^0.3.1",
|
|
495
|
+
"@remix-run/headers": "^0.21.0",
|
|
496
|
+
"@remix-run/lazy-file": "^5.0.4",
|
|
497
|
+
"@remix-run/logger-middleware": "^0.3.1",
|
|
501
498
|
"@remix-run/mime": "^0.4.1",
|
|
502
|
-
"@remix-run/multipart-parser": "^0.16.
|
|
503
|
-
"@remix-run/method-override-middleware": "^0.1.
|
|
504
|
-
"@remix-run/
|
|
505
|
-
"@remix-run/node-
|
|
506
|
-
"@remix-run/
|
|
507
|
-
"@remix-run/
|
|
508
|
-
"@remix-run/
|
|
509
|
-
"@remix-run/session-middleware": "^0.3.
|
|
510
|
-
"@remix-run/
|
|
511
|
-
"@remix-run/
|
|
512
|
-
"@remix-run/
|
|
513
|
-
"@remix-run/session-storage-memcache": "^0.1.0",
|
|
514
|
-
"@remix-run/cli": "^0.3.0",
|
|
515
|
-
"@remix-run/assert": "^0.2.0",
|
|
516
|
-
"@remix-run/test": "^0.4.0",
|
|
499
|
+
"@remix-run/multipart-parser": "^0.16.2",
|
|
500
|
+
"@remix-run/method-override-middleware": "^0.1.9",
|
|
501
|
+
"@remix-run/node-fetch-server": "^0.13.3",
|
|
502
|
+
"@remix-run/node-tsx": "^0.1.1",
|
|
503
|
+
"@remix-run/route-pattern": "^0.21.1",
|
|
504
|
+
"@remix-run/session": "^0.4.2",
|
|
505
|
+
"@remix-run/response": "^0.3.5",
|
|
506
|
+
"@remix-run/session-middleware": "^0.3.1",
|
|
507
|
+
"@remix-run/session-storage-memcache": "^0.1.1",
|
|
508
|
+
"@remix-run/static-middleware": "^0.4.10",
|
|
509
|
+
"@remix-run/session-storage-redis": "^0.1.1",
|
|
517
510
|
"@remix-run/tar-parser": "^0.7.1",
|
|
518
|
-
"@remix-run/
|
|
519
|
-
"@remix-run/
|
|
520
|
-
"@remix-run/
|
|
511
|
+
"@remix-run/assert": "^0.2.1",
|
|
512
|
+
"@remix-run/cli": "^0.3.1",
|
|
513
|
+
"@remix-run/render-middleware": "^0.1.1",
|
|
514
|
+
"@remix-run/terminal": "^0.1.1",
|
|
515
|
+
"@remix-run/test": "^0.4.1"
|
|
521
516
|
},
|
|
522
517
|
"bin": {
|
|
523
518
|
"remix": "./dist/cli-entry.js"
|
|
@@ -7,7 +7,7 @@ Key/value storage interfaces for server-side [`File` objects](https://developer.
|
|
|
7
7
|
- **Simple API** - Intuitive key/value API (like [Web Storage](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API), but for `File`s instead of strings)
|
|
8
8
|
- **Multiple Backends** - Built-in filesystem and memory backends
|
|
9
9
|
- **Streaming Support** - Stream file content to and from storage
|
|
10
|
-
- **Metadata Preservation** - Preserves all `File` metadata including `file.name`, `file.type`, and `file.lastModified`
|
|
10
|
+
- **Metadata Preservation** - Preserves all `File` metadata including `file.name`, `file.type`, `file.size`, and `file.lastModified`
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
@@ -32,9 +32,15 @@ await storage.set(key, file)
|
|
|
32
32
|
|
|
33
33
|
// Then, sometime later...
|
|
34
34
|
let fileFromStorage = await storage.get(key)
|
|
35
|
-
|
|
36
|
-
fileFromStorage
|
|
37
|
-
|
|
35
|
+
|
|
36
|
+
if (fileFromStorage != null) {
|
|
37
|
+
// All of the original file's metadata is intact
|
|
38
|
+
fileFromStorage.name // 'hello.txt'
|
|
39
|
+
fileFromStorage.type // 'text/plain'
|
|
40
|
+
|
|
41
|
+
// The filesystem backend returns a LazyFile, so you can stream it directly.
|
|
42
|
+
let response = new Response(fileFromStorage.stream())
|
|
43
|
+
}
|
|
38
44
|
|
|
39
45
|
// To remove from storage
|
|
40
46
|
await storage.remove(key)
|
|
@@ -6,7 +6,7 @@ Use this package when you want the `FileStorage` API backed by AWS S3 or an S3-c
|
|
|
6
6
|
## Features
|
|
7
7
|
|
|
8
8
|
- **S3-Compatible API** - Works with AWS S3 and S3-compatible APIs (e.g. MinIO, LocalStack)
|
|
9
|
-
- **Metadata Preservation** - Preserves `File` metadata (`name`, `type`, `lastModified`)
|
|
9
|
+
- **Metadata Preservation** - Preserves `File` metadata (`name`, `type`, `size`, `lastModified`)
|
|
10
10
|
- **Runtime-Agnostic Signing** - Uses [`aws4fetch`](https://github.com/mhart/aws4fetch) for SigV4 signing
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
@@ -118,15 +118,15 @@ try {
|
|
|
118
118
|
}
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
If you're looking for a more flexible storage solution for `
|
|
121
|
+
If you're looking for a more flexible storage solution for `FileUpload` objects, this library pairs really well with [the `file-storage` library](https://github.com/remix-run/remix/tree/main/packages/file-storage) for keeping files in various storage backends.
|
|
122
122
|
|
|
123
123
|
```ts
|
|
124
|
-
import {
|
|
124
|
+
import { createFsFileStorage } from 'remix/file-storage/fs'
|
|
125
125
|
import type { FileUpload } from 'remix/form-data-parser'
|
|
126
126
|
import { parseFormData } from 'remix/form-data-parser'
|
|
127
127
|
|
|
128
128
|
// Set up storage for uploaded files
|
|
129
|
-
const fileStorage =
|
|
129
|
+
const fileStorage = createFsFileStorage('/uploads/user-avatars')
|
|
130
130
|
|
|
131
131
|
// Define how to handle incoming file uploads
|
|
132
132
|
async function uploadHandler(fileUpload: FileUpload) {
|
|
@@ -134,11 +134,8 @@ async function uploadHandler(fileUpload: FileUpload) {
|
|
|
134
134
|
if (fileUpload.fieldName === 'user-avatar') {
|
|
135
135
|
let storageKey = `user-${user.id}-avatar`
|
|
136
136
|
|
|
137
|
-
// Put the file in storage
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// Return a lazy File object that can access the stored file when needed
|
|
141
|
-
return fileStorage.get(storageKey)
|
|
137
|
+
// Put the file in storage and return the stored LazyFile
|
|
138
|
+
return fileStorage.put(storageKey, fileUpload)
|
|
142
139
|
}
|
|
143
140
|
|
|
144
141
|
// Ignore unrecognized fields
|
package/src/headers/README.md
CHANGED
|
@@ -53,6 +53,26 @@ headers.get('Content-Type') // no typed parse needed
|
|
|
53
53
|
headers.contentType.mediaType // parses Content-Type lazily
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
Use `apply()` to apply a `SuperHeadersInit` value to an existing instance with header-aware semantics:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
let headers = new Headers({
|
|
60
|
+
contentType: 'text/html',
|
|
61
|
+
setCookie: { name: 'session', value: 'abc' },
|
|
62
|
+
vary: 'Accept-Encoding',
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
headers.apply({
|
|
66
|
+
contentType: 'application/json',
|
|
67
|
+
setCookie: { name: 'theme', value: 'dark' },
|
|
68
|
+
vary: ['Accept-Encoding', 'Accept-Language'],
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
headers.get('Content-Type') // 'application/json'
|
|
72
|
+
headers.get('Vary') // 'accept-encoding, accept-language'
|
|
73
|
+
headers.getSetCookie() // ['session=abc', 'theme=dark']
|
|
74
|
+
```
|
|
75
|
+
|
|
56
76
|
## Individual Header Utilities
|
|
57
77
|
|
|
58
78
|
Each supported header has a class that represents the header value. Use the static `from()` method to parse header values. Each class has a `toString()` method that returns the header value as a string, which you can either call manually, or will be called automatically when the header class is used in a context that expects a string.
|
|
@@ -325,7 +345,7 @@ headers.set('Content-Type', new ContentType({ mediaType: 'text/html', charset: '
|
|
|
325
345
|
|
|
326
346
|
Parse, manipulate and stringify [`Cookie` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie).
|
|
327
347
|
|
|
328
|
-
Implements
|
|
348
|
+
Implements an ordered list of name/value pairs. Duplicate cookie names are preserved, such as when cookies with the same name were set for different paths.
|
|
329
349
|
|
|
330
350
|
```ts
|
|
331
351
|
import { Cookie } from 'remix/headers'
|
|
@@ -334,6 +354,7 @@ import { Cookie } from 'remix/headers'
|
|
|
334
354
|
let cookie = Cookie.from(request.headers.get('Cookie'))
|
|
335
355
|
|
|
336
356
|
cookie.get('session_id') // 'abc123'
|
|
357
|
+
cookie.getAll('session_id') // ['abc123']
|
|
337
358
|
cookie.get('theme') // 'dark'
|
|
338
359
|
cookie.has('session_id') // true
|
|
339
360
|
cookie.size // 2
|
|
@@ -345,6 +366,7 @@ for (let [name, value] of cookie) {
|
|
|
345
366
|
|
|
346
367
|
// Modify and set header
|
|
347
368
|
cookie.set('theme', 'light')
|
|
369
|
+
cookie.append('session_id', 'def456')
|
|
348
370
|
cookie.delete('session_id')
|
|
349
371
|
headers.set('Cookie', cookie)
|
|
350
372
|
|
|
@@ -291,7 +291,6 @@ The [`demos` directory](https://github.com/remix-run/remix/tree/main/packages/no
|
|
|
291
291
|
|
|
292
292
|
## Related Packages
|
|
293
293
|
|
|
294
|
-
- [`node-serve`](https://github.com/remix-run/remix/tree/main/packages/node-serve) - Build high-performance Fetch API servers for Node.js
|
|
295
294
|
- [`fetch-proxy`](https://github.com/remix-run/remix/tree/main/packages/fetch-proxy) - Build HTTP proxy servers using the web fetch API
|
|
296
295
|
|
|
297
296
|
## Benchmarks
|
|
@@ -322,7 +321,6 @@ Simple HTML response benchmarks without inspecting the incoming request.
|
|
|
322
321
|
|
|
323
322
|
| Server | Version | Requests/sec | Avg latency | Transfer/sec |
|
|
324
323
|
| ------------------------- | --------: | -----------: | ----------: | -----------: |
|
|
325
|
-
| `remix/node-serve` | `0.0.0` | `62,225` | `6.45ms` | `9.85MB` |
|
|
326
324
|
| `node:http` | `24.15.0` | `47,110` | `10.66ms` | `9.66MB` |
|
|
327
325
|
| `remix/node-fetch-server` | `0.13.0` | `43,317` | `11.69ms` | `8.80MB` |
|
|
328
326
|
| `express` | `5.2.1` | `39,752` | `13.69ms` | `9.59MB` |
|
|
@@ -333,7 +331,6 @@ POST benchmarks that read and print the request method, headers, and a small bod
|
|
|
333
331
|
|
|
334
332
|
| Server | Version | Requests/sec | Avg latency | Transfer/sec |
|
|
335
333
|
| ------------------------- | --------: | -----------: | ----------: | -----------: |
|
|
336
|
-
| `remix/node-serve` | `0.0.0` | `31,213` | `12.75ms` | `4.94MB` |
|
|
337
334
|
| `remix/node-fetch-server` | `0.13.0` | `25,430` | `24.25ms` | `5.17MB` |
|
|
338
335
|
| `node:http` | `24.15.0` | `25,088` | `23.89ms` | `5.14MB` |
|
|
339
336
|
| `express` | `5.2.1` | `22,845` | `27.16ms` | `5.51MB` |
|
|
@@ -344,7 +341,6 @@ POST benchmarks that read and print the request method, headers, and a 1 MB body
|
|
|
344
341
|
|
|
345
342
|
| Server | Version | Requests/sec | Avg latency | Transfer/sec |
|
|
346
343
|
| ------------------------- | --------: | -----------: | ----------: | -----------: |
|
|
347
|
-
| `remix/node-serve` | `0.0.0` | `1,148` | `327.72ms` | `186.03KB` |
|
|
348
344
|
| `remix/node-fetch-server` | `0.13.0` | `1,086` | `217.69ms` | `225.87KB` |
|
|
349
345
|
| `node:http` | `24.15.0` | `1,079` | `198.67ms` | `226.54KB` |
|
|
350
346
|
| `express` | `5.2.1` | `1,022` | `216.07ms` | `252.51KB` |
|
package/src/ui/README.md
CHANGED
|
@@ -135,13 +135,15 @@ let Theme = createTheme({
|
|
|
135
135
|
Render the theme once near the top of your document:
|
|
136
136
|
|
|
137
137
|
```tsx
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
import type { Handle, RemixNode } from 'remix/ui'
|
|
139
|
+
|
|
140
|
+
function Layout(handle: Handle<{ children: RemixNode }>) {
|
|
141
|
+
return () => (
|
|
140
142
|
<html>
|
|
141
143
|
<head>
|
|
142
144
|
<Theme />
|
|
143
145
|
</head>
|
|
144
|
-
<body>{props.children}</body>
|
|
146
|
+
<body>{handle.props.children}</body>
|
|
145
147
|
</html>
|
|
146
148
|
)
|
|
147
149
|
}
|
|
@@ -168,13 +170,13 @@ let card = css({
|
|
|
168
170
|
Render shared glyphs separately from the theme styles:
|
|
169
171
|
|
|
170
172
|
```tsx
|
|
171
|
-
import type { RemixNode } from 'remix/ui'
|
|
173
|
+
import type { Handle, RemixNode } from 'remix/ui'
|
|
172
174
|
import { Button } from 'remix/ui/button'
|
|
173
175
|
import { Glyph } from 'remix/ui/glyph'
|
|
174
176
|
import { RMX_01, RMX_01_GLYPHS } from 'remix/ui/theme'
|
|
175
177
|
|
|
176
|
-
function Layout(
|
|
177
|
-
return (
|
|
178
|
+
function Layout(handle: Handle<{ children: RemixNode }>) {
|
|
179
|
+
return () => (
|
|
178
180
|
<html>
|
|
179
181
|
<head>
|
|
180
182
|
<RMX_01 />
|
|
@@ -184,7 +186,7 @@ function Layout(props: { children: RemixNode }) {
|
|
|
184
186
|
<Button startIcon={<Glyph name="add" />} tone="primary">
|
|
185
187
|
New project
|
|
186
188
|
</Button>
|
|
187
|
-
{props.children}
|
|
189
|
+
{handle.props.children}
|
|
188
190
|
</body>
|
|
189
191
|
</html>
|
|
190
192
|
)
|
|
@@ -52,12 +52,13 @@ function Toast() {
|
|
|
52
52
|
`animateExit` keeps a removed keyed element in the DOM long enough to animate from its natural styles to the provided keyframe.
|
|
53
53
|
|
|
54
54
|
```tsx
|
|
55
|
+
import type { Handle } from 'remix/ui'
|
|
55
56
|
import { animateExit } from 'remix/ui/animation'
|
|
56
57
|
|
|
57
|
-
function Item(
|
|
58
|
+
function Item(handle: Handle<{ id: string; label: string }>) {
|
|
58
59
|
return () => (
|
|
59
60
|
<li
|
|
60
|
-
key={id}
|
|
61
|
+
key={handle.props.id}
|
|
61
62
|
mix={[
|
|
62
63
|
animateExit({
|
|
63
64
|
opacity: 0,
|
|
@@ -67,7 +68,7 @@ function Item({ id, label }: { id: string; label: string }) {
|
|
|
67
68
|
}),
|
|
68
69
|
]}
|
|
69
70
|
>
|
|
70
|
-
{label}
|
|
71
|
+
{handle.props.label}
|
|
71
72
|
</li>
|
|
72
73
|
)
|
|
73
74
|
}
|
|
@@ -105,12 +106,13 @@ Exit animations can reclaim a removed keyed node if the same keyed element is re
|
|
|
105
106
|
`animateLayout` animates layout changes with a FLIP-style transform projection. Use it on elements whose position or size can change between renders.
|
|
106
107
|
|
|
107
108
|
```tsx
|
|
109
|
+
import type { Handle } from 'remix/ui'
|
|
108
110
|
import { animateLayout, spring } from 'remix/ui/animation'
|
|
109
111
|
|
|
110
|
-
function Card(
|
|
112
|
+
function Card(handle: Handle<{ expanded: boolean }>) {
|
|
111
113
|
return () => (
|
|
112
114
|
<section
|
|
113
|
-
class={expanded ? 'card expanded' : 'card'}
|
|
115
|
+
class={handle.props.expanded ? 'card expanded' : 'card'}
|
|
114
116
|
mix={[
|
|
115
117
|
animateLayout({
|
|
116
118
|
...spring('smooth'),
|
|
@@ -8,7 +8,7 @@ Use it when the user should type draft text, filter a popup list, and still comm
|
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
10
10
|
import { css, type Handle } from 'remix/ui'
|
|
11
|
-
import { Combobox, ComboboxOption, onComboboxChange } from 'remix/ui'
|
|
11
|
+
import { Combobox, ComboboxOption, onComboboxChange } from 'remix/ui/combobox'
|
|
12
12
|
|
|
13
13
|
let airports = [
|
|
14
14
|
{
|
package/src/ui/server/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Server
|
|
2
2
|
|
|
3
|
-
Remix
|
|
3
|
+
Remix UI can render to HTML on the server using two APIs:
|
|
4
4
|
|
|
5
5
|
- `renderToString` - Returns a complete HTML string. Simple, but buffers the entire response.
|
|
6
6
|
- `renderToStream` - Returns a `ReadableStream<Uint8Array>`. Sends the initial HTML immediately and streams frame content as it resolves.
|
|
@@ -26,6 +26,7 @@ import { renderToStream } from 'remix/ui/server'
|
|
|
26
26
|
|
|
27
27
|
let stream = renderToStream(<App />, {
|
|
28
28
|
frameSrc: request.url,
|
|
29
|
+
signal: request.signal,
|
|
29
30
|
resolveFrame(src, _target, context) {
|
|
30
31
|
let frameUrl = new URL(src, context?.currentFrameSrc ?? request.url)
|
|
31
32
|
return fetchHtml(frameUrl)
|
|
@@ -44,6 +45,7 @@ return new Response(stream, {
|
|
|
44
45
|
|
|
45
46
|
- **`frameSrc`** - Seeds SSR frame state for the current render. When provided, server-rendered components can read `handle.frame.src` and `handle.frames.top.src` during SSR.
|
|
46
47
|
- **`topFrameSrc`** - Overrides the root frame URL used for `handle.frames.top.src`. This is mainly useful when calling `renderToStream()` from inside `resolveFrame()` for a nested frame render.
|
|
48
|
+
- **`signal`** - Cancels pending server rendering work. Pass `request.signal` so client disconnects can stop unresolved frame work without invoking `onError` for the disconnect itself.
|
|
47
49
|
- **`resolveFrame(src, target, context)`** - Called when a `<Frame>` needs its content. Return a string of HTML, a `ReadableStream<Uint8Array>`, or a promise of either. `context.currentFrameSrc` is the URL for the frame that contains the `<Frame>`, and `context.topFrameSrc` is the outer document URL. Required if your component tree contains `<Frame>` elements.
|
|
48
50
|
- **`onError(error)`** - Called when a rendering error occurs. If not provided, the stream rejects with the error.
|
|
49
51
|
|
|
@@ -80,7 +82,7 @@ function ProductPage() {
|
|
|
80
82
|
|
|
81
83
|
## CSS
|
|
82
84
|
|
|
83
|
-
Components using the `css`
|
|
85
|
+
Components using the `css(...)` mixin through `mix` have their styles collected during rendering and emitted as a single `<style>` tag in the `<head>`. No client-side style injection is needed for server-rendered content.
|
|
84
86
|
|
|
85
87
|
## See Also
|
|
86
88
|
|
package/dist/node-serve.d.ts
DELETED
package/dist/node-serve.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"node-serve.d.ts","sourceRoot":"","sources":["../src/node-serve.ts"],"names":[],"mappings":"AACA,cAAc,uBAAuB,CAAA"}
|
package/dist/node-serve.js
DELETED
package/src/node-serve/README.md
DELETED
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
# node-serve
|
|
2
|
-
|
|
3
|
-
Build high-performance Node.js servers with web-standard Fetch API primitives. Use this package when you want Remix-style `Request`/`Response` handlers with a managed server optimized for production throughput.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Fetch API Handlers**: Serve standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) to [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) request handlers
|
|
8
|
-
- **High-Performance Node.js Server**: Start a fast managed server for Fetch API application code
|
|
9
|
-
- **HTTPS Support**: Start a TLS server with certificate and key file paths
|
|
10
|
-
- **Managed Server Lifecycle**: Start a server with `serve()`, wait for `server.ready`, and close it with `server.close()`
|
|
11
|
-
- **Custom Hostname**: Override the host and protocol used to construct incoming `request.url` values
|
|
12
|
-
- **Client Info**: Access client IP address, address family, and remote port when your handler accepts a second argument
|
|
13
|
-
- **uWebSockets.js Setup**: Register native WebSocket routes and connection filters before the Fetch fallback starts listening
|
|
14
|
-
- **Existing uWebSockets.js App Adapter**: Use `createUwsRequestHandler()` when you already own a uWebSockets.js app
|
|
15
|
-
|
|
16
|
-
## Installation
|
|
17
|
-
|
|
18
|
-
```sh
|
|
19
|
-
npm i remix
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
`node-serve` includes uWebSockets.js as its native high-performance transport.
|
|
23
|
-
|
|
24
|
-
## Usage
|
|
25
|
-
|
|
26
|
-
Use `serve()` to start a Node.js server that calls your fetch handler for every incoming request:
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
import { serve } from 'remix/node-serve'
|
|
30
|
-
|
|
31
|
-
let users = new Map([
|
|
32
|
-
['1', { id: '1', name: 'Alice', email: 'alice@example.com' }],
|
|
33
|
-
['2', { id: '2', name: 'Bob', email: 'bob@example.com' }],
|
|
34
|
-
])
|
|
35
|
-
|
|
36
|
-
async function handler(request: Request) {
|
|
37
|
-
let url = new URL(request.url)
|
|
38
|
-
|
|
39
|
-
if (url.pathname === '/' && request.method === 'GET') {
|
|
40
|
-
return new Response('Welcome to the User API! Try GET /api/users')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (url.pathname === '/api/users' && request.method === 'GET') {
|
|
44
|
-
return Response.json(Array.from(users.values()))
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let userMatch = url.pathname.match(/^\/api\/users\/(\w+)$/)
|
|
48
|
-
if (userMatch && request.method === 'GET') {
|
|
49
|
-
let user = users.get(userMatch[1])
|
|
50
|
-
if (user) return Response.json(user)
|
|
51
|
-
return new Response('User not found', { status: 404 })
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return new Response('Not Found', { status: 404 })
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let server = serve(handler, { port: 3000 })
|
|
58
|
-
|
|
59
|
-
await server.ready
|
|
60
|
-
console.log(`Server running at http://localhost:${server.port}`)
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### uWebSockets.js Setup
|
|
64
|
-
|
|
65
|
-
Use `setup(app)` when `serve()` should still manage the server lifecycle and Fetch fallback, but you need to configure native uWebSockets.js transport features before the server starts listening. The hook runs after the app is created and before `node-serve` registers its catch-all Fetch route.
|
|
66
|
-
|
|
67
|
-
This example adds a native WebSocket endpoint next to a normal Fetch handler on the same port:
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
import { serve } from 'remix/node-serve'
|
|
71
|
-
|
|
72
|
-
let server = serve(
|
|
73
|
-
(request) => {
|
|
74
|
-
let url = new URL(request.url)
|
|
75
|
-
|
|
76
|
-
if (url.pathname === '/') {
|
|
77
|
-
return new Response('Chat server')
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return new Response('Not Found', { status: 404 })
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
port: 3000,
|
|
84
|
-
setup(app) {
|
|
85
|
-
app.ws('/ws/chat', {
|
|
86
|
-
open(ws) {
|
|
87
|
-
ws.subscribe('chat')
|
|
88
|
-
},
|
|
89
|
-
message(ws, message, isBinary) {
|
|
90
|
-
ws.publish('chat', message, isBinary)
|
|
91
|
-
},
|
|
92
|
-
})
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
await server.ready
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
`app.ws(pattern, ...)` uses uWebSockets.js route patterns, not Remix `route-pattern` syntax. uWS supports simple path params such as `/rooms/:room`, and those params are available during `upgrade` through `req.getParameter('room')` or `req.getParameter(0)`. If WebSocket handlers need params, copy them into uWS user data during `upgrade`, then read them with `ws.getUserData()`:
|
|
101
|
-
|
|
102
|
-
```ts
|
|
103
|
-
serve(handler, {
|
|
104
|
-
setup(app) {
|
|
105
|
-
app.ws<{ room: string }>('/rooms/:room', {
|
|
106
|
-
upgrade(res, req, context) {
|
|
107
|
-
res.upgrade(
|
|
108
|
-
{ room: req.getParameter('room') ?? 'general' },
|
|
109
|
-
req.getHeader('sec-websocket-key'),
|
|
110
|
-
req.getHeader('sec-websocket-protocol'),
|
|
111
|
-
req.getHeader('sec-websocket-extensions'),
|
|
112
|
-
context,
|
|
113
|
-
)
|
|
114
|
-
},
|
|
115
|
-
open(ws) {
|
|
116
|
-
ws.subscribe(`room:${ws.getUserData().room}`)
|
|
117
|
-
},
|
|
118
|
-
})
|
|
119
|
-
},
|
|
120
|
-
})
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
uWS patterns do not support the full `route-pattern` feature set, including partial-segment params like `v:version`, split params like `:file.:ext`, optionals like `/api(/v:version)`, host/protocol/search matching, or named multi-segment wildcard captures like `*path`. You can use a uWS pattern like `/files/*` as a catch-all, but it does not provide Remix-style named wildcard params.
|
|
124
|
-
|
|
125
|
-
You can also register connection filters for low-level transport metrics or limits:
|
|
126
|
-
|
|
127
|
-
```ts
|
|
128
|
-
let activeConnections = 0
|
|
129
|
-
|
|
130
|
-
serve(handler, {
|
|
131
|
-
setup(app) {
|
|
132
|
-
app.filter((_res, count) => {
|
|
133
|
-
activeConnections = Number(count)
|
|
134
|
-
})
|
|
135
|
-
},
|
|
136
|
-
})
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
`app.filter()` observes low-level connection count changes. It is not Fetch response middleware and should not be used to mutate normal `Response` headers or bodies. Use Fetch handlers, middleware, or [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) for normal HTTP routing and response behavior; reserve `setup()` for uWebSockets.js transport features that must be configured before listening.
|
|
140
|
-
|
|
141
|
-
### Custom Request URLs
|
|
142
|
-
|
|
143
|
-
Use `host` and `protocol` when your server runs behind a proxy or load balancer and you need stable incoming request URLs:
|
|
144
|
-
|
|
145
|
-
```ts
|
|
146
|
-
import { serve } from 'remix/node-serve'
|
|
147
|
-
|
|
148
|
-
let server = serve(handler, {
|
|
149
|
-
host: process.env.HOST ?? 'api.example.com',
|
|
150
|
-
protocol: 'https:',
|
|
151
|
-
port: 3000,
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
await server.ready
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### HTTPS
|
|
158
|
-
|
|
159
|
-
Pass `tls` options to start an HTTPS server. `keyFile` and `certFile` are file paths, not PEM contents:
|
|
160
|
-
|
|
161
|
-
```ts
|
|
162
|
-
import { serve } from 'remix/node-serve'
|
|
163
|
-
|
|
164
|
-
let server = serve(handler, {
|
|
165
|
-
port: 443,
|
|
166
|
-
tls: {
|
|
167
|
-
keyFile: './certs/server.key',
|
|
168
|
-
certFile: './certs/server.crt',
|
|
169
|
-
},
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
await server.ready
|
|
173
|
-
console.log(`Server running at https://localhost:${server.port}`)
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
When `tls` is present, `request.url` defaults to the `https:` protocol. You can still set `protocol` explicitly when the public URL differs from the local server transport.
|
|
177
|
-
|
|
178
|
-
### Client Information
|
|
179
|
-
|
|
180
|
-
Handlers that accept a second argument receive the remote client address:
|
|
181
|
-
|
|
182
|
-
```ts
|
|
183
|
-
import { type FetchHandler, serve } from 'remix/node-serve'
|
|
184
|
-
|
|
185
|
-
let handler: FetchHandler = async (request, client) => {
|
|
186
|
-
console.log(`Request from ${client.address}:${client.port}`)
|
|
187
|
-
|
|
188
|
-
return Response.json({
|
|
189
|
-
path: new URL(request.url).pathname,
|
|
190
|
-
clientAddress: client.address,
|
|
191
|
-
})
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
serve(handler, { port: 3000 })
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Existing uWebSockets.js Apps
|
|
198
|
-
|
|
199
|
-
Most apps should use `serve()`. Use `createUwsRequestHandler()` when you already have a uWebSockets.js app and want only part of the app to use a Fetch API handler:
|
|
200
|
-
|
|
201
|
-
This example assumes `uWebSockets.js` is also a direct dependency of your app.
|
|
202
|
-
|
|
203
|
-
```ts
|
|
204
|
-
import { App } from 'uWebSockets.js'
|
|
205
|
-
import { createUwsRequestHandler } from 'remix/node-serve'
|
|
206
|
-
|
|
207
|
-
let app = App()
|
|
208
|
-
|
|
209
|
-
async function handler(request: Request) {
|
|
210
|
-
let url = new URL(request.url)
|
|
211
|
-
return Response.json({ path: url.pathname })
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
app.get('/health', (res) => {
|
|
215
|
-
res.end('ok')
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
app.any('/api/*', createUwsRequestHandler(handler))
|
|
219
|
-
|
|
220
|
-
app.listen(3000, (socket) => {
|
|
221
|
-
if (!socket) throw new Error('Could not listen on port 3000')
|
|
222
|
-
})
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
For HTTPS with an existing uWebSockets.js app, create the SSL app yourself and pass `protocol: 'https:'` when you create the request handler:
|
|
226
|
-
|
|
227
|
-
```ts
|
|
228
|
-
import { SSLApp } from 'uWebSockets.js'
|
|
229
|
-
import { createUwsRequestHandler } from 'remix/node-serve'
|
|
230
|
-
|
|
231
|
-
let app = SSLApp({
|
|
232
|
-
key_file_name: './certs/server.key',
|
|
233
|
-
cert_file_name: './certs/server.crt',
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
app.any('/*', createUwsRequestHandler(handler, { protocol: 'https:' }))
|
|
237
|
-
app.listen(443, (socket) => {
|
|
238
|
-
if (!socket) throw new Error('Could not listen on port 443')
|
|
239
|
-
})
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
## Related Packages
|
|
243
|
-
|
|
244
|
-
- [`node-fetch-server`](https://github.com/remix-run/remix/tree/main/packages/node-fetch-server) - Adapt Fetch API handlers to existing `node:http`, `node:https`, and `node:http2` servers
|
|
245
|
-
- [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Route incoming `Request` objects to Fetch API handlers
|
|
246
|
-
|
|
247
|
-
## Related Work
|
|
248
|
-
|
|
249
|
-
- [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) - Web standard `Request` and `Response` primitives
|
|
250
|
-
|
|
251
|
-
## License
|
|
252
|
-
|
|
253
|
-
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|
package/src/node-serve.ts
DELETED