html-bundle 6.3.0 → 6.3.1

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/bundle.mjs CHANGED
File without changes
package/dist/utils.mjs CHANGED
@@ -19,23 +19,31 @@ export function createDir(file) {
19
19
  export function getBuildPath(file) {
20
20
  return file.replace(`${bundleConfig.src}/`, `${bundleConfig.build}/`);
21
21
  }
22
- const CONNECTIONS = []; // In order to send the HMR information
22
+ const CONNECTIONS = new Set(); // In order to send the HMR information
23
23
  export let serverSentEvents;
24
24
  export async function createDefaultServer(isSecure) {
25
25
  const router = express.Router();
26
26
  const app = express();
27
27
  app.use(router);
28
28
  app.use(express.static(path.join(process.cwd(), bundleConfig.build)));
29
- router.get("/hmr", (_req, reply) => {
29
+ router.get("/hmr", (req, reply) => {
30
30
  reply.setHeader("Content-Type", "text/event-stream");
31
31
  reply.setHeader("Cache-Control", "no-cache");
32
32
  !isSecure && reply.setHeader("Connection", "keep-alive");
33
- CONNECTIONS.push(reply);
33
+ reply.flushHeaders();
34
+ CONNECTIONS.add(reply);
35
+ req.on("close", () => {
36
+ CONNECTIONS.delete(reply);
37
+ });
34
38
  serverSentEvents = (data) => {
35
39
  if (/\.(jsx?|tsx?)$/.test(data.file)) {
36
40
  data.file = data.file.replace(".ts", ".js").replace(".jsx", ".js");
37
41
  }
38
42
  CONNECTIONS.forEach((rep) => {
43
+ if (rep.destroyed || rep.writableEnded) {
44
+ CONNECTIONS.delete(rep);
45
+ return;
46
+ }
39
47
  rep.write(`data: ${JSON.stringify(data)}\n\n`);
40
48
  });
41
49
  };
@@ -118,14 +126,50 @@ function getHMRCode(file, id, src) {
118
126
  return `import { render, html, $, $$, setShouldSetReactivity } from "hydro-js";
119
127
  window.isHMR = true;
120
128
  window.lastCalled = new Map();
121
- if (!window.eventsource${id}) {
122
- window.eventsource${id} = new EventSource("/hmr");
123
- window.eventsource${id}.addEventListener('error', (e) => {
124
- setTimeout(() => {
125
- window.eventsource${id} = new EventSource("/hmr");
126
- }, 1000);
129
+ window.htmlBundleHMRConnections ||= new Map();
130
+ window.htmlBundleHMRActiveId = "${id}";
131
+ let shouldReconnect = true;
132
+ let reconnectTimer;
133
+
134
+ for (const [hmrId, eventSource] of window.htmlBundleHMRConnections) {
135
+ if (hmrId !== "${id}") {
136
+ eventSource.close();
137
+ window.htmlBundleHMRConnections.delete(hmrId);
138
+ delete window["eventsource" + hmrId];
139
+ }
140
+ }
141
+
142
+ function closeEventSource() {
143
+ const eventSource = window.eventsource${id};
144
+ if (eventSource) {
145
+ eventSource.close();
146
+ window.htmlBundleHMRConnections.delete("${id}");
147
+ delete window.eventsource${id};
148
+ }
149
+ }
150
+
151
+ function connectEventSource() {
152
+ closeEventSource();
153
+ shouldReconnect = true;
154
+
155
+ const eventSource = new EventSource("/hmr");
156
+ window.eventsource${id} = eventSource;
157
+ window.htmlBundleHMRConnections.set("${id}", eventSource);
158
+
159
+ eventSource.addEventListener('error', () => {
160
+ eventSource.close();
161
+ if (window.eventsource${id} === eventSource) {
162
+ window.htmlBundleHMRConnections.delete("${id}");
163
+ delete window.eventsource${id};
164
+ }
165
+
166
+ if (shouldReconnect && window.htmlBundleHMRActiveId === "${id}") {
167
+ clearTimeout(reconnectTimer);
168
+ reconnectTimer = setTimeout(connectEventSource, 1000);
169
+ }
127
170
  });
128
- window.eventsource${id}.addEventListener("message", ({ data }) => {
171
+
172
+ eventSource.addEventListener("message", ({ data }) => {
129
173
  if (window.lastScroll == null) {
130
174
  window.lastScroll = window.scrollY;
131
175
  }
@@ -231,5 +275,18 @@ function getHMRCode(file, id, src) {
231
275
  }
232
276
  });
233
277
  }
278
+
279
+ window.addEventListener("pagehide", () => {
280
+ shouldReconnect = false;
281
+ clearTimeout(reconnectTimer);
282
+ if (window.htmlBundleHMRActiveId === "${id}") {
283
+ delete window.htmlBundleHMRActiveId;
284
+ }
285
+ closeEventSource();
286
+ }, { once: true });
287
+
288
+ if (!window.eventsource${id}) {
289
+ connectEventSource();
290
+ }
234
291
  `;
235
292
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "html-bundle",
3
- "version": "6.3.0",
3
+ "version": "6.3.1",
4
4
  "description": "A very simple bundler for HTML SFC",
5
5
  "bin": "./dist/bundle.mjs",
6
6
  "main": "./dist/bundle.mjs",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "types": "./dist/bundle.d.mts",
13
13
  "scripts": {
14
- "start": "tsc",
14
+ "start": "tsc && node -e \"require('fs').chmodSync('dist/bundle.mjs', 0o755)\"",
15
15
  "test": "npm run start && node --test tests/*.test.mjs",
16
16
  "update": "npx npm-check-updates -u && npx typesync && npm i && npm outdated"
17
17
  },
package/src/utils.mts CHANGED
@@ -32,28 +32,39 @@ export function getBuildPath(file: string) {
32
32
  return file.replace(`${bundleConfig.src}/`, `${bundleConfig.build}/`);
33
33
  }
34
34
 
35
- const CONNECTIONS: Array<any> = []; // In order to send the HMR information
35
+ const CONNECTIONS = new Set<any>(); // In order to send the HMR information
36
36
  export let serverSentEvents:
37
37
  | undefined
38
38
  | (({ file, html }: { file: string; html?: string }) => void);
39
39
  export async function createDefaultServer(
40
- isSecure: boolean
40
+ isSecure: boolean,
41
41
  ): Promise<[Router, Server | HTTPSServer]> {
42
42
  const router = express.Router();
43
43
  const app = express();
44
44
  app.use(router);
45
45
  app.use(express.static(path.join(process.cwd(), bundleConfig.build)));
46
46
 
47
- router.get("/hmr", (_req, reply) => {
47
+ router.get("/hmr", (req, reply) => {
48
48
  reply.setHeader("Content-Type", "text/event-stream");
49
49
  reply.setHeader("Cache-Control", "no-cache");
50
50
  !isSecure && reply.setHeader("Connection", "keep-alive");
51
- CONNECTIONS.push(reply);
51
+ reply.flushHeaders();
52
+
53
+ CONNECTIONS.add(reply);
54
+ req.on("close", () => {
55
+ CONNECTIONS.delete(reply);
56
+ });
57
+
52
58
  serverSentEvents = (data) => {
53
59
  if (/\.(jsx?|tsx?)$/.test(data.file)) {
54
60
  data.file = data.file.replace(".ts", ".js").replace(".jsx", ".js");
55
61
  }
56
62
  CONNECTIONS.forEach((rep) => {
63
+ if (rep.destroyed || rep.writableEnded) {
64
+ CONNECTIONS.delete(rep);
65
+ return;
66
+ }
67
+
57
68
  rep.write(`data: ${JSON.stringify(data)}\n\n`);
58
69
  });
59
70
  };
@@ -65,7 +76,7 @@ export async function createDefaultServer(
65
76
  path.join(process.cwd(), bundleConfig.build, "index.html"),
66
77
  {
67
78
  encoding: "utf-8",
68
- }
79
+ },
69
80
  );
70
81
  res.send(file);
71
82
  });
@@ -82,7 +93,7 @@ export async function createDefaultServer(
82
93
  bundleConfig.cert ||
83
94
  (await readFile(path.join(process.cwd(), "localhost.pem"))),
84
95
  },
85
- app
96
+ app,
86
97
  )
87
98
  : http.createServer({}, app),
88
99
  ];
@@ -125,7 +136,7 @@ const htmlIdMap = new Map();
125
136
  export function addHMRCode(
126
137
  html: string,
127
138
  file: string,
128
- ast?: ReturnType<typeof parse | typeof parseFragment>
139
+ ast?: ReturnType<typeof parse | typeof parseFragment>,
129
140
  ) {
130
141
  if (!htmlIdMap.has(file)) {
131
142
  htmlIdMap.set(file, randomText());
@@ -133,7 +144,7 @@ export function addHMRCode(
133
144
 
134
145
  const script = createScript(
135
146
  { type: "module" },
136
- getHMRCode(file, htmlIdMap.get(file), bundleConfig.src)
147
+ getHMRCode(file, htmlIdMap.get(file), bundleConfig.src),
137
148
  );
138
149
 
139
150
  let DOM;
@@ -148,7 +159,7 @@ export function addHMRCode(
148
159
 
149
160
  //@ts-ignore
150
161
  DOM.childNodes.forEach((node) =>
151
- node.attrs?.push({ name: "data-hmr", value: htmlIdMap.get(file) })
162
+ node.attrs?.push({ name: "data-hmr", value: htmlIdMap.get(file) }),
152
163
  );
153
164
 
154
165
  return serialize(DOM as any);
@@ -162,14 +173,50 @@ function getHMRCode(file: string, id: string, src: string) {
162
173
  return `import { render, html, $, $$, setShouldSetReactivity } from "hydro-js";
163
174
  window.isHMR = true;
164
175
  window.lastCalled = new Map();
165
- if (!window.eventsource${id}) {
166
- window.eventsource${id} = new EventSource("/hmr");
167
- window.eventsource${id}.addEventListener('error', (e) => {
168
- setTimeout(() => {
169
- window.eventsource${id} = new EventSource("/hmr");
170
- }, 1000);
176
+ window.htmlBundleHMRConnections ||= new Map();
177
+ window.htmlBundleHMRActiveId = "${id}";
178
+ let shouldReconnect = true;
179
+ let reconnectTimer;
180
+
181
+ for (const [hmrId, eventSource] of window.htmlBundleHMRConnections) {
182
+ if (hmrId !== "${id}") {
183
+ eventSource.close();
184
+ window.htmlBundleHMRConnections.delete(hmrId);
185
+ delete window["eventsource" + hmrId];
186
+ }
187
+ }
188
+
189
+ function closeEventSource() {
190
+ const eventSource = window.eventsource${id};
191
+ if (eventSource) {
192
+ eventSource.close();
193
+ window.htmlBundleHMRConnections.delete("${id}");
194
+ delete window.eventsource${id};
195
+ }
196
+ }
197
+
198
+ function connectEventSource() {
199
+ closeEventSource();
200
+ shouldReconnect = true;
201
+
202
+ const eventSource = new EventSource("/hmr");
203
+ window.eventsource${id} = eventSource;
204
+ window.htmlBundleHMRConnections.set("${id}", eventSource);
205
+
206
+ eventSource.addEventListener('error', () => {
207
+ eventSource.close();
208
+ if (window.eventsource${id} === eventSource) {
209
+ window.htmlBundleHMRConnections.delete("${id}");
210
+ delete window.eventsource${id};
211
+ }
212
+
213
+ if (shouldReconnect && window.htmlBundleHMRActiveId === "${id}") {
214
+ clearTimeout(reconnectTimer);
215
+ reconnectTimer = setTimeout(connectEventSource, 1000);
216
+ }
171
217
  });
172
- window.eventsource${id}.addEventListener("message", ({ data }) => {
218
+
219
+ eventSource.addEventListener("message", ({ data }) => {
173
220
  if (window.lastScroll == null) {
174
221
  window.lastScroll = window.scrollY;
175
222
  }
@@ -275,5 +322,18 @@ function getHMRCode(file: string, id: string, src: string) {
275
322
  }
276
323
  });
277
324
  }
325
+
326
+ window.addEventListener("pagehide", () => {
327
+ shouldReconnect = false;
328
+ clearTimeout(reconnectTimer);
329
+ if (window.htmlBundleHMRActiveId === "${id}") {
330
+ delete window.htmlBundleHMRActiveId;
331
+ }
332
+ closeEventSource();
333
+ }, { once: true });
334
+
335
+ if (!window.eventsource${id}) {
336
+ connectEventSource();
337
+ }
278
338
  `;
279
339
  }
@@ -91,6 +91,12 @@ test("addHMRCode injects stable HMR wiring", async () => {
91
91
 
92
92
  assert.match(fullDocument, /<script type="module">/);
93
93
  assert.match(fullDocument, /new EventSource\("\/hmr"\)/);
94
+ assert.match(
95
+ fullDocument,
96
+ /window\.htmlBundleHMRConnections \|\|= new Map\(\)/,
97
+ );
98
+ assert.match(fullDocument, /window\.addEventListener\("pagehide"/);
99
+ assert.match(fullDocument, /eventSource\.close\(\)/);
94
100
  assert.match(fullDocument, /data-hmr="[^"]+"/);
95
101
  assert.ok(
96
102
  fullDocument.indexOf("<head>") <
@@ -99,6 +105,7 @@ test("addHMRCode injects stable HMR wiring", async () => {
99
105
 
100
106
  assert.match(fragment, /^<main data-hmr="[^"]+">Hi<\/main>/);
101
107
  assert.match(fragment, /new EventSource\("\/hmr"\)/);
108
+ assert.match(fragment, /window\.htmlBundleHMRActiveId ===/);
102
109
  });
103
110
 
104
111
  test("getBuildPath maps src paths into the build directory", async () => {