@ynode/squirrellyify 1.2.0 → 1.5.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/README.md +46 -24
- package/index.d.ts +21 -0
- package/package.json +9 -5
- package/src/config.js +6 -25
- package/src/plugin.js +30 -31
- package/src/resolver.js +2 -9
package/README.md
CHANGED
|
@@ -74,26 +74,49 @@ fastify.listen({ port: 3000 }, (err) => {
|
|
|
74
74
|
});
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
### Request-Scoped View Data
|
|
78
|
+
|
|
79
|
+
`reply.view(template, data)` automatically merges request-scoped values from `reply.locals` and `reply.context` into the
|
|
80
|
+
template data:
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
fastify.addHook("preHandler", async (request, reply) => {
|
|
84
|
+
reply.locals = { appName: "YNode", greeting: "Welcome" };
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
fastify.get("/", (request, reply) => {
|
|
88
|
+
// Route-level values win over locals/context on key conflicts.
|
|
89
|
+
return reply.view("index", { greeting: "Hello" });
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Merge precedence is:
|
|
94
|
+
|
|
95
|
+
1. `reply.context`
|
|
96
|
+
2. `reply.locals`
|
|
97
|
+
3. `reply.view(..., data)` (highest precedence)
|
|
98
|
+
|
|
77
99
|
## Configuration Options
|
|
78
100
|
|
|
79
101
|
You can pass an options object when registering the plugin.
|
|
80
102
|
|
|
81
|
-
| Option
|
|
82
|
-
|
|
|
83
|
-
| `templates`
|
|
84
|
-
| `partials`
|
|
85
|
-
| `partialsRecursive` | `boolean`
|
|
86
|
-
| `partialsNamespace` | `boolean \| string`
|
|
87
|
-
| `layout`
|
|
88
|
-
| `defaultExtension`
|
|
89
|
-
| `cache`
|
|
90
|
-
| `sqrl`
|
|
103
|
+
| Option | Type | Default | Description |
|
|
104
|
+
| ------------------- | -------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
105
|
+
| `templates` | `string \| string[]` | `path.join(process.cwd(), "views")` | The directory or directories to search for page and layout templates. Searched in the provided order. |
|
|
106
|
+
| `partials` | `string \| string[]` | `[]` | The directory or directories for partial templates. All partials are loaded on startup and available by name. |
|
|
107
|
+
| `partialsRecursive` | `boolean` | `true` | If `true`, partials are loaded recursively from subdirectories. Names use forward slashes (for example, `emails/header`). |
|
|
108
|
+
| `partialsNamespace` | `boolean \| string` | `false` | Optional namespace prefix for partial names. Use `true` to prefix with each partials directory basename, or provide a custom string. |
|
|
109
|
+
| `layout` | `string` | `undefined` | The name of the default layout file to use (without extension). Can be overridden per-route. |
|
|
110
|
+
| `defaultExtension` | `string` | `"sqrl"` | The file extension for all template files. Leading `.` is optional (for example, `"html"` or `".html"`). |
|
|
111
|
+
| `cache` | `boolean` | `NODE_ENV === "production"` | If `true`, compiled templates and resolved file paths will be cached in memory. |
|
|
112
|
+
| `sqrl` | `object` | `undefined` | Squirrelly options. Supports `{ scope: "global" \| "scoped", config, helpers, filters }`. |
|
|
91
113
|
|
|
92
114
|
Runtime API after registration:
|
|
93
115
|
|
|
94
116
|
- `fastify.viewHelpers.define(name, fn)`, `fastify.viewHelpers.get(name)`, `fastify.viewHelpers.remove(name)`
|
|
95
117
|
- `fastify.viewFilters.define(name, fn)`, `fastify.viewFilters.get(name)`, `fastify.viewFilters.remove(name)`
|
|
96
|
-
- `fastify.viewPartials.define(name, templateOrFn)`, `fastify.viewPartials.get(name)`,
|
|
118
|
+
- `fastify.viewPartials.define(name, templateOrFn)`, `fastify.viewPartials.get(name)`,
|
|
119
|
+
`fastify.viewPartials.remove(name)`
|
|
97
120
|
- `fastify.viewCache.clear()`, `fastify.viewCache.stats()`
|
|
98
121
|
|
|
99
122
|
These APIs are scope-aware:
|
|
@@ -101,8 +124,8 @@ These APIs are scope-aware:
|
|
|
101
124
|
- In `global` mode they modify shared helpers/filters/partials.
|
|
102
125
|
- In `scoped` mode they only affect the current plugin registration scope.
|
|
103
126
|
|
|
104
|
-
The cache API is process-local and lets you invalidate compiled template/path caches at runtime when
|
|
105
|
-
|
|
127
|
+
The cache API is process-local and lets you invalidate compiled template/path caches at runtime when `cache: true` is
|
|
128
|
+
used.
|
|
106
129
|
|
|
107
130
|
Invalid option types are rejected at plugin registration time with descriptive errors.
|
|
108
131
|
|
|
@@ -110,8 +133,8 @@ Invalid option types are rejected at plugin registration time with descriptive e
|
|
|
110
133
|
|
|
111
134
|
### Layouts
|
|
112
135
|
|
|
113
|
-
Layouts are wrappers for your page templates. The rendered page content is injected into the `body`
|
|
114
|
-
|
|
136
|
+
Layouts are wrappers for your page templates. The rendered page content is injected into the `body` variable within the
|
|
137
|
+
layout.
|
|
115
138
|
|
|
116
139
|
**`views/layouts/main.sqrl`**
|
|
117
140
|
|
|
@@ -124,8 +147,8 @@ variable within the layout.
|
|
|
124
147
|
<body>
|
|
125
148
|
<header>My Awesome Site</header>
|
|
126
149
|
<main>
|
|
127
|
-
{{@block("content")}} {{@try}} {{it.body | safe}} {{#catch => err}} Uh-oh, error!
|
|
128
|
-
|
|
150
|
+
{{@block("content")}} {{@try}} {{it.body | safe}} {{#catch => err}} Uh-oh, error! Message was
|
|
151
|
+
'{{err.message}}' {{/try}} {{/block}}
|
|
129
152
|
</main>
|
|
130
153
|
</body>
|
|
131
154
|
</html>
|
|
@@ -169,9 +192,8 @@ You can specify a layout in three ways (in order of precedence):
|
|
|
169
192
|
|
|
170
193
|
### Partials
|
|
171
194
|
|
|
172
|
-
Partials are reusable chunks of template code. Create a `partials` directory and place your files
|
|
173
|
-
|
|
174
|
-
partials directory root.
|
|
195
|
+
Partials are reusable chunks of template code. Create a `partials` directory and place your files there. By default,
|
|
196
|
+
partials are loaded recursively and registered by forward-slash path from the partials directory root.
|
|
175
197
|
|
|
176
198
|
**`partials/user-card.sqrl`**
|
|
177
199
|
|
|
@@ -236,8 +258,8 @@ fastify.register(squirrellyify, {
|
|
|
236
258
|
|
|
237
259
|
### Scoped Configuration (Encapsulation)
|
|
238
260
|
|
|
239
|
-
This plugin supports Fastify's encapsulation model. You can register it multiple times with
|
|
240
|
-
different
|
|
261
|
+
This plugin supports Fastify's encapsulation model. You can register it multiple times with different settings for
|
|
262
|
+
different route prefixes.
|
|
241
263
|
|
|
242
264
|
```javascript
|
|
243
265
|
import Fastify from "fastify";
|
|
@@ -286,8 +308,7 @@ Use `sqrl.scope` to choose registration mode:
|
|
|
286
308
|
- `global` (default): helpers, filters, and partials are shared across plugin registrations.
|
|
287
309
|
- `scoped`: helpers, filters, and partials are isolated to each plugin registration.
|
|
288
310
|
|
|
289
|
-
You can also add/remove helpers and filters at runtime via `fastify.viewHelpers` and
|
|
290
|
-
`fastify.viewFilters`.
|
|
311
|
+
You can also add/remove helpers and filters at runtime via `fastify.viewHelpers` and `fastify.viewFilters`.
|
|
291
312
|
|
|
292
313
|
```javascript
|
|
293
314
|
fastify.register(squirrellyify, {
|
|
@@ -310,3 +331,4 @@ fastify.register(squirrellyify, {
|
|
|
310
331
|
## License
|
|
311
332
|
|
|
312
333
|
This project is licensed under the [MIT License](./LICENSE).
|
|
334
|
+
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FastifyPluginAsync } from "fastify";
|
|
2
|
+
|
|
3
|
+
export interface SquirrellyifyOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Enable caching of templates.
|
|
6
|
+
* @default process.env.NODE_ENV === 'production'
|
|
7
|
+
*/
|
|
8
|
+
cache?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Path to the views directory.
|
|
11
|
+
* @default './views'
|
|
12
|
+
*/
|
|
13
|
+
views?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Squirrelly specific configuration options overriden.
|
|
16
|
+
*/
|
|
17
|
+
options?: Record<string, any>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const squirrellyify: FastifyPluginAsync<SquirrellyifyOptions>;
|
|
21
|
+
export default squirrellyify;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ynode/squirrellyify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Fastify plugin for rendering Squirrelly templates.",
|
|
5
5
|
"main": "src/plugin.js",
|
|
6
6
|
"type": "module",
|
|
@@ -21,16 +21,18 @@
|
|
|
21
21
|
"squirrelly"
|
|
22
22
|
],
|
|
23
23
|
"engines": {
|
|
24
|
-
"node": ">=
|
|
24
|
+
"node": ">=20"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@eslint/js": "^9.37.0",
|
|
28
28
|
"@eslint/json": "^0.13.2",
|
|
29
29
|
"@eslint/markdown": "^7.4.0",
|
|
30
30
|
"@mikinho/autover": "^2.0.1",
|
|
31
|
+
"auto-changelog": "^2.5.0",
|
|
31
32
|
"eslint": "^9.37.0",
|
|
32
33
|
"eslint-config-prettier": "^10.1.8",
|
|
33
34
|
"eslint-plugin-prettier": "^5.5.4",
|
|
35
|
+
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
34
36
|
"fastify": "^5.8.1",
|
|
35
37
|
"globals": "^16.4.0",
|
|
36
38
|
"jsdoc": "^4.0.5",
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
"rimraf": "^6.0.1"
|
|
39
41
|
},
|
|
40
42
|
"scripts": {
|
|
43
|
+
"changelog": "auto-changelog -p",
|
|
41
44
|
"docs": "node scripts/gen-docs.mjs",
|
|
42
45
|
"docs:clean": "rimraf docs || rmdir /s /q docs 2> NUL || true",
|
|
43
46
|
"docs:open": "node -e \"import('node:child_process').then(m=>m.exec(process.platform==='win32'?'start docs/index.html':(process.platform==='darwin'?'open docs/index.html':'xdg-open docs/index.html')))\"",
|
|
@@ -49,14 +52,15 @@
|
|
|
49
52
|
"test:staged": "node scripts/lint-staged.mjs",
|
|
50
53
|
"ver:preview": "npx autover --no-amend --dry-run --short",
|
|
51
54
|
"ver:apply": "npx autover --guard-unchanged --short",
|
|
52
|
-
"test": "
|
|
53
|
-
"prepublishOnly": "npm test",
|
|
55
|
+
"test": "node --test",
|
|
56
|
+
"prepublishOnly": "npm run lint && npm test",
|
|
54
57
|
"postversion": "git push && git push --tags"
|
|
55
58
|
},
|
|
56
59
|
"publishConfig": {
|
|
57
60
|
"access": "public"
|
|
58
61
|
},
|
|
59
62
|
"files": [
|
|
63
|
+
"index.d.ts",
|
|
60
64
|
"src",
|
|
61
65
|
"README.md",
|
|
62
66
|
"LICENSE"
|
|
@@ -65,7 +69,7 @@
|
|
|
65
69
|
"fastify-plugin": "^5.1.0"
|
|
66
70
|
},
|
|
67
71
|
"peerDependencies": {
|
|
68
|
-
"fastify": "
|
|
72
|
+
"fastify": "5.x",
|
|
69
73
|
"squirrelly": "^9.1.0"
|
|
70
74
|
}
|
|
71
75
|
}
|
package/src/config.js
CHANGED
|
@@ -19,18 +19,13 @@ function assertOptionType(condition, message) {
|
|
|
19
19
|
function normalizeDefaultExtension(defaultExtension) {
|
|
20
20
|
const normalized = defaultExtension.replace(/^\.+/, "");
|
|
21
21
|
if (normalized.length === 0) {
|
|
22
|
-
throw new TypeError(
|
|
23
|
-
'Invalid option "defaultExtension": must contain at least one non-dot character.',
|
|
24
|
-
);
|
|
22
|
+
throw new TypeError('Invalid option "defaultExtension": must contain at least one non-dot character.');
|
|
25
23
|
}
|
|
26
24
|
return normalized;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
export function validatePluginOptions(options = {}) {
|
|
30
|
-
assertOptionType(
|
|
31
|
-
isPlainObject(options),
|
|
32
|
-
"Invalid options: plugin options must be a plain object.",
|
|
33
|
-
);
|
|
28
|
+
assertOptionType(isPlainObject(options), "Invalid options: plugin options must be a plain object.");
|
|
34
29
|
|
|
35
30
|
if (options.templates !== undefined) {
|
|
36
31
|
assertOptionType(
|
|
@@ -52,8 +47,7 @@ export function validatePluginOptions(options = {}) {
|
|
|
52
47
|
}
|
|
53
48
|
if (options.partialsNamespace !== undefined) {
|
|
54
49
|
assertOptionType(
|
|
55
|
-
typeof options.partialsNamespace === "boolean" ||
|
|
56
|
-
typeof options.partialsNamespace === "string",
|
|
50
|
+
typeof options.partialsNamespace === "boolean" || typeof options.partialsNamespace === "string",
|
|
57
51
|
'Invalid option "partialsNamespace": expected a boolean or a string.',
|
|
58
52
|
);
|
|
59
53
|
}
|
|
@@ -80,10 +74,7 @@ export function validatePluginOptions(options = {}) {
|
|
|
80
74
|
);
|
|
81
75
|
}
|
|
82
76
|
if (options.sqrl.config !== undefined) {
|
|
83
|
-
assertOptionType(
|
|
84
|
-
isPlainObject(options.sqrl.config),
|
|
85
|
-
'Invalid option "sqrl.config": expected an object.',
|
|
86
|
-
);
|
|
77
|
+
assertOptionType(isPlainObject(options.sqrl.config), 'Invalid option "sqrl.config": expected an object.');
|
|
87
78
|
}
|
|
88
79
|
if (options.sqrl.helpers !== undefined) {
|
|
89
80
|
assertOptionType(
|
|
@@ -130,9 +121,7 @@ export function resolveInitialPartialsDirs(options = {}) {
|
|
|
130
121
|
|
|
131
122
|
export function resolveExtension(options = {}) {
|
|
132
123
|
const defaultExtension =
|
|
133
|
-
options.defaultExtension !== undefined
|
|
134
|
-
? normalizeDefaultExtension(options.defaultExtension)
|
|
135
|
-
: "sqrl";
|
|
124
|
+
options.defaultExtension !== undefined ? normalizeDefaultExtension(options.defaultExtension) : "sqrl";
|
|
136
125
|
return {
|
|
137
126
|
defaultExtension,
|
|
138
127
|
extensionWithDot: `.${defaultExtension}`,
|
|
@@ -153,15 +142,7 @@ function createScopedSqrlConfig(baseConfig) {
|
|
|
153
142
|
const scopedFilters = new Cacher({});
|
|
154
143
|
const scopedTemplates = new Cacher({});
|
|
155
144
|
|
|
156
|
-
for (const helperName of [
|
|
157
|
-
"each",
|
|
158
|
-
"foreach",
|
|
159
|
-
"include",
|
|
160
|
-
"extends",
|
|
161
|
-
"useScope",
|
|
162
|
-
"includeFile",
|
|
163
|
-
"extendsFile",
|
|
164
|
-
]) {
|
|
145
|
+
for (const helperName of ["each", "foreach", "include", "extends", "useScope", "includeFile", "extendsFile"]) {
|
|
165
146
|
const helperFn = Sqrl.helpers.get(helperName);
|
|
166
147
|
if (helperFn) {
|
|
167
148
|
scopedHelpers.define(helperName, helperFn);
|
package/src/plugin.js
CHANGED
|
@@ -31,19 +31,14 @@ import fp from "fastify-plugin";
|
|
|
31
31
|
import Sqrl from "squirrelly";
|
|
32
32
|
|
|
33
33
|
import {
|
|
34
|
-
validatePluginOptions,
|
|
35
34
|
resolveExtension,
|
|
36
35
|
resolveInitialPartialsDirs,
|
|
37
36
|
resolveInitialTemplateDirs,
|
|
38
37
|
resolveSqrlConfig,
|
|
39
38
|
resolveUseCache,
|
|
39
|
+
validatePluginOptions,
|
|
40
40
|
} from "./config.js";
|
|
41
|
-
import {
|
|
42
|
-
buildTemplateSearchDirs,
|
|
43
|
-
collectViewScope,
|
|
44
|
-
createTemplateResolver,
|
|
45
|
-
preloadPartials,
|
|
46
|
-
} from "./resolver.js";
|
|
41
|
+
import { buildTemplateSearchDirs, collectViewScope, createTemplateResolver, preloadPartials } from "./resolver.js";
|
|
47
42
|
import { createRuntimeApi } from "./runtime-api.js";
|
|
48
43
|
import { assertSafeName } from "./safety.js";
|
|
49
44
|
|
|
@@ -75,20 +70,20 @@ import { assertSafeName } from "./safety.js";
|
|
|
75
70
|
async function squirrellyify(fastify, options = {}) {
|
|
76
71
|
validatePluginOptions(options);
|
|
77
72
|
|
|
73
|
+
if (typeof fastify.hasDecorator === "function" && fastify.hasDecorator("Sqrl")) {
|
|
74
|
+
throw new Error("@ynode/squirrellyify has already been registered");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const log =
|
|
78
|
+
typeof fastify.log?.child === "function" ? fastify.log.child({ name: "@ynode/squirrellyify" }) : fastify.log;
|
|
79
|
+
|
|
78
80
|
const initialTemplatesDirs = resolveInitialTemplateDirs(options);
|
|
79
81
|
const initialPartialsDirs = resolveInitialPartialsDirs(options);
|
|
80
82
|
const initialLayout = options.layout;
|
|
81
83
|
const { extensionWithDot } = resolveExtension(options);
|
|
82
84
|
const useCache = resolveUseCache(options);
|
|
83
85
|
const { sqrlScope, sqrlConfig } = resolveSqrlConfig(options);
|
|
84
|
-
const {
|
|
85
|
-
defineSqrlHelper,
|
|
86
|
-
defineSqrlFilter,
|
|
87
|
-
defineSqrlTemplate,
|
|
88
|
-
viewHelpers,
|
|
89
|
-
viewFilters,
|
|
90
|
-
viewPartials,
|
|
91
|
-
} =
|
|
86
|
+
const { defineSqrlHelper, defineSqrlFilter, defineSqrlTemplate, viewHelpers, viewFilters, viewPartials } =
|
|
92
87
|
createRuntimeApi({
|
|
93
88
|
sqrlScope,
|
|
94
89
|
sqrlConfig,
|
|
@@ -130,32 +125,36 @@ async function squirrellyify(fastify, options = {}) {
|
|
|
130
125
|
*/
|
|
131
126
|
async function view(template, data = {}) {
|
|
132
127
|
try {
|
|
128
|
+
const requestData = data && typeof data === "object" ? data : {};
|
|
129
|
+
const replyContext = this.context && typeof this.context === "object" ? this.context : {};
|
|
130
|
+
const replyLocals = this.locals && typeof this.locals === "object" ? this.locals : {};
|
|
131
|
+
const mergedData = {
|
|
132
|
+
...replyContext,
|
|
133
|
+
...replyLocals,
|
|
134
|
+
...requestData,
|
|
135
|
+
};
|
|
136
|
+
|
|
133
137
|
assertSafeName(template);
|
|
134
|
-
if (
|
|
135
|
-
assertSafeName(
|
|
138
|
+
if (mergedData.layout && mergedData.layout !== false) {
|
|
139
|
+
assertSafeName(mergedData.layout);
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
const instance = this.request.server;
|
|
139
143
|
const { aggregatedTemplatesDirs, scopedLayout } = collectViewScope(instance);
|
|
140
|
-
const templateSearchDirs = buildTemplateSearchDirs(
|
|
141
|
-
aggregatedTemplatesDirs,
|
|
142
|
-
initialTemplatesDirs,
|
|
143
|
-
);
|
|
144
|
+
const templateSearchDirs = buildTemplateSearchDirs(aggregatedTemplatesDirs, initialTemplatesDirs);
|
|
144
145
|
|
|
145
146
|
// 1. Find and render the page template
|
|
146
147
|
const pagePath = await findTemplatePath(template, templateSearchDirs);
|
|
147
148
|
if (!pagePath) {
|
|
148
|
-
throw new Error(
|
|
149
|
-
`Template "${template}" not found in [${templateSearchDirs.join(", ")}]`,
|
|
150
|
-
);
|
|
149
|
+
throw new Error(`Template "${template}" not found in [${templateSearchDirs.join(", ")}]`);
|
|
151
150
|
}
|
|
152
151
|
|
|
153
152
|
const pageTemplate = await getTemplate(pagePath);
|
|
154
|
-
const pageHtml = await pageTemplate(
|
|
153
|
+
const pageHtml = await pageTemplate(mergedData, sqrlConfig);
|
|
155
154
|
|
|
156
155
|
// 2. Determine which layout to use
|
|
157
156
|
const currentLayout = scopedLayout !== null ? scopedLayout : initialLayout;
|
|
158
|
-
const layoutFile =
|
|
157
|
+
const layoutFile = mergedData.layout === false ? null : mergedData.layout || currentLayout;
|
|
159
158
|
|
|
160
159
|
if (!layoutFile) {
|
|
161
160
|
return this.type("text/html").send(pageHtml);
|
|
@@ -168,18 +167,18 @@ async function squirrellyify(fastify, options = {}) {
|
|
|
168
167
|
// 3. Find and render the layout, injecting the page content
|
|
169
168
|
const layoutPath = await findTemplatePath(layoutFile, templateSearchDirs);
|
|
170
169
|
if (!layoutPath) {
|
|
171
|
-
throw new Error(
|
|
172
|
-
`Layout "${layoutFile}" not found in [${templateSearchDirs.join(", ")}]`,
|
|
173
|
-
);
|
|
170
|
+
throw new Error(`Layout "${layoutFile}" not found in [${templateSearchDirs.join(", ")}]`);
|
|
174
171
|
}
|
|
175
172
|
|
|
176
173
|
const layoutTemplate = await getTemplate(layoutPath);
|
|
177
|
-
const
|
|
174
|
+
const layoutPayload =
|
|
175
|
+
mergedData.layoutData && typeof mergedData.layoutData === "object" ? mergedData.layoutData : {};
|
|
176
|
+
const layoutData = { ...mergedData, ...layoutPayload, body: pageHtml };
|
|
178
177
|
const finalHtml = await layoutTemplate(layoutData, sqrlConfig);
|
|
179
178
|
|
|
180
179
|
return this.type("text/html").send(finalHtml);
|
|
181
180
|
} catch (error) {
|
|
182
|
-
|
|
181
|
+
log.error(error);
|
|
183
182
|
if (process.env.NODE_ENV === "production") {
|
|
184
183
|
// In production, send a generic error and don't leak details
|
|
185
184
|
this.status(500).send("An internal server error occurred.");
|
package/src/resolver.js
CHANGED
|
@@ -79,12 +79,7 @@ export async function preloadPartials({
|
|
|
79
79
|
const files = await collectPartialFiles(partialsDir, extensionWithDot, partialsRecursive);
|
|
80
80
|
await Promise.all(
|
|
81
81
|
files.map(async (partialPath) => {
|
|
82
|
-
const partialName = resolvePartialName(
|
|
83
|
-
partialPath,
|
|
84
|
-
partialsDir,
|
|
85
|
-
extensionWithDot,
|
|
86
|
-
namespace,
|
|
87
|
-
);
|
|
82
|
+
const partialName = resolvePartialName(partialPath, partialsDir, extensionWithDot, namespace);
|
|
88
83
|
const content = await fs.readFile(partialPath, "utf-8");
|
|
89
84
|
fastify.log.trace(`Loaded partial: ${partialName}`);
|
|
90
85
|
defineSqrlTemplate(partialName, Sqrl.compile(content, sqrlConfig));
|
|
@@ -110,9 +105,7 @@ export function collectViewScope(instance) {
|
|
|
110
105
|
|
|
111
106
|
while (currentInstance) {
|
|
112
107
|
if (currentInstance.views) {
|
|
113
|
-
const dirs = Array.isArray(currentInstance.views)
|
|
114
|
-
? currentInstance.views
|
|
115
|
-
: [currentInstance.views];
|
|
108
|
+
const dirs = Array.isArray(currentInstance.views) ? currentInstance.views : [currentInstance.views];
|
|
116
109
|
aggregatedTemplatesDirs.push(...dirs);
|
|
117
110
|
}
|
|
118
111
|
if (scopedLayout === null && currentInstance.layout !== null && currentInstance.layout !== undefined) {
|