webspresso 0.0.66 → 0.0.67
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 +24 -1
- package/index.d.ts +6 -0
- package/index.js +4 -0
- package/package.json +9 -4
- package/src/client-runtime/bootstrap-alpine-swup.js +34 -0
- package/src/client-runtime/bootstrap-swup.js +26 -0
- package/src/client-runtime/mount.js +65 -0
- package/src/client-runtime/resolve.js +40 -0
- package/src/file-router.js +16 -2
- package/src/server.js +11 -2
- package/templates/skills/webspresso-usage/SKILL.md +2 -1
- package/views/partials/webspresso-client-runtime.njk +15 -0
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ A minimal, file-based SSR framework for Node.js with Nunjucks templating.
|
|
|
17
17
|
- **Plugin System**: Extensible architecture with version control and inter-plugin communication
|
|
18
18
|
- **Built-in Plugins**: Development dashboard, sitemap generator, SEO checker, analytics integration (Google, Yandex, Bing), self-hosted site analytics, optional Swagger UI for HTTP APIs, configurable HTTP health probe endpoint, optional REST CRUD routes from ORM models, optional admin UI for ORM query cache metrics and purge
|
|
19
19
|
- **Session authentication** (optional): `createAuth` / `quickAuth` in **`webspresso/core/auth`** — pass the manager to **`createApp({ auth })`** for `express-session`, `req.user` / `req.auth`, remember-me tokens, and policy-style authorization. Full walkthrough: **[`doc/index.html#authentication`](doc/index.html#authentication)**.
|
|
20
|
+
- **Optional client runtime** (Alpine.js + [swup](https://swup.js.org/)): **`createApp({ clientRuntime: { alpine, swup } })`** serves scripts under **`/__webspresso/client-runtime/`** and exposes **`clientRuntime`** in Nunjucks; layouts can include **`views/partials/webspresso-client-runtime.njk`**. Env overrides: **`WEBSPRESSO_ALPINE`**, **`WEBSPRESSO_SWUP`**. Demo: **`examples/alpine-swup-demo/`**. Details: **[`doc/index.html#client-runtime`](doc/index.html#client-runtime)**.
|
|
20
21
|
- **TypeScript**: Published **`index.d.ts`** (via `package.json` `"types"`) for `createApp`, ORM, plugins, and router helpers — use from TS/JS with IDE autocomplete; runtime stays CommonJS
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
@@ -357,8 +358,9 @@ Creates and configures the Express app.
|
|
|
357
358
|
- `false`: Disable Helmet
|
|
358
359
|
- `Object`: Custom Helmet configuration (merged with defaults)
|
|
359
360
|
- `middlewares` (optional): Named middleware registry for routes
|
|
361
|
+
- `clientRuntime` (optional): **`{ alpine?: boolean | object, swup?: boolean | object }`**. When either flag is enabled, mounts vendored Alpine 3 / swup 4 (+ Head + Scripts plugins) at **`/__webspresso/client-runtime/*`** and passes resolved **`{ alpine, swup }`** into SSR templates as **`clientRuntime`**. Override with env **`WEBSPRESSO_ALPINE`** / **`WEBSPRESSO_SWUP`** (`1` or `true`). Package exports **`resolveClientRuntime`** and **`CLIENT_RUNTIME_BASE`**. See **[Client runtime](#client-runtime)** below and **[`doc/index.html#client-runtime`](doc/index.html#client-runtime)**.
|
|
360
362
|
- `auth` (optional): `AuthManager` from **`createAuth()`** / **`quickAuth()`** (`require('webspresso/core/auth')`). Registers session + cookie parsing, attaches **`req.auth`** / **`req.user`**, and injects named route middleware **`auth`** and **`guest`** (do not reuse those names for custom handlers if you pass `auth`). See **[`doc/index.html#authentication`](doc/index.html#authentication)**.
|
|
361
|
-
- `setupRoutes(app, ctx)` (optional): Register custom Express routes after file routes and plugin `onRoutesReady`, before the 404 handler — use for login/logout handlers when using the auth module; **`ctx.authMiddleware`** exposes `requireAuth`, `requireGuest`, etc.
|
|
363
|
+
- `setupRoutes(app, ctx)` (optional): Register custom Express routes after file routes and plugin `onRoutesReady`, before the 404 handler — use for login/logout handlers when using the auth module; **`ctx.authMiddleware`** exposes `requireAuth`, `requireGuest`, etc.; **`ctx.clientRuntime`** is **`{ alpine, swup }`**
|
|
362
364
|
|
|
363
365
|
**Example with middlewares:**
|
|
364
366
|
|
|
@@ -423,6 +425,27 @@ middlewares: {
|
|
|
423
425
|
|
|
424
426
|
Plain `(req, res, next) => …` handlers still work as today. Tuple form **requires** a factory for that name (you get a clear error if you mix a plain handler with `['name', opts]`).
|
|
425
427
|
|
|
428
|
+
### Client runtime
|
|
429
|
+
|
|
430
|
+
Alpine.js + swup — opt-in progressive enhancement for SSR pages:
|
|
431
|
+
|
|
432
|
+
- **`clientRuntime: { alpine: true, swup: true }`** (each can be toggled independently). Default is off; no scripts are injected when both are disabled.
|
|
433
|
+
- Include **`{% include "partials/webspresso-client-runtime.njk" %}`** in your layout (copy from the npm package’s **`views/partials/`** or the framework repo). When **swup** is on, wrap the main content in **`<main id="swup">…</main>`** so transitions replace the correct region.
|
|
434
|
+
- **swup** uses Head + Scripts plugins; **Alpine** is re-bound after each visit via **`Alpine.initTree`** on the container. Use **`data-no-swup`** on links for a full page load. Paths **`/_admin`** and **`/_webspresso`** are ignored by the default bootstrap; the admin panel and dev dashboard stay separate Mithril apps.
|
|
435
|
+
- Dynamic UI can call **`pages/api/*`** from Alpine with **`fetch`** (see **`examples/alpine-swup-demo/`**).
|
|
436
|
+
- **Helmet / CSP**: production **`script-src 'self'`** works for **`/__webspresso/client-runtime/`**; some Alpine builds may need **`unsafe-eval`** — validate for your version or use a stricter build.
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
const { createApp } = require('webspresso');
|
|
440
|
+
|
|
441
|
+
const { app } = createApp({
|
|
442
|
+
pagesDir: './pages',
|
|
443
|
+
viewsDir: './views',
|
|
444
|
+
publicDir: './public',
|
|
445
|
+
clientRuntime: { alpine: true, swup: true },
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
426
449
|
### App context (`req.db`, `getDb`, `attachDbMiddleware`)
|
|
427
450
|
|
|
428
451
|
With **`createApp({ db })`**, file-based **API** routes (`pages/api/*.js`) get the same ORM instance on **`req.db`** before your **`middleware`** array and the handler run — no extra `require` in the handler:
|
package/index.d.ts
CHANGED
|
@@ -45,6 +45,11 @@ export interface CreateAppOptions {
|
|
|
45
45
|
timeout?: string | false;
|
|
46
46
|
auth?: unknown;
|
|
47
47
|
db?: DatabaseInstance | null;
|
|
48
|
+
/** Opt-in Alpine / swup assets under `/__webspresso/client-runtime/*`. Env: WEBSPRESSO_ALPINE, WEBSPRESSO_SWUP. */
|
|
49
|
+
clientRuntime?: {
|
|
50
|
+
alpine?: boolean | Record<string, unknown>;
|
|
51
|
+
swup?: boolean | Record<string, unknown>;
|
|
52
|
+
};
|
|
48
53
|
setupRoutes?: (app: Application, ctx: SetupRoutesContext) => void;
|
|
49
54
|
[key: string]: unknown;
|
|
50
55
|
}
|
|
@@ -54,6 +59,7 @@ export interface SetupRoutesContext {
|
|
|
54
59
|
authMiddleware?: RequestHandler;
|
|
55
60
|
pluginManager: PluginManager;
|
|
56
61
|
options: CreateAppOptions;
|
|
62
|
+
clientRuntime: { alpine: boolean; swup: boolean };
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
export interface CreateAppResult {
|
package/index.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const { createApp } = require('./src/server');
|
|
6
|
+
const { resolveClientRuntime } = require('./src/client-runtime/resolve');
|
|
7
|
+
const { CLIENT_RUNTIME_BASE } = require('./src/client-runtime/mount');
|
|
6
8
|
const {
|
|
7
9
|
attachDbMiddleware,
|
|
8
10
|
getAppContext,
|
|
@@ -43,6 +45,8 @@ const { schemaExplorerPlugin, adminPanelPlugin, siteAnalyticsPlugin, auditLogPlu
|
|
|
43
45
|
module.exports = {
|
|
44
46
|
// Main API
|
|
45
47
|
createApp,
|
|
48
|
+
resolveClientRuntime,
|
|
49
|
+
CLIENT_RUNTIME_BASE,
|
|
46
50
|
|
|
47
51
|
attachDbMiddleware,
|
|
48
52
|
getAppContext,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webspresso",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.67",
|
|
4
4
|
"description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -42,9 +42,13 @@
|
|
|
42
42
|
"utils/",
|
|
43
43
|
"core/",
|
|
44
44
|
"plugins/",
|
|
45
|
-
"templates/"
|
|
45
|
+
"templates/",
|
|
46
|
+
"views/partials/webspresso-client-runtime.njk"
|
|
46
47
|
],
|
|
47
48
|
"dependencies": {
|
|
49
|
+
"@swup/head-plugin": "^2.3.1",
|
|
50
|
+
"@swup/scripts-plugin": "^2.1.0",
|
|
51
|
+
"alpinejs": "^3.15.11",
|
|
48
52
|
"bcrypt": "^5.1.1",
|
|
49
53
|
"commander": "^11.1.0",
|
|
50
54
|
"connect-timeout": "^1.9.1",
|
|
@@ -57,6 +61,7 @@
|
|
|
57
61
|
"knex": "^3.1.0",
|
|
58
62
|
"nunjucks": "^3.2.4",
|
|
59
63
|
"sharp": "^0.33.5",
|
|
64
|
+
"swup": "^4.8.3",
|
|
60
65
|
"zod": "^3.23.0",
|
|
61
66
|
"zod-to-json-schema": "^3.25.2"
|
|
62
67
|
},
|
|
@@ -85,10 +90,10 @@
|
|
|
85
90
|
}
|
|
86
91
|
},
|
|
87
92
|
"devDependencies": {
|
|
88
|
-
"@types/express": "^4.17.21",
|
|
89
|
-
"@types/node": "^20.14.0",
|
|
90
93
|
"@faker-js/faker": "^9.9.0",
|
|
91
94
|
"@playwright/test": "^1.48.0",
|
|
95
|
+
"@types/express": "^4.17.21",
|
|
96
|
+
"@types/node": "^20.14.0",
|
|
92
97
|
"@vitest/coverage-v8": "^3.0.0",
|
|
93
98
|
"better-sqlite3": "^11.10.0",
|
|
94
99
|
"chokidar": "^3.5.3",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* global Swup, SwupHeadPlugin, SwupScriptsPlugin, Alpine */
|
|
2
|
+
(function () {
|
|
3
|
+
if (typeof Swup === 'undefined' || typeof SwupHeadPlugin === 'undefined' || typeof SwupScriptsPlugin === 'undefined' || typeof Alpine === 'undefined') {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function ignoreVisit(url, ctx) {
|
|
8
|
+
var el = ctx && ctx.el;
|
|
9
|
+
if (el && el.closest && el.closest('[data-no-swup]')) return true;
|
|
10
|
+
try {
|
|
11
|
+
var u = new URL(url, window.location.origin);
|
|
12
|
+
var p = u.pathname;
|
|
13
|
+
if (p.indexOf('/_admin') === 0 || p.indexOf('/_webspresso') === 0) return true;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
/* ignore */
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
var swup = new Swup({
|
|
21
|
+
containers: ['#swup'],
|
|
22
|
+
plugins: [new SwupHeadPlugin(), new SwupScriptsPlugin()],
|
|
23
|
+
ignoreVisit: ignoreVisit,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
swup.hooks.on('content:replace', function () {
|
|
27
|
+
var root = document.querySelector('#swup');
|
|
28
|
+
if (root && typeof Alpine.initTree === 'function') {
|
|
29
|
+
Alpine.initTree(root);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
window.__webspressoSwup = swup;
|
|
34
|
+
})();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* global Swup, SwupHeadPlugin, SwupScriptsPlugin */
|
|
2
|
+
(function () {
|
|
3
|
+
if (typeof Swup === 'undefined' || typeof SwupHeadPlugin === 'undefined' || typeof SwupScriptsPlugin === 'undefined') {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function ignoreVisit(url, ctx) {
|
|
8
|
+
var el = ctx && ctx.el;
|
|
9
|
+
if (el && el.closest && el.closest('[data-no-swup]')) return true;
|
|
10
|
+
try {
|
|
11
|
+
var u = new URL(url, window.location.origin);
|
|
12
|
+
var p = u.pathname;
|
|
13
|
+
if (p.indexOf('/_admin') === 0 || p.indexOf('/_webspresso') === 0) return true;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
/* ignore */
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
var swup = new Swup({
|
|
21
|
+
containers: ['#swup'],
|
|
22
|
+
plugins: [new SwupHeadPlugin(), new SwupScriptsPlugin()],
|
|
23
|
+
ignoreVisit: ignoreVisit,
|
|
24
|
+
});
|
|
25
|
+
window.__webspressoSwup = swup;
|
|
26
|
+
})();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mount Express routes that serve vendored Alpine / Swup UMD builds from node_modules
|
|
3
|
+
* and framework bootstrap scripts from src/client-runtime/.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const express = require('express');
|
|
8
|
+
|
|
9
|
+
const CLIENT_RUNTIME_BASE = '/__webspresso/client-runtime';
|
|
10
|
+
|
|
11
|
+
/** Resolve a file next to the package's resolved main entry (works with package "exports"). */
|
|
12
|
+
function pkgFileFromMain(pkg, ...segments) {
|
|
13
|
+
const entry = require.resolve(pkg);
|
|
14
|
+
return path.join(path.dirname(entry), ...segments);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('express').Express} app
|
|
19
|
+
* @param {{ alpine: boolean, swup: boolean }} flags
|
|
20
|
+
*/
|
|
21
|
+
function mountClientRuntime(app, flags) {
|
|
22
|
+
if (!flags || (!flags.alpine && !flags.swup)) return;
|
|
23
|
+
|
|
24
|
+
const router = express.Router();
|
|
25
|
+
|
|
26
|
+
function send(res, filePath) {
|
|
27
|
+
res.type('application/javascript');
|
|
28
|
+
res.sendFile(filePath);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (flags.alpine) {
|
|
32
|
+
router.get('/alpine.min.js', (req, res) => {
|
|
33
|
+
send(res, pkgFileFromMain('alpinejs', 'cdn.min.js'));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (flags.swup) {
|
|
38
|
+
router.get('/swup.umd.js', (req, res) => {
|
|
39
|
+
send(res, pkgFileFromMain('swup', 'Swup.umd.js'));
|
|
40
|
+
});
|
|
41
|
+
router.get('/swup-head-plugin.umd.js', (req, res) => {
|
|
42
|
+
send(res, pkgFileFromMain('@swup/head-plugin', 'index.umd.js'));
|
|
43
|
+
});
|
|
44
|
+
router.get('/swup-scripts-plugin.umd.js', (req, res) => {
|
|
45
|
+
send(res, pkgFileFromMain('@swup/scripts-plugin', 'index.umd.js'));
|
|
46
|
+
});
|
|
47
|
+
const runtimeDir = __dirname;
|
|
48
|
+
if (flags.alpine) {
|
|
49
|
+
router.get('/bootstrap-alpine-swup.js', (req, res) => {
|
|
50
|
+
send(res, path.join(runtimeDir, 'bootstrap-alpine-swup.js'));
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
router.get('/bootstrap-swup.js', (req, res) => {
|
|
54
|
+
send(res, path.join(runtimeDir, 'bootstrap-swup.js'));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
app.use(CLIENT_RUNTIME_BASE, router);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
mountClientRuntime,
|
|
64
|
+
CLIENT_RUNTIME_BASE,
|
|
65
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve clientRuntime flags from createApp({ clientRuntime }) and optional env overrides.
|
|
3
|
+
* Env: WEBSPRESSO_ALPINE=1|true, WEBSPRESSO_SWUP=1|true (override explicit false from options when set).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function envTruthy(name) {
|
|
7
|
+
const v = process.env[name];
|
|
8
|
+
if (v == null || v === '') return undefined;
|
|
9
|
+
return v === '1' || /^true$/i.test(String(v));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function optionEnabled(v) {
|
|
13
|
+
if (v === true) return true;
|
|
14
|
+
if (v && typeof v === 'object') return true;
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} [options]
|
|
20
|
+
* @param {object} [options.clientRuntime]
|
|
21
|
+
* @param {boolean|object} [options.clientRuntime.alpine]
|
|
22
|
+
* @param {boolean|object} [options.clientRuntime.swup]
|
|
23
|
+
* @returns {{ alpine: boolean, swup: boolean }}
|
|
24
|
+
*/
|
|
25
|
+
function resolveClientRuntime(options = {}) {
|
|
26
|
+
const cr = options.clientRuntime;
|
|
27
|
+
let alpine = false;
|
|
28
|
+
let swup = false;
|
|
29
|
+
if (cr && typeof cr === 'object') {
|
|
30
|
+
alpine = optionEnabled(cr.alpine);
|
|
31
|
+
swup = optionEnabled(cr.swup);
|
|
32
|
+
}
|
|
33
|
+
const envA = envTruthy('WEBSPRESSO_ALPINE');
|
|
34
|
+
const envS = envTruthy('WEBSPRESSO_SWUP');
|
|
35
|
+
if (envA !== undefined) alpine = envA;
|
|
36
|
+
if (envS !== undefined) swup = envS;
|
|
37
|
+
return { alpine, swup };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { resolveClientRuntime };
|
package/src/file-router.js
CHANGED
|
@@ -365,10 +365,22 @@ function resolveMiddlewares(middlewareConfig, middlewareRegistry = {}) {
|
|
|
365
365
|
* @param {Object} options.pluginManager - Plugin manager instance
|
|
366
366
|
* @param {boolean} options.silent - Suppress console output
|
|
367
367
|
* @param {Object} options.db - Database instance (exposed as ctx.db in load/meta)
|
|
368
|
+
* @param {{ alpine?: boolean, swup?: boolean }} [options.clientRuntime] - Passed to Nunjucks as `clientRuntime` (default both false)
|
|
368
369
|
* @returns {Array} Route metadata for plugins
|
|
369
370
|
*/
|
|
370
371
|
function mountPages(app, options) {
|
|
371
|
-
const {
|
|
372
|
+
const {
|
|
373
|
+
pagesDir,
|
|
374
|
+
nunjucks,
|
|
375
|
+
middlewares = {},
|
|
376
|
+
pluginManager = null,
|
|
377
|
+
silent = false,
|
|
378
|
+
db = null,
|
|
379
|
+
clientRuntime: clientRuntimeOpt = null,
|
|
380
|
+
} = options;
|
|
381
|
+
const clientRuntime = clientRuntimeOpt && typeof clientRuntimeOpt === 'object'
|
|
382
|
+
? { alpine: !!clientRuntimeOpt.alpine, swup: !!clientRuntimeOpt.swup }
|
|
383
|
+
: { alpine: false, swup: false };
|
|
372
384
|
const isDev = process.env.NODE_ENV !== 'production';
|
|
373
385
|
const log = silent ? () => {} : console.log.bind(console);
|
|
374
386
|
|
|
@@ -548,7 +560,8 @@ function mountPages(app, options) {
|
|
|
548
560
|
indexable: true,
|
|
549
561
|
canonical: null
|
|
550
562
|
},
|
|
551
|
-
fsy: { ...baseHelpers, ...pluginHelpers }
|
|
563
|
+
fsy: { ...baseHelpers, ...pluginHelpers },
|
|
564
|
+
clientRuntime,
|
|
552
565
|
};
|
|
553
566
|
|
|
554
567
|
// Execute hooks: onRequest
|
|
@@ -612,6 +625,7 @@ function mountPages(app, options) {
|
|
|
612
625
|
locale: ctx.locale,
|
|
613
626
|
t: ctx.t,
|
|
614
627
|
fsy: ctx.fsy,
|
|
628
|
+
clientRuntime: ctx.clientRuntime,
|
|
615
629
|
req: {
|
|
616
630
|
path: req.path,
|
|
617
631
|
query: req.query,
|
package/src/server.js
CHANGED
|
@@ -9,6 +9,8 @@ const nunjucks = require('nunjucks');
|
|
|
9
9
|
const timeout = require('connect-timeout');
|
|
10
10
|
|
|
11
11
|
const { setAppContext } = require('./app-context');
|
|
12
|
+
const { mountClientRuntime } = require('./client-runtime/mount');
|
|
13
|
+
const { resolveClientRuntime } = require('./client-runtime/resolve');
|
|
12
14
|
const { mountPages } = require('./file-router');
|
|
13
15
|
const { configureAssets, createHelpers, getScriptInjector } = require('./helpers');
|
|
14
16
|
const { createPluginManager } = require('./plugin-manager');
|
|
@@ -258,6 +260,7 @@ function haltOnTimedout(req, res, next) {
|
|
|
258
260
|
* @param {string|boolean} options.timeout - Request timeout (default: '30s', false to disable)
|
|
259
261
|
* @param {Object} options.auth - Authentication manager instance (from createAuth)
|
|
260
262
|
* @param {Object} options.db - Database instance (exposed as ctx.db to plugins)
|
|
263
|
+
* @param {Object} [options.clientRuntime] - Optional client assets: `{ alpine?: boolean|object, swup?: boolean|object }`. Overridable by env `WEBSPRESSO_ALPINE` / `WEBSPRESSO_SWUP` (=1 or true). Serves `/__webspresso/client-runtime/*` when either flag is on.
|
|
261
264
|
* @param {function(import('express').Express, Object): void} [options.setupRoutes] - Called after file routes and plugins, before 404 handler
|
|
262
265
|
* @returns {Object} { app, nunjucksEnv, pluginManager, authMiddleware }
|
|
263
266
|
*/
|
|
@@ -294,6 +297,8 @@ function createApp(options = {}) {
|
|
|
294
297
|
throw new Error('pagesDir is required');
|
|
295
298
|
}
|
|
296
299
|
|
|
300
|
+
const clientRuntime = resolveClientRuntime(options);
|
|
301
|
+
|
|
297
302
|
setAppContext({ db: options.db ?? null });
|
|
298
303
|
|
|
299
304
|
const app = express();
|
|
@@ -378,7 +383,9 @@ function createApp(options = {}) {
|
|
|
378
383
|
etag: true
|
|
379
384
|
}));
|
|
380
385
|
}
|
|
381
|
-
|
|
386
|
+
|
|
387
|
+
mountClientRuntime(app, clientRuntime);
|
|
388
|
+
|
|
382
389
|
// Configure Nunjucks
|
|
383
390
|
const templateDirs = viewsDir ? [pagesDir, viewsDir] : [pagesDir];
|
|
384
391
|
|
|
@@ -439,7 +446,8 @@ function createApp(options = {}) {
|
|
|
439
446
|
middlewares,
|
|
440
447
|
pluginManager,
|
|
441
448
|
silent: isTest,
|
|
442
|
-
db: options.db ?? null
|
|
449
|
+
db: options.db ?? null,
|
|
450
|
+
clientRuntime,
|
|
443
451
|
});
|
|
444
452
|
|
|
445
453
|
// Set route metadata in plugin manager
|
|
@@ -480,6 +488,7 @@ function createApp(options = {}) {
|
|
|
480
488
|
authMiddleware,
|
|
481
489
|
pluginManager,
|
|
482
490
|
options,
|
|
491
|
+
clientRuntime,
|
|
483
492
|
});
|
|
484
493
|
}
|
|
485
494
|
|
|
@@ -66,8 +66,9 @@ project/
|
|
|
66
66
|
| `timeout` | e.g. `'30s'` or `false` |
|
|
67
67
|
| `helmet` | `true` / `false` / object |
|
|
68
68
|
| `assets` | `{ version, manifestPath, prefix }` for `fsy.asset` / `fsy.css` / `fsy.js` |
|
|
69
|
+
| `clientRuntime` | Opt-in `{ alpine?, swup? }` — serves `/__webspresso/client-runtime/*`, template `clientRuntime`, partial `views/partials/webspresso-client-runtime.njk`, `<main id="swup">` when swup on. Env: `WEBSPRESSO_ALPINE`, `WEBSPRESSO_SWUP`. |
|
|
69
70
|
| `auth` | Auth manager from `createAuth` (session routes) |
|
|
70
|
-
| `setupRoutes(app, ctx)` | **Register custom Express routes here** — runs **after** file routes and plugins’ `onRoutesReady`, **before** 404. Do not rely on `app.get` *after* `createApp` returns unless routes are appended before the 404 middleware (see [`src/server.js`](../../../src/server.js)). |
|
|
71
|
+
| `setupRoutes(app, ctx)` | **Register custom Express routes here** — runs **after** file routes and plugins’ `onRoutesReady`, **before** 404. `ctx.clientRuntime` included. Do not rely on `app.get` *after* `createApp` returns unless routes are appended before the 404 middleware (see [`src/server.js`](../../../src/server.js)). |
|
|
71
72
|
|
|
72
73
|
**Returns:** `{ app, nunjucksEnv, pluginManager, authMiddleware? }` (and related).
|
|
73
74
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{# Served by createApp when clientRuntime.alpine / .swup are enabled. See src/client-runtime/. #}
|
|
2
|
+
{% if clientRuntime and clientRuntime.alpine %}
|
|
3
|
+
<style>[x-cloak] { display: none !important; }</style>
|
|
4
|
+
<script defer src="/__webspresso/client-runtime/alpine.min.js"></script>
|
|
5
|
+
{% endif %}
|
|
6
|
+
{% if clientRuntime and clientRuntime.swup %}
|
|
7
|
+
<script defer src="/__webspresso/client-runtime/swup.umd.js"></script>
|
|
8
|
+
<script defer src="/__webspresso/client-runtime/swup-head-plugin.umd.js"></script>
|
|
9
|
+
<script defer src="/__webspresso/client-runtime/swup-scripts-plugin.umd.js"></script>
|
|
10
|
+
{% if clientRuntime.alpine %}
|
|
11
|
+
<script defer src="/__webspresso/client-runtime/bootstrap-alpine-swup.js"></script>
|
|
12
|
+
{% else %}
|
|
13
|
+
<script defer src="/__webspresso/client-runtime/bootstrap-swup.js"></script>
|
|
14
|
+
{% endif %}
|
|
15
|
+
{% endif %}
|