nukejs 0.0.21 → 0.0.22
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/dist/build-cloudflare.js +31 -6
- package/dist/build-node.js +53 -0
- package/dist/build-vercel.js +23 -4
- package/dist/middleware-loader.js +6 -1
- package/package.json +1 -1
package/dist/build-cloudflare.js
CHANGED
|
@@ -133,7 +133,13 @@ class __NodeResponse__ {
|
|
|
133
133
|
}
|
|
134
134
|
`
|
|
135
135
|
);
|
|
136
|
-
function makeApiDispatcherSource(routes) {
|
|
136
|
+
function makeApiDispatcherSource(routes, middlewarePath) {
|
|
137
|
+
const middlewareImport = middlewarePath ? `import __userMiddleware__ from ${JSON.stringify(middlewarePath)};` : "";
|
|
138
|
+
const middlewareRun = middlewarePath ? `
|
|
139
|
+
// Run user middleware before routing. If it ends the response, bail out.
|
|
140
|
+
await __userMiddleware__(req, res);
|
|
141
|
+
if ((res as any).writableEnded || (res as any).headersSent) return true;
|
|
142
|
+
` : "";
|
|
137
143
|
const imports = routes.map((r, i) => `import * as __api_${i}__ from ${JSON.stringify(r.absPath)};`).join("\n");
|
|
138
144
|
const routeEntries = routes.map(
|
|
139
145
|
(r, i) => ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, mod: __api_${i}__ },`
|
|
@@ -142,6 +148,7 @@ function makeApiDispatcherSource(routes) {
|
|
|
142
148
|
/* ts */
|
|
143
149
|
`import type { IncomingMessage, ServerResponse } from 'http';
|
|
144
150
|
${imports}
|
|
151
|
+
${middlewareImport}
|
|
145
152
|
|
|
146
153
|
const __CF_API_ROUTES__ = [
|
|
147
154
|
${routeEntries}
|
|
@@ -154,7 +161,7 @@ ${routeEntries}
|
|
|
154
161
|
export async function __dispatchApi__(req: IncomingMessage, res: ServerResponse): Promise<boolean> {
|
|
155
162
|
const url = new URL((req as any).url || '/', 'http://localhost');
|
|
156
163
|
const pathname = url.pathname;
|
|
157
|
-
|
|
164
|
+
${middlewareRun}
|
|
158
165
|
for (const route of __CF_API_ROUTES__) {
|
|
159
166
|
const m = pathname.match(new RegExp(route.regex));
|
|
160
167
|
if (!m) continue;
|
|
@@ -194,7 +201,13 @@ export async function __dispatchApi__(req: IncomingMessage, res: ServerResponse)
|
|
|
194
201
|
`
|
|
195
202
|
);
|
|
196
203
|
}
|
|
197
|
-
function makePagesDispatcherSource(routes, errorAdapters2 = {}) {
|
|
204
|
+
function makePagesDispatcherSource(routes, errorAdapters2 = {}, middlewarePath) {
|
|
205
|
+
const middlewareImport = middlewarePath ? `import __userMiddleware__ from ${JSON.stringify(middlewarePath)};` : "";
|
|
206
|
+
const middlewareRun = middlewarePath ? `
|
|
207
|
+
// Run user middleware before routing. If it ends the response, bail out.
|
|
208
|
+
await __userMiddleware__(req, res);
|
|
209
|
+
if ((res as any).writableEnded || (res as any).headersSent) return true;
|
|
210
|
+
` : "";
|
|
198
211
|
const imports = routes.map((r, i) => `import __page_${i}__ from ${JSON.stringify(r.adapterPath)};`).join("\n");
|
|
199
212
|
const routeEntries = routes.map(
|
|
200
213
|
(r, i) => ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, catchAll: ${JSON.stringify(r.catchAllNames)}, handler: __page_${i}__ },`
|
|
@@ -235,6 +248,7 @@ function makePagesDispatcherSource(routes, errorAdapters2 = {}) {
|
|
|
235
248
|
${imports}
|
|
236
249
|
${error404Import}
|
|
237
250
|
${error500Import}
|
|
251
|
+
${middlewareImport}
|
|
238
252
|
|
|
239
253
|
const __CF_PAGE_ROUTES__: Array<{
|
|
240
254
|
regex: string;
|
|
@@ -252,7 +266,7 @@ ${routeEntries}
|
|
|
252
266
|
export async function __dispatchPages__(req: IncomingMessage, res: ServerResponse): Promise<boolean> {
|
|
253
267
|
const url = new URL((req as any).url || '/', 'http://localhost');
|
|
254
268
|
const pathname = url.pathname;
|
|
255
|
-
|
|
269
|
+
${middlewareRun}
|
|
256
270
|
// Client-side error \u2014 forward to _500 page if available.
|
|
257
271
|
if (url.searchParams.has('__clientError')) {
|
|
258
272
|
${clientErrHandler}
|
|
@@ -289,9 +303,14 @@ ${notFoundFallback}
|
|
|
289
303
|
`
|
|
290
304
|
);
|
|
291
305
|
}
|
|
292
|
-
function makeWorkerEntrySource(hasApi2, hasPages2, inlineStaticMap2) {
|
|
306
|
+
function makeWorkerEntrySource(hasApi2, hasPages2, inlineStaticMap2, middlewarePath) {
|
|
293
307
|
const apiImport = hasApi2 ? `import { __dispatchApi__ } from "./cf-api-dispatcher.js";` : "";
|
|
294
308
|
const pagesImport = hasPages2 ? `import { __dispatchPages__ } from "./cf-pages-dispatcher.js";` : "";
|
|
309
|
+
const middlewareImport = middlewarePath ? `import __userMiddleware__ from ${JSON.stringify(middlewarePath)};` : "";
|
|
310
|
+
const middlewareRun = middlewarePath ? `
|
|
311
|
+
await __userMiddleware__(nodeReq as any, nodeRes as any);
|
|
312
|
+
if ((nodeRes as any).writableEnded || (nodeRes as any).headersSent) return nodeRes.toResponse();
|
|
313
|
+
` : "";
|
|
295
314
|
const apiDispatch = hasApi2 ? `if (await __dispatchApi__(nodeReq as any, nodeRes as any)) return nodeRes.toResponse();` : "";
|
|
296
315
|
const pagesDispatch = hasPages2 ? `if (await __dispatchPages__(nodeReq as any, nodeRes as any)) return nodeRes.toResponse();` : "";
|
|
297
316
|
return (
|
|
@@ -300,6 +319,7 @@ function makeWorkerEntrySource(hasApi2, hasPages2, inlineStaticMap2) {
|
|
|
300
319
|
|
|
301
320
|
${apiImport}
|
|
302
321
|
${pagesImport}
|
|
322
|
+
${middlewareImport}
|
|
303
323
|
|
|
304
324
|
/**
|
|
305
325
|
* Pre-buffer the request body into a Uint8Array so we can both:
|
|
@@ -392,6 +412,8 @@ export default {
|
|
|
392
412
|
const nodeRes = new ((__NodeResponse__ as any))();
|
|
393
413
|
|
|
394
414
|
try {
|
|
415
|
+
// \u2500\u2500 User middleware \u2014 runs once, before any routing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
416
|
+
${middlewareRun}
|
|
395
417
|
// \u2500\u2500 4. API routes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
396
418
|
${apiDispatch}
|
|
397
419
|
|
|
@@ -505,6 +527,9 @@ function walkStaticDir(dir, base = dir) {
|
|
|
505
527
|
}
|
|
506
528
|
return results;
|
|
507
529
|
}
|
|
530
|
+
const cfUserMiddlewareSrc = path.resolve("middleware.ts");
|
|
531
|
+
const cfMiddlewarePath = fs.existsSync(cfUserMiddlewareSrc) ? cfUserMiddlewareSrc : void 0;
|
|
532
|
+
if (cfMiddlewarePath) console.log(" found middleware.ts (will be bundled into worker)");
|
|
508
533
|
let hasApi = false;
|
|
509
534
|
let hasPages = false;
|
|
510
535
|
if (apiRoutes.length > 0) {
|
|
@@ -625,7 +650,7 @@ const inlineStaticMap = staticEntries.map(({ rel, abs }) => {
|
|
|
625
650
|
return ` [${JSON.stringify(rel)}, { ct: ${JSON.stringify(contentType)}, body: ${JSON.stringify(b64)}, text: false }],`;
|
|
626
651
|
}
|
|
627
652
|
}).join("\n");
|
|
628
|
-
const workerSrc = makeWorkerEntrySource(hasApi, hasPages, inlineStaticMap);
|
|
653
|
+
const workerSrc = makeWorkerEntrySource(hasApi, hasPages, inlineStaticMap, cfMiddlewarePath);
|
|
629
654
|
const workerSrcPath = path.join(
|
|
630
655
|
OUTPUT_DIR,
|
|
631
656
|
`_cf_worker_entry_${randomBytes(4).toString("hex")}.ts`
|
package/dist/build-node.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { build } from "esbuild";
|
|
3
4
|
import { loadConfig } from "./config.js";
|
|
4
5
|
import {
|
|
5
6
|
analyzeFile,
|
|
@@ -27,6 +28,21 @@ const manifest = [];
|
|
|
27
28
|
function funcPathToFilename(funcPath, prefix) {
|
|
28
29
|
return funcPath.replace(new RegExp(`^\\/${prefix}\\/`), "") + ".mjs";
|
|
29
30
|
}
|
|
31
|
+
const userMiddlewareSrc = path.resolve("middleware.ts");
|
|
32
|
+
const hasUserMiddleware = fs.existsSync(userMiddlewareSrc);
|
|
33
|
+
if (hasUserMiddleware) {
|
|
34
|
+
const result = await build({
|
|
35
|
+
entryPoints: [userMiddlewareSrc],
|
|
36
|
+
bundle: true,
|
|
37
|
+
format: "esm",
|
|
38
|
+
platform: "node",
|
|
39
|
+
target: "node20",
|
|
40
|
+
packages: "external",
|
|
41
|
+
write: false
|
|
42
|
+
});
|
|
43
|
+
fs.writeFileSync(path.join(OUT_DIR, "middleware.mjs"), result.outputFiles[0].text);
|
|
44
|
+
console.log(" built middleware.ts \u2192 dist/middleware.mjs");
|
|
45
|
+
}
|
|
30
46
|
const apiFiles = walkFiles(SERVER_DIR);
|
|
31
47
|
if (apiFiles.length === 0) console.warn(`\u26A0 No server files found in ${SERVER_DIR}`);
|
|
32
48
|
const apiRoutes = apiFiles.map((relPath) => ({ ...analyzeFile(relPath, "api"), absPath: path.join(SERVER_DIR, relPath) })).sort((a, b) => b.specificity - a.specificity);
|
|
@@ -100,7 +116,44 @@ const compiled = routes.map(r => ({ ...r, regex: new RegExp(r.srcRegex) }));
|
|
|
100
116
|
const STATIC_DIR = path.join(__dirname, 'static');
|
|
101
117
|
const MIME_MAP = { ${MIME_MAP_ENTRIES} };
|
|
102
118
|
|
|
119
|
+
// \u2500\u2500 Middleware \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
120
|
+
// Load user middleware (compiled from middleware.ts at build time) once at
|
|
121
|
+
// startup. The variable stays null if no middleware was built.
|
|
122
|
+
let __middleware__ = null;
|
|
123
|
+
{
|
|
124
|
+
const mwPath = path.join(__dirname, 'middleware.mjs');
|
|
125
|
+
if (fs.existsSync(mwPath)) {
|
|
126
|
+
try {
|
|
127
|
+
const mod = await import(pathToFileURL(mwPath).href);
|
|
128
|
+
if (typeof mod.default === 'function') {
|
|
129
|
+
__middleware__ = mod.default;
|
|
130
|
+
console.log('nukejs: middleware loaded');
|
|
131
|
+
} else {
|
|
132
|
+
console.warn('nukejs: middleware.mjs does not export a default function, skipping');
|
|
133
|
+
}
|
|
134
|
+
} catch(e) { console.error('[middleware load error]', e); }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
103
138
|
const server = http.createServer(async (req, res) => {
|
|
139
|
+
// 0. User middleware \u2014 runs before every request (static files included).
|
|
140
|
+
// May mutate req.url (e.g. locale rewrites) \u2014 read it AFTER this runs.
|
|
141
|
+
// If it ends the response, skip all framework handling.
|
|
142
|
+
if (__middleware__) {
|
|
143
|
+
try {
|
|
144
|
+
await __middleware__(req, res);
|
|
145
|
+
if (res.writableEnded || res.headersSent) return;
|
|
146
|
+
} catch(e) {
|
|
147
|
+
console.error('[middleware error]', e);
|
|
148
|
+
if (!res.headersSent) {
|
|
149
|
+
res.statusCode = 500;
|
|
150
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
151
|
+
res.end('Internal Server Error');
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
104
157
|
const url = req.url || '/';
|
|
105
158
|
const clean = url.split('?')[0];
|
|
106
159
|
|
package/dist/build-vercel.js
CHANGED
|
@@ -72,13 +72,20 @@ function emitVercelFunction(name, bundleText) {
|
|
|
72
72
|
JSON.stringify({ runtime: "nodejs20.x", handler: "index.mjs", launcherType: "Nodejs" }, null, 2)
|
|
73
73
|
);
|
|
74
74
|
}
|
|
75
|
-
function makeApiDispatcherSource(routes) {
|
|
75
|
+
function makeApiDispatcherSource(routes, middlewarePath) {
|
|
76
|
+
const middlewareImport = middlewarePath ? `import __userMiddleware__ from ${JSON.stringify(middlewarePath)};` : "";
|
|
77
|
+
const middlewareRun = middlewarePath ? `
|
|
78
|
+
// Run user middleware before routing. If it ends the response, bail out.
|
|
79
|
+
await __userMiddleware__(req, res);
|
|
80
|
+
if ((res as any).writableEnded || (res as any).headersSent) return;
|
|
81
|
+
` : "";
|
|
76
82
|
const imports = routes.map((r, i) => `import * as __api_${i}__ from ${JSON.stringify(r.absPath)};`).join("\n");
|
|
77
83
|
const routeEntries = routes.map(
|
|
78
84
|
(r, i) => ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, mod: __api_${i}__ },`
|
|
79
85
|
).join("\n");
|
|
80
86
|
return `import type { IncomingMessage, ServerResponse } from 'http';
|
|
81
87
|
${imports}
|
|
88
|
+
${middlewareImport}
|
|
82
89
|
|
|
83
90
|
function enhance(res: ServerResponse) {
|
|
84
91
|
(res as any).json = function(data: any, status = 200) {
|
|
@@ -112,6 +119,7 @@ ${routeEntries}
|
|
|
112
119
|
];
|
|
113
120
|
|
|
114
121
|
export default async function handler(req: IncomingMessage, res: ServerResponse) {
|
|
122
|
+
${middlewareRun}
|
|
115
123
|
const url = new URL(req.url || '/', 'http://localhost');
|
|
116
124
|
const pathname = url.pathname;
|
|
117
125
|
|
|
@@ -143,7 +151,13 @@ export default async function handler(req: IncomingMessage, res: ServerResponse)
|
|
|
143
151
|
}
|
|
144
152
|
`;
|
|
145
153
|
}
|
|
146
|
-
function makePagesDispatcherSource(routes, errorAdapters = {}) {
|
|
154
|
+
function makePagesDispatcherSource(routes, errorAdapters = {}, middlewarePath) {
|
|
155
|
+
const middlewareImport = middlewarePath ? `import __userMiddleware__ from ${JSON.stringify(middlewarePath)};` : "";
|
|
156
|
+
const middlewareRun = middlewarePath ? `
|
|
157
|
+
// Run user middleware before routing. If it ends the response, bail out.
|
|
158
|
+
await __userMiddleware__(req, res);
|
|
159
|
+
if ((res as any).writableEnded || (res as any).headersSent) return;
|
|
160
|
+
` : "";
|
|
147
161
|
const imports = routes.map((r, i) => `import __page_${i}__ from ${JSON.stringify(r.adapterPath)};`).join("\n");
|
|
148
162
|
const routeEntries = routes.map(
|
|
149
163
|
(r, i) => ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, catchAll: ${JSON.stringify(r.catchAllNames)}, handler: __page_${i}__ },`
|
|
@@ -180,6 +194,7 @@ function makePagesDispatcherSource(routes, errorAdapters = {}) {
|
|
|
180
194
|
${imports}
|
|
181
195
|
${error404Import}
|
|
182
196
|
${error500Import}
|
|
197
|
+
${middlewareImport}
|
|
183
198
|
|
|
184
199
|
const ROUTES: Array<{
|
|
185
200
|
regex: string;
|
|
@@ -191,6 +206,7 @@ ${routeEntries}
|
|
|
191
206
|
];
|
|
192
207
|
|
|
193
208
|
export default async function handler(req: IncomingMessage, res: ServerResponse) {
|
|
209
|
+
${middlewareRun}
|
|
194
210
|
const url = new URL(req.url || '/', 'http://localhost');
|
|
195
211
|
const pathname = url.pathname;
|
|
196
212
|
|
|
@@ -227,13 +243,16 @@ ${notFoundHandler}
|
|
|
227
243
|
}
|
|
228
244
|
`;
|
|
229
245
|
}
|
|
246
|
+
const userMiddlewareSrc = path.resolve("middleware.ts");
|
|
247
|
+
const vercelMiddlewarePath = fs.existsSync(userMiddlewareSrc) ? userMiddlewareSrc : void 0;
|
|
248
|
+
if (vercelMiddlewarePath) console.log(" found middleware.ts (will be bundled into functions)");
|
|
230
249
|
const vercelRoutes = [];
|
|
231
250
|
const apiFiles = walkFiles(SERVER_DIR);
|
|
232
251
|
if (apiFiles.length === 0) console.warn(`\u26A0 No server files found in ${SERVER_DIR}`);
|
|
233
252
|
const apiRoutes = apiFiles.map((relPath) => ({ ...analyzeFile(relPath, "api"), absPath: path.join(SERVER_DIR, relPath) })).sort((a, b) => b.specificity - a.specificity);
|
|
234
253
|
if (apiRoutes.length > 0) {
|
|
235
254
|
const dispatcherPath = path.join(SERVER_DIR, `_api_dispatcher_${randomBytes(4).toString("hex")}.ts`);
|
|
236
|
-
fs.writeFileSync(dispatcherPath, makeApiDispatcherSource(apiRoutes));
|
|
255
|
+
fs.writeFileSync(dispatcherPath, makeApiDispatcherSource(apiRoutes, vercelMiddlewarePath));
|
|
237
256
|
try {
|
|
238
257
|
const result = await build({
|
|
239
258
|
entryPoints: [dispatcherPath],
|
|
@@ -320,7 +339,7 @@ if (serverPages.length > 0 || hasErrorPages) {
|
|
|
320
339
|
errorAdapterPaths.push(adapterPath);
|
|
321
340
|
}
|
|
322
341
|
const dispatcherPath = path.join(PAGES_DIR, `_pages_dispatcher_${randomBytes(4).toString("hex")}.ts`);
|
|
323
|
-
fs.writeFileSync(dispatcherPath, makePagesDispatcherSource(dispatcherRoutes, errorAdapters));
|
|
342
|
+
fs.writeFileSync(dispatcherPath, makePagesDispatcherSource(dispatcherRoutes, errorAdapters, vercelMiddlewarePath));
|
|
324
343
|
try {
|
|
325
344
|
const result = await build({
|
|
326
345
|
entryPoints: [dispatcherPath],
|
|
@@ -26,7 +26,12 @@ async function loadMiddleware() {
|
|
|
26
26
|
appDir,
|
|
27
27
|
`middleware.${appDir.endsWith("dist") ? "js" : "ts"}`
|
|
28
28
|
);
|
|
29
|
-
const
|
|
29
|
+
const userCandidates = [
|
|
30
|
+
path.join(process.cwd(), "middleware.ts"),
|
|
31
|
+
path.join(process.cwd(), "middleware.js"),
|
|
32
|
+
path.join(process.cwd(), "middleware.mjs")
|
|
33
|
+
];
|
|
34
|
+
const userPath = userCandidates.find((p) => fs.existsSync(p)) ?? userCandidates[0];
|
|
30
35
|
const paths = [.../* @__PURE__ */ new Set([builtinPath, userPath])];
|
|
31
36
|
for (const middlewarePath of paths) {
|
|
32
37
|
await loadMiddlewareFromPath(middlewarePath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nukejs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "A minimal, opinionated full-stack React framework on Node.js that server-renders everything and hydrates only interactive parts.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|