@finesoft/front 0.1.3 → 0.1.12
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 +159 -15
- package/dist/browser.cjs +1214 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +751 -0
- package/dist/browser.d.ts +751 -0
- package/dist/browser.js +97 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunk-OVGQ4NUA.js +1143 -0
- package/dist/chunk-OVGQ4NUA.js.map +1 -0
- package/dist/index.cjs +130 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -749
- package/dist/index.d.ts +16 -749
- package/dist/index.js +169 -1119
- package/dist/index.js.map +1 -1
- package/package.json +11 -6
package/dist/index.js
CHANGED
|
@@ -1,1090 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
`[ActionDispatcher] No handler for kind="${action.kind}"`
|
|
50
|
-
);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
await handler(action);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// ../core/src/intents/dispatcher.ts
|
|
58
|
-
var IntentDispatcher = class {
|
|
59
|
-
controllers = /* @__PURE__ */ new Map();
|
|
60
|
-
/** 注册一个 IntentController */
|
|
61
|
-
register(controller) {
|
|
62
|
-
this.controllers.set(controller.intentId, controller);
|
|
63
|
-
}
|
|
64
|
-
/** 分发 Intent 到对应 Controller */
|
|
65
|
-
async dispatch(intent, container) {
|
|
66
|
-
const controller = this.controllers.get(intent.id);
|
|
67
|
-
if (!controller) {
|
|
68
|
-
throw new Error(
|
|
69
|
-
`[IntentDispatcher] No controller for "${intent.id}". Registered: [${Array.from(this.controllers.keys()).join(", ")}]`
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
return controller.perform(intent, container);
|
|
73
|
-
}
|
|
74
|
-
/** 检查是否已注册某个 Intent */
|
|
75
|
-
has(intentId) {
|
|
76
|
-
return this.controllers.has(intentId);
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// ../core/src/dependencies/container.ts
|
|
81
|
-
var Container = class {
|
|
82
|
-
registrations = /* @__PURE__ */ new Map();
|
|
83
|
-
/** 注册依赖(默认单例) */
|
|
84
|
-
register(key, factory, singleton = true) {
|
|
85
|
-
this.registrations.set(key, { factory, singleton });
|
|
86
|
-
return this;
|
|
87
|
-
}
|
|
88
|
-
/** 解析依赖 */
|
|
89
|
-
resolve(key) {
|
|
90
|
-
const reg = this.registrations.get(key);
|
|
91
|
-
if (!reg) {
|
|
92
|
-
throw new Error(`[Container] No registration for key: "${key}"`);
|
|
93
|
-
}
|
|
94
|
-
if (reg.singleton) {
|
|
95
|
-
if (reg.instance === void 0) {
|
|
96
|
-
reg.instance = reg.factory();
|
|
97
|
-
}
|
|
98
|
-
return reg.instance;
|
|
99
|
-
}
|
|
100
|
-
return reg.factory();
|
|
101
|
-
}
|
|
102
|
-
/** 检查是否已注册 */
|
|
103
|
-
has(key) {
|
|
104
|
-
return this.registrations.has(key);
|
|
105
|
-
}
|
|
106
|
-
/** 销毁容器,清除所有缓存 */
|
|
107
|
-
dispose() {
|
|
108
|
-
for (const reg of this.registrations.values()) {
|
|
109
|
-
reg.instance = void 0;
|
|
110
|
-
}
|
|
111
|
-
this.registrations.clear();
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// ../core/src/logger/base.ts
|
|
116
|
-
var BaseLogger = class {
|
|
117
|
-
category;
|
|
118
|
-
constructor(category) {
|
|
119
|
-
this.category = category;
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
// ../core/src/logger/local-storage-filter.ts
|
|
124
|
-
var LEVEL_TO_NUM = {
|
|
125
|
-
"*": 4,
|
|
126
|
-
debug: 4,
|
|
127
|
-
info: 3,
|
|
128
|
-
warn: 2,
|
|
129
|
-
error: 1,
|
|
130
|
-
off: 0,
|
|
131
|
-
"": 0
|
|
132
|
-
};
|
|
133
|
-
var cachedRules;
|
|
134
|
-
var cachedRaw;
|
|
135
|
-
function parseRules() {
|
|
136
|
-
if (typeof globalThis.localStorage === "undefined") {
|
|
137
|
-
return {};
|
|
138
|
-
}
|
|
139
|
-
let raw;
|
|
140
|
-
try {
|
|
141
|
-
raw = globalThis.localStorage.getItem("onyxLog");
|
|
142
|
-
} catch {
|
|
143
|
-
return {};
|
|
144
|
-
}
|
|
145
|
-
if (!raw) return {};
|
|
146
|
-
if (raw === cachedRaw && cachedRules) return cachedRules;
|
|
147
|
-
cachedRaw = raw;
|
|
148
|
-
const rules = {};
|
|
149
|
-
const parts = raw.split(",");
|
|
150
|
-
for (const part of parts) {
|
|
151
|
-
const [name, level] = part.trim().split("=");
|
|
152
|
-
if (!name || level === void 0) continue;
|
|
153
|
-
const num = LEVEL_TO_NUM[level.toLowerCase()] ?? void 0;
|
|
154
|
-
if (num === void 0) continue;
|
|
155
|
-
if (name === "*") {
|
|
156
|
-
rules.defaultLevel = num;
|
|
157
|
-
} else {
|
|
158
|
-
rules.named ??= {};
|
|
159
|
-
rules.named[name] = num;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
cachedRules = rules;
|
|
163
|
-
return rules;
|
|
164
|
-
}
|
|
165
|
-
function shouldLog(name, level) {
|
|
166
|
-
const rules = parseRules();
|
|
167
|
-
if (rules.defaultLevel === void 0 && !rules.named) {
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
const currentNum = LEVEL_TO_NUM[level] ?? 4;
|
|
171
|
-
if (rules.named?.[name] !== void 0) {
|
|
172
|
-
return currentNum <= rules.named[name];
|
|
173
|
-
}
|
|
174
|
-
if (rules.defaultLevel !== void 0) {
|
|
175
|
-
return currentNum <= rules.defaultLevel;
|
|
176
|
-
}
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
function resetFilterCache() {
|
|
180
|
-
cachedRules = void 0;
|
|
181
|
-
cachedRaw = void 0;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ../core/src/logger/console.ts
|
|
185
|
-
var ConsoleLogger = class extends BaseLogger {
|
|
186
|
-
debug(...args) {
|
|
187
|
-
if (shouldLog(this.category, "debug")) {
|
|
188
|
-
console.debug(`[${this.category}]`, ...args);
|
|
189
|
-
}
|
|
190
|
-
return "";
|
|
191
|
-
}
|
|
192
|
-
info(...args) {
|
|
193
|
-
if (shouldLog(this.category, "info")) {
|
|
194
|
-
console.info(`[${this.category}]`, ...args);
|
|
195
|
-
}
|
|
196
|
-
return "";
|
|
197
|
-
}
|
|
198
|
-
warn(...args) {
|
|
199
|
-
if (shouldLog(this.category, "warn")) {
|
|
200
|
-
console.warn(`[${this.category}]`, ...args);
|
|
201
|
-
}
|
|
202
|
-
return "";
|
|
203
|
-
}
|
|
204
|
-
error(...args) {
|
|
205
|
-
console.error(`[${this.category}]`, ...args);
|
|
206
|
-
return "";
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
var ConsoleLoggerFactory = class {
|
|
210
|
-
loggerFor(category) {
|
|
211
|
-
return new ConsoleLogger(category);
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
// ../core/src/dependencies/make-dependencies.ts
|
|
216
|
-
var DEP_KEYS = {
|
|
217
|
-
LOGGER: "logger",
|
|
218
|
-
LOGGER_FACTORY: "loggerFactory",
|
|
219
|
-
NET: "net",
|
|
220
|
-
LOCALE: "locale",
|
|
221
|
-
STORAGE: "storage",
|
|
222
|
-
FEATURE_FLAGS: "featureFlags",
|
|
223
|
-
METRICS: "metrics",
|
|
224
|
-
FETCH: "fetch"
|
|
225
|
-
};
|
|
226
|
-
var DefaultLocale = class {
|
|
227
|
-
language = "en";
|
|
228
|
-
storefront = "us";
|
|
229
|
-
setActiveLocale(language, storefront) {
|
|
230
|
-
this.language = language;
|
|
231
|
-
this.storefront = storefront;
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
var MemoryStorage = class {
|
|
235
|
-
store = /* @__PURE__ */ new Map();
|
|
236
|
-
get(key) {
|
|
237
|
-
return this.store.get(key);
|
|
238
|
-
}
|
|
239
|
-
set(key, value) {
|
|
240
|
-
this.store.set(key, value);
|
|
241
|
-
}
|
|
242
|
-
delete(key) {
|
|
243
|
-
this.store.delete(key);
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
var DefaultFeatureFlags = class {
|
|
247
|
-
flags;
|
|
248
|
-
constructor(flags = {}) {
|
|
249
|
-
this.flags = flags;
|
|
250
|
-
}
|
|
251
|
-
isEnabled(key) {
|
|
252
|
-
return this.flags[key] === true;
|
|
253
|
-
}
|
|
254
|
-
getString(key) {
|
|
255
|
-
const v = this.flags[key];
|
|
256
|
-
return typeof v === "string" ? v : void 0;
|
|
257
|
-
}
|
|
258
|
-
getNumber(key) {
|
|
259
|
-
const v = this.flags[key];
|
|
260
|
-
return typeof v === "number" ? v : void 0;
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
var ConsoleMetrics = class {
|
|
264
|
-
recordPageView(page, fields) {
|
|
265
|
-
console.info(`[Metrics:PageView] ${page}`, fields ?? "");
|
|
266
|
-
}
|
|
267
|
-
recordEvent(name, fields) {
|
|
268
|
-
console.info(`[Metrics:Event] ${name}`, fields ?? "");
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
function makeDependencies(container, options = {}) {
|
|
272
|
-
const {
|
|
273
|
-
fetch: fetchFn = globalThis.fetch?.bind(globalThis),
|
|
274
|
-
language = "en",
|
|
275
|
-
storefront = "us",
|
|
276
|
-
featureFlags = {}
|
|
277
|
-
} = options;
|
|
278
|
-
const loggerFactory = new ConsoleLoggerFactory();
|
|
279
|
-
container.register(
|
|
280
|
-
DEP_KEYS.LOGGER_FACTORY,
|
|
281
|
-
() => loggerFactory
|
|
282
|
-
);
|
|
283
|
-
container.register(
|
|
284
|
-
DEP_KEYS.LOGGER,
|
|
285
|
-
() => loggerFactory.loggerFor("framework")
|
|
286
|
-
);
|
|
287
|
-
container.register(DEP_KEYS.NET, () => ({
|
|
288
|
-
fetch: (url, opts) => fetchFn(url, opts)
|
|
289
|
-
}));
|
|
290
|
-
container.register(DEP_KEYS.LOCALE, () => {
|
|
291
|
-
const locale = new DefaultLocale();
|
|
292
|
-
locale.setActiveLocale(language, storefront);
|
|
293
|
-
return locale;
|
|
294
|
-
});
|
|
295
|
-
container.register(DEP_KEYS.STORAGE, () => new MemoryStorage());
|
|
296
|
-
container.register(
|
|
297
|
-
DEP_KEYS.FEATURE_FLAGS,
|
|
298
|
-
() => new DefaultFeatureFlags(featureFlags)
|
|
299
|
-
);
|
|
300
|
-
container.register(
|
|
301
|
-
DEP_KEYS.METRICS,
|
|
302
|
-
() => new ConsoleMetrics()
|
|
303
|
-
);
|
|
304
|
-
container.register(DEP_KEYS.FETCH, () => fetchFn);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// ../core/src/router/router.ts
|
|
308
|
-
var Router = class {
|
|
309
|
-
routes = [];
|
|
310
|
-
/** 添加路由规则 */
|
|
311
|
-
add(pattern, intentId) {
|
|
312
|
-
const paramNames = [];
|
|
313
|
-
const regexStr = pattern.replace(
|
|
314
|
-
/\/:(\w+)(\?)?/g,
|
|
315
|
-
(_, name, optional) => {
|
|
316
|
-
paramNames.push(name);
|
|
317
|
-
return optional ? "(?:/([^/]+))?" : "/([^/]+)";
|
|
318
|
-
}
|
|
319
|
-
);
|
|
320
|
-
this.routes.push({
|
|
321
|
-
pattern,
|
|
322
|
-
intentId,
|
|
323
|
-
regex: new RegExp(`^${regexStr}/?$`),
|
|
324
|
-
paramNames
|
|
325
|
-
});
|
|
326
|
-
return this;
|
|
327
|
-
}
|
|
328
|
-
/** 解析 URL → RouteMatch */
|
|
329
|
-
resolve(urlOrPath) {
|
|
330
|
-
const path = this.extractPath(urlOrPath);
|
|
331
|
-
const queryParams = this.extractQueryParams(urlOrPath);
|
|
332
|
-
for (const route of this.routes) {
|
|
333
|
-
const match = path.match(route.regex);
|
|
334
|
-
if (match) {
|
|
335
|
-
const params = {};
|
|
336
|
-
route.paramNames.forEach((name, index) => {
|
|
337
|
-
const value = match[index + 1];
|
|
338
|
-
if (value) params[name] = value;
|
|
339
|
-
});
|
|
340
|
-
for (const [k, v] of Object.entries(queryParams)) {
|
|
341
|
-
if (!(k in params)) params[k] = v;
|
|
342
|
-
}
|
|
343
|
-
return {
|
|
344
|
-
intent: { id: route.intentId, params },
|
|
345
|
-
action: makeFlowAction(urlOrPath)
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
/** 获取所有已注册的路由 */
|
|
352
|
-
getRoutes() {
|
|
353
|
-
return this.routes.map((r) => `${r.pattern} \u2192 ${r.intentId}`);
|
|
354
|
-
}
|
|
355
|
-
extractPath(url) {
|
|
356
|
-
try {
|
|
357
|
-
const parsed = new URL(url, "http://localhost");
|
|
358
|
-
return parsed.pathname;
|
|
359
|
-
} catch {
|
|
360
|
-
return url.split("?")[0].split("#")[0];
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
extractQueryParams(url) {
|
|
364
|
-
try {
|
|
365
|
-
const parsed = new URL(url, "http://localhost");
|
|
366
|
-
const params = {};
|
|
367
|
-
parsed.searchParams.forEach((v, k) => {
|
|
368
|
-
params[k] = v;
|
|
369
|
-
});
|
|
370
|
-
return params;
|
|
371
|
-
} catch {
|
|
372
|
-
return {};
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
// ../core/src/logger/composite.ts
|
|
378
|
-
var CompositeLoggerFactory = class {
|
|
379
|
-
constructor(factories) {
|
|
380
|
-
this.factories = factories;
|
|
381
|
-
}
|
|
382
|
-
loggerFor(name) {
|
|
383
|
-
return new CompositeLogger(
|
|
384
|
-
this.factories.map((f) => f.loggerFor(name))
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
var CompositeLogger = class {
|
|
389
|
-
constructor(loggers) {
|
|
390
|
-
this.loggers = loggers;
|
|
391
|
-
}
|
|
392
|
-
debug(...args) {
|
|
393
|
-
return this.callAll("debug", args);
|
|
394
|
-
}
|
|
395
|
-
info(...args) {
|
|
396
|
-
return this.callAll("info", args);
|
|
397
|
-
}
|
|
398
|
-
warn(...args) {
|
|
399
|
-
return this.callAll("warn", args);
|
|
400
|
-
}
|
|
401
|
-
error(...args) {
|
|
402
|
-
return this.callAll("error", args);
|
|
403
|
-
}
|
|
404
|
-
callAll(method, args) {
|
|
405
|
-
for (const logger of this.loggers) {
|
|
406
|
-
logger[method](...args);
|
|
407
|
-
}
|
|
408
|
-
return "";
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
// ../core/src/prefetched-intents/stable-stringify.ts
|
|
413
|
-
function stableStringify(obj) {
|
|
414
|
-
if (obj === null || obj === void 0) return String(obj);
|
|
415
|
-
if (typeof obj !== "object") return JSON.stringify(obj);
|
|
416
|
-
if (Array.isArray(obj)) {
|
|
417
|
-
return "[" + obj.map(stableStringify).join(",") + "]";
|
|
418
|
-
}
|
|
419
|
-
const keys = Object.keys(obj).sort();
|
|
420
|
-
const parts = keys.filter((k) => obj[k] !== void 0).map(
|
|
421
|
-
(k) => JSON.stringify(k) + ":" + stableStringify(obj[k])
|
|
422
|
-
);
|
|
423
|
-
return "{" + parts.join(",") + "}";
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// ../core/src/prefetched-intents/prefetched-intents.ts
|
|
427
|
-
var PrefetchedIntents = class _PrefetchedIntents {
|
|
428
|
-
intents;
|
|
429
|
-
constructor(intents) {
|
|
430
|
-
this.intents = intents;
|
|
431
|
-
}
|
|
432
|
-
/** 从 PrefetchedIntent 数组创建缓存实例 */
|
|
433
|
-
static fromArray(items) {
|
|
434
|
-
const map = /* @__PURE__ */ new Map();
|
|
435
|
-
for (const item of items) {
|
|
436
|
-
if (item.intent && item.data !== void 0) {
|
|
437
|
-
const key = stableStringify(item.intent);
|
|
438
|
-
map.set(key, item.data);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return new _PrefetchedIntents(map);
|
|
442
|
-
}
|
|
443
|
-
/** 创建空缓存实例 */
|
|
444
|
-
static empty() {
|
|
445
|
-
return new _PrefetchedIntents(/* @__PURE__ */ new Map());
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* 获取缓存的 Intent 结果(一次性使用)。
|
|
449
|
-
* 命中后从缓存中删除。
|
|
450
|
-
*/
|
|
451
|
-
get(intent) {
|
|
452
|
-
const key = stableStringify(intent);
|
|
453
|
-
const data = this.intents.get(key);
|
|
454
|
-
if (data !== void 0) {
|
|
455
|
-
this.intents.delete(key);
|
|
456
|
-
return data;
|
|
457
|
-
}
|
|
458
|
-
return void 0;
|
|
459
|
-
}
|
|
460
|
-
/** 检查缓存中是否有某个 Intent 的数据 */
|
|
461
|
-
has(intent) {
|
|
462
|
-
return this.intents.has(stableStringify(intent));
|
|
463
|
-
}
|
|
464
|
-
/** 缓存中的条目数 */
|
|
465
|
-
get size() {
|
|
466
|
-
return this.intents.size;
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
// ../core/src/framework.ts
|
|
471
|
-
var Framework = class _Framework {
|
|
472
|
-
container;
|
|
473
|
-
intentDispatcher;
|
|
474
|
-
actionDispatcher;
|
|
475
|
-
router;
|
|
476
|
-
prefetchedIntents;
|
|
477
|
-
constructor(container, prefetchedIntents) {
|
|
478
|
-
this.container = container;
|
|
479
|
-
this.intentDispatcher = new IntentDispatcher();
|
|
480
|
-
this.actionDispatcher = new ActionDispatcher();
|
|
481
|
-
this.router = new Router();
|
|
482
|
-
this.prefetchedIntents = prefetchedIntents;
|
|
483
|
-
}
|
|
484
|
-
/** 创建并初始化 Framework 实例 */
|
|
485
|
-
static create(config = {}) {
|
|
486
|
-
const container = new Container();
|
|
487
|
-
makeDependencies(container, config);
|
|
488
|
-
const fw = new _Framework(
|
|
489
|
-
container,
|
|
490
|
-
config.prefetchedIntents ?? PrefetchedIntents.empty()
|
|
491
|
-
);
|
|
492
|
-
config.setupRoutes?.(fw.router);
|
|
493
|
-
return fw;
|
|
494
|
-
}
|
|
495
|
-
/** 分发 Intent — 获取页面数据 */
|
|
496
|
-
async dispatch(intent) {
|
|
497
|
-
const logger = this.container.resolve(DEP_KEYS.LOGGER);
|
|
498
|
-
const cached = this.prefetchedIntents.get(intent);
|
|
499
|
-
if (cached !== void 0) {
|
|
500
|
-
logger.debug(
|
|
501
|
-
`[Framework] re-using prefetched intent response for: ${intent.id}`,
|
|
502
|
-
intent.params
|
|
503
|
-
);
|
|
504
|
-
return cached;
|
|
505
|
-
}
|
|
506
|
-
logger.debug(
|
|
507
|
-
`[Framework] dispatch intent: ${intent.id}`,
|
|
508
|
-
intent.params
|
|
509
|
-
);
|
|
510
|
-
return this.intentDispatcher.dispatch(intent, this.container);
|
|
511
|
-
}
|
|
512
|
-
/** 执行 Action — 处理用户交互 */
|
|
513
|
-
async perform(action) {
|
|
514
|
-
const logger = this.container.resolve(DEP_KEYS.LOGGER);
|
|
515
|
-
logger.debug(`[Framework] perform action: ${action.kind}`);
|
|
516
|
-
return this.actionDispatcher.perform(action);
|
|
517
|
-
}
|
|
518
|
-
/** 路由 URL — 将 URL 解析为 Intent + Action */
|
|
519
|
-
routeUrl(url) {
|
|
520
|
-
return this.router.resolve(url);
|
|
521
|
-
}
|
|
522
|
-
/** 记录页面访问事件 */
|
|
523
|
-
didEnterPage(page) {
|
|
524
|
-
const metrics = this.container.resolve(
|
|
525
|
-
DEP_KEYS.METRICS
|
|
526
|
-
);
|
|
527
|
-
metrics.recordPageView(page.pageType, {
|
|
528
|
-
pageId: page.id,
|
|
529
|
-
title: page.title
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
/** 注册 Action 处理器 */
|
|
533
|
-
onAction(kind, handler) {
|
|
534
|
-
this.actionDispatcher.onAction(kind, handler);
|
|
535
|
-
}
|
|
536
|
-
/** 注册 Intent Controller */
|
|
537
|
-
registerIntent(controller) {
|
|
538
|
-
this.intentDispatcher.register(controller);
|
|
539
|
-
}
|
|
540
|
-
/** 销毁 Framework 实例 */
|
|
541
|
-
dispose() {
|
|
542
|
-
this.container.dispose();
|
|
543
|
-
}
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
// ../core/src/http/client.ts
|
|
547
|
-
var HttpError = class extends Error {
|
|
548
|
-
constructor(status, statusText, body) {
|
|
549
|
-
super(`HTTP ${status}: ${statusText}`);
|
|
550
|
-
this.status = status;
|
|
551
|
-
this.statusText = statusText;
|
|
552
|
-
this.body = body;
|
|
553
|
-
this.name = "HttpError";
|
|
554
|
-
}
|
|
555
|
-
};
|
|
556
|
-
var HttpClient = class {
|
|
557
|
-
baseUrl;
|
|
558
|
-
defaultHeaders;
|
|
559
|
-
fetchFn;
|
|
560
|
-
constructor(config) {
|
|
561
|
-
this.baseUrl = config.baseUrl;
|
|
562
|
-
this.defaultHeaders = config.defaultHeaders ?? {};
|
|
563
|
-
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
564
|
-
}
|
|
565
|
-
/** GET 请求,返回解析后的 JSON */
|
|
566
|
-
async get(path, params) {
|
|
567
|
-
return this.request("GET", path, { params });
|
|
568
|
-
}
|
|
569
|
-
/** POST 请求,自动序列化 body 为 JSON */
|
|
570
|
-
async post(path, body, params) {
|
|
571
|
-
return this.request("POST", path, { body, params });
|
|
572
|
-
}
|
|
573
|
-
/** PUT 请求 */
|
|
574
|
-
async put(path, body, params) {
|
|
575
|
-
return this.request("PUT", path, { body, params });
|
|
576
|
-
}
|
|
577
|
-
/** DELETE 请求 */
|
|
578
|
-
async del(path, params) {
|
|
579
|
-
return this.request("DELETE", path, { params });
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* 底层请求方法 — 子类可覆写以自定义行为
|
|
583
|
-
*
|
|
584
|
-
* 自动处理:
|
|
585
|
-
* - URL 拼接 (baseUrl + path + params)
|
|
586
|
-
* - 默认 headers 合并
|
|
587
|
-
* - JSON body 序列化
|
|
588
|
-
* - 响应 JSON 解析
|
|
589
|
-
* - 非 2xx 状态码抛出 HttpError
|
|
590
|
-
*/
|
|
591
|
-
async request(method, path, options) {
|
|
592
|
-
const url = this.buildUrl(path, options?.params);
|
|
593
|
-
const headers = {
|
|
594
|
-
...this.defaultHeaders,
|
|
595
|
-
...options?.headers
|
|
596
|
-
};
|
|
597
|
-
const init = { method, headers };
|
|
598
|
-
if (options?.body !== void 0) {
|
|
599
|
-
headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
|
|
600
|
-
init.body = JSON.stringify(options.body);
|
|
601
|
-
}
|
|
602
|
-
const response = await this.fetchFn(url, init);
|
|
603
|
-
if (!response.ok) {
|
|
604
|
-
const body = await response.text().catch(() => void 0);
|
|
605
|
-
throw new HttpError(response.status, response.statusText, body);
|
|
606
|
-
}
|
|
607
|
-
return response.json();
|
|
608
|
-
}
|
|
609
|
-
/** 构建完整 URL — 子类可覆写以自定义 URL 拼接逻辑 */
|
|
610
|
-
buildUrl(path, params) {
|
|
611
|
-
const base = this.baseUrl.endsWith("/") ? this.baseUrl.slice(0, -1) : this.baseUrl;
|
|
612
|
-
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
613
|
-
const url = new URL(`${base}${normalizedPath}`, "http://placeholder");
|
|
614
|
-
if (params) {
|
|
615
|
-
for (const [k, v] of Object.entries(params)) {
|
|
616
|
-
url.searchParams.set(k, v);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
if (this.baseUrl.startsWith("http")) {
|
|
620
|
-
return url.toString();
|
|
621
|
-
}
|
|
622
|
-
return `${url.pathname}${url.search}`;
|
|
623
|
-
}
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
// ../core/src/intents/base-controller.ts
|
|
627
|
-
var BaseController = class {
|
|
628
|
-
/**
|
|
629
|
-
* 错误回退 — 子类可选覆写
|
|
630
|
-
*
|
|
631
|
-
* 当 execute() 抛出异常时调用。
|
|
632
|
-
* 默认行为: 重新抛出原始错误。
|
|
633
|
-
*
|
|
634
|
-
* @param params - Intent 参数
|
|
635
|
-
* @param error - execute() 抛出的错误
|
|
636
|
-
* @returns 回退数据
|
|
637
|
-
*/
|
|
638
|
-
fallback(params, error) {
|
|
639
|
-
throw error;
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* IntentController.perform() 实现
|
|
643
|
-
*
|
|
644
|
-
* 自动 try/catch → fallback 模式。
|
|
645
|
-
*/
|
|
646
|
-
async perform(intent, container) {
|
|
647
|
-
const params = intent.params ?? {};
|
|
648
|
-
try {
|
|
649
|
-
return await this.execute(params, container);
|
|
650
|
-
} catch (e) {
|
|
651
|
-
return this.fallback(
|
|
652
|
-
params,
|
|
653
|
-
e instanceof Error ? e : new Error(String(e))
|
|
654
|
-
);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
};
|
|
658
|
-
|
|
659
|
-
// ../core/src/data/mapper.ts
|
|
660
|
-
function pipe(...mappers) {
|
|
661
|
-
return (input) => mappers.reduce((acc, mapper) => mapper(acc), input);
|
|
662
|
-
}
|
|
663
|
-
function pipeAsync(...mappers) {
|
|
664
|
-
return async (input) => {
|
|
665
|
-
let acc = input;
|
|
666
|
-
for (const mapper of mappers) {
|
|
667
|
-
acc = await mapper(acc);
|
|
668
|
-
}
|
|
669
|
-
return acc;
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
function mapEach(mapper) {
|
|
673
|
-
return (items) => items.map(mapper);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// ../core/src/bootstrap/define-routes.ts
|
|
677
|
-
function defineRoutes(framework, definitions) {
|
|
678
|
-
const registeredIntents = /* @__PURE__ */ new Set();
|
|
679
|
-
for (const def of definitions) {
|
|
680
|
-
if (def.controller && !registeredIntents.has(def.intentId)) {
|
|
681
|
-
framework.registerIntent(def.controller);
|
|
682
|
-
registeredIntents.add(def.intentId);
|
|
683
|
-
}
|
|
684
|
-
framework.router.add(def.path, def.intentId);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
// ../core/src/utils/lru-map.ts
|
|
689
|
-
var LruMap = class {
|
|
690
|
-
map = /* @__PURE__ */ new Map();
|
|
691
|
-
capacity;
|
|
692
|
-
constructor(capacity) {
|
|
693
|
-
this.capacity = capacity;
|
|
694
|
-
}
|
|
695
|
-
get(key) {
|
|
696
|
-
const value = this.map.get(key);
|
|
697
|
-
if (value !== void 0) {
|
|
698
|
-
this.map.delete(key);
|
|
699
|
-
this.map.set(key, value);
|
|
700
|
-
}
|
|
701
|
-
return value;
|
|
702
|
-
}
|
|
703
|
-
set(key, value) {
|
|
704
|
-
if (this.map.has(key)) {
|
|
705
|
-
this.map.delete(key);
|
|
706
|
-
} else if (this.map.size >= this.capacity) {
|
|
707
|
-
const oldest = this.map.keys().next().value;
|
|
708
|
-
if (oldest !== void 0) {
|
|
709
|
-
this.map.delete(oldest);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
this.map.set(key, value);
|
|
713
|
-
}
|
|
714
|
-
has(key) {
|
|
715
|
-
return this.map.has(key);
|
|
716
|
-
}
|
|
717
|
-
delete(key) {
|
|
718
|
-
return this.map.delete(key);
|
|
719
|
-
}
|
|
720
|
-
get size() {
|
|
721
|
-
return this.map.size;
|
|
722
|
-
}
|
|
723
|
-
clear() {
|
|
724
|
-
this.map.clear();
|
|
725
|
-
}
|
|
726
|
-
};
|
|
727
|
-
|
|
728
|
-
// ../core/src/utils/optional.ts
|
|
729
|
-
function isSome(value) {
|
|
730
|
-
return value !== null && value !== void 0;
|
|
731
|
-
}
|
|
732
|
-
function isNone(value) {
|
|
733
|
-
return value === null || value === void 0;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// ../core/src/utils/url.ts
|
|
737
|
-
function removeScheme(url) {
|
|
738
|
-
return url.replace(/^https?:\/\//, "");
|
|
739
|
-
}
|
|
740
|
-
function removeHost(url) {
|
|
741
|
-
try {
|
|
742
|
-
const parsed = new URL(url);
|
|
743
|
-
return parsed.pathname + parsed.search + parsed.hash;
|
|
744
|
-
} catch {
|
|
745
|
-
return url;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
function removeQueryParams(url) {
|
|
749
|
-
return url.split("?")[0];
|
|
750
|
-
}
|
|
751
|
-
function getBaseUrl(url) {
|
|
752
|
-
return url.split("?")[0].split("#")[0];
|
|
753
|
-
}
|
|
754
|
-
function buildUrl(path, params) {
|
|
755
|
-
if (!params) return path;
|
|
756
|
-
const searchParams = new URLSearchParams();
|
|
757
|
-
for (const [key, value] of Object.entries(params)) {
|
|
758
|
-
if (value !== void 0) {
|
|
759
|
-
searchParams.set(key, value);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
const qs = searchParams.toString();
|
|
763
|
-
return qs ? `${path}?${qs}` : path;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// ../core/src/utils/uuid.ts
|
|
767
|
-
function generateUuid() {
|
|
768
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
769
|
-
return crypto.randomUUID();
|
|
770
|
-
}
|
|
771
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
772
|
-
const r = Math.random() * 16 | 0;
|
|
773
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
774
|
-
return v.toString(16);
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// ../browser/src/action-handlers/external-url-action.ts
|
|
779
|
-
function registerExternalUrlHandler(deps) {
|
|
780
|
-
const { framework, log } = deps;
|
|
781
|
-
framework.onAction(
|
|
782
|
-
ACTION_KINDS.EXTERNAL_URL,
|
|
783
|
-
(action) => {
|
|
784
|
-
log.debug(`ExternalUrlAction \u2192 ${action.url}`);
|
|
785
|
-
window.open(action.url, "_blank", "noopener,noreferrer");
|
|
786
|
-
}
|
|
787
|
-
);
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
// ../browser/src/utils/try-scroll.ts
|
|
791
|
-
var MAX_TRIES = 100;
|
|
792
|
-
var FUDGE = 16;
|
|
793
|
-
var pendingFrame = null;
|
|
794
|
-
function tryScroll(log, getScrollableElement, scrollY) {
|
|
795
|
-
if (pendingFrame !== null) {
|
|
796
|
-
cancelAnimationFrame(pendingFrame);
|
|
797
|
-
pendingFrame = null;
|
|
798
|
-
}
|
|
799
|
-
let tries = 0;
|
|
800
|
-
pendingFrame = requestAnimationFrame(function attempt() {
|
|
801
|
-
if (++tries >= MAX_TRIES) {
|
|
802
|
-
log.warn(
|
|
803
|
-
`tryScroll: gave up after ${MAX_TRIES} frames, target=${scrollY}`
|
|
804
|
-
);
|
|
805
|
-
pendingFrame = null;
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
const el = getScrollableElement();
|
|
809
|
-
if (!el) {
|
|
810
|
-
log.warn(
|
|
811
|
-
"could not restore scroll: the scrollable element is missing"
|
|
812
|
-
);
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
const { scrollHeight, offsetHeight } = el;
|
|
816
|
-
const canScroll = scrollY + offsetHeight <= scrollHeight + FUDGE;
|
|
817
|
-
if (!canScroll) {
|
|
818
|
-
log.info("page is not tall enough for scroll yet", {
|
|
819
|
-
scrollHeight,
|
|
820
|
-
offsetHeight
|
|
821
|
-
});
|
|
822
|
-
pendingFrame = requestAnimationFrame(attempt);
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
el.scrollTop = scrollY;
|
|
826
|
-
log.info("scroll restored to", scrollY);
|
|
827
|
-
pendingFrame = null;
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// ../browser/src/utils/history.ts
|
|
832
|
-
var HISTORY_SIZE_LIMIT = 10;
|
|
833
|
-
var History = class {
|
|
834
|
-
entries;
|
|
835
|
-
log;
|
|
836
|
-
getScrollablePageElement;
|
|
837
|
-
currentStateId;
|
|
838
|
-
constructor(log, options, sizeLimit = HISTORY_SIZE_LIMIT) {
|
|
839
|
-
this.entries = new LruMap(sizeLimit);
|
|
840
|
-
this.log = log;
|
|
841
|
-
this.getScrollablePageElement = options.getScrollablePageElement;
|
|
842
|
-
}
|
|
843
|
-
replaceState(state, url) {
|
|
844
|
-
const id = generateUuid();
|
|
845
|
-
window.history.replaceState({ id }, "", url);
|
|
846
|
-
this.currentStateId = id;
|
|
847
|
-
this.entries.set(id, { state, scrollY: 0 });
|
|
848
|
-
this.scrollTop = 0;
|
|
849
|
-
this.log.info("replaceState", state, url, id);
|
|
850
|
-
}
|
|
851
|
-
pushState(state, url) {
|
|
852
|
-
const id = generateUuid();
|
|
853
|
-
window.history.pushState({ id }, "", url);
|
|
854
|
-
this.currentStateId = id;
|
|
855
|
-
this.entries.set(id, { state, scrollY: 0 });
|
|
856
|
-
this.scrollTop = 0;
|
|
857
|
-
this.log.info("pushState", state, url, id);
|
|
858
|
-
}
|
|
859
|
-
beforeTransition() {
|
|
860
|
-
const { state } = window.history;
|
|
861
|
-
if (!state) return;
|
|
862
|
-
const oldEntry = this.entries.get(state.id);
|
|
863
|
-
if (!oldEntry) {
|
|
864
|
-
this.log.info(
|
|
865
|
-
"current history state evicted from LRU, not saving scroll position"
|
|
866
|
-
);
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
const { scrollTop } = this;
|
|
870
|
-
this.entries.set(state.id, { ...oldEntry, scrollY: scrollTop });
|
|
871
|
-
this.log.info("saving scroll position", scrollTop);
|
|
872
|
-
}
|
|
873
|
-
onPopState(listener) {
|
|
874
|
-
window.addEventListener("popstate", (event) => {
|
|
875
|
-
this.currentStateId = event.state?.id;
|
|
876
|
-
if (!this.currentStateId) {
|
|
877
|
-
this.log.warn(
|
|
878
|
-
"encountered a null event.state.id in onPopState event:",
|
|
879
|
-
window.location.href
|
|
880
|
-
);
|
|
881
|
-
}
|
|
882
|
-
this.log.info("popstate", this.entries, this.currentStateId);
|
|
883
|
-
const entry = this.currentStateId ? this.entries.get(this.currentStateId) : void 0;
|
|
884
|
-
listener(window.location.href, entry?.state);
|
|
885
|
-
if (!entry) {
|
|
886
|
-
return;
|
|
887
|
-
}
|
|
888
|
-
const { scrollY } = entry;
|
|
889
|
-
this.log.info("restoring scroll to", scrollY);
|
|
890
|
-
tryScroll(this.log, () => this.getScrollablePageElement(), scrollY);
|
|
891
|
-
});
|
|
892
|
-
}
|
|
893
|
-
updateState(update) {
|
|
894
|
-
if (!this.currentStateId) {
|
|
895
|
-
this.log.warn(
|
|
896
|
-
"failed: encountered a null currentStateId inside updateState"
|
|
897
|
-
);
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
const currentState = this.entries.get(this.currentStateId);
|
|
901
|
-
const newState = update(currentState?.state);
|
|
902
|
-
this.log.info("updateState", newState, this.currentStateId);
|
|
903
|
-
this.entries.set(this.currentStateId, {
|
|
904
|
-
...currentState,
|
|
905
|
-
state: newState
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
get scrollTop() {
|
|
909
|
-
return this.getScrollablePageElement()?.scrollTop || 0;
|
|
910
|
-
}
|
|
911
|
-
set scrollTop(scrollTop) {
|
|
912
|
-
const element = this.getScrollablePageElement();
|
|
913
|
-
if (element) {
|
|
914
|
-
element.scrollTop = scrollTop;
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
};
|
|
918
|
-
|
|
919
|
-
// ../browser/src/action-handlers/flow-action.ts
|
|
920
|
-
function registerFlowActionHandler(deps) {
|
|
921
|
-
const { framework, log, callbacks, updateApp } = deps;
|
|
922
|
-
let isFirstPage = true;
|
|
923
|
-
const history = new History(log, {
|
|
924
|
-
getScrollablePageElement: () => document.getElementById("scrollable-page-override") || document.getElementById("scrollable-page") || document.documentElement
|
|
925
|
-
});
|
|
926
|
-
framework.onAction(ACTION_KINDS.FLOW, async (action) => {
|
|
927
|
-
const flowAction = action;
|
|
928
|
-
const url = flowAction.url;
|
|
929
|
-
log.debug(`FlowAction \u2192 ${url}`);
|
|
930
|
-
if (flowAction.presentationContext === "modal") {
|
|
931
|
-
const match2 = framework.routeUrl(url);
|
|
932
|
-
if (match2) {
|
|
933
|
-
const page = await framework.dispatch(
|
|
934
|
-
match2.intent
|
|
935
|
-
);
|
|
936
|
-
callbacks.onModal(page);
|
|
937
|
-
}
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
const shouldReplace = isFirstPage;
|
|
941
|
-
const match = framework.routeUrl(url);
|
|
942
|
-
if (!match) {
|
|
943
|
-
log.warn(`FlowAction: no route for ${url}`);
|
|
944
|
-
return;
|
|
945
|
-
}
|
|
946
|
-
const pagePromise = framework.dispatch(
|
|
947
|
-
match.intent
|
|
948
|
-
);
|
|
949
|
-
await Promise.race([
|
|
950
|
-
pagePromise,
|
|
951
|
-
new Promise((r) => setTimeout(r, 500))
|
|
952
|
-
]).catch(() => {
|
|
953
|
-
});
|
|
954
|
-
history.beforeTransition();
|
|
955
|
-
updateApp({
|
|
956
|
-
page: pagePromise.then((page) => {
|
|
957
|
-
const canonicalURL = url;
|
|
958
|
-
if (shouldReplace) {
|
|
959
|
-
history.replaceState({ page }, canonicalURL);
|
|
960
|
-
} else {
|
|
961
|
-
history.pushState({ page }, canonicalURL);
|
|
962
|
-
}
|
|
963
|
-
callbacks.onNavigate(
|
|
964
|
-
new URL(canonicalURL, window.location.origin).pathname
|
|
965
|
-
);
|
|
966
|
-
didEnterPage(page);
|
|
967
|
-
return page;
|
|
968
|
-
}),
|
|
969
|
-
isFirstPage
|
|
970
|
-
});
|
|
971
|
-
isFirstPage = false;
|
|
972
|
-
});
|
|
973
|
-
history.onPopState(async (url, cachedState) => {
|
|
974
|
-
log.debug(`popstate \u2192 ${url}, cached=${!!cachedState}`);
|
|
975
|
-
callbacks.onNavigate(new URL(url).pathname);
|
|
976
|
-
if (cachedState) {
|
|
977
|
-
const { page } = cachedState;
|
|
978
|
-
didEnterPage(page);
|
|
979
|
-
updateApp({ page, isFirstPage });
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
const parsed = new URL(url);
|
|
983
|
-
const routeMatch = framework.routeUrl(parsed.pathname + parsed.search);
|
|
984
|
-
if (!routeMatch) {
|
|
985
|
-
log.error(
|
|
986
|
-
"received popstate without data, but URL was unroutable:",
|
|
987
|
-
url
|
|
988
|
-
);
|
|
989
|
-
didEnterPage(null);
|
|
990
|
-
updateApp({
|
|
991
|
-
page: Promise.reject(new Error("404")),
|
|
992
|
-
isFirstPage
|
|
993
|
-
});
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
|
-
const pagePromise = framework.dispatch(
|
|
997
|
-
routeMatch.intent
|
|
998
|
-
);
|
|
999
|
-
await Promise.race([
|
|
1000
|
-
pagePromise,
|
|
1001
|
-
new Promise((r) => setTimeout(r, 500))
|
|
1002
|
-
]).catch(() => {
|
|
1003
|
-
});
|
|
1004
|
-
updateApp({
|
|
1005
|
-
page: pagePromise.then((page) => {
|
|
1006
|
-
didEnterPage(page);
|
|
1007
|
-
return page;
|
|
1008
|
-
}),
|
|
1009
|
-
isFirstPage
|
|
1010
|
-
});
|
|
1011
|
-
});
|
|
1012
|
-
function didEnterPage(page) {
|
|
1013
|
-
(async () => {
|
|
1014
|
-
try {
|
|
1015
|
-
if (page) {
|
|
1016
|
-
await framework.didEnterPage(page);
|
|
1017
|
-
}
|
|
1018
|
-
} catch (e) {
|
|
1019
|
-
log.error("didEnterPage error:", e);
|
|
1020
|
-
}
|
|
1021
|
-
})();
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
// ../browser/src/action-handlers/register.ts
|
|
1026
|
-
function registerActionHandlers(deps) {
|
|
1027
|
-
const { framework, log, callbacks, updateApp } = deps;
|
|
1028
|
-
registerFlowActionHandler({
|
|
1029
|
-
framework,
|
|
1030
|
-
log,
|
|
1031
|
-
callbacks,
|
|
1032
|
-
updateApp
|
|
1033
|
-
});
|
|
1034
|
-
registerExternalUrlHandler({ framework, log });
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
// ../browser/src/server-data.ts
|
|
1038
|
-
var SERVER_DATA_ID = "serialized-server-data";
|
|
1039
|
-
function deserializeServerData() {
|
|
1040
|
-
const script = document.getElementById(SERVER_DATA_ID);
|
|
1041
|
-
if (!script?.textContent) return void 0;
|
|
1042
|
-
script.parentNode?.removeChild(script);
|
|
1043
|
-
try {
|
|
1044
|
-
return JSON.parse(script.textContent);
|
|
1045
|
-
} catch {
|
|
1046
|
-
return void 0;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
function createPrefetchedIntentsFromDom() {
|
|
1050
|
-
const data = deserializeServerData();
|
|
1051
|
-
if (!data || !Array.isArray(data)) {
|
|
1052
|
-
return PrefetchedIntents.empty();
|
|
1053
|
-
}
|
|
1054
|
-
return PrefetchedIntents.fromArray(data);
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
// ../browser/src/start-app.ts
|
|
1058
|
-
async function startBrowserApp(config) {
|
|
1059
|
-
const { bootstrap, defaultLocale = "en", mount, callbacks } = config;
|
|
1060
|
-
const prefetchedIntents = createPrefetchedIntentsFromDom();
|
|
1061
|
-
const framework = Framework.create({ prefetchedIntents });
|
|
1062
|
-
bootstrap(framework);
|
|
1063
|
-
const loggerFactory = framework.container.resolve(
|
|
1064
|
-
DEP_KEYS.LOGGER_FACTORY
|
|
1065
|
-
);
|
|
1066
|
-
const log = loggerFactory.loggerFor("browser");
|
|
1067
|
-
const initialAction = framework.routeUrl(
|
|
1068
|
-
window.location.pathname + window.location.search
|
|
1069
|
-
);
|
|
1070
|
-
const locale = document.documentElement.lang || defaultLocale;
|
|
1071
|
-
const target = document.getElementById("app");
|
|
1072
|
-
const updateApp = mount(target, { framework, locale });
|
|
1073
|
-
registerActionHandlers({
|
|
1074
|
-
framework,
|
|
1075
|
-
log,
|
|
1076
|
-
callbacks,
|
|
1077
|
-
updateApp
|
|
1078
|
-
});
|
|
1079
|
-
if (initialAction) {
|
|
1080
|
-
await framework.perform(initialAction.action);
|
|
1081
|
-
} else {
|
|
1082
|
-
updateApp({
|
|
1083
|
-
page: Promise.reject(new Error("404")),
|
|
1084
|
-
isFirstPage: true
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
ACTION_KINDS,
|
|
3
|
+
ActionDispatcher,
|
|
4
|
+
BaseController,
|
|
5
|
+
BaseLogger,
|
|
6
|
+
CompositeLogger,
|
|
7
|
+
CompositeLoggerFactory,
|
|
8
|
+
ConsoleLogger,
|
|
9
|
+
ConsoleLoggerFactory,
|
|
10
|
+
Container,
|
|
11
|
+
DEP_KEYS,
|
|
12
|
+
Framework,
|
|
13
|
+
History,
|
|
14
|
+
HttpClient,
|
|
15
|
+
HttpError,
|
|
16
|
+
IntentDispatcher,
|
|
17
|
+
LruMap,
|
|
18
|
+
PrefetchedIntents,
|
|
19
|
+
Router,
|
|
20
|
+
buildUrl,
|
|
21
|
+
createPrefetchedIntentsFromDom,
|
|
22
|
+
defineRoutes,
|
|
23
|
+
deserializeServerData,
|
|
24
|
+
generateUuid,
|
|
25
|
+
getBaseUrl,
|
|
26
|
+
isCompoundAction,
|
|
27
|
+
isExternalUrlAction,
|
|
28
|
+
isFlowAction,
|
|
29
|
+
isNone,
|
|
30
|
+
isSome,
|
|
31
|
+
makeDependencies,
|
|
32
|
+
makeExternalUrlAction,
|
|
33
|
+
makeFlowAction,
|
|
34
|
+
mapEach,
|
|
35
|
+
pipe,
|
|
36
|
+
pipeAsync,
|
|
37
|
+
registerActionHandlers,
|
|
38
|
+
registerExternalUrlHandler,
|
|
39
|
+
registerFlowActionHandler,
|
|
40
|
+
removeHost,
|
|
41
|
+
removeQueryParams,
|
|
42
|
+
removeScheme,
|
|
43
|
+
resetFilterCache,
|
|
44
|
+
shouldLog,
|
|
45
|
+
stableStringify,
|
|
46
|
+
startBrowserApp,
|
|
47
|
+
tryScroll
|
|
48
|
+
} from "./chunk-OVGQ4NUA.js";
|
|
1088
49
|
|
|
1089
50
|
// ../ssr/src/render.ts
|
|
1090
51
|
async function ssrRender(options) {
|
|
@@ -1129,11 +90,20 @@ function createSSRRender(config) {
|
|
|
1129
90
|
}
|
|
1130
91
|
|
|
1131
92
|
// ../ssr/src/inject.ts
|
|
93
|
+
var SSR_PLACEHOLDERS = {
|
|
94
|
+
LANG: "<!--ssr-lang-->",
|
|
95
|
+
HEAD: "<!--ssr-head-->",
|
|
96
|
+
BODY: "<!--ssr-body-->",
|
|
97
|
+
DATA: "<!--ssr-data-->"
|
|
98
|
+
};
|
|
1132
99
|
function injectSSRContent(options) {
|
|
1133
100
|
const { template, locale, head, css, html, serializedData } = options;
|
|
1134
101
|
const cssTag = css ? `<style>${css}</style>` : "";
|
|
1135
|
-
return template.replace(
|
|
1136
|
-
${cssTag}`).replace(
|
|
102
|
+
return template.replace(SSR_PLACEHOLDERS.LANG, locale).replace(SSR_PLACEHOLDERS.HEAD, `${head}
|
|
103
|
+
${cssTag}`).replace(SSR_PLACEHOLDERS.BODY, html).replace(
|
|
104
|
+
SSR_PLACEHOLDERS.DATA,
|
|
105
|
+
`<script id="serialized-server-data" type="application/json">${serializedData}</script>`
|
|
106
|
+
);
|
|
1137
107
|
}
|
|
1138
108
|
|
|
1139
109
|
// ../ssr/src/server-data.ts
|
|
@@ -1157,10 +127,10 @@ function serializeServerData(data) {
|
|
|
1157
127
|
import { Hono } from "hono";
|
|
1158
128
|
|
|
1159
129
|
// ../server/src/locale.ts
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
if (!header) return
|
|
130
|
+
function parseAcceptLanguage(header, supported, fallback) {
|
|
131
|
+
const effectiveSupported = supported ?? ["zh", "en"];
|
|
132
|
+
const effectiveFallback = fallback ?? effectiveSupported[0] ?? "en";
|
|
133
|
+
if (!header) return effectiveFallback;
|
|
1164
134
|
const langs = header.split(",").map((part) => {
|
|
1165
135
|
const [lang, q] = part.trim().split(";q=");
|
|
1166
136
|
return {
|
|
@@ -1170,11 +140,11 @@ function parseAcceptLanguage(header, supported = DEFAULT_SUPPORTED, fallback = D
|
|
|
1170
140
|
}).sort((a, b) => b.q - a.q);
|
|
1171
141
|
for (const { lang } of langs) {
|
|
1172
142
|
const prefix = lang.split("-")[0];
|
|
1173
|
-
if (
|
|
143
|
+
if (effectiveSupported.includes(prefix)) {
|
|
1174
144
|
return prefix;
|
|
1175
145
|
}
|
|
1176
146
|
}
|
|
1177
|
-
return
|
|
147
|
+
return effectiveFallback;
|
|
1178
148
|
}
|
|
1179
149
|
|
|
1180
150
|
// ../server/src/app.ts
|
|
@@ -1191,8 +161,14 @@ function createSSRApp(options) {
|
|
|
1191
161
|
const app = new Hono();
|
|
1192
162
|
async function readTemplate(url) {
|
|
1193
163
|
if (!isProduction && vite) {
|
|
1194
|
-
const { readFileSync: readFileSync2 } = await import(
|
|
1195
|
-
|
|
164
|
+
const { readFileSync: readFileSync2 } = await import(
|
|
165
|
+
/* @vite-ignore */
|
|
166
|
+
"fs"
|
|
167
|
+
);
|
|
168
|
+
const { resolve: resolve2 } = await import(
|
|
169
|
+
/* @vite-ignore */
|
|
170
|
+
"path"
|
|
171
|
+
);
|
|
1196
172
|
const raw = readFileSync2(resolve2(root, "index.html"), "utf-8");
|
|
1197
173
|
return vite.transformIndexHtml(url, raw);
|
|
1198
174
|
}
|
|
@@ -1202,8 +178,14 @@ function createSSRApp(options) {
|
|
|
1202
178
|
new URL("../dist/client/index.html", import.meta.url)
|
|
1203
179
|
);
|
|
1204
180
|
}
|
|
1205
|
-
const { readFileSync } = await import(
|
|
1206
|
-
|
|
181
|
+
const { readFileSync } = await import(
|
|
182
|
+
/* @vite-ignore */
|
|
183
|
+
"fs"
|
|
184
|
+
);
|
|
185
|
+
const { resolve } = await import(
|
|
186
|
+
/* @vite-ignore */
|
|
187
|
+
"path"
|
|
188
|
+
);
|
|
1207
189
|
return readFileSync(resolve(root, "dist/client/index.html"), "utf-8");
|
|
1208
190
|
}
|
|
1209
191
|
async function loadSSRModule() {
|
|
@@ -1271,8 +253,14 @@ async function resolveRoot(importMetaUrl, levelsUp = 0) {
|
|
|
1271
253
|
}
|
|
1272
254
|
return url.pathname;
|
|
1273
255
|
}
|
|
1274
|
-
const { dirname, resolve, normalize } = await import(
|
|
1275
|
-
|
|
256
|
+
const { dirname, resolve, normalize } = await import(
|
|
257
|
+
/* @vite-ignore */
|
|
258
|
+
"path"
|
|
259
|
+
);
|
|
260
|
+
const { fileURLToPath } = await import(
|
|
261
|
+
/* @vite-ignore */
|
|
262
|
+
"url"
|
|
263
|
+
);
|
|
1276
264
|
let dir = normalize(dirname(fileURLToPath(importMetaUrl)));
|
|
1277
265
|
for (let i = 0; i < levelsUp; i++) {
|
|
1278
266
|
dir = resolve(dir, "..");
|
|
@@ -1283,31 +271,71 @@ async function resolveRoot(importMetaUrl, levelsUp = 0) {
|
|
|
1283
271
|
// ../server/src/start.ts
|
|
1284
272
|
import { Hono as Hono2 } from "hono";
|
|
1285
273
|
async function startServer(options) {
|
|
1286
|
-
const {
|
|
274
|
+
const {
|
|
275
|
+
app,
|
|
276
|
+
root,
|
|
277
|
+
port = 3e3,
|
|
278
|
+
isProduction,
|
|
279
|
+
vite,
|
|
280
|
+
routes,
|
|
281
|
+
locales,
|
|
282
|
+
ssrEntryPath
|
|
283
|
+
} = options;
|
|
1287
284
|
const { isDeno, isBun, isVercel } = options.runtime ?? detectRuntime();
|
|
285
|
+
function printStartupBanner() {
|
|
286
|
+
const lines = [
|
|
287
|
+
`
|
|
288
|
+
Server running at http://localhost:${port}
|
|
289
|
+
`
|
|
290
|
+
];
|
|
291
|
+
if (routes && routes.length > 0) {
|
|
292
|
+
lines.push(" Routes:");
|
|
293
|
+
for (const r of routes) {
|
|
294
|
+
lines.push(` ${r}`);
|
|
295
|
+
}
|
|
296
|
+
lines.push("");
|
|
297
|
+
}
|
|
298
|
+
if (locales && locales.length > 0) {
|
|
299
|
+
lines.push(` Locales: ${locales.join(", ")}`);
|
|
300
|
+
}
|
|
301
|
+
if (ssrEntryPath) {
|
|
302
|
+
lines.push(` SSR Entry: ${ssrEntryPath}`);
|
|
303
|
+
}
|
|
304
|
+
if (locales?.length || ssrEntryPath) {
|
|
305
|
+
lines.push("");
|
|
306
|
+
}
|
|
307
|
+
console.log(lines.join("\n"));
|
|
308
|
+
}
|
|
1288
309
|
if (isVercel) {
|
|
1289
310
|
return { vite };
|
|
1290
311
|
}
|
|
1291
312
|
if (!isProduction) {
|
|
1292
313
|
let devVite = vite;
|
|
1293
314
|
if (!devVite) {
|
|
1294
|
-
const { createServer: createViteServer } = await import(
|
|
315
|
+
const { createServer: createViteServer } = await import(
|
|
316
|
+
/* @vite-ignore */
|
|
317
|
+
"vite"
|
|
318
|
+
);
|
|
1295
319
|
devVite = await createViteServer({
|
|
1296
320
|
root,
|
|
1297
321
|
server: { middlewareMode: true },
|
|
1298
322
|
appType: "custom"
|
|
1299
323
|
});
|
|
1300
324
|
}
|
|
1301
|
-
const { getRequestListener } = await import(
|
|
1302
|
-
|
|
325
|
+
const { getRequestListener } = await import(
|
|
326
|
+
/* @vite-ignore */
|
|
327
|
+
"@hono/node-server"
|
|
328
|
+
);
|
|
329
|
+
const { createServer: createServer2 } = await import(
|
|
330
|
+
/* @vite-ignore */
|
|
331
|
+
"http"
|
|
332
|
+
);
|
|
1303
333
|
const listener = getRequestListener(app.fetch);
|
|
1304
334
|
const server = createServer2((req, res) => {
|
|
1305
335
|
devVite.middlewares(req, res, () => listener(req, res));
|
|
1306
336
|
});
|
|
1307
337
|
server.listen(port, () => {
|
|
1308
|
-
|
|
1309
|
-
Server running at http://localhost:${port}
|
|
1310
|
-
`);
|
|
338
|
+
printStartupBanner();
|
|
1311
339
|
});
|
|
1312
340
|
return { vite: devVite };
|
|
1313
341
|
}
|
|
@@ -1315,16 +343,23 @@ async function startServer(options) {
|
|
|
1315
343
|
globalThis.Deno.serve({ port }, app.fetch);
|
|
1316
344
|
} else if (isBun) {
|
|
1317
345
|
} else {
|
|
1318
|
-
const { serveStatic } = await import(
|
|
1319
|
-
|
|
346
|
+
const { serveStatic } = await import(
|
|
347
|
+
/* @vite-ignore */
|
|
348
|
+
"@hono/node-server/serve-static"
|
|
349
|
+
);
|
|
350
|
+
const { resolve } = await import(
|
|
351
|
+
/* @vite-ignore */
|
|
352
|
+
"path"
|
|
353
|
+
);
|
|
1320
354
|
const prodApp = new Hono2();
|
|
1321
355
|
prodApp.use("/*", serveStatic({ root: resolve(root, "dist/client") }));
|
|
1322
356
|
prodApp.route("/", app);
|
|
1323
|
-
const { serve } = await import(
|
|
357
|
+
const { serve } = await import(
|
|
358
|
+
/* @vite-ignore */
|
|
359
|
+
"@hono/node-server"
|
|
360
|
+
);
|
|
1324
361
|
serve({ fetch: prodApp.fetch, port }, () => {
|
|
1325
|
-
|
|
1326
|
-
Server running at http://localhost:${port}
|
|
1327
|
-
`);
|
|
362
|
+
printStartupBanner();
|
|
1328
363
|
});
|
|
1329
364
|
}
|
|
1330
365
|
return { vite };
|
|
@@ -1341,12 +376,21 @@ async function createServer(config = {}) {
|
|
|
1341
376
|
ssr
|
|
1342
377
|
} = config;
|
|
1343
378
|
const root = rootOverride ?? process.cwd();
|
|
1344
|
-
const { existsSync } = await import(
|
|
1345
|
-
|
|
379
|
+
const { existsSync } = await import(
|
|
380
|
+
/* @vite-ignore */
|
|
381
|
+
"fs"
|
|
382
|
+
);
|
|
383
|
+
const { resolve } = await import(
|
|
384
|
+
/* @vite-ignore */
|
|
385
|
+
"path"
|
|
386
|
+
);
|
|
1346
387
|
const envPath = resolve(root, ".env");
|
|
1347
388
|
if (existsSync(envPath)) {
|
|
1348
389
|
try {
|
|
1349
|
-
const { config: dotenvConfig } = await import(
|
|
390
|
+
const { config: dotenvConfig } = await import(
|
|
391
|
+
/* @vite-ignore */
|
|
392
|
+
"dotenv"
|
|
393
|
+
);
|
|
1350
394
|
dotenvConfig({ path: envPath });
|
|
1351
395
|
} catch {
|
|
1352
396
|
}
|
|
@@ -1354,7 +398,10 @@ async function createServer(config = {}) {
|
|
|
1354
398
|
const runtime = detectRuntime();
|
|
1355
399
|
let vite;
|
|
1356
400
|
if (!runtime.isProduction && !runtime.isVercel) {
|
|
1357
|
-
const { createServer: createViteServer } = await import(
|
|
401
|
+
const { createServer: createViteServer } = await import(
|
|
402
|
+
/* @vite-ignore */
|
|
403
|
+
"vite"
|
|
404
|
+
);
|
|
1358
405
|
vite = await createViteServer({
|
|
1359
406
|
root,
|
|
1360
407
|
server: { middlewareMode: true },
|
|
@@ -1380,7 +427,9 @@ async function createServer(config = {}) {
|
|
|
1380
427
|
port,
|
|
1381
428
|
isProduction: runtime.isProduction,
|
|
1382
429
|
vite,
|
|
1383
|
-
runtime
|
|
430
|
+
runtime,
|
|
431
|
+
locales,
|
|
432
|
+
ssrEntryPath: ssr?.ssrEntryPath
|
|
1384
433
|
});
|
|
1385
434
|
return { app, vite, runtime };
|
|
1386
435
|
}
|
|
@@ -1403,6 +452,7 @@ export {
|
|
|
1403
452
|
LruMap,
|
|
1404
453
|
PrefetchedIntents,
|
|
1405
454
|
Router,
|
|
455
|
+
SSR_PLACEHOLDERS,
|
|
1406
456
|
buildUrl,
|
|
1407
457
|
createPrefetchedIntentsFromDom,
|
|
1408
458
|
createSSRApp,
|