html-express-js 3.0.6 → 3.1.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 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
- res.render('homepage', {
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
- res.render('homepage');
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.0.6",
3
+ "version": "3.1.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",
@@ -45,6 +45,7 @@
45
45
  "@types/chai": "^4.3.13",
46
46
  "@types/express": "^4.17.21",
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
51
  "chokidar": "^3.5.3",
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> | undefined): Promise<void>;
1
11
  /**
2
12
  * Template literal that supports string
3
13
  * interpolating in passed HTML.
@@ -17,10 +27,11 @@ export function html(strings: any, ...data: any[]): string;
17
27
  * engine: Parameters<import('express').Application['engine']>[1],
18
28
  * }}
19
29
  */
20
- export default function _default(opts?: HTMLExpressOptions): {
30
+ export default function _default(opts?: HTMLExpressOptions | undefined): {
21
31
  staticIndexHandler: HTMLExpressStaticIndexHandler;
22
- engine: [ext: string, fn: (path: string, options: object, callback: (e: any, rendered?: string) => void) => void][1];
32
+ engine: [ext: string, fn: (path: string, options: object, callback: (e: any, rendered?: string | undefined) => void) => void][1];
23
33
  };
34
+ export type HTMLExpressStaticIndexHandler = () => import('express').RequestHandler;
24
35
  export type HTMLExpressBuildStateHandler = (req: import('express').Request) => Record<string, any>;
25
36
  export type HTMLExpressOptions = {
26
37
  /**
@@ -31,18 +42,21 @@ 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 HTMLExpressStaticIndexHandler = (options?: HTMLExpressOptions) => import('express').RequestHandler;
47
- declare function staticIndexHandler(options?: HTMLExpressOptions): import('express').RequestHandler;
60
+ export type HTMLExpressView<D extends Record<string, any> = Record<string, any>> = (data: D, state: HTMLExpressViewState) => string;
61
+ declare function staticIndexHandler(): import('express').RequestHandler;
48
62
  export {};
package/src/index.js CHANGED
@@ -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 renderHtmlFileTemplate(path, data, state) {
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 ? 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 {object} options - Options passed to original instantiation
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 renderHtmlFile(filePath, data = {}, options) {
52
- const { includesDir } = options || {};
53
- const state = options.state || {};
54
- state.includes = {};
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 renderHtmlFileTemplate(
60
- includePath,
61
- data,
62
- state,
63
- );
107
+ state.includes[key] = await renderFileTemplate(includePath, data, state);
64
108
  }
65
- return await renderHtmlFileTemplate(filePath, data, state);
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/index.js if doesnt exist.
136
+ * directories and fallback to 404 view if they don't exist.
94
137
  *
95
138
  * @type {HTMLExpressStaticIndexHandler}
96
139
  */
97
- function staticIndexHandler(options) {
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
- const absoluteFilePath = `${viewsDir}/${path}.js`;
120
- await stat(absoluteFilePath); // check if file exists
121
- const html = await renderHtmlFile(absoluteFilePath, {}, renderOptions);
122
- res.send(html);
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.send(html);
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 (opts) {
151
- const { buildRequestState, notFoundView, viewsDir } = opts;
152
- const includesDir = opts.includesDir
153
- ? opts.includesDir
154
- : `${viewsDir}/includes`;
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: (options) => {
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 renderHtmlFile(filePath, data, {
167
- includesDir,
168
- });
190
+ const html = await buildViewHtml(filePath, data);
169
191
  return callback(null, html);
170
192
  },
171
193
  };