@tramvai/module-render 2.70.1 → 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,93 @@
|
|
|
1
|
+
import flatten from '@tinkoff/utils/array/flatten';
|
|
2
|
+
import { buildPage } from '@tinkoff/htmlpagebuilder';
|
|
3
|
+
import { ResourceType, ResourceSlot } from '@tramvai/tokens-render';
|
|
4
|
+
import { safeStringify } from '@tramvai/safe-strings';
|
|
5
|
+
import { ChunkExtractor } from '@loadable/server';
|
|
6
|
+
import { bundleResource } from './blocks/bundleResource/bundleResource.es.js';
|
|
7
|
+
import { polyfillResources } from './blocks/polyfill.es.js';
|
|
8
|
+
import { addPreloadForCriticalJS } from './blocks/preload/preloadBlock.es.js';
|
|
9
|
+
import { formatAttributes } from './utils.es.js';
|
|
10
|
+
import { fetchWebpackStats } from './blocks/utils/fetchWebpackStats.es.js';
|
|
11
|
+
|
|
12
|
+
/* eslint-disable sort-class-members/sort-class-members */
|
|
13
|
+
const mapResourcesToSlots = (resources) => resources.reduce((acc, resource) => {
|
|
14
|
+
const { slot } = resource;
|
|
15
|
+
if (Array.isArray(acc[slot])) {
|
|
16
|
+
acc[slot].push(resource);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
acc[slot] = [resource];
|
|
20
|
+
}
|
|
21
|
+
return acc;
|
|
22
|
+
}, {});
|
|
23
|
+
class PageBuilder {
|
|
24
|
+
constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, }) {
|
|
25
|
+
this.htmlAttrs = htmlAttrs;
|
|
26
|
+
this.renderSlots = flatten(renderSlots || []);
|
|
27
|
+
this.pageService = pageService;
|
|
28
|
+
this.context = context;
|
|
29
|
+
this.resourcesRegistry = resourcesRegistry;
|
|
30
|
+
this.reactRender = reactRender;
|
|
31
|
+
this.htmlPageSchema = htmlPageSchema;
|
|
32
|
+
this.polyfillCondition = polyfillCondition;
|
|
33
|
+
this.modern = modern;
|
|
34
|
+
this.renderFlowAfter = renderFlowAfter || [];
|
|
35
|
+
this.log = logger('page-builder');
|
|
36
|
+
}
|
|
37
|
+
async flow() {
|
|
38
|
+
const stats = await fetchWebpackStats({ modern: this.modern });
|
|
39
|
+
const extractor = new ChunkExtractor({ stats, entrypoints: [] });
|
|
40
|
+
// first we render the application, because we need to extract information about the data used by the components
|
|
41
|
+
await this.renderApp(extractor);
|
|
42
|
+
await Promise.all(this.renderFlowAfter.map((callback) => callback().catch((error) => {
|
|
43
|
+
this.log.warn({ event: 'render-flow-after-error', callback, error });
|
|
44
|
+
})));
|
|
45
|
+
this.dehydrateState();
|
|
46
|
+
// load information and dependency for the current bundle and page
|
|
47
|
+
await this.fetchChunksInfo(extractor);
|
|
48
|
+
this.preloadBlock();
|
|
49
|
+
return this.generateHtml();
|
|
50
|
+
}
|
|
51
|
+
dehydrateState() {
|
|
52
|
+
this.resourcesRegistry.register({
|
|
53
|
+
type: ResourceType.asIs,
|
|
54
|
+
slot: ResourceSlot.BODY_END,
|
|
55
|
+
// String much better than big object, source https://v8.dev/blog/cost-of-javascript-2019#json
|
|
56
|
+
payload: `<script id="__TRAMVAI_STATE__" type="application/json">${safeStringify(this.context.dehydrate().dispatcher)}</script>`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async fetchChunksInfo(extractor) {
|
|
60
|
+
const { modern } = this;
|
|
61
|
+
const { bundle, pageComponent } = this.pageService.getConfig();
|
|
62
|
+
this.resourcesRegistry.register(await bundleResource({ bundle, modern, extractor, pageComponent }));
|
|
63
|
+
this.resourcesRegistry.register(await polyfillResources({
|
|
64
|
+
condition: this.polyfillCondition,
|
|
65
|
+
modern,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
preloadBlock() {
|
|
69
|
+
const preloadResources = addPreloadForCriticalJS(this.resourcesRegistry.getPageResources());
|
|
70
|
+
this.resourcesRegistry.register(preloadResources);
|
|
71
|
+
}
|
|
72
|
+
generateHtml() {
|
|
73
|
+
const resultSlotHandlers = mapResourcesToSlots([
|
|
74
|
+
...this.renderSlots,
|
|
75
|
+
...this.resourcesRegistry.getPageResources(),
|
|
76
|
+
]);
|
|
77
|
+
return buildPage({
|
|
78
|
+
slotHandlers: resultSlotHandlers,
|
|
79
|
+
description: this.htmlPageSchema,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async renderApp(extractor) {
|
|
83
|
+
const html = await this.reactRender.render(extractor);
|
|
84
|
+
this.renderSlots = this.renderSlots.concat({
|
|
85
|
+
type: ResourceType.asIs,
|
|
86
|
+
slot: ResourceSlot.REACT_RENDER,
|
|
87
|
+
payload: `<div ${formatAttributes(this.htmlAttrs, 'app')}>${html}</div>`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/* eslint-enable sort-class-members/sort-class-members */
|
|
92
|
+
|
|
93
|
+
export { PageBuilder, mapResourcesToSlots };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var flatten = require('@tinkoff/utils/array/flatten');
|
|
6
|
+
var htmlpagebuilder = require('@tinkoff/htmlpagebuilder');
|
|
7
|
+
var tokensRender = require('@tramvai/tokens-render');
|
|
8
|
+
var safeStrings = require('@tramvai/safe-strings');
|
|
9
|
+
var server = require('@loadable/server');
|
|
10
|
+
var bundleResource = require('./blocks/bundleResource/bundleResource.js');
|
|
11
|
+
var polyfill = require('./blocks/polyfill.js');
|
|
12
|
+
var preloadBlock = require('./blocks/preload/preloadBlock.js');
|
|
13
|
+
var utils = require('./utils.js');
|
|
14
|
+
var fetchWebpackStats = require('./blocks/utils/fetchWebpackStats.js');
|
|
15
|
+
|
|
16
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
17
|
+
|
|
18
|
+
var flatten__default = /*#__PURE__*/_interopDefaultLegacy(flatten);
|
|
19
|
+
|
|
20
|
+
/* eslint-disable sort-class-members/sort-class-members */
|
|
21
|
+
const mapResourcesToSlots = (resources) => resources.reduce((acc, resource) => {
|
|
22
|
+
const { slot } = resource;
|
|
23
|
+
if (Array.isArray(acc[slot])) {
|
|
24
|
+
acc[slot].push(resource);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
acc[slot] = [resource];
|
|
28
|
+
}
|
|
29
|
+
return acc;
|
|
30
|
+
}, {});
|
|
31
|
+
class PageBuilder {
|
|
32
|
+
constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, }) {
|
|
33
|
+
this.htmlAttrs = htmlAttrs;
|
|
34
|
+
this.renderSlots = flatten__default["default"](renderSlots || []);
|
|
35
|
+
this.pageService = pageService;
|
|
36
|
+
this.context = context;
|
|
37
|
+
this.resourcesRegistry = resourcesRegistry;
|
|
38
|
+
this.reactRender = reactRender;
|
|
39
|
+
this.htmlPageSchema = htmlPageSchema;
|
|
40
|
+
this.polyfillCondition = polyfillCondition;
|
|
41
|
+
this.modern = modern;
|
|
42
|
+
this.renderFlowAfter = renderFlowAfter || [];
|
|
43
|
+
this.log = logger('page-builder');
|
|
44
|
+
}
|
|
45
|
+
async flow() {
|
|
46
|
+
const stats = await fetchWebpackStats.fetchWebpackStats({ modern: this.modern });
|
|
47
|
+
const extractor = new server.ChunkExtractor({ stats, entrypoints: [] });
|
|
48
|
+
// first we render the application, because we need to extract information about the data used by the components
|
|
49
|
+
await this.renderApp(extractor);
|
|
50
|
+
await Promise.all(this.renderFlowAfter.map((callback) => callback().catch((error) => {
|
|
51
|
+
this.log.warn({ event: 'render-flow-after-error', callback, error });
|
|
52
|
+
})));
|
|
53
|
+
this.dehydrateState();
|
|
54
|
+
// load information and dependency for the current bundle and page
|
|
55
|
+
await this.fetchChunksInfo(extractor);
|
|
56
|
+
this.preloadBlock();
|
|
57
|
+
return this.generateHtml();
|
|
58
|
+
}
|
|
59
|
+
dehydrateState() {
|
|
60
|
+
this.resourcesRegistry.register({
|
|
61
|
+
type: tokensRender.ResourceType.asIs,
|
|
62
|
+
slot: tokensRender.ResourceSlot.BODY_END,
|
|
63
|
+
// String much better than big object, source https://v8.dev/blog/cost-of-javascript-2019#json
|
|
64
|
+
payload: `<script id="__TRAMVAI_STATE__" type="application/json">${safeStrings.safeStringify(this.context.dehydrate().dispatcher)}</script>`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async fetchChunksInfo(extractor) {
|
|
68
|
+
const { modern } = this;
|
|
69
|
+
const { bundle, pageComponent } = this.pageService.getConfig();
|
|
70
|
+
this.resourcesRegistry.register(await bundleResource.bundleResource({ bundle, modern, extractor, pageComponent }));
|
|
71
|
+
this.resourcesRegistry.register(await polyfill.polyfillResources({
|
|
72
|
+
condition: this.polyfillCondition,
|
|
73
|
+
modern,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
preloadBlock() {
|
|
77
|
+
const preloadResources = preloadBlock.addPreloadForCriticalJS(this.resourcesRegistry.getPageResources());
|
|
78
|
+
this.resourcesRegistry.register(preloadResources);
|
|
79
|
+
}
|
|
80
|
+
generateHtml() {
|
|
81
|
+
const resultSlotHandlers = mapResourcesToSlots([
|
|
82
|
+
...this.renderSlots,
|
|
83
|
+
...this.resourcesRegistry.getPageResources(),
|
|
84
|
+
]);
|
|
85
|
+
return htmlpagebuilder.buildPage({
|
|
86
|
+
slotHandlers: resultSlotHandlers,
|
|
87
|
+
description: this.htmlPageSchema,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async renderApp(extractor) {
|
|
91
|
+
const html = await this.reactRender.render(extractor);
|
|
92
|
+
this.renderSlots = this.renderSlots.concat({
|
|
93
|
+
type: tokensRender.ResourceType.asIs,
|
|
94
|
+
slot: tokensRender.ResourceSlot.REACT_RENDER,
|
|
95
|
+
payload: `<div ${utils.formatAttributes(this.htmlAttrs, 'app')}>${html}</div>`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/* eslint-enable sort-class-members/sort-class-members */
|
|
100
|
+
|
|
101
|
+
exports.PageBuilder = PageBuilder;
|
|
102
|
+
exports.mapResourcesToSlots = mapResourcesToSlots;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Writable } from 'stream';
|
|
2
|
+
import each from '@tinkoff/utils/array/each';
|
|
3
|
+
import { renderReact } from '../react/index.es.js';
|
|
4
|
+
|
|
5
|
+
const RENDER_TIMEOUT = 500;
|
|
6
|
+
class HtmlWritable extends Writable {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this.chunks = [];
|
|
10
|
+
this.html = '';
|
|
11
|
+
}
|
|
12
|
+
getHtml() {
|
|
13
|
+
return this.html;
|
|
14
|
+
}
|
|
15
|
+
_write(chunk, encoding, callback) {
|
|
16
|
+
this.chunks.push(chunk);
|
|
17
|
+
callback();
|
|
18
|
+
}
|
|
19
|
+
_final(callback) {
|
|
20
|
+
this.html = Buffer.concat(this.chunks).toString();
|
|
21
|
+
callback();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
class ReactRenderServer {
|
|
25
|
+
// eslint-disable-next-line sort-class-members/sort-class-members
|
|
26
|
+
constructor({ context, customRender, extendRender, di, renderMode, logger }) {
|
|
27
|
+
this.context = context;
|
|
28
|
+
this.customRender = customRender;
|
|
29
|
+
this.extendRender = extendRender;
|
|
30
|
+
this.di = di;
|
|
31
|
+
this.renderMode = renderMode;
|
|
32
|
+
this.log = logger('module-render');
|
|
33
|
+
}
|
|
34
|
+
render(extractor) {
|
|
35
|
+
var _a;
|
|
36
|
+
let renderResult = renderReact({ di: this.di }, this.context);
|
|
37
|
+
each((render) => {
|
|
38
|
+
renderResult = render(renderResult);
|
|
39
|
+
}, (_a = this.extendRender) !== null && _a !== void 0 ? _a : []);
|
|
40
|
+
renderResult = extractor.collectChunks(renderResult);
|
|
41
|
+
if (this.customRender) {
|
|
42
|
+
return this.customRender(renderResult);
|
|
43
|
+
}
|
|
44
|
+
if (process.env.__TRAMVAI_CONCURRENT_FEATURES && this.renderMode === 'streaming') {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const { renderToPipeableStream } = require('react-dom/server');
|
|
47
|
+
const htmlWritable = new HtmlWritable();
|
|
48
|
+
htmlWritable.on('finish', () => {
|
|
49
|
+
resolve(htmlWritable.getHtml());
|
|
50
|
+
});
|
|
51
|
+
const start = Date.now();
|
|
52
|
+
const { log } = this;
|
|
53
|
+
log.info({
|
|
54
|
+
event: 'streaming-render:start',
|
|
55
|
+
});
|
|
56
|
+
const { pipe, abort } = renderToPipeableStream(renderResult, {
|
|
57
|
+
onAllReady() {
|
|
58
|
+
log.info({
|
|
59
|
+
event: 'streaming-render:complete',
|
|
60
|
+
duration: Date.now() - start,
|
|
61
|
+
});
|
|
62
|
+
// here `write` will be called only once
|
|
63
|
+
pipe(htmlWritable);
|
|
64
|
+
},
|
|
65
|
+
onError(error) {
|
|
66
|
+
// error can be inside Suspense boundaries, this is not critical, continue rendering.
|
|
67
|
+
// for criticall errors, this callback will be called with `onShellError`,
|
|
68
|
+
// so this is a best place to error logging
|
|
69
|
+
log.error({
|
|
70
|
+
event: 'streaming-render:error',
|
|
71
|
+
error,
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
onShellError(error) {
|
|
75
|
+
// always critical error, abort rendering
|
|
76
|
+
reject(error);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
abort();
|
|
81
|
+
reject(new Error('React renderToPipeableStream timeout exceeded'));
|
|
82
|
+
}, RENDER_TIMEOUT);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const { renderToString } = require('react-dom/server');
|
|
86
|
+
return Promise.resolve(renderToString(renderResult));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { ReactRenderServer };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var stream = require('stream');
|
|
6
|
+
var each = require('@tinkoff/utils/array/each');
|
|
7
|
+
var index = require('../react/index.js');
|
|
8
|
+
|
|
9
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
10
|
+
|
|
11
|
+
var each__default = /*#__PURE__*/_interopDefaultLegacy(each);
|
|
12
|
+
|
|
13
|
+
const RENDER_TIMEOUT = 500;
|
|
14
|
+
class HtmlWritable extends stream.Writable {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this.chunks = [];
|
|
18
|
+
this.html = '';
|
|
19
|
+
}
|
|
20
|
+
getHtml() {
|
|
21
|
+
return this.html;
|
|
22
|
+
}
|
|
23
|
+
_write(chunk, encoding, callback) {
|
|
24
|
+
this.chunks.push(chunk);
|
|
25
|
+
callback();
|
|
26
|
+
}
|
|
27
|
+
_final(callback) {
|
|
28
|
+
this.html = Buffer.concat(this.chunks).toString();
|
|
29
|
+
callback();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
class ReactRenderServer {
|
|
33
|
+
// eslint-disable-next-line sort-class-members/sort-class-members
|
|
34
|
+
constructor({ context, customRender, extendRender, di, renderMode, logger }) {
|
|
35
|
+
this.context = context;
|
|
36
|
+
this.customRender = customRender;
|
|
37
|
+
this.extendRender = extendRender;
|
|
38
|
+
this.di = di;
|
|
39
|
+
this.renderMode = renderMode;
|
|
40
|
+
this.log = logger('module-render');
|
|
41
|
+
}
|
|
42
|
+
render(extractor) {
|
|
43
|
+
var _a;
|
|
44
|
+
let renderResult = index.renderReact({ di: this.di }, this.context);
|
|
45
|
+
each__default["default"]((render) => {
|
|
46
|
+
renderResult = render(renderResult);
|
|
47
|
+
}, (_a = this.extendRender) !== null && _a !== void 0 ? _a : []);
|
|
48
|
+
renderResult = extractor.collectChunks(renderResult);
|
|
49
|
+
if (this.customRender) {
|
|
50
|
+
return this.customRender(renderResult);
|
|
51
|
+
}
|
|
52
|
+
if (process.env.__TRAMVAI_CONCURRENT_FEATURES && this.renderMode === 'streaming') {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const { renderToPipeableStream } = require('react-dom/server');
|
|
55
|
+
const htmlWritable = new HtmlWritable();
|
|
56
|
+
htmlWritable.on('finish', () => {
|
|
57
|
+
resolve(htmlWritable.getHtml());
|
|
58
|
+
});
|
|
59
|
+
const start = Date.now();
|
|
60
|
+
const { log } = this;
|
|
61
|
+
log.info({
|
|
62
|
+
event: 'streaming-render:start',
|
|
63
|
+
});
|
|
64
|
+
const { pipe, abort } = renderToPipeableStream(renderResult, {
|
|
65
|
+
onAllReady() {
|
|
66
|
+
log.info({
|
|
67
|
+
event: 'streaming-render:complete',
|
|
68
|
+
duration: Date.now() - start,
|
|
69
|
+
});
|
|
70
|
+
// here `write` will be called only once
|
|
71
|
+
pipe(htmlWritable);
|
|
72
|
+
},
|
|
73
|
+
onError(error) {
|
|
74
|
+
// error can be inside Suspense boundaries, this is not critical, continue rendering.
|
|
75
|
+
// for criticall errors, this callback will be called with `onShellError`,
|
|
76
|
+
// so this is a best place to error logging
|
|
77
|
+
log.error({
|
|
78
|
+
event: 'streaming-render:error',
|
|
79
|
+
error,
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
onShellError(error) {
|
|
83
|
+
// always critical error, abort rendering
|
|
84
|
+
reject(error);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
abort();
|
|
89
|
+
reject(new Error('React renderToPipeableStream timeout exceeded'));
|
|
90
|
+
}, RENDER_TIMEOUT);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const { renderToString } = require('react-dom/server');
|
|
94
|
+
return Promise.resolve(renderToString(renderResult));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
exports.ReactRenderServer = ReactRenderServer;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import has from '@tinkoff/utils/object/has';
|
|
2
|
+
import last from '@tinkoff/utils/array/last';
|
|
3
|
+
import { ResourceType, ResourceSlot } from '@tramvai/tokens-render';
|
|
4
|
+
import { isFileSystemPageComponent, fileSystemPageToWebpackChunkName } from '@tramvai/experiments';
|
|
5
|
+
import { PRELOAD_JS } from '../../constants/performance.es.js';
|
|
6
|
+
import { flushFiles } from '../utils/flushFiles.es.js';
|
|
7
|
+
import { fetchWebpackStats } from '../utils/fetchWebpackStats.es.js';
|
|
8
|
+
|
|
9
|
+
const bundleResource = async ({ bundle, modern, extractor, pageComponent, }) => {
|
|
10
|
+
// for file-system pages preload page chunk against bundle chunk
|
|
11
|
+
const chunkNameFromBundle = isFileSystemPageComponent(pageComponent)
|
|
12
|
+
? fileSystemPageToWebpackChunkName(pageComponent)
|
|
13
|
+
: last(bundle.split('/'));
|
|
14
|
+
const webpackStats = await fetchWebpackStats({ modern });
|
|
15
|
+
const { publicPath, assetsByChunkName } = webpackStats;
|
|
16
|
+
const bundles = has('common-chunk', assetsByChunkName)
|
|
17
|
+
? ['common-chunk', chunkNameFromBundle]
|
|
18
|
+
: [chunkNameFromBundle];
|
|
19
|
+
const lazyChunks = extractor.getMainAssets().map((entry) => entry.chunk);
|
|
20
|
+
const { scripts: baseScripts } = flushFiles(['vendor'], webpackStats, {
|
|
21
|
+
ignoreDependencies: true,
|
|
22
|
+
});
|
|
23
|
+
const { scripts, styles } = flushFiles([...bundles, ...lazyChunks, 'platform'], webpackStats);
|
|
24
|
+
const genHref = (href) => `${publicPath}${href}`;
|
|
25
|
+
const result = [];
|
|
26
|
+
if (process.env.NODE_ENV === 'production' ||
|
|
27
|
+
(process.env.ASSETS_PREFIX && process.env.ASSETS_PREFIX !== 'static')) {
|
|
28
|
+
result.push({
|
|
29
|
+
type: ResourceType.inlineScript,
|
|
30
|
+
slot: ResourceSlot.HEAD_CORE_SCRIPTS,
|
|
31
|
+
payload: `window.ap = ${`"${process.env.ASSETS_PREFIX}"`};`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
styles.map((style) => result.push({
|
|
35
|
+
type: ResourceType.style,
|
|
36
|
+
slot: ResourceSlot.HEAD_CORE_STYLES,
|
|
37
|
+
payload: genHref(style),
|
|
38
|
+
attrs: {
|
|
39
|
+
'data-critical': 'true',
|
|
40
|
+
onload: `${PRELOAD_JS}()`,
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
baseScripts.map((script) => result.push({
|
|
44
|
+
type: ResourceType.script,
|
|
45
|
+
slot: ResourceSlot.HEAD_CORE_SCRIPTS,
|
|
46
|
+
payload: genHref(script),
|
|
47
|
+
attrs: {
|
|
48
|
+
'data-critical': 'true',
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
51
|
+
scripts.map((script) => result.push({
|
|
52
|
+
type: ResourceType.script,
|
|
53
|
+
slot: ResourceSlot.HEAD_CORE_SCRIPTS,
|
|
54
|
+
payload: genHref(script),
|
|
55
|
+
attrs: {
|
|
56
|
+
'data-critical': 'true',
|
|
57
|
+
},
|
|
58
|
+
}));
|
|
59
|
+
return result;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export { bundleResource };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var has = require('@tinkoff/utils/object/has');
|
|
6
|
+
var last = require('@tinkoff/utils/array/last');
|
|
7
|
+
var tokensRender = require('@tramvai/tokens-render');
|
|
8
|
+
var experiments = require('@tramvai/experiments');
|
|
9
|
+
var performance = require('../../constants/performance.js');
|
|
10
|
+
var flushFiles = require('../utils/flushFiles.js');
|
|
11
|
+
var fetchWebpackStats = require('../utils/fetchWebpackStats.js');
|
|
12
|
+
|
|
13
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
14
|
+
|
|
15
|
+
var has__default = /*#__PURE__*/_interopDefaultLegacy(has);
|
|
16
|
+
var last__default = /*#__PURE__*/_interopDefaultLegacy(last);
|
|
17
|
+
|
|
18
|
+
const bundleResource = async ({ bundle, modern, extractor, pageComponent, }) => {
|
|
19
|
+
// for file-system pages preload page chunk against bundle chunk
|
|
20
|
+
const chunkNameFromBundle = experiments.isFileSystemPageComponent(pageComponent)
|
|
21
|
+
? experiments.fileSystemPageToWebpackChunkName(pageComponent)
|
|
22
|
+
: last__default["default"](bundle.split('/'));
|
|
23
|
+
const webpackStats = await fetchWebpackStats.fetchWebpackStats({ modern });
|
|
24
|
+
const { publicPath, assetsByChunkName } = webpackStats;
|
|
25
|
+
const bundles = has__default["default"]('common-chunk', assetsByChunkName)
|
|
26
|
+
? ['common-chunk', chunkNameFromBundle]
|
|
27
|
+
: [chunkNameFromBundle];
|
|
28
|
+
const lazyChunks = extractor.getMainAssets().map((entry) => entry.chunk);
|
|
29
|
+
const { scripts: baseScripts } = flushFiles.flushFiles(['vendor'], webpackStats, {
|
|
30
|
+
ignoreDependencies: true,
|
|
31
|
+
});
|
|
32
|
+
const { scripts, styles } = flushFiles.flushFiles([...bundles, ...lazyChunks, 'platform'], webpackStats);
|
|
33
|
+
const genHref = (href) => `${publicPath}${href}`;
|
|
34
|
+
const result = [];
|
|
35
|
+
if (process.env.NODE_ENV === 'production' ||
|
|
36
|
+
(process.env.ASSETS_PREFIX && process.env.ASSETS_PREFIX !== 'static')) {
|
|
37
|
+
result.push({
|
|
38
|
+
type: tokensRender.ResourceType.inlineScript,
|
|
39
|
+
slot: tokensRender.ResourceSlot.HEAD_CORE_SCRIPTS,
|
|
40
|
+
payload: `window.ap = ${`"${process.env.ASSETS_PREFIX}"`};`,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
styles.map((style) => result.push({
|
|
44
|
+
type: tokensRender.ResourceType.style,
|
|
45
|
+
slot: tokensRender.ResourceSlot.HEAD_CORE_STYLES,
|
|
46
|
+
payload: genHref(style),
|
|
47
|
+
attrs: {
|
|
48
|
+
'data-critical': 'true',
|
|
49
|
+
onload: `${performance.PRELOAD_JS}()`,
|
|
50
|
+
},
|
|
51
|
+
}));
|
|
52
|
+
baseScripts.map((script) => result.push({
|
|
53
|
+
type: tokensRender.ResourceType.script,
|
|
54
|
+
slot: tokensRender.ResourceSlot.HEAD_CORE_SCRIPTS,
|
|
55
|
+
payload: genHref(script),
|
|
56
|
+
attrs: {
|
|
57
|
+
'data-critical': 'true',
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
scripts.map((script) => result.push({
|
|
61
|
+
type: tokensRender.ResourceType.script,
|
|
62
|
+
slot: tokensRender.ResourceSlot.HEAD_CORE_SCRIPTS,
|
|
63
|
+
payload: genHref(script),
|
|
64
|
+
attrs: {
|
|
65
|
+
'data-critical': 'true',
|
|
66
|
+
},
|
|
67
|
+
}));
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
exports.bundleResource = bundleResource;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ResourceType, ResourceSlot } from '@tramvai/tokens-render';
|
|
2
|
+
import { fetchWebpackStats } from './utils/fetchWebpackStats.es.js';
|
|
3
|
+
import { flushFiles } from './utils/flushFiles.es.js';
|
|
4
|
+
|
|
5
|
+
const polyfillResources = async ({ condition, modern, }) => {
|
|
6
|
+
const webpackStats = await fetchWebpackStats({ modern });
|
|
7
|
+
const { publicPath } = webpackStats;
|
|
8
|
+
// получает файл полифилла из stats.json\stats.modern.json.
|
|
9
|
+
// В зависимости от версии браузера будет использован полифилл из legacy или modern сборки,
|
|
10
|
+
// т.к. полифиллы для них могут отличаться на основании преобразований `@babel/preset-env`
|
|
11
|
+
const { scripts: polyfillScripts } = flushFiles(['polyfill'], webpackStats, {
|
|
12
|
+
ignoreDependencies: true,
|
|
13
|
+
});
|
|
14
|
+
const genHref = (href) => `${publicPath}${href}`;
|
|
15
|
+
const result = [];
|
|
16
|
+
polyfillScripts.forEach((script) => {
|
|
17
|
+
const href = genHref(script);
|
|
18
|
+
result.push({
|
|
19
|
+
type: ResourceType.inlineScript,
|
|
20
|
+
slot: ResourceSlot.HEAD_POLYFILLS,
|
|
21
|
+
payload: `(function (){
|
|
22
|
+
var con;
|
|
23
|
+
try {
|
|
24
|
+
con = ${condition};
|
|
25
|
+
} catch (e) {
|
|
26
|
+
con = true;
|
|
27
|
+
}
|
|
28
|
+
if (con) { document.write('<script defer="defer" charset="utf-8" data-critical="true" crossorigin="anonymous" src="${href}"><\\/script>')}
|
|
29
|
+
})()`,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { polyfillResources };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var tokensRender = require('@tramvai/tokens-render');
|
|
6
|
+
var fetchWebpackStats = require('./utils/fetchWebpackStats.js');
|
|
7
|
+
var flushFiles = require('./utils/flushFiles.js');
|
|
8
|
+
|
|
9
|
+
const polyfillResources = async ({ condition, modern, }) => {
|
|
10
|
+
const webpackStats = await fetchWebpackStats.fetchWebpackStats({ modern });
|
|
11
|
+
const { publicPath } = webpackStats;
|
|
12
|
+
// получает файл полифилла из stats.json\stats.modern.json.
|
|
13
|
+
// В зависимости от версии браузера будет использован полифилл из legacy или modern сборки,
|
|
14
|
+
// т.к. полифиллы для них могут отличаться на основании преобразований `@babel/preset-env`
|
|
15
|
+
const { scripts: polyfillScripts } = flushFiles.flushFiles(['polyfill'], webpackStats, {
|
|
16
|
+
ignoreDependencies: true,
|
|
17
|
+
});
|
|
18
|
+
const genHref = (href) => `${publicPath}${href}`;
|
|
19
|
+
const result = [];
|
|
20
|
+
polyfillScripts.forEach((script) => {
|
|
21
|
+
const href = genHref(script);
|
|
22
|
+
result.push({
|
|
23
|
+
type: tokensRender.ResourceType.inlineScript,
|
|
24
|
+
slot: tokensRender.ResourceSlot.HEAD_POLYFILLS,
|
|
25
|
+
payload: `(function (){
|
|
26
|
+
var con;
|
|
27
|
+
try {
|
|
28
|
+
con = ${condition};
|
|
29
|
+
} catch (e) {
|
|
30
|
+
con = true;
|
|
31
|
+
}
|
|
32
|
+
if (con) { document.write('<script defer="defer" charset="utf-8" data-critical="true" crossorigin="anonymous" src="${href}"><\\/script>')}
|
|
33
|
+
})()`,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
exports.polyfillResources = polyfillResources;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import each from '@tinkoff/utils/array/each';
|
|
2
|
+
import path from '@tinkoff/utils/object/path';
|
|
3
|
+
import { ResourceType, ResourceSlot } from '@tramvai/tokens-render';
|
|
4
|
+
import { PRELOAD_JS } from '../../constants/performance.es.js';
|
|
5
|
+
import { onload } from './onload.inline.es.js';
|
|
6
|
+
|
|
7
|
+
const addPreloadForCriticalJS = (pageResources) => {
|
|
8
|
+
const jsUrls = [];
|
|
9
|
+
each((res) => {
|
|
10
|
+
if (res.type === 'script' && path(['attrs', 'data-critical'], res)) {
|
|
11
|
+
jsUrls.push(res.payload);
|
|
12
|
+
}
|
|
13
|
+
}, pageResources);
|
|
14
|
+
return {
|
|
15
|
+
type: ResourceType.inlineScript,
|
|
16
|
+
slot: ResourceSlot.HEAD_PERFORMANCE,
|
|
17
|
+
payload: `window.${PRELOAD_JS}=(${onload})([${jsUrls.map((url) => `"${url}"`).join(',')}])`,
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export { addPreloadForCriticalJS };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var each = require('@tinkoff/utils/array/each');
|
|
6
|
+
var path = require('@tinkoff/utils/object/path');
|
|
7
|
+
var tokensRender = require('@tramvai/tokens-render');
|
|
8
|
+
var performance = require('../../constants/performance.js');
|
|
9
|
+
var onload_inline = require('./onload.inline.js');
|
|
10
|
+
|
|
11
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
12
|
+
|
|
13
|
+
var each__default = /*#__PURE__*/_interopDefaultLegacy(each);
|
|
14
|
+
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
15
|
+
|
|
16
|
+
const addPreloadForCriticalJS = (pageResources) => {
|
|
17
|
+
const jsUrls = [];
|
|
18
|
+
each__default["default"]((res) => {
|
|
19
|
+
if (res.type === 'script' && path__default["default"](['attrs', 'data-critical'], res)) {
|
|
20
|
+
jsUrls.push(res.payload);
|
|
21
|
+
}
|
|
22
|
+
}, pageResources);
|
|
23
|
+
return {
|
|
24
|
+
type: tokensRender.ResourceType.inlineScript,
|
|
25
|
+
slot: tokensRender.ResourceSlot.HEAD_PERFORMANCE,
|
|
26
|
+
payload: `window.${performance.PRELOAD_JS}=(${onload_inline.onload})([${jsUrls.map((url) => `"${url}"`).join(',')}])`,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
exports.addPreloadForCriticalJS = addPreloadForCriticalJS;
|