create-sprinkles 0.2.4 → 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/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/bin.mjs +30 -8
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +20 -6
- package/package.json +1 -1
- package/templates/react-router-rsc-sea/scripts/build-exe.ts.hbs +53 -0
- package/templates/react-router-rsc-sea/server.ts.hbs +52 -0
- package/templates/react-shared/react-router.config.ts.hbs +3 -0
- package/templates/shared/vite.config.ts.hbs +37 -0
- /package/templates/{react-router-rsc → react-router-rsc-cloudflare}/workers/entry.rsc.tsx +0 -0
- /package/templates/{react-router-rsc → react-router-rsc-cloudflare}/workers/entry.ssr.tsx +0 -0
- /package/templates/{react-router-rsc → react-router-rsc-cloudflare}/wrangler.rsc.jsonc.hbs +0 -0
- /package/templates/{react-router-rsc → react-router-rsc-cloudflare}/wrangler.ssr.jsonc.hbs +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Malstrom
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# create-sprinkles
|
|
2
|
+
|
|
3
|
+
Get started with development by creating projects from templates quickly.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
create-sprinkles requires [Vite+](https://viteplus.dev). Install it with Homebrew on macOS:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
brew install markmals/tap/vite-plus
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or use the official installer script:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
# macOS/Linux
|
|
17
|
+
curl -fsSL https://vite.plus | bash
|
|
18
|
+
|
|
19
|
+
# Windows
|
|
20
|
+
irm https://vite.plus/ps1 | iex
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
vp create sprinkles
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The interactive prompts will guide you through:
|
|
30
|
+
|
|
31
|
+
1. **Directory** — where to create the project
|
|
32
|
+
2. **Project kind** — choose a template (see below)
|
|
33
|
+
3. **Optional features** — add-ons specific to the chosen template
|
|
34
|
+
4. **GitHub owner** — your GitHub user or organization
|
|
35
|
+
|
|
36
|
+
## Templates
|
|
37
|
+
|
|
38
|
+
### React Router — SPA
|
|
39
|
+
|
|
40
|
+
Single-page app with React Router, Tailwind CSS, and Vite+.
|
|
41
|
+
|
|
42
|
+
- Optional: [Convex](https://convex.dev) backend
|
|
43
|
+
|
|
44
|
+
### React Router — SSR
|
|
45
|
+
|
|
46
|
+
Server-rendered app with React Router, Tailwind CSS, Vite+, and Cloudflare deployment.
|
|
47
|
+
|
|
48
|
+
- Optional: [Convex](https://convex.dev) backend
|
|
49
|
+
|
|
50
|
+
### React Router — RSC
|
|
51
|
+
|
|
52
|
+
React Server Components with React Router, Tailwind CSS, Vite+, and Cloudflare deployment.
|
|
53
|
+
|
|
54
|
+
- Optional: Content-layer plugin for MDX-based content
|
|
55
|
+
- Optional: Single Executable Application (SEA) — bundles the server and all client assets into a single portable Node.js binary via `node:sea`
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
package/dist/bin.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { createTemplate, runTemplate } from "bingo";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { handlebars } from "bingo-handlebars";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
7
8
|
//#region src/metadata.ts
|
|
8
9
|
const NAME = "create-sprinkles";
|
|
9
10
|
//#endregion
|
|
@@ -16,6 +17,7 @@ function buildContext(opts) {
|
|
|
16
17
|
...opts,
|
|
17
18
|
hasContentLayer: isRSC && Boolean(opts.contentLayer),
|
|
18
19
|
hasConvex: (isSPA || isSSR) && Boolean(opts.convex),
|
|
20
|
+
hasSEA: isRSC && Boolean(opts.sea),
|
|
19
21
|
isPackage: opts.kind === "ts-package",
|
|
20
22
|
isRSC,
|
|
21
23
|
isReactRouter: opts.kind !== "ts-package",
|
|
@@ -58,6 +60,7 @@ const options = {
|
|
|
58
60
|
};
|
|
59
61
|
//#endregion
|
|
60
62
|
//#region src/scripts.ts
|
|
63
|
+
const faviconBase64 = readFileSync(path.join(import.meta.dirname, "assets/favicon.ico")).toString("base64");
|
|
61
64
|
function buildDependencyCommands(context) {
|
|
62
65
|
const commands = [];
|
|
63
66
|
commands.push("vp add -D @types/node @typescript/native-preview");
|
|
@@ -65,12 +68,16 @@ function buildDependencyCommands(context) {
|
|
|
65
68
|
commands.push("vp add react react-dom react-router");
|
|
66
69
|
commands.push("vp add -D @types/react @types/react-dom @react-router/dev @tailwindcss/vite tailwindcss vite-plugin-devtools-json");
|
|
67
70
|
commands.push("vp add -D @rolldown/plugin-babel @vitejs/plugin-react babel-plugin-react-compiler");
|
|
68
|
-
commands.push("vp add -D @cloudflare/vite-plugin wrangler");
|
|
71
|
+
if (!context.hasSEA) commands.push("vp add -D @cloudflare/vite-plugin wrangler");
|
|
69
72
|
commands.push("vp add -D eslint-plugin-perfectionist eslint-plugin-react-hooks");
|
|
70
73
|
}
|
|
71
74
|
if (context.isReactRouter) commands.push("vp add @react-router/node isbot");
|
|
72
75
|
if (context.hasConvex) commands.push("vp add convex @convex-dev/react-query @tanstack/react-query");
|
|
73
76
|
if (context.isRSC) commands.push("vp add -D @vitejs/plugin-rsc");
|
|
77
|
+
if (context.hasSEA) {
|
|
78
|
+
commands.push("vp add @remix-run/node-fetch-server mime");
|
|
79
|
+
commands.push("vp add -D tsdown");
|
|
80
|
+
}
|
|
74
81
|
if (context.hasContentLayer) {
|
|
75
82
|
commands.push("vp add jsr:@std/jsonc jsr:@std/yaml gray-matter github-slugger @remix-run/data-schema");
|
|
76
83
|
commands.push("vp add -D @mdx-js/rollup");
|
|
@@ -89,7 +96,7 @@ function buildScripts(context) {
|
|
|
89
96
|
commands: phase0Commands,
|
|
90
97
|
phase: 0
|
|
91
98
|
});
|
|
92
|
-
if (context.isRSC) scripts.push({
|
|
99
|
+
if (context.isRSC && !context.hasSEA) scripts.push({
|
|
93
100
|
commands: ["vpx wrangler types -c wrangler.rsc.jsonc"],
|
|
94
101
|
phase: 1
|
|
95
102
|
});
|
|
@@ -106,6 +113,10 @@ function buildScripts(context) {
|
|
|
106
113
|
phase: 3,
|
|
107
114
|
silent: true
|
|
108
115
|
});
|
|
116
|
+
if (context.isReactRouter) scripts.push({
|
|
117
|
+
commands: ["mkdir -p public", `echo '${faviconBase64}' | base64 -d > public/favicon.ico`],
|
|
118
|
+
phase: 3
|
|
119
|
+
});
|
|
109
120
|
scripts.push({
|
|
110
121
|
commands: [
|
|
111
122
|
"ln -sf AGENTS.md CLAUDE.md",
|
|
@@ -122,7 +133,8 @@ function buildScripts(context) {
|
|
|
122
133
|
function buildSuggestions(context) {
|
|
123
134
|
const suggestions = [];
|
|
124
135
|
if (context.hasConvex) suggestions.push("Open the Convex dashboard: https://dashboard.convex.dev");
|
|
125
|
-
if (context.isSSR || context.isRSC) suggestions.push("Log in to Cloudflare: vpx wrangler login");
|
|
136
|
+
if ((context.isSSR || context.isRSC) && !context.hasSEA) suggestions.push("Log in to Cloudflare: vpx wrangler login");
|
|
137
|
+
if (context.hasSEA) suggestions.push("Build the executable: vp run build");
|
|
126
138
|
if (context.isReactRouter) suggestions.push("Start the dev server: vp dev");
|
|
127
139
|
else suggestions.push("Start development: vp run dev");
|
|
128
140
|
return suggestions;
|
|
@@ -150,6 +162,8 @@ async function collectAddonLayers(context) {
|
|
|
150
162
|
if (context.isPackage && context.sea) addons.push(await tryHandlebars("ts-package-sea", context));
|
|
151
163
|
if (context.hasConvex) addons.push(await tryHandlebars("react-router-convex", context));
|
|
152
164
|
if (context.isSSR && context.hasConvex) addons.push(await tryHandlebars("react-router-ssr-convex", context));
|
|
165
|
+
if (context.isRSC && !context.hasSEA) addons.push(await tryHandlebars("react-router-rsc-cloudflare", context));
|
|
166
|
+
if (context.hasSEA) addons.push(await tryHandlebars("react-router-rsc-sea", context));
|
|
153
167
|
if (context.hasContentLayer) addons.push(await tryHandlebars("react-router-rsc-content-layer", context));
|
|
154
168
|
return addons;
|
|
155
169
|
}
|
|
@@ -225,12 +239,20 @@ if (kind === "react-router-spa" || kind === "react-router-ssr") {
|
|
|
225
239
|
convex = answer;
|
|
226
240
|
}
|
|
227
241
|
if (kind === "react-router-rsc") {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
242
|
+
const features = await prompts.multiselect({
|
|
243
|
+
message: "Include optional features?",
|
|
244
|
+
options: [{
|
|
245
|
+
label: "Content-layer plugin",
|
|
246
|
+
value: "contentLayer"
|
|
247
|
+
}, {
|
|
248
|
+
label: "Single Executable Application (SEA)",
|
|
249
|
+
value: "sea"
|
|
250
|
+
}],
|
|
251
|
+
required: false
|
|
231
252
|
});
|
|
232
|
-
if (prompts.isCancel(
|
|
233
|
-
contentLayer =
|
|
253
|
+
if (prompts.isCancel(features)) process.exit(0);
|
|
254
|
+
contentLayer = features.includes("contentLayer");
|
|
255
|
+
sea = features.includes("sea");
|
|
234
256
|
}
|
|
235
257
|
if (kind === "ts-package") {
|
|
236
258
|
const features = await prompts.multiselect({
|
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { createTemplate } from "bingo";
|
|
|
2
2
|
import { handlebars } from "bingo-handlebars";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
5
6
|
//#region src/context.ts
|
|
6
7
|
function buildContext(opts) {
|
|
7
8
|
const isSPA = opts.kind === "react-router-spa";
|
|
@@ -11,6 +12,7 @@ function buildContext(opts) {
|
|
|
11
12
|
...opts,
|
|
12
13
|
hasContentLayer: isRSC && Boolean(opts.contentLayer),
|
|
13
14
|
hasConvex: (isSPA || isSSR) && Boolean(opts.convex),
|
|
15
|
+
hasSEA: isRSC && Boolean(opts.sea),
|
|
14
16
|
isPackage: opts.kind === "ts-package",
|
|
15
17
|
isRSC,
|
|
16
18
|
isReactRouter: opts.kind !== "ts-package",
|
|
@@ -20,9 +22,6 @@ function buildContext(opts) {
|
|
|
20
22
|
};
|
|
21
23
|
}
|
|
22
24
|
//#endregion
|
|
23
|
-
//#region src/metadata.ts
|
|
24
|
-
const NAME = "create-sprinkles";
|
|
25
|
-
//#endregion
|
|
26
25
|
//#region src/merge.ts
|
|
27
26
|
function isDirectory(value) {
|
|
28
27
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -37,6 +36,9 @@ function mergeFiles(...layers) {
|
|
|
37
36
|
return result;
|
|
38
37
|
}
|
|
39
38
|
//#endregion
|
|
39
|
+
//#region src/metadata.ts
|
|
40
|
+
const NAME = "create-sprinkles";
|
|
41
|
+
//#endregion
|
|
40
42
|
//#region src/options.ts
|
|
41
43
|
const kind = z.enum([
|
|
42
44
|
"react-router-spa",
|
|
@@ -56,6 +58,7 @@ const options = {
|
|
|
56
58
|
};
|
|
57
59
|
//#endregion
|
|
58
60
|
//#region src/scripts.ts
|
|
61
|
+
const faviconBase64 = readFileSync(path.join(import.meta.dirname, "assets/favicon.ico")).toString("base64");
|
|
59
62
|
function buildDependencyCommands(context) {
|
|
60
63
|
const commands = [];
|
|
61
64
|
commands.push("vp add -D @types/node @typescript/native-preview");
|
|
@@ -63,12 +66,16 @@ function buildDependencyCommands(context) {
|
|
|
63
66
|
commands.push("vp add react react-dom react-router");
|
|
64
67
|
commands.push("vp add -D @types/react @types/react-dom @react-router/dev @tailwindcss/vite tailwindcss vite-plugin-devtools-json");
|
|
65
68
|
commands.push("vp add -D @rolldown/plugin-babel @vitejs/plugin-react babel-plugin-react-compiler");
|
|
66
|
-
commands.push("vp add -D @cloudflare/vite-plugin wrangler");
|
|
69
|
+
if (!context.hasSEA) commands.push("vp add -D @cloudflare/vite-plugin wrangler");
|
|
67
70
|
commands.push("vp add -D eslint-plugin-perfectionist eslint-plugin-react-hooks");
|
|
68
71
|
}
|
|
69
72
|
if (context.isReactRouter) commands.push("vp add @react-router/node isbot");
|
|
70
73
|
if (context.hasConvex) commands.push("vp add convex @convex-dev/react-query @tanstack/react-query");
|
|
71
74
|
if (context.isRSC) commands.push("vp add -D @vitejs/plugin-rsc");
|
|
75
|
+
if (context.hasSEA) {
|
|
76
|
+
commands.push("vp add @remix-run/node-fetch-server mime");
|
|
77
|
+
commands.push("vp add -D tsdown");
|
|
78
|
+
}
|
|
72
79
|
if (context.hasContentLayer) {
|
|
73
80
|
commands.push("vp add jsr:@std/jsonc jsr:@std/yaml gray-matter github-slugger @remix-run/data-schema");
|
|
74
81
|
commands.push("vp add -D @mdx-js/rollup");
|
|
@@ -87,7 +94,7 @@ function buildScripts(context) {
|
|
|
87
94
|
commands: phase0Commands,
|
|
88
95
|
phase: 0
|
|
89
96
|
});
|
|
90
|
-
if (context.isRSC) scripts.push({
|
|
97
|
+
if (context.isRSC && !context.hasSEA) scripts.push({
|
|
91
98
|
commands: ["vpx wrangler types -c wrangler.rsc.jsonc"],
|
|
92
99
|
phase: 1
|
|
93
100
|
});
|
|
@@ -104,6 +111,10 @@ function buildScripts(context) {
|
|
|
104
111
|
phase: 3,
|
|
105
112
|
silent: true
|
|
106
113
|
});
|
|
114
|
+
if (context.isReactRouter) scripts.push({
|
|
115
|
+
commands: ["mkdir -p public", `echo '${faviconBase64}' | base64 -d > public/favicon.ico`],
|
|
116
|
+
phase: 3
|
|
117
|
+
});
|
|
107
118
|
scripts.push({
|
|
108
119
|
commands: [
|
|
109
120
|
"ln -sf AGENTS.md CLAUDE.md",
|
|
@@ -120,7 +131,8 @@ function buildScripts(context) {
|
|
|
120
131
|
function buildSuggestions(context) {
|
|
121
132
|
const suggestions = [];
|
|
122
133
|
if (context.hasConvex) suggestions.push("Open the Convex dashboard: https://dashboard.convex.dev");
|
|
123
|
-
if (context.isSSR || context.isRSC) suggestions.push("Log in to Cloudflare: vpx wrangler login");
|
|
134
|
+
if ((context.isSSR || context.isRSC) && !context.hasSEA) suggestions.push("Log in to Cloudflare: vpx wrangler login");
|
|
135
|
+
if (context.hasSEA) suggestions.push("Build the executable: vp run build");
|
|
124
136
|
if (context.isReactRouter) suggestions.push("Start the dev server: vp dev");
|
|
125
137
|
else suggestions.push("Start development: vp run dev");
|
|
126
138
|
return suggestions;
|
|
@@ -148,6 +160,8 @@ async function collectAddonLayers(context) {
|
|
|
148
160
|
if (context.isPackage && context.sea) addons.push(await tryHandlebars("ts-package-sea", context));
|
|
149
161
|
if (context.hasConvex) addons.push(await tryHandlebars("react-router-convex", context));
|
|
150
162
|
if (context.isSSR && context.hasConvex) addons.push(await tryHandlebars("react-router-ssr-convex", context));
|
|
163
|
+
if (context.isRSC && !context.hasSEA) addons.push(await tryHandlebars("react-router-rsc-cloudflare", context));
|
|
164
|
+
if (context.hasSEA) addons.push(await tryHandlebars("react-router-rsc-sea", context));
|
|
151
165
|
if (context.hasContentLayer) addons.push(await tryHandlebars("react-router-rsc-content-layer", context));
|
|
152
166
|
return addons;
|
|
153
167
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { build } from "tsdown";
|
|
4
|
+
|
|
5
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
6
|
+
|
|
7
|
+
const CLIENT_DIR = "dist/client";
|
|
8
|
+
|
|
9
|
+
// ── Collect every file under dist/client/ ──────────────────────
|
|
10
|
+
function collectFiles(dir: string, base = dir) {
|
|
11
|
+
const files = [] as string[];
|
|
12
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
13
|
+
const full = join(dir, entry.name);
|
|
14
|
+
if (entry.isDirectory()) {
|
|
15
|
+
files.push(...collectFiles(full, base));
|
|
16
|
+
} else {
|
|
17
|
+
// Key = path relative to CLIENT_DIR, e.g. "assets/index-abc123.js"
|
|
18
|
+
files.push(relative(base, full));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return files;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let clientFiles = collectFiles(CLIENT_DIR);
|
|
25
|
+
|
|
26
|
+
// ── Write manifest so server.ts knows which keys exist ──────────
|
|
27
|
+
writeFileSync("dist/asset-manifest.json", JSON.stringify(clientFiles, null, 2));
|
|
28
|
+
|
|
29
|
+
// ── Build the SEA asset map: key → file path on disk ────────────
|
|
30
|
+
// node:sea's getAsset(key) uses these keys at runtime
|
|
31
|
+
let assets = Object.fromEntries(clientFiles.map(file => [file, join(CLIENT_DIR, file)]));
|
|
32
|
+
|
|
33
|
+
console.log(`Embedding ${clientFiles.length} client assets into executable`);
|
|
34
|
+
|
|
35
|
+
// ── Run tsdown with exe enabled ─────────────────────────────────
|
|
36
|
+
await build({
|
|
37
|
+
entry: ["server.ts"],
|
|
38
|
+
outDir: "build",
|
|
39
|
+
format: "esm",
|
|
40
|
+
target: "node25",
|
|
41
|
+
// Bundle everything — the React Router server build, @remix-run/node-fetch-server, etc.
|
|
42
|
+
noExternal: [/.*/],
|
|
43
|
+
exe: {
|
|
44
|
+
fileName: packageJson.name,
|
|
45
|
+
seaConfig: {
|
|
46
|
+
disableExperimentalSEAWarning: true,
|
|
47
|
+
useCodeCache: false,
|
|
48
|
+
assets, // ← every client file, keyed by its URL path
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log(`Done — executable written to build/${packageJson.name}`);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createRequestListener } from "@remix-run/node-fetch-server";
|
|
2
|
+
import mime from "mime";
|
|
3
|
+
import * as http from "node:http";
|
|
4
|
+
import sea from "node:sea";
|
|
5
|
+
|
|
6
|
+
// @ts-expect-error: tsdown bundles this — it's the output of `vp build`
|
|
7
|
+
import app from "./dist/server/index.js";
|
|
8
|
+
|
|
9
|
+
// Generated by scripts/build-exe.ts before tsdown runs
|
|
10
|
+
import assetManifest from "./dist/asset-manifest.json" with { type: "json" };
|
|
11
|
+
|
|
12
|
+
function mimeType(path: string): string {
|
|
13
|
+
let ext = path.substring(path.lastIndexOf("."));
|
|
14
|
+
return mime.getType(ext) ?? "application/octet-stream";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Set of embedded asset paths for O(1) lookup
|
|
18
|
+
let embeddedAssets = new Set<string>(assetManifest);
|
|
19
|
+
|
|
20
|
+
async function handler(request: Request): Promise<Response> {
|
|
21
|
+
let url = new URL(request.url);
|
|
22
|
+
// Strip leading slash to match SEA asset keys
|
|
23
|
+
let assetPath = url.pathname.slice(1);
|
|
24
|
+
|
|
25
|
+
// Serve embedded client assets straight from the binary
|
|
26
|
+
if (sea.isSea() && embeddedAssets.has(assetPath)) {
|
|
27
|
+
let body = sea.getAsset(assetPath); // ArrayBuffer
|
|
28
|
+
|
|
29
|
+
// Vite fingerprints asset filenames, so anything under /assets/
|
|
30
|
+
// is safe to cache forever. Everything else gets a shorter TTL.
|
|
31
|
+
let immutable = assetPath.startsWith("assets/");
|
|
32
|
+
|
|
33
|
+
return new Response(body, {
|
|
34
|
+
headers: {
|
|
35
|
+
"Content-Type": mimeType(assetPath),
|
|
36
|
+
"Cache-Control": immutable
|
|
37
|
+
? "public, max-age=31536000, immutable"
|
|
38
|
+
: "public, max-age=3600",
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Everything else → React Router RSC
|
|
44
|
+
return app.fetch(request);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const port = Number.parseInt(process.env.PORT || "1612");
|
|
48
|
+
const server = http.createServer(createRequestListener(handler));
|
|
49
|
+
|
|
50
|
+
server.listen(port, () => {
|
|
51
|
+
console.log(`Listening on http://localhost:${port}`);
|
|
52
|
+
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
{{#if isRSC}}
|
|
2
|
+
{{#unless hasSEA}}
|
|
2
3
|
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
4
|
+
{{/unless}}
|
|
3
5
|
{{#if hasContentLayer}}
|
|
4
6
|
import mdx from "@mdx-js/rollup";
|
|
5
7
|
{{/if}}
|
|
@@ -26,7 +28,9 @@ import { defineConfig } from "vite-plus";
|
|
|
26
28
|
{{/if}}
|
|
27
29
|
|
|
28
30
|
{{#if isReactRouter}}
|
|
31
|
+
{{#unless hasSEA}}
|
|
29
32
|
const IS_TEST = Boolean(process.env.VITEST);
|
|
33
|
+
{{/unless}}
|
|
30
34
|
|
|
31
35
|
{{/if}}
|
|
32
36
|
export default defineConfig({
|
|
@@ -107,7 +111,9 @@ export default defineConfig({
|
|
|
107
111
|
node: true,
|
|
108
112
|
},
|
|
109
113
|
{{#if isRSC}}
|
|
114
|
+
{{#unless hasSEA}}
|
|
110
115
|
ignorePatterns: ["**/worker-configuration.d.ts"],
|
|
116
|
+
{{/unless}}
|
|
111
117
|
{{/if}}
|
|
112
118
|
{{#if isReactRouter}}
|
|
113
119
|
jsPlugins: [
|
|
@@ -281,6 +287,19 @@ export default defineConfig({
|
|
|
281
287
|
},
|
|
282
288
|
{{/if}}
|
|
283
289
|
{{#if isRSC}}
|
|
290
|
+
{{#if hasSEA}}
|
|
291
|
+
plugins: [
|
|
292
|
+
{{#if hasContentLayer}}
|
|
293
|
+
contentLayer(),
|
|
294
|
+
mdx(),
|
|
295
|
+
{{/if}}
|
|
296
|
+
reactRouter(),
|
|
297
|
+
rsc(),
|
|
298
|
+
reactCompiler(),
|
|
299
|
+
tailwindcss(),
|
|
300
|
+
devtoolsJson(),
|
|
301
|
+
],
|
|
302
|
+
{{else}}
|
|
284
303
|
plugins: [
|
|
285
304
|
{{#if hasContentLayer}}
|
|
286
305
|
contentLayer(),
|
|
@@ -307,6 +326,7 @@ export default defineConfig({
|
|
|
307
326
|
tailwindcss(),
|
|
308
327
|
devtoolsJson(),
|
|
309
328
|
],
|
|
329
|
+
{{/if}}
|
|
310
330
|
{{else if isSSR}}
|
|
311
331
|
plugins: [
|
|
312
332
|
tailwindcss(),
|
|
@@ -338,6 +358,22 @@ export default defineConfig({
|
|
|
338
358
|
run: {
|
|
339
359
|
tasks: {
|
|
340
360
|
{{#if isRSC}}
|
|
361
|
+
{{#if hasSEA}}
|
|
362
|
+
build: {
|
|
363
|
+
// Compound command — each half is cached independently.
|
|
364
|
+
// vp build runs React Router's production build,
|
|
365
|
+
// then the script bundles everything into a SEA.
|
|
366
|
+
command: "vp build && node scripts/build-exe.ts",
|
|
367
|
+
env: ["NODE_ENV"],
|
|
368
|
+
},
|
|
369
|
+
typecheck: {
|
|
370
|
+
command: "tsgo --noEmit",
|
|
371
|
+
dependsOn: ["typegen:react-router"],
|
|
372
|
+
},
|
|
373
|
+
"typegen:react-router": {
|
|
374
|
+
command: "react-router typegen",
|
|
375
|
+
},
|
|
376
|
+
{{else}}
|
|
341
377
|
deploy: {
|
|
342
378
|
cache: false,
|
|
343
379
|
command: "vp build && wrangler deploy",
|
|
@@ -352,6 +388,7 @@ export default defineConfig({
|
|
|
352
388
|
"typegen:react-router": {
|
|
353
389
|
command: "react-router typegen",
|
|
354
390
|
},
|
|
391
|
+
{{/if}}
|
|
355
392
|
{{else if isReactRouter}}
|
|
356
393
|
deploy: {
|
|
357
394
|
cache: false,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|