@lwrjs/core 0.5.11-alpha.1 → 0.6.0-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/env-config.cjs +31 -3
- package/build/cjs/index.cjs +41 -10
- package/build/cjs/middlewares/api-middleware.cjs +8 -7
- package/build/cjs/middlewares/ui-middleware.cjs +26 -21
- package/build/cjs/middlewares/utils.cjs +1 -1
- package/build/cjs/tools/static-generation.cjs +259 -114
- package/build/cjs/tools/utils/network-dispatcher.cjs +22 -2
- package/build/cjs/tools/utils/stream.cjs +9 -1
- package/build/cjs/validation/app-config-context.cjs +5 -2
- package/build/cjs/validation/app-config.cjs +1 -0
- package/build/es/env-config.js +32 -3
- package/build/es/index.js +48 -11
- package/build/es/middlewares/api-middleware.js +10 -7
- package/build/es/middlewares/ui-middleware.js +29 -22
- package/build/es/middlewares/utils.d.ts +2 -5
- package/build/es/middlewares/utils.js +3 -3
- package/build/es/tools/static-generation.d.ts +95 -6
- package/build/es/tools/static-generation.js +399 -166
- package/build/es/tools/types.d.ts +22 -2
- package/build/es/tools/utils/network-dispatcher.d.ts +3 -0
- package/build/es/tools/utils/network-dispatcher.js +26 -2
- package/build/es/tools/utils/stream.d.ts +5 -0
- package/build/es/tools/utils/stream.js +13 -1
- package/build/es/validation/app-config-context.d.ts +2 -2
- package/build/es/validation/app-config-context.js +3 -0
- package/build/es/validation/app-config.js +1 -0
- package/package.json +29 -25
|
@@ -1,172 +1,16 @@
|
|
|
1
|
+
import { getSpecifier, getExperimentalFeatures } from '@lwrjs/shared-utils';
|
|
1
2
|
import { join, dirname, extname } from 'path';
|
|
2
3
|
import fs from 'fs-extra';
|
|
3
4
|
import { writeResponse } from './utils/stream.js';
|
|
4
5
|
import { createDir, createResourceDir } from './utils/dir.js';
|
|
5
6
|
export default class SiteGenerator {
|
|
6
|
-
async dispatchResourceRecursive(url, dispatcher, resourceOpts, siteConfig) {
|
|
7
|
-
const { outputDir, visitedUrls } = siteConfig;
|
|
8
|
-
if (!visitedUrls.has(url)) {
|
|
9
|
-
visitedUrls.add(url); // Maintain a list of visited urls here to avoid potential infinite loops
|
|
10
|
-
// Skip urls with path segment variables (i.e. '/custom/:bar')
|
|
11
|
-
// Users can specify specific urls via the '_additionalRoutePaths' config property
|
|
12
|
-
if (url.indexOf('/:') !== -1) {
|
|
13
|
-
console.error('Skipped url with variable path segment: ' + url);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
// Generate resource
|
|
17
|
-
const context = await dispatcher.dispatchUrl(url, 'GET', siteConfig.locale);
|
|
18
|
-
const { resourceType } = resourceOpts;
|
|
19
|
-
// -- Routes (root resources) -------
|
|
20
|
-
if (resourceType === 'route') {
|
|
21
|
-
const dir = createResourceDir(url, outputDir);
|
|
22
|
-
const localeDir = createResourceDir(url, join(outputDir, siteConfig.locale));
|
|
23
|
-
// TODO: We assume HTML, revisit this in the future
|
|
24
|
-
// This fails for routes configured with non-html file extensions.
|
|
25
|
-
// Example Route: "path": "/mixed_templates.md", (Configured as '/mixed_templates' would work)
|
|
26
|
-
// "contentTemplate": "$contentDir/composed_markdown.md"
|
|
27
|
-
const filePath = join(dir, 'index.html');
|
|
28
|
-
const fileLocalePath = join(localeDir, 'index.html');
|
|
29
|
-
// Default Path (only write once)
|
|
30
|
-
// The default locale is english
|
|
31
|
-
if (siteConfig.locale.toLowerCase().startsWith('en')) {
|
|
32
|
-
await writeResponse(context, filePath);
|
|
33
|
-
}
|
|
34
|
-
// Language path (always write out)
|
|
35
|
-
createDir(localeDir);
|
|
36
|
-
await writeResponse(context, fileLocalePath);
|
|
37
|
-
// Get the metadata
|
|
38
|
-
const viewDefinition = context.fs?.metadata?.viewDefinition;
|
|
39
|
-
if (viewDefinition) {
|
|
40
|
-
// -- Dispatch dependencies
|
|
41
|
-
const assets = viewDefinition.viewRecord.assetReferences || [];
|
|
42
|
-
for (const asset of assets) {
|
|
43
|
-
const assetUrl = asset.override?.uri || asset.url;
|
|
44
|
-
// skip empty asset urls / data urls (i.e. <img src="" /> <img src="data:image/png;base64, iVBORw0..." />)
|
|
45
|
-
if (assetUrl && !assetUrl.startsWith('data:')) {
|
|
46
|
-
// eslint-disable-next-line no-await-in-loop
|
|
47
|
-
await this.dispatchResourceRecursive(assetUrl, dispatcher, { resourceType: 'asset', asset }, siteConfig);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
// Custom Elements
|
|
51
|
-
const customElements = viewDefinition.viewRecord.customElements || [];
|
|
52
|
-
for (const customElement of customElements) {
|
|
53
|
-
const jsUris = Object.values(customElement.flatGraph.uriMap);
|
|
54
|
-
for (const jsUri of jsUris) {
|
|
55
|
-
// eslint-disable-next-line no-await-in-loop
|
|
56
|
-
await this.dispatchResourceRecursive(jsUri, dispatcher, { resourceType: 'js' }, siteConfig);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
// Bootstrap modules
|
|
60
|
-
if (viewDefinition.viewRecord.bootstrapModule) {
|
|
61
|
-
const boot = viewDefinition.viewRecord.bootstrapModule;
|
|
62
|
-
const jsUris = Object.values(boot.flatGraph.uriMap);
|
|
63
|
-
for (const jsUri of jsUris) {
|
|
64
|
-
// eslint-disable-next-line no-await-in-loop
|
|
65
|
-
await this.dispatchResourceRecursive(jsUri, dispatcher, { resourceType: 'js' }, siteConfig);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// Bootstrap Resources
|
|
69
|
-
const bootstrapResources = viewDefinition.viewRecord.bootstrapModule?.resources || [];
|
|
70
|
-
for (const resource of bootstrapResources) {
|
|
71
|
-
// If resource is not inline download it for reference
|
|
72
|
-
if (!resource.inline) {
|
|
73
|
-
const resourceUri = resource.src || resource.specifier;
|
|
74
|
-
if (resourceUri) {
|
|
75
|
-
// eslint-disable-next-line no-await-in-loop
|
|
76
|
-
await this.dispatchResourceRecursive(resourceUri, dispatcher, { resourceType: 'resource' }, siteConfig);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
console.warn('Skipped inline bootstrap resource: %j', resource);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// Resources
|
|
84
|
-
const resources = viewDefinition.viewRecord.resources || [];
|
|
85
|
-
for (const resource of resources) {
|
|
86
|
-
const resourceUri = resource.src || resource.specifier || '';
|
|
87
|
-
if (resourceUri.startsWith('/')) {
|
|
88
|
-
// eslint-disable-next-line no-await-in-loop
|
|
89
|
-
await this.dispatchResourceRecursive(resourceUri, dispatcher, { resourceType: 'resource' }, siteConfig);
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
console.warn('Skipped resource: %j', resource);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
// -- Process Assets (css, images, ...) && Resources (lwr-loader-shim ...)
|
|
97
|
-
}
|
|
98
|
-
else if (resourceType === 'asset' || resourceType === 'resource') {
|
|
99
|
-
const normalizedUrl = decodeURIComponent(url);
|
|
100
|
-
createResourceDir(dirname(normalizedUrl), outputDir);
|
|
101
|
-
const fullPath = join(outputDir, normalizedUrl);
|
|
102
|
-
await writeResponse(context, fullPath);
|
|
103
|
-
// -- Process JS files
|
|
104
|
-
}
|
|
105
|
-
else if (resourceType === 'js') {
|
|
106
|
-
const normalizedUrl = decodeURIComponent(url);
|
|
107
|
-
createResourceDir(dirname(normalizedUrl), outputDir);
|
|
108
|
-
const ext = extname(normalizedUrl);
|
|
109
|
-
const fullPath = join(outputDir, `${normalizedUrl}${ext ? '' : '.js'}`);
|
|
110
|
-
await writeResponse(context, fullPath);
|
|
111
|
-
// Add URL re-writes for module redirects
|
|
112
|
-
if (normalizedUrl.indexOf('/s/') !== -1) {
|
|
113
|
-
const rewriteUrl = normalizedUrl.substring(0, normalizedUrl.indexOf('/s/'));
|
|
114
|
-
// Redirect /1/bundle/amd/l/en-US/bi/0/module/mi/lwr/navigation/v/0_1_6 -> /1/bundle/amd/l/en-US/bi/0/module/mi/lwr/navigation/v/0_1_6/s/{signature}
|
|
115
|
-
siteConfig.urlRewriteMap.set(rewriteUrl, normalizedUrl);
|
|
116
|
-
// Redirect /1/bundle/amd/l/en-US/bi/0/module/mi/lwr%2Fnavigation%2Fv%2F0_1_6 -> /1/bundle/amd/l/en-US/bi/0/module/mi/lwr/navigation/v/0_1_6/s/{signature}
|
|
117
|
-
siteConfig.urlRewriteMap.set(url.substring(0, url.indexOf('/s/')), normalizedUrl);
|
|
118
|
-
// TODO Redirect /1/bundle/amd/l/en-US/bi/0/module/mi/lwr/navigation -> /1/bundle/amd/l/en-US/bi/0/module/mi/lwr/navigation/v/0_1_6/s/{signature}
|
|
119
|
-
// Dynamic import example /1/bundle/amd/l/en-US/bi/0/module/mi/dynamic%2Fmodule?importer=examples%2Fhome%2Fv%2F0_1_6
|
|
120
|
-
}
|
|
121
|
-
// Recursively traverse dependencies
|
|
122
|
-
const moduleDefinition = context.fs?.metadata.moduleDefinition; // LinkedModuleDefinition | BundleDefinition
|
|
123
|
-
if (moduleDefinition) {
|
|
124
|
-
// Imports
|
|
125
|
-
const imports = moduleDefinition.linkedModuleRecord?.imports ||
|
|
126
|
-
moduleDefinition.bundleRecord?.imports ||
|
|
127
|
-
[];
|
|
128
|
-
// /1/module/esm/0/l/en-US/mi/lwc
|
|
129
|
-
for (const importModule of imports) {
|
|
130
|
-
const jsUri = importModule.specifier;
|
|
131
|
-
if (jsUri.startsWith('/')) {
|
|
132
|
-
// eslint-disable-next-line no-await-in-loop
|
|
133
|
-
await this.dispatchResourceRecursive(jsUri, dispatcher, { resourceType: 'js' }, siteConfig);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
if (jsUri !== 'lwc')
|
|
137
|
-
console.warn('Skipped import: ' + jsUri);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// Dynamic imports
|
|
141
|
-
const dynamicImports = moduleDefinition.linkedModuleRecord?.dynamicImports ||
|
|
142
|
-
moduleDefinition.bundleRecord?.dynamicImports ||
|
|
143
|
-
[];
|
|
144
|
-
for (const importModule of dynamicImports) {
|
|
145
|
-
const jsUri = importModule.specifier;
|
|
146
|
-
if (jsUri.startsWith('/')) {
|
|
147
|
-
// eslint-disable-next-line no-await-in-loop
|
|
148
|
-
await this.dispatchResourceRecursive(jsUri, dispatcher, { resourceType: 'js' }, siteConfig);
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
console.warn('Skipped dynamic import: ' + jsUri);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
// Bundles with unresolved module uris
|
|
156
|
-
const uris = context.fs?.metadata.resolvedUris || [];
|
|
157
|
-
for (const jsUri of uris) {
|
|
158
|
-
// eslint-disable-next-line no-await-in-loop
|
|
159
|
-
await this.dispatchResourceRecursive(jsUri, dispatcher, { resourceType: 'js' }, siteConfig);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
7
|
/**
|
|
165
8
|
* Build a static site in the configured directory
|
|
166
9
|
* - Generate all routes / modules
|
|
167
10
|
* - copy assets / resources
|
|
168
11
|
*
|
|
169
|
-
* @param
|
|
12
|
+
* @param config - LWR config for the site
|
|
13
|
+
* @param dispatcher - Facilitate server requests
|
|
170
14
|
*/
|
|
171
15
|
async buildStaticApplication(config, dispatcher) {
|
|
172
16
|
console.log('[Static Generation] starting');
|
|
@@ -175,34 +19,331 @@ export default class SiteGenerator {
|
|
|
175
19
|
staticSiteGenerator.outputDir = '__generated_site__';
|
|
176
20
|
}
|
|
177
21
|
const outputDir = join(rootDir, staticSiteGenerator.outputDir);
|
|
22
|
+
console.log(`[INFO] Clear Output Location: ${outputDir}`);
|
|
23
|
+
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
178
24
|
const urlRewriteMap = new Map();
|
|
179
25
|
// For each locale, generate all the modules
|
|
180
26
|
await this.generateRoutes(staticSiteGenerator, routes, dispatcher, outputDir, urlRewriteMap);
|
|
181
27
|
// Write redirect files
|
|
182
|
-
this.
|
|
28
|
+
this.writeNetlifyRedirectConfig(outputDir, urlRewriteMap);
|
|
183
29
|
// Copy over assets
|
|
184
30
|
this.copyAssets(assets, outputDir);
|
|
185
31
|
console.log('[Static Generation] complete');
|
|
186
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Crawl all view routes for a site
|
|
35
|
+
*/
|
|
187
36
|
async generateRoutes(staticSiteGenerator, routes, dispatcher, outputDir, urlRewriteMap = new Map()) {
|
|
188
37
|
if (!staticSiteGenerator.locales) {
|
|
189
38
|
staticSiteGenerator.locales = ['en-US'];
|
|
190
39
|
}
|
|
40
|
+
// Build up a list of dispatch requests to kick off in parallel
|
|
41
|
+
const dispatchRequests = [];
|
|
42
|
+
const generateUrl = this.createGenerateURLFunction(dispatcher);
|
|
191
43
|
for (const locale of staticSiteGenerator.locales) {
|
|
192
|
-
const visitedUrls = new Set();
|
|
193
44
|
// Generate all the routes
|
|
194
45
|
for (const route of routes) {
|
|
195
|
-
|
|
196
|
-
|
|
46
|
+
const siteConfig = this.createSiteConfig(outputDir, locale, urlRewriteMap);
|
|
47
|
+
dispatchRequests.push(generateUrl(route.path, siteConfig));
|
|
197
48
|
}
|
|
198
49
|
// Generate any additional urls
|
|
199
50
|
if (staticSiteGenerator._additionalRoutePaths) {
|
|
200
51
|
for (const uri of staticSiteGenerator._additionalRoutePaths) {
|
|
201
|
-
|
|
202
|
-
|
|
52
|
+
const siteConfig = this.createSiteConfig(outputDir, locale, urlRewriteMap);
|
|
53
|
+
dispatchRequests.push(generateUrl(uri, siteConfig));
|
|
203
54
|
}
|
|
204
55
|
}
|
|
205
56
|
}
|
|
57
|
+
// -- Dispatch routes
|
|
58
|
+
await Promise.all(dispatchRequests);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Creates a function to dispatch the root requests for a given view url
|
|
62
|
+
*/
|
|
63
|
+
createGenerateURLFunction(dispatcher) {
|
|
64
|
+
const generateRoute = async (uri, siteConfig) => {
|
|
65
|
+
const locale = siteConfig.locale;
|
|
66
|
+
console.log(`[INFO] Start Generate: ${locale} ${uri}`);
|
|
67
|
+
// Kick off site generation for the current route
|
|
68
|
+
await this.dispatchResourceRecursive(uri, dispatcher, { resourceType: 'route' }, siteConfig);
|
|
69
|
+
// If there is a view config add any extra collected import metadata to the config
|
|
70
|
+
this.addAdditionalImportMetadataToViewConfig(siteConfig);
|
|
71
|
+
console.log(`[INFO] End Generate ${locale} ${uri}`);
|
|
72
|
+
};
|
|
73
|
+
return generateRoute.bind(this);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Primary recursive dispatch function. Executes a URL and crawls its dependents.
|
|
77
|
+
*
|
|
78
|
+
* @param url - URL to execute
|
|
79
|
+
* @param dispatcher - Facilitates GET requests
|
|
80
|
+
* @param resourceOpts - Data about the URL to execute
|
|
81
|
+
* @param siteConfig - Running config for the current view
|
|
82
|
+
*/
|
|
83
|
+
async dispatchResourceRecursive(url, dispatcher, resourceOpts, siteConfig) {
|
|
84
|
+
const { visitedUrls } = siteConfig;
|
|
85
|
+
if (!visitedUrls.has(url)) {
|
|
86
|
+
visitedUrls.add(url); // Maintain a list of visited urls here to avoid potential infinite loops
|
|
87
|
+
// Skip urls with path segment variables (i.e. '/custom/:bar')
|
|
88
|
+
// Users can specify specific urls via the '_additionalRoutePaths' config property
|
|
89
|
+
if (url.indexOf('/:') !== -1) {
|
|
90
|
+
console.error('Skipped url with variable path segment: ' + url);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Generate resource
|
|
94
|
+
const context = await dispatcher.dispatchUrl(url, 'GET', siteConfig.locale);
|
|
95
|
+
const { resourceType } = resourceOpts;
|
|
96
|
+
// -- Routes (root resources) -------
|
|
97
|
+
if (resourceType === 'route') {
|
|
98
|
+
await this.handleHtmlResource(url, context, siteConfig, dispatcher);
|
|
99
|
+
// -- Process Assets (css, images, ...) && Resources (lwr-loader-shim ...)
|
|
100
|
+
}
|
|
101
|
+
else if (resourceType === 'asset' || resourceType === 'resource') {
|
|
102
|
+
await this.handleAssetOrResource(url, context, siteConfig);
|
|
103
|
+
// -- Import Metadata Mappings
|
|
104
|
+
}
|
|
105
|
+
else if (resourceType == 'mapping') {
|
|
106
|
+
await this.handleMappingResource(url, context, siteConfig, dispatcher);
|
|
107
|
+
// -- Process JS files
|
|
108
|
+
}
|
|
109
|
+
else if (resourceType === 'js') {
|
|
110
|
+
await this.handleJavascriptResource(url, context, siteConfig, dispatcher);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Handle processing a returned javascript module or bundle and follow all returned references
|
|
116
|
+
* @param url - URL of the request javascript resource
|
|
117
|
+
* @param context - Response Context
|
|
118
|
+
* @param siteConfig - Global metadata about the site
|
|
119
|
+
* @param dispatcher - Network dispatcher
|
|
120
|
+
*/
|
|
121
|
+
async handleJavascriptResource(url, context, siteConfig, dispatcher) {
|
|
122
|
+
const { outputDir } = siteConfig;
|
|
123
|
+
const normalizedUrl = decodeURIComponent(url);
|
|
124
|
+
createResourceDir(dirname(normalizedUrl), outputDir);
|
|
125
|
+
const ext = extname(normalizedUrl);
|
|
126
|
+
const fullPath = join(outputDir, `${normalizedUrl}${ext ? '' : '.js'}`);
|
|
127
|
+
await writeResponse(context, fullPath);
|
|
128
|
+
// Build up a list of dispatch requests to kick off in parallel
|
|
129
|
+
const dispatchRequests = [];
|
|
130
|
+
// Add URL re-writes for module redirects
|
|
131
|
+
if (normalizedUrl.indexOf('/s/') !== -1) {
|
|
132
|
+
const rewriteUrl = normalizedUrl.substring(0, normalizedUrl.indexOf('/s/'));
|
|
133
|
+
// Redirect /1/bundle/amd/l/en-US/bi/0/module/mi/lwr/navigation/v/0_1_6 -> /1/bundle/amd/l/en-US/bi/0/module/mi/lwr/navigation/v/0_1_6/s/{signature}
|
|
134
|
+
siteConfig.urlRewriteMap.set(rewriteUrl, normalizedUrl);
|
|
135
|
+
// Redirect /1/bundle/amd/l/en-US/bi/0/module/mi/lwr%2Fnavigation%2Fv%2F0_1_6 -> /1/bundle/amd/l/en-US/bi/0/module/mi/lwr/navigation/v/0_1_6/s/{signature}
|
|
136
|
+
siteConfig.urlRewriteMap.set(url.substring(0, url.indexOf('/s/')), normalizedUrl);
|
|
137
|
+
// TODO Redirect /1/bundle/amd/l/en-US/bi/0/module/mi/lwr/navigation -> /1/bundle/amd/l/en-US/bi/0/module/mi/lwr/navigation/v/0_1_6/s/{signature}
|
|
138
|
+
// Dynamic import example /1/bundle/amd/l/en-US/bi/0/module/mi/dynamic%2Fmodule?importer=examples%2Fhome%2Fv%2F0_1_6
|
|
139
|
+
}
|
|
140
|
+
// Recursively traverse dependencies
|
|
141
|
+
const moduleDefinition = context.fs?.metadata?.moduleDefinition; // LinkedModuleDefinition | BundleDefinition
|
|
142
|
+
if (moduleDefinition) {
|
|
143
|
+
// Imports
|
|
144
|
+
const imports = moduleDefinition.linkedModuleRecord?.imports || moduleDefinition.bundleRecord?.imports || [];
|
|
145
|
+
// /1/module/esm/0/l/en-US/mi/lwc
|
|
146
|
+
for (const importModule of imports) {
|
|
147
|
+
const jsUri = getSpecifier(importModule);
|
|
148
|
+
dispatchRequests.push(this.dispatchJSResourceRecursive(jsUri, dispatcher, siteConfig));
|
|
149
|
+
}
|
|
150
|
+
// Dynamic imports
|
|
151
|
+
const dynamicImports = moduleDefinition.linkedModuleRecord?.dynamicImports ||
|
|
152
|
+
moduleDefinition.bundleRecord?.dynamicImports ||
|
|
153
|
+
[];
|
|
154
|
+
for (const importModule of dynamicImports) {
|
|
155
|
+
const jsUri = getSpecifier(importModule);
|
|
156
|
+
dispatchRequests.push(this.dispatchJSResourceRecursive(jsUri, dispatcher, siteConfig));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Bundles with unresolved module uris
|
|
160
|
+
const uris = context.fs?.metadata?.resolvedUris || [];
|
|
161
|
+
for (const jsUri of uris) {
|
|
162
|
+
dispatchRequests.push(this.dispatchJSResourceRecursive(jsUri, dispatcher, siteConfig));
|
|
163
|
+
}
|
|
164
|
+
// -- Dispatch dependencies
|
|
165
|
+
await Promise.all(dispatchRequests);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Handle processing a returned module URI mapping resource and follow all returned references
|
|
169
|
+
* @param url - URL of the request mapping resource
|
|
170
|
+
* @param context - Response Context
|
|
171
|
+
* @param siteConfig - Global metadata about the site
|
|
172
|
+
* @param dispatcher - Network dispatcher
|
|
173
|
+
*/
|
|
174
|
+
async handleMappingResource(url, context, siteConfig, dispatcher) {
|
|
175
|
+
const { importMetadata: importMetatdata } = siteConfig;
|
|
176
|
+
const statusCode = context.response?.status;
|
|
177
|
+
// Received a server error
|
|
178
|
+
if (statusCode === 200) {
|
|
179
|
+
// Read JSON
|
|
180
|
+
const newImportMetadata = context.fs?.body;
|
|
181
|
+
if (!importMetatdata) {
|
|
182
|
+
console.warn('[WARN] Import metadata collector was never initialized');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Filter out and import metadata already included with the view
|
|
186
|
+
const filteredImportMetadata = importMetatdata.addAdditionalMetadata(newImportMetadata);
|
|
187
|
+
// Build up a list of dispatch requests to kick off in parallel
|
|
188
|
+
const dispatchRequests = [];
|
|
189
|
+
// Iterate through the import mappings and return request uris
|
|
190
|
+
for (const uri of Object.keys(filteredImportMetadata.imports)) {
|
|
191
|
+
dispatchRequests.push(this.dispatchResourceRecursive(uri, dispatcher, { resourceType: 'js' }, siteConfig));
|
|
192
|
+
}
|
|
193
|
+
// Wait for all requests to comeback
|
|
194
|
+
await Promise.all(dispatchRequests);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const body = context.fs?.body;
|
|
198
|
+
console.warn(`[WARN] Failed to fetch ${url}: (${statusCode}) ${body}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Handle processing a returned HTML resource and process any provided view metadata
|
|
203
|
+
*
|
|
204
|
+
* @param url - URL of the requested HTML resource
|
|
205
|
+
* @param context - Response Context
|
|
206
|
+
* @param siteConfig - Global metadata about the site
|
|
207
|
+
* @param dispatcher - Network Dispatcher
|
|
208
|
+
*/
|
|
209
|
+
async handleHtmlResource(url, context, siteConfig, dispatcher) {
|
|
210
|
+
const { outputDir } = siteConfig;
|
|
211
|
+
const dir = createResourceDir(url, outputDir);
|
|
212
|
+
const localeDir = createResourceDir(url, join(outputDir, siteConfig.locale));
|
|
213
|
+
// TODO: We assume HTML, revisit this in the future
|
|
214
|
+
// This fails for routes configured with non-html file extensions.
|
|
215
|
+
// Example Route: "path": "/mixed_templates.md", (Configured as '/mixed_templates' would work)
|
|
216
|
+
// "contentTemplate": "$contentDir/composed_markdown.md"
|
|
217
|
+
const filePath = join(dir, 'index.html');
|
|
218
|
+
const fileLocalePath = join(localeDir, 'index.html');
|
|
219
|
+
// Default Path (only write once)
|
|
220
|
+
// The default locale is english
|
|
221
|
+
if (siteConfig.locale.toLowerCase().startsWith('en')) {
|
|
222
|
+
await writeResponse(context, filePath);
|
|
223
|
+
}
|
|
224
|
+
// Language path (always write out)
|
|
225
|
+
createDir(localeDir);
|
|
226
|
+
await writeResponse(context, fileLocalePath);
|
|
227
|
+
// Get the metadata
|
|
228
|
+
const viewDefinition = context.fs?.metadata?.viewDefinition;
|
|
229
|
+
if (viewDefinition) {
|
|
230
|
+
// Process all references in the view
|
|
231
|
+
await this.handleViewDefinition(viewDefinition, siteConfig, dispatcher);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Handle processing a returned view definition by following all returned references
|
|
236
|
+
* @param viewDefinition - Metadata about everything directly referenced form the view
|
|
237
|
+
* @param siteConfig - Global metadata about the site
|
|
238
|
+
* @param dispatcher - Network dispatcher
|
|
239
|
+
*/
|
|
240
|
+
async handleViewDefinition(viewDefinition, siteConfig, dispatcher) {
|
|
241
|
+
// Save mapping endpoint for future
|
|
242
|
+
siteConfig.endpoints = viewDefinition.viewRecord.endpoints;
|
|
243
|
+
// Save existing import metadata
|
|
244
|
+
if (viewDefinition.viewRecord.importMetadata) {
|
|
245
|
+
// Initialize import metadata collector
|
|
246
|
+
siteConfig.importMetadata = new ViewImportMetadataImpl(viewDefinition.viewRecord.importMetadata);
|
|
247
|
+
}
|
|
248
|
+
// Build up a list of dispatch requests to kick off in parallel
|
|
249
|
+
const dispatchRequests = [];
|
|
250
|
+
// Assets
|
|
251
|
+
const assets = viewDefinition.viewRecord.assetReferences || [];
|
|
252
|
+
for (const asset of assets) {
|
|
253
|
+
const assetUrl = asset.override?.uri || asset.url;
|
|
254
|
+
// skip empty asset urls / data urls (i.e. <img src="" /> <img src="data:image/png;base64, iVBORw0..." />)
|
|
255
|
+
if (assetUrl && !assetUrl.startsWith('data:')) {
|
|
256
|
+
dispatchRequests.push(this.dispatchResourceRecursive(assetUrl, dispatcher, { resourceType: 'asset', asset }, siteConfig));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Custom Elements
|
|
260
|
+
const customElements = viewDefinition.viewRecord.customElements || [];
|
|
261
|
+
for (const customElement of customElements) {
|
|
262
|
+
const jsUris = Object.values(customElement.flatGraph.uriMap);
|
|
263
|
+
for (const jsUri of jsUris) {
|
|
264
|
+
dispatchRequests.push(this.dispatchJSResourceRecursive(jsUri, dispatcher, siteConfig));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Bootstrap modules
|
|
268
|
+
if (viewDefinition.viewRecord.bootstrapModule) {
|
|
269
|
+
const boot = viewDefinition.viewRecord.bootstrapModule;
|
|
270
|
+
const jsUris = Object.values(boot.flatGraph.uriMap);
|
|
271
|
+
for (const jsUri of jsUris) {
|
|
272
|
+
dispatchRequests.push(this.dispatchJSResourceRecursive(jsUri, dispatcher, siteConfig));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Bootstrap Resources
|
|
276
|
+
const bootstrapResources = viewDefinition.viewRecord.bootstrapModule?.resources || [];
|
|
277
|
+
for (const resource of bootstrapResources) {
|
|
278
|
+
// If resource is not inline download it for reference
|
|
279
|
+
if (!resource.inline) {
|
|
280
|
+
const resourceUri = resource.src || resource.specifier;
|
|
281
|
+
if (resourceUri) {
|
|
282
|
+
dispatchRequests.push(this.dispatchResourceRecursive(resourceUri, dispatcher, { resourceType: 'resource' }, siteConfig));
|
|
283
|
+
// Rough identify if this is the config Uri and add it to the site config to store additional import metadata
|
|
284
|
+
if (resourceUri.match(/\/application\/.*\/ai\/.*\/configuration/)) {
|
|
285
|
+
siteConfig.viewConfigPath = this.getResourcePathFromUrl(siteConfig, resourceUri);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
console.warn('Skipped inline bootstrap resource: %j', resource);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Resources
|
|
294
|
+
const resources = viewDefinition.viewRecord.resources || [];
|
|
295
|
+
for (const resource of resources) {
|
|
296
|
+
const resourceUri = resource.src || resource.specifier || '';
|
|
297
|
+
if (resourceUri.startsWith('/')) {
|
|
298
|
+
dispatchRequests.push(this.dispatchResourceRecursive(resourceUri, dispatcher, { resourceType: 'resource' }, siteConfig));
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
console.warn('Skipped resource: %j', resource);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// -- Dispatch dependencies
|
|
305
|
+
await Promise.all(dispatchRequests);
|
|
306
|
+
}
|
|
307
|
+
async dispatchJSResourceRecursive(jsUri, dispatcher, siteConfig) {
|
|
308
|
+
if (jsUri.startsWith('/')) {
|
|
309
|
+
await this.dispatchResourceRecursive(jsUri, dispatcher, { resourceType: 'js' }, siteConfig);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const supportsFingerprints = siteConfig.experimentalFeatures?.ENABLE_FINGERPRINTS;
|
|
313
|
+
if (supportsFingerprints) {
|
|
314
|
+
// Download mapping
|
|
315
|
+
const mappingEndpoint = siteConfig.endpoints?.uris?.mapping;
|
|
316
|
+
if (mappingEndpoint) {
|
|
317
|
+
const mappingURL = siteConfig.endpoints?.uris?.mapping + encodeURIComponent(jsUri);
|
|
318
|
+
await this.dispatchResourceRecursive(mappingURL, dispatcher, { resourceType: 'mapping' }, siteConfig);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
console.warn('[WARN] Unable to fetch mapping for bare specifier or variable dynamic import: "' +
|
|
322
|
+
jsUri +
|
|
323
|
+
'"');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
console.warn('[WARN] Unable to resolve bare specifier or variable dynamic import: "' + jsUri + '"');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Handle processing a returned asset or resource
|
|
333
|
+
* @param url - URL of the requested asset or resource
|
|
334
|
+
* @param context - Response Context
|
|
335
|
+
* @param siteConfig - Global metadata about the site
|
|
336
|
+
*/
|
|
337
|
+
async handleAssetOrResource(url, context, siteConfig) {
|
|
338
|
+
const fullPath = this.getResourcePathFromUrl(siteConfig, url);
|
|
339
|
+
await writeResponse(context, fullPath);
|
|
340
|
+
}
|
|
341
|
+
getResourcePathFromUrl(siteConfig, url) {
|
|
342
|
+
const { outputDir } = siteConfig;
|
|
343
|
+
const normalizedUrl = decodeURIComponent(url);
|
|
344
|
+
createResourceDir(dirname(normalizedUrl), outputDir);
|
|
345
|
+
const fullPath = join(outputDir, normalizedUrl);
|
|
346
|
+
return fullPath;
|
|
206
347
|
}
|
|
207
348
|
/**
|
|
208
349
|
* Write out redirect mapping files for static hosting services like netlify. Examples for why this is needed:
|
|
@@ -212,7 +353,7 @@ export default class SiteGenerator {
|
|
|
212
353
|
* @param outputDir
|
|
213
354
|
* @param urlRewriteMap
|
|
214
355
|
*/
|
|
215
|
-
|
|
356
|
+
writeNetlifyRedirectConfig(outputDir, urlRewriteMap) {
|
|
216
357
|
const serveJsonPath = join(outputDir, 'serve.json');
|
|
217
358
|
const _redirectsPath = join(outputDir, '_redirects');
|
|
218
359
|
if (fs.existsSync(_redirectsPath)) {
|
|
@@ -259,5 +400,97 @@ export default class SiteGenerator {
|
|
|
259
400
|
}
|
|
260
401
|
}
|
|
261
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Create a new site config for the current view
|
|
405
|
+
*/
|
|
406
|
+
createSiteConfig(outputDir, locale, urlRewriteMap) {
|
|
407
|
+
const experimentalFeatures = this.filterExperimentalFeatures();
|
|
408
|
+
return {
|
|
409
|
+
outputDir,
|
|
410
|
+
visitedUrls: new Set(),
|
|
411
|
+
locale,
|
|
412
|
+
urlRewriteMap,
|
|
413
|
+
// Only include ENABLE_FINGERPRINTS if true
|
|
414
|
+
...experimentalFeatures,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
filterExperimentalFeatures() {
|
|
418
|
+
if (getExperimentalFeatures().ENABLE_FINGERPRINTS) {
|
|
419
|
+
return { experimentalFeatures: { ENABLE_FINGERPRINTS: true } };
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Add any additional import metadata collected during static site generation to the Client Bootstrap Config for this view.
|
|
427
|
+
*/
|
|
428
|
+
addAdditionalImportMetadataToViewConfig(siteConfig) {
|
|
429
|
+
const supportsFingerprints = siteConfig.experimentalFeatures?.ENABLE_FINGERPRINTS;
|
|
430
|
+
const additionalImportMetadata = siteConfig?.importMetadata?.getAdditionalImportMetadata();
|
|
431
|
+
if (supportsFingerprints &&
|
|
432
|
+
siteConfig.viewConfigPath &&
|
|
433
|
+
additionalImportMetadata?.imports &&
|
|
434
|
+
Object.keys(additionalImportMetadata.imports).length > 0) {
|
|
435
|
+
const imports = additionalImportMetadata.imports
|
|
436
|
+
? JSON.stringify(additionalImportMetadata.imports)
|
|
437
|
+
: '{}';
|
|
438
|
+
const initImports = `if (!globalThis.LWR.imports) { globalThis.LWR.imports = {}; }`;
|
|
439
|
+
const mergeImports = `Object.assign(globalThis.LWR.imports, ${imports})`;
|
|
440
|
+
const index = additionalImportMetadata.index
|
|
441
|
+
? JSON.stringify(additionalImportMetadata.index)
|
|
442
|
+
: '{}';
|
|
443
|
+
const initIndex = `if (!globalThis.LWR.index) { globalThis.LWR.index = {}; }`;
|
|
444
|
+
const mergeIndex = `Object.assign(globalThis.LWR.index, ${index})`;
|
|
445
|
+
fs.appendFileSync(siteConfig.viewConfigPath, `\n// Appended by Static Site Generator\n${initImports}\n${mergeImports}\n${initIndex}\n${mergeIndex}\n`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Class used to track import metadata for a view
|
|
450
|
+
export class ViewImportMetadataImpl {
|
|
451
|
+
constructor(existingImportMetadata, additionalImportMetadata) {
|
|
452
|
+
this.existing = existingImportMetadata;
|
|
453
|
+
this.additional = additionalImportMetadata || { imports: {}, index: {} };
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Get the additional import metadata collected while generating this view
|
|
457
|
+
* @returns
|
|
458
|
+
*/
|
|
459
|
+
getAdditionalImportMetadata() {
|
|
460
|
+
return this.additional;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Adds any new imports found to the additional metadata map. Returns a filtered
|
|
464
|
+
* map of imports not in the initial view
|
|
465
|
+
*/
|
|
466
|
+
addAdditionalMetadata(newMetadata) {
|
|
467
|
+
const filteredImports = this.filterMetadata(newMetadata);
|
|
468
|
+
this.mergeImportMetadata(this.additional, filteredImports);
|
|
469
|
+
return filteredImports;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Filter out any existing import metadata the would have already been sent back with the view from set of additional metadata detected
|
|
473
|
+
*/
|
|
474
|
+
filterMetadata(newMetadata) {
|
|
475
|
+
// Filter Imports
|
|
476
|
+
const importsArray = Object.entries(newMetadata.imports);
|
|
477
|
+
const filteredImports = importsArray.filter(([key]) => !this.existing.imports[key]);
|
|
478
|
+
const imports = Object.fromEntries(filteredImports);
|
|
479
|
+
// Filter Index
|
|
480
|
+
const indexArray = Object.entries(newMetadata.index || {});
|
|
481
|
+
const filteredIndex = indexArray.filter(([key]) => !this.existing.index || !this.existing.index[key]);
|
|
482
|
+
const index = Object.fromEntries(filteredIndex);
|
|
483
|
+
return {
|
|
484
|
+
imports,
|
|
485
|
+
index,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Merge new import metadata into target import metadata
|
|
490
|
+
*/
|
|
491
|
+
mergeImportMetadata(targetImportMetdadata, newImportMetadata) {
|
|
492
|
+
Object.assign(targetImportMetdadata.imports, newImportMetadata.imports);
|
|
493
|
+
Object.assign(targetImportMetdadata.index, newImportMetadata.index);
|
|
494
|
+
}
|
|
262
495
|
}
|
|
263
496
|
//# sourceMappingURL=static-generation.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { Endpoints, ExperimentalFeatures, ImportMetadata } from '@lwrjs/types';
|
|
1
2
|
import { RenderedAssetReference } from '@lwrjs/types';
|
|
2
3
|
export interface BaseResourceContextOpts {
|
|
3
|
-
resourceType: 'route' | 'asset' | 'js' | 'resource';
|
|
4
|
+
resourceType: 'route' | 'asset' | 'js' | 'resource' | 'mapping';
|
|
4
5
|
}
|
|
5
6
|
export interface RouteResourceOpts extends BaseResourceContextOpts {
|
|
6
7
|
resourceType: 'route';
|
|
@@ -15,11 +16,30 @@ export interface JsResourceOpts extends BaseResourceContextOpts {
|
|
|
15
16
|
export interface ResResourceOpts extends BaseResourceContextOpts {
|
|
16
17
|
resourceType: 'resource';
|
|
17
18
|
}
|
|
18
|
-
export
|
|
19
|
+
export interface MappingResourceOpts extends BaseResourceContextOpts {
|
|
20
|
+
resourceType: 'mapping';
|
|
21
|
+
}
|
|
22
|
+
export declare type ResourceContextOpts = RouteResourceOpts | AssetResourceOpts | JsResourceOpts | ResResourceOpts | MappingResourceOpts;
|
|
23
|
+
export interface ViewImportMetadata {
|
|
24
|
+
/**
|
|
25
|
+
* Get the additional import metadata collected while generating this view
|
|
26
|
+
* @returns
|
|
27
|
+
*/
|
|
28
|
+
getAdditionalImportMetadata(): ImportMetadata;
|
|
29
|
+
/**
|
|
30
|
+
* Adds any new imports found to the additional metadata map. Returns a filtered
|
|
31
|
+
* map of imports not in the initial view
|
|
32
|
+
*/
|
|
33
|
+
addAdditionalMetadata(newMetadata: ImportMetadata): ImportMetadata;
|
|
34
|
+
}
|
|
19
35
|
export interface SiteConfig {
|
|
20
36
|
outputDir: string;
|
|
21
37
|
visitedUrls: Set<string>;
|
|
22
38
|
locale: string;
|
|
23
39
|
urlRewriteMap: Map<string, string>;
|
|
40
|
+
endpoints?: Endpoints;
|
|
41
|
+
importMetadata?: ViewImportMetadata;
|
|
42
|
+
viewConfigPath?: string;
|
|
43
|
+
experimentalFeatures?: ExperimentalFeatures;
|
|
24
44
|
}
|
|
25
45
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import http from 'http';
|
|
1
3
|
import { LwrDispatcher, FsContext } from '@lwrjs/types';
|
|
2
4
|
export default class NetworkDispatcher implements LwrDispatcher {
|
|
3
5
|
port: number;
|
|
4
6
|
internalRequestKey: string;
|
|
7
|
+
pool: http.Agent;
|
|
5
8
|
constructor(port: number, internalRequestKey: string);
|
|
6
9
|
dispatchUrl(url: string, method: string, lang: string): Promise<FsContext>;
|
|
7
10
|
}
|