nukejs 0.0.5 → 0.0.6
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 +11 -5
- package/dist/as-is/Link.js +14 -0
- package/dist/as-is/Link.js.map +7 -0
- package/dist/as-is/useRouter.js +28 -0
- package/dist/as-is/useRouter.js.map +7 -0
- package/dist/build-common.d.ts +51 -80
- package/dist/build-common.js +139 -165
- package/dist/build-common.js.map +2 -2
- package/dist/build-node.d.ts +14 -0
- package/dist/build-node.js +50 -51
- package/dist/build-node.js.map +2 -2
- package/dist/build-vercel.d.ts +18 -0
- package/dist/build-vercel.js +76 -63
- package/dist/build-vercel.js.map +2 -2
- package/dist/builder.d.ts +16 -0
- package/dist/builder.js +54 -54
- package/dist/builder.js.map +3 -3
- package/dist/bundle.js +9 -2
- package/dist/bundle.js.map +2 -2
- package/dist/component-analyzer.d.ts +7 -10
- package/dist/component-analyzer.js +14 -16
- package/dist/component-analyzer.js.map +2 -2
- package/dist/router.d.ts +19 -20
- package/dist/router.js +12 -8
- package/dist/router.js.map +2 -2
- package/dist/ssr.js +1 -1
- package/dist/ssr.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
[](https://nukejs.com)
|
|
2
|
+
|
|
3
|
+
# NukeJS
|
|
2
4
|
|
|
3
5
|
A **minimal**, opinionated full-stack React framework on Node.js that server-renders everything and hydrates only interactive parts.
|
|
4
6
|
|
|
5
7
|
```
|
|
6
|
-
npm create nuke
|
|
8
|
+
npm create nuke@latest
|
|
7
9
|
```
|
|
8
10
|
|
|
9
11
|
## Table of Contents
|
|
@@ -129,8 +131,9 @@ Each `.tsx` file in `app/pages/` maps to a URL route:
|
|
|
129
131
|
| `about.tsx` | `/about` |
|
|
130
132
|
| `blog/index.tsx` | `/blog` |
|
|
131
133
|
| `blog/[slug].tsx` | `/blog/:slug` |
|
|
132
|
-
| `docs/[...path].tsx` | `/docs/*` (catch-all) |
|
|
133
|
-
| `
|
|
134
|
+
| `docs/[...path].tsx` | `/docs/*` (catch-all, required) |
|
|
135
|
+
| `users/[[id]].tsx` | `/users` or `/users/42` (optional single segment) |
|
|
136
|
+
| `files/[[...path]].tsx` | `/files` or `/files/*` (optional catch-all) |
|
|
134
137
|
|
|
135
138
|
### Page component
|
|
136
139
|
|
|
@@ -168,9 +171,12 @@ When multiple routes could match a URL, the most specific one wins:
|
|
|
168
171
|
```
|
|
169
172
|
/users/profile → users/profile.tsx (static, wins)
|
|
170
173
|
/users/42 → users/[id].tsx (dynamic)
|
|
174
|
+
/users → users/[[id]].tsx (optional single, matches with no id)
|
|
171
175
|
/users/a/b/c → users/[...rest].tsx (catch-all)
|
|
172
176
|
```
|
|
173
177
|
|
|
178
|
+
Specificity order, highest to lowest: static → `[param]` → `[[param]]` → `[...catchAll]` → `[[...optionalCatchAll]]`.
|
|
179
|
+
|
|
174
180
|
---
|
|
175
181
|
|
|
176
182
|
## Layouts
|
|
@@ -526,4 +532,4 @@ Just import the code from GitHub.
|
|
|
526
532
|
|
|
527
533
|
## License
|
|
528
534
|
|
|
529
|
-
MIT
|
|
535
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
const Link = ({ href, children, className }) => {
|
|
4
|
+
const handleClick = (e) => {
|
|
5
|
+
e.preventDefault();
|
|
6
|
+
window.history.pushState({}, "", href);
|
|
7
|
+
};
|
|
8
|
+
return /* @__PURE__ */ jsx("a", { href, onClick: handleClick, className, children });
|
|
9
|
+
};
|
|
10
|
+
var Link_default = Link;
|
|
11
|
+
export {
|
|
12
|
+
Link_default as default
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=Link.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/as-is/Link.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\r\n\r\nconst Link = ({ href, children, className }: {\r\n href: string;\r\n children: React.ReactNode;\r\n className?: string;\r\n}) => {\r\n const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {\r\n e.preventDefault();\r\n window.history.pushState({}, '', href);\r\n };\r\n\r\n return (\r\n <a href={href} onClick={handleClick} className={className}>\r\n {children}\r\n </a>\r\n );\r\n};\r\n\r\nexport default Link;\r\n"],
|
|
5
|
+
"mappings": ";AAaQ;AAXR,MAAM,OAAO,CAAC,EAAE,MAAM,UAAU,UAAU,MAIpC;AACF,QAAM,cAAc,CAAC,MAA2C;AAC5D,MAAE,eAAe;AACjB,WAAO,QAAQ,UAAU,CAAC,GAAG,IAAI,IAAI;AAAA,EACzC;AAEA,SACI,oBAAC,OAAE,MAAY,SAAS,aAAa,WAChC,UACL;AAER;AAEA,IAAO,eAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
function useRouter() {
|
|
3
|
+
try {
|
|
4
|
+
const [path, setPath] = useState(() => window.location.pathname);
|
|
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: "" };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
useRouter as default
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=useRouter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/as-is/useRouter.ts"],
|
|
4
|
+
"sourcesContent": ["import { useCallback, useEffect, useState } from \"react\";\r\n\r\ntype Router = {\r\n path: string;\r\n push: (url: string) => void;\r\n replace: (url: string) => void;\r\n};\r\n\r\nexport default function useRouter(): Router {\r\n try {\r\n const [path, setPath] = useState(() => window.location.pathname);\r\n\r\n useEffect(() => {\r\n const handleLocationChange = () => setPath(window.location.pathname);\r\n window.addEventListener(\"locationchange\", handleLocationChange);\r\n return () => window.removeEventListener(\"locationchange\", handleLocationChange);\r\n }, []);\r\n\r\n const push = useCallback((url: string) => {\r\n window.history.pushState({}, \"\", url);\r\n setPath(url);\r\n }, []);\r\n\r\n const replace = useCallback((url: string) => {\r\n window.history.replaceState({}, \"\", url);\r\n setPath(url);\r\n }, []);\r\n\r\n return { path, push, replace };\r\n } catch {\r\n return { push: () => {}, replace: () => {}, path: \"\" };\r\n }\r\n}\r\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,aAAa,WAAW,gBAAgB;AAQlC,SAAR,YAAqC;AACxC,MAAI;AACA,UAAM,CAAC,MAAM,OAAO,IAAI,SAAS,MAAM,OAAO,SAAS,QAAQ;AAE/D,cAAU,MAAM;AACZ,YAAM,uBAAuB,MAAM,QAAQ,OAAO,SAAS,QAAQ;AACnE,aAAO,iBAAiB,kBAAkB,oBAAoB;AAC9D,aAAO,MAAM,OAAO,oBAAoB,kBAAkB,oBAAoB;AAAA,IAClF,GAAG,CAAC,CAAC;AAEL,UAAM,OAAO,YAAY,CAAC,QAAgB;AACtC,aAAO,QAAQ,UAAU,CAAC,GAAG,IAAI,GAAG;AACpC,cAAQ,GAAG;AAAA,IACf,GAAG,CAAC,CAAC;AAEL,UAAM,UAAU,YAAY,CAAC,QAAgB;AACzC,aAAO,QAAQ,aAAa,CAAC,GAAG,IAAI,GAAG;AACvC,cAAQ,GAAG;AAAA,IACf,GAAG,CAAC,CAAC;AAEL,WAAO,EAAE,MAAM,MAAM,QAAQ;AAAA,EACjC,QAAQ;AACJ,WAAO,EAAE,MAAM,MAAM;AAAA,IAAC,GAAG,SAAS,MAAM;AAAA,IAAC,GAAG,MAAM,GAAG;AAAA,EACzD;AACJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/build-common.d.ts
CHANGED
|
@@ -1,41 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* build-common.ts
|
|
2
|
+
* build-common.ts — Shared Build Logic
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Used by both build-node.ts and build-vercel.ts.
|
|
5
5
|
*
|
|
6
6
|
* Exports:
|
|
7
|
-
* —
|
|
8
|
-
*
|
|
9
|
-
* —
|
|
10
|
-
*
|
|
11
|
-
* —
|
|
12
|
-
*
|
|
7
|
+
* — types : AnalyzedRoute, ServerPage, BuiltPage,
|
|
8
|
+
* PageAdapterOptions, PageBundleOptions
|
|
9
|
+
* — utility helpers : walkFiles, analyzeFile, isServerComponent,
|
|
10
|
+
* findPageLayouts, extractDefaultExportName
|
|
11
|
+
* — collection : collectServerPages, collectGlobalClientRegistry,
|
|
12
|
+
* buildPerPageRegistry
|
|
13
|
+
* — template codegen : makeApiAdapterSource, makePageAdapterSource
|
|
14
|
+
* — bundle ops : bundleApiHandler, bundlePageHandler,
|
|
15
|
+
* bundleClientComponents, buildPages,
|
|
16
|
+
* buildCombinedBundle, copyPublicFiles
|
|
13
17
|
*/
|
|
14
18
|
export interface AnalyzedRoute {
|
|
19
|
+
/** Regex string matching the URL path, e.g. '^/users/([^/]+)$' */
|
|
15
20
|
srcRegex: string;
|
|
21
|
+
/** Names of captured groups in srcRegex order */
|
|
16
22
|
paramNames: string[];
|
|
17
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Subset of paramNames that are catch-all ([...slug] or [[...path]]).
|
|
25
|
+
* Their runtime values are string[] not string.
|
|
26
|
+
*/
|
|
27
|
+
catchAllNames: string[];
|
|
28
|
+
/** Function namespace path, e.g. '/api/users' or '/page/about' */
|
|
18
29
|
funcPath: string;
|
|
19
30
|
specificity: number;
|
|
20
31
|
}
|
|
21
32
|
export interface ServerPage extends AnalyzedRoute {
|
|
22
33
|
absPath: string;
|
|
23
34
|
}
|
|
24
|
-
/** A server page together with its fully bundled ESM text, ready to emit. */
|
|
25
35
|
export interface BuiltPage extends ServerPage {
|
|
26
36
|
bundleText: string;
|
|
27
37
|
}
|
|
28
38
|
export declare function walkFiles(dir: string, base?: string): string[];
|
|
29
39
|
/**
|
|
30
40
|
* Parses dynamic-route segments from a relative file path and returns a regex,
|
|
31
|
-
* captured param names, a function path, and a
|
|
41
|
+
* captured param names, catch-all param names, a function path, and a
|
|
42
|
+
* specificity score.
|
|
43
|
+
*
|
|
44
|
+
* Supported patterns per segment:
|
|
45
|
+
* [[...name]] optional catch-all → regex (.*) → string[]
|
|
46
|
+
* [...name] required catch-all → regex (.+) → string[]
|
|
47
|
+
* [[name]] optional single → regex ([^/]*)? → string
|
|
48
|
+
* [name] required single → regex ([^/]+) → string
|
|
49
|
+
* literal static → escaped literal
|
|
32
50
|
*
|
|
33
51
|
* @param relPath Relative path from the dir root (e.g. 'users/[id].tsx').
|
|
34
52
|
* @param prefix Namespace for funcPath ('api' | 'page').
|
|
35
53
|
*/
|
|
36
54
|
export declare function analyzeFile(relPath: string, prefix?: string): AnalyzedRoute;
|
|
37
55
|
/**
|
|
38
|
-
* Returns true when a file does NOT begin with a "use client" directive
|
|
56
|
+
* Returns true when a file does NOT begin with a "use client" directive,
|
|
39
57
|
* i.e. it is a server component.
|
|
40
58
|
*/
|
|
41
59
|
export declare function isServerComponent(filePath: string): boolean;
|
|
@@ -50,23 +68,20 @@ export declare function findPageLayouts(routeFilePath: string, pagesDir: string)
|
|
|
50
68
|
*/
|
|
51
69
|
export declare function extractDefaultExportName(filePath: string): string | null;
|
|
52
70
|
/**
|
|
53
|
-
* Returns all server-component pages inside `pagesDir`, sorted
|
|
54
|
-
*
|
|
71
|
+
* Returns all server-component pages inside `pagesDir`, sorted most-specific
|
|
72
|
+
* first so precise routes shadow catch-alls in routers.
|
|
55
73
|
* layout.tsx files and "use client" files are excluded.
|
|
56
74
|
*/
|
|
57
75
|
export declare function collectServerPages(pagesDir: string): ServerPage[];
|
|
58
76
|
/**
|
|
59
77
|
* Walks every server page and its layout chain to collect all client component
|
|
60
|
-
* IDs reachable anywhere in the app.
|
|
61
|
-
* Map key is the stable content-hash ID produced by component-analyzer.ts.
|
|
78
|
+
* IDs reachable anywhere in the app.
|
|
62
79
|
*/
|
|
63
80
|
export declare function collectGlobalClientRegistry(serverPages: ServerPage[], pagesDir: string): Map<string, string>;
|
|
64
81
|
/**
|
|
65
|
-
* Builds the per-page client component registry (page + its layout chain)
|
|
66
|
-
* returns both the id→path map and the name→id map needed by
|
|
67
|
-
*
|
|
68
|
-
* Extracted here to eliminate the identical loop duplicated across
|
|
69
|
-
* build-node.ts and build-vercel.ts.
|
|
82
|
+
* Builds the per-page client component registry (page + its layout chain)
|
|
83
|
+
* and returns both the id→path map and the name→id map needed by
|
|
84
|
+
* bundlePageHandler.
|
|
70
85
|
*/
|
|
71
86
|
export declare function buildPerPageRegistry(absPath: string, layoutPaths: string[], pagesDir: string): {
|
|
72
87
|
registry: Map<string, string>;
|
|
@@ -79,19 +94,11 @@ export declare function buildPerPageRegistry(absPath: string, layoutPaths: strin
|
|
|
79
94
|
* and collects pre-rendered HTML for each.
|
|
80
95
|
* Pass 2 — bundles every server-component page into a self-contained ESM
|
|
81
96
|
* handler and returns the results as `BuiltPage[]`.
|
|
82
|
-
*
|
|
83
|
-
* Callers (build-node, build-vercel) only need to write the bundled text to
|
|
84
|
-
* their respective output destinations — the format-specific logic stays local.
|
|
85
|
-
*
|
|
86
|
-
* Returns an empty array when there are no server pages.
|
|
87
97
|
*/
|
|
88
98
|
export declare function buildPages(pagesDir: string, staticDir: string): Promise<BuiltPage[]>;
|
|
89
99
|
/**
|
|
90
100
|
* Returns the TypeScript source for a thin HTTP adapter that wraps an API
|
|
91
101
|
* route module and exposes a single `handler(req, res)` default export.
|
|
92
|
-
*
|
|
93
|
-
* @param handlerFilename Basename of the handler file relative to the adapter
|
|
94
|
-
* (e.g. 'users.ts'). Must be in the same directory.
|
|
95
102
|
*/
|
|
96
103
|
export declare function makeApiAdapterSource(handlerFilename: string): string;
|
|
97
104
|
export interface PageAdapterOptions {
|
|
@@ -107,27 +114,24 @@ export interface PageAdapterOptions {
|
|
|
107
114
|
layoutArrayItems: string;
|
|
108
115
|
/** Pre-rendered HTML per client component ID, computed at build time */
|
|
109
116
|
prerenderedHtml: Record<string, string>;
|
|
117
|
+
/** Catch-all param names whose runtime values are string[] not string */
|
|
118
|
+
catchAllNames: string[];
|
|
110
119
|
}
|
|
111
120
|
/**
|
|
112
121
|
* Returns the TypeScript source for a fully self-contained page handler.
|
|
113
122
|
*
|
|
114
123
|
* The adapter:
|
|
115
124
|
* • Inlines the html-store so useHtml() works without external deps.
|
|
116
|
-
* • Contains an async recursive renderer
|
|
117
|
-
* components without react-dom/server.
|
|
125
|
+
* • Contains an async recursive renderer for server + client components.
|
|
118
126
|
* • Client components are identified via the pre-computed CLIENT_COMPONENTS
|
|
119
|
-
* map
|
|
120
|
-
* • Emits the
|
|
121
|
-
* __n_data blob, importmap, and initRuntime bootstrap.
|
|
127
|
+
* map — no fs.readFileSync at runtime.
|
|
128
|
+
* • Emits the full HTML document including the __n_data blob and bootstrap.
|
|
122
129
|
*/
|
|
123
130
|
export declare function makePageAdapterSource(opts: PageAdapterOptions): string;
|
|
124
131
|
/**
|
|
125
132
|
* Bundles an API route handler into a single self-contained ESM string.
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* esbuild (node_modules kept external), then removes the temp file.
|
|
129
|
-
*
|
|
130
|
-
* @returns The bundled ESM text ready to write to disk.
|
|
133
|
+
* node_modules are kept external — they exist at runtime on both Node and
|
|
134
|
+
* Vercel (Vercel bundles them separately via the pages dispatcher).
|
|
131
135
|
*/
|
|
132
136
|
export declare function bundleApiHandler(absPath: string): Promise<string>;
|
|
133
137
|
export interface PageBundleOptions {
|
|
@@ -137,59 +141,26 @@ export interface PageBundleOptions {
|
|
|
137
141
|
allClientIds: string[];
|
|
138
142
|
layoutPaths: string[];
|
|
139
143
|
prerenderedHtml: Record<string, string>;
|
|
144
|
+
catchAllNames: string[];
|
|
140
145
|
}
|
|
141
146
|
/**
|
|
142
147
|
* Bundles a server-component page into a single self-contained ESM string.
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* the component resolve from the correct base directory), bundles it with
|
|
146
|
-
* esbuild (React and all npm deps inlined, only Node built-ins stay external),
|
|
147
|
-
* then removes the temp file.
|
|
148
|
-
*
|
|
149
|
-
* @returns The bundled ESM text ready to write to disk.
|
|
148
|
+
* All npm packages are kept external — the Node production server has
|
|
149
|
+
* node_modules available at runtime.
|
|
150
150
|
*/
|
|
151
151
|
export declare function bundlePageHandler(opts: PageBundleOptions): Promise<string>;
|
|
152
152
|
/**
|
|
153
153
|
* Bundles every client component in `globalRegistry` to
|
|
154
|
-
* `<staticDir>/__client-component/<id>.js
|
|
155
|
-
*
|
|
156
|
-
* Mirrors bundleClientComponent() in bundler.ts:
|
|
157
|
-
* • browser ESM, JSX automatic
|
|
158
|
-
* • react / react-dom/client / react/jsx-runtime kept external so the
|
|
159
|
-
* importmap can resolve them to the already-loaded /__react.js bundle.
|
|
154
|
+
* `<staticDir>/__client-component/<id>.js` and pre-renders each to HTML.
|
|
160
155
|
*/
|
|
161
156
|
export declare function bundleClientComponents(globalRegistry: Map<string, string>, pagesDir: string, staticDir: string): Promise<Map<string, string>>;
|
|
162
157
|
/**
|
|
163
|
-
* Builds
|
|
164
|
-
*
|
|
165
|
-
* Inlines the full React + ReactDOM runtime together with the NukeJS client
|
|
166
|
-
* runtime (bundle.ts) so the browser only needs one file instead of two.
|
|
167
|
-
* The importmap in every page points 'react', 'react-dom/client',
|
|
168
|
-
* 'react/jsx-runtime', and 'nukejs' all to /__n.js, so dynamic imports
|
|
169
|
-
* inside the runtime (e.g. `await import('react')`) hit the module cache
|
|
170
|
-
* and return the same singleton that was already loaded.
|
|
171
|
-
*
|
|
172
|
-
* Dev mode (bundler.ts) keeps separate /__react.js and /__n.js files for
|
|
173
|
-
* easier debugging — this function is production-only.
|
|
158
|
+
* Builds the combined browser bundle (__n.js) that contains the full React
|
|
159
|
+
* runtime + NukeJS client runtime in a single file.
|
|
174
160
|
*/
|
|
175
161
|
export declare function buildCombinedBundle(staticDir: string): Promise<void>;
|
|
176
162
|
/**
|
|
177
|
-
* Recursively copies every file from `
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* Called by both build-vercel.ts (dest = .vercel/output/static/) and
|
|
181
|
-
* build-node.ts (dest = dist/static/) so that:
|
|
182
|
-
*
|
|
183
|
-
* app/public/favicon.ico → <destDir>/favicon.ico
|
|
184
|
-
* app/public/images/logo.png → <destDir>/images/logo.png
|
|
185
|
-
*
|
|
186
|
-
* On Vercel, the Build Output API v3 serves everything in .vercel/output/static/
|
|
187
|
-
* directly — no route entry needed, same as __react.js and __n.js.
|
|
188
|
-
*
|
|
189
|
-
* On Node, the serverEntry template serves files from dist/static/ with the
|
|
190
|
-
* same MIME-type logic as the dev middleware.
|
|
191
|
-
*
|
|
192
|
-
* Skips silently when the public directory does not exist so projects without
|
|
193
|
-
* one don't need any special configuration.
|
|
163
|
+
* Recursively copies every file from `publicDir` into `destDir`, preserving
|
|
164
|
+
* the directory structure. Skips silently when `publicDir` does not exist.
|
|
194
165
|
*/
|
|
195
166
|
export declare function copyPublicFiles(publicDir: string, destDir: string): void;
|