clovie 0.1.38 → 0.1.39
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/lib/createClovie.js +0 -2
- package/lib/factories/api.js +1 -0
- package/lib/factories/hooks.js +1 -0
- package/lib/factories/middleware.js +1 -0
- package/lib/factories/routes.js +1 -0
- package/lib/main.js +5 -0
- package/lib/services/Run.js +21 -5
- package/lib/utils/normalizeToFactories.js +52 -0
- package/lib/utils/transformConfig.js +1 -35
- package/lib/utils/viewsToRoutes.js +109 -0
- package/package.json +5 -4
- package/lib/services/Router.js +0 -189
package/lib/createClovie.js
CHANGED
|
@@ -3,7 +3,6 @@ import { Engine } from '@jucie.io/engine';
|
|
|
3
3
|
import { Compile } from './services/Compile.js';
|
|
4
4
|
import { Run } from './services/Run.js';
|
|
5
5
|
import { Configurator } from './services/Configurator.js';
|
|
6
|
-
import { Route } from './services/Router.js';
|
|
7
6
|
import { transformConfig } from './utils/transformConfig.js';
|
|
8
7
|
import { Server } from '@jucie.io/engine-server';
|
|
9
8
|
import { EsBuildCompiler } from '@jucie.io/engine-esbuild';
|
|
@@ -22,7 +21,6 @@ export const createClovie = async (config = {}) => {
|
|
|
22
21
|
clovie.configurator.onReady(async (opts) => {
|
|
23
22
|
// Always install core services
|
|
24
23
|
clovie.install(Compile)
|
|
25
|
-
clovie.install(Route)
|
|
26
24
|
clovie.install(Run)
|
|
27
25
|
|
|
28
26
|
if (opts.scripts) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{ defineRoutes as defineApi } from '@jucie.io/engine-server';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defineHooks } from '@jucie.io/engine-server';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defineMiddleware } from '@jucie.io/engine-server';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { defineRoutes } from '@jucie.io/engine-server';
|
package/lib/main.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { defineApi } from './factories/api.js';
|
|
2
|
+
export { defineRoutes } from './factories/routes.js';
|
|
3
|
+
export { defineHooks } from './factories/hooks.js';
|
|
4
|
+
export { defineMiddleware } from './factories/middleware.js';
|
|
5
|
+
export { createClovie } from './createClovie.js';
|
package/lib/services/Run.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ServiceProvider } from '@jucie.io/engine';
|
|
2
|
-
import
|
|
2
|
+
import { normalizeToFactories } from '../utils/normalizeToFactories.js';
|
|
3
|
+
import { viewsToRoutes, pagesToRoutes } from '../utils/viewsToRoutes.js';
|
|
4
|
+
import { defineRoutes } from '../factories/routes.js';
|
|
5
|
+
import { defineHooks } from '../factories/hooks.js';
|
|
6
|
+
import { defineMiddleware } from '../factories/middleware.js';
|
|
3
7
|
|
|
4
8
|
export class Run extends ServiceProvider {
|
|
5
9
|
static manifest = {
|
|
@@ -112,10 +116,22 @@ export class Run extends ServiceProvider {
|
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
async #serve (opts) {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
const [server, file, liveReload] = this.useContext('server', 'file', 'liveReload');
|
|
120
|
+
const services = { file, liveReload };
|
|
121
|
+
|
|
122
|
+
const viewRoutes = viewsToRoutes(opts, services);
|
|
123
|
+
const pageRoutes = pagesToRoutes(opts.routes, opts, services);
|
|
124
|
+
|
|
125
|
+
const factories = [
|
|
126
|
+
...normalizeToFactories(opts.hooks, defineHooks),
|
|
127
|
+
...normalizeToFactories(opts.middleware, defineMiddleware),
|
|
128
|
+
...normalizeToFactories(opts.api, defineRoutes),
|
|
129
|
+
...normalizeToFactories([...viewRoutes, ...pageRoutes], defineRoutes),
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
if (factories.length) {
|
|
133
|
+
server.use(...factories);
|
|
134
|
+
}
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
async #watch(opts) {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { definitionType } from '@jucie.io/engine';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes a config value into an array of engine-server factory definitions.
|
|
5
|
+
*
|
|
6
|
+
* Handles four input shapes:
|
|
7
|
+
* - Raw data (plain array or object) → wrapped in factoryFn
|
|
8
|
+
* - Single factory (created via createDefinition) → returned as-is
|
|
9
|
+
* - Array of factories → returned as-is
|
|
10
|
+
* - Intermixed (raw items + factories in the same array) → raw items
|
|
11
|
+
* are batched and wrapped, factories are preserved
|
|
12
|
+
*
|
|
13
|
+
* @param {*} value - The config value (routes, middleware, hooks, api, etc.)
|
|
14
|
+
* @param {Function} factoryFn - The define* factory to wrap raw data with
|
|
15
|
+
* @returns {Array} Array of factory definitions ready for server.use()
|
|
16
|
+
*/
|
|
17
|
+
export function normalizeToFactories(value, factoryFn) {
|
|
18
|
+
if (!value) return [];
|
|
19
|
+
|
|
20
|
+
if (isFactory(value)) return [value];
|
|
21
|
+
|
|
22
|
+
if (!Array.isArray(value)) return [wrapRaw(factoryFn, value)];
|
|
23
|
+
|
|
24
|
+
const raw = [];
|
|
25
|
+
const factories = [];
|
|
26
|
+
|
|
27
|
+
for (const item of value) {
|
|
28
|
+
if (isFactory(item)) {
|
|
29
|
+
if (raw.length) {
|
|
30
|
+
factories.push(wrapRaw(factoryFn, [...raw]));
|
|
31
|
+
raw.length = 0;
|
|
32
|
+
}
|
|
33
|
+
factories.push(item);
|
|
34
|
+
} else {
|
|
35
|
+
raw.push(item);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (raw.length) {
|
|
40
|
+
factories.push(wrapRaw(factoryFn, raw));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return factories;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isFactory(value) {
|
|
47
|
+
return typeof value === 'function' && definitionType(value) !== undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function wrapRaw(factoryFn, data) {
|
|
51
|
+
return factoryFn(() => data);
|
|
52
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import { loadRenderEngine
|
|
3
|
+
import { loadRenderEngine } from './loadRenderEngine.js';
|
|
4
4
|
|
|
5
5
|
// Add this near the top of the file, after the imports
|
|
6
6
|
const DATA_TYPES = {
|
|
@@ -195,40 +195,6 @@ export async function transformConfig(config, log = null) {
|
|
|
195
195
|
if (discovered.data) {
|
|
196
196
|
discovered.data = await resolveData(discovered.data);
|
|
197
197
|
}
|
|
198
|
-
|
|
199
|
-
// Middleware processing and adapter selection
|
|
200
|
-
if (!discovered.adapter) {
|
|
201
|
-
// Auto-detect adapter based on middleware presence
|
|
202
|
-
if (discovered.middleware && discovered.middleware.length > 0) {
|
|
203
|
-
discovered.adapter = 'express'; // Use Express for middleware
|
|
204
|
-
logger.debug('Auto-selected Express adapter due to middleware configuration');
|
|
205
|
-
} else {
|
|
206
|
-
discovered.adapter = 'http'; // Default to HTTP adapter
|
|
207
|
-
logger.debug('Using default HTTP adapter');
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Validate middleware if provided
|
|
212
|
-
if (discovered.middleware) {
|
|
213
|
-
if (!Array.isArray(discovered.middleware)) {
|
|
214
|
-
throw new Error('middleware must be an array of functions');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Validate each middleware function
|
|
218
|
-
discovered.middleware.forEach((mw, index) => {
|
|
219
|
-
if (typeof mw !== 'function') {
|
|
220
|
-
throw new Error(`middleware[${index}] must be a function, got ${typeof mw}`);
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
logger.debug(`Configured ${discovered.middleware.length} middleware functions`);
|
|
225
|
-
|
|
226
|
-
// Ensure Express adapter is used when middleware is present
|
|
227
|
-
if (discovered.adapter !== 'express') {
|
|
228
|
-
logger.warn('Middleware detected but adapter is not "express". Switching to Express adapter.');
|
|
229
|
-
discovered.adapter = 'express';
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
198
|
|
|
233
199
|
return discovered;
|
|
234
200
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scans the views directory and produces engine-server route definitions.
|
|
5
|
+
* Each template file becomes a GET route whose handler renders the template.
|
|
6
|
+
*
|
|
7
|
+
* views/index.html → GET /
|
|
8
|
+
* views/about.html → GET /about
|
|
9
|
+
* views/blog/post.html → GET /blog/post
|
|
10
|
+
*
|
|
11
|
+
* @param {object} opts - Clovie config opts
|
|
12
|
+
* @param {object} services - Injected services { file, liveReload }
|
|
13
|
+
* @returns {Array} Array of engine-server route objects ({ method, path, handler })
|
|
14
|
+
*/
|
|
15
|
+
export function viewsToRoutes(opts, services) {
|
|
16
|
+
if (!opts.views) return [];
|
|
17
|
+
|
|
18
|
+
const filePaths = services.file.getFilePaths(opts.views);
|
|
19
|
+
if (!filePaths.length) return [];
|
|
20
|
+
|
|
21
|
+
return filePaths.map(filePath => ({
|
|
22
|
+
method: 'GET',
|
|
23
|
+
path: filePathToRoutePath(filePath, opts.views),
|
|
24
|
+
handler: createTemplateHandler(filePath, opts, services),
|
|
25
|
+
meta: { source: 'views', template: filePath },
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Wraps an array of Clovie page configs ({ path, template, data }) into
|
|
31
|
+
* engine-server route objects ({ method, path, handler }).
|
|
32
|
+
*
|
|
33
|
+
* @param {Array} pages - Clovie page/route configs
|
|
34
|
+
* @param {object} opts - Clovie config opts
|
|
35
|
+
* @param {object} services - Injected services { file, liveReload }
|
|
36
|
+
* @returns {Array} Engine-server route objects
|
|
37
|
+
*/
|
|
38
|
+
export function pagesToRoutes(pages, opts, services) {
|
|
39
|
+
if (!pages || !pages.length) return [];
|
|
40
|
+
|
|
41
|
+
return pages.map(page => ({
|
|
42
|
+
method: 'GET',
|
|
43
|
+
path: page.path,
|
|
44
|
+
handler: createTemplateHandler(
|
|
45
|
+
page.template,
|
|
46
|
+
opts,
|
|
47
|
+
services,
|
|
48
|
+
page.data ? (ctx) => page.data(ctx) : null,
|
|
49
|
+
),
|
|
50
|
+
meta: page.meta || { source: 'routes', template: page.template },
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates a route handler that renders a template and returns HTML.
|
|
56
|
+
* Shared by both views-to-routes and explicit page route definitions.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} templatePath - Path to the template file
|
|
59
|
+
* @param {object} opts - Clovie config opts (renderEngine, data, mode)
|
|
60
|
+
* @param {object} services - { file, liveReload }
|
|
61
|
+
* @param {Function|null} dataFn - Optional per-request data function (ctx) => data
|
|
62
|
+
*/
|
|
63
|
+
export function createTemplateHandler(templatePath, opts, services, dataFn) {
|
|
64
|
+
return async (ctx) => {
|
|
65
|
+
const template = services.file.read(templatePath);
|
|
66
|
+
if (!template) {
|
|
67
|
+
return ctx.respond.text('Not Found', 404);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const globalData = opts.data || {};
|
|
71
|
+
const routeData = dataFn ? await dataFn(ctx) : {};
|
|
72
|
+
const mergedData = { ...globalData, ...routeData };
|
|
73
|
+
|
|
74
|
+
let html = await opts.renderEngine.render(template, mergedData);
|
|
75
|
+
|
|
76
|
+
if (services.liveReload && opts.mode === 'development') {
|
|
77
|
+
html = await services.liveReload.injectLiveReloadScript(html, opts);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return ctx.respond.html(html);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Converts a template file path to a URL route path.
|
|
86
|
+
* views/index.html → /
|
|
87
|
+
* views/about.html → /about
|
|
88
|
+
* views/blog/post.njk → /blog/post
|
|
89
|
+
* views/blog/index.html → /blog
|
|
90
|
+
*/
|
|
91
|
+
function filePathToRoutePath(filePath, viewsDir) {
|
|
92
|
+
const absoluteViewsDir = toAbsolutePath(viewsDir);
|
|
93
|
+
const absoluteFilePath = toAbsolutePath(filePath);
|
|
94
|
+
const relativePath = path.relative(absoluteViewsDir, absoluteFilePath);
|
|
95
|
+
const withoutExt = relativePath.replace(path.extname(relativePath), '');
|
|
96
|
+
|
|
97
|
+
if (withoutExt === 'index') return '/';
|
|
98
|
+
|
|
99
|
+
const segments = withoutExt.split(path.sep);
|
|
100
|
+
if (segments[segments.length - 1] === 'index') {
|
|
101
|
+
segments.pop();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return '/' + segments.join('/');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function toAbsolutePath(inputPath) {
|
|
108
|
+
return path.isAbsolute(inputPath) ? inputPath : path.resolve(process.cwd(), inputPath);
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clovie",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.39",
|
|
4
4
|
"description": "Vintage web dev tooling with modern quality of life",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"static-site-generator",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"type": "module",
|
|
36
36
|
"exports": {
|
|
37
37
|
".": {
|
|
38
|
-
"import": "./lib/
|
|
38
|
+
"import": "./lib/main.js",
|
|
39
|
+
"require": "./lib/main.js"
|
|
39
40
|
},
|
|
40
41
|
"./server": "./lib/Server/main.js",
|
|
41
42
|
"./cli": "./bin/cli.js"
|
|
@@ -75,9 +76,9 @@
|
|
|
75
76
|
"publish:info": "node scripts/publish.js info"
|
|
76
77
|
},
|
|
77
78
|
"dependencies": {
|
|
78
|
-
"@jucie.io/engine": "^1.1.
|
|
79
|
+
"@jucie.io/engine": "^1.1.68",
|
|
79
80
|
"@jucie.io/engine-esbuild": "^1.0.2",
|
|
80
|
-
"@jucie.io/engine-server": "^1.0.
|
|
81
|
+
"@jucie.io/engine-server": "^1.0.23",
|
|
81
82
|
"@jucie.io/reactive": "^1.0.26",
|
|
82
83
|
"chalk": "^5.6.2",
|
|
83
84
|
"chokidar": "^3.5.3",
|
package/lib/services/Router.js
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { ServiceProvider } from '@jucie.io/engine';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
export class Route extends ServiceProvider {
|
|
5
|
-
static manifest = {
|
|
6
|
-
name: 'Clovie Route',
|
|
7
|
-
namespace: 'router',
|
|
8
|
-
version: '1.0.0',
|
|
9
|
-
}
|
|
10
|
-
#staticCache = new Set();
|
|
11
|
-
#cache = new Map();
|
|
12
|
-
#cacheSize = 100;
|
|
13
|
-
|
|
14
|
-
initialize(useContext, config) {
|
|
15
|
-
this.#cacheSize = config.cacheSize || 100;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
actions(useContext) {
|
|
19
|
-
return {
|
|
20
|
-
setHooks: (opts) => {
|
|
21
|
-
const server = this.useContext('server');
|
|
22
|
-
if (!opts) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if (opts.hooks) {
|
|
26
|
-
server.hooks(opts.hooks);
|
|
27
|
-
}
|
|
28
|
-
return this;
|
|
29
|
-
},
|
|
30
|
-
clearStaticCache: () => {
|
|
31
|
-
this.#staticCache.forEach(outputPath => {
|
|
32
|
-
file.delete(outputPath);
|
|
33
|
-
});
|
|
34
|
-
this.#staticCache.clear();
|
|
35
|
-
},
|
|
36
|
-
serveRoutes: async (opts) => {
|
|
37
|
-
const server = this.useContext('server');
|
|
38
|
-
if (!opts.routes) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
for (const route of opts.routes) {
|
|
42
|
-
server.add('GET', route.path, async (context) => {
|
|
43
|
-
try {
|
|
44
|
-
const content = await this.#routeHandler(route, context, opts);
|
|
45
|
-
return context.respond.html(content);
|
|
46
|
-
} catch (error) {
|
|
47
|
-
console.error('Error rendering route', error);
|
|
48
|
-
return context.respond.text('Internal Server Error', 500);
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
route.meta
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
serveApi: (opts) => {
|
|
56
|
-
const server = this.useContext('server');
|
|
57
|
-
if (!opts.api) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
for (const api of opts.api) {
|
|
61
|
-
server.add(api.method, api.path, (context) => {
|
|
62
|
-
try {
|
|
63
|
-
return this.#apiHandler(api, context, opts);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error('Error rendering route', error);
|
|
66
|
-
return context.respond.text('Internal Server Error', 500);
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
api.meta
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
#apiHandler(api, context) {
|
|
78
|
-
return api.handler(context);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async #routeHandler(route, context, opts) {
|
|
82
|
-
const [file, liveReload] = this.useContext('file', 'liveReload');
|
|
83
|
-
|
|
84
|
-
// Create unique cache key from request
|
|
85
|
-
const cacheKey = this.#instancePath(context.req);
|
|
86
|
-
|
|
87
|
-
if (this.#cache.has(cacheKey)) {
|
|
88
|
-
return await this.#cache.get(cacheKey).compile();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
const instance = {
|
|
93
|
-
watcherCleanup: liveReload && opts.mode === 'development' ? file.watch(route.template, async () => {
|
|
94
|
-
instance.dirty = true;
|
|
95
|
-
if (liveReload) {
|
|
96
|
-
liveReload.notifyReload();
|
|
97
|
-
}
|
|
98
|
-
}) : null,
|
|
99
|
-
route,
|
|
100
|
-
outputPath: this.#formatOutputPath(opts.outputDir, context.req.path),
|
|
101
|
-
dirty: true,
|
|
102
|
-
data: async () => route.data ? await route.data(context) : {},
|
|
103
|
-
compile: async () => {
|
|
104
|
-
try {
|
|
105
|
-
if (!file.exists(instance.route.template)) {
|
|
106
|
-
throw new Error(`Template not found: ${instance.route.template}`);
|
|
107
|
-
}
|
|
108
|
-
instance.lastAccess = Date.now();
|
|
109
|
-
instance.expires = Date.now() + 1000 * 60 * 60 * 24 * 30;
|
|
110
|
-
const template = file.read(instance.route.template);
|
|
111
|
-
let renderedContent = await opts.renderEngine.render(template, {...(opts.data || {}), ...(await instance.data() || {})})
|
|
112
|
-
if (liveReload && opts.mode === 'development') {
|
|
113
|
-
renderedContent = await liveReload.injectLiveReloadScript(renderedContent, opts);
|
|
114
|
-
}
|
|
115
|
-
file.write(instance.outputPath, renderedContent);
|
|
116
|
-
instance.dirty = false;
|
|
117
|
-
return renderedContent;
|
|
118
|
-
// return file.read(instance.outputPath);
|
|
119
|
-
} catch (error) {
|
|
120
|
-
console.error('Error compiling route', error);
|
|
121
|
-
} finally {
|
|
122
|
-
instance.dirty = false;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
this.#addInstanceCache(cacheKey, instance);
|
|
127
|
-
return instance.compile();
|
|
128
|
-
} catch (error) {
|
|
129
|
-
console.error('Error rendering route', error);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
#addInstanceCache(path, instance) {
|
|
134
|
-
this.#cache.set(path, instance);
|
|
135
|
-
if (this.#cache.size > this.#cacheSize) {
|
|
136
|
-
this.#cache.delete(this.#cache.keys().next().value);
|
|
137
|
-
}
|
|
138
|
-
return instance;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Format output path to ensure proper .html extensions
|
|
143
|
-
* @private
|
|
144
|
-
*/
|
|
145
|
-
#formatOutputPath(outputDir, reqPath) {
|
|
146
|
-
// Handle root path
|
|
147
|
-
if (reqPath === '/' || reqPath === '') {
|
|
148
|
-
return path.join(outputDir, 'index.html');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// If already has .html extension, use as-is
|
|
152
|
-
if (reqPath.endsWith('.html')) {
|
|
153
|
-
return path.join(outputDir, reqPath);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Add .html extension
|
|
157
|
-
// For paths like /posts or /posts/my-slug, both become .html files
|
|
158
|
-
return path.join(outputDir, `${reqPath}.html`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Create unique instance cache key from request
|
|
163
|
-
* Combines path, params, and query for unique identification
|
|
164
|
-
* @private
|
|
165
|
-
*/
|
|
166
|
-
#instancePath(req) {
|
|
167
|
-
let key = req.path;
|
|
168
|
-
|
|
169
|
-
// Add route params if present (e.g., /posts/:slug)
|
|
170
|
-
if (req.params && Object.keys(req.params).length > 0) {
|
|
171
|
-
const paramsStr = Object.entries(req.params)
|
|
172
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
173
|
-
.map(([k, v]) => `${k}=${v}`)
|
|
174
|
-
.join('&');
|
|
175
|
-
key += `?params:${paramsStr}`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Add query params if present (e.g., ?page=1&sort=desc)
|
|
179
|
-
if (req.query && Object.keys(req.query).length > 0) {
|
|
180
|
-
const queryStr = Object.entries(req.query)
|
|
181
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
182
|
-
.map(([k, v]) => `${k}=${v}`)
|
|
183
|
-
.join('&');
|
|
184
|
-
key += `${req.params ? '&' : '?'}query:${queryStr}`;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return key;
|
|
188
|
-
}
|
|
189
|
-
}
|