html-bundle 5.5.2 → 6.0.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/README.md +48 -30
- package/dist/bundle.mjs +279 -566
- package/dist/utils.mjs +195 -0
- package/logo.jpg +0 -0
- package/package.json +2 -1
- package/src/bundle.mts +315 -653
- package/src/index.d.ts +3 -0
- package/src/utils.mts +231 -0
package/dist/bundle.mjs
CHANGED
|
@@ -1,628 +1,341 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import fs from "fs";
|
|
3
2
|
import { performance } from "perf_hooks";
|
|
4
|
-
import
|
|
3
|
+
import { readFile, rm, writeFile, readdir } from "fs/promises";
|
|
4
|
+
import { execFile } from "child_process";
|
|
5
|
+
import { promisify } from "util";
|
|
5
6
|
import glob from "glob";
|
|
6
|
-
import path from "path";
|
|
7
|
-
import Fastify from "fastify";
|
|
8
|
-
import fastifyStatic from "fastify-static";
|
|
9
7
|
import postcss from "postcss";
|
|
10
|
-
import postcssrc from "postcss-load-config";
|
|
11
|
-
import cssnano from "cssnano";
|
|
12
8
|
import esbuild from "esbuild";
|
|
13
9
|
import critical from "critical";
|
|
14
10
|
import { minify } from "html-minifier-terser";
|
|
15
11
|
import { watch } from "chokidar";
|
|
16
12
|
import { serialize, parse, parseFragment } from "parse5";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const isHMR = process.argv.includes("--hmr");
|
|
21
|
-
const
|
|
22
|
-
const isSecure = process.argv.includes("--secure");
|
|
23
|
-
const
|
|
13
|
+
import { getTagName, findElements } from "@web/parse5-utils";
|
|
14
|
+
import awaitSpawn from "await-spawn";
|
|
15
|
+
import { fileCopy, createDefaultServer, getPostCSSConfig, getBuildPath, createDir, bundleConfig, serverSentEvents, addHMRCode, } from "./utils.mjs";
|
|
16
|
+
const isHMR = process.argv.includes("--hmr") || bundleConfig.hmr;
|
|
17
|
+
const isCritical = process.argv.includes("--critical") || bundleConfig.critical;
|
|
18
|
+
const isSecure = process.argv.includes("--secure") || bundleConfig.secure; // uses CSP for critical too
|
|
19
|
+
const handlerFile = process.argv.includes("--handler")
|
|
20
|
+
? process.argv[process.argv.indexOf("--handler") + 1]
|
|
21
|
+
: bundleConfig.handler;
|
|
22
|
+
process.env.NODE_ENV = isHMR ? "development" : "production"; // just in case other tools are using it
|
|
23
|
+
let timer = performance.now();
|
|
24
|
+
let { plugins, options, file: postcssFile } = await getPostCSSConfig();
|
|
25
|
+
let CSSprocessor = postcss(plugins);
|
|
24
26
|
let fastify;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const inlineFiles = new Set();
|
|
28
|
+
const TEMPLATE_LITERAL_MINIFIER = /\n\s+/g;
|
|
29
|
+
const INLINE_BUNDLE_FILE = /-bundle-\d+.tsx$/;
|
|
30
|
+
const SUPPORTED_FILES = /\.(html|css|jsx?|tsx?)$/;
|
|
31
|
+
const execFilePromise = promisify(execFile);
|
|
32
|
+
if (bundleConfig.deletePrev) {
|
|
33
|
+
await rm(bundleConfig.build, { force: true, recursive: true });
|
|
29
34
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
tailwindCSSWatcher.on("change", () => rebuildCSS("tailwind"));
|
|
52
|
-
}
|
|
53
|
-
console.log(`⌛ Waiting for file changes ...`);
|
|
54
|
-
const watcher = watch(SOURCE_FOLDER);
|
|
55
|
-
// The add watcher will add all the files initially - do not rebuild them
|
|
56
|
-
let initialAdd = 0;
|
|
57
|
-
let hasJSTS = false;
|
|
58
|
-
watcher.on("add", (filename) => {
|
|
59
|
-
start = performance.now();
|
|
60
|
-
rebuildCSS();
|
|
61
|
-
// Return if it was added by the build system itself
|
|
62
|
-
if (/-bundle-\d+\.(j|t)sx?$/.test(filename)) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
filename = String.raw `${filename}`.replace(/\\/g, "/");
|
|
66
|
-
if (filename.endsWith(".html") || filename.endsWith(".css")) {
|
|
67
|
-
initialAdd++;
|
|
68
|
-
}
|
|
69
|
-
else if (hasJSTS === false && /\.(j|t)sx?$/.test(filename)) {
|
|
70
|
-
hasJSTS = true;
|
|
71
|
-
initialAdd++;
|
|
72
|
-
}
|
|
73
|
-
if (initialAdd <= expectedTasks)
|
|
74
|
-
return;
|
|
75
|
-
const [buildFilename, buildPathDir] = getBuildNames(filename);
|
|
76
|
-
fs.mkdir(buildPathDir, { recursive: true }, (err) => {
|
|
77
|
-
errorHandler(err);
|
|
78
|
-
rebuild(filename);
|
|
79
|
-
console.log(`⚡ added ${buildFilename}`);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
watcher.on("change", (filename) => {
|
|
83
|
-
start = performance.now();
|
|
84
|
-
rebuildCSS();
|
|
85
|
-
// Return if it was changed by the build system itself
|
|
86
|
-
if (/-bundle-\d+\.(j|t)sx?$/.test(filename)) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
filename = String.raw `${filename}`.replace(/\\/g, "/");
|
|
90
|
-
rebuild(filename);
|
|
91
|
-
const [buildFilename] = getBuildNames(filename);
|
|
92
|
-
console.log(`⚡ modified ${buildFilename}`);
|
|
93
|
-
});
|
|
94
|
-
watcher.on("unlink", (filename) => {
|
|
95
|
-
start = performance.now();
|
|
96
|
-
// Return if it was deleted by the build system itself
|
|
97
|
-
if (/-bundle-\d+\.(j|t)sx?$/.test(filename)) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
filename = String.raw `${filename}`.replace(/\\/g, "/");
|
|
101
|
-
JSTSFiles.delete(filename);
|
|
102
|
-
const [buildFilename, buildPathDir] = getBuildNames(filename);
|
|
103
|
-
fs.rm(tsMaybeX2JS(buildFilename), (err) => {
|
|
104
|
-
errorHandler(err);
|
|
105
|
-
console.log(`⚡ deleted ${buildFilename}`);
|
|
106
|
-
const length = fs.readdirSync(buildPathDir).length;
|
|
107
|
-
if (!length)
|
|
108
|
-
fs.rmdir(buildPathDir, errorHandler);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
35
|
+
glob(`${bundleConfig.src}/**/*`, build);
|
|
36
|
+
async function build(err, files, firstRun = true) {
|
|
37
|
+
if (err) {
|
|
38
|
+
console.error(err);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
if (isHMR && firstRun) {
|
|
42
|
+
fastify = await createDefaultServer(isSecure);
|
|
43
|
+
fastify.listen(bundleConfig.port);
|
|
44
|
+
console.log(`💻 Sever listening on port ${bundleConfig.port}.`);
|
|
45
|
+
}
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
await createDir(file);
|
|
48
|
+
if (!SUPPORTED_FILES.test(file)) {
|
|
49
|
+
if (handlerFile) {
|
|
50
|
+
const { stdout } = await execFilePromise("node", [handlerFile, file]);
|
|
51
|
+
if (String(stdout))
|
|
52
|
+
console.log("📋 Logging Handler: ", String(stdout));
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
await fileCopy(file);
|
|
111
56
|
}
|
|
112
57
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (config)
|
|
117
|
-
console.log(`⚡ modified ${config}.config – CSS will rebuild now.`);
|
|
118
|
-
const newConfig = createPostCSSConfig();
|
|
119
|
-
plugins = newConfig.plugins;
|
|
120
|
-
options = newConfig.options;
|
|
121
|
-
CSSprocessor = postcss(plugins);
|
|
122
|
-
glob(`${SOURCE_FOLDER}/**/*.css`, {}, (err, files) => {
|
|
123
|
-
errorHandler(err);
|
|
124
|
-
expectedTasks += files.length;
|
|
125
|
-
for (const filename of files) {
|
|
126
|
-
const [buildFilename, buildPathDir] = getBuildNames(filename);
|
|
127
|
-
fs.mkdirSync(buildPathDir, { recursive: true });
|
|
128
|
-
minifyCSS(filename, buildFilename);
|
|
58
|
+
else {
|
|
59
|
+
if (file.endsWith(".html")) {
|
|
60
|
+
await writeInlineScripts(file);
|
|
129
61
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const TEMPLATE_LITERAL_MINIFIER = /\n\s+/g;
|
|
136
|
-
const CONNECTIONS = []; // HMR
|
|
137
|
-
let htmlTasks = 0;
|
|
138
|
-
let serverSentEvents;
|
|
139
|
-
if (isHMR) {
|
|
140
|
-
fastify = Fastify(isSecure
|
|
141
|
-
? {
|
|
142
|
-
http2: true,
|
|
143
|
-
https: {
|
|
144
|
-
key: fs.readFileSync(path.join(process.cwd(), "localhost-key.pem")),
|
|
145
|
-
cert: fs.readFileSync(path.join(process.cwd(), "localhost.pem")),
|
|
146
|
-
},
|
|
62
|
+
else if (file.endsWith(".css")) {
|
|
63
|
+
await minifyCSS(file, getBuildPath(file));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
inlineFiles.add(file);
|
|
147
67
|
}
|
|
148
|
-
: void 0);
|
|
149
|
-
fastify.setNotFoundHandler((_req, reply) => {
|
|
150
|
-
const file = fs.readFileSync(path.join(process.cwd(), BUILD_FOLDER, "/index.html"), {
|
|
151
|
-
encoding: "utf-8",
|
|
152
|
-
});
|
|
153
|
-
reply.header("Content-Type", "text/html; charset=UTF-8");
|
|
154
|
-
return reply.send(addHMRCode(file, "build/index.html"));
|
|
155
|
-
});
|
|
156
|
-
fastify.register(fastifyStatic, {
|
|
157
|
-
root: path.join(process.cwd(), BUILD_FOLDER),
|
|
158
|
-
});
|
|
159
|
-
fastify.get("/events", (_req, reply) => {
|
|
160
|
-
reply.raw.setHeader("Content-Type", "text/event-stream");
|
|
161
|
-
reply.raw.setHeader("Cache-Control", "no-cache");
|
|
162
|
-
!isSecure && reply.raw.setHeader("Connection", "keep-alive");
|
|
163
|
-
CONNECTIONS.push(reply.raw);
|
|
164
|
-
serverSentEvents = (data) => CONNECTIONS.forEach((rep) => {
|
|
165
|
-
rep.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
// THE BUNDLE CODE
|
|
170
|
-
// Glob all files and transform the code
|
|
171
|
-
glob(`${SOURCE_FOLDER}/**/*.html`, {}, (err, files) => {
|
|
172
|
-
errorHandler(err);
|
|
173
|
-
expectedTasks += files.length;
|
|
174
|
-
htmlTasks += files.length;
|
|
175
|
-
if (isHMR) {
|
|
176
|
-
createHMRHandlers(files);
|
|
177
|
-
fastify.listen(5000);
|
|
178
|
-
console.log(`💻 Sever listening on port 5000.`);
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
glob(`${SOURCE_FOLDER}/**/*.css`, {}, (err, files) => {
|
|
182
|
-
errorHandler(err);
|
|
183
|
-
expectedTasks += files.length;
|
|
184
|
-
for (const filename of files) {
|
|
185
|
-
const [buildFilename, buildPathDir] = getBuildNames(filename);
|
|
186
|
-
fs.mkdirSync(buildPathDir, { recursive: true });
|
|
187
|
-
minifyCSS(filename, buildFilename);
|
|
188
68
|
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
69
|
+
}
|
|
70
|
+
await minifyCode();
|
|
71
|
+
for (const file of inlineFiles) {
|
|
72
|
+
if (INLINE_BUNDLE_FILE.test(file)) {
|
|
73
|
+
inlineFiles.delete(file);
|
|
74
|
+
await rm(file);
|
|
195
75
|
}
|
|
196
|
-
|
|
197
|
-
|
|
76
|
+
}
|
|
77
|
+
for (const file of files) {
|
|
78
|
+
if (file.endsWith(".html")) {
|
|
79
|
+
await minifyHTML(file, getBuildPath(file));
|
|
198
80
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
81
|
+
}
|
|
82
|
+
console.log(`🚀 Build finished in ${(performance.now() - timer).toFixed(2)}ms ✨`);
|
|
83
|
+
if (isHMR && firstRun) {
|
|
84
|
+
console.log(`⌛ Waiting for file changes ...`);
|
|
85
|
+
if (postcssFile) {
|
|
86
|
+
const postCSSWatcher = watch(postcssFile);
|
|
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");
|
|
89
|
+
const cssFiles = files.filter((file) => file.endsWith(".css"));
|
|
90
|
+
postCSSWatcher.on("change", async () => await rebuildCSS(cssFiles, "postcss"));
|
|
91
|
+
tailwindCSSWatcher.on("change", async () => await rebuildCSS(cssFiles, "tailwind"));
|
|
92
|
+
tsConfigWatcher.on("change", async () => {
|
|
93
|
+
timer = performance.now();
|
|
94
|
+
await build(null, files, false);
|
|
213
95
|
});
|
|
214
96
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
let DOM;
|
|
221
|
-
if (fileText.includes("<!DOCTYPE html>") || fileText.includes("<html")) {
|
|
222
|
-
DOM = parse(fileText);
|
|
97
|
+
const watcher = watch(bundleConfig.src);
|
|
98
|
+
watcher.on("add", async (file) => {
|
|
99
|
+
file = String.raw `${file}`.replace(/\\/g, "/"); // glob and chokidar diff
|
|
100
|
+
if (files.includes(file) || INLINE_BUNDLE_FILE.test(file)) {
|
|
101
|
+
return;
|
|
223
102
|
}
|
|
224
|
-
|
|
225
|
-
|
|
103
|
+
await rebuild(file);
|
|
104
|
+
console.log(`⚡ added ${file} to the build`);
|
|
105
|
+
});
|
|
106
|
+
watcher.on("change", async (file) => {
|
|
107
|
+
if (INLINE_BUNDLE_FILE.test(file)) {
|
|
108
|
+
return;
|
|
226
109
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const isReferencedScript = script.attrs.find((a) => a.name === "src");
|
|
231
|
-
//@ts-ignore
|
|
232
|
-
const scriptContent = scriptTextNode?.value;
|
|
233
|
-
if (!scriptContent || isReferencedScript)
|
|
234
|
-
return;
|
|
235
|
-
const jsFilename = filename.replace(".html", `-bundle-${index}.js`);
|
|
236
|
-
scriptFilenames.push(jsFilename);
|
|
237
|
-
fs.writeFileSync(jsFilename, scriptContent);
|
|
238
|
-
});
|
|
110
|
+
file = String.raw `${file}`.replace(/\\/g, "/");
|
|
111
|
+
await rebuild(file);
|
|
112
|
+
console.log(`⚡ modified ${file} on the build`);
|
|
239
113
|
});
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
114
|
+
watcher.on("unlink", async (file) => {
|
|
115
|
+
if (INLINE_BUNDLE_FILE.test(file)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
file = String.raw `${file}`.replace(/\\/g, "/");
|
|
119
|
+
inlineFiles.delete(file);
|
|
120
|
+
const buildFile = getBuildPath(file)
|
|
121
|
+
.replace(".ts", ".js")
|
|
122
|
+
.replace(".jsx", ".js");
|
|
123
|
+
await rm(buildFile);
|
|
124
|
+
const bfDir = buildFile.split("/").slice(0, -1).join("/");
|
|
125
|
+
const stats = await readdir(bfDir);
|
|
126
|
+
if (!stats.length)
|
|
127
|
+
await rm(bfDir);
|
|
128
|
+
console.log(`⚡ deleted ${file} from the build`);
|
|
129
|
+
});
|
|
130
|
+
async function rebuild(file) {
|
|
131
|
+
// Rebuild all CSS because a change in any file might need to trigger PostCSS zu rebuild(e.g. Tailwind CSS)
|
|
132
|
+
await rebuildCSS(files.filter((file) => file.endsWith(".css")));
|
|
133
|
+
let html;
|
|
134
|
+
if (file.endsWith(".html")) {
|
|
135
|
+
// To refill the inlineFiles needed to build JS
|
|
136
|
+
for (const htmlFile of files.filter((file) => file.endsWith(".html"))) {
|
|
137
|
+
await writeInlineScripts(htmlFile);
|
|
138
|
+
}
|
|
139
|
+
await minifyCode();
|
|
140
|
+
for (const file of inlineFiles) {
|
|
141
|
+
if (INLINE_BUNDLE_FILE.test(file)) {
|
|
142
|
+
inlineFiles.delete(file);
|
|
143
|
+
await rm(file);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
html = await minifyHTML(file, getBuildPath(file));
|
|
147
|
+
}
|
|
148
|
+
else if (/\.(jsx?|tsx?)$/.test(file)) {
|
|
149
|
+
inlineFiles.add(file);
|
|
150
|
+
await minifyCode();
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
const { stdout } = await execFilePromise("node", [handlerFile, file]);
|
|
154
|
+
if (String(stdout))
|
|
155
|
+
console.log("📋 Logging Handler: ", String(stdout));
|
|
247
156
|
}
|
|
248
|
-
|
|
249
|
-
}
|
|
157
|
+
serverSentEvents?.({ file, html });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function minifyCSS(file, buildFile) {
|
|
162
|
+
try {
|
|
163
|
+
const fileText = await readFile(file, { encoding: "utf-8" });
|
|
164
|
+
const result = await CSSprocessor.process(fileText, {
|
|
165
|
+
...options,
|
|
166
|
+
from: file,
|
|
167
|
+
to: buildFile,
|
|
168
|
+
});
|
|
169
|
+
await writeFile(buildFile, result.css);
|
|
250
170
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
171
|
+
catch (err) {
|
|
172
|
+
console.error(err);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function minifyCode() {
|
|
176
|
+
try {
|
|
177
|
+
return await esbuild.build({
|
|
178
|
+
entryPoints: Array.from(inlineFiles),
|
|
255
179
|
charset: "utf8",
|
|
256
180
|
format: "esm",
|
|
257
181
|
incremental: isHMR,
|
|
258
182
|
sourcemap: isHMR,
|
|
259
183
|
splitting: true,
|
|
260
184
|
define: {
|
|
261
|
-
"process.env.NODE_ENV":
|
|
185
|
+
"process.env.NODE_ENV": `"${process.env.NODE_ENV}"`,
|
|
262
186
|
},
|
|
263
187
|
loader: { ".js": "jsx", ".ts": "tsx" },
|
|
264
188
|
bundle: true,
|
|
265
189
|
minify: true,
|
|
266
|
-
outdir:
|
|
267
|
-
outbase:
|
|
268
|
-
|
|
269
|
-
.then(() => {
|
|
270
|
-
if (!isInline) {
|
|
271
|
-
taskEmitter.emit("done");
|
|
272
|
-
globHTML.emit("getReady");
|
|
273
|
-
if (serverSentEvents && file) {
|
|
274
|
-
const changedFile = tsMaybeX2JS(file);
|
|
275
|
-
const [buildFilename] = getBuildNames(changedFile);
|
|
276
|
-
const js = fs.readFileSync(buildFilename, { encoding: "utf8" });
|
|
277
|
-
serverSentEvents({
|
|
278
|
-
js,
|
|
279
|
-
filename: buildFilename.split(`${BUILD_FOLDER}/`).pop(),
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
}
|
|
190
|
+
outdir: bundleConfig.build,
|
|
191
|
+
outbase: bundleConfig.src,
|
|
192
|
+
...bundleConfig.esbuild,
|
|
283
193
|
});
|
|
194
|
+
// Stop app from crashing.
|
|
284
195
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
return CSSprocessor.process(fileText, {
|
|
288
|
-
...options,
|
|
289
|
-
from: filename,
|
|
290
|
-
to: buildFilename,
|
|
291
|
-
})
|
|
292
|
-
.then((result) => {
|
|
293
|
-
fs.writeFileSync(buildFilename, result.css);
|
|
294
|
-
taskEmitter.emit("done");
|
|
295
|
-
globHTML.emit("getReady");
|
|
296
|
-
if (serverSentEvents) {
|
|
297
|
-
serverSentEvents({
|
|
298
|
-
css: result.css,
|
|
299
|
-
filename: buildFilename.split(`${BUILD_FOLDER}/`).pop(),
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
})
|
|
303
|
-
.catch((err) => {
|
|
196
|
+
catch (err) {
|
|
197
|
+
if (!isHMR) {
|
|
304
198
|
console.error(err);
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
async function minifyHTML(filename, buildFilename) {
|
|
308
|
-
let fileText = fs.readFileSync(filename, { encoding: "utf-8" });
|
|
309
|
-
let DOM;
|
|
310
|
-
if (fileText.includes("<!DOCTYPE html>") || fileText.includes("<html")) {
|
|
311
|
-
DOM = parse(fileText);
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
DOM = parseFragment(fileText);
|
|
315
|
-
}
|
|
316
|
-
// Minify Code
|
|
317
|
-
const scripts = findElements(DOM, (e) => getTagName(e) === "script");
|
|
318
|
-
scripts.forEach((script, index) => {
|
|
319
|
-
const scriptTextNode = script.childNodes[0];
|
|
320
|
-
const isReferencedScript = script.attrs.find((a) => a.name === "src");
|
|
321
|
-
//@ts-ignore
|
|
322
|
-
if (!scriptTextNode?.value || isReferencedScript)
|
|
323
|
-
return;
|
|
324
|
-
// Use bundled file and remove it from fs
|
|
325
|
-
const bundledFilename = buildFilename.replace(".html", `-bundle-${index}.js`);
|
|
326
|
-
try {
|
|
327
|
-
const scriptContent = fs.readFileSync(bundledFilename, {
|
|
328
|
-
encoding: "utf-8",
|
|
329
|
-
});
|
|
330
|
-
fs.rmSync(bundledFilename);
|
|
331
|
-
// Replace src with bundled code
|
|
332
|
-
//@ts-ignore
|
|
333
|
-
scriptTextNode.value = scriptContent.replace(TEMPLATE_LITERAL_MINIFIER, " ");
|
|
334
|
-
}
|
|
335
|
-
catch { }
|
|
336
|
-
});
|
|
337
|
-
// Minify Inline Style
|
|
338
|
-
const styles = findElements(DOM, (e) => getTagName(e) === "style");
|
|
339
|
-
for (const style of styles) {
|
|
340
|
-
const node = style.childNodes[0];
|
|
341
|
-
//@ts-ignore
|
|
342
|
-
const styleContent = node?.value;
|
|
343
|
-
if (!styleContent)
|
|
344
|
-
continue;
|
|
345
|
-
const { css } = await CSSprocessor.process(styleContent, {
|
|
346
|
-
...options,
|
|
347
|
-
from: undefined,
|
|
348
|
-
});
|
|
349
|
-
//@ts-ignore
|
|
350
|
-
node.value = css;
|
|
351
199
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
.generate({
|
|
365
|
-
base: buildDir,
|
|
366
|
-
html: fileText,
|
|
367
|
-
target: fileWithBase,
|
|
368
|
-
inline: !isCSP,
|
|
369
|
-
extract: true,
|
|
370
|
-
rebase: () => { },
|
|
371
|
-
})
|
|
372
|
-
.then(({ html }) => {
|
|
373
|
-
taskEmitter.emit("done");
|
|
374
|
-
if (serverSentEvents) {
|
|
375
|
-
serverSentEvents({
|
|
376
|
-
html: addHMRCode(html, buildFilename),
|
|
377
|
-
filename: buildFilename,
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
})
|
|
381
|
-
.catch((err) => {
|
|
382
|
-
console.error(err);
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
fs.writeFileSync(buildFilename, fileText);
|
|
387
|
-
taskEmitter.emit("done");
|
|
388
|
-
if (serverSentEvents) {
|
|
389
|
-
serverSentEvents({
|
|
390
|
-
html: addHMRCode(fileText, buildFilename),
|
|
391
|
-
filename: buildFilename,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
// Helper functions from here
|
|
397
|
-
function createPostCSSConfig() {
|
|
398
|
-
try {
|
|
399
|
-
return postcssrc.sync({});
|
|
400
|
-
}
|
|
401
|
-
catch {
|
|
402
|
-
return { plugins: [cssnano], options: {}, file: "" };
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
function getBuildNames(filename) {
|
|
406
|
-
const buildFilename = filename.replace(`${SOURCE_FOLDER}/`, `${BUILD_FOLDER}/`);
|
|
407
|
-
const buildFilenameArr = buildFilename.split("/");
|
|
408
|
-
buildFilenameArr.pop();
|
|
409
|
-
const buildPathDir = buildFilenameArr.join("/");
|
|
410
|
-
return [buildFilename, buildPathDir];
|
|
411
|
-
}
|
|
412
|
-
async function rebuild(filename) {
|
|
413
|
-
const [buildFilename] = getBuildNames(filename);
|
|
414
|
-
if (/\.(j|t)sx?$/.test(filename)) {
|
|
415
|
-
JSTSFiles.add(filename);
|
|
416
|
-
}
|
|
417
|
-
else if (filename.endsWith(".css")) {
|
|
418
|
-
await minifyCSS(filename, buildFilename);
|
|
419
|
-
}
|
|
420
|
-
glob(`${SOURCE_FOLDER}/**/*.html`, {}, async (err, files) => {
|
|
421
|
-
errorHandler(err);
|
|
422
|
-
await createGlobalJS(files);
|
|
423
|
-
if (filename.endsWith(".html")) {
|
|
424
|
-
minifyHTML(filename, buildFilename);
|
|
425
|
-
}
|
|
426
|
-
else if (/\.(j|t)sx?$/.test(filename) ||
|
|
427
|
-
(filename.endsWith(".css") && isCritical)) {
|
|
428
|
-
for (const htmlFilename of files) {
|
|
429
|
-
const [htmlBuildFilename] = getBuildNames(htmlFilename);
|
|
430
|
-
await minifyHTML(htmlFilename, htmlBuildFilename);
|
|
431
|
-
}
|
|
432
|
-
if (/\.(j|t)sx?$/.test(filename) && serverSentEvents) {
|
|
433
|
-
const jsFile = tsMaybeX2JS(buildFilename);
|
|
434
|
-
try {
|
|
435
|
-
// Do not try to send empty files
|
|
436
|
-
serverSentEvents({
|
|
437
|
-
js: fs.readFileSync(jsFile, { encoding: "utf-8" }),
|
|
438
|
-
filename: jsFile.split(`${BUILD_FOLDER}/`).pop(),
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
catch { }
|
|
200
|
+
let missingPkg = false;
|
|
201
|
+
if (err?.errors) {
|
|
202
|
+
for (const error of err.errors) {
|
|
203
|
+
if (error.text?.startsWith("Could not resolve")) {
|
|
204
|
+
missingPkg = true;
|
|
205
|
+
const packageNameRegex = /(?<=").*(?=")/;
|
|
206
|
+
const [pkgName] = error.text.match(packageNameRegex);
|
|
207
|
+
console.log(`📦 Package ${pkgName} was installed for you`);
|
|
208
|
+
await awaitSpawn(process.platform === "win32" ? "npm.cmd" : "npm", [
|
|
209
|
+
"install",
|
|
210
|
+
pkgName,
|
|
211
|
+
]);
|
|
442
212
|
}
|
|
443
213
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (!window.eventsource${id}) {
|
|
449
|
-
setGlobalSchedule(false);
|
|
450
|
-
setShouldSetReactivity(false);
|
|
451
|
-
|
|
452
|
-
window.eventsource${id} = new EventSource("/events");
|
|
453
|
-
window.eventsource${id}.addEventListener("message", ({ data }) => {
|
|
454
|
-
const dataObj = JSON.parse(data);
|
|
455
|
-
|
|
456
|
-
if ("html" in dataObj && "${filename}" === dataObj.filename) {
|
|
457
|
-
setInsertDiffing(true);
|
|
458
|
-
|
|
459
|
-
let newHTML;
|
|
460
|
-
let isBody;
|
|
461
|
-
|
|
462
|
-
if (dataObj.html.startsWith('<!DOCTYPE html>') || dataObj.html.startsWith('<html')) {
|
|
463
|
-
newHTML = html\`\${dataObj.html}\`;
|
|
464
|
-
} else {
|
|
465
|
-
newHTML = html\`<body>\${dataObj.html}</body>\`
|
|
466
|
-
isBody = true
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
if (isBody) {
|
|
470
|
-
const hmrID = "${id}";
|
|
471
|
-
const hmrElems = Array.from(newHTML.childNodes);
|
|
472
|
-
const hmrWheres = Array.from($$(\`[data-hmr="\${hmrID}"]\`))
|
|
473
|
-
// Render new Elements in old Elements, also remove rest old Elements and add add new elements after the last old one
|
|
474
|
-
hmrWheres.forEach((where, index) => {
|
|
475
|
-
if (index < hmrElems.length) {
|
|
476
|
-
render(hmrElems[index], where);
|
|
477
|
-
} else {
|
|
478
|
-
where.remove();
|
|
214
|
+
if (missingPkg) {
|
|
215
|
+
missingPkg = false;
|
|
216
|
+
return minifyCode();
|
|
479
217
|
}
|
|
480
|
-
});
|
|
481
|
-
for (let rest = hmrWheres.length; rest < hmrElems.length; rest++) {
|
|
482
|
-
if (hmrWheres.length) {
|
|
483
|
-
const template = document.createElement('template');
|
|
484
|
-
hmrElems[hmrWheres.length - 1].after(template);
|
|
485
|
-
render(hmrElems[rest], template);
|
|
486
|
-
template.remove();
|
|
487
|
-
} else {
|
|
488
|
-
render(hmrElems[rest])
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
} else {
|
|
492
|
-
const oldElementCount = document.body.querySelectorAll('*').length;
|
|
493
|
-
render(newHTML, document.documentElement);
|
|
494
|
-
const newElementCount = document.body.querySelectorAll('*').length;
|
|
495
|
-
|
|
496
|
-
// Looks like JS did not reload? Last resort - hard refresh
|
|
497
|
-
if (newElementCount < 5 && Math.abs(newElementCount - oldElementCount) > 10) {
|
|
498
|
-
location.reload()
|
|
499
218
|
}
|
|
500
|
-
}
|
|
501
|
-
setInsertDiffing(false);
|
|
502
|
-
if (dataObj.filename === 'build/index.html') {
|
|
503
|
-
dispatchEvent(new Event("popstate"));
|
|
504
|
-
}
|
|
505
|
-
} else if ("css" in dataObj) {
|
|
506
|
-
window.onceEveryXTime(100, window.updateCSS, [updateAttr]);
|
|
507
|
-
} else if ("js" in dataObj) {
|
|
508
|
-
window.onceEveryXTime(100, window.updateJS, [updateAttr]);
|
|
509
219
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
elem.setAttribute('data-inline', String(Math.random().toFixed(4)).slice(2));
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
if (!window.updateCSS) {
|
|
524
|
-
window.updateCSS = function updateCSS(updateAttr) {
|
|
525
|
-
$$('link').forEach(updateAttr("href"));
|
|
526
|
-
window.fnToLastCalled.set(window.updateCSS, performance.now())
|
|
220
|
+
}
|
|
221
|
+
const htmlFilesCache = new Map();
|
|
222
|
+
async function writeInlineScripts(file) {
|
|
223
|
+
let fileText = await readFile(file, { encoding: "utf-8" });
|
|
224
|
+
let DOM;
|
|
225
|
+
if (fileText.includes("<!DOCTYPE html>") || fileText.includes("<html")) {
|
|
226
|
+
DOM = parse(fileText);
|
|
527
227
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
window.updateJS = function updateJS(updateAttr) {
|
|
531
|
-
window.fnToLastCalled.set(window.updateJS, performance.now());
|
|
532
|
-
const copy = html\`\${document.documentElement.outerHTML}\`;
|
|
533
|
-
copy.querySelectorAll('script').forEach(updateAttr("src"));
|
|
534
|
-
render(copy, document.documentElement);
|
|
228
|
+
else {
|
|
229
|
+
DOM = parseFragment(fileText);
|
|
535
230
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
window.fnToLastCalled = new Map();
|
|
539
|
-
}
|
|
540
|
-
if (!window.onceEveryXTime) {
|
|
541
|
-
window.onceEveryXTime = function (time, fn, params) {
|
|
542
|
-
if (!window.fnToLastCalled.has(fn) || performance.now() - window.fnToLastCalled.get(fn) > time) {
|
|
543
|
-
fn(...params)
|
|
544
|
-
}
|
|
231
|
+
if (isHMR) {
|
|
232
|
+
fileText = addHMRCode(fileText, file, DOM);
|
|
545
233
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
234
|
+
htmlFilesCache.set(file, [fileText, DOM]);
|
|
235
|
+
const scripts = findElements(DOM, (e) => getTagName(e) === "script");
|
|
236
|
+
for (let index = 0; index < scripts.length; index++) {
|
|
237
|
+
const script = scripts[index];
|
|
238
|
+
const scriptTextNode = script.childNodes[0];
|
|
239
|
+
const isReferencedScript = script.attrs.find((a) => a.name === "src");
|
|
240
|
+
const scriptContent = scriptTextNode?.value;
|
|
241
|
+
if (!scriptContent || isReferencedScript)
|
|
242
|
+
continue;
|
|
243
|
+
const jsFile = file.replace(".html", `-bundle-${index}.tsx`);
|
|
244
|
+
inlineFiles.add(jsFile);
|
|
245
|
+
await writeFile(jsFile, scriptContent);
|
|
550
246
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
247
|
+
}
|
|
248
|
+
async function minifyHTML(file, buildFile) {
|
|
249
|
+
let fileText, DOM;
|
|
250
|
+
if (htmlFilesCache.has(file)) {
|
|
251
|
+
const cache = htmlFilesCache.get(file);
|
|
252
|
+
fileText = cache[0];
|
|
253
|
+
DOM = cache[1];
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
fileText = await readFile(file, { encoding: "utf-8" });
|
|
257
|
+
if (fileText.includes("<!DOCTYPE html>") || fileText.includes("<html")) {
|
|
258
|
+
DOM = parse(fileText);
|
|
562
259
|
}
|
|
563
260
|
else {
|
|
564
|
-
|
|
565
|
-
appendChild(ast, script);
|
|
566
|
-
ast.childNodes.forEach((node) =>
|
|
567
|
-
//@ts-ignore
|
|
568
|
-
node.attrs?.push({ name: "data-hmr", value: htmlIdMap.get(filename) }));
|
|
261
|
+
DOM = parseFragment(fileText);
|
|
569
262
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
263
|
+
}
|
|
264
|
+
// Minify Code
|
|
265
|
+
const scripts = findElements(DOM, (e) => getTagName(e) === "script");
|
|
266
|
+
for (let index = 0; index < scripts.length; index++) {
|
|
267
|
+
const script = scripts[index];
|
|
268
|
+
const scriptTextNode = script.childNodes[0];
|
|
269
|
+
const isReferencedScript = script.attrs.find((a) => a.name === "src");
|
|
270
|
+
if (!scriptTextNode?.value || isReferencedScript)
|
|
271
|
+
continue;
|
|
272
|
+
// Use bundled file
|
|
273
|
+
const buildInlineScript = buildFile.replace(".html", `-bundle-${index}.js`);
|
|
274
|
+
const scriptContent = await readFile(buildInlineScript, {
|
|
275
|
+
encoding: "utf-8",
|
|
577
276
|
});
|
|
578
|
-
|
|
277
|
+
await rm(buildInlineScript);
|
|
278
|
+
scriptTextNode.value = scriptContent.replace(TEMPLATE_LITERAL_MINIFIER, " ");
|
|
579
279
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
fastify.get(newFilename, HMRHandler);
|
|
280
|
+
// Minify Inline Style
|
|
281
|
+
const styles = findElements(DOM, (e) => getTagName(e) === "style");
|
|
282
|
+
for (const style of styles) {
|
|
283
|
+
const node = style.childNodes[0];
|
|
284
|
+
const styleContent = node?.value;
|
|
285
|
+
if (!styleContent)
|
|
286
|
+
continue;
|
|
287
|
+
const { css } = await CSSprocessor.process(styleContent, {
|
|
288
|
+
...options,
|
|
289
|
+
from: undefined,
|
|
591
290
|
});
|
|
291
|
+
node.value = css;
|
|
592
292
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
encoding: "utf-8",
|
|
293
|
+
fileText = serialize(DOM);
|
|
294
|
+
// Minify HTML
|
|
295
|
+
try {
|
|
296
|
+
fileText = await minify(fileText, {
|
|
297
|
+
collapseWhitespace: true,
|
|
298
|
+
removeComments: true,
|
|
299
|
+
...bundleConfig["html-minifier-terser"],
|
|
601
300
|
});
|
|
602
|
-
reply.header("Content-Type", "text/html; charset=UTF-8");
|
|
603
|
-
return reply.send(addHMRCode(file, filename));
|
|
604
301
|
}
|
|
605
|
-
|
|
606
|
-
|
|
302
|
+
catch (e) {
|
|
303
|
+
console.error(e);
|
|
304
|
+
}
|
|
305
|
+
if (!isCritical) {
|
|
306
|
+
await writeFile(buildFile, fileText);
|
|
307
|
+
return fileText;
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
const buildFileArr = buildFile.split("/");
|
|
311
|
+
const fileWithBase = buildFileArr.pop();
|
|
312
|
+
const buildDir = buildFileArr.join("/");
|
|
313
|
+
// critical is generating the files on the fs
|
|
314
|
+
try {
|
|
315
|
+
const { html } = await critical.generate({
|
|
316
|
+
base: buildDir,
|
|
317
|
+
html: fileText,
|
|
318
|
+
target: fileWithBase,
|
|
319
|
+
inline: !isSecure,
|
|
320
|
+
extract: true,
|
|
321
|
+
rebase: () => { },
|
|
322
|
+
...bundleConfig.critical,
|
|
323
|
+
});
|
|
324
|
+
return html;
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
607
327
|
console.error(err);
|
|
608
|
-
process.exit(1);
|
|
609
328
|
}
|
|
610
329
|
}
|
|
611
|
-
function tsMaybeX2JS(filename) {
|
|
612
|
-
return filename.replace(".ts", ".js").replace(".jsx", ".js");
|
|
613
|
-
}
|
|
614
330
|
}
|
|
615
|
-
function
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
fastify.register(fastifyStatic, {
|
|
626
|
-
root: path.join(process.cwd(), BUILD_FOLDER),
|
|
627
|
-
});
|
|
331
|
+
async function rebuildCSS(files, config) {
|
|
332
|
+
const newConfig = await getPostCSSConfig();
|
|
333
|
+
plugins = newConfig.plugins;
|
|
334
|
+
options = newConfig.options;
|
|
335
|
+
CSSprocessor = postcss(plugins);
|
|
336
|
+
for (const file of files) {
|
|
337
|
+
await minifyCSS(file, getBuildPath(file));
|
|
338
|
+
}
|
|
339
|
+
if (config)
|
|
340
|
+
console.log(`⚡ modified ${config}.config`);
|
|
628
341
|
}
|