xref: /expo/packages/@expo/cli/src/export/favicon.ts (revision 7c98c357)
1import { getConfig } from '@expo/config';
2import { generateFaviconAsync, generateImageAsync } from '@expo/image-utils';
3import fs from 'fs';
4import path from 'path';
5
6import { getUserDefinedFile } from './publicFolder';
7
8const debug = require('debug')('expo:favicon') as typeof console.log;
9
10/** @returns the file system path for a user-defined favicon.ico file in the public folder. */
11export function getUserDefinedFaviconFile(projectRoot: string): string | null {
12  return getUserDefinedFile(projectRoot, ['./favicon.ico']);
13}
14
15export async function getVirtualFaviconAssetsAsync(
16  projectRoot: string,
17  { basePath, outputDir }: { outputDir: string; basePath: string }
18): Promise<((html: string) => string) | null> {
19  const existing = getUserDefinedFaviconFile(projectRoot);
20  if (existing) {
21    debug('Using user-defined favicon.ico file.');
22    return null;
23  }
24
25  const data = await getFaviconFromExpoConfigAsync(projectRoot);
26
27  if (!data) {
28    return null;
29  }
30
31  await Promise.all(
32    [data].map((asset) => {
33      const assetPath = path.join(outputDir, asset.path);
34      debug('Writing asset to disk: ' + assetPath);
35      return fs.promises.writeFile(assetPath, asset.source);
36    })
37  );
38
39  function injectFaviconTag(html: string): string {
40    if (!html.includes('</head>')) {
41      return html;
42    }
43    return html.replace(
44      '</head>',
45      `<link rel="shortcut icon" href="${basePath}/favicon.ico" /></head>`
46    );
47  }
48
49  return injectFaviconTag;
50}
51
52export async function getFaviconFromExpoConfigAsync(projectRoot: string) {
53  const { exp } = getConfig(projectRoot);
54
55  const src = exp.web?.favicon ?? null;
56  if (!src) {
57    return null;
58  }
59
60  const dims = [16, 32, 48];
61  const cacheType = 'favicon';
62
63  const size = dims[dims.length - 1];
64  const { source } = await generateImageAsync(
65    { projectRoot, cacheType },
66    {
67      resizeMode: 'contain',
68      src,
69      backgroundColor: 'transparent',
70      width: size,
71      height: size,
72      name: `favicon-${size}.png`,
73    }
74  );
75
76  const faviconBuffer = await generateFaviconAsync(source, dims);
77
78  return { source: faviconBuffer, path: 'favicon.ico' };
79}
80