@chidchanun/bcp 0.1.4 → 0.1.6
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 +28 -0
- package/README.md +39 -1
- package/docs/application-modules.md +116 -0
- package/docs/development-logging.md +13 -0
- package/package.json +6 -1
- package/packages/bundler/src/application-modules.ts +771 -0
- package/packages/bundler/src/client-boundary.ts +484 -0
- package/packages/cli/src/index.ts +36 -5
- package/packages/client/src/server-only.browser.mjs +7 -0
- package/packages/client/src/server-only.d.ts +1 -0
- package/packages/client/src/server-only.mjs +1 -0
- package/packages/server/src/critical-css-proxy.ts +559 -0
- package/packages/server/src/critical-css.ts +132 -0
- package/packages/server/src/dev-hmr.ts +86 -0
- package/packages/server/src/middleware-dev-server.ts +59 -3
- package/packages/server/src/standalone-production-runtime-v6.ts +205 -0
- package/packages/server/src/standalone-production-server.ts +1 -1
|
@@ -6,12 +6,98 @@ export const DEV_EVENTS_PATH =
|
|
|
6
6
|
const ENV_RESTART_COOKIE =
|
|
7
7
|
"__bcp_env_restart";
|
|
8
8
|
|
|
9
|
+
const INTERNAL_API_PATHS =
|
|
10
|
+
new Set([
|
|
11
|
+
"/api/bcp-styles",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const HTTP_REQUEST_LOG_PATTERN =
|
|
15
|
+
/^(?:\[bcp\]\s+)?(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(\S+)\s+\d{3}\s+\d+(?:\.\d+)?ms$/i;
|
|
16
|
+
|
|
17
|
+
let requestLogFilterInstalled =
|
|
18
|
+
false;
|
|
19
|
+
|
|
20
|
+
export function shouldLogDevApiPath(
|
|
21
|
+
pathname: string
|
|
22
|
+
): boolean {
|
|
23
|
+
const normalized =
|
|
24
|
+
pathname.split(
|
|
25
|
+
"?",
|
|
26
|
+
1
|
|
27
|
+
)[0];
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
(
|
|
31
|
+
normalized === "/api" ||
|
|
32
|
+
normalized.startsWith(
|
|
33
|
+
"/api/"
|
|
34
|
+
)
|
|
35
|
+
) &&
|
|
36
|
+
!INTERNAL_API_PATHS.has(
|
|
37
|
+
normalized
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function shouldWriteDevConsoleLine(
|
|
43
|
+
line: string
|
|
44
|
+
): boolean {
|
|
45
|
+
const match =
|
|
46
|
+
HTTP_REQUEST_LOG_PATTERN.exec(
|
|
47
|
+
line.trim()
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (!match) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return shouldLogDevApiPath(
|
|
55
|
+
match[1]
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function installRequestLogFilter(): void {
|
|
60
|
+
if (requestLogFilterInstalled) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
requestLogFilterInstalled =
|
|
65
|
+
true;
|
|
66
|
+
|
|
67
|
+
const originalLog =
|
|
68
|
+
console.log.bind(
|
|
69
|
+
console
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
console.log = (
|
|
73
|
+
...args: unknown[]
|
|
74
|
+
) => {
|
|
75
|
+
if (
|
|
76
|
+
args.length === 1 &&
|
|
77
|
+
typeof args[0] === "string" &&
|
|
78
|
+
!shouldWriteDevConsoleLine(
|
|
79
|
+
args[0]
|
|
80
|
+
)
|
|
81
|
+
) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
originalLog(
|
|
86
|
+
...args
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
9
91
|
export class DevHmrServer {
|
|
10
92
|
private readonly clients =
|
|
11
93
|
new Set<
|
|
12
94
|
http.ServerResponse
|
|
13
95
|
>();
|
|
14
96
|
|
|
97
|
+
constructor() {
|
|
98
|
+
installRequestLogFilter();
|
|
99
|
+
}
|
|
100
|
+
|
|
15
101
|
connect(
|
|
16
102
|
req: http.IncomingMessage,
|
|
17
103
|
res: http.ServerResponse
|
|
@@ -5,6 +5,10 @@ import type {
|
|
|
5
5
|
DevServerOptions,
|
|
6
6
|
} from "./index.js";
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
createCriticalCssProxy,
|
|
10
|
+
} from "./critical-css-proxy.js";
|
|
11
|
+
|
|
8
12
|
import {
|
|
9
13
|
loadProjectMiddleware,
|
|
10
14
|
} from "./middleware-loader.js";
|
|
@@ -56,6 +60,12 @@ export function createMiddlewareDevServer(
|
|
|
56
60
|
> | null =
|
|
57
61
|
null;
|
|
58
62
|
|
|
63
|
+
let criticalCssGateway:
|
|
64
|
+
ReturnType<
|
|
65
|
+
typeof createCriticalCssProxy
|
|
66
|
+
> | null =
|
|
67
|
+
null;
|
|
68
|
+
|
|
59
69
|
let started =
|
|
60
70
|
false;
|
|
61
71
|
|
|
@@ -68,6 +78,8 @@ export function createMiddlewareDevServer(
|
|
|
68
78
|
await findFreePort();
|
|
69
79
|
const middlewarePort =
|
|
70
80
|
await findFreePort();
|
|
81
|
+
const securityPort =
|
|
82
|
+
await findFreePort();
|
|
71
83
|
|
|
72
84
|
internalServer =
|
|
73
85
|
createStaticDevServer({
|
|
@@ -111,8 +123,10 @@ export function createMiddlewareDevServer(
|
|
|
111
123
|
|
|
112
124
|
securityGateway =
|
|
113
125
|
createSecurityProxy({
|
|
114
|
-
port
|
|
115
|
-
|
|
126
|
+
port:
|
|
127
|
+
securityPort,
|
|
128
|
+
hostname:
|
|
129
|
+
"127.0.0.1",
|
|
116
130
|
upstreamPort:
|
|
117
131
|
middlewarePort,
|
|
118
132
|
upstreamHostname:
|
|
@@ -133,12 +147,48 @@ export function createMiddlewareDevServer(
|
|
|
133
147
|
throw error;
|
|
134
148
|
}
|
|
135
149
|
|
|
150
|
+
criticalCssGateway =
|
|
151
|
+
createCriticalCssProxy({
|
|
152
|
+
port,
|
|
153
|
+
hostname,
|
|
154
|
+
upstreamPort:
|
|
155
|
+
securityPort,
|
|
156
|
+
upstreamHostname:
|
|
157
|
+
"127.0.0.1",
|
|
158
|
+
cssFile:
|
|
159
|
+
path.join(
|
|
160
|
+
rootDirectory,
|
|
161
|
+
"public",
|
|
162
|
+
"bcp.css"
|
|
163
|
+
),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
await criticalCssGateway.start();
|
|
168
|
+
} catch (error) {
|
|
169
|
+
await securityGateway.stop();
|
|
170
|
+
await middlewareGateway.stop();
|
|
171
|
+
await internalServer.stop();
|
|
172
|
+
internalServer =
|
|
173
|
+
null;
|
|
174
|
+
middlewareGateway =
|
|
175
|
+
null;
|
|
176
|
+
securityGateway =
|
|
177
|
+
null;
|
|
178
|
+
criticalCssGateway =
|
|
179
|
+
null;
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
|
|
136
183
|
started =
|
|
137
184
|
true;
|
|
138
185
|
|
|
139
186
|
console.log(
|
|
140
187
|
`[BCP Security] Dev gateway: http://${hostname}:${port}`
|
|
141
188
|
);
|
|
189
|
+
console.log(
|
|
190
|
+
`[BCP CSS] Inline /bcp.css when <= 8 KiB and allowed by CSP.`
|
|
191
|
+
);
|
|
142
192
|
console.log(
|
|
143
193
|
`[BCP Middleware] File: ${path.join(rootDirectory, "middleware.ts")} (optional; .tsx/.js/.jsx also supported)`
|
|
144
194
|
);
|
|
@@ -146,6 +196,12 @@ export function createMiddlewareDevServer(
|
|
|
146
196
|
}
|
|
147
197
|
|
|
148
198
|
async function stop(): Promise<void> {
|
|
199
|
+
if (criticalCssGateway) {
|
|
200
|
+
await criticalCssGateway.stop();
|
|
201
|
+
criticalCssGateway =
|
|
202
|
+
null;
|
|
203
|
+
}
|
|
204
|
+
|
|
149
205
|
if (securityGateway) {
|
|
150
206
|
await securityGateway.stop();
|
|
151
207
|
securityGateway =
|
|
@@ -257,4 +313,4 @@ async function findFreePort(): Promise<number> {
|
|
|
257
313
|
);
|
|
258
314
|
}
|
|
259
315
|
}
|
|
260
|
-
}
|
|
316
|
+
}
|
|
@@ -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
|
+
}
|