@jk2908/solas 0.4.4 → 0.5.1
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 +19 -0
- package/README.md +108 -10
- package/dist/index.js +15 -8
- package/dist/internal/browser-router/router.d.ts +1 -1
- package/dist/internal/browser-router/router.js +2 -2
- package/dist/internal/browser-router/shared.d.ts +1 -0
- package/dist/internal/browser-router/use-router.d.ts +1 -1
- package/dist/internal/build.js +14 -13
- package/dist/internal/codegen/config.js +3 -2
- package/dist/internal/codegen/environments.d.ts +2 -1
- package/dist/internal/codegen/environments.js +6 -4
- package/dist/internal/env/rsc.d.ts +1 -0
- package/dist/internal/env/rsc.js +1 -0
- package/dist/internal/http-router/router.js +8 -7
- package/dist/internal/postbuild.js +9 -8
- package/dist/internal/prerender.js +14 -13
- package/dist/internal/runtimes/bun.d.ts +9 -0
- package/dist/internal/runtimes/bun.js +33 -0
- package/dist/internal/runtimes/mime.d.ts +1 -0
- package/dist/internal/runtimes/mime.js +35 -0
- package/dist/internal/runtimes/node.d.ts +9 -0
- package/dist/internal/runtimes/node.js +31 -0
- package/dist/internal/runtimes/runtime.d.ts +29 -0
- package/dist/internal/runtimes/runtime.js +32 -0
- package/dist/runtimes/bun.d.ts +13 -0
- package/dist/runtimes/bun.js +39 -0
- package/dist/solas.d.ts +4 -1
- package/dist/solas.js +27 -6
- package/dist/types.d.ts +6 -1
- package/dist/utils/compress.js +2 -2
- package/dist/utils/export-reader.js +69 -58
- package/package.json +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1 - 2026-06-06
|
|
4
|
+
|
|
5
|
+
- Fixed production app bundling by removing the `mime-types` package dependency from Solas runtime paths, preventing client bundles from resolving `/node_modules/mime-types/*` imports.
|
|
6
|
+
- Added an internal `getMimeTypeFromPath(...)` helper for Node runtime MIME resolution with a safe `application/octet-stream` fallback for unknown extensions.
|
|
7
|
+
- Removed obsolete `src/adapters/*` runtime adapter files so runtime selection now consistently uses the internal runtime implementations.
|
|
8
|
+
|
|
9
|
+
## 0.5.0 - 2026-06-06
|
|
10
|
+
|
|
11
|
+
- Added runtime selection via `runtime: 'auto' | 'node' | 'bun'`, with `auto` choosing Bun when available and falling back to Node otherwise.
|
|
12
|
+
- Removed the default Bun runtime requirement from the documented workflow. Standard `vite dev`, `vite build`, and `vite preview` commands now work on the default Node path, while Bun-backed Vite commands remain available when you want to run Solas in Bun.
|
|
13
|
+
- Added a dedicated `@jk2908/solas/$` runtime-safe export for generated/runtime code so preview and production server bundles no longer need to pull through the package root plugin entry.
|
|
14
|
+
- Fixed prerender static param resolution to stop depending on host support for `Promise.try`, so build-time route processing now works correctly under Node-based Vite runs.
|
|
15
|
+
|
|
16
|
+
## 0.4.5 - 2026-05-29
|
|
17
|
+
|
|
18
|
+
- Documented concrete `href` and `router.go(...)` usage in the README, including how explicit `query` values merge with an existing query string and take precedence for duplicate keys.
|
|
19
|
+
- Clarified in the README that `router.go(...)` and `router.refresh()` are awaitable, and that `router.refresh()` always refreshes the current browser location at call time.
|
|
20
|
+
- Fixed browser-router typing so `refresh` is exposed as a promise-returning method, matching the runtime implementation.
|
|
21
|
+
|
|
3
22
|
## 0.4.4 - 2026-05-29
|
|
4
23
|
|
|
5
24
|
- Added `router.refresh()` to the browser router, and made it clear that it clears the current route cache before fetching a fresh RSC payload.
|
package/README.md
CHANGED
|
@@ -4,8 +4,6 @@ Solas is a minimal React meta-framework powered by Vite, created for experimenti
|
|
|
4
4
|
|
|
5
5
|
Solas is experimental and currently has no automated test suite, so expect rough edges.
|
|
6
6
|
|
|
7
|
-
Solas currently requires Bun 1.2+ on your `PATH`. You can still manage dependencies with `npm`, `pnpm`, or `yarn`, but the Solas CLI and Vite plugin runtime use Bun APIs directly.
|
|
8
|
-
|
|
9
7
|
## Install
|
|
10
8
|
|
|
11
9
|
```sh
|
|
@@ -100,6 +98,24 @@ That gives you:
|
|
|
100
98
|
- rejected params for static routes that do not accept them
|
|
101
99
|
- typed query and navigation options on `router.go(...)`
|
|
102
100
|
|
|
101
|
+
If you already have a concrete path, you can pass that directly instead of using route params:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
;<Link href={`/p/${post.id}`} />
|
|
105
|
+
|
|
106
|
+
await router.go(`/p/${post.id}`)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
In that form the path is already resolved, so `params` are not used. Typed `Link` and `router.go(...)` only allow `params` when you pass a route pattern like `/p/:id`.
|
|
110
|
+
|
|
111
|
+
If the target already contains a query string and you also pass `query`, Solas merges them. Existing query entries are kept unless you override the same key in `query`, and explicit `query` values win:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
<Link href={`/p/${post.id}?tab=summary`} query={{ draft: true, tab: 'full' }} />
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
That resolves to `/p/${post.id}?tab=full&draft=true`.
|
|
118
|
+
|
|
103
119
|
Use `Link` for same-origin app navigation. Prefetching is opt-in:
|
|
104
120
|
|
|
105
121
|
```tsx
|
|
@@ -123,7 +139,7 @@ export function Controls() {
|
|
|
123
139
|
|
|
124
140
|
return (
|
|
125
141
|
<>
|
|
126
|
-
<button type="button" onClick={() =>
|
|
142
|
+
<button type="button" onClick={() => router.go('/posts')}>
|
|
127
143
|
Go to posts
|
|
128
144
|
</button>
|
|
129
145
|
|
|
@@ -131,7 +147,7 @@ export function Controls() {
|
|
|
131
147
|
Prefetch posts
|
|
132
148
|
</button>
|
|
133
149
|
|
|
134
|
-
<button type="button" onClick={() =>
|
|
150
|
+
<button type="button" onClick={() => router.refresh()}>
|
|
135
151
|
Refresh current route
|
|
136
152
|
</button>
|
|
137
153
|
</>
|
|
@@ -139,6 +155,18 @@ export function Controls() {
|
|
|
139
155
|
}
|
|
140
156
|
```
|
|
141
157
|
|
|
158
|
+
`router.go(...)` and `router.refresh()` both return promises, so you can `await` either of them when you need to sequence work after navigation completes:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
const finalPath = await router.go('/posts')
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
await router.refresh()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`router.refresh()` always refreshes the current browser location at the moment you call it. If you call it after `await router.go('/posts')`, it refreshes `/posts` (or the final redirected path).
|
|
169
|
+
|
|
142
170
|
`router.go(...)` accepts route params and query values using the same typed route rules as `Link`:
|
|
143
171
|
|
|
144
172
|
```tsx
|
|
@@ -173,6 +201,74 @@ That keeps your route params aligned across links, imperative navigation, metada
|
|
|
173
201
|
|
|
174
202
|
All Solas options are passed to `solas()` inside `defineConfig`.
|
|
175
203
|
|
|
204
|
+
## Runtime
|
|
205
|
+
|
|
206
|
+
Solas uses a runtime for filesystem access, mime lookup, and hashing.
|
|
207
|
+
|
|
208
|
+
This is not a deployment or packaging adapter. Platform adapters are not available yet.
|
|
209
|
+
|
|
210
|
+
Use the `runtime` config key to select the runtime used by Solas server code.
|
|
211
|
+
|
|
212
|
+
Supported values are `'auto'`, `'node'`, and `'bun'`.
|
|
213
|
+
|
|
214
|
+
`solas()` defaults to `runtime: 'auto'`. In a Bun process, that selects Bun. Otherwise it falls back to Node.
|
|
215
|
+
|
|
216
|
+
If you already run Vite through Bun, `runtime: 'auto'` is usually enough and Solas will detect Bun automatically.
|
|
217
|
+
|
|
218
|
+
### Node runtime
|
|
219
|
+
|
|
220
|
+
If you want to pin Node explicitly, set `runtime: 'node'`:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { defineConfig } from 'vite'
|
|
224
|
+
|
|
225
|
+
import solas from '@jk2908/solas'
|
|
226
|
+
import react from '@vitejs/plugin-react'
|
|
227
|
+
|
|
228
|
+
export default defineConfig({
|
|
229
|
+
plugins: [solas({ runtime: 'node' }), react()],
|
|
230
|
+
})
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Use the normal Vite commands with the Node runtime:
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"scripts": {
|
|
238
|
+
"dev": "vite dev",
|
|
239
|
+
"build": "vite build",
|
|
240
|
+
"preview": "vite preview"
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Bun runtime
|
|
246
|
+
|
|
247
|
+
If you want Solas runtime code to execute in Bun, set `runtime: 'bun'`:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { defineConfig } from 'vite'
|
|
251
|
+
|
|
252
|
+
import solas from '@jk2908/solas'
|
|
253
|
+
import react from '@vitejs/plugin-react'
|
|
254
|
+
|
|
255
|
+
export default defineConfig({
|
|
256
|
+
plugins: [solas({ runtime: 'bun' }), react()],
|
|
257
|
+
})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
When you use the Bun runtime, run Vite through Bun so the server/runtime code executes in a Bun process:
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"scripts": {
|
|
265
|
+
"dev": "bunx --bun vite dev",
|
|
266
|
+
"build": "bunx --bun vite build",
|
|
267
|
+
"preview": "bunx --bun vite preview"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
176
272
|
### `url`
|
|
177
273
|
|
|
178
274
|
`url` is optional. If you set it, Solas treats it as the public origin for your app.
|
|
@@ -492,18 +588,20 @@ Add scripts to your app:
|
|
|
492
588
|
```json
|
|
493
589
|
{
|
|
494
590
|
"scripts": {
|
|
495
|
-
"dev": "
|
|
496
|
-
"build": "
|
|
497
|
-
"preview": "
|
|
591
|
+
"dev": "vite dev",
|
|
592
|
+
"build": "vite build",
|
|
593
|
+
"preview": "vite preview"
|
|
498
594
|
}
|
|
499
595
|
}
|
|
500
596
|
```
|
|
501
597
|
|
|
598
|
+
These defaults assume either `runtime: 'node'` or `runtime: 'auto'` while running Vite under Node. If you set `runtime: 'bun'`, or let `runtime: 'auto'` resolve to Bun by running Vite through Bun, use the Bun-backed commands shown in the runtime section above.
|
|
599
|
+
|
|
502
600
|
## Commands
|
|
503
601
|
|
|
504
|
-
- `
|
|
505
|
-
- `
|
|
506
|
-
- `
|
|
602
|
+
- `vite dev` starts the development server.
|
|
603
|
+
- `vite build` creates a production build. Solas finalizes that build by prerendering configured routes, writing the runtime manifest, generating `sitemap.xml` when enabled, and precompressing output when enabled.
|
|
604
|
+
- `vite preview` serves the built app for local verification.
|
|
507
605
|
|
|
508
606
|
## Security
|
|
509
607
|
|
package/dist/index.js
CHANGED
|
@@ -14,19 +14,26 @@ import { writeMaps } from './internal/codegen/maps.js';
|
|
|
14
14
|
import { writeTypes } from './internal/codegen/types.js';
|
|
15
15
|
import { postbuild } from './internal/postbuild.js';
|
|
16
16
|
import { collect as collectPublicFiles } from './internal/public-files.js';
|
|
17
|
+
import { Runtime } from './internal/runtimes/runtime.js';
|
|
17
18
|
import { Solas } from './solas.js';
|
|
18
19
|
const DEFAULT_CONFIG = {
|
|
20
|
+
runtime: 'auto',
|
|
19
21
|
precompress: false,
|
|
20
22
|
prerender: false,
|
|
21
23
|
trustedOrigins: [],
|
|
22
24
|
trailingSlash: 'never',
|
|
23
25
|
};
|
|
24
26
|
function solas(c) {
|
|
25
|
-
const
|
|
26
|
-
...DEFAULT_CONFIG,
|
|
27
|
+
const validatedConfig = Solas.Config.validate({
|
|
27
28
|
...c,
|
|
28
29
|
url: c?.url ?? process.env.VITE_APP_URL?.toString(),
|
|
29
30
|
});
|
|
31
|
+
const config = {
|
|
32
|
+
...DEFAULT_CONFIG,
|
|
33
|
+
...validatedConfig,
|
|
34
|
+
runtime: validatedConfig.runtime ?? DEFAULT_CONFIG.runtime,
|
|
35
|
+
};
|
|
36
|
+
Runtime.runtime = Solas.Runtime.create(config.runtime);
|
|
30
37
|
if (config.logger?.level)
|
|
31
38
|
Logger.defaultLevel = config.logger.level;
|
|
32
39
|
const logger = new Logger();
|
|
@@ -43,11 +50,11 @@ function solas(c) {
|
|
|
43
50
|
const cached = fileCache.get(filePath);
|
|
44
51
|
if (cached === content) {
|
|
45
52
|
// if content is unchanged and file exists, skip write
|
|
46
|
-
if (await
|
|
53
|
+
if (await Runtime.exists(filePath))
|
|
47
54
|
return null;
|
|
48
55
|
// else, file is missing but cached content is the same as
|
|
49
56
|
// last time we saw it, write it
|
|
50
|
-
await
|
|
57
|
+
await Runtime.write(filePath, content);
|
|
51
58
|
fileCache.set(filePath, content);
|
|
52
59
|
return path.relative(process.cwd(), filePath);
|
|
53
60
|
}
|
|
@@ -57,7 +64,7 @@ function solas(c) {
|
|
|
57
64
|
if (curr === content)
|
|
58
65
|
return null;
|
|
59
66
|
try {
|
|
60
|
-
await
|
|
67
|
+
await Runtime.write(filePath, content);
|
|
61
68
|
fileCache.set(filePath, content);
|
|
62
69
|
return path.relative(process.cwd(), filePath);
|
|
63
70
|
}
|
|
@@ -70,7 +77,7 @@ function solas(c) {
|
|
|
70
77
|
// file doesn't exist, write it
|
|
71
78
|
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
|
|
72
79
|
try {
|
|
73
|
-
await
|
|
80
|
+
await Runtime.write(filePath, content);
|
|
74
81
|
fileCache.set(filePath, content);
|
|
75
82
|
return path.relative(process.cwd(), filePath);
|
|
76
83
|
}
|
|
@@ -100,7 +107,7 @@ function solas(c) {
|
|
|
100
107
|
['manifest.ts', writeManifest(manifest)],
|
|
101
108
|
['maps.ts', writeMaps(imports, modules)],
|
|
102
109
|
[`${Solas.Config.SLUG}.d.ts`, writeTypes(manifest)],
|
|
103
|
-
[Solas.Config.ENTRY_RSC, writeRSCEntry()],
|
|
110
|
+
[Solas.Config.ENTRY_RSC, writeRSCEntry(config)],
|
|
104
111
|
[Solas.Config.ENTRY_SSR, writeSSREntry()],
|
|
105
112
|
[Solas.Config.ENTRY_BROWSER, writeBrowserEntry()],
|
|
106
113
|
];
|
|
@@ -299,7 +306,7 @@ function solas(c) {
|
|
|
299
306
|
}
|
|
300
307
|
// write build manifest
|
|
301
308
|
const generatedDir = path.join(process.cwd(), Solas.Config.GENERATED_DIR);
|
|
302
|
-
await
|
|
309
|
+
await Runtime.write(path.join(generatedDir, 'build.json'), JSON.stringify({
|
|
303
310
|
base: resolvedViteConfig?.base ?? '/',
|
|
304
311
|
publicFiles: await collectPublicFiles(resolvedViteConfig?.publicDir),
|
|
305
312
|
prerenderRoutes: Array.from(buildContext.prerenderRoutes),
|
|
@@ -4,7 +4,7 @@ export { BrowserRouter } from './shared.js';
|
|
|
4
4
|
export declare const BrowserRouterContext: import("react").Context<{
|
|
5
5
|
go: BrowserRouter.Go;
|
|
6
6
|
prefetch: (path: string) => void;
|
|
7
|
-
refresh:
|
|
7
|
+
refresh: BrowserRouter.Refresh;
|
|
8
8
|
isNavigating: boolean;
|
|
9
9
|
url: {
|
|
10
10
|
pathname?: string | undefined;
|
|
@@ -10,7 +10,7 @@ export { BrowserRouter } from './shared.js';
|
|
|
10
10
|
export const BrowserRouterContext = createContext({
|
|
11
11
|
go: async () => '',
|
|
12
12
|
prefetch: () => { },
|
|
13
|
-
refresh: () =>
|
|
13
|
+
refresh: async () => '',
|
|
14
14
|
isNavigating: false,
|
|
15
15
|
url: {},
|
|
16
16
|
});
|
|
@@ -130,7 +130,7 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
130
130
|
const currentPath = window.location.pathname + window.location.search;
|
|
131
131
|
const key = ResponseCache.toCacheKey(currentPath, window.location.origin);
|
|
132
132
|
if (!key)
|
|
133
|
-
return;
|
|
133
|
+
return Promise.resolve(currentPath);
|
|
134
134
|
if (responseCache.has(key))
|
|
135
135
|
responseCache.remove(key);
|
|
136
136
|
return go(currentPath, {
|
|
@@ -156,6 +156,7 @@ export declare namespace BrowserRouter {
|
|
|
156
156
|
<TTo extends Target>(to: TTo, opts?: TargetConfig & Replace): Promise<string>;
|
|
157
157
|
<TTo extends string>(to: string extends TTo ? TTo : never, opts?: GoOptions): Promise<string>;
|
|
158
158
|
};
|
|
159
|
+
export type Refresh = () => Promise<string>;
|
|
159
160
|
/**
|
|
160
161
|
* Convert a route pattern and params into a real path string. This is used internally
|
|
161
162
|
* to implement <Link /> and router.go
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare function useRouter(): {
|
|
2
2
|
go: import("./shared.js").BrowserRouter.Go;
|
|
3
3
|
prefetch: (path: string) => void;
|
|
4
|
-
refresh: ()
|
|
4
|
+
refresh: import("./shared.js").BrowserRouter.Refresh;
|
|
5
5
|
isNavigating: boolean;
|
|
6
6
|
url: {
|
|
7
7
|
pathname?: string | undefined;
|
package/dist/internal/build.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Logger } from '../utils/logger.js';
|
|
|
4
4
|
import { Solas } from '../solas.js';
|
|
5
5
|
import { normalisePathname } from './http-router/utils.js';
|
|
6
6
|
import { Prerender } from './prerender.js';
|
|
7
|
+
import { Runtime } from './runtimes/runtime.js';
|
|
7
8
|
export { Build };
|
|
8
9
|
/**
|
|
9
10
|
* Types, constants, and the Finder class for route discovery and manifest generation.
|
|
@@ -345,7 +346,7 @@ var Build;
|
|
|
345
346
|
currentPrerenderMode = flag;
|
|
346
347
|
}
|
|
347
348
|
const shellImport = Finder.getImportPath(shellPath);
|
|
348
|
-
const shellId = `${Build.EntryKind.SHELL}${
|
|
349
|
+
const shellId = `${Build.EntryKind.SHELL}${Runtime.hash(shellImport)}`;
|
|
349
350
|
const layoutIds = [];
|
|
350
351
|
const unauthorisedIds = [];
|
|
351
352
|
const forbiddenIds = [];
|
|
@@ -366,7 +367,7 @@ var Build;
|
|
|
366
367
|
continue;
|
|
367
368
|
}
|
|
368
369
|
const layoutImport = Finder.getImportPath(layoutPath);
|
|
369
|
-
const layoutId = `${Build.EntryKind.LAYOUT}${
|
|
370
|
+
const layoutId = `${Build.EntryKind.LAYOUT}${Runtime.hash(layoutImport)}`;
|
|
370
371
|
if (!processed.has(layoutPath)) {
|
|
371
372
|
prerenderCache.set(layoutPath, await Prerender.Build.getStaticFlag(layoutPath, this.buildContext));
|
|
372
373
|
imports.components.dynamic.set(layoutId, layoutImport);
|
|
@@ -381,7 +382,7 @@ var Build;
|
|
|
381
382
|
continue;
|
|
382
383
|
}
|
|
383
384
|
const unauthorisedImport = Finder.getImportPath(unauthorisedPath);
|
|
384
|
-
const unauthorisedId = `${Build.EntryKind['401']}${
|
|
385
|
+
const unauthorisedId = `${Build.EntryKind['401']}${Runtime.hash(unauthorisedImport)}`;
|
|
385
386
|
unauthorisedIds.push(unauthorisedId);
|
|
386
387
|
if (!processed.has(unauthorisedPath)) {
|
|
387
388
|
imports.components.dynamic.set(unauthorisedId, unauthorisedImport);
|
|
@@ -394,7 +395,7 @@ var Build;
|
|
|
394
395
|
continue;
|
|
395
396
|
}
|
|
396
397
|
const forbiddenImport = Finder.getImportPath(forbiddenPath);
|
|
397
|
-
const forbiddenId = `${Build.EntryKind['403']}${
|
|
398
|
+
const forbiddenId = `${Build.EntryKind['403']}${Runtime.hash(forbiddenImport)}`;
|
|
398
399
|
forbiddenIds.push(forbiddenId);
|
|
399
400
|
if (!processed.has(forbiddenPath)) {
|
|
400
401
|
imports.components.dynamic.set(forbiddenId, forbiddenImport);
|
|
@@ -409,7 +410,7 @@ var Build;
|
|
|
409
410
|
continue;
|
|
410
411
|
}
|
|
411
412
|
const notFoundImport = Finder.getImportPath(notFoundPath);
|
|
412
|
-
const notFoundId = `${Build.EntryKind['404']}${
|
|
413
|
+
const notFoundId = `${Build.EntryKind['404']}${Runtime.hash(notFoundImport)}`;
|
|
413
414
|
notFoundIds.push(notFoundId);
|
|
414
415
|
// dedupe imports but still assign the slot for this route
|
|
415
416
|
if (!processed.has(notFoundPath)) {
|
|
@@ -423,7 +424,7 @@ var Build;
|
|
|
423
424
|
continue;
|
|
424
425
|
}
|
|
425
426
|
const serverErrorImport = Finder.getImportPath(serverErrorPath);
|
|
426
|
-
const serverErrorId = `${Build.EntryKind['500']}${
|
|
427
|
+
const serverErrorId = `${Build.EntryKind['500']}${Runtime.hash(serverErrorImport)}`;
|
|
427
428
|
serverErrorIds.push(serverErrorId);
|
|
428
429
|
if (!processed.has(serverErrorPath)) {
|
|
429
430
|
imports.components.dynamic.set(serverErrorId, serverErrorImport);
|
|
@@ -438,7 +439,7 @@ var Build;
|
|
|
438
439
|
continue;
|
|
439
440
|
}
|
|
440
441
|
const loaderImport = Finder.getImportPath(loaderPath);
|
|
441
|
-
const loaderId = `${Build.EntryKind.LOADING}${
|
|
442
|
+
const loaderId = `${Build.EntryKind.LOADING}${Runtime.hash(loaderImport)}`;
|
|
442
443
|
loadingIds.push(loaderId);
|
|
443
444
|
// dedupe imports but still assign the slot for this route
|
|
444
445
|
if (!processed.has(loaderPath)) {
|
|
@@ -452,7 +453,7 @@ var Build;
|
|
|
452
453
|
continue;
|
|
453
454
|
}
|
|
454
455
|
const middlewareImport = Finder.getImportPath(middlewarePath);
|
|
455
|
-
const middlewareId = `${Build.EntryKind.MIDDLEWARE}${
|
|
456
|
+
const middlewareId = `${Build.EntryKind.MIDDLEWARE}${Runtime.hash(middlewareImport)}`;
|
|
456
457
|
middlewareIds.push(middlewareId);
|
|
457
458
|
if (!processed.has(middlewarePath)) {
|
|
458
459
|
// route scanning only tells us this is a +middleware file path
|
|
@@ -466,8 +467,8 @@ var Build;
|
|
|
466
467
|
}
|
|
467
468
|
// generate entry id based on page if exists, otherwise dir
|
|
468
469
|
const entryId = pagePath
|
|
469
|
-
? `${Build.EntryKind.PAGE}${
|
|
470
|
-
: `${Build.EntryKind.PAGE}${
|
|
470
|
+
? `${Build.EntryKind.PAGE}${Runtime.hash(Finder.getImportPath(pagePath))}`
|
|
471
|
+
: `${Build.EntryKind.PAGE}${Runtime.hash(route)}`;
|
|
471
472
|
if (pagePath) {
|
|
472
473
|
const pagePrerender = await Prerender.Build.getStaticFlag(pagePath, this.buildContext);
|
|
473
474
|
applyPrerenderMode(pagePrerender);
|
|
@@ -568,12 +569,12 @@ var Build;
|
|
|
568
569
|
continue;
|
|
569
570
|
}
|
|
570
571
|
const m = method.toLowerCase();
|
|
571
|
-
const endpointId = `${Build.EntryKind.ENDPOINT}${
|
|
572
|
+
const endpointId = `${Build.EntryKind.ENDPOINT}${Runtime.hash(Finder.getImportPath(endpointFilePath))}_${m}`;
|
|
572
573
|
const middlewareIds = await Promise.all(endpointMiddlewarePaths.map(async middlewarePath => {
|
|
573
574
|
if (!middlewarePath)
|
|
574
575
|
return null;
|
|
575
576
|
const middlewareImport = Finder.getImportPath(middlewarePath);
|
|
576
|
-
const middlewareId = `${Build.EntryKind.MIDDLEWARE}${
|
|
577
|
+
const middlewareId = `${Build.EntryKind.MIDDLEWARE}${Runtime.hash(middlewareImport)}`;
|
|
577
578
|
if (!processed.has(middlewarePath)) {
|
|
578
579
|
// endpoint middleware discovery gives us file paths, not proof
|
|
579
580
|
// of the export so check the module shape first
|
|
@@ -605,7 +606,7 @@ var Build;
|
|
|
605
606
|
modules[route] = {
|
|
606
607
|
...(modules[route] ?? {}),
|
|
607
608
|
middlewareIds: endpointMiddlewarePaths.map(middlewarePath => middlewarePath
|
|
608
|
-
? `${Build.EntryKind.MIDDLEWARE}${
|
|
609
|
+
? `${Build.EntryKind.MIDDLEWARE}${Runtime.hash(Finder.getImportPath(middlewarePath))}`
|
|
609
610
|
: null),
|
|
610
611
|
};
|
|
611
612
|
}
|
|
@@ -4,14 +4,15 @@ import { AUTOGEN_MSG, source, toSourceLiteral } from './utils.js';
|
|
|
4
4
|
* Generates the code to create an exported config object
|
|
5
5
|
*/
|
|
6
6
|
export function writeConfig(config) {
|
|
7
|
+
const { runtime: _runtime, ...runtimeConfig } = config;
|
|
7
8
|
const loggerLevel = config.logger?.level;
|
|
8
9
|
const importLines = [
|
|
9
|
-
`import type {
|
|
10
|
+
`import type { RuntimeConfig } from '${Solas.Config.PKG_NAME}'`,
|
|
10
11
|
loggerLevel ? `import { Logger } from '${Solas.Config.PKG_NAME}/utils/logger'` : '',
|
|
11
12
|
]
|
|
12
13
|
.filter(Boolean)
|
|
13
14
|
.join('\n');
|
|
14
|
-
const configStatement = `const config = ${toSourceLiteral(
|
|
15
|
+
const configStatement = `const config = ${toSourceLiteral(runtimeConfig)} as const satisfies RuntimeConfig`;
|
|
15
16
|
const loggerStatement = loggerLevel
|
|
16
17
|
? `Logger.defaultLevel = ${toSourceLiteral(loggerLevel)}`
|
|
17
18
|
: '';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { ConfiguredPluginConfig } from '../../types.js';
|
|
1
2
|
/**
|
|
2
3
|
* Generates the RSC entry code
|
|
3
4
|
*/
|
|
4
|
-
export declare function writeRSCEntry(): string;
|
|
5
|
+
export declare function writeRSCEntry(config: ConfiguredPluginConfig): string;
|
|
5
6
|
/**
|
|
6
7
|
* Generates the SSR entry code
|
|
7
8
|
*/
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { Solas } from '../../solas.js';
|
|
2
|
-
import { AUTOGEN_MSG, source } from './utils.js';
|
|
2
|
+
import { AUTOGEN_MSG, source, toStringLiteral } from './utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates the RSC entry code
|
|
5
5
|
*/
|
|
6
|
-
export function writeRSCEntry() {
|
|
6
|
+
export function writeRSCEntry(config) {
|
|
7
|
+
const runtime = toStringLiteral(config.runtime);
|
|
7
8
|
return source `
|
|
8
9
|
${AUTOGEN_MSG}
|
|
9
10
|
|
|
10
|
-
import { createHandler } from '${Solas.Config.PKG_NAME}/env/rsc'
|
|
11
|
-
import { Solas } from '${Solas.Config.PKG_NAME}'
|
|
11
|
+
import { createHandler, Runtime } from '${Solas.Config.PKG_NAME}/env/rsc'
|
|
12
|
+
import { Solas } from '${Solas.Config.PKG_NAME}/$'
|
|
12
13
|
|
|
13
14
|
import { manifest } from './manifest.js'
|
|
14
15
|
import { importMap } from './maps.js'
|
|
15
16
|
import { config } from './config.js'
|
|
16
17
|
|
|
18
|
+
Runtime.runtime = Solas.Runtime.create(${runtime})
|
|
17
19
|
const runtimeManifest = await Solas.Runtime.loadManifest(Solas.Config.OUT_DIR)
|
|
18
20
|
|
|
19
21
|
export default createHandler(config, manifest, importMap, runtimeManifest)
|
|
@@ -2,6 +2,7 @@ import type { ReactFormState } from 'react-dom/client';
|
|
|
2
2
|
import type { ImportMap, Manifest, RuntimeConfig } from '../../types.js';
|
|
3
3
|
import { Solas } from '../../solas.js';
|
|
4
4
|
import { Metadata } from '../metadata.js';
|
|
5
|
+
export { Runtime } from '../runtimes/runtime.js';
|
|
5
6
|
export type RscPayload = {
|
|
6
7
|
returnValue?: {
|
|
7
8
|
ok: boolean;
|
package/dist/internal/env/rsc.js
CHANGED
|
@@ -17,6 +17,7 @@ import { processActionRequest } from '../server/actions.js';
|
|
|
17
17
|
import DefaultErr from '../ui/defaults/error.js';
|
|
18
18
|
import { RequestContext } from './request-context.js';
|
|
19
19
|
import { getKnownDigest, isKnownError } from './utils.js';
|
|
20
|
+
export { Runtime } from '../runtimes/runtime.js';
|
|
20
21
|
const logger = new Logger();
|
|
21
22
|
const BASE_PATH = BasePath.normalise(import.meta.env.BASE_URL);
|
|
22
23
|
function resolveFilePath(root, relativePath) {
|
|
@@ -2,6 +2,7 @@ import { match as createMatch } from 'path-to-regexp';
|
|
|
2
2
|
import { BasePath } from '../../utils/base-path.js';
|
|
3
3
|
import { Solas } from '../../solas.js';
|
|
4
4
|
import { HttpException } from '../navigation/http-exception.js';
|
|
5
|
+
import { Runtime } from '../runtimes/runtime.js';
|
|
5
6
|
import { maybeAction } from '../server/actions.js';
|
|
6
7
|
import { enforce } from '../server/csrf.js';
|
|
7
8
|
import { getAlternatePathname, normalisePathname, toPathPattern } from './utils.js';
|
|
@@ -253,24 +254,24 @@ export class HttpRouter {
|
|
|
253
254
|
*/
|
|
254
255
|
static async serveStatic(filePath, req, precompress = false, headers = {}) {
|
|
255
256
|
const accept = req.headers.get('accept-encoding') ?? '';
|
|
256
|
-
let
|
|
257
|
+
let resolvedPath = filePath;
|
|
257
258
|
let encoding = null;
|
|
258
259
|
if (precompress) {
|
|
259
260
|
// prefer a precompressed variant when the client accepts it and one was emitted
|
|
260
261
|
if (accept.includes('br')) {
|
|
261
|
-
const
|
|
262
|
-
if (await
|
|
263
|
-
|
|
262
|
+
const brotliPath = `${filePath}.br`;
|
|
263
|
+
if (await Runtime.exists(brotliPath)) {
|
|
264
|
+
resolvedPath = brotliPath;
|
|
264
265
|
encoding = 'br';
|
|
265
266
|
}
|
|
266
267
|
}
|
|
267
268
|
}
|
|
268
|
-
if (!(await
|
|
269
|
+
if (!(await Runtime.exists(resolvedPath))) {
|
|
269
270
|
return new Response('Not found', { status: 404 });
|
|
270
271
|
}
|
|
271
272
|
// get mime type from original path, not compressed variant
|
|
272
|
-
const mimeType =
|
|
273
|
-
const res = new Response(
|
|
273
|
+
const mimeType = Runtime.mimeType(filePath);
|
|
274
|
+
const res = new Response(await Runtime.readBuffer(resolvedPath), {
|
|
274
275
|
headers: {
|
|
275
276
|
'Content-Type': headers['Content-Type'] ?? mimeType,
|
|
276
277
|
},
|
|
@@ -4,6 +4,7 @@ import { Compress } from '../utils/compress.js';
|
|
|
4
4
|
import { Logger } from '../utils/logger.js';
|
|
5
5
|
import { Solas } from '../solas.js';
|
|
6
6
|
import { Prerender } from './prerender.js';
|
|
7
|
+
import { Runtime } from './runtimes/runtime.js';
|
|
7
8
|
const logger = new Logger();
|
|
8
9
|
export async function postbuild(cwd = process.cwd()) {
|
|
9
10
|
const manifestPath = path.join(cwd, Solas.Config.GENERATED_DIR, 'build.json');
|
|
@@ -59,8 +60,8 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
59
60
|
if (artifact.mode === 'ppr') {
|
|
60
61
|
await fs.mkdir(artifactDir, { recursive: true });
|
|
61
62
|
const writes = [
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
Runtime.write(path.join(artifactDir, 'prelude.html'), artifact.html),
|
|
64
|
+
Runtime.write(path.join(artifactDir, 'metadata.json'), JSON.stringify({
|
|
64
65
|
schema: artifact.schema,
|
|
65
66
|
route: artifact.route,
|
|
66
67
|
createdAt: artifact.createdAt,
|
|
@@ -68,7 +69,7 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
68
69
|
})),
|
|
69
70
|
];
|
|
70
71
|
if (artifact.postponed !== undefined) {
|
|
71
|
-
writes.push(
|
|
72
|
+
writes.push(Runtime.write(path.join(artifactDir, 'postponed.json'), JSON.stringify(artifact.postponed)));
|
|
72
73
|
}
|
|
73
74
|
await Promise.all(writes);
|
|
74
75
|
artifactManifest[route] = {
|
|
@@ -82,13 +83,13 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
82
83
|
}
|
|
83
84
|
await fs.mkdir(artifactDir, { recursive: true });
|
|
84
85
|
await Promise.all([
|
|
85
|
-
|
|
86
|
+
Runtime.write(path.join(artifactDir, 'metadata.json'), JSON.stringify({
|
|
86
87
|
schema: artifact.schema,
|
|
87
88
|
route: artifact.route,
|
|
88
89
|
createdAt: artifact.createdAt,
|
|
89
90
|
mode: artifact.mode,
|
|
90
91
|
})),
|
|
91
|
-
|
|
92
|
+
Runtime.write(Prerender.Artifact.getFilePath(outDir, route, Prerender.Artifact.FULL_PRERENDER_FILENAME), artifact.html),
|
|
92
93
|
]);
|
|
93
94
|
artifactManifest[route] = {
|
|
94
95
|
mode: artifact.mode,
|
|
@@ -108,7 +109,7 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
108
109
|
artifacts: artifactManifest,
|
|
109
110
|
publicFiles: manifest.publicFiles,
|
|
110
111
|
};
|
|
111
|
-
await
|
|
112
|
+
await Runtime.write(Solas.Runtime.getManifestPath(outDir), JSON.stringify(runtimeManifest));
|
|
112
113
|
if (manifest.sitemapRoutes.length > 0 && manifest.url) {
|
|
113
114
|
const origin = manifest.url.replace(/\/$/, '');
|
|
114
115
|
const urls = manifest.sitemapRoutes
|
|
@@ -120,7 +121,7 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
120
121
|
urls,
|
|
121
122
|
'</urlset>',
|
|
122
123
|
].join('\n');
|
|
123
|
-
await
|
|
124
|
+
await Runtime.write(path.join(outDir, 'sitemap.xml'), sitemap);
|
|
124
125
|
logger.info('[sitemap]', `generated ${manifest.sitemapRoutes.length} urls`);
|
|
125
126
|
}
|
|
126
127
|
if (manifest.precompress) {
|
|
@@ -144,7 +145,7 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
144
145
|
normalisedPath.endsWith(`/${Prerender.Artifact.FULL_PRERENDER_FILENAME}`));
|
|
145
146
|
},
|
|
146
147
|
})) {
|
|
147
|
-
await
|
|
148
|
+
await Runtime.write(`${input}.br`, compressed);
|
|
148
149
|
logger.info('[precompress]', `${path.basename(input)}.br`);
|
|
149
150
|
}
|
|
150
151
|
}
|