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 15/** Ensure the project has the required web support settings. */ 16export class WebSupportProjectPrerequisite extends ProjectPrerequisite { 17 /** Ensure a project that hasn't explicitly disabled web support has all the required packages for running in the browser. */ 18 async assertImplementation(): Promise<void> { 19 if (env.EXPO_NO_WEB_SETUP) { 20 Log.warn('Skipping web setup: EXPO_NO_WEB_SETUP is enabled.'); 21 return; 22 } 23 Log.debug('Ensuring web support is setup'); 24 25 const result = await this._shouldSetupWebSupportAsync(); 26 27 // Ensure web packages are installed 28 await this._ensureWebDependenciesInstalledAsync({ exp: result.exp }); 29 } 30 31 /** Exposed for testing. */ 32 async _shouldSetupWebSupportAsync(): Promise<ProjectConfig> { 33 const config = getConfig(this.projectRoot); 34 35 // Detect if the 'web' string is purposefully missing from the platforms array. 36 if (isWebPlatformExcluded(config.rootConfig)) { 37 // Get exact config description with paths. 38 const configName = getProjectConfigDescriptionWithPaths(this.projectRoot, config); 39 throw new PrerequisiteCommandError( 40 'WEB_SUPPORT', 41 chalk`Skipping web setup: {bold "web"} is not included in the project ${configName} {bold "platforms"} array.` 42 ); 43 } 44 45 return config; 46 } 47 48 /** Exposed for testing. */ 49 async _ensureWebDependenciesInstalledAsync({ exp }: { exp: ExpoConfig }): Promise<boolean> { 50 try { 51 return await ensureDependenciesAsync(this.projectRoot, { 52 exp, 53 installMessage: `It looks like you're trying to use web support but don't have the required dependencies installed.`, 54 warningMessage: chalk`If you're not using web, please remove the {bold "web"} string from the platforms array in the project Expo config.`, 55 requiredPackages: [ 56 // use react-native-web/package.json to skip node module cache issues when the user installs 57 // the package and attempts to resolve the module in the same process. 58 { file: 'react-native-web/package.json', pkg: 'react-native-web', version: '~0.17.1' }, 59 { file: 'react-dom/package.json', pkg: 'react-dom', version: '^17.0.1' }, 60 // `webpack` and `webpack-dev-server` should be installed in the `@expo/webpack-config` 61 // package, but just in case we'll do the check now. 62 { 63 file: 'webpack-dev-server/package.json', 64 // https://github.com/expo/expo-cli/pull/4282 65 pkg: 'webpack-dev-server', 66 version: '~3.11.0', 67 dev: true, 68 }, 69 { 70 file: '@expo/webpack-config/package.json', 71 pkg: '@expo/webpack-config', 72 version: '~0.16.2', 73 dev: true, 74 }, 75 ], 76 }); 77 } catch (error) { 78 // 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. 79 this.resetAssertion(); 80 throw error; 81 } 82 } 83} 84 85/** Return `true` if the `web` platform is purposefully excluded from the project Expo config. */ 86export function isWebPlatformExcluded(rootConfig: AppJSONConfig): boolean { 87 // Detect if the 'web' string is purposefully missing from the platforms array. 88 const isWebExcluded = 89 Array.isArray(rootConfig.expo?.platforms) && 90 !!rootConfig.expo?.platforms.length && 91 !rootConfig.expo?.platforms.includes('web'); 92 return isWebExcluded; 93} 94