in modules/builders/src/prerender/index.ts [82:179]
async function _renderUniversal(
routes: string[],
context: BuilderContext,
browserResult: BuildBuilderOutput,
serverResult: BuildBuilderOutput,
browserOptions: BrowserBuilderOptions,
numProcesses?: number,
): Promise<PrerenderBuilderOutput> {
const projectName = context.target && context.target.project;
if (!projectName) {
throw new Error('The builder requires a target.');
}
const root = normalize(context.workspaceRoot);
const projectMetadata = await context.getProjectMetadata(projectName);
const projectRoot = resolvePath(root, normalize((projectMetadata.root as string) || ''));
// Users can specify a different base html file e.g. "src/home.html"
const indexFile = getIndexOutputFile(browserOptions);
const { styles: normalizedStylesOptimization } = normalizeOptimization(
browserOptions.optimization,
);
const { baseOutputPath = '' } = serverResult;
const worker = new Piscina({
filename: path.join(__dirname, 'worker.js'),
name: 'render',
maxThreads: numProcesses,
});
try {
// We need to render the routes for each locale from the browser output.
for (const outputPath of browserResult.outputPaths) {
const localeDirectory = path.relative(browserResult.baseOutputPath, outputPath);
const serverBundlePath = path.join(baseOutputPath, localeDirectory, 'main.js');
if (!fs.existsSync(serverBundlePath)) {
throw new Error(`Could not find the main bundle: ${serverBundlePath}`);
}
const spinner = ora(`Prerendering ${routes.length} route(s) to ${outputPath}...`).start();
try {
const results = (await Promise.all(
routes.map((route) => {
const options: RenderOptions = {
indexFile,
deployUrl: browserOptions.deployUrl || '',
inlineCriticalCss: !!normalizedStylesOptimization.inlineCritical,
minifyCss: !!normalizedStylesOptimization.minify,
outputPath,
route,
serverBundlePath,
};
return worker.run(options, { name: 'render' });
}),
)) as RenderResult[];
let numErrors = 0;
for (const { errors, warnings } of results) {
spinner.stop();
errors?.forEach((e) => context.logger.error(e));
warnings?.forEach((e) => context.logger.warn(e));
spinner.start();
numErrors += errors?.length ?? 0;
}
if (numErrors > 0) {
throw Error(`Rendering failed with ${numErrors} worker errors.`);
}
} catch (error) {
spinner.fail(`Prerendering routes to ${outputPath} failed.`);
return { success: false, error: error.message };
}
spinner.succeed(`Prerendering routes to ${outputPath} complete.`);
if (browserOptions.serviceWorker) {
spinner.start('Generating service worker...');
try {
await augmentAppWithServiceWorker(
projectRoot,
normalize(outputPath),
browserOptions.baseHref || '/',
browserOptions.ngswConfigPath,
);
} catch (error) {
spinner.fail('Service worker generation failed.');
return { success: false, error: error.message };
}
spinner.succeed('Service worker generation complete.');
}
}
} finally {
void worker.destroy();
}
return browserResult;
}