-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmake-singlefiles.js
More file actions
101 lines (87 loc) · 2.91 KB
/
Copy pathmake-singlefiles.js
File metadata and controls
101 lines (87 loc) · 2.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import fs from 'fs/promises';
import path from 'path';
const dist = path.resolve(process.cwd(), 'dist');
const htmlFiles = ['index.html', 'plugin.html'];
async function fileExists(p) {
try {
await fs.access(p);
return true;
} catch (e) {
return false;
}
}
async function inlineHtml(file) {
const filePath = path.join(dist, file);
if (!(await fileExists(filePath))) {
console.warn('[make-singlefiles] skipping, not found:', filePath);
return;
}
let html = await fs.readFile(filePath, 'utf8');
// Inline CSS <link rel="stylesheet" href="...">
const linkRe = /<link\s+[^>]*rel=["']stylesheet["'][^>]*href=["']([^"']+)["'][^>]*>/gi;
html = await replaceAsync(html, linkRe, async (match, href) => {
const assetPath = href.startsWith('/') ? href.slice(1) : href;
const full = path.join(dist, assetPath);
if (!(await fileExists(full))) {
console.warn('[make-singlefiles] css not found:', full);
return '';
}
const css = await fs.readFile(full, 'utf8');
return `\n<style>/* inlined ${assetPath} */\n${css}\n</style>`;
});
// Inline <script src="..."></script>
const scriptRe = /<script\s+([^>]*)src=["']([^"']+)["']([^>]*)>\s*<\/script>/gi;
html = await replaceAsync(html, scriptRe, async (match, beforeAttrs, src, afterAttrs) => {
const assetPath = src.startsWith('/') ? src.slice(1) : src;
const full = path.join(dist, assetPath);
if (!(await fileExists(full))) {
console.warn('[make-singlefiles] js not found:', full);
return match;
}
const js = await fs.readFile(full, 'utf8');
const attrs = (beforeAttrs + ' ' + afterAttrs).trim();
const isModule = /type=["']module["']/.test(attrs);
const typeAttr = isModule ? ' type="module"' : '';
return `\n<script${typeAttr}>\n// inlined ${assetPath}\n${js}\n</script>`;
});
await fs.writeFile(filePath, html, 'utf8');
console.log('[make-singlefiles] inlined', filePath);
}
async function replaceAsync(str, re, asyncFn) {
const parts = [];
let lastIndex = 0;
let match;
while ((match = re.exec(str)) !== null) {
parts.push(str.slice(lastIndex, match.index));
const repl = await asyncFn(...match);
parts.push(repl);
lastIndex = re.lastIndex;
}
parts.push(str.slice(lastIndex));
return parts.join('');
}
async function removeAssetsDir() {
const assetsDir = path.join(dist, 'assets');
if (!(await fileExists(assetsDir))) {
return;
}
await fs.rm(assetsDir, { recursive: true, force: true });
console.log('[make-singlefiles] removed', assetsDir);
}
(async () => {
let hadError = false;
for (const hf of htmlFiles) {
try {
await inlineHtml(hf);
} catch (err) {
hadError = true;
console.error('[make-singlefiles] error inlining', hf, err);
}
}
if (hadError) {
console.warn('[make-singlefiles] keeping assets because inlining failed');
process.exitCode = 1;
return;
}
await removeAssetsDir();
})();