1import { 2 AppJSONConfig, 3 ExpoConfig, 4 getConfig, 5 getProjectConfigDescriptionWithPaths, 6 ProjectConfig, 7} from '@expo/config'; 8import chalk from 'chalk'; 9 10import * as Log from '../../../log'; 11import { env } from '../../../utils/env'; 12import { PrerequisiteCommandError, ProjectPrerequisite } from '../Prerequisite'; 13import { ensureDependenciesAsync } from '../dependencies/ensureDependenciesAsync'; 14 15const debug = require('debug')('expo:doctor:webSupport') as typeof console.log; 16 17/** Ensure the project has the required web support settings. */ 18export class WebSupportProjectPrerequisite extends ProjectPrerequisite { 19 /** Ensure a project that hasn't explicitly disabled web support has all the required packages for running in the browser. */ 20 async assertImplementation(): Promise<void> { 21 if (env.EXPO_NO_WEB_SETUP) { 22 Log.warn('Skipping web setup: EXPO_NO_WEB_SETUP is enabled.'); 23 return; 24 } 25 debug('Ensuring web support is setup'); 26 27 const result = await this._shouldSetupWebSupportAsync(); 28 29 // Ensure web packages are installed 30 await this._ensureWebDependenciesInstalledAsync({ exp: result.exp }); 31 } 32 33 /** Exposed for testing. */ 34 async _shouldSetupWebSupportAsync(): Promise<ProjectConfig> { 35 const config = getConfig(this.projectRoot); 36 37 // Detect if the 'web' string is purposefully missing from the platforms array. 38 if (isWebPlatformExcluded(config.rootConfig)) { 39 // Get exact config description with paths. 40 const configName = getProjectConfigDescriptionWithPaths(this.projectRoot, config); 41 throw new PrerequisiteCommandError( 42 'WEB_SUPPORT', 43 chalk`Skipping web setup: {bold "web"} is not included in the project ${configName} {bold "platforms"} array.` 44 ); 45 } 46 47 return config; 48 } 49 50 /** Exposed for testing. */ 51 async _ensureWebDependenciesInstalledAsync({ exp }: { exp: ExpoConfig }): Promise<boolean> { 52 try { 53 return await ensureDependenciesAsync(this.projectRoot, { 54 // This never seems to work when prompting, installing, and running -- instead just inform the user to run the install command and try again. 55 skipPrompt: true, 56 exp, 57 installMessage: `It looks like you're trying to use web support but don't have the required dependencies installed.`, 58 warningMessage: chalk`If you're not using web, please ensure you remove the {bold "web"} string from the platforms array in the project Expo config.`, 59 requiredPackages: [ 60 // use react-native-web/package.json to skip node module cache issues when the user installs 61 // the package and attempts to resolve the module in the same process. 62 { file: 'react-native-web/package.json', pkg: 'react-native-web', version: '~0.17.1' }, 63 { file: 'react-dom/package.json', pkg: 'react-dom', version: '^17.0.1' }, 64 // `webpack` and `webpack-dev-server` should be installed in the `@expo/webpack-config` 65 { 66 file: '@expo/webpack-config/package.json', 67 pkg: '@expo/webpack-config', 68 version: '~0.16.2', 69 dev: true, 70 }, 71 ], 72 }); 73 } catch (error) { 74 // Reset the cached check so we can re-run the check if the user re-runs the command by pressing 'w' in the Terminal UI. 75 this.resetAssertion(); 76 throw error; 77 } 78 } 79} 80 81/** Return `true` if the `web` platform is purposefully excluded from the project Expo config. */ 82export function isWebPlatformExcluded(rootConfig: AppJSONConfig): boolean { 83 // Detect if the 'web' string is purposefully missing from the platforms array. 84 const isWebExcluded = 85 Array.isArray(rootConfig.expo?.platforms) && 86 !!rootConfig.expo?.platforms.length && 87 !rootConfig.expo?.platforms.includes('web'); 88 return isWebExcluded; 89} 90