@lwrjs/view-registry 0.15.0-alpha.2 → 0.15.0-alpha.20

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.
@@ -51,7 +51,12 @@ var LwrViewRegistry = class {
51
51
  this.globalData = context.globalData;
52
52
  this.appEmitter = context.appEmitter;
53
53
  const observer = context.appObserver;
54
- this.viewDefinitions = new import_lru_cache.LRUCache({max: parseInt(process.env.VIEW_CACHE_SIZE || "500", 10)});
54
+ this.viewDefinitions = new import_lru_cache.LRUCache({
55
+ max: parseInt(process.env.VIEW_CACHE_SIZE ?? "500", 10),
56
+ dispose: (_value, key) => {
57
+ import_diagnostics.logger.verbose(`View evicted from cache ${key}`);
58
+ }
59
+ });
55
60
  observer.onViewSourceChange(({payload}) => this.onViewSourceChange(payload));
56
61
  observer.onModuleDefinitionChange(({payload}) => this.onModuleDefinitionChange(payload));
57
62
  observer.onAssetSourceChange(({payload}) => this.onAssetSourceChange(payload));
@@ -173,7 +178,8 @@ var LwrViewRegistry = class {
173
178
  }
174
179
  return false;
175
180
  }
176
- async getViewDefinition(view, viewParams, runtimeEnvironment, runtimeParams, renderOptions) {
181
+ async getViewDefinition(view, viewParams, runtimeEnvironment, runtimeParams = {requestCache: {}}, renderOptions) {
182
+ runtimeParams.requestCache = runtimeParams.requestCache ?? {};
177
183
  try {
178
184
  const {freezeAssets, viewParamCacheKey} = (0, import_utils.normalizeRenderOptions)(this.runtimeEnvironment, renderOptions);
179
185
  const viewDefCacheKey = (0, import_shared_utils.getCacheKeyFromJson)({
@@ -187,8 +193,8 @@ var LwrViewRegistry = class {
187
193
  import_diagnostics.logger.debug(`[view-registry][getViewDefinition] viewDefCacheKey=${viewDefCacheKey}`);
188
194
  const viewParamKey = viewParamCacheKey ? (0, import_shared_utils.getCacheKeyFromJson)(viewParamCacheKey) : (0, import_shared_utils.getCacheKeyFromJson)(viewParams);
189
195
  import_diagnostics.logger.debug(`[view-registry][getViewDefinition] viewParamKey=${viewParamKey}`);
190
- if (this.viewDefinitions.has(viewDefCacheKey)) {
191
- const viewDefinition2 = this.viewDefinitions.get(viewDefCacheKey);
196
+ if (this.viewDefinitions.has(viewDefCacheKey) || runtimeParams.requestCache[viewDefCacheKey]) {
197
+ const viewDefinition2 = this.viewDefinitions.get(viewDefCacheKey) || runtimeParams.requestCache[viewDefCacheKey];
192
198
  if (viewDefinition2 && viewDefinition2.paramKey === viewParamKey && viewDefinition2.viewDefinition.immutable) {
193
199
  return viewDefinition2.viewDefinition;
194
200
  }
@@ -209,11 +215,12 @@ var LwrViewRegistry = class {
209
215
  const maxTtl = maxViewCacheTtl && parseInt(maxViewCacheTtl, 10);
210
216
  const leastTtl = (0, import_shared_utils.shortestTtl)(viewDefinition.cache?.ttl, route?.cache?.ttl, maxTtl);
211
217
  const ttl = leastTtl !== void 0 ? leastTtl === 0 ? 10 : leastTtl * 1e3 : void 0;
212
- this.viewDefinitions.set(viewDefCacheKey, {
213
- view,
214
- viewDefinition,
215
- paramKey: viewParamKey
216
- }, {ttl});
218
+ const viewDefCacheEntry = {view, viewDefinition, paramKey: viewParamKey};
219
+ if (view.bootstrap?.includeCookiesForSSR) {
220
+ runtimeParams.requestCache[viewDefCacheKey] = viewDefCacheEntry;
221
+ } else {
222
+ this.viewDefinitions.set(viewDefCacheKey, viewDefCacheEntry, {ttl});
223
+ }
217
224
  return viewDefinition;
218
225
  } catch (err) {
219
226
  if (err instanceof import_diagnostics.DiagnosticsError) {
@@ -225,10 +232,10 @@ var LwrViewRegistry = class {
225
232
  throw (0, import_diagnostics.createSingleDiagnosticError)({description: import_diagnostics.descriptions.SERVER.UNEXPECTED_ERROR(message)}, import_diagnostics.LwrServerError);
226
233
  }
227
234
  }
228
- async renderView(view, viewParams, runtimeEnvironment, runtimeParams = {}, renderOptions) {
235
+ async renderView(view, viewParams, runtimeEnvironment, runtimeParams, renderOptions) {
229
236
  const {id, contentTemplate, rootComponent, layoutTemplate} = view;
230
237
  const lwrResourcesId = `__LWR_RESOURCES__${Date.now()}`;
231
- const renderedContent = await this.render({id, contentTemplate, rootComponent}, {...viewParams, lwr_resources: lwrResourcesId}, runtimeParams, runtimeEnvironment, renderOptions);
238
+ const renderedContent = await this.render({id, contentTemplate, rootComponent}, {...viewParams, lwr_resources: lwrResourcesId}, runtimeParams, runtimeEnvironment);
232
239
  let normalizedRenderOptions = (0, import_utils.normalizeRenderOptions)(this.runtimeEnvironment, renderedContent.options, renderOptions);
233
240
  const layout = layoutTemplate || renderedContent.compiledView.layoutTemplate;
234
241
  if (!layout) {
@@ -274,7 +281,8 @@ var LwrViewRegistry = class {
274
281
  ...renderedLayout.metadata.serverDebug
275
282
  }
276
283
  },
277
- cache: renderedContent.cache
284
+ cache: renderedContent.cache,
285
+ redirect: renderedContent.redirect
278
286
  }, {
279
287
  view: {...view, layoutTemplate: layoutTemplatePath},
280
288
  viewParams,
@@ -286,7 +294,7 @@ var LwrViewRegistry = class {
286
294
  });
287
295
  return renderedViewDef;
288
296
  }
289
- async render(viewId, viewParams, runtimeParams, runtimeEnvironment, renderOptions) {
297
+ async render(viewId, viewParams, runtimeParams, runtimeEnvironment) {
290
298
  const globalContext = this.globalData;
291
299
  const {id, rootComponent, contentTemplate} = viewId;
292
300
  const compiledView = await this.getView({id, contentTemplate, rootComponent});
@@ -354,7 +362,8 @@ var LwrViewRegistry = class {
354
362
  assetReferences: (0, import_utils.reduceSourceAssetReferences)(linkedMetadata.assetReferences),
355
363
  ...viewRecord
356
364
  },
357
- cache: {ttl: pageTtl}
365
+ cache: {ttl: pageTtl},
366
+ redirect: renderedView.redirect
358
367
  };
359
368
  }
360
369
  return {
@@ -364,7 +373,8 @@ var LwrViewRegistry = class {
364
373
  assetReferences: (0, import_utils.reduceSourceAssetReferences)(linkedMetadata.assetReferences),
365
374
  moduleResources: []
366
375
  },
367
- cache: {ttl: pageTtl}
376
+ cache: {ttl: pageTtl},
377
+ redirect: renderedView.redirect
368
378
  };
369
379
  }
370
380
  };
@@ -231,7 +231,8 @@ async function getHtmlResources(view, viewParams, resourceContext) {
231
231
  url: viewParams?.page?.url,
232
232
  configAsSrc: view.bootstrap?.configAsSrc || false,
233
233
  mixedMode: view.bootstrap?.mixedMode || false,
234
- nonce: viewParams?.page?.nonce
234
+ nonce: viewParams?.page?.nonce,
235
+ ssr: view.bootstrap?.ssr || false
235
236
  }, {
236
237
  appId: appIdentity.appName,
237
238
  bootstrapModule: versionedSpecifier,
@@ -41,7 +41,7 @@ async function linkLwrResources(source, view, viewParams, cxt) {
41
41
  return LEGACY_LOADER ? (0, import_legacy_view_bootstrap.getHtmlResources)(view, viewParams, resourceContext) : (0, import_view_bootstrap.getHtmlResources)(view, viewParams, resourceContext);
42
42
  });
43
43
  return {
44
- renderedView: source.replace(lwrResourcesId, partial),
44
+ renderedView: source.replace(lwrResourcesId, () => partial),
45
45
  viewRecord
46
46
  };
47
47
  }
@@ -63,7 +63,7 @@ function getViewBootstrapConfigurationResource(viewInfo, config, runtimeEnvironm
63
63
  })});`,
64
64
  `globalThis.LWR = {...globalThis.LWR, env: ${JSON.stringify(lwrEnv)}};`,
65
65
  `globalThis.process={...globalThis.process,env:{...globalThis.process?.env,...${JSON.stringify(nodeEnv)}}};`,
66
- `globalThis.lwcRuntimeFlags = { ENABLE_MIXED_SHADOW_MODE: ${viewInfo.mixedMode} };`,
66
+ `globalThis.lwcRuntimeFlags = { ENABLE_MIXED_SHADOW_MODE: ${viewInfo.mixedMode}, ENABLE_WIRE_SYNC_EMIT: ${viewInfo.ssr} };`,
67
67
  debug && debugMessage && `console.error(${JSON.stringify(debugMessage)});`
68
68
  ].filter(Boolean).join("\n");
69
69
  if (viewInfo.configAsSrc) {
@@ -206,7 +206,8 @@ async function getHtmlResources(view, viewParams, resourceContext) {
206
206
  url: viewParams?.page?.url,
207
207
  configAsSrc: view.bootstrap?.configAsSrc || false,
208
208
  mixedMode: view.bootstrap?.mixedMode || false,
209
- nonce: viewParams?.page?.nonce
209
+ nonce: viewParams?.page?.nonce,
210
+ ssr: view.bootstrap?.ssr || false
210
211
  }, {
211
212
  appId: appIdentity.appName,
212
213
  bootstrapModule: versionedSpecifier,
@@ -27,11 +27,13 @@ __export(exports, {
27
27
  addExternalScriptNonce: () => addExternalScriptNonce,
28
28
  createJsonModule: () => createJsonModule,
29
29
  generateHtmlTag: () => generateHtmlTag,
30
+ generateLinkHeaders: () => generateLinkHeaders,
30
31
  generatePageContext: () => generatePageContext,
31
32
  generateViewNonce: () => generateViewNonce,
32
33
  getModuleResource: () => getModuleResource,
33
34
  getModuleResourceByUri: () => getModuleResourceByUri,
34
35
  getViewNonce: () => getViewNonce,
36
+ isViewDefinitionResponse: () => isViewDefinitionResponse,
35
37
  isViewResponse: () => isViewResponse,
36
38
  normalizeRenderOptions: () => normalizeRenderOptions,
37
39
  normalizeRenderedResult: () => normalizeRenderedResult,
@@ -108,7 +110,8 @@ function normalizeRenderedResult({
108
110
  renderedView,
109
111
  metadata,
110
112
  options,
111
- cache
113
+ cache,
114
+ redirect
112
115
  }) {
113
116
  return {
114
117
  renderedView,
@@ -121,7 +124,8 @@ function normalizeRenderedResult({
121
124
  options: {
122
125
  skipMetadataCollection: options ? options.skipMetadataCollection : false
123
126
  },
124
- cache: cache || {}
127
+ cache: cache || {},
128
+ redirect
125
129
  };
126
130
  }
127
131
  function reduceSourceAssetReferences(assets) {
@@ -154,6 +158,9 @@ function generatePageContext({requestPath: url}, {id, contentTemplate, propertie
154
158
  function isViewResponse(response) {
155
159
  return response.body !== void 0;
156
160
  }
161
+ function isViewDefinitionResponse(response) {
162
+ return response.view !== void 0;
163
+ }
157
164
  async function toJsonFormat(viewRequest, viewDefinition, route, runtimeEnvironment, runtimeParams, moduleRegistry) {
158
165
  const {viewRecord} = viewDefinition;
159
166
  const {bootstrap, id: appName} = route;
@@ -291,3 +298,23 @@ function addExternalScriptNonce(def, nonce) {
291
298
  def.nonce = nonce;
292
299
  }
293
300
  }
301
+ function generateLinkHeaders(assets, patterns) {
302
+ const assetConfig = {};
303
+ for (let i = 0; i < patterns.length; i++) {
304
+ const pattern = patterns[i].match;
305
+ assets.forEach((asset) => {
306
+ const path = (asset.override?.uri || asset.url).split("?")[0];
307
+ if (path.endsWith(pattern)) {
308
+ assetConfig[path] = patterns[i].attributes;
309
+ }
310
+ });
311
+ }
312
+ return Object.keys(assetConfig).reduce((linkHeader, path) => {
313
+ linkHeader = `${linkHeader ? linkHeader + ", " : ""}<${path}>`;
314
+ const properties = assetConfig[path];
315
+ for (const prop in properties) {
316
+ linkHeader += properties[prop] !== "" ? `; ${prop}=${properties[prop]}` : `; ${prop}`;
317
+ }
318
+ return linkHeader;
319
+ }, "");
320
+ }
@@ -46,9 +46,11 @@ var LwrViewHandler = class {
46
46
  }
47
47
  const {view, viewParams, renderOptions} = normalizeViewProperties(viewRequest, response, route, this.globalConfig, runtimeParams);
48
48
  const viewDefinition2 = await this.viewRegistry.getViewDefinition(view, viewParams, runtimeEnvironment, runtimeParams, renderOptions);
49
+ const link2 = !!route?.bootstrap?.preloadResources?.patterns?.length && (0, import_utils.generateLinkHeaders)(viewDefinition2.viewRecord.assetReferences || [], route?.bootstrap?.preloadResources?.patterns || []);
49
50
  return {
50
51
  ...response,
51
52
  body: viewDefinition2.renderedView,
53
+ ...!!link2 && {headers: {link: link2}},
52
54
  metadata: {
53
55
  viewDefinition: viewDefinition2
54
56
  },
@@ -56,12 +58,14 @@ var LwrViewHandler = class {
56
58
  };
57
59
  }
58
60
  const viewDefinition = await this.getDefaultRouteViewDefinition(viewRequest, route, runtimeEnvironment, runtimeParams);
61
+ const link = !!route?.bootstrap?.preloadResources?.patterns?.length && (0, import_utils.generateLinkHeaders)(viewDefinition.viewRecord.assetReferences || [], route.bootstrap?.preloadResources?.patterns || []);
59
62
  return {
60
63
  body: viewDefinition.renderedView,
61
64
  metadata: {
62
65
  viewDefinition
63
66
  },
64
- cache: viewDefinition.cache
67
+ cache: viewDefinition.cache,
68
+ ...!!link && {headers: {link}}
65
69
  };
66
70
  }
67
71
  async getViewJson(viewRequest, route, runtimeEnvironment, runtimeParams = {}) {
@@ -153,6 +157,19 @@ var LwrViewHandler = class {
153
157
  if (response?.locale) {
154
158
  runtimeParams.locale = response.locale;
155
159
  }
160
+ if ((0, import_utils.isViewDefinitionResponse)(response)) {
161
+ let viewDefintionMeta = response.metadata?.viewDefinition;
162
+ if (!viewDefintionMeta) {
163
+ viewDefintionMeta = (await viewApi.getViewResponse(response.view, response.viewParams, response.renderOptions)).metadata?.viewDefinition;
164
+ }
165
+ const link = !!route?.bootstrap?.preloadResources?.patterns?.length && (0, import_utils.generateLinkHeaders)(viewDefintionMeta?.viewRecord.assetReferences || [], route?.bootstrap?.preloadResources?.patterns || []);
166
+ if (link) {
167
+ response.headers = {
168
+ ...response.headers,
169
+ link
170
+ };
171
+ }
172
+ }
156
173
  return response;
157
174
  }
158
175
  getBoundApi(viewRequest, route, runtimeEnvironment, runtimeParams) {
package/build/es/index.js CHANGED
@@ -30,7 +30,12 @@ export class LwrViewRegistry {
30
30
  this.globalData = context.globalData;
31
31
  this.appEmitter = context.appEmitter;
32
32
  const observer = context.appObserver;
33
- this.viewDefinitions = new LRUCache({ max: parseInt(process.env.VIEW_CACHE_SIZE || '500', 10) });
33
+ this.viewDefinitions = new LRUCache({
34
+ max: parseInt(process.env.VIEW_CACHE_SIZE ?? '500', 10),
35
+ dispose: (_value, key) => {
36
+ logger.verbose(`View evicted from cache ${key}`);
37
+ },
38
+ });
34
39
  // Observers for cached entries external dependencies -- view templates, modules, and assets
35
40
  observer.onViewSourceChange(({ payload }) => this.onViewSourceChange(payload));
36
41
  observer.onModuleDefinitionChange(({ payload }) => this.onModuleDefinitionChange(payload));
@@ -168,7 +173,8 @@ export class LwrViewRegistry {
168
173
  }
169
174
  return false;
170
175
  }
171
- async getViewDefinition(view, viewParams, runtimeEnvironment, runtimeParams, renderOptions) {
176
+ async getViewDefinition(view, viewParams, runtimeEnvironment, runtimeParams = { requestCache: {} }, renderOptions) {
177
+ runtimeParams.requestCache = runtimeParams.requestCache ?? {};
172
178
  try {
173
179
  const { freezeAssets, viewParamCacheKey } = normalizeRenderOptions(this.runtimeEnvironment, renderOptions);
174
180
  const viewDefCacheKey = getCacheKeyFromJson({
@@ -189,8 +195,9 @@ export class LwrViewRegistry {
189
195
  : getCacheKeyFromJson(viewParams);
190
196
  logger.debug(`[view-registry][getViewDefinition] viewParamKey=${viewParamKey}`);
191
197
  // important: cache key does not include the unbounded viewParams
192
- if (this.viewDefinitions.has(viewDefCacheKey)) {
193
- const viewDefinition = this.viewDefinitions.get(viewDefCacheKey);
198
+ // check the cross-request cache, then the request cache (which is cleaned up after each base doc request)
199
+ if (this.viewDefinitions.has(viewDefCacheKey) || runtimeParams.requestCache[viewDefCacheKey]) {
200
+ const viewDefinition = this.viewDefinitions.get(viewDefCacheKey) || runtimeParams.requestCache[viewDefCacheKey];
194
201
  if (viewDefinition &&
195
202
  viewDefinition.paramKey === viewParamKey &&
196
203
  viewDefinition.viewDefinition.immutable) {
@@ -218,11 +225,17 @@ export class LwrViewRegistry {
218
225
  const leastTtl = shortestTtl(viewDefinition.cache?.ttl, route?.cache?.ttl, maxTtl);
219
226
  const ttl = leastTtl !== undefined ? (leastTtl === 0 ? 10 : leastTtl * 1000) : undefined;
220
227
  // cache view definition for the shortest ttl or until it is the least recently used when ttl is undefined
221
- this.viewDefinitions.set(viewDefCacheKey, {
222
- view,
223
- viewDefinition,
224
- paramKey: viewParamKey,
225
- }, { ttl });
228
+ const viewDefCacheEntry = { view, viewDefinition, paramKey: viewParamKey };
229
+ if (view.bootstrap?.includeCookiesForSSR) {
230
+ // important: DO NOT cache authenticated view definitions beyond a single request.
231
+ // Keep the cache entry only during the request in case viewRegistry.getViewDefinition is called > once.
232
+ // This can happen when a route handler calls viewApi.getViewResponse then returns a ViewDefinitionResponse.
233
+ runtimeParams.requestCache[viewDefCacheKey] = viewDefCacheEntry;
234
+ }
235
+ else {
236
+ // Standard cross-request caching
237
+ this.viewDefinitions.set(viewDefCacheKey, viewDefCacheEntry, { ttl });
238
+ }
226
239
  return viewDefinition;
227
240
  }
228
241
  catch (err) {
@@ -235,10 +248,10 @@ export class LwrViewRegistry {
235
248
  throw createSingleDiagnosticError({ description: descriptions.SERVER.UNEXPECTED_ERROR(message) }, LwrServerError);
236
249
  }
237
250
  }
238
- async renderView(view, viewParams, runtimeEnvironment, runtimeParams = {}, renderOptions) {
251
+ async renderView(view, viewParams, runtimeEnvironment, runtimeParams, renderOptions) {
239
252
  const { id, contentTemplate, rootComponent, layoutTemplate } = view;
240
253
  const lwrResourcesId = `__LWR_RESOURCES__${Date.now()}`;
241
- const renderedContent = await this.render({ id, contentTemplate, rootComponent }, { ...viewParams, lwr_resources: lwrResourcesId }, runtimeParams, runtimeEnvironment, renderOptions);
254
+ const renderedContent = await this.render({ id, contentTemplate, rootComponent }, { ...viewParams, lwr_resources: lwrResourcesId }, runtimeParams, runtimeEnvironment);
242
255
  // normalize the renderOptions provided by the CompiledView content with the request options.
243
256
  let normalizedRenderOptions = normalizeRenderOptions(this.runtimeEnvironment, renderedContent.options, renderOptions);
244
257
  const layout = layoutTemplate || renderedContent.compiledView.layoutTemplate;
@@ -291,6 +304,7 @@ export class LwrViewRegistry {
291
304
  },
292
305
  },
293
306
  cache: renderedContent.cache,
307
+ redirect: renderedContent.redirect,
294
308
  },
295
309
  // Render Content now contains a layout
296
310
  {
@@ -306,7 +320,7 @@ export class LwrViewRegistry {
306
320
  });
307
321
  return renderedViewDef;
308
322
  }
309
- async render(viewId, viewParams, runtimeParams, runtimeEnvironment, renderOptions) {
323
+ async render(viewId, viewParams, runtimeParams, runtimeEnvironment) {
310
324
  const globalContext = this.globalData;
311
325
  const { id, rootComponent, contentTemplate } = viewId;
312
326
  const compiledView = await this.getView({ id, contentTemplate, rootComponent });
@@ -383,6 +397,7 @@ export class LwrViewRegistry {
383
397
  ...viewRecord,
384
398
  },
385
399
  cache: { ttl: pageTtl },
400
+ redirect: renderedView.redirect,
386
401
  };
387
402
  }
388
403
  return {
@@ -393,6 +408,7 @@ export class LwrViewRegistry {
393
408
  moduleResources: [],
394
409
  },
395
410
  cache: { ttl: pageTtl },
411
+ redirect: renderedView.redirect,
396
412
  };
397
413
  }
398
414
  }
@@ -266,6 +266,7 @@ export async function getHtmlResources(view, viewParams, resourceContext) {
266
266
  configAsSrc: view.bootstrap?.configAsSrc || false,
267
267
  mixedMode: view.bootstrap?.mixedMode || false,
268
268
  nonce: viewParams?.page?.nonce,
269
+ ssr: view.bootstrap?.ssr || false,
269
270
  }, {
270
271
  appId: appIdentity.appName,
271
272
  bootstrapModule: versionedSpecifier,
@@ -16,7 +16,9 @@ export async function linkLwrResources(source, view, viewParams, cxt) {
16
16
  });
17
17
  // Finally replace the token with the real resources
18
18
  return {
19
- renderedView: source.replace(lwrResourcesId, partial),
19
+ // partial may contain regex-token-like characters such as $$,
20
+ // so we want to bypass regex replacement
21
+ renderedView: source.replace(lwrResourcesId, () => partial),
20
22
  viewRecord,
21
23
  };
22
24
  }
@@ -37,7 +37,8 @@ export function getViewBootstrapConfigurationResource(viewInfo, config, runtimeE
37
37
  })});`,
38
38
  `globalThis.LWR = {...globalThis.LWR, env: ${JSON.stringify(lwrEnv)}};`,
39
39
  `globalThis.process={...globalThis.process,env:{...globalThis.process?.env,...${JSON.stringify(nodeEnv)}}};`,
40
- `globalThis.lwcRuntimeFlags = { ENABLE_MIXED_SHADOW_MODE: ${viewInfo.mixedMode} };`,
40
+ // TODO: evaluate moving these to app layer
41
+ `globalThis.lwcRuntimeFlags = { ENABLE_MIXED_SHADOW_MODE: ${viewInfo.mixedMode}, ENABLE_WIRE_SYNC_EMIT: ${viewInfo.ssr} };`,
41
42
  debug && debugMessage && `console.error(${JSON.stringify(debugMessage)});`,
42
43
  ]
43
44
  .filter(Boolean)
@@ -229,6 +229,7 @@ export async function getHtmlResources(view, viewParams, resourceContext) {
229
229
  configAsSrc: view.bootstrap?.configAsSrc || false,
230
230
  mixedMode: view.bootstrap?.mixedMode || false,
231
231
  nonce: viewParams?.page?.nonce,
232
+ ssr: view.bootstrap?.ssr || false,
232
233
  }, {
233
234
  appId: appIdentity.appName,
234
235
  bootstrapModule: versionedSpecifier,
@@ -1,11 +1,12 @@
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 } from '@lwrjs/types';
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 } 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, }: RenderingResult): NormalizedRenderingResult;
4
+ export declare function normalizeRenderedResult({ renderedView, metadata, options, cache, redirect, }: 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>;
8
8
  export declare function isViewResponse(response: RouteHandlerViewResponse): response is ViewResponse;
9
+ export declare function isViewDefinitionResponse(response: RouteHandlerViewResponse): response is ViewDefinitionResponse;
9
10
  export declare function toJsonFormat(viewRequest: ViewRequest, viewDefinition: LinkedViewDefinition, route: LwrRoute | LwrErrorRoute, runtimeEnvironment: RuntimeEnvironment, runtimeParams: RuntimeParams, moduleRegistry: ModuleRegistry): Promise<ViewResponse>;
10
11
  export declare function getModuleResource(moduleId: Pick<ModuleId, 'specifier' | 'version'>, runtimeEnvironment: RuntimeEnvironment, moduleResourceMeta: ViewModuleResourceContext, defRegistry: ModuleRegistry | ModuleBundler, runtimeParams: RuntimeParams): Promise<ResourceDefinition>;
11
12
  export declare function getModuleResourceByUri(uri: string, runtimeEnvironment: RuntimeEnvironment, moduleResourceMeta: ViewModuleResourceContext): ResourceDefinition;
@@ -22,4 +23,5 @@ export declare function generateViewNonce(viewParams: ViewParams): void;
22
23
  * Add a nonce to a script definition in view if it is external (not inline)
23
24
  */
24
25
  export declare function addExternalScriptNonce(def: ResourceDefinition, nonce?: string): void;
26
+ export declare function generateLinkHeaders(assets: RenderedAssetReference[], patterns: Record<string, any>[]): string;
25
27
  //# sourceMappingURL=utils.d.ts.map
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, redirect, }) {
70
70
  return {
71
71
  renderedView,
72
72
  metadata: {
@@ -79,6 +79,7 @@ export function normalizeRenderedResult({ renderedView, metadata, options, cache
79
79
  skipMetadataCollection: options ? options.skipMetadataCollection : false,
80
80
  },
81
81
  cache: cache || {},
82
+ redirect,
82
83
  };
83
84
  }
84
85
  export function reduceSourceAssetReferences(assets) {
@@ -117,6 +118,9 @@ export function generatePageContext({ requestPath: url }, { id, contentTemplate,
117
118
  export function isViewResponse(response) {
118
119
  return response.body !== undefined;
119
120
  }
121
+ export function isViewDefinitionResponse(response) {
122
+ return response.view !== undefined;
123
+ }
120
124
  export async function toJsonFormat(viewRequest, viewDefinition, route, runtimeEnvironment, runtimeParams, moduleRegistry) {
121
125
  const { viewRecord } = viewDefinition;
122
126
  const { bootstrap, id: appName } = route;
@@ -281,4 +285,30 @@ export function addExternalScriptNonce(def, nonce) {
281
285
  def.nonce = nonce;
282
286
  }
283
287
  }
288
+ export function generateLinkHeaders(assets, patterns) {
289
+ // store each Path's config in this config
290
+ const assetConfig = {};
291
+ // Iterate through the patterns to filter the file paths and populate assetConfig
292
+ for (let i = 0; i < patterns.length; i++) {
293
+ const pattern = patterns[i].match;
294
+ assets.forEach((asset) => {
295
+ const path = (asset.override?.uri || asset.url).split('?')[0];
296
+ if (path.endsWith(pattern)) {
297
+ assetConfig[path] = patterns[i].attributes;
298
+ }
299
+ });
300
+ }
301
+ // Use the assetConfig to construct the Link Header
302
+ return Object.keys(assetConfig).reduce((linkHeader, path) => {
303
+ linkHeader = `${linkHeader ? linkHeader + ', ' : ''}<${path}>`;
304
+ const properties = assetConfig[path];
305
+ // Add the properties mentioned on the preloadResource patterns except match prop
306
+ for (const prop in properties) {
307
+ // Check if the prop has empty string, if so add the prop name alone
308
+ // eg: if crossorigin: '' => </global.css>; as=style; crossorigin
309
+ linkHeader += properties[prop] !== '' ? `; ${prop}=${properties[prop]}` : `; ${prop}`;
310
+ }
311
+ return linkHeader;
312
+ }, '');
313
+ }
284
314
  //# sourceMappingURL=utils.js.map
@@ -1,7 +1,7 @@
1
1
  import { resolve } from 'path';
2
2
  import { normalizeResourcePath, shortestTtl } from '@lwrjs/shared-utils';
3
3
  import { getTracer, ViewSpan } from '@lwrjs/instrumentation';
4
- import { generateHtmlTag, generatePageContext, isViewResponse, toJsonFormat } from './utils.js';
4
+ import { generateHtmlTag, generateLinkHeaders, generatePageContext, isViewResponse, isViewDefinitionResponse, toJsonFormat, } from './utils.js';
5
5
  import { DiagnosticsError, LwrApplicationError, createSingleDiagnosticError, descriptions, } from '@lwrjs/diagnostics';
6
6
  export class LwrViewHandler {
7
7
  constructor(context, globalConfig) {
@@ -27,9 +27,12 @@ export class LwrViewHandler {
27
27
  // Process ViewDefinitionResponse
28
28
  const { view, viewParams, renderOptions } = normalizeViewProperties(viewRequest, response, route, this.globalConfig, runtimeParams);
29
29
  const viewDefinition = await this.viewRegistry.getViewDefinition(view, viewParams, runtimeEnvironment, runtimeParams, renderOptions);
30
+ const link = !!route?.bootstrap?.preloadResources?.patterns?.length &&
31
+ generateLinkHeaders(viewDefinition.viewRecord.assetReferences || [], route?.bootstrap?.preloadResources?.patterns || []);
30
32
  return {
31
33
  ...response,
32
34
  body: viewDefinition.renderedView,
35
+ ...(!!link && { headers: { link } }),
33
36
  metadata: {
34
37
  viewDefinition,
35
38
  },
@@ -38,12 +41,15 @@ export class LwrViewHandler {
38
41
  }
39
42
  // default static view
40
43
  const viewDefinition = await this.getDefaultRouteViewDefinition(viewRequest, route, runtimeEnvironment, runtimeParams);
44
+ const link = !!route?.bootstrap?.preloadResources?.patterns?.length &&
45
+ generateLinkHeaders(viewDefinition.viewRecord.assetReferences || [], route.bootstrap?.preloadResources?.patterns || []);
41
46
  return {
42
47
  body: viewDefinition.renderedView,
43
48
  metadata: {
44
49
  viewDefinition,
45
50
  },
46
51
  cache: viewDefinition.cache,
52
+ ...(!!link && { headers: { link } }),
47
53
  };
48
54
  }
49
55
  // Get the View's JSON Manifest Response
@@ -171,6 +177,24 @@ export class LwrViewHandler {
171
177
  if (response?.locale) {
172
178
  runtimeParams.locale = response.locale;
173
179
  }
180
+ // Add link headers if this is a viewDefinition response
181
+ if (isViewDefinitionResponse(response)) {
182
+ let viewDefintionMeta = response.metadata?.viewDefinition;
183
+ if (!viewDefintionMeta) {
184
+ viewDefintionMeta = (await viewApi.getViewResponse(response.view, response.viewParams, response.renderOptions)).metadata?.viewDefinition;
185
+ }
186
+ const link = !!route?.bootstrap?.preloadResources?.patterns?.length &&
187
+ generateLinkHeaders(
188
+ // idk when metadata?.viewDefinition would be undefined but
189
+ // it's optional....probably Darrell's fault
190
+ viewDefintionMeta?.viewRecord.assetReferences || [], route?.bootstrap?.preloadResources?.patterns || []);
191
+ if (link) {
192
+ response.headers = {
193
+ ...response.headers,
194
+ link,
195
+ };
196
+ }
197
+ }
174
198
  return response;
175
199
  }
176
200
  /*
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.15.0-alpha.2",
7
+ "version": "0.15.0-alpha.20",
8
8
  "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
9
  "repository": {
10
10
  "type": "git",
@@ -33,17 +33,17 @@
33
33
  "build": "tsc -b"
34
34
  },
35
35
  "dependencies": {
36
- "@lwrjs/app-service": "0.15.0-alpha.2",
37
- "@lwrjs/diagnostics": "0.15.0-alpha.2",
38
- "@lwrjs/instrumentation": "0.15.0-alpha.2",
39
- "@lwrjs/shared-utils": "0.15.0-alpha.2",
36
+ "@lwrjs/app-service": "0.15.0-alpha.20",
37
+ "@lwrjs/diagnostics": "0.15.0-alpha.20",
38
+ "@lwrjs/instrumentation": "0.15.0-alpha.20",
39
+ "@lwrjs/shared-utils": "0.15.0-alpha.20",
40
40
  "lru-cache": "^10.4.3"
41
41
  },
42
42
  "devDependencies": {
43
- "@lwrjs/types": "0.15.0-alpha.2"
43
+ "@lwrjs/types": "0.15.0-alpha.20"
44
44
  },
45
45
  "engines": {
46
46
  "node": ">=18.0.0"
47
47
  },
48
- "gitHead": "58fda4341e2aa0f571a63d83f4aa59865e6a54bd"
48
+ "gitHead": "112ed1a3472b68a90c6884422137006a5dcf032c"
49
49
  }