webhanger 1.0.5 ā 1.0.6
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/bin/cli.js +157 -0
- package/core/atomizer.js +95 -0
- package/core/builder.js +55 -19
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -345,6 +345,159 @@ switch (command) {
|
|
|
345
345
|
console.log(chalk.gray(` "cdn": { "url": "https://webhanger-edge.your-subdomain.workers.dev" }`));
|
|
346
346
|
break;
|
|
347
347
|
}
|
|
348
|
+
case "release": {
|
|
349
|
+
const releaseType = args[1] || "patch"; // patch | minor | major
|
|
350
|
+
const { execSync } = await import("child_process");
|
|
351
|
+
const { default: fsExtra } = await import("fs-extra");
|
|
352
|
+
const { default: pathMod } = await import("path");
|
|
353
|
+
|
|
354
|
+
console.log(chalk.cyan(`\nš Releasing webhanger + webhanger-front (${releaseType})...\n`));
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
// 1. Bump + publish webhanger
|
|
358
|
+
console.log(chalk.white(" [1/4] Publishing webhanger..."));
|
|
359
|
+
execSync(`npm version ${releaseType} --no-git-tag-version`, { stdio: "inherit" });
|
|
360
|
+
execSync("npm publish --access public", { stdio: "inherit" });
|
|
361
|
+
const whPkg = await fsExtra.readJson("./package.json");
|
|
362
|
+
console.log(chalk.green(` ā
webhanger@${whPkg.version} published`));
|
|
363
|
+
|
|
364
|
+
// 2. Build + bump + publish webhanger-front
|
|
365
|
+
console.log(chalk.white("\n [2/4] Building webhanger-front..."));
|
|
366
|
+
execSync("npm run build", { cwd: "./webhanger-front", stdio: "inherit" });
|
|
367
|
+
execSync(`npm version ${releaseType} --no-git-tag-version`, { cwd: "./webhanger-front", stdio: "inherit" });
|
|
368
|
+
execSync("npm publish --access public", { cwd: "./webhanger-front", stdio: "inherit" });
|
|
369
|
+
const whfPkg = await fsExtra.readJson("./webhanger-front/package.json");
|
|
370
|
+
console.log(chalk.green(` ā
webhanger-front@${whfPkg.version} published`));
|
|
371
|
+
|
|
372
|
+
// 3. Update all HTML files to use new unpkg version
|
|
373
|
+
console.log(chalk.white("\n [3/4] Updating unpkg references..."));
|
|
374
|
+
const newUrl = `https://unpkg.com/webhanger-front@${whfPkg.version}/browser.min.js`;
|
|
375
|
+
const oldPattern = /https:\/\/unpkg\.com\/webhanger-front@[^/]+\/browser(?:\.min)?\.js/g;
|
|
376
|
+
|
|
377
|
+
const htmlFiles = [];
|
|
378
|
+
async function findHtml(dir) {
|
|
379
|
+
const entries = await fsExtra.readdir(dir, { withFileTypes: true });
|
|
380
|
+
for (const e of entries) {
|
|
381
|
+
if (e.name === "node_modules" || e.name === ".git") continue;
|
|
382
|
+
const full = pathMod.join(dir, e.name);
|
|
383
|
+
if (e.isDirectory()) await findHtml(full);
|
|
384
|
+
else if (e.name.endsWith(".html") || e.name.endsWith(".js")) htmlFiles.push(full);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
await findHtml(".");
|
|
388
|
+
|
|
389
|
+
let updated = 0;
|
|
390
|
+
for (const file of htmlFiles) {
|
|
391
|
+
const content = await fsExtra.readFile(file, "utf-8");
|
|
392
|
+
if (oldPattern.test(content)) {
|
|
393
|
+
await fsExtra.writeFile(file, content.replace(oldPattern, newUrl), "utf-8");
|
|
394
|
+
console.log(chalk.gray(` ā ${file}`));
|
|
395
|
+
updated++;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
console.log(chalk.green(` ā
Updated ${updated} files to ${newUrl}`));
|
|
399
|
+
|
|
400
|
+
// 4. Summary
|
|
401
|
+
console.log(chalk.green(`\nā
Release complete!`));
|
|
402
|
+
console.log(chalk.white(` webhanger : v${whPkg.version}`));
|
|
403
|
+
console.log(chalk.white(` webhanger-front : v${whfPkg.version}`));
|
|
404
|
+
console.log(chalk.gray(` unpkg URL : ${newUrl}\n`));
|
|
405
|
+
|
|
406
|
+
} catch (err) {
|
|
407
|
+
console.log(chalk.red(`\nā Release failed: ${err.message}`));
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
case "atomize": {
|
|
413
|
+
const atomFile = args[1];
|
|
414
|
+
const atomOut = args[2] || "./atomized";
|
|
415
|
+
const atomVer = args[3] || "1.0.0";
|
|
416
|
+
|
|
417
|
+
if (!atomFile) {
|
|
418
|
+
console.log(chalk.red("Usage: wh atomize <html-file> [components-dir] [version]"));
|
|
419
|
+
console.log(chalk.gray("Example: wh atomize ./docs/index.html ./atomized 1.0.0"));
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const { atomize } = await import("../core/atomizer.js");
|
|
424
|
+
const { deploy: registryDeploy } = await import("../core/registry.js");
|
|
425
|
+
const { default: fsExtra } = await import("fs-extra");
|
|
426
|
+
const { default: pathMod } = await import("path");
|
|
427
|
+
const loadConfigFn = (await import("../helper/loadConfig.js")).default;
|
|
428
|
+
const config = loadConfigFn();
|
|
429
|
+
const { projectId } = config;
|
|
430
|
+
|
|
431
|
+
console.log(chalk.cyan(`\nāļø Atomizing ${atomFile}...\n`));
|
|
432
|
+
|
|
433
|
+
// Step 1: Split into components
|
|
434
|
+
const { components, cdnAssets, globalJs } = await atomize(atomFile, atomOut);
|
|
435
|
+
console.log(chalk.green(` ā
Split into ${components.length} components:`));
|
|
436
|
+
components.forEach(c => console.log(chalk.gray(` ā ${c.name}`)));
|
|
437
|
+
|
|
438
|
+
// Step 2: Deploy each component
|
|
439
|
+
console.log(chalk.cyan(`\nš Deploying components...\n`));
|
|
440
|
+
const deployed = {};
|
|
441
|
+
for (const comp of components) {
|
|
442
|
+
process.stdout.write(` ${comp.name}@${atomVer}... `);
|
|
443
|
+
try {
|
|
444
|
+
deployed[comp.name] = await registryDeploy(config, comp.dir, comp.name, atomVer);
|
|
445
|
+
console.log(chalk.green("ā
"));
|
|
446
|
+
} catch (err) {
|
|
447
|
+
console.log(chalk.red(`ā ${err.message}`));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Step 3: Write manifest
|
|
452
|
+
const manifest = {
|
|
453
|
+
pid: projectId,
|
|
454
|
+
components: Object.fromEntries(
|
|
455
|
+
Object.entries(deployed).map(([name, d]) => [
|
|
456
|
+
name, { url: d.cdnUrl, urls: d.cdnUrls || [d.cdnUrl], token: d.token, expires: d.expires }
|
|
457
|
+
])
|
|
458
|
+
)
|
|
459
|
+
};
|
|
460
|
+
const manifestPath = pathMod.join(pathMod.dirname(atomFile), "wh-manifest.json");
|
|
461
|
+
await fsExtra.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
462
|
+
|
|
463
|
+
// Step 4: Write globalJs to a separate file to avoid template literal conflicts
|
|
464
|
+
const globalJsFile = pathMod.join(pathMod.dirname(atomFile), "wh-global.js");
|
|
465
|
+
if (globalJs) {
|
|
466
|
+
await fsExtra.writeFile(globalJsFile, globalJs, "utf-8");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Step 5: Generate production-ready CDN-powered HTML
|
|
470
|
+
const mounts = components.map(c => ` <wh-component name="${c.name}"></wh-component>`).join("\n");
|
|
471
|
+
const outHtml = `<!DOCTYPE html>
|
|
472
|
+
<html lang="en">
|
|
473
|
+
<head>
|
|
474
|
+
<meta charset="UTF-8">
|
|
475
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
476
|
+
<title>WebHanger</title>
|
|
477
|
+
${cdnAssets.filter(a => a.type === "style").map(a => `<link rel="stylesheet" href="${a.url}">`).join("\n ")}
|
|
478
|
+
</head>
|
|
479
|
+
<body>
|
|
480
|
+
${mounts}
|
|
481
|
+
${cdnAssets.filter(a => a.type === "script").map(a => `<script src="${a.url}"></script>`).join("\n ")}
|
|
482
|
+
<script src="https://unpkg.com/webhanger-front@latest/browser.min.js"></script>
|
|
483
|
+
<script>
|
|
484
|
+
WebHangerFront.initialize("./wh-manifest.json");
|
|
485
|
+
</script>
|
|
486
|
+
${globalJs ? `<script src="./wh-global.js"></script>` : ""}
|
|
487
|
+
</body>
|
|
488
|
+
</html>`;
|
|
489
|
+
|
|
490
|
+
const outHtmlPath = pathMod.join(pathMod.dirname(atomFile), "atomized.html");
|
|
491
|
+
await fsExtra.writeFile(outHtmlPath, outHtml, "utf-8");
|
|
492
|
+
|
|
493
|
+
console.log(chalk.green(`\nā
Atomize complete!`));
|
|
494
|
+
console.log(chalk.white(` Components : ${components.length}`));
|
|
495
|
+
console.log(chalk.white(` Manifest : ${manifestPath}`));
|
|
496
|
+
console.log(chalk.white(` Output : ${outHtmlPath}`));
|
|
497
|
+
if (globalJs) console.log(chalk.white(` Global JS : ${globalJsFile}`));
|
|
498
|
+
console.log(chalk.gray(`\n Open atomized.html ā entire page loads from CDN.\n`));
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
348
501
|
case "breakdown": {
|
|
349
502
|
const bdDir = args[1];
|
|
350
503
|
if (!bdDir) {
|
|
@@ -510,6 +663,7 @@ switch (command) {
|
|
|
510
663
|
buildResult.pages.forEach(p => {
|
|
511
664
|
const kb = (p.size / 1024).toFixed(1);
|
|
512
665
|
console.log(chalk.gray(` ${p.fileName.padEnd(25)} ${kb} kB`));
|
|
666
|
+
(p.assets || []).forEach(a => console.log(chalk.gray(` ā³ ${a}`)));
|
|
513
667
|
});
|
|
514
668
|
} catch (err) {
|
|
515
669
|
console.log(chalk.yellow(` Build warning: ${err.message}`));
|
|
@@ -714,6 +868,7 @@ ${mounts}
|
|
|
714
868
|
result.pages.forEach(p => {
|
|
715
869
|
const kb = (p.size / 1024).toFixed(1);
|
|
716
870
|
console.log(chalk.white(` ${p.fileName.padEnd(25)} ${kb} kB`));
|
|
871
|
+
(p.assets || []).forEach(a => console.log(chalk.gray(` ā³ ${a}`)));
|
|
717
872
|
});
|
|
718
873
|
console.log(chalk.gray(`\n Output: ${result.outDir}`));
|
|
719
874
|
} catch (err) {
|
|
@@ -769,11 +924,13 @@ ${mounts}
|
|
|
769
924
|
console.log(chalk.cyan(BANNER));
|
|
770
925
|
console.log(chalk.white("Commands:"));
|
|
771
926
|
console.log(chalk.gray(" wh init ā setup your project"));
|
|
927
|
+
console.log(chalk.gray(" wh release [patch|minor|major] ā publish both packages + update all unpkg refs"));
|
|
772
928
|
console.log(chalk.gray(" wh ship <comp-dir> <site-dir> [version] [out-dir] ā deploy + build + zip in one shot"));
|
|
773
929
|
console.log(chalk.gray(" wh deploy <dir> <name> <version> ā deploy a single component"));
|
|
774
930
|
console.log(chalk.gray(" wh graph-deploy <comp-dir> [version] [out-dir] ā deploy all + resolve dep graph"));
|
|
775
931
|
console.log(chalk.gray(" wh build <src-dir> [out-dir] ā production build"));
|
|
776
932
|
console.log(chalk.gray(" wh zip <src-dir> [out-file] ā zip for deployment"));
|
|
933
|
+
console.log(chalk.gray(" wh atomize <html-file> [out-dir] [version] ā split page into CDN components"));
|
|
777
934
|
console.log(chalk.gray(" wh breakdown <dir> ā extract CSS/JS from single HTML file"));
|
|
778
935
|
console.log(chalk.gray(" wh access grant|revoke|list ā role-based access control"));
|
|
779
936
|
console.log(chalk.gray(" wh convert <dir> <name> <target> [out-dir] ā convert to react/vue/svelte/next/astro"));
|
package/core/atomizer.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Atomizer ā splits a single HTML page into section-based WebHanger components.
|
|
6
|
+
* Each component gets the full CSS (encrypted on CDN) + its own HTML.
|
|
7
|
+
* Global JS runs once in the host page after all components mount.
|
|
8
|
+
*/
|
|
9
|
+
export async function atomize(htmlFile, outputDir) {
|
|
10
|
+
const raw = await fs.readFile(htmlFile, "utf-8");
|
|
11
|
+
|
|
12
|
+
// āā Extract global <style> āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
13
|
+
const styleBlocks = [];
|
|
14
|
+
let stripped = raw.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (_, css) => {
|
|
15
|
+
styleBlocks.push(css.trim());
|
|
16
|
+
return "";
|
|
17
|
+
});
|
|
18
|
+
const globalCss = styleBlocks.join("\n");
|
|
19
|
+
|
|
20
|
+
// āā Extract global <script> blocks āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
21
|
+
const scriptBlocks = [];
|
|
22
|
+
stripped = stripped.replace(/<script(?![^>]*src)[^>]*>([\s\S]*?)<\/script>/gi, (_, js) => {
|
|
23
|
+
scriptBlocks.push(js.trim());
|
|
24
|
+
return "";
|
|
25
|
+
});
|
|
26
|
+
const globalJs = scriptBlocks.join("\n");
|
|
27
|
+
|
|
28
|
+
// āā Extract external CDN assets āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
29
|
+
const cdnAssets = [];
|
|
30
|
+
stripped = stripped.replace(/<link[^>]+href="(https?:[^"]+\.css)"[^>]*>/gi, (_, url) => {
|
|
31
|
+
cdnAssets.push({ type: "style", url });
|
|
32
|
+
return "";
|
|
33
|
+
});
|
|
34
|
+
stripped = stripped.replace(/<script[^>]+src="(https?:[^"]+)"[^>]*><\/script>/gi, (_, url) => {
|
|
35
|
+
cdnAssets.push({ type: "script", url });
|
|
36
|
+
return "";
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// āā Extract body āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
40
|
+
const bodyMatch = stripped.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
41
|
+
const body = (bodyMatch ? bodyMatch[1] : stripped).trim();
|
|
42
|
+
|
|
43
|
+
// āā Split into top-level semantic sections āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
44
|
+
const found = [];
|
|
45
|
+
const topLevelRegex = /<(nav|header|section|footer|div)(\s[^>]*?)?>([\s\S]*?)<\/\1>/gi;
|
|
46
|
+
let m;
|
|
47
|
+
while ((m = topLevelRegex.exec(body)) !== null) {
|
|
48
|
+
const tag = m[1];
|
|
49
|
+
const attrs = m[2] || "";
|
|
50
|
+
const content = m[3];
|
|
51
|
+
|
|
52
|
+
const idMatch = attrs.match(/id="([^"]+)"/);
|
|
53
|
+
const classMatch = attrs.match(/class="([^\s"]+)/);
|
|
54
|
+
const name = idMatch ? idMatch[1]
|
|
55
|
+
: classMatch ? classMatch[1].replace(/[^a-z0-9]/gi, "-")
|
|
56
|
+
: `${tag}-${found.length + 1}`;
|
|
57
|
+
|
|
58
|
+
found.push({
|
|
59
|
+
name: name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-"),
|
|
60
|
+
tag,
|
|
61
|
+
attrs,
|
|
62
|
+
html: `<${tag}${attrs}>${content}</${tag}>`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!found.length) {
|
|
67
|
+
found.push({ name: "main", tag: "div", attrs: "", html: body });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// āā Write component folders āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
71
|
+
await fs.ensureDir(outputDir);
|
|
72
|
+
const components = [];
|
|
73
|
+
|
|
74
|
+
for (const section of found) {
|
|
75
|
+
const compDir = path.join(outputDir, section.name);
|
|
76
|
+
await fs.ensureDir(compDir);
|
|
77
|
+
|
|
78
|
+
await fs.writeFile(path.join(compDir, "index.html"), section.html.trim(), "utf-8");
|
|
79
|
+
|
|
80
|
+
// Full CSS per component ā encrypted on CDN, correct rendering guaranteed
|
|
81
|
+
if (globalCss) {
|
|
82
|
+
await fs.writeFile(path.join(compDir, "style.css"), globalCss, "utf-8");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// No JS per component ā global JS runs once in host page after mount
|
|
86
|
+
await fs.writeJson(path.join(compDir, "webhanger.component.json"), {
|
|
87
|
+
assets: cdnAssets,
|
|
88
|
+
dependencies: []
|
|
89
|
+
}, { spaces: 2 });
|
|
90
|
+
|
|
91
|
+
components.push({ name: section.name, dir: compDir });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { components, cdnAssets, globalJs };
|
|
95
|
+
}
|
package/core/builder.js
CHANGED
|
@@ -25,10 +25,9 @@ function minifyCss(css) {
|
|
|
25
25
|
|
|
26
26
|
function minifyJs(js) {
|
|
27
27
|
return js
|
|
28
|
-
.replace(
|
|
29
|
-
.replace(
|
|
30
|
-
.replace(
|
|
31
|
-
.replace(/\s*([=+\-*/{};:,()[\]])\s*/g, "$1") // spaces around operators
|
|
28
|
+
.replace(/\/\*[\s\S]*?\*\//g, "") // remove block comments only
|
|
29
|
+
.replace(/[ \t]+/g, " ") // collapse spaces/tabs (not newlines)
|
|
30
|
+
.replace(/^\s+/gm, "") // trim line starts
|
|
32
31
|
.trim();
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -42,34 +41,71 @@ function hashContent(content) {
|
|
|
42
41
|
|
|
43
42
|
async function processHtml(filePath, outDir, assetMap) {
|
|
44
43
|
let html = await fs.readFile(filePath, "utf-8");
|
|
44
|
+
const baseName = path.basename(filePath, ".html");
|
|
45
|
+
|
|
46
|
+
// āā Extract embedded <style> blocks ā separate .css file āāāāāāāāāāāāāāāāā
|
|
47
|
+
const styleBlocks = [];
|
|
48
|
+
html = html.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (_, css) => {
|
|
49
|
+
styleBlocks.push(css.trim());
|
|
50
|
+
return "";
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (styleBlocks.length) {
|
|
54
|
+
const css = minifyCss(styleBlocks.join("\n"));
|
|
55
|
+
const hash = hashContent(css);
|
|
56
|
+
const cssFile = `${baseName}.${hash}.css`;
|
|
57
|
+
await fs.writeFile(path.join(outDir, cssFile), css, "utf-8");
|
|
58
|
+
// Inject <link> before </head>
|
|
59
|
+
html = html.replace("</head>", `<link rel="stylesheet" href="./${cssFile}"></head>`);
|
|
60
|
+
assetMap[baseName + ".css"] = cssFile;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// āā Extract embedded <script> blocks (non-src) ā separate .js file āāāāāāā
|
|
64
|
+
const scriptBlocks = [];
|
|
65
|
+
html = html.replace(/<script(?![^>]*src)[^>]*>([\s\S]*?)<\/script>/gi, (_, js) => {
|
|
66
|
+
const trimmed = js.trim();
|
|
67
|
+
if (trimmed) scriptBlocks.push(trimmed);
|
|
68
|
+
return "";
|
|
69
|
+
});
|
|
45
70
|
|
|
46
|
-
|
|
71
|
+
if (scriptBlocks.length) {
|
|
72
|
+
const js = minifyJs(scriptBlocks.join("\n"));
|
|
73
|
+
const hash = hashContent(js);
|
|
74
|
+
const jsFile = `${baseName}.${hash}.js`;
|
|
75
|
+
await fs.writeFile(path.join(outDir, jsFile), js, "utf-8");
|
|
76
|
+
// Inject <script> before </body>
|
|
77
|
+
html = html.replace("</body>", `<script src="./${jsFile}"></script></body>`);
|
|
78
|
+
assetMap[baseName + ".js"] = jsFile;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// āā Inline external local <link> CSS files āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
47
82
|
html = await replaceAsync(html, /<link[^>]+href="([^"]+\.css)"[^>]*>/g, async (match, href) => {
|
|
48
|
-
if (href.startsWith("http")
|
|
83
|
+
if (href.startsWith("http") || href.startsWith("./") && href.includes(".")) {
|
|
84
|
+
// Already a hashed file we just wrote ā keep it
|
|
85
|
+
if (Object.values(assetMap).some(v => href.includes(v))) return match;
|
|
86
|
+
}
|
|
87
|
+
if (href.startsWith("http")) return match;
|
|
49
88
|
const cssPath = path.resolve(path.dirname(filePath), href);
|
|
50
89
|
if (!await fs.pathExists(cssPath)) return match;
|
|
51
90
|
const css = minifyCss(await fs.readFile(cssPath, "utf-8"));
|
|
52
|
-
|
|
91
|
+
const hash = hashContent(css);
|
|
92
|
+
const cssFile = path.basename(href, ".css") + "." + hash + ".css";
|
|
93
|
+
await fs.writeFile(path.join(outDir, cssFile), css, "utf-8");
|
|
94
|
+
return match.replace(href, "./" + cssFile);
|
|
53
95
|
});
|
|
54
96
|
|
|
55
|
-
//
|
|
56
|
-
html = await replaceAsync(html, /<script[^>]+src="([^"]+\.js)"[^>]*><\/script>/g, async (match, src) => {
|
|
57
|
-
if (src.startsWith("http") || src.includes("unpkg") || src.includes("cdn")) return match;
|
|
58
|
-
const jsPath = path.resolve(path.dirname(filePath), src);
|
|
59
|
-
if (!await fs.pathExists(jsPath)) return match;
|
|
60
|
-
const js = minifyJs(await fs.readFile(jsPath, "utf-8"));
|
|
61
|
-
return `<script>${js}</script>`;
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Minify the HTML itself
|
|
97
|
+
// āā Minify HTML āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
65
98
|
html = minifyHtml(html);
|
|
66
99
|
|
|
67
|
-
// Write to dist
|
|
68
100
|
const fileName = path.basename(filePath);
|
|
69
101
|
const outPath = path.join(outDir, fileName);
|
|
70
102
|
await fs.writeFile(outPath, html, "utf-8");
|
|
71
103
|
|
|
72
|
-
return {
|
|
104
|
+
return {
|
|
105
|
+
fileName,
|
|
106
|
+
size: Buffer.byteLength(html, "utf-8"),
|
|
107
|
+
assets: Object.values(assetMap)
|
|
108
|
+
};
|
|
73
109
|
}
|
|
74
110
|
|
|
75
111
|
// āāā Async replace helper āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|