1import { ExpoConfig, getConfig, getNameFromConfig } from '@expo/config'; 2import fs from 'fs'; 3import path from 'path'; 4 5import { TEMPLATES } from '../../customize/templates'; 6import { env } from '../../utils/env'; 7 8/** 9 * Create a static HTML for SPA styled websites. 10 * This method attempts to reuse the same patterns as `@expo/webpack-config`. 11 */ 12export async function createTemplateHtmlFromExpoConfigAsync( 13 projectRoot: string, 14 { 15 scripts, 16 exp = getConfig(projectRoot, { skipSDKVersionRequirement: true }).exp, 17 }: { 18 scripts: string[]; 19 exp?: ExpoConfig; 20 } 21) { 22 return createTemplateHtmlAsync(projectRoot, { 23 langIsoCode: exp.web?.lang ?? 'en', 24 scripts, 25 title: getNameFromConfig(exp).webName ?? 'Expo App', 26 description: exp.web?.description, 27 themeColor: exp.web?.themeColor, 28 }); 29} 30 31function getFileFromLocalPublicFolder( 32 projectRoot: string, 33 { publicFolder, filePath }: { publicFolder: string; filePath: string } 34): string | null { 35 const localFilePath = path.resolve(projectRoot, publicFolder, filePath); 36 if (!fs.existsSync(localFilePath)) { 37 return null; 38 } 39 return localFilePath; 40} 41 42/** Attempt to read the `index.html` from the local project before falling back on the template `index.html`. */ 43async function getTemplateIndexHtmlAsync(projectRoot: string): Promise<string> { 44 let filePath = getFileFromLocalPublicFolder(projectRoot, { 45 // TODO: Maybe use the app.json override. 46 publicFolder: env.EXPO_PUBLIC_FOLDER, 47 filePath: 'index.html', 48 }); 49 if (!filePath) { 50 filePath = TEMPLATES.find((value) => value.id === 'index.html')!.file(projectRoot); 51 } 52 return fs.promises.readFile(filePath, 'utf8'); 53} 54 55/** Return an `index.html` string with template values added. */ 56export async function createTemplateHtmlAsync( 57 projectRoot: string, 58 { 59 scripts, 60 description, 61 langIsoCode, 62 title, 63 themeColor, 64 }: { 65 scripts: string[]; 66 description?: string; 67 langIsoCode: string; 68 title: string; 69 themeColor?: string; 70 } 71): Promise<string> { 72 // Resolve the best possible index.html template file. 73 let contents = await getTemplateIndexHtmlAsync(projectRoot); 74 75 contents = contents.replace('%LANG_ISO_CODE%', langIsoCode); 76 contents = contents.replace('%WEB_TITLE%', title); 77 contents = contents.replace( 78 '</body>', 79 scripts.map((url) => `<script src="${url}"></script>`).join('') + '</body>' 80 ); 81 82 if (themeColor) { 83 contents = addMeta(contents, `name="theme-color" content="${themeColor}"`); 84 } 85 86 if (description) { 87 contents = addMeta(contents, `name="description" content="${description}"`); 88 } 89 90 return contents; 91} 92 93/** Add a `<meta />` tag to the `<head />` element. */ 94function addMeta(contents: string, meta: string): string { 95 return contents.replace('</head>', `<meta ${meta}>\n</head>`); 96} 97