html-bundle 5.5.2 → 6.0.0-beta
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 +30 -21
- package/dist/bundle.mjs +267 -568
- package/dist/utils.js +168 -0
- package/package.json +2 -1
- package/src/bundle.mts +307 -658
- package/src/index.d.ts +3 -0
- package/src/utils.ts +205 -0
package/dist/bundle.mjs
CHANGED
|
@@ -1,628 +1,327 @@
|
|
|
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.js";
|
|
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
|
+
const timer = performance.now();
|
|
24
|
+
let { plugins, options, file: postcssFile } = await getPostCSSConfig();
|
|
25
|
+
let CSSprocessor = postcss(plugins);
|
|
24
26
|
let fastify;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
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
|
-
});
|
|
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|m?jsx?|m?tsx?)$/;
|
|
31
|
+
const execFilePromise = promisify(execFile);
|
|
32
|
+
await rm(bundleConfig.build, { force: true, recursive: true });
|
|
33
|
+
glob(`${bundleConfig.src}/**/*.*`, build);
|
|
34
|
+
async function build(err, files) {
|
|
35
|
+
if (err) {
|
|
36
|
+
console.error(err);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
if (isHMR) {
|
|
40
|
+
fastify = await createDefaultServer(isSecure);
|
|
41
|
+
fastify.listen(bundleConfig.port);
|
|
42
|
+
console.log(`💻 Sever listening on port ${bundleConfig.port}.`);
|
|
43
|
+
}
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
await createDir(file);
|
|
46
|
+
if (!SUPPORTED_FILES.test(file)) {
|
|
47
|
+
if (handlerFile) {
|
|
48
|
+
const { stdout } = await execFilePromise("node", [handlerFile, file]);
|
|
49
|
+
console.log("📋 Logging Handler: ", String(stdout));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
await fileCopy(file);
|
|
111
53
|
}
|
|
112
54
|
}
|
|
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);
|
|
55
|
+
else {
|
|
56
|
+
if (file.endsWith(".html")) {
|
|
57
|
+
await writeInlineScripts(file);
|
|
129
58
|
}
|
|
130
|
-
|
|
59
|
+
else if (file.endsWith(".css")) {
|
|
60
|
+
await minifyCSS(file, getBuildPath(file));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
inlineFiles.add(file);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
await minifyCode();
|
|
68
|
+
for (const file of inlineFiles) {
|
|
69
|
+
if (INLINE_BUNDLE_FILE.test(file)) {
|
|
70
|
+
inlineFiles.delete(file);
|
|
71
|
+
await rm(file);
|
|
72
|
+
}
|
|
131
73
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
let serverSentEvents;
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
if (file.endsWith(".html")) {
|
|
76
|
+
await minifyHTML(file, getBuildPath(file));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
console.log(`🚀 Build finished in ${(performance.now() - timer).toFixed(2)}ms ✨`);
|
|
139
80
|
if (isHMR) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
81
|
+
console.log(`⌛ Waiting for file changes ...`);
|
|
82
|
+
if (postcssFile) {
|
|
83
|
+
const postCSSWatcher = watch(postcssFile);
|
|
84
|
+
const tailwindCSSWatcher = watch(postcssFile.replace("postcss", "tailwind")); // Assuming that the file ext is the same
|
|
85
|
+
const cssFiles = files.filter((file) => file.endsWith(".css"));
|
|
86
|
+
postCSSWatcher.on("change", async () => await rebuildCSS(cssFiles, "postcss"));
|
|
87
|
+
tailwindCSSWatcher.on("change", async () => await rebuildCSS(cssFiles, "tailwind"));
|
|
88
|
+
}
|
|
89
|
+
const watcher = watch(bundleConfig.src);
|
|
90
|
+
let addCount = 0; // The add watcher will add all the files initially - do not rebuild them
|
|
91
|
+
watcher.on("add", async (file) => {
|
|
92
|
+
if (addCount++ <= files.length || INLINE_BUNDLE_FILE.test(file)) {
|
|
93
|
+
return;
|
|
147
94
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
encoding: "utf-8",
|
|
152
|
-
});
|
|
153
|
-
reply.header("Content-Type", "text/html; charset=UTF-8");
|
|
154
|
-
return reply.send(addHMRCode(file, "build/index.html"));
|
|
95
|
+
file = String.raw `${file}`.replace(/\\/g, "/"); // glob and chokidar diff
|
|
96
|
+
await rebuild(file);
|
|
97
|
+
console.log(`⚡ added ${file} to the build`);
|
|
155
98
|
});
|
|
156
|
-
|
|
157
|
-
|
|
99
|
+
watcher.on("change", async (file) => {
|
|
100
|
+
if (INLINE_BUNDLE_FILE.test(file)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
file = String.raw `${file}`.replace(/\\/g, "/");
|
|
104
|
+
await rebuild(file);
|
|
105
|
+
console.log(`⚡ modified ${file} on the build`);
|
|
158
106
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
107
|
+
watcher.on("unlink", async (file) => {
|
|
108
|
+
if (INLINE_BUNDLE_FILE.test(file)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
file = String.raw `${file}`.replace(/\\/g, "/");
|
|
112
|
+
inlineFiles.delete(file);
|
|
113
|
+
const buildFile = getBuildPath(file)
|
|
114
|
+
.replace(".ts", ".js")
|
|
115
|
+
.replace(".jsx", ".js");
|
|
116
|
+
await rm(buildFile);
|
|
117
|
+
const bfDir = buildFile.split("/").slice(0, -1).join("/");
|
|
118
|
+
const stats = await readdir(bfDir);
|
|
119
|
+
if (!stats.length)
|
|
120
|
+
await rm(bfDir);
|
|
121
|
+
console.log(`⚡ deleted ${file} from the build`);
|
|
167
122
|
});
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
const JSTSFiles = new Set();
|
|
191
|
-
glob(`${SOURCE_FOLDER}/**/!(*.d).{ts,js,tsx,jsx}`, {}, (err, files) => {
|
|
192
|
-
errorHandler(err);
|
|
193
|
-
if (files.length) {
|
|
194
|
-
expectedTasks += 1;
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
globHTML.emit("getReady");
|
|
198
|
-
}
|
|
199
|
-
files.forEach((file) => JSTSFiles.add(file));
|
|
200
|
-
minifyTSJS().catch(errorHandler);
|
|
201
|
-
});
|
|
202
|
-
globHTML.on("getReady", () => {
|
|
203
|
-
if (expectedTasks - htmlTasks === finishedTasks) {
|
|
204
|
-
// After CSS and JS because critical needs file built css files and inline script might reference js files.
|
|
205
|
-
glob(`${SOURCE_FOLDER}/**/*.html`, {}, async (err, files) => {
|
|
206
|
-
errorHandler(err);
|
|
207
|
-
await createGlobalJS(files);
|
|
208
|
-
files.forEach((filename) => {
|
|
209
|
-
const [buildFilename, buildPathDir] = getBuildNames(filename);
|
|
210
|
-
fs.mkdirSync(buildPathDir, { recursive: true });
|
|
211
|
-
minifyHTML(filename, buildFilename);
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
function createGlobalJS(files) {
|
|
217
|
-
const scriptFilenames = [];
|
|
218
|
-
files.forEach((filename) => {
|
|
219
|
-
const fileText = fs.readFileSync(filename, { encoding: "utf-8" });
|
|
220
|
-
let DOM;
|
|
221
|
-
if (fileText.includes("<!DOCTYPE html>") || fileText.includes("<html")) {
|
|
222
|
-
DOM = parse(fileText);
|
|
123
|
+
async function rebuild(file) {
|
|
124
|
+
// Rebuild all CSS because a change in any file might need to trigger PostCSS zu rebuild(e.g. Tailwind CSS)
|
|
125
|
+
await rebuildCSS(files.filter((file) => file.endsWith(".css")));
|
|
126
|
+
let html;
|
|
127
|
+
if (file.endsWith(".html")) {
|
|
128
|
+
// To refill the inlineFiles needed to build JS
|
|
129
|
+
for (const htmlFile of files.filter((file) => file.endsWith("html"))) {
|
|
130
|
+
await writeInlineScripts(htmlFile);
|
|
131
|
+
}
|
|
132
|
+
await minifyCode();
|
|
133
|
+
for (const file of inlineFiles) {
|
|
134
|
+
if (INLINE_BUNDLE_FILE.test(file)) {
|
|
135
|
+
inlineFiles.delete(file);
|
|
136
|
+
await rm(file);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
html = await minifyHTML(file, getBuildPath(file));
|
|
140
|
+
}
|
|
141
|
+
else if (/(m?jsx?|m?tsx?)$/.test(file)) {
|
|
142
|
+
await minifyCode();
|
|
223
143
|
}
|
|
224
144
|
else {
|
|
225
|
-
|
|
145
|
+
const { stdout } = await execFilePromise("node", [handlerFile, file]);
|
|
146
|
+
console.log("📋 Logging Handler: ", String(stdout));
|
|
226
147
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
});
|
|
148
|
+
serverSentEvents({ file, html });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function minifyCSS(file, buildFile) {
|
|
153
|
+
try {
|
|
154
|
+
const fileText = await readFile(file, { encoding: "utf-8" });
|
|
155
|
+
const result = await CSSprocessor.process(fileText, {
|
|
156
|
+
...options,
|
|
157
|
+
from: file,
|
|
158
|
+
to: buildFile,
|
|
239
159
|
});
|
|
240
|
-
|
|
241
|
-
return minifyTSJS(true)
|
|
242
|
-
.catch(console.error)
|
|
243
|
-
.finally(() => scriptFilenames.forEach((file) => {
|
|
244
|
-
JSTSFiles.delete(file);
|
|
245
|
-
try {
|
|
246
|
-
fs.rmSync(file);
|
|
247
|
-
}
|
|
248
|
-
catch { }
|
|
249
|
-
}));
|
|
160
|
+
await writeFile(buildFile, result.css);
|
|
250
161
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
162
|
+
catch (err) {
|
|
163
|
+
console.error(err);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function minifyCode() {
|
|
167
|
+
try {
|
|
168
|
+
return await esbuild.build({
|
|
169
|
+
entryPoints: Array.from(inlineFiles),
|
|
255
170
|
charset: "utf8",
|
|
256
171
|
format: "esm",
|
|
257
172
|
incremental: isHMR,
|
|
258
173
|
sourcemap: isHMR,
|
|
259
174
|
splitting: true,
|
|
260
175
|
define: {
|
|
261
|
-
"process.env.NODE_ENV":
|
|
176
|
+
"process.env.NODE_ENV": process.env.NODE_ENV,
|
|
262
177
|
},
|
|
263
178
|
loader: { ".js": "jsx", ".ts": "tsx" },
|
|
264
179
|
bundle: true,
|
|
265
180
|
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
|
-
}
|
|
181
|
+
outdir: bundleConfig.build,
|
|
182
|
+
outbase: bundleConfig.src,
|
|
183
|
+
...bundleConfig.esbuild,
|
|
283
184
|
});
|
|
185
|
+
// Stop app from crashing.
|
|
284
186
|
}
|
|
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) => {
|
|
187
|
+
catch (err) {
|
|
188
|
+
if (!isHMR) {
|
|
304
189
|
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
190
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
}
|
|
352
|
-
fileText = serialize(DOM);
|
|
353
|
-
// Minify HTML
|
|
354
|
-
fileText = await minify(fileText, {
|
|
355
|
-
collapseWhitespace: true,
|
|
356
|
-
removeComments: true,
|
|
357
|
-
});
|
|
358
|
-
if (isCritical) {
|
|
359
|
-
const buildFilenameArr = buildFilename.split("/");
|
|
360
|
-
const fileWithBase = buildFilenameArr.pop();
|
|
361
|
-
const buildDir = buildFilenameArr.join("/");
|
|
362
|
-
// critical is generating the files on the fs
|
|
363
|
-
return critical
|
|
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 { }
|
|
191
|
+
let missingPkg = false;
|
|
192
|
+
if (err?.errors) {
|
|
193
|
+
for (const error of err.errors) {
|
|
194
|
+
if (error.text?.startsWith("Could not resolve")) {
|
|
195
|
+
missingPkg = true;
|
|
196
|
+
const packageNameRegex = /(?<=").*(?=")/;
|
|
197
|
+
const [pkgName] = error.text.match(packageNameRegex);
|
|
198
|
+
console.log(`📦 Package ${pkgName} was installed for you`);
|
|
199
|
+
await awaitSpawn(process.platform === "win32" ? "npm.cmd" : "npm", [
|
|
200
|
+
"install",
|
|
201
|
+
pkgName,
|
|
202
|
+
]);
|
|
442
203
|
}
|
|
443
204
|
}
|
|
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();
|
|
205
|
+
if (missingPkg) {
|
|
206
|
+
missingPkg = false;
|
|
207
|
+
return minifyCode();
|
|
479
208
|
}
|
|
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
209
|
}
|
|
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
|
-
}
|
|
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
210
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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())
|
|
211
|
+
}
|
|
212
|
+
const htmlFilesCache = new Map();
|
|
213
|
+
async function writeInlineScripts(file) {
|
|
214
|
+
let fileText = await readFile(file, { encoding: "utf-8" });
|
|
215
|
+
let DOM;
|
|
216
|
+
if (fileText.includes("<!DOCTYPE html>") || fileText.includes("<html")) {
|
|
217
|
+
DOM = parse(fileText);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
DOM = parseFragment(fileText);
|
|
527
221
|
}
|
|
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);
|
|
222
|
+
if (isHMR) {
|
|
223
|
+
fileText = addHMRCode(fileText, file, DOM);
|
|
535
224
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
225
|
+
htmlFilesCache.set(file, [fileText, DOM]);
|
|
226
|
+
const scripts = findElements(DOM, (e) => getTagName(e) === "script");
|
|
227
|
+
for (let index = 0; index < scripts.length; index++) {
|
|
228
|
+
const script = scripts[index];
|
|
229
|
+
const scriptTextNode = script.childNodes[0];
|
|
230
|
+
const isReferencedScript = script.attrs.find((a) => a.name === "src");
|
|
231
|
+
const scriptContent = scriptTextNode?.value;
|
|
232
|
+
if (!scriptContent || isReferencedScript)
|
|
233
|
+
continue;
|
|
234
|
+
const jsFile = file.replace(".html", `-bundle-${index}.tsx`);
|
|
235
|
+
inlineFiles.add(jsFile);
|
|
236
|
+
await writeFile(jsFile, scriptContent);
|
|
545
237
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
238
|
+
}
|
|
239
|
+
async function minifyHTML(file, buildFile) {
|
|
240
|
+
let fileText, DOM;
|
|
241
|
+
if (htmlFilesCache.has(file)) {
|
|
242
|
+
const cache = htmlFilesCache.get(file);
|
|
243
|
+
fileText = cache[0];
|
|
244
|
+
DOM = cache[1];
|
|
550
245
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
const script = createScript({ type: "module" }, getHMRCode(filename, htmlIdMap.get(filename)));
|
|
557
|
-
let ast;
|
|
558
|
-
if (html.includes("<!DOCTYPE html>") || html.includes("<html")) {
|
|
559
|
-
ast = parse(html);
|
|
560
|
-
const headNode = findElement(ast, (e) => getTagName(e) === "head");
|
|
561
|
-
appendChild(headNode, script);
|
|
246
|
+
else {
|
|
247
|
+
fileText = await readFile(file, { encoding: "utf-8" });
|
|
248
|
+
if (fileText.includes("<!DOCTYPE html>") || fileText.includes("<html")) {
|
|
249
|
+
DOM = parse(fileText);
|
|
562
250
|
}
|
|
563
251
|
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) }));
|
|
252
|
+
DOM = parseFragment(fileText);
|
|
569
253
|
}
|
|
570
|
-
// Burst CSS cache
|
|
571
|
-
findElements(ast, (e) => getTagName(e) === "link").forEach((link) => {
|
|
572
|
-
const href = link.attrs.find((attr) => attr.name === "href");
|
|
573
|
-
const rel = link.attrs.find((attr) => attr.name === "rel");
|
|
574
|
-
if (rel.value === "stylesheet") {
|
|
575
|
-
href.value += `?v=${Math.random().toFixed(4)}`;
|
|
576
|
-
}
|
|
577
|
-
});
|
|
578
|
-
return serialize(ast);
|
|
579
254
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
255
|
+
// Minify Code
|
|
256
|
+
const scripts = findElements(DOM, (e) => getTagName(e) === "script");
|
|
257
|
+
for (let index = 0; index < scripts.length; index++) {
|
|
258
|
+
const script = scripts[index];
|
|
259
|
+
const scriptTextNode = script.childNodes[0];
|
|
260
|
+
const isReferencedScript = script.attrs.find((a) => a.name === "src");
|
|
261
|
+
if (!scriptTextNode?.value || isReferencedScript)
|
|
262
|
+
continue;
|
|
263
|
+
// Use bundled file
|
|
264
|
+
const buildInlineScript = buildFile.replace(".html", `-bundle-${index}.js`);
|
|
265
|
+
const scriptContent = await readFile(buildInlineScript, {
|
|
266
|
+
encoding: "utf-8",
|
|
591
267
|
});
|
|
268
|
+
await rm(buildInlineScript);
|
|
269
|
+
scriptTextNode.value = scriptContent.replace(TEMPLATE_LITERAL_MINIFIER, " ");
|
|
592
270
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
271
|
+
// Minify Inline Style
|
|
272
|
+
const styles = findElements(DOM, (e) => getTagName(e) === "style");
|
|
273
|
+
for (const style of styles) {
|
|
274
|
+
const node = style.childNodes[0];
|
|
275
|
+
const styleContent = node?.value;
|
|
276
|
+
if (!styleContent)
|
|
277
|
+
continue;
|
|
278
|
+
const { css } = await CSSprocessor.process(styleContent, {
|
|
279
|
+
...options,
|
|
280
|
+
from: undefined,
|
|
601
281
|
});
|
|
602
|
-
|
|
603
|
-
|
|
282
|
+
node.value = css;
|
|
283
|
+
}
|
|
284
|
+
fileText = serialize(DOM);
|
|
285
|
+
// Minify HTML
|
|
286
|
+
fileText = await minify(fileText, {
|
|
287
|
+
collapseWhitespace: true,
|
|
288
|
+
removeComments: true,
|
|
289
|
+
...bundleConfig["html-minifier-terser"],
|
|
290
|
+
});
|
|
291
|
+
if (!isCritical) {
|
|
292
|
+
await writeFile(buildFile, fileText);
|
|
293
|
+
return fileText;
|
|
604
294
|
}
|
|
605
|
-
|
|
606
|
-
|
|
295
|
+
else {
|
|
296
|
+
const buildFileArr = buildFile.split("/");
|
|
297
|
+
const fileWithBase = buildFileArr.pop();
|
|
298
|
+
const buildDir = buildFileArr.join("/");
|
|
299
|
+
// critical is generating the files on the fs
|
|
300
|
+
try {
|
|
301
|
+
const { html } = await critical.generate({
|
|
302
|
+
base: buildDir,
|
|
303
|
+
html: fileText,
|
|
304
|
+
target: fileWithBase,
|
|
305
|
+
inline: !isSecure,
|
|
306
|
+
extract: true,
|
|
307
|
+
rebase: () => { },
|
|
308
|
+
...bundleConfig.critical,
|
|
309
|
+
});
|
|
310
|
+
return html;
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
607
313
|
console.error(err);
|
|
608
|
-
process.exit(1);
|
|
609
314
|
}
|
|
610
315
|
}
|
|
611
|
-
function tsMaybeX2JS(filename) {
|
|
612
|
-
return filename.replace(".ts", ".js").replace(".jsx", ".js");
|
|
613
|
-
}
|
|
614
316
|
}
|
|
615
|
-
function
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
fastify.register(fastifyStatic, {
|
|
626
|
-
root: path.join(process.cwd(), BUILD_FOLDER),
|
|
627
|
-
});
|
|
317
|
+
async function rebuildCSS(files, config) {
|
|
318
|
+
const newConfig = await getPostCSSConfig();
|
|
319
|
+
plugins = newConfig.plugins;
|
|
320
|
+
options = newConfig.options;
|
|
321
|
+
CSSprocessor = postcss(plugins);
|
|
322
|
+
for (const file of files) {
|
|
323
|
+
await minifyCSS(file, getBuildPath(file));
|
|
324
|
+
}
|
|
325
|
+
if (config)
|
|
326
|
+
console.log(`⚡ modified ${config}.config`);
|
|
628
327
|
}
|