@junejs/server 0.0.9 → 0.0.11
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/package.json +8 -3
- package/src/adapter.ts +226 -0
- package/src/app.ts +65 -3
- package/src/blob.ts +1 -1
- package/src/build.ts +178 -55
- package/src/client-bundle.ts +24 -11
- package/src/css-modules-ambient.d.ts +9 -0
- package/src/css-modules-loader.d.mts +7 -0
- package/src/css-modules-loader.mjs +20 -0
- package/src/css-modules.ts +185 -0
- package/src/css.ts +118 -0
- package/src/d1-migrate.ts +209 -0
- package/src/d1.ts +53 -0
- package/src/db.ts +8 -47
- package/src/deploy.ts +121 -21
- package/src/dev-reload.ts +56 -10
- package/src/dev.ts +32 -1
- package/src/host.ts +7 -78
- package/src/index.ts +28 -1
- package/src/kv.ts +1 -1
- package/src/migrate.ts +122 -0
- package/src/pipeline.ts +119 -18
- package/src/resources.ts +47 -0
- package/src/sqlite-driver.ts +160 -0
- package/src/worker.ts +41 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "June host adapters + dev server. Host-coupled; depends on the pure @junejs/core contract layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"./db": "./src/db.ts",
|
|
12
12
|
"./kv": "./src/kv.ts",
|
|
13
13
|
"./blob": "./src/blob.ts",
|
|
14
|
+
"./resources": "./src/resources.ts",
|
|
14
15
|
"./config": "./src/config-loader.ts",
|
|
15
16
|
"./content": "./src/content.ts",
|
|
16
17
|
"./router": "./src/router.ts",
|
|
@@ -29,9 +30,13 @@
|
|
|
29
30
|
"typecheck": "tsc --noEmit"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@junejs/core": "0.0.
|
|
33
|
+
"@junejs/core": "0.0.11",
|
|
34
|
+
"@junejs/db": "0.0.11",
|
|
33
35
|
"marked": "^18.0.5",
|
|
34
|
-
"rolldown": "^1.1.0"
|
|
36
|
+
"rolldown": "^1.1.0",
|
|
37
|
+
"postcss": "^8.4.0",
|
|
38
|
+
"postcss-modules": "^6.0.0",
|
|
39
|
+
"lightningcss": "^1.27.0"
|
|
35
40
|
},
|
|
36
41
|
"peerDependencies": {
|
|
37
42
|
"react": "^19.2.0",
|
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Deploy adapters — the seam between June's portable `fetch(Request) → Response`
|
|
2
|
+
// build and a deploy target. June's core artifact (createWorker(manifest)) is
|
|
3
|
+
// already runtime-portable (the cake demo runs the SAME bundle on Workers AND
|
|
4
|
+
// Vercel Edge), so an adapter does PACKAGING + BINDINGS + entry glue, never a
|
|
5
|
+
// re-bundle. See docs/adapter-interface-spec.md.
|
|
6
|
+
//
|
|
7
|
+
// `workers()` is the zero-config default (this file, no import). Other targets
|
|
8
|
+
// are explicit `deploy: { adapter: vercel() }` from their own package.
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { cp, copyFile, mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
11
|
+
import { basename, join } from "node:path";
|
|
12
|
+
|
|
13
|
+
import type { JuneConfig } from "@junejs/core/config";
|
|
14
|
+
|
|
15
|
+
export type AdapterCapabilities = {
|
|
16
|
+
runtime: "edge" | "node" | "serverless" | "native" | "static";
|
|
17
|
+
// SSE/WebSocket → live-RSC push & server-push HMR. The framework degrades
|
|
18
|
+
// features that need it (with a build notice) when false. (Not yet consumed —
|
|
19
|
+
// the foundation for it.)
|
|
20
|
+
persistentConnections: boolean;
|
|
21
|
+
// Who serves prerendered static files: the platform CDN, the server, or none.
|
|
22
|
+
assets: "platform" | "server" | "none";
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// What the adapter contributes to the generated worker entry: extra imports and
|
|
26
|
+
// the `export default` that wraps the portable pipeline.
|
|
27
|
+
export type AdapterEntry = {
|
|
28
|
+
imports: string[];
|
|
29
|
+
wrap(pipelineVar: string): string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// What the app's declared resources need from the platform. Derived from
|
|
33
|
+
// config.resources at build time; the adapter turns it into platform bindings
|
|
34
|
+
// (workers() → wrangler `d1_databases` etc.). Target-neutral so a future
|
|
35
|
+
// vercel()/node() adapter maps the SAME plan to its own provisioning.
|
|
36
|
+
export type ResourcePlan = {
|
|
37
|
+
// A declared `db` → a D1 binding named `binding`. The runtime provider
|
|
38
|
+
// (bindWorkerResources) reads env[binding]; keep in sync (default "DB").
|
|
39
|
+
db?: { binding: string; databaseName: string };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type AdapterEmitContext = {
|
|
43
|
+
appRoot: string;
|
|
44
|
+
outDir: string;
|
|
45
|
+
hasAssets: boolean;
|
|
46
|
+
linkHeader: string | null;
|
|
47
|
+
config: JuneConfig;
|
|
48
|
+
// Platform bindings the declared resources need (empty object = none).
|
|
49
|
+
plan: ResourcePlan;
|
|
50
|
+
// package.json name (or sanitized dir name) — the fallback worker/app name.
|
|
51
|
+
defaultName: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export interface JuneAdapter {
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly capabilities: AdapterCapabilities;
|
|
57
|
+
// Rolldown export conditions BAKED into the bundle at build time (the target
|
|
58
|
+
// runtime has none). Target-specific: workers → "workerd", vercel → "edge-light".
|
|
59
|
+
// The same portable graph, resolved for the platform's module surface.
|
|
60
|
+
readonly conditions: string[];
|
|
61
|
+
// Reject a config this target can't honor, BEFORE the expensive build — e.g.
|
|
62
|
+
// Vercel has no D1, so a declared db fails fast with a clear message instead of
|
|
63
|
+
// a cryptic prerender/runtime error. Optional; default is "accept everything".
|
|
64
|
+
validate?(ctx: { plan: ResourcePlan; config: JuneConfig }): void;
|
|
65
|
+
// Wrap the portable pipeline into the platform's entry source.
|
|
66
|
+
entry(opts: { linkHeader: string | null }): AdapterEntry;
|
|
67
|
+
// Write the deploy structure (config files, asset placement, server entry).
|
|
68
|
+
emit(ctx: AdapterEmitContext): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The built-in default. Reproduces exactly what build.ts emitted inline:
|
|
72
|
+
// withAssets(pipeline) as the entry, and a wrangler.jsonc with the assets
|
|
73
|
+
// binding (run_worker_first) + optional custom domain.
|
|
74
|
+
export function workers(opts?: { name?: string; domain?: string }): JuneAdapter {
|
|
75
|
+
return {
|
|
76
|
+
name: "workers",
|
|
77
|
+
capabilities: { runtime: "edge", persistentConnections: true, assets: "platform" },
|
|
78
|
+
conditions: ["workerd", "edge", "import", "default"],
|
|
79
|
+
|
|
80
|
+
entry({ linkHeader }) {
|
|
81
|
+
return {
|
|
82
|
+
imports: [`import { withAssets } from "@junejs/server/worker";`],
|
|
83
|
+
wrap: (pipelineVar) =>
|
|
84
|
+
`export default withAssets(${pipelineVar}, { link: ${JSON.stringify(linkHeader)} });`,
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
async emit({ appRoot, outDir, hasAssets, config, plan, defaultName }) {
|
|
89
|
+
// An app that manages its own wrangler config wins — don't overwrite it.
|
|
90
|
+
if (existsSync(join(appRoot, "wrangler.toml")) || existsSync(join(appRoot, "wrangler.jsonc"))) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const deployCfg = config.deploy;
|
|
94
|
+
await writeFile(
|
|
95
|
+
join(outDir, "wrangler.jsonc"),
|
|
96
|
+
JSON.stringify(
|
|
97
|
+
{
|
|
98
|
+
name: opts?.name ?? deployCfg?.name ?? defaultName,
|
|
99
|
+
main: "./worker.js",
|
|
100
|
+
compatibility_date: "2025-01-01",
|
|
101
|
+
compatibility_flags: ["nodejs_compat"],
|
|
102
|
+
// run_worker_first: the worker wraps asset serving (withAssets) so
|
|
103
|
+
// prerendered pages still get Link headers + Accept:markdown
|
|
104
|
+
// negotiation. The ASSETS binding lets the worker serve assets.
|
|
105
|
+
...(hasAssets
|
|
106
|
+
? { assets: { directory: "./assets", binding: "ASSETS", run_worker_first: true } }
|
|
107
|
+
: {}),
|
|
108
|
+
// A declared `db` resource → a D1 binding. database_id is per-account
|
|
109
|
+
// (run `wrangler d1 create <name>` and paste it); emitted empty so
|
|
110
|
+
// the binding is wired and the one missing value is obvious. With it,
|
|
111
|
+
// the same `sqlite()` declaration runs on D1 at the edge.
|
|
112
|
+
...(plan.db
|
|
113
|
+
? {
|
|
114
|
+
d1_databases: [
|
|
115
|
+
{
|
|
116
|
+
binding: plan.db.binding,
|
|
117
|
+
database_name: plan.db.databaseName,
|
|
118
|
+
database_id: "",
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
}
|
|
122
|
+
: {}),
|
|
123
|
+
// config deploy.domain → a Workers custom domain; without it the
|
|
124
|
+
// regenerated file would silently drop a hand-attached domain.
|
|
125
|
+
...(opts?.domain ?? deployCfg?.domain
|
|
126
|
+
? { routes: [{ pattern: opts?.domain ?? deployCfg?.domain, custom_domain: true }] }
|
|
127
|
+
: {}),
|
|
128
|
+
},
|
|
129
|
+
null,
|
|
130
|
+
2,
|
|
131
|
+
) + "\n",
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Vercel target — emits the Build Output API v3 tree (.vercel/output/). June's
|
|
138
|
+
// worker is already a Web-standard fetch handler, so this is packaging, not a
|
|
139
|
+
// re-bundle: the same portable graph, built with edge-light conditions, becomes a
|
|
140
|
+
// Vercel Edge Function. v1 scope: SSR + static + the full agent surface. No db
|
|
141
|
+
// (D1 is Cloudflare-only) — declaring one fails fast; an HTTP-driver db backend
|
|
142
|
+
// for Vercel is a planned follow-up.
|
|
143
|
+
//
|
|
144
|
+
// Routing: only the hashed framework assets (/_june/*) are served statically by
|
|
145
|
+
// the CDN (their exact-path URLs match June's scheme); every page + projection
|
|
146
|
+
// (.md/.json/mcp/llms.txt) renders through the one edge function, so the
|
|
147
|
+
// dual-audience surface is identical to Workers.
|
|
148
|
+
const VERCEL_FUNCTION = "__june"; // → /__june, the catch-all SSR function
|
|
149
|
+
|
|
150
|
+
export function vercel(opts?: { regions?: string[] }): JuneAdapter {
|
|
151
|
+
return {
|
|
152
|
+
name: "vercel",
|
|
153
|
+
capabilities: { runtime: "edge", persistentConnections: false, assets: "platform" },
|
|
154
|
+
conditions: ["edge-light", "worker", "browser", "import", "default"],
|
|
155
|
+
|
|
156
|
+
validate({ plan }) {
|
|
157
|
+
if (plan.db) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
"vercel(): the Vercel adapter has no db backend yet (D1 is Cloudflare-only).\n" +
|
|
160
|
+
" Remove the `db` resource from june.config.ts, or deploy to Workers.\n" +
|
|
161
|
+
" An HTTP-driver db (Neon/Turso) for Vercel is planned.",
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
entry() {
|
|
167
|
+
// No withAssets: the CDN serves /_june/*; this function renders everything
|
|
168
|
+
// else. Resources arrive via process.env (Vercel injects env vars into the
|
|
169
|
+
// edge runtime); there are no platform bindings.
|
|
170
|
+
return {
|
|
171
|
+
imports: [],
|
|
172
|
+
wrap: (pipelineVar) =>
|
|
173
|
+
`const __env = typeof process !== "undefined" && process.env ? process.env : {};\n` +
|
|
174
|
+
`export default (request) => ${pipelineVar}.fetch(request, __env);`,
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
async emit({ appRoot, outDir, hasAssets }) {
|
|
179
|
+
const out = join(appRoot, ".vercel", "output");
|
|
180
|
+
const fnDir = join(out, "functions", `${VERCEL_FUNCTION}.func`);
|
|
181
|
+
await rm(out, { recursive: true, force: true }); // clean — no stale outputs
|
|
182
|
+
await mkdir(fnDir, { recursive: true });
|
|
183
|
+
|
|
184
|
+
// the edge function = June's portable worker bundle. Rolldown code-splits
|
|
185
|
+
// (worker.js imports ./cache-*.js, ./instrumentation-*.js, …), so copy EVERY
|
|
186
|
+
// top-level .js — the chunks must sit beside the entry or Vercel rejects the
|
|
187
|
+
// function ("referencing unsupported modules"). assets/ is handled below.
|
|
188
|
+
const jsFiles = (await readdir(outDir)).filter((f) => f.endsWith(".js"));
|
|
189
|
+
for (const f of jsFiles) await copyFile(join(outDir, f), join(fnDir, f));
|
|
190
|
+
await writeFile(
|
|
191
|
+
join(fnDir, ".vc-config.json"),
|
|
192
|
+
JSON.stringify(
|
|
193
|
+
{ runtime: "edge", entrypoint: "worker.js", ...(opts?.regions ? { regions: opts.regions } : {}) },
|
|
194
|
+
null,
|
|
195
|
+
2,
|
|
196
|
+
) + "\n",
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// static: only the hashed framework assets under /_june/ (exact-path URLs)
|
|
200
|
+
if (hasAssets) {
|
|
201
|
+
const src = join(outDir, "assets", "_june");
|
|
202
|
+
if (existsSync(src)) await cp(src, join(out, "static", "_june"), { recursive: true });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
await writeFile(
|
|
206
|
+
join(out, "config.json"),
|
|
207
|
+
JSON.stringify(
|
|
208
|
+
{
|
|
209
|
+
version: 3,
|
|
210
|
+
routes: [
|
|
211
|
+
{
|
|
212
|
+
src: "^/_june/(.*)$",
|
|
213
|
+
headers: { "cache-control": "public, max-age=31536000, immutable" },
|
|
214
|
+
continue: true,
|
|
215
|
+
},
|
|
216
|
+
{ handle: "filesystem" }, // serve any static file by exact path
|
|
217
|
+
{ src: "^/.*$", dest: `/${VERCEL_FUNCTION}` }, // everything else → SSR
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
null,
|
|
221
|
+
2,
|
|
222
|
+
) + "\n",
|
|
223
|
+
);
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
package/src/app.ts
CHANGED
|
@@ -22,6 +22,8 @@ import { findExtraFile, listRoutes, matchRouteTree, resolveNotFound, routeFiles,
|
|
|
22
22
|
import { createPipeline, type ExtraHandler, type LayoutComponent, type Pipeline, type Resolved } from "./pipeline";
|
|
23
23
|
import { memoizeResources } from "./resources";
|
|
24
24
|
import { findClientEntry, bundleClientToString, CLIENT_SCRIPT_URL } from "./client-bundle";
|
|
25
|
+
import { findGlobalCss, processCssCached, STYLES_URL } from "./css";
|
|
26
|
+
import { buildModuleCss, registerCssModules, MODULE_STYLES_URL, type ModuleMaps } from "./css-modules";
|
|
25
27
|
|
|
26
28
|
export type CreateAppOptions = {
|
|
27
29
|
appDir: string;
|
|
@@ -48,6 +50,20 @@ async function loadLayout(file: string): Promise<LayoutComponent | null> {
|
|
|
48
50
|
return mod.default;
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
// The nearest loading.tsx up the segment chain (deepest wins) → the streaming
|
|
54
|
+
// Suspense fallback. Memoized like layouts.
|
|
55
|
+
const loadingCache = new Map<string, React.ComponentType>();
|
|
56
|
+
async function nearestLoading(segments: SegmentMatch[]): Promise<React.ComponentType | undefined> {
|
|
57
|
+
const file = [...segments].reverse().find((s) => s.loading)?.loading;
|
|
58
|
+
if (!file) return undefined;
|
|
59
|
+
const cached = loadingCache.get(file);
|
|
60
|
+
if (cached) return cached;
|
|
61
|
+
const mod = (await import(pathToFileURL(file).href)) as { default?: React.ComponentType };
|
|
62
|
+
if (typeof mod.default !== "function") return undefined;
|
|
63
|
+
loadingCache.set(file, mod.default);
|
|
64
|
+
return mod.default;
|
|
65
|
+
}
|
|
66
|
+
|
|
51
67
|
async function loadChain(segments: SegmentMatch[]): Promise<LayoutComponent[]> {
|
|
52
68
|
const chain: LayoutComponent[] = [];
|
|
53
69
|
for (const seg of segments) {
|
|
@@ -68,19 +84,29 @@ export function createApp({ appDir: appDirInput, config = {} }: CreateAppOptions
|
|
|
68
84
|
// (bundled lazily, memoized). Detected the same way the build freezes it, so
|
|
69
85
|
// dev and built surfaces agree.
|
|
70
86
|
const clientEntry = findClientEntry(appDir);
|
|
87
|
+
const cssEntry = findGlobalCss(appDir);
|
|
71
88
|
const docConfig: DocumentConfig = {
|
|
72
89
|
site: config.site ?? {},
|
|
73
90
|
speculationRules: resolveSpeculationRules(speculation ?? undefined),
|
|
74
91
|
speculationDelivery: speculation ? speculation.delivery ?? "inline" : "inline",
|
|
75
92
|
viewTransitions: config.viewTransitions ?? true,
|
|
93
|
+
clientRouter: config.clientRouter ?? false,
|
|
76
94
|
clientScript: clientEntry ? CLIENT_SCRIPT_URL : null,
|
|
95
|
+
styles: cssEntry ? STYLES_URL : null,
|
|
77
96
|
};
|
|
78
97
|
|
|
98
|
+
// CSS Modules: glob + transform app/**/*.module.css ONCE (memoized) → the maps
|
|
99
|
+
// (runtime interceptor + client bundle look these up) + the collected sheet.
|
|
100
|
+
let moduleCssPromise: Promise<{ maps: ModuleMaps; css: string | null }> | undefined;
|
|
101
|
+
const getModuleCss = () => (moduleCssPromise ??= buildModuleCss(appDir, dirname(appDir)));
|
|
102
|
+
|
|
79
103
|
let clientBundle: Promise<string> | undefined;
|
|
80
104
|
const serveClient = (): Promise<string> =>
|
|
81
105
|
// cwd = the app ROOT (appDir's parent) so rolldown resolves node_modules
|
|
82
|
-
// from the project, exactly like the build does.
|
|
83
|
-
(clientBundle ??=
|
|
106
|
+
// from the project, exactly like the build does. Islands may import .module.css.
|
|
107
|
+
(clientBundle ??= getModuleCss().then(({ maps }) =>
|
|
108
|
+
bundleClientToString(clientEntry!, dirname(appDir), maps),
|
|
109
|
+
));
|
|
84
110
|
|
|
85
111
|
const routePaths = () => listRoutes(appDir, { pageConvention: true });
|
|
86
112
|
|
|
@@ -92,6 +118,13 @@ export function createApp({ appDir: appDirInput, config = {} }: CreateAppOptions
|
|
|
92
118
|
let pipelinePromise: Promise<Pipeline> | undefined;
|
|
93
119
|
const getPipeline = (): Promise<Pipeline> => (pipelinePromise ??= buildPipeline());
|
|
94
120
|
async function buildPipeline(): Promise<Pipeline> {
|
|
121
|
+
// Wire CSS Modules BEFORE any route import: the runtime interceptor must be
|
|
122
|
+
// active so `import "x.module.css"` in a route resolves to the scoped map,
|
|
123
|
+
// and the document must link the collected sheet.
|
|
124
|
+
const { maps, css } = await getModuleCss();
|
|
125
|
+
await registerCssModules(maps);
|
|
126
|
+
if (css !== null) docConfig.moduleStyles = MODULE_STYLES_URL;
|
|
127
|
+
|
|
95
128
|
const { notFound } = await resolveNotFound(appDir, "/");
|
|
96
129
|
let notFoundComponent: ComponentType<{ pathname: string }> | undefined;
|
|
97
130
|
if (notFound) {
|
|
@@ -120,7 +153,12 @@ export function createApp({ appDir: appDirInput, config = {} }: CreateAppOptions
|
|
|
120
153
|
const mod = await import(pathToFileURL(match.file).href);
|
|
121
154
|
const def = routeFromModule(mod);
|
|
122
155
|
if (!def) return null;
|
|
123
|
-
return {
|
|
156
|
+
return {
|
|
157
|
+
def,
|
|
158
|
+
params: match.params,
|
|
159
|
+
chain: await loadChain(match.segments),
|
|
160
|
+
loading: await nearestLoading(match.segments),
|
|
161
|
+
};
|
|
124
162
|
},
|
|
125
163
|
});
|
|
126
164
|
}
|
|
@@ -131,6 +169,30 @@ export function createApp({ appDir: appDirInput, config = {} }: CreateAppOptions
|
|
|
131
169
|
|
|
132
170
|
return {
|
|
133
171
|
fetch(request) {
|
|
172
|
+
// Dev serves the global stylesheet (build ships it as a static asset);
|
|
173
|
+
// re-read+processed each hit so edits show on reload, like /client.js.
|
|
174
|
+
if (cssEntry && new URL(request.url).pathname === STYLES_URL) {
|
|
175
|
+
return processCssCached(appDir).then(
|
|
176
|
+
(css) =>
|
|
177
|
+
new Response(css ?? "", {
|
|
178
|
+
headers: { "content-type": "text/css; charset=utf-8" },
|
|
179
|
+
}),
|
|
180
|
+
(err: unknown) => {
|
|
181
|
+
console.error("[june] global.css failed:", err);
|
|
182
|
+
return new Response(`/* global.css failed: ${err instanceof Error ? err.message : String(err)} */\n`, {
|
|
183
|
+
status: 500,
|
|
184
|
+
headers: { "content-type": "text/css; charset=utf-8" },
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
// Dev serves the collected CSS-Modules stylesheet (build ships it hashed).
|
|
190
|
+
if (new URL(request.url).pathname === MODULE_STYLES_URL) {
|
|
191
|
+
return getModuleCss().then(
|
|
192
|
+
({ css }) =>
|
|
193
|
+
new Response(css ?? "", { headers: { "content-type": "text/css; charset=utf-8" } }),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
134
196
|
// Dev serves the islands runtime itself (build ships it as a static
|
|
135
197
|
// asset); bundled on first hit, memoized after.
|
|
136
198
|
if (clientEntry && new URL(request.url).pathname === CLIENT_SCRIPT_URL) {
|
package/src/blob.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `blob` resource adapters — the object/file store seam (JuneBlob). The
|
|
2
2
|
// zero-config dev default is a local directory; R2 (and S3-shaped backends) are
|
|
3
3
|
// the deploy adapters. Same binding model as db/kv: declare `resources.blob` in
|
|
4
|
-
// june.config.ts →
|
|
4
|
+
// june.config.ts → use the ambient `blob` (decoupled from ctx).
|
|
5
5
|
|
|
6
6
|
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
7
7
|
import { dirname, join } from "node:path";
|