@zulaica/site-bundler 0.7.0 → 0.8.0
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/lib/helpers/constants.mjs +46 -45
- package/lib/helpers/logger.mjs +6 -5
- package/lib/helpers/tests.mjs +17 -8
- package/lib/index.mjs +9 -9
- package/lib/index.test.mjs +44 -44
- package/lib/modules/copyAssets.mjs +9 -9
- package/lib/modules/copyAssets.test.mjs +37 -37
- package/lib/modules/index.mjs +5 -5
- package/lib/modules/preflight.mjs +9 -9
- package/lib/modules/preflight.test.mjs +14 -14
- package/lib/modules/processCSS.mjs +14 -14
- package/lib/modules/processCSS.test.mjs +23 -23
- package/lib/modules/processHTML.mjs +65 -18
- package/lib/modules/processHTML.test.mjs +22 -22
- package/lib/modules/processJS.mjs +16 -16
- package/lib/modules/processJS.test.mjs +25 -25
- package/package.json +23 -19
|
@@ -1,92 +1,93 @@
|
|
|
1
1
|
/* node:coverage disable */
|
|
2
2
|
export const EMOJI = Object.freeze({
|
|
3
|
-
artistPalette:
|
|
4
|
-
barChart:
|
|
5
|
-
cardIndexDividers:
|
|
6
|
-
constructionWorker:
|
|
7
|
-
fileCabinet:
|
|
8
|
-
fileFolder:
|
|
9
|
-
noEntry:
|
|
10
|
-
package:
|
|
11
|
-
wastebasket:
|
|
3
|
+
artistPalette: "\u{1f3a8}",
|
|
4
|
+
barChart: "\u{1f4ca}",
|
|
5
|
+
cardIndexDividers: "\u{1f5c2}",
|
|
6
|
+
constructionWorker: "\u{1f477}",
|
|
7
|
+
fileCabinet: "\u{1f5c4} ",
|
|
8
|
+
fileFolder: "\u{1f4c1}",
|
|
9
|
+
noEntry: "\u{26D4}",
|
|
10
|
+
package: "\u{1f4e6}",
|
|
11
|
+
wastebasket: "\u{1f5d1} ",
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
export const EXCLUSIONS = Object.freeze([
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
"index.html",
|
|
16
|
+
"script.hash.js",
|
|
17
|
+
"scripts",
|
|
18
|
+
"style.hash.css",
|
|
19
|
+
"styles",
|
|
20
20
|
]);
|
|
21
21
|
|
|
22
22
|
export const OPTIONS = Object.freeze({
|
|
23
23
|
babel: {
|
|
24
24
|
presets: [
|
|
25
25
|
[
|
|
26
|
-
|
|
26
|
+
"@babel/preset-env",
|
|
27
27
|
{
|
|
28
|
-
targets:
|
|
29
|
-
modules: false
|
|
30
|
-
}
|
|
31
|
-
]
|
|
28
|
+
targets: "defaults",
|
|
29
|
+
modules: false,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
32
|
],
|
|
33
33
|
babelrc: false,
|
|
34
34
|
comments: false,
|
|
35
35
|
configFile: false,
|
|
36
|
-
minified: true
|
|
36
|
+
minified: true,
|
|
37
37
|
},
|
|
38
38
|
rollup: {
|
|
39
39
|
onwarn(warning, warn) {
|
|
40
|
-
if (warning.code ===
|
|
40
|
+
if (warning.code === "EMPTY_BUNDLE") return;
|
|
41
41
|
warn(warning);
|
|
42
|
-
}
|
|
42
|
+
},
|
|
43
43
|
},
|
|
44
44
|
postcss: {
|
|
45
45
|
cssnano: {
|
|
46
46
|
preset: [
|
|
47
|
-
|
|
47
|
+
"advanced",
|
|
48
48
|
{
|
|
49
49
|
minifyFontValues: false,
|
|
50
50
|
normalizeUrl: false,
|
|
51
51
|
normalizeString: {
|
|
52
|
-
preferredQuote:
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
]
|
|
52
|
+
preferredQuote: "single",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
56
|
},
|
|
57
57
|
url: [
|
|
58
58
|
{
|
|
59
|
-
filter:
|
|
60
|
-
url:
|
|
59
|
+
filter: "**/*.woff2",
|
|
60
|
+
url: "rebase",
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
|
-
filter:
|
|
64
|
-
url:
|
|
63
|
+
filter: "**/*.woff",
|
|
64
|
+
url: "rebase",
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
|
-
filter:
|
|
68
|
-
url:
|
|
67
|
+
filter: "**/*.ttf",
|
|
68
|
+
url: "rebase",
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
|
-
filter:
|
|
72
|
-
url: (asset) => asset.relativePath
|
|
71
|
+
filter: "**/*.webp",
|
|
72
|
+
url: (asset) => asset.relativePath,
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
|
-
filter:
|
|
76
|
-
url: (asset) => asset.relativePath
|
|
77
|
-
}
|
|
78
|
-
]
|
|
75
|
+
filter: "**/*.jpg",
|
|
76
|
+
url: (asset) => asset.relativePath,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
79
|
},
|
|
80
80
|
posthtml: {
|
|
81
81
|
htmlnano: {
|
|
82
82
|
collapseBooleanAttributes: {
|
|
83
|
-
amphtml: false
|
|
83
|
+
amphtml: false,
|
|
84
84
|
},
|
|
85
|
-
collapseWhitespace:
|
|
85
|
+
collapseWhitespace: "aggressive",
|
|
86
|
+
minifyCss: false,
|
|
86
87
|
minifyJs: false,
|
|
87
|
-
removeComments:
|
|
88
|
-
removeEmptyAttributes: false
|
|
89
|
-
}
|
|
90
|
-
}
|
|
88
|
+
removeComments: "all",
|
|
89
|
+
removeEmptyAttributes: false,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
91
92
|
});
|
|
92
93
|
/* node:coverage enable */
|
package/lib/helpers/logger.mjs
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { exit, stderr, stdout } from
|
|
2
|
-
import { clearLine, cursorTo } from
|
|
3
|
-
import { EMOJI } from
|
|
1
|
+
import { exit, stderr, stdout } from "node:process";
|
|
2
|
+
import { clearLine, cursorTo } from "node:readline";
|
|
3
|
+
import { EMOJI } from "./constants.mjs";
|
|
4
4
|
|
|
5
5
|
export default class Logger {
|
|
6
6
|
static error = (error) => {
|
|
7
7
|
stderr.write(`${EMOJI.noEntry} An error has occurred\n`);
|
|
8
8
|
stderr.write(`${error.toString()}\n\n`);
|
|
9
|
-
stderr.write(
|
|
9
|
+
stderr.write("", () => exit(1));
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
static message = (message, update = false) => {
|
|
13
|
-
if (process.env.NODE_ENV !==
|
|
13
|
+
if (process.env.NODE_ENV !== "test") {
|
|
14
14
|
if (update) {
|
|
15
|
+
message += "\n";
|
|
15
16
|
cursorTo(stdout, 0);
|
|
16
17
|
clearLine(stdout, 0);
|
|
17
18
|
}
|
package/lib/helpers/tests.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { mkdir, rm } from
|
|
2
|
-
import { tmpdir } from
|
|
3
|
-
import { join } from
|
|
4
|
-
import { afterEach, beforeEach } from
|
|
1
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach } from "node:test";
|
|
5
5
|
|
|
6
6
|
const baseDir = join(tmpdir(), `site-bundler-test-${Date.now()}`);
|
|
7
7
|
|
|
8
|
-
export const inputDir = join(baseDir,
|
|
9
|
-
export const outputDir = join(baseDir,
|
|
8
|
+
export const inputDir = join(baseDir, "input");
|
|
9
|
+
export const outputDir = join(baseDir, "output");
|
|
10
10
|
|
|
11
11
|
export const inputCSS = `
|
|
12
12
|
/**
|
|
@@ -25,7 +25,7 @@ h1 {
|
|
|
25
25
|
`;
|
|
26
26
|
|
|
27
27
|
export const processedCSS =
|
|
28
|
-
|
|
28
|
+
"body{font-family:Arial,sans-serif;margin:0;padding:20px}h1{color:#333}";
|
|
29
29
|
|
|
30
30
|
export const inputHTML = `
|
|
31
31
|
<!DOCTYPE html>
|
|
@@ -33,7 +33,16 @@ export const inputHTML = `
|
|
|
33
33
|
<head>
|
|
34
34
|
<!-- This is a comment -->
|
|
35
35
|
<meta charset="utf-8" />
|
|
36
|
+
<style>
|
|
37
|
+
body {
|
|
38
|
+
margin: 0;
|
|
39
|
+
padding: 20px;
|
|
40
|
+
}
|
|
41
|
+
</style>
|
|
36
42
|
<link rel="stylesheet" href="style.hash.css" />
|
|
43
|
+
<script>
|
|
44
|
+
document.documentElement.classList.add("js");
|
|
45
|
+
</script>
|
|
37
46
|
<script src="script.hash.js"></script>
|
|
38
47
|
</head>
|
|
39
48
|
<body>
|
|
@@ -66,7 +75,7 @@ export const inputHTML = `
|
|
|
66
75
|
`;
|
|
67
76
|
|
|
68
77
|
export const processedHTML =
|
|
69
|
-
'<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><link rel="stylesheet" href="style.hash.css"><script src="script.hash.js"></script></head><body><header><h1>Test Site</h1><nav><ul><li><a href="#home">Home</a></li><li><a href="#about">About</a></li></ul></nav></header><main><section><article><h2>Article Title</h2><p>Article content goes here.</p><p aria-hidden="true">This is hidden.</p></article></section><aside><input type="text" disabled placeholder="Enter text"></aside></main><footer><p
|
|
78
|
+
'<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><style>body{margin:0;padding:20px}</style><link rel="stylesheet" href="style.hash.css"><script>document.documentElement.classList.add("js");</script><script src="script.hash.js"></script></head><body><header><h1>Test Site</h1><nav><ul><li><a href="#home">Home</a></li><li><a href="#about">About</a></li></ul></nav></header><main><section><article><h2>Article Title</h2><p>Article content goes here.</p><p aria-hidden="true">This is hidden.</p></article></section><aside><input type="text" disabled placeholder="Enter text"></aside></main><footer><p>© Year</p></footer></body></html>';
|
|
70
79
|
|
|
71
80
|
export const inputJS = `
|
|
72
81
|
// This is a comment
|
package/lib/index.mjs
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { program } from
|
|
4
|
-
import { access, readFile } from
|
|
5
|
-
import { EMOJI } from
|
|
6
|
-
import Logger from
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
import { access, readFile } from "node:fs/promises";
|
|
5
|
+
import { EMOJI } from "./helpers/constants.mjs";
|
|
6
|
+
import Logger from "./helpers/logger.mjs";
|
|
7
7
|
import {
|
|
8
8
|
copyAssets,
|
|
9
9
|
preflight,
|
|
10
10
|
processCSS,
|
|
11
11
|
processHTML,
|
|
12
|
-
processJS
|
|
13
|
-
} from
|
|
12
|
+
processJS,
|
|
13
|
+
} from "./modules/index.mjs";
|
|
14
14
|
|
|
15
15
|
const { version } = JSON.parse(
|
|
16
|
-
await readFile(new URL(
|
|
16
|
+
await readFile(new URL("../package.json", import.meta.url)),
|
|
17
17
|
);
|
|
18
18
|
program
|
|
19
19
|
.version(version)
|
|
20
|
-
.requiredOption(
|
|
21
|
-
.requiredOption(
|
|
20
|
+
.requiredOption("-i, --input-dir <input>", "The input directory")
|
|
21
|
+
.requiredOption("-o, --output-dir <output>", "The output directory")
|
|
22
22
|
.parse();
|
|
23
23
|
const opts = program.opts();
|
|
24
24
|
|
package/lib/index.test.mjs
CHANGED
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
import assert from
|
|
2
|
-
import { execFile } from
|
|
3
|
-
import { readdir, readFile, writeFile } from
|
|
4
|
-
import { join } from
|
|
5
|
-
import { describe, it } from
|
|
6
|
-
import { promisify } from
|
|
7
|
-
import { EMOJI } from
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { describe, it } from "node:test";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { EMOJI } from "./helpers/constants.mjs";
|
|
8
8
|
import {
|
|
9
9
|
inputCSS,
|
|
10
10
|
inputDir,
|
|
11
11
|
inputHTML,
|
|
12
12
|
inputJS,
|
|
13
13
|
outputDir,
|
|
14
|
-
setUpTestHooks
|
|
15
|
-
} from
|
|
14
|
+
setUpTestHooks,
|
|
15
|
+
} from "./helpers/tests.mjs";
|
|
16
16
|
|
|
17
17
|
const execFileAsync = promisify(execFile);
|
|
18
18
|
const { version } = JSON.parse(
|
|
19
|
-
await readFile(new URL(
|
|
19
|
+
await readFile(new URL("../package.json", import.meta.url), "utf8"),
|
|
20
20
|
);
|
|
21
21
|
|
|
22
22
|
describe(`${EMOJI.package} CLI Integration`, () => {
|
|
23
23
|
setUpTestHooks();
|
|
24
24
|
|
|
25
|
-
it(
|
|
26
|
-
const { stdout } = await execFileAsync(
|
|
27
|
-
join(process.cwd(),
|
|
28
|
-
|
|
25
|
+
it("should display version", async () => {
|
|
26
|
+
const { stdout } = await execFileAsync("node", [
|
|
27
|
+
join(process.cwd(), "lib", "index.mjs"),
|
|
28
|
+
"--version",
|
|
29
29
|
]);
|
|
30
30
|
|
|
31
31
|
assert.ok(stdout.includes(version));
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
it(
|
|
35
|
-
await writeFile(join(inputDir,
|
|
36
|
-
await writeFile(join(inputDir,
|
|
37
|
-
await writeFile(join(inputDir,
|
|
38
|
-
await writeFile(join(inputDir,
|
|
34
|
+
it("should process complete site", async () => {
|
|
35
|
+
await writeFile(join(inputDir, "index.html"), inputHTML);
|
|
36
|
+
await writeFile(join(inputDir, "style.hash.css"), inputCSS);
|
|
37
|
+
await writeFile(join(inputDir, "script.hash.js"), inputJS);
|
|
38
|
+
await writeFile(join(inputDir, "favicon.ico"), "");
|
|
39
39
|
|
|
40
40
|
await assert.doesNotReject(() =>
|
|
41
|
-
execFileAsync(
|
|
42
|
-
join(process.cwd(),
|
|
43
|
-
|
|
41
|
+
execFileAsync("node", [
|
|
42
|
+
join(process.cwd(), "lib", "index.mjs"),
|
|
43
|
+
"-i",
|
|
44
44
|
inputDir,
|
|
45
|
-
|
|
46
|
-
outputDir
|
|
47
|
-
])
|
|
45
|
+
"-o",
|
|
46
|
+
outputDir,
|
|
47
|
+
]),
|
|
48
48
|
);
|
|
49
49
|
|
|
50
50
|
const outputFiles = await readdir(outputDir);
|
|
51
51
|
|
|
52
52
|
assert.deepEqual(outputFiles.sort(), [
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
"favicon.ico",
|
|
54
|
+
"index.html",
|
|
55
|
+
"script.hash.js",
|
|
56
|
+
"style.hash.css",
|
|
57
57
|
]);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
it(
|
|
61
|
-
const nonExistentDir = join(inputDir,
|
|
60
|
+
it("should handle non-existent input directory", async () => {
|
|
61
|
+
const nonExistentDir = join(inputDir, "non-existent");
|
|
62
62
|
|
|
63
63
|
await assert.rejects(
|
|
64
64
|
() =>
|
|
65
|
-
execFileAsync(
|
|
66
|
-
join(process.cwd(),
|
|
67
|
-
|
|
65
|
+
execFileAsync("node", [
|
|
66
|
+
join(process.cwd(), "lib", "index.mjs"),
|
|
67
|
+
"-i",
|
|
68
68
|
nonExistentDir,
|
|
69
|
-
|
|
70
|
-
outputDir
|
|
69
|
+
"-o",
|
|
70
|
+
outputDir,
|
|
71
71
|
]),
|
|
72
|
-
/Input directory does not exist
|
|
72
|
+
/Input directory does not exist/,
|
|
73
73
|
);
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
it(
|
|
76
|
+
it("should handle empty input directory", async () => {
|
|
77
77
|
await assert.rejects(
|
|
78
78
|
() =>
|
|
79
|
-
execFileAsync(
|
|
80
|
-
join(process.cwd(),
|
|
81
|
-
|
|
79
|
+
execFileAsync("node", [
|
|
80
|
+
join(process.cwd(), "lib", "index.mjs"),
|
|
81
|
+
"-i",
|
|
82
82
|
inputDir,
|
|
83
|
-
|
|
84
|
-
outputDir
|
|
83
|
+
"-o",
|
|
84
|
+
outputDir,
|
|
85
85
|
]),
|
|
86
|
-
/An error has occurred
|
|
86
|
+
/An error has occurred/,
|
|
87
87
|
);
|
|
88
88
|
});
|
|
89
89
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { cp, readdir } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import { EMOJI, EXCLUSIONS } from
|
|
4
|
-
import Logger from
|
|
1
|
+
import { cp, readdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { EMOJI, EXCLUSIONS } from "../helpers/constants.mjs";
|
|
4
|
+
import Logger from "../helpers/logger.mjs";
|
|
5
5
|
|
|
6
6
|
const copyAssets = async ({ inputDir, outputDir }) => {
|
|
7
7
|
const assets = await readdir(inputDir);
|
|
@@ -12,20 +12,20 @@ const copyAssets = async ({ inputDir, outputDir }) => {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
Logger.message(
|
|
15
|
-
`${EMOJI.cardIndexDividers} Copying ${filteredAssets.length} assets
|
|
15
|
+
`${EMOJI.cardIndexDividers} Copying ${filteredAssets.length} assets…`,
|
|
16
16
|
);
|
|
17
17
|
|
|
18
18
|
await Promise.all(
|
|
19
19
|
filteredAssets.map(async (asset) => {
|
|
20
20
|
await cp(join(inputDir, asset), join(outputDir, asset), {
|
|
21
|
-
recursive: true
|
|
21
|
+
recursive: true,
|
|
22
22
|
});
|
|
23
|
-
})
|
|
23
|
+
}),
|
|
24
24
|
);
|
|
25
25
|
|
|
26
26
|
Logger.message(
|
|
27
|
-
`${EMOJI.cardIndexDividers} Copied ${filteredAssets.length} assets
|
|
28
|
-
true
|
|
27
|
+
`${EMOJI.cardIndexDividers} Copied ${filteredAssets.length} assets`,
|
|
28
|
+
true,
|
|
29
29
|
);
|
|
30
30
|
};
|
|
31
31
|
|
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import assert from
|
|
2
|
-
import { mkdir, readdir, writeFile } from
|
|
3
|
-
import { join } from
|
|
4
|
-
import { describe, it } from
|
|
5
|
-
import { EMOJI } from
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, it } from "node:test";
|
|
5
|
+
import { EMOJI } from "../helpers/constants.mjs";
|
|
6
6
|
import {
|
|
7
7
|
inputCSS,
|
|
8
8
|
inputDir,
|
|
9
9
|
inputHTML,
|
|
10
10
|
inputJS,
|
|
11
11
|
outputDir,
|
|
12
|
-
setUpTestHooks
|
|
13
|
-
} from
|
|
14
|
-
import copyAssets from
|
|
12
|
+
setUpTestHooks,
|
|
13
|
+
} from "../helpers/tests.mjs";
|
|
14
|
+
import copyAssets from "./copyAssets.mjs";
|
|
15
15
|
|
|
16
16
|
describe(`${EMOJI.cardIndexDividers} copyAssets()`, () => {
|
|
17
17
|
setUpTestHooks();
|
|
18
18
|
|
|
19
|
-
it(
|
|
20
|
-
await writeFile(join(inputDir,
|
|
21
|
-
await writeFile(join(inputDir,
|
|
22
|
-
await writeFile(join(inputDir,
|
|
19
|
+
it("should copy all non-excluded assets", async () => {
|
|
20
|
+
await writeFile(join(inputDir, "image.png"), "");
|
|
21
|
+
await writeFile(join(inputDir, "document.pdf"), "");
|
|
22
|
+
await writeFile(join(inputDir, "script.js"), inputJS);
|
|
23
23
|
|
|
24
24
|
await assert.doesNotReject(() => copyAssets({ inputDir, outputDir }));
|
|
25
25
|
|
|
26
26
|
const copiedFiles = await readdir(outputDir);
|
|
27
27
|
|
|
28
28
|
assert.deepEqual(copiedFiles.sort(), [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
"document.pdf",
|
|
30
|
+
"image.png",
|
|
31
|
+
"script.js",
|
|
32
32
|
]);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it(
|
|
36
|
-
await mkdir(join(inputDir,
|
|
37
|
-
await mkdir(join(inputDir,
|
|
38
|
-
await writeFile(join(inputDir,
|
|
39
|
-
await writeFile(join(inputDir,
|
|
40
|
-
await writeFile(join(inputDir,
|
|
41
|
-
await writeFile(join(inputDir,
|
|
42
|
-
await writeFile(join(inputDir,
|
|
43
|
-
await writeFile(join(inputDir,
|
|
35
|
+
it("should exclude files listed in EXCLUSIONS", async () => {
|
|
36
|
+
await mkdir(join(inputDir, "scripts"), { recursive: true });
|
|
37
|
+
await mkdir(join(inputDir, "styles"), { recursive: true });
|
|
38
|
+
await writeFile(join(inputDir, "image.png"), "");
|
|
39
|
+
await writeFile(join(inputDir, "index.html"), inputHTML);
|
|
40
|
+
await writeFile(join(inputDir, "script.hash.js"), inputJS);
|
|
41
|
+
await writeFile(join(inputDir, "style.hash.css"), inputCSS);
|
|
42
|
+
await writeFile(join(inputDir, "scripts", "app.js"), inputJS);
|
|
43
|
+
await writeFile(join(inputDir, "styles", "main.css"), inputCSS);
|
|
44
44
|
|
|
45
45
|
await assert.doesNotReject(() => copyAssets({ inputDir, outputDir }));
|
|
46
46
|
|
|
47
47
|
const copiedFiles = await readdir(outputDir);
|
|
48
48
|
|
|
49
|
-
assert.deepStrictEqual(copiedFiles, [
|
|
49
|
+
assert.deepStrictEqual(copiedFiles, ["image.png"]);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
it(
|
|
52
|
+
it("should handle empty input directory", async () => {
|
|
53
53
|
await assert.doesNotReject(() => copyAssets({ inputDir, outputDir }));
|
|
54
54
|
|
|
55
55
|
const copiedFiles = await readdir(outputDir);
|
|
@@ -57,25 +57,25 @@ describe(`${EMOJI.cardIndexDividers} copyAssets()`, () => {
|
|
|
57
57
|
assert.deepStrictEqual(copiedFiles, []);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
it(
|
|
61
|
-
await mkdir(join(inputDir,
|
|
62
|
-
await mkdir(join(inputDir,
|
|
63
|
-
await writeFile(join(inputDir,
|
|
64
|
-
await writeFile(join(inputDir,
|
|
60
|
+
it("should copy directories recursively", async () => {
|
|
61
|
+
await mkdir(join(inputDir, "assets"), { recursive: true });
|
|
62
|
+
await mkdir(join(inputDir, "assets", "images"), { recursive: true });
|
|
63
|
+
await writeFile(join(inputDir, "assets", "file.txt"), "");
|
|
64
|
+
await writeFile(join(inputDir, "assets", "images", "logo.png"), "");
|
|
65
65
|
|
|
66
66
|
await assert.doesNotReject(() => copyAssets({ inputDir, outputDir }));
|
|
67
67
|
|
|
68
68
|
const copiedFiles = await readdir(outputDir, { withFileTypes: true });
|
|
69
|
-
const assetsDir = copiedFiles.find((f) => f.name ===
|
|
70
|
-
const subFiles = await readdir(join(outputDir,
|
|
69
|
+
const assetsDir = copiedFiles.find((f) => f.name === "assets");
|
|
70
|
+
const subFiles = await readdir(join(outputDir, "assets"));
|
|
71
71
|
|
|
72
72
|
assert.ok(assetsDir.isDirectory());
|
|
73
|
-
assert.deepStrictEqual(subFiles.sort(), [
|
|
73
|
+
assert.deepStrictEqual(subFiles.sort(), ["file.txt", "images"]);
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
it(
|
|
77
|
-
await writeFile(join(inputDir,
|
|
78
|
-
await writeFile(join(inputDir,
|
|
76
|
+
it("should handle only excluded files", async () => {
|
|
77
|
+
await writeFile(join(inputDir, "index.html"), inputHTML);
|
|
78
|
+
await writeFile(join(inputDir, "script.hash.js"), inputCSS);
|
|
79
79
|
|
|
80
80
|
await assert.doesNotReject(() => copyAssets({ inputDir, outputDir }));
|
|
81
81
|
|
package/lib/modules/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import copyAssets from
|
|
2
|
-
import preflight from
|
|
3
|
-
import processCSS from
|
|
4
|
-
import processHTML from
|
|
5
|
-
import processJS from
|
|
1
|
+
import copyAssets from "./copyAssets.mjs";
|
|
2
|
+
import preflight from "./preflight.mjs";
|
|
3
|
+
import processCSS from "./processCSS.mjs";
|
|
4
|
+
import processHTML from "./processHTML.mjs";
|
|
5
|
+
import processJS from "./processJS.mjs";
|
|
6
6
|
|
|
7
7
|
export { copyAssets, preflight, processCSS, processHTML, processJS };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { mkdir, readdir, rm } from
|
|
2
|
-
import { EMOJI } from
|
|
3
|
-
import Logger from
|
|
1
|
+
import { mkdir, readdir, rm } from "node:fs/promises";
|
|
2
|
+
import { EMOJI } from "../helpers/constants.mjs";
|
|
3
|
+
import Logger from "../helpers/logger.mjs";
|
|
4
4
|
|
|
5
5
|
const preflight = async (outputDir) => {
|
|
6
6
|
try {
|
|
@@ -13,22 +13,22 @@ const preflight = async (outputDir) => {
|
|
|
13
13
|
await mkdir(outputDir);
|
|
14
14
|
|
|
15
15
|
Logger.message(
|
|
16
|
-
`${EMOJI.wastebasket} Removed old ${outputDir} files
|
|
17
|
-
true
|
|
16
|
+
`${EMOJI.wastebasket} Removed old ${outputDir} files`,
|
|
17
|
+
true,
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
20
|
} catch (error) {
|
|
21
|
-
if (error.code ===
|
|
21
|
+
if (error.code === "ENOENT") {
|
|
22
22
|
Logger.message(`${EMOJI.fileFolder} Creating ${outputDir} directory…`);
|
|
23
23
|
|
|
24
24
|
await mkdir(outputDir);
|
|
25
25
|
|
|
26
26
|
Logger.message(
|
|
27
|
-
`${EMOJI.fileFolder} Created ${outputDir} directory
|
|
28
|
-
true
|
|
27
|
+
`${EMOJI.fileFolder} Created ${outputDir} directory`,
|
|
28
|
+
true,
|
|
29
29
|
);
|
|
30
30
|
} else {
|
|
31
|
-
Logger.error(
|
|
31
|
+
Logger.error(error);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import assert from
|
|
2
|
-
import { mkdir, readdir, writeFile } from
|
|
3
|
-
import { join } from
|
|
4
|
-
import { describe, it } from
|
|
5
|
-
import { EMOJI } from
|
|
6
|
-
import { outputDir, setUpTestHooks } from
|
|
7
|
-
import preflight from
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, it } from "node:test";
|
|
5
|
+
import { EMOJI } from "../helpers/constants.mjs";
|
|
6
|
+
import { outputDir, setUpTestHooks } from "../helpers/tests.mjs";
|
|
7
|
+
import preflight from "./preflight.mjs";
|
|
8
8
|
|
|
9
9
|
describe(`${EMOJI.fileFolder} preflight()`, () => {
|
|
10
10
|
setUpTestHooks();
|
|
11
11
|
|
|
12
|
-
it(
|
|
13
|
-
const nonExistentDir = join(outputDir,
|
|
12
|
+
it("should create output directory if it does not exist", async () => {
|
|
13
|
+
const nonExistentDir = join(outputDir, "non-existent");
|
|
14
14
|
await assert.doesNotReject(() => preflight(nonExistentDir));
|
|
15
15
|
|
|
16
16
|
const files = await readdir(nonExistentDir);
|
|
@@ -19,11 +19,11 @@ describe(`${EMOJI.fileFolder} preflight()`, () => {
|
|
|
19
19
|
assert.deepEqual(files, []);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
it(
|
|
23
|
-
await mkdir(join(outputDir,
|
|
24
|
-
await writeFile(join(outputDir,
|
|
25
|
-
await writeFile(join(outputDir,
|
|
26
|
-
await writeFile(join(outputDir,
|
|
22
|
+
it("should remove all files and recreate output directory", async () => {
|
|
23
|
+
await mkdir(join(outputDir, "subdir"), { recursive: true });
|
|
24
|
+
await writeFile(join(outputDir, "file1.txt"), "");
|
|
25
|
+
await writeFile(join(outputDir, "file2.txt"), "");
|
|
26
|
+
await writeFile(join(outputDir, "subdir", "file3.txt"), "");
|
|
27
27
|
|
|
28
28
|
const initialFiles = await readdir(outputDir, { withFileTypes: true });
|
|
29
29
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import cssnanoPlugin from
|
|
2
|
-
import { readdir, readFile, writeFile } from
|
|
3
|
-
import { join } from
|
|
4
|
-
import postcss from
|
|
5
|
-
import atImport from
|
|
6
|
-
import postcssPresetEnv from
|
|
7
|
-
import url from
|
|
8
|
-
import { EMOJI, OPTIONS } from
|
|
9
|
-
import Logger from
|
|
1
|
+
import cssnanoPlugin from "cssnano";
|
|
2
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import postcss from "postcss";
|
|
5
|
+
import atImport from "postcss-import";
|
|
6
|
+
import postcssPresetEnv from "postcss-preset-env";
|
|
7
|
+
import url from "postcss-url";
|
|
8
|
+
import { EMOJI, OPTIONS } from "../helpers/constants.mjs";
|
|
9
|
+
import Logger from "../helpers/logger.mjs";
|
|
10
10
|
|
|
11
11
|
const { cssnano, url: cssUrl } = OPTIONS.postcss;
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ const postcssProcessor = postcss()
|
|
|
18
18
|
|
|
19
19
|
const processCSS = async ({ inputDir, outputDir }) => {
|
|
20
20
|
const assets = await readdir(inputDir);
|
|
21
|
-
const file =
|
|
21
|
+
const file = "style.hash.css";
|
|
22
22
|
|
|
23
23
|
if (!assets.includes(file)) {
|
|
24
24
|
return;
|
|
@@ -28,15 +28,15 @@ const processCSS = async ({ inputDir, outputDir }) => {
|
|
|
28
28
|
|
|
29
29
|
const input = join(inputDir, file);
|
|
30
30
|
const output = join(outputDir, file);
|
|
31
|
-
const data = await readFile(input, { encoding:
|
|
32
|
-
const code = await
|
|
31
|
+
const data = await readFile(input, { encoding: "utf8" });
|
|
32
|
+
const code = await processCSSData(input, data);
|
|
33
33
|
|
|
34
34
|
await writeFile(output, code);
|
|
35
35
|
|
|
36
|
-
Logger.message(`${EMOJI.artistPalette} Processed styles
|
|
36
|
+
Logger.message(`${EMOJI.artistPalette} Processed styles`, true);
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
async function
|
|
39
|
+
export async function processCSSData(input, data) {
|
|
40
40
|
const { css } = await postcssProcessor.process(data, { from: input });
|
|
41
41
|
|
|
42
42
|
return css;
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import assert from
|
|
2
|
-
import { readFile, writeFile } from
|
|
3
|
-
import { join } from
|
|
4
|
-
import { describe, it } from
|
|
5
|
-
import { EMOJI } from
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, it } from "node:test";
|
|
5
|
+
import { EMOJI } from "../helpers/constants.mjs";
|
|
6
6
|
import {
|
|
7
7
|
inputCSS,
|
|
8
8
|
inputDir,
|
|
9
9
|
outputDir,
|
|
10
10
|
processedCSS,
|
|
11
|
-
setUpTestHooks
|
|
12
|
-
} from
|
|
13
|
-
import processCSS from
|
|
11
|
+
setUpTestHooks,
|
|
12
|
+
} from "../helpers/tests.mjs";
|
|
13
|
+
import processCSS from "./processCSS.mjs";
|
|
14
14
|
|
|
15
15
|
describe(`${EMOJI.artistPalette} processCSS()`, () => {
|
|
16
16
|
setUpTestHooks();
|
|
17
17
|
|
|
18
|
-
it(
|
|
19
|
-
await writeFile(join(inputDir,
|
|
18
|
+
it("should process CSS file and write output", async (sub) => {
|
|
19
|
+
await writeFile(join(inputDir, "style.hash.css"), inputCSS);
|
|
20
20
|
|
|
21
21
|
await assert.doesNotReject(() => processCSS({ inputDir, outputDir }));
|
|
22
22
|
|
|
23
|
-
const output = await readFile(join(outputDir,
|
|
23
|
+
const output = await readFile(join(outputDir, "style.hash.css"), "utf8");
|
|
24
24
|
|
|
25
|
-
sub.test(
|
|
25
|
+
sub.test("minimized as a single line", () => {
|
|
26
26
|
assert.equal(output, processedCSS);
|
|
27
27
|
});
|
|
28
|
-
sub.test(
|
|
28
|
+
sub.test("with whitespace trimmed", () => {
|
|
29
29
|
assert.ok(output.trim().length < inputCSS.trim().length);
|
|
30
30
|
});
|
|
31
|
-
sub.test(
|
|
31
|
+
sub.test("with comments removed", () => {
|
|
32
32
|
assert.doesNotMatch(output, /\/\*\*/);
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
it(
|
|
37
|
-
await writeFile(join(inputDir,
|
|
38
|
-
await writeFile(join(inputDir,
|
|
36
|
+
it("should handle CSS imports", async () => {
|
|
37
|
+
await writeFile(join(inputDir, "style.hash.css"), '@import "base.css";');
|
|
38
|
+
await writeFile(join(inputDir, "base.css"), inputCSS);
|
|
39
39
|
|
|
40
40
|
await assert.doesNotReject(() => processCSS({ inputDir, outputDir }));
|
|
41
41
|
|
|
42
|
-
const output = await readFile(join(outputDir,
|
|
42
|
+
const output = await readFile(join(outputDir, "style.hash.css"), "utf8");
|
|
43
43
|
|
|
44
44
|
assert.ok(output.includes(processedCSS));
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
it(
|
|
48
|
-
await writeFile(join(inputDir,
|
|
47
|
+
it("should handle empty CSS file", async () => {
|
|
48
|
+
await writeFile(join(inputDir, "style.hash.css"), "");
|
|
49
49
|
|
|
50
50
|
await assert.doesNotReject(() => processCSS({ inputDir, outputDir }));
|
|
51
51
|
|
|
52
|
-
const output = await readFile(join(outputDir,
|
|
52
|
+
const output = await readFile(join(outputDir, "style.hash.css"), "utf8");
|
|
53
53
|
|
|
54
|
-
assert.equal(output,
|
|
54
|
+
assert.equal(output, "");
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
it(
|
|
57
|
+
it("should skip processing if CSS file does not exist", async () => {
|
|
58
58
|
await assert.doesNotReject(() => processCSS({ inputDir, outputDir }));
|
|
59
59
|
});
|
|
60
60
|
});
|
|
@@ -1,37 +1,40 @@
|
|
|
1
|
-
import htmlnano from
|
|
2
|
-
import { readFile, writeFile } from
|
|
3
|
-
import { join } from
|
|
4
|
-
import posthtml from
|
|
5
|
-
import { hash } from
|
|
6
|
-
import { EMOJI, OPTIONS } from
|
|
7
|
-
import Logger from
|
|
1
|
+
import htmlnano from "htmlnano";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import posthtml from "posthtml";
|
|
5
|
+
import { hash } from "posthtml-hash";
|
|
6
|
+
import { EMOJI, OPTIONS } from "../helpers/constants.mjs";
|
|
7
|
+
import Logger from "../helpers/logger.mjs";
|
|
8
|
+
import { processCSSData } from "./processCSS.mjs";
|
|
9
|
+
import { processJSData } from "./processJS.mjs";
|
|
8
10
|
|
|
9
11
|
const { htmlnano: htmlnanoOptions } = OPTIONS.posthtml;
|
|
10
12
|
|
|
11
13
|
const processHTML = async ({ inputDir, outputDir }) => {
|
|
12
14
|
Logger.message(`${EMOJI.fileCabinet} Processing HTML…`);
|
|
13
|
-
const file =
|
|
15
|
+
const file = "index.html";
|
|
14
16
|
const input = join(inputDir, file);
|
|
15
17
|
const output = join(outputDir, file);
|
|
16
18
|
|
|
17
|
-
const data = await readFile(input, { encoding:
|
|
18
|
-
const code = await
|
|
19
|
+
const data = await readFile(input, { encoding: "utf8" });
|
|
20
|
+
const code = await processHTMLData(outputDir, data);
|
|
19
21
|
|
|
20
22
|
await writeFile(output, code);
|
|
21
23
|
|
|
22
|
-
Logger.message(`${EMOJI.fileCabinet} Processed HTML
|
|
24
|
+
Logger.message(`${EMOJI.fileCabinet} Processed HTML`, true);
|
|
23
25
|
};
|
|
24
26
|
|
|
25
|
-
async function
|
|
26
|
-
let posthtmlInstance = posthtml()
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
async function processHTMLData(path, data) {
|
|
28
|
+
let posthtmlInstance = posthtml()
|
|
29
|
+
.use(processInlineStyles())
|
|
30
|
+
.use(processInlineScripts())
|
|
31
|
+
.use(htmlnano(htmlnanoOptions, htmlnano.presets.safe));
|
|
29
32
|
|
|
30
33
|
/* node:coverage disable */
|
|
31
|
-
//
|
|
32
|
-
if (process.env.NODE_ENV !==
|
|
34
|
+
// Do not perform hash replacement when running tests
|
|
35
|
+
if (process.env.NODE_ENV !== "test") {
|
|
33
36
|
posthtmlInstance = posthtmlInstance.use(
|
|
34
|
-
hash({ path, pattern: new RegExp(/hash/) })
|
|
37
|
+
hash({ path, pattern: new RegExp(/hash/) }),
|
|
35
38
|
);
|
|
36
39
|
}
|
|
37
40
|
/* node:coverage enable */
|
|
@@ -41,4 +44,48 @@ async function _processData(path, data) {
|
|
|
41
44
|
return html;
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
/* node:coverage disable */
|
|
48
|
+
function processInlineContent(tagName, shouldProcess, processFn) {
|
|
49
|
+
return function () {
|
|
50
|
+
return async function (tree) {
|
|
51
|
+
const itemsToProcess = [];
|
|
52
|
+
|
|
53
|
+
tree.walk((node) => {
|
|
54
|
+
if (node.tag === tagName && shouldProcess(node)) {
|
|
55
|
+
const content = Array.isArray(node.content)
|
|
56
|
+
? node.content.join("")
|
|
57
|
+
: node.content;
|
|
58
|
+
|
|
59
|
+
itemsToProcess.push({ node, content });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return node;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await Promise.all(
|
|
66
|
+
itemsToProcess.map(async ({ node, content }) => {
|
|
67
|
+
node.content = await processFn(content);
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/* node:coverage enable */
|
|
74
|
+
|
|
75
|
+
/* node:coverage disable */
|
|
76
|
+
const processInlineScripts = processInlineContent(
|
|
77
|
+
"script",
|
|
78
|
+
(node) => node.content && !node.attrs?.src,
|
|
79
|
+
processJSData,
|
|
80
|
+
);
|
|
81
|
+
/* node:coverage enable */
|
|
82
|
+
|
|
83
|
+
/* node:coverage disable */
|
|
84
|
+
const processInlineStyles = processInlineContent(
|
|
85
|
+
"style",
|
|
86
|
+
(node) => node.content,
|
|
87
|
+
(content) => processCSSData(null, content),
|
|
88
|
+
);
|
|
89
|
+
/* node:coverage enable */
|
|
90
|
+
|
|
44
91
|
export default processHTML;
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import assert from
|
|
2
|
-
import { readFile, writeFile } from
|
|
3
|
-
import { join } from
|
|
4
|
-
import { describe, it } from
|
|
5
|
-
import { EMOJI } from
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, it } from "node:test";
|
|
5
|
+
import { EMOJI } from "../helpers/constants.mjs";
|
|
6
6
|
import {
|
|
7
7
|
inputDir,
|
|
8
8
|
inputHTML,
|
|
9
9
|
outputDir,
|
|
10
10
|
processedHTML,
|
|
11
|
-
setUpTestHooks
|
|
12
|
-
} from
|
|
13
|
-
import processHTML from
|
|
11
|
+
setUpTestHooks,
|
|
12
|
+
} from "../helpers/tests.mjs";
|
|
13
|
+
import processHTML from "./processHTML.mjs";
|
|
14
14
|
|
|
15
15
|
describe(`${EMOJI.fileCabinet} processHTML()`, () => {
|
|
16
16
|
setUpTestHooks();
|
|
17
17
|
|
|
18
|
-
it(
|
|
19
|
-
await writeFile(join(inputDir,
|
|
18
|
+
it("should process HTML file and write output", async (sub) => {
|
|
19
|
+
await writeFile(join(inputDir, "index.html"), inputHTML);
|
|
20
20
|
|
|
21
21
|
await assert.doesNotReject(() => processHTML({ inputDir, outputDir }));
|
|
22
22
|
|
|
23
|
-
const output = await readFile(join(outputDir,
|
|
23
|
+
const output = await readFile(join(outputDir, "index.html"), "utf8");
|
|
24
24
|
|
|
25
|
-
sub.test(
|
|
25
|
+
sub.test("minimized as a single line", () => {
|
|
26
26
|
assert.equal(output, processedHTML);
|
|
27
27
|
});
|
|
28
|
-
sub.test(
|
|
28
|
+
sub.test("with whitespace trimmed", () => {
|
|
29
29
|
assert.ok(output.trim().length < inputHTML.trim().length);
|
|
30
|
-
assert.ok(output.includes(
|
|
31
|
-
assert.ok(output.includes(
|
|
30
|
+
assert.ok(output.includes("Test Site"));
|
|
31
|
+
assert.ok(output.includes("Article Title"));
|
|
32
32
|
});
|
|
33
|
-
sub.test(
|
|
33
|
+
sub.test("with comments removed", () => {
|
|
34
34
|
assert.doesNotMatch(output, /<!--/);
|
|
35
35
|
});
|
|
36
|
-
sub.test(
|
|
37
|
-
assert.ok(output.includes(
|
|
36
|
+
sub.test("with boolean attributes handled correctly", () => {
|
|
37
|
+
assert.ok(output.includes("disabled"));
|
|
38
38
|
assert.doesNotMatch(output, /disabled="disabled"/);
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
it(
|
|
43
|
-
await writeFile(join(inputDir,
|
|
42
|
+
it("should handle empty HTML file", async () => {
|
|
43
|
+
await writeFile(join(inputDir, "index.html"), "");
|
|
44
44
|
await processHTML({ inputDir, outputDir });
|
|
45
45
|
|
|
46
|
-
const output = await readFile(join(outputDir,
|
|
46
|
+
const output = await readFile(join(outputDir, "index.html"), "utf8");
|
|
47
47
|
|
|
48
|
-
assert.equal(output,
|
|
48
|
+
assert.equal(output, "");
|
|
49
49
|
});
|
|
50
50
|
});
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { transformFromAstAsync } from
|
|
2
|
-
import { parse } from
|
|
3
|
-
import { readdir, writeFile } from
|
|
4
|
-
import { join } from
|
|
5
|
-
import { rollup } from
|
|
6
|
-
import { EMOJI, OPTIONS } from
|
|
7
|
-
import Logger from
|
|
1
|
+
import { transformFromAstAsync } from "@babel/core";
|
|
2
|
+
import { parse } from "@babel/parser";
|
|
3
|
+
import { readdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { rollup } from "rollup";
|
|
6
|
+
import { EMOJI, OPTIONS } from "../helpers/constants.mjs";
|
|
7
|
+
import Logger from "../helpers/logger.mjs";
|
|
8
8
|
|
|
9
9
|
const { babel: babelOptions, rollup: rollupOptions } = OPTIONS;
|
|
10
10
|
|
|
11
11
|
const processJS = async ({ inputDir, outputDir }) => {
|
|
12
12
|
const assets = await readdir(inputDir);
|
|
13
|
-
const file =
|
|
13
|
+
const file = "script.hash.js";
|
|
14
14
|
|
|
15
15
|
if (!assets.includes(file)) {
|
|
16
16
|
return;
|
|
@@ -20,28 +20,28 @@ const processJS = async ({ inputDir, outputDir }) => {
|
|
|
20
20
|
|
|
21
21
|
const input = join(inputDir, file);
|
|
22
22
|
const output = join(outputDir, file);
|
|
23
|
-
const bundledCode = await
|
|
24
|
-
const code = await
|
|
23
|
+
const bundledCode = await bundleJS(input);
|
|
24
|
+
const code = await processJSData(bundledCode);
|
|
25
25
|
|
|
26
26
|
await writeFile(output, code);
|
|
27
27
|
|
|
28
|
-
Logger.message(`${EMOJI.barChart} Processed scripts
|
|
28
|
+
Logger.message(`${EMOJI.barChart} Processed scripts`, true);
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
async function
|
|
31
|
+
async function bundleJS(input) {
|
|
32
32
|
const bundle = await rollup({
|
|
33
33
|
input,
|
|
34
|
-
...rollupOptions
|
|
34
|
+
...rollupOptions,
|
|
35
35
|
});
|
|
36
|
-
const { output } = await bundle.generate({ format:
|
|
36
|
+
const { output } = await bundle.generate({ format: "es" });
|
|
37
37
|
|
|
38
38
|
await bundle.close();
|
|
39
39
|
|
|
40
40
|
return output[0].code;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
async function
|
|
44
|
-
const ast = parse(data, { sourceType:
|
|
43
|
+
export async function processJSData(data) {
|
|
44
|
+
const ast = parse(data, { sourceType: "module" });
|
|
45
45
|
const { code } = await transformFromAstAsync(ast, data, babelOptions);
|
|
46
46
|
|
|
47
47
|
return code;
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
import assert from
|
|
2
|
-
import { readFile, writeFile } from
|
|
3
|
-
import { join } from
|
|
4
|
-
import { describe, it } from
|
|
5
|
-
import { EMOJI } from
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, it } from "node:test";
|
|
5
|
+
import { EMOJI } from "../helpers/constants.mjs";
|
|
6
6
|
import {
|
|
7
7
|
inputDir,
|
|
8
8
|
inputJS,
|
|
9
9
|
outputDir,
|
|
10
10
|
processedJS,
|
|
11
|
-
setUpTestHooks
|
|
12
|
-
} from
|
|
13
|
-
import processJS from
|
|
11
|
+
setUpTestHooks,
|
|
12
|
+
} from "../helpers/tests.mjs";
|
|
13
|
+
import processJS from "./processJS.mjs";
|
|
14
14
|
|
|
15
15
|
describe(`${EMOJI.barChart} processJS()`, () => {
|
|
16
16
|
setUpTestHooks();
|
|
17
17
|
|
|
18
|
-
it(
|
|
19
|
-
await writeFile(join(inputDir,
|
|
18
|
+
it("should process JavaScript file and write output", async (sub) => {
|
|
19
|
+
await writeFile(join(inputDir, "script.hash.js"), inputJS);
|
|
20
20
|
|
|
21
21
|
await assert.doesNotReject(() => processJS({ inputDir, outputDir }));
|
|
22
22
|
|
|
23
|
-
const output = await readFile(join(outputDir,
|
|
23
|
+
const output = await readFile(join(outputDir, "script.hash.js"), "utf8");
|
|
24
24
|
|
|
25
|
-
sub.test(
|
|
25
|
+
sub.test("minimized as a single line", () => {
|
|
26
26
|
assert.equal(output, processedJS);
|
|
27
27
|
});
|
|
28
|
-
sub.test(
|
|
28
|
+
sub.test("with whitespace trimmed", () => {
|
|
29
29
|
assert.ok(output.trim().length < inputJS.trim().length);
|
|
30
30
|
});
|
|
31
|
-
sub.test(
|
|
31
|
+
sub.test("with comments removed", () => {
|
|
32
32
|
assert.doesNotMatch(output, /\/\/ This is a comment/);
|
|
33
33
|
assert.doesNotMatch(output, /\/\*\*/);
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
it(
|
|
37
|
+
it("should bundle ES modules", async () => {
|
|
38
38
|
await writeFile(
|
|
39
|
-
join(inputDir,
|
|
39
|
+
join(inputDir, "script.hash.js"),
|
|
40
40
|
`
|
|
41
41
|
import sum from './input.js';
|
|
42
42
|
console.log(sum(2, 3));
|
|
43
|
-
|
|
43
|
+
`,
|
|
44
44
|
);
|
|
45
|
-
await writeFile(join(inputDir,
|
|
45
|
+
await writeFile(join(inputDir, "input.js"), inputJS);
|
|
46
46
|
|
|
47
47
|
await assert.doesNotReject(() => processJS({ inputDir, outputDir }));
|
|
48
48
|
|
|
49
|
-
const output = await readFile(join(outputDir,
|
|
49
|
+
const output = await readFile(join(outputDir, "script.hash.js"), "utf8");
|
|
50
50
|
|
|
51
|
-
assert.ok(output.includes(
|
|
51
|
+
assert.ok(output.includes("const sum=(a,b)=>{return a+b}"));
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
it(
|
|
55
|
-
await writeFile(join(inputDir,
|
|
54
|
+
it("should handle empty JavaScript file", async () => {
|
|
55
|
+
await writeFile(join(inputDir, "script.hash.js"), "");
|
|
56
56
|
|
|
57
57
|
await assert.doesNotReject(() => processJS({ inputDir, outputDir }));
|
|
58
58
|
|
|
59
|
-
const output = await readFile(join(outputDir,
|
|
59
|
+
const output = await readFile(join(outputDir, "script.hash.js"), "utf8");
|
|
60
60
|
|
|
61
|
-
assert.equal(output,
|
|
61
|
+
assert.equal(output, "");
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
it(
|
|
64
|
+
it("should skip processing if JavaScript file does not exist", async () => {
|
|
65
65
|
await assert.doesNotReject(() => processJS({ inputDir, outputDir }));
|
|
66
66
|
});
|
|
67
67
|
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zulaica/site-bundler",
|
|
3
3
|
"author": "David Zulaica",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=24.
|
|
7
|
+
"node": ">=24.18.0"
|
|
8
8
|
},
|
|
9
9
|
"license": "UNLICENSED",
|
|
10
10
|
"bin": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"lint": "eslint \"lib/**\"",
|
|
23
23
|
"test": "NODE_ENV=test node --test --test-concurrency=1 --require \"./lib/helpers/tests.mjs\"",
|
|
24
24
|
"test:watch": "npm run test -- --watch",
|
|
25
|
-
"test:coverage": "npm run test -- --experimental-test-coverage"
|
|
25
|
+
"test:coverage": "npm run test -- --experimental-test-coverage --test-coverage-exclude=\"**/helpers/**\" --test-coverage-exclude=\"**/*.test.mjs\""
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
@@ -33,29 +33,33 @@
|
|
|
33
33
|
},
|
|
34
34
|
"homepage": "https://github.com/zulaica/site-bundler#readme",
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@babel/core": "
|
|
37
|
-
"@babel/parser": "
|
|
38
|
-
"@babel/preset-env": "
|
|
39
|
-
"commander": "
|
|
40
|
-
"cssnano": "
|
|
41
|
-
"cssnano-preset-advanced": "
|
|
42
|
-
"htmlnano": "3.
|
|
43
|
-
"postcss": "8.5.
|
|
36
|
+
"@babel/core": "8.0.1",
|
|
37
|
+
"@babel/parser": "8.0.4",
|
|
38
|
+
"@babel/preset-env": "8.0.2",
|
|
39
|
+
"commander": "15.0.0",
|
|
40
|
+
"cssnano": "8.0.2",
|
|
41
|
+
"cssnano-preset-advanced": "8.0.2",
|
|
42
|
+
"htmlnano": "3.4.0",
|
|
43
|
+
"postcss": "8.5.20",
|
|
44
44
|
"postcss-import": "16.1.1",
|
|
45
|
-
"postcss-preset-env": "
|
|
46
|
-
"postcss-url": "10.1.
|
|
45
|
+
"postcss-preset-env": "11.3.2",
|
|
46
|
+
"postcss-url": "10.1.4",
|
|
47
47
|
"posthtml": "0.16.7",
|
|
48
48
|
"posthtml-hash": "1.2.2",
|
|
49
|
-
"rollup": "4.
|
|
49
|
+
"rollup": "4.62.2"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"eslint": "
|
|
52
|
+
"@eslint/js": "10.0.1",
|
|
53
|
+
"eslint": "10.7.0",
|
|
53
54
|
"eslint-config-prettier": "10.1.8",
|
|
54
|
-
"eslint-plugin-prettier": "5.5.
|
|
55
|
-
"globals": "
|
|
56
|
-
"prettier": "3.
|
|
55
|
+
"eslint-plugin-prettier": "5.5.6",
|
|
56
|
+
"globals": "17.7.0",
|
|
57
|
+
"prettier": "3.9.5"
|
|
57
58
|
},
|
|
58
59
|
"volta": {
|
|
59
|
-
"node": "24.
|
|
60
|
+
"node": "24.18.0"
|
|
61
|
+
},
|
|
62
|
+
"allowScripts": {
|
|
63
|
+
"fsevents@2.3.3": true
|
|
60
64
|
}
|
|
61
65
|
}
|