@jk2908/solas 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +36 -11
- package/dist/adapters/cloudflare/assets.d.ts +37 -0
- package/dist/adapters/cloudflare/assets.js +71 -0
- package/dist/adapters/cloudflare/bindings.d.ts +7 -0
- package/dist/adapters/cloudflare/bindings.js +26 -0
- package/dist/adapters/cloudflare/index.d.ts +2 -2
- package/dist/adapters/cloudflare/index.js +1 -2
- package/dist/adapters/cloudflare/runtime.d.ts +2 -37
- package/dist/adapters/cloudflare/runtime.js +2 -71
- package/dist/adapters/cloudflare/vite.d.ts +5 -2
- package/dist/adapters/cloudflare/vite.js +9 -4
- package/dist/adapters/cloudflare/worker.d.ts +1 -1
- package/dist/adapters/cloudflare/worker.js +1 -1
- package/dist/adapters/cloudflare/workers-shim.d.ts +20 -0
- package/dist/adapters/cloudflare/workers-shim.js +50 -0
- package/dist/adapters/node.d.ts +2 -1
- package/dist/adapters/node.js +2 -1
- package/dist/adapters/static.d.ts +27 -0
- package/dist/adapters/static.js +59 -0
- package/dist/config.d.ts +2 -1
- package/dist/config.js +3 -2
- package/dist/index.js +3 -0
- package/dist/internal/codegen/environments.js +1 -1
- package/dist/internal/guards.d.ts +21 -0
- package/dist/internal/guards.js +52 -0
- package/dist/internal/postbuild.d.ts +14 -0
- package/dist/internal/postbuild.js +14 -1
- package/dist/internal/prerender.js +1 -1
- package/dist/types.d.ts +8 -3
- package/package.json +11 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- A route's static-params export is now `staticParams` (was `params`), matching the `Route.StaticParams` type. `Route.Prerender` became `Route.PrerenderMode` (`'full' | 'ppr'`); the `prerender` option and route export are typed `Route.PrerenderMode | false` (`false` turns prerendering off).
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- Added build guards, installed by the adapters. An adapter declares the build features it supports (`actions`, `endpoints`) and Solas warns when the graph uses one it does not. The static adapter supports none, so it warns about server actions and `+endpoint` routes.
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
|
|
15
|
+
- The build-time prerender now shims `cloudflare:workers`, so an app that imports bindings at module scope no longer breaks the build. Bindings do not exist at build time, so reading one warns and returns `undefined`.
|
|
16
|
+
|
|
17
|
+
## 0.10.0
|
|
18
|
+
|
|
19
|
+
### Breaking
|
|
20
|
+
|
|
21
|
+
- Adapters moved under `@jk2908/solas/adapters/*` and are now default exports: `import cloudflare from '@jk2908/solas/adapters/cloudflare'`, `import serve from '@jk2908/solas/adapters/node'`, `import staticAdapter from '@jk2908/solas/adapters/static'`. The Cloudflare runtime (`createWorker`, `createCloudflareAssets`) is now `@jk2908/solas/adapters/cloudflare/runtime`. The old `/cloudflare`, `/cloudflare/vite`, and `/node` entries are removed.
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
- Added a static site adapter, `@jk2908/solas/adapters/static`. It sets `prerender: 'full'` and merges the client assets, full prerenders, `sitemap.xml`, and `robots.txt` into one directory any static host can serve.
|
|
26
|
+
|
|
3
27
|
## 0.9.1
|
|
4
28
|
|
|
5
29
|
- Trimmed the install section of the README.
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Platform deployment is configured with the `adapter` option, which registers the
|
|
|
34
34
|
import { defineConfig } from 'vite'
|
|
35
35
|
|
|
36
36
|
import solas from '@jk2908/solas'
|
|
37
|
-
import
|
|
37
|
+
import cloudflare from '@jk2908/solas/adapters/cloudflare'
|
|
38
38
|
import react from '@vitejs/plugin-react'
|
|
39
39
|
|
|
40
40
|
export default defineConfig({
|
|
@@ -205,7 +205,7 @@ export const metadata: Route.Metadata<Solas.Routes['/writing/:slug']> = ({ param
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
export const
|
|
208
|
+
export const staticParams: Route.StaticParams<Solas.Routes['/writing/:slug']> = () =>
|
|
209
209
|
allPosts?.map(p => ({ slug: p.__mdsrc.slug })) ?? []
|
|
210
210
|
```
|
|
211
211
|
|
|
@@ -318,10 +318,10 @@ export const prerender = false
|
|
|
318
318
|
|
|
319
319
|
In that example, the app default is `full`, the `about` layout overrides it to `ppr`, and the page overrides it again to `false`.
|
|
320
320
|
|
|
321
|
-
For dynamic routes, prerendering uses the
|
|
321
|
+
For dynamic routes, prerendering uses the staticParams you export from the page:
|
|
322
322
|
|
|
323
323
|
```tsx
|
|
324
|
-
export const
|
|
324
|
+
export const staticParams = () => [{ id: 'post-1' }, { id: 'post-2' }]
|
|
325
325
|
export const prerender = 'full'
|
|
326
326
|
```
|
|
327
327
|
|
|
@@ -482,7 +482,7 @@ Use `sitemap` to generate a `sitemap.xml` at build time.
|
|
|
482
482
|
|
|
483
483
|
Default: `false`
|
|
484
484
|
|
|
485
|
-
When enabled, Solas writes a sitemap containing all routes with deterministic URLs: static routes, prerendered routes, and dynamic routes resolved via `
|
|
485
|
+
When enabled, Solas writes a sitemap containing all routes with deterministic URLs: static routes, prerendered routes, and dynamic routes resolved via `staticParams`. The origin for each URL comes from the resolved origin (see Config > `url`).
|
|
486
486
|
|
|
487
487
|
```ts
|
|
488
488
|
export default defineConfig(({ mode }) => ({
|
|
@@ -567,7 +567,7 @@ npm install -D @cloudflare/vite-plugin@beta cf
|
|
|
567
567
|
import { defineConfig } from 'vite'
|
|
568
568
|
|
|
569
569
|
import solas from '@jk2908/solas'
|
|
570
|
-
import
|
|
570
|
+
import cloudflare from '@jk2908/solas/adapters/cloudflare'
|
|
571
571
|
import react from '@vitejs/plugin-react'
|
|
572
572
|
|
|
573
573
|
export default defineConfig({
|
|
@@ -621,6 +621,8 @@ const value = await env.MY_KV.get('key')
|
|
|
621
621
|
|
|
622
622
|
The Cloudflare Vite plugin emulates bindings in `cf dev` and `vite preview`, so there is nothing extra to configure locally.
|
|
623
623
|
|
|
624
|
+
Bindings do not exist during the build-time prerender, where Solas imports the Worker in Node with `cloudflare:workers` shimmed. Reading one then warns and returns `undefined`, so guard it with a fallback or read it at request time.
|
|
625
|
+
|
|
624
626
|
For types, the plugin writes `.cloudflare/types/index.d.ts` (bindings inferred from `cloudflare.config.ts`). Include it in `tsconfig.json` so `cloudflare:workers`'s `env` is typed:
|
|
625
627
|
|
|
626
628
|
```jsonc
|
|
@@ -643,18 +645,18 @@ Because workers have no filesystem, the Worker reads its own output through the
|
|
|
643
645
|
`cloudflare()` forwards its `cloudflare` option to `@cloudflare/vite-plugin`. To build a Worker manually, `createWorker` composes the entry behaviour, and `createCloudflareAssets` builds the store directly:
|
|
644
646
|
|
|
645
647
|
```ts
|
|
646
|
-
import { createCloudflareAssets } from '@jk2908/solas/cloudflare'
|
|
648
|
+
import { createCloudflareAssets } from '@jk2908/solas/adapters/cloudflare/runtime'
|
|
647
649
|
|
|
648
650
|
const assets = createCloudflareAssets(env)
|
|
649
651
|
```
|
|
650
652
|
|
|
651
653
|
## Node
|
|
652
654
|
|
|
653
|
-
`vite dev` and `vite preview` already run the built handler in Node (via `@vitejs/plugin-rsc`'s default server handler), so nothing extra is needed locally. For a production Node server, use `@jk2908/solas/node`:
|
|
655
|
+
`vite dev` and `vite preview` already run the built handler in Node (via `@vitejs/plugin-rsc`'s default server handler), so nothing extra is needed locally. For a production Node server, use `@jk2908/solas/adapters/node`:
|
|
654
656
|
|
|
655
657
|
```ts
|
|
656
658
|
// server.ts
|
|
657
|
-
import
|
|
659
|
+
import serve from '@jk2908/solas/adapters/node'
|
|
658
660
|
|
|
659
661
|
await serve()
|
|
660
662
|
```
|
|
@@ -686,11 +688,34 @@ import handler from './dist/rsc/index.js'
|
|
|
686
688
|
Deno.serve(request => handler.fetch(request))
|
|
687
689
|
```
|
|
688
690
|
|
|
689
|
-
The handler reads `dist` through `node:fs`, which Bun and Deno (2.x, via Node compat) provide. Run from the project root. Bun can also run `@jk2908/solas/node` as-is, since it implements `node:http`.
|
|
691
|
+
The handler reads `dist` through `node:fs`, which Bun and Deno (2.x, via Node compat) provide. Run from the project root. Bun can also run `@jk2908/solas/adapters/node` as-is, since it implements `node:http`.
|
|
692
|
+
|
|
693
|
+
## Static
|
|
694
|
+
|
|
695
|
+
The static adapter sets `prerender: 'full'` so every route is rendered to HTML at build time, then assembles a directory any static host can serve. There is no server.
|
|
696
|
+
|
|
697
|
+
```ts
|
|
698
|
+
// vite.config.ts
|
|
699
|
+
import { defineConfig } from 'vite'
|
|
700
|
+
|
|
701
|
+
import solas from '@jk2908/solas'
|
|
702
|
+
import staticAdapter from '@jk2908/solas/adapters/static'
|
|
703
|
+
import react from '@vitejs/plugin-react'
|
|
704
|
+
|
|
705
|
+
export default defineConfig({
|
|
706
|
+
plugins: [solas({ adapter: staticAdapter() }), react()],
|
|
707
|
+
})
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
`vite build` writes the site to `build/` (override with `outDir`): the client assets, each route as `<route>.html` (or `<route>/index.html` under `trailingSlash: 'always'`), plus `sitemap.xml` and `robots.txt`. Serve that directory with anything — `npx serve build`, GitHub Pages, Netlify, an S3 bucket, nginx.
|
|
711
|
+
|
|
712
|
+
Because it forces full prerendering, routes that need a server are not a fit: per-route PPR or SSR opt-ins and dynamic routes without `params()`.
|
|
713
|
+
|
|
714
|
+
Server actions and runtime endpoints also need a server, so the build warns when the graph contains any (a `'use server'` module or a `+endpoint` file).
|
|
690
715
|
|
|
691
716
|
## Runtime
|
|
692
717
|
|
|
693
|
-
At request time Solas reads its output through an `Assets` interface (`exists`/`readText`/`readBuffer`/`mimeType`) injected into `createHandler`, defaulting to the Node standard library (which Bun runs natively). References are logical (`{ namespace, path }`), where the namespaces are `client` (Vite client output), `static` (full prerenders), and `artifact` (runtime manifest and ppr files). Platforms without a filesystem provide their own store; `@jk2908/solas/cloudflare` builds one around the `ASSETS` binding via `createWorker`. Build-time reads and writes use `node:fs` directly and are not abstracted.
|
|
718
|
+
At request time Solas reads its output through an `Assets` interface (`exists`/`readText`/`readBuffer`/`mimeType`) injected into `createHandler`, defaulting to the Node standard library (which Bun runs natively). References are logical (`{ namespace, path }`), where the namespaces are `client` (Vite client output), `static` (full prerenders), and `artifact` (runtime manifest and ppr files). Platforms without a filesystem provide their own store; `@jk2908/solas/adapters/cloudflare/runtime` builds one around the `ASSETS` binding via `createWorker`. Build-time reads and writes use `node:fs` directly and are not abstracted.
|
|
694
719
|
|
|
695
720
|
## Scripts
|
|
696
721
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AssetNamespace, Assets } from '../../internal/runtimes/assets.js';
|
|
2
|
+
/**
|
|
3
|
+
* The subset of the Cloudflare `assets` Fetcher binding that Solas needs.
|
|
4
|
+
*/
|
|
5
|
+
export type CloudflareAssets = {
|
|
6
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The bindings shape the Cloudflare asset store reads from.
|
|
10
|
+
*/
|
|
11
|
+
export type CloudflareEnv = {
|
|
12
|
+
ASSETS?: CloudflareAssets;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Path prefix the adapter copies `dist/.solas` artifacts under in the asset
|
|
17
|
+
* output.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ARTIFACT_PREFIX = "_solas-artifacts";
|
|
20
|
+
/**
|
|
21
|
+
* Where each namespace lives in the asset output, keyed by the shared
|
|
22
|
+
* `AssetNamespace`. `client` files sit at the asset root; `static` (prerenders)
|
|
23
|
+
* and `artifact` (manifest, ppr) live under the artifact prefix.
|
|
24
|
+
*
|
|
25
|
+
* Prerenders must not sit at their route paths: the asset layer ignores
|
|
26
|
+
* `Accept`, so it would answer an RSC navigation (`Accept: text/x-component`)
|
|
27
|
+
* with HTML instead of letting the Worker render it.
|
|
28
|
+
*/
|
|
29
|
+
export declare const NAMESPACE_PREFIX: Record<AssetNamespace, string>;
|
|
30
|
+
/**
|
|
31
|
+
* Create a Solas asset store backed by the Cloudflare assets binding.
|
|
32
|
+
*
|
|
33
|
+
* Workers have no writable filesystem, so this is read-only. `client` files are
|
|
34
|
+
* served from the asset root; `static` and `artifact` files are served from the
|
|
35
|
+
* adapter's artifact prefix.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createCloudflareAssets(env: CloudflareEnv): Assets;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getMimeTypeFromPath } from '../../internal/runtimes/mime.js';
|
|
2
|
+
/**
|
|
3
|
+
* Path prefix the adapter copies `dist/.solas` artifacts under in the asset
|
|
4
|
+
* output.
|
|
5
|
+
*/
|
|
6
|
+
export const ARTIFACT_PREFIX = '_solas-artifacts';
|
|
7
|
+
// The assets binding matches on pathname only, so this is only used to satisfy
|
|
8
|
+
// the URL constructor
|
|
9
|
+
const ASSET_ORIGIN = 'https://solas-assets.invalid';
|
|
10
|
+
/**
|
|
11
|
+
* Where each namespace lives in the asset output, keyed by the shared
|
|
12
|
+
* `AssetNamespace`. `client` files sit at the asset root; `static` (prerenders)
|
|
13
|
+
* and `artifact` (manifest, ppr) live under the artifact prefix.
|
|
14
|
+
*
|
|
15
|
+
* Prerenders must not sit at their route paths: the asset layer ignores
|
|
16
|
+
* `Accept`, so it would answer an RSC navigation (`Accept: text/x-component`)
|
|
17
|
+
* with HTML instead of letting the Worker render it.
|
|
18
|
+
*/
|
|
19
|
+
export const NAMESPACE_PREFIX = {
|
|
20
|
+
client: '',
|
|
21
|
+
static: `${ARTIFACT_PREFIX}/static`,
|
|
22
|
+
artifact: ARTIFACT_PREFIX,
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Create a Solas asset store backed by the Cloudflare assets binding.
|
|
26
|
+
*
|
|
27
|
+
* Workers have no writable filesystem, so this is read-only. `client` files are
|
|
28
|
+
* served from the asset root; `static` and `artifact` files are served from the
|
|
29
|
+
* adapter's artifact prefix.
|
|
30
|
+
*/
|
|
31
|
+
export function createCloudflareAssets(env) {
|
|
32
|
+
const assets = env.ASSETS;
|
|
33
|
+
if (!assets || typeof assets.fetch !== 'function') {
|
|
34
|
+
throw new Error('[solas] createCloudflareAssets requires an `ASSETS` binding. Add an assets binding to your Worker.');
|
|
35
|
+
}
|
|
36
|
+
// narrowed here, and `const` keeps that type inside the closures below
|
|
37
|
+
const binding = assets;
|
|
38
|
+
function toUrl(asset) {
|
|
39
|
+
const prefix = NAMESPACE_PREFIX[asset.namespace];
|
|
40
|
+
const path = prefix ? `${prefix}/${asset.path}` : asset.path;
|
|
41
|
+
return new URL(`/${path}`, ASSET_ORIGIN).toString();
|
|
42
|
+
}
|
|
43
|
+
async function read(asset) {
|
|
44
|
+
const res = await binding.fetch(toUrl(asset), {
|
|
45
|
+
// asset responses should be passed through verbatim
|
|
46
|
+
headers: { 'accept-encoding': 'identity' },
|
|
47
|
+
});
|
|
48
|
+
return res.ok ? res : null;
|
|
49
|
+
}
|
|
50
|
+
async function readOrThrow(asset) {
|
|
51
|
+
const res = await read(asset);
|
|
52
|
+
if (!res) {
|
|
53
|
+
throw new Error(`[solas] asset not found: ${asset.namespace}/${asset.path}`);
|
|
54
|
+
}
|
|
55
|
+
return res;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
async exists(asset) {
|
|
59
|
+
return (await read(asset)) !== null;
|
|
60
|
+
},
|
|
61
|
+
async readText(asset) {
|
|
62
|
+
return (await readOrThrow(asset)).text();
|
|
63
|
+
},
|
|
64
|
+
async readBuffer(asset) {
|
|
65
|
+
return (await readOrThrow(asset)).arrayBuffer();
|
|
66
|
+
},
|
|
67
|
+
mimeType(filePath) {
|
|
68
|
+
return getMimeTypeFromPath(filePath);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PrerenderImport } from '../../internal/postbuild.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wrap the prerender import so `cloudflare:workers` resolves to a local shim
|
|
4
|
+
* while the server bundle loads. The bundle on disk keeps the real import; this
|
|
5
|
+
* only affects the Node import Solas makes to prerender.
|
|
6
|
+
*/
|
|
7
|
+
export declare function withBindingsShim(next: PrerenderImport): PrerenderImport;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The shim module `cloudflare:workers` resolves to during the prerender. */
|
|
2
|
+
const WORKERS_SHIM = new URL('./workers-shim.js', import.meta.url).href;
|
|
3
|
+
/**
|
|
4
|
+
* Wrap the prerender import so `cloudflare:workers` resolves to a local shim
|
|
5
|
+
* while the server bundle loads. The bundle on disk keeps the real import; this
|
|
6
|
+
* only affects the Node import Solas makes to prerender.
|
|
7
|
+
*/
|
|
8
|
+
export function withBindingsShim(next) {
|
|
9
|
+
return async () => {
|
|
10
|
+
const { registerHooks } = await import('node:module');
|
|
11
|
+
const hooks = registerHooks({
|
|
12
|
+
resolve(specifier, context, nextResolve) {
|
|
13
|
+
if (specifier === 'cloudflare:workers') {
|
|
14
|
+
return { url: WORKERS_SHIM, shortCircuit: true };
|
|
15
|
+
}
|
|
16
|
+
return nextResolve(specifier, context);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
try {
|
|
20
|
+
return await next();
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
hooks.deregister();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { adapter as default } from './vite.js';
|
|
2
|
+
export type { SolasCloudflareOptions } from './vite.js';
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { createWorker } from './worker.js';
|
|
1
|
+
export { adapter as default } from './vite.js';
|
|
@@ -1,37 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* The subset of the Cloudflare `assets` Fetcher binding that Solas needs.
|
|
4
|
-
*/
|
|
5
|
-
export type CloudflareAssets = {
|
|
6
|
-
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* The bindings shape the Cloudflare asset store reads from.
|
|
10
|
-
*/
|
|
11
|
-
export type CloudflareEnv = {
|
|
12
|
-
ASSETS?: CloudflareAssets;
|
|
13
|
-
[key: string]: unknown;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Path prefix the adapter copies `dist/.solas` artifacts under in the asset
|
|
17
|
-
* output.
|
|
18
|
-
*/
|
|
19
|
-
export declare const ARTIFACT_PREFIX = "_solas-artifacts";
|
|
20
|
-
/**
|
|
21
|
-
* Where each namespace lives in the asset output, keyed by the shared
|
|
22
|
-
* `AssetNamespace`. `client` files sit at the asset root; `static` (prerenders)
|
|
23
|
-
* and `artifact` (manifest, ppr) live under the artifact prefix.
|
|
24
|
-
*
|
|
25
|
-
* Prerenders must not sit at their route paths: the asset layer ignores
|
|
26
|
-
* `Accept`, so it would answer an RSC navigation (`Accept: text/x-component`)
|
|
27
|
-
* with HTML instead of letting the Worker render it.
|
|
28
|
-
*/
|
|
29
|
-
export declare const NAMESPACE_PREFIX: Record<AssetNamespace, string>;
|
|
30
|
-
/**
|
|
31
|
-
* Create a Solas asset store backed by the Cloudflare assets binding.
|
|
32
|
-
*
|
|
33
|
-
* Workers have no writable filesystem, so this is read-only. `client` files are
|
|
34
|
-
* served from the asset root; `static` and `artifact` files are served from the
|
|
35
|
-
* adapter's artifact prefix.
|
|
36
|
-
*/
|
|
37
|
-
export declare function createCloudflareAssets(env: CloudflareEnv): Assets;
|
|
1
|
+
export { ARTIFACT_PREFIX, createCloudflareAssets, type CloudflareAssets, type CloudflareEnv, } from './assets.js';
|
|
2
|
+
export { createWorker, type CreateWorkerOptions } from './worker.js';
|
|
@@ -1,71 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Path prefix the adapter copies `dist/.solas` artifacts under in the asset
|
|
4
|
-
* output.
|
|
5
|
-
*/
|
|
6
|
-
export const ARTIFACT_PREFIX = '_solas-artifacts';
|
|
7
|
-
// The assets binding matches on pathname only, so this is only used to satisfy
|
|
8
|
-
// the URL constructor
|
|
9
|
-
const ASSET_ORIGIN = 'https://solas-assets.invalid';
|
|
10
|
-
/**
|
|
11
|
-
* Where each namespace lives in the asset output, keyed by the shared
|
|
12
|
-
* `AssetNamespace`. `client` files sit at the asset root; `static` (prerenders)
|
|
13
|
-
* and `artifact` (manifest, ppr) live under the artifact prefix.
|
|
14
|
-
*
|
|
15
|
-
* Prerenders must not sit at their route paths: the asset layer ignores
|
|
16
|
-
* `Accept`, so it would answer an RSC navigation (`Accept: text/x-component`)
|
|
17
|
-
* with HTML instead of letting the Worker render it.
|
|
18
|
-
*/
|
|
19
|
-
export const NAMESPACE_PREFIX = {
|
|
20
|
-
client: '',
|
|
21
|
-
static: `${ARTIFACT_PREFIX}/static`,
|
|
22
|
-
artifact: ARTIFACT_PREFIX,
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Create a Solas asset store backed by the Cloudflare assets binding.
|
|
26
|
-
*
|
|
27
|
-
* Workers have no writable filesystem, so this is read-only. `client` files are
|
|
28
|
-
* served from the asset root; `static` and `artifact` files are served from the
|
|
29
|
-
* adapter's artifact prefix.
|
|
30
|
-
*/
|
|
31
|
-
export function createCloudflareAssets(env) {
|
|
32
|
-
const assets = env.ASSETS;
|
|
33
|
-
if (!assets || typeof assets.fetch !== 'function') {
|
|
34
|
-
throw new Error('[solas] createCloudflareAssets requires an `ASSETS` binding. Add an assets binding to your Worker.');
|
|
35
|
-
}
|
|
36
|
-
// narrowed here, and `const` keeps that type inside the closures below
|
|
37
|
-
const binding = assets;
|
|
38
|
-
function toUrl(asset) {
|
|
39
|
-
const prefix = NAMESPACE_PREFIX[asset.namespace];
|
|
40
|
-
const path = prefix ? `${prefix}/${asset.path}` : asset.path;
|
|
41
|
-
return new URL(`/${path}`, ASSET_ORIGIN).toString();
|
|
42
|
-
}
|
|
43
|
-
async function read(asset) {
|
|
44
|
-
const res = await binding.fetch(toUrl(asset), {
|
|
45
|
-
// asset responses should be passed through verbatim
|
|
46
|
-
headers: { 'accept-encoding': 'identity' },
|
|
47
|
-
});
|
|
48
|
-
return res.ok ? res : null;
|
|
49
|
-
}
|
|
50
|
-
async function readOrThrow(asset) {
|
|
51
|
-
const res = await read(asset);
|
|
52
|
-
if (!res) {
|
|
53
|
-
throw new Error(`[solas] asset not found: ${asset.namespace}/${asset.path}`);
|
|
54
|
-
}
|
|
55
|
-
return res;
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
async exists(asset) {
|
|
59
|
-
return (await read(asset)) !== null;
|
|
60
|
-
},
|
|
61
|
-
async readText(asset) {
|
|
62
|
-
return (await readOrThrow(asset)).text();
|
|
63
|
-
},
|
|
64
|
-
async readBuffer(asset) {
|
|
65
|
-
return (await readOrThrow(asset)).arrayBuffer();
|
|
66
|
-
},
|
|
67
|
-
mimeType(filePath) {
|
|
68
|
-
return getMimeTypeFromPath(filePath);
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
1
|
+
export { ARTIFACT_PREFIX, createCloudflareAssets, } from './assets.js';
|
|
2
|
+
export { createWorker } from './worker.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { PluginOption } from 'vite';
|
|
1
2
|
import { cloudflare as cloudflareVitePlugin } from '@cloudflare/vite-plugin';
|
|
2
|
-
import type { Adapter } from '../../types.js';
|
|
3
3
|
/**
|
|
4
4
|
* @see {@link https://developers.cloudflare.com/workers/vite-plugin/}
|
|
5
5
|
*/
|
|
@@ -17,5 +17,8 @@ export type SolasCloudflareOptions = {
|
|
|
17
17
|
* asset-backed store, points Solas's prerender step at the Cloudflare Worker
|
|
18
18
|
* bundle, and copies prerender artifacts into the asset output.
|
|
19
19
|
*/
|
|
20
|
-
export declare function
|
|
20
|
+
export declare function adapter(options?: SolasCloudflareOptions): {
|
|
21
|
+
name: string;
|
|
22
|
+
plugins: PluginOption[];
|
|
23
|
+
};
|
|
21
24
|
export {};
|
|
@@ -3,8 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
import { cloudflare as cloudflareVitePlugin } from '@cloudflare/vite-plugin';
|
|
4
4
|
import * as Config from '../../config.js';
|
|
5
5
|
import { writeCloudflareEntry } from '../../internal/codegen/environments.js';
|
|
6
|
-
import { setRscEntry } from '../../internal/postbuild.js';
|
|
7
|
-
import { NAMESPACE_PREFIX } from './
|
|
6
|
+
import { setPrerenderImport, setRscEntry } from '../../internal/postbuild.js';
|
|
7
|
+
import { NAMESPACE_PREFIX } from './assets.js';
|
|
8
|
+
import { withBindingsShim } from './bindings.js';
|
|
8
9
|
const VIRTUAL_ENTRY = 'virtual:solas-cloudflare-entry';
|
|
9
10
|
/**
|
|
10
11
|
* Solas dependencies that are imported at request time from the Worker
|
|
@@ -103,7 +104,7 @@ function withAdapterConfig(config) {
|
|
|
103
104
|
* asset-backed store, points Solas's prerender step at the Cloudflare Worker
|
|
104
105
|
* bundle, and copies prerender artifacts into the asset output.
|
|
105
106
|
*/
|
|
106
|
-
export function
|
|
107
|
+
export function adapter(options = {}) {
|
|
107
108
|
const cwd = process.cwd();
|
|
108
109
|
// back the virtual entry id with a path inside the generated dir so the
|
|
109
110
|
// generated entry's relative imports (`./manifest.js`, ...) resolve
|
|
@@ -127,6 +128,9 @@ export function cloudflare(options = {}) {
|
|
|
127
128
|
if (rscOutDir) {
|
|
128
129
|
setRscEntry(path.resolve(cwd, rscOutDir, 'index.js'));
|
|
129
130
|
}
|
|
131
|
+
// the Worker bundle imports `cloudflare:workers`, which only exists in
|
|
132
|
+
// workerd, so shim it around the Node import
|
|
133
|
+
setPrerenderImport(withBindingsShim);
|
|
130
134
|
},
|
|
131
135
|
configEnvironment(name, environment) {
|
|
132
136
|
// Pre-bundle the Worker environments so workerd never has to re-optimize
|
|
@@ -163,5 +167,6 @@ export function cloudflare(options = {}) {
|
|
|
163
167
|
},
|
|
164
168
|
},
|
|
165
169
|
};
|
|
166
|
-
|
|
170
|
+
const plugins = [adapter, ...cfPlugins];
|
|
171
|
+
return { name: 'cloudflare', plugins };
|
|
167
172
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRuntimeHandler } from '../../internal/env/rsc.js';
|
|
2
2
|
import { nodeAssets } from '../../internal/runtimes/assets.js';
|
|
3
|
-
import { createCloudflareAssets } from './
|
|
3
|
+
import { createCloudflareAssets } from './assets.js';
|
|
4
4
|
/**
|
|
5
5
|
* Build a Cloudflare Worker around Solas's RSC handler.
|
|
6
6
|
*
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const env: Record<string, unknown>;
|
|
2
|
+
export declare const exports: Record<string, unknown>;
|
|
3
|
+
export declare const cache: Record<string, unknown>;
|
|
4
|
+
export declare const tracing: Record<string, unknown>;
|
|
5
|
+
export declare function waitUntil(_promise: Promise<unknown>): void;
|
|
6
|
+
export declare function withEnv<T>(_env: unknown, fn: () => T): T;
|
|
7
|
+
export declare function withExports<T>(_exports: unknown, fn: () => T): T;
|
|
8
|
+
export declare function withEnvAndExports<T>(_env: unknown, _exports: unknown, fn: () => T): T;
|
|
9
|
+
export declare class RpcStub {
|
|
10
|
+
}
|
|
11
|
+
export declare class RpcTarget {
|
|
12
|
+
}
|
|
13
|
+
export declare class WorkerEntrypoint {
|
|
14
|
+
}
|
|
15
|
+
export declare class DurableObject {
|
|
16
|
+
}
|
|
17
|
+
export declare class WorkflowStep {
|
|
18
|
+
}
|
|
19
|
+
export declare class WorkflowEntrypoint {
|
|
20
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stand-in for `cloudflare:workers`, loaded only while Solas imports the
|
|
3
|
+
* Worker bundle in Node for the build-time prerender (see `bindings.ts`). The
|
|
4
|
+
* real module is a workerd built-in, which Node cannot resolve.
|
|
5
|
+
*
|
|
6
|
+
* The exports mirror `@cloudflare/workers-types`' `CloudflareWorkersModule`.
|
|
7
|
+
* There are no bindings at build time, so reading one warns and returns
|
|
8
|
+
* `undefined` rather than throwing, and the classes are inert stubs so a module
|
|
9
|
+
* that extends one still loads. None of this runs on the deployed Worker.
|
|
10
|
+
*/
|
|
11
|
+
const warned = new Set();
|
|
12
|
+
function binding() {
|
|
13
|
+
return new Proxy({}, {
|
|
14
|
+
get(_target, key) {
|
|
15
|
+
if (!warned.has(key)) {
|
|
16
|
+
warned.add(key);
|
|
17
|
+
console.warn(`[solas] "${String(key)}" was read during the build-time ` +
|
|
18
|
+
`prerender, where no bindings exist. It returned undefined. ` +
|
|
19
|
+
`Read it at request time, or guard it with a fallback.`);
|
|
20
|
+
}
|
|
21
|
+
return;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export const env = binding();
|
|
26
|
+
export const exports = binding();
|
|
27
|
+
export const cache = binding();
|
|
28
|
+
export const tracing = binding();
|
|
29
|
+
export function waitUntil(_promise) { }
|
|
30
|
+
export function withEnv(_env, fn) {
|
|
31
|
+
return fn();
|
|
32
|
+
}
|
|
33
|
+
export function withExports(_exports, fn) {
|
|
34
|
+
return fn();
|
|
35
|
+
}
|
|
36
|
+
export function withEnvAndExports(_env, _exports, fn) {
|
|
37
|
+
return fn();
|
|
38
|
+
}
|
|
39
|
+
export class RpcStub {
|
|
40
|
+
}
|
|
41
|
+
export class RpcTarget {
|
|
42
|
+
}
|
|
43
|
+
export class WorkerEntrypoint {
|
|
44
|
+
}
|
|
45
|
+
export class DurableObject {
|
|
46
|
+
}
|
|
47
|
+
export class WorkflowStep {
|
|
48
|
+
}
|
|
49
|
+
export class WorkflowEntrypoint {
|
|
50
|
+
}
|
package/dist/adapters/node.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export type Server = http.Server & AsyncDisposable;
|
|
|
38
38
|
* Deno and Bun do not need this helper: use `Deno.serve(handler.fetch)` or
|
|
39
39
|
* `Bun.serve({ fetch: handler.fetch })` directly.
|
|
40
40
|
*/
|
|
41
|
-
|
|
41
|
+
declare function adapter(options?: ServeOptions): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse> & {
|
|
42
42
|
[Symbol.asyncDispose](): Promise<void>;
|
|
43
43
|
}>;
|
|
44
|
+
export { adapter as default };
|
package/dist/adapters/node.js
CHANGED
|
@@ -114,7 +114,7 @@ async function sendResponse(res, response) {
|
|
|
114
114
|
* Deno and Bun do not need this helper: use `Deno.serve(handler.fetch)` or
|
|
115
115
|
* `Bun.serve({ fetch: handler.fetch })` directly.
|
|
116
116
|
*/
|
|
117
|
-
|
|
117
|
+
async function adapter(options = {}) {
|
|
118
118
|
const outDir = options.outDir ?? 'dist';
|
|
119
119
|
const entry = path.resolve(options.entry ?? path.join(outDir, 'rsc', 'index.js'));
|
|
120
120
|
const port = options.port ?? (Number(process.env.PORT) || 3000);
|
|
@@ -142,3 +142,4 @@ export async function serve(options = {}) {
|
|
|
142
142
|
},
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
|
+
export { adapter as default };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PluginOption } from 'vite';
|
|
2
|
+
export type SolasStaticOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Directory, relative to the project root, the static site is written to.
|
|
5
|
+
*
|
|
6
|
+
* @default 'build'
|
|
7
|
+
*/
|
|
8
|
+
outDir?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Static site adapter for Solas.
|
|
12
|
+
*
|
|
13
|
+
* Sets `prerender: 'full'` so every route is rendered to HTML at build time,
|
|
14
|
+
* then merges Solas's output into a single directory any static host can serve:
|
|
15
|
+
* the client assets, the full prerenders (at their route paths) and the
|
|
16
|
+
* generated `sitemap.xml` / `robots.txt`.
|
|
17
|
+
*
|
|
18
|
+
* It installs the guards with no server features supported, so the build warns
|
|
19
|
+
* when the graph contains server actions or `+endpoint` routes, which a static
|
|
20
|
+
* host cannot run.
|
|
21
|
+
*/
|
|
22
|
+
declare function adapter(options?: SolasStaticOptions): {
|
|
23
|
+
name: string;
|
|
24
|
+
plugins: PluginOption[];
|
|
25
|
+
prerender: "full";
|
|
26
|
+
};
|
|
27
|
+
export { adapter as default };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as Config from '../config.js';
|
|
4
|
+
import { Features, guards } from '../internal/guards.js';
|
|
5
|
+
const ROOT_FILES = ['sitemap.xml', 'robots.txt'];
|
|
6
|
+
/**
|
|
7
|
+
* Static site adapter for Solas.
|
|
8
|
+
*
|
|
9
|
+
* Sets `prerender: 'full'` so every route is rendered to HTML at build time,
|
|
10
|
+
* then merges Solas's output into a single directory any static host can serve:
|
|
11
|
+
* the client assets, the full prerenders (at their route paths) and the
|
|
12
|
+
* generated `sitemap.xml` / `robots.txt`.
|
|
13
|
+
*
|
|
14
|
+
* It installs the guards with no server features supported, so the build warns
|
|
15
|
+
* when the graph contains server actions or `+endpoint` routes, which a static
|
|
16
|
+
* host cannot run.
|
|
17
|
+
*/
|
|
18
|
+
function adapter(options = {}) {
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
const outDir = options.outDir ?? 'build';
|
|
21
|
+
const plugin = {
|
|
22
|
+
name: 'solas-static',
|
|
23
|
+
enforce: 'post',
|
|
24
|
+
buildApp: {
|
|
25
|
+
order: 'post',
|
|
26
|
+
async handler() {
|
|
27
|
+
// runs after Solas's own `post` buildApp, so `dist/client`,
|
|
28
|
+
// `dist/static`, the sitemap and robots.txt already exist
|
|
29
|
+
const dist = path.join(cwd, Config.OUT_DIR);
|
|
30
|
+
const target = path.resolve(cwd, outDir);
|
|
31
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
32
|
+
for (const dir of ['client', 'static']) {
|
|
33
|
+
const source = path.join(dist, dir);
|
|
34
|
+
if (await exists(source)) {
|
|
35
|
+
await fs.cp(source, target, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const file of ROOT_FILES) {
|
|
39
|
+
const source = path.join(dist, file);
|
|
40
|
+
if (await exists(source)) {
|
|
41
|
+
await fs.copyFile(source, path.join(target, file));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
const plugins = [guards(Features.None), plugin];
|
|
48
|
+
return { name: 'static', plugins, prerender: 'full' };
|
|
49
|
+
}
|
|
50
|
+
async function exists(filePath) {
|
|
51
|
+
try {
|
|
52
|
+
await fs.access(filePath);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export { adapter as default };
|
package/dist/config.d.ts
CHANGED
|
@@ -13,7 +13,8 @@ export declare const PUBLIC_DIR = "public";
|
|
|
13
13
|
export declare const $: unique symbol;
|
|
14
14
|
export declare const REQUEST_META_KEY: string;
|
|
15
15
|
export declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error", "fatal"];
|
|
16
|
-
export declare const PRERENDER_MODES: readonly ["full", "ppr"
|
|
16
|
+
export declare const PRERENDER_MODES: readonly ["full", "ppr"];
|
|
17
|
+
export declare const PRERENDER_OPTIONS: readonly ["full", "ppr", false];
|
|
17
18
|
export declare const TRAILING_SLASH_MODES: readonly ["always", "never", "ignore"];
|
|
18
19
|
export declare const RUNTIME_MANIFEST = "runtime-manifest.json";
|
|
19
20
|
export declare function validate(input: unknown, envUrl?: string): PluginConfig;
|
package/dist/config.js
CHANGED
|
@@ -12,7 +12,8 @@ export const PUBLIC_DIR = 'public';
|
|
|
12
12
|
export const $ = Symbol(SLUG);
|
|
13
13
|
export const REQUEST_META_KEY = `__${SLUG.toUpperCase()}__`;
|
|
14
14
|
export const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'];
|
|
15
|
-
export const PRERENDER_MODES = ['full', 'ppr'
|
|
15
|
+
export const PRERENDER_MODES = ['full', 'ppr'];
|
|
16
|
+
export const PRERENDER_OPTIONS = [...PRERENDER_MODES, false];
|
|
16
17
|
export const TRAILING_SLASH_MODES = ['always', 'never', 'ignore'];
|
|
17
18
|
export const RUNTIME_MANIFEST = 'runtime-manifest.json';
|
|
18
19
|
const CONFIG_KEYS = new Set([
|
|
@@ -122,7 +123,7 @@ export function validate(input, envUrl) {
|
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
if ('prerender' in input && input.prerender !== undefined) {
|
|
125
|
-
if (!new Set(
|
|
126
|
+
if (!new Set(PRERENDER_OPTIONS).has(input.prerender)) {
|
|
126
127
|
errors.push("prerender must be 'full', 'ppr', or false");
|
|
127
128
|
}
|
|
128
129
|
}
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,9 @@ function solas(c) {
|
|
|
37
37
|
const config = {
|
|
38
38
|
...DEFAULT_CONFIG,
|
|
39
39
|
...validatedConfig,
|
|
40
|
+
// a platform's required prerender mode wins: a static host, for example,
|
|
41
|
+
// has no server to render PPR or SSR routes
|
|
42
|
+
...(c?.adapter?.prerender !== undefined ? { prerender: c.adapter.prerender } : {}),
|
|
40
43
|
};
|
|
41
44
|
let resolvedUrl = normaliseOrigin(c?.url) ?? normaliseOrigin(envUrl);
|
|
42
45
|
if (config.logger?.level)
|
|
@@ -31,7 +31,7 @@ export function writeCloudflareEntry() {
|
|
|
31
31
|
return source `
|
|
32
32
|
${AUTOGEN_MSG}
|
|
33
33
|
|
|
34
|
-
import { createWorker } from '${Config.PKG_NAME}/cloudflare'
|
|
34
|
+
import { createWorker } from '${Config.PKG_NAME}/adapters/cloudflare/runtime'
|
|
35
35
|
|
|
36
36
|
import { manifest } from './manifest.js'
|
|
37
37
|
import { importMap } from './maps.js'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build features a platform may not support, as flags so they combine:
|
|
3
|
+
* `Features.Actions | Features.Endpoints`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const Features: {
|
|
6
|
+
readonly None: 0;
|
|
7
|
+
readonly Actions: number;
|
|
8
|
+
readonly Endpoints: number;
|
|
9
|
+
readonly All: number;
|
|
10
|
+
};
|
|
11
|
+
export type Feature = (typeof Features)[keyof typeof Features];
|
|
12
|
+
/**
|
|
13
|
+
* Build guards, installed by an adapter: warns when the build graph uses a
|
|
14
|
+
* feature the adapter does not support. `supports` is a set of `Features` flags
|
|
15
|
+
* (`Features.Actions | Features.Endpoints`), defaulting to `Features.All`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function guards(supports?: Feature): {
|
|
18
|
+
name: string;
|
|
19
|
+
enforce: "pre";
|
|
20
|
+
transform(this: import("rolldown").TransformPluginContext, code: string, id: string): undefined;
|
|
21
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { Logger } from '../utils/logger.js';
|
|
3
|
+
const logger = new Logger();
|
|
4
|
+
/**
|
|
5
|
+
* Build features a platform may not support, as flags so they combine:
|
|
6
|
+
* `Features.Actions | Features.Endpoints`.
|
|
7
|
+
*/
|
|
8
|
+
export const Features = {
|
|
9
|
+
None: 0,
|
|
10
|
+
Actions: 1 << 0,
|
|
11
|
+
Endpoints: 1 << 1,
|
|
12
|
+
All: (1 << 0) | (1 << 1),
|
|
13
|
+
};
|
|
14
|
+
const LABEL = {
|
|
15
|
+
[Features.Actions]: 'a server action',
|
|
16
|
+
[Features.Endpoints]: 'a runtime endpoint',
|
|
17
|
+
};
|
|
18
|
+
const SOURCE = /\.(m?[jt]sx?)$/;
|
|
19
|
+
const ENDPOINT = /\+endpoint\.m?[jt]sx?$/;
|
|
20
|
+
const USE_SERVER = /^\s*['"]use server['"]/m;
|
|
21
|
+
/**
|
|
22
|
+
* Build guards, installed by an adapter: warns when the build graph uses a
|
|
23
|
+
* feature the adapter does not support. `supports` is a set of `Features` flags
|
|
24
|
+
* (`Features.Actions | Features.Endpoints`), defaulting to `Features.All`.
|
|
25
|
+
*/
|
|
26
|
+
export function guards(supports = Features.All) {
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
const warned = new Set();
|
|
29
|
+
return {
|
|
30
|
+
name: 'solas:guards',
|
|
31
|
+
enforce: 'pre',
|
|
32
|
+
transform(code, id) {
|
|
33
|
+
const file = id.split('?')[0];
|
|
34
|
+
if (!path.isAbsolute(file) || !file.startsWith(cwd) || warned.has(file))
|
|
35
|
+
return;
|
|
36
|
+
if (!SOURCE.test(file))
|
|
37
|
+
return;
|
|
38
|
+
let feature;
|
|
39
|
+
if (ENDPOINT.test(file)) {
|
|
40
|
+
feature = Features.Endpoints;
|
|
41
|
+
}
|
|
42
|
+
else if (USE_SERVER.test(code)) {
|
|
43
|
+
feature = Features.Actions;
|
|
44
|
+
}
|
|
45
|
+
if (feature === undefined || (supports & feature) !== 0)
|
|
46
|
+
return;
|
|
47
|
+
warned.add(file);
|
|
48
|
+
logger.warn(`[build] this build uses ${LABEL[feature]} (${path.relative(cwd, file)}), ` +
|
|
49
|
+
`which the adapter does not support`);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -1,2 +1,16 @@
|
|
|
1
|
+
import * as Prerender from './prerender.js';
|
|
1
2
|
export declare function setRscEntry(entry: string): void;
|
|
3
|
+
type App = Parameters<typeof Prerender.run>[0];
|
|
4
|
+
/** Imports the server bundle `postbuild` prerenders with. */
|
|
5
|
+
export type PrerenderImport = () => Promise<{
|
|
6
|
+
default: App;
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Set a wrapper for the import `postbuild` makes to prerender. It receives the
|
|
10
|
+
* plain import and returns one that sets up and tears down platform-specific
|
|
11
|
+
* module resolution around it, for example shimming a runtime module that Node
|
|
12
|
+
* cannot resolve (such as `cloudflare:workers`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function setPrerenderImport(wrap: (next: PrerenderImport) => PrerenderImport): void;
|
|
2
15
|
export declare function postbuild(cwd?: string): Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -15,6 +15,16 @@ let rscEntryOverride;
|
|
|
15
15
|
export function setRscEntry(entry) {
|
|
16
16
|
rscEntryOverride = entry;
|
|
17
17
|
}
|
|
18
|
+
let prerenderImport;
|
|
19
|
+
/**
|
|
20
|
+
* Set a wrapper for the import `postbuild` makes to prerender. It receives the
|
|
21
|
+
* plain import and returns one that sets up and tears down platform-specific
|
|
22
|
+
* module resolution around it, for example shimming a runtime module that Node
|
|
23
|
+
* cannot resolve (such as `cloudflare:workers`).
|
|
24
|
+
*/
|
|
25
|
+
export function setPrerenderImport(wrap) {
|
|
26
|
+
prerenderImport = wrap;
|
|
27
|
+
}
|
|
18
28
|
export async function postbuild(cwd = process.cwd()) {
|
|
19
29
|
const manifestPath = path.join(cwd, Config.GENERATED_DIR, 'build.json');
|
|
20
30
|
let manifest;
|
|
@@ -44,7 +54,10 @@ export async function postbuild(cwd = process.cwd()) {
|
|
|
44
54
|
logger.info('[prerender]', `prerendering ${manifest.prerenderRoutes.length} routes (concurrency: ${concurrency})...`);
|
|
45
55
|
const rscEntry = rscEntryOverride ?? path.join(rscDir, 'index.js');
|
|
46
56
|
rscEntryOverride = undefined;
|
|
47
|
-
const
|
|
57
|
+
const base = () => import(/* @vite-ignore */ rscEntry);
|
|
58
|
+
const load = prerenderImport ? prerenderImport(base) : base;
|
|
59
|
+
prerenderImport = undefined;
|
|
60
|
+
const { default: app } = await load();
|
|
48
61
|
async function enqueueWrite(task) {
|
|
49
62
|
const write = task().finally(() => {
|
|
50
63
|
pendingWrites.delete(write);
|
|
@@ -270,7 +270,7 @@ export async function getStaticFlag(filePath, buildContext) {
|
|
|
270
270
|
* in the route module and calling it to get the list of parameter objects.
|
|
271
271
|
*/
|
|
272
272
|
export async function getStaticParams(filePath, buildContext) {
|
|
273
|
-
const params = await buildContext.exportReader.value(filePath, '
|
|
273
|
+
const params = await buildContext.exportReader.value(filePath, 'staticParams', (v) => typeof v === 'function');
|
|
274
274
|
if (!params)
|
|
275
275
|
return [];
|
|
276
276
|
const resolved = await Promise.resolve().then(() => params());
|
package/dist/types.d.ts
CHANGED
|
@@ -26,10 +26,15 @@ export type HeadersInput = HeaderEntries | ((req: Request) => HeaderEntries | Pr
|
|
|
26
26
|
export type Adapter = {
|
|
27
27
|
name: string;
|
|
28
28
|
plugins: PluginOption[];
|
|
29
|
+
/**
|
|
30
|
+
* Prerender mode the platform requires, which overrides the `solas()`
|
|
31
|
+
* option. A static host has no server, so it needs `'full'`.
|
|
32
|
+
*/
|
|
33
|
+
prerender?: Route.PrerenderMode | false;
|
|
29
34
|
};
|
|
30
35
|
type PluginConfigBase = {
|
|
31
36
|
precompress?: boolean;
|
|
32
|
-
prerender?: Route.
|
|
37
|
+
prerender?: Route.PrerenderMode | false;
|
|
33
38
|
metadata?: Metadata.Item;
|
|
34
39
|
trailingSlash?: (typeof Config.TRAILING_SLASH_MODES)[number];
|
|
35
40
|
trustedOrigins?: readonly string[];
|
|
@@ -83,7 +88,7 @@ export type Segment = {
|
|
|
83
88
|
page?: string | null;
|
|
84
89
|
};
|
|
85
90
|
error?: HttpException | Error;
|
|
86
|
-
prerender: Route.
|
|
91
|
+
prerender: Route.PrerenderMode | false;
|
|
87
92
|
dynamic: boolean;
|
|
88
93
|
wildcard: boolean;
|
|
89
94
|
};
|
|
@@ -143,7 +148,7 @@ export declare namespace Route {
|
|
|
143
148
|
} : {
|
|
144
149
|
params: ParamsOf<TRoute>;
|
|
145
150
|
}) & ErrorPropsOf<TError>;
|
|
146
|
-
export type
|
|
151
|
+
export type PrerenderMode = (typeof Config.PRERENDER_MODES)[number];
|
|
147
152
|
export {};
|
|
148
153
|
}
|
|
149
154
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jk2908/solas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "A Vite + React meta-framework exploring streaming, Server Components, and partial prerendering. Designed for simplicity and lightness",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -69,17 +69,21 @@
|
|
|
69
69
|
"types": "./dist/error-boundary.d.ts",
|
|
70
70
|
"import": "./dist/error-boundary.js"
|
|
71
71
|
},
|
|
72
|
-
"./cloudflare": {
|
|
72
|
+
"./adapters/cloudflare": {
|
|
73
73
|
"types": "./dist/adapters/cloudflare/index.d.ts",
|
|
74
74
|
"import": "./dist/adapters/cloudflare/index.js"
|
|
75
75
|
},
|
|
76
|
-
"./cloudflare/
|
|
77
|
-
"types": "./dist/adapters/cloudflare/
|
|
78
|
-
"import": "./dist/adapters/cloudflare/
|
|
76
|
+
"./adapters/cloudflare/runtime": {
|
|
77
|
+
"types": "./dist/adapters/cloudflare/runtime.d.ts",
|
|
78
|
+
"import": "./dist/adapters/cloudflare/runtime.js"
|
|
79
79
|
},
|
|
80
|
-
"./node": {
|
|
80
|
+
"./adapters/node": {
|
|
81
81
|
"types": "./dist/adapters/node.d.ts",
|
|
82
82
|
"import": "./dist/adapters/node.js"
|
|
83
|
+
},
|
|
84
|
+
"./adapters/static": {
|
|
85
|
+
"types": "./dist/adapters/static.d.ts",
|
|
86
|
+
"import": "./dist/adapters/static.js"
|
|
83
87
|
}
|
|
84
88
|
},
|
|
85
89
|
"scripts": {
|
|
@@ -103,6 +107,7 @@
|
|
|
103
107
|
"path-to-regexp": "^8.3.0"
|
|
104
108
|
},
|
|
105
109
|
"devDependencies": {
|
|
110
|
+
"@cloudflare/workers-types": "^5.20261015.1",
|
|
106
111
|
"@prettier/plugin-oxc": "^0.1.3",
|
|
107
112
|
"@typescript/native-preview": "^7.0.0-dev.20260224.1",
|
|
108
113
|
"@vitejs/plugin-react": "^6.1.2",
|