@marko/run 0.0.1-beta2 → 0.0.1-beta4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -74
- package/dist/adapter/default-entry.mjs +9 -3
- package/dist/adapter/dev-server.d.ts +1 -1
- package/dist/adapter/index.cjs +189 -8
- package/dist/adapter/index.d.ts +1 -0
- package/dist/adapter/index.js +188 -7
- package/dist/adapter/middleware.cjs +199 -0
- package/dist/adapter/middleware.d.ts +55 -0
- package/dist/adapter/middleware.js +168 -0
- package/dist/cli/default.config.mjs +1 -1
- package/dist/cli/index.mjs +88 -59
- package/dist/runtime/index.cjs +0 -16
- package/dist/runtime/index.d.ts +10 -2
- package/dist/runtime/index.js +0 -7
- package/dist/runtime/internal.cjs +148 -0
- package/dist/runtime/internal.d.ts +10 -0
- package/dist/runtime/internal.js +115 -0
- package/dist/runtime/router.cjs +9 -7
- package/dist/runtime/router.d.ts +4 -4
- package/dist/runtime/router.js +7 -5
- package/dist/runtime/types.d.ts +29 -16
- package/dist/vite/codegen/index.d.ts +4 -3
- package/dist/vite/codegen/writer.d.ts +1 -1
- package/dist/vite/constants.d.ts +3 -2
- package/dist/vite/index.cjs +557 -282
- package/dist/vite/index.d.ts +4 -3
- package/dist/vite/index.js +554 -282
- package/dist/vite/types.d.ts +14 -7
- package/dist/vite/utils/config.d.ts +5 -3
- package/dist/vite/utils/server.d.ts +3 -1
- package/package.json +23 -11
package/README.md
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
|
-
<
|
|
1
|
+
<div align="center">
|
|
2
2
|
<!-- Logo -->
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
<h1>
|
|
4
|
+
<img alt="" src="https://user-images.githubusercontent.com/4985201/115444712-ca550500-a1c9-11eb-9897-238ece59129c.png" height="118"/>
|
|
5
|
+
<br/>
|
|
6
|
+
@marko/run
|
|
7
|
+
</h1>
|
|
7
8
|
|
|
8
9
|
<!-- Language -->
|
|
9
|
-
<a href="
|
|
10
|
+
<a href="https://www.typescriptlang.org">
|
|
10
11
|
<img src="https://img.shields.io/badge/%3C%2F%3E-typescript-blue.svg" alt="TypeScript"/>
|
|
11
12
|
</a>
|
|
12
|
-
</
|
|
13
|
+
</div>
|
|
13
14
|
|
|
14
|
-
Vite plugin for Marko with these features
|
|
15
|
-
|
|
15
|
+
`@marko/serve` is a Vite plugin for [Marko](https://markojs.com), with these features:
|
|
16
|
+
|
|
17
|
+
- Encapsulates [`@marko/vite`](https://github.com/marko-js/vite)
|
|
16
18
|
- File-based routing with layouts and middleware
|
|
17
19
|
- Efficient routing using a compiled static trie
|
|
18
|
-
- Designed with web standards to run anywhere
|
|
20
|
+
- [Designed with web standards](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern/URLPattern) to run anywhere
|
|
19
21
|
|
|
20
|
-
##
|
|
22
|
+
## Installation
|
|
21
23
|
|
|
22
|
-
```
|
|
24
|
+
```sh
|
|
23
25
|
npm install @marko/run
|
|
24
26
|
```
|
|
25
27
|
|
|
26
28
|
## Vite Plugin
|
|
27
29
|
|
|
28
|
-
This package
|
|
30
|
+
This package’s Vite plugin discovers your route files, generates the routing code, and registers the `@marko/vite` plugin to compile your `.marko` files.
|
|
29
31
|
|
|
30
32
|
```ts
|
|
31
33
|
// vite.config.ts
|
|
@@ -35,27 +37,32 @@ import marko from "@marko/run/vite"; // Import the Vite plugin
|
|
|
35
37
|
export default defineConfig({
|
|
36
38
|
plugins: [marko()], // Register the Vite plugin
|
|
37
39
|
build: {
|
|
38
|
-
sourcemap: true, // Generate sourcemaps for all builds
|
|
39
|
-
emptyOutDir: false, // Avoid server & client deleting files from each other.
|
|
40
|
+
sourcemap: true, // Generate sourcemaps for all builds
|
|
41
|
+
emptyOutDir: false, // Avoid server & client deleting files from each other. TODO: do we have to make the user set this themselves?
|
|
40
42
|
}
|
|
41
43
|
})
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
## Runtime
|
|
45
47
|
|
|
46
|
-
Generally
|
|
48
|
+
Generally, using one of the adapters is more convenient <!-- TODO: is an “adapter” the same thing as using the Vite plugin above? -->, but this package also provides a runtime API:
|
|
47
49
|
|
|
48
50
|
```ts
|
|
49
51
|
import { router, getMatchedRoute } from '@marko/run`;
|
|
50
52
|
```
|
|
51
53
|
|
|
52
54
|
### `router`
|
|
55
|
+
|
|
53
56
|
```ts
|
|
54
57
|
(request: Request) => Promise<Response>;
|
|
55
58
|
```
|
|
56
|
-
|
|
59
|
+
|
|
60
|
+
This asynchronous function takes a [WHATWG `Request` object](https://fetch.spec.whatwg.org/#request-class) and returns a [`Response` object](https://fetch.spec.whatwg.org/#response-class) generated by executing any matched route files.
|
|
61
|
+
|
|
62
|
+
If no match is found, returns a response with a `404` status code. If an unhandled error occurs, returns a response with a `500` status code.
|
|
57
63
|
|
|
58
64
|
### `getMatchedRoute`
|
|
65
|
+
|
|
59
66
|
```ts
|
|
60
67
|
(method: string, url: URL) => {
|
|
61
68
|
params: Record<string, string>;
|
|
@@ -63,20 +70,25 @@ This asynchronouse function takes a [WHATWG request](https://fetch.spec.whatwg.o
|
|
|
63
70
|
invoke(request: Request): Promise<Response>;
|
|
64
71
|
} | null;
|
|
65
72
|
```
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- `
|
|
73
|
+
|
|
74
|
+
This synchronous function takes an HTTP method + URL, then returns an object representing the best match — or `null` if no match is found.
|
|
75
|
+
|
|
76
|
+
- `params` - a `{ key: value }` collection of any path parameters for the route
|
|
77
|
+
- `meta` - metadata for the route
|
|
78
|
+
- `invoke` - an asynchronous function that takes a `Request` and returns the `Response` generated from matched route files.
|
|
79
|
+
> **Note**: unlike the top-level `router` function, errors will not be caught.
|
|
70
80
|
|
|
71
81
|
## File-based Routing
|
|
72
82
|
|
|
73
|
-
|
|
83
|
+
### Nested Routing
|
|
74
84
|
|
|
75
|
-
|
|
85
|
+
*🎗 TODO: provide a quick overview*
|
|
76
86
|
|
|
77
87
|
### Routes Directory
|
|
78
88
|
|
|
79
|
-
|
|
89
|
+
The plugin looks for route files in the configured **routes directory**. By default, that’s `./src/routes`, relative to the Vite config file.
|
|
90
|
+
|
|
91
|
+
To change what directory routes are found in:
|
|
80
92
|
|
|
81
93
|
```ts
|
|
82
94
|
// vite.config.ts
|
|
@@ -86,55 +98,56 @@ import marko from "@marko/run/vite";
|
|
|
86
98
|
export default defineConfig({
|
|
87
99
|
plugins: [marko({
|
|
88
100
|
routesDir: 'src/pages' // Use `./src/pages` (relative to this file) as the routes directory
|
|
89
|
-
})]
|
|
90
|
-
//...
|
|
101
|
+
})]
|
|
91
102
|
})
|
|
92
103
|
```
|
|
93
104
|
|
|
94
105
|
### Routeable Files
|
|
95
106
|
|
|
96
|
-
|
|
107
|
+
To allow for colocation of files that shouldn’t be served (like tests, assets, etc.), the router only recognizes certain filenames.
|
|
108
|
+
|
|
109
|
+
The following filenames will be discovered in any directory inside your application’s [routes directory](#routes-directory).
|
|
97
110
|
|
|
98
111
|
#### `+page.marko`
|
|
99
|
-
|
|
112
|
+
|
|
113
|
+
These files establish a route at the current directory path which will be served for `GET` requests with the HTML content of the page. Only one page may exists for any served path.
|
|
100
114
|
|
|
101
115
|
#### `+layout.marko`
|
|
102
|
-
These files provide a layout component which will wrap all nested layouts and pages.
|
|
103
116
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
</div>
|
|
116
|
-
```
|
|
117
|
-
</details>
|
|
117
|
+
These files provide a **layout component**, which will wrap all nested layouts and pages.
|
|
118
|
+
|
|
119
|
+
Layouts are like any other Marko component with no extra constraints. Each layout receives the request, path params, URL, and route metadata as input, as well as a `renderBody` which will be the next layout or page to project.
|
|
120
|
+
|
|
121
|
+
```marko
|
|
122
|
+
<main>
|
|
123
|
+
<h1>My Products</h1>
|
|
124
|
+
|
|
125
|
+
${input.renderBody} // render the page or layout here
|
|
126
|
+
</main>
|
|
127
|
+
```
|
|
118
128
|
|
|
119
129
|
#### `+handler.*`
|
|
120
|
-
|
|
130
|
+
|
|
131
|
+
These files establish a route at the current directory path which can handle requests for `GET`, `POST`, `PUT`, and `DELETE` HTTP methods. <!-- TODO: what about HEAD? -->
|
|
132
|
+
|
|
133
|
+
Typically, these will be `.js` or `.ts` files depending on your project. Like pages, only one handler may exist for any served path. A handler should export functions
|
|
121
134
|
|
|
122
135
|
<details>
|
|
123
136
|
<summary>More Info</summary>
|
|
124
137
|
|
|
125
|
-
- Valid exports are
|
|
126
|
-
- Each
|
|
127
|
-
|
|
128
|
-
|
|
138
|
+
- Valid exports are functions named `get`, `post`, `put`, or `del`.
|
|
139
|
+
- Each export receives a `context` and `next` argument, and should return a WHATWG `Response` either synchronously or asynchronously.
|
|
140
|
+
- The `context` argument contains the WHATWG request object, path parameters, URL, and route metadata.
|
|
141
|
+
- The `next` argument will call the page for get requests where applicable or return a `204` response.
|
|
129
142
|
|
|
130
|
-
```
|
|
143
|
+
```js
|
|
131
144
|
export function post(context, next) {
|
|
132
145
|
const { request, params, url, meta } = context;
|
|
133
|
-
return new Response('updated', { status: 200 });
|
|
146
|
+
return new Response('Successfully updated', { status: 200 });
|
|
134
147
|
}
|
|
135
148
|
|
|
136
149
|
export function put(context, next) {
|
|
137
|
-
return new Response('created', { status: 201 }); // handle the request
|
|
150
|
+
return new Response('Successfully created', { status: 201 }); // handle the request
|
|
138
151
|
}
|
|
139
152
|
|
|
140
153
|
export function get(context, next) {
|
|
@@ -142,29 +155,33 @@ These files establish a route at the current directory path which can handle req
|
|
|
142
155
|
}
|
|
143
156
|
|
|
144
157
|
export function del(context, next) {
|
|
145
|
-
return new Response('removed', { status: 204 });
|
|
158
|
+
return new Response('Successfully removed', { status: 204 });
|
|
146
159
|
}
|
|
147
160
|
```
|
|
148
161
|
</details>
|
|
149
162
|
|
|
150
163
|
|
|
151
|
-
#### `+
|
|
152
|
-
|
|
164
|
+
#### `+middleware.*`
|
|
165
|
+
|
|
166
|
+
These files are like layouts, but for handlers. Middleware get called before handlers and let you perform arbitrary work before and after.
|
|
167
|
+
|
|
168
|
+
> **Note**: Unlike handlers, middleware run for all HTTP methods.
|
|
153
169
|
|
|
154
170
|
<details>
|
|
155
171
|
<summary>More Info</summary>
|
|
156
172
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
- The `
|
|
173
|
+
Expects a `default` export that receives a `context` and `next` argument, and should return a WHATWG response either synchronously or asynchronously.
|
|
174
|
+
|
|
175
|
+
- The `context` argument contains the WHATWG `Request` object, path parameters, URL, and route metadata.
|
|
176
|
+
- The `next` argument will call the next middleware, handler, or page for the route.
|
|
160
177
|
|
|
161
178
|
```ts
|
|
162
179
|
export default async function(context, next) {
|
|
163
|
-
const requestName = `${ctx.request.method} ${ctx.url.href}`;
|
|
180
|
+
const requestName = `${ctx.request.method} ${ctx.url.href}`; // TODO: could this be just `ctx.url` with a `toString` that then grabs `.href`?
|
|
164
181
|
let success = true;
|
|
165
182
|
console.log(`${requestName} request started`)
|
|
166
183
|
try {
|
|
167
|
-
return await next(); //
|
|
184
|
+
return await next(); // Wait for subsequent middleware/handler/page
|
|
168
185
|
} catch (err) {
|
|
169
186
|
success = false;
|
|
170
187
|
throw err;
|
|
@@ -176,30 +193,42 @@ These files are analagous to layouts for handlers. Middlware get called before h
|
|
|
176
193
|
</details>
|
|
177
194
|
|
|
178
195
|
#### `+meta.*`
|
|
179
|
-
|
|
196
|
+
|
|
197
|
+
These files represent metadata to attach to the route. This metadata will be automatically provided on the the route `context` when invoking a route.
|
|
180
198
|
|
|
181
199
|
### Special Files
|
|
182
200
|
|
|
183
|
-
In addition to the files above which can be defined in any directory under the _routes directory_, there are some special files which can only be defined
|
|
201
|
+
In addition to the files above which can be defined in any directory under the _routes directory_, there are some special files which can only be defined at the top-level of the _routes directory_. <!-- TODO: do we want to keep this restriction? Having nested 404s would be handy for disambiguating things like “there’s no user with that name” or “that promotion wasn’t found, it may have expired” -->
|
|
202
|
+
|
|
203
|
+
These special pages are subject to a root layout file (`pages/+layout.marko` in the default configuration).
|
|
184
204
|
|
|
185
205
|
#### `+404.marko`
|
|
186
|
-
|
|
206
|
+
|
|
207
|
+
This special page responds to any request where:
|
|
208
|
+
|
|
209
|
+
- The `Accept` request header includes `text/html`
|
|
210
|
+
- *And* no other handler or page rendered the request
|
|
211
|
+
|
|
212
|
+
Responses with this page will have a `404` status code.
|
|
187
213
|
|
|
188
214
|
#### `+500.marko`
|
|
189
|
-
This is a special page wich will be rendered for any request whose `Accept` header includes `text/html` if an uncaught error occurs while handling the request. This page will be subject to the root layout file (ie. +layout.marko file in the _routes directory_). Responses with this page will have a `500` status code.
|
|
190
215
|
|
|
216
|
+
This special page responds to any request where:
|
|
217
|
+
|
|
218
|
+
- The `Accept` request header includes `text/html`
|
|
219
|
+
- *And* an uncaught error occurs while serving the request
|
|
220
|
+
|
|
221
|
+
Responses with this page will have a `500` status code.
|
|
191
222
|
|
|
192
223
|
### Execution Order
|
|
193
224
|
|
|
194
|
-
For a matched route the routable files execute in the following order
|
|
225
|
+
For a matched route, the routable files execute in the following order:
|
|
226
|
+
|
|
195
227
|
1. Middlewares from root-most to leaf-most
|
|
196
228
|
2. Handler
|
|
197
229
|
3. Layouts from root-most to leaf-most
|
|
198
230
|
4. Page
|
|
199
231
|
|
|
200
|
-
<details>
|
|
201
|
-
<summary>Diagram</summary>
|
|
202
|
-
|
|
203
232
|
```mermaid
|
|
204
233
|
sequenceDiagram
|
|
205
234
|
participant Middleware1
|
|
@@ -218,13 +247,12 @@ sequenceDiagram
|
|
|
218
247
|
Handler-->>Middleware2: Response
|
|
219
248
|
Middleware2-->>Middleware1: Response
|
|
220
249
|
```
|
|
221
|
-
</details>
|
|
222
250
|
|
|
223
251
|
### Path Structure
|
|
224
252
|
|
|
225
|
-
Within the _routes directory_ the directory structure will determine the path the route will be served. There are four types of directory names:
|
|
253
|
+
Within the _routes directory_, the directory structure will determine the path the route will be served. There are four types of directory names: static, pathless, dynamic, and catch-all.
|
|
226
254
|
|
|
227
|
-
|
|
255
|
+
1. **Static directories** - The most common type. Each static directory contributes its name as a segment in the route's served path, like a traditional fileserver. Unless a directory name matches the requirements for one of the below types, it defaults to a static directory.
|
|
228
256
|
|
|
229
257
|
Examples:
|
|
230
258
|
```
|
|
@@ -233,7 +261,7 @@ Within the _routes directory_ the directory structure will determine the path th
|
|
|
233
261
|
/projects
|
|
234
262
|
```
|
|
235
263
|
|
|
236
|
-
|
|
264
|
+
2. **Pathless directories** - These directories do **not** contribute their name to the route's served path. Directory names that start with an underscore (`_`) or directories named `index` will be a pathless directory.
|
|
237
265
|
|
|
238
266
|
Examples:
|
|
239
267
|
```
|
|
@@ -242,7 +270,7 @@ Within the _routes directory_ the directory structure will determine the path th
|
|
|
242
270
|
/index
|
|
243
271
|
```
|
|
244
272
|
|
|
245
|
-
|
|
273
|
+
3. **Dynamic directories** - These directories introduce a dynamic parameter to the route's served path and will match any value at that segment. Any directory name that starts with a single dollar sign (`$`) will be a dynamic directory, and the remaining directory name will be the parameter at runtime. If the directory name is exactly `$/`, the parameter will not exist at runtime but will be matched.
|
|
246
274
|
|
|
247
275
|
Examples:
|
|
248
276
|
```
|
|
@@ -251,9 +279,9 @@ Within the _routes directory_ the directory structure will determine the path th
|
|
|
251
279
|
/$
|
|
252
280
|
```
|
|
253
281
|
|
|
254
|
-
|
|
282
|
+
4. **Catch-all directories** - These directories are similar to dynamic directories and introduce a dynamic parameter, but instead of matching a single path segment, they match to the end of the path. Any directory that starts with two dollar signs (`$$`) will be a catch-all directory, and the remaining directory name will be the parameter at runtime. In the case of a directory named `$$/`, the parameter name at runtime will be `*`. Catch-all directories can be used to make `404` Not Found routes at any level, including the root.
|
|
255
283
|
|
|
256
|
-
Because catch-all directories
|
|
284
|
+
Because catch-all directories match any path segment and consume the rest of the path, you cannot nest route files in them and no further directories will be traversed.
|
|
257
285
|
|
|
258
286
|
Examples:
|
|
259
287
|
```
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import createStaticServe from "serve-static";
|
|
2
|
+
import compression from "compression";
|
|
2
3
|
import { createServer } from "http";
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
4
|
+
import createMiddleware from "@marko/run/adapter/middleware";
|
|
5
|
+
import { router } from "@marko/run/router";
|
|
5
6
|
|
|
6
7
|
const { PORT = 3456 } = process.env;
|
|
7
8
|
|
|
8
9
|
const dir = process.cwd();
|
|
9
|
-
const middleware = createMiddleware(
|
|
10
|
+
const middleware = createMiddleware(router);
|
|
11
|
+
const compress = compression({
|
|
12
|
+
threshold: 500,
|
|
13
|
+
});
|
|
10
14
|
const staticServe = createStaticServe(dir, {
|
|
11
15
|
index: false,
|
|
12
16
|
immutable: true,
|
|
@@ -14,5 +18,7 @@ const staticServe = createStaticServe(dir, {
|
|
|
14
18
|
});
|
|
15
19
|
|
|
16
20
|
createServer((req, res) =>
|
|
21
|
+
compress(req, res, () =>
|
|
17
22
|
staticServe(req, res, () => middleware(req, res))
|
|
23
|
+
)
|
|
18
24
|
).listen(PORT);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { ViteDevServer } from "vite";
|
|
2
|
-
import { type NodeMiddleware } from "
|
|
2
|
+
import { type NodeMiddleware } from "./middleware";
|
|
3
3
|
export declare function createViteDevMiddleware<T>(devServer: ViteDevServer, load: (prev: T | undefined) => Promise<T>, factory: (value: T) => NodeMiddleware): NodeMiddleware;
|
|
4
4
|
export declare function createDevServer(configFile?: string): Promise<import("vite").Connect.Server>;
|
package/dist/adapter/index.cjs
CHANGED
|
@@ -36,7 +36,173 @@ var import_url = require("url");
|
|
|
36
36
|
|
|
37
37
|
// src/adapter/dev-server.ts
|
|
38
38
|
var import_vite = require("vite");
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
// src/adapter/middleware.ts
|
|
41
|
+
var webStream = __toESM(require("stream/web"), 1);
|
|
42
|
+
var import_crypto = __toESM(require("@hattip/polyfills/crypto"), 1);
|
|
43
|
+
(0, import_crypto.default)();
|
|
44
|
+
for (const key of Object.keys(webStream)) {
|
|
45
|
+
if (!(key in global)) {
|
|
46
|
+
global[key] = webStream[key];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function getForwardedHeader(req, name) {
|
|
50
|
+
const value = req.headers["x-forwarded-" + name];
|
|
51
|
+
if (value) {
|
|
52
|
+
if (typeof value === "string") {
|
|
53
|
+
const index = value.indexOf(",");
|
|
54
|
+
return index < 0 ? value : value.slice(0, index);
|
|
55
|
+
}
|
|
56
|
+
return value[0];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function getOrigin(req, protocol, host, trustProxy) {
|
|
60
|
+
var _a;
|
|
61
|
+
protocol ?? (protocol = req.protocol || trustProxy && getForwardedHeader(req, "proto") || ((_a = req.socket) == null ? void 0 : _a.encrypted) && "https" || "http");
|
|
62
|
+
host ?? (host = trustProxy && getForwardedHeader(req, "host") || req.headers.host);
|
|
63
|
+
if (!host) {
|
|
64
|
+
if (process.env.NODE_ENV !== "production") {
|
|
65
|
+
host = "localhost";
|
|
66
|
+
console.warn(
|
|
67
|
+
`Could not automatically determine the origin host, using 'localhost'. Use the 'origin' option or the 'ORIGIN' environment variable to set the origin explicitly.`
|
|
68
|
+
);
|
|
69
|
+
} else {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Could not automatically determine the origin host. Use the 'origin' option or the 'ORIGIN' environment variable to set the origin explicitly.`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return `${protocol}://${host}`;
|
|
76
|
+
}
|
|
77
|
+
function createMiddleware(router, options = {}) {
|
|
78
|
+
const { trustProxy = process.env.TRUST_PROXY === "1" } = options;
|
|
79
|
+
let { origin = process.env.ORIGIN } = options;
|
|
80
|
+
let protocol;
|
|
81
|
+
let host;
|
|
82
|
+
if (origin) {
|
|
83
|
+
({ protocol, host } = new URL(origin));
|
|
84
|
+
protocol = protocol.slice(0, -1);
|
|
85
|
+
}
|
|
86
|
+
return async (req, res, next) => {
|
|
87
|
+
origin ?? (origin = getOrigin(req, protocol, host, trustProxy));
|
|
88
|
+
const url = new URL(req.url, origin);
|
|
89
|
+
const ip = req.ip || trustProxy && getForwardedHeader(req, "for") || req.socket.remoteAddress || "";
|
|
90
|
+
const requestContext = {
|
|
91
|
+
method: req.method,
|
|
92
|
+
url,
|
|
93
|
+
platform: {
|
|
94
|
+
ip,
|
|
95
|
+
request: req,
|
|
96
|
+
response: res,
|
|
97
|
+
setCookie(cookie) {
|
|
98
|
+
res.appendHeader("set-cookie", cookie);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
Object.defineProperty(requestContext, "request", {
|
|
103
|
+
get() {
|
|
104
|
+
const headers = req.headers;
|
|
105
|
+
const body = req.method === "GET" || req.method === "HEAD" ? void 0 : req.socket ? req : new ReadableStream({
|
|
106
|
+
start(controller) {
|
|
107
|
+
req.on("data", (chunk) => controller.enqueue(chunk));
|
|
108
|
+
req.on("end", () => controller.close());
|
|
109
|
+
req.on("error", (err) => controller.error(err));
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
const request = new Request(url, {
|
|
113
|
+
method: req.method,
|
|
114
|
+
headers,
|
|
115
|
+
body,
|
|
116
|
+
duplex: "half"
|
|
117
|
+
});
|
|
118
|
+
Object.defineProperty(this, "request", {
|
|
119
|
+
value: request,
|
|
120
|
+
enumerable: true,
|
|
121
|
+
configurable: true
|
|
122
|
+
});
|
|
123
|
+
return request;
|
|
124
|
+
},
|
|
125
|
+
enumerable: true,
|
|
126
|
+
configurable: true
|
|
127
|
+
});
|
|
128
|
+
const response = await router(requestContext);
|
|
129
|
+
if (!response) {
|
|
130
|
+
if (next) {
|
|
131
|
+
next();
|
|
132
|
+
} else {
|
|
133
|
+
res.statusCode = 404;
|
|
134
|
+
res.setHeader("content-length", "0");
|
|
135
|
+
res.end();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
res.statusCode = response.status;
|
|
141
|
+
for (const [key, value] of response.headers) {
|
|
142
|
+
if (key === "set-cookie") {
|
|
143
|
+
let sepIndex = value.indexOf(",") + 1;
|
|
144
|
+
if (!sepIndex) {
|
|
145
|
+
res.setHeader(key, value);
|
|
146
|
+
} else {
|
|
147
|
+
let index = 0;
|
|
148
|
+
do {
|
|
149
|
+
res.appendHeader(key, value.slice(index, sepIndex - 1));
|
|
150
|
+
index = sepIndex;
|
|
151
|
+
sepIndex = value.indexOf(",", sepIndex) + 1;
|
|
152
|
+
} while (sepIndex);
|
|
153
|
+
res.appendHeader(key, value.slice(index));
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
res.setHeader(key, value);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!response.body) {
|
|
160
|
+
if (!response.headers.has("content-length")) {
|
|
161
|
+
res.setHeader("content-length", "0");
|
|
162
|
+
}
|
|
163
|
+
res.end();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const reader = response.body.getReader();
|
|
167
|
+
if (res.destroyed) {
|
|
168
|
+
reader.cancel();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
res.on("close", cancel);
|
|
172
|
+
res.on("error", cancel);
|
|
173
|
+
write();
|
|
174
|
+
function cancel(error) {
|
|
175
|
+
res.off("close", cancel);
|
|
176
|
+
res.off("error", cancel);
|
|
177
|
+
reader.cancel(error).catch(() => {
|
|
178
|
+
});
|
|
179
|
+
error && res.destroy(error);
|
|
180
|
+
}
|
|
181
|
+
async function write() {
|
|
182
|
+
try {
|
|
183
|
+
while (true) {
|
|
184
|
+
const { done, value } = await reader.read();
|
|
185
|
+
if (done) {
|
|
186
|
+
res.end();
|
|
187
|
+
return;
|
|
188
|
+
} else if (!res.write(value)) {
|
|
189
|
+
res.once("drain", write);
|
|
190
|
+
return;
|
|
191
|
+
} else if (res.flush) {
|
|
192
|
+
res.flush();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
const error = err instanceof Error ? err : new Error("Error while writing to node response", {
|
|
197
|
+
cause: err
|
|
198
|
+
});
|
|
199
|
+
cancel(error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/adapter/dev-server.ts
|
|
40
206
|
function createViteDevMiddleware(devServer, load, factory) {
|
|
41
207
|
let value;
|
|
42
208
|
let middleware;
|
|
@@ -64,8 +230,8 @@ async function createDevServer(configFile) {
|
|
|
64
230
|
});
|
|
65
231
|
const middleware = createViteDevMiddleware(
|
|
66
232
|
devServer,
|
|
67
|
-
async () => (await devServer.ssrLoadModule("@marko/run/router")).
|
|
68
|
-
|
|
233
|
+
async () => (await devServer.ssrLoadModule("@marko/run/router")).router,
|
|
234
|
+
createMiddleware
|
|
69
235
|
);
|
|
70
236
|
return devServer.middlewares.use(middleware);
|
|
71
237
|
}
|
|
@@ -73,16 +239,30 @@ async function createDevServer(configFile) {
|
|
|
73
239
|
// src/vite/utils/server.ts
|
|
74
240
|
var import_net = __toESM(require("net"), 1);
|
|
75
241
|
var import_child_process = __toESM(require("child_process"), 1);
|
|
76
|
-
|
|
242
|
+
var import_dotenv = require("dotenv");
|
|
243
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
244
|
+
async function parseEnv(envFile) {
|
|
245
|
+
if (import_fs.default.existsSync(envFile)) {
|
|
246
|
+
const content = await import_fs.default.promises.readFile(envFile, "utf8");
|
|
247
|
+
return (0, import_dotenv.parse)(content);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function loadEnv(envFile) {
|
|
251
|
+
(0, import_dotenv.config)({ path: envFile });
|
|
252
|
+
}
|
|
253
|
+
async function spawnServer(cmd, port = 0, env, cwd = process.cwd(), wait = 3e4) {
|
|
77
254
|
if (port <= 0) {
|
|
78
255
|
port = await getAvailablePort();
|
|
79
256
|
}
|
|
257
|
+
if (typeof env === "string") {
|
|
258
|
+
env = await parseEnv(env);
|
|
259
|
+
}
|
|
80
260
|
const proc = import_child_process.default.spawn(cmd, {
|
|
81
261
|
cwd,
|
|
82
262
|
shell: true,
|
|
83
263
|
stdio: "inherit",
|
|
84
264
|
windowsHide: true,
|
|
85
|
-
env: { NODE_ENV: "development", ...process.env, PORT: `${port}` }
|
|
265
|
+
env: { ...env, NODE_ENV: "development", ...process.env, PORT: `${port}` }
|
|
86
266
|
});
|
|
87
267
|
const close = () => {
|
|
88
268
|
proc.unref();
|
|
@@ -136,7 +316,8 @@ function adapter() {
|
|
|
136
316
|
const entry = import_path.default.join(__dirname, "default-entry");
|
|
137
317
|
return entry;
|
|
138
318
|
},
|
|
139
|
-
async startDev(configFile, port) {
|
|
319
|
+
async startDev(configFile, port, envFile) {
|
|
320
|
+
envFile && await loadEnv(envFile);
|
|
140
321
|
const server = await createDevServer(configFile);
|
|
141
322
|
return new Promise((resolve) => {
|
|
142
323
|
const listener = server.listen(port, () => {
|
|
@@ -146,8 +327,8 @@ function adapter() {
|
|
|
146
327
|
});
|
|
147
328
|
});
|
|
148
329
|
},
|
|
149
|
-
async startPreview(dir, entry, port) {
|
|
150
|
-
const server = await spawnServer(`node ${entry}`, port, dir);
|
|
330
|
+
async startPreview(dir, entry, port, envFile) {
|
|
331
|
+
const server = await spawnServer(`node ${entry}`, port, envFile, dir);
|
|
151
332
|
console.log(`Preview server started: http://localhost:${server.port}`);
|
|
152
333
|
}
|
|
153
334
|
};
|
package/dist/adapter/index.d.ts
CHANGED