@chidchanun/bcp 0.1.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +241 -0
  4. package/docs/caching.md +76 -0
  5. package/docs/configuration.md +97 -0
  6. package/docs/deployment.md +74 -0
  7. package/docs/getting-started.md +82 -0
  8. package/docs/middleware.md +58 -0
  9. package/docs/releasing.md +299 -0
  10. package/docs/routing.md +103 -0
  11. package/docs/security.md +57 -0
  12. package/package.json +68 -0
  13. package/packages/bundler/src/client-islands.ts +1457 -0
  14. package/packages/bundler/src/incremental-context.ts +206 -0
  15. package/packages/bundler/src/index.ts +1991 -0
  16. package/packages/bundler/src/module-graph.ts +317 -0
  17. package/packages/bundler/src/partial-hydration.ts +414 -0
  18. package/packages/bundler/src/production.ts +974 -0
  19. package/packages/bundler/src/server-production-middleware.ts +447 -0
  20. package/packages/bundler/src/server-production.ts +1193 -0
  21. package/packages/bundler/src/special-files.ts +131 -0
  22. package/packages/cache/src/index.ts +761 -0
  23. package/packages/cli/bin/bcp.mjs +93 -0
  24. package/packages/cli/src/args.ts +305 -0
  25. package/packages/cli/src/bootstrap.ts +514 -0
  26. package/packages/cli/src/index.ts +504 -0
  27. package/packages/cli/src/version.ts +45 -0
  28. package/packages/client/src/cache.ts +11 -0
  29. package/packages/client/src/config.ts +18 -0
  30. package/packages/client/src/error-boundary.tsx +149 -0
  31. package/packages/client/src/hydration.ts +3 -0
  32. package/packages/client/src/index.tsx +57 -0
  33. package/packages/client/src/islands.tsx +315 -0
  34. package/packages/client/src/metadata.ts +281 -0
  35. package/packages/client/src/navigation-loading.ts +52 -0
  36. package/packages/client/src/navigation-state.ts +80 -0
  37. package/packages/client/src/not-found.ts +34 -0
  38. package/packages/client/src/persistent-layout-runtime.ts +273 -0
  39. package/packages/client/src/router-v2.tsx +969 -0
  40. package/packages/client/src/router.tsx +1 -0
  41. package/packages/config/src/index.ts +1038 -0
  42. package/packages/env/src/index.ts +593 -0
  43. package/packages/router/src/advanced-router.ts +1032 -0
  44. package/packages/router/src/index.ts +1 -0
  45. package/packages/server/src/compression.ts +249 -0
  46. package/packages/server/src/dev-document-metadata.ts +154 -0
  47. package/packages/server/src/dev-hmr.ts +225 -0
  48. package/packages/server/src/index.ts +2265 -0
  49. package/packages/server/src/metadata.ts +478 -0
  50. package/packages/server/src/middleware-dev-server.ts +260 -0
  51. package/packages/server/src/middleware-loader.ts +140 -0
  52. package/packages/server/src/middleware-proxy.ts +516 -0
  53. package/packages/server/src/middleware.ts +704 -0
  54. package/packages/server/src/navigation-payload.ts +471 -0
  55. package/packages/server/src/production-server.ts +1746 -0
  56. package/packages/server/src/response-cache-proxy.ts +828 -0
  57. package/packages/server/src/security-proxy.ts +406 -0
  58. package/packages/server/src/security.ts +451 -0
  59. package/packages/server/src/standalone-production-runtime-v2.ts +2047 -0
  60. package/packages/server/src/standalone-production-runtime-v3.ts +250 -0
  61. package/packages/server/src/standalone-production-runtime-v4.ts +289 -0
  62. package/packages/server/src/standalone-production-runtime-v5.ts +289 -0
  63. package/packages/server/src/standalone-production-runtime.ts +1951 -0
  64. package/packages/server/src/standalone-production-server.ts +6 -0
  65. package/packages/server/src/static-assets.ts +262 -0
  66. package/packages/server/src/static-dev-server.ts +854 -0
@@ -0,0 +1,57 @@
1
+ import "./persistent-layout-runtime.js";
2
+
3
+ export {
4
+ Link,
5
+ navigate,
6
+ useRouter,
7
+ type LinkProps,
8
+ type NavigationOptions,
9
+ type Router,
10
+ } from "./router-v2.js";
11
+
12
+ export {
13
+ useNavigation,
14
+ type NavigationState,
15
+ } from "./navigation-state.js";
16
+
17
+ export {
18
+ createIsland,
19
+ type BcpIslandMetadata,
20
+ type IslandComponent,
21
+ type IslandHydrationStrategy,
22
+ type IslandOptions,
23
+ type IslandPrefetchStrategy,
24
+ } from "./islands.js";
25
+
26
+ export {
27
+ isNotFoundError,
28
+ notFound,
29
+ type BcpNotFoundError,
30
+ } from "./not-found.js";
31
+
32
+ export {
33
+ BcpRouteErrorBoundary,
34
+ type BcpErrorComponentProps,
35
+ type BcpRouteErrorBoundaryProps,
36
+ } from "./error-boundary.js";
37
+
38
+ export {
39
+ applyResolvedMetadata,
40
+ createMetadataHeadEntries,
41
+ type GenerateMetadata,
42
+ type GenerateMetadataContext,
43
+ type Metadata,
44
+ type MetadataAlternates,
45
+ type MetadataHeadEntry,
46
+ type MetadataIcons,
47
+ type MetadataOpenGraph,
48
+ type MetadataRobots,
49
+ type MetadataRouteParam,
50
+ type ResolvedMetadata,
51
+ type ResolvedMetadataIcons,
52
+ type ResolvedMetadataOpenGraph,
53
+ } from "./metadata.js";
54
+
55
+ export type {
56
+ HydrationMode,
57
+ } from "./hydration.js";
@@ -0,0 +1,315 @@
1
+ import {
2
+ createElement,
3
+ type ComponentType,
4
+ type ReactElement,
5
+ } from "react";
6
+
7
+ export type IslandHydrationStrategy =
8
+ | "load"
9
+ | "idle"
10
+ | "visible"
11
+ | "media";
12
+
13
+ export type IslandPrefetchStrategy =
14
+ | "none"
15
+ | "idle"
16
+ | "visible"
17
+ | "interaction";
18
+
19
+ export interface IslandOptions {
20
+ hydrate?: IslandHydrationStrategy;
21
+ media?: string;
22
+ prefetch?: IslandPrefetchStrategy;
23
+ preload?: boolean;
24
+ }
25
+
26
+ export interface BcpIslandMetadata<
27
+ Props extends object
28
+ > {
29
+ id: string;
30
+ component: ComponentType<Props>;
31
+ hydrate: IslandHydrationStrategy;
32
+ media: string | null;
33
+ prefetch: IslandPrefetchStrategy;
34
+ preload: boolean;
35
+ }
36
+
37
+ export type IslandComponent<
38
+ Props extends object
39
+ > = ComponentType<Props> & {
40
+ __BCP_ISLAND__:
41
+ BcpIslandMetadata<Props>;
42
+ };
43
+
44
+ export function createIsland<
45
+ Props extends object
46
+ >(
47
+ id: string,
48
+ Component: ComponentType<Props>,
49
+ options: IslandOptions = {}
50
+ ): IslandComponent<Props> {
51
+ assertIslandId(
52
+ id
53
+ );
54
+
55
+ const loading =
56
+ resolveIslandOptions(
57
+ id,
58
+ options
59
+ );
60
+
61
+ function Island(
62
+ props: Props
63
+ ): ReactElement {
64
+ const serializedProps =
65
+ serializeIslandProps(
66
+ props,
67
+ id
68
+ );
69
+
70
+ return createElement(
71
+ "div",
72
+ {
73
+ "data-bcp-island":
74
+ id,
75
+ "data-bcp-island-props":
76
+ serializedProps,
77
+ "data-bcp-island-hydrate":
78
+ loading.hydrate,
79
+ "data-bcp-island-prefetch":
80
+ loading.prefetch,
81
+ "data-bcp-island-preload":
82
+ loading.preload
83
+ ? "true"
84
+ : "false",
85
+ ...(
86
+ loading.media
87
+ ? {
88
+ "data-bcp-island-media":
89
+ loading.media,
90
+ }
91
+ : {}
92
+ ),
93
+ },
94
+ createElement(
95
+ Component,
96
+ props
97
+ )
98
+ );
99
+ }
100
+
101
+ const island =
102
+ Island as
103
+ IslandComponent<Props>;
104
+
105
+ island.__BCP_ISLAND__ = {
106
+ id,
107
+ component:
108
+ Component,
109
+ hydrate:
110
+ loading.hydrate,
111
+ media:
112
+ loading.media,
113
+ prefetch:
114
+ loading.prefetch,
115
+ preload:
116
+ loading.preload,
117
+ };
118
+
119
+ return island;
120
+ }
121
+
122
+ function resolveIslandOptions(
123
+ islandId: string,
124
+ options: IslandOptions
125
+ ): {
126
+ hydrate: IslandHydrationStrategy;
127
+ media: string | null;
128
+ prefetch: IslandPrefetchStrategy;
129
+ preload: boolean;
130
+ } {
131
+ const hydrate =
132
+ options.hydrate ??
133
+ "load";
134
+
135
+ if (
136
+ hydrate !== "load" &&
137
+ hydrate !== "idle" &&
138
+ hydrate !== "visible" &&
139
+ hydrate !== "media"
140
+ ) {
141
+ throw new Error(
142
+ `BCP Framework: island "${islandId}" has an invalid hydration strategy.`
143
+ );
144
+ }
145
+
146
+ const prefetch =
147
+ options.prefetch ??
148
+ "none";
149
+
150
+ if (
151
+ prefetch !== "none" &&
152
+ prefetch !== "idle" &&
153
+ prefetch !== "visible" &&
154
+ prefetch !== "interaction"
155
+ ) {
156
+ throw new Error(
157
+ `BCP Framework: island "${islandId}" has an invalid prefetch strategy.`
158
+ );
159
+ }
160
+
161
+ const media =
162
+ options.media?.trim() ??
163
+ "";
164
+
165
+ if (
166
+ hydrate === "media"
167
+ ) {
168
+ if (!media) {
169
+ throw new Error(
170
+ `BCP Framework: island "${islandId}" uses hydrate: "media" and requires a non-empty media query.`
171
+ );
172
+ }
173
+ } else if (media) {
174
+ throw new Error(
175
+ `BCP Framework: island "${islandId}" defines a media query but hydrate is "${hydrate}". Use hydrate: "media".`
176
+ );
177
+ }
178
+
179
+ return {
180
+ hydrate,
181
+ media:
182
+ hydrate === "media"
183
+ ? media
184
+ : null,
185
+ prefetch,
186
+ preload:
187
+ options.preload ??
188
+ hydrate === "load",
189
+ };
190
+ }
191
+
192
+ function assertIslandId(
193
+ id: string
194
+ ): void {
195
+ if (
196
+ !/^[A-Za-z][A-Za-z0-9._-]*$/.test(
197
+ id
198
+ )
199
+ ) {
200
+ throw new Error(
201
+ `BCP Framework: island id "${id}" is invalid. Use letters, numbers, dots, underscores, or hyphens, and start with a letter.`
202
+ );
203
+ }
204
+ }
205
+
206
+ function serializeIslandProps(
207
+ props: object,
208
+ islandId: string
209
+ ): string {
210
+ assertSerializable(
211
+ props,
212
+ `island "${islandId}" props`,
213
+ new Set<object>()
214
+ );
215
+
216
+ const serialized =
217
+ JSON.stringify(
218
+ props
219
+ );
220
+
221
+ if (
222
+ typeof serialized !==
223
+ "string"
224
+ ) {
225
+ throw new Error(
226
+ `BCP Framework: island "${islandId}" props could not be serialized.`
227
+ );
228
+ }
229
+
230
+ return serialized;
231
+ }
232
+
233
+ function assertSerializable(
234
+ value: unknown,
235
+ path: string,
236
+ seen: Set<object>
237
+ ): void {
238
+ if (
239
+ value === null ||
240
+ typeof value === "string" ||
241
+ typeof value === "number" ||
242
+ typeof value === "boolean"
243
+ ) {
244
+ return;
245
+ }
246
+
247
+ if (
248
+ typeof value === "undefined" ||
249
+ typeof value === "function" ||
250
+ typeof value === "symbol" ||
251
+ typeof value === "bigint"
252
+ ) {
253
+ throw new Error(
254
+ `BCP Framework: ${path} must be JSON-serializable.`
255
+ );
256
+ }
257
+
258
+ if (
259
+ typeof value !== "object"
260
+ ) {
261
+ return;
262
+ }
263
+
264
+ if (
265
+ seen.has(
266
+ value
267
+ )
268
+ ) {
269
+ throw new Error(
270
+ `BCP Framework: ${path} contains a circular reference.`
271
+ );
272
+ }
273
+
274
+ seen.add(
275
+ value
276
+ );
277
+
278
+ if (
279
+ Array.isArray(
280
+ value
281
+ )
282
+ ) {
283
+ value.forEach(
284
+ (
285
+ item,
286
+ index
287
+ ) =>
288
+ assertSerializable(
289
+ item,
290
+ `${path}[${index}]`,
291
+ seen
292
+ )
293
+ );
294
+ } else {
295
+ for (
296
+ const [
297
+ key,
298
+ item,
299
+ ]
300
+ of Object.entries(
301
+ value
302
+ )
303
+ ) {
304
+ assertSerializable(
305
+ item,
306
+ `${path}.${key}`,
307
+ seen
308
+ );
309
+ }
310
+ }
311
+
312
+ seen.delete(
313
+ value
314
+ );
315
+ }
@@ -0,0 +1,281 @@
1
+ export interface MetadataRobots {
2
+ index?: boolean;
3
+ follow?: boolean;
4
+ noarchive?: boolean;
5
+ nosnippet?: boolean;
6
+ noimageindex?: boolean;
7
+ }
8
+
9
+ export interface MetadataOpenGraph {
10
+ title?: string;
11
+ description?: string;
12
+ url?: string;
13
+ type?: string;
14
+ siteName?: string;
15
+ images?: string | string[];
16
+ }
17
+
18
+ export interface MetadataIcons {
19
+ icon?: string;
20
+ shortcut?: string;
21
+ apple?: string;
22
+ }
23
+
24
+ export interface MetadataAlternates {
25
+ canonical?: string;
26
+ }
27
+
28
+ export interface Metadata {
29
+ title?: string;
30
+ description?: string;
31
+ robots?: string | MetadataRobots;
32
+ alternates?: MetadataAlternates;
33
+ openGraph?: MetadataOpenGraph;
34
+ icons?: string | MetadataIcons;
35
+ }
36
+
37
+ export interface ResolvedMetadataOpenGraph {
38
+ title: string | null;
39
+ description: string | null;
40
+ url: string | null;
41
+ type: string | null;
42
+ siteName: string | null;
43
+ images: string[];
44
+ }
45
+
46
+ export interface ResolvedMetadataIcons {
47
+ icon: string | null;
48
+ shortcut: string | null;
49
+ apple: string | null;
50
+ }
51
+
52
+ export interface ResolvedMetadata {
53
+ title: string;
54
+ description: string | null;
55
+ robots: string | null;
56
+ canonical: string | null;
57
+ openGraph: ResolvedMetadataOpenGraph | null;
58
+ icons: ResolvedMetadataIcons;
59
+ }
60
+
61
+ export type MetadataRouteParam =
62
+ | string
63
+ | string[]
64
+ | undefined;
65
+
66
+ export interface GenerateMetadataContext {
67
+ params: Record<
68
+ string,
69
+ MetadataRouteParam
70
+ >;
71
+ parent: ResolvedMetadata;
72
+ }
73
+
74
+ export type GenerateMetadata = (
75
+ context: GenerateMetadataContext
76
+ ) => Metadata | Promise<Metadata>;
77
+
78
+ export interface MetadataHeadEntry {
79
+ tag: "meta" | "link";
80
+ key: string;
81
+ attributes: Record<string, string>;
82
+ }
83
+
84
+ export function createMetadataHeadEntries(
85
+ metadata: ResolvedMetadata
86
+ ): MetadataHeadEntry[] {
87
+ const entries: MetadataHeadEntry[] = [];
88
+
89
+ if (metadata.description) {
90
+ entries.push({
91
+ tag: "meta",
92
+ key: "description",
93
+ attributes: {
94
+ name: "description",
95
+ content: metadata.description,
96
+ },
97
+ });
98
+ }
99
+
100
+ if (metadata.robots) {
101
+ entries.push({
102
+ tag: "meta",
103
+ key: "robots",
104
+ attributes: {
105
+ name: "robots",
106
+ content: metadata.robots,
107
+ },
108
+ });
109
+ }
110
+
111
+ if (metadata.canonical) {
112
+ entries.push({
113
+ tag: "link",
114
+ key: "canonical",
115
+ attributes: {
116
+ rel: "canonical",
117
+ href: metadata.canonical,
118
+ },
119
+ });
120
+ }
121
+
122
+ if (metadata.openGraph) {
123
+ const openGraph = metadata.openGraph;
124
+
125
+ pushProperty(
126
+ entries,
127
+ "og:title",
128
+ openGraph.title
129
+ );
130
+ pushProperty(
131
+ entries,
132
+ "og:description",
133
+ openGraph.description
134
+ );
135
+ pushProperty(
136
+ entries,
137
+ "og:url",
138
+ openGraph.url
139
+ );
140
+ pushProperty(
141
+ entries,
142
+ "og:type",
143
+ openGraph.type
144
+ );
145
+ pushProperty(
146
+ entries,
147
+ "og:site_name",
148
+ openGraph.siteName
149
+ );
150
+
151
+ openGraph.images.forEach(
152
+ (image, index) => {
153
+ entries.push({
154
+ tag: "meta",
155
+ key: `og:image:${index}`,
156
+ attributes: {
157
+ property: "og:image",
158
+ content: image,
159
+ },
160
+ });
161
+ }
162
+ );
163
+ }
164
+
165
+ pushLink(
166
+ entries,
167
+ "icon",
168
+ "icon",
169
+ metadata.icons.icon
170
+ );
171
+ pushLink(
172
+ entries,
173
+ "shortcut-icon",
174
+ "shortcut icon",
175
+ metadata.icons.shortcut
176
+ );
177
+ pushLink(
178
+ entries,
179
+ "apple-touch-icon",
180
+ "apple-touch-icon",
181
+ metadata.icons.apple
182
+ );
183
+
184
+ return entries;
185
+ }
186
+
187
+ export function applyResolvedMetadata(
188
+ metadata: ResolvedMetadata
189
+ ): void {
190
+ if (
191
+ typeof document ===
192
+ "undefined"
193
+ ) {
194
+ return;
195
+ }
196
+
197
+ document.title =
198
+ metadata.title;
199
+
200
+ document
201
+ .head
202
+ .querySelectorAll(
203
+ "[data-bcp-metadata]"
204
+ )
205
+ .forEach(
206
+ (element) =>
207
+ element.remove()
208
+ );
209
+
210
+ for (
211
+ const entry
212
+ of createMetadataHeadEntries(
213
+ metadata
214
+ )
215
+ ) {
216
+ const element =
217
+ document.createElement(
218
+ entry.tag
219
+ );
220
+
221
+ element.setAttribute(
222
+ "data-bcp-metadata",
223
+ entry.key
224
+ );
225
+
226
+ for (
227
+ const [name, value]
228
+ of Object.entries(
229
+ entry.attributes
230
+ )
231
+ ) {
232
+ element.setAttribute(
233
+ name,
234
+ value
235
+ );
236
+ }
237
+
238
+ document.head.appendChild(
239
+ element
240
+ );
241
+ }
242
+ }
243
+
244
+ function pushProperty(
245
+ entries: MetadataHeadEntry[],
246
+ property: string,
247
+ value: string | null
248
+ ): void {
249
+ if (!value) {
250
+ return;
251
+ }
252
+
253
+ entries.push({
254
+ tag: "meta",
255
+ key: property,
256
+ attributes: {
257
+ property,
258
+ content: value,
259
+ },
260
+ });
261
+ }
262
+
263
+ function pushLink(
264
+ entries: MetadataHeadEntry[],
265
+ key: string,
266
+ rel: string,
267
+ href: string | null
268
+ ): void {
269
+ if (!href) {
270
+ return;
271
+ }
272
+
273
+ entries.push({
274
+ tag: "link",
275
+ key,
276
+ attributes: {
277
+ rel,
278
+ href,
279
+ },
280
+ });
281
+ }
@@ -0,0 +1,52 @@
1
+ const LOADING_ELEMENT_ID =
2
+ "__bcp_navigation_loading__";
3
+
4
+ export function showNavigationLoading(
5
+ html: string | null | undefined
6
+ ): void {
7
+ hideNavigationLoading();
8
+
9
+ if (
10
+ typeof document === "undefined" ||
11
+ !html
12
+ ) {
13
+ return;
14
+ }
15
+
16
+ const container =
17
+ document.createElement(
18
+ "div"
19
+ );
20
+
21
+ container.id =
22
+ LOADING_ELEMENT_ID;
23
+
24
+ container.dataset.bcpNavigationLoading =
25
+ "true";
26
+
27
+ container.setAttribute(
28
+ "aria-live",
29
+ "polite"
30
+ );
31
+
32
+ container.innerHTML =
33
+ html;
34
+
35
+ document.body.appendChild(
36
+ container
37
+ );
38
+ }
39
+
40
+ export function hideNavigationLoading(): void {
41
+ if (
42
+ typeof document === "undefined"
43
+ ) {
44
+ return;
45
+ }
46
+
47
+ document
48
+ .getElementById(
49
+ LOADING_ELEMENT_ID
50
+ )
51
+ ?.remove();
52
+ }