nukejs 0.0.4 → 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 CHANGED
@@ -1,9 +1,11 @@
1
- # ☢️ NukeJS
1
+ [![NukeJS Banner](.github/banner.png)](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
- | `files/[[...path]].tsx` | `/files` or `/files/*` (optional) |
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
+ }
@@ -1,41 +1,59 @@
1
1
  /**
2
- * build-common.ts
2
+ * build-common.ts — Shared Build Logic
3
3
  *
4
- * Shared build logic used by both build-vercel.ts and build-node.ts.
4
+ * Used by both build-node.ts and build-vercel.ts.
5
5
  *
6
6
  * Exports:
7
- * — utility helpers : walkFiles, analyzeFile, isServerComponent,
8
- * findPageLayouts, extractDefaultExportName
9
- * — collection : collectServerPages, collectGlobalClientRegistry
10
- * — template codegen : makeApiAdapterSource, makePageAdapterSource
11
- * — bundle operations : bundleApiHandler, bundlePageHandler,
12
- * bundleClientComponents, buildReactBundle, buildNukeBundle
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
- /** Path used as function namespace, e.g. '/api/users' or '/page/about'. */
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 specificity score.
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 by specificity
54
- * (most-specific first so more-precise routes shadow catch-alls in routers).
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. Deduplication is automatic because the
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) and
66
- * returns both the id→path map and the name→id map needed by bundlePageHandler.
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 that handles server + client
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 (no fs.readFileSync at runtime).
120
- * • Emits the same full HTML document structure as ssr.ts including 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
- * Writes a temporary adapter next to `absPath`, bundles them together with
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,56 +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
- * Writes a temporary adapter next to `absPath` (so relative imports inside
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 the full React + ReactDOM browser bundle to `<staticDir>/__react.js`.
164
- * Exports every public hook and helper so client component bundles can import
165
- * them via the importmap without bundling React a second time.
166
- */
167
- export declare function buildReactBundle(staticDir: string): Promise<void>;
168
- /**
169
- * Builds the nukejs client runtime to `<staticDir>/__n.js`.
170
- * React and react-dom/client are kept external (resolved via importmap).
158
+ * Builds the combined browser bundle (__n.js) that contains the full React
159
+ * runtime + NukeJS client runtime in a single file.
171
160
  */
172
- export declare function buildNukeBundle(staticDir: string): Promise<void>;
161
+ export declare function buildCombinedBundle(staticDir: string): Promise<void>;
173
162
  /**
174
- * Recursively copies every file from `app/public/` into `destDir`,
175
- * preserving the directory structure.
176
- *
177
- * Called by both build-vercel.ts (dest = .vercel/output/static/) and
178
- * build-node.ts (dest = dist/static/) so that:
179
- *
180
- * app/public/favicon.ico → <destDir>/favicon.ico
181
- * app/public/images/logo.png → <destDir>/images/logo.png
182
- *
183
- * On Vercel, the Build Output API v3 serves everything in .vercel/output/static/
184
- * directly — no route entry needed, same as __react.js and __n.js.
185
- *
186
- * On Node, the serverEntry template serves files from dist/static/ with the
187
- * same MIME-type logic as the dev middleware.
188
- *
189
- * Skips silently when the public directory does not exist so projects without
190
- * 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.
191
165
  */
192
166
  export declare function copyPublicFiles(publicDir: string, destDir: string): void;