@jk2908/solas 0.5.4 → 0.7.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 +30 -0
- package/README.md +34 -94
- package/dist/config.d.ts +24 -0
- package/dist/config.js +143 -0
- package/dist/events.d.ts +4 -0
- package/dist/events.js +5 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +59 -41
- package/dist/internal/boundary-error.d.ts +4 -0
- package/dist/internal/boundary-error.js +8 -0
- package/dist/internal/browser-router/history.d.ts +1 -1
- package/dist/internal/browser-router/link.d.ts +1 -1
- package/dist/internal/browser-router/link.js +1 -1
- package/dist/internal/browser-router/router.d.ts +2 -2
- package/dist/internal/browser-router/router.js +5 -5
- package/dist/internal/browser-router/shared.d.ts +182 -184
- package/dist/internal/browser-router/shared.js +49 -56
- package/dist/internal/browser-router/use-router.d.ts +1 -1
- package/dist/internal/browser-router/use-search-params.js +3 -3
- package/dist/internal/build.d.ts +108 -110
- package/dist/internal/build.js +569 -581
- package/dist/internal/codegen/config.js +4 -5
- package/dist/internal/codegen/environments.d.ts +1 -1
- package/dist/internal/codegen/environments.js +6 -9
- package/dist/internal/codegen/format.d.ts +6 -0
- package/dist/internal/codegen/format.js +24 -0
- package/dist/internal/codegen/manifest.js +2 -2
- package/dist/internal/codegen/maps.d.ts +1 -1
- package/dist/internal/codegen/maps.js +2 -2
- package/dist/internal/codegen/types.js +4 -4
- package/dist/internal/codegen/utils.js +2 -2
- package/dist/internal/env/request-context.js +1 -1
- package/dist/internal/env/rsc.d.ts +3 -3
- package/dist/internal/env/rsc.js +26 -26
- package/dist/internal/env/ssr.js +4 -4
- package/dist/internal/http-router/router.js +8 -8
- package/dist/internal/metadata.d.ts +1 -1
- package/dist/internal/metadata.js +1 -1
- package/dist/internal/navigation/http-exception-boundary.d.ts +1 -1
- package/dist/internal/navigation/redirect-boundary.d.ts +1 -1
- package/dist/internal/navigation/redirect.js +6 -6
- package/dist/internal/postbuild.js +18 -17
- package/dist/internal/prerender.d.ts +130 -138
- package/dist/internal/prerender.js +326 -354
- package/dist/internal/public-files.js +2 -4
- package/dist/internal/render/head.js +2 -2
- package/dist/internal/resolver.js +1 -1
- package/dist/internal/runtimes/runtime.d.ts +7 -21
- package/dist/internal/runtimes/runtime.js +24 -19
- package/dist/internal/server/actions.js +2 -2
- package/dist/internal/ui/error-boundary.d.ts +1 -1
- package/dist/manifest.d.ts +7 -0
- package/dist/manifest.js +85 -0
- package/dist/prerender.d.ts +1 -1
- package/dist/prerender.js +1 -1
- package/dist/solas.d.ts +2 -46
- package/dist/solas.js +6 -266
- package/dist/types.d.ts +14 -25
- package/dist/types.js +1 -1
- package/dist/utils/base-path.d.ts +12 -14
- package/dist/utils/base-path.js +75 -82
- package/dist/utils/compress.d.ts +9 -11
- package/dist/utils/compress.js +59 -64
- package/dist/utils/context.d.ts +4 -6
- package/dist/utils/context.js +17 -22
- package/dist/utils/logger.js +2 -2
- package/dist/utils/time.d.ts +2 -4
- package/dist/utils/time.js +23 -29
- package/package.json +18 -8
- package/dist/internal/runtimes/bun.d.ts +0 -9
- package/dist/internal/runtimes/bun.js +0 -33
- package/dist/internal/runtimes/create.d.ts +0 -4
- package/dist/internal/runtimes/create.js +0 -9
- package/dist/internal/runtimes/node.d.ts +0 -9
- package/dist/internal/runtimes/node.js +0 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- Removed the `runtime` config option and the Bun/Node runtime adapters. Solas now uses the Node standard library directly, which Bun runs natively, so there is no runtime selection to configure. Generated entries no longer call `createRuntime`.
|
|
8
|
+
|
|
9
|
+
## 0.6.0
|
|
10
|
+
|
|
11
|
+
### Breaking
|
|
12
|
+
|
|
13
|
+
- Removed the `Solas` runtime namespace exports (`Solas.Config`, `Solas.Runtime`, `Solas.Events`) from the `@jk2908/solas` entry. Runtime constants now live in dedicated internal modules, and generated code no longer reaches through the namespace.
|
|
14
|
+
- Removed the `@jk2908/solas/$` export path. Generated environment entries now import directly from `@jk2908/solas/env/rsc`.
|
|
15
|
+
- Removed the `port` config option from `solas()`. The development port now comes from Vite's `server.port`. `url` remains available as an optional public origin and now resolves from the `url` option → `VITE_APP_URL` → the Vite dev server `host`/`port`.
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- Generated `.solas` files are formatted with oxfmt before writing, and stale generated files are pruned when the codegen stops emitting them.
|
|
20
|
+
- Added a Vitest test suite covering unit, integration, and browser (React Testing Library) tests.
|
|
21
|
+
|
|
22
|
+
### Fixes
|
|
23
|
+
|
|
24
|
+
- Generated `entry.rsc.tsx` now emits valid code (`loadManifest` from `env/rsc`) instead of referencing an undefined `Config` import.
|
|
25
|
+
- Fixed the release pipeline by committing the postbuild measurement script.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Pinned `@vitejs/plugin-rsc` to `0.5.34`.
|
|
30
|
+
- Removed the Playwright e2e suite, fixtures, and scripts for now; unit, integration, and browser tests run through Vitest.
|
|
31
|
+
- Declared `engines.node` (`^20.19.0 || >=22.12.0`).
|
|
32
|
+
|
|
3
33
|
## 0.5.4 - 2026-06-08
|
|
4
34
|
|
|
5
35
|
- Add a BrowserRouterHistory API to sync app back/forward navigation with browser navigation.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Solas is a minimal React meta-framework powered by Vite, created for experimenting with routing, streaming, and prerendering with React Server Components.
|
|
4
4
|
|
|
5
|
-
Solas is experimental and
|
|
5
|
+
Solas is experimental and under active development, so expect rough edges.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -201,118 +201,45 @@ That keeps your route params aligned across links, imperative navigation, metada
|
|
|
201
201
|
|
|
202
202
|
All Solas options are passed to `solas()` inside `defineConfig`.
|
|
203
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
|
-
|
|
272
204
|
### `url`
|
|
273
205
|
|
|
274
|
-
`url`
|
|
206
|
+
Use `url` to set the public origin for your app. It is optional.
|
|
275
207
|
|
|
276
|
-
Solas resolves
|
|
208
|
+
Solas resolves the origin in this order:
|
|
277
209
|
|
|
278
210
|
- the `url` option passed to `solas()`
|
|
279
211
|
- `VITE_APP_URL`
|
|
212
|
+
- the Vite dev server `host` and `port` (defaults to `http://localhost:8787`)
|
|
280
213
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
In practice, you only need `url` if your app reads `import.meta.env.VITE_APP_URL` or your prerendered output needs a real public origin.
|
|
214
|
+
The resolved origin is exposed to your app as `import.meta.env.VITE_APP_URL` and is used for build-time prerender requests and `sitemap` URLs. When `url` is set, it is also used as a trusted origin for CSRF.
|
|
284
215
|
|
|
285
|
-
|
|
216
|
+
You usually only need `url` in production, where the Vite dev server origin is meaningless. For example, to generate a `sitemap.xml` with real URLs:
|
|
286
217
|
|
|
287
218
|
```ts
|
|
288
219
|
export default defineConfig(({ mode }) => ({
|
|
289
220
|
plugins: [
|
|
290
221
|
solas({
|
|
291
|
-
url: mode === 'production' ? 'https://example.com' :
|
|
222
|
+
url: mode === 'production' ? 'https://example.com' : undefined,
|
|
223
|
+
sitemap: true,
|
|
292
224
|
}),
|
|
293
225
|
],
|
|
294
226
|
}))
|
|
295
227
|
```
|
|
296
228
|
|
|
297
|
-
If you prefer an environment variable, set
|
|
229
|
+
If you prefer an environment variable, set `VITE_APP_URL` instead:
|
|
298
230
|
|
|
299
231
|
```sh
|
|
300
232
|
VITE_APP_URL=https://example.com
|
|
301
233
|
```
|
|
302
234
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
Use `port` to change the development server port.
|
|
306
|
-
|
|
307
|
-
Default: `8787`
|
|
235
|
+
Set the development port with Vite's `server.port`:
|
|
308
236
|
|
|
309
237
|
```ts
|
|
310
238
|
export default defineConfig({
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
],
|
|
239
|
+
server: {
|
|
240
|
+
port: 4000,
|
|
241
|
+
},
|
|
242
|
+
plugins: [solas(), react()],
|
|
316
243
|
})
|
|
317
244
|
```
|
|
318
245
|
|
|
@@ -510,13 +437,13 @@ Use `sitemap` to generate a `sitemap.xml` at build time.
|
|
|
510
437
|
|
|
511
438
|
Default: `false`
|
|
512
439
|
|
|
513
|
-
When enabled, Solas writes a sitemap containing all routes with deterministic URLs: static routes, prerendered routes, and dynamic routes resolved via `params`. The origin for each URL comes from `
|
|
440
|
+
When enabled, Solas writes a sitemap containing all routes with deterministic URLs: static routes, prerendered routes, and dynamic routes resolved via `params`. The origin for each URL comes from the resolved origin (see Config > `url`).
|
|
514
441
|
|
|
515
442
|
```ts
|
|
516
443
|
export default defineConfig(({ mode }) => ({
|
|
517
444
|
plugins: [
|
|
518
445
|
solas({
|
|
519
|
-
url: mode === 'production' ? 'https://example.com' :
|
|
446
|
+
url: mode === 'production' ? 'https://example.com' : undefined,
|
|
520
447
|
sitemap: true,
|
|
521
448
|
}),
|
|
522
449
|
],
|
|
@@ -528,10 +455,9 @@ Routes with dynamic segments (`[id]`) or catch-all segments (`[...param]`) are o
|
|
|
528
455
|
To add routes that Solas cannot discover automatically (for example, catch-all routes backed by a CMS), pass an object with a `routes` function. The function receives the auto-discovered routes and returns the final list:
|
|
529
456
|
|
|
530
457
|
```ts
|
|
531
|
-
export default defineConfig(
|
|
458
|
+
export default defineConfig({
|
|
532
459
|
plugins: [
|
|
533
460
|
solas({
|
|
534
|
-
url: mode === 'production' ? 'https://example.com' : 'http://localhost:8787',
|
|
535
461
|
sitemap: {
|
|
536
462
|
async routes(existing) {
|
|
537
463
|
const posts = await getPosts()
|
|
@@ -540,7 +466,7 @@ export default defineConfig(({ mode }) => ({
|
|
|
540
466
|
},
|
|
541
467
|
}),
|
|
542
468
|
],
|
|
543
|
-
})
|
|
469
|
+
})
|
|
544
470
|
```
|
|
545
471
|
|
|
546
472
|
The `routes` function can be async. The callback also lets you filter routes:
|
|
@@ -581,6 +507,10 @@ export default defineConfig({
|
|
|
581
507
|
})
|
|
582
508
|
```
|
|
583
509
|
|
|
510
|
+
## Runtime
|
|
511
|
+
|
|
512
|
+
This is not a deployment or packaging adapter. Platform adapters are not available yet.
|
|
513
|
+
|
|
584
514
|
## Scripts
|
|
585
515
|
|
|
586
516
|
Add scripts to your app:
|
|
@@ -595,7 +525,17 @@ Add scripts to your app:
|
|
|
595
525
|
}
|
|
596
526
|
```
|
|
597
527
|
|
|
598
|
-
|
|
528
|
+
If you run Vite through Bun, use the Bun-backed commands instead:
|
|
529
|
+
|
|
530
|
+
```json
|
|
531
|
+
{
|
|
532
|
+
"scripts": {
|
|
533
|
+
"dev": "bunx --bun vite dev",
|
|
534
|
+
"build": "bunx --bun vite build",
|
|
535
|
+
"preview": "bunx --bun vite preview"
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
```
|
|
599
539
|
|
|
600
540
|
## Commands
|
|
601
541
|
|
|
@@ -619,7 +559,7 @@ When available, Solas checks browser provenance using:
|
|
|
619
559
|
- `Origin`
|
|
620
560
|
- `Referer`
|
|
621
561
|
|
|
622
|
-
Solas also considers the effective request origin when your app is behind a proxy by using `X-Forwarded-Host
|
|
562
|
+
Solas also considers the effective request origin when your app is behind a proxy by using `X-Forwarded-Host` and `X-Forwarded-Proto`.
|
|
623
563
|
|
|
624
564
|
If you need to allow a trusted third-party browser POST source, configure it explicitly:
|
|
625
565
|
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { PluginConfig } from './types.js';
|
|
2
|
+
export declare const NAME = "Solas";
|
|
3
|
+
export declare const SLUG: string;
|
|
4
|
+
export declare const PKG_NAME: string;
|
|
5
|
+
export declare const OUT_DIR = "dist";
|
|
6
|
+
export declare const APP_DIR = "app";
|
|
7
|
+
export declare const GENERATED_DIR: string;
|
|
8
|
+
export declare const ENTRY_RSC = "entry.rsc.tsx";
|
|
9
|
+
export declare const ENTRY_SSR = "entry.ssr.tsx";
|
|
10
|
+
export declare const ENTRY_BROWSER = "entry.browser.tsx";
|
|
11
|
+
export declare const ASSETS_DIR: string;
|
|
12
|
+
export declare const PUBLIC_DIR = "public";
|
|
13
|
+
export declare const $: unique symbol;
|
|
14
|
+
export declare const REQUEST_META_KEY: string;
|
|
15
|
+
export declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error", "fatal"];
|
|
16
|
+
export declare const PRERENDER_MODES: readonly ["full", "ppr", false];
|
|
17
|
+
export declare const TRAILING_SLASH_MODES: readonly ["always", "never", "ignore"];
|
|
18
|
+
export declare const RUNTIME_MANIFEST = "runtime-manifest.json";
|
|
19
|
+
/**
|
|
20
|
+
* Validate the plugin configuration object, throwing an error if invalid
|
|
21
|
+
* @param input - the unvalidated configuration object
|
|
22
|
+
* @return the typed and validated configuration object
|
|
23
|
+
*/
|
|
24
|
+
export declare function validate(input: unknown): PluginConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export const NAME = 'Solas';
|
|
2
|
+
export const SLUG = NAME.toLowerCase();
|
|
3
|
+
export const PKG_NAME = `@jk2908/${SLUG}`;
|
|
4
|
+
export const OUT_DIR = 'dist';
|
|
5
|
+
export const APP_DIR = 'app';
|
|
6
|
+
export const GENERATED_DIR = `.${SLUG}`;
|
|
7
|
+
export const ENTRY_RSC = 'entry.rsc.tsx';
|
|
8
|
+
export const ENTRY_SSR = 'entry.ssr.tsx';
|
|
9
|
+
export const ENTRY_BROWSER = 'entry.browser.tsx';
|
|
10
|
+
export const ASSETS_DIR = `_${SLUG}`;
|
|
11
|
+
export const PUBLIC_DIR = 'public';
|
|
12
|
+
export const $ = Symbol(SLUG);
|
|
13
|
+
export const REQUEST_META_KEY = `__${SLUG.toUpperCase()}__`;
|
|
14
|
+
export const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'];
|
|
15
|
+
export const PRERENDER_MODES = ['full', 'ppr', false];
|
|
16
|
+
export const TRAILING_SLASH_MODES = ['always', 'never', 'ignore'];
|
|
17
|
+
export const RUNTIME_MANIFEST = 'runtime-manifest.json';
|
|
18
|
+
const CONFIG_KEYS = new Set([
|
|
19
|
+
'logger',
|
|
20
|
+
'metadata',
|
|
21
|
+
'precompress',
|
|
22
|
+
'prerender',
|
|
23
|
+
'sitemap',
|
|
24
|
+
'trustedOrigins',
|
|
25
|
+
'trailingSlash',
|
|
26
|
+
'url',
|
|
27
|
+
]);
|
|
28
|
+
const LOGGER_KEYS = new Set(['level']);
|
|
29
|
+
/**
|
|
30
|
+
* Validate the plugin configuration object, throwing an error if invalid
|
|
31
|
+
* @param input - the unvalidated configuration object
|
|
32
|
+
* @return the typed and validated configuration object
|
|
33
|
+
*/
|
|
34
|
+
export function validate(input) {
|
|
35
|
+
if (input === undefined)
|
|
36
|
+
return {};
|
|
37
|
+
const errors = [];
|
|
38
|
+
if (!isRecord(input)) {
|
|
39
|
+
throw new Error(`[${NAME}] Invalid config:\n- Expected plugin config to be an object`);
|
|
40
|
+
}
|
|
41
|
+
for (const key of Object.keys(input)) {
|
|
42
|
+
if (!CONFIG_KEYS.has(key)) {
|
|
43
|
+
errors.push(`Unknown config key: ${key}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if ('url' in input && input.url !== undefined) {
|
|
47
|
+
if (typeof input.url !== 'string') {
|
|
48
|
+
errors.push('url must be a string');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
try {
|
|
52
|
+
const url = new URL(input.url);
|
|
53
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
54
|
+
errors.push('url must use http:// or https://');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
errors.push('url must be a valid URL');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if ('precompress' in input && input.precompress !== undefined) {
|
|
63
|
+
if (typeof input.precompress !== 'boolean') {
|
|
64
|
+
errors.push('precompress must be a boolean');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if ('trustedOrigins' in input && input.trustedOrigins !== undefined) {
|
|
68
|
+
if (!Array.isArray(input.trustedOrigins)) {
|
|
69
|
+
errors.push('trustedOrigins must be an array of origins');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
for (const [index, value] of input.trustedOrigins.entries()) {
|
|
73
|
+
if (typeof value !== 'string') {
|
|
74
|
+
errors.push(`trustedOrigins[${index}] must be a string`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const url = new URL(value);
|
|
79
|
+
const canonical = value.replace(/\/$/, '');
|
|
80
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
81
|
+
errors.push(`trustedOrigins[${index}] must use http:// or https://`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (canonical !== url.origin) {
|
|
85
|
+
errors.push(`trustedOrigins[${index}] must be an origin without a path, query, or hash`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
errors.push(`trustedOrigins[${index}] must be a valid URL origin`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if ('sitemap' in input && input.sitemap !== undefined && input.sitemap !== false) {
|
|
95
|
+
if (typeof input.sitemap !== 'boolean' && typeof input.sitemap !== 'object') {
|
|
96
|
+
errors.push('sitemap must be a boolean or an object with a routes function');
|
|
97
|
+
}
|
|
98
|
+
if (typeof input.sitemap === 'object' &&
|
|
99
|
+
input.sitemap !== null &&
|
|
100
|
+
typeof input.sitemap.routes !== 'function') {
|
|
101
|
+
errors.push('sitemap.routes must be a function');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if ('prerender' in input && input.prerender !== undefined) {
|
|
105
|
+
if (!new Set(PRERENDER_MODES).has(input.prerender)) {
|
|
106
|
+
errors.push("prerender must be 'full', 'ppr', or false");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if ('trailingSlash' in input && input.trailingSlash !== undefined) {
|
|
110
|
+
if (typeof input.trailingSlash !== 'string' ||
|
|
111
|
+
!new Set(TRAILING_SLASH_MODES).has(input.trailingSlash)) {
|
|
112
|
+
errors.push("trailingSlash must be 'always', 'never', or 'ignore'");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if ('metadata' in input && input.metadata !== undefined && !isRecord(input.metadata)) {
|
|
116
|
+
errors.push('metadata must be an object when provided');
|
|
117
|
+
}
|
|
118
|
+
if ('logger' in input && input.logger !== undefined) {
|
|
119
|
+
if (!isRecord(input.logger)) {
|
|
120
|
+
errors.push('logger must be an object when provided');
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
for (const key of Object.keys(input.logger)) {
|
|
124
|
+
if (!LOGGER_KEYS.has(key)) {
|
|
125
|
+
errors.push(`Unknown logger key: ${key}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if ('level' in input.logger && input.logger.level !== undefined) {
|
|
129
|
+
if (typeof input.logger.level !== 'string' ||
|
|
130
|
+
!new Set(LOG_LEVELS).has(input.logger.level)) {
|
|
131
|
+
errors.push('logger.level must be one of: debug, info, warn, error, fatal');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (errors.length > 0) {
|
|
137
|
+
throw new Error(`[${NAME}] Invalid config:\n- ${errors.join('\n- ')}`);
|
|
138
|
+
}
|
|
139
|
+
return input;
|
|
140
|
+
}
|
|
141
|
+
function isRecord(value) {
|
|
142
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
143
|
+
}
|
package/dist/events.d.ts
ADDED
package/dist/events.js
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,5 @@ import { type PluginOption } from 'vite';
|
|
|
2
2
|
import type { PluginConfig } from './types.js';
|
|
3
3
|
declare function solas(c?: PluginConfig): PluginOption[];
|
|
4
4
|
export default solas;
|
|
5
|
-
export
|
|
6
|
-
export { Solas } from './solas.js';
|
|
5
|
+
export * as Solas from './solas.js';
|
|
7
6
|
export type * from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -5,36 +5,32 @@ import { createServer, loadConfigFromFile, } from 'vite';
|
|
|
5
5
|
import rsc from '@vitejs/plugin-rsc';
|
|
6
6
|
import { ExportReader } from './utils/export-reader.js';
|
|
7
7
|
import { Logger } from './utils/logger.js';
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
8
|
+
import { debounce } from './utils/time.js';
|
|
9
|
+
import * as Config from './config.js';
|
|
10
|
+
import * as Build from './internal/build.js';
|
|
10
11
|
import { writeConfig } from './internal/codegen/config.js';
|
|
11
12
|
import { writeBrowserEntry, writeRSCEntry, writeSSREntry, } from './internal/codegen/environments.js';
|
|
13
|
+
import { format as formatSource } from './internal/codegen/format.js';
|
|
12
14
|
import { writeManifest } from './internal/codegen/manifest.js';
|
|
13
15
|
import { writeMaps } from './internal/codegen/maps.js';
|
|
14
16
|
import { writeTypes } from './internal/codegen/types.js';
|
|
15
17
|
import { postbuild } from './internal/postbuild.js';
|
|
16
18
|
import { collect as collectPublicFiles } from './internal/public-files.js';
|
|
17
|
-
import { createRuntime } from './internal/runtimes/create.js';
|
|
18
19
|
import { Runtime } from './internal/runtimes/runtime.js';
|
|
19
|
-
import { Solas } from './solas.js';
|
|
20
20
|
const DEFAULT_CONFIG = {
|
|
21
|
-
runtime: 'auto',
|
|
22
21
|
precompress: false,
|
|
23
22
|
prerender: false,
|
|
24
23
|
trustedOrigins: [],
|
|
25
24
|
trailingSlash: 'never',
|
|
26
25
|
};
|
|
27
26
|
function solas(c) {
|
|
28
|
-
const validatedConfig =
|
|
29
|
-
...c,
|
|
30
|
-
url: c?.url ?? process.env.VITE_APP_URL?.toString(),
|
|
31
|
-
});
|
|
27
|
+
const validatedConfig = Config.validate(c);
|
|
32
28
|
const config = {
|
|
33
29
|
...DEFAULT_CONFIG,
|
|
34
30
|
...validatedConfig,
|
|
35
|
-
runtime: validatedConfig.runtime ?? DEFAULT_CONFIG.runtime,
|
|
36
31
|
};
|
|
37
|
-
|
|
32
|
+
const envUrl = process.env.VITE_APP_URL?.toString();
|
|
33
|
+
let resolvedUrl = c?.url ?? (envUrl && envUrl.length > 0 ? envUrl : undefined);
|
|
38
34
|
if (config.logger?.level)
|
|
39
35
|
Logger.defaultLevel = config.logger.level;
|
|
40
36
|
const logger = new Logger();
|
|
@@ -93,8 +89,8 @@ function solas(c) {
|
|
|
93
89
|
}
|
|
94
90
|
async function build() {
|
|
95
91
|
const cwd = process.cwd();
|
|
96
|
-
const routesDir = path.join(cwd,
|
|
97
|
-
const generatedDir = path.join(cwd,
|
|
92
|
+
const routesDir = path.join(cwd, Config.APP_DIR);
|
|
93
|
+
const generatedDir = path.join(cwd, Config.GENERATED_DIR);
|
|
98
94
|
await Promise.all([
|
|
99
95
|
fs.mkdir(routesDir, { recursive: true }),
|
|
100
96
|
fs.mkdir(generatedDir, { recursive: true }),
|
|
@@ -107,12 +103,27 @@ function solas(c) {
|
|
|
107
103
|
['config.ts', writeConfig(config)],
|
|
108
104
|
['manifest.ts', writeManifest(manifest)],
|
|
109
105
|
['maps.ts', writeMaps(imports, modules)],
|
|
110
|
-
[`${
|
|
111
|
-
[
|
|
112
|
-
[
|
|
113
|
-
[
|
|
106
|
+
[`${Config.SLUG}.d.ts`, writeTypes(manifest)],
|
|
107
|
+
[Config.ENTRY_RSC, writeRSCEntry(config)],
|
|
108
|
+
[Config.ENTRY_SSR, writeSSREntry()],
|
|
109
|
+
[Config.ENTRY_BROWSER, writeBrowserEntry()],
|
|
114
110
|
];
|
|
115
|
-
|
|
111
|
+
// remove generated files no longer emitted by the current codegen, so
|
|
112
|
+
// old artifacts don't linger and break module resolution. keep
|
|
113
|
+
// closeBundle's build.json, which lives in the same directory
|
|
114
|
+
const currentFiles = new Set(files.map(([file]) => file));
|
|
115
|
+
const existing = await fs
|
|
116
|
+
.readdir(generatedDir, { withFileTypes: true })
|
|
117
|
+
.catch(() => []);
|
|
118
|
+
await Promise.all(existing
|
|
119
|
+
.filter(entry => entry.isFile() &&
|
|
120
|
+
entry.name !== 'build.json' &&
|
|
121
|
+
!currentFiles.has(entry.name))
|
|
122
|
+
.map(entry => fs.rm(path.join(generatedDir, entry.name), { force: true })));
|
|
123
|
+
const writes = await Promise.all(files.map(async ([file, content]) => {
|
|
124
|
+
const formatted = await formatSource(file, content);
|
|
125
|
+
return maybeWrite(path.join(generatedDir, file), formatted);
|
|
126
|
+
}));
|
|
116
127
|
const changed = writes.filter(n => n !== null);
|
|
117
128
|
// early return if nothing has changed
|
|
118
129
|
if (!changed.length)
|
|
@@ -125,7 +136,7 @@ function solas(c) {
|
|
|
125
136
|
// normalise all watcher paths to forward slashes so path checks behave the
|
|
126
137
|
// same on Windows and POSIX
|
|
127
138
|
const WATCH_CWD = process.cwd().replace(/\\/g, '/');
|
|
128
|
-
const WATCH_APP_ROOT = `${WATCH_CWD}/${
|
|
139
|
+
const WATCH_APP_ROOT = `${WATCH_CWD}/${Config.APP_DIR}/`;
|
|
129
140
|
// convert watcher paths to a consistent slash format before comparing them
|
|
130
141
|
const normaliseWatchPath = (p) => p.replace(/\\/g, '/');
|
|
131
142
|
// resolve relative watcher paths against the project root so prefix checks are reliable
|
|
@@ -136,7 +147,7 @@ function solas(c) {
|
|
|
136
147
|
// edits needing special treatment because verb exports can change in-place
|
|
137
148
|
const routeFile = /\/\+(layout|page|401|403|404|500|loading|middleware|endpoint)\.(t|j)sx?$/;
|
|
138
149
|
const endpointFile = /\/\+endpoint\.(t|j)sx?$/;
|
|
139
|
-
const rebuild =
|
|
150
|
+
const rebuild = debounce((event, p) => {
|
|
140
151
|
const queue = () => {
|
|
141
152
|
void (async () => {
|
|
142
153
|
// collapse bursts of file events into one active rebuild plus a single
|
|
@@ -197,9 +208,7 @@ function solas(c) {
|
|
|
197
208
|
return [];
|
|
198
209
|
if (Array.isArray(plugin))
|
|
199
210
|
return plugin.flatMap(flatten);
|
|
200
|
-
if (typeof plugin === 'object' &&
|
|
201
|
-
'name' in plugin &&
|
|
202
|
-
plugin.name === Solas.Config.NAME) {
|
|
211
|
+
if (typeof plugin === 'object' && 'name' in plugin && plugin.name === Config.NAME) {
|
|
203
212
|
return [];
|
|
204
213
|
}
|
|
205
214
|
return [plugin];
|
|
@@ -219,26 +228,35 @@ function solas(c) {
|
|
|
219
228
|
return utilityServer;
|
|
220
229
|
}
|
|
221
230
|
const plugin = {
|
|
222
|
-
name:
|
|
231
|
+
name: Config.NAME,
|
|
223
232
|
async config(viteConfig) {
|
|
224
233
|
const pkg = JSON.parse(fsSync.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
225
234
|
if (typeof pkg.name !== 'string' || pkg.name.length === 0) {
|
|
226
|
-
throw new Error(`Missing ${
|
|
235
|
+
throw new Error(`Missing ${Config.NAME} package name`);
|
|
227
236
|
}
|
|
228
237
|
if (typeof pkg.version !== 'string' || pkg.version.length === 0) {
|
|
229
|
-
throw new Error(`Missing ${
|
|
238
|
+
throw new Error(`Missing ${Config.NAME} package version`);
|
|
230
239
|
}
|
|
231
240
|
viteConfig.build ??= {};
|
|
232
|
-
viteConfig.build.outDir =
|
|
241
|
+
viteConfig.build.outDir = Config.OUT_DIR;
|
|
233
242
|
// keep framework files under one reserved url prefix
|
|
234
|
-
viteConfig.build.assetsDir =
|
|
243
|
+
viteConfig.build.assetsDir = Config.ASSETS_DIR;
|
|
235
244
|
viteConfig.build.emptyOutDir = true;
|
|
236
245
|
// let users move the source public folder if they want
|
|
237
|
-
viteConfig.publicDir ??=
|
|
246
|
+
viteConfig.publicDir ??= Config.PUBLIC_DIR;
|
|
238
247
|
viteConfig.server ??= {};
|
|
239
|
-
viteConfig.server.port
|
|
248
|
+
viteConfig.server.port ??= 8787;
|
|
249
|
+
// derive the public url from vite's dev server settings when no
|
|
250
|
+
// origin was set explicitly via the url option or VITE_APP_URL
|
|
251
|
+
if (resolvedUrl === undefined) {
|
|
252
|
+
const { host, port } = viteConfig.server;
|
|
253
|
+
const hostname = typeof host === 'string' && host.length > 0 && host !== '0.0.0.0'
|
|
254
|
+
? host
|
|
255
|
+
: 'localhost';
|
|
256
|
+
resolvedUrl = `http://${hostname}:${port}`;
|
|
257
|
+
}
|
|
240
258
|
viteConfig.define ??= {};
|
|
241
|
-
viteConfig.define['import.meta.env.VITE_APP_URL'] = JSON.stringify(
|
|
259
|
+
viteConfig.define['import.meta.env.VITE_APP_URL'] = JSON.stringify(resolvedUrl);
|
|
242
260
|
viteConfig.define['import.meta.env.SOLAS_VERSION'] = JSON.stringify(pkg.version);
|
|
243
261
|
viteConfig.optimizeDeps ??= {};
|
|
244
262
|
viteConfig.optimizeDeps.exclude = [
|
|
@@ -255,12 +273,12 @@ function solas(c) {
|
|
|
255
273
|
...viteConfig.resolve.alias,
|
|
256
274
|
{
|
|
257
275
|
find: '.solas',
|
|
258
|
-
replacement: path.resolve(process.cwd(),
|
|
276
|
+
replacement: path.resolve(process.cwd(), Config.GENERATED_DIR),
|
|
259
277
|
},
|
|
260
278
|
]
|
|
261
279
|
: {
|
|
262
280
|
...viteConfig.resolve.alias,
|
|
263
|
-
'.solas': path.resolve(process.cwd(),
|
|
281
|
+
'.solas': path.resolve(process.cwd(), Config.GENERATED_DIR),
|
|
264
282
|
};
|
|
265
283
|
},
|
|
266
284
|
configResolved(resolvedConfig) {
|
|
@@ -268,7 +286,7 @@ function solas(c) {
|
|
|
268
286
|
buildContext.command = resolvedConfig.command;
|
|
269
287
|
},
|
|
270
288
|
configureServer(server) {
|
|
271
|
-
logger.info('[configureServer]', `Watching for changes in ./${
|
|
289
|
+
logger.info('[configureServer]', `Watching for changes in ./${Config.APP_DIR}...`);
|
|
272
290
|
server.watcher
|
|
273
291
|
.on('add', (p) => rebuild('add', p))
|
|
274
292
|
.on('change', (p) => rebuild('change', p))
|
|
@@ -294,7 +312,7 @@ function solas(c) {
|
|
|
294
312
|
}
|
|
295
313
|
// resolve sitemap routes
|
|
296
314
|
let sitemapRoutes = [];
|
|
297
|
-
if (config.sitemap &&
|
|
315
|
+
if (config.sitemap && resolvedUrl) {
|
|
298
316
|
const auto = [
|
|
299
317
|
...new Set([...buildContext.knownRoutes, ...buildContext.prerenderRoutes]),
|
|
300
318
|
];
|
|
@@ -306,7 +324,7 @@ function solas(c) {
|
|
|
306
324
|
}
|
|
307
325
|
}
|
|
308
326
|
// write build manifest
|
|
309
|
-
const generatedDir = path.join(process.cwd(),
|
|
327
|
+
const generatedDir = path.join(process.cwd(), Config.GENERATED_DIR);
|
|
310
328
|
await Runtime.write(path.join(generatedDir, 'build.json'), JSON.stringify({
|
|
311
329
|
base: resolvedViteConfig?.base ?? '/',
|
|
312
330
|
publicFiles: await collectPublicFiles(resolvedViteConfig?.publicDir),
|
|
@@ -314,7 +332,7 @@ function solas(c) {
|
|
|
314
332
|
sitemapRoutes,
|
|
315
333
|
precompress: config.precompress,
|
|
316
334
|
trailingSlash: config.trailingSlash,
|
|
317
|
-
url:
|
|
335
|
+
url: resolvedUrl,
|
|
318
336
|
}));
|
|
319
337
|
},
|
|
320
338
|
buildApp: {
|
|
@@ -328,12 +346,12 @@ function solas(c) {
|
|
|
328
346
|
plugin,
|
|
329
347
|
rsc({
|
|
330
348
|
entries: {
|
|
331
|
-
rsc: `./${
|
|
332
|
-
ssr: `./${
|
|
333
|
-
client: `./${
|
|
349
|
+
rsc: `./${Config.GENERATED_DIR}/${Config.ENTRY_RSC}`,
|
|
350
|
+
ssr: `./${Config.GENERATED_DIR}/${Config.ENTRY_SSR}`,
|
|
351
|
+
client: `./${Config.GENERATED_DIR}/${Config.ENTRY_BROWSER}`,
|
|
334
352
|
},
|
|
335
353
|
}),
|
|
336
354
|
];
|
|
337
355
|
}
|
|
338
356
|
export default solas;
|
|
339
|
-
export
|
|
357
|
+
export * as Solas from './solas.js';
|