@foxford/den 3.1.3 → 3.2.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/bin/den.cjs +3 -3
- package/bin/den.js +1 -1
- package/builder/index.cjs +2 -2
- package/builder/index.d.cts +22 -2
- package/builder/index.d.ts +22 -2
- package/builder/index.js +1 -1
- package/{chunk-YECHY3NR.js → chunk-BMI5HNGE.js} +123 -92
- package/{chunk-ZBB5SBEV.cjs → chunk-RRR2DJMN.cjs} +123 -92
- package/island/index.cjs +17 -1
- package/island/index.d.cts +21 -1
- package/island/index.d.ts +21 -1
- package/island/index.js +16 -0
- package/package.json +6 -6
- package/serve/index.cjs +51 -1
- package/serve/index.d.cts +24 -2
- package/serve/index.d.ts +24 -2
- package/serve/index.js +50 -0
package/bin/den.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var _chunkRRR2DJMNcjs = require('../chunk-RRR2DJMN.cjs');
|
|
5
5
|
require('../chunk-A6ZPAM6Z.cjs');
|
|
6
6
|
|
|
7
7
|
|
|
@@ -69,11 +69,11 @@ function run(argv) {
|
|
|
69
69
|
}
|
|
70
70
|
const target = { base: options["base"], root: (_a = options["root"]) != null ? _a : process.cwd() };
|
|
71
71
|
if (command === "build") {
|
|
72
|
-
yield
|
|
72
|
+
yield _chunkRRR2DJMNcjs.buildApp.call(void 0, target);
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
if (command === "dev") {
|
|
76
|
-
yield
|
|
76
|
+
yield _chunkRRR2DJMNcjs.devApp.call(void 0, target);
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
79
|
fail(`\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \xAB${command}\xBB`);
|
package/bin/den.js
CHANGED
package/builder/index.cjs
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkRRR2DJMNcjs = require('../chunk-RRR2DJMN.cjs');
|
|
6
6
|
require('../chunk-A6ZPAM6Z.cjs');
|
|
7
7
|
require('../chunk-T5QVCXVB.cjs');
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
exports.buildApp =
|
|
12
|
+
exports.buildApp = _chunkRRR2DJMNcjs.buildApp; exports.defineAppConfig = _chunkRRR2DJMNcjs.defineAppConfig; exports.devApp = _chunkRRR2DJMNcjs.devApp;
|
package/builder/index.d.cts
CHANGED
|
@@ -53,6 +53,14 @@ interface AppServerConfig {
|
|
|
53
53
|
* по parent-chain на любом рендере.
|
|
54
54
|
*/
|
|
55
55
|
container?: (container: Container) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Порт процесса приложения.
|
|
58
|
+
*
|
|
59
|
+
* `PORT` в окружении важнее: порт назначает тот, кто процесс поднимает, — в проде оркестратор.
|
|
60
|
+
* Объявление здесь — умолчание самого приложения, чтобы адрес не приходилось повторять в каждой
|
|
61
|
+
* команде запуска и в префиксе артефактов.
|
|
62
|
+
*/
|
|
63
|
+
port?: number;
|
|
56
64
|
/**
|
|
57
65
|
* Расширение сетевого инстанса до начала прослушивания: Sentry, метрики, cors.
|
|
58
66
|
*
|
|
@@ -99,18 +107,30 @@ type BuildMode = 'production' | 'development';
|
|
|
99
107
|
interface BuildAppOptions {
|
|
100
108
|
/** Корень приложения. */
|
|
101
109
|
root: string;
|
|
102
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Префикс адресов артефактов: в проде CDN.
|
|
112
|
+
*
|
|
113
|
+
* Не задан — в разработке подставляется адрес самого приложения (артефакты отдаёт оно),
|
|
114
|
+
* в проде корень.
|
|
115
|
+
*/
|
|
103
116
|
base?: string;
|
|
104
117
|
/** Режим сборки; по умолчанию `production`. */
|
|
105
118
|
mode?: BuildMode;
|
|
106
119
|
}
|
|
120
|
+
/** Чем сборка закончилась — то, что нужно знать поднимающему процесс. */
|
|
121
|
+
interface AppBuildResult {
|
|
122
|
+
/** Префикс адресов артефактов, с которым собран клиентский выход. */
|
|
123
|
+
base: string;
|
|
124
|
+
/** Порт, который процесс будет слушать: окружение, затем умолчание приложения. */
|
|
125
|
+
port: number;
|
|
126
|
+
}
|
|
107
127
|
/**
|
|
108
128
|
* Собирает приложение целиком.
|
|
109
129
|
*
|
|
110
130
|
* @param options - Корень приложения и префикс адресов артефактов
|
|
111
131
|
* @throws Если конфига нет либо пресет сообщил, что ему не хватает объявленного
|
|
112
132
|
*/
|
|
113
|
-
declare function buildApp(options: BuildAppOptions): Promise<
|
|
133
|
+
declare function buildApp(options: BuildAppOptions): Promise<AppBuildResult>;
|
|
114
134
|
|
|
115
135
|
/** Наблюдение берёт те же опции, что сборка: путь у них один. */
|
|
116
136
|
type DevAppOptions = BuildAppOptions;
|
package/builder/index.d.ts
CHANGED
|
@@ -53,6 +53,14 @@ interface AppServerConfig {
|
|
|
53
53
|
* по parent-chain на любом рендере.
|
|
54
54
|
*/
|
|
55
55
|
container?: (container: Container) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Порт процесса приложения.
|
|
58
|
+
*
|
|
59
|
+
* `PORT` в окружении важнее: порт назначает тот, кто процесс поднимает, — в проде оркестратор.
|
|
60
|
+
* Объявление здесь — умолчание самого приложения, чтобы адрес не приходилось повторять в каждой
|
|
61
|
+
* команде запуска и в префиксе артефактов.
|
|
62
|
+
*/
|
|
63
|
+
port?: number;
|
|
56
64
|
/**
|
|
57
65
|
* Расширение сетевого инстанса до начала прослушивания: Sentry, метрики, cors.
|
|
58
66
|
*
|
|
@@ -99,18 +107,30 @@ type BuildMode = 'production' | 'development';
|
|
|
99
107
|
interface BuildAppOptions {
|
|
100
108
|
/** Корень приложения. */
|
|
101
109
|
root: string;
|
|
102
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Префикс адресов артефактов: в проде CDN.
|
|
112
|
+
*
|
|
113
|
+
* Не задан — в разработке подставляется адрес самого приложения (артефакты отдаёт оно),
|
|
114
|
+
* в проде корень.
|
|
115
|
+
*/
|
|
103
116
|
base?: string;
|
|
104
117
|
/** Режим сборки; по умолчанию `production`. */
|
|
105
118
|
mode?: BuildMode;
|
|
106
119
|
}
|
|
120
|
+
/** Чем сборка закончилась — то, что нужно знать поднимающему процесс. */
|
|
121
|
+
interface AppBuildResult {
|
|
122
|
+
/** Префикс адресов артефактов, с которым собран клиентский выход. */
|
|
123
|
+
base: string;
|
|
124
|
+
/** Порт, который процесс будет слушать: окружение, затем умолчание приложения. */
|
|
125
|
+
port: number;
|
|
126
|
+
}
|
|
107
127
|
/**
|
|
108
128
|
* Собирает приложение целиком.
|
|
109
129
|
*
|
|
110
130
|
* @param options - Корень приложения и префикс адресов артефактов
|
|
111
131
|
* @throws Если конфига нет либо пресет сообщил, что ему не хватает объявленного
|
|
112
132
|
*/
|
|
113
|
-
declare function buildApp(options: BuildAppOptions): Promise<
|
|
133
|
+
declare function buildApp(options: BuildAppOptions): Promise<AppBuildResult>;
|
|
114
134
|
|
|
115
135
|
/** Наблюдение берёт те же опции, что сборка: путь у них один. */
|
|
116
136
|
type DevAppOptions = BuildAppOptions;
|
package/builder/index.js
CHANGED
|
@@ -35,7 +35,7 @@ function collectAssets(outDir, base) {
|
|
|
35
35
|
const css = [];
|
|
36
36
|
const js = [];
|
|
37
37
|
const seen = /* @__PURE__ */ new Set();
|
|
38
|
-
const prefix = base.endsWith("/") ? base : `${base}/`;
|
|
38
|
+
const prefix = base === "" ? "" : base.endsWith("/") ? base : `${base}/`;
|
|
39
39
|
function walk(key) {
|
|
40
40
|
var _a, _b;
|
|
41
41
|
if (seen.has(key)) {
|
|
@@ -64,6 +64,10 @@ function collectAssets(outDir, base) {
|
|
|
64
64
|
|
|
65
65
|
// src/serve/port.ts
|
|
66
66
|
var DEFAULT_PORT = 3e3;
|
|
67
|
+
function resolvePort(declared) {
|
|
68
|
+
var _a, _b;
|
|
69
|
+
return Number((_b = (_a = process.env["PORT"]) != null ? _a : declared) != null ? _b : DEFAULT_PORT);
|
|
70
|
+
}
|
|
67
71
|
|
|
68
72
|
// src/builder/entries.ts
|
|
69
73
|
var VIRTUAL_CLIENT = "virtual:den/client";
|
|
@@ -102,27 +106,29 @@ if (server.render) {
|
|
|
102
106
|
export default descriptor
|
|
103
107
|
`;
|
|
104
108
|
}
|
|
105
|
-
function generateServerProcess(configPath) {
|
|
106
|
-
|
|
109
|
+
function generateServerProcess(configPath, base, mode) {
|
|
110
|
+
const assets = mode === "development" ? ` assets: { base: ${JSON.stringify(base)}, dir: fileURLToPath(new URL('./client', import.meta.url)) },
|
|
111
|
+
` : "";
|
|
112
|
+
return `${mode === "development" ? "import { fileURLToPath } from 'node:url'\n\n" : ""}import { serveApp } from '@foxford/den/serve'
|
|
107
113
|
|
|
108
114
|
import app from ${JSON.stringify(VIRTUAL_SERVER_APP)}
|
|
109
115
|
import config from ${JSON.stringify(configPath)}
|
|
110
116
|
|
|
111
117
|
await serveApp(app, {
|
|
112
|
-
configure: config.server?.configure,
|
|
118
|
+
${assets} configure: config.server?.configure,
|
|
113
119
|
host: process.env.HOST,
|
|
114
|
-
port: Number(process.env.PORT ?? ${String(DEFAULT_PORT)}),
|
|
120
|
+
port: Number(process.env.PORT ?? config.server?.port ?? ${String(DEFAULT_PORT)}),
|
|
115
121
|
})
|
|
116
122
|
`;
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
// src/builder/plugin.ts
|
|
120
126
|
function denAppModules(options) {
|
|
121
|
-
const { assets, config, configPath, entry } = options;
|
|
127
|
+
const { assets, base, config, configPath, entry, mode } = options;
|
|
122
128
|
const sources = {
|
|
123
129
|
[VIRTUAL_CLIENT]: () => generateClientEntry(entry, config.preset.clientEntry),
|
|
124
130
|
[VIRTUAL_SERVER_APP]: () => generateServerApp(entry, configPath, assets),
|
|
125
|
-
[VIRTUAL_SERVER_PROCESS]: () => generateServerProcess(configPath)
|
|
131
|
+
[VIRTUAL_SERVER_PROCESS]: () => generateServerProcess(configPath, base, mode)
|
|
126
132
|
};
|
|
127
133
|
return {
|
|
128
134
|
load: (id) => {
|
|
@@ -184,7 +190,9 @@ function viteConfigFor(options) {
|
|
|
184
190
|
const { assets, base, config, configPath, entryFileNames, input, mode, outDir, root, target } = options;
|
|
185
191
|
const entry = (_a = config.entry) != null ? _a : "src/index.ts";
|
|
186
192
|
const composed = {
|
|
187
|
-
|
|
193
|
+
// Сборщику относительный префикс, если своего нет: ленивые чанки тогда грузятся от адреса
|
|
194
|
+
// уже загруженного чанка, и подмена CDN уводит за собой и их
|
|
195
|
+
base: base === "" ? "./" : base,
|
|
188
196
|
build: {
|
|
189
197
|
// Клиентский выход чистит свой каталог сам, серверные — нет: они пишут рядом с уже
|
|
190
198
|
// собранным клиентским, и очистка снесла бы его вместе с манифестом
|
|
@@ -202,7 +210,10 @@ function viteConfigFor(options) {
|
|
|
202
210
|
// Режим объявляем явно: плагины сборщика выбирают по нему рантайм фреймворка, а через
|
|
203
211
|
// программный вызов он иначе не доедет.
|
|
204
212
|
mode,
|
|
205
|
-
plugins: [
|
|
213
|
+
plugins: [
|
|
214
|
+
...config.preset.plugins,
|
|
215
|
+
denAppModules({ assets, base, config, configPath, entry: path2.join(root, entry), mode })
|
|
216
|
+
],
|
|
206
217
|
root,
|
|
207
218
|
ssr: { noExternal: (_c = (_b = config.server) == null ? void 0 : _b.noExternal) != null ? _c : [] }
|
|
208
219
|
};
|
|
@@ -210,12 +221,14 @@ function viteConfigFor(options) {
|
|
|
210
221
|
}
|
|
211
222
|
function buildApp(options) {
|
|
212
223
|
return __async(this, null, function* () {
|
|
213
|
-
var _a, _b;
|
|
214
|
-
const {
|
|
224
|
+
var _a, _b, _c, _d;
|
|
225
|
+
const { mode = "production" } = options;
|
|
215
226
|
process.env["NODE_ENV"] = mode;
|
|
216
227
|
const root = path2.resolve(options.root);
|
|
217
228
|
const { config, configPath } = yield loadAppConfig(root);
|
|
218
|
-
const
|
|
229
|
+
const port = resolvePort((_a = config.server) == null ? void 0 : _a.port);
|
|
230
|
+
const base = (_b = options.base) != null ? _b : "";
|
|
231
|
+
const refusal = (_d = (_c = config.preset).validate) == null ? void 0 : _d.call(_c, dependenciesOf(root));
|
|
219
232
|
if (refusal !== void 0 && refusal !== null) {
|
|
220
233
|
throw new Error(`den: ${refusal}`);
|
|
221
234
|
}
|
|
@@ -246,11 +259,11 @@ function buildApp(options) {
|
|
|
246
259
|
target: "server"
|
|
247
260
|
}))
|
|
248
261
|
);
|
|
262
|
+
return { base, port };
|
|
249
263
|
});
|
|
250
264
|
}
|
|
251
265
|
|
|
252
266
|
// src/builder/dev.ts
|
|
253
|
-
import { spawn } from "child_process";
|
|
254
267
|
import fs3 from "fs";
|
|
255
268
|
import path3 from "path";
|
|
256
269
|
import { pathToFileURL } from "url";
|
|
@@ -284,7 +297,7 @@ function assetsSummary(assets) {
|
|
|
284
297
|
}
|
|
285
298
|
function bannerLines(facts2) {
|
|
286
299
|
var _a;
|
|
287
|
-
const port = (_a =
|
|
300
|
+
const port = String((_a = facts2.port) != null ? _a : "");
|
|
288
301
|
const network = networkAddress();
|
|
289
302
|
const label = (title) => ` \u2192 ${title.padEnd(PAD)}`;
|
|
290
303
|
return [
|
|
@@ -296,7 +309,8 @@ function bannerLines(facts2) {
|
|
|
296
309
|
`${label("\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E")}http://localhost:${port}`,
|
|
297
310
|
...network === void 0 ? [] : [`${label("\u0432 \u0441\u0435\u0442\u0438")}http://${network}:${port}`],
|
|
298
311
|
`${label("\u0440\u0443\u0447\u043A\u0438")}${ENDPOINTS.join(" ")}`,
|
|
299
|
-
`${label("\u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u044B")}${
|
|
312
|
+
...facts2.base === void 0 ? [] : [`${label("\u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u044B")}${facts2.base}`],
|
|
313
|
+
`${label("\u0447\u0430\u043D\u043A\u0438")}${assetsSummary(facts2.assets)}`,
|
|
300
314
|
`${label("\u0432\u044B\u0445\u043E\u0434\u044B")}${OUTPUTS.join(", ")}`,
|
|
301
315
|
`${label("\u043F\u0440\u0430\u0432\u043A\u0438")}${facts2.watched.join(", ")}`,
|
|
302
316
|
`${label("\u043A\u043B\u0430\u0432\u0438\u0448\u0438")}${SHORTCUTS.map(({ key, title }) => `${key} \u2014 ${title}`).join(", ")}`,
|
|
@@ -307,6 +321,63 @@ function helpLines() {
|
|
|
307
321
|
return ["", " \u043A\u043B\u0430\u0432\u0438\u0448\u0430 + enter:", ...SHORTCUTS.map(({ key, title }) => ` ${key} ${title}`), ""];
|
|
308
322
|
}
|
|
309
323
|
|
|
324
|
+
// src/builder/process.ts
|
|
325
|
+
import { spawn } from "child_process";
|
|
326
|
+
var SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
327
|
+
function signalGroup(pid, signal) {
|
|
328
|
+
try {
|
|
329
|
+
process.kill(-pid, signal);
|
|
330
|
+
} catch (e) {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function appProcess(entry) {
|
|
334
|
+
let child = null;
|
|
335
|
+
function stop() {
|
|
336
|
+
return __async(this, null, function* () {
|
|
337
|
+
const running = child;
|
|
338
|
+
child = null;
|
|
339
|
+
if (!running || running.exitCode !== null || running.pid === void 0) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const { pid } = running;
|
|
343
|
+
yield new Promise((resolve) => {
|
|
344
|
+
const forced = setTimeout(() => signalGroup(pid, "SIGKILL"), SHUTDOWN_TIMEOUT_MS);
|
|
345
|
+
running.once("exit", () => {
|
|
346
|
+
clearTimeout(forced);
|
|
347
|
+
resolve();
|
|
348
|
+
});
|
|
349
|
+
signalGroup(pid, "SIGTERM");
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
function start(env) {
|
|
354
|
+
const started = spawn(process.execPath, [...process.execArgv, entry], {
|
|
355
|
+
detached: true,
|
|
356
|
+
env: __spreadValues(__spreadValues({}, process.env), env),
|
|
357
|
+
stdio: "inherit"
|
|
358
|
+
});
|
|
359
|
+
child = started;
|
|
360
|
+
started.on("exit", (code, signal) => {
|
|
361
|
+
if (child !== started) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
child = null;
|
|
365
|
+
logger.error(`\u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441\u0430\u043C (\u043A\u043E\u0434 ${String(code)}, \u0441\u0438\u0433\u043D\u0430\u043B ${String(signal)})`);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
369
|
+
process.once(signal, () => {
|
|
370
|
+
void stop().then(() => process.exit(0));
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
process.on("exit", () => {
|
|
374
|
+
if ((child == null ? void 0 : child.pid) !== void 0) {
|
|
375
|
+
signalGroup(child.pid, "SIGTERM");
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
return { start, stop };
|
|
379
|
+
}
|
|
380
|
+
|
|
310
381
|
// src/builder/dev.ts
|
|
311
382
|
var SERVER_ENTRY = "build/server.mjs";
|
|
312
383
|
var WATCHED = ["src", "den.config.ts"];
|
|
@@ -315,7 +386,21 @@ function debugPattern(name) {
|
|
|
315
386
|
return (name === void 0 ? DEBUG_SCOPES : [`${name}:*`, ...DEBUG_SCOPES]).join(",");
|
|
316
387
|
}
|
|
317
388
|
var DEBOUNCE_MS = 150;
|
|
318
|
-
|
|
389
|
+
function watchSources(root, onChange) {
|
|
390
|
+
let pending = null;
|
|
391
|
+
for (const target of WATCHED) {
|
|
392
|
+
const watched = path3.join(root, target);
|
|
393
|
+
if (!fs3.existsSync(watched)) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
fs3.watch(watched, { recursive: true }, () => {
|
|
397
|
+
if (pending) {
|
|
398
|
+
clearTimeout(pending);
|
|
399
|
+
}
|
|
400
|
+
pending = setTimeout(onChange, DEBOUNCE_MS);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
319
404
|
function facts(root) {
|
|
320
405
|
return __async(this, null, function* () {
|
|
321
406
|
try {
|
|
@@ -338,52 +423,10 @@ function devApp(options) {
|
|
|
338
423
|
return __async(this, null, function* () {
|
|
339
424
|
logger.setDebug(true);
|
|
340
425
|
const root = path3.resolve(options.root);
|
|
341
|
-
const entry = path3.join(root, SERVER_ENTRY);
|
|
342
|
-
let child = null;
|
|
343
|
-
let pending = null;
|
|
344
426
|
let building = false;
|
|
345
427
|
let dirty = false;
|
|
346
428
|
const describe = describer(root);
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
process.kill(-pid, signal);
|
|
350
|
-
} catch (e) {
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
function stop() {
|
|
354
|
-
return __async(this, null, function* () {
|
|
355
|
-
const running = child;
|
|
356
|
-
child = null;
|
|
357
|
-
if (!running || running.exitCode !== null || running.pid === void 0) {
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const { pid } = running;
|
|
361
|
-
yield new Promise((resolve) => {
|
|
362
|
-
const forced = setTimeout(() => signalGroup(pid, "SIGKILL"), SHUTDOWN_TIMEOUT_MS);
|
|
363
|
-
running.once("exit", () => {
|
|
364
|
-
clearTimeout(forced);
|
|
365
|
-
resolve();
|
|
366
|
-
});
|
|
367
|
-
signalGroup(pid, "SIGTERM");
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
function start(name) {
|
|
372
|
-
var _a;
|
|
373
|
-
const started = spawn(process.execPath, [...process.execArgv, entry], {
|
|
374
|
-
detached: true,
|
|
375
|
-
env: __spreadProps(__spreadValues({}, process.env), { DEBUG: (_a = process.env["DEBUG"]) != null ? _a : debugPattern(name) }),
|
|
376
|
-
stdio: "inherit"
|
|
377
|
-
});
|
|
378
|
-
child = started;
|
|
379
|
-
started.on("exit", (code, signal) => {
|
|
380
|
-
if (child !== started) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
child = null;
|
|
384
|
-
logger.error(`\u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441\u0430\u043C (\u043A\u043E\u0434 ${String(code)}, \u0441\u0438\u0433\u043D\u0430\u043B ${String(signal)})`);
|
|
385
|
-
});
|
|
386
|
-
}
|
|
429
|
+
const app = appProcess(path3.join(root, SERVER_ENTRY));
|
|
387
430
|
function show(lines) {
|
|
388
431
|
process.stdout.write(`${lines.join("\n")}
|
|
389
432
|
`);
|
|
@@ -402,26 +445,27 @@ function devApp(options) {
|
|
|
402
445
|
show(helpLines());
|
|
403
446
|
}
|
|
404
447
|
if (key === "q") {
|
|
405
|
-
void stop().then(() => process.exit(0));
|
|
448
|
+
void app.stop().then(() => process.exit(0));
|
|
406
449
|
}
|
|
407
450
|
});
|
|
408
451
|
}
|
|
409
452
|
function rebuild() {
|
|
410
453
|
return __async(this, null, function* () {
|
|
454
|
+
var _a;
|
|
411
455
|
if (building) {
|
|
412
456
|
dirty = true;
|
|
413
|
-
return
|
|
457
|
+
return null;
|
|
414
458
|
}
|
|
415
459
|
building = true;
|
|
416
|
-
let
|
|
460
|
+
let built = null;
|
|
417
461
|
try {
|
|
418
462
|
do {
|
|
419
463
|
dirty = false;
|
|
420
|
-
yield stop();
|
|
464
|
+
yield app.stop();
|
|
421
465
|
try {
|
|
422
|
-
yield buildApp(__spreadProps(__spreadValues({}, options), { mode: "development" }));
|
|
423
|
-
start((yield describe()).name);
|
|
424
|
-
|
|
466
|
+
const result = yield buildApp(__spreadProps(__spreadValues({}, options), { mode: "development" }));
|
|
467
|
+
app.start({ DEBUG: (_a = process.env["DEBUG"]) != null ? _a : debugPattern((yield describe()).name) });
|
|
468
|
+
built = result;
|
|
425
469
|
} catch (error) {
|
|
426
470
|
logger.error("\u0441\u0431\u043E\u0440\u043A\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430 \u2014 \u0441\u0435\u0440\u0432\u0438\u0441 \u041D\u0415 \u041F\u041E\u0414\u041D\u042F\u0422, \u0436\u0434\u0443 \u043F\u0440\u0430\u0432\u043A\u0438:", error);
|
|
427
471
|
}
|
|
@@ -429,38 +473,25 @@ function devApp(options) {
|
|
|
429
473
|
} finally {
|
|
430
474
|
building = false;
|
|
431
475
|
}
|
|
432
|
-
return
|
|
476
|
+
return built;
|
|
433
477
|
});
|
|
434
478
|
}
|
|
435
479
|
const startedAt = Date.now();
|
|
436
|
-
const
|
|
480
|
+
const address = yield rebuild();
|
|
437
481
|
const elapsed = Date.now() - startedAt;
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
if (built) {
|
|
451
|
-
show(bannerLines(__spreadValues({ elapsedMs: elapsed, mode: "development", watched: WATCHED }, yield describe())));
|
|
482
|
+
watchSources(root, () => void rebuild());
|
|
483
|
+
if (address !== null) {
|
|
484
|
+
show(
|
|
485
|
+
bannerLines(__spreadValues({
|
|
486
|
+
base: address.base,
|
|
487
|
+
elapsedMs: elapsed,
|
|
488
|
+
mode: "development",
|
|
489
|
+
port: address.port,
|
|
490
|
+
watched: WATCHED
|
|
491
|
+
}, yield describe()))
|
|
492
|
+
);
|
|
452
493
|
}
|
|
453
494
|
listenForKeys();
|
|
454
|
-
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
455
|
-
process.once(signal, () => {
|
|
456
|
-
void stop().then(() => process.exit(0));
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
process.on("exit", () => {
|
|
460
|
-
if ((child == null ? void 0 : child.pid) !== void 0) {
|
|
461
|
-
signalGroup(child.pid, "SIGTERM");
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
495
|
});
|
|
465
496
|
}
|
|
466
497
|
|
|
@@ -35,7 +35,7 @@ function collectAssets(outDir, base) {
|
|
|
35
35
|
const css = [];
|
|
36
36
|
const js = [];
|
|
37
37
|
const seen = /* @__PURE__ */ new Set();
|
|
38
|
-
const prefix = base.endsWith("/") ? base : `${base}/`;
|
|
38
|
+
const prefix = base === "" ? "" : base.endsWith("/") ? base : `${base}/`;
|
|
39
39
|
function walk(key) {
|
|
40
40
|
var _a, _b;
|
|
41
41
|
if (seen.has(key)) {
|
|
@@ -64,6 +64,10 @@ function collectAssets(outDir, base) {
|
|
|
64
64
|
|
|
65
65
|
// src/serve/port.ts
|
|
66
66
|
var DEFAULT_PORT = 3e3;
|
|
67
|
+
function resolvePort(declared) {
|
|
68
|
+
var _a, _b;
|
|
69
|
+
return Number((_b = (_a = process.env["PORT"]) != null ? _a : declared) != null ? _b : DEFAULT_PORT);
|
|
70
|
+
}
|
|
67
71
|
|
|
68
72
|
// src/builder/entries.ts
|
|
69
73
|
var VIRTUAL_CLIENT = "virtual:den/client";
|
|
@@ -102,27 +106,29 @@ if (server.render) {
|
|
|
102
106
|
export default descriptor
|
|
103
107
|
`;
|
|
104
108
|
}
|
|
105
|
-
function generateServerProcess(configPath) {
|
|
106
|
-
|
|
109
|
+
function generateServerProcess(configPath, base, mode) {
|
|
110
|
+
const assets = mode === "development" ? ` assets: { base: ${JSON.stringify(base)}, dir: fileURLToPath(new URL('./client', import.meta.url)) },
|
|
111
|
+
` : "";
|
|
112
|
+
return `${mode === "development" ? "import { fileURLToPath } from 'node:url'\n\n" : ""}import { serveApp } from '@foxford/den/serve'
|
|
107
113
|
|
|
108
114
|
import app from ${JSON.stringify(VIRTUAL_SERVER_APP)}
|
|
109
115
|
import config from ${JSON.stringify(configPath)}
|
|
110
116
|
|
|
111
117
|
await serveApp(app, {
|
|
112
|
-
configure: config.server?.configure,
|
|
118
|
+
${assets} configure: config.server?.configure,
|
|
113
119
|
host: process.env.HOST,
|
|
114
|
-
port: Number(process.env.PORT ?? ${String(DEFAULT_PORT)}),
|
|
120
|
+
port: Number(process.env.PORT ?? config.server?.port ?? ${String(DEFAULT_PORT)}),
|
|
115
121
|
})
|
|
116
122
|
`;
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
// src/builder/plugin.ts
|
|
120
126
|
function denAppModules(options) {
|
|
121
|
-
const { assets, config, configPath, entry } = options;
|
|
127
|
+
const { assets, base, config, configPath, entry, mode } = options;
|
|
122
128
|
const sources = {
|
|
123
129
|
[VIRTUAL_CLIENT]: () => generateClientEntry(entry, config.preset.clientEntry),
|
|
124
130
|
[VIRTUAL_SERVER_APP]: () => generateServerApp(entry, configPath, assets),
|
|
125
|
-
[VIRTUAL_SERVER_PROCESS]: () => generateServerProcess(configPath)
|
|
131
|
+
[VIRTUAL_SERVER_PROCESS]: () => generateServerProcess(configPath, base, mode)
|
|
126
132
|
};
|
|
127
133
|
return {
|
|
128
134
|
load: (id) => {
|
|
@@ -184,7 +190,9 @@ function viteConfigFor(options) {
|
|
|
184
190
|
const { assets, base, config, configPath, entryFileNames, input, mode, outDir, root, target } = options;
|
|
185
191
|
const entry = (_a = config.entry) != null ? _a : "src/index.ts";
|
|
186
192
|
const composed = {
|
|
187
|
-
|
|
193
|
+
// Сборщику относительный префикс, если своего нет: ленивые чанки тогда грузятся от адреса
|
|
194
|
+
// уже загруженного чанка, и подмена CDN уводит за собой и их
|
|
195
|
+
base: base === "" ? "./" : base,
|
|
188
196
|
build: {
|
|
189
197
|
// Клиентский выход чистит свой каталог сам, серверные — нет: они пишут рядом с уже
|
|
190
198
|
// собранным клиентским, и очистка снесла бы его вместе с манифестом
|
|
@@ -202,7 +210,10 @@ function viteConfigFor(options) {
|
|
|
202
210
|
// Режим объявляем явно: плагины сборщика выбирают по нему рантайм фреймворка, а через
|
|
203
211
|
// программный вызов он иначе не доедет.
|
|
204
212
|
mode,
|
|
205
|
-
plugins: [
|
|
213
|
+
plugins: [
|
|
214
|
+
...config.preset.plugins,
|
|
215
|
+
denAppModules({ assets, base, config, configPath, entry: _path2.default.join(root, entry), mode })
|
|
216
|
+
],
|
|
206
217
|
root,
|
|
207
218
|
ssr: { noExternal: (_c = (_b = config.server) == null ? void 0 : _b.noExternal) != null ? _c : [] }
|
|
208
219
|
};
|
|
@@ -210,12 +221,14 @@ function viteConfigFor(options) {
|
|
|
210
221
|
}
|
|
211
222
|
function buildApp(options) {
|
|
212
223
|
return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
|
|
213
|
-
var _a, _b;
|
|
214
|
-
const {
|
|
224
|
+
var _a, _b, _c, _d;
|
|
225
|
+
const { mode = "production" } = options;
|
|
215
226
|
process.env["NODE_ENV"] = mode;
|
|
216
227
|
const root = _path2.default.resolve(options.root);
|
|
217
228
|
const { config, configPath } = yield loadAppConfig(root);
|
|
218
|
-
const
|
|
229
|
+
const port = resolvePort((_a = config.server) == null ? void 0 : _a.port);
|
|
230
|
+
const base = (_b = options.base) != null ? _b : "";
|
|
231
|
+
const refusal = (_d = (_c = config.preset).validate) == null ? void 0 : _d.call(_c, dependenciesOf(root));
|
|
219
232
|
if (refusal !== void 0 && refusal !== null) {
|
|
220
233
|
throw new Error(`den: ${refusal}`);
|
|
221
234
|
}
|
|
@@ -246,11 +259,11 @@ function buildApp(options) {
|
|
|
246
259
|
target: "server"
|
|
247
260
|
}))
|
|
248
261
|
);
|
|
262
|
+
return { base, port };
|
|
249
263
|
});
|
|
250
264
|
}
|
|
251
265
|
|
|
252
266
|
// src/builder/dev.ts
|
|
253
|
-
var _child_process = require('child_process');
|
|
254
267
|
|
|
255
268
|
|
|
256
269
|
var _url = require('url');
|
|
@@ -284,7 +297,7 @@ function assetsSummary(assets) {
|
|
|
284
297
|
}
|
|
285
298
|
function bannerLines(facts2) {
|
|
286
299
|
var _a;
|
|
287
|
-
const port = (_a =
|
|
300
|
+
const port = String((_a = facts2.port) != null ? _a : "");
|
|
288
301
|
const network = networkAddress();
|
|
289
302
|
const label = (title) => ` \u2192 ${title.padEnd(PAD)}`;
|
|
290
303
|
return [
|
|
@@ -296,7 +309,8 @@ function bannerLines(facts2) {
|
|
|
296
309
|
`${label("\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E")}http://localhost:${port}`,
|
|
297
310
|
...network === void 0 ? [] : [`${label("\u0432 \u0441\u0435\u0442\u0438")}http://${network}:${port}`],
|
|
298
311
|
`${label("\u0440\u0443\u0447\u043A\u0438")}${ENDPOINTS.join(" ")}`,
|
|
299
|
-
`${label("\u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u044B")}${
|
|
312
|
+
...facts2.base === void 0 ? [] : [`${label("\u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u044B")}${facts2.base}`],
|
|
313
|
+
`${label("\u0447\u0430\u043D\u043A\u0438")}${assetsSummary(facts2.assets)}`,
|
|
300
314
|
`${label("\u0432\u044B\u0445\u043E\u0434\u044B")}${OUTPUTS.join(", ")}`,
|
|
301
315
|
`${label("\u043F\u0440\u0430\u0432\u043A\u0438")}${facts2.watched.join(", ")}`,
|
|
302
316
|
`${label("\u043A\u043B\u0430\u0432\u0438\u0448\u0438")}${SHORTCUTS.map(({ key, title }) => `${key} \u2014 ${title}`).join(", ")}`,
|
|
@@ -307,6 +321,63 @@ function helpLines() {
|
|
|
307
321
|
return ["", " \u043A\u043B\u0430\u0432\u0438\u0448\u0430 + enter:", ...SHORTCUTS.map(({ key, title }) => ` ${key} ${title}`), ""];
|
|
308
322
|
}
|
|
309
323
|
|
|
324
|
+
// src/builder/process.ts
|
|
325
|
+
var _child_process = require('child_process');
|
|
326
|
+
var SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
327
|
+
function signalGroup(pid, signal) {
|
|
328
|
+
try {
|
|
329
|
+
process.kill(-pid, signal);
|
|
330
|
+
} catch (e) {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function appProcess(entry) {
|
|
334
|
+
let child = null;
|
|
335
|
+
function stop() {
|
|
336
|
+
return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
|
|
337
|
+
const running = child;
|
|
338
|
+
child = null;
|
|
339
|
+
if (!running || running.exitCode !== null || running.pid === void 0) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const { pid } = running;
|
|
343
|
+
yield new Promise((resolve) => {
|
|
344
|
+
const forced = setTimeout(() => signalGroup(pid, "SIGKILL"), SHUTDOWN_TIMEOUT_MS);
|
|
345
|
+
running.once("exit", () => {
|
|
346
|
+
clearTimeout(forced);
|
|
347
|
+
resolve();
|
|
348
|
+
});
|
|
349
|
+
signalGroup(pid, "SIGTERM");
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
function start(env) {
|
|
354
|
+
const started = _child_process.spawn.call(void 0, process.execPath, [...process.execArgv, entry], {
|
|
355
|
+
detached: true,
|
|
356
|
+
env: _chunkT5QVCXVBcjs.__spreadValues.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, process.env), env),
|
|
357
|
+
stdio: "inherit"
|
|
358
|
+
});
|
|
359
|
+
child = started;
|
|
360
|
+
started.on("exit", (code, signal) => {
|
|
361
|
+
if (child !== started) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
child = null;
|
|
365
|
+
_chunkA6ZPAM6Zcjs.logger.error(`\u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441\u0430\u043C (\u043A\u043E\u0434 ${String(code)}, \u0441\u0438\u0433\u043D\u0430\u043B ${String(signal)})`);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
369
|
+
process.once(signal, () => {
|
|
370
|
+
void stop().then(() => process.exit(0));
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
process.on("exit", () => {
|
|
374
|
+
if ((child == null ? void 0 : child.pid) !== void 0) {
|
|
375
|
+
signalGroup(child.pid, "SIGTERM");
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
return { start, stop };
|
|
379
|
+
}
|
|
380
|
+
|
|
310
381
|
// src/builder/dev.ts
|
|
311
382
|
var SERVER_ENTRY = "build/server.mjs";
|
|
312
383
|
var WATCHED = ["src", "den.config.ts"];
|
|
@@ -315,7 +386,21 @@ function debugPattern(name) {
|
|
|
315
386
|
return (name === void 0 ? DEBUG_SCOPES : [`${name}:*`, ...DEBUG_SCOPES]).join(",");
|
|
316
387
|
}
|
|
317
388
|
var DEBOUNCE_MS = 150;
|
|
318
|
-
|
|
389
|
+
function watchSources(root, onChange) {
|
|
390
|
+
let pending = null;
|
|
391
|
+
for (const target of WATCHED) {
|
|
392
|
+
const watched = _path2.default.join(root, target);
|
|
393
|
+
if (!_fs2.default.existsSync(watched)) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
_fs2.default.watch(watched, { recursive: true }, () => {
|
|
397
|
+
if (pending) {
|
|
398
|
+
clearTimeout(pending);
|
|
399
|
+
}
|
|
400
|
+
pending = setTimeout(onChange, DEBOUNCE_MS);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
319
404
|
function facts(root) {
|
|
320
405
|
return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
|
|
321
406
|
try {
|
|
@@ -338,52 +423,10 @@ function devApp(options) {
|
|
|
338
423
|
return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
|
|
339
424
|
_chunkA6ZPAM6Zcjs.logger.setDebug(true);
|
|
340
425
|
const root = _path2.default.resolve(options.root);
|
|
341
|
-
const entry = _path2.default.join(root, SERVER_ENTRY);
|
|
342
|
-
let child = null;
|
|
343
|
-
let pending = null;
|
|
344
426
|
let building = false;
|
|
345
427
|
let dirty = false;
|
|
346
428
|
const describe = describer(root);
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
process.kill(-pid, signal);
|
|
350
|
-
} catch (e) {
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
function stop() {
|
|
354
|
-
return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
|
|
355
|
-
const running = child;
|
|
356
|
-
child = null;
|
|
357
|
-
if (!running || running.exitCode !== null || running.pid === void 0) {
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const { pid } = running;
|
|
361
|
-
yield new Promise((resolve) => {
|
|
362
|
-
const forced = setTimeout(() => signalGroup(pid, "SIGKILL"), SHUTDOWN_TIMEOUT_MS);
|
|
363
|
-
running.once("exit", () => {
|
|
364
|
-
clearTimeout(forced);
|
|
365
|
-
resolve();
|
|
366
|
-
});
|
|
367
|
-
signalGroup(pid, "SIGTERM");
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
function start(name) {
|
|
372
|
-
var _a;
|
|
373
|
-
const started = _child_process.spawn.call(void 0, process.execPath, [...process.execArgv, entry], {
|
|
374
|
-
detached: true,
|
|
375
|
-
env: _chunkT5QVCXVBcjs.__spreadProps.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, process.env), { DEBUG: (_a = process.env["DEBUG"]) != null ? _a : debugPattern(name) }),
|
|
376
|
-
stdio: "inherit"
|
|
377
|
-
});
|
|
378
|
-
child = started;
|
|
379
|
-
started.on("exit", (code, signal) => {
|
|
380
|
-
if (child !== started) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
child = null;
|
|
384
|
-
_chunkA6ZPAM6Zcjs.logger.error(`\u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441\u0430\u043C (\u043A\u043E\u0434 ${String(code)}, \u0441\u0438\u0433\u043D\u0430\u043B ${String(signal)})`);
|
|
385
|
-
});
|
|
386
|
-
}
|
|
429
|
+
const app = appProcess(_path2.default.join(root, SERVER_ENTRY));
|
|
387
430
|
function show(lines) {
|
|
388
431
|
process.stdout.write(`${lines.join("\n")}
|
|
389
432
|
`);
|
|
@@ -402,26 +445,27 @@ function devApp(options) {
|
|
|
402
445
|
show(helpLines());
|
|
403
446
|
}
|
|
404
447
|
if (key === "q") {
|
|
405
|
-
void stop().then(() => process.exit(0));
|
|
448
|
+
void app.stop().then(() => process.exit(0));
|
|
406
449
|
}
|
|
407
450
|
});
|
|
408
451
|
}
|
|
409
452
|
function rebuild() {
|
|
410
453
|
return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
|
|
454
|
+
var _a;
|
|
411
455
|
if (building) {
|
|
412
456
|
dirty = true;
|
|
413
|
-
return
|
|
457
|
+
return null;
|
|
414
458
|
}
|
|
415
459
|
building = true;
|
|
416
|
-
let
|
|
460
|
+
let built = null;
|
|
417
461
|
try {
|
|
418
462
|
do {
|
|
419
463
|
dirty = false;
|
|
420
|
-
yield stop();
|
|
464
|
+
yield app.stop();
|
|
421
465
|
try {
|
|
422
|
-
yield buildApp(_chunkT5QVCXVBcjs.__spreadProps.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, options), { mode: "development" }));
|
|
423
|
-
start((yield describe()).name);
|
|
424
|
-
|
|
466
|
+
const result = yield buildApp(_chunkT5QVCXVBcjs.__spreadProps.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, options), { mode: "development" }));
|
|
467
|
+
app.start({ DEBUG: (_a = process.env["DEBUG"]) != null ? _a : debugPattern((yield describe()).name) });
|
|
468
|
+
built = result;
|
|
425
469
|
} catch (error) {
|
|
426
470
|
_chunkA6ZPAM6Zcjs.logger.error("\u0441\u0431\u043E\u0440\u043A\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430 \u2014 \u0441\u0435\u0440\u0432\u0438\u0441 \u041D\u0415 \u041F\u041E\u0414\u041D\u042F\u0422, \u0436\u0434\u0443 \u043F\u0440\u0430\u0432\u043A\u0438:", error);
|
|
427
471
|
}
|
|
@@ -429,38 +473,25 @@ function devApp(options) {
|
|
|
429
473
|
} finally {
|
|
430
474
|
building = false;
|
|
431
475
|
}
|
|
432
|
-
return
|
|
476
|
+
return built;
|
|
433
477
|
});
|
|
434
478
|
}
|
|
435
479
|
const startedAt = Date.now();
|
|
436
|
-
const
|
|
480
|
+
const address = yield rebuild();
|
|
437
481
|
const elapsed = Date.now() - startedAt;
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
if (built) {
|
|
451
|
-
show(bannerLines(_chunkT5QVCXVBcjs.__spreadValues.call(void 0, { elapsedMs: elapsed, mode: "development", watched: WATCHED }, yield describe())));
|
|
482
|
+
watchSources(root, () => void rebuild());
|
|
483
|
+
if (address !== null) {
|
|
484
|
+
show(
|
|
485
|
+
bannerLines(_chunkT5QVCXVBcjs.__spreadValues.call(void 0, {
|
|
486
|
+
base: address.base,
|
|
487
|
+
elapsedMs: elapsed,
|
|
488
|
+
mode: "development",
|
|
489
|
+
port: address.port,
|
|
490
|
+
watched: WATCHED
|
|
491
|
+
}, yield describe()))
|
|
492
|
+
);
|
|
452
493
|
}
|
|
453
494
|
listenForKeys();
|
|
454
|
-
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
455
|
-
process.once(signal, () => {
|
|
456
|
-
void stop().then(() => process.exit(0));
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
process.on("exit", () => {
|
|
460
|
-
if ((child == null ? void 0 : child.pid) !== void 0) {
|
|
461
|
-
signalGroup(child.pid, "SIGTERM");
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
495
|
});
|
|
465
496
|
}
|
|
466
497
|
|
package/island/index.cjs
CHANGED
|
@@ -37,6 +37,19 @@ function onIslandNavigate(node, onNavigate) {
|
|
|
37
37
|
node.removeEventListener(NAVIGATE_EVENT, listener);
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
+
var DOCUMENT_EVENT = "den:document";
|
|
41
|
+
function publishIslandDocument(node, contribution) {
|
|
42
|
+
node.dispatchEvent(new CustomEvent(DOCUMENT_EVENT, { bubbles: true, detail: contribution }));
|
|
43
|
+
}
|
|
44
|
+
function onIslandDocument(node, onDocument) {
|
|
45
|
+
const listener = (event) => {
|
|
46
|
+
onDocument(event.detail);
|
|
47
|
+
};
|
|
48
|
+
node.addEventListener(DOCUMENT_EVENT, listener);
|
|
49
|
+
return () => {
|
|
50
|
+
node.removeEventListener(DOCUMENT_EVENT, listener);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
40
53
|
|
|
41
54
|
// src/island/slot-address.ts
|
|
42
55
|
function slotAddress(app, slot) {
|
|
@@ -106,4 +119,7 @@ function describeIsland(descriptor, denState) {
|
|
|
106
119
|
|
|
107
120
|
|
|
108
121
|
|
|
109
|
-
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
exports.DOCUMENT_EVENT = DOCUMENT_EVENT; exports.NAVIGATE_EVENT = NAVIGATE_EVENT; exports.ViewLayerToken = _chunkXOJ2VWX4cjs.ViewLayerToken; exports.activateIslandViewModels = _chunkZOU6W6ZQcjs.activateIslandViewModels; exports.collectDocumentContribution = _chunkZOU6W6ZQcjs.collectDocumentContribution; exports.collectIslandState = _chunkZOU6W6ZQcjs.collectIslandState; exports.createIslandContainer = _chunkZOU6W6ZQcjs.createIslandContainer; exports.describeIsland = describeIsland; exports.getAppContainer = _chunkZOU6W6ZQcjs.getAppContainer; exports.getIslandsSnapshot = getIslandsSnapshot; exports.hydrateIslandState = _chunkZOU6W6ZQcjs.hydrateIslandState; exports.isDocumentSource = _chunkZOU6W6ZQcjs.isDocumentSource; exports.matchRoute = _chunkZOU6W6ZQcjs.matchRoute; exports.navigateIsland = navigateIsland; exports.normalizeRoutes = _chunkZOU6W6ZQcjs.normalizeRoutes; exports.onIslandDocument = onIslandDocument; exports.onIslandNavigate = onIslandNavigate; exports.pathPattern = _chunkZOU6W6ZQcjs.pathPattern; exports.publishIslandDocument = publishIslandDocument; exports.registerIsland = registerIsland; exports.renderApp = _chunkZOU6W6ZQcjs.renderApp; exports.resolveEagerUnits = _chunkZOU6W6ZQcjs.resolveEagerUnits; exports.resolveViewAdapter = _chunkXOJ2VWX4cjs.resolveViewAdapter; exports.slotAddress = slotAddress; exports.subscribeIslands = subscribeIslands; exports.viewAdapterOf = _chunkXOJ2VWX4cjs.viewAdapterOf;
|
package/island/index.d.cts
CHANGED
|
@@ -182,6 +182,26 @@ declare function navigateIsland(node: EventTarget, route: IslandRoute): void;
|
|
|
182
182
|
* @returns Отписка
|
|
183
183
|
*/
|
|
184
184
|
declare function onIslandNavigate(node: EventTarget, onNavigate: (route: IslandRoute) => void): () => void;
|
|
185
|
+
/** Имя события, которым приложение сообщает свой вклад в документ. */
|
|
186
|
+
declare const DOCUMENT_EVENT = "den:document";
|
|
187
|
+
/**
|
|
188
|
+
* Сообщает хосту вклад приложения в документ.
|
|
189
|
+
*
|
|
190
|
+
* Зовётся ПОСЛЕ активации: вклад считается по состоянию VM, а состояние нового адреса приезжает
|
|
191
|
+
* асинхронно. Событие всплывает — хост слушает документ одним слушателем и не ведёт учёт узлов.
|
|
192
|
+
*
|
|
193
|
+
* @param node - Узел приложения
|
|
194
|
+
* @param contribution - Вклад в документ, собранный движком
|
|
195
|
+
*/
|
|
196
|
+
declare function publishIslandDocument(node: EventTarget, contribution: DocumentContribution): void;
|
|
197
|
+
/**
|
|
198
|
+
* Подписывает хост на вклад приложений в документ.
|
|
199
|
+
*
|
|
200
|
+
* @param node - Узел, который слышит всплывающие события (обычно документ)
|
|
201
|
+
* @param onDocument - Что делать с вкладом
|
|
202
|
+
* @returns Отписка
|
|
203
|
+
*/
|
|
204
|
+
declare function onIslandDocument(node: EventTarget, onDocument: (contribution: DocumentContribution) => void): () => void;
|
|
185
205
|
|
|
186
206
|
/** Запрос на рендер приложения. */
|
|
187
207
|
interface RenderAppRequest {
|
|
@@ -297,4 +317,4 @@ declare function getAppContainer(): Container;
|
|
|
297
317
|
*/
|
|
298
318
|
declare function slotAddress(app: string, slot: string): string;
|
|
299
319
|
|
|
300
|
-
export { AppAssets, type CreateIslandContainerOptions, type DocumentContribution, type DocumentHead, type DocumentSource, IslandDescriptor, type IslandRecord, type IslandRoute, NAVIGATE_EVENT, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, isDocumentSource, navigateIsland, onIslandNavigate, registerIsland, renderApp, resolveEagerUnits, slotAddress, subscribeIslands };
|
|
320
|
+
export { AppAssets, type CreateIslandContainerOptions, DOCUMENT_EVENT, type DocumentContribution, type DocumentHead, type DocumentSource, IslandDescriptor, type IslandRecord, type IslandRoute, NAVIGATE_EVENT, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, isDocumentSource, navigateIsland, onIslandDocument, onIslandNavigate, publishIslandDocument, registerIsland, renderApp, resolveEagerUnits, slotAddress, subscribeIslands };
|
package/island/index.d.ts
CHANGED
|
@@ -182,6 +182,26 @@ declare function navigateIsland(node: EventTarget, route: IslandRoute): void;
|
|
|
182
182
|
* @returns Отписка
|
|
183
183
|
*/
|
|
184
184
|
declare function onIslandNavigate(node: EventTarget, onNavigate: (route: IslandRoute) => void): () => void;
|
|
185
|
+
/** Имя события, которым приложение сообщает свой вклад в документ. */
|
|
186
|
+
declare const DOCUMENT_EVENT = "den:document";
|
|
187
|
+
/**
|
|
188
|
+
* Сообщает хосту вклад приложения в документ.
|
|
189
|
+
*
|
|
190
|
+
* Зовётся ПОСЛЕ активации: вклад считается по состоянию VM, а состояние нового адреса приезжает
|
|
191
|
+
* асинхронно. Событие всплывает — хост слушает документ одним слушателем и не ведёт учёт узлов.
|
|
192
|
+
*
|
|
193
|
+
* @param node - Узел приложения
|
|
194
|
+
* @param contribution - Вклад в документ, собранный движком
|
|
195
|
+
*/
|
|
196
|
+
declare function publishIslandDocument(node: EventTarget, contribution: DocumentContribution): void;
|
|
197
|
+
/**
|
|
198
|
+
* Подписывает хост на вклад приложений в документ.
|
|
199
|
+
*
|
|
200
|
+
* @param node - Узел, который слышит всплывающие события (обычно документ)
|
|
201
|
+
* @param onDocument - Что делать с вкладом
|
|
202
|
+
* @returns Отписка
|
|
203
|
+
*/
|
|
204
|
+
declare function onIslandDocument(node: EventTarget, onDocument: (contribution: DocumentContribution) => void): () => void;
|
|
185
205
|
|
|
186
206
|
/** Запрос на рендер приложения. */
|
|
187
207
|
interface RenderAppRequest {
|
|
@@ -297,4 +317,4 @@ declare function getAppContainer(): Container;
|
|
|
297
317
|
*/
|
|
298
318
|
declare function slotAddress(app: string, slot: string): string;
|
|
299
319
|
|
|
300
|
-
export { AppAssets, type CreateIslandContainerOptions, type DocumentContribution, type DocumentHead, type DocumentSource, IslandDescriptor, type IslandRecord, type IslandRoute, NAVIGATE_EVENT, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, isDocumentSource, navigateIsland, onIslandNavigate, registerIsland, renderApp, resolveEagerUnits, slotAddress, subscribeIslands };
|
|
320
|
+
export { AppAssets, type CreateIslandContainerOptions, DOCUMENT_EVENT, type DocumentContribution, type DocumentHead, type DocumentSource, IslandDescriptor, type IslandRecord, type IslandRoute, NAVIGATE_EVENT, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, isDocumentSource, navigateIsland, onIslandDocument, onIslandNavigate, publishIslandDocument, registerIsland, renderApp, resolveEagerUnits, slotAddress, subscribeIslands };
|
package/island/index.js
CHANGED
|
@@ -37,6 +37,19 @@ function onIslandNavigate(node, onNavigate) {
|
|
|
37
37
|
node.removeEventListener(NAVIGATE_EVENT, listener);
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
+
var DOCUMENT_EVENT = "den:document";
|
|
41
|
+
function publishIslandDocument(node, contribution) {
|
|
42
|
+
node.dispatchEvent(new CustomEvent(DOCUMENT_EVENT, { bubbles: true, detail: contribution }));
|
|
43
|
+
}
|
|
44
|
+
function onIslandDocument(node, onDocument) {
|
|
45
|
+
const listener = (event) => {
|
|
46
|
+
onDocument(event.detail);
|
|
47
|
+
};
|
|
48
|
+
node.addEventListener(DOCUMENT_EVENT, listener);
|
|
49
|
+
return () => {
|
|
50
|
+
node.removeEventListener(DOCUMENT_EVENT, listener);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
40
53
|
|
|
41
54
|
// src/island/slot-address.ts
|
|
42
55
|
function slotAddress(app, slot) {
|
|
@@ -83,6 +96,7 @@ function describeIsland(descriptor, denState) {
|
|
|
83
96
|
};
|
|
84
97
|
}
|
|
85
98
|
export {
|
|
99
|
+
DOCUMENT_EVENT,
|
|
86
100
|
NAVIGATE_EVENT,
|
|
87
101
|
ViewLayerToken,
|
|
88
102
|
activateIslandViewModels,
|
|
@@ -97,8 +111,10 @@ export {
|
|
|
97
111
|
matchRoute,
|
|
98
112
|
navigateIsland,
|
|
99
113
|
normalizeRoutes,
|
|
114
|
+
onIslandDocument,
|
|
100
115
|
onIslandNavigate,
|
|
101
116
|
pathPattern,
|
|
117
|
+
publishIslandDocument,
|
|
102
118
|
registerIsland,
|
|
103
119
|
renderApp,
|
|
104
120
|
resolveEagerUnits,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foxford/den",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Den — декларативный метафреймворк Foxford (core runtime)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"foxford",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"fastify": "^5.11.2",
|
|
27
|
-
"@foxford/
|
|
28
|
-
"@foxford/
|
|
27
|
+
"@foxford/logger": "^1.2.2",
|
|
28
|
+
"@foxford/ioc": "^1.1.2"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"vite": ">=5.0.0"
|
|
@@ -86,17 +86,17 @@
|
|
|
86
86
|
"chunk-6FNC3XMI.js",
|
|
87
87
|
"chunk-6QV4L6R2.cjs",
|
|
88
88
|
"chunk-A6ZPAM6Z.cjs",
|
|
89
|
+
"chunk-BMI5HNGE.js",
|
|
89
90
|
"chunk-C7BM4DGX.cjs",
|
|
90
91
|
"chunk-E23E2VUC.js",
|
|
91
92
|
"chunk-MCN4GFEQ.js",
|
|
93
|
+
"chunk-RRR2DJMN.cjs",
|
|
92
94
|
"chunk-T5QVCXVB.cjs",
|
|
93
95
|
"chunk-TPBI6TOU.js",
|
|
94
96
|
"chunk-TPHJUDHG.js",
|
|
95
97
|
"chunk-WBHHHICS.js",
|
|
96
98
|
"chunk-XOJ2VWX4.cjs",
|
|
97
|
-
"chunk-YECHY3NR.js",
|
|
98
99
|
"chunk-Z5BL4DJA.cjs",
|
|
99
|
-
"chunk-ZBB5SBEV.cjs",
|
|
100
100
|
"chunk-ZOU6W6ZQ.cjs",
|
|
101
101
|
"define-repository-CwAXed2X.d.cts",
|
|
102
102
|
"define-repository-q_T4hqeP.d.ts",
|
|
@@ -125,6 +125,6 @@
|
|
|
125
125
|
"view-adapter-B4gEb-ms.d.ts",
|
|
126
126
|
"view-adapter-DJ7yB6Ml.d.cts"
|
|
127
127
|
],
|
|
128
|
-
"sha": "
|
|
128
|
+
"sha": "c382a3d",
|
|
129
129
|
"scripts": {}
|
|
130
130
|
}
|
package/serve/index.cjs
CHANGED
|
@@ -16,6 +16,52 @@ var _chunkT5QVCXVBcjs = require('../chunk-T5QVCXVB.cjs');
|
|
|
16
16
|
var _fastify = require('fastify'); var _fastify2 = _interopRequireDefault(_fastify);
|
|
17
17
|
var _logger = require('@foxford/logger');
|
|
18
18
|
|
|
19
|
+
// src/serve/assets.ts
|
|
20
|
+
var _fs = require('fs');
|
|
21
|
+
var _path = require('path'); var _path2 = _interopRequireDefault(_path);
|
|
22
|
+
var MIME = {
|
|
23
|
+
".css": "text/css",
|
|
24
|
+
".gif": "image/gif",
|
|
25
|
+
".jpeg": "image/jpeg",
|
|
26
|
+
".jpg": "image/jpeg",
|
|
27
|
+
".js": "text/javascript",
|
|
28
|
+
".json": "application/json",
|
|
29
|
+
".map": "application/json",
|
|
30
|
+
".mjs": "text/javascript",
|
|
31
|
+
".png": "image/png",
|
|
32
|
+
".svg": "image/svg+xml",
|
|
33
|
+
".ttf": "font/ttf",
|
|
34
|
+
".webp": "image/webp",
|
|
35
|
+
".woff": "font/woff",
|
|
36
|
+
".woff2": "font/woff2"
|
|
37
|
+
};
|
|
38
|
+
var HASHED_DIR = "assets/";
|
|
39
|
+
function assetsPrefix(base) {
|
|
40
|
+
const { pathname } = new URL(base, "http://assets.invalid");
|
|
41
|
+
return pathname.endsWith("/") ? pathname : `${pathname}/`;
|
|
42
|
+
}
|
|
43
|
+
function assetFile(dir, relative) {
|
|
44
|
+
const file = _path2.default.normalize(_path2.default.join(dir, relative));
|
|
45
|
+
return file.startsWith(_path2.default.normalize(dir)) ? file : null;
|
|
46
|
+
}
|
|
47
|
+
function registerAssets(instance, source) {
|
|
48
|
+
const dir = _path2.default.resolve(source.dir);
|
|
49
|
+
const prefix = assetsPrefix(source.base);
|
|
50
|
+
instance.get(`${prefix}*`, (request, reply) => {
|
|
51
|
+
var _a;
|
|
52
|
+
const relative = request.params["*"];
|
|
53
|
+
const file = assetFile(dir, relative);
|
|
54
|
+
if (file === null) {
|
|
55
|
+
return reply.code(404).send();
|
|
56
|
+
}
|
|
57
|
+
const stat = _fs.existsSync.call(void 0, file) ? _fs.statSync.call(void 0, file) : null;
|
|
58
|
+
if (stat === null || !stat.isFile()) {
|
|
59
|
+
return reply.code(404).send();
|
|
60
|
+
}
|
|
61
|
+
return reply.header("access-control-allow-origin", "*").header("cache-control", relative.startsWith(HASHED_DIR) ? "public, max-age=31536000, immutable" : "no-cache").header("content-length", stat.size).type((_a = MIME[_path2.default.extname(file)]) != null ? _a : "application/octet-stream").send(_fs.createReadStream.call(void 0, file));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
19
65
|
// src/serve/plugin.ts
|
|
20
66
|
|
|
21
67
|
var DEFAULT_SLOW_RENDER_MS = 1e3;
|
|
@@ -106,6 +152,9 @@ function serveApp(_0) {
|
|
|
106
152
|
const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
|
|
107
153
|
const instance = _fastify2.default.call(void 0, { logger: false });
|
|
108
154
|
yield instance.register(appPlugin, { app, slowRenderMs: options.slowRenderMs });
|
|
155
|
+
if (options.assets) {
|
|
156
|
+
registerAssets(instance, options.assets);
|
|
157
|
+
}
|
|
109
158
|
instance.addHook("onRequest", (req, _reply, done) => {
|
|
110
159
|
serveLog.debug(`\u2192 ${req.method} ${req.url}`);
|
|
111
160
|
done();
|
|
@@ -134,4 +183,5 @@ function serveApp(_0) {
|
|
|
134
183
|
|
|
135
184
|
|
|
136
185
|
|
|
137
|
-
|
|
186
|
+
|
|
187
|
+
exports.appPlugin = appPlugin; exports.registerAssets = registerAssets; exports.serveApp = serveApp;
|
package/serve/index.d.cts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
|
+
import { FastifyInstance, FastifyPluginAsync } from 'fastify';
|
|
1
2
|
import { I as IslandDescriptor } from '../descriptor-Dujg_1on.cjs';
|
|
2
3
|
import { A as AppRoute } from '../routes-B0hDrkiF.cjs';
|
|
3
|
-
import { FastifyPluginAsync, FastifyInstance } from 'fastify';
|
|
4
4
|
import '../define-slot-DYNCLDJY.cjs';
|
|
5
5
|
import '../types-Dx5qGiwk.cjs';
|
|
6
6
|
import '@foxford/ioc';
|
|
7
7
|
|
|
8
|
+
/** Что раздаётся и по какому префиксу. */
|
|
9
|
+
interface AppAssetsSource {
|
|
10
|
+
/** Префикс адресов артефактов — тот, с которым собран клиентский выход. */
|
|
11
|
+
base: string;
|
|
12
|
+
/** Каталог клиентского выхода. */
|
|
13
|
+
dir: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Заводит раздачу артефактов на инстансе.
|
|
17
|
+
*
|
|
18
|
+
* @param instance - Инстанс fastify
|
|
19
|
+
* @param source - Префикс адресов и каталог выхода
|
|
20
|
+
*/
|
|
21
|
+
declare function registerAssets(instance: FastifyInstance, source: AppAssetsSource): void;
|
|
22
|
+
|
|
8
23
|
/** Приложение, которое можно поднять сервисом: остров плюс его заявка на адреса. */
|
|
9
24
|
type ServableApp = IslandDescriptor & {
|
|
10
25
|
routes?: ReadonlyArray<AppRoute>;
|
|
@@ -44,6 +59,13 @@ interface ServeAppOptions {
|
|
|
44
59
|
* который именно так и просит его уйти, а незакрытый инстанс рвёт запросы на середине.
|
|
45
60
|
*/
|
|
46
61
|
gracefulShutdown?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Клиентские артефакты приложения: префикс адресов и каталог выхода.
|
|
64
|
+
*
|
|
65
|
+
* Отдаёт их само приложение — адреса знает его сборка, а хост получает только ссылки. В
|
|
66
|
+
* проде перед процессом стоит CDN, и до него эти адреса не доходят.
|
|
67
|
+
*/
|
|
68
|
+
assets?: AppAssetsSource;
|
|
47
69
|
}
|
|
48
70
|
/**
|
|
49
71
|
* Поднимает приложение отдельным сервисом.
|
|
@@ -57,4 +79,4 @@ interface ServeAppOptions {
|
|
|
57
79
|
*/
|
|
58
80
|
declare function serveApp(app: ServableApp, options?: ServeAppOptions): Promise<FastifyInstance>;
|
|
59
81
|
|
|
60
|
-
export { type AppPluginOptions, type ServableApp, type ServeAppOptions, appPlugin, serveApp };
|
|
82
|
+
export { type AppAssetsSource, type AppPluginOptions, type ServableApp, type ServeAppOptions, appPlugin, registerAssets, serveApp };
|
package/serve/index.d.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
|
+
import { FastifyInstance, FastifyPluginAsync } from 'fastify';
|
|
1
2
|
import { I as IslandDescriptor } from '../descriptor-DQ6Gi7A_.js';
|
|
2
3
|
import { A as AppRoute } from '../routes-0uXJ0fux.js';
|
|
3
|
-
import { FastifyPluginAsync, FastifyInstance } from 'fastify';
|
|
4
4
|
import '../define-slot-Cngzae6i.js';
|
|
5
5
|
import '../types-Dx5qGiwk.js';
|
|
6
6
|
import '@foxford/ioc';
|
|
7
7
|
|
|
8
|
+
/** Что раздаётся и по какому префиксу. */
|
|
9
|
+
interface AppAssetsSource {
|
|
10
|
+
/** Префикс адресов артефактов — тот, с которым собран клиентский выход. */
|
|
11
|
+
base: string;
|
|
12
|
+
/** Каталог клиентского выхода. */
|
|
13
|
+
dir: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Заводит раздачу артефактов на инстансе.
|
|
17
|
+
*
|
|
18
|
+
* @param instance - Инстанс fastify
|
|
19
|
+
* @param source - Префикс адресов и каталог выхода
|
|
20
|
+
*/
|
|
21
|
+
declare function registerAssets(instance: FastifyInstance, source: AppAssetsSource): void;
|
|
22
|
+
|
|
8
23
|
/** Приложение, которое можно поднять сервисом: остров плюс его заявка на адреса. */
|
|
9
24
|
type ServableApp = IslandDescriptor & {
|
|
10
25
|
routes?: ReadonlyArray<AppRoute>;
|
|
@@ -44,6 +59,13 @@ interface ServeAppOptions {
|
|
|
44
59
|
* который именно так и просит его уйти, а незакрытый инстанс рвёт запросы на середине.
|
|
45
60
|
*/
|
|
46
61
|
gracefulShutdown?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Клиентские артефакты приложения: префикс адресов и каталог выхода.
|
|
64
|
+
*
|
|
65
|
+
* Отдаёт их само приложение — адреса знает его сборка, а хост получает только ссылки. В
|
|
66
|
+
* проде перед процессом стоит CDN, и до него эти адреса не доходят.
|
|
67
|
+
*/
|
|
68
|
+
assets?: AppAssetsSource;
|
|
47
69
|
}
|
|
48
70
|
/**
|
|
49
71
|
* Поднимает приложение отдельным сервисом.
|
|
@@ -57,4 +79,4 @@ interface ServeAppOptions {
|
|
|
57
79
|
*/
|
|
58
80
|
declare function serveApp(app: ServableApp, options?: ServeAppOptions): Promise<FastifyInstance>;
|
|
59
81
|
|
|
60
|
-
export { type AppPluginOptions, type ServableApp, type ServeAppOptions, appPlugin, serveApp };
|
|
82
|
+
export { type AppAssetsSource, type AppPluginOptions, type ServableApp, type ServeAppOptions, appPlugin, registerAssets, serveApp };
|
package/serve/index.js
CHANGED
|
@@ -16,6 +16,52 @@ import {
|
|
|
16
16
|
import Fastify from "fastify";
|
|
17
17
|
import { log as log2 } from "@foxford/logger";
|
|
18
18
|
|
|
19
|
+
// src/serve/assets.ts
|
|
20
|
+
import { createReadStream, existsSync, statSync } from "fs";
|
|
21
|
+
import path from "path";
|
|
22
|
+
var MIME = {
|
|
23
|
+
".css": "text/css",
|
|
24
|
+
".gif": "image/gif",
|
|
25
|
+
".jpeg": "image/jpeg",
|
|
26
|
+
".jpg": "image/jpeg",
|
|
27
|
+
".js": "text/javascript",
|
|
28
|
+
".json": "application/json",
|
|
29
|
+
".map": "application/json",
|
|
30
|
+
".mjs": "text/javascript",
|
|
31
|
+
".png": "image/png",
|
|
32
|
+
".svg": "image/svg+xml",
|
|
33
|
+
".ttf": "font/ttf",
|
|
34
|
+
".webp": "image/webp",
|
|
35
|
+
".woff": "font/woff",
|
|
36
|
+
".woff2": "font/woff2"
|
|
37
|
+
};
|
|
38
|
+
var HASHED_DIR = "assets/";
|
|
39
|
+
function assetsPrefix(base) {
|
|
40
|
+
const { pathname } = new URL(base, "http://assets.invalid");
|
|
41
|
+
return pathname.endsWith("/") ? pathname : `${pathname}/`;
|
|
42
|
+
}
|
|
43
|
+
function assetFile(dir, relative) {
|
|
44
|
+
const file = path.normalize(path.join(dir, relative));
|
|
45
|
+
return file.startsWith(path.normalize(dir)) ? file : null;
|
|
46
|
+
}
|
|
47
|
+
function registerAssets(instance, source) {
|
|
48
|
+
const dir = path.resolve(source.dir);
|
|
49
|
+
const prefix = assetsPrefix(source.base);
|
|
50
|
+
instance.get(`${prefix}*`, (request, reply) => {
|
|
51
|
+
var _a;
|
|
52
|
+
const relative = request.params["*"];
|
|
53
|
+
const file = assetFile(dir, relative);
|
|
54
|
+
if (file === null) {
|
|
55
|
+
return reply.code(404).send();
|
|
56
|
+
}
|
|
57
|
+
const stat = existsSync(file) ? statSync(file) : null;
|
|
58
|
+
if (stat === null || !stat.isFile()) {
|
|
59
|
+
return reply.code(404).send();
|
|
60
|
+
}
|
|
61
|
+
return reply.header("access-control-allow-origin", "*").header("cache-control", relative.startsWith(HASHED_DIR) ? "public, max-age=31536000, immutable" : "no-cache").header("content-length", stat.size).type((_a = MIME[path.extname(file)]) != null ? _a : "application/octet-stream").send(createReadStream(file));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
19
65
|
// src/serve/plugin.ts
|
|
20
66
|
import { log } from "@foxford/logger";
|
|
21
67
|
var DEFAULT_SLOW_RENDER_MS = 1e3;
|
|
@@ -106,6 +152,9 @@ function serveApp(_0) {
|
|
|
106
152
|
const serveLog = (_b = appLog.getLogger("serve")) != null ? _b : appLog;
|
|
107
153
|
const instance = Fastify({ logger: false });
|
|
108
154
|
yield instance.register(appPlugin, { app, slowRenderMs: options.slowRenderMs });
|
|
155
|
+
if (options.assets) {
|
|
156
|
+
registerAssets(instance, options.assets);
|
|
157
|
+
}
|
|
109
158
|
instance.addHook("onRequest", (req, _reply, done) => {
|
|
110
159
|
serveLog.debug(`\u2192 ${req.method} ${req.url}`);
|
|
111
160
|
done();
|
|
@@ -133,5 +182,6 @@ function serveApp(_0) {
|
|
|
133
182
|
}
|
|
134
183
|
export {
|
|
135
184
|
appPlugin,
|
|
185
|
+
registerAssets,
|
|
136
186
|
serveApp
|
|
137
187
|
};
|