@jk2908/solas 0.9.1 → 0.10.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 +10 -0
- package/README.md +28 -7
- package/dist/adapters/cloudflare/assets.d.ts +37 -0
- package/dist/adapters/cloudflare/assets.js +71 -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 +4 -3
- package/dist/adapters/cloudflare/worker.d.ts +1 -1
- package/dist/adapters/cloudflare/worker.js +1 -1
- package/dist/adapters/node.d.ts +2 -1
- package/dist/adapters/node.js +2 -1
- package/dist/adapters/static.d.ts +23 -0
- package/dist/adapters/static.js +54 -0
- package/dist/index.js +3 -0
- package/dist/internal/codegen/environments.js +1 -1
- package/dist/types.d.ts +5 -0
- package/package.json +10 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
|
|
3
13
|
## 0.9.1
|
|
4
14
|
|
|
5
15
|
- 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({
|
|
@@ -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({
|
|
@@ -643,18 +643,18 @@ Because workers have no filesystem, the Worker reads its own output through the
|
|
|
643
643
|
`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
644
|
|
|
645
645
|
```ts
|
|
646
|
-
import { createCloudflareAssets } from '@jk2908/solas/cloudflare'
|
|
646
|
+
import { createCloudflareAssets } from '@jk2908/solas/adapters/cloudflare/runtime'
|
|
647
647
|
|
|
648
648
|
const assets = createCloudflareAssets(env)
|
|
649
649
|
```
|
|
650
650
|
|
|
651
651
|
## Node
|
|
652
652
|
|
|
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`:
|
|
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/adapters/node`:
|
|
654
654
|
|
|
655
655
|
```ts
|
|
656
656
|
// server.ts
|
|
657
|
-
import
|
|
657
|
+
import serve from '@jk2908/solas/adapters/node'
|
|
658
658
|
|
|
659
659
|
await serve()
|
|
660
660
|
```
|
|
@@ -686,11 +686,32 @@ import handler from './dist/rsc/index.js'
|
|
|
686
686
|
Deno.serve(request => handler.fetch(request))
|
|
687
687
|
```
|
|
688
688
|
|
|
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`.
|
|
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/adapters/node` as-is, since it implements `node:http`.
|
|
690
|
+
|
|
691
|
+
## Static
|
|
692
|
+
|
|
693
|
+
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.
|
|
694
|
+
|
|
695
|
+
```ts
|
|
696
|
+
// vite.config.ts
|
|
697
|
+
import { defineConfig } from 'vite'
|
|
698
|
+
|
|
699
|
+
import solas from '@jk2908/solas'
|
|
700
|
+
import staticAdapter from '@jk2908/solas/adapters/static'
|
|
701
|
+
import react from '@vitejs/plugin-react'
|
|
702
|
+
|
|
703
|
+
export default defineConfig({
|
|
704
|
+
plugins: [solas({ adapter: staticAdapter() }), react()],
|
|
705
|
+
})
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
`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.
|
|
709
|
+
|
|
710
|
+
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()`.
|
|
690
711
|
|
|
691
712
|
## Runtime
|
|
692
713
|
|
|
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.
|
|
714
|
+
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
715
|
|
|
695
716
|
## Scripts
|
|
696
717
|
|
|
@@ -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
|
+
}
|
|
@@ -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 {};
|
|
@@ -4,7 +4,7 @@ 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
6
|
import { setRscEntry } from '../../internal/postbuild.js';
|
|
7
|
-
import { NAMESPACE_PREFIX } from './
|
|
7
|
+
import { NAMESPACE_PREFIX } from './assets.js';
|
|
8
8
|
const VIRTUAL_ENTRY = 'virtual:solas-cloudflare-entry';
|
|
9
9
|
/**
|
|
10
10
|
* Solas dependencies that are imported at request time from the Worker
|
|
@@ -103,7 +103,7 @@ function withAdapterConfig(config) {
|
|
|
103
103
|
* asset-backed store, points Solas's prerender step at the Cloudflare Worker
|
|
104
104
|
* bundle, and copies prerender artifacts into the asset output.
|
|
105
105
|
*/
|
|
106
|
-
export function
|
|
106
|
+
export function adapter(options = {}) {
|
|
107
107
|
const cwd = process.cwd();
|
|
108
108
|
// back the virtual entry id with a path inside the generated dir so the
|
|
109
109
|
// generated entry's relative imports (`./manifest.js`, ...) resolve
|
|
@@ -163,5 +163,6 @@ export function cloudflare(options = {}) {
|
|
|
163
163
|
},
|
|
164
164
|
},
|
|
165
165
|
};
|
|
166
|
-
|
|
166
|
+
const plugins = [adapter, ...cfPlugins];
|
|
167
|
+
return { name: 'cloudflare', plugins };
|
|
167
168
|
}
|
|
@@ -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
|
*
|
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,23 @@
|
|
|
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
|
+
declare function adapter(options?: SolasStaticOptions): {
|
|
19
|
+
name: string;
|
|
20
|
+
plugins: PluginOption[];
|
|
21
|
+
prerender: "full";
|
|
22
|
+
};
|
|
23
|
+
export { adapter as default };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as Config from '../config.js';
|
|
4
|
+
const ROOT_FILES = ['sitemap.xml', 'robots.txt'];
|
|
5
|
+
/**
|
|
6
|
+
* Static site adapter for Solas.
|
|
7
|
+
*
|
|
8
|
+
* Sets `prerender: 'full'` so every route is rendered to HTML at build time,
|
|
9
|
+
* then merges Solas's output into a single directory any static host can serve:
|
|
10
|
+
* the client assets, the full prerenders (at their route paths) and the
|
|
11
|
+
* generated `sitemap.xml` / `robots.txt`.
|
|
12
|
+
*/
|
|
13
|
+
function adapter(options = {}) {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const outDir = options.outDir ?? 'build';
|
|
16
|
+
const plugin = {
|
|
17
|
+
name: 'solas-static',
|
|
18
|
+
enforce: 'post',
|
|
19
|
+
buildApp: {
|
|
20
|
+
order: 'post',
|
|
21
|
+
async handler() {
|
|
22
|
+
// runs after Solas's own `post` buildApp, so `dist/client`,
|
|
23
|
+
// `dist/static`, the sitemap and robots.txt already exist
|
|
24
|
+
const dist = path.join(cwd, Config.OUT_DIR);
|
|
25
|
+
const target = path.resolve(cwd, outDir);
|
|
26
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
27
|
+
for (const dir of ['client', 'static']) {
|
|
28
|
+
const source = path.join(dist, dir);
|
|
29
|
+
if (await exists(source)) {
|
|
30
|
+
await fs.cp(source, target, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (const file of ROOT_FILES) {
|
|
34
|
+
const source = path.join(dist, file);
|
|
35
|
+
if (await exists(source)) {
|
|
36
|
+
await fs.copyFile(source, path.join(target, file));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const plugins = [plugin];
|
|
43
|
+
return { name: 'static', plugins, prerender: 'full' };
|
|
44
|
+
}
|
|
45
|
+
async function exists(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(filePath);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export { adapter as default };
|
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'
|
package/dist/types.d.ts
CHANGED
|
@@ -26,6 +26,11 @@ 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.Prerender;
|
|
29
34
|
};
|
|
30
35
|
type PluginConfigBase = {
|
|
31
36
|
precompress?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jk2908/solas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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": {
|