@tramvai/module-render 2.33.3 → 2.35.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.
@@ -1,7 +1,7 @@
1
1
  import type { ExtractDependencyType } from '@tinkoff/dippy';
2
2
  import type { DI_TOKEN } from '@tramvai/core';
3
- import type { CONTEXT_TOKEN } from '@tramvai/module-common';
4
- import type { EXTEND_RENDER, CUSTOM_RENDER } from '@tramvai/tokens-render';
3
+ import type { CONTEXT_TOKEN, LOGGER_TOKEN } from '@tramvai/module-common';
4
+ import type { EXTEND_RENDER, CUSTOM_RENDER, REACT_SERVER_RENDER_MODE } from '@tramvai/tokens-render';
5
5
  import type { PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';
6
6
  import type { ChunkExtractor } from '@loadable/server';
7
7
  export declare class ReactRenderServer {
@@ -10,12 +10,16 @@ export declare class ReactRenderServer {
10
10
  context: typeof CONTEXT_TOKEN;
11
11
  pageService: typeof PAGE_SERVICE_TOKEN;
12
12
  di: typeof DI_TOKEN;
13
- constructor({ pageService, context, customRender, extendRender, di }: {
13
+ log: ReturnType<typeof LOGGER_TOKEN>;
14
+ renderMode: typeof REACT_SERVER_RENDER_MODE;
15
+ constructor({ pageService, context, customRender, extendRender, di, renderMode, logger }: {
14
16
  pageService: any;
15
17
  context: any;
16
18
  customRender: any;
17
19
  extendRender: any;
18
20
  di: any;
21
+ renderMode: any;
22
+ logger: any;
19
23
  });
20
24
  render(extractor: ChunkExtractor): Promise<string>;
21
25
  }
package/lib/server.es.js CHANGED
@@ -5,7 +5,7 @@ import { Module, provide, commandLineListTokens, DI_TOKEN } from '@tramvai/core'
5
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
- 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, MODERN_SATISFIES_TOKEN, RENDER_FLOW_AFTER_TOKEN, CUSTOM_RENDER, EXTEND_RENDER } from '@tramvai/tokens-render';
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, MODERN_SATISFIES_TOKEN, RENDER_FLOW_AFTER_TOKEN, CUSTOM_RENDER, EXTEND_RENDER, REACT_SERVER_RENDER_MODE } from '@tramvai/tokens-render';
9
9
  export * from '@tramvai/tokens-render';
10
10
  import { createToken, Scope } from '@tinkoff/dippy';
11
11
  import { WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN } from '@tramvai/tokens-server-private';
@@ -29,6 +29,7 @@ import * as path from 'path';
29
29
  import each from '@tinkoff/utils/array/each';
30
30
  import path$1 from '@tinkoff/utils/object/path';
31
31
  import { o as onload } from './server_inline.inline.es.js';
32
+ import { Writable } from 'stream';
32
33
  import { jsx } from 'react/jsx-runtime';
33
34
  import { createEvent, createReducer, useStore, Provider } from '@tramvai/state';
34
35
  import { useRoute, useUrl } from '@tramvai/module-router';
@@ -702,13 +703,35 @@ function renderReact({ pageService, di }, context) {
702
703
  return (jsx(Provider, { context: context, serverState: serverState, children: jsx(DIContext.Provider, { value: di, children: jsx(PageErrorBoundary, { pageService: pageService, children: jsx(Root, { pageService: pageService }) }) }) }));
703
704
  }
704
705
 
706
+ const RENDER_TIMEOUT = 500;
707
+ class HtmlWritable extends Writable {
708
+ constructor() {
709
+ super(...arguments);
710
+ this.chunks = [];
711
+ this.html = '';
712
+ }
713
+ getHtml() {
714
+ return this.html;
715
+ }
716
+ _write(chunk, encoding, callback) {
717
+ this.chunks.push(chunk);
718
+ callback();
719
+ }
720
+ _final(callback) {
721
+ this.html = Buffer.concat(this.chunks).toString();
722
+ callback();
723
+ }
724
+ }
705
725
  class ReactRenderServer {
706
- constructor({ pageService, context, customRender, extendRender, di }) {
726
+ // eslint-disable-next-line sort-class-members/sort-class-members
727
+ constructor({ pageService, context, customRender, extendRender, di, renderMode, logger }) {
707
728
  this.pageService = pageService;
708
729
  this.context = context;
709
730
  this.customRender = customRender;
710
731
  this.extendRender = extendRender;
711
732
  this.di = di;
733
+ this.renderMode = renderMode;
734
+ this.log = logger('module-render');
712
735
  }
713
736
  render(extractor) {
714
737
  var _a;
@@ -720,6 +743,46 @@ class ReactRenderServer {
720
743
  if (this.customRender) {
721
744
  return this.customRender(renderResult);
722
745
  }
746
+ if (process.env.__TRAMVAI_CONCURRENT_FEATURES && this.renderMode === 'streaming') {
747
+ return new Promise((resolve, reject) => {
748
+ const { renderToPipeableStream } = require('react-dom/server');
749
+ const htmlWritable = new HtmlWritable();
750
+ htmlWritable.on('finish', () => {
751
+ resolve(htmlWritable.getHtml());
752
+ });
753
+ const start = Date.now();
754
+ const { log } = this;
755
+ log.info({
756
+ event: 'streaming-render:start',
757
+ });
758
+ const { pipe, abort } = renderToPipeableStream(renderResult, {
759
+ onAllReady() {
760
+ log.info({
761
+ event: 'streaming-render:complete',
762
+ duration: Date.now() - start,
763
+ });
764
+ // here `write` will be called only once
765
+ pipe(htmlWritable);
766
+ },
767
+ onError(error) {
768
+ // error can be inside Suspense boundaries, this is not critical, continue rendering
769
+ log.error({
770
+ event: 'streaming-render:error',
771
+ error,
772
+ });
773
+ },
774
+ onShellError(error) {
775
+ // always critical error, abort rendering
776
+ reject(error);
777
+ },
778
+ });
779
+ setTimeout(() => {
780
+ abort();
781
+ reject(new Error('React renderToPipeableStream timeout exceeded'));
782
+ }, RENDER_TIMEOUT);
783
+ });
784
+ }
785
+ const { renderToString } = require('react-dom/server');
723
786
  return Promise.resolve(renderToString(renderResult));
724
787
  }
725
788
  }
@@ -906,6 +969,8 @@ RenderModule = RenderModule_1 = __decorate([
906
969
  customRender: { token: CUSTOM_RENDER, optional: true },
907
970
  extendRender: { token: EXTEND_RENDER, optional: true },
908
971
  di: DI_TOKEN,
972
+ renderMode: { token: REACT_SERVER_RENDER_MODE, optional: true },
973
+ logger: LOGGER_TOKEN,
909
974
  },
910
975
  }),
911
976
  provide({
package/lib/server.js CHANGED
@@ -32,6 +32,7 @@ var path = require('path');
32
32
  var each = require('@tinkoff/utils/array/each');
33
33
  var path$1 = require('@tinkoff/utils/object/path');
34
34
  var inline_inline = require('./server_inline.inline.js');
35
+ var stream = require('stream');
35
36
  var jsxRuntime = require('react/jsx-runtime');
36
37
  var state = require('@tramvai/state');
37
38
  var moduleRouter = require('@tramvai/module-router');
@@ -738,13 +739,35 @@ function renderReact({ pageService, di }, context) {
738
739
  return (jsxRuntime.jsx(state.Provider, { context: context, serverState: serverState, children: jsxRuntime.jsx(react$1.DIContext.Provider, { value: di, children: jsxRuntime.jsx(PageErrorBoundary, { pageService: pageService, children: jsxRuntime.jsx(Root, { pageService: pageService }) }) }) }));
739
740
  }
740
741
 
742
+ const RENDER_TIMEOUT = 500;
743
+ class HtmlWritable extends stream.Writable {
744
+ constructor() {
745
+ super(...arguments);
746
+ this.chunks = [];
747
+ this.html = '';
748
+ }
749
+ getHtml() {
750
+ return this.html;
751
+ }
752
+ _write(chunk, encoding, callback) {
753
+ this.chunks.push(chunk);
754
+ callback();
755
+ }
756
+ _final(callback) {
757
+ this.html = Buffer.concat(this.chunks).toString();
758
+ callback();
759
+ }
760
+ }
741
761
  class ReactRenderServer {
742
- constructor({ pageService, context, customRender, extendRender, di }) {
762
+ // eslint-disable-next-line sort-class-members/sort-class-members
763
+ constructor({ pageService, context, customRender, extendRender, di, renderMode, logger }) {
743
764
  this.pageService = pageService;
744
765
  this.context = context;
745
766
  this.customRender = customRender;
746
767
  this.extendRender = extendRender;
747
768
  this.di = di;
769
+ this.renderMode = renderMode;
770
+ this.log = logger('module-render');
748
771
  }
749
772
  render(extractor) {
750
773
  var _a;
@@ -756,7 +779,47 @@ class ReactRenderServer {
756
779
  if (this.customRender) {
757
780
  return this.customRender(renderResult);
758
781
  }
759
- return Promise.resolve(server$1.renderToString(renderResult));
782
+ if (process.env.__TRAMVAI_CONCURRENT_FEATURES && this.renderMode === 'streaming') {
783
+ return new Promise((resolve, reject) => {
784
+ const { renderToPipeableStream } = require('react-dom/server');
785
+ const htmlWritable = new HtmlWritable();
786
+ htmlWritable.on('finish', () => {
787
+ resolve(htmlWritable.getHtml());
788
+ });
789
+ const start = Date.now();
790
+ const { log } = this;
791
+ log.info({
792
+ event: 'streaming-render:start',
793
+ });
794
+ const { pipe, abort } = renderToPipeableStream(renderResult, {
795
+ onAllReady() {
796
+ log.info({
797
+ event: 'streaming-render:complete',
798
+ duration: Date.now() - start,
799
+ });
800
+ // here `write` will be called only once
801
+ pipe(htmlWritable);
802
+ },
803
+ onError(error) {
804
+ // error can be inside Suspense boundaries, this is not critical, continue rendering
805
+ log.error({
806
+ event: 'streaming-render:error',
807
+ error,
808
+ });
809
+ },
810
+ onShellError(error) {
811
+ // always critical error, abort rendering
812
+ reject(error);
813
+ },
814
+ });
815
+ setTimeout(() => {
816
+ abort();
817
+ reject(new Error('React renderToPipeableStream timeout exceeded'));
818
+ }, RENDER_TIMEOUT);
819
+ });
820
+ }
821
+ const { renderToString } = require('react-dom/server');
822
+ return Promise.resolve(renderToString(renderResult));
760
823
  }
761
824
  }
762
825
 
@@ -942,6 +1005,8 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
942
1005
  customRender: { token: tokensRender.CUSTOM_RENDER, optional: true },
943
1006
  extendRender: { token: tokensRender.EXTEND_RENDER, optional: true },
944
1007
  di: core.DI_TOKEN,
1008
+ renderMode: { token: tokensRender.REACT_SERVER_RENDER_MODE, optional: true },
1009
+ logger: tokensCommon.LOGGER_TOKEN,
945
1010
  },
946
1011
  }),
947
1012
  core.provide({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tramvai/module-render",
3
- "version": "2.33.3",
3
+ "version": "2.35.0",
4
4
  "description": "",
5
5
  "browser": "lib/browser.js",
6
6
  "main": "lib/server.js",
@@ -26,13 +26,13 @@
26
26
  "@tinkoff/htmlpagebuilder": "0.5.5",
27
27
  "@tinkoff/layout-factory": "0.3.4",
28
28
  "@tinkoff/url": "0.8.4",
29
- "@tinkoff/user-agent": "0.4.76",
30
- "@tramvai/module-client-hints": "2.33.3",
31
- "@tramvai/module-router": "2.33.3",
32
- "@tramvai/react": "2.33.3",
29
+ "@tinkoff/user-agent": "0.4.78",
30
+ "@tramvai/module-client-hints": "2.35.0",
31
+ "@tramvai/module-router": "2.35.0",
32
+ "@tramvai/react": "2.35.0",
33
33
  "@tramvai/safe-strings": "0.5.5",
34
- "@tramvai/tokens-render": "2.33.3",
35
- "@tramvai/experiments": "2.33.3",
34
+ "@tramvai/tokens-render": "2.35.0",
35
+ "@tramvai/experiments": "2.35.0",
36
36
  "@types/loadable__server": "^5.12.6",
37
37
  "node-fetch": "^2.6.1"
38
38
  },
@@ -40,14 +40,14 @@
40
40
  "@tinkoff/dippy": "0.8.8",
41
41
  "@tinkoff/utils": "^2.1.2",
42
42
  "@tinkoff/react-hooks": "0.1.4",
43
- "@tramvai/cli": "2.33.3",
44
- "@tramvai/core": "2.33.3",
45
- "@tramvai/module-common": "2.33.3",
46
- "@tramvai/state": "2.33.3",
47
- "@tramvai/test-helpers": "2.33.3",
48
- "@tramvai/tokens-common": "2.33.3",
49
- "@tramvai/tokens-router": "2.33.3",
50
- "@tramvai/tokens-server-private": "2.33.3",
43
+ "@tramvai/cli": "2.35.0",
44
+ "@tramvai/core": "2.35.0",
45
+ "@tramvai/module-common": "2.35.0",
46
+ "@tramvai/state": "2.35.0",
47
+ "@tramvai/test-helpers": "2.35.0",
48
+ "@tramvai/tokens-common": "2.35.0",
49
+ "@tramvai/tokens-router": "2.35.0",
50
+ "@tramvai/tokens-server-private": "2.35.0",
51
51
  "express": "^4.17.1",
52
52
  "prop-types": "^15.6.2",
53
53
  "react": ">=16.14.0",