@chidchanun/bcp 0.1.5 → 0.1.7
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 +47 -1
- package/README.md +83 -1
- package/docs/server-request-apis.md +404 -0
- package/package.json +6 -1
- package/packages/bundler/src/client-boundary.ts +8 -6
- package/packages/bundler/src/server-production.ts +17 -0
- package/packages/client/src/server.ts +23 -0
- package/packages/server/src/critical-css-proxy.ts +559 -0
- package/packages/server/src/critical-css.ts +132 -0
- package/packages/server/src/index.ts +32 -13
- package/packages/server/src/middleware-dev-server.ts +59 -3
- package/packages/server/src/request-context.ts +1043 -0
- package/packages/server/src/server-response.ts +82 -0
- package/packages/server/src/standalone-production-runtime-v2.ts +44 -13
- package/packages/server/src/standalone-production-runtime-v6.ts +205 -0
- package/packages/server/src/standalone-production-server.ts +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
requestUrl,
|
|
3
|
+
} from "./request-context.js";
|
|
4
|
+
|
|
5
|
+
export type RedirectStatus =
|
|
6
|
+
| 301
|
|
7
|
+
| 302
|
|
8
|
+
| 303
|
|
9
|
+
| 307
|
|
10
|
+
| 308;
|
|
11
|
+
|
|
12
|
+
export function json<T>(
|
|
13
|
+
data: T,
|
|
14
|
+
init?: ResponseInit
|
|
15
|
+
): Response {
|
|
16
|
+
return Response.json(
|
|
17
|
+
data,
|
|
18
|
+
init
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function redirect(
|
|
23
|
+
destination:
|
|
24
|
+
string |
|
|
25
|
+
URL,
|
|
26
|
+
status:
|
|
27
|
+
RedirectStatus =
|
|
28
|
+
307
|
|
29
|
+
): Promise<Response> {
|
|
30
|
+
assertRedirectStatus(
|
|
31
|
+
status
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const base =
|
|
35
|
+
await requestUrl();
|
|
36
|
+
|
|
37
|
+
const target =
|
|
38
|
+
destination instanceof URL
|
|
39
|
+
? new URL(
|
|
40
|
+
destination.href
|
|
41
|
+
)
|
|
42
|
+
: new URL(
|
|
43
|
+
destination,
|
|
44
|
+
base
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
target.protocol !== "http:" &&
|
|
49
|
+
target.protocol !== "https:"
|
|
50
|
+
) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`BCP Framework: redirect only supports http and https URLs, received "${target.protocol}".`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return new Response(
|
|
57
|
+
null,
|
|
58
|
+
{
|
|
59
|
+
status,
|
|
60
|
+
headers: {
|
|
61
|
+
Location:
|
|
62
|
+
target.href,
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function assertRedirectStatus(
|
|
69
|
+
status: number
|
|
70
|
+
): asserts status is RedirectStatus {
|
|
71
|
+
if (
|
|
72
|
+
status !== 301 &&
|
|
73
|
+
status !== 302 &&
|
|
74
|
+
status !== 303 &&
|
|
75
|
+
status !== 307 &&
|
|
76
|
+
status !== 308
|
|
77
|
+
) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`BCP Framework: invalid redirect status ${status}. Use 301, 302, 303, 307 or 308.`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -49,6 +49,11 @@ import {
|
|
|
49
49
|
type MetadataSource,
|
|
50
50
|
} from "./metadata.js";
|
|
51
51
|
|
|
52
|
+
import {
|
|
53
|
+
applyResponseCookies,
|
|
54
|
+
runWithRequestContext,
|
|
55
|
+
} from "./request-context.ts";
|
|
56
|
+
|
|
52
57
|
import {
|
|
53
58
|
getContentType,
|
|
54
59
|
readPublicAsset,
|
|
@@ -1405,7 +1410,8 @@ async function handleApiRoute(
|
|
|
1405
1410
|
|
|
1406
1411
|
if (
|
|
1407
1412
|
!handler &&
|
|
1408
|
-
method ===
|
|
1413
|
+
method ===
|
|
1414
|
+
"HEAD"
|
|
1409
1415
|
) {
|
|
1410
1416
|
handler =
|
|
1411
1417
|
getApiHandler(
|
|
@@ -1434,23 +1440,48 @@ async function handleApiRoute(
|
|
|
1434
1440
|
return;
|
|
1435
1441
|
}
|
|
1436
1442
|
|
|
1443
|
+
const request =
|
|
1444
|
+
await createWebRequest(
|
|
1445
|
+
req,
|
|
1446
|
+
requestUrl
|
|
1447
|
+
);
|
|
1448
|
+
|
|
1437
1449
|
const response =
|
|
1438
|
-
await
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1450
|
+
await runWithRequestContext(
|
|
1451
|
+
request,
|
|
1452
|
+
|
|
1453
|
+
async () => {
|
|
1454
|
+
const result =
|
|
1455
|
+
await handler(
|
|
1456
|
+
request,
|
|
1457
|
+
{
|
|
1458
|
+
params,
|
|
1459
|
+
}
|
|
1460
|
+
);
|
|
1461
|
+
|
|
1462
|
+
if (
|
|
1463
|
+
!(
|
|
1464
|
+
result instanceof
|
|
1465
|
+
Response
|
|
1466
|
+
)
|
|
1467
|
+
) {
|
|
1468
|
+
throw new Error(
|
|
1469
|
+
`API handler "${method} ${definition.pathname}" must return a Response object.`
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
return applyResponseCookies(
|
|
1474
|
+
result
|
|
1475
|
+
);
|
|
1476
|
+
},
|
|
1443
1477
|
{
|
|
1444
|
-
|
|
1478
|
+
remoteAddress:
|
|
1479
|
+
req.socket
|
|
1480
|
+
.remoteAddress ??
|
|
1481
|
+
null,
|
|
1445
1482
|
}
|
|
1446
1483
|
);
|
|
1447
1484
|
|
|
1448
|
-
if (!(response instanceof Response)) {
|
|
1449
|
-
throw new Error(
|
|
1450
|
-
`API handler "${method} ${definition.pathname}" must return a Response object.`
|
|
1451
|
-
);
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
1485
|
await sendWebResponse(
|
|
1455
1486
|
req,
|
|
1456
1487
|
res,
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import * as net from "node:net";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createCriticalCssProxy,
|
|
6
|
+
} from "./critical-css-proxy.js";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
createStandaloneProductionServer as createBaseStandaloneProductionServer,
|
|
10
|
+
type StandaloneApiRoute,
|
|
11
|
+
type StandalonePageRoute,
|
|
12
|
+
type StandaloneProductionServerOptions as BaseStandaloneProductionServerOptions,
|
|
13
|
+
} from "./standalone-production-runtime-v5.js";
|
|
14
|
+
|
|
15
|
+
export type {
|
|
16
|
+
StandaloneApiRoute,
|
|
17
|
+
StandalonePageRoute,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface StandaloneProductionServerOptions
|
|
21
|
+
extends BaseStandaloneProductionServerOptions {}
|
|
22
|
+
|
|
23
|
+
export function createStandaloneProductionServer(
|
|
24
|
+
options: StandaloneProductionServerOptions
|
|
25
|
+
) {
|
|
26
|
+
let baseServer:
|
|
27
|
+
ReturnType<
|
|
28
|
+
typeof createBaseStandaloneProductionServer
|
|
29
|
+
> | null =
|
|
30
|
+
null;
|
|
31
|
+
|
|
32
|
+
let criticalCssProxy:
|
|
33
|
+
ReturnType<
|
|
34
|
+
typeof createCriticalCssProxy
|
|
35
|
+
> | null =
|
|
36
|
+
null;
|
|
37
|
+
|
|
38
|
+
let started =
|
|
39
|
+
false;
|
|
40
|
+
|
|
41
|
+
async function start(): Promise<void> {
|
|
42
|
+
if (started) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const internalPort =
|
|
47
|
+
await findFreePort();
|
|
48
|
+
|
|
49
|
+
baseServer =
|
|
50
|
+
createBaseStandaloneProductionServer({
|
|
51
|
+
...options,
|
|
52
|
+
port:
|
|
53
|
+
internalPort,
|
|
54
|
+
hostname:
|
|
55
|
+
"127.0.0.1",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await baseServer.start();
|
|
59
|
+
|
|
60
|
+
criticalCssProxy =
|
|
61
|
+
createCriticalCssProxy({
|
|
62
|
+
port:
|
|
63
|
+
options.port,
|
|
64
|
+
hostname:
|
|
65
|
+
options.hostname,
|
|
66
|
+
upstreamPort:
|
|
67
|
+
internalPort,
|
|
68
|
+
upstreamHostname:
|
|
69
|
+
"127.0.0.1",
|
|
70
|
+
cssFile:
|
|
71
|
+
path.join(
|
|
72
|
+
options.buildDirectory,
|
|
73
|
+
"public",
|
|
74
|
+
"bcp.css"
|
|
75
|
+
),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await criticalCssProxy.start();
|
|
80
|
+
} catch (error) {
|
|
81
|
+
await baseServer.stop();
|
|
82
|
+
baseServer =
|
|
83
|
+
null;
|
|
84
|
+
criticalCssProxy =
|
|
85
|
+
null;
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
started =
|
|
90
|
+
true;
|
|
91
|
+
|
|
92
|
+
console.log(
|
|
93
|
+
`[BCP CSS] Inline /bcp.css when <= 8 KiB.`
|
|
94
|
+
);
|
|
95
|
+
console.log("");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function stop(): Promise<void> {
|
|
99
|
+
if (criticalCssProxy) {
|
|
100
|
+
await criticalCssProxy.stop();
|
|
101
|
+
criticalCssProxy =
|
|
102
|
+
null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (baseServer) {
|
|
106
|
+
await baseServer.stop();
|
|
107
|
+
baseServer =
|
|
108
|
+
null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
started =
|
|
112
|
+
false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
start,
|
|
117
|
+
stop,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function findFreePort(): Promise<number> {
|
|
122
|
+
const server =
|
|
123
|
+
net.createServer();
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
await new Promise<void>(
|
|
127
|
+
(
|
|
128
|
+
resolve,
|
|
129
|
+
reject
|
|
130
|
+
) => {
|
|
131
|
+
const onError =
|
|
132
|
+
(error: Error) => {
|
|
133
|
+
server.off(
|
|
134
|
+
"listening",
|
|
135
|
+
onListening
|
|
136
|
+
);
|
|
137
|
+
reject(
|
|
138
|
+
error
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const onListening =
|
|
143
|
+
() => {
|
|
144
|
+
server.off(
|
|
145
|
+
"error",
|
|
146
|
+
onError
|
|
147
|
+
);
|
|
148
|
+
resolve();
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
server.once(
|
|
152
|
+
"error",
|
|
153
|
+
onError
|
|
154
|
+
);
|
|
155
|
+
server.once(
|
|
156
|
+
"listening",
|
|
157
|
+
onListening
|
|
158
|
+
);
|
|
159
|
+
server.listen(
|
|
160
|
+
0,
|
|
161
|
+
"127.0.0.1"
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const address =
|
|
167
|
+
server.address();
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
!address ||
|
|
171
|
+
typeof address ===
|
|
172
|
+
"string"
|
|
173
|
+
) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"BCP Framework: could not allocate critical CSS production port."
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return address.port;
|
|
180
|
+
} finally {
|
|
181
|
+
if (
|
|
182
|
+
server.listening
|
|
183
|
+
) {
|
|
184
|
+
await new Promise<void>(
|
|
185
|
+
(
|
|
186
|
+
resolve,
|
|
187
|
+
reject
|
|
188
|
+
) => {
|
|
189
|
+
server.close(
|
|
190
|
+
(error) => {
|
|
191
|
+
if (error) {
|
|
192
|
+
reject(
|
|
193
|
+
error
|
|
194
|
+
);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
resolve();
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|