@lwrjs/view-registry 0.17.2-alpha.0 → 0.17.2-alpha.10
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/build/cjs/index.cjs +31 -22
- package/build/cjs/linkers/legacy_view_bootstrap.cjs +2 -2
- package/build/cjs/linkers/preload-utils.cjs +1 -3
- package/build/cjs/linkers/utils.cjs +6 -2
- package/build/cjs/linkers/view_bootstrap.cjs +2 -2
- package/build/cjs/utils.cjs +9 -7
- package/build/cjs/view-handler.cjs +2 -2
- package/build/es/index.d.ts +1 -0
- package/build/es/index.js +32 -21
- package/build/es/linkers/legacy_view_bootstrap.js +2 -2
- package/build/es/linkers/preload-utils.js +2 -4
- package/build/es/linkers/utils.d.ts +1 -1
- package/build/es/linkers/utils.js +6 -2
- package/build/es/linkers/view_bootstrap.js +2 -2
- package/build/es/utils.d.ts +1 -1
- package/build/es/utils.js +8 -4
- package/build/es/view-handler.js +3 -3
- package/package.json +7 -7
package/build/cjs/index.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var import_instrumentation = __toModule(require("@lwrjs/instrumentation"));
|
|
|
32
32
|
var import_utils = __toModule(require("./utils.cjs"));
|
|
33
33
|
var import_link_lwr_resources = __toModule(require("./linkers/link-lwr-resources.cjs"));
|
|
34
34
|
var import_lru_cache = __toModule(require("lru-cache"));
|
|
35
|
+
var import_crypto = __toModule(require("crypto"));
|
|
35
36
|
var import_diagnostics = __toModule(require("@lwrjs/diagnostics"));
|
|
36
37
|
var import_view_handler = __toModule(require("./view-handler.cjs"));
|
|
37
38
|
var LwrViewRegistry = class {
|
|
@@ -40,6 +41,7 @@ var LwrViewRegistry = class {
|
|
|
40
41
|
this.compiledViews = new Map();
|
|
41
42
|
this.immutableAssets = new Map();
|
|
42
43
|
this.pendingViewDefinitions = new import_shared_utils.InflightTasks();
|
|
44
|
+
this.metadataCache = new Map();
|
|
43
45
|
this.name = "lwr-view-registry";
|
|
44
46
|
this.resourceRegistry = context.resourceRegistry;
|
|
45
47
|
this.runtimeEnvironment = context.runtimeEnvironment;
|
|
@@ -187,13 +189,14 @@ var LwrViewRegistry = class {
|
|
|
187
189
|
const updatableViewParams = {...viewParams};
|
|
188
190
|
(0, import_utils.generateViewNonce)(updatableViewParams);
|
|
189
191
|
const pendingViewDefCacheKey = viewDefCacheKey + viewParamKey;
|
|
190
|
-
const
|
|
192
|
+
const pendingViewDefCacheKeyHashed = (0, import_crypto.createHash)("md5").update(pendingViewDefCacheKey).digest("hex");
|
|
193
|
+
const viewDefinition = await this.pendingViewDefinitions.execute(pendingViewDefCacheKeyHashed, () => (0, import_instrumentation.getTracer)().trace({
|
|
191
194
|
name: import_instrumentation.ViewSpan.RenderView,
|
|
192
195
|
attributes: {
|
|
193
196
|
view: view.id,
|
|
194
197
|
ssr: view.bootstrap?.ssr === true
|
|
195
198
|
}
|
|
196
|
-
}, () => this.renderView(view, updatableViewParams, runtimeEnvironment, runtimeParams,
|
|
199
|
+
}, () => this.renderView(view, updatableViewParams, runtimeEnvironment, runtimeParams, pendingViewDefCacheKeyHashed, renderOptions)));
|
|
197
200
|
viewDefinition.nonce = (0, import_utils.getViewNonce)(updatableViewParams);
|
|
198
201
|
const route = this.globalConfig.routes.find((r) => r.id === view.id);
|
|
199
202
|
const maxViewCacheTtl = (0, import_shared_utils.getFeatureFlags)().MAX_VIEW_CACHE_TTL;
|
|
@@ -208,18 +211,18 @@ var LwrViewRegistry = class {
|
|
|
208
211
|
}
|
|
209
212
|
return viewDefinition;
|
|
210
213
|
} catch (err) {
|
|
211
|
-
if (err instanceof import_diagnostics.
|
|
214
|
+
if (err instanceof import_diagnostics.LwrError) {
|
|
212
215
|
throw err;
|
|
213
216
|
}
|
|
214
217
|
import_diagnostics.logger.error(`Failed to get view definition "${view.id}"`);
|
|
215
218
|
import_diagnostics.logger.error(err);
|
|
216
219
|
const message = err instanceof Error ? err.message : String(err);
|
|
217
|
-
throw
|
|
220
|
+
throw new import_diagnostics.LwrServerError(import_diagnostics.descriptions.SERVER.UNEXPECTED_ERROR(message));
|
|
218
221
|
}
|
|
219
222
|
}
|
|
220
223
|
async renderView(view, viewParams, runtimeEnvironment, runtimeParams, viewCacheKey, renderOptions) {
|
|
221
224
|
const {id, contentTemplate, rootComponent, layoutTemplate} = view;
|
|
222
|
-
const lwrResourcesId = `__LWR_RESOURCES__${
|
|
225
|
+
const lwrResourcesId = `__LWR_RESOURCES__${viewCacheKey}`;
|
|
223
226
|
const renderedContent = await this.render({id, contentTemplate, rootComponent}, {...viewParams, lwr_resources: lwrResourcesId}, runtimeParams, runtimeEnvironment);
|
|
224
227
|
let normalizedRenderOptions = (0, import_utils.normalizeRenderOptions)(this.runtimeEnvironment, renderedContent.options, renderOptions);
|
|
225
228
|
const layout = layoutTemplate || renderedContent.compiledView.layoutTemplate;
|
|
@@ -243,6 +246,10 @@ var LwrViewRegistry = class {
|
|
|
243
246
|
lwr_resources: lwrResourcesId
|
|
244
247
|
}, runtimeParams, runtimeEnvironment);
|
|
245
248
|
normalizedRenderOptions = (0, import_utils.normalizeRenderOptions)(this.runtimeEnvironment, renderedLayout.options, normalizedRenderOptions);
|
|
249
|
+
const warnings = [
|
|
250
|
+
...renderedContent.metadata.serverDebug?.warnings || [],
|
|
251
|
+
...renderedLayout.metadata.serverDebug?.warnings || []
|
|
252
|
+
];
|
|
246
253
|
const renderedViewDef = await this.link({
|
|
247
254
|
...renderedLayout,
|
|
248
255
|
compiledView: {
|
|
@@ -262,14 +269,10 @@ var LwrViewRegistry = class {
|
|
|
262
269
|
...renderedContent.metadata.serverData,
|
|
263
270
|
...renderedLayout.metadata.serverData
|
|
264
271
|
},
|
|
265
|
-
serverDebug: {
|
|
266
|
-
...renderedContent.metadata.serverDebug,
|
|
267
|
-
...renderedLayout.metadata.serverDebug
|
|
268
|
-
},
|
|
272
|
+
serverDebug: {warnings},
|
|
269
273
|
serverBundles: renderedContent.metadata.serverBundles
|
|
270
274
|
},
|
|
271
|
-
cache: renderedContent.cache
|
|
272
|
-
status: renderedContent.status
|
|
275
|
+
cache: renderedContent.cache
|
|
273
276
|
}, {
|
|
274
277
|
view: {...view, layoutTemplate: layoutTemplatePath},
|
|
275
278
|
viewParams,
|
|
@@ -290,7 +293,8 @@ var LwrViewRegistry = class {
|
|
|
290
293
|
...runtimeParams,
|
|
291
294
|
...globalContext,
|
|
292
295
|
...compiledView.properties,
|
|
293
|
-
...viewParams
|
|
296
|
+
...viewParams,
|
|
297
|
+
runtimeParams: {...runtimeParams}
|
|
294
298
|
}, runtimeEnvironment);
|
|
295
299
|
const normalizedResult = (0, import_utils.normalizeRenderedResult)(result);
|
|
296
300
|
return {
|
|
@@ -325,7 +329,17 @@ var LwrViewRegistry = class {
|
|
|
325
329
|
ssr: view.bootstrap?.ssr === true
|
|
326
330
|
}
|
|
327
331
|
}, () => {
|
|
328
|
-
|
|
332
|
+
let linkedMetadata2 = renderedViewMetadata;
|
|
333
|
+
if (!skipMetadataCollection) {
|
|
334
|
+
const contentHash = (0, import_crypto.createHash)("md5").update(renderedViewContent).digest("hex");
|
|
335
|
+
const cacheKey = `${view.id}:${contentHash}`;
|
|
336
|
+
if (this.metadataCache.has(cacheKey)) {
|
|
337
|
+
linkedMetadata2 = this.metadataCache.get(cacheKey);
|
|
338
|
+
} else {
|
|
339
|
+
linkedMetadata2 = (0, import_shared_utils.extractMetadataFromHtml)(renderedViewContent, renderedViewMetadata, this.globalConfig);
|
|
340
|
+
this.metadataCache.set(cacheKey, linkedMetadata2);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
329
343
|
const stringBuilder2 = (0, import_shared_utils.createStringBuilder)(renderedViewContent);
|
|
330
344
|
return {linkedMetadata: linkedMetadata2, stringBuilder: stringBuilder2};
|
|
331
345
|
});
|
|
@@ -336,7 +350,6 @@ var LwrViewRegistry = class {
|
|
|
336
350
|
importer: importer || renderedView.compiledView.filePath
|
|
337
351
|
};
|
|
338
352
|
let pageTtl = renderedView.cache.ttl;
|
|
339
|
-
let pageStatus = renderedView.status;
|
|
340
353
|
for (const viewTransformer of this.viewTransformers) {
|
|
341
354
|
const linkResults = await (0, import_instrumentation.getTracer)().trace({
|
|
342
355
|
name: import_instrumentation.ViewSpan.Transform,
|
|
@@ -346,9 +359,6 @@ var LwrViewRegistry = class {
|
|
|
346
359
|
}, () => viewTransformer.link?.(stringBuilder, mergedViewContext, linkedMetadata));
|
|
347
360
|
const ttl = linkResults && linkResults.cache?.ttl;
|
|
348
361
|
pageTtl = (0, import_shared_utils.shortestTtl)(ttl || void 0, pageTtl);
|
|
349
|
-
if (!pageStatus && linkResults) {
|
|
350
|
-
pageStatus = linkResults.status;
|
|
351
|
-
}
|
|
352
362
|
}
|
|
353
363
|
const linkedAssetContent = stringBuilder.toString();
|
|
354
364
|
if (linkedAssetContent.includes(lwrResourcesId)) {
|
|
@@ -370,7 +380,8 @@ var LwrViewRegistry = class {
|
|
|
370
380
|
return res;
|
|
371
381
|
res.push({
|
|
372
382
|
url: asset.src,
|
|
373
|
-
relative: (0, import_shared_utils.isRelative)(asset.src)
|
|
383
|
+
relative: (0, import_shared_utils.isRelative)(asset.src),
|
|
384
|
+
integrity: asset.integrity
|
|
374
385
|
});
|
|
375
386
|
return res;
|
|
376
387
|
}, []));
|
|
@@ -383,8 +394,7 @@ var LwrViewRegistry = class {
|
|
|
383
394
|
...viewRecord,
|
|
384
395
|
serverBundles: linkedMetadata.serverBundles
|
|
385
396
|
},
|
|
386
|
-
cache: {ttl: pageTtl}
|
|
387
|
-
status: pageStatus
|
|
397
|
+
cache: {ttl: pageTtl}
|
|
388
398
|
};
|
|
389
399
|
}
|
|
390
400
|
return {
|
|
@@ -395,8 +405,7 @@ var LwrViewRegistry = class {
|
|
|
395
405
|
moduleResources: [],
|
|
396
406
|
serverBundles: linkedMetadata.serverBundles
|
|
397
407
|
},
|
|
398
|
-
cache: {ttl: pageTtl}
|
|
399
|
-
status: pageStatus
|
|
408
|
+
cache: {ttl: pageTtl}
|
|
400
409
|
};
|
|
401
410
|
}
|
|
402
411
|
};
|
|
@@ -251,7 +251,7 @@ async function getHtmlResources(view, viewParams, resourceContext) {
|
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
}));
|
|
254
|
-
if (viewContainsLiveElements || serverDebug?.
|
|
254
|
+
if (viewContainsLiveElements || serverDebug?.warnings?.length) {
|
|
255
255
|
if ((0, import_shared_utils.isLocalDev)()) {
|
|
256
256
|
const localDevSpecifier = "lwr_local_dev/bootstrap";
|
|
257
257
|
rootComponents.push(localDevSpecifier);
|
|
@@ -278,7 +278,7 @@ async function getHtmlResources(view, viewParams, resourceContext) {
|
|
|
278
278
|
serverData,
|
|
279
279
|
...isAMD && {requiredModules: requiredAmdModules},
|
|
280
280
|
...isAMD && {preloadModules: viewPreloads.specifiers}
|
|
281
|
-
}, runtimeEnvironment, runtimeParams, serverDebug?.
|
|
281
|
+
}, runtimeEnvironment, runtimeParams, serverDebug?.warnings));
|
|
282
282
|
}
|
|
283
283
|
if (!isAMD && hmrEnabled) {
|
|
284
284
|
configResources.unshift((0, import_utils2.getViewHmrConfigurationResource)(view, viewMetadata));
|
|
@@ -31,9 +31,7 @@ var import_diagnostics = __toModule(require("@lwrjs/diagnostics"));
|
|
|
31
31
|
var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
|
|
32
32
|
function setPreloadModulesMeta(specifier, uri, integrity, groups, preloads) {
|
|
33
33
|
if (!uri) {
|
|
34
|
-
throw
|
|
35
|
-
description: import_diagnostics.descriptions.UNRESOLVABLE.PRELOAD_MODULE(specifier)
|
|
36
|
-
}, import_diagnostics.LwrUnresolvableError);
|
|
34
|
+
throw new import_diagnostics.LwrInvalidError(import_diagnostics.descriptions.INVALID.PRELOAD_MODULE(specifier));
|
|
37
35
|
}
|
|
38
36
|
const [removedVersion, version] = specifier.split("/v/");
|
|
39
37
|
const normalizedSpecifier = version === import_shared_utils.VERSION_NOT_PROVIDED ? removedVersion : specifier;
|
|
@@ -29,9 +29,10 @@ __export(exports, {
|
|
|
29
29
|
getViewBootstrapConfigurationResource: () => getViewBootstrapConfigurationResource,
|
|
30
30
|
getViewHmrConfigurationResource: () => getViewHmrConfigurationResource
|
|
31
31
|
});
|
|
32
|
+
var import_diagnostics = __toModule(require("@lwrjs/diagnostics"));
|
|
32
33
|
var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
|
|
33
34
|
var CONTENT_TYPE = "application/javascript";
|
|
34
|
-
function getViewBootstrapConfigurationResource(viewInfo, config, runtimeEnvironment, runtimeParams,
|
|
35
|
+
function getViewBootstrapConfigurationResource(viewInfo, config, runtimeEnvironment, runtimeParams, warnings) {
|
|
35
36
|
const {compat, debug, hmrEnabled, apiVersion, format} = runtimeEnvironment;
|
|
36
37
|
const isESM = format === "esm";
|
|
37
38
|
const defaultUrl = (0, import_shared_utils.getModuleUriPrefix)(runtimeEnvironment, runtimeParams);
|
|
@@ -53,6 +54,9 @@ function getViewBootstrapConfigurationResource(viewInfo, config, runtimeEnvironm
|
|
|
53
54
|
SSR: false,
|
|
54
55
|
...(0, import_shared_utils.buildEnvironmentContext)(runtimeParams)
|
|
55
56
|
};
|
|
57
|
+
let warnMessages = `console.group('Server-side rendering warnings:');`;
|
|
58
|
+
warnings?.forEach((warning) => warnMessages += `console.warn('${(0, import_diagnostics.stringifyError)(warning)}');`);
|
|
59
|
+
warnMessages += "console.groupEnd();";
|
|
56
60
|
const configString = [
|
|
57
61
|
"/* This script is generated */",
|
|
58
62
|
"/* Client Bootstrap configuration */",
|
|
@@ -64,7 +68,7 @@ function getViewBootstrapConfigurationResource(viewInfo, config, runtimeEnvironm
|
|
|
64
68
|
`globalThis.LWR = {...globalThis.LWR, env: ${JSON.stringify(lwrEnv)}};`,
|
|
65
69
|
`globalThis.process={...globalThis.process,env:{...globalThis.process?.env,...${JSON.stringify(nodeEnv)}}};`,
|
|
66
70
|
`globalThis.lwcRuntimeFlags = { ENABLE_MIXED_SHADOW_MODE: ${viewInfo.mixedMode}, ENABLE_WIRE_SYNC_EMIT: ${viewInfo.ssr} };`,
|
|
67
|
-
|
|
71
|
+
warnings?.length && warnMessages
|
|
68
72
|
].filter(Boolean).join("\n");
|
|
69
73
|
if (viewInfo.configAsSrc) {
|
|
70
74
|
const viewUrl = viewInfo.url || "/";
|
|
@@ -234,7 +234,7 @@ async function getHtmlResources(view, viewParams, resourceContext) {
|
|
|
234
234
|
importMetadata = await (0, import_shared_utils.toImportMetadata)(graph, importMetadata, moduleRegistry, runtimeEnvironment, runtimeParams);
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
-
if (viewContainsLiveElements || serverDebug?.
|
|
237
|
+
if (viewContainsLiveElements || serverDebug?.warnings?.length) {
|
|
238
238
|
configResources.unshift((0, import_utils2.getViewBootstrapConfigurationResource)({
|
|
239
239
|
id: view.id,
|
|
240
240
|
url: viewParams?.page?.url,
|
|
@@ -252,7 +252,7 @@ async function getHtmlResources(view, viewParams, resourceContext) {
|
|
|
252
252
|
serverData,
|
|
253
253
|
...isAMD && {requiredModules: requiredAmdModules},
|
|
254
254
|
...isAMD && {preloadModules: viewPreloads.specifiers}
|
|
255
|
-
}, runtimeEnvironment, runtimeParams, serverDebug?.
|
|
255
|
+
}, runtimeEnvironment, runtimeParams, serverDebug?.warnings));
|
|
256
256
|
}
|
|
257
257
|
if (!isAMD && hmrEnabled) {
|
|
258
258
|
configResources.unshift((0, import_utils2.getViewHmrConfigurationResource)(view, viewMetadata));
|
package/build/cjs/utils.cjs
CHANGED
|
@@ -112,8 +112,7 @@ function normalizeRenderedResult({
|
|
|
112
112
|
renderedView,
|
|
113
113
|
metadata,
|
|
114
114
|
options,
|
|
115
|
-
cache
|
|
116
|
-
status
|
|
115
|
+
cache
|
|
117
116
|
}) {
|
|
118
117
|
return {
|
|
119
118
|
renderedView,
|
|
@@ -127,16 +126,16 @@ function normalizeRenderedResult({
|
|
|
127
126
|
options: {
|
|
128
127
|
skipMetadataCollection: options ? options.skipMetadataCollection : false
|
|
129
128
|
},
|
|
130
|
-
cache: cache || {}
|
|
131
|
-
status
|
|
129
|
+
cache: cache || {}
|
|
132
130
|
};
|
|
133
131
|
}
|
|
134
132
|
function reduceSourceAssetReferences(assets) {
|
|
135
|
-
return assets.map(({url, tagName, override}) => {
|
|
133
|
+
return assets.map(({url, tagName, override, integrity}) => {
|
|
136
134
|
return {
|
|
137
135
|
url,
|
|
138
136
|
tagName,
|
|
139
|
-
override
|
|
137
|
+
override,
|
|
138
|
+
integrity
|
|
140
139
|
};
|
|
141
140
|
});
|
|
142
141
|
}
|
|
@@ -314,7 +313,7 @@ function generateLinkHeaders(assets, patterns) {
|
|
|
314
313
|
for (const filePattern of matchPatterns) {
|
|
315
314
|
const regex = new RegExp(filePattern);
|
|
316
315
|
if (regex.test(path)) {
|
|
317
|
-
matched = pattern.attributes;
|
|
316
|
+
matched = pattern.attributes || {};
|
|
318
317
|
break;
|
|
319
318
|
}
|
|
320
319
|
}
|
|
@@ -323,6 +322,9 @@ function generateLinkHeaders(assets, patterns) {
|
|
|
323
322
|
}
|
|
324
323
|
if (matched) {
|
|
325
324
|
assetConfig[path] = matched;
|
|
325
|
+
if (assetRef.integrity) {
|
|
326
|
+
assetConfig[path].integrity = assetRef.integrity;
|
|
327
|
+
}
|
|
326
328
|
}
|
|
327
329
|
}
|
|
328
330
|
return Object.keys(assetConfig).reduce((linkHeader, path) => {
|
|
@@ -147,11 +147,11 @@ var LwrViewHandler = class {
|
|
|
147
147
|
try {
|
|
148
148
|
return await routeHandlerFn({...viewRequest, locale, basePath, assetBasePath, uiBasePath}, {route, viewApi, ...paths}, routeHandlerOptions);
|
|
149
149
|
} catch (err) {
|
|
150
|
-
if (err instanceof import_diagnostics.
|
|
150
|
+
if (err instanceof import_diagnostics.LwrError) {
|
|
151
151
|
throw err;
|
|
152
152
|
}
|
|
153
153
|
const message = err instanceof Error ? err.message : String(err);
|
|
154
|
-
throw
|
|
154
|
+
throw new import_diagnostics.LwrApplicationError(import_diagnostics.descriptions.APPLICATION.ROUTE_HANDLER_ERROR(route.id, message));
|
|
155
155
|
}
|
|
156
156
|
});
|
|
157
157
|
if (response?.locale) {
|
package/build/es/index.d.ts
CHANGED
|
@@ -47,6 +47,7 @@ export declare class LwrViewRegistry implements ViewRegistry {
|
|
|
47
47
|
getViewDefinition(view: View, viewParams: ViewParams, runtimeEnvironment: RuntimeEnvironment, runtimeParams?: RuntimeParams, renderOptions?: RenderOptions): Promise<LinkedViewDefinition>;
|
|
48
48
|
private renderView;
|
|
49
49
|
private render;
|
|
50
|
+
private metadataCache;
|
|
50
51
|
private link;
|
|
51
52
|
}
|
|
52
53
|
//# sourceMappingURL=index.d.ts.map
|
package/build/es/index.js
CHANGED
|
@@ -4,7 +4,8 @@ import { generateViewNonce, getViewNonce, normalizeRenderOptions, normalizeRende
|
|
|
4
4
|
import { linkLwrResources } from './linkers/link-lwr-resources.js';
|
|
5
5
|
// TODO: investigate perf impact W-16056356
|
|
6
6
|
import { LRUCache } from 'lru-cache';
|
|
7
|
-
import {
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
import { LwrError, LwrServerError, descriptions, logger } from '@lwrjs/diagnostics';
|
|
8
9
|
export { LwrViewHandler } from './view-handler.js';
|
|
9
10
|
export class LwrViewRegistry {
|
|
10
11
|
constructor(context, globalConfig) {
|
|
@@ -19,6 +20,7 @@ export class LwrViewRegistry {
|
|
|
19
20
|
// Pending view definitions are tracked to prevent concurrent resolution of the same view.
|
|
20
21
|
// Subsequent requests for the same view will await the original promise.
|
|
21
22
|
this.pendingViewDefinitions = new InflightTasks();
|
|
23
|
+
this.metadataCache = new Map();
|
|
22
24
|
this.name = 'lwr-view-registry';
|
|
23
25
|
this.resourceRegistry = context.resourceRegistry;
|
|
24
26
|
this.runtimeEnvironment = context.runtimeEnvironment;
|
|
@@ -194,13 +196,16 @@ export class LwrViewRegistry {
|
|
|
194
196
|
const updatableViewParams = { ...viewParams };
|
|
195
197
|
generateViewNonce(updatableViewParams);
|
|
196
198
|
const pendingViewDefCacheKey = viewDefCacheKey + viewParamKey;
|
|
197
|
-
const
|
|
199
|
+
const pendingViewDefCacheKeyHashed = createHash('md5')
|
|
200
|
+
.update(pendingViewDefCacheKey)
|
|
201
|
+
.digest('hex');
|
|
202
|
+
const viewDefinition = await this.pendingViewDefinitions.execute(pendingViewDefCacheKeyHashed, () => getTracer().trace({
|
|
198
203
|
name: ViewSpan.RenderView,
|
|
199
204
|
attributes: {
|
|
200
205
|
view: view.id,
|
|
201
206
|
ssr: view.bootstrap?.ssr === true,
|
|
202
207
|
},
|
|
203
|
-
}, () => this.renderView(view, updatableViewParams, runtimeEnvironment, runtimeParams,
|
|
208
|
+
}, () => this.renderView(view, updatableViewParams, runtimeEnvironment, runtimeParams, pendingViewDefCacheKeyHashed, renderOptions)));
|
|
204
209
|
// Once the view is generated add the nonce to the response so it can be cached and then added to the headers
|
|
205
210
|
viewDefinition.nonce = getViewNonce(updatableViewParams);
|
|
206
211
|
const route = this.globalConfig.routes.find((r) => r.id === view.id);
|
|
@@ -225,18 +230,18 @@ export class LwrViewRegistry {
|
|
|
225
230
|
return viewDefinition;
|
|
226
231
|
}
|
|
227
232
|
catch (err) {
|
|
228
|
-
if (err instanceof
|
|
233
|
+
if (err instanceof LwrError) {
|
|
229
234
|
throw err;
|
|
230
235
|
}
|
|
231
236
|
logger.error(`Failed to get view definition "${view.id}"`);
|
|
232
237
|
logger.error(err);
|
|
233
238
|
const message = err instanceof Error ? err.message : String(err);
|
|
234
|
-
throw
|
|
239
|
+
throw new LwrServerError(descriptions.SERVER.UNEXPECTED_ERROR(message));
|
|
235
240
|
}
|
|
236
241
|
}
|
|
237
242
|
async renderView(view, viewParams, runtimeEnvironment, runtimeParams, viewCacheKey, renderOptions) {
|
|
238
243
|
const { id, contentTemplate, rootComponent, layoutTemplate } = view;
|
|
239
|
-
const lwrResourcesId = `__LWR_RESOURCES__${
|
|
244
|
+
const lwrResourcesId = `__LWR_RESOURCES__${viewCacheKey}`;
|
|
240
245
|
const renderedContent = await this.render({ id, contentTemplate, rootComponent }, { ...viewParams, lwr_resources: lwrResourcesId }, runtimeParams, runtimeEnvironment);
|
|
241
246
|
// normalize the renderOptions provided by the CompiledView content with the request options.
|
|
242
247
|
let normalizedRenderOptions = normalizeRenderOptions(this.runtimeEnvironment, renderedContent.options, renderOptions);
|
|
@@ -264,6 +269,10 @@ export class LwrViewRegistry {
|
|
|
264
269
|
lwr_resources: lwrResourcesId,
|
|
265
270
|
}, runtimeParams, runtimeEnvironment);
|
|
266
271
|
normalizedRenderOptions = normalizeRenderOptions(this.runtimeEnvironment, renderedLayout.options, normalizedRenderOptions);
|
|
272
|
+
const warnings = [
|
|
273
|
+
...(renderedContent.metadata.serverDebug?.warnings || []),
|
|
274
|
+
...(renderedLayout.metadata.serverDebug?.warnings || []),
|
|
275
|
+
];
|
|
267
276
|
const renderedViewDef = await this.link({
|
|
268
277
|
...renderedLayout,
|
|
269
278
|
// Rendered Layout view's immutability is a composite of the layouts mutability and the body's mutability
|
|
@@ -285,14 +294,10 @@ export class LwrViewRegistry {
|
|
|
285
294
|
...renderedContent.metadata.serverData,
|
|
286
295
|
...renderedLayout.metadata.serverData,
|
|
287
296
|
},
|
|
288
|
-
serverDebug: {
|
|
289
|
-
...renderedContent.metadata.serverDebug,
|
|
290
|
-
...renderedLayout.metadata.serverDebug,
|
|
291
|
-
},
|
|
297
|
+
serverDebug: { warnings },
|
|
292
298
|
serverBundles: renderedContent.metadata.serverBundles, // 1st pass of SSR
|
|
293
299
|
},
|
|
294
300
|
cache: renderedContent.cache,
|
|
295
|
-
status: renderedContent.status,
|
|
296
301
|
},
|
|
297
302
|
// Render Content now contains a layout
|
|
298
303
|
{
|
|
@@ -328,6 +333,8 @@ export class LwrViewRegistry {
|
|
|
328
333
|
...globalContext,
|
|
329
334
|
...compiledView.properties,
|
|
330
335
|
...viewParams,
|
|
336
|
+
// provides access to runtimeParams which are not overwritten by context/props/viewParams
|
|
337
|
+
runtimeParams: { ...runtimeParams },
|
|
331
338
|
}, runtimeEnvironment);
|
|
332
339
|
const normalizedResult = normalizeRenderedResult(result);
|
|
333
340
|
return {
|
|
@@ -350,9 +357,19 @@ export class LwrViewRegistry {
|
|
|
350
357
|
ssr: view.bootstrap?.ssr === true,
|
|
351
358
|
},
|
|
352
359
|
}, () => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
360
|
+
let linkedMetadata = renderedViewMetadata;
|
|
361
|
+
if (!skipMetadataCollection) {
|
|
362
|
+
// Create a unique key based on view ID and content hash
|
|
363
|
+
const contentHash = createHash('md5').update(renderedViewContent).digest('hex');
|
|
364
|
+
const cacheKey = `${view.id}:${contentHash}`;
|
|
365
|
+
if (this.metadataCache.has(cacheKey)) {
|
|
366
|
+
linkedMetadata = this.metadataCache.get(cacheKey);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
linkedMetadata = extractMetadataFromHtml(renderedViewContent, renderedViewMetadata, this.globalConfig);
|
|
370
|
+
this.metadataCache.set(cacheKey, linkedMetadata);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
356
373
|
const stringBuilder = createStringBuilder(renderedViewContent);
|
|
357
374
|
return { linkedMetadata, stringBuilder };
|
|
358
375
|
});
|
|
@@ -364,7 +381,6 @@ export class LwrViewRegistry {
|
|
|
364
381
|
};
|
|
365
382
|
// Note: this is the TTL for the page NOT for the view registry cache
|
|
366
383
|
let pageTtl = renderedView.cache.ttl;
|
|
367
|
-
let pageStatus = renderedView.status;
|
|
368
384
|
for (const viewTransformer of this.viewTransformers) {
|
|
369
385
|
// eslint-disable-next-line no-await-in-loop
|
|
370
386
|
const linkResults = await getTracer().trace({
|
|
@@ -376,10 +392,6 @@ export class LwrViewRegistry {
|
|
|
376
392
|
// Keep track of the shortest TTL from each view transformer (e.g. lwcSsrViewTransformer)
|
|
377
393
|
const ttl = linkResults && linkResults.cache?.ttl;
|
|
378
394
|
pageTtl = shortestTtl(ttl || undefined, pageTtl);
|
|
379
|
-
// Set the status info, if it doesn't already exist
|
|
380
|
-
if (!pageStatus && linkResults) {
|
|
381
|
-
pageStatus = linkResults.status;
|
|
382
|
-
}
|
|
383
395
|
}
|
|
384
396
|
const linkedAssetContent = stringBuilder.toString();
|
|
385
397
|
// Link LWR related resources
|
|
@@ -406,6 +418,7 @@ export class LwrViewRegistry {
|
|
|
406
418
|
res.push({
|
|
407
419
|
url: asset.src,
|
|
408
420
|
relative: isRelative(asset.src),
|
|
421
|
+
integrity: asset.integrity,
|
|
409
422
|
});
|
|
410
423
|
return res;
|
|
411
424
|
}, []));
|
|
@@ -420,7 +433,6 @@ export class LwrViewRegistry {
|
|
|
420
433
|
serverBundles: linkedMetadata.serverBundles,
|
|
421
434
|
},
|
|
422
435
|
cache: { ttl: pageTtl },
|
|
423
|
-
status: pageStatus,
|
|
424
436
|
};
|
|
425
437
|
}
|
|
426
438
|
return {
|
|
@@ -432,7 +444,6 @@ export class LwrViewRegistry {
|
|
|
432
444
|
serverBundles: linkedMetadata.serverBundles,
|
|
433
445
|
},
|
|
434
446
|
cache: { ttl: pageTtl },
|
|
435
|
-
status: pageStatus,
|
|
436
447
|
};
|
|
437
448
|
}
|
|
438
449
|
}
|
|
@@ -290,7 +290,7 @@ export async function getHtmlResources(view, viewParams, resourceContext) {
|
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
}));
|
|
293
|
-
if (viewContainsLiveElements || serverDebug?.
|
|
293
|
+
if (viewContainsLiveElements || serverDebug?.warnings?.length) {
|
|
294
294
|
if (isLocalDev()) {
|
|
295
295
|
// ADD the client-side bootstrap module and mapping for local-dev
|
|
296
296
|
const localDevSpecifier = 'lwr_local_dev/bootstrap';
|
|
@@ -320,7 +320,7 @@ export async function getHtmlResources(view, viewParams, resourceContext) {
|
|
|
320
320
|
...(isAMD && { requiredModules: requiredAmdModules }),
|
|
321
321
|
// in AMD we need to tell the loader what modules we are preloading
|
|
322
322
|
...(isAMD && { preloadModules: viewPreloads.specifiers }),
|
|
323
|
-
}, runtimeEnvironment, runtimeParams, serverDebug?.
|
|
323
|
+
}, runtimeEnvironment, runtimeParams, serverDebug?.warnings));
|
|
324
324
|
}
|
|
325
325
|
if (!isAMD && hmrEnabled) {
|
|
326
326
|
configResources.unshift(getViewHmrConfigurationResource(view, viewMetadata));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LwrInvalidError, descriptions, logger } from '@lwrjs/diagnostics';
|
|
2
2
|
import { explodeSpecifier, getGroupName, getVersionedModuleId, normalizeVersionToUri, VERSION_NOT_PROVIDED, getSpecifier, isGroupie, } from '@lwrjs/shared-utils';
|
|
3
3
|
/**
|
|
4
4
|
* keeps track of preloadModules metadata
|
|
@@ -6,9 +6,7 @@ import { explodeSpecifier, getGroupName, getVersionedModuleId, normalizeVersionT
|
|
|
6
6
|
export function setPreloadModulesMeta(specifier, uri, integrity, groups, preloads) {
|
|
7
7
|
// Throw a very specific error if we get this back and the uri property is not set
|
|
8
8
|
if (!uri) {
|
|
9
|
-
throw
|
|
10
|
-
description: descriptions.UNRESOLVABLE.PRELOAD_MODULE(specifier),
|
|
11
|
-
}, LwrUnresolvableError);
|
|
9
|
+
throw new LwrInvalidError(descriptions.INVALID.PRELOAD_MODULE(specifier));
|
|
12
10
|
}
|
|
13
11
|
// We need to support version-less preloadModules, including version-less rootComponents.
|
|
14
12
|
// Removing the "/v/version_not_provided" hack from the preloadModules specifier
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ClientBootstrapConfig, RuntimeEnvironment, RuntimeParams, ResourceDefinition, RenderedViewMetadata, CustomElementReference, View, ViewInfo, FlattenedModuleGraphs } from '@lwrjs/types';
|
|
2
|
-
export declare function getViewBootstrapConfigurationResource(viewInfo: ViewInfo, config: ClientBootstrapConfig, runtimeEnvironment: RuntimeEnvironment, runtimeParams: RuntimeParams,
|
|
2
|
+
export declare function getViewBootstrapConfigurationResource(viewInfo: ViewInfo, config: ClientBootstrapConfig, runtimeEnvironment: RuntimeEnvironment, runtimeParams: RuntimeParams, warnings?: (Error | string)[]): ResourceDefinition;
|
|
3
3
|
export declare function getViewHmrConfigurationResource(view: View, viewMetadata: RenderedViewMetadata): ResourceDefinition;
|
|
4
4
|
export declare function flattenCustomElements(arr: CustomElementReference[], isSSR?: boolean): CustomElementReference[];
|
|
5
5
|
export declare function getBundleIntegrity(bootstrapModuleGraph: FlattenedModuleGraphs, versionedSpecifier: string): string | undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { stringifyError } from '@lwrjs/diagnostics';
|
|
1
2
|
import { buildEnvironmentContext, getMappingUriPrefix, getModuleUriPrefix, getClientBootstrapConfigurationUri, hashContent, } from '@lwrjs/shared-utils';
|
|
2
3
|
const CONTENT_TYPE = 'application/javascript';
|
|
3
|
-
export function getViewBootstrapConfigurationResource(viewInfo, config, runtimeEnvironment, runtimeParams,
|
|
4
|
+
export function getViewBootstrapConfigurationResource(viewInfo, config, runtimeEnvironment, runtimeParams, warnings) {
|
|
4
5
|
const { compat, debug, hmrEnabled, apiVersion, format } = runtimeEnvironment;
|
|
5
6
|
const isESM = format === 'esm';
|
|
6
7
|
const defaultUrl = getModuleUriPrefix(runtimeEnvironment, runtimeParams);
|
|
@@ -27,6 +28,9 @@ export function getViewBootstrapConfigurationResource(viewInfo, config, runtimeE
|
|
|
27
28
|
// Used by `lwr/environment`
|
|
28
29
|
...buildEnvironmentContext(runtimeParams),
|
|
29
30
|
};
|
|
31
|
+
let warnMessages = `console.group('Server-side rendering warnings:');`;
|
|
32
|
+
warnings?.forEach((warning) => (warnMessages += `console.warn('${stringifyError(warning)}');`));
|
|
33
|
+
warnMessages += 'console.groupEnd();';
|
|
30
34
|
const configString = [
|
|
31
35
|
'/* This script is generated */',
|
|
32
36
|
'/* Client Bootstrap configuration */',
|
|
@@ -39,7 +43,7 @@ export function getViewBootstrapConfigurationResource(viewInfo, config, runtimeE
|
|
|
39
43
|
`globalThis.process={...globalThis.process,env:{...globalThis.process?.env,...${JSON.stringify(nodeEnv)}}};`,
|
|
40
44
|
// TODO: evaluate moving these to app layer
|
|
41
45
|
`globalThis.lwcRuntimeFlags = { ENABLE_MIXED_SHADOW_MODE: ${viewInfo.mixedMode}, ENABLE_WIRE_SYNC_EMIT: ${viewInfo.ssr} };`,
|
|
42
|
-
|
|
46
|
+
warnings?.length && warnMessages,
|
|
43
47
|
]
|
|
44
48
|
.filter(Boolean)
|
|
45
49
|
.join('\n');
|
|
@@ -262,7 +262,7 @@ export async function getHtmlResources(view, viewParams, resourceContext) {
|
|
|
262
262
|
importMetadata = await toImportMetadata(graph, importMetadata, moduleRegistry, runtimeEnvironment, runtimeParams);
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
|
-
if (viewContainsLiveElements || serverDebug?.
|
|
265
|
+
if (viewContainsLiveElements || serverDebug?.warnings?.length) {
|
|
266
266
|
// ADD configuration of the bootstrapModule
|
|
267
267
|
configResources.unshift(getViewBootstrapConfigurationResource({
|
|
268
268
|
id: view.id,
|
|
@@ -282,7 +282,7 @@ export async function getHtmlResources(view, viewParams, resourceContext) {
|
|
|
282
282
|
...(isAMD && { requiredModules: requiredAmdModules }),
|
|
283
283
|
// in AMD we need to tell the loader what modules we are preloading
|
|
284
284
|
...(isAMD && { preloadModules: viewPreloads.specifiers }),
|
|
285
|
-
}, runtimeEnvironment, runtimeParams, serverDebug?.
|
|
285
|
+
}, runtimeEnvironment, runtimeParams, serverDebug?.warnings));
|
|
286
286
|
}
|
|
287
287
|
if (!isAMD && hmrEnabled) {
|
|
288
288
|
configResources.unshift(getViewHmrConfigurationResource(view, viewMetadata));
|
package/build/es/utils.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AssetReference, JsonCompatible, LinkedViewDefinition, LwrErrorRoute, LwrRoute, ModuleBundler, ModuleId, ModuleJson, ModuleRegistry, NormalizedRenderingResult, PublicModuleRegistry, RenderOptions, RenderedAssetReference, RenderingResult, ResourceDefinition, RouteHandlerViewResponse, RuntimeEnvironment, RuntimeParams, ViewModuleResourceContext, ViewPageContext, ViewParams, ViewRequest, ViewResponse, ViewDefinitionResponse, View } from '@lwrjs/types';
|
|
2
2
|
export type HTMLResource = Partial<ResourceDefinition>;
|
|
3
3
|
export declare function generateHtmlTag(definition: HTMLResource): Promise<string>;
|
|
4
|
-
export declare function normalizeRenderedResult({ renderedView, metadata, options, cache,
|
|
4
|
+
export declare function normalizeRenderedResult({ renderedView, metadata, options, cache, }: RenderingResult): NormalizedRenderingResult;
|
|
5
5
|
export declare function reduceSourceAssetReferences(assets: AssetReference[]): RenderedAssetReference[];
|
|
6
6
|
export declare function normalizeRenderOptions(runtimeEnvironment: RuntimeEnvironment, overrideRenderOptions?: RenderOptions, baseRenderOptions?: RenderOptions): Required<RenderOptions>;
|
|
7
7
|
export declare function generatePageContext({ requestPath: url }: ViewRequest, { id, contentTemplate, properties }: LwrRoute | LwrErrorRoute, runtimeParams: RuntimeParams): JsonCompatible<ViewPageContext>;
|
package/build/es/utils.js
CHANGED
|
@@ -66,7 +66,7 @@ export async function generateHtmlTag(definition) {
|
|
|
66
66
|
}
|
|
67
67
|
throw new Error(`Invalid external Resource Definition: missing a "src": "${definition.specifier}"`);
|
|
68
68
|
}
|
|
69
|
-
export function normalizeRenderedResult({ renderedView, metadata, options, cache,
|
|
69
|
+
export function normalizeRenderedResult({ renderedView, metadata, options, cache, }) {
|
|
70
70
|
return {
|
|
71
71
|
renderedView,
|
|
72
72
|
metadata: {
|
|
@@ -80,15 +80,15 @@ export function normalizeRenderedResult({ renderedView, metadata, options, cache
|
|
|
80
80
|
skipMetadataCollection: options ? options.skipMetadataCollection : false,
|
|
81
81
|
},
|
|
82
82
|
cache: cache || {},
|
|
83
|
-
status,
|
|
84
83
|
};
|
|
85
84
|
}
|
|
86
85
|
export function reduceSourceAssetReferences(assets) {
|
|
87
|
-
return assets.map(({ url, tagName, override }) => {
|
|
86
|
+
return assets.map(({ url, tagName, override, integrity }) => {
|
|
88
87
|
return {
|
|
89
88
|
url,
|
|
90
89
|
tagName,
|
|
91
90
|
override,
|
|
91
|
+
integrity,
|
|
92
92
|
};
|
|
93
93
|
});
|
|
94
94
|
}
|
|
@@ -300,7 +300,7 @@ export function generateLinkHeaders(assets, patterns) {
|
|
|
300
300
|
for (const filePattern of matchPatterns) {
|
|
301
301
|
const regex = new RegExp(filePattern);
|
|
302
302
|
if (regex.test(path)) {
|
|
303
|
-
matched = pattern.attributes;
|
|
303
|
+
matched = pattern.attributes || {};
|
|
304
304
|
break;
|
|
305
305
|
}
|
|
306
306
|
}
|
|
@@ -309,6 +309,10 @@ export function generateLinkHeaders(assets, patterns) {
|
|
|
309
309
|
}
|
|
310
310
|
if (matched) {
|
|
311
311
|
assetConfig[path] = matched;
|
|
312
|
+
if (assetRef.integrity) {
|
|
313
|
+
// always add the integrity attribute, if it exists
|
|
314
|
+
assetConfig[path].integrity = assetRef.integrity;
|
|
315
|
+
}
|
|
312
316
|
}
|
|
313
317
|
}
|
|
314
318
|
// Use the assetConfig to construct the Link Header
|
package/build/es/view-handler.js
CHANGED
|
@@ -2,7 +2,7 @@ import { resolve } from 'path';
|
|
|
2
2
|
import { normalizeResourcePath, shortestTtl } from '@lwrjs/shared-utils';
|
|
3
3
|
import { getTracer, ViewSpan } from '@lwrjs/instrumentation';
|
|
4
4
|
import { generateHtmlTag, generateLinkHeaders, generatePageContext, isViewResponse, isViewDefinitionResponse, toJsonFormat, } from './utils.js';
|
|
5
|
-
import {
|
|
5
|
+
import { LwrApplicationError, LwrError, descriptions } from '@lwrjs/diagnostics';
|
|
6
6
|
export class LwrViewHandler {
|
|
7
7
|
constructor(context, globalConfig) {
|
|
8
8
|
this.globalConfig = globalConfig;
|
|
@@ -166,11 +166,11 @@ export class LwrViewHandler {
|
|
|
166
166
|
return await routeHandlerFn({ ...viewRequest, locale, basePath, assetBasePath, uiBasePath }, { route: route, viewApi, ...paths }, routeHandlerOptions);
|
|
167
167
|
}
|
|
168
168
|
catch (err) {
|
|
169
|
-
if (err instanceof
|
|
169
|
+
if (err instanceof LwrError) {
|
|
170
170
|
throw err;
|
|
171
171
|
}
|
|
172
172
|
const message = err instanceof Error ? err.message : String(err);
|
|
173
|
-
throw
|
|
173
|
+
throw new LwrApplicationError(descriptions.APPLICATION.ROUTE_HANDLER_ERROR(route.id, message));
|
|
174
174
|
}
|
|
175
175
|
});
|
|
176
176
|
// if the locale was returned by the route handler update the runtime time params
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.17.2-alpha.
|
|
7
|
+
"version": "0.17.2-alpha.10",
|
|
8
8
|
"homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
"build": "tsc -b"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@lwrjs/app-service": "0.17.2-alpha.
|
|
37
|
-
"@lwrjs/diagnostics": "0.17.2-alpha.
|
|
38
|
-
"@lwrjs/instrumentation": "0.17.2-alpha.
|
|
39
|
-
"@lwrjs/shared-utils": "0.17.2-alpha.
|
|
36
|
+
"@lwrjs/app-service": "0.17.2-alpha.10",
|
|
37
|
+
"@lwrjs/diagnostics": "0.17.2-alpha.10",
|
|
38
|
+
"@lwrjs/instrumentation": "0.17.2-alpha.10",
|
|
39
|
+
"@lwrjs/shared-utils": "0.17.2-alpha.10",
|
|
40
40
|
"lru-cache": "^10.4.3"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@lwrjs/types": "0.17.2-alpha.
|
|
43
|
+
"@lwrjs/types": "0.17.2-alpha.10"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=18.0.0"
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"volta": {
|
|
49
49
|
"extends": "../../../package.json"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "3938f97f31973d7953bb61cff8388c71980c22c7"
|
|
52
52
|
}
|