@jk2908/solas 0.4.4 → 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 +13 -0
- package/README.md +108 -10
- 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/router.d.ts +1 -1
- package/dist/internal/browser-router/router.js +2 -2
- package/dist/internal/browser-router/shared.d.ts +1 -0
- package/dist/internal/browser-router/use-router.d.ts +1 -1
- package/dist/internal/build.js +14 -13
- package/dist/internal/codegen/config.js +3 -2
- package/dist/internal/codegen/environments.d.ts +2 -1
- package/dist/internal/codegen/environments.js +6 -4
- package/dist/internal/env/rsc.d.ts +1 -0
- package/dist/internal/env/rsc.js +1 -0
- package/dist/internal/http-router/router.js +8 -7
- package/dist/internal/postbuild.js +9 -8
- package/dist/internal/prerender.js +14 -13
- package/dist/internal/runtimes/bun.d.ts +9 -0
- package/dist/internal/runtimes/bun.js +33 -0
- package/dist/internal/runtimes/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
|
@@ -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
|
}
|