@jk2908/solas 0.3.0 → 0.3.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/CHANGELOG.md +12 -0
- package/dist/cli/build.d.ts +7 -0
- package/dist/cli/build.js +183 -0
- package/dist/cli/dev.d.ts +4 -0
- package/dist/cli/dev.js +13 -0
- package/dist/cli/preview.d.ts +1 -0
- package/dist/cli/preview.js +47 -0
- package/dist/cli.js +4 -238
- package/dist/index.js +2 -0
- package/dist/internal/browser-router/link.d.ts +17 -0
- package/dist/internal/{navigation → browser-router}/link.js +22 -16
- package/dist/internal/browser-router/router.d.ts +184 -0
- package/dist/internal/{router/router-provider.js → browser-router/router.js} +81 -12
- package/dist/internal/{router → browser-router}/use-router.d.ts +1 -1
- package/dist/internal/browser-router/use-router.js +5 -0
- package/dist/internal/{navigation → browser-router}/use-search-params.js +1 -1
- package/dist/internal/build.js +2 -2
- package/dist/internal/codegen/config.js +17 -8
- package/dist/internal/codegen/environments.js +7 -7
- package/dist/internal/codegen/manifest.js +3 -3
- package/dist/internal/codegen/maps.js +11 -15
- package/dist/internal/codegen/types.d.ts +5 -0
- package/dist/internal/codegen/types.js +48 -0
- package/dist/internal/codegen/utils.d.ts +10 -0
- package/dist/internal/codegen/utils.js +27 -2
- package/dist/internal/env/browser.js +6 -6
- package/dist/internal/env/flight.d.ts +29 -0
- package/dist/internal/env/flight.js +187 -0
- package/dist/internal/env/request-context.d.ts +1 -1
- package/dist/internal/env/rsc.d.ts +1 -1
- package/dist/internal/env/rsc.js +23 -28
- package/dist/internal/env/ssr.d.ts +2 -2
- package/dist/internal/env/ssr.js +27 -13
- package/dist/internal/env/utils.js +13 -1
- package/dist/internal/http-router/create-http-router.d.ts +6 -0
- package/dist/internal/{router/create-router.js → http-router/create-http-router.js} +5 -5
- package/dist/internal/{router → http-router}/router.d.ts +9 -9
- package/dist/internal/{router → http-router}/router.js +20 -19
- package/dist/internal/{router → http-router}/utils.d.ts +11 -3
- package/dist/internal/{router → http-router}/utils.js +9 -1
- package/dist/internal/metadata.js +10 -10
- package/dist/internal/prerender.d.ts +4 -9
- package/dist/internal/prerender.js +6 -23
- package/dist/internal/render/head.js +1 -1
- package/dist/internal/render/tree.d.ts +1 -1
- package/dist/internal/render/tree.js +17 -13
- package/dist/internal/{router/resolver.d.ts → resolver.d.ts} +41 -41
- package/dist/internal/{router/resolver.js → resolver.js} +7 -7
- package/dist/internal/server/actions.js +1 -1
- package/dist/internal/server/cookies.d.ts +3 -2
- package/dist/internal/server/cookies.js +4 -3
- package/dist/internal/server/dynamic.d.ts +1 -3
- package/dist/internal/server/dynamic.js +3 -11
- package/dist/internal/server/headers.d.ts +2 -2
- package/dist/internal/server/headers.js +3 -3
- package/dist/internal/server/url.d.ts +2 -2
- package/dist/internal/server/url.js +3 -3
- package/dist/navigation.d.ts +2 -4
- package/dist/navigation.js +2 -4
- package/dist/router.d.ts +3 -4
- package/dist/router.js +3 -4
- package/dist/solas.d.ts +3 -1
- package/dist/solas.js +1 -1
- package/dist/types.d.ts +15 -7
- package/dist/utils/logger.js +1 -1
- package/package.json +2 -7
- package/dist/internal/navigation/link.d.ts +0 -13
- package/dist/internal/router/create-router.d.ts +0 -6
- package/dist/internal/router/router-context.d.ts +0 -15
- package/dist/internal/router/router-context.js +0 -8
- package/dist/internal/router/router-provider.d.ts +0 -10
- package/dist/internal/router/use-router.js +0 -5
- /package/dist/internal/{navigation → browser-router}/use-search-params.d.ts +0 -0
- /package/dist/internal/{router/prefetcher.d.ts → prefetcher.d.ts} +0 -0
- /package/dist/internal/{router/prefetcher.js → prefetcher.js} +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { RscPayload } from '../env/rsc.js';
|
|
2
|
+
import { Solas } from '../../solas.js';
|
|
3
|
+
export declare namespace BrowserRouter {
|
|
4
|
+
export type Params = Record<string, string>;
|
|
5
|
+
export type Query = Record<string, string | number | boolean>;
|
|
6
|
+
export type Path = keyof Solas.Routes & string;
|
|
7
|
+
type Replace = {
|
|
8
|
+
replace?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type GoOptions = {
|
|
11
|
+
replace?: boolean;
|
|
12
|
+
query?: Query;
|
|
13
|
+
params?: Params;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* These targets are used as-is. They are not matched against the route table,
|
|
17
|
+
* so this covers normal external URLs and hash-only links
|
|
18
|
+
*/
|
|
19
|
+
export type ExternalTarget = `${string}:${string}` | `//${string}` | `#${string}`;
|
|
20
|
+
export function isHashOnlyTarget(target: string): boolean;
|
|
21
|
+
export function isExternalTarget(target: string, origin: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Turn a route pattern into the real path shape a caller can use. In practice,
|
|
24
|
+
* every ':param' or '*' part becomes a plain string slot
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* // '/p/:id' becomes '/p/${string}'
|
|
29
|
+
* // '/test/*' becomes '/test/${string}'
|
|
30
|
+
* // '/posts' stays '/posts'
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* type A = ResolvedPath<'/posts/:id'>
|
|
36
|
+
* // '/posts/${string}'
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* type B = ResolvedPath<'/docs/*'>
|
|
42
|
+
* // '/docs/${string}'
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export type ResolvedPath<TPath extends string> = TPath extends `${infer Start}:${string}/${infer Rest}` ? `${Start}${string}/${ResolvedPath<Rest>}` : TPath extends `${infer Start}:${string}` ? `${Start}${string}` : TPath extends `${infer Start}*${infer Rest}` ? `${Start}${string}${ResolvedPath<Rest>}` : TPath;
|
|
46
|
+
/**
|
|
47
|
+
* Once we have a real path, also allow the usual query-string and hash forms
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* type A = TargetSuffix<'/posts/123'>
|
|
52
|
+
* // '/posts/123' | '/posts/123?${string}' | '/posts/123#${string}' | '/posts/123?${string}#${string}'
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export type TargetSuffix<TPath extends string> = TPath | `${TPath}?${string}` | `${TPath}#${string}` | `${TPath}?${string}#${string}`;
|
|
56
|
+
/**
|
|
57
|
+
* This is the final string form a caller can navigate to. It can be an external
|
|
58
|
+
* URL, or a concrete URL that matches one of the known routes
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* type A = Target
|
|
63
|
+
* // 'https://example.com'
|
|
64
|
+
* // '#intro'
|
|
65
|
+
* // '/posts/123'
|
|
66
|
+
* // '/posts/123?draft=true'
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export type Target = ExternalTarget | TargetSuffix<ResolvedPath<Path>>;
|
|
70
|
+
/**
|
|
71
|
+
* Extra options for callers who already have a finished target string
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const a: TargetConfig = { query: { page: 2 } }
|
|
76
|
+
* // params is rejected here because the path is already complete
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
type TargetConfig = {
|
|
80
|
+
params?: never;
|
|
81
|
+
query?: Query;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Extra options for callers who pass a route pattern and params separately.
|
|
85
|
+
* If the route definition says that route needs params, this type makes
|
|
86
|
+
* those params required. If the route has no params, it rejects them
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // if Solas.Routes['/posts/:id'] is { params: { id: string } }
|
|
91
|
+
* type A = PatternConfig<'/posts/:id'>
|
|
92
|
+
* // { query?: Query } & { params: { id: string } }
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* // if Solas.Routes['/about'] has no params field
|
|
98
|
+
* type B = PatternConfig<'/about'>
|
|
99
|
+
* // { query?: Query } & { params?: never }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
type PatternConfig<TPath extends Path> = {
|
|
103
|
+
query?: Query;
|
|
104
|
+
} & (Solas.Routes[TPath] extends {
|
|
105
|
+
params: infer TParams extends Params;
|
|
106
|
+
} ? {
|
|
107
|
+
params: TParams;
|
|
108
|
+
} : {
|
|
109
|
+
params?: never;
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* Typed <Link /> props, using `href` instead of the internal `to` name
|
|
113
|
+
*
|
|
114
|
+
* `query` is always allowed
|
|
115
|
+
* `params` are only allowed when `href` is a known route pattern
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const a: LinkProps = { href: '/posts/:id', params: { id: '123' } }
|
|
120
|
+
* const b: LinkProps = { href: '/posts/123?draft=true' }
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export type LinkProps = ({
|
|
124
|
+
href: Target;
|
|
125
|
+
} & TargetConfig) | (keyof Solas.Routes extends never ? never : {
|
|
126
|
+
[TPath in Path]: {
|
|
127
|
+
href: TPath;
|
|
128
|
+
} & PatternConfig<TPath>;
|
|
129
|
+
}[Path]);
|
|
130
|
+
/**
|
|
131
|
+
* Typed input for router.go(), using the same route rules as <Link />
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* go('/p/post-2')
|
|
136
|
+
* go('/?foo=bar', { replace: true })
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* go('/p/:id', { params: { id: 'post-2' }, replace: true })
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* The last overload is the fallback for plain `string` values. The
|
|
145
|
+
* `string extends TTo` check stops that fallback from taking over
|
|
146
|
+
* when TypeScript already knows the caller passed a more specific
|
|
147
|
+
* string literal
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* declare const dynamicPath: string
|
|
152
|
+
* go(dynamicPath, { replace: true })
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export type Go = {
|
|
156
|
+
<TTo extends Path>(to: TTo, opts?: PatternConfig<TTo> & Replace): Promise<string>;
|
|
157
|
+
<TTo extends Target>(to: TTo, opts?: TargetConfig & Replace): Promise<string>;
|
|
158
|
+
<TTo extends string>(to: string extends TTo ? TTo : never, opts?: GoOptions): Promise<string>;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Convert a route pattern and params into a real path string. This is used internally
|
|
162
|
+
* to implement <Link /> and router.go
|
|
163
|
+
*/
|
|
164
|
+
export function toTarget(path: string, params?: Record<string, string>, query?: BrowserRouter.Query): string;
|
|
165
|
+
export {};
|
|
166
|
+
}
|
|
167
|
+
export declare const BrowserRouterContext: import("react").Context<{
|
|
168
|
+
go: BrowserRouter.Go;
|
|
169
|
+
prefetch: (path: string) => void;
|
|
170
|
+
isNavigating: boolean;
|
|
171
|
+
url: {
|
|
172
|
+
pathname?: string | undefined;
|
|
173
|
+
search?: string | undefined;
|
|
174
|
+
};
|
|
175
|
+
}>;
|
|
176
|
+
export declare function BrowserRouterProvider({ children, setPayload, isNavigating, url }: {
|
|
177
|
+
children: React.ReactNode;
|
|
178
|
+
setPayload?: (payload: RscPayload) => void;
|
|
179
|
+
isNavigating?: boolean;
|
|
180
|
+
url?: {
|
|
181
|
+
pathname?: string;
|
|
182
|
+
search?: string;
|
|
183
|
+
};
|
|
184
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,17 +1,85 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { createContext, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { createFromFetch } from '@vitejs/plugin-rsc/browser';
|
|
5
5
|
import { Logger } from '../../utils/logger.js';
|
|
6
6
|
import { Solas } from '../../solas.js';
|
|
7
|
-
import { Prefetcher } from '
|
|
8
|
-
|
|
7
|
+
import { Prefetcher } from './../prefetcher.js';
|
|
8
|
+
export { BrowserRouter };
|
|
9
|
+
var BrowserRouter;
|
|
10
|
+
(function (BrowserRouter) {
|
|
11
|
+
function isHashOnlyTarget(target) {
|
|
12
|
+
return target.startsWith('#');
|
|
13
|
+
}
|
|
14
|
+
BrowserRouter.isHashOnlyTarget = isHashOnlyTarget;
|
|
15
|
+
function isExternalTarget(target, origin) {
|
|
16
|
+
if (isHashOnlyTarget(target))
|
|
17
|
+
return false;
|
|
18
|
+
try {
|
|
19
|
+
return new URL(target, origin).origin !== origin;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
BrowserRouter.isExternalTarget = isExternalTarget;
|
|
26
|
+
/**
|
|
27
|
+
* Convert a route pattern and params into a real path string. This is used internally
|
|
28
|
+
* to implement <Link /> and router.go
|
|
29
|
+
*/
|
|
30
|
+
function toTarget(path, params, query) {
|
|
31
|
+
// keep track of which params were consumed by named `:param` slots
|
|
32
|
+
const used = new Set();
|
|
33
|
+
// replace each named route param with its URL-encoded value
|
|
34
|
+
let to = path.replaceAll(/:([A-Za-z0-9_]+)/g, (_, key) => {
|
|
35
|
+
const value = params?.[key];
|
|
36
|
+
if (value == null) {
|
|
37
|
+
throw new Error(`[Link]: missing route param: ${key}`);
|
|
38
|
+
}
|
|
39
|
+
used.add(key);
|
|
40
|
+
return encodeURIComponent(value);
|
|
41
|
+
});
|
|
42
|
+
if (to.includes('*')) {
|
|
43
|
+
// wildcard routes use the one param that was not already matched by a named slot
|
|
44
|
+
const remaining = Object.entries(params ?? {}).filter(([key]) => !used.has(key));
|
|
45
|
+
if (remaining.length !== 1) {
|
|
46
|
+
throw new Error('[Link]: wildcard routes require exactly one unmatched param');
|
|
47
|
+
}
|
|
48
|
+
// encode each path segment separately so embedded '/' still acts like a path separator
|
|
49
|
+
to = to.replace('*', remaining[0][1].split('/').map(encodeURIComponent).join('/'));
|
|
50
|
+
}
|
|
51
|
+
if (!query)
|
|
52
|
+
return to;
|
|
53
|
+
// split the URL up so new query params can be merged without losing an existing hash
|
|
54
|
+
const hashIndex = to.indexOf('#');
|
|
55
|
+
const hash = hashIndex >= 0 ? to.slice(hashIndex) : '';
|
|
56
|
+
const pathWithSearch = hashIndex >= 0 ? to.slice(0, hashIndex) : to;
|
|
57
|
+
const searchIndex = pathWithSearch.indexOf('?');
|
|
58
|
+
const pathname = searchIndex >= 0 ? pathWithSearch.slice(0, searchIndex) : pathWithSearch;
|
|
59
|
+
const currentSearch = searchIndex >= 0 ? pathWithSearch.slice(searchIndex + 1) : '';
|
|
60
|
+
const search = new URLSearchParams(currentSearch);
|
|
61
|
+
// later values win, so passed query props overwrite any existing query string values
|
|
62
|
+
for (const [key, value] of Object.entries(query)) {
|
|
63
|
+
search.set(key, String(value));
|
|
64
|
+
}
|
|
65
|
+
const value = search.toString();
|
|
66
|
+
// rebuild the URL in the same order: pathname, optional query string, then hash
|
|
67
|
+
return `${pathname}${value.length > 0 ? `?${value}` : ''}${hash}`;
|
|
68
|
+
}
|
|
69
|
+
BrowserRouter.toTarget = toTarget;
|
|
70
|
+
})(BrowserRouter || (BrowserRouter = {}));
|
|
71
|
+
export const BrowserRouterContext = createContext({
|
|
72
|
+
go: async () => '',
|
|
73
|
+
prefetch: () => { },
|
|
74
|
+
isNavigating: false,
|
|
75
|
+
url: {},
|
|
76
|
+
});
|
|
9
77
|
const DEFAULT_GO_CONFIG = {
|
|
10
78
|
replace: false,
|
|
11
79
|
};
|
|
12
80
|
const logger = new Logger();
|
|
13
81
|
const prefetcher = new Prefetcher();
|
|
14
|
-
export function
|
|
82
|
+
export function BrowserRouterProvider({ children, setPayload, isNavigating = false, url, }) {
|
|
15
83
|
// id to track active navigations
|
|
16
84
|
const id = useRef(0);
|
|
17
85
|
// abort controller for in-flight navigation
|
|
@@ -35,16 +103,15 @@ export function RouterProvider({ children, setPayload, isNavigating = false, url
|
|
|
35
103
|
// opportunistically for this navigation
|
|
36
104
|
let existing = false;
|
|
37
105
|
try {
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
url.searchParams.set(key, String(value));
|
|
42
|
-
}
|
|
106
|
+
const target = BrowserRouter.toTarget(to, opts.params, opts.query);
|
|
107
|
+
if (BrowserRouter.isExternalTarget(target, window.location.origin)) {
|
|
108
|
+
throw new Error('[router.go]: external URLs are not supported. Use <a> instead');
|
|
43
109
|
}
|
|
110
|
+
const url = new URL(target, window.location.origin);
|
|
44
111
|
const key = Prefetcher.key(url.toString(), window.location.origin);
|
|
45
112
|
if (!key)
|
|
46
113
|
throw new Error('Invalid navigation url');
|
|
47
|
-
// switch to the
|
|
114
|
+
// switch to the normalised target once the url is valid
|
|
48
115
|
path = key;
|
|
49
116
|
// if the target was already prefetched, use the cached response promise
|
|
50
117
|
// and set existing to true so we don't remove it from cache
|
|
@@ -128,7 +195,9 @@ export function RouterProvider({ children, setPayload, isNavigating = false, url
|
|
|
128
195
|
prefetcher.set(key, fetch(key, { headers: { Accept: 'text/x-component' } }));
|
|
129
196
|
}, []);
|
|
130
197
|
useEffect(() => {
|
|
131
|
-
const handler = () => go(window.location.
|
|
198
|
+
const handler = () => go(BrowserRouter.toTarget(window.location.pathname + window.location.search), {
|
|
199
|
+
replace: true,
|
|
200
|
+
});
|
|
132
201
|
window.addEventListener('popstate', handler);
|
|
133
202
|
return () => {
|
|
134
203
|
controller.current?.abort();
|
|
@@ -145,5 +214,5 @@ export function RouterProvider({ children, setPayload, isNavigating = false, url
|
|
|
145
214
|
search: url?.search,
|
|
146
215
|
},
|
|
147
216
|
}), [go, prefetch, isNavigating, url]);
|
|
148
|
-
return _jsx(
|
|
217
|
+
return _jsx(BrowserRouterContext, { value: value, children: children });
|
|
149
218
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo, useSyncExternalStore } from 'react';
|
|
2
|
-
import { useRouter } from '../../router.js';
|
|
3
2
|
import { Solas } from '../../solas.js';
|
|
3
|
+
import { useRouter } from '../browser-router/use-router.js';
|
|
4
4
|
export function useSearchParams() {
|
|
5
5
|
const { url } = useRouter();
|
|
6
6
|
const search = useSyncExternalStore(fn => {
|
package/dist/internal/build.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { Solas } from '../solas.js';
|
|
4
3
|
import { Logger } from '../utils/logger.js';
|
|
5
|
-
import {
|
|
4
|
+
import { Solas } from '../solas.js';
|
|
5
|
+
import { normalisePathname } from './http-router/utils.js';
|
|
6
6
|
import { Prerender } from './prerender.js';
|
|
7
7
|
export { Build };
|
|
8
8
|
/**
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import { Solas } from '../../solas.js';
|
|
2
|
-
import { AUTOGEN_MSG, toSourceLiteral } from './utils.js';
|
|
2
|
+
import { AUTOGEN_MSG, source, toSourceLiteral } from './utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates the code to create an exported config object
|
|
5
5
|
*/
|
|
6
6
|
export function writeConfig(config) {
|
|
7
|
-
|
|
7
|
+
const loggerLevel = config.logger?.level;
|
|
8
|
+
const importLines = [
|
|
9
|
+
`import type { PluginConfig } from '${Solas.Config.PKG_NAME}'`,
|
|
10
|
+
loggerLevel ? `import { Logger } from '${Solas.Config.PKG_NAME}/utils/logger'` : '',
|
|
11
|
+
]
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
.join('\n');
|
|
14
|
+
const configStatement = `const config = ${toSourceLiteral(config)} as const satisfies PluginConfig`;
|
|
15
|
+
const loggerStatement = loggerLevel
|
|
16
|
+
? `Logger.defaultLevel = ${toSourceLiteral(loggerLevel)}`
|
|
17
|
+
: '';
|
|
18
|
+
return source `
|
|
8
19
|
${AUTOGEN_MSG}
|
|
9
20
|
|
|
10
|
-
|
|
11
|
-
import { Logger } from '${Solas.Config.PKG_NAME}/utils/logger'
|
|
21
|
+
${importLines}
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (config.logger?.level) Logger.defaultLevel = config.logger.level
|
|
23
|
+
${configStatement}
|
|
24
|
+
${loggerStatement}
|
|
16
25
|
|
|
17
26
|
export { config }
|
|
18
|
-
|
|
27
|
+
`;
|
|
19
28
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Solas } from '../../solas.js';
|
|
2
|
-
import { AUTOGEN_MSG } from './utils.js';
|
|
2
|
+
import { AUTOGEN_MSG, source } from './utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates the RSC entry code
|
|
5
5
|
*/
|
|
6
6
|
export function writeRSCEntry() {
|
|
7
|
-
return `
|
|
7
|
+
return source `
|
|
8
8
|
${AUTOGEN_MSG}
|
|
9
9
|
|
|
10
10
|
import { createHandler } from '${Solas.Config.PKG_NAME}/env/rsc'
|
|
@@ -20,27 +20,27 @@ export function writeRSCEntry() {
|
|
|
20
20
|
export default createHandler(config, manifest, importMap, artifactManifest)
|
|
21
21
|
|
|
22
22
|
import.meta.hot?.accept()
|
|
23
|
-
|
|
23
|
+
`;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Generates the SSR entry code
|
|
27
27
|
*/
|
|
28
28
|
export function writeSSREntry() {
|
|
29
|
-
return `
|
|
29
|
+
return source `
|
|
30
30
|
${AUTOGEN_MSG}
|
|
31
31
|
|
|
32
32
|
export { prerender, resume, ssr } from '${Solas.Config.PKG_NAME}/env/ssr'
|
|
33
|
-
|
|
33
|
+
`;
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* Generates the browser entry code
|
|
37
37
|
*/
|
|
38
38
|
export function writeBrowserEntry() {
|
|
39
|
-
return `
|
|
39
|
+
return source `
|
|
40
40
|
${AUTOGEN_MSG}
|
|
41
41
|
|
|
42
42
|
import { browser } from '${Solas.Config.PKG_NAME}/env/browser'
|
|
43
43
|
|
|
44
44
|
browser()
|
|
45
|
-
|
|
45
|
+
`;
|
|
46
46
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Solas } from '../../solas.js';
|
|
2
|
-
import { AUTOGEN_MSG, toSourceLiteral } from './utils.js';
|
|
2
|
+
import { AUTOGEN_MSG, source, toSourceLiteral } from './utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates the code to create an exported manifest object
|
|
5
5
|
*/
|
|
6
6
|
export function writeManifest(manifest) {
|
|
7
|
-
return `
|
|
7
|
+
return source `
|
|
8
8
|
${AUTOGEN_MSG}
|
|
9
9
|
|
|
10
10
|
import type { Manifest } from '${Solas.Config.PKG_NAME}'
|
|
11
11
|
|
|
12
12
|
export const manifest = ${toSourceLiteral(manifest)} as const satisfies Manifest
|
|
13
|
-
|
|
13
|
+
`;
|
|
14
14
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Solas } from '../../solas.js';
|
|
2
|
-
import { AUTOGEN_MSG, toIdentifier, toIdentifierList, toRelativeModuleSpecifier, toStringLiteral, } from './utils.js';
|
|
2
|
+
import { AUTOGEN_MSG, indent, source, toIdentifier, toIdentifierList, toRelativeModuleSpecifier, toStringLiteral, } from './utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates the import map for all route components, endpoints, layouts, shells, and middlewares
|
|
5
5
|
*/
|
|
@@ -61,26 +61,22 @@ export function writeMaps(imports, modules) {
|
|
|
61
61
|
parts.push(`middlewares: [${middleware}]`);
|
|
62
62
|
}
|
|
63
63
|
if (parts.length === 0)
|
|
64
|
-
return
|
|
65
|
-
return
|
|
64
|
+
return `${toStringLiteral(moduleId)}: {}`;
|
|
65
|
+
return `${toStringLiteral(moduleId)}: {\n${parts.map(part => indent(part, 1)).join(',\n')}\n}`;
|
|
66
66
|
});
|
|
67
|
-
|
|
67
|
+
const importLines = [...statics, ...dynamics].join('\n');
|
|
68
|
+
const entries = map.map(entry => indent(entry, 1)).join(',\n');
|
|
69
|
+
return source `
|
|
68
70
|
${AUTOGEN_MSG}
|
|
69
71
|
|
|
70
72
|
import type { ImportMap } from '${Solas.Config.PKG_NAME}'
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
`
|
|
76
|
-
: ''}${dynamics.length
|
|
77
|
-
? `${dynamics.join('\n')}
|
|
78
|
-
|
|
79
|
-
`
|
|
73
|
+
${importLines
|
|
74
|
+
? `
|
|
75
|
+
${importLines}`
|
|
80
76
|
: ''}
|
|
81
77
|
|
|
82
78
|
export const importMap = {
|
|
83
|
-
|
|
79
|
+
${entries}
|
|
84
80
|
} as const satisfies ImportMap
|
|
85
|
-
|
|
81
|
+
`;
|
|
86
82
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Solas } from '../../solas.js';
|
|
2
|
+
import { Build } from '../build.js';
|
|
3
|
+
import { AUTOGEN_MSG, source } from './utils.js';
|
|
4
|
+
function render(path, params) {
|
|
5
|
+
if (!params || params.length === 0) {
|
|
6
|
+
return `\t\t\t'${path}': {}`;
|
|
7
|
+
}
|
|
8
|
+
const fields = params.map(param => `\t\t\t\t\t${param}: string`).join('\n');
|
|
9
|
+
return [`\t\t\t'${path}': {`, `\t\t\t\tparams: {`, fields, `\t\t\t\t}`, `\t\t\t}`].join('\n');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generates runtime types
|
|
13
|
+
*/
|
|
14
|
+
export function writeTypes(manifest) {
|
|
15
|
+
const routes = new Map();
|
|
16
|
+
for (const path in manifest) {
|
|
17
|
+
const route = manifest[path];
|
|
18
|
+
if (Array.isArray(route)) {
|
|
19
|
+
for (const r of route) {
|
|
20
|
+
if (r.__kind !== Build.EntryKind.PAGE)
|
|
21
|
+
continue;
|
|
22
|
+
routes.set(r.__path, r.__params);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
if (route.__kind !== Build.EntryKind.PAGE)
|
|
27
|
+
continue;
|
|
28
|
+
routes.set(route.__path, route.__params);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const body = [...routes.entries()]
|
|
32
|
+
// sort routes by path for stable output
|
|
33
|
+
.toSorted(([a], [b]) => a.localeCompare(b))
|
|
34
|
+
.map(([path, params]) => render(path, params))
|
|
35
|
+
.join('\n');
|
|
36
|
+
return source `
|
|
37
|
+
${AUTOGEN_MSG}
|
|
38
|
+
|
|
39
|
+
import '${Solas.Config.PKG_NAME}'
|
|
40
|
+
|
|
41
|
+
declare module '${Solas.Config.PKG_NAME}' {
|
|
42
|
+
export namespace Solas {
|
|
43
|
+
export interface Routes {
|
|
44
|
+
${body}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}`;
|
|
48
|
+
}
|
|
@@ -15,7 +15,17 @@ export declare function toIdentifierList(values: readonly (string | null)[], lab
|
|
|
15
15
|
* Escape text into a safe string literal for generated source
|
|
16
16
|
*/
|
|
17
17
|
export declare function toStringLiteral(value: string, quoteStyle?: "'" | '"'): string;
|
|
18
|
+
/**
|
|
19
|
+
* Dedent an interpolated template literal while preserving indentation for
|
|
20
|
+
* multiline substitutions
|
|
21
|
+
*/
|
|
22
|
+
export declare function source(strings: TemplateStringsArray, ...values: Array<string | number | boolean | false | null | undefined>): string;
|
|
18
23
|
/**
|
|
19
24
|
* Emit readable ts source for generated config and manifest data
|
|
20
25
|
*/
|
|
21
26
|
export declare function toSourceLiteral(value: unknown, level?: number): string;
|
|
27
|
+
/**
|
|
28
|
+
* Indent each line of a block of source code by the specified level for embedding in
|
|
29
|
+
* generated output
|
|
30
|
+
*/
|
|
31
|
+
export declare function indent(value: string, level?: number): string;
|
|
@@ -60,6 +60,31 @@ export function toStringLiteral(value, quoteStyle = "'") {
|
|
|
60
60
|
.replace(/\r/g, '\\r')
|
|
61
61
|
.replace(/\t/g, '\\t')}${quoteStyle}`;
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Dedent an interpolated template literal while preserving indentation for
|
|
65
|
+
* multiline substitutions
|
|
66
|
+
*/
|
|
67
|
+
export function source(strings, ...values) {
|
|
68
|
+
let text = strings[0] ?? '';
|
|
69
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
70
|
+
const value = values[index];
|
|
71
|
+
const indentation = text.match(/(?:^|\n)([ \t]*)$/)?.[1] ?? '';
|
|
72
|
+
const chunk = value === false || value == null ? '' : String(value);
|
|
73
|
+
text += chunk.replace(/\n/g, `\n${indentation}`);
|
|
74
|
+
text += strings[index + 1] ?? '';
|
|
75
|
+
}
|
|
76
|
+
const lines = text.replace(/^\n+|\n+$/g, '').split('\n');
|
|
77
|
+
const margin = lines
|
|
78
|
+
.filter(line => line.trim().length > 0)
|
|
79
|
+
.reduce((smallest, line) => {
|
|
80
|
+
const indentation = line.match(/^[ \t]*/)?.[0].length ?? 0;
|
|
81
|
+
return Math.min(smallest, indentation);
|
|
82
|
+
}, Number.POSITIVE_INFINITY);
|
|
83
|
+
if (!Number.isFinite(margin) || margin === 0) {
|
|
84
|
+
return lines.join('\n');
|
|
85
|
+
}
|
|
86
|
+
return lines.map(line => line.slice(margin)).join('\n');
|
|
87
|
+
}
|
|
63
88
|
/**
|
|
64
89
|
* Convert a string into a valid unquoted property key if possible, otherwise quote it
|
|
65
90
|
* as a string literal for generated source
|
|
@@ -120,7 +145,7 @@ export function toSourceLiteral(value, level = 0) {
|
|
|
120
145
|
}
|
|
121
146
|
return `${prefix}${source.replace(/\n/g, `\n${INDENT.repeat(level + 1)}`)}`;
|
|
122
147
|
}
|
|
123
|
-
return `${prefix}${toSourceLiteral(entryValue, level + 1)
|
|
148
|
+
return `${prefix}${toSourceLiteral(entryValue, level + 1)}`;
|
|
124
149
|
})
|
|
125
150
|
.join(',\n'),
|
|
126
151
|
`${INDENT.repeat(level)}}`,
|
|
@@ -132,7 +157,7 @@ export function toSourceLiteral(value, level = 0) {
|
|
|
132
157
|
* Indent each line of a block of source code by the specified level for embedding in
|
|
133
158
|
* generated output
|
|
134
159
|
*/
|
|
135
|
-
function indent(value, level = 1) {
|
|
160
|
+
export function indent(value, level = 1) {
|
|
136
161
|
const prefix = INDENT.repeat(level);
|
|
137
162
|
return value
|
|
138
163
|
.split('\n')
|
|
@@ -2,18 +2,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { StrictMode, Suspense, useCallback, useState, useTransition } from 'react';
|
|
3
3
|
import { hydrateRoot } from 'react-dom/client';
|
|
4
4
|
import { createFromFetch, createFromReadableStream, createTemporaryReferenceSet, encodeReply, setServerCallback, } from '@vitejs/plugin-rsc/browser';
|
|
5
|
-
import {
|
|
5
|
+
import { BrowserRouterProvider } from '../browser-router/router.js';
|
|
6
6
|
import { RedirectBoundary } from '../navigation/redirect-boundary.js';
|
|
7
7
|
import { Head } from '../render/head.js';
|
|
8
|
-
import { RouterProvider } from '../router/router-provider.js';
|
|
9
8
|
import { ErrorBoundary } from '../ui/error-boundary.js';
|
|
9
|
+
import { rscStream } from './flight.js';
|
|
10
10
|
/**
|
|
11
11
|
* Browser RSC hydration entry point
|
|
12
12
|
*/
|
|
13
13
|
export async function browser() {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
// read the initial payload from the inline __FLIGHT_DATA pushes that were
|
|
15
|
+
// injected into the html document during ssr/prerender
|
|
16
|
+
const payload = await createFromReadableStream(rscStream);
|
|
17
17
|
const payloadSetter = {
|
|
18
18
|
current: () => { },
|
|
19
19
|
};
|
|
@@ -28,7 +28,7 @@ export async function browser() {
|
|
|
28
28
|
// make the latest payload updater available to action/hmr callbacks
|
|
29
29
|
// immediately during render, without waiting for an effect to run
|
|
30
30
|
payloadSetter.current = setPayloadInTransition;
|
|
31
|
-
return (_jsx(RedirectBoundary, { children: _jsxs(
|
|
31
|
+
return (_jsx(RedirectBoundary, { children: _jsxs(BrowserRouterProvider, { setPayload: setPayloadInTransition, isNavigating: isPending, url: p.url, children: [
|
|
32
32
|
_jsx(ErrorBoundary, { fallback: null, children: _jsx(Suspense, { fallback: null, children: _jsx(Head, { metadata: p.metadata }) }) }), p.root] }) }));
|
|
33
33
|
}
|
|
34
34
|
setServerCallback(async (id, args) => {
|