html-express-js 3.0.6 → 4.0.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 +4 -4
- package/package.json +10 -9
- package/src/index.d.ts +20 -8
- package/src/index.js +78 -56
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ The following is a high level example of how the package can be used as an Expre
|
|
|
23
23
|
Set up your Express app to use this engine:
|
|
24
24
|
|
|
25
25
|
```js
|
|
26
|
-
import htmlExpress from 'html-express-js';
|
|
26
|
+
import htmlExpress, { renderView } from 'html-express-js';
|
|
27
27
|
|
|
28
28
|
const app = express();
|
|
29
29
|
const __dirname = resolve();
|
|
@@ -47,7 +47,7 @@ app.set('views', viewsDir);
|
|
|
47
47
|
|
|
48
48
|
// render HTML in public/homepage.js with data
|
|
49
49
|
app.get('/', function (req, res, next) {
|
|
50
|
-
|
|
50
|
+
renderView('homepage', req, res, {
|
|
51
51
|
title: 'Awesome Homepage',
|
|
52
52
|
name: 'Bob',
|
|
53
53
|
});
|
|
@@ -101,7 +101,7 @@ export const view = (data, state) => html`
|
|
|
101
101
|
The following shows an example of showing a logged out state based on the cookie on a request.
|
|
102
102
|
|
|
103
103
|
```js
|
|
104
|
-
import htmlExpress from 'html-express-js';
|
|
104
|
+
import htmlExpress, { renderView } from 'html-express-js';
|
|
105
105
|
|
|
106
106
|
const app = express();
|
|
107
107
|
const __dirname = resolve();
|
|
@@ -127,7 +127,7 @@ app.set('view engine', 'js');
|
|
|
127
127
|
app.set('views', viewsDir);
|
|
128
128
|
|
|
129
129
|
app.get('/', function (req, res, next) {
|
|
130
|
-
|
|
130
|
+
renderView('homepage', req, res);
|
|
131
131
|
});
|
|
132
132
|
```
|
|
133
133
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "html-express-js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "An Express template engine to render HTML views using native JavaScript",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -39,22 +39,23 @@
|
|
|
39
39
|
},
|
|
40
40
|
"homepage": "https://github.com/markcellus/html-express-js#readme",
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"glob": "^
|
|
42
|
+
"glob": "^11.0.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@types/chai": "^
|
|
46
|
-
"@types/express": "^
|
|
45
|
+
"@types/chai": "^5.0.0",
|
|
46
|
+
"@types/express": "^5.0.0",
|
|
47
47
|
"@types/mocha": "^10.0.6",
|
|
48
|
+
"@types/reload": "^3.2.3",
|
|
48
49
|
"@types/sinon": "^17.0.3",
|
|
49
50
|
"chai": "^5.1.0",
|
|
50
|
-
"chokidar": "^
|
|
51
|
-
"express": "^
|
|
51
|
+
"chokidar": "^4.0.1",
|
|
52
|
+
"express": "^5.1.0",
|
|
52
53
|
"husky": "^9.0.7",
|
|
53
|
-
"mocha": "^
|
|
54
|
+
"mocha": "^11.0.1",
|
|
54
55
|
"prettier": "^3.0.3",
|
|
55
|
-
"release-it": "
|
|
56
|
+
"release-it": "17.1.1",
|
|
56
57
|
"reload": "^3.2.0",
|
|
57
|
-
"sinon": "^
|
|
58
|
+
"sinon": "^20.0.0",
|
|
58
59
|
"typescript": "^5.4.3"
|
|
59
60
|
}
|
|
60
61
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a JS HTML file and adds all includes to state object.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} filePath - The path to html file
|
|
5
|
+
* @param {import('express').Request} req
|
|
6
|
+
* @param {import('express').Response} res
|
|
7
|
+
* @param {Record<string, any>} [data]
|
|
8
|
+
* @returns {Promise<void>} HTML with includes available (appended to state)
|
|
9
|
+
*/
|
|
10
|
+
export function renderView(filePath: string, req: import("express").Request, res: import("express").Response, data?: Record<string, any>): Promise<void>;
|
|
1
11
|
/**
|
|
2
12
|
* Template literal that supports string
|
|
3
13
|
* interpolating in passed HTML.
|
|
@@ -19,9 +29,10 @@ export function html(strings: any, ...data: any[]): string;
|
|
|
19
29
|
*/
|
|
20
30
|
export default function _default(opts?: HTMLExpressOptions): {
|
|
21
31
|
staticIndexHandler: HTMLExpressStaticIndexHandler;
|
|
22
|
-
engine:
|
|
32
|
+
engine: Parameters<import("express").Application["engine"]>[1];
|
|
23
33
|
};
|
|
24
|
-
export type
|
|
34
|
+
export type HTMLExpressStaticIndexHandler = () => import("express").RequestHandler;
|
|
35
|
+
export type HTMLExpressBuildStateHandler = (req: import("express").Request) => Promise<Record<string, any>>;
|
|
25
36
|
export type HTMLExpressOptions = {
|
|
26
37
|
/**
|
|
27
38
|
* - The directory that houses any potential index files
|
|
@@ -31,18 +42,19 @@ export type HTMLExpressOptions = {
|
|
|
31
42
|
* - The directory that houses all of the includes
|
|
32
43
|
* that will be available on the includes property of each static page.
|
|
33
44
|
*/
|
|
34
|
-
includesDir?: string;
|
|
45
|
+
includesDir?: string | undefined;
|
|
35
46
|
/**
|
|
36
47
|
* - The path of a file relative to the views
|
|
37
48
|
* directory that should be served as 404 when no matching index page exists. Defaults to `404/index`.
|
|
38
49
|
*/
|
|
39
|
-
notFoundView?: string;
|
|
50
|
+
notFoundView?: string | undefined;
|
|
40
51
|
/**
|
|
41
52
|
* - A callback function that allows for
|
|
42
53
|
* building a state object from request information, that will be merged with default state and made available to all views
|
|
43
54
|
*/
|
|
44
|
-
buildRequestState?: HTMLExpressBuildStateHandler;
|
|
55
|
+
buildRequestState?: HTMLExpressBuildStateHandler | undefined;
|
|
56
|
+
};
|
|
57
|
+
export type HTMLExpressViewState = Record<string, string> & {
|
|
58
|
+
includes: Record<string, string>;
|
|
45
59
|
};
|
|
46
|
-
export type
|
|
47
|
-
declare function staticIndexHandler(options?: HTMLExpressOptions): import('express').RequestHandler;
|
|
48
|
-
export {};
|
|
60
|
+
export type HTMLExpressView<D extends Record<string, any> = Record<string, any>> = (data: D, state: HTMLExpressViewState) => string;
|
package/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { stat } from 'fs/promises';
|
|
|
5
5
|
/**
|
|
6
6
|
* @callback HTMLExpressBuildStateHandler
|
|
7
7
|
* @param {import('express').Request} req
|
|
8
|
-
* @returns {Record<string, any
|
|
8
|
+
* @returns {Promise<Record<string, any>>}
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -19,6 +19,40 @@ import { stat } from 'fs/promises';
|
|
|
19
19
|
* building a state object from request information, that will be merged with default state and made available to all views
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Record<string, string> & {
|
|
24
|
+
* includes: Record<string, string>
|
|
25
|
+
* }} HTMLExpressViewState
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @template {Record<string, any>} [D=Record<string, any>]
|
|
30
|
+
* @callback HTMLExpressView
|
|
31
|
+
* @param {D} data
|
|
32
|
+
* @param {HTMLExpressViewState} state
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @type {string}
|
|
38
|
+
*/
|
|
39
|
+
let includesDir = '';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @type {string}
|
|
43
|
+
*/
|
|
44
|
+
let viewsDir = '';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @type {HTMLExpressBuildStateHandler | undefined}
|
|
48
|
+
*/
|
|
49
|
+
let buildRequestState;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @type {string}
|
|
53
|
+
*/
|
|
54
|
+
let notFoundView = `404/index`;
|
|
55
|
+
|
|
22
56
|
/**
|
|
23
57
|
* Renders an HTML template in a file.
|
|
24
58
|
*
|
|
@@ -28,7 +62,7 @@ import { stat } from 'fs/promises';
|
|
|
28
62
|
* @param {object} [state] - Page-level attributes
|
|
29
63
|
* @returns {Promise<string>} HTML
|
|
30
64
|
*/
|
|
31
|
-
async function
|
|
65
|
+
async function renderFileTemplate(path, data, state) {
|
|
32
66
|
const { view } = await import(path);
|
|
33
67
|
const rendered = view(data, state);
|
|
34
68
|
let html = '';
|
|
@@ -38,31 +72,41 @@ async function renderHtmlFileTemplate(path, data, state) {
|
|
|
38
72
|
return html;
|
|
39
73
|
}
|
|
40
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Renders a JS HTML file and adds all includes to state object.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} filePath - The path to html file
|
|
79
|
+
* @param {import('express').Request} req
|
|
80
|
+
* @param {import('express').Response} res
|
|
81
|
+
* @param {Record<string, any>} [data]
|
|
82
|
+
* @returns {Promise<void>} HTML with includes available (appended to state)
|
|
83
|
+
*/
|
|
84
|
+
export async function renderView(filePath, req, res, data = {}) {
|
|
85
|
+
const requestState = buildRequestState ? await buildRequestState(req) : {};
|
|
86
|
+
const html = await buildViewHtml(filePath, data, requestState);
|
|
87
|
+
res.send(html);
|
|
88
|
+
}
|
|
89
|
+
|
|
41
90
|
/**
|
|
42
91
|
* Renders a JS HTML file and adds all includes to state object.
|
|
43
92
|
*
|
|
44
93
|
* @param {string} filePath - The path to html file
|
|
45
94
|
* @param {object} data - Data to be made available in view
|
|
46
|
-
* @param {
|
|
47
|
-
* @param {HTMLExpressOptions['includesDir']} options.includesDir
|
|
48
|
-
* @param {Record<string, any>} [options.state]
|
|
95
|
+
* @param {Record<string, any>} [customState]
|
|
49
96
|
* @returns {Promise<string>} HTML with includes available (appended to state)
|
|
50
97
|
*/
|
|
51
|
-
async function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
98
|
+
async function buildViewHtml(filePath, data = {}, customState = {}) {
|
|
99
|
+
/**
|
|
100
|
+
* @type {Record<string, any>}
|
|
101
|
+
*/
|
|
102
|
+
const state = { ...customState, includes: {} };
|
|
55
103
|
|
|
56
104
|
const includeFilePaths = await glob(`${includesDir}/*.js`);
|
|
57
105
|
for await (const includePath of includeFilePaths) {
|
|
58
106
|
const key = basename(includePath, '.js');
|
|
59
|
-
state.includes[key] = await
|
|
60
|
-
includePath,
|
|
61
|
-
data,
|
|
62
|
-
state,
|
|
63
|
-
);
|
|
107
|
+
state.includes[key] = await renderFileTemplate(includePath, data, state);
|
|
64
108
|
}
|
|
65
|
-
return await
|
|
109
|
+
return await renderFileTemplate(`${viewsDir}/${filePath}.js`, data, state);
|
|
66
110
|
}
|
|
67
111
|
|
|
68
112
|
/**
|
|
@@ -84,19 +128,16 @@ export function html(strings, ...data) {
|
|
|
84
128
|
|
|
85
129
|
/**
|
|
86
130
|
* @callback HTMLExpressStaticIndexHandler
|
|
87
|
-
* @param {HTMLExpressOptions} [options]
|
|
88
131
|
* @returns {import('express').RequestHandler}
|
|
89
132
|
*/
|
|
90
133
|
|
|
91
134
|
/**
|
|
92
135
|
* Attempts to render index.js pages when requesting to
|
|
93
|
-
* directories and fallback to 404
|
|
136
|
+
* directories and fallback to 404 view if they don't exist.
|
|
94
137
|
*
|
|
95
138
|
* @type {HTMLExpressStaticIndexHandler}
|
|
96
139
|
*/
|
|
97
|
-
function staticIndexHandler(
|
|
98
|
-
const { viewsDir, notFoundView, includesDir, buildRequestState } = options;
|
|
99
|
-
|
|
140
|
+
function staticIndexHandler() {
|
|
100
141
|
return async function (req, res, next) {
|
|
101
142
|
const { path: rawPath } = req;
|
|
102
143
|
const fileExtension = extname(rawPath);
|
|
@@ -108,31 +149,17 @@ function staticIndexHandler(options) {
|
|
|
108
149
|
? `${pathWithoutPrecedingSlash}/index`
|
|
109
150
|
: 'index';
|
|
110
151
|
|
|
111
|
-
const requestState = buildRequestState ? buildRequestState(req) : {};
|
|
112
|
-
|
|
113
|
-
const renderOptions = {
|
|
114
|
-
includesDir,
|
|
115
|
-
state: requestState,
|
|
116
|
-
};
|
|
117
152
|
res.setHeader('Content-Type', 'text/html');
|
|
118
153
|
try {
|
|
119
|
-
|
|
120
|
-
await
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
} catch (e) {
|
|
154
|
+
await stat(`${viewsDir}/${path}.js`); // check if file exists
|
|
155
|
+
await renderView(path, req, res);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
const e = /** @type {Error & {code?: string}} */ (err);
|
|
124
158
|
if (e.code !== 'ENOENT') {
|
|
125
159
|
throw e;
|
|
126
160
|
}
|
|
127
|
-
const notFoundViewPath = notFoundView || `404/index`;
|
|
128
|
-
const notFoundAbsoluteFilePath = `${viewsDir}/${notFoundViewPath}.js`;
|
|
129
|
-
const html = await renderHtmlFile(
|
|
130
|
-
notFoundAbsoluteFilePath,
|
|
131
|
-
{},
|
|
132
|
-
renderOptions,
|
|
133
|
-
);
|
|
134
161
|
res.status(404);
|
|
135
|
-
res
|
|
162
|
+
renderView(notFoundView, req, res);
|
|
136
163
|
}
|
|
137
164
|
};
|
|
138
165
|
}
|
|
@@ -147,25 +174,20 @@ function staticIndexHandler(options) {
|
|
|
147
174
|
* engine: Parameters<import('express').Application['engine']>[1],
|
|
148
175
|
* }}
|
|
149
176
|
*/
|
|
150
|
-
export default function (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
177
|
+
export default function (
|
|
178
|
+
opts = {
|
|
179
|
+
viewsDir: '',
|
|
180
|
+
},
|
|
181
|
+
) {
|
|
182
|
+
notFoundView = opts.notFoundView || notFoundView;
|
|
183
|
+
buildRequestState = opts.buildRequestState;
|
|
184
|
+
viewsDir = opts.viewsDir;
|
|
185
|
+
includesDir = opts.includesDir ? opts.includesDir : `${viewsDir}/includes`;
|
|
186
|
+
|
|
155
187
|
return {
|
|
156
|
-
staticIndexHandler
|
|
157
|
-
return staticIndexHandler({
|
|
158
|
-
includesDir,
|
|
159
|
-
viewsDir,
|
|
160
|
-
notFoundView,
|
|
161
|
-
buildRequestState,
|
|
162
|
-
...options,
|
|
163
|
-
});
|
|
164
|
-
},
|
|
188
|
+
staticIndexHandler,
|
|
165
189
|
engine: async (filePath, data, callback) => {
|
|
166
|
-
const html = await
|
|
167
|
-
includesDir,
|
|
168
|
-
});
|
|
190
|
+
const html = await buildViewHtml(filePath, data);
|
|
169
191
|
return callback(null, html);
|
|
170
192
|
},
|
|
171
193
|
};
|