async function _renderUniversal()

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;
}