@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.
Files changed (58) hide show
  1. package/lib/browser.js +9 -233
  2. package/lib/client/index.browser.js +48 -0
  3. package/lib/client/renderer.browser.js +50 -0
  4. package/lib/react/index.browser.js +11 -0
  5. package/lib/react/index.es.js +11 -0
  6. package/lib/react/index.js +15 -0
  7. package/lib/react/pageErrorBoundary.browser.js +23 -0
  8. package/lib/react/pageErrorBoundary.es.js +23 -0
  9. package/lib/react/pageErrorBoundary.js +27 -0
  10. package/lib/react/root.browser.js +58 -0
  11. package/lib/react/root.es.js +58 -0
  12. package/lib/react/root.js +62 -0
  13. package/lib/resourcesInliner/externalFilesHelper.es.js +17 -0
  14. package/lib/resourcesInliner/externalFilesHelper.js +26 -0
  15. package/lib/resourcesInliner/fileProcessor.es.js +31 -0
  16. package/lib/resourcesInliner/fileProcessor.js +40 -0
  17. package/lib/resourcesInliner/resourcesInliner.es.js +204 -0
  18. package/lib/resourcesInliner/resourcesInliner.js +213 -0
  19. package/lib/resourcesInliner/tokens.es.js +15 -0
  20. package/lib/resourcesInliner/tokens.js +20 -0
  21. package/lib/resourcesRegistry/index.es.js +28 -0
  22. package/lib/resourcesRegistry/index.js +36 -0
  23. package/lib/server/PageBuilder.es.js +93 -0
  24. package/lib/server/PageBuilder.js +102 -0
  25. package/lib/server/ReactRenderServer.es.js +90 -0
  26. package/lib/server/ReactRenderServer.js +98 -0
  27. package/lib/server/blocks/bundleResource/bundleResource.es.js +62 -0
  28. package/lib/server/blocks/bundleResource/bundleResource.js +71 -0
  29. package/lib/server/blocks/polyfill.es.js +35 -0
  30. package/lib/server/blocks/polyfill.js +39 -0
  31. package/lib/{server_inline.inline.es.js → server/blocks/preload/onload.inline.es.js} +1 -1
  32. package/lib/{server_inline.inline.js → server/blocks/preload/onload.inline.js} +2 -0
  33. package/lib/server/blocks/preload/preloadBlock.es.js +21 -0
  34. package/lib/server/blocks/preload/preloadBlock.js +30 -0
  35. package/lib/server/blocks/utils/fetchWebpackStats.es.js +88 -0
  36. package/lib/server/blocks/utils/fetchWebpackStats.js +115 -0
  37. package/lib/server/blocks/utils/flushFiles.es.js +33 -0
  38. package/lib/server/blocks/utils/flushFiles.js +44 -0
  39. package/lib/server/blocks/utils/requireFunc.es.js +5 -0
  40. package/lib/server/blocks/utils/requireFunc.js +9 -0
  41. package/lib/server/constants/performance.es.js +3 -0
  42. package/lib/server/constants/performance.js +7 -0
  43. package/lib/server/htmlPageSchema.es.js +33 -0
  44. package/lib/server/htmlPageSchema.js +37 -0
  45. package/lib/server/utils.es.js +16 -0
  46. package/lib/server/utils.js +20 -0
  47. package/lib/server.es.js +18 -859
  48. package/lib/server.js +33 -909
  49. package/lib/shared/LayoutModule.browser.js +40 -0
  50. package/lib/shared/LayoutModule.es.js +40 -0
  51. package/lib/shared/LayoutModule.js +42 -0
  52. package/lib/shared/pageErrorStore.browser.js +19 -0
  53. package/lib/shared/pageErrorStore.es.js +19 -0
  54. package/lib/shared/pageErrorStore.js +26 -0
  55. package/lib/shared/providers.browser.js +18 -0
  56. package/lib/shared/providers.es.js +18 -0
  57. package/lib/shared/providers.js +22 -0
  58. 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;
@@ -17,4 +17,4 @@ function onload(next) {
17
17
  };
18
18
  }
19
19
 
20
- export { onload as o };
20
+ export { onload };
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
3
5
  function onload(next) {
4
6
  let called = false;
5
7
  return () => {
@@ -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;