@marko/run 0.0.1-beta1
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 +267 -0
- package/dist/adapter/default-entry.mjs +18 -0
- package/dist/adapter/dev-server.d.ts +4 -0
- package/dist/adapter/index.cjs +159 -0
- package/dist/adapter/index.d.ts +4 -0
- package/dist/adapter/index.js +126 -0
- package/dist/adapter/server-old.d.ts +3 -0
- package/dist/adapter/server.d.ts +6 -0
- package/dist/adapters/node/index.d.ts +5 -0
- package/dist/adapters/node/server.d.ts +3 -0
- package/dist/adapters/static/crawler.d.ts +9 -0
- package/dist/adapters/static/default-entry.mjs +1 -0
- package/dist/adapters/static/index.cjs +371 -0
- package/dist/adapters/static/index.d.ts +5 -0
- package/dist/adapters/static/index.js +341 -0
- package/dist/adapters/static/server.d.ts +3 -0
- package/dist/cli/default.config.mjs +7 -0
- package/dist/cli/index.mjs +223 -0
- package/dist/runtime/index.cjs +34 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/request.d.ts +4 -0
- package/dist/runtime/router.cjs +39 -0
- package/dist/runtime/router.d.ts +4 -0
- package/dist/runtime/router.js +12 -0
- package/dist/runtime/types.d.ts +22 -0
- package/dist/vite/codegen/index.d.ts +5 -0
- package/dist/vite/codegen/writer.d.ts +20 -0
- package/dist/vite/constants.d.ts +19 -0
- package/dist/vite/index.cjs +1394 -0
- package/dist/vite/index.d.ts +3 -0
- package/dist/vite/index.js +1359 -0
- package/dist/vite/plugin.d.ts +3 -0
- package/dist/vite/routes/builder.d.ts +7 -0
- package/dist/vite/routes/routeTrie.d.ts +2 -0
- package/dist/vite/routes/walk.d.ts +14 -0
- package/dist/vite/types.d.ts +62 -0
- package/dist/vite/utils/ast.d.ts +1 -0
- package/dist/vite/utils/config.d.ts +3 -0
- package/dist/vite/utils/log.d.ts +3 -0
- package/dist/vite/utils/route.d.ts +3 -0
- package/dist/vite/utils/server.d.ts +7 -0
- package/package.json +93 -0
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<!-- Logo -->
|
|
3
|
+
<img src="https://user-images.githubusercontent.com/4985201/115444712-ca550500-a1c9-11eb-9897-238ece59129c.png" height="118"/>
|
|
4
|
+
<br/>
|
|
5
|
+
@marko/run
|
|
6
|
+
<br/>
|
|
7
|
+
|
|
8
|
+
<!-- Language -->
|
|
9
|
+
<a href="http://typescriptlang.org">
|
|
10
|
+
<img src="https://img.shields.io/badge/%3C%2F%3E-typescript-blue.svg" alt="TypeScript"/>
|
|
11
|
+
</a>
|
|
12
|
+
</h1>
|
|
13
|
+
|
|
14
|
+
Vite plugin for Marko with these features
|
|
15
|
+
- Encapsulates [@marko/vite](https://github.com/marko-js/vite)
|
|
16
|
+
- File-based routing with layouts and middleware
|
|
17
|
+
- Efficient routing using a compiled static trie
|
|
18
|
+
- Designed with web standards to run anywhere
|
|
19
|
+
|
|
20
|
+
## Intallation
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
npm install @marko/run
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Vite Plugin
|
|
27
|
+
|
|
28
|
+
This package provides both a Vite plugin and a runtime import. The Vite plugin is responsible for discovering your route files, generating the routing code and registering the @marko/vite plugin which builds your .marko files.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// vite.config.ts
|
|
32
|
+
import { defineConfig } from "vite";
|
|
33
|
+
import marko from "@marko/run/vite"; // Import the Vite plugin
|
|
34
|
+
|
|
35
|
+
export default defineConfig({
|
|
36
|
+
plugins: [marko()], // Register the Vite plugin
|
|
37
|
+
build: {
|
|
38
|
+
sourcemap: true, // Generate sourcemaps for all builds.
|
|
39
|
+
emptyOutDir: false, // Avoid server & client deleting files from each other.
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Runtime
|
|
45
|
+
|
|
46
|
+
Generally you'll want to use one of the adapters to provide a more convenient experience but this package provides the following runtime interface:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { router, getMatchedRoute } from '@marko/run`;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### `router`
|
|
53
|
+
```ts
|
|
54
|
+
(request: Request) => Promise<Response>;
|
|
55
|
+
```
|
|
56
|
+
This asynchronouse function takes a [WHATWG request](https://fetch.spec.whatwg.org/#request-class) object and returns a [response](https://fetch.spec.whatwg.org/#response-class) object generated by executing the corresponding matched route files. If no match is found, a response with a 404 status code is returned. If an unhandled error occurs, a response with a 500 status code will be returned.
|
|
57
|
+
|
|
58
|
+
### `getMatchedRoute`
|
|
59
|
+
```ts
|
|
60
|
+
(method: string, url: URL) => {
|
|
61
|
+
params: Record<string, string>;
|
|
62
|
+
meta: unknown;
|
|
63
|
+
invoke(request: Request): Promise<Response>;
|
|
64
|
+
} | null;
|
|
65
|
+
```
|
|
66
|
+
This synchronous function takes a HTTP verb method, and URL and returns an object representing the best match or null if no match is found.
|
|
67
|
+
- `params` - a key-value collection of any path parameters for the route
|
|
68
|
+
- `meta` - meta data for the route
|
|
69
|
+
- `invoke` - an asynchronouse function that takes a WHATWG request and returns the response generated by executing the matched route files. Note: unlike the top-level `router` function errors will not be caught.
|
|
70
|
+
|
|
71
|
+
## File-based Routing
|
|
72
|
+
|
|
73
|
+
## Nested Routing
|
|
74
|
+
|
|
75
|
+
*TODO: provide a quick overview*
|
|
76
|
+
|
|
77
|
+
### Routes Directory
|
|
78
|
+
|
|
79
|
+
By default, the plugin will look for files in the configured _routes directory_. By default it will look in `./src/routes` (relative to the Vite config file) which can be configured:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// vite.config.ts
|
|
83
|
+
import { defineConfig } from "vite";
|
|
84
|
+
import marko from "@marko/run/vite";
|
|
85
|
+
|
|
86
|
+
export default defineConfig({
|
|
87
|
+
plugins: [marko({
|
|
88
|
+
routesDir: 'src/pages' // Use `./src/pages` (relative to this file) as the routes directory
|
|
89
|
+
})],
|
|
90
|
+
//...
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Routeable Files
|
|
95
|
+
|
|
96
|
+
In order to allow for co-location of files that should not be served (e.g. tests, stories, assets), the router only recognizes a specific set of file names. The following files will be discovered in any directory within your application's _routes directory_.
|
|
97
|
+
|
|
98
|
+
#### `+page.marko`
|
|
99
|
+
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
|
+
|
|
101
|
+
#### `+layout.marko`
|
|
102
|
+
These files provide a layout component which will wrap all nested layouts and pages.
|
|
103
|
+
|
|
104
|
+
<details>
|
|
105
|
+
<summary>More Info</summary>
|
|
106
|
+
|
|
107
|
+
Layouts are just like any other Marko component with no extra constraints. Each layout receives the request, path params, URL and route meta data as input as well as a renderBody which will be the subsequent layout or page to project.
|
|
108
|
+
|
|
109
|
+
```marko
|
|
110
|
+
<div>
|
|
111
|
+
<h1>My Products</h1>
|
|
112
|
+
<main>
|
|
113
|
+
${input.renderBody} // project the page or layout here
|
|
114
|
+
</main>
|
|
115
|
+
</div>
|
|
116
|
+
```
|
|
117
|
+
</details>
|
|
118
|
+
|
|
119
|
+
#### `+handler.*`
|
|
120
|
+
These files establish a route at the current directory path which can handle requests for GET, POST, PUT and DELETE HTTP verbs. Typically these will be .js or .ts files depending on your project. Just like pages, only one handler may exist for any served path. A handler should export functions
|
|
121
|
+
|
|
122
|
+
<details>
|
|
123
|
+
<summary>More Info</summary>
|
|
124
|
+
|
|
125
|
+
- Valid exports are: `get`, `post`, `put`, `del`.
|
|
126
|
+
- Each method takes a `context` and `next` argument and should return a WHATWG response either synchronously or asynchronously.
|
|
127
|
+
- The `context` argument contains the WHATWG request object, path parameters, URL and route meta data.
|
|
128
|
+
- The `next` argument will call the page for get requests where applicable or return a 204 response.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
export function post(context, next) {
|
|
132
|
+
const { request, params, url, meta } = context;
|
|
133
|
+
return new Response('updated', { status: 200 });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function put(context, next) {
|
|
137
|
+
return new Response('created', { status: 201 }); // handle the request
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function get(context, next) {
|
|
141
|
+
return next(); // Call the next handler
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function del(context, next) {
|
|
145
|
+
return new Response('removed', { status: 204 });
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
</details>
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
#### `+middlware.*`
|
|
152
|
+
These files are analagous to layouts for handlers. Middlware get called before handlers and let you perform arbitrary work before and after. Unlike handlers, middleware run for all HTTP verbs.
|
|
153
|
+
|
|
154
|
+
<details>
|
|
155
|
+
<summary>More Info</summary>
|
|
156
|
+
|
|
157
|
+
- Expects default export that takes a `context` and `next` argument and should return a WHATWG response either synchronously or asynchronously.
|
|
158
|
+
- The `context` argument contains the WHATWG request object, path parameters, URL and route meta data.
|
|
159
|
+
- The `next` argument will call the next middleware, handler or page as applicable for the route.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
export default async function(context, next) {
|
|
163
|
+
const requestName = `${ctx.request.method} ${ctx.url.href}`;
|
|
164
|
+
let success = true;
|
|
165
|
+
console.log(`${requestName} request started`)
|
|
166
|
+
try {
|
|
167
|
+
return await next(); // wait for subsequent middleware/handler/page
|
|
168
|
+
} catch (err) {
|
|
169
|
+
success = false;
|
|
170
|
+
throw err;
|
|
171
|
+
} finally {
|
|
172
|
+
console.log(`${requestName} completed ${success ? 'successfully' : 'with errors'}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
</details>
|
|
177
|
+
|
|
178
|
+
#### `+meta.*`
|
|
179
|
+
These files represent some meta data to attach to the route. This meta data will be automatically imported by the router and provided on the the route context when invoking a route.
|
|
180
|
+
|
|
181
|
+
### Special Files
|
|
182
|
+
|
|
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 within the _routes directory_:
|
|
184
|
+
|
|
185
|
+
#### `+404.marko`
|
|
186
|
+
This is a special page wich will be rendered for any request whose `Accept` header includes `text/html` if no other handler or page handled 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 `404` status code.
|
|
187
|
+
|
|
188
|
+
#### `+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
|
+
|
|
191
|
+
|
|
192
|
+
### Execution Order
|
|
193
|
+
|
|
194
|
+
For a matched route the routable files execute in the following order
|
|
195
|
+
1. Middlewares from root-most to leaf-most
|
|
196
|
+
2. Handler
|
|
197
|
+
3. Layouts from root-most to leaf-most
|
|
198
|
+
4. Page
|
|
199
|
+
|
|
200
|
+
<details>
|
|
201
|
+
<summary>Diagram</summary>
|
|
202
|
+
|
|
203
|
+
```mermaid
|
|
204
|
+
sequenceDiagram
|
|
205
|
+
participant Middleware1
|
|
206
|
+
participant Middleware2
|
|
207
|
+
participant Handler
|
|
208
|
+
participant Layout1
|
|
209
|
+
participant Layout2
|
|
210
|
+
participant Page
|
|
211
|
+
Note over Layout1,Page: Combined at build-time as a single component
|
|
212
|
+
Middleware1->>Middleware2: next()
|
|
213
|
+
Middleware2->>Handler: next()
|
|
214
|
+
Handler->>Layout1: next()
|
|
215
|
+
Layout1->Layout2: ${input.renderBody}
|
|
216
|
+
Layout2->Page: ${input.renderBody}
|
|
217
|
+
Layout1-->>Handler: Stream Response
|
|
218
|
+
Handler-->>Middleware2: Response
|
|
219
|
+
Middleware2-->>Middleware1: Response
|
|
220
|
+
```
|
|
221
|
+
</details>
|
|
222
|
+
|
|
223
|
+
### Path Structure
|
|
224
|
+
|
|
225
|
+
Within the _routes directory_ the directory structure will determine the path the route will be served. There are four types of directory names:
|
|
226
|
+
|
|
227
|
+
- **Static directories** - The most common type of directory. Each static directory will contribute its name as a segment in the route's served path like a traditional file server. This is the default and unless the directory matches the requirements for one of the below types, it will be a static directory.
|
|
228
|
+
|
|
229
|
+
Examples:
|
|
230
|
+
```
|
|
231
|
+
/foo
|
|
232
|
+
/users
|
|
233
|
+
/projects
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- **Pathless directories** - These directories do not contribute their name to the route's served path. Directory names that start with an underscore ('_') as well as directories named 'index' will be a pathless directory.
|
|
237
|
+
|
|
238
|
+
Examples:
|
|
239
|
+
```
|
|
240
|
+
/_users
|
|
241
|
+
/_public
|
|
242
|
+
/index
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- **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. In the case the directory name is '$', the parameter will not exist at runtime but will be matched.
|
|
246
|
+
|
|
247
|
+
Examples:
|
|
248
|
+
```
|
|
249
|
+
/$id
|
|
250
|
+
/$name
|
|
251
|
+
/$
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
- **Catcha-all directories** - These directories are similar to dynamic directories and introduce a dynamic parameter but instead of matching a single sement of the path, 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 name '$$', the parameter name at runtime will be '*'. Catch-all directories can be used to make not found/404 routes at any level including the root.
|
|
255
|
+
|
|
256
|
+
Because catch-all directories always match and consume all the remaining path, you cannot have nested routes within and no further directories will be traversed.
|
|
257
|
+
|
|
258
|
+
Examples:
|
|
259
|
+
```
|
|
260
|
+
/$$all
|
|
261
|
+
/$$rest
|
|
262
|
+
/$$
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Match Ranking
|
|
266
|
+
|
|
267
|
+
*TODO: Write some things*
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import createStaticServe from "serve-static";
|
|
2
|
+
import { createServer } from "http";
|
|
3
|
+
import { createMiddleware } from "@hattip/adapter-node";
|
|
4
|
+
import { handler } from "@marko/run";
|
|
5
|
+
|
|
6
|
+
const { PORT = 3456 } = process.env;
|
|
7
|
+
|
|
8
|
+
const dir = process.cwd();
|
|
9
|
+
const middleware = createMiddleware(handler);
|
|
10
|
+
const staticServe = createStaticServe(dir, {
|
|
11
|
+
index: false,
|
|
12
|
+
immutable: true,
|
|
13
|
+
maxAge: "365 days",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
createServer((req, res) =>
|
|
17
|
+
staticServe(req, res, () => middleware(req, res))
|
|
18
|
+
).listen(PORT);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ViteDevServer } from "vite";
|
|
2
|
+
import { type NodeMiddleware } from "@hattip/adapter-node";
|
|
3
|
+
export declare function createViteDevMiddleware<T>(devServer: ViteDevServer, load: (prev: T | undefined) => Promise<T>, factory: (value: T) => NodeMiddleware): NodeMiddleware;
|
|
4
|
+
export declare function createDevServer(configFile?: string): Promise<import("vite").Connect.Server>;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
25
|
+
|
|
26
|
+
// src/adapter/index.ts
|
|
27
|
+
var adapter_exports = {};
|
|
28
|
+
__export(adapter_exports, {
|
|
29
|
+
createDevServer: () => createDevServer,
|
|
30
|
+
createViteDevMiddleware: () => createViteDevMiddleware,
|
|
31
|
+
default: () => adapter
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(adapter_exports);
|
|
34
|
+
var import_path = __toESM(require("path"), 1);
|
|
35
|
+
var import_url = require("url");
|
|
36
|
+
|
|
37
|
+
// src/adapter/dev-server.ts
|
|
38
|
+
var import_vite = require("vite");
|
|
39
|
+
var import_adapter_node = require("@hattip/adapter-node");
|
|
40
|
+
function createViteDevMiddleware(devServer, load, factory) {
|
|
41
|
+
let value;
|
|
42
|
+
let middleware;
|
|
43
|
+
return async (req, res, next) => {
|
|
44
|
+
try {
|
|
45
|
+
const nextValue = await load(value);
|
|
46
|
+
if (nextValue !== value) {
|
|
47
|
+
value = nextValue;
|
|
48
|
+
middleware = factory(value);
|
|
49
|
+
}
|
|
50
|
+
await middleware(req, res, next);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err instanceof Error) {
|
|
53
|
+
devServer.ssrFixStacktrace(err);
|
|
54
|
+
}
|
|
55
|
+
return next == null ? void 0 : next();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function createDevServer(configFile) {
|
|
60
|
+
const devServer = await (0, import_vite.createServer)({
|
|
61
|
+
configFile,
|
|
62
|
+
appType: "custom",
|
|
63
|
+
server: { middlewareMode: true }
|
|
64
|
+
});
|
|
65
|
+
const middleware = createViteDevMiddleware(
|
|
66
|
+
devServer,
|
|
67
|
+
async () => (await devServer.ssrLoadModule("@marko/run")).handler,
|
|
68
|
+
import_adapter_node.createMiddleware
|
|
69
|
+
);
|
|
70
|
+
return devServer.middlewares.use(middleware);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/vite/utils/server.ts
|
|
74
|
+
var import_net = __toESM(require("net"), 1);
|
|
75
|
+
var import_child_process = __toESM(require("child_process"), 1);
|
|
76
|
+
async function spawnServer(cmd, port = 0, cwd = process.cwd(), wait = 3e4) {
|
|
77
|
+
if (port <= 0) {
|
|
78
|
+
port = await getAvailablePort();
|
|
79
|
+
}
|
|
80
|
+
const proc = import_child_process.default.spawn(cmd, {
|
|
81
|
+
cwd,
|
|
82
|
+
shell: true,
|
|
83
|
+
stdio: "inherit",
|
|
84
|
+
windowsHide: true,
|
|
85
|
+
env: { NODE_ENV: "development", ...process.env, PORT: `${port}` }
|
|
86
|
+
});
|
|
87
|
+
const close = () => {
|
|
88
|
+
proc.unref();
|
|
89
|
+
proc.kill();
|
|
90
|
+
};
|
|
91
|
+
let remaining = wait > 0 ? wait : Infinity;
|
|
92
|
+
while (!await isPortInUse(port)) {
|
|
93
|
+
if (remaining >= 100) {
|
|
94
|
+
remaining -= 100;
|
|
95
|
+
await sleep(100);
|
|
96
|
+
} else {
|
|
97
|
+
close();
|
|
98
|
+
throw new Error(
|
|
99
|
+
`site-write: timeout while wating for server to start on port "${port}".`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
port,
|
|
105
|
+
close
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function isPortInUse(port) {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
const connection = import_net.default.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => done(false)).on("connect", () => done(true));
|
|
111
|
+
function done(connected) {
|
|
112
|
+
connection.end();
|
|
113
|
+
resolve(connected);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function getAvailablePort() {
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
const server = import_net.default.createServer().listen(0, () => {
|
|
120
|
+
const { port } = server.address();
|
|
121
|
+
server.close(() => resolve(port));
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function sleep(ms) {
|
|
126
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/adapter/index.ts
|
|
130
|
+
var import_meta = {};
|
|
131
|
+
var __dirname = (0, import_url.fileURLToPath)(new URL(".", import_meta.url));
|
|
132
|
+
function adapter() {
|
|
133
|
+
return {
|
|
134
|
+
name: "base-adapter",
|
|
135
|
+
async getEntryFile() {
|
|
136
|
+
const entry = import_path.default.join(__dirname, "default-entry");
|
|
137
|
+
return entry;
|
|
138
|
+
},
|
|
139
|
+
async startDev(configFile, port) {
|
|
140
|
+
const server = await createDevServer(configFile);
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
const listener = server.listen(port, () => {
|
|
143
|
+
const address = listener.address();
|
|
144
|
+
console.log(`Dev server started: http://localhost:${address.port}`);
|
|
145
|
+
resolve();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
async startPreview(dir, entry, port) {
|
|
150
|
+
const server = await spawnServer(`node ${entry}`, port, dir);
|
|
151
|
+
console.log(`Preview server started: http://localhost:${server.port}`);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
156
|
+
0 && (module.exports = {
|
|
157
|
+
createDevServer,
|
|
158
|
+
createViteDevMiddleware
|
|
159
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/adapter/index.ts
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
// src/adapter/dev-server.ts
|
|
6
|
+
import { createServer } from "vite";
|
|
7
|
+
import { createMiddleware } from "@hattip/adapter-node";
|
|
8
|
+
function createViteDevMiddleware(devServer, load, factory) {
|
|
9
|
+
let value;
|
|
10
|
+
let middleware;
|
|
11
|
+
return async (req, res, next) => {
|
|
12
|
+
try {
|
|
13
|
+
const nextValue = await load(value);
|
|
14
|
+
if (nextValue !== value) {
|
|
15
|
+
value = nextValue;
|
|
16
|
+
middleware = factory(value);
|
|
17
|
+
}
|
|
18
|
+
await middleware(req, res, next);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (err instanceof Error) {
|
|
21
|
+
devServer.ssrFixStacktrace(err);
|
|
22
|
+
}
|
|
23
|
+
return next == null ? void 0 : next();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function createDevServer(configFile) {
|
|
28
|
+
const devServer = await createServer({
|
|
29
|
+
configFile,
|
|
30
|
+
appType: "custom",
|
|
31
|
+
server: { middlewareMode: true }
|
|
32
|
+
});
|
|
33
|
+
const middleware = createViteDevMiddleware(
|
|
34
|
+
devServer,
|
|
35
|
+
async () => (await devServer.ssrLoadModule("@marko/run")).handler,
|
|
36
|
+
createMiddleware
|
|
37
|
+
);
|
|
38
|
+
return devServer.middlewares.use(middleware);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/vite/utils/server.ts
|
|
42
|
+
import net from "net";
|
|
43
|
+
import cp from "child_process";
|
|
44
|
+
async function spawnServer(cmd, port = 0, cwd = process.cwd(), wait = 3e4) {
|
|
45
|
+
if (port <= 0) {
|
|
46
|
+
port = await getAvailablePort();
|
|
47
|
+
}
|
|
48
|
+
const proc = cp.spawn(cmd, {
|
|
49
|
+
cwd,
|
|
50
|
+
shell: true,
|
|
51
|
+
stdio: "inherit",
|
|
52
|
+
windowsHide: true,
|
|
53
|
+
env: { NODE_ENV: "development", ...process.env, PORT: `${port}` }
|
|
54
|
+
});
|
|
55
|
+
const close = () => {
|
|
56
|
+
proc.unref();
|
|
57
|
+
proc.kill();
|
|
58
|
+
};
|
|
59
|
+
let remaining = wait > 0 ? wait : Infinity;
|
|
60
|
+
while (!await isPortInUse(port)) {
|
|
61
|
+
if (remaining >= 100) {
|
|
62
|
+
remaining -= 100;
|
|
63
|
+
await sleep(100);
|
|
64
|
+
} else {
|
|
65
|
+
close();
|
|
66
|
+
throw new Error(
|
|
67
|
+
`site-write: timeout while wating for server to start on port "${port}".`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
port,
|
|
73
|
+
close
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function isPortInUse(port) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
const connection = net.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => done(false)).on("connect", () => done(true));
|
|
79
|
+
function done(connected) {
|
|
80
|
+
connection.end();
|
|
81
|
+
resolve(connected);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function getAvailablePort() {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
const server = net.createServer().listen(0, () => {
|
|
88
|
+
const { port } = server.address();
|
|
89
|
+
server.close(() => resolve(port));
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function sleep(ms) {
|
|
94
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/adapter/index.ts
|
|
98
|
+
var __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
99
|
+
function adapter() {
|
|
100
|
+
return {
|
|
101
|
+
name: "base-adapter",
|
|
102
|
+
async getEntryFile() {
|
|
103
|
+
const entry = path.join(__dirname, "default-entry");
|
|
104
|
+
return entry;
|
|
105
|
+
},
|
|
106
|
+
async startDev(configFile, port) {
|
|
107
|
+
const server = await createDevServer(configFile);
|
|
108
|
+
return new Promise((resolve) => {
|
|
109
|
+
const listener = server.listen(port, () => {
|
|
110
|
+
const address = listener.address();
|
|
111
|
+
console.log(`Dev server started: http://localhost:${address.port}`);
|
|
112
|
+
resolve();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
async startPreview(dir, entry, port) {
|
|
117
|
+
const server = await spawnServer(`node ${entry}`, port, dir);
|
|
118
|
+
console.log(`Preview server started: http://localhost:${server.port}`);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
createDevServer,
|
|
124
|
+
createViteDevMiddleware,
|
|
125
|
+
adapter as default
|
|
126
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { ViteDevServer } from "vite";
|
|
3
|
+
import { type NodeMiddleware } from "@hattip/adapter-node";
|
|
4
|
+
import type { Server } from "http";
|
|
5
|
+
export declare function createViteDevMiddleware<T>(devServer: ViteDevServer, load: (prev: T | undefined) => Promise<T>, factory: (value: T) => NodeMiddleware): NodeMiddleware;
|
|
6
|
+
export default function (entryFile?: string): Promise<Server>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface Options {
|
|
2
|
+
out?: string;
|
|
3
|
+
origin?: string;
|
|
4
|
+
notFoundPath?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Crawler {
|
|
7
|
+
crawl(paths: string[]): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export default function createCrawler(makeRequest: (request: Request) => Promise<Response>, opts?: Options): Crawler;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { router } from '@marko/run';
|