@tramvai/module-render 2.20.1 → 2.22.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 (3) hide show
  1. package/package.json +18 -16
  2. package/tests.d.ts +13 -0
  3. package/tests.js +267 -0
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@tramvai/module-render",
3
- "version": "2.20.1",
3
+ "version": "2.22.0",
4
4
  "description": "",
5
5
  "browser": "lib/browser.js",
6
6
  "main": "lib/server.js",
7
7
  "typings": "lib/server.d.ts",
8
8
  "files": [
9
9
  "lib",
10
- "__migrations__"
10
+ "__migrations__",
11
+ "tests.js",
12
+ "tests.d.ts"
11
13
  ],
12
14
  "sideEffects": false,
13
15
  "repository": {
@@ -24,13 +26,13 @@
24
26
  "@tinkoff/htmlpagebuilder": "0.5.2",
25
27
  "@tinkoff/layout-factory": "0.3.2",
26
28
  "@tinkoff/url": "0.8.2",
27
- "@tinkoff/user-agent": "0.4.52",
28
- "@tramvai/module-client-hints": "2.20.1",
29
- "@tramvai/module-router": "2.20.1",
30
- "@tramvai/react": "2.20.1",
29
+ "@tinkoff/user-agent": "0.4.55",
30
+ "@tramvai/module-client-hints": "2.22.0",
31
+ "@tramvai/module-router": "2.22.0",
32
+ "@tramvai/react": "2.22.0",
31
33
  "@tramvai/safe-strings": "0.5.2",
32
- "@tramvai/tokens-render": "2.20.1",
33
- "@tramvai/experiments": "2.20.1",
34
+ "@tramvai/tokens-render": "2.22.0",
35
+ "@tramvai/experiments": "2.22.0",
34
36
  "@types/loadable__server": "^5.12.6",
35
37
  "node-fetch": "^2.6.1"
36
38
  },
@@ -38,14 +40,14 @@
38
40
  "@tinkoff/dippy": "0.8.2",
39
41
  "@tinkoff/utils": "^2.1.2",
40
42
  "@tinkoff/react-hooks": "0.1.2",
41
- "@tramvai/cli": "2.20.1",
42
- "@tramvai/core": "2.20.1",
43
- "@tramvai/module-common": "2.20.1",
44
- "@tramvai/state": "2.20.1",
45
- "@tramvai/test-helpers": "2.20.1",
46
- "@tramvai/tokens-common": "2.20.1",
47
- "@tramvai/tokens-router": "2.20.1",
48
- "@tramvai/tokens-server-private": "2.20.1",
43
+ "@tramvai/cli": "2.22.0",
44
+ "@tramvai/core": "2.22.0",
45
+ "@tramvai/module-common": "2.22.0",
46
+ "@tramvai/state": "2.22.0",
47
+ "@tramvai/test-helpers": "2.22.0",
48
+ "@tramvai/tokens-common": "2.22.0",
49
+ "@tramvai/tokens-router": "2.22.0",
50
+ "@tramvai/tokens-server-private": "2.22.0",
49
51
  "express": "^4.17.1",
50
52
  "prop-types": "^15.6.2",
51
53
  "react": ">=16.14.0",
package/tests.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { getDiWrapper } from "@tramvai/test-helpers";
2
+ type Options = Parameters<typeof getDiWrapper>[0];
3
+ declare const testPageResources: (options: Options) => {
4
+ render: () => {
5
+ parsed: import("node-html-parser").HTMLElement;
6
+ body: string;
7
+ head: string;
8
+ application: string;
9
+ };
10
+ di: import("@tinkoff/dippy").Container;
11
+ runLine: (line: import("@tinkoff/dippy").MultiTokenInterface<import("@tramvai/core").Command>) => Promise<any[]>;
12
+ };
13
+ export { testPageResources };
package/tests.js ADDED
@@ -0,0 +1,267 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var flatten = require('@tinkoff/utils/array/flatten');
6
+ var testHelpers = require('@tramvai/test-helpers');
7
+ var core = require('@tramvai/core');
8
+ var tokensRender = require('@tramvai/tokens-render');
9
+ var htmlpagebuilder = require('@tinkoff/htmlpagebuilder');
10
+ var toArray = require('@tinkoff/utils/array/toArray');
11
+ require('@tinkoff/utils/is/undefined');
12
+ require('@tinkoff/utils/is/empty');
13
+ require('@tinkoff/url');
14
+ require('node-fetch');
15
+ require('@tinkoff/utils/string/startsWith');
16
+ var dippy = require('@tinkoff/dippy');
17
+ require('@tramvai/safe-strings');
18
+ require('@loadable/server');
19
+ require('@tinkoff/utils/object/has');
20
+ require('@tinkoff/utils/array/last');
21
+ require('@tramvai/experiments');
22
+ require('@tinkoff/utils/array/uniq');
23
+ var path = require('path');
24
+ require('@tinkoff/utils/array/each');
25
+ require('@tinkoff/utils/object/path');
26
+
27
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
28
+
29
+ function _interopNamespace(e) {
30
+ if (e && e.__esModule) return e;
31
+ var n = Object.create(null);
32
+ if (e) {
33
+ Object.keys(e).forEach(function (k) {
34
+ if (k !== 'default') {
35
+ var d = Object.getOwnPropertyDescriptor(e, k);
36
+ Object.defineProperty(n, k, d.get ? d : {
37
+ enumerable: true,
38
+ get: function () { return e[k]; }
39
+ });
40
+ }
41
+ });
42
+ }
43
+ n["default"] = e;
44
+ return n;
45
+ }
46
+
47
+ var flatten__default = /*#__PURE__*/_interopDefaultLegacy(flatten);
48
+ var toArray__default = /*#__PURE__*/_interopDefaultLegacy(toArray);
49
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
50
+
51
+ class ResourcesRegistry {
52
+ constructor({ resourceInliner }) {
53
+ this.resources = new Set();
54
+ this.resourceInliner = resourceInliner;
55
+ }
56
+ register(resourceOrResources) {
57
+ toArray__default["default"](resourceOrResources).forEach((resource) => {
58
+ this.resources.add(resource);
59
+ });
60
+ }
61
+ getPageResources() {
62
+ return Array.from(this.resources.values())
63
+ .reduce((acc, resource) => {
64
+ if (this.resourceInliner.shouldInline(resource)) {
65
+ Array.prototype.push.apply(acc, this.resourceInliner.inlineResource(resource));
66
+ }
67
+ else {
68
+ acc.push(resource);
69
+ }
70
+ return acc;
71
+ }, [])
72
+ .filter((resource) => this.resourceInliner.shouldAddResource(resource));
73
+ }
74
+ }
75
+
76
+ process.env.NODE_ENV === 'development' &&
77
+ (process.env.ASSETS_PREFIX === 'static' || !process.env.ASSETS_PREFIX)
78
+ ? `http://localhost:${process.env.PORT_STATIC}/dist/`
79
+ : process.env.ASSETS_PREFIX;
80
+
81
+ /**
82
+ * @description
83
+ * Инлайнер ресурсов - используется на сервере для регистрации файлов, которые должны быть вставлены
84
+ * в итоговую html-страницу в виде ссылки на файл или заинлайнеными полностью
85
+ */
86
+ const RESOURCE_INLINER = dippy.createToken('resourceInliner');
87
+ /**
88
+ * @description
89
+ * Кэш загруженных ресурсов.
90
+ */
91
+ dippy.createToken('resourcesRegistryCache');
92
+
93
+ const requireFunc =
94
+ // @ts-ignore
95
+ typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require;
96
+
97
+ let appConfig;
98
+ try {
99
+ appConfig = require('@tramvai/cli/lib/external/config').appConfig;
100
+ }
101
+ catch (e) { }
102
+ if (process.env.NODE_ENV === 'development') ;
103
+ if (process.env.NODE_ENV === 'test') ;
104
+ if (process.env.NODE_ENV === 'production') {
105
+ const SEARCH_PATHS = [process.cwd(), __dirname];
106
+ const webpackStats = (fileName) => {
107
+ let stats;
108
+ for (const dir of SEARCH_PATHS) {
109
+ try {
110
+ const statsPath = path__namespace.resolve(dir, fileName);
111
+ stats = requireFunc(statsPath);
112
+ break;
113
+ }
114
+ catch (e) {
115
+ // ignore errors as this function is used to load stats for several optional destinations
116
+ // and these destinations may not have stats file
117
+ }
118
+ }
119
+ if (!stats) {
120
+ return;
121
+ }
122
+ if (!process.env.ASSETS_PREFIX) {
123
+ if (process.env.STATIC_PREFIX) {
124
+ throw new Error('Required env variable "ASSETS_PREFIX" is not set. Instead of using "STATIC_PREFIX" env please define "ASSETS_PREFIX: STATIC_PREFIX + /compiled"');
125
+ }
126
+ throw new Error('Required env variable "ASSETS_PREFIX" is not set');
127
+ }
128
+ return {
129
+ ...stats,
130
+ publicPath: process.env.ASSETS_PREFIX,
131
+ };
132
+ };
133
+ const statsLegacy = webpackStats('stats.json');
134
+ webpackStats('stats.modern.json') || statsLegacy;
135
+ if (!statsLegacy) {
136
+ throw new Error(`Cannot find stats.json.
137
+ It should be placed in one of the next places:
138
+ ${SEARCH_PATHS.join('\n\t')}
139
+ In case it happens on deployment:
140
+ - In case you are using two independent jobs for building app
141
+ - Either do not split build command by two independent jobs and use one common job with "tramvai build" command without --buildType
142
+ - Or copy stats.json (and stats.modern.json if present) file from client build output to server output by yourself in your CI
143
+ - Otherwise report issue to tramvai team
144
+ In case it happens locally:
145
+ - prefer to use command "tramvai start-prod" to test prod-build locally
146
+ - copy stats.json next to built server.js file
147
+ `);
148
+ }
149
+ }
150
+
151
+ const formatAttributes = (htmlAttrs, target) => {
152
+ if (!htmlAttrs) {
153
+ return '';
154
+ }
155
+ const targetAttrs = htmlAttrs.filter((item) => item.target === target);
156
+ const collectedAttrs = targetAttrs.reduce((acc, item) => ({ ...acc, ...item.attrs }), {});
157
+ const attrsString = Object.keys(collectedAttrs).reduce((acc, name) => {
158
+ if (collectedAttrs[name] === true) {
159
+ return `${acc} ${name}`;
160
+ }
161
+ return `${acc} ${name}="${collectedAttrs[name]}"`;
162
+ }, '');
163
+ return attrsString.trim();
164
+ };
165
+
166
+ /* eslint-disable sort-class-members/sort-class-members */
167
+ const mapResourcesToSlots = (resources) => resources.reduce((acc, resource) => {
168
+ const { slot } = resource;
169
+ acc[slot] = Array.isArray(acc[slot]) ? [...acc[slot], resource] : [resource];
170
+ return acc;
171
+ }, {});
172
+ /* eslint-enable sort-class-members/sort-class-members */
173
+
174
+ const { REACT_RENDER, HEAD_CORE_SCRIPTS, HEAD_DYNAMIC_SCRIPTS, HEAD_META, HEAD_POLYFILLS, HEAD_CORE_STYLES, HEAD_PERFORMANCE, HEAD_ANALYTICS, BODY_START, BODY_END, HEAD_ICONS, BODY_TAIL_ANALYTICS, BODY_TAIL, } = tokensRender.ResourceSlot;
175
+ const htmlPageSchemaFactory = ({ htmlAttrs, }) => {
176
+ return [
177
+ htmlpagebuilder.staticRender('<!DOCTYPE html>'),
178
+ htmlpagebuilder.staticRender(`<html ${formatAttributes(htmlAttrs, 'html')}>`),
179
+ htmlpagebuilder.staticRender('<head>'),
180
+ htmlpagebuilder.staticRender('<meta charset="UTF-8">'),
181
+ htmlpagebuilder.dynamicRender(HEAD_META),
182
+ htmlpagebuilder.dynamicRender(HEAD_PERFORMANCE),
183
+ htmlpagebuilder.dynamicRender(HEAD_CORE_STYLES),
184
+ htmlpagebuilder.dynamicRender(HEAD_POLYFILLS),
185
+ htmlpagebuilder.dynamicRender(HEAD_DYNAMIC_SCRIPTS),
186
+ htmlpagebuilder.dynamicRender(HEAD_CORE_SCRIPTS),
187
+ htmlpagebuilder.dynamicRender(HEAD_ANALYTICS),
188
+ htmlpagebuilder.dynamicRender(HEAD_ICONS),
189
+ htmlpagebuilder.staticRender('</head>'),
190
+ htmlpagebuilder.staticRender(`<body ${formatAttributes(htmlAttrs, 'body')}>`),
191
+ htmlpagebuilder.dynamicRender(BODY_START),
192
+ // react app
193
+ htmlpagebuilder.dynamicRender(REACT_RENDER),
194
+ htmlpagebuilder.dynamicRender(BODY_END),
195
+ htmlpagebuilder.dynamicRender(BODY_TAIL_ANALYTICS),
196
+ htmlpagebuilder.dynamicRender(BODY_TAIL),
197
+ htmlpagebuilder.staticRender('</body>'),
198
+ htmlpagebuilder.staticRender('</html>'),
199
+ ];
200
+ };
201
+
202
+ const testPageResources = (options) => {
203
+ var _a;
204
+ const { modules, providers = [] } = options;
205
+ const { di, runLine } = testHelpers.getDiWrapper({
206
+ di: options.di,
207
+ modules,
208
+ providers: [
209
+ {
210
+ provide: 'htmlPageSchema',
211
+ useFactory: htmlPageSchemaFactory,
212
+ deps: {
213
+ htmlAttrs: tokensRender.HTML_ATTRS,
214
+ },
215
+ },
216
+ {
217
+ provide: tokensRender.HTML_ATTRS,
218
+ useValue: {
219
+ target: 'html',
220
+ attrs: {
221
+ class: 'no-js',
222
+ lang: 'ru',
223
+ },
224
+ },
225
+ multi: true,
226
+ },
227
+ ...providers,
228
+ core.provide({
229
+ provide: tokensRender.RESOURCES_REGISTRY,
230
+ useClass: ResourcesRegistry,
231
+ deps: {
232
+ resourceInliner: RESOURCE_INLINER,
233
+ },
234
+ }),
235
+ core.provide({
236
+ provide: RESOURCE_INLINER,
237
+ useValue: {
238
+ shouldInline() {
239
+ return false;
240
+ },
241
+ shouldAddResource() {
242
+ return true;
243
+ },
244
+ inlineResource() {
245
+ return [];
246
+ },
247
+ },
248
+ }),
249
+ ],
250
+ });
251
+ const renderSlots = flatten__default["default"]((_a = di.get({ token: tokensRender.RENDER_SLOTS, optional: true })) !== null && _a !== void 0 ? _a : []);
252
+ const resourcesRegistry = di.get(tokensRender.RESOURCES_REGISTRY);
253
+ const render = () => {
254
+ const rawHtml = htmlpagebuilder.buildPage({
255
+ slotHandlers: mapResourcesToSlots([...renderSlots, ...resourcesRegistry.getPageResources()]),
256
+ description: di.get('htmlPageSchema'),
257
+ });
258
+ return testHelpers.parseHtml(rawHtml, {});
259
+ };
260
+ return {
261
+ render,
262
+ di,
263
+ runLine,
264
+ };
265
+ };
266
+
267
+ exports.testPageResources = testPageResources;