@tramvai/module-render 2.134.0 → 2.139.2

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.
@@ -12,7 +12,8 @@ export declare class PageBuilder {
12
12
  private renderFlowAfter;
13
13
  private log;
14
14
  private fetchWebpackStats;
15
- constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, fetchWebpackStats, }: {
15
+ private di;
16
+ constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, fetchWebpackStats, di, }: {
16
17
  renderSlots: any;
17
18
  pageService: any;
18
19
  resourcesRegistry: any;
@@ -25,6 +26,7 @@ export declare class PageBuilder {
25
26
  renderFlowAfter: any;
26
27
  logger: any;
27
28
  fetchWebpackStats: any;
29
+ di: any;
28
30
  });
29
31
  flow(): Promise<string>;
30
32
  dehydrateState(): void;
@@ -20,7 +20,7 @@ const mapResourcesToSlots = (resources) => resources.reduce((acc, resource) => {
20
20
  return acc;
21
21
  }, {});
22
22
  class PageBuilder {
23
- constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, fetchWebpackStats, }) {
23
+ constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, fetchWebpackStats, di, }) {
24
24
  this.htmlAttrs = htmlAttrs;
25
25
  this.renderSlots = flatten(renderSlots || []);
26
26
  this.pageService = pageService;
@@ -33,6 +33,7 @@ class PageBuilder {
33
33
  this.renderFlowAfter = renderFlowAfter || [];
34
34
  this.log = logger('page-builder');
35
35
  this.fetchWebpackStats = fetchWebpackStats;
36
+ this.di = di;
36
37
  }
37
38
  async flow() {
38
39
  const stats = await this.fetchWebpackStats({ modern: this.modern });
@@ -91,10 +92,12 @@ class PageBuilder {
91
92
  }
92
93
  async renderApp(extractor) {
93
94
  const html = await this.reactRender.render(extractor);
95
+ const appHtmlAttrs = formatAttributes(this.htmlAttrs, 'app');
96
+ this.di.register({ provide: 'tramvai app html attributes', useValue: appHtmlAttrs });
94
97
  this.renderSlots = this.renderSlots.concat({
95
98
  type: ResourceType.asIs,
96
99
  slot: ResourceSlot.REACT_RENDER,
97
- payload: `<div ${formatAttributes(this.htmlAttrs, 'app')}>${html}</div>`,
100
+ payload: `<div ${appHtmlAttrs}>${html}</div>`,
98
101
  });
99
102
  }
100
103
  }
@@ -28,7 +28,7 @@ const mapResourcesToSlots = (resources) => resources.reduce((acc, resource) => {
28
28
  return acc;
29
29
  }, {});
30
30
  class PageBuilder {
31
- constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, fetchWebpackStats, }) {
31
+ constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, fetchWebpackStats, di, }) {
32
32
  this.htmlAttrs = htmlAttrs;
33
33
  this.renderSlots = flatten__default["default"](renderSlots || []);
34
34
  this.pageService = pageService;
@@ -41,6 +41,7 @@ class PageBuilder {
41
41
  this.renderFlowAfter = renderFlowAfter || [];
42
42
  this.log = logger('page-builder');
43
43
  this.fetchWebpackStats = fetchWebpackStats;
44
+ this.di = di;
44
45
  }
45
46
  async flow() {
46
47
  const stats = await this.fetchWebpackStats({ modern: this.modern });
@@ -99,10 +100,12 @@ class PageBuilder {
99
100
  }
100
101
  async renderApp(extractor) {
101
102
  const html = await this.reactRender.render(extractor);
103
+ const appHtmlAttrs = utils.formatAttributes(this.htmlAttrs, 'app');
104
+ this.di.register({ provide: 'tramvai app html attributes', useValue: appHtmlAttrs });
102
105
  this.renderSlots = this.renderSlots.concat({
103
106
  type: tokensRender.ResourceType.asIs,
104
107
  slot: tokensRender.ResourceSlot.REACT_RENDER,
105
- payload: `<div ${utils.formatAttributes(this.htmlAttrs, 'app')}>${html}</div>`,
108
+ payload: `<div ${appHtmlAttrs}>${html}</div>`,
106
109
  });
107
110
  }
108
111
  }
@@ -3,20 +3,25 @@ import type { DI_TOKEN } from '@tramvai/core';
3
3
  import type { CONTEXT_TOKEN, LOGGER_TOKEN } from '@tramvai/module-common';
4
4
  import type { EXTEND_RENDER, CUSTOM_RENDER, REACT_SERVER_RENDER_MODE } from '@tramvai/tokens-render';
5
5
  import type { ChunkExtractor } from '@loadable/server';
6
+ import type { SERVER_RESPONSE_STREAM, SERVER_RESPONSE_TASK_MANAGER } from '@tramvai/tokens-server-private';
6
7
  export declare class ReactRenderServer {
7
8
  customRender: typeof CUSTOM_RENDER;
8
9
  extendRender: ExtractDependencyType<typeof EXTEND_RENDER>;
9
10
  context: typeof CONTEXT_TOKEN;
10
11
  di: typeof DI_TOKEN;
11
12
  log: ReturnType<typeof LOGGER_TOKEN>;
13
+ responseTaskManager: typeof SERVER_RESPONSE_TASK_MANAGER;
14
+ responseStream: typeof SERVER_RESPONSE_STREAM;
12
15
  renderMode: typeof REACT_SERVER_RENDER_MODE;
13
- constructor({ context, customRender, extendRender, di, renderMode, logger }: {
16
+ constructor({ context, customRender, extendRender, di, renderMode, logger, responseTaskManager, responseStream, }: {
14
17
  context: any;
15
18
  customRender: any;
16
19
  extendRender: any;
17
20
  di: any;
18
21
  renderMode: any;
19
22
  logger: any;
23
+ responseTaskManager: any;
24
+ responseStream: any;
20
25
  });
21
26
  render(extractor: ChunkExtractor): Promise<string>;
22
27
  }
@@ -4,32 +4,42 @@ import { renderReact } from '../react/index.es.js';
4
4
 
5
5
  const RENDER_TIMEOUT = 500;
6
6
  class HtmlWritable extends Writable {
7
- constructor() {
8
- super(...arguments);
9
- this.chunks = [];
10
- this.html = '';
11
- }
12
- getHtml() {
13
- return this.html;
7
+ constructor({ responseTaskManager, responseStream, }) {
8
+ super();
9
+ this.responseTaskManager = responseTaskManager;
10
+ this.responseStream = responseStream;
14
11
  }
15
12
  _write(chunk, encoding, callback) {
16
- this.chunks.push(chunk);
17
- callback();
18
- }
19
- _final(callback) {
20
- this.html = Buffer.concat(this.chunks).toString();
13
+ const html = chunk.toString('utf-8');
14
+ // delay writing HTML to response stream
15
+ // @todo some priorities, to prevent conflicts with deferred actions scripts?
16
+ this.responseTaskManager.push(async () => {
17
+ this.responseStream.push(html);
18
+ });
21
19
  callback();
22
20
  }
23
21
  }
22
+ const Deferred = () => {
23
+ let resolve;
24
+ let reject;
25
+ // eslint-disable-next-line promise/param-names
26
+ const promise = new Promise((res, rej) => {
27
+ resolve = res;
28
+ reject = rej;
29
+ });
30
+ return { promise, resolve, reject };
31
+ };
24
32
  class ReactRenderServer {
25
33
  // eslint-disable-next-line sort-class-members/sort-class-members
26
- constructor({ context, customRender, extendRender, di, renderMode, logger }) {
34
+ constructor({ context, customRender, extendRender, di, renderMode, logger, responseTaskManager, responseStream, }) {
27
35
  this.context = context;
28
36
  this.customRender = customRender;
29
37
  this.extendRender = extendRender;
30
38
  this.di = di;
31
39
  this.renderMode = renderMode;
32
40
  this.log = logger('module-render');
41
+ this.responseTaskManager = responseTaskManager;
42
+ this.responseStream = responseStream;
33
43
  }
34
44
  render(extractor) {
35
45
  var _a;
@@ -44,23 +54,36 @@ class ReactRenderServer {
44
54
  if (process.env.__TRAMVAI_CONCURRENT_FEATURES && this.renderMode === 'streaming') {
45
55
  return new Promise((resolve, reject) => {
46
56
  const { renderToPipeableStream } = require('react-dom/server');
47
- const htmlWritable = new HtmlWritable();
48
- htmlWritable.on('finish', () => {
49
- resolve(htmlWritable.getHtml());
50
- });
57
+ const { responseTaskManager, responseStream, log } = this;
58
+ const htmlWritable = new HtmlWritable({ responseTaskManager, responseStream });
59
+ const allReadyDeferred = Deferred();
51
60
  const start = Date.now();
52
- const { log } = this;
61
+ // prevent sent reply before all suspended components are resolved
62
+ responseTaskManager.push(() => {
63
+ // eslint-disable-next-line promise/param-names
64
+ return allReadyDeferred.promise;
65
+ });
53
66
  log.info({
54
67
  event: 'streaming-render:start',
55
68
  });
56
69
  const { pipe, abort } = renderToPipeableStream(renderResult, {
57
- onAllReady() {
70
+ onShellReady() {
58
71
  log.info({
59
- event: 'streaming-render:complete',
72
+ event: 'streaming-render:shell-ready',
60
73
  duration: Date.now() - start,
61
74
  });
62
- // here `write` will be called only once
75
+ // here all HTML are ready except suspended components
63
76
  pipe(htmlWritable);
77
+ // resolve empty HTML, because we will stream it later
78
+ resolve('');
79
+ },
80
+ onAllReady() {
81
+ log.info({
82
+ event: 'streaming-render:all-ready',
83
+ duration: Date.now() - start,
84
+ });
85
+ // here all suspended components are resolved
86
+ allReadyDeferred.resolve();
64
87
  },
65
88
  onError(error) {
66
89
  // error can be inside Suspense boundaries, this is not critical, continue rendering.
@@ -12,32 +12,42 @@ var each__default = /*#__PURE__*/_interopDefaultLegacy(each);
12
12
 
13
13
  const RENDER_TIMEOUT = 500;
14
14
  class HtmlWritable extends stream.Writable {
15
- constructor() {
16
- super(...arguments);
17
- this.chunks = [];
18
- this.html = '';
19
- }
20
- getHtml() {
21
- return this.html;
15
+ constructor({ responseTaskManager, responseStream, }) {
16
+ super();
17
+ this.responseTaskManager = responseTaskManager;
18
+ this.responseStream = responseStream;
22
19
  }
23
20
  _write(chunk, encoding, callback) {
24
- this.chunks.push(chunk);
25
- callback();
26
- }
27
- _final(callback) {
28
- this.html = Buffer.concat(this.chunks).toString();
21
+ const html = chunk.toString('utf-8');
22
+ // delay writing HTML to response stream
23
+ // @todo some priorities, to prevent conflicts with deferred actions scripts?
24
+ this.responseTaskManager.push(async () => {
25
+ this.responseStream.push(html);
26
+ });
29
27
  callback();
30
28
  }
31
29
  }
30
+ const Deferred = () => {
31
+ let resolve;
32
+ let reject;
33
+ // eslint-disable-next-line promise/param-names
34
+ const promise = new Promise((res, rej) => {
35
+ resolve = res;
36
+ reject = rej;
37
+ });
38
+ return { promise, resolve, reject };
39
+ };
32
40
  class ReactRenderServer {
33
41
  // eslint-disable-next-line sort-class-members/sort-class-members
34
- constructor({ context, customRender, extendRender, di, renderMode, logger }) {
42
+ constructor({ context, customRender, extendRender, di, renderMode, logger, responseTaskManager, responseStream, }) {
35
43
  this.context = context;
36
44
  this.customRender = customRender;
37
45
  this.extendRender = extendRender;
38
46
  this.di = di;
39
47
  this.renderMode = renderMode;
40
48
  this.log = logger('module-render');
49
+ this.responseTaskManager = responseTaskManager;
50
+ this.responseStream = responseStream;
41
51
  }
42
52
  render(extractor) {
43
53
  var _a;
@@ -52,23 +62,36 @@ class ReactRenderServer {
52
62
  if (process.env.__TRAMVAI_CONCURRENT_FEATURES && this.renderMode === 'streaming') {
53
63
  return new Promise((resolve, reject) => {
54
64
  const { renderToPipeableStream } = require('react-dom/server');
55
- const htmlWritable = new HtmlWritable();
56
- htmlWritable.on('finish', () => {
57
- resolve(htmlWritable.getHtml());
58
- });
65
+ const { responseTaskManager, responseStream, log } = this;
66
+ const htmlWritable = new HtmlWritable({ responseTaskManager, responseStream });
67
+ const allReadyDeferred = Deferred();
59
68
  const start = Date.now();
60
- const { log } = this;
69
+ // prevent sent reply before all suspended components are resolved
70
+ responseTaskManager.push(() => {
71
+ // eslint-disable-next-line promise/param-names
72
+ return allReadyDeferred.promise;
73
+ });
61
74
  log.info({
62
75
  event: 'streaming-render:start',
63
76
  });
64
77
  const { pipe, abort } = renderToPipeableStream(renderResult, {
65
- onAllReady() {
78
+ onShellReady() {
66
79
  log.info({
67
- event: 'streaming-render:complete',
80
+ event: 'streaming-render:shell-ready',
68
81
  duration: Date.now() - start,
69
82
  });
70
- // here `write` will be called only once
83
+ // here all HTML are ready except suspended components
71
84
  pipe(htmlWritable);
85
+ // resolve empty HTML, because we will stream it later
86
+ resolve('');
87
+ },
88
+ onAllReady() {
89
+ log.info({
90
+ event: 'streaming-render:all-ready',
91
+ duration: Date.now() - start,
92
+ });
93
+ // here all suspended components are resolved
94
+ allReadyDeferred.resolve();
72
95
  },
73
96
  onError(error) {
74
97
  // error can be inside Suspense boundaries, this is not critical, continue rendering.
package/lib/server.es.js CHANGED
@@ -11,6 +11,7 @@ import { isRedirectFoundError } from '@tinkoff/errors';
11
11
  import { setPageErrorEvent, PageErrorStore, deserializeError } from '@tramvai/module-router';
12
12
  export { PageErrorStore, setPageErrorEvent } from '@tramvai/module-router';
13
13
  import { COOKIE_MANAGER_TOKEN } from '@tramvai/module-common';
14
+ import { SERVER_RESPONSE_TASK_MANAGER, SERVER_RESPONSE_STREAM } from '@tramvai/tokens-server-private';
14
15
  import { ResourcesInliner } from './resourcesInliner/resourcesInliner.es.js';
15
16
  import { RESOURCE_INLINER, RESOURCES_REGISTRY_CACHE } from './resourcesInliner/tokens.es.js';
16
17
  import { ResourcesRegistry } from './resourcesRegistry/index.es.js';
@@ -197,6 +198,7 @@ Page Error Boundary will be rendered for the client`,
197
198
  renderFlowAfter: { token: RENDER_FLOW_AFTER_TOKEN, optional: true },
198
199
  logger: LOGGER_TOKEN,
199
200
  fetchWebpackStats: FETCH_WEBPACK_STATS_TOKEN,
201
+ di: DI_TOKEN,
200
202
  },
201
203
  }),
202
204
  provide({
@@ -209,6 +211,8 @@ Page Error Boundary will be rendered for the client`,
209
211
  di: DI_TOKEN,
210
212
  renderMode: { token: REACT_SERVER_RENDER_MODE, optional: true },
211
213
  logger: LOGGER_TOKEN,
214
+ responseTaskManager: SERVER_RESPONSE_TASK_MANAGER,
215
+ responseStream: SERVER_RESPONSE_STREAM,
212
216
  },
213
217
  }),
214
218
  provide({
package/lib/server.js CHANGED
@@ -13,6 +13,7 @@ var userAgent = require('@tinkoff/user-agent');
13
13
  var errors = require('@tinkoff/errors');
14
14
  var moduleRouter = require('@tramvai/module-router');
15
15
  var moduleCommon = require('@tramvai/module-common');
16
+ var tokensServerPrivate = require('@tramvai/tokens-server-private');
16
17
  var resourcesInliner = require('./resourcesInliner/resourcesInliner.js');
17
18
  var tokens = require('./resourcesInliner/tokens.js');
18
19
  var index = require('./resourcesRegistry/index.js');
@@ -198,6 +199,7 @@ Page Error Boundary will be rendered for the client`,
198
199
  renderFlowAfter: { token: tokensRender.RENDER_FLOW_AFTER_TOKEN, optional: true },
199
200
  logger: tokensCommon.LOGGER_TOKEN,
200
201
  fetchWebpackStats: tokensRender.FETCH_WEBPACK_STATS_TOKEN,
202
+ di: core.DI_TOKEN,
201
203
  },
202
204
  }),
203
205
  core.provide({
@@ -210,6 +212,8 @@ Page Error Boundary will be rendered for the client`,
210
212
  di: core.DI_TOKEN,
211
213
  renderMode: { token: tokensRender.REACT_SERVER_RENDER_MODE, optional: true },
212
214
  logger: tokensCommon.LOGGER_TOKEN,
215
+ responseTaskManager: tokensServerPrivate.SERVER_RESPONSE_TASK_MANAGER,
216
+ responseStream: tokensServerPrivate.SERVER_RESPONSE_STREAM,
213
217
  },
214
218
  }),
215
219
  core.provide({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tramvai/module-render",
3
- "version": "2.134.0",
3
+ "version": "2.139.2",
4
4
  "description": "",
5
5
  "browser": "lib/browser.js",
6
6
  "main": "lib/server.js",
@@ -26,13 +26,13 @@
26
26
  "@tinkoff/layout-factory": "0.3.8",
27
27
  "@tinkoff/errors": "0.3.8",
28
28
  "@tinkoff/url": "0.8.6",
29
- "@tinkoff/user-agent": "0.4.400",
30
- "@tramvai/module-client-hints": "2.134.0",
31
- "@tramvai/module-router": "2.134.0",
32
- "@tramvai/react": "2.134.0",
29
+ "@tinkoff/user-agent": "0.4.408",
30
+ "@tramvai/module-client-hints": "2.139.2",
31
+ "@tramvai/module-router": "2.139.2",
32
+ "@tramvai/react": "2.139.2",
33
33
  "@tramvai/safe-strings": "0.5.11",
34
- "@tramvai/tokens-render": "2.134.0",
35
- "@tramvai/experiments": "2.134.0",
34
+ "@tramvai/tokens-render": "2.139.2",
35
+ "@tramvai/experiments": "2.139.2",
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.15",
41
41
  "@tinkoff/utils": "^2.1.2",
42
42
  "@tinkoff/react-hooks": "0.1.6",
43
- "@tramvai/cli": "2.134.0",
44
- "@tramvai/core": "2.134.0",
45
- "@tramvai/module-common": "2.134.0",
46
- "@tramvai/state": "2.134.0",
47
- "@tramvai/test-helpers": "2.134.0",
48
- "@tramvai/tokens-common": "2.134.0",
49
- "@tramvai/tokens-router": "2.134.0",
50
- "@tramvai/tokens-server-private": "2.134.0",
43
+ "@tramvai/cli": "2.139.2",
44
+ "@tramvai/core": "2.139.2",
45
+ "@tramvai/module-common": "2.139.2",
46
+ "@tramvai/state": "2.139.2",
47
+ "@tramvai/test-helpers": "2.139.2",
48
+ "@tramvai/tokens-common": "2.139.2",
49
+ "@tramvai/tokens-router": "2.139.2",
50
+ "@tramvai/tokens-server-private": "2.139.2",
51
51
  "express": "^4.17.1",
52
52
  "prop-types": "^15.6.2",
53
53
  "react": ">=16.14.0",