xref: /expo/packages/@expo/cli/src/export/favicon.ts (revision dbfb9e4d)
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  outputDir: 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  return injectFaviconTag;
40}
41
42function injectFaviconTag(html: string): string {
43  if (!html.includes('</head>')) {
44    return html;
45  }
46  return html.replace('</head>', `<link rel="shortcut icon" href="/favicon.ico" /></head>`);
47}
48
49export async function getFaviconFromExpoConfigAsync(projectRoot: string) {
50  const { exp } = getConfig(projectRoot);
51
52  const src = exp.web?.favicon ?? null;
53  if (!src) {
54    return null;
55  }
56
57  const dims = [16, 32, 48];
58  const cacheType = 'favicon';
59
60  const size = dims[dims.length - 1];
61  const { source } = await generateImageAsync(
62    { projectRoot, cacheType },
63    {
64      resizeMode: 'contain',
65      src,
66      backgroundColor: 'transparent',
67      width: size,
68      height: size,
69      name: `favicon-${size}.png`,
70    }
71  );
72
73  const faviconBuffer = await generateFaviconAsync(source, dims);
74
75  return { source: faviconBuffer, path: 'favicon.ico' };
76}
77