ghtml 3.1.4 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +2 -4
- package/bench/results.json +1 -0
- package/coverage/tmp/coverage-45124-1764795335312-0.json +1 -0
- package/coverage/tmp/coverage-45125-1764795335348-0.json +1 -0
- package/package.json +6 -10
- package/bin/README.md +0 -66
- package/bin/example/assets/script.js +0 -1
- package/bin/example/assets/style.css +0 -19
- package/bin/example/package.json +0 -13
- package/bin/example/routes/index.js +0 -32
- package/bin/example/server.js +0 -21
- package/bin/src/index.js +0 -56
- package/bin/src/utils.js +0 -136
package/package.json
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
"description": "Replace your template engine with fast JavaScript by leveraging the power of tagged templates.",
|
|
4
4
|
"author": "Gürgün Dayıoğlu",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "4.0.1",
|
|
7
7
|
"type": "commonjs",
|
|
8
|
-
"bin": "./bin/src/index.js",
|
|
9
8
|
"main": "./src/index.js",
|
|
10
9
|
"exports": {
|
|
11
10
|
".": "./src/index.js",
|
|
@@ -21,15 +20,12 @@
|
|
|
21
20
|
"lint:fix": "eslint --fix . && prettier --write .",
|
|
22
21
|
"typescript": "tsc src/*.js --allowJs --declaration --emitDeclarationOnly --skipLibCheck"
|
|
23
22
|
},
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"glob": "^10.4.5"
|
|
26
|
-
},
|
|
27
23
|
"devDependencies": {
|
|
28
|
-
"@fastify/pre-commit": "^2.2.
|
|
29
|
-
"c8": "^10.1.
|
|
30
|
-
"globals": "^16.
|
|
31
|
-
"grules": "^0.26.
|
|
32
|
-
"tinybench": "^
|
|
24
|
+
"@fastify/pre-commit": "^2.2.1",
|
|
25
|
+
"c8": "^10.1.3",
|
|
26
|
+
"globals": "^16.5.0",
|
|
27
|
+
"grules": "^0.26.2",
|
|
28
|
+
"tinybench": "^6.0.0",
|
|
33
29
|
"typescript": ">=5.7.2"
|
|
34
30
|
},
|
|
35
31
|
"repository": {
|
package/bin/README.md
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
Append unique hashes to assets referenced in your views to aggressively cache them while guaranteeing that clients receive the most recent versions.
|
|
2
|
-
|
|
3
|
-
## Usage
|
|
4
|
-
|
|
5
|
-
Running the following command will scan asset files found in the `roots` path(s) and replace their references in the `refs` path(s) with hashed versions:
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
npx ghtml --roots="base/path/to/scan/assets/1/,base/path/to/scan/assets/2/" --refs="views/path/to/append/hashes/1/,views/path/to/append/hashes/2/" --prefix="/optional/prefix/"
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Example (Fastify)
|
|
12
|
-
|
|
13
|
-
Register `@fastify/static`:
|
|
14
|
-
|
|
15
|
-
```js
|
|
16
|
-
await fastify.register(import("@fastify/static"), {
|
|
17
|
-
root: new URL("assets/", import.meta.url).pathname,
|
|
18
|
-
prefix: "/p/assets/",
|
|
19
|
-
wildcard: false,
|
|
20
|
-
index: false,
|
|
21
|
-
immutable: true,
|
|
22
|
-
maxAge: process.env.NODE_ENV === "production" ? 31536000 * 1000 : 0,
|
|
23
|
-
});
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Add the `ghtml` command to the build script:
|
|
27
|
-
|
|
28
|
-
```json
|
|
29
|
-
"scripts": {
|
|
30
|
-
"build": "npx ghtml --roots=assets/ --refs=views/,routes/ --prefix=/p/assets/",
|
|
31
|
-
},
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Make sure to `npm run build` in `Dockerfile`:
|
|
35
|
-
|
|
36
|
-
```dockerfile
|
|
37
|
-
FROM node:latest
|
|
38
|
-
|
|
39
|
-
WORKDIR /app
|
|
40
|
-
|
|
41
|
-
COPY package*.json ./
|
|
42
|
-
|
|
43
|
-
RUN npm ci --include=dev
|
|
44
|
-
|
|
45
|
-
COPY . .
|
|
46
|
-
|
|
47
|
-
RUN npm run build
|
|
48
|
-
|
|
49
|
-
RUN npm prune --omit=dev
|
|
50
|
-
|
|
51
|
-
CMD ["npm", "start"]
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Demo
|
|
55
|
-
|
|
56
|
-
A full project that uses the `ghtml` executable can be found in the `example` folder:
|
|
57
|
-
|
|
58
|
-
```sh
|
|
59
|
-
cd example
|
|
60
|
-
|
|
61
|
-
npm i
|
|
62
|
-
|
|
63
|
-
npm run build
|
|
64
|
-
|
|
65
|
-
node .
|
|
66
|
-
```
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
globalThis.console.log("Hello World!");
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
body {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
|
-
align-items: center;
|
|
5
|
-
justify-content: center;
|
|
6
|
-
height: 100vh;
|
|
7
|
-
margin: 0;
|
|
8
|
-
padding: 0;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
img {
|
|
12
|
-
max-width: 100%;
|
|
13
|
-
height: auto;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.caption {
|
|
17
|
-
text-align: center;
|
|
18
|
-
margin-top: 10px;
|
|
19
|
-
}
|
package/bin/example/package.json
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"type": "module",
|
|
3
|
-
"main": "./server.js",
|
|
4
|
-
"scripts": {
|
|
5
|
-
"start": "node server.js",
|
|
6
|
-
"build": "ghtml --roots=assets/ --refs=routes/ --prefix=/p/assets/"
|
|
7
|
-
},
|
|
8
|
-
"dependencies": {
|
|
9
|
-
"@fastify/static": "^8.0.1",
|
|
10
|
-
"fastify": "^5.0.0",
|
|
11
|
-
"ghtml": "file:../../"
|
|
12
|
-
}
|
|
13
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { html } from "ghtml";
|
|
2
|
-
|
|
3
|
-
export default async (fastify) => {
|
|
4
|
-
fastify.decorateReply("html", function (inner) {
|
|
5
|
-
this.type("text/html; charset=utf-8");
|
|
6
|
-
|
|
7
|
-
return html`<!doctype html>
|
|
8
|
-
<html lang="en">
|
|
9
|
-
<head>
|
|
10
|
-
<meta charset="UTF-8" />
|
|
11
|
-
<meta
|
|
12
|
-
name="viewport"
|
|
13
|
-
content="width=device-width, initial-scale=1.0"
|
|
14
|
-
/>
|
|
15
|
-
<title>Document</title>
|
|
16
|
-
<link rel="stylesheet" href="/p/assets/style.css" />
|
|
17
|
-
<script src="/p/assets/script.js"></script>
|
|
18
|
-
</head>
|
|
19
|
-
<body>
|
|
20
|
-
!${inner}
|
|
21
|
-
</body>
|
|
22
|
-
</html>`;
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
fastify.get("/", async (request, reply) => {
|
|
26
|
-
return reply.html(html`
|
|
27
|
-
<h1 class="caption">Hello, world!</h1>
|
|
28
|
-
<p>This is a simple example of a Fastify server.</p>
|
|
29
|
-
<p>It uses <code>ghtml</code>.</p>
|
|
30
|
-
`);
|
|
31
|
-
});
|
|
32
|
-
};
|
package/bin/example/server.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import Fastify from "fastify";
|
|
2
|
-
|
|
3
|
-
const fastify = Fastify();
|
|
4
|
-
|
|
5
|
-
// Plugins
|
|
6
|
-
await fastify.register(import("@fastify/static"), {
|
|
7
|
-
root: new globalThis.URL("assets/", import.meta.url).pathname,
|
|
8
|
-
prefix: "/p/assets/",
|
|
9
|
-
wildcard: false,
|
|
10
|
-
index: false,
|
|
11
|
-
immutable: true,
|
|
12
|
-
maxAge: 31536000 * 1000,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
// Routes
|
|
16
|
-
fastify.register(import("./routes/index.js"));
|
|
17
|
-
|
|
18
|
-
// Listen
|
|
19
|
-
const address = await fastify.listen({ port: 5050 });
|
|
20
|
-
|
|
21
|
-
globalThis.console.log(`Server listening at ${address}`);
|
package/bin/src/index.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
"use strict";
|
|
4
|
-
|
|
5
|
-
const { generateHashesAndReplace } = require("./utils.js");
|
|
6
|
-
|
|
7
|
-
const parseArguments = (args) => {
|
|
8
|
-
let roots = null;
|
|
9
|
-
let refs = null;
|
|
10
|
-
let prefix = "/";
|
|
11
|
-
|
|
12
|
-
for (const arg of args) {
|
|
13
|
-
if (arg.startsWith("--roots=")) {
|
|
14
|
-
roots = arg.split("=", 2)[1].split(",");
|
|
15
|
-
} else if (arg.startsWith("--refs=")) {
|
|
16
|
-
refs = arg.split("=", 2)[1].split(",");
|
|
17
|
-
} else if (arg.startsWith("--prefix=")) {
|
|
18
|
-
prefix = arg.split("=", 2)[1];
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (!roots || !refs) {
|
|
23
|
-
globalThis.console.log(
|
|
24
|
-
'Usage: npx ghtml --roots="base/path/to/scan/assets/1/,base/path/to/scan/assets/2/" --refs="views/path/to/append/hashes/1/,views/path/to/append/hashes/2/" [--prefix="/optional/prefix/"]',
|
|
25
|
-
);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return { roots, refs, prefix };
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const main = async () => {
|
|
33
|
-
const { roots, refs, prefix } = parseArguments(process.argv.slice(2));
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
globalThis.console.log(`Generating hashes and updating file paths...`);
|
|
37
|
-
globalThis.console.log(`Scanning files in: ${roots}`);
|
|
38
|
-
globalThis.console.log(`Updating files in: ${refs}`);
|
|
39
|
-
globalThis.console.log(`Using prefix: ${prefix}`);
|
|
40
|
-
|
|
41
|
-
await generateHashesAndReplace({
|
|
42
|
-
roots,
|
|
43
|
-
refs,
|
|
44
|
-
prefix,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
globalThis.console.log(
|
|
48
|
-
"Hash generation and file updates completed successfully.",
|
|
49
|
-
);
|
|
50
|
-
} catch (error) {
|
|
51
|
-
globalThis.console.log(`Error occurred: ${error.message}`);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
main();
|
package/bin/src/utils.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const { Glob } = require("glob");
|
|
4
|
-
const { createHash } = require("node:crypto");
|
|
5
|
-
const { readFile, writeFile } = require("node:fs/promises");
|
|
6
|
-
const { win32, posix } = require("node:path");
|
|
7
|
-
|
|
8
|
-
const generateFileHash = async (filePath) => {
|
|
9
|
-
try {
|
|
10
|
-
const fileBuffer = await readFile(filePath);
|
|
11
|
-
|
|
12
|
-
return createHash("md5").update(fileBuffer).digest("hex").slice(0, 16);
|
|
13
|
-
} catch (err) {
|
|
14
|
-
if (err.code !== "ENOENT") {
|
|
15
|
-
throw err;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return "";
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const updateFilePathsWithHashes = async (
|
|
23
|
-
fileHashes,
|
|
24
|
-
refs,
|
|
25
|
-
prefix,
|
|
26
|
-
includeDotFiles,
|
|
27
|
-
skipPatterns,
|
|
28
|
-
) => {
|
|
29
|
-
for (let ref of refs) {
|
|
30
|
-
ref = ref.replaceAll(win32.sep, posix.sep);
|
|
31
|
-
|
|
32
|
-
if (!ref.endsWith("/")) {
|
|
33
|
-
ref += "/";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const filesIterable = new Glob("**/**", {
|
|
37
|
-
nodir: true,
|
|
38
|
-
follow: true,
|
|
39
|
-
absolute: true,
|
|
40
|
-
cwd: ref,
|
|
41
|
-
dot: includeDotFiles,
|
|
42
|
-
ignore: skipPatterns,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
for await (const filePath of filesIterable) {
|
|
46
|
-
let content = await readFile(filePath, "utf8");
|
|
47
|
-
let found = false;
|
|
48
|
-
|
|
49
|
-
for (const [path, hash] of fileHashes) {
|
|
50
|
-
const fullPath = prefix + path;
|
|
51
|
-
const escapedPath = fullPath.replace(
|
|
52
|
-
/[$()*+.?[\\\]^{|}]/gu,
|
|
53
|
-
String.raw`\$&`,
|
|
54
|
-
);
|
|
55
|
-
const regex = new RegExp(
|
|
56
|
-
`(?<path>${escapedPath})(\\?(?<queryString>[^#"'\`]*))?`,
|
|
57
|
-
"gu",
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
content = content.replace(
|
|
61
|
-
regex,
|
|
62
|
-
(match, p1, p2, p3, offset, string, groups) => {
|
|
63
|
-
found = true;
|
|
64
|
-
const { path, queryString } = groups;
|
|
65
|
-
|
|
66
|
-
return !queryString
|
|
67
|
-
? `${path}?hash=${hash}`
|
|
68
|
-
: queryString.includes("hash=")
|
|
69
|
-
? `${path}?${queryString.replace(/(?<hash>hash=)[\dA-Fa-f]*/u, `$1${hash}`)}`
|
|
70
|
-
: `${path}?hash=${hash}&${queryString}`;
|
|
71
|
-
},
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (found) {
|
|
76
|
-
await writeFile(filePath, content);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const generateHashesAndReplace = async ({
|
|
83
|
-
roots,
|
|
84
|
-
refs,
|
|
85
|
-
prefix,
|
|
86
|
-
includeDotFiles = false,
|
|
87
|
-
skipPatterns = ["**/node_modules/**"],
|
|
88
|
-
}) => {
|
|
89
|
-
const fileHashes = new Map();
|
|
90
|
-
|
|
91
|
-
roots = Array.isArray(roots) ? roots : [roots];
|
|
92
|
-
refs = Array.isArray(refs) ? refs : [refs];
|
|
93
|
-
|
|
94
|
-
for (let root of roots) {
|
|
95
|
-
root = root.replaceAll(win32.sep, posix.sep);
|
|
96
|
-
|
|
97
|
-
if (!root.endsWith("/")) {
|
|
98
|
-
root += "/";
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const queue = [];
|
|
102
|
-
const files = [];
|
|
103
|
-
const filesIterable = new Glob("**/**", {
|
|
104
|
-
nodir: true,
|
|
105
|
-
follow: true,
|
|
106
|
-
absolute: true,
|
|
107
|
-
cwd: root,
|
|
108
|
-
dot: includeDotFiles,
|
|
109
|
-
ignore: skipPatterns,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
for await (let filePath of filesIterable) {
|
|
113
|
-
filePath = filePath.replaceAll(win32.sep, posix.sep);
|
|
114
|
-
queue.push(generateFileHash(filePath));
|
|
115
|
-
files.push(filePath);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const hashes = await Promise.all(queue);
|
|
119
|
-
|
|
120
|
-
for (let i = 0; i < files.length; ++i) {
|
|
121
|
-
const fileRelativePath = posix.relative(root, files[i]);
|
|
122
|
-
|
|
123
|
-
fileHashes.set(fileRelativePath, hashes[i]);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
await updateFilePathsWithHashes(
|
|
128
|
-
fileHashes,
|
|
129
|
-
refs,
|
|
130
|
-
prefix,
|
|
131
|
-
includeDotFiles,
|
|
132
|
-
skipPatterns,
|
|
133
|
-
);
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
module.exports.generateHashesAndReplace = generateHashesAndReplace;
|