react-bun-ssr 0.2.0 → 0.3.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/README.md +36 -17
- package/framework/cli/commands.ts +52 -30
- package/framework/cli/dev-client-watch.ts +12 -5
- package/framework/cli/internal.ts +7 -3
- package/framework/cli/scaffold.ts +183 -5
- package/framework/runtime/build-tools.ts +3 -1
- package/framework/runtime/client-runtime.tsx +83 -33
- package/framework/runtime/config.ts +7 -2
- package/framework/runtime/index.ts +1 -1
- package/framework/runtime/link.tsx +3 -11
- package/framework/runtime/module-loader.ts +13 -1
- package/framework/runtime/render.tsx +94 -2
- package/framework/runtime/route-api.ts +1 -1
- package/framework/runtime/router.ts +75 -4
- package/framework/runtime/server.ts +8 -0
- package/framework/runtime/tree.tsx +24 -2
- package/framework/runtime/types.ts +1 -1
- package/package.json +13 -8
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
`react-bun-ssr` is a Bun-native SSR React framework with file-based routing, loaders, actions, middleware, streaming, soft navigation, and first-class markdown routes.
|
|
4
4
|
|
|
5
|
-
- Documentation: https://react-bun-ssr.
|
|
6
|
-
- API reference: https://react-bun-ssr.
|
|
7
|
-
- Blog: https://react-bun-ssr.
|
|
5
|
+
- Documentation: https://react-bun-ssr.dev/docs
|
|
6
|
+
- API reference: https://react-bun-ssr.dev/docs/api/overview
|
|
7
|
+
- Blog: https://react-bun-ssr.dev/blog
|
|
8
8
|
- Repository: https://github.com/react-formation/react-bun-ssr
|
|
9
9
|
|
|
10
10
|
## Why react-bun-ssr?
|
|
@@ -50,16 +50,22 @@ http://127.0.0.1:3000
|
|
|
50
50
|
|
|
51
51
|
For the full setup walkthrough, read the installation guide:
|
|
52
52
|
|
|
53
|
-
- https://react-bun-ssr.
|
|
53
|
+
- https://react-bun-ssr.dev/docs/start/installation
|
|
54
54
|
|
|
55
55
|
## What `rbssr init` gives you
|
|
56
56
|
|
|
57
57
|
`rbssr init` scaffolds a small Bun-first SSR app:
|
|
58
58
|
|
|
59
59
|
```text
|
|
60
|
+
package.json
|
|
61
|
+
tsconfig.json
|
|
62
|
+
.gitignore
|
|
60
63
|
app/
|
|
61
64
|
root.tsx
|
|
65
|
+
root.module.css
|
|
62
66
|
middleware.ts
|
|
67
|
+
public/
|
|
68
|
+
favicon.svg
|
|
63
69
|
routes/
|
|
64
70
|
index.tsx
|
|
65
71
|
api/
|
|
@@ -67,15 +73,20 @@ app/
|
|
|
67
73
|
rbssr.config.ts
|
|
68
74
|
```
|
|
69
75
|
|
|
76
|
+
- `package.json`: Bun scripts and framework/runtime dependencies
|
|
77
|
+
- `tsconfig.json`: starter TypeScript config for Bun + JSX
|
|
78
|
+
- `.gitignore`: minimal app-level ignore rules
|
|
70
79
|
- `app/root.tsx`: document shell and top-level layout
|
|
80
|
+
- `app/root.module.css`: starter CSS Module for layout and base presentation
|
|
71
81
|
- `app/middleware.ts`: global request pipeline hook
|
|
82
|
+
- `app/public/favicon.svg`: starter public asset
|
|
72
83
|
- `app/routes/index.tsx`: first SSR page route
|
|
73
84
|
- `app/routes/api/health.ts`: first API route
|
|
74
85
|
- `rbssr.config.ts`: runtime configuration entrypoint
|
|
75
86
|
|
|
76
87
|
The quickest follow-up is:
|
|
77
88
|
|
|
78
|
-
- https://react-bun-ssr.
|
|
89
|
+
- https://react-bun-ssr.dev/docs/start/quick-start
|
|
79
90
|
|
|
80
91
|
## How it works
|
|
81
92
|
|
|
@@ -85,7 +96,7 @@ Routes live under `app/routes`. Page routes, API routes, dynamic params, and mar
|
|
|
85
96
|
|
|
86
97
|
Read more:
|
|
87
98
|
|
|
88
|
-
- https://react-bun-ssr.
|
|
99
|
+
- https://react-bun-ssr.dev/docs/routing/file-based-routing
|
|
89
100
|
|
|
90
101
|
### Request pipeline
|
|
91
102
|
|
|
@@ -93,8 +104,8 @@ For a page request, the framework resolves the matching route, runs global and n
|
|
|
93
104
|
|
|
94
105
|
Read more:
|
|
95
106
|
|
|
96
|
-
- https://react-bun-ssr.
|
|
97
|
-
- https://react-bun-ssr.
|
|
107
|
+
- https://react-bun-ssr.dev/docs/routing/middleware
|
|
108
|
+
- https://react-bun-ssr.dev/docs/data/loaders
|
|
98
109
|
|
|
99
110
|
### Rendering model
|
|
100
111
|
|
|
@@ -102,8 +113,8 @@ SSR is the default model. HTML responses stream, deferred loader data is support
|
|
|
102
113
|
|
|
103
114
|
Read more:
|
|
104
115
|
|
|
105
|
-
- https://react-bun-ssr.
|
|
106
|
-
- https://react-bun-ssr.
|
|
116
|
+
- https://react-bun-ssr.dev/docs/rendering/streaming-deferred
|
|
117
|
+
- https://react-bun-ssr.dev/docs/routing/navigation
|
|
107
118
|
|
|
108
119
|
### Bun-first runtime
|
|
109
120
|
|
|
@@ -111,7 +122,7 @@ Bun provides the runtime, server, bundler, markdown support, and file APIs that
|
|
|
111
122
|
|
|
112
123
|
Read more:
|
|
113
124
|
|
|
114
|
-
- https://react-bun-ssr.
|
|
125
|
+
- https://react-bun-ssr.dev/docs/api/bun-runtime-apis
|
|
115
126
|
|
|
116
127
|
## Core commands
|
|
117
128
|
|
|
@@ -132,7 +143,7 @@ Repository maintenance commands:
|
|
|
132
143
|
|
|
133
144
|
CLI reference:
|
|
134
145
|
|
|
135
|
-
- https://react-bun-ssr.
|
|
146
|
+
- https://react-bun-ssr.dev/docs/tooling/cli
|
|
136
147
|
|
|
137
148
|
## Working on this repository
|
|
138
149
|
|
|
@@ -147,16 +158,24 @@ bun run docs:dev
|
|
|
147
158
|
|
|
148
159
|
That starts the docs site locally using the framework itself.
|
|
149
160
|
|
|
161
|
+
Dependency ownership is split intentionally:
|
|
162
|
+
|
|
163
|
+
- the repo-root `package.json` is the published framework manifest
|
|
164
|
+
- [`app/package.json`](/Users/react-formation/code/my-app/app/package.json) owns docs-app runtime dependencies
|
|
165
|
+
|
|
166
|
+
Contributors should still use the repo-root commands; the workspace split is there to keep npm package metadata accurate, not to change the day-to-day workflow.
|
|
167
|
+
|
|
150
168
|
## Project layout
|
|
151
169
|
|
|
152
170
|
- `framework/`: runtime, renderer, route handling, build tooling, and CLI internals
|
|
153
171
|
- `bin/rbssr.ts`: CLI entrypoint
|
|
154
172
|
- `app/`: docs site routes, layouts, middleware, blog, and styles
|
|
173
|
+
- `app/package.json`: private docs-app dependency manifest
|
|
155
174
|
- `app/routes/docs/**/*.md`: authored documentation pages
|
|
156
175
|
- `app/routes/blog/*.md`: authored blog posts
|
|
157
176
|
- `scripts/`: generators and validation scripts
|
|
158
|
-
- `tests/`: unit and
|
|
159
|
-
- `
|
|
177
|
+
- `tests/framework/`: framework runtime, CLI, build, unit/integration, and framework Playwright tests
|
|
178
|
+
- `tests/docs-app/`: docs site, blog, analytics, and docs-app Playwright tests
|
|
160
179
|
|
|
161
180
|
## Contributing
|
|
162
181
|
|
|
@@ -184,6 +203,6 @@ fly deploy
|
|
|
184
203
|
|
|
185
204
|
Full deployment docs:
|
|
186
205
|
|
|
187
|
-
- https://react-bun-ssr.
|
|
188
|
-
- https://react-bun-ssr.
|
|
189
|
-
- https://react-bun-ssr.
|
|
206
|
+
- https://react-bun-ssr.dev/docs/deployment/bun-deployment
|
|
207
|
+
- https://react-bun-ssr.dev/docs/deployment/configuration
|
|
208
|
+
- https://react-bun-ssr.dev/docs/deployment/troubleshooting
|
|
@@ -25,6 +25,21 @@ function log(message: string): void {
|
|
|
25
25
|
console.log(`[rbssr] ${message}`);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
async function withNodeEnv<T>(nodeEnv: "development" | "production", run: () => Promise<T>): Promise<T> {
|
|
29
|
+
const previousNodeEnv = process.env.NODE_ENV;
|
|
30
|
+
process.env.NODE_ENV = nodeEnv;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
return await run();
|
|
34
|
+
} finally {
|
|
35
|
+
if (previousNodeEnv === undefined) {
|
|
36
|
+
delete process.env.NODE_ENV;
|
|
37
|
+
} else {
|
|
38
|
+
process.env.NODE_ENV = previousNodeEnv;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
async function getConfig(cwd: string): Promise<{ userConfig: FrameworkConfig; resolved: ResolvedConfig }> {
|
|
29
44
|
const userConfig = await loadUserConfig(cwd);
|
|
30
45
|
const resolved = resolveConfig(userConfig, cwd);
|
|
@@ -48,41 +63,47 @@ export async function runInit(args: string[], cwd = process.cwd()): Promise<void
|
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
export async function runBuild(cwd = process.cwd()): Promise<void> {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
await withNodeEnv("production", async () => {
|
|
67
|
+
const userConfig = await loadUserConfig(cwd);
|
|
68
|
+
const resolved = resolveConfig({
|
|
69
|
+
...userConfig,
|
|
70
|
+
mode: "production",
|
|
71
|
+
}, cwd);
|
|
72
|
+
|
|
73
|
+
const distClientDir = path.join(resolved.distDir, "client");
|
|
74
|
+
const generatedDir = path.resolve(cwd, ".rbssr/generated/client-entries");
|
|
75
|
+
|
|
76
|
+
await Promise.all([
|
|
77
|
+
ensureCleanDirectory(resolved.distDir),
|
|
78
|
+
ensureCleanDirectory(generatedDir),
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
const routeManifest = await buildRouteManifest(resolved);
|
|
82
|
+
const entries = await generateClientEntries({
|
|
83
|
+
config: resolved,
|
|
84
|
+
manifest: routeManifest,
|
|
85
|
+
generatedDir,
|
|
86
|
+
});
|
|
67
87
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
88
|
+
const routeAssets = await bundleClientEntries({
|
|
89
|
+
entries,
|
|
90
|
+
outDir: distClientDir,
|
|
91
|
+
dev: false,
|
|
92
|
+
publicPrefix: "/client/",
|
|
93
|
+
});
|
|
74
94
|
|
|
75
|
-
|
|
95
|
+
await copyDirRecursive(resolved.publicDir, distClientDir);
|
|
76
96
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
97
|
+
const buildManifest = createBuildManifest(routeAssets);
|
|
98
|
+
await writeText(
|
|
99
|
+
path.join(resolved.distDir, "manifest.json"),
|
|
100
|
+
JSON.stringify(buildManifest, null, 2),
|
|
101
|
+
);
|
|
82
102
|
|
|
83
|
-
|
|
103
|
+
await writeProductionServerEntrypoint({ distDir: resolved.distDir });
|
|
84
104
|
|
|
85
|
-
|
|
105
|
+
log(`build complete: ${resolved.distDir}`);
|
|
106
|
+
});
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
export async function runDev(cwd = process.cwd()): Promise<void> {
|
|
@@ -121,6 +142,7 @@ export async function runDev(cwd = process.cwd()): Promise<void> {
|
|
|
121
142
|
stderr: "inherit",
|
|
122
143
|
env: {
|
|
123
144
|
...process.env,
|
|
145
|
+
NODE_ENV: "development",
|
|
124
146
|
RBSSR_DEV_LAUNCHER: "1",
|
|
125
147
|
RBSSR_DEV_CHILD: "1",
|
|
126
148
|
},
|
|
@@ -89,6 +89,13 @@ export function createDevClientWatch(options: {
|
|
|
89
89
|
let metafilePoller: ReturnType<typeof setInterval> | undefined;
|
|
90
90
|
let lastMetafileMtime = "";
|
|
91
91
|
|
|
92
|
+
const resetReadyState = (): void => {
|
|
93
|
+
const deferred = createDeferred();
|
|
94
|
+
state.readyPromise = deferred.promise;
|
|
95
|
+
state.resolveReady = deferred.resolve;
|
|
96
|
+
state.rejectReady = deferred.reject;
|
|
97
|
+
};
|
|
98
|
+
|
|
92
99
|
const stopPolling = (): void => {
|
|
93
100
|
if (metafilePoller) {
|
|
94
101
|
clearInterval(metafilePoller);
|
|
@@ -152,10 +159,6 @@ export function createDevClientWatch(options: {
|
|
|
152
159
|
await removePath(options.metafilePath);
|
|
153
160
|
|
|
154
161
|
const previousOutputFiles = [...state.outputFiles];
|
|
155
|
-
const deferred = createDeferred();
|
|
156
|
-
state.readyPromise = deferred.promise;
|
|
157
|
-
state.resolveReady = deferred.resolve;
|
|
158
|
-
state.rejectReady = deferred.reject;
|
|
159
162
|
state.buildCount = 0;
|
|
160
163
|
state.outputFiles = new Set<string>();
|
|
161
164
|
lastMetafileMtime = "";
|
|
@@ -195,7 +198,10 @@ export function createDevClientWatch(options: {
|
|
|
195
198
|
stdin: "ignore",
|
|
196
199
|
stdout: "inherit",
|
|
197
200
|
stderr: "inherit",
|
|
198
|
-
env:
|
|
201
|
+
env: {
|
|
202
|
+
...process.env,
|
|
203
|
+
NODE_ENV: "development",
|
|
204
|
+
},
|
|
199
205
|
});
|
|
200
206
|
|
|
201
207
|
void state.process.exited.then((exitCode) => {
|
|
@@ -244,6 +250,7 @@ export function createDevClientWatch(options: {
|
|
|
244
250
|
}
|
|
245
251
|
|
|
246
252
|
state.entrySetSignature = nextEntrySetSignature;
|
|
253
|
+
resetReadyState();
|
|
247
254
|
await stopProcess();
|
|
248
255
|
await startProcess();
|
|
249
256
|
options.onLog?.("restarted Bun client watch after entry set change");
|
|
@@ -22,11 +22,14 @@ export function parseFlags(args: string[]): CliFlags {
|
|
|
22
22
|
|
|
23
23
|
export function createProductionServerEntrypointSource(): string {
|
|
24
24
|
return `import path from "node:path";
|
|
25
|
-
import config from "../../rbssr.config.ts";
|
|
26
25
|
import { startHttpServer } from "react-bun-ssr";
|
|
27
26
|
|
|
27
|
+
process.env.NODE_ENV = "production";
|
|
28
|
+
|
|
28
29
|
const rootDir = path.resolve(path.dirname(Bun.fileURLToPath(import.meta.url)), "../..");
|
|
29
30
|
process.chdir(rootDir);
|
|
31
|
+
const configModule = await import("../../rbssr.config.ts");
|
|
32
|
+
const config = configModule.default;
|
|
30
33
|
|
|
31
34
|
const manifestPath = path.resolve(rootDir, "dist/manifest.json");
|
|
32
35
|
const manifest = await Bun.file(manifestPath).json();
|
|
@@ -53,6 +56,7 @@ export function createDevHotEntrypointSource(options: {
|
|
|
53
56
|
|
|
54
57
|
return `import { runHotDevChild } from ${runtimeModulePath};
|
|
55
58
|
|
|
59
|
+
process.env.NODE_ENV = "development";
|
|
56
60
|
process.chdir(${cwd});
|
|
57
61
|
await runHotDevChild({
|
|
58
62
|
cwd: ${cwd},
|
|
@@ -93,8 +97,8 @@ export function createTestCommands(extraArgs: string[]): string[][] {
|
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
return [
|
|
96
|
-
["bun", "test", "./tests/unit"],
|
|
97
|
-
["bun", "test", "./tests/integration"],
|
|
100
|
+
["bun", "test", "./tests/framework/unit", "./tests/docs-app/unit"],
|
|
101
|
+
["bun", "test", "./tests/framework/integration", "./tests/docs-app/integration"],
|
|
98
102
|
["bun", "x", "playwright", "test"],
|
|
99
103
|
];
|
|
100
104
|
}
|
|
@@ -6,6 +6,23 @@ interface ScaffoldFile {
|
|
|
6
6
|
content: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
interface FrameworkPackageManifest {
|
|
10
|
+
version?: string;
|
|
11
|
+
devDependencies?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_FRAMEWORK_VERSION = "0.0.0";
|
|
15
|
+
const DEFAULT_TYPESCRIPT_VERSION = "^5";
|
|
16
|
+
const DEFAULT_BUN_TYPES_VERSION = "latest";
|
|
17
|
+
const DEFAULT_REACT_TYPES_VERSION = "^19";
|
|
18
|
+
const FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">
|
|
19
|
+
<rect width="96" height="96" rx="20" fill="#111827"/>
|
|
20
|
+
<path d="M25 28h20c13.807 0 25 11.193 25 25S58.807 78 45 78H25V28Zm18 40c8.837 0 16-7.163 16-16s-7.163-16-16-16h-8v32h8Z" fill="#f9fafb"/>
|
|
21
|
+
</svg>
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
let frameworkPackageManifestPromise: Promise<FrameworkPackageManifest> | null = null;
|
|
25
|
+
|
|
9
26
|
async function writeIfMissing(filePath: string, content: string, force: boolean): Promise<void> {
|
|
10
27
|
if (!force && await existsPath(filePath)) {
|
|
11
28
|
return;
|
|
@@ -14,8 +31,115 @@ async function writeIfMissing(filePath: string, content: string, force: boolean)
|
|
|
14
31
|
await writeText(filePath, content);
|
|
15
32
|
}
|
|
16
33
|
|
|
17
|
-
function
|
|
34
|
+
function getFrameworkPackageManifest(): Promise<FrameworkPackageManifest> {
|
|
35
|
+
if (!frameworkPackageManifestPromise) {
|
|
36
|
+
const packageJsonPath = path.resolve(import.meta.dir, "../../package.json");
|
|
37
|
+
frameworkPackageManifestPromise = Bun.file(packageJsonPath).json() as Promise<FrameworkPackageManifest>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return frameworkPackageManifestPromise;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizePackageName(cwd: string): string {
|
|
44
|
+
const baseName = path.basename(path.resolve(cwd));
|
|
45
|
+
const normalized = baseName
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
48
|
+
.replace(/[._-]{2,}/g, "-")
|
|
49
|
+
.replace(/^[._-]+|[._-]+$/g, "");
|
|
50
|
+
|
|
51
|
+
return normalized || "rbssr-app";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createPackageJsonContent(options: {
|
|
55
|
+
cwd: string;
|
|
56
|
+
frameworkVersion: string;
|
|
57
|
+
typescriptVersion: string;
|
|
58
|
+
bunTypesVersion: string;
|
|
59
|
+
reactTypesVersion: string;
|
|
60
|
+
reactDomTypesVersion: string;
|
|
61
|
+
}): string {
|
|
62
|
+
const packageJson = {
|
|
63
|
+
name: normalizePackageName(options.cwd),
|
|
64
|
+
version: "0.0.0",
|
|
65
|
+
private: true,
|
|
66
|
+
type: "module",
|
|
67
|
+
scripts: {
|
|
68
|
+
dev: "rbssr dev",
|
|
69
|
+
build: "rbssr build",
|
|
70
|
+
start: "rbssr start",
|
|
71
|
+
typecheck: "bunx tsc --noEmit",
|
|
72
|
+
},
|
|
73
|
+
dependencies: {
|
|
74
|
+
"react-bun-ssr": options.frameworkVersion,
|
|
75
|
+
react: "^19",
|
|
76
|
+
"react-dom": "^19",
|
|
77
|
+
},
|
|
78
|
+
devDependencies: {
|
|
79
|
+
"@types/react": options.reactTypesVersion,
|
|
80
|
+
"@types/react-dom": options.reactDomTypesVersion,
|
|
81
|
+
"bun-types": options.bunTypesVersion,
|
|
82
|
+
typescript: options.typescriptVersion,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return `${JSON.stringify(packageJson, null, 2)}\n`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createTsconfigContent(): string {
|
|
90
|
+
const tsconfig = {
|
|
91
|
+
compilerOptions: {
|
|
92
|
+
target: "ESNext",
|
|
93
|
+
module: "Preserve",
|
|
94
|
+
moduleResolution: "Bundler",
|
|
95
|
+
jsx: "react-jsx",
|
|
96
|
+
strict: true,
|
|
97
|
+
types: ["bun-types"],
|
|
98
|
+
},
|
|
99
|
+
include: ["app", "rbssr.config.ts"],
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return `${JSON.stringify(tsconfig, null, 2)}\n`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createGitignoreContent(): string {
|
|
106
|
+
return `node_modules
|
|
107
|
+
dist
|
|
108
|
+
.rbssr
|
|
109
|
+
.env
|
|
110
|
+
.env.local
|
|
111
|
+
.DS_Store
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function templateFiles(cwd: string): Promise<ScaffoldFile[]> {
|
|
116
|
+
const frameworkPackage = await getFrameworkPackageManifest();
|
|
117
|
+
const frameworkVersion = frameworkPackage.version ?? DEFAULT_FRAMEWORK_VERSION;
|
|
118
|
+
const typescriptVersion = frameworkPackage.devDependencies?.typescript ?? DEFAULT_TYPESCRIPT_VERSION;
|
|
119
|
+
const bunTypesVersion = frameworkPackage.devDependencies?.["bun-types"] ?? DEFAULT_BUN_TYPES_VERSION;
|
|
120
|
+
const reactTypesVersion = frameworkPackage.devDependencies?.["@types/react"] ?? DEFAULT_REACT_TYPES_VERSION;
|
|
121
|
+
const reactDomTypesVersion = frameworkPackage.devDependencies?.["@types/react-dom"] ?? DEFAULT_REACT_TYPES_VERSION;
|
|
122
|
+
|
|
18
123
|
return [
|
|
124
|
+
{
|
|
125
|
+
filePath: path.join(cwd, "package.json"),
|
|
126
|
+
content: createPackageJsonContent({
|
|
127
|
+
cwd,
|
|
128
|
+
frameworkVersion,
|
|
129
|
+
typescriptVersion,
|
|
130
|
+
bunTypesVersion,
|
|
131
|
+
reactTypesVersion,
|
|
132
|
+
reactDomTypesVersion,
|
|
133
|
+
}),
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
filePath: path.join(cwd, "tsconfig.json"),
|
|
137
|
+
content: createTsconfigContent(),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
filePath: path.join(cwd, ".gitignore"),
|
|
141
|
+
content: createGitignoreContent(),
|
|
142
|
+
},
|
|
19
143
|
{
|
|
20
144
|
filePath: path.join(cwd, "rbssr.config.ts"),
|
|
21
145
|
content: `import { defineConfig } from "react-bun-ssr";
|
|
@@ -29,14 +153,17 @@ export default defineConfig({
|
|
|
29
153
|
{
|
|
30
154
|
filePath: path.join(cwd, "app/root.tsx"),
|
|
31
155
|
content: `import { Outlet } from "react-bun-ssr/route";
|
|
156
|
+
import styles from "./root.module.css";
|
|
32
157
|
|
|
33
158
|
export default function RootLayout() {
|
|
34
159
|
return (
|
|
35
|
-
<main className=
|
|
36
|
-
<header className=
|
|
160
|
+
<main className={styles.shell}>
|
|
161
|
+
<header className={styles.top}>
|
|
37
162
|
<h1>react-bun-ssr</h1>
|
|
38
163
|
</header>
|
|
39
|
-
<
|
|
164
|
+
<section className={styles.content}>
|
|
165
|
+
<Outlet />
|
|
166
|
+
</section>
|
|
40
167
|
</main>
|
|
41
168
|
);
|
|
42
169
|
}
|
|
@@ -44,6 +171,53 @@ export default function RootLayout() {
|
|
|
44
171
|
export function head() {
|
|
45
172
|
return <title>react-bun-ssr app</title>;
|
|
46
173
|
}
|
|
174
|
+
`,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
filePath: path.join(cwd, "app/root.module.css"),
|
|
178
|
+
content: `:global(*) {
|
|
179
|
+
box-sizing: border-box;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
:global(html) {
|
|
183
|
+
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
184
|
+
background: linear-gradient(180deg, #f8fafc 0%, #e2e8f0 100%);
|
|
185
|
+
color: #0f172a;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
:global(body) {
|
|
189
|
+
margin: 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.shell {
|
|
193
|
+
min-height: 100vh;
|
|
194
|
+
width: min(100%, 72rem);
|
|
195
|
+
margin: 0 auto;
|
|
196
|
+
padding: 3rem 1.5rem 4rem;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.top {
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
justify-content: space-between;
|
|
203
|
+
margin-bottom: 2rem;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.top h1 {
|
|
207
|
+
margin: 0;
|
|
208
|
+
font-size: clamp(2rem, 5vw, 3.5rem);
|
|
209
|
+
line-height: 1;
|
|
210
|
+
letter-spacing: -0.04em;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.content {
|
|
214
|
+
padding: 1.5rem;
|
|
215
|
+
border: 1px solid rgba(15, 23, 42, 0.08);
|
|
216
|
+
border-radius: 1.5rem;
|
|
217
|
+
background: rgba(255, 255, 255, 0.84);
|
|
218
|
+
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.12);
|
|
219
|
+
backdrop-filter: blur(18px);
|
|
220
|
+
}
|
|
47
221
|
`,
|
|
48
222
|
},
|
|
49
223
|
{
|
|
@@ -87,11 +261,15 @@ export const middleware: Middleware = async (ctx, next) => {
|
|
|
87
261
|
};
|
|
88
262
|
`,
|
|
89
263
|
},
|
|
264
|
+
{
|
|
265
|
+
filePath: path.join(cwd, "app/public/favicon.svg"),
|
|
266
|
+
content: FAVICON_SVG,
|
|
267
|
+
},
|
|
90
268
|
];
|
|
91
269
|
}
|
|
92
270
|
|
|
93
271
|
export async function scaffoldApp(cwd: string, options: { force: boolean }): Promise<void> {
|
|
94
|
-
for (const file of templateFiles(cwd)) {
|
|
272
|
+
for (const file of await templateFiles(cwd)) {
|
|
95
273
|
await writeIfMissing(file.filePath, file.content, options.force);
|
|
96
274
|
}
|
|
97
275
|
}
|
|
@@ -23,7 +23,6 @@ const BUILD_OPTIMIZE_IMPORTS = [
|
|
|
23
23
|
'react-bun-ssr/route',
|
|
24
24
|
'react',
|
|
25
25
|
'react-dom',
|
|
26
|
-
'@datadog/browser-rum-react',
|
|
27
26
|
];
|
|
28
27
|
|
|
29
28
|
export interface ClientEntryFile {
|
|
@@ -338,6 +337,9 @@ export async function bundleClientEntries(options: {
|
|
|
338
337
|
sourcemap: dev ? 'inline' : 'external',
|
|
339
338
|
minify: !dev,
|
|
340
339
|
naming: dev ? '[name].[ext]' : '[name]-[hash].[ext]',
|
|
340
|
+
define: {
|
|
341
|
+
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
|
|
342
|
+
},
|
|
341
343
|
});
|
|
342
344
|
|
|
343
345
|
if (!result.success) {
|