@modern-js/server 1.2.2-beta.0 → 1.3.2
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/CHANGELOG.md +59 -0
- package/dist/js/modern/server/dev-server/dev-server.js +7 -3
- package/dist/js/node/server/dev-server/dev-server.js +7 -3
- package/dist/types/type.d.ts +1 -1
- package/jest.config.js +1 -0
- package/package.json +10 -10
- package/tests/dev.test.ts +7 -2
- package/tests/fixtures/pure/tsconfig.json +0 -1
- package/tests/server.test.ts +19 -0
- package/tests/watcher.test.ts +98 -0
- package/dist.zip +0 -0
- package/src/constants.ts +0 -26
- package/src/dev-tools/babel/register.ts +0 -37
- package/src/dev-tools/dev-server-plugin.ts +0 -48
- package/src/dev-tools/https/global.d.ts +0 -3
- package/src/dev-tools/https/index.ts +0 -12
- package/src/dev-tools/launch-editor/index.ts +0 -29
- package/src/dev-tools/mock/getMockData.ts +0 -109
- package/src/dev-tools/mock/index.ts +0 -63
- package/src/dev-tools/socket-server.ts +0 -192
- package/src/dev-tools/watcher/dependency-tree.ts +0 -94
- package/src/dev-tools/watcher/index.ts +0 -80
- package/src/dev-tools/watcher/stats-cache.ts +0 -53
- package/src/index.ts +0 -16
- package/src/libs/context/context.ts +0 -176
- package/src/libs/context/index.ts +0 -7
- package/src/libs/hook-api/route.ts +0 -38
- package/src/libs/hook-api/template.ts +0 -53
- package/src/libs/metrics.ts +0 -15
- package/src/libs/proxy.ts +0 -85
- package/src/libs/render/cache/__tests__/cache.fun.test.ts +0 -94
- package/src/libs/render/cache/__tests__/cache.test.ts +0 -240
- package/src/libs/render/cache/__tests__/cacheable.ts +0 -44
- package/src/libs/render/cache/__tests__/error-configuration.ts +0 -34
- package/src/libs/render/cache/__tests__/matched-cache.ts +0 -88
- package/src/libs/render/cache/index.ts +0 -75
- package/src/libs/render/cache/page-caches/index.ts +0 -11
- package/src/libs/render/cache/page-caches/lru.ts +0 -38
- package/src/libs/render/cache/spr.ts +0 -301
- package/src/libs/render/cache/type.ts +0 -59
- package/src/libs/render/cache/util.ts +0 -97
- package/src/libs/render/index.ts +0 -78
- package/src/libs/render/modern/browser-list.ts +0 -7
- package/src/libs/render/modern/index.ts +0 -41
- package/src/libs/render/modern/module.d.ts +0 -4
- package/src/libs/render/reader.ts +0 -119
- package/src/libs/render/ssr.ts +0 -62
- package/src/libs/render/static.ts +0 -52
- package/src/libs/render/type.ts +0 -38
- package/src/libs/route/index.ts +0 -77
- package/src/libs/route/matcher.ts +0 -93
- package/src/libs/route/route.ts +0 -32
- package/src/libs/serve-file.ts +0 -34
- package/src/server/dev-server/dev-server-split.ts +0 -41
- package/src/server/dev-server/dev-server.ts +0 -297
- package/src/server/dev-server/index.ts +0 -2
- package/src/server/index.ts +0 -163
- package/src/server/modern-server-split.ts +0 -97
- package/src/server/modern-server.ts +0 -636
- package/src/type.ts +0 -88
- package/src/utils.ts +0 -79
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import LRU from 'lru-cache';
|
|
2
|
-
import { PageCache, PageCachesInterface } from '../type';
|
|
3
|
-
|
|
4
|
-
export class LRUCaches implements PageCachesInterface {
|
|
5
|
-
caches: LRU<string, PageCache>;
|
|
6
|
-
|
|
7
|
-
private readonly max: number;
|
|
8
|
-
|
|
9
|
-
constructor(options: { max: number }) {
|
|
10
|
-
this.max = options.max;
|
|
11
|
-
this.caches = new LRU(this.max);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
init() {
|
|
15
|
-
return Promise.resolve();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
public keys(): string[] {
|
|
19
|
-
return this.caches.keys();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public get(key: string) {
|
|
23
|
-
return Promise.resolve(this.caches.get(key) || null);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public peek(key: string) {
|
|
27
|
-
return this.caches.peek(key) || null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
public set(key: string, cache: PageCache) {
|
|
31
|
-
this.caches.set(key, cache);
|
|
32
|
-
return Promise.resolve();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public del(key: string) {
|
|
36
|
-
this.caches.del(key);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/member-ordering */
|
|
2
|
-
/* eslint-disable no-lone-blocks */
|
|
3
|
-
import crypto from 'crypto';
|
|
4
|
-
import { IncomingHttpHeaders } from 'http';
|
|
5
|
-
import url from 'url';
|
|
6
|
-
import LRUCache from 'lru-cache';
|
|
7
|
-
import mime from 'mime-types';
|
|
8
|
-
import {
|
|
9
|
-
cacheAddition,
|
|
10
|
-
connectFactor,
|
|
11
|
-
fname,
|
|
12
|
-
maybeSync,
|
|
13
|
-
namespaceHash,
|
|
14
|
-
valueFactory,
|
|
15
|
-
withCoalescedInvoke,
|
|
16
|
-
} from './util';
|
|
17
|
-
import { createPageCaches } from './page-caches';
|
|
18
|
-
import {
|
|
19
|
-
PageCache,
|
|
20
|
-
PageCachesInterface,
|
|
21
|
-
CacheContent,
|
|
22
|
-
CacheManagerOptions,
|
|
23
|
-
CacheResult,
|
|
24
|
-
CacheContext,
|
|
25
|
-
CacheConfig,
|
|
26
|
-
} from './type';
|
|
27
|
-
|
|
28
|
-
const MAX_CACHE_EACH_REQ = Number(process.env.ROUTE_CACHE_LIMIT) || 10;
|
|
29
|
-
const MAX_SIZE_EACH_CLUSTER = Number(process.env.CLUSTER_CACHE_LIMIT) || 100;
|
|
30
|
-
const BASE_LEVEL = 0;
|
|
31
|
-
const QUERY_LEVEL = 1;
|
|
32
|
-
const HEADER_LEVEL = 2;
|
|
33
|
-
const QUERY_HEADER_LEVEL = 3;
|
|
34
|
-
|
|
35
|
-
class CacheManager {
|
|
36
|
-
cache: LRUCache<string, CacheContent>;
|
|
37
|
-
|
|
38
|
-
cacheOptions: CacheManagerOptions;
|
|
39
|
-
|
|
40
|
-
constructor(cacheOptions: CacheManagerOptions) {
|
|
41
|
-
this.cacheOptions = cacheOptions;
|
|
42
|
-
|
|
43
|
-
this.cache = new LRUCache({
|
|
44
|
-
max: Math.min(MAX_SIZE_EACH_CLUSTER, 600) * 1024 * 1024, // 默认存 100M,最大 600M
|
|
45
|
-
length(n: CacheContent) {
|
|
46
|
-
const len = n.caches
|
|
47
|
-
.keys()
|
|
48
|
-
.reduce((total, cur) => total + (n.caches.peek(cur)?.size || 0), 1);
|
|
49
|
-
return len;
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
private md5(content: string) {
|
|
55
|
-
const md5 = crypto.createHash('md5');
|
|
56
|
-
return md5.update(content).digest('hex');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private generateRequestKey(context: CacheContext) {
|
|
60
|
-
const { pathname, entry } = context;
|
|
61
|
-
return this.md5(`${pathname}_${entry}`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private replaceValue(value: string, matcher: Record<string, string>) {
|
|
65
|
-
let final = value;
|
|
66
|
-
Object.keys(matcher).some(replacer => {
|
|
67
|
-
const reg = new RegExp(matcher[replacer]);
|
|
68
|
-
if (reg.test(value)) {
|
|
69
|
-
final = replacer;
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
return false;
|
|
73
|
-
});
|
|
74
|
-
return final;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private factor(
|
|
78
|
-
keys: string[],
|
|
79
|
-
obj: url.URLSearchParams | IncomingHttpHeaders,
|
|
80
|
-
matches: Record<string, Record<string, string>> = {},
|
|
81
|
-
) {
|
|
82
|
-
keys.sort();
|
|
83
|
-
const getValue = valueFactory(obj);
|
|
84
|
-
|
|
85
|
-
const factorAry = keys.reduce((ary: string[], key: string) => {
|
|
86
|
-
let value: string = getValue(key) || '';
|
|
87
|
-
const matcher = matches[key];
|
|
88
|
-
if (matcher) {
|
|
89
|
-
value = this.replaceValue(value, matcher);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return ary.concat([key, value]);
|
|
93
|
-
}, []);
|
|
94
|
-
return factorAry.join(',');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private queryFactor(context: CacheContext, data: CacheContent) {
|
|
98
|
-
const queryKeys = data.includes?.query;
|
|
99
|
-
const queryMatches = data.matches?.query;
|
|
100
|
-
if (!queryKeys || queryKeys.length === 0) {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
const requestQuery: any = context.query;
|
|
104
|
-
const queryFactor = this.factor(queryKeys, requestQuery, queryMatches);
|
|
105
|
-
return queryFactor;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private headerFactor(context: CacheContext, data: CacheContent) {
|
|
109
|
-
const headerKeys = data.includes?.header;
|
|
110
|
-
const headerMatches = data.matches?.header;
|
|
111
|
-
if (!headerKeys || headerKeys.length === 0) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
const requestHeader: any = context.headers;
|
|
115
|
-
const headerFactor = this.factor(headerKeys, requestHeader, headerMatches);
|
|
116
|
-
return headerFactor;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private readonly find: any = (() => {
|
|
120
|
-
{
|
|
121
|
-
// eslint-disable-next-line consistent-this,@typescript-eslint/no-this-alias
|
|
122
|
-
const _this = this;
|
|
123
|
-
return {
|
|
124
|
-
[fname(BASE_LEVEL)](
|
|
125
|
-
context: CacheContext,
|
|
126
|
-
cacheKey: string,
|
|
127
|
-
// data: CacheContent,
|
|
128
|
-
): string | null {
|
|
129
|
-
return _this.md5(cacheKey);
|
|
130
|
-
},
|
|
131
|
-
[fname(QUERY_LEVEL)](
|
|
132
|
-
context: CacheContext,
|
|
133
|
-
cacheKey: string,
|
|
134
|
-
data: CacheContent,
|
|
135
|
-
): string | null {
|
|
136
|
-
const queryFactor = _this.queryFactor(context, data);
|
|
137
|
-
if (!queryFactor) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
return _this.md5(connectFactor(cacheKey, queryFactor));
|
|
141
|
-
},
|
|
142
|
-
[fname(HEADER_LEVEL)](
|
|
143
|
-
context: CacheContext,
|
|
144
|
-
cacheKey: string,
|
|
145
|
-
data: CacheContent,
|
|
146
|
-
): string | null {
|
|
147
|
-
const headerFactor = _this.headerFactor(context, data);
|
|
148
|
-
if (!headerFactor) {
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
return _this.md5(connectFactor(cacheKey, headerFactor));
|
|
152
|
-
},
|
|
153
|
-
[fname(QUERY_HEADER_LEVEL)](
|
|
154
|
-
context: CacheContext,
|
|
155
|
-
cacheKey: string,
|
|
156
|
-
data: CacheContent,
|
|
157
|
-
): string | null {
|
|
158
|
-
const queryFactor = _this.queryFactor(context, data);
|
|
159
|
-
const headerFactor = _this.headerFactor(context, data);
|
|
160
|
-
if (!queryFactor || !headerFactor) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
return _this.md5(connectFactor(cacheKey, headerFactor, queryFactor));
|
|
164
|
-
},
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
})();
|
|
168
|
-
|
|
169
|
-
private async best(
|
|
170
|
-
context: CacheContext,
|
|
171
|
-
cacheKey: string,
|
|
172
|
-
data: CacheContent,
|
|
173
|
-
): Promise<PageCache | null> {
|
|
174
|
-
const { level } = data;
|
|
175
|
-
const cacheHash = this.find[fname(level)](context, cacheKey, data);
|
|
176
|
-
if (!cacheHash) {
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
return data.caches.get(cacheHash);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
private createCacheContent(
|
|
183
|
-
config: CacheConfig,
|
|
184
|
-
caches: PageCachesInterface,
|
|
185
|
-
): CacheContent {
|
|
186
|
-
return {
|
|
187
|
-
level: config.level,
|
|
188
|
-
interval: config.interval,
|
|
189
|
-
includes: config.includes || null,
|
|
190
|
-
limit: config.staleLimit,
|
|
191
|
-
matches: config.matches || null,
|
|
192
|
-
caches,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async get(context: CacheContext): Promise<CacheResult | null> {
|
|
197
|
-
const cacheKey = this.generateRequestKey(context);
|
|
198
|
-
const data = this.cache.get(cacheKey);
|
|
199
|
-
|
|
200
|
-
// no cache key matched
|
|
201
|
-
if (!data) {
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const dest = await this.best(context, cacheKey, data);
|
|
206
|
-
// no cache for current page with current config
|
|
207
|
-
if (!dest) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const { expireTime, limitTime, html, cacheHash } = dest;
|
|
212
|
-
const isStale = Date.now() - expireTime > 0;
|
|
213
|
-
const isGarbage = limitTime ? Date.now() - limitTime > 0 : false;
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
content: html || '',
|
|
217
|
-
contentType: mime.contentType('html') as string,
|
|
218
|
-
isStale,
|
|
219
|
-
isGarbage,
|
|
220
|
-
hash: cacheHash,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async set(
|
|
225
|
-
context: CacheContext,
|
|
226
|
-
html: string,
|
|
227
|
-
cacheConfig: CacheConfig,
|
|
228
|
-
sync = false,
|
|
229
|
-
) {
|
|
230
|
-
if (!cacheConfig) {
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// each version with route is a separate cache
|
|
235
|
-
const cacheKey = this.generateRequestKey(context);
|
|
236
|
-
let data = this.cache.get(cacheKey);
|
|
237
|
-
if (!data) {
|
|
238
|
-
const caches = await createPageCaches(MAX_CACHE_EACH_REQ);
|
|
239
|
-
// eslint-disable-next-line require-atomic-updates
|
|
240
|
-
data = this.createCacheContent(cacheConfig, caches);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const cacheHash = this.find[fname(cacheConfig.level)](
|
|
244
|
-
context,
|
|
245
|
-
cacheKey,
|
|
246
|
-
data,
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
// if cacheHash is null, maybe level not match meta key, do not cache
|
|
250
|
-
if (!cacheHash) {
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const cacheSyncOrAsync = async () => {
|
|
255
|
-
const next = data as CacheContent;
|
|
256
|
-
const limit = cacheConfig.staleLimit;
|
|
257
|
-
|
|
258
|
-
const storeHTML = cacheAddition(html, cacheHash);
|
|
259
|
-
const size = storeHTML.length;
|
|
260
|
-
await next.caches.set(cacheHash, {
|
|
261
|
-
expireTime: Date.now() + cacheConfig.interval * 1000,
|
|
262
|
-
limitTime: typeof limit === 'number' ? Date.now() + limit * 1000 : null,
|
|
263
|
-
cacheHash,
|
|
264
|
-
html: storeHTML,
|
|
265
|
-
size,
|
|
266
|
-
});
|
|
267
|
-
this.cache.set(cacheKey, next);
|
|
268
|
-
return true;
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
// cache set is async, each hash is cached only once at the same time
|
|
272
|
-
const doCache = withCoalescedInvoke(cacheSyncOrAsync).bind(
|
|
273
|
-
null,
|
|
274
|
-
namespaceHash('stream', cacheHash),
|
|
275
|
-
[],
|
|
276
|
-
);
|
|
277
|
-
return maybeSync(doCache)(sync);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
async del(context: CacheContext, cacheHash: string) {
|
|
281
|
-
const cacheKey = this.generateRequestKey(context);
|
|
282
|
-
const data = this.cache.get(cacheKey);
|
|
283
|
-
data?.caches.del(cacheHash);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
let manager: CacheManager;
|
|
288
|
-
export function createCache() {
|
|
289
|
-
if (manager) {
|
|
290
|
-
return manager;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
manager = new CacheManager({ max: 0 });
|
|
294
|
-
return manager;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
export function destroyCache() {
|
|
298
|
-
manager = null!;
|
|
299
|
-
}
|
|
300
|
-
/* eslint-enable no-lone-blocks */
|
|
301
|
-
/* eslint-enable @typescript-eslint/member-ordering */
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { IncomingHttpHeaders } from 'http';
|
|
2
|
-
import { RenderResult } from '../../../type';
|
|
3
|
-
|
|
4
|
-
export type { CacheConfig } from '../type';
|
|
5
|
-
|
|
6
|
-
export type CacheContent = {
|
|
7
|
-
level: number;
|
|
8
|
-
includes: {
|
|
9
|
-
header?: string[];
|
|
10
|
-
query?: string[];
|
|
11
|
-
} | null;
|
|
12
|
-
matches?: {
|
|
13
|
-
header?: Record<string, Record<string, string>>;
|
|
14
|
-
query?: Record<string, Record<string, string>>;
|
|
15
|
-
} | null;
|
|
16
|
-
interval: number;
|
|
17
|
-
limit: number | boolean;
|
|
18
|
-
caches: PageCachesInterface;
|
|
19
|
-
};
|
|
20
|
-
export interface PageCachesInterface {
|
|
21
|
-
caches: any;
|
|
22
|
-
|
|
23
|
-
init: () => void;
|
|
24
|
-
|
|
25
|
-
get: (key: string) => Promise<PageCache | null>;
|
|
26
|
-
|
|
27
|
-
peek: (key: string) => PageCache | null;
|
|
28
|
-
|
|
29
|
-
set: (key: string, cache: PageCache) => Promise<void>;
|
|
30
|
-
|
|
31
|
-
del: (key: string) => void;
|
|
32
|
-
|
|
33
|
-
keys: () => string[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type PageCache = {
|
|
37
|
-
html: string;
|
|
38
|
-
expireTime: number;
|
|
39
|
-
limitTime: number | null;
|
|
40
|
-
cacheHash: string;
|
|
41
|
-
size: number;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export type CacheManagerOptions = {
|
|
45
|
-
max: number;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export interface CacheResult extends RenderResult {
|
|
49
|
-
isStale: boolean;
|
|
50
|
-
isGarbage: boolean;
|
|
51
|
-
hash: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export type CacheContext = {
|
|
55
|
-
entry: string;
|
|
56
|
-
pathname: string;
|
|
57
|
-
query: Record<string, string>;
|
|
58
|
-
headers: IncomingHttpHeaders;
|
|
59
|
-
};
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { IncomingHttpHeaders } from 'http';
|
|
2
|
-
import url from 'url';
|
|
3
|
-
|
|
4
|
-
export function namespaceHash(namespace: string, hash: string) {
|
|
5
|
-
return `${namespace}/${hash}`;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function fname(lv: number) {
|
|
9
|
-
return `f${lv}`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function connectFactor(...args: string[]) {
|
|
13
|
-
return args.join('-');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function valueFactory(obj: url.URLSearchParams | IncomingHttpHeaders) {
|
|
17
|
-
if (obj instanceof url.URLSearchParams) {
|
|
18
|
-
return function (key: string) {
|
|
19
|
-
return obj.get(key);
|
|
20
|
-
};
|
|
21
|
-
} else {
|
|
22
|
-
return function (key: string) {
|
|
23
|
-
const value = obj[key];
|
|
24
|
-
if (Array.isArray(value)) {
|
|
25
|
-
return value.join(',');
|
|
26
|
-
}
|
|
27
|
-
return value;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function getTime([s, ns]: [number, number]): number {
|
|
33
|
-
return Math.floor(s * 1e3 + ns / 1e6);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const RE_START_IN_HEAD = /<head>/;
|
|
37
|
-
export function cacheAddition(html: string, hash: string) {
|
|
38
|
-
const additionHtml = html.replace(
|
|
39
|
-
RE_START_IN_HEAD,
|
|
40
|
-
`<head><meta name="x-moden-spr" content="${hash}">`,
|
|
41
|
-
);
|
|
42
|
-
return additionHtml;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
type CoalescedInvoke<T> = {
|
|
46
|
-
isOrigin: boolean;
|
|
47
|
-
value: T;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
|
|
51
|
-
const globalInvokeCache = new Map<string, Promise<CoalescedInvoke<unknown>>>();
|
|
52
|
-
export function withCoalescedInvoke<F extends (...args: any[]) => Promise<any>>(
|
|
53
|
-
func: F,
|
|
54
|
-
): (
|
|
55
|
-
key: string,
|
|
56
|
-
args: Parameters<F>,
|
|
57
|
-
) => Promise<CoalescedInvoke<UnwrapPromise<ReturnType<F>>>> {
|
|
58
|
-
return async function (key: string, args: Parameters<F>) {
|
|
59
|
-
const entry = globalInvokeCache.get(key);
|
|
60
|
-
if (entry) {
|
|
61
|
-
// eslint-disable-next-line promise/prefer-await-to-then
|
|
62
|
-
return entry.then(res => ({
|
|
63
|
-
isOrigin: false,
|
|
64
|
-
value: res.value as UnwrapPromise<ReturnType<F>>,
|
|
65
|
-
}));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function __wrapper() {
|
|
69
|
-
return func(...args);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const future = __wrapper()
|
|
73
|
-
// eslint-disable-next-line promise/prefer-await-to-then
|
|
74
|
-
.then(res => {
|
|
75
|
-
globalInvokeCache.delete(key);
|
|
76
|
-
return { isOrigin: true, value: res as UnwrapPromise<ReturnType<F>> };
|
|
77
|
-
})
|
|
78
|
-
// eslint-disable-next-line promise/prefer-await-to-then
|
|
79
|
-
.catch(err => {
|
|
80
|
-
globalInvokeCache.delete(key);
|
|
81
|
-
throw err;
|
|
82
|
-
});
|
|
83
|
-
globalInvokeCache.set(key, future);
|
|
84
|
-
return future;
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function maybeSync(fn: () => Promise<any>) {
|
|
89
|
-
return (sync: boolean) => {
|
|
90
|
-
if (sync) {
|
|
91
|
-
return fn();
|
|
92
|
-
} else {
|
|
93
|
-
fn();
|
|
94
|
-
return Promise.resolve();
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
}
|
package/src/libs/render/index.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { fs } from '@modern-js/utils';
|
|
3
|
-
import mime from 'mime-types';
|
|
4
|
-
import { RenderResult, ServerHookRunner } from '../../type';
|
|
5
|
-
import { ModernRoute } from '../route';
|
|
6
|
-
import { ModernServerContext } from '../context';
|
|
7
|
-
import { ERROR_DIGEST } from '../../constants';
|
|
8
|
-
import { handleDirectory } from './static';
|
|
9
|
-
import { readFile } from './reader';
|
|
10
|
-
import * as ssr from './ssr';
|
|
11
|
-
import { supportModern, getModernEntry } from './modern';
|
|
12
|
-
|
|
13
|
-
export const createRenderHandler = ({
|
|
14
|
-
distDir,
|
|
15
|
-
staticGenerate,
|
|
16
|
-
}: {
|
|
17
|
-
distDir: string;
|
|
18
|
-
staticGenerate: boolean;
|
|
19
|
-
}) =>
|
|
20
|
-
async function render({
|
|
21
|
-
ctx,
|
|
22
|
-
route,
|
|
23
|
-
runner,
|
|
24
|
-
}: {
|
|
25
|
-
ctx: ModernServerContext;
|
|
26
|
-
route: ModernRoute;
|
|
27
|
-
runner: ServerHookRunner;
|
|
28
|
-
}): Promise<RenderResult | null> {
|
|
29
|
-
if (ctx.resHasHandled()) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const { entryPath, urlPath } = route;
|
|
34
|
-
const entry = path.join(distDir, entryPath);
|
|
35
|
-
|
|
36
|
-
if (!route.isSPA) {
|
|
37
|
-
const result = await handleDirectory(ctx, entry, urlPath);
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// only spa can use es6-html
|
|
42
|
-
const modernEntry = getModernEntry(entry);
|
|
43
|
-
const useModern =
|
|
44
|
-
// route.enableModernMode &&
|
|
45
|
-
supportModern(ctx) && fs.existsSync(modernEntry);
|
|
46
|
-
const templateHTML = useModern ? modernEntry : entry;
|
|
47
|
-
|
|
48
|
-
// handles ssr first
|
|
49
|
-
if (route.isSSR) {
|
|
50
|
-
try {
|
|
51
|
-
const result = await ssr.render(
|
|
52
|
-
ctx,
|
|
53
|
-
{
|
|
54
|
-
distDir,
|
|
55
|
-
entryName: route.entryName,
|
|
56
|
-
bundle: route.bundle,
|
|
57
|
-
template: templateHTML,
|
|
58
|
-
staticGenerate,
|
|
59
|
-
},
|
|
60
|
-
runner,
|
|
61
|
-
);
|
|
62
|
-
return result;
|
|
63
|
-
} catch (err) {
|
|
64
|
-
ctx.error(ERROR_DIGEST.ERENDER, (err as Error).stack);
|
|
65
|
-
ctx.res.setHeader('x-modern-ssr-fallback', '1');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const content = await readFile(templateHTML);
|
|
70
|
-
if (!content) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
content,
|
|
76
|
-
contentType: mime.contentType(path.extname(templateHTML)) as string,
|
|
77
|
-
};
|
|
78
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import Parser from 'ua-parser-js';
|
|
2
|
-
import compareVersions from 'compare-versions';
|
|
3
|
-
import { ModernServerContext } from '../../context';
|
|
4
|
-
import { NativeModuleNameMap } from './browser-list';
|
|
5
|
-
|
|
6
|
-
const nativeModules = require('@babel/compat-data/native-modules');
|
|
7
|
-
|
|
8
|
-
export const supportModern = (context: ModernServerContext) => {
|
|
9
|
-
if (context.query.modern_es6) {
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// no ua in request headers
|
|
14
|
-
const userAgent = context.headers['user-agent'];
|
|
15
|
-
if (!userAgent || typeof userAgent !== 'string') {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const parsedUA = Parser(userAgent);
|
|
20
|
-
const browserName = parsedUA.browser.name;
|
|
21
|
-
const browserVersion = parsedUA.browser.version;
|
|
22
|
-
if (!browserName || !browserVersion) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const nativeUAName = NativeModuleNameMap[browserName];
|
|
27
|
-
if (!nativeUAName) {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const version = nativeModules['es6.module'][nativeUAName];
|
|
32
|
-
if (!version) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const result = compareVersions(browserVersion, version);
|
|
37
|
-
return result >= 0;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const getModernEntry = (filepath: string) =>
|
|
41
|
-
filepath.replace(/\.html$/, '-es6.html');
|