@tramvai/module-render 2.5.0 → 2.7.1
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 +8 -0
- package/lib/resourcesInliner/resourcesInliner.d.ts +3 -0
- package/lib/resourcesInliner/tokens.d.ts +1 -2
- package/lib/server.d.ts +1 -1
- package/lib/server.es.js +82 -64
- package/lib/server.js +93 -75
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -6,8 +6,10 @@ export interface ResourcesInlinerType {
|
|
|
6
6
|
}
|
|
7
7
|
export declare class ResourcesInliner implements ResourcesInlinerType {
|
|
8
8
|
private resourceInlineThreshold?;
|
|
9
|
+
private internalFilesCache;
|
|
9
10
|
private resourcesRegistryCache;
|
|
10
11
|
private log;
|
|
12
|
+
private runningRequests;
|
|
11
13
|
private scheduleFileLoad;
|
|
12
14
|
private scheduleFileSizeLoad;
|
|
13
15
|
constructor({ resourcesRegistryCache, resourceInlineThreshold, logger }: {
|
|
@@ -15,6 +17,7 @@ export declare class ResourcesInliner implements ResourcesInlinerType {
|
|
|
15
17
|
resourceInlineThreshold: any;
|
|
16
18
|
logger: any;
|
|
17
19
|
});
|
|
20
|
+
private getFilesCache;
|
|
18
21
|
shouldAddResource(resource: PageResource): boolean;
|
|
19
22
|
shouldInline(resource: PageResource): boolean;
|
|
20
23
|
inlineResource(resource: PageResource): PageResource[];
|
|
@@ -9,8 +9,7 @@ export declare const RESOURCE_INLINER: import("@tinkoff/dippy/lib/createToken/cr
|
|
|
9
9
|
export declare type ResourcesRegistryCache = {
|
|
10
10
|
filesCache: Cache;
|
|
11
11
|
sizeCache: Cache;
|
|
12
|
-
|
|
13
|
-
disabledUrlsCache: any;
|
|
12
|
+
disabledUrlsCache: Cache;
|
|
14
13
|
};
|
|
15
14
|
/**
|
|
16
15
|
* @description
|
package/lib/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RenderModuleConfig } from './shared/types';
|
|
2
2
|
export * from './shared/pageErrorStore';
|
|
3
3
|
export * from '@tramvai/tokens-render';
|
|
4
|
-
export declare const DEFAULT_POLYFILL_CONDITION = "!window.Promise.prototype.finally || !window.URL || !window.URLSearchParams || !window.AbortController || !window.IntersectionObserver || !Object.fromEntries";
|
|
4
|
+
export declare const DEFAULT_POLYFILL_CONDITION = "!window.Promise.prototype.finally || !window.URL || !window.URLSearchParams || !window.AbortController || !window.IntersectionObserver || !Object.fromEntries || !window.ResizeObserver";
|
|
5
5
|
export declare class RenderModule {
|
|
6
6
|
static forRoot({ polyfillCondition }: RenderModuleConfig): {
|
|
7
7
|
mainModule: typeof RenderModule;
|
package/lib/server.es.js
CHANGED
|
@@ -2,7 +2,7 @@ import { __decorate } from 'tslib';
|
|
|
2
2
|
import { PureComponent, useMemo, createElement } from 'react';
|
|
3
3
|
import { renderToString } from 'react-dom/server';
|
|
4
4
|
import { Module, provide, commandLineListTokens, DI_TOKEN } from '@tramvai/core';
|
|
5
|
-
import { CREATE_CACHE_TOKEN, LOGGER_TOKEN, REQUEST_MANAGER_TOKEN, RESPONSE_MANAGER_TOKEN, CONTEXT_TOKEN } from '@tramvai/
|
|
5
|
+
import { COMBINE_REDUCERS, CREATE_CACHE_TOKEN, LOGGER_TOKEN, REQUEST_MANAGER_TOKEN, RESPONSE_MANAGER_TOKEN, CONTEXT_TOKEN } from '@tramvai/tokens-common';
|
|
6
6
|
import { PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';
|
|
7
7
|
import { ClientHintsModule, USER_AGENT_TOKEN } from '@tramvai/module-client-hints';
|
|
8
8
|
import { ResourceType, ResourceSlot, DEFAULT_LAYOUT_COMPONENT, LAYOUT_OPTIONS, DEFAULT_FOOTER_COMPONENT, DEFAULT_HEADER_COMPONENT, TRAMVAI_RENDER_MODE, RESOURCES_REGISTRY, RESOURCE_INLINE_OPTIONS, RENDER_SLOTS, POLYFILL_CONDITION, HTML_ATTRS, CUSTOM_RENDER, EXTEND_RENDER } from '@tramvai/tokens-render';
|
|
@@ -33,7 +33,6 @@ import { jsx } from 'react/jsx-runtime';
|
|
|
33
33
|
import { createEvent, createReducer, useStore, Provider } from '@tramvai/state';
|
|
34
34
|
import { useRoute, useUrl } from '@tramvai/module-router';
|
|
35
35
|
import { composeLayoutOptions, createLayout } from '@tinkoff/layout-factory';
|
|
36
|
-
import { COMBINE_REDUCERS } from '@tramvai/tokens-common';
|
|
37
36
|
|
|
38
37
|
const thirtySeconds = 1000 * 30;
|
|
39
38
|
const getFileContentLength = async (url) => {
|
|
@@ -75,6 +74,11 @@ const processFile = (resource, file) => {
|
|
|
75
74
|
return file;
|
|
76
75
|
};
|
|
77
76
|
|
|
77
|
+
const INTERNAL_CACHE_SIZE = 50;
|
|
78
|
+
const ASSETS_PREFIX = process.env.NODE_ENV === 'development' &&
|
|
79
|
+
(process.env.ASSETS_PREFIX === 'static' || !process.env.ASSETS_PREFIX)
|
|
80
|
+
? `http://localhost:${process.env.PORT_STATIC}/dist/`
|
|
81
|
+
: process.env.ASSETS_PREFIX;
|
|
78
82
|
const getInlineType = (type) => {
|
|
79
83
|
switch (type) {
|
|
80
84
|
case ResourceType.style:
|
|
@@ -95,51 +99,53 @@ const getResourceUrl = (resource) => {
|
|
|
95
99
|
};
|
|
96
100
|
class ResourcesInliner {
|
|
97
101
|
constructor({ resourcesRegistryCache, resourceInlineThreshold, logger }) {
|
|
98
|
-
this.
|
|
102
|
+
this.internalFilesCache = new Map();
|
|
103
|
+
this.runningRequests = new Set();
|
|
104
|
+
this.scheduleFileLoad = async (resource, resourceInlineThreshold) => {
|
|
99
105
|
const url = getResourceUrl(resource);
|
|
100
106
|
const requestKey = `file${url}`;
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
const filesCache = this.getFilesCache(url);
|
|
108
|
+
const result = filesCache.get(url);
|
|
109
|
+
if (result) {
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
if (!this.runningRequests.has(requestKey)) {
|
|
113
|
+
this.runningRequests.add(url);
|
|
114
|
+
try {
|
|
115
|
+
const file = await getFile(url);
|
|
110
116
|
if (file === undefined) {
|
|
111
117
|
this.resourcesRegistryCache.disabledUrlsCache.set(url, true);
|
|
112
118
|
return;
|
|
113
119
|
}
|
|
114
120
|
const size = file.length;
|
|
115
121
|
if (size < resourceInlineThreshold) {
|
|
116
|
-
|
|
122
|
+
filesCache.set(url, processFile(resource, file));
|
|
117
123
|
}
|
|
118
124
|
this.resourcesRegistryCache.sizeCache.set(url, size);
|
|
119
|
-
}
|
|
120
|
-
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
121
127
|
this.log.warn({
|
|
122
128
|
event: 'file-load-failed',
|
|
123
129
|
url,
|
|
124
130
|
error,
|
|
125
131
|
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
this.
|
|
129
|
-
}
|
|
130
|
-
this.resourcesRegistryCache.requestsCache.set(requestKey, getFilePromise);
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
this.runningRequests.delete(requestKey);
|
|
135
|
+
}
|
|
131
136
|
}
|
|
132
137
|
};
|
|
133
|
-
this.scheduleFileSizeLoad = (resource, resourceInlineThreshold) => {
|
|
138
|
+
this.scheduleFileSizeLoad = async (resource, resourceInlineThreshold) => {
|
|
134
139
|
const url = getResourceUrl(resource);
|
|
135
140
|
const requestKey = `size${url}`;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
const result = this.resourcesRegistryCache.sizeCache.get(url);
|
|
142
|
+
if (result) {
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
if (!this.runningRequests.has(requestKey)) {
|
|
146
|
+
this.runningRequests.add(requestKey);
|
|
147
|
+
try {
|
|
148
|
+
const contentLength = await getFileContentLength(url);
|
|
143
149
|
const size = isUndefined(contentLength) ? 0 : +contentLength;
|
|
144
150
|
if (size) {
|
|
145
151
|
this.resourcesRegistryCache.sizeCache.set(url, size);
|
|
@@ -147,41 +153,48 @@ class ResourcesInliner {
|
|
|
147
153
|
if (size < resourceInlineThreshold) {
|
|
148
154
|
this.scheduleFileLoad(resource, resourceInlineThreshold);
|
|
149
155
|
}
|
|
150
|
-
}
|
|
151
|
-
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
152
158
|
this.log.warn({
|
|
153
159
|
event: 'file-content-length-load-failed',
|
|
154
160
|
url,
|
|
155
161
|
error,
|
|
156
162
|
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
this.
|
|
160
|
-
}
|
|
161
|
-
this.resourcesRegistryCache.requestsCache.set(requestKey, getFileSizePromise);
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
this.runningRequests.delete(requestKey);
|
|
166
|
+
}
|
|
162
167
|
}
|
|
163
168
|
};
|
|
164
169
|
this.resourcesRegistryCache = resourcesRegistryCache;
|
|
165
170
|
this.resourceInlineThreshold = resourceInlineThreshold;
|
|
166
171
|
this.log = logger('resources-inliner');
|
|
167
172
|
}
|
|
168
|
-
|
|
173
|
+
getFilesCache(url) {
|
|
174
|
+
if (url.startsWith(ASSETS_PREFIX)) {
|
|
175
|
+
// internal resources are resources generated by the current app itself
|
|
176
|
+
// these kind of resources are pretty static and won't be changed while app is running
|
|
177
|
+
// so we can cache it with bare Map and do not care about how to cleanup cache from outdated entries
|
|
178
|
+
return this.internalFilesCache;
|
|
179
|
+
}
|
|
180
|
+
return this.resourcesRegistryCache.filesCache;
|
|
181
|
+
}
|
|
182
|
+
// check that resource's preload-link should be added to render
|
|
169
183
|
shouldAddResource(resource) {
|
|
170
184
|
if (resource.type !== ResourceType.preloadLink) {
|
|
171
|
-
//
|
|
172
|
-
// попасть в итоговую выборку.
|
|
185
|
+
// only checking preload-links
|
|
173
186
|
return true;
|
|
174
187
|
}
|
|
175
188
|
const url = getResourceUrl(resource);
|
|
176
189
|
if (isUndefined(url)) {
|
|
177
|
-
//
|
|
190
|
+
// if url is undefined that file is not in cache
|
|
178
191
|
return true;
|
|
179
192
|
}
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
return !this.
|
|
193
|
+
// if file is residing in cache that means it will be inlined in page render
|
|
194
|
+
// therefore no need to have preload-link for the inlined resource
|
|
195
|
+
return !this.getFilesCache(url).has(url);
|
|
183
196
|
}
|
|
184
|
-
//
|
|
197
|
+
// method for check is passed resource should be inlined in HTML-page
|
|
185
198
|
shouldInline(resource) {
|
|
186
199
|
var _a;
|
|
187
200
|
if (!(((_a = this.resourceInlineThreshold) === null || _a === void 0 ? void 0 : _a.types) || []).includes(resource.type)) {
|
|
@@ -192,7 +205,16 @@ class ResourcesInliner {
|
|
|
192
205
|
return false;
|
|
193
206
|
}
|
|
194
207
|
const url = getResourceUrl(resource);
|
|
195
|
-
|
|
208
|
+
const filesCache = this.getFilesCache(url);
|
|
209
|
+
if (isUndefined(url) || this.resourcesRegistryCache.disabledUrlsCache.has(url)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
if (filesCache.has(url)) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
if (filesCache === this.internalFilesCache &&
|
|
216
|
+
this.internalFilesCache.size >= INTERNAL_CACHE_SIZE) {
|
|
217
|
+
// if we've exceeded limits for the internal resources cache ignore any new entries
|
|
196
218
|
return false;
|
|
197
219
|
}
|
|
198
220
|
if (!this.resourcesRegistryCache.sizeCache.has(url)) {
|
|
@@ -203,25 +225,22 @@ class ResourcesInliner {
|
|
|
203
225
|
if (size > resourceInlineThreshold) {
|
|
204
226
|
return false;
|
|
205
227
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
return true;
|
|
228
|
+
this.scheduleFileLoad(resource, resourceInlineThreshold);
|
|
229
|
+
return false;
|
|
211
230
|
}
|
|
212
231
|
inlineResource(resource) {
|
|
213
232
|
const url = getResourceUrl(resource);
|
|
214
233
|
if (isUndefined(url)) {
|
|
215
|
-
//
|
|
234
|
+
// usually, it should not happen but anyway check it for safety
|
|
216
235
|
return [resource];
|
|
217
236
|
}
|
|
218
|
-
const text = this.
|
|
237
|
+
const text = this.getFilesCache(url).get(url);
|
|
219
238
|
if (isEmpty(text)) {
|
|
220
239
|
return [resource];
|
|
221
240
|
}
|
|
222
241
|
const result = [];
|
|
223
242
|
if (process.env.NODE_ENV === 'development') {
|
|
224
|
-
//
|
|
243
|
+
// html comment for debugging inlining in dev mode
|
|
225
244
|
result.push({
|
|
226
245
|
slot: resource.slot,
|
|
227
246
|
type: ResourceType.asIs,
|
|
@@ -234,11 +253,10 @@ class ResourcesInliner {
|
|
|
234
253
|
payload: text,
|
|
235
254
|
});
|
|
236
255
|
if (resource.type === ResourceType.style) {
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
// использовать тэг выше, а приходится генерировать новый.
|
|
256
|
+
// If we don't add data-href then extract-css-chunks-webpack-plugin
|
|
257
|
+
// will add link to resources to the html head (https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/blob/master/src/index.js#L346)
|
|
258
|
+
// wherein link in case of css files plugin will look for a link tag, but we add a style tag
|
|
259
|
+
// so we can't use tag from above and have to generate new one
|
|
242
260
|
result.push({
|
|
243
261
|
slot: resource.slot,
|
|
244
262
|
type: ResourceType.style,
|
|
@@ -736,7 +754,8 @@ const providers = [
|
|
|
736
754
|
];
|
|
737
755
|
|
|
738
756
|
var RenderModule_1;
|
|
739
|
-
const
|
|
757
|
+
const REQUEST_TTL = 5 * 60 * 1000;
|
|
758
|
+
const DEFAULT_POLYFILL_CONDITION = '!window.Promise.prototype.finally || !window.URL || !window.URLSearchParams || !window.AbortController || !window.IntersectionObserver || !Object.fromEntries || !window.ResizeObserver';
|
|
740
759
|
let RenderModule = RenderModule_1 = class RenderModule {
|
|
741
760
|
static forRoot({ polyfillCondition }) {
|
|
742
761
|
const providers = [];
|
|
@@ -768,12 +787,10 @@ RenderModule = RenderModule_1 = __decorate([
|
|
|
768
787
|
provide: RESOURCES_REGISTRY_CACHE,
|
|
769
788
|
scope: Scope.SINGLETON,
|
|
770
789
|
useFactory: ({ createCache }) => {
|
|
771
|
-
const thirtyMinutes = 1000 * 60 * 30;
|
|
772
790
|
return {
|
|
773
|
-
filesCache: createCache('memory', { max: 50
|
|
774
|
-
sizeCache: createCache('memory', { max: 100
|
|
775
|
-
|
|
776
|
-
disabledUrlsCache: createCache('memory', { max: 150, ttl: 1000 * 60 * 5 }),
|
|
791
|
+
filesCache: createCache('memory', { max: 50 }),
|
|
792
|
+
sizeCache: createCache('memory', { max: 100 }),
|
|
793
|
+
disabledUrlsCache: createCache('memory', { max: 150, ttl: REQUEST_TTL }),
|
|
777
794
|
};
|
|
778
795
|
},
|
|
779
796
|
deps: {
|
|
@@ -782,6 +799,7 @@ RenderModule = RenderModule_1 = __decorate([
|
|
|
782
799
|
}),
|
|
783
800
|
provide({
|
|
784
801
|
provide: RESOURCE_INLINER,
|
|
802
|
+
scope: Scope.SINGLETON,
|
|
785
803
|
useClass: ResourcesInliner,
|
|
786
804
|
deps: {
|
|
787
805
|
resourcesRegistryCache: RESOURCES_REGISTRY_CACHE,
|
|
@@ -890,7 +908,7 @@ RenderModule = RenderModule_1 = __decorate([
|
|
|
890
908
|
useValue: DEFAULT_POLYFILL_CONDITION,
|
|
891
909
|
}),
|
|
892
910
|
provide({
|
|
893
|
-
//
|
|
911
|
+
// by default, enable inlining for css files with size below 40kb before gzip
|
|
894
912
|
provide: RESOURCE_INLINE_OPTIONS,
|
|
895
913
|
useValue: {
|
|
896
914
|
threshold: 40960,
|
package/lib/server.js
CHANGED
|
@@ -6,7 +6,7 @@ var tslib = require('tslib');
|
|
|
6
6
|
var react = require('react');
|
|
7
7
|
var server$1 = require('react-dom/server');
|
|
8
8
|
var core = require('@tramvai/core');
|
|
9
|
-
var
|
|
9
|
+
var tokensCommon = require('@tramvai/tokens-common');
|
|
10
10
|
var tokensRouter = require('@tramvai/tokens-router');
|
|
11
11
|
var moduleClientHints = require('@tramvai/module-client-hints');
|
|
12
12
|
var tokensRender = require('@tramvai/tokens-render');
|
|
@@ -36,7 +36,6 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
36
36
|
var state = require('@tramvai/state');
|
|
37
37
|
var moduleRouter = require('@tramvai/module-router');
|
|
38
38
|
var layoutFactory = require('@tinkoff/layout-factory');
|
|
39
|
-
var tokensCommon = require('@tramvai/tokens-common');
|
|
40
39
|
|
|
41
40
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
42
41
|
|
|
@@ -111,6 +110,11 @@ const processFile = (resource, file) => {
|
|
|
111
110
|
return file;
|
|
112
111
|
};
|
|
113
112
|
|
|
113
|
+
const INTERNAL_CACHE_SIZE = 50;
|
|
114
|
+
const ASSETS_PREFIX = process.env.NODE_ENV === 'development' &&
|
|
115
|
+
(process.env.ASSETS_PREFIX === 'static' || !process.env.ASSETS_PREFIX)
|
|
116
|
+
? `http://localhost:${process.env.PORT_STATIC}/dist/`
|
|
117
|
+
: process.env.ASSETS_PREFIX;
|
|
114
118
|
const getInlineType = (type) => {
|
|
115
119
|
switch (type) {
|
|
116
120
|
case tokensRender.ResourceType.style:
|
|
@@ -131,51 +135,53 @@ const getResourceUrl = (resource) => {
|
|
|
131
135
|
};
|
|
132
136
|
class ResourcesInliner {
|
|
133
137
|
constructor({ resourcesRegistryCache, resourceInlineThreshold, logger }) {
|
|
134
|
-
this.
|
|
138
|
+
this.internalFilesCache = new Map();
|
|
139
|
+
this.runningRequests = new Set();
|
|
140
|
+
this.scheduleFileLoad = async (resource, resourceInlineThreshold) => {
|
|
135
141
|
const url = getResourceUrl(resource);
|
|
136
142
|
const requestKey = `file${url}`;
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
const filesCache = this.getFilesCache(url);
|
|
144
|
+
const result = filesCache.get(url);
|
|
145
|
+
if (result) {
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
if (!this.runningRequests.has(requestKey)) {
|
|
149
|
+
this.runningRequests.add(url);
|
|
150
|
+
try {
|
|
151
|
+
const file = await getFile(url);
|
|
146
152
|
if (file === undefined) {
|
|
147
153
|
this.resourcesRegistryCache.disabledUrlsCache.set(url, true);
|
|
148
154
|
return;
|
|
149
155
|
}
|
|
150
156
|
const size = file.length;
|
|
151
157
|
if (size < resourceInlineThreshold) {
|
|
152
|
-
|
|
158
|
+
filesCache.set(url, processFile(resource, file));
|
|
153
159
|
}
|
|
154
160
|
this.resourcesRegistryCache.sizeCache.set(url, size);
|
|
155
|
-
}
|
|
156
|
-
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
157
163
|
this.log.warn({
|
|
158
164
|
event: 'file-load-failed',
|
|
159
165
|
url,
|
|
160
166
|
error,
|
|
161
167
|
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
this.
|
|
165
|
-
}
|
|
166
|
-
this.resourcesRegistryCache.requestsCache.set(requestKey, getFilePromise);
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
this.runningRequests.delete(requestKey);
|
|
171
|
+
}
|
|
167
172
|
}
|
|
168
173
|
};
|
|
169
|
-
this.scheduleFileSizeLoad = (resource, resourceInlineThreshold) => {
|
|
174
|
+
this.scheduleFileSizeLoad = async (resource, resourceInlineThreshold) => {
|
|
170
175
|
const url = getResourceUrl(resource);
|
|
171
176
|
const requestKey = `size${url}`;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
const result = this.resourcesRegistryCache.sizeCache.get(url);
|
|
178
|
+
if (result) {
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
if (!this.runningRequests.has(requestKey)) {
|
|
182
|
+
this.runningRequests.add(requestKey);
|
|
183
|
+
try {
|
|
184
|
+
const contentLength = await getFileContentLength(url);
|
|
179
185
|
const size = isUndefined__default["default"](contentLength) ? 0 : +contentLength;
|
|
180
186
|
if (size) {
|
|
181
187
|
this.resourcesRegistryCache.sizeCache.set(url, size);
|
|
@@ -183,41 +189,48 @@ class ResourcesInliner {
|
|
|
183
189
|
if (size < resourceInlineThreshold) {
|
|
184
190
|
this.scheduleFileLoad(resource, resourceInlineThreshold);
|
|
185
191
|
}
|
|
186
|
-
}
|
|
187
|
-
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
188
194
|
this.log.warn({
|
|
189
195
|
event: 'file-content-length-load-failed',
|
|
190
196
|
url,
|
|
191
197
|
error,
|
|
192
198
|
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
this.
|
|
196
|
-
}
|
|
197
|
-
this.resourcesRegistryCache.requestsCache.set(requestKey, getFileSizePromise);
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
this.runningRequests.delete(requestKey);
|
|
202
|
+
}
|
|
198
203
|
}
|
|
199
204
|
};
|
|
200
205
|
this.resourcesRegistryCache = resourcesRegistryCache;
|
|
201
206
|
this.resourceInlineThreshold = resourceInlineThreshold;
|
|
202
207
|
this.log = logger('resources-inliner');
|
|
203
208
|
}
|
|
204
|
-
|
|
209
|
+
getFilesCache(url) {
|
|
210
|
+
if (url.startsWith(ASSETS_PREFIX)) {
|
|
211
|
+
// internal resources are resources generated by the current app itself
|
|
212
|
+
// these kind of resources are pretty static and won't be changed while app is running
|
|
213
|
+
// so we can cache it with bare Map and do not care about how to cleanup cache from outdated entries
|
|
214
|
+
return this.internalFilesCache;
|
|
215
|
+
}
|
|
216
|
+
return this.resourcesRegistryCache.filesCache;
|
|
217
|
+
}
|
|
218
|
+
// check that resource's preload-link should be added to render
|
|
205
219
|
shouldAddResource(resource) {
|
|
206
220
|
if (resource.type !== tokensRender.ResourceType.preloadLink) {
|
|
207
|
-
//
|
|
208
|
-
// попасть в итоговую выборку.
|
|
221
|
+
// only checking preload-links
|
|
209
222
|
return true;
|
|
210
223
|
}
|
|
211
224
|
const url = getResourceUrl(resource);
|
|
212
225
|
if (isUndefined__default["default"](url)) {
|
|
213
|
-
//
|
|
226
|
+
// if url is undefined that file is not in cache
|
|
214
227
|
return true;
|
|
215
228
|
}
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
return !this.
|
|
229
|
+
// if file is residing in cache that means it will be inlined in page render
|
|
230
|
+
// therefore no need to have preload-link for the inlined resource
|
|
231
|
+
return !this.getFilesCache(url).has(url);
|
|
219
232
|
}
|
|
220
|
-
//
|
|
233
|
+
// method for check is passed resource should be inlined in HTML-page
|
|
221
234
|
shouldInline(resource) {
|
|
222
235
|
var _a;
|
|
223
236
|
if (!(((_a = this.resourceInlineThreshold) === null || _a === void 0 ? void 0 : _a.types) || []).includes(resource.type)) {
|
|
@@ -228,7 +241,16 @@ class ResourcesInliner {
|
|
|
228
241
|
return false;
|
|
229
242
|
}
|
|
230
243
|
const url = getResourceUrl(resource);
|
|
231
|
-
|
|
244
|
+
const filesCache = this.getFilesCache(url);
|
|
245
|
+
if (isUndefined__default["default"](url) || this.resourcesRegistryCache.disabledUrlsCache.has(url)) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (filesCache.has(url)) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
if (filesCache === this.internalFilesCache &&
|
|
252
|
+
this.internalFilesCache.size >= INTERNAL_CACHE_SIZE) {
|
|
253
|
+
// if we've exceeded limits for the internal resources cache ignore any new entries
|
|
232
254
|
return false;
|
|
233
255
|
}
|
|
234
256
|
if (!this.resourcesRegistryCache.sizeCache.has(url)) {
|
|
@@ -239,25 +261,22 @@ class ResourcesInliner {
|
|
|
239
261
|
if (size > resourceInlineThreshold) {
|
|
240
262
|
return false;
|
|
241
263
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
return true;
|
|
264
|
+
this.scheduleFileLoad(resource, resourceInlineThreshold);
|
|
265
|
+
return false;
|
|
247
266
|
}
|
|
248
267
|
inlineResource(resource) {
|
|
249
268
|
const url = getResourceUrl(resource);
|
|
250
269
|
if (isUndefined__default["default"](url)) {
|
|
251
|
-
//
|
|
270
|
+
// usually, it should not happen but anyway check it for safety
|
|
252
271
|
return [resource];
|
|
253
272
|
}
|
|
254
|
-
const text = this.
|
|
273
|
+
const text = this.getFilesCache(url).get(url);
|
|
255
274
|
if (isEmpty__default["default"](text)) {
|
|
256
275
|
return [resource];
|
|
257
276
|
}
|
|
258
277
|
const result = [];
|
|
259
278
|
if (process.env.NODE_ENV === 'development') {
|
|
260
|
-
//
|
|
279
|
+
// html comment for debugging inlining in dev mode
|
|
261
280
|
result.push({
|
|
262
281
|
slot: resource.slot,
|
|
263
282
|
type: tokensRender.ResourceType.asIs,
|
|
@@ -270,11 +289,10 @@ class ResourcesInliner {
|
|
|
270
289
|
payload: text,
|
|
271
290
|
});
|
|
272
291
|
if (resource.type === tokensRender.ResourceType.style) {
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
// использовать тэг выше, а приходится генерировать новый.
|
|
292
|
+
// If we don't add data-href then extract-css-chunks-webpack-plugin
|
|
293
|
+
// will add link to resources to the html head (https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/blob/master/src/index.js#L346)
|
|
294
|
+
// wherein link in case of css files plugin will look for a link tag, but we add a style tag
|
|
295
|
+
// so we can't use tag from above and have to generate new one
|
|
278
296
|
result.push({
|
|
279
297
|
slot: resource.slot,
|
|
280
298
|
type: tokensRender.ResourceType.style,
|
|
@@ -772,7 +790,8 @@ const providers = [
|
|
|
772
790
|
];
|
|
773
791
|
|
|
774
792
|
var RenderModule_1;
|
|
775
|
-
const
|
|
793
|
+
const REQUEST_TTL = 5 * 60 * 1000;
|
|
794
|
+
const DEFAULT_POLYFILL_CONDITION = '!window.Promise.prototype.finally || !window.URL || !window.URLSearchParams || !window.AbortController || !window.IntersectionObserver || !Object.fromEntries || !window.ResizeObserver';
|
|
776
795
|
exports.RenderModule = RenderModule_1 = class RenderModule {
|
|
777
796
|
static forRoot({ polyfillCondition }) {
|
|
778
797
|
const providers = [];
|
|
@@ -804,25 +823,24 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
804
823
|
provide: RESOURCES_REGISTRY_CACHE,
|
|
805
824
|
scope: dippy.Scope.SINGLETON,
|
|
806
825
|
useFactory: ({ createCache }) => {
|
|
807
|
-
const thirtyMinutes = 1000 * 60 * 30;
|
|
808
826
|
return {
|
|
809
|
-
filesCache: createCache('memory', { max: 50
|
|
810
|
-
sizeCache: createCache('memory', { max: 100
|
|
811
|
-
|
|
812
|
-
disabledUrlsCache: createCache('memory', { max: 150, ttl: 1000 * 60 * 5 }),
|
|
827
|
+
filesCache: createCache('memory', { max: 50 }),
|
|
828
|
+
sizeCache: createCache('memory', { max: 100 }),
|
|
829
|
+
disabledUrlsCache: createCache('memory', { max: 150, ttl: REQUEST_TTL }),
|
|
813
830
|
};
|
|
814
831
|
},
|
|
815
832
|
deps: {
|
|
816
|
-
createCache:
|
|
833
|
+
createCache: tokensCommon.CREATE_CACHE_TOKEN,
|
|
817
834
|
},
|
|
818
835
|
}),
|
|
819
836
|
core.provide({
|
|
820
837
|
provide: RESOURCE_INLINER,
|
|
838
|
+
scope: dippy.Scope.SINGLETON,
|
|
821
839
|
useClass: ResourcesInliner,
|
|
822
840
|
deps: {
|
|
823
841
|
resourcesRegistryCache: RESOURCES_REGISTRY_CACHE,
|
|
824
842
|
resourceInlineThreshold: { token: tokensRender.RESOURCE_INLINE_OPTIONS, optional: true },
|
|
825
|
-
logger:
|
|
843
|
+
logger: tokensCommon.LOGGER_TOKEN,
|
|
826
844
|
},
|
|
827
845
|
}),
|
|
828
846
|
core.provide({
|
|
@@ -859,11 +877,11 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
859
877
|
};
|
|
860
878
|
},
|
|
861
879
|
deps: {
|
|
862
|
-
logger:
|
|
863
|
-
requestManager:
|
|
864
|
-
responseManager:
|
|
880
|
+
logger: tokensCommon.LOGGER_TOKEN,
|
|
881
|
+
requestManager: tokensCommon.REQUEST_MANAGER_TOKEN,
|
|
882
|
+
responseManager: tokensCommon.RESPONSE_MANAGER_TOKEN,
|
|
865
883
|
htmlBuilder: 'htmlBuilder',
|
|
866
|
-
context:
|
|
884
|
+
context: tokensCommon.CONTEXT_TOKEN,
|
|
867
885
|
},
|
|
868
886
|
multi: true,
|
|
869
887
|
}),
|
|
@@ -874,7 +892,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
874
892
|
reactRender: 'reactRender',
|
|
875
893
|
pageService: tokensRouter.PAGE_SERVICE_TOKEN,
|
|
876
894
|
resourcesRegistry: tokensRender.RESOURCES_REGISTRY,
|
|
877
|
-
context:
|
|
895
|
+
context: tokensCommon.CONTEXT_TOKEN,
|
|
878
896
|
htmlPageSchema: 'htmlPageSchema',
|
|
879
897
|
renderSlots: { token: tokensRender.RENDER_SLOTS, optional: true },
|
|
880
898
|
polyfillCondition: tokensRender.POLYFILL_CONDITION,
|
|
@@ -886,7 +904,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
886
904
|
provide: 'reactRender',
|
|
887
905
|
useClass: ReactRenderServer,
|
|
888
906
|
deps: {
|
|
889
|
-
context:
|
|
907
|
+
context: tokensCommon.CONTEXT_TOKEN,
|
|
890
908
|
pageService: tokensRouter.PAGE_SERVICE_TOKEN,
|
|
891
909
|
customRender: { token: tokensRender.CUSTOM_RENDER, optional: true },
|
|
892
910
|
extendRender: { token: tokensRender.EXTEND_RENDER, optional: true },
|
|
@@ -926,7 +944,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
926
944
|
useValue: DEFAULT_POLYFILL_CONDITION,
|
|
927
945
|
}),
|
|
928
946
|
core.provide({
|
|
929
|
-
//
|
|
947
|
+
// by default, enable inlining for css files with size below 40kb before gzip
|
|
930
948
|
provide: tokensRender.RESOURCE_INLINE_OPTIONS,
|
|
931
949
|
useValue: {
|
|
932
950
|
threshold: 40960,
|
|
@@ -959,7 +977,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
959
977
|
},
|
|
960
978
|
deps: {
|
|
961
979
|
RootErrorBoundary: { token: react$1.ROOT_ERROR_BOUNDARY_COMPONENT_TOKEN, optional: true },
|
|
962
|
-
logger:
|
|
980
|
+
logger: tokensCommon.LOGGER_TOKEN,
|
|
963
981
|
},
|
|
964
982
|
}),
|
|
965
983
|
core.provide({
|
|
@@ -974,7 +992,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
974
992
|
return result;
|
|
975
993
|
},
|
|
976
994
|
deps: {
|
|
977
|
-
requestManager:
|
|
995
|
+
requestManager: tokensCommon.REQUEST_MANAGER_TOKEN,
|
|
978
996
|
userAgent: moduleClientHints.USER_AGENT_TOKEN,
|
|
979
997
|
cache: 'modernSatisfiesLruCache',
|
|
980
998
|
},
|
|
@@ -986,7 +1004,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
986
1004
|
return createCache('memory', { max: 50 });
|
|
987
1005
|
},
|
|
988
1006
|
deps: {
|
|
989
|
-
createCache:
|
|
1007
|
+
createCache: tokensCommon.CREATE_CACHE_TOKEN,
|
|
990
1008
|
},
|
|
991
1009
|
}),
|
|
992
1010
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tramvai/module-render",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"browser": "lib/browser.js",
|
|
6
6
|
"main": "lib/server.js",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"@tinkoff/htmlpagebuilder": "0.4.24",
|
|
25
25
|
"@tinkoff/layout-factory": "0.2.31",
|
|
26
26
|
"@tinkoff/url": "0.7.39",
|
|
27
|
-
"@tinkoff/user-agent": "0.4.
|
|
28
|
-
"@tramvai/module-client-hints": "2.
|
|
29
|
-
"@tramvai/module-router": "2.
|
|
30
|
-
"@tramvai/react": "2.
|
|
27
|
+
"@tinkoff/user-agent": "0.4.24",
|
|
28
|
+
"@tramvai/module-client-hints": "2.7.1",
|
|
29
|
+
"@tramvai/module-router": "2.7.1",
|
|
30
|
+
"@tramvai/react": "2.7.1",
|
|
31
31
|
"@tramvai/safe-strings": "0.4.5",
|
|
32
|
-
"@tramvai/tokens-render": "2.
|
|
33
|
-
"@tramvai/experiments": "2.
|
|
32
|
+
"@tramvai/tokens-render": "2.7.1",
|
|
33
|
+
"@tramvai/experiments": "2.7.1",
|
|
34
34
|
"@types/loadable__server": "^5.12.6",
|
|
35
35
|
"node-fetch": "^2.6.1"
|
|
36
36
|
},
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
"@tinkoff/dippy": "0.7.44",
|
|
39
39
|
"@tinkoff/utils": "^2.1.2",
|
|
40
40
|
"@tinkoff/react-hooks": "0.0.27",
|
|
41
|
-
"@tramvai/cli": "2.
|
|
42
|
-
"@tramvai/core": "2.
|
|
43
|
-
"@tramvai/module-common": "2.
|
|
44
|
-
"@tramvai/state": "2.
|
|
45
|
-
"@tramvai/test-helpers": "2.
|
|
46
|
-
"@tramvai/tokens-common": "2.
|
|
47
|
-
"@tramvai/tokens-router": "2.
|
|
48
|
-
"@tramvai/tokens-server-private": "2.
|
|
41
|
+
"@tramvai/cli": "2.7.1",
|
|
42
|
+
"@tramvai/core": "2.7.1",
|
|
43
|
+
"@tramvai/module-common": "2.7.1",
|
|
44
|
+
"@tramvai/state": "2.7.1",
|
|
45
|
+
"@tramvai/test-helpers": "2.7.1",
|
|
46
|
+
"@tramvai/tokens-common": "2.7.1",
|
|
47
|
+
"@tramvai/tokens-router": "2.7.1",
|
|
48
|
+
"@tramvai/tokens-server-private": "2.7.1",
|
|
49
49
|
"express": "^4.17.1",
|
|
50
50
|
"prop-types": "^15.6.2",
|
|
51
51
|
"react": ">=16.14.0",
|