html-bundle 6.2.2 → 6.3.0

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.
@@ -0,0 +1,219 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { execFile } from "node:child_process";
4
+ import {
5
+ mkdtemp,
6
+ mkdir,
7
+ readdir,
8
+ readFile,
9
+ rm,
10
+ writeFile,
11
+ } from "node:fs/promises";
12
+ import { tmpdir } from "node:os";
13
+ import path from "node:path";
14
+ import { pathToFileURL } from "node:url";
15
+ import { promisify } from "node:util";
16
+
17
+ const execFilePromise = promisify(execFile);
18
+ const repoRoot = path.resolve(new URL("..", import.meta.url).pathname);
19
+ const bundlePath = path.join(repoRoot, "dist", "bundle.mjs");
20
+ const utilsPath = path.join(repoRoot, "dist", "utils.mjs");
21
+
22
+ test("CLI builds HTML, CSS, JS, and static files", async (t) => {
23
+ const cwd = await mkdtemp(path.join(tmpdir(), "html-bundle-"));
24
+ t.after(() => rm(cwd, { force: true, recursive: true }));
25
+
26
+ await mkdir(path.join(cwd, "src", "assets"), { recursive: true });
27
+ await writeFile(
28
+ path.join(cwd, "src", "index.html"),
29
+ `<!DOCTYPE html>
30
+ <html lang="en">
31
+ <head>
32
+ <title>Fixture</title>
33
+ <link rel="stylesheet" href="./styles.css">
34
+ <style>
35
+ main { color: red; }
36
+ </style>
37
+ <script type="module" src="./app.js"></script>
38
+ <script type="module">
39
+ const value: string = "inline";
40
+ window.inlineResult = value;
41
+ </script>
42
+ </head>
43
+ <body>
44
+ <main>Fixture</main>
45
+ </body>
46
+ </html>`,
47
+ );
48
+ await writeFile(
49
+ path.join(cwd, "src", "styles.css"),
50
+ "body { margin: 0; color: #ff0000; }",
51
+ );
52
+ await writeFile(
53
+ path.join(cwd, "src", "app.ts"),
54
+ `const message: string = "external";
55
+ document.body.dataset.external = message;`,
56
+ );
57
+ await writeFile(path.join(cwd, "src", "assets", "note.txt"), "static");
58
+
59
+ await execFilePromise(process.execPath, [bundlePath], { cwd });
60
+
61
+ const html = await readFile(path.join(cwd, "build", "index.html"), "utf8");
62
+ const css = await readFile(path.join(cwd, "build", "styles.css"), "utf8");
63
+ const js = await readFile(path.join(cwd, "build", "app.js"), "utf8");
64
+ const copied = await readFile(
65
+ path.join(cwd, "build", "assets", "note.txt"),
66
+ "utf8",
67
+ );
68
+
69
+ assert.match(html, /main\{color:red\}/);
70
+ assert.match(html, /inlineResult/);
71
+ assert.match(html, /inline/);
72
+ assert.doesNotMatch(html, /value: string/);
73
+ assert.equal(css, "body{margin:0;color:red}");
74
+ assert.match(js, /external/);
75
+ assert.equal(copied, "static");
76
+
77
+ await assert.rejects(
78
+ readFile(path.join(cwd, "src", "index-bundle-1.tsx"), "utf8"),
79
+ { code: "ENOENT" },
80
+ );
81
+ });
82
+
83
+ test("addHMRCode injects stable HMR wiring", async () => {
84
+ const { addHMRCode } = await import(pathToFileURL(utilsPath).href);
85
+
86
+ const fullDocument = addHMRCode(
87
+ "<!DOCTYPE html><html><head><title>Fixture</title></head><body><main>Hi</main></body></html>",
88
+ "src/index.html",
89
+ );
90
+ const fragment = addHMRCode("<main>Hi</main>", "src/fragment.html");
91
+
92
+ assert.match(fullDocument, /<script type="module">/);
93
+ assert.match(fullDocument, /new EventSource\("\/hmr"\)/);
94
+ assert.match(fullDocument, /data-hmr="[^"]+"/);
95
+ assert.ok(
96
+ fullDocument.indexOf("<head>") <
97
+ fullDocument.indexOf('<script type="module">'),
98
+ );
99
+
100
+ assert.match(fragment, /^<main data-hmr="[^"]+">Hi<\/main>/);
101
+ assert.match(fragment, /new EventSource\("\/hmr"\)/);
102
+ });
103
+
104
+ test("getBuildPath maps src paths into the build directory", async () => {
105
+ const { getBuildPath } = await import(pathToFileURL(utilsPath).href);
106
+
107
+ assert.equal(getBuildPath("src/index.html"), "build/index.html");
108
+ assert.equal(
109
+ getBuildPath("src/nested/deep/app.ts"),
110
+ "build/nested/deep/app.ts",
111
+ );
112
+ });
113
+
114
+ test("addHMRCode ids are stable per file and unique across files", async () => {
115
+ const { addHMRCode } = await import(pathToFileURL(utilsPath).href);
116
+
117
+ const idOf = (html) => html.match(/data-hmr="([^"]+)"/)?.[1];
118
+
119
+ const first = addHMRCode("<main>Hi</main>", "src/stable.html");
120
+ const second = addHMRCode("<main>Hi</main>", "src/stable.html");
121
+ const other = addHMRCode("<main>Hi</main>", "src/other.html");
122
+
123
+ assert.equal(idOf(first), idOf(second));
124
+ assert.notEqual(idOf(first), idOf(other));
125
+ });
126
+
127
+ test("addHMRCode tags every fragment root node with the same id", async () => {
128
+ const { addHMRCode } = await import(pathToFileURL(utilsPath).href);
129
+
130
+ const fragment = addHMRCode(
131
+ "<main>Hi</main><section>Yo</section>",
132
+ "src/multi.html",
133
+ );
134
+ const mainId = fragment.match(/<main data-hmr="([^"]+)">Hi<\/main>/)?.[1];
135
+ const sectionId = fragment.match(
136
+ /<section data-hmr="([^"]+)">Yo<\/section>/,
137
+ )?.[1];
138
+
139
+ assert.ok(mainId);
140
+ assert.equal(mainId, sectionId);
141
+ });
142
+
143
+ test("CLI keeps importmap and JSON-LD scripts and cleans stale bundles", async (t) => {
144
+ const cwd = await mkdtemp(path.join(tmpdir(), "html-bundle-"));
145
+ t.after(() => rm(cwd, { force: true, recursive: true }));
146
+
147
+ await mkdir(path.join(cwd, "src"), { recursive: true });
148
+ await writeFile(
149
+ path.join(cwd, "src", "app.ts"),
150
+ `export const x: number = 1;`,
151
+ );
152
+ await writeFile(
153
+ path.join(cwd, "src", "index.html"),
154
+ `<!DOCTYPE html>
155
+ <html lang="en">
156
+ <head>
157
+ <title>Fixture</title>
158
+ <script type="importmap">{ "imports": { "app": "./app.js" } }</script>
159
+ <script type="application/ld+json">{ "@context": "https://schema.org", "@type": "WebSite" }</script>
160
+ <script type="module">globalThis.__real__ = true;</script>
161
+ </head>
162
+ <body>
163
+ <main>Hi</main>
164
+ </body>
165
+ </html>`,
166
+ );
167
+
168
+ await execFilePromise(process.execPath, [bundlePath], { cwd });
169
+
170
+ const html = await readFile(path.join(cwd, "build", "index.html"), "utf8");
171
+
172
+ assert.match(html, /type="importmap"/);
173
+ assert.match(html, /"imports"/);
174
+ assert.match(html, /application\/ld\+json/);
175
+ assert.match(html, /schema\.org/);
176
+ assert.match(html, /__real__/);
177
+
178
+ const srcEntries = await readdir(path.join(cwd, "src"), { recursive: true });
179
+ const buildEntries = await readdir(path.join(cwd, "build"), {
180
+ recursive: true,
181
+ });
182
+ assert.ok(!srcEntries.some((e) => /-bundle-\d+\.tsx$/.test(String(e))));
183
+ assert.ok(!buildEntries.some((e) => /-bundle-\d+\.js$/.test(String(e))));
184
+ });
185
+
186
+ test("CLI honors a custom bundle.config.js src and build directories", async (t) => {
187
+ const cwd = await mkdtemp(path.join(tmpdir(), "html-bundle-"));
188
+ t.after(() => rm(cwd, { force: true, recursive: true }));
189
+
190
+ await mkdir(path.join(cwd, "source"), { recursive: true });
191
+ await writeFile(
192
+ path.join(cwd, "package.json"),
193
+ JSON.stringify({ type: "module" }),
194
+ );
195
+ await writeFile(
196
+ path.join(cwd, "bundle.config.js"),
197
+ `export default { src: "source", build: "out" };`,
198
+ );
199
+ await writeFile(
200
+ path.join(cwd, "source", "index.html"),
201
+ `<!DOCTYPE html><html lang="en"><head><title>T</title><style>main { color: red; }</style></head><body><main>Hi</main></body></html>`,
202
+ );
203
+ await writeFile(
204
+ path.join(cwd, "source", "styles.css"),
205
+ "body { margin: 0; color: #ff0000; }",
206
+ );
207
+
208
+ await execFilePromise(process.execPath, [bundlePath], { cwd });
209
+
210
+ const html = await readFile(path.join(cwd, "out", "index.html"), "utf8");
211
+ const css = await readFile(path.join(cwd, "out", "styles.css"), "utf8");
212
+
213
+ assert.match(html, /main\{color:red\}/);
214
+ assert.equal(css, "body{margin:0;color:red}");
215
+ await assert.rejects(
216
+ readFile(path.join(cwd, "build", "index.html"), "utf8"),
217
+ { code: "ENOENT" },
218
+ );
219
+ });
package/tsconfig.json CHANGED
@@ -1,16 +1,15 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "lib": ["ESNext"],
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "declaration": true,
10
- "skipLibCheck": true,
11
- "moduleResolution": "node",
12
- "esModuleInterop": true,
13
- "forceConsistentCasingInFileNames": true
14
- },
15
- "include": ["src", "types"]
16
- }
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "lib": ["ESNext"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "declaration": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "forceConsistentCasingInFileNames": true
13
+ },
14
+ "include": ["src", "types"]
15
+ }
package/types/static.d.ts CHANGED
@@ -1 +1 @@
1
- declare module "critical";
1
+ declare module "critical";