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