@tramvai/module-page-render-mode 7.5.3 → 7.7.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.
Files changed (35) hide show
  1. package/lib/PageRenderWrapper.browser.js +4 -4
  2. package/lib/PageRenderWrapper.d.ts +1 -1
  3. package/lib/PageRenderWrapper.es.js +4 -4
  4. package/lib/PageRenderWrapper.js +3 -3
  5. package/lib/browser.js +27 -3
  6. package/lib/private-tokens.browser.js +9 -0
  7. package/lib/private-tokens.d.ts +27 -0
  8. package/lib/private-tokens.es.js +9 -0
  9. package/lib/private-tokens.js +17 -0
  10. package/lib/server.es.js +1 -1
  11. package/lib/server.js +5 -0
  12. package/lib/staticPages/backgroundFetchService.d.ts +9 -7
  13. package/lib/staticPages/backgroundFetchService.es.js +20 -20
  14. package/lib/staticPages/backgroundFetchService.js +20 -20
  15. package/lib/staticPages/fileSystemCache.d.ts +72 -0
  16. package/lib/staticPages/fileSystemCache.es.js +367 -0
  17. package/lib/staticPages/fileSystemCache.js +376 -0
  18. package/lib/staticPages/staticPagesService.d.ts +16 -9
  19. package/lib/staticPages/staticPagesService.es.js +124 -39
  20. package/lib/staticPages/staticPagesService.js +124 -39
  21. package/lib/staticPages.d.ts +410 -155
  22. package/lib/staticPages.es.js +233 -67
  23. package/lib/staticPages.js +232 -70
  24. package/lib/tokens.browser.js +15 -1
  25. package/lib/tokens.d.ts +90 -32
  26. package/lib/tokens.es.js +15 -1
  27. package/lib/tokens.js +19 -0
  28. package/lib/utils/cacheKey.d.ts +4 -6
  29. package/lib/utils/cacheKey.es.js +8 -3
  30. package/lib/utils/cacheKey.js +8 -2
  31. package/lib/utils/getPageRenderMode.browser.js +14 -2
  32. package/lib/utils/getPageRenderMode.d.ts +8 -3
  33. package/lib/utils/getPageRenderMode.es.js +14 -2
  34. package/lib/utils/getPageRenderMode.js +14 -2
  35. package/package.json +16 -14
@@ -1,32 +1,31 @@
1
- import { createToken, provide, Scope, commandLineListTokens, DI_TOKEN } from '@tramvai/core';
2
- import { CREATE_CACHE_TOKEN, REQUEST_MANAGER_TOKEN, LOGGER_TOKEN, RESPONSE_MANAGER_TOKEN, ENV_MANAGER_TOKEN } from '@tramvai/tokens-common';
1
+ import { provide, Scope, commandLineListTokens, optional, DI_TOKEN } from '@tramvai/core';
2
+ import { CREATE_CACHE_TOKEN, LOGGER_TOKEN, REQUEST_MANAGER_TOKEN, ENV_MANAGER_TOKEN, RESPONSE_MANAGER_TOKEN } from '@tramvai/tokens-common';
3
3
  import { FASTIFY_RESPONSE } from '@tramvai/tokens-server-private';
4
- import { PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';
5
- import { USER_AGENT_TOKEN } from '@tramvai/module-client-hints';
4
+ import { ROUTER_TOKEN, PAGE_SERVICE_TOKEN, LINK_PREFETCH_MANAGER_TOKEN, PAGE_REGISTRY_TOKEN } from '@tramvai/tokens-router';
6
5
  import { SERVER_MODULE_PAPI_PRIVATE_ROUTE } from '@tramvai/tokens-server';
7
6
  import { METRICS_MODULE_TOKEN } from '@tramvai/tokens-metrics';
8
7
  import { createPapiMethod } from '@tramvai/papi';
9
8
  import { StopCommandLineRunnerError } from './error.es.js';
10
- import { STATIC_PAGES_CACHE_TOKEN, STATIC_PAGES_OPTIONS_TOKEN, STATIC_PAGES_SHOULD_USE_CACHE, STATIC_PAGES_BACKGROUND_FETCH_ENABLED, STATIC_PAGES_CACHE_5xx_RESPONSE, STATIC_PAGES_MODIFY_CACHE, STATIC_PAGES_COMMAND_LINE, PAGE_RENDER_DEFAULT_MODE } from './tokens.es.js';
9
+ import { STATIC_PAGES_CACHE_TOKEN, STATIC_PAGES_OPTIONS_TOKEN, STATIC_PAGES_KEY_TOKEN, STATIC_PAGES_CACHE_CONTROL_HEADER_TOKEN, STATIC_PAGES_FS_CACHE_ENABLED, STATIC_PAGES_FS_CACHE_OPTIONS_TOKEN, STATIC_PAGES_SHOULD_USE_CACHE, PAGE_RENDER_DEFAULT_MODE, STATIC_PAGES_BACKGROUND_FETCH_ENABLED, STATIC_PAGES_CACHE_5xx_RESPONSE, STATIC_PAGES_SERVICE, STATIC_PAGES_MODIFY_CACHE, STATIC_PAGES_COMMAND_LINE } from './tokens.es.js';
11
10
  import { getPageRenderMode } from './utils/getPageRenderMode.es.js';
12
11
  import { getCacheKey } from './utils/cacheKey.es.js';
13
12
  import { BackgroundFetchService } from './staticPages/backgroundFetchService.es.js';
14
13
  import { StaticPagesService } from './staticPages/staticPagesService.es.js';
14
+ import { FileSystemCache } from './staticPages/fileSystemCache.es.js';
15
+ import { STATIC_PAGES_CACHE_METRICS_TOKEN, STATIC_PAGES_FS_CACHE_METRICS_TOKEN, STATIC_PAGES_FS_CACHE_TOKEN, STATIC_PAGES_RESOLVE_PAGE_RENDER_MODE, STATIC_PAGES_BACKGROUND_FETCH_SERVICE } from './private-tokens.es.js';
15
16
 
16
- const STATIC_PAGES_BACKGROUND_FETCH_SERVICE = createToken();
17
- const STATIC_PAGES_GET_CACHE_KEY_TOKEN = createToken();
18
- const STATIC_PAGES_CACHE_HIT_METRIC_TOKEN = createToken();
19
- const STATIC_PAGES_SERVICE = createToken();
20
17
  const staticPagesProviders = [
21
18
  provide({
22
- provide: STATIC_PAGES_CACHE_HIT_METRIC_TOKEN,
19
+ provide: STATIC_PAGES_CACHE_METRICS_TOKEN,
23
20
  scope: Scope.SINGLETON,
24
21
  useFactory: ({ metrics }) => {
25
- return metrics.counter({
26
- name: 'static_pages_cache_hit',
27
- help: 'Total static pages returned from cache',
28
- labelNames: [],
29
- });
22
+ return {
23
+ hit: metrics.counter({
24
+ name: 'static_pages_cache_hit',
25
+ help: 'Total static pages returned from cache',
26
+ labelNames: [],
27
+ }),
28
+ };
30
29
  },
31
30
  deps: {
32
31
  metrics: METRICS_MODULE_TOKEN,
@@ -50,40 +49,143 @@ const staticPagesProviders = [
50
49
  provide: STATIC_PAGES_OPTIONS_TOKEN,
51
50
  useValue: {
52
51
  // @TODO: unique ttl per pages
53
- ttl: 60 * 1000,
54
- // @TODO: too much, better save to file-system
55
- maxSize: 1000,
52
+ ttl: 5 * 60 * 1000,
53
+ maxSize: 100,
54
+ allowStale: true,
55
+ allowedHeaders: [],
56
56
  },
57
57
  }),
58
58
  provide({
59
- provide: STATIC_PAGES_GET_CACHE_KEY_TOKEN,
60
- useFactory: ({ requestManager, userAgent }) => {
59
+ provide: STATIC_PAGES_KEY_TOKEN,
60
+ useFactory: () => {
61
61
  return () => {
62
- const deviceType = userAgent.mobileOS ? 'mobile' : 'desktop';
63
- return getCacheKey({
64
- method: requestManager.getMethod(),
65
- host: requestManager.getHost(),
66
- path: requestManager.getParsedUrl().pathname,
67
- deviceType,
68
- });
62
+ return '';
63
+ };
64
+ },
65
+ }),
66
+ provide({
67
+ provide: STATIC_PAGES_CACHE_CONTROL_HEADER_TOKEN,
68
+ useValue: ({ ttl }) => {
69
+ // prevent browser caching for pages in development mode
70
+ if (process.env.NODE_ENV === 'development') {
71
+ return 'private, no-cache, no-store, max-age=0, must-revalidate';
72
+ }
73
+ return `public, max-age=${ttl / 1000}`;
74
+ },
75
+ }),
76
+ provide({
77
+ provide: STATIC_PAGES_FS_CACHE_ENABLED,
78
+ useValue: () => false,
79
+ }),
80
+ provide({
81
+ provide: STATIC_PAGES_FS_CACHE_OPTIONS_TOKEN,
82
+ useFactory: () => {
83
+ return {
84
+ directory: process.env.__TRAMVAI_OUTPUT_STATIC ?? 'dist/static',
85
+ maxSize: 1000,
86
+ ttl: 5 * 60 * 1000,
87
+ allowStale: true,
88
+ };
89
+ },
90
+ }),
91
+ provide({
92
+ provide: STATIC_PAGES_FS_CACHE_METRICS_TOKEN,
93
+ scope: Scope.SINGLETON,
94
+ useFactory: ({ metrics }) => {
95
+ return {
96
+ hit: metrics.counter({
97
+ name: 'static_pages_fs_cache_hit',
98
+ help: 'Total static pages returned from file system cache',
99
+ labelNames: [],
100
+ }),
101
+ miss: metrics.counter({
102
+ name: 'static_pages_fs_cache_miss',
103
+ help: 'Total static pages not found in file system cache',
104
+ labelNames: [],
105
+ }),
106
+ size: metrics.gauge({
107
+ name: 'static_pages_fs_cache_size',
108
+ help: 'Number of files in file system cache',
109
+ labelNames: [],
110
+ }),
111
+ bytes: metrics.gauge({
112
+ name: 'static_pages_fs_cache_bytes',
113
+ help: 'Total size of files in file system cache in bytes',
114
+ labelNames: [],
115
+ }),
69
116
  };
70
117
  },
71
118
  deps: {
72
- requestManager: REQUEST_MANAGER_TOKEN,
73
- userAgent: USER_AGENT_TOKEN,
119
+ metrics: METRICS_MODULE_TOKEN,
120
+ },
121
+ }),
122
+ provide({
123
+ provide: STATIC_PAGES_FS_CACHE_TOKEN,
124
+ scope: Scope.SINGLETON,
125
+ useFactory: ({ fsCacheEnabled, fsCacheOptions, logger, metrics }) => {
126
+ if (!fsCacheEnabled()) {
127
+ return null;
128
+ }
129
+ return new FileSystemCache({
130
+ ...fsCacheOptions,
131
+ logger,
132
+ metrics,
133
+ });
134
+ },
135
+ deps: {
136
+ fsCacheEnabled: STATIC_PAGES_FS_CACHE_ENABLED,
137
+ fsCacheOptions: STATIC_PAGES_FS_CACHE_OPTIONS_TOKEN,
138
+ logger: LOGGER_TOKEN,
139
+ metrics: STATIC_PAGES_FS_CACHE_METRICS_TOKEN,
140
+ },
141
+ }),
142
+ provide({
143
+ provide: commandLineListTokens.init,
144
+ multi: true,
145
+ scope: Scope.SINGLETON,
146
+ useFactory: ({ fsCache, fsCacheEnabled }) => {
147
+ return async function initFileSystemCache() {
148
+ if (fsCacheEnabled() && fsCache) {
149
+ await fsCache.init();
150
+ }
151
+ };
152
+ },
153
+ deps: {
154
+ fsCache: STATIC_PAGES_FS_CACHE_TOKEN,
155
+ fsCacheEnabled: STATIC_PAGES_FS_CACHE_ENABLED,
74
156
  },
75
157
  }),
76
158
  provide({
77
159
  provide: STATIC_PAGES_SHOULD_USE_CACHE,
78
160
  useFactory: ({ requestManager }) => {
79
161
  return () => {
80
- return !requestManager.getHeader('x-tramvai-static-page-revalidate');
162
+ return (!requestManager.getHeader('x-tramvai-static-page-revalidate') &&
163
+ !requestManager.getHeader('x-tramvai-prerender'));
81
164
  };
82
165
  },
83
166
  deps: {
84
167
  requestManager: REQUEST_MANAGER_TOKEN,
85
168
  },
86
169
  }),
170
+ provide({
171
+ provide: STATIC_PAGES_RESOLVE_PAGE_RENDER_MODE,
172
+ useFactory: ({ requestManager, pageService, router, defaultRenderMode, fileSystemCache }) => {
173
+ return () => getPageRenderMode({
174
+ pageService,
175
+ router,
176
+ requestManager,
177
+ defaultRenderMode,
178
+ fileSystemCache,
179
+ });
180
+ },
181
+ deps: {
182
+ requestManager: REQUEST_MANAGER_TOKEN,
183
+ router: optional(ROUTER_TOKEN),
184
+ pageService: optional(PAGE_SERVICE_TOKEN),
185
+ defaultRenderMode: PAGE_RENDER_DEFAULT_MODE,
186
+ fileSystemCache: optional(STATIC_PAGES_FS_CACHE_TOKEN),
187
+ },
188
+ }),
87
189
  provide({
88
190
  provide: STATIC_PAGES_BACKGROUND_FETCH_ENABLED,
89
191
  useValue: () => {
@@ -102,6 +204,7 @@ const staticPagesProviders = [
102
204
  useClass: BackgroundFetchService,
103
205
  deps: {
104
206
  logger: LOGGER_TOKEN,
207
+ envManager: ENV_MANAGER_TOKEN,
105
208
  backgroundFetchEnabled: STATIC_PAGES_BACKGROUND_FETCH_ENABLED,
106
209
  },
107
210
  }),
@@ -110,19 +213,21 @@ const staticPagesProviders = [
110
213
  scope: Scope.REQUEST,
111
214
  useClass: StaticPagesService,
112
215
  deps: {
113
- getCacheKey: STATIC_PAGES_GET_CACHE_KEY_TOKEN,
216
+ staticPagesKey: STATIC_PAGES_KEY_TOKEN,
114
217
  requestManager: REQUEST_MANAGER_TOKEN,
115
218
  responseManager: RESPONSE_MANAGER_TOKEN,
116
219
  response: FASTIFY_RESPONSE,
117
220
  environmentManager: ENV_MANAGER_TOKEN,
118
- userAgent: USER_AGENT_TOKEN,
119
221
  logger: LOGGER_TOKEN,
120
222
  cache: STATIC_PAGES_CACHE_TOKEN,
223
+ fsCache: STATIC_PAGES_FS_CACHE_TOKEN,
224
+ fsCacheEnabled: STATIC_PAGES_FS_CACHE_ENABLED,
121
225
  modifyCache: { token: STATIC_PAGES_MODIFY_CACHE, optional: true },
122
226
  shouldUseCache: STATIC_PAGES_SHOULD_USE_CACHE,
123
227
  backgroundFetchService: STATIC_PAGES_BACKGROUND_FETCH_SERVICE,
124
228
  options: STATIC_PAGES_OPTIONS_TOKEN,
125
229
  cache5xxResponse: STATIC_PAGES_CACHE_5xx_RESPONSE,
230
+ cacheControlFactory: STATIC_PAGES_CACHE_CONTROL_HEADER_TOKEN,
126
231
  },
127
232
  }),
128
233
  provide({
@@ -135,22 +240,61 @@ const staticPagesProviders = [
135
240
  provide: staticPagesCommandLine
136
241
  ? commandLineListTokens[staticPagesCommandLine]
137
242
  : commandLineListTokens.customerStart,
138
- useFactory: ({ staticPagesService, staticPagesCacheHitMetric, logger }) => {
139
- logger('static-pages');
140
- return function staticPagesFromCache() {
141
- if (staticPagesService.shouldUseCache()) {
142
- staticPagesService.respond(() => {
143
- // @TODO: маска урла на этом этапе?
144
- staticPagesCacheHitMetric.inc();
145
- throw new StopCommandLineRunnerError();
243
+ useFactory: ({ staticPagesService, staticPagesCacheMetrics, logger, requestManager, responseManager, staticPagesKey, linkPrefetchManager, resolvePageRenderMode, router, pageRegistry, }) => {
244
+ const log = logger('static-pages');
245
+ return async function staticPagesFromCache() {
246
+ const isPrerenderRequest = !!requestManager.getHeader('x-tramvai-prerender');
247
+ const { pathname } = requestManager.getParsedUrl();
248
+ const route = router?.getCurrentRoute() ?? router?.resolve(pathname);
249
+ // prefetch route only for `tramvai static` because it can be async and slow in runtime
250
+ if (isPrerenderRequest) {
251
+ log.debug(`Should prefetch route and component to determine if page is static: ${pathname}`);
252
+ await linkPrefetchManager.prefetch(pathname);
253
+ // for already resolved routes we can prefetch page component in runtime without significant performance impact
254
+ }
255
+ else if (route) {
256
+ log.debug(`Should prefetch page component to determine if page is static: ${pathname}`);
257
+ await pageRegistry?.resolve(route).catch((error) => {
258
+ if (!route.redirect) {
259
+ log.info(`${pathname} page component for prefetch failed: ${error.message}`);
260
+ }
146
261
  });
147
262
  }
263
+ const isStatic = resolvePageRenderMode() === 'static';
264
+ const shouldUseCache = staticPagesService.shouldUseCache();
265
+ if (isStatic) {
266
+ responseManager.setHeader('X-Tramvai-Static-Page-Key', staticPagesKey());
267
+ // we need to tell for `tramvai static` prerendering command that this page has `static` render mode,
268
+ // and also provide route info to generate list of static routes in `dist/client/meta.json`.
269
+ // this list will be used in `STATIC_PAGES_RESOLVE_PAGE_RENDER_MODE` to determine render mode of the page in runtime before route resolving.
270
+ if (isPrerenderRequest && route) {
271
+ responseManager.setHeader('X-Tramvai-Static-Page-Route', JSON.stringify(route));
272
+ }
273
+ if (shouldUseCache) {
274
+ log.debug(`Should use static pages cache: ${pathname}`);
275
+ await staticPagesService.respond(() => {
276
+ log.debug(`Successful static page response from cache: ${pathname}`);
277
+ staticPagesCacheMetrics.hit.inc();
278
+ throw new StopCommandLineRunnerError();
279
+ });
280
+ }
281
+ else {
282
+ log.debug(`Static pages cache is not used for this request: ${pathname}`);
283
+ }
284
+ }
148
285
  };
149
286
  },
150
287
  deps: {
151
288
  staticPagesService: STATIC_PAGES_SERVICE,
152
- staticPagesCacheHitMetric: STATIC_PAGES_CACHE_HIT_METRIC_TOKEN,
289
+ staticPagesCacheMetrics: STATIC_PAGES_CACHE_METRICS_TOKEN,
153
290
  logger: LOGGER_TOKEN,
291
+ requestManager: REQUEST_MANAGER_TOKEN,
292
+ responseManager: RESPONSE_MANAGER_TOKEN,
293
+ staticPagesKey: STATIC_PAGES_KEY_TOKEN,
294
+ linkPrefetchManager: LINK_PREFETCH_MANAGER_TOKEN,
295
+ resolvePageRenderMode: STATIC_PAGES_RESOLVE_PAGE_RENDER_MODE,
296
+ router: optional(ROUTER_TOKEN),
297
+ pageRegistry: optional(PAGE_REGISTRY_TOKEN),
154
298
  },
155
299
  });
156
300
  };
@@ -160,38 +304,57 @@ const staticPagesProviders = [
160
304
  staticPagesCommandLine: { token: STATIC_PAGES_COMMAND_LINE, optional: true },
161
305
  },
162
306
  }),
163
- provide({
164
- provide: commandLineListTokens.clear,
165
- useFactory: ({ staticPagesService, pageService, defaultRenderMode }) => {
166
- return function cacheStaticPages() {
167
- const isStaticPage = getPageRenderMode({ pageService, defaultRenderMode }) === 'static';
168
- if (!isStaticPage) {
169
- return;
170
- }
171
- staticPagesService.revalidate();
172
- };
173
- },
174
- deps: {
175
- staticPagesService: STATIC_PAGES_SERVICE,
176
- pageService: PAGE_SERVICE_TOKEN,
177
- defaultRenderMode: PAGE_RENDER_DEFAULT_MODE,
178
- },
179
- }),
180
307
  provide({
181
308
  provide: SERVER_MODULE_PAPI_PRIVATE_ROUTE,
182
- useFactory: ({ staticPagesCache }) => {
309
+ useFactory: ({ staticPagesCache, fsCache, fsCacheEnabled, logger }) => {
183
310
  return createPapiMethod({
184
311
  path: '/revalidate/',
185
312
  method: 'post',
186
- async handler({ body = {} }) {
187
- const { path } = body;
188
- const pathKey = `/${path}/`;
189
- if (!path) {
313
+ async handler({ body = {}, headers }) {
314
+ const log = logger('static-pages:revalidate');
315
+ const { pathname, key = '' } = body;
316
+ if (!pathname) {
317
+ log.info({
318
+ event: 'revalidate-all',
319
+ request: body,
320
+ headers,
321
+ });
190
322
  staticPagesCache.clear();
323
+ if (fsCacheEnabled() && fsCache) {
324
+ await fsCache.clear();
325
+ }
191
326
  }
192
- else if (staticPagesCache.has(pathKey)) {
193
- staticPagesCache.set(pathKey, new Map());
194
- // @TODO: revalidate request with background fetch?
327
+ else {
328
+ const cacheKey = getCacheKey({ pathname, key });
329
+ log.info({
330
+ event: 'revalidate-path',
331
+ request: body,
332
+ cacheKey,
333
+ });
334
+ if (key) {
335
+ if (staticPagesCache.has(cacheKey)) {
336
+ staticPagesCache.delete(cacheKey);
337
+ }
338
+ if (fsCacheEnabled() && fsCache && fsCache.has(cacheKey)) {
339
+ await fsCache.delete(cacheKey);
340
+ }
341
+ }
342
+ else {
343
+ const memoryCaches = staticPagesCache.dump();
344
+ for (const entry of memoryCaches) {
345
+ if (entry[0].startsWith(`${pathname}^`)) {
346
+ staticPagesCache.delete(entry[0]);
347
+ }
348
+ }
349
+ if (fsCacheEnabled() && fsCache) {
350
+ const fsCaches = fsCache.dump();
351
+ for (const entry of fsCaches) {
352
+ if (entry[0].startsWith(`${pathname}^`)) {
353
+ fsCache.delete(entry[0]);
354
+ }
355
+ }
356
+ }
357
+ }
195
358
  }
196
359
  return 'Success';
197
360
  },
@@ -199,8 +362,11 @@ const staticPagesProviders = [
199
362
  },
200
363
  deps: {
201
364
  staticPagesCache: STATIC_PAGES_CACHE_TOKEN,
365
+ fsCache: STATIC_PAGES_FS_CACHE_TOKEN,
366
+ fsCacheEnabled: STATIC_PAGES_FS_CACHE_ENABLED,
367
+ logger: LOGGER_TOKEN,
202
368
  },
203
369
  }),
204
370
  ];
205
371
 
206
- export { STATIC_PAGES_BACKGROUND_FETCH_SERVICE, STATIC_PAGES_CACHE_HIT_METRIC_TOKEN, STATIC_PAGES_GET_CACHE_KEY_TOKEN, STATIC_PAGES_SERVICE, staticPagesProviders };
372
+ export { staticPagesProviders };