@marko/run 0.0.1-beta8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +301 -283
- package/dist/.tsbuildinfo +1 -0
- package/dist/adapter/dev-server.d.ts +1 -1
- package/dist/adapter/index.cjs +4 -185
- package/dist/adapter/index.js +4 -185
- package/dist/adapter/middleware.cjs +34 -6
- package/dist/adapter/middleware.d.ts +1 -3
- package/dist/adapter/middleware.js +34 -6
- package/dist/adapter/polyfill.d.ts +5 -0
- package/dist/cli/default.config.mjs +2 -2
- package/dist/cli/index.mjs +3 -1
- package/dist/runtime/index.d.ts +8 -8
- package/dist/runtime/internal.cjs +11 -2
- package/dist/runtime/internal.d.ts +6 -5
- package/dist/runtime/internal.js +9 -1
- package/dist/runtime/types.d.ts +3 -3
- package/dist/vite/index.cjs +23 -34
- package/dist/vite/index.js +23 -34
- package/package.json +18 -9
package/README.md
CHANGED
|
@@ -1,226 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
> This project is in BETA - use at your own peril, but please do provide helpful feedback.
|
|
1
|
+
|
|
3
2
|
|
|
4
3
|
<div align="center">
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
</
|
|
11
|
-
|
|
12
|
-
<!-- Language -->
|
|
13
|
-
<a href="https://www.typescriptlang.org">
|
|
14
|
-
<img src="https://img.shields.io/badge/%3C%2F%3E-typescript-blue.svg" alt="TypeScript"/>
|
|
15
|
-
</a>
|
|
4
|
+
<picture>
|
|
5
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/marko-js/run/raw/main/assets/marko-run-darkmode.png">
|
|
6
|
+
<source media="(prefers-color-scheme: light)" srcset="https://github.com/marko-js/run/raw/main/assets/marko-run.png">
|
|
7
|
+
<img alt="Marko Run Logo" src="https://github.com/marko-js/run/raw/main/assets/marko-run.png" width="400">
|
|
8
|
+
</picture>
|
|
9
|
+
<h4>Get your app up and <em>running</em> with <a href="https://markojs.com">Marko</a>!</h4>
|
|
16
10
|
</div>
|
|
17
11
|
|
|
18
|
-
|
|
12
|
+
## Features
|
|
13
|
+
* 🚀 Fastest way to build a Marko app
|
|
14
|
+
* 💖 Scales from zero configuration
|
|
15
|
+
* ⚡️ Pages live-reload as you make changes
|
|
16
|
+
* 📁 Directory-based routes, layouts, and middleware
|
|
17
|
+
* 🖌️ TypeScript powered editor support
|
|
18
|
+
* 🧬 [Designed with web standards](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern/URLPattern) to run anywhere
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
And when you build your production-ready app:
|
|
21
|
+
|
|
22
|
+
* 🔥 Blazing fast server-side rendering
|
|
23
|
+
* 🌊 Streams content to users ASAP
|
|
24
|
+
* 📦 Partial hydration & automatic code splitting
|
|
25
|
+
* 🚢 Deploy to multiple platforms
|
|
26
26
|
|
|
27
27
|
## Installation
|
|
28
|
+
> **Warning**
|
|
29
|
+
> This project is in BETA - use at your own peril, but please do provide helpful feedback.
|
|
30
|
+
|
|
28
31
|
|
|
29
32
|
```sh
|
|
30
33
|
npm install @marko/run
|
|
31
34
|
```
|
|
32
35
|
|
|
33
|
-
## CLI
|
|
34
|
-
|
|
35
|
-
The package provides a command line tool `marko-run` which can be run using scripts in your package.json or with npx.
|
|
36
|
-
|
|
37
36
|
### Getting Started / Zero Config
|
|
38
37
|
|
|
38
|
+
`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.
|
|
39
39
|
|
|
40
|
+
To get started from a template:
|
|
41
|
+
1. `npm init marko -- -t basic`
|
|
42
|
+
2. `cd ./<PROJECT_NAME>`
|
|
43
|
+
4. `npm run dev`
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
Or manually create a project:
|
|
42
46
|
1. Install `@marko/run`
|
|
43
47
|
2. Create file `src/routes/+page.marko`
|
|
44
|
-
3. Run `
|
|
45
|
-
4. Open browser to `http://localhost:3000`
|
|
46
|
-
|
|
47
|
-
### Commands
|
|
48
|
-
|
|
49
|
-
**`dev`** - Start development server in watch mode
|
|
50
|
-
```bash
|
|
51
|
-
> npx marko-run dev
|
|
52
|
-
```
|
|
53
|
-
or (default command)
|
|
54
|
-
```bash
|
|
55
|
-
> npx marko-run
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
**`build`** - Create a production build
|
|
60
|
-
```bash
|
|
61
|
-
> npx marko-run build
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
**`serve`** - Create a production build and serve
|
|
65
|
-
```bash
|
|
66
|
-
> npx marko-run serve
|
|
67
|
-
```
|
|
68
|
-
## Vite Plugin
|
|
69
|
-
|
|
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.
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
// vite.config.ts
|
|
74
|
-
import { defineConfig } from "vite";
|
|
75
|
-
import marko from "@marko/run/vite"; // Import the Vite plugin
|
|
76
|
-
|
|
77
|
-
export default defineConfig({
|
|
78
|
-
plugins: [marko()] // Register the Vite plugin
|
|
79
|
-
})
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Adapters
|
|
83
|
-
|
|
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)
|
|
108
|
-
|
|
109
|
-
## Runtime
|
|
110
|
-
|
|
111
|
-
Generally, when using an adapter, this runtime will be abstracted away.
|
|
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
|
-
|
|
116
|
-
```ts
|
|
117
|
-
import * as Run from '@marko/run/router`;
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Emdedding in Existing Server
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
### `Run.fetch`
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
async function fetch<T>(request: Request, platform: T) => Promise<Response | void>;
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
|
|
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";
|
|
48
|
+
3. Run `npm exec marko-run`
|
|
138
49
|
|
|
139
|
-
|
|
140
|
-
.use(async (req, res, next) => {
|
|
141
|
-
const request = // ...code to create a WHATWG Request from `req`
|
|
50
|
+
Finally open `http://localhost:3000` 🚀
|
|
142
51
|
|
|
143
|
-
|
|
144
|
-
req,
|
|
145
|
-
res
|
|
146
|
-
});
|
|
52
|
+
### CLI
|
|
147
53
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
next();
|
|
152
|
-
}
|
|
153
|
-
})
|
|
154
|
-
.listen(3000);
|
|
54
|
+
**`dev`** - Start a development server in watch mode
|
|
55
|
+
```sh
|
|
56
|
+
> npm exec marko-run
|
|
155
57
|
```
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
161
|
-
|
|
162
|
-
### `Run.match`
|
|
163
|
-
|
|
164
|
-
```ts
|
|
165
|
-
interface interface Route {
|
|
166
|
-
params: Record<string, string>;
|
|
167
|
-
meta: unknown;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function match(method: string, pathname: string) => Route | null;
|
|
58
|
+
or (with explicit sub command)
|
|
59
|
+
```sh
|
|
60
|
+
> npm exec marko-run dev
|
|
171
61
|
```
|
|
172
62
|
|
|
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.
|
|
174
63
|
|
|
175
|
-
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
### `Run.invoke`
|
|
179
|
-
|
|
180
|
-
```ts
|
|
181
|
-
async function invoke<T>(route: Route, request: Request, platform: T) => Promise<Response | void>;
|
|
64
|
+
**`build`** - Create a production build
|
|
65
|
+
```sh
|
|
66
|
+
> npm exec marko-run build
|
|
182
67
|
```
|
|
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
68
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
res
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
if (response) {
|
|
214
|
-
// ...code to apply response to `res`
|
|
215
|
-
} else {
|
|
216
|
-
next();
|
|
217
|
-
}
|
|
218
|
-
})
|
|
219
|
-
.listen(3000);
|
|
69
|
+
**`preview`** - Create a production build and start the preview server
|
|
70
|
+
```sh
|
|
71
|
+
> npm exec marko-run preview
|
|
220
72
|
```
|
|
221
73
|
|
|
222
|
-
|
|
223
|
-
|
|
224
74
|
## File-based Routing
|
|
225
75
|
|
|
226
76
|
<!-- ### Nested Routing
|
|
@@ -247,25 +97,23 @@ export default defineConfig({
|
|
|
247
97
|
|
|
248
98
|
### Routeable Files
|
|
249
99
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
The following filenames will be discovered in any directory inside your application’s [routes directory](#routes-directory).
|
|
100
|
+
The router only recognizes certain filenames which are all prefixed with `+` ([Why?](#What-about-markoserve)). The following filenames will be discovered in any directory inside your application’s [routes directory](#routes-directory).
|
|
253
101
|
|
|
254
102
|
#### `+page.marko`
|
|
255
103
|
|
|
256
|
-
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
|
|
104
|
+
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 exist for any served path.
|
|
257
105
|
|
|
258
106
|
#### `+layout.marko`
|
|
259
107
|
|
|
260
108
|
These files provide a **layout component**, which will wrap all nested layouts and pages.
|
|
261
109
|
|
|
262
|
-
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
|
|
110
|
+
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 refers to the nested page that is being rendered.
|
|
263
111
|
|
|
264
112
|
```marko
|
|
265
113
|
<main>
|
|
266
114
|
<h1>My Products</h1>
|
|
267
115
|
|
|
268
|
-
|
|
116
|
+
<${input.renderBody}/> // render the page or layout here
|
|
269
117
|
</main>
|
|
270
118
|
```
|
|
271
119
|
|
|
@@ -286,36 +134,36 @@ Typically, these will be `.js` or `.ts` files depending on your project. Like pa
|
|
|
286
134
|
- Handler functions are synchronous or asynchronous functions that
|
|
287
135
|
- Receives a `context` and `next` argument,
|
|
288
136
|
- The `context` argument contains the WHATWG request object, path parameters, URL, and route metadata.
|
|
289
|
-
- The `next` argument will call the page for
|
|
290
|
-
- Return a WHATWG response, throw a WHATWG response, return undefined. If the function
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
137
|
+
- The `next` argument will call the page for `GET` requests where applicable or return a `204` response.
|
|
138
|
+
- Return a WHATWG response, throw a WHATWG response, and return undefined. If the function returns undefined the `next` argument with be automatically called and used as the response.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
export function POST(context, next) {
|
|
142
|
+
const { request, params, url, meta } = context;
|
|
143
|
+
return new Response('Successfully updated', { status: 200 });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function PUT(context, next) {
|
|
147
|
+
// `next` will be called for you by the runtime
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function GET(context, next) {
|
|
151
|
+
// do something before calling `next`
|
|
152
|
+
const response = await next();
|
|
153
|
+
// do something with the response from `next`
|
|
154
|
+
return response;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function DELETE(context, next) {
|
|
158
|
+
return new Response('Successfully removed', { status: 204 });
|
|
159
|
+
}
|
|
160
|
+
```
|
|
313
161
|
</details>
|
|
314
162
|
|
|
315
163
|
|
|
316
164
|
#### `+middleware.*`
|
|
317
165
|
|
|
318
|
-
These files are like layouts, but for handlers. Middleware
|
|
166
|
+
These files are like layouts, but for handlers. Middleware files are called before handlers and let you perform arbitrary work before and after.
|
|
319
167
|
|
|
320
168
|
> **Note**: Unlike handlers, middleware run for all HTTP methods.
|
|
321
169
|
|
|
@@ -329,35 +177,33 @@ These files are like layouts, but for handlers. Middleware get called before han
|
|
|
329
177
|
- Handler functions are synchronous or asynchronous functions that
|
|
330
178
|
- Receives a `context` and `next` argument,
|
|
331
179
|
- The `context` argument contains the WHATWG request object, path parameters, URL, and route metadata.
|
|
332
|
-
- The `next` argument will call the page for
|
|
333
|
-
- Return a WHATWG response, throw a WHATWG response, return undefined. If the function
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
180
|
+
- The `next` argument will call the page for `GET` requests where applicable or return a `204` response.
|
|
181
|
+
- Return a WHATWG response, throw a WHATWG response, and return undefined. If the function returns undefined the `next` argument with be automatically called and used as the response.
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
export default async function(context, next) {
|
|
185
|
+
const requestName = `${context.request.method} ${context.url.href}`;
|
|
186
|
+
let success = true;
|
|
187
|
+
console.log(`${requestName} request started`)
|
|
188
|
+
try {
|
|
189
|
+
return await next(); // Wait for subsequent middleware, handler, and page
|
|
190
|
+
} catch (err) {
|
|
191
|
+
success = false;
|
|
192
|
+
throw err;
|
|
193
|
+
} finally {
|
|
194
|
+
console.log(`${requestName} completed ${success ? 'successfully' : 'with errors'}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
350
198
|
</details>
|
|
351
199
|
|
|
352
200
|
#### `+meta.*`
|
|
353
201
|
|
|
354
|
-
These files represent static metadata to attach to the route. This metadata will be automatically provided on the
|
|
355
|
-
|
|
356
|
-
|
|
202
|
+
These files represent static metadata to attach to the route. This metadata will be automatically provided on the route `context` when invoking a route.
|
|
357
203
|
|
|
358
204
|
### Special Files
|
|
359
205
|
|
|
360
|
-
In addition to the files above which can be defined in any directory under the
|
|
206
|
+
In addition to the files above which can be defined in any directory under the [routes directory](#routes-directory), some special files can only be defined at its top level. <!-- 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” -->
|
|
361
207
|
|
|
362
208
|
These special pages are subject to a root layout file (`pages/+layout.marko` in the default configuration).
|
|
363
209
|
|
|
@@ -395,7 +241,7 @@ routes/
|
|
|
395
241
|
+page.marko
|
|
396
242
|
</pre>
|
|
397
243
|
|
|
398
|
-
When the path
|
|
244
|
+
When the path `/about` is requested, the routable files execute in the following order:
|
|
399
245
|
|
|
400
246
|
1. Middlewares from root-most to leaf-most
|
|
401
247
|
2. Handler
|
|
@@ -423,82 +269,254 @@ sequenceDiagram
|
|
|
423
269
|
|
|
424
270
|
### Path Structure
|
|
425
271
|
|
|
426
|
-
Within the
|
|
272
|
+
Within the [routes directory](#routes-directory), the directory structure determines the path from which the route is served. There are four types of directory names: **static**, **pathless**, **dynamic**, and **catch-all**.
|
|
427
273
|
|
|
428
|
-
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
|
|
274
|
+
1. **Static directories** - The most common type, and the default behavior. 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 is seen as a static directory.
|
|
429
275
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
276
|
+
Examples:
|
|
277
|
+
```
|
|
278
|
+
/foo
|
|
279
|
+
/users
|
|
280
|
+
/projects
|
|
281
|
+
```
|
|
436
282
|
|
|
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
|
|
283
|
+
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 ignored when parsing the route.
|
|
438
284
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
285
|
+
Examples:
|
|
286
|
+
```
|
|
287
|
+
/_users
|
|
288
|
+
/_public
|
|
289
|
+
```
|
|
444
290
|
|
|
445
291
|
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.
|
|
446
292
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
293
|
+
Examples:
|
|
294
|
+
```
|
|
295
|
+
/$id
|
|
296
|
+
/$name
|
|
297
|
+
/$
|
|
298
|
+
```
|
|
453
299
|
|
|
454
300
|
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.
|
|
455
301
|
|
|
456
|
-
|
|
302
|
+
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.
|
|
457
303
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
304
|
+
Examples:
|
|
305
|
+
```
|
|
306
|
+
/$$all
|
|
307
|
+
/$$rest
|
|
308
|
+
/$$
|
|
309
|
+
```
|
|
464
310
|
|
|
465
311
|
<!-- ### Match Ranking
|
|
466
312
|
|
|
467
313
|
*TODO: Write some things* -->
|
|
468
314
|
|
|
469
315
|
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
## Vite Plugin
|
|
319
|
+
|
|
320
|
+
This package’s Vite plugin discovers your route files, generates the routing code, and registers the `@marko/vite` plugin to compile your `.marko` files.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
// vite.config.ts
|
|
324
|
+
import { defineConfig } from "vite";
|
|
325
|
+
import marko from "@marko/run/vite"; // Import the Vite plugin
|
|
326
|
+
|
|
327
|
+
export default defineConfig({
|
|
328
|
+
plugins: [marko()] // Register the Vite plugin
|
|
329
|
+
})
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Adapters
|
|
333
|
+
|
|
334
|
+
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.
|
|
335
|
+
|
|
336
|
+
Specify your adapter in the Vite config when registering the `@marko/run` plugin
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
// vite.config.ts
|
|
340
|
+
import { defineConfig } from "vite";
|
|
341
|
+
import marko from "@marko/run/vite";
|
|
342
|
+
import netlify from "@marko/run-adapter-netlify" // Import the adapter
|
|
343
|
+
|
|
344
|
+
export default defineConfig({
|
|
345
|
+
plugins: [marko({
|
|
346
|
+
adapter: netlify({ edge: true }) // Configure and apply the adapter
|
|
347
|
+
})]
|
|
348
|
+
})
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
#### Available Adapters
|
|
352
|
+
|
|
353
|
+
- [@marko/run-adapter-node](https://github.com/marko-js/run/blob/main/packages/adapters/node/README.md)
|
|
354
|
+
- [@marko/run-adapter-netlify](https://github.com/marko-js/run/blob/main/packages/adapters/netlify/README.md)
|
|
355
|
+
- [@marko/run-adapter-static](https://github.com/marko-js/run/blob/main/packages/adapters/static/README.md)
|
|
356
|
+
## Runtime
|
|
357
|
+
|
|
358
|
+
Generally, when using an adapter, this runtime will be abstracted away.
|
|
359
|
+
|
|
360
|
+
<!-- TODO: Add examples -->
|
|
361
|
+
<!-- TODO: Split fetch and match + invoke in two sections and explain why you might use one or the other -->
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
import * as Run from '@marko/run/router`;
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Emdedding in Existing Server
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
### `Run.fetch`
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
async function fetch<T>(request: Request, platform: T) => Promise<Response | void>;
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
This asynchronous function takes a [WHATWG `Request` object](https://fetch.spec.whatwg.org/#request-class) and an object containing any platform-specific data you may want access to, and returns any of
|
|
380
|
+
|
|
381
|
+
- a [WHATWG `Response` object](https://fetch.spec.whatwg.org/#response-class) (generated from executing any matched route files)
|
|
382
|
+
- `undefined` (if the request was not explicitly handled)
|
|
383
|
+
- a `404` status code response (if no route matches the requested path)
|
|
384
|
+
- a `500` status code response (if an error occurs)
|
|
385
|
+
|
|
386
|
+
Express example:
|
|
387
|
+
```ts
|
|
388
|
+
import express from "express";
|
|
389
|
+
import * as Run from "@marko/run/router";
|
|
390
|
+
|
|
391
|
+
express()
|
|
392
|
+
.use(async (req, res, next) => {
|
|
393
|
+
const request = // ...code to create a WHATWG Request from `req`
|
|
394
|
+
|
|
395
|
+
const response = await Run.fetch(request, {
|
|
396
|
+
req,
|
|
397
|
+
res
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
if (response) {
|
|
401
|
+
// ...code to apply a response to `res`
|
|
402
|
+
} else {
|
|
403
|
+
next();
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
.listen(3000);
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
### Other APIs
|
|
411
|
+
|
|
412
|
+
In some cases, you might want more control over when route matching and invocation (creating a response) occur. For instance, you may have middleware in your server which needs to know if there is a matched route. The runtime provides these additional methods:
|
|
413
|
+
|
|
414
|
+
### `Run.match`
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
interface Route {
|
|
418
|
+
params: Record<string, string>;
|
|
419
|
+
meta: unknown;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function match(method: string, pathname: string) => Route | null;
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
This synchronous function takes an HTTP method and path name and returns an object representing the best match, or `null` if no match is found.
|
|
426
|
+
|
|
427
|
+
- `params` - a `{ key: value }` collection of any path parameters for the route
|
|
428
|
+
- `meta` - metadata for the route
|
|
429
|
+
|
|
430
|
+
### `Run.invoke`
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
async function invoke(route: Route, request: Request, platform: any) => Promise<Response | void>;
|
|
434
|
+
```
|
|
435
|
+
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.
|
|
436
|
+
|
|
437
|
+
Express example:
|
|
438
|
+
```ts
|
|
439
|
+
import express from "express";
|
|
440
|
+
import * as Run from "@marko/run/router";
|
|
441
|
+
|
|
442
|
+
express()
|
|
443
|
+
.use((req, res) => {
|
|
444
|
+
const matchedRoute = Run.match(req.method, req.path);
|
|
445
|
+
if (matchedRoute) {
|
|
446
|
+
req.match = matchedRoute;
|
|
447
|
+
}
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
// ...other middleware
|
|
451
|
+
|
|
452
|
+
.use(async (req, res, next) => {
|
|
453
|
+
// Check if a route was previously matched
|
|
454
|
+
if (!req.match) {
|
|
455
|
+
next();
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const request = // ...code to create a WHATWG Request from `req`
|
|
460
|
+
const response = await Run.invoke(req.match, request, {
|
|
461
|
+
req,
|
|
462
|
+
res
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
if (response) {
|
|
466
|
+
// ...code to apply a response to `res`
|
|
467
|
+
} else {
|
|
468
|
+
next();
|
|
469
|
+
}
|
|
470
|
+
})
|
|
471
|
+
.listen(3000);
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
|
|
470
476
|
## TypeScript
|
|
471
477
|
|
|
472
478
|
|
|
473
479
|
### Global Namespace
|
|
474
|
-
marko/run provides a global namespace `MarkoRun` with the
|
|
480
|
+
`marko/run` provides a global namespace `MarkoRun` with the following types:
|
|
475
481
|
|
|
476
482
|
**`MarkoRun.Handler`** - Type that represents a handler function to be exported by a +handler or +middleware file
|
|
477
483
|
|
|
478
|
-
**`MarkoRun.CurrentRoute`** - Type of the route's params and
|
|
484
|
+
**`MarkoRun.CurrentRoute`** - Type of the route's params and metadata
|
|
479
485
|
|
|
480
486
|
**`MarkoRun.CurrentContext`** - Type of the request context object in a handler and `out.global` in your Marko files
|
|
481
487
|
|
|
482
488
|
|
|
483
489
|
### 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.
|
|
490
|
+
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.
|
|
491
|
+
> **Note** TypeScript will not include this file by default. You should use the [Marko VSCode plugin](https://marketplace.visualstudio.com/items?itemName=Marko-JS.marko-vscode) and [add it in your tsconfig](https://www.typescriptlang.org/tsconfig#include).
|
|
486
492
|
|
|
487
|
-
These types are replaced with more specific versions per
|
|
493
|
+
These types are replaced with more specific versions per routable file:
|
|
488
494
|
|
|
489
495
|
**`MarkoRun.Handler`**
|
|
490
496
|
- Overrides context with specific MarkoRun.CurrentContext
|
|
491
497
|
|
|
492
498
|
**`MarkoRun.CurrentRoute`**
|
|
493
499
|
- 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
|
|
500
|
+
- In middleware and layouts which are used in many routes, this type will be a union of all possible routes that the file will see
|
|
495
501
|
|
|
496
502
|
**`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.
|
|
503
|
+
- In middleware and layouts which are used in many routes, this type will be a union of all possible routes that the file will see.
|
|
498
504
|
- When an adapter is used, it can provide types for the platform
|
|
499
505
|
|
|
500
506
|
## Beta Roadmap
|
|
501
507
|
|
|
502
508
|
- Error handling
|
|
503
509
|
- Error component
|
|
504
|
-
- Redirect component
|
|
510
|
+
- Redirect component
|
|
511
|
+
|
|
512
|
+
## What about @marko/serve?
|
|
513
|
+
|
|
514
|
+
Once stable @marko/run will replace @marko/serve and improves upon that project in several critical ways.
|
|
515
|
+
|
|
516
|
+
1. Special "route files" (e.g. `+page.marko`) improve the developer ergonomics quite substantially. While they may cause a double take initially, making these explicit allows colocating additional components, tests, stories, config, utilities, and whatever else you need alongside the page components. With `@marko/serve` it was far too easy to "accidentally" serve some of your test fixtures :see-no-evil:.
|
|
517
|
+
2. @marko/serve was built around Webpack. Since Webpack doesn't have great support for SSR, a lot of this work was up to us. This was not only a maintenance burden but also lead to some rough edges such as no HMR support (just full page reloading in dev). By switching to Vite with its first-class SSR support, things all come together much more smoothly. <!-- TODO: And we're in good company (LINK TO OTHER META FRAMEWORKS ON VITE)! -->
|
|
518
|
+
3. @marko/serve was primarily designed with a node target in mind. @marko/run instead supports an "adapter" model, where you can author in web standard APIs and build your application to run in Node, Deno, Netlify, Cloudflare, and even a static site.
|
|
519
|
+
4. The programmatic API of `@marko/serve` left some to be desired which made it difficult to integrate into existing development servers and projects. Because of this, public-facing applications at eBay (the largest consumer of Marko) were not able to bring in `@marko/serve`. With `@marko/run` we've worked from the ground up to ensure a flexible enough programmatic API to allow embedding in existing complex applications. Because of this, we're confident that `@marko/run` will see much more use than `@marko/serve` and more investment from us!
|
|
520
|
+
5. Built-in layout management. Strictly speaking, Marko does not need the concept of `+layout.marko` "route file". If you've used Marko before you know it's very easy to treat layouts as normal components. But by bringing these layouts into the router we're able to reduce the amount of JavaScript naively sent to the browser, reduce the amount of boilerplate, and prime ourselves for some plans we have for after Marko 6 is out :eyes:.
|
|
521
|
+
|
|
522
|
+
There's more of course, but we're committed to making `@marko/run` the _best way_ to build a Marko application.
|