nukejs 0.0.18 → 0.0.19
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-common.js +28 -16
- package/dist/bundler.js +71 -22
- package/dist/hmr.js +2 -0
- package/dist/use-router.d.ts +2 -0
- package/dist/use-router.js +28 -20
- package/package.json +1 -1
package/dist/build-common.js
CHANGED
|
@@ -721,23 +721,36 @@ async function bundleClientComponents(globalRegistry, pagesDir, staticDir) {
|
|
|
721
721
|
if (globalRegistry.size === 0) return /* @__PURE__ */ new Map();
|
|
722
722
|
const outDir = path.join(staticDir, "__client-component");
|
|
723
723
|
fs.mkdirSync(outDir, { recursive: true });
|
|
724
|
-
const
|
|
724
|
+
const entryPoints = {};
|
|
725
725
|
for (const [id, filePath] of globalRegistry) {
|
|
726
|
+
entryPoints[id] = filePath;
|
|
726
727
|
console.log(` bundling client ${id} (${path.relative(pagesDir, filePath)})`);
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
728
|
+
}
|
|
729
|
+
await build({
|
|
730
|
+
entryPoints,
|
|
731
|
+
bundle: true,
|
|
732
|
+
splitting: true,
|
|
733
|
+
// ← shared deps extracted into chunks
|
|
734
|
+
format: "esm",
|
|
735
|
+
// splitting requires ESM
|
|
736
|
+
platform: "browser",
|
|
737
|
+
jsx: "automatic",
|
|
738
|
+
minify: true,
|
|
739
|
+
write: true,
|
|
740
|
+
// splitting requires write:true + outdir
|
|
741
|
+
outdir: outDir,
|
|
742
|
+
conditions: ["module", "browser", "import"],
|
|
743
|
+
banner: { js: `const require=(m)=>{if(m==='react')return window.__nukejs_react__;if(m==='react/jsx-runtime')return window.__nukejs_jsx__;throw new Error('Dynamic require of "'+m+'" is not supported');};` },
|
|
744
|
+
external: ["react", "react-dom/client", "react/jsx-runtime"],
|
|
745
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
746
|
+
entryNames: "[name]",
|
|
747
|
+
// cc_abc123.js (no hash on entries)
|
|
748
|
+
chunkNames: "__chunks/[hash]"
|
|
749
|
+
// __chunks/ABCDEF.js
|
|
750
|
+
});
|
|
751
|
+
console.log(` bundled ${globalRegistry.size} client component(s) \u2192 ${path.relative(process.cwd(), outDir)}/`);
|
|
752
|
+
const prerendered = /* @__PURE__ */ new Map();
|
|
753
|
+
for (const [id, filePath] of globalRegistry) {
|
|
741
754
|
const ssrTmp = path.join(
|
|
742
755
|
path.dirname(filePath),
|
|
743
756
|
`_ssr_${id}_${randomBytes(4).toString("hex")}.mjs`
|
|
@@ -766,7 +779,6 @@ async function bundleClientComponents(globalRegistry, pagesDir, staticDir) {
|
|
|
766
779
|
if (fs.existsSync(ssrTmp)) fs.unlinkSync(ssrTmp);
|
|
767
780
|
}
|
|
768
781
|
}
|
|
769
|
-
console.log(` bundled ${globalRegistry.size} client component(s) \u2192 ${path.relative(process.cwd(), outDir)}/`);
|
|
770
782
|
return prerendered;
|
|
771
783
|
}
|
|
772
784
|
async function buildErrorPages(pagesDir, outPagesDir, prerenderedHtml) {
|
package/dist/bundler.js
CHANGED
|
@@ -1,40 +1,89 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
2
4
|
import { fileURLToPath } from "url";
|
|
3
5
|
import { build } from "esbuild";
|
|
4
6
|
import { log } from "./logger.js";
|
|
5
|
-
import {
|
|
7
|
+
import { getComponentCache } from "./component-analyzer.js";
|
|
6
8
|
let reactBundlePromise = null;
|
|
7
9
|
let nukeBundlePromise = null;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const SPLIT_OUT_DIR = path.join(os.tmpdir(), "nukejs-dev-components");
|
|
11
|
+
let splitBuildValid = false;
|
|
12
|
+
let splitBuildPromise = null;
|
|
13
|
+
async function buildAllComponentsSplit() {
|
|
14
|
+
const cache = getComponentCache();
|
|
15
|
+
const clientComponents = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const [filePath, info] of cache) {
|
|
17
|
+
if (info.isClientComponent && info.clientComponentId) {
|
|
18
|
+
clientComponents.set(info.clientComponentId, filePath);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (clientComponents.size === 0) {
|
|
22
|
+
splitBuildValid = true;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const entryPoints = {};
|
|
26
|
+
for (const [id, filePath] of clientComponents) {
|
|
27
|
+
entryPoints[id] = filePath;
|
|
28
|
+
}
|
|
29
|
+
fs.mkdirSync(SPLIT_OUT_DIR, { recursive: true });
|
|
30
|
+
log.verbose(`[bundler] Split build: ${clientComponents.size} component(s) \u2192 ${SPLIT_OUT_DIR}`);
|
|
31
|
+
await build({
|
|
32
|
+
entryPoints,
|
|
11
33
|
bundle: true,
|
|
34
|
+
splitting: true,
|
|
35
|
+
// ← shared deps extracted into chunks
|
|
12
36
|
format: "esm",
|
|
37
|
+
// splitting requires ESM
|
|
13
38
|
platform: "browser",
|
|
14
|
-
write: false,
|
|
15
39
|
jsx: "automatic",
|
|
16
|
-
|
|
17
|
-
//
|
|
40
|
+
minify: false,
|
|
41
|
+
// keep readable in dev
|
|
42
|
+
write: true,
|
|
43
|
+
// splitting requires write:true + outdir
|
|
44
|
+
outdir: SPLIT_OUT_DIR,
|
|
18
45
|
conditions: ["module", "browser", "import"],
|
|
19
|
-
// Shim require() for CJS
|
|
20
|
-
// runtime. Resolves to the already-loaded React instance on window.
|
|
46
|
+
// Shim require() for CJS packages that call require('react') at runtime.
|
|
21
47
|
banner: { js: `const require=(m)=>{if(m==='react')return window.__nukejs_react__;if(m==='react/jsx-runtime')return window.__nukejs_jsx__;throw new Error('Dynamic require of "'+m+'" is not supported');};` },
|
|
22
|
-
|
|
23
|
-
|
|
48
|
+
external: ["react", "react-dom/client", "react/jsx-runtime"],
|
|
49
|
+
define: { "process.env.NODE_ENV": '"development"' },
|
|
50
|
+
entryNames: "[name]",
|
|
51
|
+
// cc_abc123.js (stable, no hash)
|
|
52
|
+
chunkNames: "__chunks/[hash]"
|
|
53
|
+
// __chunks/ABCDEF.js
|
|
24
54
|
});
|
|
25
|
-
|
|
55
|
+
splitBuildValid = true;
|
|
56
|
+
log.verbose("[bundler] Split build complete");
|
|
57
|
+
}
|
|
58
|
+
async function ensureSplitBuild() {
|
|
59
|
+
if (splitBuildValid) return;
|
|
60
|
+
if (!splitBuildPromise) {
|
|
61
|
+
splitBuildPromise = buildAllComponentsSplit().finally(() => {
|
|
62
|
+
splitBuildPromise = null;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
await splitBuildPromise;
|
|
66
|
+
}
|
|
67
|
+
function invalidateSplitBundle() {
|
|
68
|
+
splitBuildValid = false;
|
|
69
|
+
log.verbose("[bundler] Split bundle invalidated");
|
|
26
70
|
}
|
|
27
71
|
async function serveClientComponentBundle(componentId, res) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
72
|
+
await ensureSplitBuild();
|
|
73
|
+
const outPath = path.join(SPLIT_OUT_DIR, `${componentId}.js`);
|
|
74
|
+
if (!fs.existsSync(outPath)) {
|
|
75
|
+
log.verbose(`[bundler] ${componentId} not in split build \u2014 rebuilding`);
|
|
76
|
+
invalidateSplitBundle();
|
|
77
|
+
await ensureSplitBuild();
|
|
78
|
+
if (!fs.existsSync(outPath)) {
|
|
79
|
+
log.error(`Client component not found: ${componentId}`);
|
|
80
|
+
res.statusCode = 404;
|
|
81
|
+
res.end("Client component not found");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
34
84
|
}
|
|
35
|
-
|
|
36
|
-
res.
|
|
37
|
-
res.end("Client component not found");
|
|
85
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
86
|
+
res.end(fs.readFileSync(outPath));
|
|
38
87
|
}
|
|
39
88
|
async function serveReactBundle(res) {
|
|
40
89
|
log.verbose("Bundling React runtime");
|
|
@@ -105,7 +154,7 @@ async function serveNukeBundle(res) {
|
|
|
105
154
|
res.end(await nukeBundlePromise);
|
|
106
155
|
}
|
|
107
156
|
export {
|
|
108
|
-
|
|
157
|
+
invalidateSplitBundle,
|
|
109
158
|
serveClientComponentBundle,
|
|
110
159
|
serveNukeBundle,
|
|
111
160
|
serveReactBundle
|
package/dist/hmr.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, watch } from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { log } from "./logger.js";
|
|
4
4
|
import { invalidateComponentCache } from "./component-analyzer.js";
|
|
5
|
+
import { invalidateSplitBundle } from "./bundler.js";
|
|
5
6
|
const hmrClients = /* @__PURE__ */ new Set();
|
|
6
7
|
function broadcastHmr(payload) {
|
|
7
8
|
const data = `data: ${JSON.stringify(payload)}
|
|
@@ -51,6 +52,7 @@ function watchDir(dir, label) {
|
|
|
51
52
|
const payload = buildPayload(filename);
|
|
52
53
|
log.info(`[HMR] ${label} changed: ${filename}`, JSON.stringify(payload));
|
|
53
54
|
if (dir) invalidateComponentCache(path.resolve(dir, filename));
|
|
55
|
+
invalidateSplitBundle();
|
|
54
56
|
broadcastHmr(payload);
|
|
55
57
|
pending.delete(filename);
|
|
56
58
|
}, 100);
|
package/dist/use-router.d.ts
CHANGED
package/dist/use-router.js
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
const SSR_ROUTER = { push: () => {
|
|
3
|
+
}, replace: () => {
|
|
4
|
+
}, back: () => {
|
|
5
|
+
}, refresh: () => {
|
|
6
|
+
}, path: "" };
|
|
2
7
|
function useRouter() {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
useEffect(() => {
|
|
6
|
-
const handleLocationChange = () => setPath(window.location.pathname);
|
|
7
|
-
window.addEventListener("locationchange", handleLocationChange);
|
|
8
|
-
return () => window.removeEventListener("locationchange", handleLocationChange);
|
|
9
|
-
}, []);
|
|
10
|
-
const push = useCallback((url) => {
|
|
11
|
-
window.history.pushState({}, "", url);
|
|
12
|
-
setPath(url);
|
|
13
|
-
}, []);
|
|
14
|
-
const replace = useCallback((url) => {
|
|
15
|
-
window.history.replaceState({}, "", url);
|
|
16
|
-
setPath(url);
|
|
17
|
-
}, []);
|
|
18
|
-
return { path, push, replace };
|
|
19
|
-
} catch {
|
|
20
|
-
return { push: () => {
|
|
21
|
-
}, replace: () => {
|
|
22
|
-
}, path: "" };
|
|
8
|
+
if (typeof window === "undefined") {
|
|
9
|
+
return SSR_ROUTER;
|
|
23
10
|
}
|
|
11
|
+
const [path, setPath] = useState(() => window.location.pathname);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const handleLocationChange = () => setPath(window.location.pathname);
|
|
14
|
+
window.addEventListener("locationchange", handleLocationChange);
|
|
15
|
+
return () => window.removeEventListener("locationchange", handleLocationChange);
|
|
16
|
+
}, []);
|
|
17
|
+
const push = useCallback((url) => {
|
|
18
|
+
window.history.pushState({}, "", url);
|
|
19
|
+
setPath(url);
|
|
20
|
+
}, []);
|
|
21
|
+
const replace = useCallback((url) => {
|
|
22
|
+
window.history.replaceState({}, "", url);
|
|
23
|
+
setPath(url);
|
|
24
|
+
}, []);
|
|
25
|
+
const back = useCallback(() => {
|
|
26
|
+
window.history.back();
|
|
27
|
+
}, []);
|
|
28
|
+
const refresh = useCallback(() => {
|
|
29
|
+
window.dispatchEvent(new Event("locationchange"));
|
|
30
|
+
}, []);
|
|
31
|
+
return { path, push, replace, back, refresh };
|
|
24
32
|
}
|
|
25
33
|
export {
|
|
26
34
|
useRouter as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nukejs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
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",
|