@tramvai/module-render 2.70.0 → 2.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser.js +9 -233
- package/lib/client/index.browser.js +48 -0
- package/lib/client/renderer.browser.js +50 -0
- package/lib/react/index.browser.js +11 -0
- package/lib/react/index.es.js +11 -0
- package/lib/react/index.js +15 -0
- package/lib/react/pageErrorBoundary.browser.js +23 -0
- package/lib/react/pageErrorBoundary.es.js +23 -0
- package/lib/react/pageErrorBoundary.js +27 -0
- package/lib/react/root.browser.js +58 -0
- package/lib/react/root.es.js +58 -0
- package/lib/react/root.js +62 -0
- package/lib/resourcesInliner/externalFilesHelper.es.js +17 -0
- package/lib/resourcesInliner/externalFilesHelper.js +26 -0
- package/lib/resourcesInliner/fileProcessor.es.js +31 -0
- package/lib/resourcesInliner/fileProcessor.js +40 -0
- package/lib/resourcesInliner/resourcesInliner.es.js +204 -0
- package/lib/resourcesInliner/resourcesInliner.js +213 -0
- package/lib/resourcesInliner/tokens.es.js +15 -0
- package/lib/resourcesInliner/tokens.js +20 -0
- package/lib/resourcesRegistry/index.es.js +28 -0
- package/lib/resourcesRegistry/index.js +36 -0
- package/lib/server/PageBuilder.es.js +93 -0
- package/lib/server/PageBuilder.js +102 -0
- package/lib/server/ReactRenderServer.es.js +90 -0
- package/lib/server/ReactRenderServer.js +98 -0
- package/lib/server/blocks/bundleResource/bundleResource.es.js +62 -0
- package/lib/server/blocks/bundleResource/bundleResource.js +71 -0
- package/lib/server/blocks/polyfill.es.js +35 -0
- package/lib/server/blocks/polyfill.js +39 -0
- package/lib/{server_inline.inline.es.js → server/blocks/preload/onload.inline.es.js} +1 -1
- package/lib/{server_inline.inline.js → server/blocks/preload/onload.inline.js} +2 -0
- package/lib/server/blocks/preload/preloadBlock.es.js +21 -0
- package/lib/server/blocks/preload/preloadBlock.js +30 -0
- package/lib/server/blocks/utils/fetchWebpackStats.es.js +88 -0
- package/lib/server/blocks/utils/fetchWebpackStats.js +115 -0
- package/lib/server/blocks/utils/flushFiles.es.js +33 -0
- package/lib/server/blocks/utils/flushFiles.js +44 -0
- package/lib/server/blocks/utils/requireFunc.es.js +5 -0
- package/lib/server/blocks/utils/requireFunc.js +9 -0
- package/lib/server/constants/performance.es.js +3 -0
- package/lib/server/constants/performance.js +7 -0
- package/lib/server/htmlPageSchema.es.js +33 -0
- package/lib/server/htmlPageSchema.js +37 -0
- package/lib/server/utils.es.js +16 -0
- package/lib/server/utils.js +20 -0
- package/lib/server.es.js +18 -859
- package/lib/server.js +33 -909
- package/lib/shared/LayoutModule.browser.js +40 -0
- package/lib/shared/LayoutModule.es.js +40 -0
- package/lib/shared/LayoutModule.js +42 -0
- package/lib/shared/pageErrorStore.browser.js +19 -0
- package/lib/shared/pageErrorStore.es.js +19 -0
- package/lib/shared/pageErrorStore.js +26 -0
- package/lib/shared/providers.browser.js +18 -0
- package/lib/shared/providers.es.js +18 -0
- package/lib/shared/providers.js +22 -0
- package/package.json +23 -24
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var fetch = require('node-fetch');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
|
+
|
|
9
|
+
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
10
|
+
|
|
11
|
+
const thirtySeconds = 1000 * 30;
|
|
12
|
+
const getFileContentLength = async (url) => {
|
|
13
|
+
const info = await fetch__default["default"](url, { method: 'HEAD', timeout: thirtySeconds });
|
|
14
|
+
return info.headers.get('content-length');
|
|
15
|
+
};
|
|
16
|
+
const getFile = async (url) => {
|
|
17
|
+
const fileResponse = await fetch__default["default"](url, { timeout: thirtySeconds });
|
|
18
|
+
if (fileResponse.ok) {
|
|
19
|
+
const file = await fileResponse.text();
|
|
20
|
+
return file;
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
exports.getFile = getFile;
|
|
26
|
+
exports.getFileContentLength = getFileContentLength;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { resolve } from '@tinkoff/url';
|
|
2
|
+
import startsWith from '@tinkoff/utils/string/startsWith';
|
|
3
|
+
import { ResourceType } from '@tramvai/tokens-render';
|
|
4
|
+
|
|
5
|
+
const URL_OCCURRENCES_RE = /(url\((['"]?))(.*?)(\2\))/gi;
|
|
6
|
+
const isAbsoluteUrl = (resourceUrl) => ['http://', 'https://', '//'].some((prefix) => startsWith(prefix, resourceUrl));
|
|
7
|
+
const toHttpsUrl = (resourceUrl) => {
|
|
8
|
+
if (resourceUrl.indexOf('//localhost') !== -1) {
|
|
9
|
+
return resourceUrl;
|
|
10
|
+
}
|
|
11
|
+
if (startsWith('http://', resourceUrl)) {
|
|
12
|
+
return resourceUrl.replace('http://', 'https://');
|
|
13
|
+
}
|
|
14
|
+
if (startsWith('//', resourceUrl)) {
|
|
15
|
+
return resourceUrl.replace('//', 'https://');
|
|
16
|
+
}
|
|
17
|
+
return resourceUrl;
|
|
18
|
+
};
|
|
19
|
+
const urlReplacerCreator = (resourceUrl) => (str, leftGroup, _, extractedUrl, rightGroup) => {
|
|
20
|
+
return isAbsoluteUrl(extractedUrl)
|
|
21
|
+
? str
|
|
22
|
+
: `${leftGroup}${resolve(toHttpsUrl(resourceUrl), extractedUrl)}${rightGroup}`;
|
|
23
|
+
};
|
|
24
|
+
const processFile = (resource, file) => {
|
|
25
|
+
if (resource.type === ResourceType.style) {
|
|
26
|
+
return file.replace(URL_OCCURRENCES_RE, urlReplacerCreator(resource.payload));
|
|
27
|
+
}
|
|
28
|
+
return file;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { URL_OCCURRENCES_RE, processFile };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var url = require('@tinkoff/url');
|
|
6
|
+
var startsWith = require('@tinkoff/utils/string/startsWith');
|
|
7
|
+
var tokensRender = require('@tramvai/tokens-render');
|
|
8
|
+
|
|
9
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
10
|
+
|
|
11
|
+
var startsWith__default = /*#__PURE__*/_interopDefaultLegacy(startsWith);
|
|
12
|
+
|
|
13
|
+
const URL_OCCURRENCES_RE = /(url\((['"]?))(.*?)(\2\))/gi;
|
|
14
|
+
const isAbsoluteUrl = (resourceUrl) => ['http://', 'https://', '//'].some((prefix) => startsWith__default["default"](prefix, resourceUrl));
|
|
15
|
+
const toHttpsUrl = (resourceUrl) => {
|
|
16
|
+
if (resourceUrl.indexOf('//localhost') !== -1) {
|
|
17
|
+
return resourceUrl;
|
|
18
|
+
}
|
|
19
|
+
if (startsWith__default["default"]('http://', resourceUrl)) {
|
|
20
|
+
return resourceUrl.replace('http://', 'https://');
|
|
21
|
+
}
|
|
22
|
+
if (startsWith__default["default"]('//', resourceUrl)) {
|
|
23
|
+
return resourceUrl.replace('//', 'https://');
|
|
24
|
+
}
|
|
25
|
+
return resourceUrl;
|
|
26
|
+
};
|
|
27
|
+
const urlReplacerCreator = (resourceUrl) => (str, leftGroup, _, extractedUrl, rightGroup) => {
|
|
28
|
+
return isAbsoluteUrl(extractedUrl)
|
|
29
|
+
? str
|
|
30
|
+
: `${leftGroup}${url.resolve(toHttpsUrl(resourceUrl), extractedUrl)}${rightGroup}`;
|
|
31
|
+
};
|
|
32
|
+
const processFile = (resource, file) => {
|
|
33
|
+
if (resource.type === tokensRender.ResourceType.style) {
|
|
34
|
+
return file.replace(URL_OCCURRENCES_RE, urlReplacerCreator(resource.payload));
|
|
35
|
+
}
|
|
36
|
+
return file;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
exports.URL_OCCURRENCES_RE = URL_OCCURRENCES_RE;
|
|
40
|
+
exports.processFile = processFile;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import isUndefined from '@tinkoff/utils/is/undefined';
|
|
2
|
+
import isEmpty from '@tinkoff/utils/is/empty';
|
|
3
|
+
import { ResourceType } from '@tramvai/tokens-render';
|
|
4
|
+
import { isAbsoluteUrl } from '@tinkoff/url';
|
|
5
|
+
import { getFile, getFileContentLength } from './externalFilesHelper.es.js';
|
|
6
|
+
import { processFile } from './fileProcessor.es.js';
|
|
7
|
+
|
|
8
|
+
const INTERNAL_CACHE_SIZE = 50;
|
|
9
|
+
const ASSETS_PREFIX = process.env.NODE_ENV === 'development' &&
|
|
10
|
+
(process.env.ASSETS_PREFIX === 'static' || !process.env.ASSETS_PREFIX)
|
|
11
|
+
? `http://localhost:${process.env.PORT_STATIC}/dist/`
|
|
12
|
+
: process.env.ASSETS_PREFIX;
|
|
13
|
+
const getInlineType = (type) => {
|
|
14
|
+
switch (type) {
|
|
15
|
+
case ResourceType.style:
|
|
16
|
+
return ResourceType.inlineStyle;
|
|
17
|
+
case ResourceType.script:
|
|
18
|
+
return ResourceType.inlineScript;
|
|
19
|
+
default:
|
|
20
|
+
return type;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const getResourceUrl = (resource) => {
|
|
24
|
+
if (isEmpty(resource.payload) || !isAbsoluteUrl(resource.payload)) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return resource.payload.startsWith('//')
|
|
28
|
+
? `https://${resource.payload.substr(2)}`
|
|
29
|
+
: resource.payload;
|
|
30
|
+
};
|
|
31
|
+
class ResourcesInliner {
|
|
32
|
+
constructor({ resourcesRegistryCache, resourceInlineThreshold, logger }) {
|
|
33
|
+
this.internalFilesCache = new Map();
|
|
34
|
+
this.runningRequests = new Set();
|
|
35
|
+
this.scheduleFileLoad = async (resource, resourceInlineThreshold) => {
|
|
36
|
+
const url = getResourceUrl(resource);
|
|
37
|
+
const requestKey = `file${url}`;
|
|
38
|
+
const filesCache = this.getFilesCache(url);
|
|
39
|
+
const result = filesCache.get(url);
|
|
40
|
+
if (result) {
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
if (!this.runningRequests.has(requestKey)) {
|
|
44
|
+
this.runningRequests.add(url);
|
|
45
|
+
try {
|
|
46
|
+
const file = await getFile(url);
|
|
47
|
+
if (file === undefined) {
|
|
48
|
+
this.resourcesRegistryCache.disabledUrlsCache.set(url, true);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const size = file.length;
|
|
52
|
+
if (size < resourceInlineThreshold) {
|
|
53
|
+
filesCache.set(url, processFile(resource, file));
|
|
54
|
+
}
|
|
55
|
+
this.resourcesRegistryCache.sizeCache.set(url, size);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
this.log.warn({
|
|
59
|
+
event: 'file-load-failed',
|
|
60
|
+
url,
|
|
61
|
+
error,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
this.runningRequests.delete(requestKey);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
this.scheduleFileSizeLoad = async (resource, resourceInlineThreshold) => {
|
|
70
|
+
const url = getResourceUrl(resource);
|
|
71
|
+
const requestKey = `size${url}`;
|
|
72
|
+
const result = this.resourcesRegistryCache.sizeCache.get(url);
|
|
73
|
+
if (result) {
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
if (!this.runningRequests.has(requestKey)) {
|
|
77
|
+
this.runningRequests.add(requestKey);
|
|
78
|
+
try {
|
|
79
|
+
const contentLength = await getFileContentLength(url);
|
|
80
|
+
const size = isUndefined(contentLength) ? 0 : +contentLength;
|
|
81
|
+
if (size) {
|
|
82
|
+
this.resourcesRegistryCache.sizeCache.set(url, size);
|
|
83
|
+
}
|
|
84
|
+
if (size < resourceInlineThreshold) {
|
|
85
|
+
this.scheduleFileLoad(resource, resourceInlineThreshold);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
this.log.warn({
|
|
90
|
+
event: 'file-content-length-load-failed',
|
|
91
|
+
url,
|
|
92
|
+
error,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
this.runningRequests.delete(requestKey);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
this.resourcesRegistryCache = resourcesRegistryCache;
|
|
101
|
+
this.resourceInlineThreshold = resourceInlineThreshold;
|
|
102
|
+
this.log = logger('resources-inliner');
|
|
103
|
+
}
|
|
104
|
+
getFilesCache(url) {
|
|
105
|
+
if (url.startsWith(ASSETS_PREFIX)) {
|
|
106
|
+
// internal resources are resources generated by the current app itself
|
|
107
|
+
// these kind of resources are pretty static and won't be changed while app is running
|
|
108
|
+
// so we can cache it with bare Map and do not care about how to cleanup cache from outdated entries
|
|
109
|
+
return this.internalFilesCache;
|
|
110
|
+
}
|
|
111
|
+
return this.resourcesRegistryCache.filesCache;
|
|
112
|
+
}
|
|
113
|
+
// check that resource's preload-link should be added to render
|
|
114
|
+
shouldAddResource(resource) {
|
|
115
|
+
if (resource.type !== ResourceType.preloadLink) {
|
|
116
|
+
// only checking preload-links
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
const url = getResourceUrl(resource);
|
|
120
|
+
if (isUndefined(url)) {
|
|
121
|
+
// if url is undefined that file is not in cache
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
// if file is residing in cache that means it will be inlined in page render
|
|
125
|
+
// therefore no need to have preload-link for the inlined resource
|
|
126
|
+
return !this.getFilesCache(url).has(url);
|
|
127
|
+
}
|
|
128
|
+
// method for check is passed resource should be inlined in HTML-page
|
|
129
|
+
shouldInline(resource) {
|
|
130
|
+
var _a;
|
|
131
|
+
if (!(((_a = this.resourceInlineThreshold) === null || _a === void 0 ? void 0 : _a.types) || []).includes(resource.type)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const resourceInlineThreshold = this.resourceInlineThreshold.threshold;
|
|
135
|
+
if (isUndefined(resourceInlineThreshold)) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const url = getResourceUrl(resource);
|
|
139
|
+
if (isUndefined(url) || this.resourcesRegistryCache.disabledUrlsCache.has(url)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const filesCache = this.getFilesCache(url);
|
|
143
|
+
if (filesCache.has(url)) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
if (filesCache === this.internalFilesCache &&
|
|
147
|
+
this.internalFilesCache.size >= INTERNAL_CACHE_SIZE) {
|
|
148
|
+
// if we've exceeded limits for the internal resources cache ignore any new entries
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
if (!this.resourcesRegistryCache.sizeCache.has(url)) {
|
|
152
|
+
this.scheduleFileSizeLoad(resource, resourceInlineThreshold);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const size = this.resourcesRegistryCache.sizeCache.get(url);
|
|
156
|
+
if (size > resourceInlineThreshold) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
this.scheduleFileLoad(resource, resourceInlineThreshold);
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
inlineResource(resource) {
|
|
163
|
+
const url = getResourceUrl(resource);
|
|
164
|
+
if (isUndefined(url)) {
|
|
165
|
+
// usually, it should not happen but anyway check it for safety
|
|
166
|
+
return [resource];
|
|
167
|
+
}
|
|
168
|
+
const text = this.getFilesCache(url).get(url);
|
|
169
|
+
if (isEmpty(text)) {
|
|
170
|
+
return [resource];
|
|
171
|
+
}
|
|
172
|
+
const result = [];
|
|
173
|
+
if (process.env.NODE_ENV === 'development') {
|
|
174
|
+
// html comment for debugging inlining in dev mode
|
|
175
|
+
result.push({
|
|
176
|
+
slot: resource.slot,
|
|
177
|
+
type: ResourceType.asIs,
|
|
178
|
+
payload: `<!-- Inlined file ${url} -->`,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
result.push({
|
|
182
|
+
...resource,
|
|
183
|
+
type: getInlineType(resource.type),
|
|
184
|
+
payload: text,
|
|
185
|
+
});
|
|
186
|
+
if (resource.type === ResourceType.style) {
|
|
187
|
+
// If we don't add data-href then extract-css-chunks-webpack-plugin
|
|
188
|
+
// will add link to resources to the html head (https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/blob/master/src/index.js#L346)
|
|
189
|
+
// wherein link in case of css files plugin will look for a link tag, but we add a style tag
|
|
190
|
+
// so we can't use tag from above and have to generate new one
|
|
191
|
+
result.push({
|
|
192
|
+
slot: resource.slot,
|
|
193
|
+
type: ResourceType.style,
|
|
194
|
+
payload: null,
|
|
195
|
+
attrs: {
|
|
196
|
+
'data-href': resource.payload,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { ResourcesInliner };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var isUndefined = require('@tinkoff/utils/is/undefined');
|
|
6
|
+
var isEmpty = require('@tinkoff/utils/is/empty');
|
|
7
|
+
var tokensRender = require('@tramvai/tokens-render');
|
|
8
|
+
var url = require('@tinkoff/url');
|
|
9
|
+
var externalFilesHelper = require('./externalFilesHelper.js');
|
|
10
|
+
var fileProcessor = require('./fileProcessor.js');
|
|
11
|
+
|
|
12
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
13
|
+
|
|
14
|
+
var isUndefined__default = /*#__PURE__*/_interopDefaultLegacy(isUndefined);
|
|
15
|
+
var isEmpty__default = /*#__PURE__*/_interopDefaultLegacy(isEmpty);
|
|
16
|
+
|
|
17
|
+
const INTERNAL_CACHE_SIZE = 50;
|
|
18
|
+
const ASSETS_PREFIX = process.env.NODE_ENV === 'development' &&
|
|
19
|
+
(process.env.ASSETS_PREFIX === 'static' || !process.env.ASSETS_PREFIX)
|
|
20
|
+
? `http://localhost:${process.env.PORT_STATIC}/dist/`
|
|
21
|
+
: process.env.ASSETS_PREFIX;
|
|
22
|
+
const getInlineType = (type) => {
|
|
23
|
+
switch (type) {
|
|
24
|
+
case tokensRender.ResourceType.style:
|
|
25
|
+
return tokensRender.ResourceType.inlineStyle;
|
|
26
|
+
case tokensRender.ResourceType.script:
|
|
27
|
+
return tokensRender.ResourceType.inlineScript;
|
|
28
|
+
default:
|
|
29
|
+
return type;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const getResourceUrl = (resource) => {
|
|
33
|
+
if (isEmpty__default["default"](resource.payload) || !url.isAbsoluteUrl(resource.payload)) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
return resource.payload.startsWith('//')
|
|
37
|
+
? `https://${resource.payload.substr(2)}`
|
|
38
|
+
: resource.payload;
|
|
39
|
+
};
|
|
40
|
+
class ResourcesInliner {
|
|
41
|
+
constructor({ resourcesRegistryCache, resourceInlineThreshold, logger }) {
|
|
42
|
+
this.internalFilesCache = new Map();
|
|
43
|
+
this.runningRequests = new Set();
|
|
44
|
+
this.scheduleFileLoad = async (resource, resourceInlineThreshold) => {
|
|
45
|
+
const url = getResourceUrl(resource);
|
|
46
|
+
const requestKey = `file${url}`;
|
|
47
|
+
const filesCache = this.getFilesCache(url);
|
|
48
|
+
const result = filesCache.get(url);
|
|
49
|
+
if (result) {
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
if (!this.runningRequests.has(requestKey)) {
|
|
53
|
+
this.runningRequests.add(url);
|
|
54
|
+
try {
|
|
55
|
+
const file = await externalFilesHelper.getFile(url);
|
|
56
|
+
if (file === undefined) {
|
|
57
|
+
this.resourcesRegistryCache.disabledUrlsCache.set(url, true);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const size = file.length;
|
|
61
|
+
if (size < resourceInlineThreshold) {
|
|
62
|
+
filesCache.set(url, fileProcessor.processFile(resource, file));
|
|
63
|
+
}
|
|
64
|
+
this.resourcesRegistryCache.sizeCache.set(url, size);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
this.log.warn({
|
|
68
|
+
event: 'file-load-failed',
|
|
69
|
+
url,
|
|
70
|
+
error,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
this.runningRequests.delete(requestKey);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
this.scheduleFileSizeLoad = async (resource, resourceInlineThreshold) => {
|
|
79
|
+
const url = getResourceUrl(resource);
|
|
80
|
+
const requestKey = `size${url}`;
|
|
81
|
+
const result = this.resourcesRegistryCache.sizeCache.get(url);
|
|
82
|
+
if (result) {
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
if (!this.runningRequests.has(requestKey)) {
|
|
86
|
+
this.runningRequests.add(requestKey);
|
|
87
|
+
try {
|
|
88
|
+
const contentLength = await externalFilesHelper.getFileContentLength(url);
|
|
89
|
+
const size = isUndefined__default["default"](contentLength) ? 0 : +contentLength;
|
|
90
|
+
if (size) {
|
|
91
|
+
this.resourcesRegistryCache.sizeCache.set(url, size);
|
|
92
|
+
}
|
|
93
|
+
if (size < resourceInlineThreshold) {
|
|
94
|
+
this.scheduleFileLoad(resource, resourceInlineThreshold);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
this.log.warn({
|
|
99
|
+
event: 'file-content-length-load-failed',
|
|
100
|
+
url,
|
|
101
|
+
error,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
this.runningRequests.delete(requestKey);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
this.resourcesRegistryCache = resourcesRegistryCache;
|
|
110
|
+
this.resourceInlineThreshold = resourceInlineThreshold;
|
|
111
|
+
this.log = logger('resources-inliner');
|
|
112
|
+
}
|
|
113
|
+
getFilesCache(url) {
|
|
114
|
+
if (url.startsWith(ASSETS_PREFIX)) {
|
|
115
|
+
// internal resources are resources generated by the current app itself
|
|
116
|
+
// these kind of resources are pretty static and won't be changed while app is running
|
|
117
|
+
// so we can cache it with bare Map and do not care about how to cleanup cache from outdated entries
|
|
118
|
+
return this.internalFilesCache;
|
|
119
|
+
}
|
|
120
|
+
return this.resourcesRegistryCache.filesCache;
|
|
121
|
+
}
|
|
122
|
+
// check that resource's preload-link should be added to render
|
|
123
|
+
shouldAddResource(resource) {
|
|
124
|
+
if (resource.type !== tokensRender.ResourceType.preloadLink) {
|
|
125
|
+
// only checking preload-links
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
const url = getResourceUrl(resource);
|
|
129
|
+
if (isUndefined__default["default"](url)) {
|
|
130
|
+
// if url is undefined that file is not in cache
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
// if file is residing in cache that means it will be inlined in page render
|
|
134
|
+
// therefore no need to have preload-link for the inlined resource
|
|
135
|
+
return !this.getFilesCache(url).has(url);
|
|
136
|
+
}
|
|
137
|
+
// method for check is passed resource should be inlined in HTML-page
|
|
138
|
+
shouldInline(resource) {
|
|
139
|
+
var _a;
|
|
140
|
+
if (!(((_a = this.resourceInlineThreshold) === null || _a === void 0 ? void 0 : _a.types) || []).includes(resource.type)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const resourceInlineThreshold = this.resourceInlineThreshold.threshold;
|
|
144
|
+
if (isUndefined__default["default"](resourceInlineThreshold)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const url = getResourceUrl(resource);
|
|
148
|
+
if (isUndefined__default["default"](url) || this.resourcesRegistryCache.disabledUrlsCache.has(url)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const filesCache = this.getFilesCache(url);
|
|
152
|
+
if (filesCache.has(url)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
if (filesCache === this.internalFilesCache &&
|
|
156
|
+
this.internalFilesCache.size >= INTERNAL_CACHE_SIZE) {
|
|
157
|
+
// if we've exceeded limits for the internal resources cache ignore any new entries
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
if (!this.resourcesRegistryCache.sizeCache.has(url)) {
|
|
161
|
+
this.scheduleFileSizeLoad(resource, resourceInlineThreshold);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
const size = this.resourcesRegistryCache.sizeCache.get(url);
|
|
165
|
+
if (size > resourceInlineThreshold) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
this.scheduleFileLoad(resource, resourceInlineThreshold);
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
inlineResource(resource) {
|
|
172
|
+
const url = getResourceUrl(resource);
|
|
173
|
+
if (isUndefined__default["default"](url)) {
|
|
174
|
+
// usually, it should not happen but anyway check it for safety
|
|
175
|
+
return [resource];
|
|
176
|
+
}
|
|
177
|
+
const text = this.getFilesCache(url).get(url);
|
|
178
|
+
if (isEmpty__default["default"](text)) {
|
|
179
|
+
return [resource];
|
|
180
|
+
}
|
|
181
|
+
const result = [];
|
|
182
|
+
if (process.env.NODE_ENV === 'development') {
|
|
183
|
+
// html comment for debugging inlining in dev mode
|
|
184
|
+
result.push({
|
|
185
|
+
slot: resource.slot,
|
|
186
|
+
type: tokensRender.ResourceType.asIs,
|
|
187
|
+
payload: `<!-- Inlined file ${url} -->`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
result.push({
|
|
191
|
+
...resource,
|
|
192
|
+
type: getInlineType(resource.type),
|
|
193
|
+
payload: text,
|
|
194
|
+
});
|
|
195
|
+
if (resource.type === tokensRender.ResourceType.style) {
|
|
196
|
+
// If we don't add data-href then extract-css-chunks-webpack-plugin
|
|
197
|
+
// will add link to resources to the html head (https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/blob/master/src/index.js#L346)
|
|
198
|
+
// wherein link in case of css files plugin will look for a link tag, but we add a style tag
|
|
199
|
+
// so we can't use tag from above and have to generate new one
|
|
200
|
+
result.push({
|
|
201
|
+
slot: resource.slot,
|
|
202
|
+
type: tokensRender.ResourceType.style,
|
|
203
|
+
payload: null,
|
|
204
|
+
attrs: {
|
|
205
|
+
'data-href': resource.payload,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
exports.ResourcesInliner = ResourcesInliner;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createToken } from '@tinkoff/dippy';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @description
|
|
5
|
+
* Инлайнер ресурсов - используется на сервере для регистрации файлов, которые должны быть вставлены
|
|
6
|
+
* в итоговую html-страницу в виде ссылки на файл или заинлайнеными полностью
|
|
7
|
+
*/
|
|
8
|
+
const RESOURCE_INLINER = createToken('resourceInliner');
|
|
9
|
+
/**
|
|
10
|
+
* @description
|
|
11
|
+
* Кэш загруженных ресурсов.
|
|
12
|
+
*/
|
|
13
|
+
const RESOURCES_REGISTRY_CACHE = createToken('resourcesRegistryCache');
|
|
14
|
+
|
|
15
|
+
export { RESOURCES_REGISTRY_CACHE, RESOURCE_INLINER };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var dippy = require('@tinkoff/dippy');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @description
|
|
9
|
+
* Инлайнер ресурсов - используется на сервере для регистрации файлов, которые должны быть вставлены
|
|
10
|
+
* в итоговую html-страницу в виде ссылки на файл или заинлайнеными полностью
|
|
11
|
+
*/
|
|
12
|
+
const RESOURCE_INLINER = dippy.createToken('resourceInliner');
|
|
13
|
+
/**
|
|
14
|
+
* @description
|
|
15
|
+
* Кэш загруженных ресурсов.
|
|
16
|
+
*/
|
|
17
|
+
const RESOURCES_REGISTRY_CACHE = dippy.createToken('resourcesRegistryCache');
|
|
18
|
+
|
|
19
|
+
exports.RESOURCES_REGISTRY_CACHE = RESOURCES_REGISTRY_CACHE;
|
|
20
|
+
exports.RESOURCE_INLINER = RESOURCE_INLINER;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import toArray from '@tinkoff/utils/array/toArray';
|
|
2
|
+
|
|
3
|
+
class ResourcesRegistry {
|
|
4
|
+
constructor({ resourceInliner }) {
|
|
5
|
+
this.resources = new Set();
|
|
6
|
+
this.resourceInliner = resourceInliner;
|
|
7
|
+
}
|
|
8
|
+
register(resourceOrResources) {
|
|
9
|
+
toArray(resourceOrResources).forEach((resource) => {
|
|
10
|
+
this.resources.add(resource);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
getPageResources() {
|
|
14
|
+
return Array.from(this.resources.values())
|
|
15
|
+
.reduce((acc, resource) => {
|
|
16
|
+
if (this.resourceInliner.shouldInline(resource)) {
|
|
17
|
+
Array.prototype.push.apply(acc, this.resourceInliner.inlineResource(resource));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
acc.push(resource);
|
|
21
|
+
}
|
|
22
|
+
return acc;
|
|
23
|
+
}, [])
|
|
24
|
+
.filter((resource) => this.resourceInliner.shouldAddResource(resource));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { ResourcesRegistry };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var toArray = require('@tinkoff/utils/array/toArray');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
|
+
|
|
9
|
+
var toArray__default = /*#__PURE__*/_interopDefaultLegacy(toArray);
|
|
10
|
+
|
|
11
|
+
class ResourcesRegistry {
|
|
12
|
+
constructor({ resourceInliner }) {
|
|
13
|
+
this.resources = new Set();
|
|
14
|
+
this.resourceInliner = resourceInliner;
|
|
15
|
+
}
|
|
16
|
+
register(resourceOrResources) {
|
|
17
|
+
toArray__default["default"](resourceOrResources).forEach((resource) => {
|
|
18
|
+
this.resources.add(resource);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
getPageResources() {
|
|
22
|
+
return Array.from(this.resources.values())
|
|
23
|
+
.reduce((acc, resource) => {
|
|
24
|
+
if (this.resourceInliner.shouldInline(resource)) {
|
|
25
|
+
Array.prototype.push.apply(acc, this.resourceInliner.inlineResource(resource));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
acc.push(resource);
|
|
29
|
+
}
|
|
30
|
+
return acc;
|
|
31
|
+
}, [])
|
|
32
|
+
.filter((resource) => this.resourceInliner.shouldAddResource(resource));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
exports.ResourcesRegistry = ResourcesRegistry;
|