html-bundle 6.0.1-beta → 6.0.4
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 +20 -15
- package/dist/bundle.mjs +36 -22
- package/dist/utils.mjs +71 -21
- package/example.gif +0 -0
- package/logo.jpg +0 -0
- package/package.json +4 -3
- package/src/bundle.mts +36 -22
- package/src/utils.mts +68 -19
- package/dist/utils.js +0 -168
- package/output.JPG +0 -0
package/README.md
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
# html-bundle
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="./logo.jpg" style="width:500px;" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
3
7
|
A (primarily) zero-config bundler for HTML files. The idea is to use HTML as Single File Components, because HTML can already include `<style>` and `<script>` elements.
|
|
4
8
|
|
|
5
9
|
## Features
|
|
6
10
|
|
|
7
11
|
- 🦾 TypeScript (reference it as .js or write inline TS)
|
|
8
12
|
- 📦 Automatic Package Installation
|
|
9
|
-
- 💨 HMR
|
|
13
|
+
- 💨 HMR and automatic reconnect
|
|
10
14
|
- ⚡ [ESBuild](https://github.com/evanw/esbuild)
|
|
11
15
|
- 🦔 [Critical CSS](https://github.com/evanw/esbuild)
|
|
12
|
-
-
|
|
16
|
+
- 🚋 Watcher on PostCSS and Tailwind CSS and TS Config
|
|
13
17
|
- 🛡️ Almost no need to restart
|
|
14
18
|
|
|
19
|
+
## Demo
|
|
20
|
+
|
|
15
21
|

|
|
16
22
|
|
|
17
23
|
## Installation and Usage
|
|
@@ -39,7 +45,7 @@ $ npm run build
|
|
|
39
45
|
|
|
40
46
|
## CLI
|
|
41
47
|
|
|
42
|
-
`--hmr`: boots up a static server and enables Hot Module Replacement. **This generates a development build
|
|
48
|
+
`--hmr`: boots up a static server and enables Hot Module Replacement. **This generates a development build and works best when not triggered from the main index.html**<br>
|
|
43
49
|
`--secure`: creates a secure HTTP2 over HTTPS instance. This requires the files `localhost.pem` and `localhost-key.pem` in the root folder. You can generate them with [mkcert](https://github.com/FiloSottile/mkcert) for instance.<br>
|
|
44
50
|
`--critical`: uses critical to extract and inline critical-path CSS to HTML.<br>
|
|
45
51
|
`--handler`: path to your custom handler. Here, you can handle all non-supported files. You can get the filename via `process.argv[2]`.
|
|
@@ -52,6 +58,7 @@ Generate the config in the root and call it "bundle.config.js"
|
|
|
52
58
|
**src:** input path. Default to "src"<br>
|
|
53
59
|
**build:** output path. Defaults to "build"<br>
|
|
54
60
|
**port:** For the HMR Server. Defaults to 5000<br>
|
|
61
|
+
**deletePrev:** Whether to delelte the build folder. Defaults to true<br>
|
|
55
62
|
**esbuild:** Your additional config<br>
|
|
56
63
|
**html-minifier-terser:** Your additional config<br>
|
|
57
64
|
**critical:** Your additional config<br>
|
|
@@ -76,13 +83,14 @@ Set `"jsxFactory": "h"` in `tsconfig.json` for JSX.
|
|
|
76
83
|
<title>Example</title>
|
|
77
84
|
<meta name="Description" content="Example for html-bundle" />
|
|
78
85
|
<script type="module">
|
|
79
|
-
import { render, h } from "hydro-js";
|
|
86
|
+
import { render, h, reactive } from "hydro-js";
|
|
80
87
|
|
|
81
|
-
function Example() {
|
|
82
|
-
return <main id="app">
|
|
88
|
+
function Example({ name }) {
|
|
89
|
+
return <main id="app">Hi {name}</main>;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
|
|
92
|
+
const name = reactive("Tester");
|
|
93
|
+
render(<Example name={name} />, "#app");
|
|
86
94
|
</script>
|
|
87
95
|
<style>
|
|
88
96
|
body {
|
|
@@ -96,11 +104,7 @@ Set `"jsxFactory": "h"` in `tsconfig.json` for JSX.
|
|
|
96
104
|
</html>
|
|
97
105
|
```
|
|
98
106
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-

|
|
102
|
-
|
|
103
|
-
## Example Vue.js
|
|
107
|
+
## Example Vue.js@next
|
|
104
108
|
|
|
105
109
|
Set `"jsxFactory": "h"` in `tsconfig.json`.
|
|
106
110
|
|
|
@@ -136,7 +140,7 @@ Set `"jsxFactory": "h"` in `tsconfig.json`.
|
|
|
136
140
|
|
|
137
141
|
## Example React
|
|
138
142
|
|
|
139
|
-
Set `"jsxFactory": "
|
|
143
|
+
Set `"jsxFactory": "h"` in `tsconfig.json`.
|
|
140
144
|
|
|
141
145
|
```html
|
|
142
146
|
<!DOCTYPE html>
|
|
@@ -149,6 +153,7 @@ Set `"jsxFactory": "React.createElement"` in `tsconfig.json`.
|
|
|
149
153
|
<script type="module">
|
|
150
154
|
import React, { useState } from "react";
|
|
151
155
|
import { render } from "react-dom";
|
|
156
|
+
const h = React.createElement;
|
|
152
157
|
|
|
153
158
|
function Example() {
|
|
154
159
|
const [count, setCount] = useState(0);
|
|
@@ -161,10 +166,10 @@ Set `"jsxFactory": "React.createElement"` in `tsconfig.json`.
|
|
|
161
166
|
);
|
|
162
167
|
}
|
|
163
168
|
|
|
164
|
-
render(<Example />, document.getElementById("
|
|
169
|
+
render(<Example />, document.getElementById("app"));
|
|
165
170
|
</script>
|
|
166
171
|
<body>
|
|
167
|
-
<div id="
|
|
172
|
+
<div id="app"></div>
|
|
168
173
|
</body>
|
|
169
174
|
</html>
|
|
170
175
|
```
|
package/dist/bundle.mjs
CHANGED
|
@@ -20,23 +20,25 @@ const handlerFile = process.argv.includes("--handler")
|
|
|
20
20
|
? process.argv[process.argv.indexOf("--handler") + 1]
|
|
21
21
|
: bundleConfig.handler;
|
|
22
22
|
process.env.NODE_ENV = isHMR ? "development" : "production"; // just in case other tools are using it
|
|
23
|
-
|
|
23
|
+
let timer = performance.now();
|
|
24
24
|
let { plugins, options, file: postcssFile } = await getPostCSSConfig();
|
|
25
25
|
let CSSprocessor = postcss(plugins);
|
|
26
26
|
let fastify;
|
|
27
27
|
const inlineFiles = new Set();
|
|
28
28
|
const TEMPLATE_LITERAL_MINIFIER = /\n\s+/g;
|
|
29
29
|
const INLINE_BUNDLE_FILE = /-bundle-\d+.tsx$/;
|
|
30
|
-
const SUPPORTED_FILES = /\.(html|css|
|
|
30
|
+
const SUPPORTED_FILES = /\.(html|css|jsx?|tsx?)$/;
|
|
31
31
|
const execFilePromise = promisify(execFile);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
if (bundleConfig.deletePrev) {
|
|
33
|
+
await rm(bundleConfig.build, { force: true, recursive: true });
|
|
34
|
+
}
|
|
35
|
+
glob(`${bundleConfig.src}/**/*`, build);
|
|
36
|
+
async function build(err, files, firstRun = true) {
|
|
35
37
|
if (err) {
|
|
36
38
|
console.error(err);
|
|
37
39
|
process.exit(1);
|
|
38
40
|
}
|
|
39
|
-
if (isHMR) {
|
|
41
|
+
if (isHMR && firstRun) {
|
|
40
42
|
fastify = await createDefaultServer(isSecure);
|
|
41
43
|
fastify.listen(bundleConfig.port);
|
|
42
44
|
console.log(`💻 Sever listening on port ${bundleConfig.port}.`);
|
|
@@ -46,7 +48,8 @@ async function build(err, files) {
|
|
|
46
48
|
if (!SUPPORTED_FILES.test(file)) {
|
|
47
49
|
if (handlerFile) {
|
|
48
50
|
const { stdout } = await execFilePromise("node", [handlerFile, file]);
|
|
49
|
-
|
|
51
|
+
if (String(stdout))
|
|
52
|
+
console.log("📋 Logging Handler: ", String(stdout));
|
|
50
53
|
}
|
|
51
54
|
else {
|
|
52
55
|
await fileCopy(file);
|
|
@@ -77,22 +80,26 @@ async function build(err, files) {
|
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
console.log(`🚀 Build finished in ${(performance.now() - timer).toFixed(2)}ms ✨`);
|
|
80
|
-
if (isHMR) {
|
|
83
|
+
if (isHMR && firstRun) {
|
|
81
84
|
console.log(`⌛ Waiting for file changes ...`);
|
|
82
85
|
if (postcssFile) {
|
|
83
86
|
const postCSSWatcher = watch(postcssFile);
|
|
84
87
|
const tailwindCSSWatcher = watch(postcssFile.replace("postcss", "tailwind")); // Assuming that the file ext is the same
|
|
88
|
+
const tsConfigWatcher = watch(postcssFile.split("\\").slice(0, -1).join("\\") + "\\tsconfig.json");
|
|
85
89
|
const cssFiles = files.filter((file) => file.endsWith(".css"));
|
|
86
90
|
postCSSWatcher.on("change", async () => await rebuildCSS(cssFiles, "postcss"));
|
|
87
91
|
tailwindCSSWatcher.on("change", async () => await rebuildCSS(cssFiles, "tailwind"));
|
|
92
|
+
tsConfigWatcher.on("change", async () => {
|
|
93
|
+
timer = performance.now();
|
|
94
|
+
await build(null, files, false);
|
|
95
|
+
});
|
|
88
96
|
}
|
|
89
97
|
const watcher = watch(bundleConfig.src);
|
|
90
|
-
let addCount = 0; // The add watcher will add all the files initially - do not rebuild them
|
|
91
98
|
watcher.on("add", async (file) => {
|
|
92
|
-
|
|
99
|
+
file = String.raw `${file}`.replace(/\\/g, "/"); // glob and chokidar diff
|
|
100
|
+
if (files.includes(file) || INLINE_BUNDLE_FILE.test(file)) {
|
|
93
101
|
return;
|
|
94
102
|
}
|
|
95
|
-
file = String.raw `${file}`.replace(/\\/g, "/"); // glob and chokidar diff
|
|
96
103
|
await rebuild(file);
|
|
97
104
|
console.log(`⚡ added ${file} to the build`);
|
|
98
105
|
});
|
|
@@ -126,7 +133,7 @@ async function build(err, files) {
|
|
|
126
133
|
let html;
|
|
127
134
|
if (file.endsWith(".html")) {
|
|
128
135
|
// To refill the inlineFiles needed to build JS
|
|
129
|
-
for (const htmlFile of files.filter((file) => file.endsWith("html"))) {
|
|
136
|
+
for (const htmlFile of files.filter((file) => file.endsWith(".html"))) {
|
|
130
137
|
await writeInlineScripts(htmlFile);
|
|
131
138
|
}
|
|
132
139
|
await minifyCode();
|
|
@@ -138,14 +145,16 @@ async function build(err, files) {
|
|
|
138
145
|
}
|
|
139
146
|
html = await minifyHTML(file, getBuildPath(file));
|
|
140
147
|
}
|
|
141
|
-
else if (
|
|
148
|
+
else if (/\.(jsx?|tsx?)$/.test(file)) {
|
|
149
|
+
inlineFiles.add(file);
|
|
142
150
|
await minifyCode();
|
|
143
151
|
}
|
|
144
152
|
else {
|
|
145
153
|
const { stdout } = await execFilePromise("node", [handlerFile, file]);
|
|
146
|
-
|
|
154
|
+
if (String(stdout))
|
|
155
|
+
console.log("📋 Logging Handler: ", String(stdout));
|
|
147
156
|
}
|
|
148
|
-
serverSentEvents({ file, html });
|
|
157
|
+
serverSentEvents?.({ file, html });
|
|
149
158
|
}
|
|
150
159
|
}
|
|
151
160
|
}
|
|
@@ -173,7 +182,7 @@ async function minifyCode() {
|
|
|
173
182
|
sourcemap: isHMR,
|
|
174
183
|
splitting: true,
|
|
175
184
|
define: {
|
|
176
|
-
"process.env.NODE_ENV": process.env.NODE_ENV
|
|
185
|
+
"process.env.NODE_ENV": `"${process.env.NODE_ENV}"`,
|
|
177
186
|
},
|
|
178
187
|
loader: { ".js": "jsx", ".ts": "tsx" },
|
|
179
188
|
bundle: true,
|
|
@@ -195,11 +204,11 @@ async function minifyCode() {
|
|
|
195
204
|
missingPkg = true;
|
|
196
205
|
const packageNameRegex = /(?<=").*(?=")/;
|
|
197
206
|
const [pkgName] = error.text.match(packageNameRegex);
|
|
198
|
-
console.log(`📦 Package ${pkgName} was installed for you`);
|
|
199
207
|
await awaitSpawn(process.platform === "win32" ? "npm.cmd" : "npm", [
|
|
200
208
|
"install",
|
|
201
209
|
pkgName,
|
|
202
210
|
]);
|
|
211
|
+
console.log(`📦 Package ${pkgName} was installed for you`);
|
|
203
212
|
}
|
|
204
213
|
}
|
|
205
214
|
if (missingPkg) {
|
|
@@ -283,11 +292,16 @@ async function minifyHTML(file, buildFile) {
|
|
|
283
292
|
}
|
|
284
293
|
fileText = serialize(DOM);
|
|
285
294
|
// Minify HTML
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
295
|
+
try {
|
|
296
|
+
fileText = await minify(fileText, {
|
|
297
|
+
collapseWhitespace: true,
|
|
298
|
+
removeComments: true,
|
|
299
|
+
...bundleConfig["html-minifier-terser"],
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
catch (e) {
|
|
303
|
+
console.error(e);
|
|
304
|
+
}
|
|
291
305
|
if (!isCritical) {
|
|
292
306
|
await writeFile(buildFile, fileText);
|
|
293
307
|
return fileText;
|
package/dist/utils.mjs
CHANGED
|
@@ -40,20 +40,25 @@ export async function createDefaultServer(isSecure) {
|
|
|
40
40
|
fastify.register(fastifyStatic, {
|
|
41
41
|
root: path.join(process.cwd(), bundleConfig.build),
|
|
42
42
|
});
|
|
43
|
-
fastify.get("/
|
|
43
|
+
fastify.get("/hmr", (_req, reply) => {
|
|
44
44
|
reply.raw.setHeader("Content-Type", "text/event-stream");
|
|
45
45
|
reply.raw.setHeader("Cache-Control", "no-cache");
|
|
46
46
|
!isSecure && reply.raw.setHeader("Connection", "keep-alive");
|
|
47
47
|
CONNECTIONS.push(reply.raw);
|
|
48
|
-
serverSentEvents = (data) =>
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
serverSentEvents = (data) => {
|
|
49
|
+
if (/\.(jsx?|tsx?)$/.test(data.file)) {
|
|
50
|
+
data.file = data.file.replace(".ts", ".js").replace(".jsx", ".js");
|
|
51
|
+
}
|
|
52
|
+
CONNECTIONS.forEach((rep) => {
|
|
53
|
+
rep.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
51
56
|
});
|
|
52
57
|
return fastify;
|
|
53
58
|
}
|
|
54
|
-
export function getPostCSSConfig() {
|
|
59
|
+
export async function getPostCSSConfig() {
|
|
55
60
|
try {
|
|
56
|
-
return postcssrc({});
|
|
61
|
+
return await postcssrc({});
|
|
57
62
|
}
|
|
58
63
|
catch {
|
|
59
64
|
return { plugins: [cssnano], options: {}, file: "" };
|
|
@@ -67,6 +72,7 @@ async function getBundleConfig() {
|
|
|
67
72
|
esbuild: {},
|
|
68
73
|
"html-minifier-terser": {},
|
|
69
74
|
critical: {},
|
|
75
|
+
deletePrev: true,
|
|
70
76
|
};
|
|
71
77
|
try {
|
|
72
78
|
const cfgPath = path.resolve(process.cwd(), "bundle.config.js");
|
|
@@ -101,21 +107,35 @@ function randomText() {
|
|
|
101
107
|
return Math.random().toString(32).slice(2);
|
|
102
108
|
}
|
|
103
109
|
function getHMRCode(file, id, src) {
|
|
104
|
-
return `import { render, html, $, $$,
|
|
110
|
+
return `import { render, html, $, $$, setShouldSetReactivity } from "hydro-js";
|
|
111
|
+
window.isHMR = true;
|
|
112
|
+
window.lastCalled = new Map();
|
|
105
113
|
if (!window.eventsource${id}) {
|
|
106
|
-
|
|
107
|
-
window.eventsource${id}
|
|
114
|
+
window.eventsource${id} = new EventSource("/hmr");
|
|
115
|
+
window.eventsource${id}.addEventListener('error', (e) => {
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
window.eventsource${id} = new EventSource("/hmr");
|
|
118
|
+
}, 1000);
|
|
119
|
+
});
|
|
108
120
|
window.eventsource${id}.addEventListener("message", ({ data }) => {
|
|
109
121
|
const dataObj = JSON.parse(data);
|
|
110
122
|
const file = "${file}";
|
|
111
123
|
|
|
112
124
|
if (file === dataObj.file && "html" in dataObj) {
|
|
125
|
+
let newHTML;
|
|
126
|
+
try {
|
|
127
|
+
newHTML = html\`\${dataObj.html}\`
|
|
128
|
+
} catch {
|
|
129
|
+
setShouldSetReactivity(false);
|
|
130
|
+
newHTML = html\`\${dataObj.html}\`
|
|
131
|
+
setShouldSetReactivity(true);
|
|
132
|
+
}
|
|
133
|
+
|
|
113
134
|
if (dataObj.html.startsWith('<!DOCTYPE html>') || dataObj.html.startsWith('<html')) {
|
|
114
135
|
document.head.remove(); // Don't try to diff the head – just re-run the scripts
|
|
115
|
-
render(
|
|
136
|
+
render(newHTML, document.documentElement, false);
|
|
116
137
|
} else {
|
|
117
138
|
const hmrID = "${id}";
|
|
118
|
-
const newHTML = html\`\${dataObj.html}\`;
|
|
119
139
|
const hmrElems = Array.from(newHTML.childNodes);
|
|
120
140
|
const hmrWheres = Array.from($$(\`[data-hmr="\${hmrID}"]\`))
|
|
121
141
|
// render new elements for old elements. Then, remove rest old elements and add add new elements after the last old one
|
|
@@ -137,13 +157,35 @@ function getHMRCode(file, id, src) {
|
|
|
137
157
|
}
|
|
138
158
|
}
|
|
139
159
|
}
|
|
160
|
+
|
|
161
|
+
$$('link[rel="stylesheet"][href]').forEach(link => {
|
|
162
|
+
link.setAttribute("href", link.getAttribute("href") + "?v=" + String(Math.random().toFixed(4)).slice(2));
|
|
163
|
+
})
|
|
164
|
+
if (dataObj.html.includes("<script")) updateElem("script");
|
|
165
|
+
|
|
140
166
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
} else if (dataObj.file.endsWith("
|
|
145
|
-
|
|
167
|
+
if (dataObj.file === \`${src}/index.html\`) {
|
|
168
|
+
dispatchEvent(new Event("popstate"));
|
|
169
|
+
}
|
|
170
|
+
} else if (dataObj.file.endsWith(".css")) {
|
|
171
|
+
const now = performance.now();
|
|
172
|
+
if (!window.lastCalled.has(dataObj.file) || now - window.lastCalled.get(dataObj.file) > 100) {
|
|
173
|
+
$$('link[rel="stylesheet"][href]').forEach(link => {
|
|
174
|
+
link.setAttribute("href", link.getAttribute("href") + "?v=" + String(Math.random().toFixed(4)).slice(2));
|
|
175
|
+
})
|
|
176
|
+
window.lastCalled.set(dataObj.file, now)
|
|
177
|
+
}
|
|
178
|
+
} else if (dataObj.file.endsWith(".js")) {
|
|
179
|
+
const now = performance.now();
|
|
180
|
+
if (!window.lastCalled.has(dataObj.file) || now - window.lastCalled.get(dataObj.file) > 100) {
|
|
181
|
+
$$('link[rel="stylesheet"][href]').forEach(link => {
|
|
182
|
+
link.setAttribute("href", link.getAttribute("href") + "?v=" + String(Math.random().toFixed(4)).slice(2));
|
|
183
|
+
})
|
|
184
|
+
updateElem("script");
|
|
185
|
+
window.lastCalled.set(dataObj.file, now)
|
|
186
|
+
}
|
|
146
187
|
}
|
|
188
|
+
|
|
147
189
|
|
|
148
190
|
function updateElem(type) {
|
|
149
191
|
const hmrId = "${id}";
|
|
@@ -152,14 +194,22 @@ function getHMRCode(file, id, src) {
|
|
|
152
194
|
const elem = $(\`[data-hmr="\${hmrId}"] \${type}[\${attr}^="\${noSrcFile}"]\`); // could be $(\`\${type}[data-hmr="\${hmrId}"][\${attr}^="\${noSrcFile}"]\`) ?
|
|
153
195
|
|
|
154
196
|
if (elem) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
197
|
+
updateOne(type, attr, elem)
|
|
198
|
+
} else {
|
|
199
|
+
for(const e of $$(\`[data-hmr="\${hmrId}"] \${type}\`)) {
|
|
200
|
+
updateOne(type, attr, e);
|
|
158
201
|
}
|
|
159
|
-
clone.setAttribute(attr, elem.getAttribute(attr) + "?v=" + String(Math.random().toFixed(4)).slice(2));
|
|
160
|
-
render(clone, elem, false);
|
|
161
202
|
}
|
|
162
203
|
}
|
|
204
|
+
|
|
205
|
+
function updateOne(type, attr, elem) {
|
|
206
|
+
const clone = document.createElement(type);
|
|
207
|
+
for (const key of elem.getAttributeNames()) {
|
|
208
|
+
clone.setAttribute(key, elem.getAttribute(key));
|
|
209
|
+
}
|
|
210
|
+
clone.setAttribute(attr, elem.getAttribute(attr) + "?v=" + String(Math.random().toFixed(4)).slice(2));
|
|
211
|
+
render(clone, elem, false);
|
|
212
|
+
}
|
|
163
213
|
});
|
|
164
214
|
}
|
|
165
215
|
`;
|
package/example.gif
CHANGED
|
Binary file
|
package/logo.jpg
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "html-bundle",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.4",
|
|
4
4
|
"description": "A very simple bundler for HTML SFC",
|
|
5
5
|
"bin": "./dist/bundle.mjs",
|
|
6
6
|
"scripts": {
|
|
@@ -36,11 +36,12 @@
|
|
|
36
36
|
"chokidar": "^3.5.2",
|
|
37
37
|
"critical": "^4.0.1",
|
|
38
38
|
"cssnano": "^5.0.14",
|
|
39
|
-
"esbuild": "^0.14.
|
|
40
|
-
"fastify": "^3.25.
|
|
39
|
+
"esbuild": "^0.14.9",
|
|
40
|
+
"fastify": "^3.25.3",
|
|
41
41
|
"fastify-static": "^4.5.0",
|
|
42
42
|
"glob": "^7.2.0",
|
|
43
43
|
"html-minifier-terser": "^6.1.0",
|
|
44
|
+
"hydro-js": "^1.5.3",
|
|
44
45
|
"parse5": "^6.0.1",
|
|
45
46
|
"postcss": "^8.4.5",
|
|
46
47
|
"postcss-load-config": "^3.1.0"
|
package/src/bundle.mts
CHANGED
|
@@ -34,27 +34,29 @@ const handlerFile = process.argv.includes("--handler")
|
|
|
34
34
|
: bundleConfig.handler;
|
|
35
35
|
|
|
36
36
|
process.env.NODE_ENV = isHMR ? "development" : "production"; // just in case other tools are using it
|
|
37
|
-
|
|
37
|
+
let timer = performance.now();
|
|
38
38
|
let { plugins, options, file: postcssFile } = await getPostCSSConfig();
|
|
39
39
|
let CSSprocessor = postcss(plugins as AcceptedPlugin[]);
|
|
40
40
|
let fastify;
|
|
41
41
|
const inlineFiles = new Set<string>();
|
|
42
42
|
const TEMPLATE_LITERAL_MINIFIER = /\n\s+/g;
|
|
43
43
|
const INLINE_BUNDLE_FILE = /-bundle-\d+.tsx$/;
|
|
44
|
-
const SUPPORTED_FILES = /\.(html|css|
|
|
44
|
+
const SUPPORTED_FILES = /\.(html|css|jsx?|tsx?)$/;
|
|
45
45
|
const execFilePromise = promisify(execFile);
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
if (bundleConfig.deletePrev) {
|
|
48
|
+
await rm(bundleConfig.build, { force: true, recursive: true });
|
|
49
|
+
}
|
|
48
50
|
|
|
49
|
-
glob(`${bundleConfig.src}
|
|
51
|
+
glob(`${bundleConfig.src}/**/*`, build);
|
|
50
52
|
|
|
51
|
-
async function build(err: any, files: string[]) {
|
|
53
|
+
async function build(err: any, files: string[], firstRun = true) {
|
|
52
54
|
if (err) {
|
|
53
55
|
console.error(err);
|
|
54
56
|
process.exit(1);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
if (isHMR) {
|
|
59
|
+
if (isHMR && firstRun) {
|
|
58
60
|
fastify = await createDefaultServer(isSecure);
|
|
59
61
|
fastify.listen(bundleConfig.port);
|
|
60
62
|
console.log(`💻 Sever listening on port ${bundleConfig.port}.`);
|
|
@@ -66,7 +68,7 @@ async function build(err: any, files: string[]) {
|
|
|
66
68
|
if (!SUPPORTED_FILES.test(file)) {
|
|
67
69
|
if (handlerFile) {
|
|
68
70
|
const { stdout } = await execFilePromise("node", [handlerFile, file]);
|
|
69
|
-
console.log("📋 Logging Handler: ", String(stdout));
|
|
71
|
+
if (String(stdout)) console.log("📋 Logging Handler: ", String(stdout));
|
|
70
72
|
} else {
|
|
71
73
|
await fileCopy(file);
|
|
72
74
|
}
|
|
@@ -97,7 +99,7 @@ async function build(err: any, files: string[]) {
|
|
|
97
99
|
`🚀 Build finished in ${(performance.now() - timer).toFixed(2)}ms ✨`
|
|
98
100
|
);
|
|
99
101
|
|
|
100
|
-
if (isHMR) {
|
|
102
|
+
if (isHMR && firstRun) {
|
|
101
103
|
console.log(`⌛ Waiting for file changes ...`);
|
|
102
104
|
|
|
103
105
|
if (postcssFile) {
|
|
@@ -105,6 +107,9 @@ async function build(err: any, files: string[]) {
|
|
|
105
107
|
const tailwindCSSWatcher = watch(
|
|
106
108
|
postcssFile.replace("postcss", "tailwind")
|
|
107
109
|
); // Assuming that the file ext is the same
|
|
110
|
+
const tsConfigWatcher = watch(
|
|
111
|
+
postcssFile.split("\\").slice(0, -1).join("\\") + "\\tsconfig.json"
|
|
112
|
+
);
|
|
108
113
|
|
|
109
114
|
const cssFiles = files.filter((file) => file.endsWith(".css"));
|
|
110
115
|
postCSSWatcher.on(
|
|
@@ -115,15 +120,18 @@ async function build(err: any, files: string[]) {
|
|
|
115
120
|
"change",
|
|
116
121
|
async () => await rebuildCSS(cssFiles, "tailwind")
|
|
117
122
|
);
|
|
123
|
+
tsConfigWatcher.on("change", async () => {
|
|
124
|
+
timer = performance.now();
|
|
125
|
+
await build(null, files, false);
|
|
126
|
+
});
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
const watcher = watch(bundleConfig.src);
|
|
121
|
-
let addCount = 0; // The add watcher will add all the files initially - do not rebuild them
|
|
122
130
|
watcher.on("add", async (file) => {
|
|
123
|
-
|
|
131
|
+
file = String.raw`${file}`.replace(/\\/g, "/"); // glob and chokidar diff
|
|
132
|
+
if (files.includes(file) || INLINE_BUNDLE_FILE.test(file)) {
|
|
124
133
|
return;
|
|
125
134
|
}
|
|
126
|
-
file = String.raw`${file}`.replace(/\\/g, "/"); // glob and chokidar diff
|
|
127
135
|
|
|
128
136
|
await rebuild(file);
|
|
129
137
|
|
|
@@ -165,7 +173,7 @@ async function build(err: any, files: string[]) {
|
|
|
165
173
|
let html;
|
|
166
174
|
if (file.endsWith(".html")) {
|
|
167
175
|
// To refill the inlineFiles needed to build JS
|
|
168
|
-
for (const htmlFile of files.filter((file) => file.endsWith("html"))) {
|
|
176
|
+
for (const htmlFile of files.filter((file) => file.endsWith(".html"))) {
|
|
169
177
|
await writeInlineScripts(htmlFile);
|
|
170
178
|
}
|
|
171
179
|
await minifyCode();
|
|
@@ -176,14 +184,15 @@ async function build(err: any, files: string[]) {
|
|
|
176
184
|
}
|
|
177
185
|
}
|
|
178
186
|
html = await minifyHTML(file, getBuildPath(file));
|
|
179
|
-
} else if (
|
|
187
|
+
} else if (/\.(jsx?|tsx?)$/.test(file)) {
|
|
188
|
+
inlineFiles.add(file);
|
|
180
189
|
await minifyCode();
|
|
181
190
|
} else {
|
|
182
191
|
const { stdout } = await execFilePromise("node", [handlerFile, file]);
|
|
183
|
-
console.log("📋 Logging Handler: ", String(stdout));
|
|
192
|
+
if (String(stdout)) console.log("📋 Logging Handler: ", String(stdout));
|
|
184
193
|
}
|
|
185
194
|
|
|
186
|
-
serverSentEvents
|
|
195
|
+
serverSentEvents?.({ file, html });
|
|
187
196
|
}
|
|
188
197
|
}
|
|
189
198
|
}
|
|
@@ -212,7 +221,7 @@ async function minifyCode(): Promise<unknown> {
|
|
|
212
221
|
sourcemap: isHMR,
|
|
213
222
|
splitting: true,
|
|
214
223
|
define: {
|
|
215
|
-
"process.env.NODE_ENV": process.env.NODE_ENV
|
|
224
|
+
"process.env.NODE_ENV": `"${process.env.NODE_ENV}"`,
|
|
216
225
|
},
|
|
217
226
|
loader: { ".js": "jsx", ".ts": "tsx" },
|
|
218
227
|
bundle: true,
|
|
@@ -234,12 +243,13 @@ async function minifyCode(): Promise<unknown> {
|
|
|
234
243
|
missingPkg = true;
|
|
235
244
|
const packageNameRegex = /(?<=").*(?=")/;
|
|
236
245
|
const [pkgName] = error.text.match(packageNameRegex);
|
|
237
|
-
console.log(`📦 Package ${pkgName} was installed for you`);
|
|
238
246
|
|
|
239
247
|
await awaitSpawn(process.platform === "win32" ? "npm.cmd" : "npm", [
|
|
240
248
|
"install",
|
|
241
249
|
pkgName,
|
|
242
250
|
]);
|
|
251
|
+
|
|
252
|
+
console.log(`📦 Package ${pkgName} was installed for you`);
|
|
243
253
|
}
|
|
244
254
|
}
|
|
245
255
|
|
|
@@ -336,11 +346,15 @@ async function minifyHTML(file: string, buildFile: string) {
|
|
|
336
346
|
fileText = serialize(DOM);
|
|
337
347
|
|
|
338
348
|
// Minify HTML
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
349
|
+
try {
|
|
350
|
+
fileText = await minify(fileText, {
|
|
351
|
+
collapseWhitespace: true,
|
|
352
|
+
removeComments: true,
|
|
353
|
+
...bundleConfig["html-minifier-terser"],
|
|
354
|
+
});
|
|
355
|
+
} catch (e) {
|
|
356
|
+
console.error(e);
|
|
357
|
+
}
|
|
344
358
|
|
|
345
359
|
if (!isCritical) {
|
|
346
360
|
await writeFile(buildFile, fileText);
|
package/src/utils.mts
CHANGED
|
@@ -58,24 +58,28 @@ export async function createDefaultServer(isSecure: boolean) {
|
|
|
58
58
|
fastify.register(fastifyStatic, {
|
|
59
59
|
root: path.join(process.cwd(), bundleConfig.build),
|
|
60
60
|
});
|
|
61
|
-
fastify.get("/
|
|
61
|
+
fastify.get("/hmr", (_req, reply) => {
|
|
62
62
|
reply.raw.setHeader("Content-Type", "text/event-stream");
|
|
63
63
|
reply.raw.setHeader("Cache-Control", "no-cache");
|
|
64
64
|
!isSecure && reply.raw.setHeader("Connection", "keep-alive");
|
|
65
65
|
|
|
66
66
|
CONNECTIONS.push(reply.raw);
|
|
67
67
|
|
|
68
|
-
serverSentEvents = (data) =>
|
|
68
|
+
serverSentEvents = (data) => {
|
|
69
|
+
if (/\.(jsx?|tsx?)$/.test(data.file)) {
|
|
70
|
+
data.file = data.file.replace(".ts", ".js").replace(".jsx", ".js");
|
|
71
|
+
}
|
|
69
72
|
CONNECTIONS.forEach((rep) => {
|
|
70
73
|
rep.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
71
74
|
});
|
|
75
|
+
};
|
|
72
76
|
});
|
|
73
77
|
return fastify;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
export function getPostCSSConfig() {
|
|
80
|
+
export async function getPostCSSConfig() {
|
|
77
81
|
try {
|
|
78
|
-
return postcssrc({});
|
|
82
|
+
return await postcssrc({});
|
|
79
83
|
} catch {
|
|
80
84
|
return { plugins: [cssnano], options: {}, file: "" };
|
|
81
85
|
}
|
|
@@ -89,6 +93,7 @@ async function getBundleConfig() {
|
|
|
89
93
|
esbuild: {},
|
|
90
94
|
"html-minifier-terser": {},
|
|
91
95
|
critical: {},
|
|
96
|
+
deletePrev: true,
|
|
92
97
|
};
|
|
93
98
|
|
|
94
99
|
try {
|
|
@@ -138,21 +143,35 @@ function randomText() {
|
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
function getHMRCode(file: string, id: string, src: string) {
|
|
141
|
-
return `import { render, html, $, $$,
|
|
146
|
+
return `import { render, html, $, $$, setShouldSetReactivity } from "hydro-js";
|
|
147
|
+
window.isHMR = true;
|
|
148
|
+
window.lastCalled = new Map();
|
|
142
149
|
if (!window.eventsource${id}) {
|
|
143
|
-
|
|
144
|
-
window.eventsource${id}
|
|
150
|
+
window.eventsource${id} = new EventSource("/hmr");
|
|
151
|
+
window.eventsource${id}.addEventListener('error', (e) => {
|
|
152
|
+
setTimeout(() => {
|
|
153
|
+
window.eventsource${id} = new EventSource("/hmr");
|
|
154
|
+
}, 1000);
|
|
155
|
+
});
|
|
145
156
|
window.eventsource${id}.addEventListener("message", ({ data }) => {
|
|
146
157
|
const dataObj = JSON.parse(data);
|
|
147
158
|
const file = "${file}";
|
|
148
159
|
|
|
149
160
|
if (file === dataObj.file && "html" in dataObj) {
|
|
161
|
+
let newHTML;
|
|
162
|
+
try {
|
|
163
|
+
newHTML = html\`\${dataObj.html}\`
|
|
164
|
+
} catch {
|
|
165
|
+
setShouldSetReactivity(false);
|
|
166
|
+
newHTML = html\`\${dataObj.html}\`
|
|
167
|
+
setShouldSetReactivity(true);
|
|
168
|
+
}
|
|
169
|
+
|
|
150
170
|
if (dataObj.html.startsWith('<!DOCTYPE html>') || dataObj.html.startsWith('<html')) {
|
|
151
171
|
document.head.remove(); // Don't try to diff the head – just re-run the scripts
|
|
152
|
-
render(
|
|
172
|
+
render(newHTML, document.documentElement, false);
|
|
153
173
|
} else {
|
|
154
174
|
const hmrID = "${id}";
|
|
155
|
-
const newHTML = html\`\${dataObj.html}\`;
|
|
156
175
|
const hmrElems = Array.from(newHTML.childNodes);
|
|
157
176
|
const hmrWheres = Array.from($$(\`[data-hmr="\${hmrID}"]\`))
|
|
158
177
|
// render new elements for old elements. Then, remove rest old elements and add add new elements after the last old one
|
|
@@ -174,13 +193,35 @@ function getHMRCode(file: string, id: string, src: string) {
|
|
|
174
193
|
}
|
|
175
194
|
}
|
|
176
195
|
}
|
|
196
|
+
|
|
197
|
+
$$('link[rel="stylesheet"][href]').forEach(link => {
|
|
198
|
+
link.setAttribute("href", link.getAttribute("href") + "?v=" + String(Math.random().toFixed(4)).slice(2));
|
|
199
|
+
})
|
|
200
|
+
if (dataObj.html.includes("<script")) updateElem("script");
|
|
201
|
+
|
|
177
202
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
} else if (dataObj.file.endsWith("
|
|
182
|
-
|
|
203
|
+
if (dataObj.file === \`${src}/index.html\`) {
|
|
204
|
+
dispatchEvent(new Event("popstate"));
|
|
205
|
+
}
|
|
206
|
+
} else if (dataObj.file.endsWith(".css")) {
|
|
207
|
+
const now = performance.now();
|
|
208
|
+
if (!window.lastCalled.has(dataObj.file) || now - window.lastCalled.get(dataObj.file) > 100) {
|
|
209
|
+
$$('link[rel="stylesheet"][href]').forEach(link => {
|
|
210
|
+
link.setAttribute("href", link.getAttribute("href") + "?v=" + String(Math.random().toFixed(4)).slice(2));
|
|
211
|
+
})
|
|
212
|
+
window.lastCalled.set(dataObj.file, now)
|
|
213
|
+
}
|
|
214
|
+
} else if (dataObj.file.endsWith(".js")) {
|
|
215
|
+
const now = performance.now();
|
|
216
|
+
if (!window.lastCalled.has(dataObj.file) || now - window.lastCalled.get(dataObj.file) > 100) {
|
|
217
|
+
$$('link[rel="stylesheet"][href]').forEach(link => {
|
|
218
|
+
link.setAttribute("href", link.getAttribute("href") + "?v=" + String(Math.random().toFixed(4)).slice(2));
|
|
219
|
+
})
|
|
220
|
+
updateElem("script");
|
|
221
|
+
window.lastCalled.set(dataObj.file, now)
|
|
222
|
+
}
|
|
183
223
|
}
|
|
224
|
+
|
|
184
225
|
|
|
185
226
|
function updateElem(type) {
|
|
186
227
|
const hmrId = "${id}";
|
|
@@ -189,14 +230,22 @@ function getHMRCode(file: string, id: string, src: string) {
|
|
|
189
230
|
const elem = $(\`[data-hmr="\${hmrId}"] \${type}[\${attr}^="\${noSrcFile}"]\`); // could be $(\`\${type}[data-hmr="\${hmrId}"][\${attr}^="\${noSrcFile}"]\`) ?
|
|
190
231
|
|
|
191
232
|
if (elem) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
233
|
+
updateOne(type, attr, elem)
|
|
234
|
+
} else {
|
|
235
|
+
for(const e of $$(\`[data-hmr="\${hmrId}"] \${type}\`)) {
|
|
236
|
+
updateOne(type, attr, e);
|
|
195
237
|
}
|
|
196
|
-
clone.setAttribute(attr, elem.getAttribute(attr) + "?v=" + String(Math.random().toFixed(4)).slice(2));
|
|
197
|
-
render(clone, elem, false);
|
|
198
238
|
}
|
|
199
239
|
}
|
|
240
|
+
|
|
241
|
+
function updateOne(type, attr, elem) {
|
|
242
|
+
const clone = document.createElement(type);
|
|
243
|
+
for (const key of elem.getAttributeNames()) {
|
|
244
|
+
clone.setAttribute(key, elem.getAttribute(key));
|
|
245
|
+
}
|
|
246
|
+
clone.setAttribute(attr, elem.getAttribute(attr) + "?v=" + String(Math.random().toFixed(4)).slice(2));
|
|
247
|
+
render(clone, elem, false);
|
|
248
|
+
}
|
|
200
249
|
});
|
|
201
250
|
}
|
|
202
251
|
`;
|
package/dist/utils.js
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { access, copyFile, mkdir, readFile } from "fs/promises";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import Fastify from "fastify";
|
|
4
|
-
import fastifyStatic from "fastify-static";
|
|
5
|
-
import postcssrc from "postcss-load-config";
|
|
6
|
-
import cssnano from "cssnano";
|
|
7
|
-
import { parse, parseFragment, serialize } from "parse5";
|
|
8
|
-
import { createScript, getTagName, findElement, appendChild, } from "@web/parse5-utils";
|
|
9
|
-
export const bundleConfig = await getBundleConfig();
|
|
10
|
-
export function fileCopy(file) {
|
|
11
|
-
return copyFile(file, getBuildPath(file));
|
|
12
|
-
}
|
|
13
|
-
export function createDir(file) {
|
|
14
|
-
const buildPath = getBuildPath(file);
|
|
15
|
-
const dir = buildPath.split("/").slice(0, -1).join("/");
|
|
16
|
-
return mkdir(dir, { recursive: true });
|
|
17
|
-
}
|
|
18
|
-
export function getBuildPath(file) {
|
|
19
|
-
return file.replace(`${bundleConfig.src}/`, `${bundleConfig.build}/`);
|
|
20
|
-
}
|
|
21
|
-
const CONNECTIONS = []; // In order to send the HMR information
|
|
22
|
-
export let serverSentEvents;
|
|
23
|
-
export async function createDefaultServer(isSecure) {
|
|
24
|
-
const fastify = Fastify(isSecure
|
|
25
|
-
? {
|
|
26
|
-
http2: true,
|
|
27
|
-
https: {
|
|
28
|
-
key: await readFile(path.join(process.cwd(), "localhost-key.pem")),
|
|
29
|
-
cert: await readFile(path.join(process.cwd(), "localhost.pem")),
|
|
30
|
-
},
|
|
31
|
-
}
|
|
32
|
-
: void 0);
|
|
33
|
-
fastify.setNotFoundHandler(async (_req, reply) => {
|
|
34
|
-
const file = await readFile(path.join(process.cwd(), bundleConfig.build, "/index.html"), {
|
|
35
|
-
encoding: "utf-8",
|
|
36
|
-
});
|
|
37
|
-
reply.header("Content-Type", "text/html; charset=UTF-8");
|
|
38
|
-
return reply.send(addHMRCode(file, `${bundleConfig.src}/index.html`));
|
|
39
|
-
});
|
|
40
|
-
fastify.register(fastifyStatic, {
|
|
41
|
-
root: path.join(process.cwd(), bundleConfig.build),
|
|
42
|
-
});
|
|
43
|
-
fastify.get("/events", (_req, reply) => {
|
|
44
|
-
reply.raw.setHeader("Content-Type", "text/event-stream");
|
|
45
|
-
reply.raw.setHeader("Cache-Control", "no-cache");
|
|
46
|
-
!isSecure && reply.raw.setHeader("Connection", "keep-alive");
|
|
47
|
-
CONNECTIONS.push(reply.raw);
|
|
48
|
-
serverSentEvents = (data) => CONNECTIONS.forEach((rep) => {
|
|
49
|
-
rep.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
return fastify;
|
|
53
|
-
}
|
|
54
|
-
export function getPostCSSConfig() {
|
|
55
|
-
try {
|
|
56
|
-
return postcssrc({});
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
return { plugins: [cssnano], options: {}, file: "" };
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
async function getBundleConfig() {
|
|
63
|
-
const base = {
|
|
64
|
-
build: "build",
|
|
65
|
-
src: "src",
|
|
66
|
-
port: 5000,
|
|
67
|
-
esbuild: {},
|
|
68
|
-
"html-minifier-terser": {},
|
|
69
|
-
critical: {},
|
|
70
|
-
};
|
|
71
|
-
try {
|
|
72
|
-
const cfgPath = path.resolve(process.cwd(), "bundle.config.cjs");
|
|
73
|
-
await access(cfgPath, 0);
|
|
74
|
-
const config = await import(`file://${cfgPath}`);
|
|
75
|
-
return { ...base, ...config.default };
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
console.log("catch");
|
|
79
|
-
return base;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const htmlIdMap = new Map();
|
|
83
|
-
export function addHMRCode(html, file, ast) {
|
|
84
|
-
if (!htmlIdMap.has(file)) {
|
|
85
|
-
htmlIdMap.set(file, randomText());
|
|
86
|
-
}
|
|
87
|
-
const script = createScript({ type: "module" }, getHMRCode(file, htmlIdMap.get(file), bundleConfig.src, bundleConfig.build));
|
|
88
|
-
let DOM;
|
|
89
|
-
if (html.includes("<!DOCTYPE html>") || html.includes("<html")) {
|
|
90
|
-
DOM = ast || parse(html);
|
|
91
|
-
const headNode = findElement(DOM, (e) => getTagName(e) === "head");
|
|
92
|
-
appendChild(headNode, script);
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
DOM = ast || parseFragment(html);
|
|
96
|
-
appendChild(DOM, script);
|
|
97
|
-
}
|
|
98
|
-
//@ts-ignore
|
|
99
|
-
DOM.childNodes.forEach((node) => node.attrs?.push({ name: "data-hmr", value: htmlIdMap.get(file) }));
|
|
100
|
-
return serialize(DOM);
|
|
101
|
-
}
|
|
102
|
-
function randomText() {
|
|
103
|
-
return Math.random().toString(32).slice(2);
|
|
104
|
-
}
|
|
105
|
-
function getHMRCode(file, id, src, build) {
|
|
106
|
-
return `import { render, html, $, $$, setInsertDiffing } from "hydro-js";
|
|
107
|
-
if (!window.eventsource${id}) {
|
|
108
|
-
setInsertDiffing(true);
|
|
109
|
-
window.eventsource${id} = new EventSource("/events");
|
|
110
|
-
window.eventsource${id}.addEventListener("message", ({ data }) => {
|
|
111
|
-
const dataObj = JSON.parse(data);
|
|
112
|
-
const file = "${file}";
|
|
113
|
-
|
|
114
|
-
if (file === dataObj.file && "html" in dataObj) {
|
|
115
|
-
if (dataObj.html.startsWith('<!DOCTYPE html>') || dataObj.html.startsWith('<html')) {
|
|
116
|
-
document.head.remove(); // Don't try to diff the head – just re-run the scripts
|
|
117
|
-
render(html\`\${dataObj.html}\`, document.documentElement);
|
|
118
|
-
} else {
|
|
119
|
-
const hmrID = "${id}";
|
|
120
|
-
const newHTML = html\`\${dataObj.html}\`;
|
|
121
|
-
const hmrElems = Array.from(newHTML.childNodes);
|
|
122
|
-
const hmrWheres = Array.from($$(\`[data-hmr="\${hmrID}"]\`))
|
|
123
|
-
// render new elements for old elements. Then, remove rest old elements and add add new elements after the last old one
|
|
124
|
-
hmrWheres.forEach((where, index) => {
|
|
125
|
-
if (index < hmrElems.length) {
|
|
126
|
-
render(hmrElems[index], where, false);
|
|
127
|
-
} else {
|
|
128
|
-
where.remove();
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
for (let rest = hmrWheres.length; rest < hmrElems.length; rest++) {
|
|
132
|
-
if (hmrWheres.length) {
|
|
133
|
-
const template = document.createElement('template');
|
|
134
|
-
hmrElems[hmrWheres.length - 1].after(template);
|
|
135
|
-
render(hmrElems[rest], template, false);
|
|
136
|
-
template.remove();
|
|
137
|
-
} else {
|
|
138
|
-
render(hmrElems[rest], false, false)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
setTimeout(() => dispatchEvent(new Event("popstate")));
|
|
144
|
-
} else if (dataObj.file.endsWith("css")) {
|
|
145
|
-
updateElem("link");
|
|
146
|
-
} else if (dataObj.file.endsWith("js")) {
|
|
147
|
-
updateElem("script")
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function updateElem(type) {
|
|
151
|
-
const hmrId = "${id}";
|
|
152
|
-
const noSrcFile = dataObj.file.replace(\`${src}/\`, '');
|
|
153
|
-
const attr = type === "script" ? "src" : "href";
|
|
154
|
-
const elem = $(\`[data-hmr="\${hmrId}"] \${type}[\${attr}^="\${noSrcFile}"]\`); // could be $(\`\${type}[data-hmr="\${hmrId}"][\${attr}^="\${noSrcFile}"]\`) ?
|
|
155
|
-
|
|
156
|
-
if (elem) {
|
|
157
|
-
const clone = document.createElement(type);
|
|
158
|
-
for (const key of elem.getAttributeNames()) {
|
|
159
|
-
clone.setAttribute(key, elem.getAttribute(key));
|
|
160
|
-
}
|
|
161
|
-
clone.setAttribute(attr, elem.getAttribute(attr) + "?v=" + String(Math.random().toFixed(4)).slice(2));
|
|
162
|
-
render(clone, elem, false);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
`;
|
|
168
|
-
}
|
package/output.JPG
DELETED
|
Binary file
|