next-single-file 1.0.0 โ 1.0.2
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 +80 -38
- package/dist/cli.js +9 -6
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -1,39 +1,50 @@
|
|
|
1
|
-
#
|
|
1
|
+
# next-single-file
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/next-single-file)
|
|
4
|
+
[](https://www.npmjs.com/package/next-single-file)
|
|
5
|
+
[](https://nextjs.org)
|
|
6
|
+
[](https://bun.sh)
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
A CLI tool that transforms a Next.js static export into a **single, self-contained HTML file** with hash-based routing. Regex-based, zero runtime dependencies.
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
## How it Works
|
|
11
|
+
|
|
12
|
+
The tool parses your `out/` directory, extracts all routes, and bundles everything into one file. All assets (JS, CSS, fonts, images) are inlined as base64 data URIs, and a hash-based router is injected for client-side navigation.
|
|
9
13
|
|
|
10
14
|
```mermaid
|
|
11
15
|
graph TD
|
|
12
16
|
A[Next.js App] -->|next build| B[out/ Directory]
|
|
13
|
-
B -->|Parser| C[Asset Map
|
|
14
|
-
C -->|Inliner| D[Data URIs
|
|
17
|
+
B -->|Parser| C[Asset Map & Routes]
|
|
18
|
+
C -->|Inliner| D[Data URIs & Bundles]
|
|
15
19
|
D -->|Bundler| E[Single index.html]
|
|
16
|
-
F[Hash Router
|
|
17
|
-
|
|
18
|
-
subgraph "Single HTML File"
|
|
19
|
-
E
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
E -->|Browser| G[Hash-based Navigation]
|
|
23
|
-
G -->|#/about| H[DOM Swap via ROUTE_MAP]
|
|
20
|
+
F[Hash Router] -->|Injected| E
|
|
21
|
+
E -->|Browser| G[Hash Navigation]
|
|
24
22
|
```
|
|
25
23
|
|
|
26
|
-
##
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- **Self-Contained** โ Zero external dependencies. Fonts, images, and scripts are all inlined.
|
|
27
|
+
- **Hash Routing** โ Automatically converts path navigation to `#/hash` navigation.
|
|
28
|
+
- **Next.js Compatible** โ Supports Geist fonts, Turbopack, and modern Next.js features.
|
|
29
|
+
- **Robust Encoding** โ Uses Base64 for the internal route map to prevent minification issues.
|
|
30
|
+
- **Browser Shims** โ Polyfills `document.currentScript` and other APIs Next.js expects.
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bunx next-single-file --input out --output dist/index.html
|
|
36
|
+
```
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
Or with npm (requires Bun to be installed):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx next-single-file --input out --output dist/index.html
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
### 1. Configure Next.js for Static Export
|
|
35
47
|
|
|
36
|
-
### 1. Generate Static Export
|
|
37
48
|
Ensure your `next.config.js` has `output: 'export'`:
|
|
38
49
|
|
|
39
50
|
```javascript
|
|
@@ -41,35 +52,66 @@ Ensure your `next.config.js` has `output: 'export'`:
|
|
|
41
52
|
const nextConfig = {
|
|
42
53
|
output: 'export',
|
|
43
54
|
};
|
|
55
|
+
|
|
44
56
|
module.exports = nextConfig;
|
|
45
57
|
```
|
|
46
58
|
|
|
47
|
-
|
|
59
|
+
> [!WARNING]
|
|
60
|
+
> **Purely Client-Side Runtime**
|
|
61
|
+
> This tool generates a standalone bundle with no backend.
|
|
62
|
+
> - **Server Logic:** Features like Server Actions, `cookies()`, and Middleware are not supported.
|
|
63
|
+
> - **Dynamic Routes:** You must use `generateStaticParams` for any dynamic paths (e.g., `[id].tsx`) to ensure they are pre-rendered into the `out/` directory before bundling.
|
|
64
|
+
> - **RSC:** React Server Components are supported only insofar as they can be statically rendered to HTML at build time.
|
|
65
|
+
|
|
66
|
+
### 2. Build Your Next.js App
|
|
67
|
+
|
|
48
68
|
```bash
|
|
49
|
-
|
|
50
|
-
# or
|
|
69
|
+
# Any pkg manager is fine
|
|
51
70
|
bun run build
|
|
52
71
|
```
|
|
53
72
|
|
|
54
|
-
###
|
|
73
|
+
### 3. Generate Single HTML File
|
|
74
|
+
|
|
55
75
|
```bash
|
|
56
|
-
# you need bun installed
|
|
76
|
+
# npx works too, you need bun installed on your system though
|
|
57
77
|
bunx next-single-file --input out --output dist/index.html
|
|
58
|
-
# or npm, needs bun installed
|
|
59
|
-
npx next-single-file --input out --output dist/index.html
|
|
60
|
-
|
|
61
78
|
```
|
|
62
79
|
|
|
63
|
-
##
|
|
80
|
+
## Use Cases
|
|
81
|
+
|
|
82
|
+
| Use Case | Description |
|
|
83
|
+
|----------|-------------|
|
|
84
|
+
| **Portable Demos** | Send a fully functional web app as a single email attachment |
|
|
85
|
+
| **Offline Documentation** | Create interactive docs that work without internet |
|
|
86
|
+
| **Embedded UIs** | Embed Next.js interfaces into desktop apps or dashboards |
|
|
87
|
+
| **Simple Hosting** | Host multi-page apps on GitHub Gists or basic file servers |
|
|
88
|
+
|
|
89
|
+
## Benchmark
|
|
90
|
+
|
|
91
|
+
Performance on the included test Next.js app (averaged over 3 runs):
|
|
92
|
+
|
|
93
|
+
| Metric | Value |
|
|
94
|
+
|--------|-------|
|
|
95
|
+
| **Duration** | ~392 ms |
|
|
96
|
+
| **Output Size** | ~13.9 MB |
|
|
97
|
+
| **Memory Usage** | ~77 MB |
|
|
64
98
|
|
|
65
|
-
|
|
66
|
-
- **Offline Documentation**: Create rich, interactive docs that work without an internet connection.
|
|
67
|
-
- **Embedded UIs**: Embed a Next.js interface into desktop applications or hardware dashboards.
|
|
68
|
-
- **Simple Hosting**: Host a multi-page app on GitHub Gists or any basic file server.
|
|
99
|
+
Run your own benchmark:
|
|
69
100
|
|
|
70
|
-
|
|
101
|
+
```bash
|
|
102
|
+
bun run benchmark
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
> Results may vary based on your app size and system. The test app includes 4 routes with images, fonts, and interactivity.
|
|
106
|
+
|
|
107
|
+
## Development
|
|
71
108
|
|
|
72
|
-
To run the tests:
|
|
73
109
|
```bash
|
|
110
|
+
bun install
|
|
74
111
|
bun test
|
|
112
|
+
bun run build
|
|
75
113
|
```
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/parser.ts
|
|
5
|
-
import { readdir, readFile } from "
|
|
6
|
-
import { join, relative, extname } from "
|
|
5
|
+
import { readdir, readFile } from "fs/promises";
|
|
6
|
+
import { join, relative, extname } from "path";
|
|
7
7
|
async function walk(dir) {
|
|
8
8
|
const files = [];
|
|
9
9
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -412,14 +412,14 @@ function extractMeta(html) {
|
|
|
412
412
|
function escapeScriptTag(js) {
|
|
413
413
|
return js.replace(/<\/script>/gi, "<\\/script>");
|
|
414
414
|
}
|
|
415
|
+
function minifyHtml(html) {
|
|
416
|
+
return html.replace(/<!--[\s\S]*?-->/g, "").replace(/>\s+</g, "><").replace(/\s{2,}/g, " ").trim();
|
|
417
|
+
}
|
|
415
418
|
function bundleToSingleHtml(inlined, routerShim) {
|
|
416
419
|
const indexRoute = inlined.routes.find((r) => r.path === "/");
|
|
417
420
|
if (!indexRoute) {
|
|
418
421
|
throw new Error("No index route found");
|
|
419
422
|
}
|
|
420
|
-
const anyRoute = inlined.routes[0];
|
|
421
|
-
if (!anyRoute)
|
|
422
|
-
throw new Error("No routes found");
|
|
423
423
|
const htmlAttrs = extractHtmlAttrs(indexRoute.htmlContent);
|
|
424
424
|
const bodyAttrs = extractBodyAttrs(indexRoute.htmlContent);
|
|
425
425
|
const title = extractTitle(indexRoute.headContent);
|
|
@@ -445,7 +445,7 @@ function bundleToSingleHtml(inlined, routerShim) {
|
|
|
445
445
|
};
|
|
446
446
|
}
|
|
447
447
|
`;
|
|
448
|
-
|
|
448
|
+
const finalHtml = `<!DOCTYPE html><!--${inlined.buildId}-->
|
|
449
449
|
<html${htmlAttrs}>
|
|
450
450
|
<head>
|
|
451
451
|
<meta charset="utf-8">
|
|
@@ -473,6 +473,7 @@ ${escapeScriptTag(routerShim)}
|
|
|
473
473
|
</script>
|
|
474
474
|
</body>
|
|
475
475
|
</html>`;
|
|
476
|
+
return minifyHtml(finalHtml);
|
|
476
477
|
}
|
|
477
478
|
|
|
478
479
|
// index.ts
|
|
@@ -511,3 +512,5 @@ await $`mkdir -p ${outputFile.split("/").slice(0, -1).join("/") || "."}`.quiet()
|
|
|
511
512
|
await Bun.write(outputFile, html);
|
|
512
513
|
console.log(`\u2705 Done! Output: ${outputFile}`);
|
|
513
514
|
console.log(` Size: ${(html.length / 1024).toFixed(1)} KB`);
|
|
515
|
+
console.log("Star us: https://github.com/simples-tools/next-single-file");
|
|
516
|
+
console.log("Report bugs: https://github.com/simples-tools/next-single-file/issues");
|
package/package.json
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-single-file",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Convert Next.js static export to a single HTML file with hash routing",
|
|
5
5
|
"bin": {
|
|
6
|
-
"next-single-file": "
|
|
6
|
+
"next-single-file": "dist/cli.js"
|
|
7
7
|
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/simples-tools/next-single-file.git"
|
|
11
|
+
},
|
|
12
|
+
"author": "@brrock <github.com/brrock>",
|
|
8
13
|
"files": [
|
|
9
14
|
"dist/cli.js"
|
|
10
15
|
],
|
|
11
16
|
"scripts": {
|
|
12
|
-
"build": "bun build ./index.ts --target
|
|
17
|
+
"build": "bun build ./index.ts --target bun --outfile ./dist/cli.js && chmod +x ./dist/cli.js",
|
|
13
18
|
"test": "bun test",
|
|
19
|
+
"benchmark": "bun run src/benchmark.ts",
|
|
14
20
|
"convert": "bun run ./index.ts"
|
|
15
21
|
},
|
|
16
22
|
"type": "module",
|