async render()

in modules/common/clover/server/src/server-engine.ts [38:141]


  async render(options: RenderOptions): Promise<string> {
    const { pathname, origin } = new URL(options.url);
    const prerenderedSnapshot = await this.getPrerenderedSnapshot(options.publicPath, pathname);

    if (prerenderedSnapshot) {
      return prerenderedSnapshot;
    }

    let htmlContent = await this.getHtmlTemplate(
      options.publicPath,
      pathname,
      options.htmlFilename,
    );
    const inlineCriticalCss = options.inlineCriticalCss !== false;

    const customResourceLoader = new CustomResourceLoader(
      origin,
      options.publicPath,
      this.resourceLoaderCache,
    );

    let dom: JSDOM | undefined;

    if (inlineCriticalCss) {
      // Workaround for https://github.com/GoogleChromeLabs/critters/issues/64
      htmlContent = htmlContent.replace(
        / media="print" onload="this\.media='all'"><noscript><link .+?><\/noscript>/g,
        '>',
      );
    }

    // JSDOM doesn't support type=module
    // https://github.com/jsdom/jsdom/issues/2475
    htmlContent = htmlContent.replace(/ type="module"/g, '');

    try {
      dom = new JSDOM(htmlContent, {
        runScripts: 'dangerously',
        resources: customResourceLoader,
        url: options.url,
        referrer: options.headers?.referrer as string | undefined,
        userAgent: options.headers?.['user-agent'] as string | undefined,
        beforeParse: (window) => {
          augmentWindowWithStubs(window);
          window.ngRenderMode = true;
        },
      });

      const doc = dom.window.document;

      // 60s timeout.
      const stablizationTimeout = setTimeout(() => {
        throw new Error('Angular application failed to stablize after in time.');
      }, 60000);

      const ngRenderMode = await new Promise<NGRenderModeAPI>((resolve) => {
        const interval = setInterval(() => {
          const ngDOMMode = dom?.window.ngRenderMode as NGRenderMode;
          if (ngDOMMode && typeof ngDOMMode === 'object') {
            // Poll until ngDOMMode is an object.
            clearTimeout(stablizationTimeout);
            clearInterval(interval);
            resolve(ngDOMMode);
          }
        }, 30);
      });

      await ngRenderMode.getWhenStable();
      doc.querySelector('[ng-version]')?.setAttribute('ng-clover', '');

      // Add Angular state
      const state = ngRenderMode.getSerializedState();
      if (state) {
        const script = doc.createElement('script');
        script.id = `${ngRenderMode.appId}-state`;
        script.setAttribute('type', 'application/json');
        script.textContent = state;
        doc.body.appendChild(script);
      }

      const content = dom.serialize();
      if (!inlineCriticalCss) {
        return content;
      }

      const baseHref = doc.querySelector('base[href]')?.getAttribute('href') ?? '';
      const {
        content: contentWithInlineCSS,
        warnings,
        errors,
      } = await this.inlineCriticalCssProcessor.process(content, {
        outputPath: path.join(options.publicPath, baseHref),
      });

      // eslint-disable-next-line no-console
      warnings?.forEach((m) => console.warn(m));
      // eslint-disable-next-line no-console
      errors?.forEach((m) => console.error(m));

      return contentWithInlineCSS;
    } finally {
      dom?.window.close();
    }
  }