export async function augmentIndexHtml()

in packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html.ts [54:197]


export async function augmentIndexHtml(
  params: AugmentIndexHtmlOptions,
): Promise<{ content: string; warnings: string[]; errors: string[] }> {
  const { loadOutputFile, files, entrypoints, sri, deployUrl = '', lang, baseHref, html } = params;

  const warnings: string[] = [];
  const errors: string[] = [];

  let { crossOrigin = 'none' } = params;
  if (sri && crossOrigin === 'none') {
    crossOrigin = 'anonymous';
  }

  const stylesheets = new Set<string>();
  const scripts = new Map</** file name */ string, /** isModule */ boolean>();

  // Sort files in the order we want to insert them by entrypoint
  for (const [entrypoint, isModule] of entrypoints) {
    for (const { extension, file, name } of files) {
      if (name !== entrypoint || scripts.has(file) || stylesheets.has(file)) {
        continue;
      }

      switch (extension) {
        case '.js':
          // Also, non entrypoints need to be loaded as no module as they can contain problematic code.
          scripts.set(file, isModule);
          break;
        case '.css':
          stylesheets.add(file);
          break;
      }
    }
  }

  let scriptTags: string[] = [];
  for (const [src, isModule] of scripts) {
    const attrs = [`src="${deployUrl}${src}"`];

    // This is also need for non entry-points as they may contain problematic code.
    if (isModule) {
      attrs.push('type="module"');
    } else {
      attrs.push('defer');
    }

    if (crossOrigin !== 'none') {
      attrs.push(`crossorigin="${crossOrigin}"`);
    }

    if (sri) {
      const content = await loadOutputFile(src);
      attrs.push(generateSriAttributes(content));
    }

    scriptTags.push(`<script ${attrs.join(' ')}></script>`);
  }

  let linkTags: string[] = [];
  for (const src of stylesheets) {
    const attrs = [`rel="stylesheet"`, `href="${deployUrl}${src}"`];

    if (crossOrigin !== 'none') {
      attrs.push(`crossorigin="${crossOrigin}"`);
    }

    if (sri) {
      const content = await loadOutputFile(src);
      attrs.push(generateSriAttributes(content));
    }

    linkTags.push(`<link ${attrs.join(' ')}>`);
  }

  const dir = lang ? await getLanguageDirection(lang, warnings) : undefined;
  const { rewriter, transformedContent } = await htmlRewritingStream(html);
  const baseTagExists = html.includes('<base');

  rewriter
    .on('startTag', (tag) => {
      switch (tag.tagName) {
        case 'html':
          // Adjust document locale if specified
          if (isString(lang)) {
            updateAttribute(tag, 'lang', lang);
          }

          if (dir) {
            updateAttribute(tag, 'dir', dir);
          }
          break;
        case 'head':
          // Base href should be added before any link, meta tags
          if (!baseTagExists && isString(baseHref)) {
            rewriter.emitStartTag(tag);
            rewriter.emitRaw(`<base href="${baseHref}">`);

            return;
          }
          break;
        case 'base':
          // Adjust base href if specified
          if (isString(baseHref)) {
            updateAttribute(tag, 'href', baseHref);
          }
          break;
      }

      rewriter.emitStartTag(tag);
    })
    .on('endTag', (tag) => {
      switch (tag.tagName) {
        case 'head':
          for (const linkTag of linkTags) {
            rewriter.emitRaw(linkTag);
          }

          linkTags = [];
          break;
        case 'body':
          // Add script tags
          for (const scriptTag of scriptTags) {
            rewriter.emitRaw(scriptTag);
          }

          scriptTags = [];
          break;
      }

      rewriter.emitEndTag(tag);
    });

  const content = await transformedContent;

  return {
    content:
      linkTags.length || scriptTags.length
        ? // In case no body/head tags are not present (dotnet partial templates)
          linkTags.join('') + scriptTags.join('') + content
        : content,
    warnings,
    errors,
  };
}