cpeak 2.2.4 → 2.3.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/README.md +135 -16
- package/lib/index.js +46 -11
- package/lib/utils/index.js +2 -1
- package/lib/utils/parseJSON.js +20 -12
- package/lib/utils/render.js +64 -0
- package/package.json +3 -1
- package/playground.js +63 -0
package/README.md
CHANGED
|
@@ -8,11 +8,13 @@ This project is designed to be improved until it's ready for use in complex prod
|
|
|
8
8
|
|
|
9
9
|
This is an educational project that was started as part of the [Understanding Node.js: Core Concepts](https://www.udemy.com/course/understanding-nodejs-core-concepts/?referralCode=0BC21AC4DD6958AE6A95) course. If you want to learn how to build a framework like this, and get to a point where you can build things like this yourself, check out this course!
|
|
10
10
|
|
|
11
|
+
<em>This is the current demo, and the development of the project will begin starting from **September 2025.**</em>
|
|
12
|
+
|
|
11
13
|
## Why Cpeak?
|
|
12
14
|
|
|
13
15
|
- **Minimalism**: No unnecessary bloat, with zero dependencies. Just the core essentials you need to build fast and reliable applications.
|
|
14
16
|
- **Performance**: Engineered to be fast, **Cpeak** won’t sacrifice speed for excessive customizability.
|
|
15
|
-
- **Educational**: Every new change made in the project will be explained in great detail in
|
|
17
|
+
- **Educational**: Every new change made in the project will be explained in great detail in this [YouTube playlist](https://www.youtube.com/playlist?list=PLCiGw8i6NhvqsA-ZZcChJ0kaHZ3hcIVdY). Follow this project and let's see what it takes to build an industry-leading product!
|
|
16
18
|
- **Express.js Compatible**: You can easily refactor from Cpeak to Express.js and vice versa. Many npm packages that work with Express.js will also work with Cpeak.
|
|
17
19
|
|
|
18
20
|
## Table of Contents
|
|
@@ -24,13 +26,16 @@ This is an educational project that was started as part of the [Understanding No
|
|
|
24
26
|
- [Initializing](#initializing)
|
|
25
27
|
- [Middleware](#middleware)
|
|
26
28
|
- [Route Handling](#route-handling)
|
|
29
|
+
- [Route Middleware](#route-middleware)
|
|
27
30
|
- [URL Variables & Parameters](#url-variables--parameters)
|
|
28
31
|
- [Sending Files](#sending-files)
|
|
32
|
+
- [Redirecting](#redirecting)
|
|
29
33
|
- [Error Handling](#error-handling)
|
|
30
34
|
- [Listening](#listening)
|
|
31
35
|
- [Util Functions](#util-functions)
|
|
32
36
|
- [serveStatic](#servestatic)
|
|
33
37
|
- [parseJSON](#parsejson)
|
|
38
|
+
- [render](#render)
|
|
34
39
|
- [Complete Example](#complete-example)
|
|
35
40
|
- [Versioning Notice](#versioning-notice)
|
|
36
41
|
|
|
@@ -114,6 +119,40 @@ server.beforeEach((req, res, next) => {
|
|
|
114
119
|
});
|
|
115
120
|
```
|
|
116
121
|
|
|
122
|
+
### Route Middleware
|
|
123
|
+
|
|
124
|
+
You can also add middleware functions for a particular route handler like this:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
const requireAuth = (req, res, next, handleErr) => {
|
|
128
|
+
// Check if user is logged in, if so then:
|
|
129
|
+
req.test = "this is a test value";
|
|
130
|
+
next();
|
|
131
|
+
|
|
132
|
+
// If user is not logged in:
|
|
133
|
+
return handleErr({ status: 401, message: "Unauthorized" });
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
server.route("get", "/profile", requireAuth, (req, res, handleErr) => {
|
|
137
|
+
console.log(req.test); // this is a test value
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
You can add as many middleware functions as you want for a route:
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
server.route(
|
|
145
|
+
"get",
|
|
146
|
+
"/profile",
|
|
147
|
+
requireAuth,
|
|
148
|
+
anotherFunction,
|
|
149
|
+
oneMore,
|
|
150
|
+
(req, res, handleErr) => {
|
|
151
|
+
// your logic
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
```
|
|
155
|
+
|
|
117
156
|
### Route Handling
|
|
118
157
|
|
|
119
158
|
You can add new routes like this:
|
|
@@ -159,9 +198,17 @@ server.route("get", "/testing", (req, res) => {
|
|
|
159
198
|
|
|
160
199
|
The file’s binary content will be in the HTTP response body content. Make sure you specify a correct path relative to your CWD (use the `path` module for better compatibility) and also the correct HTTP MIME type for that file.
|
|
161
200
|
|
|
201
|
+
### Redirecting
|
|
202
|
+
|
|
203
|
+
If you want to redirect to a new URL, you can simply do:
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
res.redirect("https://whatever.com");
|
|
207
|
+
```
|
|
208
|
+
|
|
162
209
|
### Error Handling
|
|
163
210
|
|
|
164
|
-
If anywhere in your route functions you want to return an error, it's cleaner to pass it to the `handleErr` function like this:
|
|
211
|
+
If anywhere in your route functions or route middleware functions you want to return an error, it's cleaner to pass it to the `handleErr` function like this:
|
|
165
212
|
|
|
166
213
|
```javascript
|
|
167
214
|
server.route("get", "/api/document/:title", (req, res, handleErr) => {
|
|
@@ -210,6 +257,7 @@ The list of utility functions as of now:
|
|
|
210
257
|
|
|
211
258
|
- serveStatic
|
|
212
259
|
- parseJSON
|
|
260
|
+
- render
|
|
213
261
|
|
|
214
262
|
Including any one of them is done like this:
|
|
215
263
|
|
|
@@ -269,12 +317,50 @@ server.route("put", "/api/user", (req, res) => {
|
|
|
269
317
|
});
|
|
270
318
|
```
|
|
271
319
|
|
|
320
|
+
#### render
|
|
321
|
+
|
|
322
|
+
With this function you can do server side rendering before sending a file to a client. This can be useful for dynamic customization and search engine optimization.
|
|
323
|
+
|
|
324
|
+
First fire it up like this:
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
server.beforeEach(render());
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
And then for rendering:
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
server.route("get", "/", (req, res, next) => {
|
|
334
|
+
return res.render(
|
|
335
|
+
"./public/index.html",
|
|
336
|
+
{
|
|
337
|
+
title: "Page title",
|
|
338
|
+
name: "Allan",
|
|
339
|
+
},
|
|
340
|
+
"text/html"
|
|
341
|
+
);
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
You can then inject the variables into your file in {{ variable_name }} like this:
|
|
346
|
+
|
|
347
|
+
```HTML
|
|
348
|
+
<html>
|
|
349
|
+
<head>
|
|
350
|
+
<title>{{ title }}</title>
|
|
351
|
+
</head>
|
|
352
|
+
<body>
|
|
353
|
+
<h1>{{ name }}</h1>
|
|
354
|
+
</body>
|
|
355
|
+
</html>
|
|
356
|
+
```
|
|
357
|
+
|
|
272
358
|
## Complete Example
|
|
273
359
|
|
|
274
360
|
Here you can see all the features that Cpeak offers, in one small piece of code:
|
|
275
361
|
|
|
276
362
|
```javascript
|
|
277
|
-
import cpeak, { serveStatic, parseJSON } from "cpeak";
|
|
363
|
+
import cpeak, { serveStatic, parseJSON, render } from "cpeak";
|
|
278
364
|
|
|
279
365
|
const server = new cpeak();
|
|
280
366
|
|
|
@@ -284,6 +370,8 @@ server.beforeEach(
|
|
|
284
370
|
})
|
|
285
371
|
);
|
|
286
372
|
|
|
373
|
+
server.beforeEach(render());
|
|
374
|
+
|
|
287
375
|
// For parsing JSON bodies
|
|
288
376
|
server.beforeEach(parseJSON);
|
|
289
377
|
|
|
@@ -293,25 +381,56 @@ server.beforeEach((req, res, next) => {
|
|
|
293
381
|
next();
|
|
294
382
|
});
|
|
295
383
|
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const title = req.vars.title;
|
|
384
|
+
// A middleware function that can be specified to run before some particular routes
|
|
385
|
+
const testRouteMiddleware = (req, res, next, handleErr) => {
|
|
386
|
+
req.whatever = "some calculated value maybe";
|
|
300
387
|
|
|
301
|
-
|
|
302
|
-
|
|
388
|
+
if (req.vars.test !== "something special") {
|
|
389
|
+
return handleErr({ status: 400, message: "an error message" });
|
|
390
|
+
}
|
|
303
391
|
|
|
304
|
-
|
|
305
|
-
|
|
392
|
+
next();
|
|
393
|
+
};
|
|
306
394
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
395
|
+
// Adding route handlers
|
|
396
|
+
server.route("get", "/", (req, res, next) => {
|
|
397
|
+
return res.render(
|
|
398
|
+
"<path-to-file-relative-to-cwd>",
|
|
399
|
+
{
|
|
400
|
+
test: "some testing value",
|
|
401
|
+
number: "2343242",
|
|
402
|
+
},
|
|
403
|
+
"<mime-type>"
|
|
404
|
+
);
|
|
405
|
+
});
|
|
310
406
|
|
|
311
|
-
|
|
312
|
-
res.
|
|
407
|
+
server.route("get", "/old-url", testRouteMiddleware, (req, res, next) => {
|
|
408
|
+
return res.redirect("/new-url");
|
|
313
409
|
});
|
|
314
410
|
|
|
411
|
+
server.route(
|
|
412
|
+
"get",
|
|
413
|
+
"/api/document/:title",
|
|
414
|
+
testRouteMiddleware,
|
|
415
|
+
(req, res, handleErr) => {
|
|
416
|
+
// Reading URL variables
|
|
417
|
+
const title = req.vars.title;
|
|
418
|
+
|
|
419
|
+
// Reading URL parameters (like /users?filter=active)
|
|
420
|
+
const filter = req.params.filter;
|
|
421
|
+
|
|
422
|
+
// Reading JSON request body
|
|
423
|
+
const anything = req.body.anything;
|
|
424
|
+
|
|
425
|
+
// Handling errors
|
|
426
|
+
if (anything === "not-expected-thing")
|
|
427
|
+
return handleErr({ status: 400, message: "Invalid property." });
|
|
428
|
+
|
|
429
|
+
// Sending a JSON response
|
|
430
|
+
res.status(200).json({ message: "This is a test response" });
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
|
|
315
434
|
// Sending a file response
|
|
316
435
|
server.route("get", "/file", (req, res) => {
|
|
317
436
|
// Make sure to specify a correct path and MIME type...
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
|
|
4
|
-
import { serveStatic, parseJSON } from "./utils/index.js";
|
|
4
|
+
import { serveStatic, parseJSON, render } from "./utils/index.js";
|
|
5
5
|
|
|
6
6
|
class Cpeak {
|
|
7
7
|
constructor() {
|
|
@@ -27,6 +27,13 @@ class Cpeak {
|
|
|
27
27
|
return res;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
// Redirects to a new URL
|
|
31
|
+
res.redirect = (location) => {
|
|
32
|
+
res.writeHead(302, { Location: location });
|
|
33
|
+
res.end();
|
|
34
|
+
return res;
|
|
35
|
+
};
|
|
36
|
+
|
|
30
37
|
// Send a json data back to the client (for small json data, less than the highWaterMark)
|
|
31
38
|
res.json = (data) => {
|
|
32
39
|
// This is only good for bodies that their size is less than the highWaterMark value
|
|
@@ -42,9 +49,35 @@ class Cpeak {
|
|
|
42
49
|
const params = new URLSearchParams(req.url.split("?")[1]);
|
|
43
50
|
req.params = Object.fromEntries(params.entries());
|
|
44
51
|
|
|
45
|
-
// Run all the middleware functions
|
|
52
|
+
// Run all the specific middleware functions for that router only and then run the handler
|
|
53
|
+
const runHandler = (req, res, middleware, cb, index) => {
|
|
54
|
+
// Our exit point...
|
|
55
|
+
if (index === middleware.length) {
|
|
56
|
+
// Call the route handler with the modified req and res objects
|
|
57
|
+
return cb(req, res, (error) => {
|
|
58
|
+
res.setHeader("Connection", "close");
|
|
59
|
+
this.handleErr(error, req, res);
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
middleware[index](
|
|
63
|
+
req,
|
|
64
|
+
res,
|
|
65
|
+
// The next function
|
|
66
|
+
() => {
|
|
67
|
+
runHandler(req, res, middleware, cb, index + 1);
|
|
68
|
+
},
|
|
69
|
+
// Error handler for a route middleware
|
|
70
|
+
(error) => {
|
|
71
|
+
res.setHeader("Connection", "close");
|
|
72
|
+
this.handleErr(error, req, res);
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Run all the middleware functions (beforeEach functions) before we run the corresponding route
|
|
46
79
|
const runMiddleware = (req, res, middleware, index) => {
|
|
47
|
-
//
|
|
80
|
+
// Our exit point...
|
|
48
81
|
if (index === middleware.length) {
|
|
49
82
|
const routes = this.routes[req.method.toLowerCase()];
|
|
50
83
|
if (routes && typeof routes[Symbol.iterator] === "function")
|
|
@@ -56,11 +89,7 @@ class Cpeak {
|
|
|
56
89
|
const vars = this.#extractVars(route.path, match);
|
|
57
90
|
req.vars = vars;
|
|
58
91
|
|
|
59
|
-
|
|
60
|
-
return route.cb(req, res, (error) => {
|
|
61
|
-
res.setHeader("Connection", "close");
|
|
62
|
-
this.handleErr(error, req, res);
|
|
63
|
-
});
|
|
92
|
+
return runHandler(req, res, route.middleware, route.cb, 0);
|
|
64
93
|
}
|
|
65
94
|
}
|
|
66
95
|
|
|
@@ -79,11 +108,17 @@ class Cpeak {
|
|
|
79
108
|
});
|
|
80
109
|
}
|
|
81
110
|
|
|
82
|
-
route(method, path,
|
|
111
|
+
route(method, path, ...args) {
|
|
83
112
|
if (!this.routes[method]) this.routes[method] = [];
|
|
84
113
|
|
|
114
|
+
// The last argument is always our handler
|
|
115
|
+
const cb = args.pop();
|
|
116
|
+
|
|
117
|
+
// Rest will be our middleware functions
|
|
118
|
+
const middleware = args.flat();
|
|
119
|
+
|
|
85
120
|
const regex = this.#pathToRegex(path);
|
|
86
|
-
this.routes[method].push({ path, regex, cb });
|
|
121
|
+
this.routes[method].push({ path, regex, middleware, cb });
|
|
87
122
|
}
|
|
88
123
|
|
|
89
124
|
beforeEach(cb) {
|
|
@@ -132,6 +167,6 @@ class Cpeak {
|
|
|
132
167
|
}
|
|
133
168
|
}
|
|
134
169
|
|
|
135
|
-
export { serveStatic, parseJSON };
|
|
170
|
+
export { serveStatic, parseJSON, render };
|
|
136
171
|
|
|
137
172
|
export default Cpeak;
|
package/lib/utils/index.js
CHANGED
package/lib/utils/parseJSON.js
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
// Parsing JSON
|
|
2
2
|
const parseJSON = (req, res, next) => {
|
|
3
3
|
// This is only good for bodies that their size is less than the highWaterMark value
|
|
4
|
-
if (req.headers["content-type"] === "application/json") {
|
|
5
|
-
let body = "";
|
|
6
|
-
req.on("data", (chunk) => {
|
|
7
|
-
body += chunk.toString("utf-8");
|
|
8
|
-
});
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
function isJSON(contentType = "") {
|
|
6
|
+
// Remove any params like "; charset=UTF-8"
|
|
7
|
+
const [type] = contentType.split(";");
|
|
8
|
+
return (
|
|
9
|
+
type.trim().toLowerCase() === "application/json" ||
|
|
10
|
+
/\+json$/i.test(type.trim())
|
|
11
|
+
);
|
|
17
12
|
}
|
|
13
|
+
|
|
14
|
+
if (!isJSON(req.headers["content-type"])) return next();
|
|
15
|
+
|
|
16
|
+
let body = "";
|
|
17
|
+
req.on("data", (chunk) => {
|
|
18
|
+
body += chunk.toString("utf-8");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
req.on("end", () => {
|
|
22
|
+
body = JSON.parse(body);
|
|
23
|
+
req.body = body;
|
|
24
|
+
return next();
|
|
25
|
+
});
|
|
18
26
|
};
|
|
19
27
|
|
|
20
28
|
export { parseJSON };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
function renderTemplate(templateStr, data) {
|
|
4
|
+
// Initialize variables
|
|
5
|
+
let result = [];
|
|
6
|
+
|
|
7
|
+
let currentIndex = 0;
|
|
8
|
+
|
|
9
|
+
while (currentIndex < templateStr.length) {
|
|
10
|
+
// Find the next opening placeholder
|
|
11
|
+
const startIdx = templateStr.indexOf("{{", currentIndex);
|
|
12
|
+
if (startIdx === -1) {
|
|
13
|
+
// No more placeholders, push the remaining string
|
|
14
|
+
result.push(templateStr.slice(currentIndex));
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Push the part before the placeholder
|
|
19
|
+
result.push(templateStr.slice(currentIndex, startIdx));
|
|
20
|
+
|
|
21
|
+
// Find the closing placeholder
|
|
22
|
+
const endIdx = templateStr.indexOf("}}", startIdx);
|
|
23
|
+
if (endIdx === -1) {
|
|
24
|
+
// No closing brace found, treat the rest as plain text
|
|
25
|
+
result.push(templateStr.slice(startIdx));
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Extract the variable name
|
|
30
|
+
const varName = templateStr.slice(startIdx + 2, endIdx).trim();
|
|
31
|
+
|
|
32
|
+
// Replace the variable with its value from the data, or use an empty string if not found
|
|
33
|
+
const replacement = data[varName] !== undefined ? data[varName] : "";
|
|
34
|
+
|
|
35
|
+
// Push the replacement to the result array
|
|
36
|
+
result.push(replacement);
|
|
37
|
+
|
|
38
|
+
// Move the index past the current closing placeholder
|
|
39
|
+
currentIndex = endIdx + 2;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Join all parts into a final string
|
|
43
|
+
return result.join("");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Errors to return: recommend to not render files larger than 100KB
|
|
47
|
+
// To Explore: Doing the operation in C++ and return the data as stream back to the client
|
|
48
|
+
// @TODO: remove the file from static map
|
|
49
|
+
// @TODO: escape the string to prevent XSS
|
|
50
|
+
// @TODO: add another {{{ }}} option to not escape the string
|
|
51
|
+
const render = () => {
|
|
52
|
+
return function (req, res, next) {
|
|
53
|
+
res.render = async (path, data, mime) => {
|
|
54
|
+
let fileStr = await fs.readFile(path, "utf-8");
|
|
55
|
+
const finalStr = renderTemplate(fileStr, data);
|
|
56
|
+
res.setHeader("Content-Type", mime);
|
|
57
|
+
res.end(finalStr);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
next();
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export { render };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cpeak",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "A minimal and fast Node.js HTTP framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"keywords": [
|
|
21
21
|
"cpeak",
|
|
22
|
+
"backend",
|
|
23
|
+
"router",
|
|
22
24
|
"nodejs",
|
|
23
25
|
"http",
|
|
24
26
|
"framework"
|
package/playground.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
function renderTemplate(templateStr, data) {
|
|
2
|
+
// Initialize variables
|
|
3
|
+
let result = [];
|
|
4
|
+
let currentIndex = 0;
|
|
5
|
+
|
|
6
|
+
while (currentIndex < templateStr.length) {
|
|
7
|
+
// Find the next opening placeholder
|
|
8
|
+
const startIdx = templateStr.indexOf("{{", currentIndex);
|
|
9
|
+
if (startIdx === -1) {
|
|
10
|
+
// No more placeholders, push the remaining string
|
|
11
|
+
result.push(templateStr.slice(currentIndex));
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Push the part before the placeholder
|
|
16
|
+
result.push(templateStr.slice(currentIndex, startIdx));
|
|
17
|
+
|
|
18
|
+
// Find the closing placeholder
|
|
19
|
+
const endIdx = templateStr.indexOf("}}", startIdx);
|
|
20
|
+
if (endIdx === -1) {
|
|
21
|
+
// No closing brace found, treat the rest as plain text
|
|
22
|
+
result.push(templateStr.slice(startIdx));
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Extract the variable name
|
|
27
|
+
const varName = templateStr.slice(startIdx + 2, endIdx).trim();
|
|
28
|
+
|
|
29
|
+
// Replace the variable with its value from the data, or use an empty string if not found
|
|
30
|
+
const replacement = data[varName] !== undefined ? data[varName] : "";
|
|
31
|
+
|
|
32
|
+
// Push the replacement to the result array
|
|
33
|
+
result.push(replacement);
|
|
34
|
+
|
|
35
|
+
// Move the index past the current closing placeholder
|
|
36
|
+
currentIndex = endIdx + 2;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Join all parts into a final string
|
|
40
|
+
return result.join("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Example usage:
|
|
44
|
+
const templateStr = `
|
|
45
|
+
<!DOCTYPE html>
|
|
46
|
+
<html>
|
|
47
|
+
<head>
|
|
48
|
+
<title>Poster</title>
|
|
49
|
+
|
|
50
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
51
|
+
</head>
|
|
52
|
+
<body style="color: #333">
|
|
53
|
+
<h1>{{ msg1 }} {{ msg2 }}</h1>
|
|
54
|
+
|
|
55
|
+
<!-- <strong>{{msgBar}}</strong> -->
|
|
56
|
+
|
|
57
|
+
<p>
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const data = { msg1: "Alice", msg2: 30 };
|
|
61
|
+
|
|
62
|
+
const output = renderTemplate(templateStr, data);
|
|
63
|
+
console.log(output); // Output: "Hello, Alice! You are 30 years old."
|