@solidjs/start 2.0.0-beta.9 → 2.0.0-rc.1
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 +26 -1
- package/dist/config/env.js +11 -8
- package/dist/config/fs-routes/tree-shake.js +175 -32
- package/dist/config/index.js +7 -2
- package/dist/fns/handler.js +13 -2
- package/dist/middleware/index.js +4 -1
- package/dist/server/StartServer.jsx +1 -1
- package/dist/server/fetchEvent.d.ts +10 -0
- package/dist/server/fetchEvent.js +10 -0
- package/dist/server/handler.js +7 -11
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.jsx +1 -0
- package/dist/server/spa/StartServer.jsx +1 -1
- package/dist/server/strip-path-base.d.ts +1 -0
- package/dist/server/strip-path-base.js +10 -0
- package/dist/server/web-stream.d.ts +9 -0
- package/dist/server/web-stream.js +26 -0
- package/dist/shared/dev-overlay/createStackFrame.js +46 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @solidjs/start
|
|
2
2
|
|
|
3
|
+
## 2.0.0-rc.1
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 02e561e: Expose experimental `decorateHandler` and `decorateMiddleware` helpers from
|
|
8
|
+
`@solidjs/start/server` for providing Solid's request context to custom h3 handlers and
|
|
9
|
+
middleware.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- f66b5a9: fix: keep non-picked route exports that picked exports reference instead of deleting their bindings, so API handlers can call helpers exported from the same file (#2100); also fixes picked exports declared via `export { ... }` specifiers being dropped (#1659)
|
|
14
|
+
- 1763296: fix: run `onBeforeResponse` middleware in declared order (#2131)
|
|
15
|
+
|
|
16
|
+
Applications that reversed their `onBeforeResponse` arrays as a workaround should restore the intended declaration order.
|
|
17
|
+
|
|
18
|
+
- cd98e7d: Fix API route matching when `server.baseURL` is configured.
|
|
19
|
+
|
|
20
|
+
## 2.0.0-beta.10
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- 8af6304: Support for Vite 8.1 Experimental Bundled Dev Mode
|
|
25
|
+
- 9d91484: fix: don't crash when a server function throws an error whose message contains non-latin1 characters.
|
|
26
|
+
- 068b64c: Return a cancellation-safe web `ReadableStream` for streaming SSR in development. Returning Solid's
|
|
27
|
+
raw stream only rendered on Node; Bun and Deno coerced it to `[object Object]`.
|
|
28
|
+
|
|
3
29
|
## 2.0.0-beta.9
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
|
@@ -118,7 +144,6 @@
|
|
|
118
144
|
- 8256190: Rework `@solidjs/start/env`
|
|
119
145
|
- 6cbba24: Fix multiple Set-Cookie headers being lost on redirect responses
|
|
120
146
|
- d4cc548: ## Bump Seroval
|
|
121
|
-
|
|
122
147
|
- version `1.4.1`
|
|
123
148
|
|
|
124
149
|
- dd40610: Handle base url in api routes
|
package/dist/config/env.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadEnv } from "vite";
|
|
2
|
+
import { parseIdQuery } from "./utils.js";
|
|
2
3
|
const LOADERS = {
|
|
3
4
|
node: `export default key => process.env[key];`,
|
|
4
5
|
"cloudflare-workers": `import { env } from 'cloudflare:workers';export default key => env[key];`,
|
|
@@ -39,17 +40,19 @@ export function envPlugin(options) {
|
|
|
39
40
|
env = config.mode !== "production" ? "development" : "production";
|
|
40
41
|
},
|
|
41
42
|
resolveId(id) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const { filename } = parseIdQuery(id);
|
|
44
|
+
if (filename === SERVER_ENV ||
|
|
45
|
+
filename === CLIENT_ENV ||
|
|
46
|
+
filename === SERVER_RUNTIME_ENV ||
|
|
47
|
+
filename === SERVER_RUNTIME_LOADER) {
|
|
46
48
|
return id;
|
|
47
49
|
}
|
|
48
50
|
return undefined;
|
|
49
51
|
},
|
|
50
52
|
load(id) {
|
|
53
|
+
const { filename } = parseIdQuery(id);
|
|
51
54
|
const isServer = this.environment.config.consumer === "server";
|
|
52
|
-
if (
|
|
55
|
+
if (filename === SERVER_ENV) {
|
|
53
56
|
if (!isServer) {
|
|
54
57
|
return SERVER_ONLY_MODULE;
|
|
55
58
|
}
|
|
@@ -58,19 +61,19 @@ export function envPlugin(options) {
|
|
|
58
61
|
: loadEnv(env, '.', serverPrefix);
|
|
59
62
|
return convertObjectToModule(vars);
|
|
60
63
|
}
|
|
61
|
-
if (
|
|
64
|
+
if (filename === CLIENT_ENV) {
|
|
62
65
|
const vars = currentOptions.client?.load
|
|
63
66
|
? currentOptions.client.load(env)
|
|
64
67
|
: loadEnv(env, '.', clientPrefix);
|
|
65
68
|
return convertObjectToModule(vars);
|
|
66
69
|
}
|
|
67
|
-
if (
|
|
70
|
+
if (filename === SERVER_RUNTIME_LOADER) {
|
|
68
71
|
if (!isServer) {
|
|
69
72
|
return SERVER_ONLY_MODULE;
|
|
70
73
|
}
|
|
71
74
|
return runtimeCode;
|
|
72
75
|
}
|
|
73
|
-
if (
|
|
76
|
+
if (filename === SERVER_RUNTIME_ENV) {
|
|
74
77
|
if (!isServer) {
|
|
75
78
|
return SERVER_ONLY_MODULE;
|
|
76
79
|
}
|
|
@@ -20,13 +20,28 @@ function treeShakeTransform({ types: t }) {
|
|
|
20
20
|
}
|
|
21
21
|
return path.node.id && path.node.id.type === "Identifier" ? path.get("id") : null;
|
|
22
22
|
}
|
|
23
|
-
function
|
|
23
|
+
function isTypeOnlyReference(path) {
|
|
24
|
+
return path.getAncestry().some(parent => {
|
|
25
|
+
if (parent.isTSType())
|
|
26
|
+
return true;
|
|
27
|
+
if (parent.isExportNamedDeclaration())
|
|
28
|
+
return parent.node.exportKind === "type";
|
|
29
|
+
if (parent.isExportSpecifier())
|
|
30
|
+
return parent.node.exportKind === "type";
|
|
31
|
+
return false;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function runtimeReferences(binding) {
|
|
35
|
+
return binding.referencePaths.filter(ref => !isTypeOnlyReference(ref));
|
|
36
|
+
}
|
|
37
|
+
function isIdentifierReferenced(ident, includeTypes = false) {
|
|
24
38
|
const b = ident.scope.getBinding(ident.node.name);
|
|
25
|
-
if (b
|
|
39
|
+
if (b) {
|
|
40
|
+
const references = b.constantViolations.concat(includeTypes ? b.referencePaths : runtimeReferences(b));
|
|
41
|
+
if (!references.length)
|
|
42
|
+
return false;
|
|
26
43
|
if (b.path.type === "FunctionDeclaration") {
|
|
27
|
-
return !b.
|
|
28
|
-
.concat(b.referencePaths)
|
|
29
|
-
.every(ref => ref.findParent(p => p === b.path));
|
|
44
|
+
return !references.every(ref => ref.findParent(p => p === b.path));
|
|
30
45
|
}
|
|
31
46
|
return true;
|
|
32
47
|
}
|
|
@@ -34,13 +49,13 @@ function treeShakeTransform({ types: t }) {
|
|
|
34
49
|
}
|
|
35
50
|
function markFunction(path, state) {
|
|
36
51
|
const ident = getIdentifier(path);
|
|
37
|
-
if (ident && ident.node && isIdentifierReferenced(ident)) {
|
|
52
|
+
if (ident && ident.node && isIdentifierReferenced(ident, true)) {
|
|
38
53
|
state.refs.add(ident);
|
|
39
54
|
}
|
|
40
55
|
}
|
|
41
56
|
function markImport(path, state) {
|
|
42
57
|
const local = path.get("local");
|
|
43
|
-
if (isIdentifierReferenced(local)) {
|
|
58
|
+
if (isIdentifierReferenced(local, true)) {
|
|
44
59
|
state.refs.add(local);
|
|
45
60
|
}
|
|
46
61
|
}
|
|
@@ -49,12 +64,13 @@ function treeShakeTransform({ types: t }) {
|
|
|
49
64
|
Program: {
|
|
50
65
|
enter(path, state) {
|
|
51
66
|
state.refs = new Set();
|
|
67
|
+
state.candidates = new Set();
|
|
52
68
|
state.done = false;
|
|
53
69
|
path.traverse({
|
|
54
70
|
VariableDeclarator(variablePath, variableState) {
|
|
55
71
|
if (variablePath.node.id.type === "Identifier") {
|
|
56
72
|
const local = variablePath.get("id");
|
|
57
|
-
if (isIdentifierReferenced(local)) {
|
|
73
|
+
if (isIdentifierReferenced(local, true)) {
|
|
58
74
|
variableState.refs.add(local);
|
|
59
75
|
}
|
|
60
76
|
}
|
|
@@ -69,7 +85,7 @@ function treeShakeTransform({ types: t }) {
|
|
|
69
85
|
: (() => {
|
|
70
86
|
throw new Error("invariant");
|
|
71
87
|
})());
|
|
72
|
-
if (isIdentifierReferenced(local)) {
|
|
88
|
+
if (isIdentifierReferenced(local, true)) {
|
|
73
89
|
variableState.refs.add(local);
|
|
74
90
|
}
|
|
75
91
|
});
|
|
@@ -88,29 +104,41 @@ function treeShakeTransform({ types: t }) {
|
|
|
88
104
|
else {
|
|
89
105
|
return;
|
|
90
106
|
}
|
|
91
|
-
if (isIdentifierReferenced(local)) {
|
|
107
|
+
if (isIdentifierReferenced(local, true)) {
|
|
92
108
|
variableState.refs.add(local);
|
|
93
109
|
}
|
|
94
110
|
});
|
|
95
111
|
}
|
|
96
112
|
},
|
|
97
113
|
ExportDefaultDeclaration(exportNamedPath) {
|
|
98
|
-
// if opts.keep is true, we don't remove the routeData export
|
|
99
114
|
if (state.opts.pick && !state.opts.pick.includes("default")) {
|
|
100
|
-
exportNamedPath.
|
|
115
|
+
const decl = exportNamedPath.node.declaration;
|
|
116
|
+
// A named function/class declaration creates a module-scope
|
|
117
|
+
// binding that picked exports may reference, so only strip
|
|
118
|
+
// the `export default`; the sweep below removes the
|
|
119
|
+
// declaration again if nothing references it.
|
|
120
|
+
if ((t.isFunctionDeclaration(decl) || t.isClassDeclaration(decl)) && decl.id) {
|
|
121
|
+
const [newPath] = exportNamedPath.replaceWith(decl);
|
|
122
|
+
const id = newPath.get("id");
|
|
123
|
+
state.refs.add(id);
|
|
124
|
+
state.candidates.add(id);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
exportNamedPath.remove();
|
|
128
|
+
}
|
|
101
129
|
}
|
|
102
130
|
},
|
|
103
131
|
ExportNamedDeclaration(exportNamedPath) {
|
|
104
|
-
//
|
|
132
|
+
// No pick list means nothing gets pruned.
|
|
105
133
|
if (!state.opts.pick) {
|
|
106
134
|
return;
|
|
107
135
|
}
|
|
108
136
|
const specifiers = exportNamedPath.get("specifiers");
|
|
109
137
|
if (specifiers.length) {
|
|
110
138
|
specifiers.forEach(s => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
139
|
+
const exported = s.node.exported;
|
|
140
|
+
const exportedName = t.isIdentifier(exported) ? exported.name : exported.value;
|
|
141
|
+
if (!state.opts.pick.includes(exportedName)) {
|
|
114
142
|
s.remove();
|
|
115
143
|
}
|
|
116
144
|
});
|
|
@@ -124,24 +152,51 @@ function treeShakeTransform({ types: t }) {
|
|
|
124
152
|
return;
|
|
125
153
|
}
|
|
126
154
|
switch (decl.node.type) {
|
|
127
|
-
case "FunctionDeclaration":
|
|
155
|
+
case "FunctionDeclaration":
|
|
156
|
+
case "ClassDeclaration": {
|
|
128
157
|
const name = decl.node.id?.name;
|
|
129
158
|
if (name && state.opts.pick && !state.opts.pick.includes(name)) {
|
|
130
|
-
|
|
159
|
+
// Keep the declaration in module scope since picked
|
|
160
|
+
// exports may reference it; the sweep below removes it
|
|
161
|
+
// again if unreferenced.
|
|
162
|
+
const [newPath] = exportNamedPath.replaceWith(decl.node);
|
|
163
|
+
const id = newPath.get("id");
|
|
164
|
+
state.refs.add(id);
|
|
165
|
+
state.candidates.add(id);
|
|
131
166
|
}
|
|
132
167
|
break;
|
|
133
168
|
}
|
|
134
169
|
case "VariableDeclaration": {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
170
|
+
const declNode = decl.node;
|
|
171
|
+
// Destructuring declarators are conservatively treated as
|
|
172
|
+
// picked (matching the previous behavior of leaving them
|
|
173
|
+
// untouched).
|
|
174
|
+
const isPicked = (d) => d.id.type !== "Identifier" || state.opts.pick.includes(d.id.name);
|
|
175
|
+
if (declNode.declarations.every(isPicked)) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
// Split into one declaration per declarator (preserving
|
|
179
|
+
// evaluation order) and export only the picked ones.
|
|
180
|
+
// Unpicked bindings are kept since picked exports may
|
|
181
|
+
// reference them; the sweep below removes them again if
|
|
182
|
+
// unreferenced.
|
|
183
|
+
const statements = declNode.declarations.map(d => {
|
|
184
|
+
const single = t.variableDeclaration(declNode.kind, [d]);
|
|
185
|
+
single.declare = declNode.declare;
|
|
186
|
+
return isPicked(d) ? t.exportNamedDeclaration(single, []) : single;
|
|
144
187
|
});
|
|
188
|
+
const newPaths = exportNamedPath.replaceWithMultiple(statements);
|
|
189
|
+
for (const p of newPaths) {
|
|
190
|
+
if (!p.isVariableDeclaration())
|
|
191
|
+
continue;
|
|
192
|
+
for (const d of p.get("declarations")) {
|
|
193
|
+
if (d.node.id.type === "Identifier") {
|
|
194
|
+
const id = d.get("id");
|
|
195
|
+
state.refs.add(id);
|
|
196
|
+
state.candidates.add(id);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
145
200
|
break;
|
|
146
201
|
}
|
|
147
202
|
default: {
|
|
@@ -152,6 +207,8 @@ function treeShakeTransform({ types: t }) {
|
|
|
152
207
|
FunctionDeclaration: markFunction,
|
|
153
208
|
FunctionExpression: markFunction,
|
|
154
209
|
ArrowFunctionExpression: markFunction,
|
|
210
|
+
ClassDeclaration: markFunction,
|
|
211
|
+
ClassExpression: markFunction,
|
|
155
212
|
ImportSpecifier: markImport,
|
|
156
213
|
ImportDefaultSpecifier: markImport,
|
|
157
214
|
ImportNamespaceSpecifier: markImport,
|
|
@@ -163,11 +220,95 @@ function treeShakeTransform({ types: t }) {
|
|
|
163
220
|
}
|
|
164
221
|
},
|
|
165
222
|
}, state);
|
|
223
|
+
// References alone cannot distinguish live bindings from dead cycles.
|
|
224
|
+
// Build a graph of localized exports and retain only those reachable
|
|
225
|
+
// from runtime code outside that graph.
|
|
226
|
+
path.scope.crawl();
|
|
227
|
+
const tracked = new Map();
|
|
228
|
+
const candidateOwners = new Map();
|
|
229
|
+
const addOwner = (owners, binding, owner) => {
|
|
230
|
+
const paths = owners.get(binding) ?? new Set();
|
|
231
|
+
paths.add(owner);
|
|
232
|
+
owners.set(binding, paths);
|
|
233
|
+
};
|
|
234
|
+
for (const identifier of state.refs) {
|
|
235
|
+
if (identifier.node?.type !== "Identifier" || !identifier.parentPath)
|
|
236
|
+
continue;
|
|
237
|
+
const binding = identifier.scope.getBinding(identifier.node.name);
|
|
238
|
+
if (binding)
|
|
239
|
+
addOwner(tracked, binding, identifier.parentPath);
|
|
240
|
+
}
|
|
241
|
+
for (const identifier of state.candidates) {
|
|
242
|
+
if (!identifier.parentPath)
|
|
243
|
+
continue;
|
|
244
|
+
const binding = identifier.scope.getBinding(identifier.node.name);
|
|
245
|
+
if (!binding)
|
|
246
|
+
continue;
|
|
247
|
+
addOwner(tracked, binding, identifier.parentPath);
|
|
248
|
+
addOwner(candidateOwners, binding, identifier.parentPath);
|
|
249
|
+
}
|
|
250
|
+
const dependencies = new Map();
|
|
251
|
+
const live = new Set();
|
|
252
|
+
for (const [binding, owners] of tracked) {
|
|
253
|
+
dependencies.set(binding, new Set());
|
|
254
|
+
if ([...owners].some(owner => owner.findParent(parent => parent.isExportNamedDeclaration() || parent.isExportDefaultDeclaration()))) {
|
|
255
|
+
live.add(binding);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const ownerBindings = new Map();
|
|
259
|
+
for (const [binding, owners] of tracked) {
|
|
260
|
+
for (const owner of owners) {
|
|
261
|
+
ownerBindings.set(owner, binding);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const owningBinding = (reference) => {
|
|
265
|
+
const owner = reference.find(parent => ownerBindings.has(parent));
|
|
266
|
+
return owner && ownerBindings.get(owner);
|
|
267
|
+
};
|
|
268
|
+
for (const target of tracked.keys()) {
|
|
269
|
+
const references = target.constantViolations.concat(runtimeReferences(target));
|
|
270
|
+
for (const reference of references) {
|
|
271
|
+
const source = owningBinding(reference);
|
|
272
|
+
if (source)
|
|
273
|
+
dependencies.get(source)?.add(target);
|
|
274
|
+
else
|
|
275
|
+
live.add(target);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const pending = [...live];
|
|
279
|
+
while (pending.length) {
|
|
280
|
+
const source = pending.pop();
|
|
281
|
+
for (const dependency of dependencies.get(source) ?? []) {
|
|
282
|
+
if (live.has(dependency))
|
|
283
|
+
continue;
|
|
284
|
+
live.add(dependency);
|
|
285
|
+
pending.push(dependency);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const deadCandidates = new Set([...candidateOwners.keys()].filter(candidate => !live.has(candidate)));
|
|
289
|
+
path.traverse({
|
|
290
|
+
VariableDeclarator(variablePath) {
|
|
291
|
+
if (variablePath.node.id.type !== "Identifier")
|
|
292
|
+
return;
|
|
293
|
+
const binding = variablePath.scope.getBinding(variablePath.node.id.name);
|
|
294
|
+
if (binding && deadCandidates.has(binding))
|
|
295
|
+
variablePath.remove();
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
for (const [candidate, owners] of candidateOwners) {
|
|
299
|
+
if (!deadCandidates.has(candidate))
|
|
300
|
+
continue;
|
|
301
|
+
for (const owner of owners) {
|
|
302
|
+
if (!owner.removed && !owner.isVariableDeclarator())
|
|
303
|
+
owner.remove();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
166
306
|
const refs = state.refs;
|
|
307
|
+
const refNodes = new Set([...refs].map(ref => ref.node));
|
|
167
308
|
let count = 0;
|
|
168
309
|
const sweepFunction = (sweepPath) => {
|
|
169
310
|
const ident = getIdentifier(sweepPath);
|
|
170
|
-
if (ident && ident.node &&
|
|
311
|
+
if (ident && ident.node && refNodes.has(ident.node) && !isIdentifierReferenced(ident)) {
|
|
171
312
|
++count;
|
|
172
313
|
if (t.isAssignmentExpression(sweepPath.parentPath) ||
|
|
173
314
|
t.isVariableDeclarator(sweepPath.parentPath)) {
|
|
@@ -180,7 +321,7 @@ function treeShakeTransform({ types: t }) {
|
|
|
180
321
|
};
|
|
181
322
|
function sweepImport(sweepPath) {
|
|
182
323
|
const local = sweepPath.get("local");
|
|
183
|
-
if (
|
|
324
|
+
if (refNodes.has(local.node) && !isIdentifierReferenced(local)) {
|
|
184
325
|
++count;
|
|
185
326
|
sweepPath.remove();
|
|
186
327
|
if (sweepPath.parent.specifiers.length === 0) {
|
|
@@ -195,7 +336,7 @@ function treeShakeTransform({ types: t }) {
|
|
|
195
336
|
VariableDeclarator(variablePath) {
|
|
196
337
|
if (variablePath.node.id.type === "Identifier") {
|
|
197
338
|
const local = variablePath.get("id");
|
|
198
|
-
if (
|
|
339
|
+
if (refNodes.has(local.node) && !isIdentifierReferenced(local)) {
|
|
199
340
|
++count;
|
|
200
341
|
variablePath.remove();
|
|
201
342
|
}
|
|
@@ -212,7 +353,7 @@ function treeShakeTransform({ types: t }) {
|
|
|
212
353
|
: (() => {
|
|
213
354
|
throw new Error("invariant");
|
|
214
355
|
})());
|
|
215
|
-
if (
|
|
356
|
+
if (refNodes.has(local.node) && !isIdentifierReferenced(local)) {
|
|
216
357
|
++count;
|
|
217
358
|
p.remove();
|
|
218
359
|
}
|
|
@@ -236,7 +377,7 @@ function treeShakeTransform({ types: t }) {
|
|
|
236
377
|
else {
|
|
237
378
|
return;
|
|
238
379
|
}
|
|
239
|
-
if (
|
|
380
|
+
if (refNodes.has(local.node) && !isIdentifierReferenced(local)) {
|
|
240
381
|
++count;
|
|
241
382
|
e.remove();
|
|
242
383
|
}
|
|
@@ -249,6 +390,8 @@ function treeShakeTransform({ types: t }) {
|
|
|
249
390
|
FunctionDeclaration: sweepFunction,
|
|
250
391
|
FunctionExpression: sweepFunction,
|
|
251
392
|
ArrowFunctionExpression: sweepFunction,
|
|
393
|
+
ClassDeclaration: sweepFunction,
|
|
394
|
+
ClassExpression: sweepFunction,
|
|
252
395
|
ImportSpecifier: sweepImport,
|
|
253
396
|
ImportDefaultSpecifier: sweepImport,
|
|
254
397
|
ImportNamespaceSpecifier: sweepImport,
|
package/dist/config/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defu } from "defu";
|
|
2
2
|
import { globSync } from "node:fs";
|
|
3
|
-
import { extname, isAbsolute, join } from "node:path";
|
|
3
|
+
import { basename, extname, isAbsolute, join } from "node:path";
|
|
4
4
|
import solid from "vite-plugin-solid";
|
|
5
5
|
import { serverFunctionsPlugin } from "../directives/index.js";
|
|
6
6
|
import { boundaryModules } from "./boundary-modules.js";
|
|
@@ -61,8 +61,11 @@ export function solidStart(options) {
|
|
|
61
61
|
},
|
|
62
62
|
};
|
|
63
63
|
},
|
|
64
|
-
async config(
|
|
64
|
+
async config(config, env) {
|
|
65
65
|
const clientInput = [handlers.client];
|
|
66
|
+
const clientEntryUrl = env.command === "serve" && config.experimental?.bundledDev
|
|
67
|
+
? `assets/${basename(handlers.client, entryExtension)}.js`
|
|
68
|
+
: handlers.client;
|
|
66
69
|
if (env.command === "build") {
|
|
67
70
|
const clientRouter = globalThis.ROUTERS.client;
|
|
68
71
|
for (const route of await clientRouter.getRoutes()) {
|
|
@@ -149,7 +152,9 @@ export function solidStart(options) {
|
|
|
149
152
|
// esbuild receives a valid JS string literal for the define value
|
|
150
153
|
"import.meta.env.START_APP_ENTRY": JSON.stringify(appEntryPath),
|
|
151
154
|
"import.meta.env.START_CLIENT_ENTRY": JSON.stringify(handlers.client),
|
|
155
|
+
"import.meta.env.START_CLIENT_ENTRY_URL": JSON.stringify(clientEntryUrl),
|
|
152
156
|
"import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay),
|
|
157
|
+
"import.meta.env.SERVER_BASE_URL": JSON.stringify(config.server?.baseURL ?? ""),
|
|
153
158
|
"import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || "json"),
|
|
154
159
|
},
|
|
155
160
|
builder: {
|
package/dist/fns/handler.js
CHANGED
|
@@ -144,7 +144,7 @@ export async function handleServerFunction(h3Event) {
|
|
|
144
144
|
}
|
|
145
145
|
else if (instance) {
|
|
146
146
|
const error = x instanceof Error ? x.message : typeof x === "string" ? x : "true";
|
|
147
|
-
h3Event.res.headers.set("X-Error", error
|
|
147
|
+
h3Event.res.headers.set("X-Error", toHeaderValue(error));
|
|
148
148
|
}
|
|
149
149
|
else {
|
|
150
150
|
x = handleNoJS(x, request, parsed, true);
|
|
@@ -158,7 +158,7 @@ export async function handleServerFunction(h3Event) {
|
|
|
158
158
|
headers.set("X-Error", errorHeader);
|
|
159
159
|
}
|
|
160
160
|
return new Response(body.body, {
|
|
161
|
-
headers
|
|
161
|
+
headers,
|
|
162
162
|
});
|
|
163
163
|
}
|
|
164
164
|
h3Event.res.headers.set(BODY_FORMAT_KEY, BodyFormat.Seroval);
|
|
@@ -172,6 +172,17 @@ export async function handleServerFunction(h3Event) {
|
|
|
172
172
|
return x;
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
+
// header values must be ByteString-safe (no chars > 0xFF), otherwise Headers.set throws
|
|
176
|
+
function toHeaderValue(value) {
|
|
177
|
+
const stripped = value.replace(/[\r\n]+/g, "");
|
|
178
|
+
try {
|
|
179
|
+
return /[^\x00-\xFF]/.test(stripped) ? encodeURIComponent(stripped) : stripped;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// encodeURIComponent throws on lone surrogates
|
|
183
|
+
return "true";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
175
186
|
function handleNoJS(result, request, parsed, thrown) {
|
|
176
187
|
const url = new URL(request.url);
|
|
177
188
|
const isError = result instanceof Error;
|
package/dist/middleware/index.js
CHANGED
|
@@ -54,7 +54,10 @@ export function createMiddleware(args) {
|
|
|
54
54
|
mw.push(wrapResponseMiddleware(args.onBeforeResponse));
|
|
55
55
|
}
|
|
56
56
|
else if (Array.isArray(args.onBeforeResponse)) {
|
|
57
|
-
|
|
57
|
+
// h3 middleware compose onion-style: logic after `await next()` unwinds
|
|
58
|
+
// innermost-first (reverse registration order). Register the wrappers
|
|
59
|
+
// reversed so onBeforeResponse functions run in their declared order.
|
|
60
|
+
mw.push(...args.onBeforeResponse.map(wrapResponseMiddleware).reverse());
|
|
58
61
|
}
|
|
59
62
|
return mw;
|
|
60
63
|
}
|
|
@@ -19,7 +19,7 @@ export function StartServer(props) {
|
|
|
19
19
|
<TopErrorBoundary>
|
|
20
20
|
<props.document assets={<HydrationScript />} scripts={<>
|
|
21
21
|
<PatchVirtualDevStyles nonce={nonce}/>
|
|
22
|
-
<script type="module" nonce={nonce} async src={getSsrManifest("client").path(import.meta.env.
|
|
22
|
+
<script type="module" nonce={nonce} async src={getSsrManifest("client").path(import.meta.env.START_CLIENT_ENTRY_URL)}/>
|
|
23
23
|
</>}>
|
|
24
24
|
{!import.meta.env.START_ISLANDS ? (<Hydration>
|
|
25
25
|
<ErrorBoundary>
|
|
@@ -3,5 +3,15 @@ import type { FetchEvent } from "./types.ts";
|
|
|
3
3
|
export declare function createFetchEvent(event: H3Event): FetchEvent;
|
|
4
4
|
export declare function getFetchEvent(h3Event: H3Event): FetchEvent;
|
|
5
5
|
export declare function mergeResponseHeaders(h3Event: H3Event, headers: Headers): void;
|
|
6
|
+
/**
|
|
7
|
+
* Wrap an h3 event handler so Solid's request context is available while it runs.
|
|
8
|
+
*
|
|
9
|
+
* @experimental
|
|
10
|
+
*/
|
|
6
11
|
export declare const decorateHandler: <T extends EventHandler>(fn: T) => T;
|
|
12
|
+
/**
|
|
13
|
+
* Wrap h3 middleware so Solid's request context is available while it runs.
|
|
14
|
+
*
|
|
15
|
+
* @experimental
|
|
16
|
+
*/
|
|
7
17
|
export declare const decorateMiddleware: <T extends Middleware>(fn: T) => T;
|
|
@@ -22,5 +22,15 @@ export function mergeResponseHeaders(h3Event, headers) {
|
|
|
22
22
|
h3Event.res.headers.append(key, value);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Wrap an h3 event handler so Solid's request context is available while it runs.
|
|
27
|
+
*
|
|
28
|
+
* @experimental
|
|
29
|
+
*/
|
|
25
30
|
export const decorateHandler = (fn) => (event => provideRequestEvent(getFetchEvent(event), () => fn(event)));
|
|
31
|
+
/**
|
|
32
|
+
* Wrap h3 middleware so Solid's request context is available while it runs.
|
|
33
|
+
*
|
|
34
|
+
* @experimental
|
|
35
|
+
*/
|
|
26
36
|
export const decorateMiddleware = (fn) => ((event, next) => provideRequestEvent(getFetchEvent(event), () => fn(event, next)));
|
package/dist/server/handler.js
CHANGED
|
@@ -8,6 +8,8 @@ import { getSsrManifest } from "./manifest/ssr-manifest.js";
|
|
|
8
8
|
import { matchAPIRoute } from "./routes.js";
|
|
9
9
|
import { handleServerFunction } from "../fns/handler.js";
|
|
10
10
|
import { getExpectedRedirectStatus } from "./util.js";
|
|
11
|
+
import { toWebReadableStream } from "./web-stream.js";
|
|
12
|
+
import { stripPathBase } from "./strip-path-base.js";
|
|
11
13
|
const SERVER_FN_BASE = "/_server";
|
|
12
14
|
export function createBaseHandler(createPageEvent, fn, options = {}) {
|
|
13
15
|
const handler = defineHandler({
|
|
@@ -93,14 +95,9 @@ export function createBaseHandler(createPageEvent, fn, options = {}) {
|
|
|
93
95
|
if (mode === "async")
|
|
94
96
|
return await stream;
|
|
95
97
|
delete stream.then;
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
return stream;
|
|
100
|
-
// returning stream directly breaks cloudflare workers
|
|
101
|
-
const { writable, readable } = new TransformStream();
|
|
102
|
-
stream.pipeTo(writable);
|
|
103
|
-
return readable;
|
|
98
|
+
// h3 expects a standard web ReadableStream across runtimes. The adapter
|
|
99
|
+
// also tolerates cancellation while Solid finishes outstanding work.
|
|
100
|
+
return toWebReadableStream(stream);
|
|
104
101
|
}),
|
|
105
102
|
});
|
|
106
103
|
const app = new H3();
|
|
@@ -216,7 +213,6 @@ function produceResponseWithEventHeaders(res) {
|
|
|
216
213
|
return ret;
|
|
217
214
|
}
|
|
218
215
|
function stripBaseUrl(path) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
return path.slice(import.meta.env.BASE_URL.length);
|
|
216
|
+
const base = import.meta.env.SERVER_BASE_URL || import.meta.env.BASE_URL || "/";
|
|
217
|
+
return stripPathBase(path, base);
|
|
222
218
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { default as lazy } from "../shared/lazy.ts";
|
|
2
2
|
export { getServerFunctionMeta } from "../shared/serverFunction.ts";
|
|
3
3
|
export { StartServer } from "./StartServer.tsx";
|
|
4
|
+
export { decorateHandler, decorateMiddleware } from "./fetchEvent.ts";
|
|
4
5
|
export { createHandler } from "./handler.ts";
|
|
5
6
|
export type { APIEvent, APIHandler, Asset, ContextMatches, DocumentComponentProps, FetchEvent, HandlerOptions, PageEvent, ResponseStub, ServerFunctionMeta, } from "./types.ts";
|
|
6
7
|
/**
|
package/dist/server/index.jsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { default as lazy } from "../shared/lazy.js";
|
|
2
2
|
export { getServerFunctionMeta } from "../shared/serverFunction.js";
|
|
3
3
|
export { StartServer } from "./StartServer.jsx";
|
|
4
|
+
export { decorateHandler, decorateMiddleware } from "./fetchEvent.js";
|
|
4
5
|
export { createHandler } from "./handler.js";
|
|
5
6
|
/**
|
|
6
7
|
* Checks if user has set a redirect status in the response.
|
|
@@ -18,7 +18,7 @@ export function StartServer(props) {
|
|
|
18
18
|
<TopErrorBoundary>
|
|
19
19
|
<props.document scripts={<>
|
|
20
20
|
<PatchVirtualDevStyles nonce={nonce}/>
|
|
21
|
-
<script type="module" src={getSsrManifest("client").path(import.meta.env.
|
|
21
|
+
<script type="module" src={getSsrManifest("client").path(import.meta.env.START_CLIENT_ENTRY_URL)}/>
|
|
22
22
|
</>}/>
|
|
23
23
|
</TopErrorBoundary>
|
|
24
24
|
</NoHydration>);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function stripPathBase(path: string, base: string): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function stripPathBase(path, base) {
|
|
2
|
+
if (!base || base === "/")
|
|
3
|
+
return path;
|
|
4
|
+
const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
5
|
+
if (path === normalizedBase)
|
|
6
|
+
return "/";
|
|
7
|
+
if (path.startsWith(`${normalizedBase}/`))
|
|
8
|
+
return path.slice(normalizedBase.length);
|
|
9
|
+
return path;
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type PipeableStream = {
|
|
2
|
+
pipe(writable: {
|
|
3
|
+
write(payload: string): void;
|
|
4
|
+
end(): void;
|
|
5
|
+
}): void;
|
|
6
|
+
};
|
|
7
|
+
/** Convert Solid's streaming SSR result into a cancellation-safe web stream. */
|
|
8
|
+
export declare function toWebReadableStream(stream: PipeableStream): ReadableStream<Uint8Array>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Convert Solid's streaming SSR result into a cancellation-safe web stream. */
|
|
2
|
+
export function toWebReadableStream(stream) {
|
|
3
|
+
const encoder = new TextEncoder();
|
|
4
|
+
let active = true;
|
|
5
|
+
return new ReadableStream({
|
|
6
|
+
start(controller) {
|
|
7
|
+
stream.pipe({
|
|
8
|
+
write(payload) {
|
|
9
|
+
if (active)
|
|
10
|
+
controller.enqueue(encoder.encode(payload));
|
|
11
|
+
},
|
|
12
|
+
end() {
|
|
13
|
+
if (!active)
|
|
14
|
+
return;
|
|
15
|
+
active = false;
|
|
16
|
+
controller.close();
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
cancel() {
|
|
21
|
+
// Solid may still resolve Suspense resources after the response is
|
|
22
|
+
// cancelled. Ignore those writes and let Solid finish its cleanup.
|
|
23
|
+
active = false;
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { createMemo, createResource } from "solid-js";
|
|
2
2
|
import getSourceMap from "./get-source-map.js";
|
|
3
|
+
const HTTP_URL_REGEX = /^https?:\/\//;
|
|
4
|
+
const LEADING_SLASH_REGEX = /^\/+/;
|
|
5
|
+
// Frames from an SSR error carry filesystem paths (or file:// URLs) rather
|
|
6
|
+
// than the http URL the module is served from. Their positions are already
|
|
7
|
+
// mapped back to the original source by the Vite module runner, unlike
|
|
8
|
+
// client frames whose positions refer to the compiled module.
|
|
9
|
+
function isServerSource(path) {
|
|
10
|
+
return !HTTP_URL_REGEX.test(path);
|
|
11
|
+
}
|
|
3
12
|
function getActualFileSource(path) {
|
|
4
13
|
if (path.startsWith("file://")) {
|
|
5
|
-
return "
|
|
14
|
+
return "/@fs/" + path.substring("file://".length).replace(LEADING_SLASH_REGEX, "");
|
|
15
|
+
}
|
|
16
|
+
if (isServerSource(path)) {
|
|
17
|
+
return "/@fs/" + path.replace(LEADING_SLASH_REGEX, "");
|
|
6
18
|
}
|
|
7
19
|
return path;
|
|
8
20
|
}
|
|
@@ -16,16 +28,18 @@ export function createStackFrame(stackframe, isCompiled) {
|
|
|
16
28
|
if (!source.fileName) {
|
|
17
29
|
return null;
|
|
18
30
|
}
|
|
19
|
-
const
|
|
31
|
+
const url = getActualFileSource(source.fileName);
|
|
32
|
+
const response = await fetch(url);
|
|
20
33
|
if (!response.ok) {
|
|
21
34
|
return null;
|
|
22
35
|
}
|
|
23
36
|
const content = await response.text();
|
|
24
|
-
const sourceMap = await getSourceMap(
|
|
37
|
+
const sourceMap = await getSourceMap(url, content);
|
|
25
38
|
return {
|
|
26
39
|
source,
|
|
27
40
|
content,
|
|
28
41
|
sourceMap,
|
|
42
|
+
isServer: isServerSource(source.fileName),
|
|
29
43
|
};
|
|
30
44
|
});
|
|
31
45
|
const info = createMemo(() => {
|
|
@@ -33,16 +47,36 @@ export function createStackFrame(stackframe, isCompiled) {
|
|
|
33
47
|
if (!current) {
|
|
34
48
|
return undefined;
|
|
35
49
|
}
|
|
36
|
-
const { source, content, sourceMap } = current;
|
|
50
|
+
const { source, content, sourceMap, isServer } = current;
|
|
37
51
|
if (!isCompiled() && source.line && source.column && sourceMap) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
if (isServer) {
|
|
53
|
+
// The position is already original; only the original content needs
|
|
54
|
+
// to be pulled out of the source map.
|
|
55
|
+
const originalContent = sourceMap.sources.length
|
|
56
|
+
? sourceMap.sourceContentFor(sourceMap.sources[0], true)
|
|
57
|
+
: null;
|
|
58
|
+
if (originalContent) {
|
|
59
|
+
return {
|
|
60
|
+
source: source.fileName,
|
|
61
|
+
line: source.line,
|
|
62
|
+
column: source.column,
|
|
63
|
+
name: source.functionName,
|
|
64
|
+
content: originalContent,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const result = sourceMap.originalPositionFor({
|
|
70
|
+
line: source.line,
|
|
71
|
+
column: source.column,
|
|
72
|
+
});
|
|
73
|
+
if (result.source) {
|
|
74
|
+
return {
|
|
75
|
+
...result,
|
|
76
|
+
content: sourceMap.sourceContentFor(result.source, true),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
46
80
|
}
|
|
47
81
|
return {
|
|
48
82
|
source: source.fileName,
|