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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remix",
3
- "version": "3.0.0-beta.1",
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/async-context-middleware": "^0.3.0",
477
- "@remix-run/assets": "^0.4.0",
478
- "@remix-run/auth-middleware": "^0.2.0",
479
- "@remix-run/auth": "^0.2.2",
480
- "@remix-run/ui": "^0.1.2",
481
- "@remix-run/compression-middleware": "^0.1.8",
482
- "@remix-run/cors-middleware": "^0.1.3",
483
- "@remix-run/cop-middleware": "^0.1.3",
484
- "@remix-run/cookie": "^0.5.2",
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.1",
492
- "@remix-run/file-storage": "^0.13.4",
493
- "@remix-run/file-storage-s3": "^0.1.1",
494
- "@remix-run/fetch-router": "^0.19.0",
495
- "@remix-run/form-data-parser": "^0.17.1",
496
- "@remix-run/fs": "^0.4.3",
497
- "@remix-run/form-data-middleware": "^0.3.0",
498
- "@remix-run/headers": "^0.20.0",
499
- "@remix-run/html-template": "^0.3.0",
500
- "@remix-run/lazy-file": "^5.0.3",
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.1",
503
- "@remix-run/method-override-middleware": "^0.1.8",
504
- "@remix-run/logger-middleware": "^0.3.0",
505
- "@remix-run/node-fetch-server": "^0.13.2",
506
- "@remix-run/node-tsx": "^0.1.0",
507
- "@remix-run/response": "^0.3.4",
508
- "@remix-run/session": "^0.4.1",
509
- "@remix-run/session-middleware": "^0.3.0",
510
- "@remix-run/route-pattern": "^0.21.0",
511
- "@remix-run/session-storage-redis": "^0.1.0",
512
- "@remix-run/static-middleware": "^0.4.9",
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/terminal": "^0.1.0",
519
- "@remix-run/render-middleware": "^0.1.0",
520
- "@remix-run/node-serve": "^0.2.0"
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
- // All of the original file's metadata is intact
36
- fileFromStorage.name // 'hello.txt'
37
- fileFromStorage.type // 'text/plain'
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 `File` objects that are uploaded, 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.
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 { LocalFileStorage } from 'remix/file-storage/local'
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 = new LocalFileStorage('/uploads/user-avatars')
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
- await fileStorage.set(storageKey, fileUpload)
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
@@ -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 `Map<name, value>`.
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
- function Layout(props: { children: RemixNode }) {
139
- return (
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(props: { children: RemixNode }) {
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({ id, label }: { id: string; label: string }) {
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({ expanded }: { expanded: boolean }) {
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
  {
@@ -1,6 +1,6 @@
1
1
  # Server
2
2
 
3
- Remix Component can render to HTML on the server using two APIs:
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` prop 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.
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
 
@@ -1,2 +0,0 @@
1
- export * from '@remix-run/node-serve';
2
- //# sourceMappingURL=node-serve.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"node-serve.d.ts","sourceRoot":"","sources":["../src/node-serve.ts"],"names":[],"mappings":"AACA,cAAc,uBAAuB,CAAA"}
@@ -1,2 +0,0 @@
1
- // IMPORTANT: This file is auto-generated, please do not edit manually.
2
- export * from '@remix-run/node-serve';
@@ -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
@@ -1,2 +0,0 @@
1
- // IMPORTANT: This file is auto-generated, please do not edit manually.
2
- export * from '@remix-run/node-serve'