asjs-express 1.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/LICENSE +21 -0
- package/README.md +883 -0
- package/index.js +1 -0
- package/lib/asjs.js +1579 -0
- package/lib/client/asjs-core.css +92 -0
- package/lib/client/asjs-router.js +989 -0
- package/lib/client/asjs-theme.css +1073 -0
- package/package.json +63 -0
package/lib/asjs.js
ADDED
|
@@ -0,0 +1,1579 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const express = require('express');
|
|
5
|
+
|
|
6
|
+
function escapeHtml(value) {
|
|
7
|
+
return String(value ?? '')
|
|
8
|
+
.replace(/&/g, '&')
|
|
9
|
+
.replace(/</g, '<')
|
|
10
|
+
.replace(/>/g, '>')
|
|
11
|
+
.replace(/"/g, '"')
|
|
12
|
+
.replace(/'/g, ''');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function ensureExtension(name, extension) {
|
|
16
|
+
return path.extname(name) ? name : `${name}.${extension}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeBooleanOption(value, fallback) {
|
|
20
|
+
return value === undefined ? fallback : Boolean(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeNumberOption(value, fallback) {
|
|
24
|
+
const normalized = Number(value);
|
|
25
|
+
return Number.isFinite(normalized) && normalized >= 0 ? normalized : fallback;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeAssetMountPath(value) {
|
|
29
|
+
const rawValue = value === undefined || value === null || value === ''
|
|
30
|
+
? '_asjs'
|
|
31
|
+
: String(value);
|
|
32
|
+
const normalized = rawValue.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
33
|
+
|
|
34
|
+
return `/${normalized || '_asjs'}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function joinAssetUrl(basePath, fileName) {
|
|
38
|
+
return `${normalizeAssetMountPath(basePath)}/${String(fileName).replace(/^\/+/, '')}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function appendAssetVersion(url, version) {
|
|
42
|
+
if (!version) {
|
|
43
|
+
return url;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
47
|
+
return `${url}${separator}v=${encodeURIComponent(version)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function serializeHtmlAttributes(attributes = {}) {
|
|
51
|
+
return Object.entries(attributes)
|
|
52
|
+
.filter(([, value]) => value !== false && value !== null && value !== undefined)
|
|
53
|
+
.map(([name, value]) => {
|
|
54
|
+
if (value === true) {
|
|
55
|
+
return ` ${name}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return ` ${name}="${escapeHtml(String(value))}"`;
|
|
59
|
+
})
|
|
60
|
+
.join('');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createFileHash(filePath) {
|
|
64
|
+
try {
|
|
65
|
+
return crypto
|
|
66
|
+
.createHash('sha1')
|
|
67
|
+
.update(fs.readFileSync(filePath))
|
|
68
|
+
.digest('hex')
|
|
69
|
+
.slice(0, 10);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return 'dev';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getPackageVersion(packagePaths) {
|
|
76
|
+
try {
|
|
77
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePaths.packageJsonPath, 'utf8'));
|
|
78
|
+
return typeof packageJson.version === 'string' && packageJson.version
|
|
79
|
+
? packageJson.version
|
|
80
|
+
: 'dev';
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return 'dev';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolveAssetVersion(filePath, config) {
|
|
87
|
+
if (config.assetVersion === false) {
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const baseVersion = typeof config.assetVersion === 'string' && config.assetVersion
|
|
92
|
+
? config.assetVersion
|
|
93
|
+
: (config.packageVersion || 'dev');
|
|
94
|
+
|
|
95
|
+
return `${baseVersion}-${createFileHash(filePath)}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createClientAssets(config) {
|
|
99
|
+
const routerScriptVersion = resolveAssetVersion(config.packagePaths.routerPath, config);
|
|
100
|
+
const coreStylesheetVersion = resolveAssetVersion(config.packagePaths.stylesheetPath, config);
|
|
101
|
+
const themeStylesheetVersion = resolveAssetVersion(config.packagePaths.themeStylesheetPath, config);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
mountPath: config.assetMountPath,
|
|
105
|
+
routerScript: appendAssetVersion(
|
|
106
|
+
joinAssetUrl(config.assetMountPath, 'asjs-router.js'),
|
|
107
|
+
routerScriptVersion
|
|
108
|
+
),
|
|
109
|
+
coreStylesheet: appendAssetVersion(
|
|
110
|
+
joinAssetUrl(config.assetMountPath, 'asjs-core.css'),
|
|
111
|
+
coreStylesheetVersion
|
|
112
|
+
),
|
|
113
|
+
themeStylesheet: appendAssetVersion(
|
|
114
|
+
joinAssetUrl(config.assetMountPath, 'asjs-theme.css'),
|
|
115
|
+
themeStylesheetVersion
|
|
116
|
+
),
|
|
117
|
+
versions: {
|
|
118
|
+
routerScript: routerScriptVersion,
|
|
119
|
+
coreStylesheet: coreStylesheetVersion,
|
|
120
|
+
themeStylesheet: themeStylesheetVersion
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function renderClientTags(assets, options = {}) {
|
|
126
|
+
const settings = options && typeof options === 'object' ? options : {};
|
|
127
|
+
const tags = [];
|
|
128
|
+
const includeTheme = Boolean(settings.theme);
|
|
129
|
+
|
|
130
|
+
if (settings.preload) {
|
|
131
|
+
if (settings.styles !== false) {
|
|
132
|
+
tags.push(`<link${serializeHtmlAttributes({
|
|
133
|
+
rel: 'preload',
|
|
134
|
+
as: 'style',
|
|
135
|
+
href: assets.coreStylesheet
|
|
136
|
+
})}>`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (settings.script !== false) {
|
|
140
|
+
tags.push(`<link${serializeHtmlAttributes({
|
|
141
|
+
rel: 'preload',
|
|
142
|
+
as: 'script',
|
|
143
|
+
href: assets.routerScript
|
|
144
|
+
})}>`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (includeTheme) {
|
|
148
|
+
tags.push(`<link${serializeHtmlAttributes({
|
|
149
|
+
rel: 'preload',
|
|
150
|
+
as: 'style',
|
|
151
|
+
href: assets.themeStylesheet
|
|
152
|
+
})}>`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (settings.styles !== false) {
|
|
157
|
+
tags.push(`<link${serializeHtmlAttributes({
|
|
158
|
+
rel: 'stylesheet',
|
|
159
|
+
href: assets.coreStylesheet,
|
|
160
|
+
...(settings.styleAttrs || {})
|
|
161
|
+
})}>`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (includeTheme) {
|
|
165
|
+
tags.push(`<link${serializeHtmlAttributes({
|
|
166
|
+
rel: 'stylesheet',
|
|
167
|
+
href: assets.themeStylesheet,
|
|
168
|
+
...(settings.themeAttrs || {})
|
|
169
|
+
})}>`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (settings.script !== false) {
|
|
173
|
+
const scriptAttributes = {
|
|
174
|
+
src: assets.routerScript,
|
|
175
|
+
...(settings.module ? { type: 'module' } : {}),
|
|
176
|
+
...((settings.async || settings.defer === false || settings.module) ? {} : { defer: true }),
|
|
177
|
+
...(settings.async ? { async: true } : {}),
|
|
178
|
+
...(settings.scriptAttrs || {})
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
tags.push(`<script${serializeHtmlAttributes(scriptAttributes)}></script>`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return tags.join('\n');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function normalizeFormMode(value, fallback = 'view') {
|
|
188
|
+
const normalized = String(value || fallback || 'view').toLowerCase();
|
|
189
|
+
return ['view', 'json'].includes(normalized) ? normalized : (fallback || 'view');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function normalizeFormSwap(value, fallback = 'replace') {
|
|
193
|
+
const normalized = String(value || fallback || 'replace').toLowerCase();
|
|
194
|
+
return ['replace', 'append', 'prepend'].includes(normalized)
|
|
195
|
+
? normalized
|
|
196
|
+
: (fallback || 'replace');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function normalizeFormOptions(value, fallback = null) {
|
|
200
|
+
const base = fallback
|
|
201
|
+
? { ...fallback }
|
|
202
|
+
: {
|
|
203
|
+
enabled: true,
|
|
204
|
+
selector: 'form[data-asjs-form], form[data-webas-submit]',
|
|
205
|
+
mode: 'view',
|
|
206
|
+
history: false,
|
|
207
|
+
resetOnSuccess: false,
|
|
208
|
+
target: '',
|
|
209
|
+
swap: 'replace'
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (value === undefined) {
|
|
213
|
+
return base;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (value === false || value === null) {
|
|
217
|
+
return {
|
|
218
|
+
...base,
|
|
219
|
+
enabled: false
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (value === true) {
|
|
224
|
+
return {
|
|
225
|
+
...base,
|
|
226
|
+
enabled: true
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (typeof value === 'string') {
|
|
231
|
+
return {
|
|
232
|
+
...base,
|
|
233
|
+
enabled: true,
|
|
234
|
+
selector: value
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (value && typeof value === 'object') {
|
|
239
|
+
return {
|
|
240
|
+
...base,
|
|
241
|
+
enabled: normalizeBooleanOption(value.enabled, base.enabled),
|
|
242
|
+
selector: value.selector ? String(value.selector) : base.selector,
|
|
243
|
+
mode: normalizeFormMode(value.mode, base.mode),
|
|
244
|
+
history: normalizeBooleanOption(value.history, base.history),
|
|
245
|
+
resetOnSuccess: normalizeBooleanOption(value.resetOnSuccess, base.resetOnSuccess),
|
|
246
|
+
target: value.target ? String(value.target) : base.target,
|
|
247
|
+
swap: normalizeFormSwap(value.swap, base.swap)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return base;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function renderFormAttrs(asjs, options = {}) {
|
|
255
|
+
const forms = asjs && asjs.forms ? asjs.forms : normalizeFormOptions(true);
|
|
256
|
+
const settings = options && typeof options === 'object' ? options : {};
|
|
257
|
+
const attributes = {
|
|
258
|
+
method: settings.method || 'post',
|
|
259
|
+
action: settings.action || undefined,
|
|
260
|
+
enctype: settings.enctype || undefined,
|
|
261
|
+
novalidate: settings.noValidate ? true : undefined,
|
|
262
|
+
'data-asjs-form': settings.enabled === false ? undefined : 'true',
|
|
263
|
+
'data-asjs-form-mode': settings.mode ? normalizeFormMode(settings.mode, forms.mode) : undefined,
|
|
264
|
+
'data-asjs-form-history': Object.prototype.hasOwnProperty.call(settings, 'history')
|
|
265
|
+
? (settings.history ? 'true' : 'false')
|
|
266
|
+
: undefined,
|
|
267
|
+
'data-asjs-form-reset-on-success': Object.prototype.hasOwnProperty.call(settings, 'resetOnSuccess')
|
|
268
|
+
? (settings.resetOnSuccess ? 'true' : 'false')
|
|
269
|
+
: undefined,
|
|
270
|
+
'data-asjs-form-target': settings.target || undefined,
|
|
271
|
+
'data-asjs-form-swap': settings.swap ? normalizeFormSwap(settings.swap, forms.swap) : undefined,
|
|
272
|
+
'data-asjs-transition': settings.transition || undefined,
|
|
273
|
+
...((settings.attrs && typeof settings.attrs === 'object') ? settings.attrs : {})
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
return serializeHtmlAttributes(attributes);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function normalizePluginList(value) {
|
|
280
|
+
if (Array.isArray(value)) {
|
|
281
|
+
return value.filter(Boolean);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return value ? [value] : [];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function registerHook(registry, name, handler) {
|
|
288
|
+
if (!(registry instanceof Map) || typeof name !== 'string' || typeof handler !== 'function') {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const key = name.trim();
|
|
293
|
+
if (!key) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!registry.has(key)) {
|
|
298
|
+
registry.set(key, []);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
registry.get(key).push(handler);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function createHookRegistry(inputHooks = {}) {
|
|
305
|
+
const registry = new Map();
|
|
306
|
+
|
|
307
|
+
if (!inputHooks || typeof inputHooks !== 'object') {
|
|
308
|
+
return registry;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Object.entries(inputHooks).forEach(([name, handlers]) => {
|
|
312
|
+
const normalizedHandlers = Array.isArray(handlers) ? handlers : [handlers];
|
|
313
|
+
|
|
314
|
+
normalizedHandlers.forEach((handler) => {
|
|
315
|
+
registerHook(registry, name, handler);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return registry;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function runSyncHooks(registry, name, context) {
|
|
323
|
+
const handlers = registry instanceof Map ? (registry.get(name) || []) : [];
|
|
324
|
+
|
|
325
|
+
return handlers.reduce((currentContext, handler) => {
|
|
326
|
+
const result = handler(currentContext);
|
|
327
|
+
|
|
328
|
+
if (result && typeof result === 'object') {
|
|
329
|
+
return {
|
|
330
|
+
...currentContext,
|
|
331
|
+
...result
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return currentContext;
|
|
336
|
+
}, context);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function runHookChain(registry, name, context) {
|
|
340
|
+
const handlers = registry instanceof Map ? (registry.get(name) || []) : [];
|
|
341
|
+
|
|
342
|
+
return handlers.reduce((promise, handler) => {
|
|
343
|
+
return promise.then((currentContext) => {
|
|
344
|
+
return Promise.resolve(handler(currentContext)).then((result) => {
|
|
345
|
+
if (result && typeof result === 'object') {
|
|
346
|
+
return {
|
|
347
|
+
...currentContext,
|
|
348
|
+
...result
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return currentContext;
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
}, Promise.resolve(context));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function applyAsjsPlugin(plugin, api) {
|
|
359
|
+
if (!plugin) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (typeof plugin === 'function') {
|
|
364
|
+
plugin(api);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const candidate = ['setup', 'install', 'register']
|
|
369
|
+
.map((key) => plugin[key])
|
|
370
|
+
.find((handler) => typeof handler === 'function');
|
|
371
|
+
|
|
372
|
+
if (candidate) {
|
|
373
|
+
candidate.call(plugin, api);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function renderBodyAttrs(asjs, extraAttributes = {}) {
|
|
378
|
+
return serializeHtmlAttributes({
|
|
379
|
+
'data-asjs-transition': asjs.transition.enabled ? asjs.transition.name : 'none',
|
|
380
|
+
'data-asjs-transition-duration': asjs.transition.duration,
|
|
381
|
+
'data-asjs-prefetch': asjs.prefetch ? 'true' : 'false',
|
|
382
|
+
'data-asjs-prefetch-ttl': asjs.prefetchTtl,
|
|
383
|
+
'data-asjs-loading-bar': asjs.loadingBar ? 'true' : 'false',
|
|
384
|
+
'data-asjs-forms': asjs.forms && asjs.forms.enabled ? 'true' : 'false',
|
|
385
|
+
'data-asjs-form-selector': asjs.forms ? asjs.forms.selector : undefined,
|
|
386
|
+
'data-asjs-form-mode': asjs.forms ? asjs.forms.mode : undefined,
|
|
387
|
+
'data-asjs-form-history': asjs.forms && asjs.forms.history ? 'true' : 'false',
|
|
388
|
+
'data-asjs-form-reset-on-success': asjs.forms && asjs.forms.resetOnSuccess ? 'true' : 'false',
|
|
389
|
+
'data-asjs-form-target': asjs.forms && asjs.forms.target ? asjs.forms.target : undefined,
|
|
390
|
+
'data-asjs-form-swap': asjs.forms ? asjs.forms.swap : undefined,
|
|
391
|
+
...extraAttributes
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function renderViewAttrs(asjs, extraAttributes = {}) {
|
|
396
|
+
return serializeHtmlAttributes({
|
|
397
|
+
'data-asjs-view': true,
|
|
398
|
+
'data-asjs-transition': asjs.transition.enabled ? asjs.transition.name : 'none',
|
|
399
|
+
'data-asjs-transition-duration': asjs.transition.duration,
|
|
400
|
+
...extraAttributes
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function renderProgressMarkup() {
|
|
405
|
+
return '<div class="asjs-progress" data-asjs-progress aria-hidden="true"><span class="asjs-progress-bar"></span></div>';
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function createAsjsViewModel(inputAsjs, config, overrides = {}) {
|
|
409
|
+
const current = inputAsjs && typeof inputAsjs === 'object' ? inputAsjs : {};
|
|
410
|
+
const assets = config.clientAssets || createClientAssets(config);
|
|
411
|
+
const transition = Object.prototype.hasOwnProperty.call(overrides, 'transition')
|
|
412
|
+
? normalizeTransitionOptions(overrides.transition, config.transitions)
|
|
413
|
+
: normalizeTransitionOptions(current.transition, config.transitions);
|
|
414
|
+
|
|
415
|
+
const viewModel = {
|
|
416
|
+
...current,
|
|
417
|
+
debug: config.debug,
|
|
418
|
+
transition,
|
|
419
|
+
prefetch: Object.prototype.hasOwnProperty.call(current, 'prefetch')
|
|
420
|
+
? Boolean(current.prefetch)
|
|
421
|
+
: config.prefetch,
|
|
422
|
+
prefetchTtl: Object.prototype.hasOwnProperty.call(current, 'prefetchTtl')
|
|
423
|
+
? normalizeNumberOption(current.prefetchTtl, config.prefetchTtl)
|
|
424
|
+
: config.prefetchTtl,
|
|
425
|
+
loadingBar: Object.prototype.hasOwnProperty.call(current, 'loadingBar')
|
|
426
|
+
? Boolean(current.loadingBar)
|
|
427
|
+
: config.loadingBar,
|
|
428
|
+
forms: Object.prototype.hasOwnProperty.call(current, 'forms')
|
|
429
|
+
? normalizeFormOptions(current.forms, config.forms)
|
|
430
|
+
: normalizeFormOptions(undefined, config.forms),
|
|
431
|
+
cache: Object.prototype.hasOwnProperty.call(current, 'cache')
|
|
432
|
+
? Boolean(current.cache)
|
|
433
|
+
: config.cache,
|
|
434
|
+
assets,
|
|
435
|
+
assetVersions: assets.versions
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
viewModel.clientTags = (options) => renderClientTags(assets, options);
|
|
439
|
+
viewModel.bodyAttrs = (extraAttributes) => renderBodyAttrs(viewModel, extraAttributes);
|
|
440
|
+
viewModel.viewAttrs = (extraAttributes) => renderViewAttrs(viewModel, extraAttributes);
|
|
441
|
+
viewModel.formAttrs = (options) => renderFormAttrs(viewModel, options);
|
|
442
|
+
viewModel.progressMarkup = () => renderProgressMarkup();
|
|
443
|
+
|
|
444
|
+
return viewModel;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function normalizeComponentName(name, extension = 'asjs') {
|
|
448
|
+
return ensureExtension(String(name).replace(/\\/g, '/'), extension);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function normalizeComponentSpec(spec) {
|
|
452
|
+
if (!spec || typeof spec !== 'object' || Array.isArray(spec)) {
|
|
453
|
+
return {
|
|
454
|
+
props: spec && typeof spec === 'object' ? spec : {},
|
|
455
|
+
strict: false
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (Object.prototype.hasOwnProperty.call(spec, 'props') || Object.prototype.hasOwnProperty.call(spec, 'schema')) {
|
|
460
|
+
return {
|
|
461
|
+
props: spec.props || spec.schema || {},
|
|
462
|
+
strict: Boolean(spec.strict)
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
props: spec,
|
|
468
|
+
strict: false
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function normalizeComponentRegistry(components = {}, extension = 'asjs') {
|
|
473
|
+
const registry = {};
|
|
474
|
+
|
|
475
|
+
Object.entries(components).forEach(([name, spec]) => {
|
|
476
|
+
registry[normalizeComponentName(name, extension)] = normalizeComponentSpec(spec);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
return registry;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function normalizeTypeList(type) {
|
|
483
|
+
if (type === undefined || type === null || type === 'any') {
|
|
484
|
+
return ['any'];
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return Array.isArray(type) ? type : [type];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function getTypeLabel(type) {
|
|
491
|
+
if (typeof type === 'string') {
|
|
492
|
+
return type;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (type === String) {
|
|
496
|
+
return 'string';
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (type === Number) {
|
|
500
|
+
return 'number';
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (type === Boolean) {
|
|
504
|
+
return 'boolean';
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (type === Array) {
|
|
508
|
+
return 'array';
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (type === Object) {
|
|
512
|
+
return 'object';
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (type === Date) {
|
|
516
|
+
return 'date';
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return type && type.name ? type.name : 'custom';
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function getValueType(value) {
|
|
523
|
+
if (Array.isArray(value)) {
|
|
524
|
+
return 'array';
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (value instanceof Date) {
|
|
528
|
+
return 'date';
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (value === null) {
|
|
532
|
+
return 'null';
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return typeof value;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function matchesExpectedType(value, expected) {
|
|
539
|
+
if (expected === 'any') {
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (typeof expected === 'string') {
|
|
544
|
+
if (expected === 'array') {
|
|
545
|
+
return Array.isArray(value);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (expected === 'date') {
|
|
549
|
+
return value instanceof Date;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (expected === 'null') {
|
|
553
|
+
return value === null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (expected === 'object') {
|
|
557
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return typeof value === expected;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (expected === String) {
|
|
564
|
+
return typeof value === 'string';
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (expected === Number) {
|
|
568
|
+
return typeof value === 'number' && !Number.isNaN(value);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (expected === Boolean) {
|
|
572
|
+
return typeof value === 'boolean';
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (expected === Array) {
|
|
576
|
+
return Array.isArray(value);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (expected === Object) {
|
|
580
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (expected === Date) {
|
|
584
|
+
return value instanceof Date;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return typeof expected === 'function' ? value instanceof expected : false;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function normalizePropRule(rule) {
|
|
591
|
+
if (typeof rule === 'string' || Array.isArray(rule) || typeof rule === 'function') {
|
|
592
|
+
return {
|
|
593
|
+
required: true,
|
|
594
|
+
hasDefault: false,
|
|
595
|
+
defaultValue: undefined,
|
|
596
|
+
types: normalizeTypeList(rule),
|
|
597
|
+
validate: null,
|
|
598
|
+
message: null
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (rule && typeof rule === 'object') {
|
|
603
|
+
return {
|
|
604
|
+
required: rule.required !== false && !Object.prototype.hasOwnProperty.call(rule, 'default'),
|
|
605
|
+
hasDefault: Object.prototype.hasOwnProperty.call(rule, 'default'),
|
|
606
|
+
defaultValue: rule.default,
|
|
607
|
+
types: normalizeTypeList(rule.type || 'any'),
|
|
608
|
+
validate: typeof rule.validate === 'function' ? rule.validate : null,
|
|
609
|
+
message: rule.message || null
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return {
|
|
614
|
+
required: false,
|
|
615
|
+
hasDefault: false,
|
|
616
|
+
defaultValue: undefined,
|
|
617
|
+
types: ['any'],
|
|
618
|
+
validate: null,
|
|
619
|
+
message: null
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function resolveDefaultValue(rule, props) {
|
|
624
|
+
if (!rule.hasDefault) {
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return typeof rule.defaultValue === 'function' ? rule.defaultValue(props) : rule.defaultValue;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function validateComponentProps(componentName, inputProps, schema = {}, options = {}) {
|
|
632
|
+
const props = inputProps && typeof inputProps === 'object' ? { ...inputProps } : {};
|
|
633
|
+
const validatedProps = { ...props };
|
|
634
|
+
const strict = Boolean(options.strict);
|
|
635
|
+
|
|
636
|
+
Object.entries(schema).forEach(([propName, rawRule]) => {
|
|
637
|
+
const rule = normalizePropRule(rawRule);
|
|
638
|
+
const hasValue = Object.prototype.hasOwnProperty.call(props, propName) && props[propName] !== undefined;
|
|
639
|
+
|
|
640
|
+
if (!hasValue) {
|
|
641
|
+
if (rule.hasDefault) {
|
|
642
|
+
validatedProps[propName] = resolveDefaultValue(rule, validatedProps);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (rule.required) {
|
|
647
|
+
throw new TypeError(`ASJS component props hatasi (${componentName}): "${propName}" gerekli.`);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const value = props[propName];
|
|
654
|
+
const validType = rule.types.includes('any') || rule.types.some((expected) => matchesExpectedType(value, expected));
|
|
655
|
+
|
|
656
|
+
if (!validType) {
|
|
657
|
+
throw new TypeError(
|
|
658
|
+
`ASJS component props hatasi (${componentName}): "${propName}" beklenen tip ${rule.types.map(getTypeLabel).join(' | ')}, gelen ${getValueType(value)}.`
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (rule.validate) {
|
|
663
|
+
const validationResult = rule.validate(value, validatedProps);
|
|
664
|
+
|
|
665
|
+
if (validationResult !== true && validationResult !== undefined) {
|
|
666
|
+
throw new TypeError(
|
|
667
|
+
typeof validationResult === 'string'
|
|
668
|
+
? validationResult
|
|
669
|
+
: (rule.message || `ASJS component props hatasi (${componentName}): "${propName}" dogrulamasi basarisiz.`)
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
if (strict) {
|
|
676
|
+
Object.keys(props).forEach((propName) => {
|
|
677
|
+
if (!Object.prototype.hasOwnProperty.call(schema, propName)) {
|
|
678
|
+
throw new TypeError(`ASJS component props hatasi (${componentName}): tanimsiz prop "${propName}".`);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return validatedProps;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function getCompiledTemplate(filePath, runtime) {
|
|
687
|
+
if (!runtime.cacheEnabled) {
|
|
688
|
+
return compileTemplate(fs.readFileSync(filePath, 'utf8'), filePath);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const stats = fs.statSync(filePath);
|
|
692
|
+
const cached = runtime.templateCache.get(filePath);
|
|
693
|
+
|
|
694
|
+
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
695
|
+
return cached.template;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const template = compileTemplate(fs.readFileSync(filePath, 'utf8'), filePath);
|
|
699
|
+
|
|
700
|
+
runtime.templateCache.set(filePath, {
|
|
701
|
+
mtimeMs: stats.mtimeMs,
|
|
702
|
+
template
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
return template;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function resolveComponentSpec(templateName, runtime, explicitSchema) {
|
|
709
|
+
if (explicitSchema) {
|
|
710
|
+
return normalizeComponentSpec(explicitSchema);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return runtime.components[normalizeComponentName(templateName, runtime.extension)] || null;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function normalizeTransitionOptions(value, fallback = null) {
|
|
717
|
+
if (value === undefined) {
|
|
718
|
+
return fallback
|
|
719
|
+
? { ...fallback }
|
|
720
|
+
: { enabled: false, name: 'none', duration: 0 };
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (value === false || value === null || value === 'none') {
|
|
724
|
+
return { enabled: false, name: 'none', duration: 0 };
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (value === true) {
|
|
728
|
+
return { enabled: true, name: 'fade', duration: 260 };
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (typeof value === 'string') {
|
|
732
|
+
return { enabled: true, name: value, duration: 260 };
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (typeof value === 'object') {
|
|
736
|
+
const enabled = value.enabled !== false;
|
|
737
|
+
const duration = Number.isFinite(Number(value.duration)) && Number(value.duration) > 0
|
|
738
|
+
? Number(value.duration)
|
|
739
|
+
: 260;
|
|
740
|
+
|
|
741
|
+
return {
|
|
742
|
+
enabled,
|
|
743
|
+
name: enabled ? String(value.name || 'fade') : 'none',
|
|
744
|
+
duration: enabled ? duration : 0
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return fallback
|
|
749
|
+
? { ...fallback }
|
|
750
|
+
: { enabled: false, name: 'none', duration: 0 };
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function resolveTemplatePath(name, currentFile, viewsDir, extension) {
|
|
754
|
+
const normalizedName = ensureExtension(name, extension);
|
|
755
|
+
|
|
756
|
+
if (path.isAbsolute(normalizedName)) {
|
|
757
|
+
return normalizedName;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (normalizedName.startsWith('./') || normalizedName.startsWith('../')) {
|
|
761
|
+
return path.resolve(path.dirname(currentFile), normalizedName);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return path.resolve(viewsDir, normalizedName);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function compileTemplate(source, filename) {
|
|
768
|
+
const matcher = /<%[-=]?[\s\S]+?%>/g;
|
|
769
|
+
let cursor = 0;
|
|
770
|
+
let match;
|
|
771
|
+
|
|
772
|
+
const chunks = [
|
|
773
|
+
'let __output = "";',
|
|
774
|
+
'const __append = (value) => { if (value !== undefined && value !== null) { __output += String(value); } };'
|
|
775
|
+
];
|
|
776
|
+
|
|
777
|
+
while ((match = matcher.exec(source)) !== null) {
|
|
778
|
+
const text = source.slice(cursor, match.index);
|
|
779
|
+
if (text) {
|
|
780
|
+
chunks.push(`__output += ${JSON.stringify(text)};`);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const tag = match[0];
|
|
784
|
+
const body = tag.slice(tag.startsWith('<%=') || tag.startsWith('<%-') ? 3 : 2, -2).trim();
|
|
785
|
+
|
|
786
|
+
if (tag.startsWith('<%=')) {
|
|
787
|
+
chunks.push(`__output += __escape(${body});`);
|
|
788
|
+
} else if (tag.startsWith('<%-')) {
|
|
789
|
+
chunks.push(`__append(${body});`);
|
|
790
|
+
} else {
|
|
791
|
+
chunks.push(body);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
cursor = match.index + tag.length;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const tail = source.slice(cursor);
|
|
798
|
+
if (tail) {
|
|
799
|
+
chunks.push(`__output += ${JSON.stringify(tail)};`);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
chunks.push('return __output;');
|
|
803
|
+
|
|
804
|
+
try {
|
|
805
|
+
return new Function(
|
|
806
|
+
'__locals',
|
|
807
|
+
'__helpers',
|
|
808
|
+
'__escape',
|
|
809
|
+
`with (__helpers) { with (__locals) { ${chunks.join('\n')} } }`
|
|
810
|
+
);
|
|
811
|
+
} catch (error) {
|
|
812
|
+
error.message = `ASJS derleme hatasi (${filename}): ${error.message}`;
|
|
813
|
+
throw error;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function renderTemplateFile(filePath, locals, runtime) {
|
|
818
|
+
const template = getCompiledTemplate(filePath, runtime);
|
|
819
|
+
const renderState = runtime.state || { layout: null };
|
|
820
|
+
|
|
821
|
+
const helpers = {
|
|
822
|
+
escape: escapeHtml,
|
|
823
|
+
raw(value) {
|
|
824
|
+
return value == null ? '' : String(value);
|
|
825
|
+
},
|
|
826
|
+
print(...values) {
|
|
827
|
+
return values.filter((value) => value != null).join('');
|
|
828
|
+
},
|
|
829
|
+
layout(name) {
|
|
830
|
+
renderState.layout = name;
|
|
831
|
+
return '';
|
|
832
|
+
},
|
|
833
|
+
include(templateName, extraLocals = {}) {
|
|
834
|
+
const includePath = resolveTemplatePath(
|
|
835
|
+
templateName,
|
|
836
|
+
filePath,
|
|
837
|
+
runtime.viewsDir,
|
|
838
|
+
runtime.extension
|
|
839
|
+
);
|
|
840
|
+
|
|
841
|
+
return renderTemplateFile(
|
|
842
|
+
includePath,
|
|
843
|
+
{ ...locals, ...extraLocals },
|
|
844
|
+
{
|
|
845
|
+
...runtime,
|
|
846
|
+
state: { layout: null }
|
|
847
|
+
}
|
|
848
|
+
).html;
|
|
849
|
+
},
|
|
850
|
+
props(schema, values, componentName = path.basename(filePath)) {
|
|
851
|
+
const spec = normalizeComponentSpec(schema);
|
|
852
|
+
return validateComponentProps(componentName, values, spec.props, { strict: spec.strict });
|
|
853
|
+
},
|
|
854
|
+
defineProps(schema, values, componentName = path.basename(filePath)) {
|
|
855
|
+
const spec = normalizeComponentSpec(schema);
|
|
856
|
+
return validateComponentProps(componentName, values, spec.props, { strict: spec.strict });
|
|
857
|
+
},
|
|
858
|
+
component(templateName, componentProps = {}, schema) {
|
|
859
|
+
const includePath = resolveTemplatePath(
|
|
860
|
+
templateName,
|
|
861
|
+
filePath,
|
|
862
|
+
runtime.viewsDir,
|
|
863
|
+
runtime.extension
|
|
864
|
+
);
|
|
865
|
+
const spec = resolveComponentSpec(templateName, runtime, schema);
|
|
866
|
+
const validatedProps = spec
|
|
867
|
+
? validateComponentProps(templateName, componentProps, spec.props, { strict: spec.strict })
|
|
868
|
+
: componentProps;
|
|
869
|
+
|
|
870
|
+
return renderTemplateFile(
|
|
871
|
+
includePath,
|
|
872
|
+
{ ...locals, ...validatedProps },
|
|
873
|
+
{
|
|
874
|
+
...runtime,
|
|
875
|
+
state: { layout: null }
|
|
876
|
+
}
|
|
877
|
+
).html;
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
try {
|
|
882
|
+
return {
|
|
883
|
+
html: template(locals, helpers, escapeHtml),
|
|
884
|
+
state: renderState
|
|
885
|
+
};
|
|
886
|
+
} catch (error) {
|
|
887
|
+
error.message = `ASJS render hatasi (${filePath}): ${error.message}`;
|
|
888
|
+
throw error;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function createAsjsEngine(options = {}) {
|
|
893
|
+
const extension = String(options.extension || 'asjs').replace(/^\./, '');
|
|
894
|
+
|
|
895
|
+
return function asjsEngine(filePath, data, callback) {
|
|
896
|
+
try {
|
|
897
|
+
const viewsDir = options.viewsDir || data.settings?.views || path.dirname(filePath);
|
|
898
|
+
const initialLayout = Object.prototype.hasOwnProperty.call(data, 'layout')
|
|
899
|
+
? data.layout
|
|
900
|
+
: options.defaultLayout || null;
|
|
901
|
+
|
|
902
|
+
const locals = {
|
|
903
|
+
...data
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
delete locals.layout;
|
|
907
|
+
locals.asjs = createAsjsViewModel(locals.asjs, options);
|
|
908
|
+
|
|
909
|
+
const pageState = { layout: initialLayout };
|
|
910
|
+
const page = renderTemplateFile(filePath, locals, {
|
|
911
|
+
cacheEnabled: options.cache !== false,
|
|
912
|
+
components: options.components || {},
|
|
913
|
+
extension,
|
|
914
|
+
templateCache: options.templateCache || new Map(),
|
|
915
|
+
viewsDir,
|
|
916
|
+
state: pageState
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
let html = page.html;
|
|
920
|
+
|
|
921
|
+
if (pageState.layout) {
|
|
922
|
+
const layoutPath = resolveTemplatePath(pageState.layout, filePath, viewsDir, extension);
|
|
923
|
+
html = renderTemplateFile(layoutPath, {
|
|
924
|
+
...locals,
|
|
925
|
+
asjs: createAsjsViewModel(locals.asjs, options),
|
|
926
|
+
body: html
|
|
927
|
+
}, {
|
|
928
|
+
cacheEnabled: options.cache !== false,
|
|
929
|
+
components: options.components || {},
|
|
930
|
+
extension,
|
|
931
|
+
templateCache: options.templateCache || new Map(),
|
|
932
|
+
viewsDir,
|
|
933
|
+
state: { layout: null }
|
|
934
|
+
}).html;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
callback(null, html);
|
|
938
|
+
} catch (error) {
|
|
939
|
+
callback(error);
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function createAsjsConfig(options = {}) {
|
|
945
|
+
const extension = String(options.extension || 'asjs').replace(/^\./, '');
|
|
946
|
+
const packagePaths = getAsjsPackagePaths();
|
|
947
|
+
const packageVersion = getPackageVersion(packagePaths);
|
|
948
|
+
const assetMountPath = normalizeAssetMountPath(options.assetMountPath);
|
|
949
|
+
const rootDir = options.rootDir || process.cwd();
|
|
950
|
+
|
|
951
|
+
const config = {
|
|
952
|
+
extension,
|
|
953
|
+
assetMountPath,
|
|
954
|
+
rootDir,
|
|
955
|
+
viewsDir: options.viewsDir || path.join(rootDir, 'views'),
|
|
956
|
+
publicDir: options.publicDir || null,
|
|
957
|
+
defaultLayout: options.defaultLayout || null,
|
|
958
|
+
debug: Boolean(options.debug),
|
|
959
|
+
cache: normalizeBooleanOption(options.cache, !options.debug),
|
|
960
|
+
navItems: Array.isArray(options.navItems) ? options.navItems : [],
|
|
961
|
+
locals: options.locals && typeof options.locals === 'object' ? options.locals : {},
|
|
962
|
+
components: normalizeComponentRegistry(options.components || {}, extension),
|
|
963
|
+
templateCache: new Map(),
|
|
964
|
+
transitions: normalizeTransitionOptions(options.transitions),
|
|
965
|
+
prefetch: normalizeBooleanOption(options.prefetch, true),
|
|
966
|
+
prefetchTtl: normalizeNumberOption(options.prefetchTtl, 30000),
|
|
967
|
+
loadingBar: normalizeBooleanOption(options.loadingBar, true),
|
|
968
|
+
forms: normalizeFormOptions(
|
|
969
|
+
Object.prototype.hasOwnProperty.call(options, 'forms') ? options.forms : true
|
|
970
|
+
),
|
|
971
|
+
hooks: createHookRegistry(options.hooks),
|
|
972
|
+
plugins: normalizePluginList(options.plugins),
|
|
973
|
+
packagePaths,
|
|
974
|
+
packageVersion,
|
|
975
|
+
assetVersion: Object.prototype.hasOwnProperty.call(options, 'assetVersion')
|
|
976
|
+
? options.assetVersion
|
|
977
|
+
: packageVersion,
|
|
978
|
+
serveClientAssets: normalizeBooleanOption(options.serveClientAssets, true)
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
config.clientAssets = createClientAssets(config);
|
|
982
|
+
|
|
983
|
+
return config;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function getAsjsPackagePaths() {
|
|
987
|
+
const rootDir = path.resolve(__dirname, '..');
|
|
988
|
+
const clientDir = path.join(__dirname, 'client');
|
|
989
|
+
|
|
990
|
+
return {
|
|
991
|
+
rootDir,
|
|
992
|
+
clientDir,
|
|
993
|
+
entryPath: path.join(rootDir, 'index.js'),
|
|
994
|
+
packageJsonPath: path.join(rootDir, 'package.json'),
|
|
995
|
+
routerPath: path.join(clientDir, 'asjs-router.js'),
|
|
996
|
+
stylesheetPath: path.join(clientDir, 'asjs-core.css'),
|
|
997
|
+
themeStylesheetPath: path.join(clientDir, 'asjs-theme.css')
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function renderDebugErrorPage(error, context = {}) {
|
|
1002
|
+
const status = Number(context.status || error.status || 500);
|
|
1003
|
+
const title = context.title || 'ASJS Debug Hatası';
|
|
1004
|
+
const method = escapeHtml(context.method || 'GET');
|
|
1005
|
+
const requestPath = escapeHtml(context.requestPath || '/');
|
|
1006
|
+
const timestamp = escapeHtml(new Date().toLocaleString('tr-TR'));
|
|
1007
|
+
const message = escapeHtml(error.message || 'Bilinmeyen hata');
|
|
1008
|
+
const errorName = escapeHtml(error.name || 'Error');
|
|
1009
|
+
const stackLines = String(error.stack || error.message || 'Stack bilgisi yok')
|
|
1010
|
+
.split('\n')
|
|
1011
|
+
.map((line) => line.trim())
|
|
1012
|
+
.filter(Boolean);
|
|
1013
|
+
const stack = escapeHtml(stackLines.join('\n'));
|
|
1014
|
+
const primaryFrame = escapeHtml(stackLines[1] || 'Çerçeve bilgisi yok');
|
|
1015
|
+
const cause = error.cause ? escapeHtml(String(error.cause.stack || error.cause.message || error.cause)) : '';
|
|
1016
|
+
const templateMatch = String(error.message || '').match(/\(([^)]+\.asjs)\)/);
|
|
1017
|
+
const templatePath = escapeHtml(templateMatch ? templateMatch[1] : (context.templatePath || 'Bulunamadı'));
|
|
1018
|
+
const hintItems = [
|
|
1019
|
+
'Şablon içinde <% %> bloklarının açılıp kapandığını kontrol et.',
|
|
1020
|
+
'Layout ve include yollarının views klasörüne göre doğru çözüldüğünden emin ol.',
|
|
1021
|
+
'component() kullanıyorsan zorunlu props alanlarının tam gönderildiğini doğrula.',
|
|
1022
|
+
'Özel CSS veya script ekliyorsan bunların ASJS body ve view alanlarını bozmadığını kontrol et.'
|
|
1023
|
+
];
|
|
1024
|
+
const environmentItems = [
|
|
1025
|
+
{ label: 'Hata türü', value: errorName },
|
|
1026
|
+
{ label: 'İstek', value: `${method} ${requestPath}` },
|
|
1027
|
+
{ label: 'Şablon', value: templatePath },
|
|
1028
|
+
{ label: 'İlk çerçeve', value: primaryFrame },
|
|
1029
|
+
{ label: 'Zaman', value: timestamp },
|
|
1030
|
+
{ label: 'Sürüm', value: escapeHtml(context.packageVersion || 'dev') }
|
|
1031
|
+
];
|
|
1032
|
+
|
|
1033
|
+
return `<!DOCTYPE html>
|
|
1034
|
+
<html lang="tr">
|
|
1035
|
+
<head>
|
|
1036
|
+
<meta charset="UTF-8">
|
|
1037
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1038
|
+
<title>${title}</title>
|
|
1039
|
+
<style>
|
|
1040
|
+
:root {
|
|
1041
|
+
--bg: #08111f;
|
|
1042
|
+
--panel: rgba(11, 21, 38, 0.88);
|
|
1043
|
+
--panel-strong: rgba(17, 31, 56, 0.96);
|
|
1044
|
+
--text: #eef4fb;
|
|
1045
|
+
--muted: #a7b2c6;
|
|
1046
|
+
--accent: #ff8a65;
|
|
1047
|
+
--accent-2: #63e6be;
|
|
1048
|
+
--accent-3: #5fd2ff;
|
|
1049
|
+
--line: rgba(255, 255, 255, 0.08);
|
|
1050
|
+
--radius-xl: 30px;
|
|
1051
|
+
--radius-lg: 22px;
|
|
1052
|
+
--radius-md: 16px;
|
|
1053
|
+
--shadow: 0 34px 90px rgba(0, 0, 0, 0.38);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
* { box-sizing: border-box; }
|
|
1057
|
+
|
|
1058
|
+
body {
|
|
1059
|
+
margin: 0;
|
|
1060
|
+
min-height: 100vh;
|
|
1061
|
+
color: var(--text);
|
|
1062
|
+
font-family: "Space Grotesk", "Segoe UI", sans-serif;
|
|
1063
|
+
background:
|
|
1064
|
+
radial-gradient(circle at top left, rgba(255, 138, 101, 0.18), transparent 28%),
|
|
1065
|
+
radial-gradient(circle at top right, rgba(99, 230, 190, 0.16), transparent 30%),
|
|
1066
|
+
linear-gradient(140deg, #040a14, #08111f 52%, #10203b);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
body::before {
|
|
1070
|
+
content: "";
|
|
1071
|
+
position: fixed;
|
|
1072
|
+
inset: 0;
|
|
1073
|
+
pointer-events: none;
|
|
1074
|
+
background-image:
|
|
1075
|
+
linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
|
|
1076
|
+
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
|
|
1077
|
+
background-size: 42px 42px;
|
|
1078
|
+
mask-image: radial-gradient(circle at center, black 55%, transparent 92%);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
.shell {
|
|
1082
|
+
width: min(1220px, calc(100% - 32px));
|
|
1083
|
+
margin: 24px auto 40px;
|
|
1084
|
+
position: relative;
|
|
1085
|
+
z-index: 1;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
.hero,
|
|
1089
|
+
.panel,
|
|
1090
|
+
.stack-panel,
|
|
1091
|
+
.meta-card {
|
|
1092
|
+
background: var(--panel);
|
|
1093
|
+
border: 1px solid var(--line);
|
|
1094
|
+
border-radius: var(--radius-xl);
|
|
1095
|
+
backdrop-filter: blur(18px);
|
|
1096
|
+
box-shadow: var(--shadow);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
.hero {
|
|
1100
|
+
padding: 30px;
|
|
1101
|
+
display: grid;
|
|
1102
|
+
gap: 26px;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.eyebrow {
|
|
1106
|
+
display: inline-flex;
|
|
1107
|
+
align-items: center;
|
|
1108
|
+
gap: 8px;
|
|
1109
|
+
width: fit-content;
|
|
1110
|
+
padding: 8px 12px;
|
|
1111
|
+
border-radius: 999px;
|
|
1112
|
+
background: rgba(255, 138, 101, 0.12);
|
|
1113
|
+
color: var(--accent);
|
|
1114
|
+
font-size: 0.84rem;
|
|
1115
|
+
text-transform: uppercase;
|
|
1116
|
+
letter-spacing: 0.08em;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.hero-top {
|
|
1120
|
+
display: grid;
|
|
1121
|
+
grid-template-columns: 1.2fr 0.8fr;
|
|
1122
|
+
gap: 20px;
|
|
1123
|
+
align-items: start;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
h1 {
|
|
1127
|
+
margin: 16px 0 10px;
|
|
1128
|
+
font-size: clamp(2.6rem, 7vw, 5.2rem);
|
|
1129
|
+
line-height: 0.96;
|
|
1130
|
+
letter-spacing: -0.05em;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
p,
|
|
1134
|
+
li {
|
|
1135
|
+
color: var(--muted);
|
|
1136
|
+
line-height: 1.7;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
.grid {
|
|
1140
|
+
display: grid;
|
|
1141
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1142
|
+
gap: 18px;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
.panel {
|
|
1146
|
+
padding: 22px;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
.meta-grid {
|
|
1150
|
+
display: grid;
|
|
1151
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1152
|
+
gap: 16px;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
.meta-card {
|
|
1156
|
+
padding: 18px;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
.headline {
|
|
1160
|
+
margin: 0;
|
|
1161
|
+
font-size: 1.3rem;
|
|
1162
|
+
line-height: 1.3;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.status-badge {
|
|
1166
|
+
display: inline-flex;
|
|
1167
|
+
align-items: center;
|
|
1168
|
+
gap: 8px;
|
|
1169
|
+
padding: 10px 14px;
|
|
1170
|
+
border-radius: 999px;
|
|
1171
|
+
background: rgba(255, 138, 101, 0.12);
|
|
1172
|
+
color: var(--text);
|
|
1173
|
+
font-weight: 700;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.status-dot {
|
|
1177
|
+
width: 10px;
|
|
1178
|
+
height: 10px;
|
|
1179
|
+
border-radius: 50%;
|
|
1180
|
+
background: linear-gradient(135deg, var(--accent), #ffb38a);
|
|
1181
|
+
box-shadow: 0 0 18px rgba(255, 138, 101, 0.48);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.label {
|
|
1185
|
+
display: block;
|
|
1186
|
+
color: var(--muted);
|
|
1187
|
+
font-size: 0.9rem;
|
|
1188
|
+
margin-bottom: 8px;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
.value {
|
|
1192
|
+
display: block;
|
|
1193
|
+
color: var(--text);
|
|
1194
|
+
font-weight: 700;
|
|
1195
|
+
word-break: break-word;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
.section-title {
|
|
1199
|
+
margin: 0 0 12px;
|
|
1200
|
+
font-size: 1.08rem;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.stack-grid {
|
|
1204
|
+
display: grid;
|
|
1205
|
+
grid-template-columns: 1.1fr 0.9fr;
|
|
1206
|
+
gap: 18px;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.stack-panel {
|
|
1210
|
+
padding: 22px;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
.stack {
|
|
1214
|
+
margin-top: 12px;
|
|
1215
|
+
padding: 20px;
|
|
1216
|
+
border-radius: var(--radius-lg);
|
|
1217
|
+
background: rgba(5, 10, 19, 0.82);
|
|
1218
|
+
border: 1px solid rgba(255, 255, 255, 0.07);
|
|
1219
|
+
overflow-x: auto;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
pre {
|
|
1223
|
+
margin: 0;
|
|
1224
|
+
white-space: pre-wrap;
|
|
1225
|
+
word-break: break-word;
|
|
1226
|
+
font-family: "Cascadia Code", "Consolas", monospace;
|
|
1227
|
+
color: #f6f8fb;
|
|
1228
|
+
line-height: 1.65;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
.summary-list,
|
|
1232
|
+
.tips {
|
|
1233
|
+
margin: 0;
|
|
1234
|
+
padding-left: 18px;
|
|
1235
|
+
display: grid;
|
|
1236
|
+
gap: 10px;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
.tips strong {
|
|
1240
|
+
color: var(--accent-2);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
.summary-list strong {
|
|
1244
|
+
color: var(--accent-3);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
.callout {
|
|
1248
|
+
margin-top: 10px;
|
|
1249
|
+
padding: 16px 18px;
|
|
1250
|
+
border-radius: var(--radius-md);
|
|
1251
|
+
background: linear-gradient(180deg, rgba(17, 31, 56, 0.9), rgba(10, 20, 36, 0.84));
|
|
1252
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
code {
|
|
1256
|
+
font-family: "Cascadia Code", "Consolas", monospace;
|
|
1257
|
+
font-size: 0.95em;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.subtle {
|
|
1261
|
+
color: var(--muted);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
details {
|
|
1265
|
+
margin-top: 16px;
|
|
1266
|
+
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
|
1267
|
+
padding-top: 14px;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
summary {
|
|
1271
|
+
cursor: pointer;
|
|
1272
|
+
color: var(--text);
|
|
1273
|
+
font-weight: 700;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
@media (max-width: 860px) {
|
|
1277
|
+
.hero-top,
|
|
1278
|
+
.grid {
|
|
1279
|
+
grid-template-columns: 1fr;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
.meta-grid,
|
|
1283
|
+
.stack-grid {
|
|
1284
|
+
grid-template-columns: 1fr;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
body {
|
|
1288
|
+
padding: 0;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
.shell {
|
|
1292
|
+
width: min(100%, calc(100% - 24px));
|
|
1293
|
+
margin: 12px auto 24px;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
.hero {
|
|
1297
|
+
padding: 22px;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
</style>
|
|
1301
|
+
</head>
|
|
1302
|
+
<body>
|
|
1303
|
+
<div class="shell">
|
|
1304
|
+
<section class="hero">
|
|
1305
|
+
<div class="hero-top">
|
|
1306
|
+
<div>
|
|
1307
|
+
<span class="eyebrow">Debug modu açık</span>
|
|
1308
|
+
<h1>ASJS bir hata yakaladı.</h1>
|
|
1309
|
+
<p>Bu sayfa yalnızca debug modu etkinken gösterilir. Amaç, hatayı kısa özetle görünür kılmak ve asıl teknik ayrıntıları aynı ekranda profesyonel şekilde sunmaktır.</p>
|
|
1310
|
+
<div class="callout">
|
|
1311
|
+
<div class="status-badge"><span class="status-dot"></span> HTTP ${status} · ${errorName}</div>
|
|
1312
|
+
<p class="subtle">${message}</p>
|
|
1313
|
+
</div>
|
|
1314
|
+
</div>
|
|
1315
|
+
|
|
1316
|
+
<article class="panel">
|
|
1317
|
+
<h2 class="headline">Hızlı özet</h2>
|
|
1318
|
+
<ul class="summary-list">
|
|
1319
|
+
<li><strong>İstek:</strong> ${method} ${requestPath}</li>
|
|
1320
|
+
<li><strong>Şablon:</strong> <code>${templatePath}</code></li>
|
|
1321
|
+
<li><strong>İlk çerçeve:</strong> <code>${primaryFrame}</code></li>
|
|
1322
|
+
<li><strong>Zaman:</strong> ${timestamp}</li>
|
|
1323
|
+
</ul>
|
|
1324
|
+
</article>
|
|
1325
|
+
</div>
|
|
1326
|
+
|
|
1327
|
+
<div class="meta-grid">
|
|
1328
|
+
${environmentItems.map((item) => `
|
|
1329
|
+
<article class="meta-card">
|
|
1330
|
+
<span class="label">${item.label}</span>
|
|
1331
|
+
<span class="value">${item.value}</span>
|
|
1332
|
+
</article>`).join('')}
|
|
1333
|
+
</div>
|
|
1334
|
+
|
|
1335
|
+
<div class="stack-grid">
|
|
1336
|
+
<article class="stack-panel">
|
|
1337
|
+
<h2 class="section-title">Stack trace</h2>
|
|
1338
|
+
<div class="stack">
|
|
1339
|
+
<pre>${stack}</pre>
|
|
1340
|
+
</div>
|
|
1341
|
+
${cause ? `
|
|
1342
|
+
<details>
|
|
1343
|
+
<summary>Asıl sebep</summary>
|
|
1344
|
+
<div class="stack"><pre>${cause}</pre></div>
|
|
1345
|
+
</details>` : ''}
|
|
1346
|
+
</article>
|
|
1347
|
+
|
|
1348
|
+
<article class="stack-panel">
|
|
1349
|
+
<h2 class="section-title">Muhtemel kontrol listesi</h2>
|
|
1350
|
+
<ul class="tips">
|
|
1351
|
+
${hintItems.map((item) => `<li><strong>Kontrol:</strong> ${escapeHtml(item)}</li>`).join('')}
|
|
1352
|
+
</ul>
|
|
1353
|
+
<details>
|
|
1354
|
+
<summary>Teknik not</summary>
|
|
1355
|
+
<p class="subtle">Bu görünüm, route zinciri kırıldığında ham Express hata çıktısı yerine daha okunur bir teknik özet verir. Üretim ortamında debug kapalıysa bu ekran gösterilmez.</p>
|
|
1356
|
+
</details>
|
|
1357
|
+
</article>
|
|
1358
|
+
</div>
|
|
1359
|
+
</section>
|
|
1360
|
+
</div>
|
|
1361
|
+
</body>
|
|
1362
|
+
</html>`;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function buildPageLocals(req, config, pageData = {}) {
|
|
1366
|
+
const input = pageData && typeof pageData === 'object' ? pageData : {};
|
|
1367
|
+
const status = Number.isInteger(input.status) ? input.status : null;
|
|
1368
|
+
const transition = Object.prototype.hasOwnProperty.call(input, 'transition')
|
|
1369
|
+
? normalizeTransitionOptions(input.transition, config.transitions)
|
|
1370
|
+
: { ...config.transitions };
|
|
1371
|
+
const pageAsjs = input.asjs && typeof input.asjs === 'object' ? input.asjs : {};
|
|
1372
|
+
const locals = {
|
|
1373
|
+
...config.locals,
|
|
1374
|
+
...input
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
delete locals.status;
|
|
1378
|
+
delete locals.transition;
|
|
1379
|
+
delete locals.asjs;
|
|
1380
|
+
|
|
1381
|
+
locals.navItems = Array.isArray(input.navItems) ? input.navItems : config.navItems;
|
|
1382
|
+
locals.currentPath = input.currentPath || req.path;
|
|
1383
|
+
locals.asjs = createAsjsViewModel(pageAsjs, config, { transition });
|
|
1384
|
+
|
|
1385
|
+
return {
|
|
1386
|
+
locals,
|
|
1387
|
+
status
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
function setupAsjs(app, options = {}) {
|
|
1392
|
+
const config = createAsjsConfig(options);
|
|
1393
|
+
|
|
1394
|
+
function extendLocals(extraLocals = {}) {
|
|
1395
|
+
if (extraLocals && typeof extraLocals === 'object') {
|
|
1396
|
+
config.locals = {
|
|
1397
|
+
...config.locals,
|
|
1398
|
+
...extraLocals
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
return api;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
function defineComponents(components = {}) {
|
|
1406
|
+
config.components = {
|
|
1407
|
+
...config.components,
|
|
1408
|
+
...normalizeComponentRegistry(components, config.extension)
|
|
1409
|
+
};
|
|
1410
|
+
|
|
1411
|
+
return api;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
function addHook(name, handler) {
|
|
1415
|
+
registerHook(config.hooks, name, handler);
|
|
1416
|
+
return api;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
function clearCache() {
|
|
1420
|
+
config.templateCache.clear();
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
function render(res, viewName, pageData = {}) {
|
|
1424
|
+
const renderContext = runSyncHooks(config.hooks, 'beforeRender', {
|
|
1425
|
+
app,
|
|
1426
|
+
config,
|
|
1427
|
+
req: res.req,
|
|
1428
|
+
res,
|
|
1429
|
+
viewName,
|
|
1430
|
+
pageData
|
|
1431
|
+
});
|
|
1432
|
+
const { locals, status } = buildPageLocals(res.req, config, renderContext.pageData);
|
|
1433
|
+
|
|
1434
|
+
if (status) {
|
|
1435
|
+
res.status(status);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
const output = res.render(viewName, locals);
|
|
1439
|
+
|
|
1440
|
+
runSyncHooks(config.hooks, 'afterRender', {
|
|
1441
|
+
...renderContext,
|
|
1442
|
+
locals,
|
|
1443
|
+
status
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
return output;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
function resolvePageData(req, res, next, viewName, pageData) {
|
|
1450
|
+
return runHookChain(config.hooks, 'beforePage', {
|
|
1451
|
+
app,
|
|
1452
|
+
config,
|
|
1453
|
+
req,
|
|
1454
|
+
res,
|
|
1455
|
+
next,
|
|
1456
|
+
viewName,
|
|
1457
|
+
pageData: undefined
|
|
1458
|
+
}).then((beforePageContext) => {
|
|
1459
|
+
return Promise.resolve(
|
|
1460
|
+
typeof pageData === 'function'
|
|
1461
|
+
? pageData(beforePageContext.req, beforePageContext.res, beforePageContext.next)
|
|
1462
|
+
: pageData
|
|
1463
|
+
).then((resolvedPageData) => {
|
|
1464
|
+
return runHookChain(config.hooks, 'afterPage', {
|
|
1465
|
+
...beforePageContext,
|
|
1466
|
+
pageData: resolvedPageData
|
|
1467
|
+
});
|
|
1468
|
+
});
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
function page(viewName, pageData) {
|
|
1473
|
+
return function asjsPageHandler(req, res, next) {
|
|
1474
|
+
resolvePageData(req, res, next, viewName, pageData)
|
|
1475
|
+
.then((context) => {
|
|
1476
|
+
render(res, viewName, context.pageData || {});
|
|
1477
|
+
})
|
|
1478
|
+
.catch(next);
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
function notFound(viewName, pageData) {
|
|
1483
|
+
return function asjsNotFoundHandler(req, res, next) {
|
|
1484
|
+
if (res.headersSent) {
|
|
1485
|
+
next();
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
resolvePageData(req, res, next, viewName, pageData)
|
|
1490
|
+
.then((context) => {
|
|
1491
|
+
render(res, viewName, {
|
|
1492
|
+
...(context.pageData || {}),
|
|
1493
|
+
status: 404
|
|
1494
|
+
});
|
|
1495
|
+
})
|
|
1496
|
+
.catch(next);
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function errors() {
|
|
1501
|
+
return function asjsErrorHandler(error, req, res, next) {
|
|
1502
|
+
if (res.headersSent) {
|
|
1503
|
+
next(error);
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
const status = Number(error.status || 500);
|
|
1508
|
+
|
|
1509
|
+
if (config.debug) {
|
|
1510
|
+
res.status(status).send(renderDebugErrorPage(error, {
|
|
1511
|
+
packageVersion: config.packageVersion,
|
|
1512
|
+
status,
|
|
1513
|
+
requestPath: req.originalUrl || req.url,
|
|
1514
|
+
method: req.method
|
|
1515
|
+
}));
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
res.status(status).send(status === 404 ? 'Sayfa bulunamadi' : 'Sunucu hatasi');
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
const api = {
|
|
1524
|
+
app,
|
|
1525
|
+
config,
|
|
1526
|
+
clearCache,
|
|
1527
|
+
express,
|
|
1528
|
+
render,
|
|
1529
|
+
page,
|
|
1530
|
+
route: page,
|
|
1531
|
+
notFound,
|
|
1532
|
+
errors,
|
|
1533
|
+
renderDebugErrorPage,
|
|
1534
|
+
packagePaths: config.packagePaths,
|
|
1535
|
+
extendLocals,
|
|
1536
|
+
defineComponents,
|
|
1537
|
+
addHook,
|
|
1538
|
+
use(plugin) {
|
|
1539
|
+
applyAsjsPlugin(plugin, api);
|
|
1540
|
+
return api;
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
|
|
1544
|
+
config.plugins.forEach((plugin) => {
|
|
1545
|
+
api.use(plugin);
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
app.engine(config.extension, createAsjsEngine(config));
|
|
1549
|
+
app.set('view engine', config.extension);
|
|
1550
|
+
app.set('views', config.viewsDir);
|
|
1551
|
+
|
|
1552
|
+
if (config.serveClientAssets) {
|
|
1553
|
+
app.use(config.assetMountPath, express.static(config.packagePaths.clientDir, {
|
|
1554
|
+
fallthrough: true,
|
|
1555
|
+
immutable: !config.debug,
|
|
1556
|
+
index: false,
|
|
1557
|
+
maxAge: config.debug ? 0 : '1h'
|
|
1558
|
+
}));
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
if (config.publicDir) {
|
|
1562
|
+
app.use(express.static(config.publicDir));
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
app.locals.asjs = createAsjsViewModel({}, config);
|
|
1566
|
+
|
|
1567
|
+
return api;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
module.exports = {
|
|
1571
|
+
createAsjsConfig,
|
|
1572
|
+
createAsjsEngine,
|
|
1573
|
+
escapeHtml,
|
|
1574
|
+
getAsjsPackagePaths,
|
|
1575
|
+
renderClientTags,
|
|
1576
|
+
renderDebugErrorPage,
|
|
1577
|
+
validateComponentProps,
|
|
1578
|
+
setupAsjs
|
|
1579
|
+
};
|