@marko/run 0.0.1-beta6 → 0.0.1-beta8

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 CHANGED
@@ -1,3 +1,6 @@
1
+ > **Warning**
2
+ > This project is in BETA - use at your own peril, but please do provide helpful feedback.
3
+
1
4
  <div align="center">
2
5
  <!-- Logo -->
3
6
  <h1>
@@ -12,7 +15,7 @@
12
15
  </a>
13
16
  </div>
14
17
 
15
- `@marko/run` is an application framework for [Marko](https://markojs.com), with these features:
18
+ `@marko/run` will help you get up and *running* with [Marko](https://markojs.com)
16
19
 
17
20
  - Vite plugin that encapsulates [`@marko/vite`](https://github.com/marko-js/vite)
18
21
  - CLI to simplify build modes
@@ -33,10 +36,12 @@ The package provides a command line tool `marko-run` which can be run using scri
33
36
 
34
37
  ### Getting Started / Zero Config
35
38
 
39
+
40
+
36
41
  `marko-run` makes it easy to get started without little to no config. The package ships with a default Vite config and node-based adapter that means a minimal project start can be:
37
42
  1. Install `@marko/run`
38
43
  2. Create file `src/routes/+page.marko`
39
- 3. Run `npx marko-run dev`
44
+ 3. Run `npx marko-run`
40
45
  4. Open browser to `http://localhost:3000`
41
46
 
42
47
  ### Commands
@@ -45,21 +50,21 @@ The package provides a command line tool `marko-run` which can be run using scri
45
50
  ```bash
46
51
  > npx marko-run dev
47
52
  ```
53
+ or (default command)
54
+ ```bash
55
+ > npx marko-run
56
+ ```
57
+
48
58
 
49
59
  **`build`** - Create a production build
50
60
  ```bash
51
61
  > npx marko-run build
52
62
  ```
53
63
 
54
- **`preview`** - Create a production build and serve
55
- ```bash
56
- > npx marko-run preview
57
- ```
58
- or (default command)
64
+ **`serve`** - Create a production build and serve
59
65
  ```bash
60
- > npx marko-run
66
+ > npx marko-run serve
61
67
  ```
62
-
63
68
  ## Vite Plugin
64
69
 
65
70
  This package’s Vite plugin discovers your route files, generates the routing code, and registers the `@marko/vite` plugin to compile your `.marko` files.
@@ -70,44 +75,91 @@ import { defineConfig } from "vite";
70
75
  import marko from "@marko/run/vite"; // Import the Vite plugin
71
76
 
72
77
  export default defineConfig({
73
- plugins: [marko()], // Register the Vite plugin
78
+ plugins: [marko()] // Register the Vite plugin
74
79
  })
75
80
  ```
76
81
 
77
82
  ## Adapters
78
83
 
79
- *🎗 TODO: provide a quick overview*
84
+ Adapters provide the means to change the development, build and preview process to fit different deployment platforms and runtimes while allowing authors to write idiomatic code.
85
+
86
+ ### Configure
87
+
88
+ Specify your adapter in the Vite config when registering the `@marko/run` plugin
89
+
90
+ ```ts
91
+ // vite.config.ts
92
+ import { defineConfig } from "vite";
93
+ import marko from "@marko/run/vite";
94
+ import netlify from "@makor/run-adapter-netlify" // Import the adapter
95
+
96
+ export default defineConfig({
97
+ plugins: [marko({
98
+ adapter: netlify({ edge: true }) // Configure and apply the adapter
99
+ })]
100
+ })
101
+ ```
102
+
103
+ ### Adapter List
104
+
105
+ - ### [@marko/run-adapter-node](./packages/adapters/node/README.md)
106
+ - ### [@marko/run-adapter-netlify](./packages/adapters/netlify/README.md)
107
+ - ### [@marko/run-adapter-static](./packages/adapters/static/README.md)
80
108
 
81
109
  ## Runtime
82
110
 
83
111
  Generally, when using an adapter, this runtime will be abstracted away.
84
112
 
113
+ <!-- TODO: Add examples -->
114
+ <!-- TODO: Split fetch and match + invoke in two sections and explain why you might use one or the other -->
115
+
85
116
  ```ts
86
- import { router, matchRoute, invokeRoute } from '@marko/run/router`;
117
+ import * as Run from '@marko/run/router`;
87
118
  ```
88
119
 
89
- ### `router`
120
+ ### Emdedding in Existing Server
121
+
122
+
123
+
124
+ ### `Run.fetch`
90
125
 
91
126
  ```ts
92
- interface RequestContext<T> {
93
- url: URL;
94
- method: string;
95
- request: Request;
96
- platform: T;
97
- }
127
+ async function fetch<T>(request: Request, platform: T) => Promise<Response | void>;
128
+ ```
129
+
98
130
 
99
- async function router(context: RequestContext) => Promise<Response | void>;
131
+
132
+ This asynchronous function takes a [WHATWG `Request` object](https://fetch.spec.whatwg.org/#request-class) object and an object containing any platform specific data you may want access to and returns the [WHATWG `Response` object](https://fetch.spec.whatwg.org/#response-class) from executing any matched route files or undefined if the request was explicitly not handled. If no route matches the requested path, a `404` status code response will be returned. If an error occurs a `500` status code response will be returned.
133
+
134
+ Express example:
135
+ ```ts
136
+ import express from "express";
137
+ import * as Run from "@marko/run/router";
138
+
139
+ express()
140
+ .use(async (req, res, next) => {
141
+ const request = // ...code to create a WHATWG Request from `req`
142
+
143
+ const response = await Run.fetch(request, {
144
+ req,
145
+ res
146
+ });
147
+
148
+ if (response) {
149
+ // ...code to apply response to `res`
150
+ } else {
151
+ next();
152
+ }
153
+ })
154
+ .listen(3000);
100
155
  ```
101
156
 
102
- This asynchronous function takes a context object and returns the [WHATWG `Response` object](https://fetch.spec.whatwg.org/#response-class) from executing any matched route files or undefined if the request was explicitly not handled. If no route matches the requested path, a `404` status code response will be returned. If an error occurs a `500` status code response will be returned.
103
157
 
104
- The context object contains the following properties
105
- - `url`: The URL reprensting the requested resource
106
- - `method`: The HTTP method used
107
- - `request`: [WHATWG `Request` object](https://fetch.spec.whatwg.org/#request-class)
108
- - `platform`: An object containing any platform-specific data (eg Node request/response) and will vary between adapters.
158
+ ### Other APIs
159
+
160
+ In some cases you might want more control over when route matching and invokation (creating a response) occur. For instance you may have middleware in your server which need to know if there is a matched route. The runtime provides these additional methods
109
161
 
110
- ### `matchRoute`
162
+ ### `Run.match`
111
163
 
112
164
  ```ts
113
165
  interface interface Route {
@@ -115,7 +167,7 @@ interface interface Route {
115
167
  meta: unknown;
116
168
  }
117
169
 
118
- function matchRoute(method: string, pathname: string) => Route | null;
170
+ function match(method: string, pathname: string) => Route | null;
119
171
  ```
120
172
 
121
173
  This synchronous function takes an HTTP method and path name, then returns an object representing the best match — or `null` if no match is found.
@@ -123,20 +175,57 @@ This synchronous function takes an HTTP method and path name, then returns an ob
123
175
  - `params` - a `{ key: value }` collection of any path parameters for the route
124
176
  - `meta` - metadata for the route
125
177
 
126
- ### `invokeRoute`
178
+ ### `Run.invoke`
127
179
 
128
180
  ```ts
129
- function invokeRoute(route: Route, context: RequestContext) => Promise<Response | void>;
181
+ async function invoke<T>(route: Route, request: Request, platform: T) => Promise<Response | void>;
182
+ ```
183
+ This asynchronous function takes a route object returned by [Run.match](#Run.match) the request and platform data and returns a response in the same way the [Run.fetch](#Run.fetch) does.
184
+
185
+ Express example:
186
+ ```ts
187
+ import express from "express";
188
+ import * as Run from "@marko/run/router";
189
+
190
+ express()
191
+ .use((req, res) => {
192
+ const matchedRoute = Run.match(req.method, req.path);
193
+ if (matchedRoute) {
194
+ req.match = matchedRoute;
195
+ }
196
+ })
197
+
198
+ // ...other middleware
199
+
200
+ .use(async (req, res, next) => {
201
+ // Check if a route was previously matched
202
+ if (!req.match) {
203
+ next();
204
+ return;
205
+ }
206
+
207
+ const request = // ...code to create a WHATWG Request from `req`
208
+ const response = await Run.invoke(req.match, request, {
209
+ req,
210
+ res
211
+ });
212
+
213
+ if (response) {
214
+ // ...code to apply response to `res`
215
+ } else {
216
+ next();
217
+ }
218
+ })
219
+ .listen(3000);
130
220
  ```
131
- This asynchronous function takes a route object returned by [matchRoute](#matchRoute) and a context object and returns a response in the same way the [router](#router) does.
132
221
 
133
222
 
134
223
 
135
224
  ## File-based Routing
136
225
 
137
- ### Nested Routing
226
+ <!-- ### Nested Routing
138
227
 
139
- *🎗 TODO: provide a quick overview*
228
+ *🎗 TODO: provide a quick overview* -->
140
229
 
141
230
  ### Routes Directory
142
231
 
@@ -189,26 +278,35 @@ Typically, these will be `.js` or `.ts` files depending on your project. Like pa
189
278
  <details>
190
279
  <summary>More Info</summary>
191
280
 
192
- - Valid exports are functions named `get`, `post`, `put`, or `del`.
193
- - Each export receives a `context` and `next` argument, and should return a WHATWG `Response` either synchronously or asynchronously.
194
- - The `context` argument contains the WHATWG request object, path parameters, URL, and route metadata.
195
- - The `next` argument will call the page for get requests where applicable or return a `204` response.
281
+ - Valid exports are functions named `GET`, `POST`, `PUT`, or `DELETE`.
282
+ - Exports can be one of the following
283
+ - Handler function (see below)
284
+ - Array of handler functions - will be composed by calling them in order
285
+ - Promise that resolves to a handler function or array of handler functions
286
+ - Handler functions are synchronous or asynchronous functions that
287
+ - Receives a `context` and `next` argument,
288
+ - The `context` argument contains the WHATWG request object, path parameters, URL, and route metadata.
289
+ - The `next` argument will call the page for get requests where applicable or return a `204` response.
290
+ - Return a WHATWG response, throw a WHATWG response, return undefined. If the function return's undefined the `next` argument with be automatically called and used as the response.
196
291
 
197
292
  ```js
198
- export function post(context, next) {
293
+ export function POST(context, next) {
199
294
  const { request, params, url, meta } = context;
200
295
  return new Response('Successfully updated', { status: 200 });
201
296
  }
202
297
 
203
- export function put(context, next) {
204
- return new Response('Successfully created', { status: 201 }); // handle the request
298
+ export function PUT(context, next) {
299
+ // `next` will be called for you by the runtime
205
300
  }
206
301
 
207
- export function get(context, next) {
208
- return next(); // Call the next handler
302
+ export async function GET(context, next) {
303
+ // do something before calling `next`
304
+ const response = await next();
305
+ // do something with the response from `next`
306
+ return response;
209
307
  }
210
308
 
211
- export function del(context, next) {
309
+ export function DELETE(context, next) {
212
310
  return new Response('Successfully removed', { status: 204 });
213
311
  }
214
312
  ```
@@ -224,18 +322,23 @@ These files are like layouts, but for handlers. Middleware get called before han
224
322
  <details>
225
323
  <summary>More Info</summary>
226
324
 
227
- Expects a `default` export that receives a `context` and `next` argument, and should return a WHATWG response either synchronously or asynchronously.
228
-
229
- - The `context` argument contains the WHATWG `Request` object, path parameters, URL, and route metadata.
230
- - The `next` argument will call the next middleware, handler, or page for the route.
325
+ - Expects a `default` export that can be one of the following
326
+ - Handler function (see below)
327
+ - Array of handler functions - will be composed by calling them in order
328
+ - Promise that resolves to a handler function or array of handler functions
329
+ - Handler functions are synchronous or asynchronous functions that
330
+ - Receives a `context` and `next` argument,
331
+ - The `context` argument contains the WHATWG request object, path parameters, URL, and route metadata.
332
+ - The `next` argument will call the page for get requests where applicable or return a `204` response.
333
+ - Return a WHATWG response, throw a WHATWG response, return undefined. If the function return's undefined the `next` argument with be automatically called and used as the response.
231
334
 
232
335
  ```ts
233
336
  export default async function(context, next) {
234
- const requestName = `${ctx.request.method} ${ctx.url.href}`; // TODO: could this be just `ctx.url` with a `toString` that then grabs `.href`?
337
+ const requestName = `${context.request.method} ${context.url.href}`;
235
338
  let success = true;
236
339
  console.log(`${requestName} request started`)
237
340
  try {
238
- return await next(); // Wait for subsequent middleware/handler/page
341
+ return await next(); // Wait for subsequent middleware, handler and page
239
342
  } catch (err) {
240
343
  success = false;
241
344
  throw err;
@@ -248,7 +351,9 @@ These files are like layouts, but for handlers. Middleware get called before han
248
351
 
249
352
  #### `+meta.*`
250
353
 
251
- These files represent metadata to attach to the route. This metadata will be automatically provided on the the route `context` when invoking a route.
354
+ These files represent static metadata to attach to the route. This metadata will be automatically provided on the the route `context` when invoking a route.
355
+
356
+
252
357
 
253
358
  ### Special Files
254
359
 
@@ -276,7 +381,21 @@ Responses with this page will have a `500` status code.
276
381
 
277
382
  ### Execution Order
278
383
 
279
- For a matched route, the routable files execute in the following order:
384
+ Given the following routes directory structure
385
+
386
+ <pre>
387
+ routes/
388
+ about/
389
+ +handler.js
390
+ +layout.marko
391
+ +middleware.js
392
+ +page.marko
393
+ +layout.marko
394
+ +middleware.js
395
+ +page.marko
396
+ </pre>
397
+
398
+ When the path `"/about"` is requested, the routable files execute in the following order:
280
399
 
281
400
  1. Middlewares from root-most to leaf-most
282
401
  2. Handler
@@ -285,21 +404,21 @@ For a matched route, the routable files execute in the following order:
285
404
 
286
405
  ```mermaid
287
406
  sequenceDiagram
288
- participant Middleware1
289
- participant Middleware2
290
- participant Handler
291
- participant Layout1
292
- participant Layout2
293
- participant Page
294
- Note over Layout1,Page: Combined at build-time as a single component
295
- Middleware1->>Middleware2: next()
296
- Middleware2->>Handler: next()
297
- Handler->>Layout1: next()
298
- Layout1->Layout2: ${input.renderBody}
299
- Layout2->Page: ${input.renderBody}
300
- Layout1-->>Handler: Stream Response
301
- Handler-->>Middleware2: Response
302
- Middleware2-->>Middleware1: Response
407
+ participant MW1 as routes/+middleware.js
408
+ participant MW2 as routes/about/+middleware.js
409
+ participant H as routes/about/+handler.js
410
+ participant L1 as routes/+layout.marko
411
+ participant L2 as routes/about/+layout.marko
412
+ participant P as routes/about/+page.marko
413
+ Note over L1,P: Combined at build-time as a single component
414
+ MW1->>MW2: next()
415
+ MW2->>H: next()
416
+ H->>L1: next()
417
+ L1->L2: ${input.renderBody}
418
+ L2->P: ${input.renderBody}
419
+ L1-->>H: Stream Response
420
+ H-->>MW2: Response
421
+ MW2-->>MW1: Response
303
422
  ```
304
423
 
305
424
  ### Path Structure
@@ -315,16 +434,15 @@ Within the _routes directory_, the directory structure will determine the path t
315
434
  /projects
316
435
  ```
317
436
 
318
- 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.
437
+ 2. **Pathless directories** - These directories do **not** contribute their name to the route's served path. Directory names that start with an underscore (`_`) will be a pathless directory.
319
438
 
320
439
  Examples:
321
440
  ```
322
441
  /_users
323
442
  /_public
324
- /index
325
443
  ```
326
444
 
327
- 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.
445
+ 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 be captured but it will be matched.
328
446
 
329
447
  Examples:
330
448
  ```
@@ -333,7 +451,7 @@ Within the _routes directory_, the directory structure will determine the path t
333
451
  /$
334
452
  ```
335
453
 
336
- 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.
454
+ 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 will not be captured but it will match. Catch-all directories can be used to make `404` Not Found routes at any level, including the root.
337
455
 
338
456
  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.
339
457
 
@@ -344,6 +462,43 @@ Within the _routes directory_, the directory structure will determine the path t
344
462
  /$$
345
463
  ```
346
464
 
347
- ### Match Ranking
465
+ <!-- ### Match Ranking
466
+
467
+ *TODO: Write some things* -->
468
+
469
+
470
+ ## TypeScript
471
+
472
+
473
+ ### Global Namespace
474
+ marko/run provides a global namespace `MarkoRun` with the folling types:
475
+
476
+ **`MarkoRun.Handler`** - Type that represents a handler function to be exported by a +handler or +middleware file
477
+
478
+ **`MarkoRun.CurrentRoute`** - Type of the route's params and meta data
479
+
480
+ **`MarkoRun.CurrentContext`** - Type of the request context object in a handler and `out.global` in your Marko files
481
+
482
+
483
+ ### Generated Types
484
+ If a [TSConfig](https://www.typescriptlang.org/tsconfig) file is discovered in the project root, the Vite plugin will automatically generate a .d.ts file which provides more specific types for each of your middleware, handlers, layouts and pages. This file will be generated at `.marko-run/routes.d.ts` whenever the project is built - including dev.
485
+ > **Note** TypeScript will not include this file by default. If you are not using the [Marko VSCode plugin](https://marketplace.visualstudio.com/items?itemName=Marko-JS.marko-vscode) and you will need to [add it in your tsconfig](https://www.typescriptlang.org/tsconfig#include).
486
+
487
+ These types are replaced with more specific versions per routeable file:
488
+
489
+ **`MarkoRun.Handler`**
490
+ - Overrides context with specific MarkoRun.CurrentContext
491
+
492
+ **`MarkoRun.CurrentRoute`**
493
+ - Adds specific parameters and meta types
494
+ - In middleware and layouts which are used in many routes, this type will be a union of all possible routes that file will see
495
+
496
+ **`MarkoRun.CurrentContext`**
497
+ - In middleware and layouts which are used in many routes, this type will be a union of all possible routes that file will see.
498
+ - When an adapter is used, it can provide types for the platform
499
+
500
+ ## Beta Roadmap
348
501
 
349
- *TODO: Write some things*
502
+ - Error handling
503
+ - Error component
504
+ - Redirect component
@@ -1,17 +1,20 @@
1
1
  import createStaticServe from "serve-static";
2
2
  import compression from "compression";
3
3
  import { createServer } from "http";
4
- import createMiddleware from "@marko/run/adapter/middleware";
5
- import { router } from "@marko/run/router";
4
+ import { createMiddleware } from "@marko/run/adapter/middleware";
5
+ import { fetch } from "@marko/run/router";
6
+ import { dirname } from 'path';
7
+ import { fileURLToPath } from 'url';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
10
 
7
11
  const { PORT = 3456 } = process.env;
8
12
 
9
- const dir = process.cwd();
10
- const middleware = createMiddleware(router);
13
+ const middleware = createMiddleware(fetch);
11
14
  const compress = compression({
12
15
  threshold: 500,
13
16
  });
14
- const staticServe = createStaticServe(dir, {
17
+ const staticServe = createStaticServe(__dirname, {
15
18
  index: false,
16
19
  immutable: true,
17
20
  maxAge: "365 days",
@@ -94,8 +94,8 @@ function getOrigin(req, protocol, host, trustProxy) {
94
94
  }
95
95
  return `${protocol}://${host}`;
96
96
  }
97
- function createMiddleware(router, options = {}) {
98
- const { trustProxy = process.env.TRUST_PROXY === "1" } = options;
97
+ function createMiddleware(fetch2, options = {}) {
98
+ const { trustProxy = process.env.TRUST_PROXY === "1", devServer } = options;
99
99
  let { origin = process.env.ORIGIN } = options;
100
100
  let protocol;
101
101
  let host;
@@ -107,45 +107,28 @@ function createMiddleware(router, options = {}) {
107
107
  origin ?? (origin = getOrigin(req, protocol, host, trustProxy));
108
108
  const url = new URL(req.url, origin);
109
109
  const ip = req.ip || trustProxy && getForwardedHeader(req, "for") || req.socket.remoteAddress || "";
110
- const requestContext = {
110
+ const headers = req.headers;
111
+ const body = req.method === "GET" || req.method === "HEAD" ? void 0 : req.socket ? req : new ReadableStream({
112
+ start(controller) {
113
+ req.on("data", (chunk) => controller.enqueue(chunk));
114
+ req.on("end", () => controller.close());
115
+ req.on("error", (err) => controller.error(err));
116
+ }
117
+ });
118
+ const request = new Request(url, {
111
119
  method: req.method,
112
- url,
113
- platform: {
114
- ip,
115
- request: req,
116
- response: res,
117
- setCookie(cookie) {
118
- res.appendHeader("set-cookie", cookie);
119
- }
120
+ headers,
121
+ body,
122
+ duplex: "half"
123
+ });
124
+ const response = await fetch2(request, {
125
+ ip,
126
+ request: req,
127
+ response: res,
128
+ setCookie(cookie) {
129
+ res.appendHeader("set-cookie", cookie);
120
130
  }
121
- };
122
- Object.defineProperty(requestContext, "request", {
123
- get() {
124
- const headers = req.headers;
125
- const body = req.method === "GET" || req.method === "HEAD" ? void 0 : req.socket ? req : new ReadableStream({
126
- start(controller) {
127
- req.on("data", (chunk) => controller.enqueue(chunk));
128
- req.on("end", () => controller.close());
129
- req.on("error", (err) => controller.error(err));
130
- }
131
- });
132
- const request = new Request(url, {
133
- method: req.method,
134
- headers,
135
- body,
136
- duplex: "half"
137
- });
138
- Object.defineProperty(this, "request", {
139
- value: request,
140
- enumerable: true,
141
- configurable: true
142
- });
143
- return request;
144
- },
145
- enumerable: true,
146
- configurable: true
147
131
  });
148
- const response = await router(requestContext);
149
132
  if (!response) {
150
133
  if (next) {
151
134
  next();
@@ -196,7 +179,17 @@ function createMiddleware(router, options = {}) {
196
179
  res.off("error", cancel);
197
180
  reader.cancel(error).catch(() => {
198
181
  });
199
- error && res.destroy(error);
182
+ if (error) {
183
+ if (process.env.NODE_ENV !== "production" && devServer) {
184
+ res.end();
185
+ devServer.ws.send({
186
+ type: "error",
187
+ err: { message: error.message, stack: error.stack || "" }
188
+ });
189
+ } else {
190
+ res.destroy(error);
191
+ }
192
+ }
200
193
  }
201
194
  async function write() {
202
195
  try {
@@ -223,6 +216,7 @@ function createMiddleware(router, options = {}) {
223
216
  }
224
217
 
225
218
  // src/adapter/dev-server.ts
219
+ var fixedErrors = /* @__PURE__ */ new WeakSet();
226
220
  function createViteDevMiddleware(devServer, load, factory) {
227
221
  let value;
228
222
  let middleware;
@@ -235,10 +229,16 @@ function createViteDevMiddleware(devServer, load, factory) {
235
229
  }
236
230
  await middleware(req, res, next);
237
231
  } catch (err) {
232
+ res.statusCode = 500;
238
233
  if (err instanceof Error) {
239
- devServer.ssrFixStacktrace(err);
234
+ if (!fixedErrors.has(err)) {
235
+ fixedErrors.add(err);
236
+ devServer.ssrFixStacktrace(err);
237
+ }
238
+ res.end(err.stack);
239
+ } else {
240
+ res.end();
240
241
  }
241
- return next == null ? void 0 : next();
242
242
  }
243
243
  };
244
244
  }
@@ -246,12 +246,16 @@ async function createDevServer(configFile) {
246
246
  const devServer = await (0, import_vite.createServer)({
247
247
  configFile,
248
248
  appType: "custom",
249
- server: { middlewareMode: true }
249
+ server: { middlewareMode: true },
250
+ resolve: {
251
+ dedupe: ["marko"],
252
+ conditions: ["worker"]
253
+ }
250
254
  });
251
255
  const middleware = createViteDevMiddleware(
252
256
  devServer,
253
- async () => (await devServer.ssrLoadModule("@marko/run/router")).router,
254
- createMiddleware
257
+ async () => await devServer.ssrLoadModule("@marko/run/router"),
258
+ (module2) => createMiddleware(module2.fetch, { devServer })
255
259
  );
256
260
  return devServer.middlewares.use(middleware);
257
261
  }
@@ -347,8 +351,8 @@ function adapter() {
347
351
  });
348
352
  });
349
353
  },
350
- async startPreview(dir, entry, port, envFile) {
351
- const server = await spawnServer(`node ${entry}`, port, envFile, dir);
354
+ async startPreview(_dir, entry, port, envFile) {
355
+ const server = await spawnServer(`node ${entry}`, port, envFile);
352
356
  console.log(`Preview server started: http://localhost:${server.port}`);
353
357
  }
354
358
  };