@jk2908/solas 0.4.3 → 0.5.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 +20 -0
- package/README.md +200 -8
- package/dist/adapters/bun.d.ts +12 -0
- package/dist/adapters/bun.js +39 -0
- package/dist/adapters/node.d.ts +11 -0
- package/dist/adapters/node.js +34 -0
- package/dist/index.js +15 -8
- package/dist/internal/browser-router/link.js +2 -0
- package/dist/internal/{prefetcher.d.ts → browser-router/response-cache.d.ts} +12 -5
- package/dist/internal/{prefetcher.js → browser-router/response-cache.js} +11 -4
- package/dist/internal/browser-router/router.d.ts +1 -0
- package/dist/internal/browser-router/router.js +45 -13
- package/dist/internal/browser-router/shared.d.ts +1 -0
- package/dist/internal/browser-router/shared.js +1 -0
- package/dist/internal/browser-router/use-router.d.ts +1 -0
- package/dist/internal/build.js +14 -13
- package/dist/internal/codegen/config.js +3 -2
- package/dist/internal/codegen/environments.d.ts +2 -1
- package/dist/internal/codegen/environments.js +6 -4
- package/dist/internal/codegen/maps.js +7 -5
- package/dist/internal/env/rsc.d.ts +1 -0
- package/dist/internal/env/rsc.js +1 -0
- package/dist/internal/http-router/router.js +8 -7
- package/dist/internal/postbuild.js +9 -8
- package/dist/internal/prerender.js +14 -13
- package/dist/internal/runtimes/bun.d.ts +9 -0
- package/dist/internal/runtimes/bun.js +33 -0
- package/dist/internal/runtimes/node.d.ts +9 -0
- package/dist/internal/runtimes/node.js +31 -0
- package/dist/internal/runtimes/runtime.d.ts +29 -0
- package/dist/internal/runtimes/runtime.js +32 -0
- package/dist/runtimes/bun.d.ts +13 -0
- package/dist/runtimes/bun.js +39 -0
- package/dist/solas.d.ts +4 -1
- package/dist/solas.js +27 -6
- package/dist/types.d.ts +6 -1
- package/dist/utils/compress.js +2 -2
- package/dist/utils/export-reader.js +69 -58
- package/package.json +9 -5
package/dist/solas.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Prerender } from './internal/prerender.js';
|
|
2
|
-
import type { PluginConfig } from './types.js';
|
|
2
|
+
import type { PluginConfig, Runtime } from './types.js';
|
|
3
|
+
import { Runtime as InternalRuntime } from './internal/runtimes/runtime.js';
|
|
3
4
|
export declare namespace Solas {
|
|
4
5
|
interface Routes {
|
|
5
6
|
}
|
|
@@ -18,6 +19,7 @@ export declare namespace Solas {
|
|
|
18
19
|
const $: unique symbol;
|
|
19
20
|
const REQUEST_META_KEY: string;
|
|
20
21
|
const LOG_LEVELS: readonly ["debug", "info", "warn", "error", "fatal"];
|
|
22
|
+
const RUNTIMES: readonly ["auto", "node", "bun"];
|
|
21
23
|
const PRERENDER_MODES: readonly ["full", "ppr", false];
|
|
22
24
|
const TRAILING_SLASH_MODES: readonly ["always", "never", "ignore"];
|
|
23
25
|
const RUNTIME_MANIFEST = "runtime-manifest.json";
|
|
@@ -34,6 +36,7 @@ export declare namespace Solas {
|
|
|
34
36
|
artifacts: Prerender.Artifact.Manifest;
|
|
35
37
|
publicFiles: ReadonlySet<string>;
|
|
36
38
|
};
|
|
39
|
+
function create(runtime: Runtime): InternalRuntime.Impl;
|
|
37
40
|
function getManifestPath(outDir: string): string;
|
|
38
41
|
function loadManifest(outDir: string): Promise<Manifest | null>;
|
|
39
42
|
}
|
package/dist/solas.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { RuntimeBun } from './internal/runtimes/bun.js';
|
|
2
|
+
import { RuntimeNode } from './internal/runtimes/node.js';
|
|
3
|
+
import { Runtime as InternalRuntime } from './internal/runtimes/runtime.js';
|
|
1
4
|
export { Solas };
|
|
2
5
|
var Solas;
|
|
3
6
|
(function (Solas) {
|
|
@@ -17,10 +20,12 @@ var Solas;
|
|
|
17
20
|
Config.$ = Symbol(Config.SLUG);
|
|
18
21
|
Config.REQUEST_META_KEY = `__${Config.SLUG.toUpperCase()}__`;
|
|
19
22
|
Config.LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'];
|
|
23
|
+
Config.RUNTIMES = ['auto', 'node', 'bun'];
|
|
20
24
|
Config.PRERENDER_MODES = ['full', 'ppr', false];
|
|
21
25
|
Config.TRAILING_SLASH_MODES = ['always', 'never', 'ignore'];
|
|
22
26
|
Config.RUNTIME_MANIFEST = 'runtime-manifest.json';
|
|
23
27
|
const CONFIG_KEYS = new Set([
|
|
28
|
+
'runtime',
|
|
24
29
|
'port',
|
|
25
30
|
'logger',
|
|
26
31
|
'metadata',
|
|
@@ -49,6 +54,11 @@ var Solas;
|
|
|
49
54
|
errors.push(`Unknown config key: ${key}`);
|
|
50
55
|
}
|
|
51
56
|
}
|
|
57
|
+
if ('runtime' in input &&
|
|
58
|
+
input.runtime !== undefined &&
|
|
59
|
+
!isRuntime(input.runtime)) {
|
|
60
|
+
errors.push("config.runtime must be 'auto', 'node', or 'bun'");
|
|
61
|
+
}
|
|
52
62
|
if ('url' in input && input.url !== undefined) {
|
|
53
63
|
if (typeof input.url !== 'string') {
|
|
54
64
|
errors.push('config.url must be a string');
|
|
@@ -161,6 +171,14 @@ var Solas;
|
|
|
161
171
|
Solas.getVersion = getVersion;
|
|
162
172
|
let Runtime;
|
|
163
173
|
(function (Runtime) {
|
|
174
|
+
function create(runtime) {
|
|
175
|
+
if (runtime === 'bun' ||
|
|
176
|
+
(runtime === 'auto' && typeof globalThis.Bun !== 'undefined')) {
|
|
177
|
+
return new RuntimeBun();
|
|
178
|
+
}
|
|
179
|
+
return new RuntimeNode();
|
|
180
|
+
}
|
|
181
|
+
Runtime.create = create;
|
|
164
182
|
const manifestCache = new Map();
|
|
165
183
|
function getManifestPath(outDir) {
|
|
166
184
|
return [outDir, Config.GENERATED_DIR, Config.RUNTIME_MANIFEST]
|
|
@@ -177,13 +195,13 @@ var Solas;
|
|
|
177
195
|
if (manifestCache.has(outDir)) {
|
|
178
196
|
return manifestCache.get(outDir) ?? null;
|
|
179
197
|
}
|
|
180
|
-
const
|
|
181
|
-
if (!(await
|
|
198
|
+
const manifestPath = getManifestPath(outDir);
|
|
199
|
+
if (!(await InternalRuntime.exists(manifestPath))) {
|
|
182
200
|
manifestCache.set(outDir, null);
|
|
183
201
|
return null;
|
|
184
202
|
}
|
|
185
203
|
try {
|
|
186
|
-
const value = JSON.parse(await
|
|
204
|
+
const value = JSON.parse(await InternalRuntime.readText(manifestPath));
|
|
187
205
|
if (!isRecord(value)) {
|
|
188
206
|
manifestCache.set(outDir, null);
|
|
189
207
|
return null;
|
|
@@ -230,12 +248,12 @@ var Solas;
|
|
|
230
248
|
return null;
|
|
231
249
|
}
|
|
232
250
|
}
|
|
233
|
-
const
|
|
251
|
+
const runtimeManifest = {
|
|
234
252
|
artifacts: artifacts,
|
|
235
253
|
publicFiles: new Set(publicFiles ?? []),
|
|
236
254
|
};
|
|
237
|
-
manifestCache.set(outDir,
|
|
238
|
-
return
|
|
255
|
+
manifestCache.set(outDir, runtimeManifest);
|
|
256
|
+
return runtimeManifest;
|
|
239
257
|
}
|
|
240
258
|
catch {
|
|
241
259
|
manifestCache.set(outDir, null);
|
|
@@ -255,3 +273,6 @@ var Solas;
|
|
|
255
273
|
function isRecord(value) {
|
|
256
274
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
257
275
|
}
|
|
276
|
+
function isRuntime(value) {
|
|
277
|
+
return typeof value === 'string' && new Set(Solas.Config.RUNTIMES).has(value);
|
|
278
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -9,8 +9,10 @@ import type { Metadata } from './internal/metadata.js';
|
|
|
9
9
|
import type { HttpException } from './internal/navigation/http-exception.js';
|
|
10
10
|
import { Solas } from './solas.js';
|
|
11
11
|
export type LogLevel = (typeof Solas.Config.LOG_LEVELS)[number];
|
|
12
|
+
export type Runtime = (typeof Solas.Config.RUNTIMES)[number];
|
|
12
13
|
type Origin = `http://${string}` | `https://${string}`;
|
|
13
14
|
type PluginConfigBase = {
|
|
15
|
+
runtime?: Runtime;
|
|
14
16
|
port?: number;
|
|
15
17
|
precompress?: boolean;
|
|
16
18
|
prerender?: Route.Prerender;
|
|
@@ -30,7 +32,10 @@ export type PluginConfig = PluginConfigBase & ({
|
|
|
30
32
|
url?: Origin;
|
|
31
33
|
sitemap?: false;
|
|
32
34
|
});
|
|
33
|
-
export type
|
|
35
|
+
export type ConfiguredPluginConfig = PluginConfig & {
|
|
36
|
+
runtime: Runtime;
|
|
37
|
+
};
|
|
38
|
+
export type RuntimeConfig = Omit<PluginConfig, 'runtime'> & {
|
|
34
39
|
precompress: NonNullable<PluginConfig['precompress']>;
|
|
35
40
|
trailingSlash: NonNullable<PluginConfig['trailingSlash']>;
|
|
36
41
|
trustedOrigins: NonNullable<PluginConfig['trustedOrigins']>;
|
package/dist/utils/compress.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { brotliCompress } from 'node:zlib';
|
|
5
|
+
import { Runtime } from '../internal/runtimes/runtime.js';
|
|
5
6
|
export { Compress };
|
|
6
7
|
var Compress;
|
|
7
8
|
(function (Compress) {
|
|
@@ -25,8 +26,7 @@ var Compress;
|
|
|
25
26
|
return output;
|
|
26
27
|
}
|
|
27
28
|
async function compress(input) {
|
|
28
|
-
const
|
|
29
|
-
const buffer = Buffer.from(await file.arrayBuffer());
|
|
29
|
+
const buffer = Buffer.from(await Runtime.readBuffer(input));
|
|
30
30
|
const compressed = await new Promise((fulfill, reject) => {
|
|
31
31
|
brotliCompress(buffer, (err, res) => {
|
|
32
32
|
if (err) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
1
2
|
import path from 'node:path';
|
|
3
|
+
import { parseSync, } from 'oxc-parser';
|
|
2
4
|
export class ExportReader {
|
|
3
|
-
#transpilers = new Map();
|
|
4
5
|
#loadModule = null;
|
|
5
6
|
/**
|
|
6
|
-
* Pick the
|
|
7
|
+
* Pick the parser language that matches the source file extension
|
|
7
8
|
*/
|
|
8
9
|
static #getLoaderType(filePath) {
|
|
9
10
|
const ext = path.extname(filePath).toLowerCase();
|
|
@@ -17,28 +18,6 @@ export class ExportReader {
|
|
|
17
18
|
return 'tsx';
|
|
18
19
|
throw new Error(`Unsupported module extension: ${ext || '(none)'} in ${filePath}`);
|
|
19
20
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Parse a literal value from a string
|
|
22
|
-
*/
|
|
23
|
-
static #parse(value) {
|
|
24
|
-
const trimmed = value.trim();
|
|
25
|
-
// keep quoted literals as strings without evaluating the
|
|
26
|
-
// source text
|
|
27
|
-
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
28
|
-
(trimmed.startsWith("'") && trimmed.endsWith("'")) ||
|
|
29
|
-
(trimmed.startsWith('`') && trimmed.endsWith('`'))) {
|
|
30
|
-
return trimmed.slice(1, -1);
|
|
31
|
-
}
|
|
32
|
-
if (trimmed === 'true')
|
|
33
|
-
return true;
|
|
34
|
-
if (trimmed === 'false')
|
|
35
|
-
return false;
|
|
36
|
-
if (trimmed === 'null')
|
|
37
|
-
return null;
|
|
38
|
-
const n = Number(trimmed);
|
|
39
|
-
if (Number.isFinite(n))
|
|
40
|
-
return n;
|
|
41
|
-
}
|
|
42
21
|
/**
|
|
43
22
|
* Set the Vite server's SSR module loader so we can execute modules
|
|
44
23
|
*/
|
|
@@ -46,30 +25,35 @@ export class ExportReader {
|
|
|
46
25
|
this.#loadModule = l;
|
|
47
26
|
}
|
|
48
27
|
/**
|
|
49
|
-
*
|
|
28
|
+
* Parse a source file as an ESM route module
|
|
50
29
|
*/
|
|
51
|
-
#
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
30
|
+
async #parse(filePath) {
|
|
31
|
+
const source = await this.raw(filePath);
|
|
32
|
+
const result = parseSync(filePath, source, {
|
|
33
|
+
lang: ExportReader.#getLoaderType(filePath),
|
|
34
|
+
sourceType: 'module',
|
|
35
|
+
preserveParens: false,
|
|
36
|
+
});
|
|
37
|
+
if (result.errors.length > 0) {
|
|
38
|
+
throw new Error(result.errors[0]?.message ?? `Failed to parse ${filePath}`);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
59
41
|
}
|
|
60
42
|
/**
|
|
61
43
|
* Read the raw text content of a file
|
|
62
44
|
*/
|
|
63
45
|
async raw(filePath) {
|
|
64
|
-
return
|
|
46
|
+
return fs.readFile(filePath, 'utf-8');
|
|
65
47
|
}
|
|
66
48
|
/**
|
|
67
49
|
* Get the names of all exports from a file
|
|
68
50
|
*/
|
|
69
51
|
async exports(filePath) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
52
|
+
const { module } = await this.#parse(filePath);
|
|
53
|
+
return Array.from(new Set(module.staticExports.flatMap((entry) => entry.entries
|
|
54
|
+
.filter((specifier) => !specifier.isType)
|
|
55
|
+
.map((specifier) => specifier.exportName.name)
|
|
56
|
+
.filter((name) => typeof name === 'string' && name.length > 0))));
|
|
73
57
|
}
|
|
74
58
|
/**
|
|
75
59
|
* Check if a file exports a specific name
|
|
@@ -86,27 +70,8 @@ export class ExportReader {
|
|
|
86
70
|
async literal(filePath, name, validate) {
|
|
87
71
|
if (!(await this.has(filePath, name)))
|
|
88
72
|
return;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const code = this.#getTranspiler(filePath).transformSync(await this.raw(filePath));
|
|
92
|
-
// build the matcher from escaped plain-text pieces so arbitrary export names
|
|
93
|
-
// cannot change the regex shape
|
|
94
|
-
const source =
|
|
95
|
-
// match: `export const|let|var ` at statement boundaries
|
|
96
|
-
'(?:^|[;\\n])\\s*export\\s+(?:const|let|var)\\s+' +
|
|
97
|
-
// treat export name as plain text in regex
|
|
98
|
-
name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
|
|
99
|
-
// capture one supported literal value (string, number, boolean, null)
|
|
100
|
-
'\\s*=\\s*(?<value>(?:"(?:[^"\\\\]|\\\\.)*"|\'(?:[^\'\\\\]|\\\\.)*\'|\\x60(?:[^\\x60\\\\]|\\\\.)*\\x60|true|false|null|-?\\d+(?:\\.\\d+)?))(?=\\s|;|$)';
|
|
101
|
-
// multiline mode lets ^ match the start of each transpiled line, so the
|
|
102
|
-
// export regex stays anchored to a real statement boundary instead of
|
|
103
|
-
// the file start
|
|
104
|
-
const text = code.match(new RegExp(source, 'm'))?.groups?.value;
|
|
105
|
-
if (!text)
|
|
106
|
-
return;
|
|
107
|
-
// only support cheap literal parsing here. Anything richer should go through
|
|
108
|
-
// value() so module semantics stay correct
|
|
109
|
-
const value = ExportReader.#parse(text);
|
|
73
|
+
const { program } = await this.#parse(filePath);
|
|
74
|
+
const value = ExportReader.#readLiteralExport(program, name);
|
|
110
75
|
if (value === undefined)
|
|
111
76
|
return;
|
|
112
77
|
if (!validate || validate(value))
|
|
@@ -130,4 +95,50 @@ export class ExportReader {
|
|
|
130
95
|
if (!validate || validate(value))
|
|
131
96
|
return value;
|
|
132
97
|
}
|
|
98
|
+
static #readLiteralExport(program, name) {
|
|
99
|
+
for (const statement of program.body) {
|
|
100
|
+
if (statement.type !== 'ExportNamedDeclaration')
|
|
101
|
+
continue;
|
|
102
|
+
const declaration = statement.declaration;
|
|
103
|
+
if (!declaration || declaration.type !== 'VariableDeclaration')
|
|
104
|
+
continue;
|
|
105
|
+
for (const declarator of declaration.declarations) {
|
|
106
|
+
if (declarator.id.type !== 'Identifier' || declarator.id.name !== name)
|
|
107
|
+
continue;
|
|
108
|
+
return ExportReader.#readLiteralValue(declarator.init);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
static #readLiteralValue(value) {
|
|
113
|
+
if (!value || typeof value !== 'object' || !('type' in value))
|
|
114
|
+
return;
|
|
115
|
+
const node = value;
|
|
116
|
+
if (node.type === 'Literal') {
|
|
117
|
+
const literal = value;
|
|
118
|
+
if (typeof literal.value === 'string' ||
|
|
119
|
+
typeof literal.value === 'number' ||
|
|
120
|
+
typeof literal.value === 'boolean' ||
|
|
121
|
+
literal.value === null) {
|
|
122
|
+
return literal.value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (node.type === 'TemplateLiteral' &&
|
|
126
|
+
Array.isArray(value.expressions) &&
|
|
127
|
+
value.expressions.length === 0 &&
|
|
128
|
+
Array.isArray(value.quasis) &&
|
|
129
|
+
value.quasis.length === 1) {
|
|
130
|
+
const quasi = value.quasis[0];
|
|
131
|
+
if (quasi?.value && typeof quasi.value.cooked === 'string') {
|
|
132
|
+
return quasi.value.cooked;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (node.type === 'UnaryExpression' &&
|
|
136
|
+
value.operator === '-' &&
|
|
137
|
+
value.argument.type === 'Literal' &&
|
|
138
|
+
value.argument.value !== null &&
|
|
139
|
+
typeof value.argument.value === 'number') {
|
|
140
|
+
const argument = value.argument.value;
|
|
141
|
+
return -argument;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
133
144
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jk2908/solas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"types": "./dist/index.d.ts",
|
|
34
34
|
"import": "./dist/index.js"
|
|
35
35
|
},
|
|
36
|
+
"./$": {
|
|
37
|
+
"types": "./dist/solas.d.ts",
|
|
38
|
+
"import": "./dist/solas.js"
|
|
39
|
+
},
|
|
36
40
|
"./env/rsc": {
|
|
37
41
|
"types": "./dist/internal/env/rsc.d.ts",
|
|
38
42
|
"import": "./dist/internal/env/rsc.js"
|
|
@@ -82,11 +86,13 @@
|
|
|
82
86
|
},
|
|
83
87
|
"dependencies": {
|
|
84
88
|
"@vitejs/plugin-rsc": "^0.5.20",
|
|
89
|
+
"mime-types": "^3.0.2",
|
|
90
|
+
"oxc-parser": "^0.134.0",
|
|
85
91
|
"path-to-regexp": "^8.3.0"
|
|
86
92
|
},
|
|
87
93
|
"devDependencies": {
|
|
88
94
|
"@prettier/plugin-oxc": "^0.1.3",
|
|
89
|
-
"@types/
|
|
95
|
+
"@types/mime-types": "^3.0.1",
|
|
90
96
|
"@typescript/native-preview": "^7.0.0-dev.20260224.1",
|
|
91
97
|
"network-information-types": "^0.1.1",
|
|
92
98
|
"oxfmt": "^0.35.0",
|
|
@@ -105,7 +111,5 @@
|
|
|
105
111
|
"optional": true
|
|
106
112
|
}
|
|
107
113
|
},
|
|
108
|
-
"engines": {
|
|
109
|
-
"bun": ">=1.2.0"
|
|
110
|
-
}
|
|
114
|
+
"engines": {}
|
|
111
115
|
}
|