@kestack/kabuki 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -33
- package/dist/core/actor/actor.d.ts +24 -13
- package/dist/core/actor/actor.d.ts.map +1 -1
- package/dist/core/actor/actor.js +37 -26
- package/dist/core/actor/index.d.ts +2 -2
- package/dist/core/actor/index.d.ts.map +1 -1
- package/dist/core/actor/types.d.ts +91 -47
- package/dist/core/actor/types.d.ts.map +1 -1
- package/dist/core/history/history.d.ts +11 -3
- package/dist/core/history/history.d.ts.map +1 -1
- package/dist/core/history/history.js +106 -27
- package/dist/core/history/replay.d.ts +2 -0
- package/dist/core/history/replay.d.ts.map +1 -1
- package/dist/core/history/replay.js +1 -0
- package/dist/core/history/saved.d.ts +1 -1
- package/dist/core/history/saved.d.ts.map +1 -1
- package/dist/core/history/saved.js +13 -9
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/snapshot/invoke.d.ts +2 -3
- package/dist/core/snapshot/invoke.d.ts.map +1 -1
- package/dist/core/snapshot/invoke.js +42 -49
- package/dist/core/snapshot/restore.js +8 -7
- package/dist/core/snapshot/tree.d.ts +1 -1
- package/dist/core/snapshot/tree.d.ts.map +1 -1
- package/dist/core/snapshot/tree.js +1 -1
- package/dist/react/app.d.ts +30 -3
- package/dist/react/app.d.ts.map +1 -1
- package/dist/react/app.js +119 -40
- package/dist/react/drawn.d.ts +1 -1
- package/dist/react/drawn.d.ts.map +1 -1
- package/dist/react/drawn.js +2 -1
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -0
- package/dist/react/lazy.d.ts +30 -0
- package/dist/react/lazy.d.ts.map +1 -0
- package/dist/react/lazy.js +56 -0
- package/dist/react/live.d.ts +7 -0
- package/dist/react/live.d.ts.map +1 -0
- package/dist/react/live.js +13 -0
- package/dist/react/view.d.ts +26 -6
- package/dist/react/view.d.ts.map +1 -1
- package/dist/react/view.js +17 -5
- package/dist/routing/adapter/adapter.d.ts +38 -0
- package/dist/routing/adapter/adapter.d.ts.map +1 -0
- package/dist/routing/adapter/adapter.js +10 -0
- package/dist/routing/adapter/browser/browser.d.ts +8 -0
- package/dist/routing/adapter/browser/browser.d.ts.map +1 -0
- package/dist/routing/adapter/browser/browser.js +33 -0
- package/dist/routing/adapter/browser/index.d.ts +2 -0
- package/dist/routing/adapter/browser/index.d.ts.map +1 -0
- package/dist/routing/adapter/browser/index.js +1 -0
- package/dist/routing/adapter/index.d.ts +3 -0
- package/dist/routing/adapter/index.d.ts.map +1 -0
- package/dist/routing/adapter/index.js +1 -0
- package/dist/routing/adapter/tanstack/index.d.ts +3 -0
- package/dist/routing/adapter/tanstack/index.d.ts.map +1 -0
- package/dist/routing/adapter/tanstack/index.js +1 -0
- package/dist/routing/adapter/tanstack/tanstack.d.ts +27 -0
- package/dist/routing/adapter/tanstack/tanstack.d.ts.map +1 -0
- package/dist/routing/adapter/tanstack/tanstack.js +38 -0
- package/dist/routing/capability/cache.d.ts +12 -0
- package/dist/routing/capability/cache.d.ts.map +1 -0
- package/dist/routing/capability/cache.js +170 -0
- package/dist/routing/capability/index.d.ts +6 -0
- package/dist/routing/capability/index.d.ts.map +1 -0
- package/dist/routing/capability/index.js +3 -0
- package/dist/routing/capability/loading.d.ts +82 -0
- package/dist/routing/capability/loading.d.ts.map +1 -0
- package/dist/routing/capability/loading.js +53 -0
- package/dist/routing/capability/navigation.d.ts +66 -0
- package/dist/routing/capability/navigation.d.ts.map +1 -0
- package/dist/routing/capability/navigation.js +65 -0
- package/dist/routing/index.d.ts +10 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +4 -0
- package/dist/routing/page/index.d.ts +5 -0
- package/dist/routing/page/index.d.ts.map +1 -0
- package/dist/routing/page/index.js +2 -0
- package/dist/routing/page/page.d.ts +151 -0
- package/dist/routing/page/page.d.ts.map +1 -0
- package/dist/routing/page/page.js +121 -0
- package/dist/routing/page/status.d.ts +121 -0
- package/dist/routing/page/status.d.ts.map +1 -0
- package/dist/routing/page/status.js +69 -0
- package/dist/routing/route/index.d.ts +11 -0
- package/dist/routing/route/index.d.ts.map +1 -0
- package/dist/routing/route/index.js +6 -0
- package/dist/routing/route/loader.d.ts +98 -0
- package/dist/routing/route/loader.d.ts.map +1 -0
- package/dist/routing/route/loader.js +17 -0
- package/dist/routing/route/metadata.d.ts +44 -0
- package/dist/routing/route/metadata.d.ts.map +1 -0
- package/dist/routing/route/metadata.js +53 -0
- package/dist/routing/route/node.d.ts +162 -0
- package/dist/routing/route/node.d.ts.map +1 -0
- package/dist/routing/route/node.js +3 -0
- package/dist/routing/route/places.d.ts +15 -0
- package/dist/routing/route/places.d.ts.map +1 -0
- package/dist/routing/route/places.js +87 -0
- package/dist/routing/route/route.d.ts +53 -0
- package/dist/routing/route/route.d.ts.map +1 -0
- package/dist/routing/route/route.js +148 -0
- package/dist/routing/route/url.d.ts +13 -0
- package/dist/routing/route/url.d.ts.map +1 -0
- package/dist/routing/route/url.js +79 -0
- package/dist/routing/route/values.d.ts +12 -0
- package/dist/routing/route/values.d.ts.map +1 -0
- package/dist/routing/route/values.js +47 -0
- package/dist/routing/router/context.d.ts +123 -0
- package/dist/routing/router/context.d.ts.map +1 -0
- package/dist/routing/router/context.js +156 -0
- package/dist/routing/router/data.d.ts +117 -0
- package/dist/routing/router/data.d.ts.map +1 -0
- package/dist/routing/router/data.js +247 -0
- package/dist/routing/router/define.d.ts +52 -0
- package/dist/routing/router/define.d.ts.map +1 -0
- package/dist/routing/router/define.js +48 -0
- package/dist/routing/router/frame.d.ts +10 -0
- package/dist/routing/router/frame.d.ts.map +1 -0
- package/dist/routing/router/frame.js +10 -0
- package/dist/routing/router/index.d.ts +7 -0
- package/dist/routing/router/index.d.ts.map +1 -0
- package/dist/routing/router/index.js +3 -0
- package/dist/routing/router/router.d.ts +165 -0
- package/dist/routing/router/router.d.ts.map +1 -0
- package/dist/routing/router/router.js +801 -0
- package/dist/routing/routing.d.ts +72 -0
- package/dist/routing/routing.d.ts.map +1 -0
- package/dist/routing/routing.js +53 -0
- package/dist/routing/scroll/index.d.ts +4 -0
- package/dist/routing/scroll/index.d.ts.map +1 -0
- package/dist/routing/scroll/index.js +2 -0
- package/dist/routing/scroll/script.d.ts +6 -0
- package/dist/routing/scroll/script.d.ts.map +1 -0
- package/dist/routing/scroll/script.js +52 -0
- package/dist/routing/scroll/scroll.d.ts +75 -0
- package/dist/routing/scroll/scroll.d.ts.map +1 -0
- package/dist/routing/scroll/scroll.js +201 -0
- package/dist/routing/scroll/selector.d.ts +14 -0
- package/dist/routing/scroll/selector.d.ts.map +1 -0
- package/dist/routing/scroll/selector.js +40 -0
- package/dist/routing/scroll/session.d.ts +6 -0
- package/dist/routing/scroll/session.d.ts.map +1 -0
- package/dist/routing/scroll/session.js +23 -0
- package/dist/start/client/client.d.ts +11 -0
- package/dist/start/client/client.d.ts.map +1 -0
- package/dist/start/client/client.js +24 -0
- package/dist/start/client/index.d.ts +3 -0
- package/dist/start/client/index.d.ts.map +1 -0
- package/dist/start/client/index.js +2 -0
- package/dist/start/client/rpc.d.ts +14 -0
- package/dist/start/client/rpc.d.ts.map +1 -0
- package/dist/start/client/rpc.js +70 -0
- package/dist/start/common/app.d.ts +66 -0
- package/dist/start/common/app.d.ts.map +1 -0
- package/dist/start/common/app.js +23 -0
- package/dist/start/common/common.d.ts +26 -0
- package/dist/start/common/common.d.ts.map +1 -0
- package/dist/start/common/common.js +19 -0
- package/dist/start/common/document.d.ts +44 -0
- package/dist/start/common/document.d.ts.map +1 -0
- package/dist/start/common/document.js +58 -0
- package/dist/start/common/index.d.ts +9 -0
- package/dist/start/common/index.d.ts.map +1 -0
- package/dist/start/common/index.js +5 -0
- package/dist/start/common/page.d.ts +67 -0
- package/dist/start/common/page.d.ts.map +1 -0
- package/dist/start/common/page.js +29 -0
- package/dist/start/common/wire.d.ts +37 -0
- package/dist/start/common/wire.d.ts.map +1 -0
- package/dist/start/common/wire.js +73 -0
- package/dist/start/platform/cloudflare/cloudflare.d.ts +8 -0
- package/dist/start/platform/cloudflare/cloudflare.d.ts.map +1 -0
- package/dist/start/platform/cloudflare/cloudflare.js +31 -0
- package/dist/start/platform/cloudflare/index.d.ts +2 -0
- package/dist/start/platform/cloudflare/index.d.ts.map +1 -0
- package/dist/start/platform/cloudflare/index.js +1 -0
- package/dist/start/platform/index.d.ts +2 -0
- package/dist/start/platform/index.d.ts.map +1 -0
- package/dist/start/platform/index.js +1 -0
- package/dist/start/platform/platform.d.ts +31 -0
- package/dist/start/platform/platform.d.ts.map +1 -0
- package/dist/start/platform/platform.js +13 -0
- package/dist/start/server/cache.d.ts +4 -0
- package/dist/start/server/cache.d.ts.map +1 -0
- package/dist/start/server/cache.js +26 -0
- package/dist/start/server/draw.d.ts +10 -0
- package/dist/start/server/draw.d.ts.map +1 -0
- package/dist/start/server/draw.js +75 -0
- package/dist/start/server/index.d.ts +3 -0
- package/dist/start/server/index.d.ts.map +1 -0
- package/dist/start/server/index.js +2 -0
- package/dist/start/server/layers.d.ts +97 -0
- package/dist/start/server/layers.d.ts.map +1 -0
- package/dist/start/server/layers.js +37 -0
- package/dist/start/server/respond.d.ts +15 -0
- package/dist/start/server/respond.d.ts.map +1 -0
- package/dist/start/server/respond.js +35 -0
- package/dist/start/server/rpc.d.ts +11 -0
- package/dist/start/server/rpc.d.ts.map +1 -0
- package/dist/start/server/rpc.js +51 -0
- package/dist/start/server/server.d.ts +13 -0
- package/dist/start/server/server.d.ts.map +1 -0
- package/dist/start/server/server.js +138 -0
- package/dist/start/server/tiers.d.ts +14 -0
- package/dist/start/server/tiers.d.ts.map +1 -0
- package/dist/start/server/tiers.js +31 -0
- package/dist/start/vite/dev.d.ts +5 -0
- package/dist/start/vite/dev.d.ts.map +1 -0
- package/dist/start/vite/dev.js +56 -0
- package/dist/start/vite/entries.d.ts +27 -0
- package/dist/start/vite/entries.d.ts.map +1 -0
- package/dist/start/vite/entries.js +66 -0
- package/dist/start/vite/files.d.ts +22 -0
- package/dist/start/vite/files.d.ts.map +1 -0
- package/dist/start/vite/files.js +23 -0
- package/dist/start/vite/index.d.ts +2 -0
- package/dist/start/vite/index.d.ts.map +1 -0
- package/dist/start/vite/index.js +1 -0
- package/dist/start/vite/lazy.d.ts +4 -0
- package/dist/start/vite/lazy.d.ts.map +1 -0
- package/dist/start/vite/lazy.js +30 -0
- package/dist/start/vite/manifest.d.ts +18 -0
- package/dist/start/vite/manifest.d.ts.map +1 -0
- package/dist/start/vite/manifest.js +42 -0
- package/dist/start/vite/styles.d.ts +7 -0
- package/dist/start/vite/styles.d.ts.map +1 -0
- package/dist/start/vite/styles.js +58 -0
- package/dist/start/vite/vite.d.ts +11 -0
- package/dist/start/vite/vite.d.ts.map +1 -0
- package/dist/start/vite/vite.js +217 -0
- package/package.json +53 -3
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Context, Effect, Exit, Layer, Scope, Stream } from 'effect';
|
|
2
|
+
import { Loading, Router } from '../../routing/index.js';
|
|
3
|
+
import { knownOf } from '../../routing/page/index.js';
|
|
4
|
+
import { Document, PATH, appOf } from '../common/index.js';
|
|
5
|
+
import { kept } from './cache.js';
|
|
6
|
+
import { drawer } from './draw.js';
|
|
7
|
+
import { loadersOf } from './layers.js';
|
|
8
|
+
import { answer } from './rpc.js';
|
|
9
|
+
import { tiers } from './tiers.js';
|
|
10
|
+
import { PRIVATE, SHARED, broken, found, headOf, html, notAllowed, permanent, } from './respond.js';
|
|
11
|
+
/** The places drawn on the server: every place above the first client one. */
|
|
12
|
+
const drawnHere = (places) => {
|
|
13
|
+
const client = places.findIndex((place) => place.render === 'client');
|
|
14
|
+
return client < 0 ? places : places.slice(0, client);
|
|
15
|
+
};
|
|
16
|
+
/** The path of a URL, without its query. */
|
|
17
|
+
const pathOf = (url, base) => new URL(url, base).pathname;
|
|
18
|
+
export const server = (options) => {
|
|
19
|
+
const { build } = options;
|
|
20
|
+
const { app, document = Document } = appOf(options.app);
|
|
21
|
+
const { loaders, tiers: given } = loadersOf(options.loaders);
|
|
22
|
+
const draw = drawer(app, document, build);
|
|
23
|
+
const layerFor = tiers(given);
|
|
24
|
+
const modes = new Map(app.routes.every.map((place) => [place.name, place.render]));
|
|
25
|
+
// 4. Load
|
|
26
|
+
/** What the Loaders of `wanted` run with: the memory Loading, handed this request, and their tiers. */
|
|
27
|
+
const loadingFor = (request, env, headers, wanted) => Router.memory(app.routes, loaders, {
|
|
28
|
+
request: () => request,
|
|
29
|
+
}).pipe(Layer.provide(layerFor({
|
|
30
|
+
env,
|
|
31
|
+
request,
|
|
32
|
+
headers,
|
|
33
|
+
visitor: wanted.some(({ name }) => modes.get(name) !== 'static'),
|
|
34
|
+
})));
|
|
35
|
+
/** Runs the Loaders of `wanted` at once, as the memory Loading does, handed this request. */
|
|
36
|
+
const load = (request, env, headers) => (wanted) => wanted.length === 0
|
|
37
|
+
? Effect.succeed({})
|
|
38
|
+
: Effect.gen(function* () {
|
|
39
|
+
const loading = yield* Loading;
|
|
40
|
+
return yield* loading.load(wanted);
|
|
41
|
+
}).pipe(Effect.provide(loadingFor(request, env, headers, wanted)));
|
|
42
|
+
/**
|
|
43
|
+
* The Loaders of `wanted`, each answered as it is done, for the Loader
|
|
44
|
+
* RPC. What they run with is made first, so the headers it adds are there
|
|
45
|
+
* before the answer starts; it is let go once the last place is answered.
|
|
46
|
+
*/
|
|
47
|
+
const each = (request, env, headers) => async (wanted) => {
|
|
48
|
+
const scope = await Effect.runPromise(Scope.make());
|
|
49
|
+
const close = Scope.close(scope, Exit.void);
|
|
50
|
+
const context = await Effect.runPromise(Layer.buildWithScope(loadingFor(request, env, headers, wanted), scope).pipe(Effect.onError(() => close)));
|
|
51
|
+
const loading = Context.get(context, Loading);
|
|
52
|
+
return (loading.each
|
|
53
|
+
? loading.each(wanted)
|
|
54
|
+
: Stream.unwrap(Effect.map(loading.load(wanted), (answers) => Stream.fromIterable(Object.entries(answers))))).pipe(Stream.ensuring(close));
|
|
55
|
+
};
|
|
56
|
+
/** What each place with data drawn here came to; a Redirect, the outermost first, instead. */
|
|
57
|
+
const loaded = async (run, places) => {
|
|
58
|
+
const wanted = places.filter((place) => place.input);
|
|
59
|
+
const outcomes = await Effect.runPromise(run(wanted.map(({ name, params }) => ({ name, params }))));
|
|
60
|
+
const known = {};
|
|
61
|
+
let status = 200;
|
|
62
|
+
for (const { name } of wanted) {
|
|
63
|
+
const outcome = outcomes[name] ?? {
|
|
64
|
+
_tag: 'Broken',
|
|
65
|
+
reason: `no answer for ${name}`,
|
|
66
|
+
};
|
|
67
|
+
if (outcome._tag === 'Redirect')
|
|
68
|
+
return outcome;
|
|
69
|
+
if (outcome._tag === 'NotFound')
|
|
70
|
+
status = Math.max(status, 404);
|
|
71
|
+
if (outcome._tag === 'Broken') {
|
|
72
|
+
console.error(`[@kestack/kabuki/start] the Loader of ${name} broke:`, outcome.reason);
|
|
73
|
+
status = 500;
|
|
74
|
+
}
|
|
75
|
+
known[name] = knownOf(outcome, Date.now(), 'server');
|
|
76
|
+
}
|
|
77
|
+
return { _tag: 'Loaded', known, status };
|
|
78
|
+
};
|
|
79
|
+
// 5. Draw
|
|
80
|
+
const drawn = async (at, status, cache) => html(await draw(at), status, cache);
|
|
81
|
+
const page = async (request, host, headers) => {
|
|
82
|
+
// 2. Read
|
|
83
|
+
const url = new URL(request.url);
|
|
84
|
+
const at = app.startAt(`${url.pathname}${url.search}`);
|
|
85
|
+
if (!at)
|
|
86
|
+
return new Response(null, { status: 404 });
|
|
87
|
+
const route = at.route;
|
|
88
|
+
const written = app.href(route);
|
|
89
|
+
if (route._tag !== 'NotFound' && pathOf(written, url) !== url.pathname)
|
|
90
|
+
return permanent(written.includes('?') ? written : `${written}${url.search}`);
|
|
91
|
+
// 3. Split
|
|
92
|
+
const here = drawnHere(app.routes.places(route));
|
|
93
|
+
const shared = build.id !== undefined &&
|
|
94
|
+
here.every((place) => place.render === 'static');
|
|
95
|
+
const cache = shared ? SHARED : PRIVATE;
|
|
96
|
+
const drawPage = async () => {
|
|
97
|
+
const answer = await loaded(load(request, host.env, headers), here);
|
|
98
|
+
if (answer._tag === 'Redirect') {
|
|
99
|
+
const to = answer.href ?? app.href(answer.route);
|
|
100
|
+
return answer.permanent ? permanent(to) : found(to);
|
|
101
|
+
}
|
|
102
|
+
const status = route._tag === 'NotFound' ? 404 : answer.status;
|
|
103
|
+
return drawn({ route, known: answer.known }, status, status === 200 ? cache : PRIVATE);
|
|
104
|
+
};
|
|
105
|
+
// 6. Keep
|
|
106
|
+
return shared
|
|
107
|
+
? kept(host.cache, SHARED, build.id, url, drawPage)
|
|
108
|
+
: drawPage();
|
|
109
|
+
};
|
|
110
|
+
/** The response, with the headers the Loaders added. */
|
|
111
|
+
const sent = (response, headers) => {
|
|
112
|
+
for (const [name, value] of headers)
|
|
113
|
+
response.headers.append(name, value);
|
|
114
|
+
return response;
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
fetch: async (request, host) => {
|
|
118
|
+
try {
|
|
119
|
+
const headers = new Headers();
|
|
120
|
+
// 1. Route
|
|
121
|
+
const { pathname } = new URL(request.url);
|
|
122
|
+
if (pathname === PATH)
|
|
123
|
+
return request.method === 'POST'
|
|
124
|
+
? sent(await answer(app.routes, request, each(request, host.env, headers)), headers)
|
|
125
|
+
: new Response(null, { status: 405, headers: { allow: 'POST' } });
|
|
126
|
+
if (request.method === 'HEAD')
|
|
127
|
+
return headOf(sent(await page(new Request(request, { method: 'GET' }), host, headers), headers));
|
|
128
|
+
if (request.method !== 'GET')
|
|
129
|
+
return notAllowed();
|
|
130
|
+
return sent(await page(request, host, headers), headers);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error('[@kestack/kabuki/start] a request failed:', error);
|
|
134
|
+
return broken();
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import type { Tiers } from './layers.ts';
|
|
3
|
+
/** What one request's Loaders run with. */
|
|
4
|
+
export interface Asked {
|
|
5
|
+
readonly env: unknown;
|
|
6
|
+
readonly request: Request;
|
|
7
|
+
/** The headers its response is sent with. */
|
|
8
|
+
readonly headers: Headers;
|
|
9
|
+
/** Whether it loads a dynamic or client place, which may use the visitor tier. */
|
|
10
|
+
readonly visitor: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** The Layer each request's Loaders run with, the instance tier made once for all of them. */
|
|
13
|
+
export declare const tiers: (layers: Tiers) => (asked: Asked) => Layer.Layer<any, never, never>;
|
|
14
|
+
//# sourceMappingURL=tiers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tiers.d.ts","sourceRoot":"","sources":["../../../src/start/server/tiers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,EAAS,MAAM,QAAQ,CAAC;AAG9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAyBzC,2CAA2C;AAC3C,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,8FAA8F;AAC9F,eAAO,MAAM,KAAK,WAAY,KAAK,aAelB,KAAK,KAAG,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAuBrD,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Effect, Layer, Scope } from 'effect';
|
|
2
|
+
import { ResponseHeaders } from './layers.js';
|
|
3
|
+
const made = (tier, env) => (tier === undefined ? Layer.empty : tier(env));
|
|
4
|
+
/** The Layer each request's Loaders run with, the instance tier made once for all of them. */
|
|
5
|
+
export const tiers = (layers) => {
|
|
6
|
+
let instance;
|
|
7
|
+
const kept = (env) => {
|
|
8
|
+
if (!instance) {
|
|
9
|
+
const making = Effect.runPromise(Layer.buildWithScope(made(layers.instance, env), Scope.makeUnsafe()));
|
|
10
|
+
instance = making;
|
|
11
|
+
making.catch(() => {
|
|
12
|
+
if (instance === making)
|
|
13
|
+
instance = undefined;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return instance;
|
|
17
|
+
};
|
|
18
|
+
return (asked) => Layer.unwrap(Effect.promise(() => kept(asked.env)).pipe(Effect.map((context) => {
|
|
19
|
+
const shared = made(layers.request, asked.env).pipe(Layer.provideMerge(Layer.succeedContext(context)));
|
|
20
|
+
if (!asked.visitor)
|
|
21
|
+
return shared;
|
|
22
|
+
const above = Layer.merge(shared, Layer.succeed(ResponseHeaders, {
|
|
23
|
+
append: (name, value) => Effect.sync(() => asked.headers.append(name, value)),
|
|
24
|
+
}));
|
|
25
|
+
return layers.visitor
|
|
26
|
+
? layers
|
|
27
|
+
.visitor({ env: asked.env, request: asked.request })
|
|
28
|
+
.pipe(Layer.provideMerge(above))
|
|
29
|
+
: above;
|
|
30
|
+
}))).pipe(Layer.orDie);
|
|
31
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { Handler } from '../platform/index.ts';
|
|
3
|
+
/** A Connect middleware that answers every request it is handed with the Worker. */
|
|
4
|
+
export declare const serveHandler: (load: () => Promise<Handler>, onError: (error: Error) => void) => (incoming: IncomingMessage, outgoing: ServerResponse, next: (error?: unknown) => void) => Promise<void>;
|
|
5
|
+
//# sourceMappingURL=dev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../src/start/vite/dev.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AA4CpD,oFAAoF;AACpF,eAAO,MAAM,YAAY,SAChB,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,gBAElD,eAAe,YACf,cAAc,QAClB,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,kBAYhC,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
/*
|
|
3
|
+
* `vite dev` with nothing else to run the server: Start's handler, which
|
|
4
|
+
* knows no platform, run in Vite's own server environment, in Node, each
|
|
5
|
+
* request handed to it with an empty env and no Cache, and its response
|
|
6
|
+
* written back. Where a platform's own plugin runs the server, as the
|
|
7
|
+
* Cloudflare plugin under `alchemy dev` runs workerd, this stands aside.
|
|
8
|
+
*/
|
|
9
|
+
const toRequest = (incoming) => {
|
|
10
|
+
const host = incoming.headers.host ?? 'localhost';
|
|
11
|
+
const url = new URL(incoming.url ?? '/', `http://${host}`);
|
|
12
|
+
const headers = new Headers();
|
|
13
|
+
for (const [name, value] of Object.entries(incoming.headers)) {
|
|
14
|
+
if (value === undefined || name.startsWith(':'))
|
|
15
|
+
continue;
|
|
16
|
+
for (const each of Array.isArray(value) ? value : [value])
|
|
17
|
+
headers.append(name, each);
|
|
18
|
+
}
|
|
19
|
+
const hasBody = incoming.method !== 'GET' && incoming.method !== 'HEAD';
|
|
20
|
+
return new Request(url, {
|
|
21
|
+
method: incoming.method ?? 'GET',
|
|
22
|
+
headers,
|
|
23
|
+
...(hasBody
|
|
24
|
+
? {
|
|
25
|
+
body: Readable.toWeb(incoming),
|
|
26
|
+
duplex: 'half',
|
|
27
|
+
}
|
|
28
|
+
: {}),
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const send = async (response, outgoing) => {
|
|
32
|
+
outgoing.statusCode = response.status;
|
|
33
|
+
for (const [name, value] of response.headers)
|
|
34
|
+
if (name !== 'set-cookie')
|
|
35
|
+
outgoing.setHeader(name, value);
|
|
36
|
+
const cookies = response.headers.getSetCookie();
|
|
37
|
+
if (cookies.length > 0)
|
|
38
|
+
outgoing.setHeader('set-cookie', cookies);
|
|
39
|
+
if (!response.body)
|
|
40
|
+
return void outgoing.end();
|
|
41
|
+
for await (const chunk of response.body)
|
|
42
|
+
outgoing.write(chunk);
|
|
43
|
+
outgoing.end();
|
|
44
|
+
};
|
|
45
|
+
/** A Connect middleware that answers every request it is handed with the Worker. */
|
|
46
|
+
export const serveHandler = (load, onError) => async (incoming, outgoing, next) => {
|
|
47
|
+
try {
|
|
48
|
+
const handler = await load();
|
|
49
|
+
await send(await handler.fetch(toRequest(incoming), { env: {} }), outgoing);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof Error)
|
|
53
|
+
onError(error);
|
|
54
|
+
next(error);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Build, Script } from '../common/index.ts';
|
|
2
|
+
import type { Platform } from '../platform/index.ts';
|
|
3
|
+
import type { Files } from './files.ts';
|
|
4
|
+
export declare const HANDLER = "virtual:kabuki-start/handler";
|
|
5
|
+
export declare const WORKER = "virtual:kabuki-start/worker";
|
|
6
|
+
export declare const CLIENT = "virtual:kabuki-start/client";
|
|
7
|
+
export declare const BUILD = "virtual:kabuki-start/build";
|
|
8
|
+
/** The name the client entry's chunk is given, to find it in the manifest. */
|
|
9
|
+
export declare const ENTRY = "kabuki-start";
|
|
10
|
+
/** Start's handler, made from the app's two files: what every platform serves. */
|
|
11
|
+
export declare const handlerModule: (files: Files) => string;
|
|
12
|
+
/** The server's entry: the handler in the platform's adapter, its only export. */
|
|
13
|
+
export declare const workerEntry: (platform: Platform) => string;
|
|
14
|
+
/**
|
|
15
|
+
* The browser's entry: hydrates the page the Worker drew. In dev it takes
|
|
16
|
+
* itself back whenever the app changes, and draws the page again with it,
|
|
17
|
+
* so an edit anywhere in the app is a hot reload, not a full one. React
|
|
18
|
+
* Refresh cannot swap the app, which is a value, not a component; the app
|
|
19
|
+
* carries on from its History, replayed with the new code. In dev it also
|
|
20
|
+
* turns off the styles the server linked in the head, which the app's own
|
|
21
|
+
* scripts have added again by then.
|
|
22
|
+
*/
|
|
23
|
+
export declare const clientEntry: (files: Files, devStyles?: string) => string;
|
|
24
|
+
export declare const buildModule: (build: Build) => string;
|
|
25
|
+
/** The scripts Vite writes in an HTML page's head in dev: its client, and React Refresh's preamble. */
|
|
26
|
+
export declare const devScripts: (html: string) => ReadonlyArray<Script>;
|
|
27
|
+
//# sourceMappingURL=entries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../../src/start/vite/entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AASxC,eAAO,MAAM,OAAO,iCAAiC,CAAC;AACtD,eAAO,MAAM,MAAM,gCAAgC,CAAC;AACpD,eAAO,MAAM,MAAM,gCAAgC,CAAC;AACpD,eAAO,MAAM,KAAK,+BAA+B,CAAC;AAElD,8EAA8E;AAC9E,eAAO,MAAM,KAAK,iBAAiB,CAAC;AAIpC,kFAAkF;AAClF,eAAO,MAAM,aAAa,UAAW,KAAK,WAS5B,CAAC;AAEf,kFAAkF;AAClF,eAAO,MAAM,WAAW,aAAc,QAAQ,WAKhC,CAAC;AAEf;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,UAAW,KAAK,cAAc,MAAM,WAmB9C,CAAC;AAEf,eAAO,MAAM,WAAW,UAAW,KAAK,WACI,CAAC;AAE7C,uGAAuG;AACvG,eAAO,MAAM,UAAU,SAAU,MAAM,KAAG,aAAa,CAAC,MAAM,CAS7D,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The modules the plugin writes, so the app writes no entry: Start's
|
|
3
|
+
* handler, which knows no platform; the server's entry, which hands it to
|
|
4
|
+
* the platform's adapter; the browser's; and what the server knows of the
|
|
5
|
+
* build.
|
|
6
|
+
*/
|
|
7
|
+
export const HANDLER = 'virtual:kabuki-start/handler';
|
|
8
|
+
export const WORKER = 'virtual:kabuki-start/worker';
|
|
9
|
+
export const CLIENT = 'virtual:kabuki-start/client';
|
|
10
|
+
export const BUILD = 'virtual:kabuki-start/build';
|
|
11
|
+
/** The name the client entry's chunk is given, to find it in the manifest. */
|
|
12
|
+
export const ENTRY = 'kabuki-start';
|
|
13
|
+
const from = (file) => JSON.stringify(file.replace(/\\/g, '/'));
|
|
14
|
+
/** Start's handler, made from the app's two files: what every platform serves. */
|
|
15
|
+
export const handlerModule = (files) => [
|
|
16
|
+
`import { server } from '@kestack/kabuki/start/server';`,
|
|
17
|
+
`import app from ${from(files.app)};`,
|
|
18
|
+
files.loaders
|
|
19
|
+
? `import loaders from ${from(files.loaders)};`
|
|
20
|
+
: `const loaders = undefined;`,
|
|
21
|
+
`import build from '${BUILD}';`,
|
|
22
|
+
`export default server({ app, loaders, build });`,
|
|
23
|
+
].join('\n');
|
|
24
|
+
/** The server's entry: the handler in the platform's adapter, its only export. */
|
|
25
|
+
export const workerEntry = (platform) => [
|
|
26
|
+
`import { ${platform.serve.name} as serve } from ${JSON.stringify(platform.serve.from)};`,
|
|
27
|
+
`import handler from '${HANDLER}';`,
|
|
28
|
+
`export default serve(handler);`,
|
|
29
|
+
].join('\n');
|
|
30
|
+
/**
|
|
31
|
+
* The browser's entry: hydrates the page the Worker drew. In dev it takes
|
|
32
|
+
* itself back whenever the app changes, and draws the page again with it,
|
|
33
|
+
* so an edit anywhere in the app is a hot reload, not a full one. React
|
|
34
|
+
* Refresh cannot swap the app, which is a value, not a component; the app
|
|
35
|
+
* carries on from its History, replayed with the new code. In dev it also
|
|
36
|
+
* turns off the styles the server linked in the head, which the app's own
|
|
37
|
+
* scripts have added again by then.
|
|
38
|
+
*/
|
|
39
|
+
export const clientEntry = (files, devStyles) => [
|
|
40
|
+
`import { client } from '@kestack/kabuki/start/client';`,
|
|
41
|
+
`import app from ${from(files.app)};`,
|
|
42
|
+
// The app's scripts have added its styles: the server's copy goes off.
|
|
43
|
+
...(devStyles
|
|
44
|
+
? [
|
|
45
|
+
`for (const link of window.document.querySelectorAll('link[rel="stylesheet"][href=${JSON.stringify(devStyles)}]'))`,
|
|
46
|
+
` if (link.sheet) link.sheet.disabled = true;`,
|
|
47
|
+
]
|
|
48
|
+
: []),
|
|
49
|
+
`const drawn = import.meta.hot?.data.hydrated;`,
|
|
50
|
+
`const hydrated = drawn`,
|
|
51
|
+
` ? drawn.then((page) => (page.redraw(app), page))`,
|
|
52
|
+
` : client(app);`,
|
|
53
|
+
`if (import.meta.hot) {`,
|
|
54
|
+
` import.meta.hot.data.hydrated = hydrated;`,
|
|
55
|
+
` import.meta.hot.accept();`,
|
|
56
|
+
`}`,
|
|
57
|
+
].join('\n');
|
|
58
|
+
export const buildModule = (build) => `export default ${JSON.stringify(build)};`;
|
|
59
|
+
/** The scripts Vite writes in an HTML page's head in dev: its client, and React Refresh's preamble. */
|
|
60
|
+
export const devScripts = (html) => {
|
|
61
|
+
const head = /<head>([\s\S]*?)<\/head>/.exec(html)?.[1] ?? '';
|
|
62
|
+
return Array.from(head.matchAll(/<script([^>]*)>([\s\S]*?)<\/script>/g), ([, attributes = '', code = '']) => {
|
|
63
|
+
const src = /\bsrc="([^"]*)"/.exec(attributes)?.[1];
|
|
64
|
+
return src !== undefined ? { src } : { code };
|
|
65
|
+
});
|
|
66
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Where Start finds each of the app's files, from the Vite root. */
|
|
2
|
+
export interface FileOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Default-exports the app, with its Document and what it needs in the
|
|
5
|
+
* browser: `Start.app(router, AppView, { … })`, from
|
|
6
|
+
* `@kestack/kabuki/start`. By default `src/app`.
|
|
7
|
+
*/
|
|
8
|
+
readonly app?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Default-exports the Loaders, with what they need on the server:
|
|
11
|
+
* `Start.layers()…loaders(Routes.loaders({ … }))`, from
|
|
12
|
+
* `@kestack/kabuki/start/server`; optional. By default
|
|
13
|
+
* `src/loaders.server`.
|
|
14
|
+
*/
|
|
15
|
+
readonly loaders?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface Files {
|
|
18
|
+
readonly app: string;
|
|
19
|
+
readonly loaders: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
export declare const findFiles: (root: string, options: FileOptions) => Files;
|
|
22
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/start/vite/files.ts"],"names":[],"mappings":"AAUA,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAsBD,eAAO,MAAM,SAAS,SAAU,MAAM,WAAW,WAAW,KAAG,KAM7D,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js', '.mts', '.mjs'];
|
|
4
|
+
/** The file at `name`, as written or with the first extension that exists. */
|
|
5
|
+
const find = (root, name) => {
|
|
6
|
+
const at = path.resolve(root, name);
|
|
7
|
+
if (path.extname(at) && existsSync(at))
|
|
8
|
+
return at;
|
|
9
|
+
return EXTENSIONS.map((extension) => `${at}${extension}`).find((file) => existsSync(file));
|
|
10
|
+
};
|
|
11
|
+
const required = (root, name, what) => {
|
|
12
|
+
const found = find(root, name);
|
|
13
|
+
if (!found)
|
|
14
|
+
throw new Error(`[@kestack/kabuki/start] no ${what} at ${path.join(root, name)}: write it, or name where it is in start({ … })`);
|
|
15
|
+
return found;
|
|
16
|
+
};
|
|
17
|
+
export const findFiles = (root, options) => ({
|
|
18
|
+
app: required(root, options.app ?? 'src/app', 'app'),
|
|
19
|
+
// Left out, it may be missing; named in the options, it must be there.
|
|
20
|
+
loaders: options.loaders
|
|
21
|
+
? required(root, options.loaders, 'Loaders')
|
|
22
|
+
: find(root, 'src/loaders.server'),
|
|
23
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/start/vite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { start } from './vite.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const mentionsLazy: (code: string) => boolean;
|
|
2
|
+
/** The code with an id on every lazy View that has none; `undefined` if there is none. */
|
|
3
|
+
export declare const withIds: (code: string, root: string, resolve: (specifier: string) => Promise<string | undefined>) => Promise<string | undefined>;
|
|
4
|
+
//# sourceMappingURL=lazy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy.d.ts","sourceRoot":"","sources":["../../../src/start/vite/lazy.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,YAAY,SAAU,MAAM,YAA+B,CAAC;AAEzE,0FAA0F;AAC1F,eAAO,MAAM,OAAO,SACZ,MAAM,QACN,MAAM,WACH,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,KAC1D,OAAO,CAAC,MAAM,GAAG,SAAS,CAiB5B,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
/*
|
|
3
|
+
* Every `View.lazy(Actor, () => import('./x'))` given the id of the module it
|
|
4
|
+
* loads: its path from the Vite root, which is its key in the client
|
|
5
|
+
* manifest. The server and the browser both see the same id, so a page
|
|
6
|
+
* names the lazy Views it drew and the browser preloads them.
|
|
7
|
+
*/
|
|
8
|
+
const LAZY = /View\.lazy\(\s*[\w$.]+\s*,\s*\(\)\s*=>\s*import\(\s*(['"`])([^'"`]+)\1\s*\)\s*\)/g;
|
|
9
|
+
export const mentionsLazy = (code) => code.includes('View.lazy');
|
|
10
|
+
/** The code with an id on every lazy View that has none; `undefined` if there is none. */
|
|
11
|
+
export const withIds = async (code, root, resolve) => {
|
|
12
|
+
const found = [...code.matchAll(LAZY)];
|
|
13
|
+
if (found.length === 0)
|
|
14
|
+
return undefined;
|
|
15
|
+
let out = '';
|
|
16
|
+
let at = 0;
|
|
17
|
+
for (const match of found) {
|
|
18
|
+
const resolved = await resolve(match[2]);
|
|
19
|
+
if (!resolved)
|
|
20
|
+
continue;
|
|
21
|
+
const id = path
|
|
22
|
+
.relative(root, resolved.split('?')[0])
|
|
23
|
+
.split(path.sep)
|
|
24
|
+
.join('/');
|
|
25
|
+
const end = match.index + match[0].length - 1;
|
|
26
|
+
out += `${code.slice(at, end)}, { id: ${JSON.stringify(id)} })`;
|
|
27
|
+
at = end + 1;
|
|
28
|
+
}
|
|
29
|
+
return at === 0 ? undefined : out + code.slice(at);
|
|
30
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Assets } from '../common/index.ts';
|
|
2
|
+
interface Chunk {
|
|
3
|
+
readonly file: string;
|
|
4
|
+
readonly name?: string;
|
|
5
|
+
readonly isEntry?: boolean;
|
|
6
|
+
readonly isDynamicEntry?: boolean;
|
|
7
|
+
readonly imports?: ReadonlyArray<string>;
|
|
8
|
+
readonly css?: ReadonlyArray<string>;
|
|
9
|
+
}
|
|
10
|
+
export type Manifest = Readonly<Record<string, Chunk>>;
|
|
11
|
+
export declare const filesOf: (manifest: Manifest, base: string) => {
|
|
12
|
+
entry: Assets;
|
|
13
|
+
views: {
|
|
14
|
+
[k: string]: Assets;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/start/vite/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AASjD,UAAU,KAAK;IACb,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACtC;AAED,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AA2BvD,eAAO,MAAM,OAAO,aAAc,QAAQ,QAAQ,MAAM;IAUpD,KAAK;IACL,KAAK;;;CAMR,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ENTRY } from './entries.js';
|
|
2
|
+
/** A chunk's script, its styles and those of every chunk it imports, and those chunks. */
|
|
3
|
+
const assetsOf = (manifest, key, base) => {
|
|
4
|
+
const styles = new Set();
|
|
5
|
+
const preloads = new Set();
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
const walk = (at) => {
|
|
8
|
+
if (seen.has(at))
|
|
9
|
+
return;
|
|
10
|
+
seen.add(at);
|
|
11
|
+
const chunk = manifest[at];
|
|
12
|
+
if (!chunk)
|
|
13
|
+
return;
|
|
14
|
+
for (const css of chunk.css ?? [])
|
|
15
|
+
styles.add(`${base}${css}`);
|
|
16
|
+
for (const imported of chunk.imports ?? []) {
|
|
17
|
+
const file = manifest[imported]?.file;
|
|
18
|
+
if (file)
|
|
19
|
+
preloads.add(`${base}${file}`);
|
|
20
|
+
walk(imported);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
walk(key);
|
|
24
|
+
return {
|
|
25
|
+
script: `${base}${manifest[key].file}`,
|
|
26
|
+
styles: [...styles],
|
|
27
|
+
preloads: [...preloads],
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export const filesOf = (manifest, base) => {
|
|
31
|
+
const entries = Object.entries(manifest);
|
|
32
|
+
const entry = entries.find(([, chunk]) => chunk.isEntry && chunk.name === ENTRY) ??
|
|
33
|
+
entries.find(([, chunk]) => chunk.isEntry);
|
|
34
|
+
if (!entry)
|
|
35
|
+
throw new Error('[@kestack/kabuki/start] the client build has no entry: is another plugin building the client?');
|
|
36
|
+
return {
|
|
37
|
+
entry: assetsOf(manifest, entry[0], base),
|
|
38
|
+
views: Object.fromEntries(entries
|
|
39
|
+
.filter(([, chunk]) => chunk.isDynamicEntry)
|
|
40
|
+
.map(([key]) => [key, assetsOf(manifest, key, base)])),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { ViteDevServer } from 'vite';
|
|
3
|
+
/** Where the stylesheet is served, after Vite's `base`. */
|
|
4
|
+
export declare const DEV_STYLES = "@kabuki-start/styles.css";
|
|
5
|
+
/** The stylesheet the head links in dev: an `@import` of each of the app's, from the server's drawing, else the browser's. */
|
|
6
|
+
export declare const serveStyles: (server: ViteDevServer, files: () => ReadonlyArray<string>) => (incoming: IncomingMessage, outgoing: ServerResponse, next: () => void) => void;
|
|
7
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../src/start/vite/styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAyB,aAAa,EAAE,MAAM,MAAM,CAAC;AAYjE,2DAA2D;AAC3D,eAAO,MAAM,UAAU,6BAA6B,CAAC;AA+BrD,8HAA8H;AAC9H,eAAO,MAAM,WAAW,WACb,aAAa,SAAS,MAAM,aAAa,CAAC,MAAM,CAAC,gBAC/C,eAAe,YAAY,cAAc,QAAQ,MAAM,IAAI,SAwBrE,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The app's styles in dev, before its scripts. Vite serves a stylesheet as
|
|
3
|
+
* a module that adds it to the page when it runs, so a page the server drew
|
|
4
|
+
* is painted bare until the browser has loaded the app's scripts. The head
|
|
5
|
+
* links one stylesheet, which imports every stylesheet the app imported
|
|
6
|
+
* while the server drew it, as plain CSS; the browser waits for them before
|
|
7
|
+
* the first paint. Once the app's scripts have added their own, the client
|
|
8
|
+
* entry turns it off, so an edit is drawn by Vite's styles alone.
|
|
9
|
+
*/
|
|
10
|
+
/** Where the stylesheet is served, after Vite's `base`. */
|
|
11
|
+
export const DEV_STYLES = '@kabuki-start/styles.css';
|
|
12
|
+
const CSS = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
|
|
13
|
+
const NOT_A_STYLESHEET = /[?&](?:inline|url|raw|worker|sharedworker)\b/;
|
|
14
|
+
/** Every stylesheet imported, at any depth, by the modules of `files`, in the order they are imported. */
|
|
15
|
+
const stylesOf = (graph, files) => {
|
|
16
|
+
const styles = [];
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
const walk = (module) => {
|
|
19
|
+
if (seen.has(module))
|
|
20
|
+
return;
|
|
21
|
+
seen.add(module);
|
|
22
|
+
if (CSS.test(module.url)) {
|
|
23
|
+
// A stylesheet's own imports are already in it, once Vite has served it.
|
|
24
|
+
if (!NOT_A_STYLESHEET.test(module.url))
|
|
25
|
+
styles.push(module.url);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
for (const imported of module.importedModules)
|
|
29
|
+
walk(imported);
|
|
30
|
+
};
|
|
31
|
+
for (const file of files)
|
|
32
|
+
for (const module of graph.getModulesByFile(file) ?? [])
|
|
33
|
+
walk(module);
|
|
34
|
+
return styles;
|
|
35
|
+
};
|
|
36
|
+
/** The stylesheet the head links in dev: an `@import` of each of the app's, from the server's drawing, else the browser's. */
|
|
37
|
+
export const serveStyles = (server, files) => (incoming, outgoing, next) => {
|
|
38
|
+
const base = server.config.base;
|
|
39
|
+
if (incoming.url?.split('?')[0] !== `${base}${DEV_STYLES}`)
|
|
40
|
+
return next();
|
|
41
|
+
const environments = Object.values(server.environments);
|
|
42
|
+
const drawn = environments
|
|
43
|
+
.filter((environment) => environment.name !== 'client')
|
|
44
|
+
.flatMap((environment) => stylesOf(environment.moduleGraph, files()));
|
|
45
|
+
const styles = [
|
|
46
|
+
...new Set(drawn.length > 0
|
|
47
|
+
? drawn
|
|
48
|
+
: stylesOf(server.environments.client.moduleGraph, files())),
|
|
49
|
+
];
|
|
50
|
+
outgoing.setHeader('content-type', 'text/css');
|
|
51
|
+
outgoing.setHeader('cache-control', 'no-store');
|
|
52
|
+
outgoing.end(styles
|
|
53
|
+
.map((url) => {
|
|
54
|
+
const href = `${base}${url.replace(/^\//, '')}${url.includes('?') ? '&' : '?'}direct`;
|
|
55
|
+
return `@import url(${JSON.stringify(href)});`;
|
|
56
|
+
})
|
|
57
|
+
.join('\n'));
|
|
58
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import type { Platform } from '../platform/index.ts';
|
|
3
|
+
import type { FileOptions } from './files.ts';
|
|
4
|
+
/** Where the server runs, and where Start finds the app's files, from the Vite root. */
|
|
5
|
+
export interface StartOptions extends FileOptions {
|
|
6
|
+
/** Where the server runs, as its adapter says: `cloudflare()` from `@kestack/kabuki/start/platform/cloudflare`. */
|
|
7
|
+
readonly platform: Platform;
|
|
8
|
+
}
|
|
9
|
+
/** Start's Vite plugin. Put it beside the React plugin: `plugins: [react(), start({ platform: cloudflare() })]`. */
|
|
10
|
+
export declare const start: (options: StartOptions) => Plugin;
|
|
11
|
+
//# sourceMappingURL=vite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../../../src/start/vite/vite.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAElD,OAAO,KAAK,EAAW,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAe9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA6B9C,wFAAwF;AACxF,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,mHAAmH;IACnH,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAcD,oHAAoH;AACpH,eAAO,MAAM,KAAK,YAAa,YAAY,KAAG,MA2O7C,CAAC"}
|