diff options
Diffstat (limited to 'src/write/build-modes')
-rw-r--r-- | src/write/build-modes/live-dev-server.js | 169 |
1 files changed, 147 insertions, 22 deletions
diff --git a/src/write/build-modes/live-dev-server.js b/src/write/build-modes/live-dev-server.js index 6dfa7d71..10b40cf0 100644 --- a/src/write/build-modes/live-dev-server.js +++ b/src/write/build-modes/live-dev-server.js @@ -11,7 +11,7 @@ import {serializeThings} from '../../data/serialize.js'; import * as pageSpecs from '../../page/index.js'; import {logInfo, logWarn, progressCallAll} from '../../util/cli.js'; - +import {empty} from '../../util/sugar.js'; import { getPagePathname, getPagePathnameAcrossLanguages, @@ -20,11 +20,21 @@ import { } from '../../util/urls.js'; import { - generateDocumentHTML, generateGlobalWikiDataJSON, generateRedirectHTML, } from '../page-template.js'; +import { + watchContentDependencies, +} from '../../content/dependencies/index.js'; + +import { + fillRelationsLayoutFromSlotResults, + flattenRelationsTree, + getRelationsTree, + getNeededContentDependencyNames, +} from '../../content-function.js'; + const defaultHost = '0.0.0.0'; const defaultPort = 8002; @@ -64,20 +74,35 @@ export async function go({ developersComment, getSizeOfAdditionalFile, getSizeOfImageFile, + niceShowAggregate, }) { + const showError = (error) => { + if (error instanceof AggregateError && niceShowAggregate) { + niceShowAggregate(error); + } else { + console.error(error); + } + }; + const host = cliOptions['host'] ?? defaultHost; const port = parseInt(cliOptions['port'] ?? defaultPort); + const contentDependenciesWatcher = await watchContentDependencies(); + const {contentDependencies: allContentDependencies} = contentDependenciesWatcher; + + contentDependenciesWatcher.on('error', () => {}); + await new Promise(resolve => contentDependenciesWatcher.once('ready', resolve)); + let targetSpecPairs = getPageSpecsWithTargets({wikiData}); const pages = progressCallAll(`Computing page data & paths for ${targetSpecPairs.length} targets.`, - targetSpecPairs.map(({ + targetSpecPairs.flatMap(({ pageSpec, target, targetless, }) => () => targetless - ? pageSpec.writeTargetless({wikiData}) - : pageSpec.write(target, {wikiData}))).flat(); + ? [pageSpec.writeTargetless({wikiData})] + : pageSpec.pathsForTarget(target))).flat(); logInfo`Will be serving a total of ${pages.length} pages.`; @@ -143,7 +168,7 @@ export async function go({ response.writeHead(500, contentTypeJSON); response.end({error: `Internal error serializing wiki JSON`}); console.error(`${requestHead} [500] /data.json`); - console.error(error); + showError(error); } return; } @@ -186,7 +211,7 @@ export async function go({ response.writeHead(500, contentTypePlain); response.end(`Internal error accessing ${localFileArea} file for: ${safePath}`); console.error(`${requestHead} [500] ${pathname}`); - console.error(error); + showError(error); } return; } @@ -239,7 +264,7 @@ export async function go({ response.writeHead(500, contentTypePlain); response.end(`Failed during file-to-response pipeline`); console.error(`${requestHead} [500] ${pathname}`); - console.error(error); + showError(error); } return; } @@ -305,8 +330,6 @@ export async function go({ return; } - response.writeHead(200, contentTypeHTML); - const localizedPathnames = getPagePathnameAcrossLanguages({ defaultLanguage, languages, @@ -314,37 +337,139 @@ export async function go({ urls, }); + const {name, args} = page.contentFunction; + const bound = bindUtilities({ absoluteTo, + cachebust, defaultLanguage, getSizeOfAdditionalFile, getSizeOfImageFile, language, languages, + pagePath: servePath, to, urls, wikiData, }); - const pageInfo = page.page(bound); - - const pageHTML = generateDocumentHTML(pageInfo, { + const allExtraDependencies = { ...bound, - cachebust, - developersComment, - localizedPathnames, - oEmbedJSONHref: null, // No oEmbed support for live dev server - pagePath: servePath, - pathname, - }); + + appendIndexHTML: false, + transformMultiline: text => `<p>${text}</p>`, + }; + + // NOTE: ALL THIS STUFF IS PASTED, REVIEW AND INTEGRATE SOON(TM) + + const treeInfo = getRelationsTree(allContentDependencies, name, wikiData, ...args); + const flatTreeInfo = flattenRelationsTree(treeInfo); + const {root, relationIdentifier, flatRelationSlots} = flatTreeInfo; + + const neededContentDependencyNames = + getNeededContentDependencyNames(allContentDependencies, name); + + // Content functions aren't recursive, so by following the set above + // sequentually, we will always provide fulfilled content functions as the + // dependencies for later content functions. + const fulfilledContentDependencies = {}; + for (const name of neededContentDependencyNames) { + const unfulfilledContentFunction = allContentDependencies[name]; + if (!unfulfilledContentFunction) continue; + + const {contentDependencies, extraDependencies} = unfulfilledContentFunction; + + if (empty(contentDependencies) && empty(extraDependencies)) { + fulfilledContentDependencies[name] = unfulfilledContentFunction; + continue; + } + + const fulfillments = {}; + + for (const dependencyName of contentDependencies ?? []) { + if (dependencyName in fulfilledContentDependencies) { + fulfillments[dependencyName] = + fulfilledContentDependencies[dependencyName]; + } + } + + for (const dependencyName of extraDependencies ?? []) { + if (dependencyName in allExtraDependencies) { + fulfillments[dependencyName] = + allExtraDependencies[dependencyName]; + } + } + + fulfilledContentDependencies[name] = + unfulfilledContentFunction.fulfill(fulfillments); + } + + // There might still be unfulfilled content functions if dependencies weren't + // provided as part of allContentDependencies or allExtraDependencies. + // Catch and report these early, together in an aggregate error. + const unfulfilledErrors = []; + const unfulfilledNames = []; + for (const name of neededContentDependencyNames) { + const contentFunction = fulfilledContentDependencies[name]; + if (!contentFunction) continue; + if (!contentFunction.fulfilled) { + try { + contentFunction(); + } catch (error) { + error.message = `(${name}) ${error.message}`; + unfulfilledErrors.push(error); + unfulfilledNames.push(name); + } + } + } + + if (!empty(unfulfilledErrors)) { + throw new AggregateError(unfulfilledErrors, `Content functions unfulfilled (${unfulfilledNames.join(', ')})`); + } + + const slotResults = {}; + + function runContentFunction({name, args, relations: flatRelations}) { + const contentFunction = fulfilledContentDependencies[name]; + + if (!contentFunction) { + throw new Error(`Content function ${name} unfulfilled or not listed`); + } + + const sprawl = + contentFunction.sprawl?.(allExtraDependencies.wikiData, ...args); + + const relations = + fillRelationsLayoutFromSlotResults(relationIdentifier, slotResults, flatRelations); + + const data = + (sprawl + ? contentFunction.data?.(sprawl, ...args) + : contentFunction.data?.(...args)); + + const generateArgs = [data, relations].filter(Boolean); + + return contentFunction(...generateArgs); + } + + for (const slot of Object.getOwnPropertySymbols(flatRelationSlots)) { + slotResults[slot] = runContentFunction(flatRelationSlots[slot]); + } + + const topLevelResult = runContentFunction(root); + + // END PASTE + + const pageHTML = topLevelResult.toString(); console.log(`${requestHead} [200] ${pathname}`); + response.writeHead(200, contentTypeHTML); response.end(pageHTML); } catch (error) { response.writeHead(500, contentTypePlain); response.end(`Error generating page, view server log for details\n`); console.error(`${requestHead} [500] ${pathname}`); - console.error(error); + showError(error); } }); @@ -360,7 +485,7 @@ export async function go({ }, 10_000); } else { console.error(`Server error detected (code: ${error.code})`); - console.error(error); + showError(error); } }); |