From 1cb92e538a13eae28c17c36f82a3fbbc8e96392e Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Fri, 11 Sep 2020 12:49:25 +0200 Subject: [PATCH 01/19] Added support for disabling dynamic config evaluation --- packages/config/package.json | 1 + packages/config/src/Config.ts | 8 ++ packages/config/src/Config.types.ts | 9 +- .../config/src/__tests__/getConfig-test.js | 94 ++++++++++--------- packages/config/src/evalConfig.ts | 40 ++++++++ packages/config/src/getConfig.ts | 33 +++---- packages/config/src/scripts/read-config.ts | 33 ++----- 7 files changed, 133 insertions(+), 85 deletions(-) create mode 100644 packages/config/src/evalConfig.ts diff --git a/packages/config/package.json b/packages/config/package.json index deb693d628..bdfed5bb75 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -38,6 +38,7 @@ "@expo/json-file": "8.2.22", "@expo/plist": "0.0.9", "fs-extra": "9.0.0", + "getenv": "~1.0.0", "glob": "7.1.6", "invariant": "^2.2.4", "resolve-from": "^5.0.0", diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 80d0736d27..54c018c207 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -1,5 +1,6 @@ import JsonFile, { JSONObject } from '@expo/json-file'; import fs from 'fs-extra'; +import { boolish } from 'getenv'; import { sync as globSync } from 'glob'; import path from 'path'; import semver from 'semver'; @@ -22,6 +23,8 @@ import { getRootPackageJsonPath, projectHasModule } from './Modules'; import { getExpoSDKVersion } from './Project'; import { getDynamicConfig, getStaticConfig } from './getConfig'; +export const isDynamicEvalEnabled = boolish('EXPO_HOT_CONFIG', false); + /** * If a config has an `expo` object then that will be used as the config. * This method reduces out other top level values if an `expo` object exists. @@ -82,6 +85,10 @@ function getSupportedPlatforms( * @param options enforce criteria for a project config */ export function getConfig(projectRoot: string, options: GetConfigOptions = {}): ProjectConfig { + if (typeof options.useDynamicEval === 'undefined' && isDynamicEvalEnabled) { + options.useDynamicEval = isDynamicEvalEnabled; + } + const paths = getConfigFilePaths(projectRoot); const rawStaticConfig = paths.staticConfigPath ? getStaticConfig(paths.staticConfigPath) : null; @@ -125,6 +132,7 @@ export function getConfig(projectRoot: string, options: GetConfigOptions = {}): staticConfigPath: paths.staticConfigPath, packageJsonPath, config: getContextConfig(staticConfig), + useDynamicEval: options.useDynamicEval, } ); // Allow for the app.config.js to `export default null;` diff --git a/packages/config/src/Config.types.ts b/packages/config/src/Config.types.ts index 149733514d..0d0f4c6f0d 100644 --- a/packages/config/src/Config.types.ts +++ b/packages/config/src/Config.types.ts @@ -1037,9 +1037,16 @@ export type ConfigContext = { staticConfigPath: string | null; packageJsonPath: string | null; config: Partial; + /** + * true when the dynamic config should be read from a spawned process. Has no effect on static (JSON) configs. + * This ensures that the config results are fresh without having to restart the running process that requested the config. + * + * Spawning can have huge performance penalties on low-end computers. + */ + useDynamicEval?: boolean; }; -export type GetConfigOptions = { +export type GetConfigOptions = Pick & { skipSDKVersionRequirement?: boolean; strict?: boolean; }; diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js index 18a97860aa..8db120d731 100644 --- a/packages/config/src/__tests__/getConfig-test.js +++ b/packages/config/src/__tests__/getConfig-test.js @@ -37,54 +37,64 @@ describe('modifyConfigAsync', () => { }); describe('getDynamicConfig', () => { - // This tests error are thrown properly and ensures that a more specific - // config is used instead of defaulting to a valid substitution. - it(`throws a useful error for dynamic configs with a syntax error`, () => { - const paths = getConfigFilePaths(join(__dirname, 'fixtures/behavior/syntax-error')); - expect(() => getDynamicConfig(paths.dynamicConfigPath, {})).toThrowError( - 'Unexpected token (3:4)' - ); - }); - it(`exports a function`, () => { - expect( - getDynamicConfig( - join(__dirname, 'fixtures/behavior/dynamic-export-types/exports-function.app.config.js'), - {} - ).exportedObjectType - ).toBe('function'); - }); - it(`exports an object`, () => { - expect( - getDynamicConfig( - join(__dirname, 'fixtures/behavior/dynamic-export-types/exports-object.app.config.js'), - {} - ).exportedObjectType - ).toBe('object'); - }); + for (const useDynamicEval of [true, false]) { + describe(useDynamicEval ? 'dynamic eval' : 'standard eval', () => { + // This tests error are thrown properly and ensures that a more specific + // config is used instead of defaulting to a valid substitution. + it(`throws a useful error for dynamic configs with a syntax error`, () => { + const paths = getConfigFilePaths(join(__dirname, 'fixtures/behavior/syntax-error')); + expect(() => getDynamicConfig(paths.dynamicConfigPath, { useDynamicEval })).toThrowError( + 'Unexpected token (3:4)' + ); + }); + it(`exports a function`, () => { + expect( + getDynamicConfig( + join( + __dirname, + 'fixtures/behavior/dynamic-export-types/exports-function.app.config.js' + ), + { useDynamicEval } + ).exportedObjectType + ).toBe('function'); + }); + it(`exports an object`, () => { + expect( + getDynamicConfig( + join(__dirname, 'fixtures/behavior/dynamic-export-types/exports-object.app.config.js'), + { useDynamicEval } + ).exportedObjectType + ).toBe('object'); + }); - describe('process.cwd in a child process', () => { - const originalCwd = process.cwd(); - const projectRoot = join(__dirname, 'fixtures/behavior/dynamic-cwd'); + describe('process.cwd in a child process', () => { + const originalCwd = process.cwd(); + const projectRoot = join(__dirname, 'fixtures/behavior/dynamic-cwd'); - beforeEach(() => { - process.chdir(__dirname); - }); + beforeEach(() => { + process.chdir(__dirname); + }); - afterEach(() => { - process.chdir(originalCwd); - }); + afterEach(() => { + process.chdir(originalCwd); + }); - it('process.cwd in read-config script is not equal to the project root', () => { - const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), {}); - expect(config.processCwd).toBe(__dirname); - }); - it('process.cwd in read-config script is equal to the project root', () => { - const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), { - projectRoot, + it('process.cwd in read-config script is not equal to the project root', () => { + const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), { + useDynamicEval, + }); + expect(config.processCwd).toBe(__dirname); + }); + it('process.cwd in read-config script is equal to the project root', () => { + const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), { + projectRoot, + useDynamicEval, + }); + expect(config.processCwd).toBe(projectRoot); + }); }); - expect(config.processCwd).toBe(projectRoot); }); - }); + } }); describe('getStaticConfig', () => { diff --git a/packages/config/src/evalConfig.ts b/packages/config/src/evalConfig.ts new file mode 100644 index 0000000000..a67c39af38 --- /dev/null +++ b/packages/config/src/evalConfig.ts @@ -0,0 +1,40 @@ +import { AppJSONConfig, ConfigContext, ExpoConfig } from './Config.types'; +import { ConfigError } from './Errors'; +import { serializeAndEvaluate } from './Serialize'; + +type RawDynamicConfig = AppJSONConfig | Partial | null; + +export type DynamicConfigResults = { config: RawDynamicConfig; exportedObjectType: string }; + +/** + * transpile and evaluate the dynamic config object. + * This method is shared between the standard reading method in getConfig, and the headless script. + * + * @param options configPath path to the dynamic app.config.*, request to send to the dynamic config if it exports a function. + * @returns the serialized and evaluated config along with the exported object type (object or function). + */ +export function evalConfig( + configFile: string, + request: ConfigContext | null +): DynamicConfigResults { + require('@babel/register')({ + only: [configFile], + extensions: ['.ts', '.js'], + presets: [require.resolve('@expo/babel-preset-cli')], + }); + + let result = require(configFile); + if (result.default != null) { + result = result.default; + } + const exportedObjectType = typeof result; + if (typeof result === 'function') { + result = result(request); + } + + if (result instanceof Promise) { + throw new ConfigError(`Config file ${configFile} cannot return a Promise.`, 'INVALID_CONFIG'); + } + + return { config: serializeAndEvaluate(result), exportedObjectType }; +} diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index cefa2b4208..d2cd04c080 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -3,25 +3,21 @@ import { spawnSync } from 'child_process'; import { AppJSONConfig, ConfigContext, ExpoConfig } from './Config.types'; import { ConfigError, errorFromJSON } from './Errors'; -import { fileExists } from './Modules'; import { serializeAndEvaluate } from './Serialize'; - -type RawDynamicConfig = AppJSONConfig | Partial | null; - -type DynamicConfigResults = { config: RawDynamicConfig; exportedObjectType: string }; +import { DynamicConfigResults, evalConfig } from './evalConfig'; function isMissingFileCode(code: string): boolean { return ['ENOENT', 'MODULE_NOT_FOUND', 'ENOTDIR'].includes(code); } -function readConfigFile( - configFilePath: string, - context: ConfigContext -): null | DynamicConfigResults { - if (!fileExists(configFilePath)) return null; - +// We cannot use async config resolution right now because Next.js doesn't support async configs. +// If they don't add support for async Webpack configs then we may need to pull support for Next.js. +function readConfigFile(configFile: string, context: ConfigContext): null | DynamicConfigResults { try { - return evalConfig(configFilePath, context); + if (context.useDynamicEval) { + return spawnAndEvalConfig(configFile, context); + } + return evalConfig(configFile, context); } catch (error) { // If the file doesn't exist then we should skip it and continue searching. if (!isMissingFileCode(error.code)) { @@ -34,22 +30,23 @@ function readConfigFile( export function getDynamicConfig(configPath: string, request: ConfigContext): DynamicConfigResults { const config = readConfigFile(configPath, request); if (config) { - return serializeAndEvaluate(config); + // The config must be serialized and evaluated ahead of time so the spawned process can send it over. + return config; } + // TODO: It seems this is only thrown if the file cannot be found (which may never happen). + // If so we should throw a more helpful error. throw new ConfigError(`Failed to read config at: ${configPath}`, 'INVALID_CONFIG'); } -export function getStaticConfig(configPath: string): AppJSONConfig | ExpoConfig | null { +export function getStaticConfig(configPath: string): AppJSONConfig | ExpoConfig { const config = JsonFile.read(configPath, { json5: true }); if (config) { - return serializeAndEvaluate(config); + return config as any; } throw new ConfigError(`Failed to read config at: ${configPath}`, 'INVALID_CONFIG'); } -// We cannot use async config resolution right now because Next.js doesn't support async configs. -// If they don't add support for async Webpack configs then we may need to pull support for Next.js. -function evalConfig(configFile: string, request: ConfigContext): DynamicConfigResults { +function spawnAndEvalConfig(configFile: string, request: ConfigContext): DynamicConfigResults { const spawnResults = spawnSync( 'node', [ diff --git a/packages/config/src/scripts/read-config.ts b/packages/config/src/scripts/read-config.ts index 2f89ae2029..5528a3244b 100644 --- a/packages/config/src/scripts/read-config.ts +++ b/packages/config/src/scripts/read-config.ts @@ -4,8 +4,8 @@ import path from 'path'; import { ConfigContext } from '../Config.types'; -import { ConfigError, errorToJSON } from '../Errors'; -import { serializeAndEvaluate } from '../Serialize'; +import { errorToJSON } from '../Errors'; +import { evalConfig } from '../evalConfig'; // Makes the script crash on unhandled rejections instead of silently // ignoring them. In the future, promise rejections that are not handled will @@ -21,32 +21,17 @@ let request: ConfigContext | null = null; if (typeof requestArg === 'string') { try { request = JSON.parse(requestArg) as ConfigContext; - } catch (_) {} + } catch { + // do nothing if the request fails to parse. + // TODO: document a case for why we should do nothing, perhaps it would make sense to have an error classification for this in the future. + } } try { + // TODO: do we need to resolve here? const configFile = path.resolve(configFileArg); - - require('@babel/register')({ - only: [configFile], - extensions: ['.ts', '.js'], - presets: [require.resolve('@expo/babel-preset-cli')], - }); - - let result = require(configFile); - if (result.default != null) { - result = result.default; - } - const exportedObjectType = typeof result; - if (typeof result === 'function') { - result = result(request); - } - - if (result instanceof Promise) { - throw new ConfigError(`Config file ${configFile} cannot return a Promise.`, 'INVALID_CONFIG'); - } - - console.log(JSON.stringify({ config: serializeAndEvaluate(result), exportedObjectType })); + const result = evalConfig(configFile, request); + console.log(JSON.stringify(result)); process.exit(0); } catch (error) { console.error(JSON.stringify(errorToJSON(error))); From 5dfb0e3e2d51120447c11f7d4624c4b28d684898 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Fri, 11 Sep 2020 14:16:17 +0200 Subject: [PATCH 02/19] Update getConfig-test.js --- .../config/src/__tests__/getConfig-test.js | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js index 8db120d731..5a5063f767 100644 --- a/packages/config/src/__tests__/getConfig-test.js +++ b/packages/config/src/__tests__/getConfig-test.js @@ -79,19 +79,23 @@ describe('getDynamicConfig', () => { process.chdir(originalCwd); }); - it('process.cwd in read-config script is not equal to the project root', () => { - const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), { - useDynamicEval, + if (useDynamicEval) { + // Test that dynamic evaluation is spawned in the expected location + // https://github.com/expo/expo-cli/pull/2220 + it('process.cwd in read-config script is not equal to the project root', () => { + const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), { + useDynamicEval, + }); + expect(config.processCwd).toBe(__dirname); }); - expect(config.processCwd).toBe(__dirname); - }); - it('process.cwd in read-config script is equal to the project root', () => { - const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), { - projectRoot, - useDynamicEval, + it('process.cwd in read-config script is equal to the project root', () => { + const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), { + projectRoot, + useDynamicEval, + }); + expect(config.processCwd).toBe(projectRoot); }); - expect(config.processCwd).toBe(projectRoot); - }); + } }); }); } From d8c63e6332a88c3af5c2901b921a1547e26ccbcb Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Fri, 11 Sep 2020 17:01:20 +0200 Subject: [PATCH 03/19] bump versions --- packages/config/package.json | 4 ++-- packages/config/src/Config.ts | 8 +++++++- yarn.lock | 13 ++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/config/package.json b/packages/config/package.json index bdfed5bb75..cdb879d78f 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -32,13 +32,13 @@ "paths" ], "dependencies": { - "@babel/register": "^7.8.3", + "@babel/register": "~7.11.5", "@expo/babel-preset-cli": "0.2.17", "@expo/image-utils": "0.3.5", "@expo/json-file": "8.2.22", "@expo/plist": "0.0.9", "fs-extra": "9.0.0", - "getenv": "~1.0.0", + "getenv": "0.7.0", "glob": "7.1.6", "invariant": "^2.2.4", "resolve-from": "^5.0.0", diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 54c018c207..1ff01e9294 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -158,7 +158,13 @@ function getPackageJsonAndPath( config: Pick = {} ): [PackageJSONConfig, string] { const packageJsonPath = getRootPackageJsonPath(projectRoot, config); - return [JsonFile.read(packageJsonPath), packageJsonPath]; + + try { + return [JsonFile.read(packageJsonPath), packageJsonPath]; + } catch (error) { + error.message = `Failed to parse package.json at ${packageJsonPath}\n${error.message}`; + throw error; + } } export function readConfigJson( diff --git a/yarn.lock b/yarn.lock index 63e9f8e3a9..83c0a9f4e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1129,7 +1129,7 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-transform-typescript" "^7.7.4" -"@babel/register@^7.0.0", "@babel/register@^7.8.3": +"@babel/register@^7.0.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.8.3.tgz#5d5d30cfcc918437535d724b8ac1e4a60c5db1f8" integrity sha512-t7UqebaWwo9nXWClIPLPloa5pN33A2leVs8Hf0e9g9YwUP8/H9NeR7DJU+4CXo23QtjChQv5a3DjEtT83ih1rg== @@ -1140,6 +1140,17 @@ pirates "^4.0.0" source-map-support "^0.5.16" +"@babel/register@~7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.11.5.tgz#79becf89e0ddd0fba8b92bc279bc0f5d2d7ce2ea" + integrity sha512-CAml0ioKX+kOAvBQDHa/+t1fgOt3qkTIz0TrRtRAT6XY0m5qYZXR85k6/sLCNPMGhYDlCFHCYuU0ybTJbvlC6w== + dependencies: + find-cache-dir "^2.0.0" + lodash "^4.17.19" + make-dir "^2.1.0" + pirates "^4.0.0" + source-map-support "^0.5.16" + "@babel/runtime-corejs2@7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.1.2.tgz#8695811a3fd8091f54f274b9320334e5e8c62200" From 8b37d9a0f139ef8a47fce6ab261b7a72bcff6d0e Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Sat, 12 Sep 2020 20:14:49 +0200 Subject: [PATCH 04/19] Update evalConfig.ts --- packages/config/src/evalConfig.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/config/src/evalConfig.ts b/packages/config/src/evalConfig.ts index a67c39af38..619eb102d5 100644 --- a/packages/config/src/evalConfig.ts +++ b/packages/config/src/evalConfig.ts @@ -19,6 +19,7 @@ export function evalConfig( ): DynamicConfigResults { require('@babel/register')({ only: [configFile], + cache: false, extensions: ['.ts', '.js'], presets: [require.resolve('@expo/babel-preset-cli')], }); From c8a336015ace03f4a9f1caef4939a889e23849d7 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Sat, 12 Sep 2020 20:16:24 +0200 Subject: [PATCH 05/19] dynamic eval -> hot eval --- packages/config/src/Config.ts | 6 +++--- packages/config/src/Config.types.ts | 4 ++-- .../config/src/__tests__/getConfig-test.js | 18 +++++++++--------- packages/config/src/getConfig.ts | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 1ff01e9294..3ed513116f 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -85,8 +85,8 @@ function getSupportedPlatforms( * @param options enforce criteria for a project config */ export function getConfig(projectRoot: string, options: GetConfigOptions = {}): ProjectConfig { - if (typeof options.useDynamicEval === 'undefined' && isDynamicEvalEnabled) { - options.useDynamicEval = isDynamicEvalEnabled; + if (typeof options.useHotEval === 'undefined' && isDynamicEvalEnabled) { + options.useHotEval = isDynamicEvalEnabled; } const paths = getConfigFilePaths(projectRoot); @@ -132,7 +132,7 @@ export function getConfig(projectRoot: string, options: GetConfigOptions = {}): staticConfigPath: paths.staticConfigPath, packageJsonPath, config: getContextConfig(staticConfig), - useDynamicEval: options.useDynamicEval, + useHotEval: options.useHotEval, } ); // Allow for the app.config.js to `export default null;` diff --git a/packages/config/src/Config.types.ts b/packages/config/src/Config.types.ts index 0d0f4c6f0d..92059e151d 100644 --- a/packages/config/src/Config.types.ts +++ b/packages/config/src/Config.types.ts @@ -1043,10 +1043,10 @@ export type ConfigContext = { * * Spawning can have huge performance penalties on low-end computers. */ - useDynamicEval?: boolean; + useHotEval?: boolean; }; -export type GetConfigOptions = Pick & { +export type GetConfigOptions = Pick & { skipSDKVersionRequirement?: boolean; strict?: boolean; }; diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js index 5a5063f767..ed46501dc4 100644 --- a/packages/config/src/__tests__/getConfig-test.js +++ b/packages/config/src/__tests__/getConfig-test.js @@ -37,13 +37,13 @@ describe('modifyConfigAsync', () => { }); describe('getDynamicConfig', () => { - for (const useDynamicEval of [true, false]) { - describe(useDynamicEval ? 'dynamic eval' : 'standard eval', () => { + for (const useHotEval of [true, false]) { + describe(useHotEval ? 'hot eval' : 'standard eval', () => { // This tests error are thrown properly and ensures that a more specific // config is used instead of defaulting to a valid substitution. it(`throws a useful error for dynamic configs with a syntax error`, () => { const paths = getConfigFilePaths(join(__dirname, 'fixtures/behavior/syntax-error')); - expect(() => getDynamicConfig(paths.dynamicConfigPath, { useDynamicEval })).toThrowError( + expect(() => getDynamicConfig(paths.dynamicConfigPath, { useHotEval })).toThrowError( 'Unexpected token (3:4)' ); }); @@ -54,7 +54,7 @@ describe('getDynamicConfig', () => { __dirname, 'fixtures/behavior/dynamic-export-types/exports-function.app.config.js' ), - { useDynamicEval } + { useHotEval } ).exportedObjectType ).toBe('function'); }); @@ -62,7 +62,7 @@ describe('getDynamicConfig', () => { expect( getDynamicConfig( join(__dirname, 'fixtures/behavior/dynamic-export-types/exports-object.app.config.js'), - { useDynamicEval } + { useHotEval } ).exportedObjectType ).toBe('object'); }); @@ -79,19 +79,19 @@ describe('getDynamicConfig', () => { process.chdir(originalCwd); }); - if (useDynamicEval) { - // Test that dynamic evaluation is spawned in the expected location + if (useHotEval) { + // Test that hot evaluation is spawned in the expected location // https://github.com/expo/expo-cli/pull/2220 it('process.cwd in read-config script is not equal to the project root', () => { const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), { - useDynamicEval, + useHotEval, }); expect(config.processCwd).toBe(__dirname); }); it('process.cwd in read-config script is equal to the project root', () => { const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), { projectRoot, - useDynamicEval, + useHotEval, }); expect(config.processCwd).toBe(projectRoot); }); diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index d2cd04c080..0b9eb66429 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -14,7 +14,7 @@ function isMissingFileCode(code: string): boolean { // If they don't add support for async Webpack configs then we may need to pull support for Next.js. function readConfigFile(configFile: string, context: ConfigContext): null | DynamicConfigResults { try { - if (context.useDynamicEval) { + if (context.useHotEval) { return spawnAndEvalConfig(configFile, context); } return evalConfig(configFile, context); From 59cf81810da2bc2be3d4176a122243e9aa1fd5ac Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Sat, 12 Sep 2020 20:29:44 +0200 Subject: [PATCH 06/19] Update getConfig-test.js --- packages/config/src/__tests__/getConfig-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js index ed46501dc4..ba62dda764 100644 --- a/packages/config/src/__tests__/getConfig-test.js +++ b/packages/config/src/__tests__/getConfig-test.js @@ -37,7 +37,7 @@ describe('modifyConfigAsync', () => { }); describe('getDynamicConfig', () => { - for (const useHotEval of [true, false]) { + for (const useHotEval of [false, true]) { describe(useHotEval ? 'hot eval' : 'standard eval', () => { // This tests error are thrown properly and ensures that a more specific // config is used instead of defaulting to a valid substitution. From 5ba91ffffc9282094c507bb37439a5bd618a08f2 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Sat, 12 Sep 2020 20:50:34 +0200 Subject: [PATCH 07/19] create a more useful stacktrace for config errors --- packages/config/src/getConfig.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index 0b9eb66429..dda89def36 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -73,6 +73,12 @@ function spawnAndEvalConfig(configFile: string, request: ConfigContext): Dynamic } else { // Parse the error data and throw it as expected const errorData = JSON.parse(spawnResults.stderr.toString('utf8')); - throw errorFromJSON(errorData); + const error = errorFromJSON(errorData); + + // @ts-ignore + error.isConfigError = true; + // @ts-ignore: Replace the babel stack with a more relevant stack. + error.stack = new Error().stack; + throw error; } } From 1b66653b066e12de54c0e79bf919d8e197435394 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Sun, 13 Sep 2020 19:16:58 +0200 Subject: [PATCH 08/19] use same babel version --- packages/config/package.json | 2 +- yarn.lock | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/config/package.json b/packages/config/package.json index cdb879d78f..fea1d0e2c3 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -32,7 +32,7 @@ "paths" ], "dependencies": { - "@babel/register": "~7.11.5", + "@babel/register": "7.8.3", "@expo/babel-preset-cli": "0.2.17", "@expo/image-utils": "0.3.5", "@expo/json-file": "8.2.22", diff --git a/yarn.lock b/yarn.lock index 83c0a9f4e9..7fe38024ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1129,7 +1129,7 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-transform-typescript" "^7.7.4" -"@babel/register@^7.0.0": +"@babel/register@7.8.3", "@babel/register@^7.0.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.8.3.tgz#5d5d30cfcc918437535d724b8ac1e4a60c5db1f8" integrity sha512-t7UqebaWwo9nXWClIPLPloa5pN33A2leVs8Hf0e9g9YwUP8/H9NeR7DJU+4CXo23QtjChQv5a3DjEtT83ih1rg== @@ -1140,17 +1140,6 @@ pirates "^4.0.0" source-map-support "^0.5.16" -"@babel/register@~7.11.5": - version "7.11.5" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.11.5.tgz#79becf89e0ddd0fba8b92bc279bc0f5d2d7ce2ea" - integrity sha512-CAml0ioKX+kOAvBQDHa/+t1fgOt3qkTIz0TrRtRAT6XY0m5qYZXR85k6/sLCNPMGhYDlCFHCYuU0ybTJbvlC6w== - dependencies: - find-cache-dir "^2.0.0" - lodash "^4.17.19" - make-dir "^2.1.0" - pirates "^4.0.0" - source-map-support "^0.5.16" - "@babel/runtime-corejs2@7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.1.2.tgz#8695811a3fd8091f54f274b9320334e5e8c62200" From de78be54bbbb5be439e5609cead2215a3e8bd07d Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Sun, 13 Sep 2020 19:21:03 +0200 Subject: [PATCH 09/19] Update evalConfig.ts --- packages/config/src/evalConfig.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/config/src/evalConfig.ts b/packages/config/src/evalConfig.ts index 619eb102d5..2ac45c4485 100644 --- a/packages/config/src/evalConfig.ts +++ b/packages/config/src/evalConfig.ts @@ -7,10 +7,10 @@ type RawDynamicConfig = AppJSONConfig | Partial | null; export type DynamicConfigResults = { config: RawDynamicConfig; exportedObjectType: string }; /** - * transpile and evaluate the dynamic config object. + * Transpile and evaluate the dynamic config object. * This method is shared between the standard reading method in getConfig, and the headless script. * - * @param options configPath path to the dynamic app.config.*, request to send to the dynamic config if it exports a function. + * @param options configFile path to the dynamic app.config.*, request to send to the dynamic config if it exports a function. * @returns the serialized and evaluated config along with the exported object type (object or function). */ export function evalConfig( From ed0ddd74f8c2d7ae1d4f4d5e302426fe8f701bd3 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Sun, 13 Sep 2020 19:26:46 +0200 Subject: [PATCH 10/19] disable hot tests --- packages/config/src/__tests__/getConfig-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js index ba62dda764..6a60a1e22b 100644 --- a/packages/config/src/__tests__/getConfig-test.js +++ b/packages/config/src/__tests__/getConfig-test.js @@ -37,7 +37,7 @@ describe('modifyConfigAsync', () => { }); describe('getDynamicConfig', () => { - for (const useHotEval of [false, true]) { + for (const useHotEval of [false]) { describe(useHotEval ? 'hot eval' : 'standard eval', () => { // This tests error are thrown properly and ensures that a more specific // config is used instead of defaulting to a valid substitution. From 77f13fd97f038be97fd0729fbecbfe9e99a06ad6 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Sun, 13 Sep 2020 19:46:23 +0200 Subject: [PATCH 11/19] fix error stack --- packages/config/jest.config.js | 2 ++ packages/config/src/__tests__/getConfig-test.js | 2 +- packages/config/src/getConfig.ts | 9 ++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/config/jest.config.js b/packages/config/jest.config.js index 28d2fb05af..6d76b22436 100644 --- a/packages/config/jest.config.js +++ b/packages/config/jest.config.js @@ -2,6 +2,8 @@ const path = require('path'); module.exports = { preset: '../../jest/unit-test-config', + transformIgnorePatterns: ['@babel/register'], + verbose: true, rootDir: path.resolve(__dirname), displayName: require('./package').name, roots: ['__mocks__', 'src'], diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js index 6a60a1e22b..ba62dda764 100644 --- a/packages/config/src/__tests__/getConfig-test.js +++ b/packages/config/src/__tests__/getConfig-test.js @@ -37,7 +37,7 @@ describe('modifyConfigAsync', () => { }); describe('getDynamicConfig', () => { - for (const useHotEval of [false]) { + for (const useHotEval of [false, true]) { describe(useHotEval ? 'hot eval' : 'standard eval', () => { // This tests error are thrown properly and ensures that a more specific // config is used instead of defaulting to a valid substitution. diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index dda89def36..fde5754c77 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -21,6 +21,10 @@ function readConfigFile(configFile: string, context: ConfigContext): null | Dyna } catch (error) { // If the file doesn't exist then we should skip it and continue searching. if (!isMissingFileCode(error.code)) { + // @ts-ignore + error.isConfigError = true; + // @ts-ignore: Replace the babel stack with a more relevant stack. + error.stack = new Error().stack; throw error; } } @@ -74,11 +78,6 @@ function spawnAndEvalConfig(configFile: string, request: ConfigContext): Dynamic // Parse the error data and throw it as expected const errorData = JSON.parse(spawnResults.stderr.toString('utf8')); const error = errorFromJSON(errorData); - - // @ts-ignore - error.isConfigError = true; - // @ts-ignore: Replace the babel stack with a more relevant stack. - error.stack = new Error().stack; throw error; } } From c3d6df4d08faeb29406c77d224b6db1d3ff8aefe Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Sun, 13 Sep 2020 19:55:13 +0200 Subject: [PATCH 12/19] ignore other babel --- packages/config/jest.config.js | 2 +- packages/config/src/getConfig.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/config/jest.config.js b/packages/config/jest.config.js index 6d76b22436..67ae2853d6 100644 --- a/packages/config/jest.config.js +++ b/packages/config/jest.config.js @@ -2,7 +2,7 @@ const path = require('path'); module.exports = { preset: '../../jest/unit-test-config', - transformIgnorePatterns: ['@babel/register'], + transformIgnorePatterns: ['node_modules/@babel/core'], verbose: true, rootDir: path.resolve(__dirname), displayName: require('./package').name, diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index fde5754c77..06edfa50ce 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -24,7 +24,7 @@ function readConfigFile(configFile: string, context: ConfigContext): null | Dyna // @ts-ignore error.isConfigError = true; // @ts-ignore: Replace the babel stack with a more relevant stack. - error.stack = new Error().stack; + // error.stack = new Error().stack; throw error; } } From 4fd692a772a0790e884e101ddf67f918647d4806 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Sun, 13 Sep 2020 20:28:42 +0200 Subject: [PATCH 13/19] disable test --- packages/config/jest.config.js | 2 - .../config/src/__tests__/getConfig-test.js | 40 +++++++++---------- packages/config/src/getConfig.ts | 2 +- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/config/jest.config.js b/packages/config/jest.config.js index 67ae2853d6..28d2fb05af 100644 --- a/packages/config/jest.config.js +++ b/packages/config/jest.config.js @@ -2,8 +2,6 @@ const path = require('path'); module.exports = { preset: '../../jest/unit-test-config', - transformIgnorePatterns: ['node_modules/@babel/core'], - verbose: true, rootDir: path.resolve(__dirname), displayName: require('./package').name, roots: ['__mocks__', 'src'], diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js index ba62dda764..458cdf9bf9 100644 --- a/packages/config/src/__tests__/getConfig-test.js +++ b/packages/config/src/__tests__/getConfig-test.js @@ -39,14 +39,6 @@ describe('modifyConfigAsync', () => { describe('getDynamicConfig', () => { for (const useHotEval of [false, true]) { describe(useHotEval ? 'hot eval' : 'standard eval', () => { - // This tests error are thrown properly and ensures that a more specific - // config is used instead of defaulting to a valid substitution. - it(`throws a useful error for dynamic configs with a syntax error`, () => { - const paths = getConfigFilePaths(join(__dirname, 'fixtures/behavior/syntax-error')); - expect(() => getDynamicConfig(paths.dynamicConfigPath, { useHotEval })).toThrowError( - 'Unexpected token (3:4)' - ); - }); it(`exports a function`, () => { expect( getDynamicConfig( @@ -67,19 +59,27 @@ describe('getDynamicConfig', () => { ).toBe('object'); }); - describe('process.cwd in a child process', () => { - const originalCwd = process.cwd(); - const projectRoot = join(__dirname, 'fixtures/behavior/dynamic-cwd'); - - beforeEach(() => { - process.chdir(__dirname); + if (useHotEval) { + // This tests error are thrown properly and ensures that a more specific + // config is used instead of defaulting to a valid substitution. + it(`throws a useful error for dynamic configs with a syntax error`, () => { + const paths = getConfigFilePaths(join(__dirname, 'fixtures/behavior/syntax-error')); + expect(() => getDynamicConfig(paths.dynamicConfigPath, { useHotEval })).toThrowError( + 'Unexpected token (3:4)' + ); }); + describe('process.cwd in a child process', () => { + const originalCwd = process.cwd(); + const projectRoot = join(__dirname, 'fixtures/behavior/dynamic-cwd'); - afterEach(() => { - process.chdir(originalCwd); - }); + beforeEach(() => { + process.chdir(__dirname); + }); + + afterEach(() => { + process.chdir(originalCwd); + }); - if (useHotEval) { // Test that hot evaluation is spawned in the expected location // https://github.com/expo/expo-cli/pull/2220 it('process.cwd in read-config script is not equal to the project root', () => { @@ -95,8 +95,8 @@ describe('getDynamicConfig', () => { }); expect(config.processCwd).toBe(projectRoot); }); - } - }); + }); + } }); } }); diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index 06edfa50ce..fde5754c77 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -24,7 +24,7 @@ function readConfigFile(configFile: string, context: ConfigContext): null | Dyna // @ts-ignore error.isConfigError = true; // @ts-ignore: Replace the babel stack with a more relevant stack. - // error.stack = new Error().stack; + error.stack = new Error().stack; throw error; } } From b4f9bae4ab5e0163e8bcc14f79e65598bb6fdc25 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 14 Sep 2020 12:22:39 +0200 Subject: [PATCH 14/19] wip --- packages/config/src/__tests__/getConfig-test.js | 16 ++++++++-------- packages/config/src/evalConfig.ts | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js index 458cdf9bf9..ca188a5152 100644 --- a/packages/config/src/__tests__/getConfig-test.js +++ b/packages/config/src/__tests__/getConfig-test.js @@ -59,15 +59,15 @@ describe('getDynamicConfig', () => { ).toBe('object'); }); + // This tests error are thrown properly and ensures that a more specific + // config is used instead of defaulting to a valid substitution. + it(`throws a useful error for dynamic configs with a syntax error`, () => { + const paths = getConfigFilePaths(join(__dirname, 'fixtures/behavior/syntax-error')); + expect(() => getDynamicConfig(paths.dynamicConfigPath, { useHotEval })).toThrowError( + 'Unexpected token (3:4)' + ); + }); if (useHotEval) { - // This tests error are thrown properly and ensures that a more specific - // config is used instead of defaulting to a valid substitution. - it(`throws a useful error for dynamic configs with a syntax error`, () => { - const paths = getConfigFilePaths(join(__dirname, 'fixtures/behavior/syntax-error')); - expect(() => getDynamicConfig(paths.dynamicConfigPath, { useHotEval })).toThrowError( - 'Unexpected token (3:4)' - ); - }); describe('process.cwd in a child process', () => { const originalCwd = process.cwd(); const projectRoot = join(__dirname, 'fixtures/behavior/dynamic-cwd'); diff --git a/packages/config/src/evalConfig.ts b/packages/config/src/evalConfig.ts index 2ac45c4485..63eac79767 100644 --- a/packages/config/src/evalConfig.ts +++ b/packages/config/src/evalConfig.ts @@ -20,6 +20,7 @@ export function evalConfig( require('@babel/register')({ only: [configFile], cache: false, + cwd: request?.projectRoot ?? process.cwd(), extensions: ['.ts', '.js'], presets: [require.resolve('@expo/babel-preset-cli')], }); From f790e769fce7cf85bface12932f833c530ef9a36 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 14 Sep 2020 14:46:56 +0200 Subject: [PATCH 15/19] [wip] use babel-core directly --- packages/config/package.json | 1 + packages/config/src/evalConfig.ts | 14 +++++++++----- yarn.lock | 5 +++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/config/package.json b/packages/config/package.json index fea1d0e2c3..f484d6369f 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -41,6 +41,7 @@ "getenv": "0.7.0", "glob": "7.1.6", "invariant": "^2.2.4", + "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", "semver": "^7.1.3", "slugify": "^1.3.4", diff --git a/packages/config/src/evalConfig.ts b/packages/config/src/evalConfig.ts index 63eac79767..0d7ac2a059 100644 --- a/packages/config/src/evalConfig.ts +++ b/packages/config/src/evalConfig.ts @@ -1,3 +1,6 @@ +import fs from 'fs-extra'; +import fromString from 'require-from-string'; + import { AppJSONConfig, ConfigContext, ExpoConfig } from './Config.types'; import { ConfigError } from './Errors'; import { serializeAndEvaluate } from './Serialize'; @@ -17,15 +20,16 @@ export function evalConfig( configFile: string, request: ConfigContext | null ): DynamicConfigResults { - require('@babel/register')({ + const babel = require('@babel/core'); + const { code } = babel.transformFileSync(require.resolve(configFile), { only: [configFile], - cache: false, - cwd: request?.projectRoot ?? process.cwd(), - extensions: ['.ts', '.js'], + babelrc: false, + ignore: [/node_modules/], + filename: 'unknown', presets: [require.resolve('@expo/babel-preset-cli')], }); - let result = require(configFile); + let result = fromString(code); if (result.default != null) { result = result.default; } diff --git a/yarn.lock b/yarn.lock index 7fe38024ca..ac1fe4cf3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18653,6 +18653,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" From 5cd3718052cd6784bda842314c4ebadc8821dc90 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 14 Sep 2020 14:49:32 +0200 Subject: [PATCH 16/19] Update evalConfig.ts --- packages/config/src/evalConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/config/src/evalConfig.ts b/packages/config/src/evalConfig.ts index 0d7ac2a059..7c1d4ead4d 100644 --- a/packages/config/src/evalConfig.ts +++ b/packages/config/src/evalConfig.ts @@ -1,4 +1,4 @@ -import fs from 'fs-extra'; +// @ts-ignore import fromString from 'require-from-string'; import { AppJSONConfig, ConfigContext, ExpoConfig } from './Config.types'; From 2d5fef253dec669cd1aea378becca3d927ec5338 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 14 Sep 2020 16:19:32 +0200 Subject: [PATCH 17/19] Update package.json --- packages/config/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/config/package.json b/packages/config/package.json index f484d6369f..5ae05b6176 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -32,7 +32,7 @@ "paths" ], "dependencies": { - "@babel/register": "7.8.3", + "@babel/core": "7.9.0", "@expo/babel-preset-cli": "0.2.17", "@expo/image-utils": "0.3.5", "@expo/json-file": "8.2.22", From 6a653895a84987d35e43e8188f0de2b64772c303 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 14 Sep 2020 16:33:19 +0200 Subject: [PATCH 18/19] Remove useHotEval --- packages/config/package.json | 1 - packages/config/src/Config.ts | 8 -- packages/config/src/Config.types.ts | 9 +- .../config/src/__tests__/getConfig-test.js | 102 ++++++++---------- packages/config/src/evalConfig.ts | 6 +- packages/config/src/getConfig.ts | 39 +------ packages/config/src/scripts/read-config.ts | 39 ------- 7 files changed, 51 insertions(+), 153 deletions(-) delete mode 100644 packages/config/src/scripts/read-config.ts diff --git a/packages/config/package.json b/packages/config/package.json index 5ae05b6176..32b21ffbe7 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -38,7 +38,6 @@ "@expo/json-file": "8.2.22", "@expo/plist": "0.0.9", "fs-extra": "9.0.0", - "getenv": "0.7.0", "glob": "7.1.6", "invariant": "^2.2.4", "require-from-string": "^2.0.2", diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 3ed513116f..2fe47ee1a8 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -1,6 +1,5 @@ import JsonFile, { JSONObject } from '@expo/json-file'; import fs from 'fs-extra'; -import { boolish } from 'getenv'; import { sync as globSync } from 'glob'; import path from 'path'; import semver from 'semver'; @@ -23,8 +22,6 @@ import { getRootPackageJsonPath, projectHasModule } from './Modules'; import { getExpoSDKVersion } from './Project'; import { getDynamicConfig, getStaticConfig } from './getConfig'; -export const isDynamicEvalEnabled = boolish('EXPO_HOT_CONFIG', false); - /** * If a config has an `expo` object then that will be used as the config. * This method reduces out other top level values if an `expo` object exists. @@ -85,10 +82,6 @@ function getSupportedPlatforms( * @param options enforce criteria for a project config */ export function getConfig(projectRoot: string, options: GetConfigOptions = {}): ProjectConfig { - if (typeof options.useHotEval === 'undefined' && isDynamicEvalEnabled) { - options.useHotEval = isDynamicEvalEnabled; - } - const paths = getConfigFilePaths(projectRoot); const rawStaticConfig = paths.staticConfigPath ? getStaticConfig(paths.staticConfigPath) : null; @@ -132,7 +125,6 @@ export function getConfig(projectRoot: string, options: GetConfigOptions = {}): staticConfigPath: paths.staticConfigPath, packageJsonPath, config: getContextConfig(staticConfig), - useHotEval: options.useHotEval, } ); // Allow for the app.config.js to `export default null;` diff --git a/packages/config/src/Config.types.ts b/packages/config/src/Config.types.ts index 92059e151d..149733514d 100644 --- a/packages/config/src/Config.types.ts +++ b/packages/config/src/Config.types.ts @@ -1037,16 +1037,9 @@ export type ConfigContext = { staticConfigPath: string | null; packageJsonPath: string | null; config: Partial; - /** - * true when the dynamic config should be read from a spawned process. Has no effect on static (JSON) configs. - * This ensures that the config results are fresh without having to restart the running process that requested the config. - * - * Spawning can have huge performance penalties on low-end computers. - */ - useHotEval?: boolean; }; -export type GetConfigOptions = Pick & { +export type GetConfigOptions = { skipSDKVersionRequirement?: boolean; strict?: boolean; }; diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js index ca188a5152..9350e340e0 100644 --- a/packages/config/src/__tests__/getConfig-test.js +++ b/packages/config/src/__tests__/getConfig-test.js @@ -37,68 +37,56 @@ describe('modifyConfigAsync', () => { }); describe('getDynamicConfig', () => { - for (const useHotEval of [false, true]) { - describe(useHotEval ? 'hot eval' : 'standard eval', () => { - it(`exports a function`, () => { - expect( - getDynamicConfig( - join( - __dirname, - 'fixtures/behavior/dynamic-export-types/exports-function.app.config.js' - ), - { useHotEval } - ).exportedObjectType - ).toBe('function'); - }); - it(`exports an object`, () => { - expect( - getDynamicConfig( - join(__dirname, 'fixtures/behavior/dynamic-export-types/exports-object.app.config.js'), - { useHotEval } - ).exportedObjectType - ).toBe('object'); - }); + it(`exports a function`, () => { + expect( + getDynamicConfig( + join(__dirname, 'fixtures/behavior/dynamic-export-types/exports-function.app.config.js'), + {} + ).exportedObjectType + ).toBe('function'); + }); + it(`exports an object`, () => { + expect( + getDynamicConfig( + join(__dirname, 'fixtures/behavior/dynamic-export-types/exports-object.app.config.js'), + {} + ).exportedObjectType + ).toBe('object'); + }); + + // This tests error are thrown properly and ensures that a more specific + // config is used instead of defaulting to a valid substitution. + it(`throws a useful error for dynamic configs with a syntax error`, () => { + const paths = getConfigFilePaths(join(__dirname, 'fixtures/behavior/syntax-error')); + expect(() => getDynamicConfig(paths.dynamicConfigPath, {})).toThrowError( + 'Unexpected token (3:4)' + ); + }); - // This tests error are thrown properly and ensures that a more specific - // config is used instead of defaulting to a valid substitution. - it(`throws a useful error for dynamic configs with a syntax error`, () => { - const paths = getConfigFilePaths(join(__dirname, 'fixtures/behavior/syntax-error')); - expect(() => getDynamicConfig(paths.dynamicConfigPath, { useHotEval })).toThrowError( - 'Unexpected token (3:4)' - ); - }); - if (useHotEval) { - describe('process.cwd in a child process', () => { - const originalCwd = process.cwd(); - const projectRoot = join(__dirname, 'fixtures/behavior/dynamic-cwd'); + describe('process.cwd in a child process', () => { + const originalCwd = process.cwd(); + const projectRoot = join(__dirname, 'fixtures/behavior/dynamic-cwd'); - beforeEach(() => { - process.chdir(__dirname); - }); + beforeEach(() => { + process.chdir(__dirname); + }); - afterEach(() => { - process.chdir(originalCwd); - }); + afterEach(() => { + process.chdir(originalCwd); + }); - // Test that hot evaluation is spawned in the expected location - // https://github.com/expo/expo-cli/pull/2220 - it('process.cwd in read-config script is not equal to the project root', () => { - const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), { - useHotEval, - }); - expect(config.processCwd).toBe(__dirname); - }); - it('process.cwd in read-config script is equal to the project root', () => { - const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), { - projectRoot, - useHotEval, - }); - expect(config.processCwd).toBe(projectRoot); - }); - }); - } + // Test that hot evaluation is spawned in the expected location + // https://github.com/expo/expo-cli/pull/2220 + it('process.cwd in read-config script is not equal to the project root', () => { + const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), {}); + expect(config.processCwd).toBe(__dirname); + expect( + getDynamicConfig(join(projectRoot, 'app.config.ts'), { + projectRoot, + }).config.processCwd + ).toBe(__dirname); }); - } + }); }); describe('getStaticConfig', () => { diff --git a/packages/config/src/evalConfig.ts b/packages/config/src/evalConfig.ts index 7c1d4ead4d..61e467bb65 100644 --- a/packages/config/src/evalConfig.ts +++ b/packages/config/src/evalConfig.ts @@ -1,9 +1,10 @@ // @ts-ignore -import fromString from 'require-from-string'; +import requireString from 'require-from-string'; import { AppJSONConfig, ConfigContext, ExpoConfig } from './Config.types'; import { ConfigError } from './Errors'; import { serializeAndEvaluate } from './Serialize'; +// import babel from '@babel/core'; type RawDynamicConfig = AppJSONConfig | Partial | null; @@ -23,13 +24,14 @@ export function evalConfig( const babel = require('@babel/core'); const { code } = babel.transformFileSync(require.resolve(configFile), { only: [configFile], + cwd: request?.projectRoot || process.cwd(), babelrc: false, ignore: [/node_modules/], filename: 'unknown', presets: [require.resolve('@expo/babel-preset-cli')], }); - let result = fromString(code); + let result = requireString(code); if (result.default != null) { result = result.default; } diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index fde5754c77..e8fc35140f 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -1,9 +1,7 @@ import JsonFile from '@expo/json-file'; -import { spawnSync } from 'child_process'; import { AppJSONConfig, ConfigContext, ExpoConfig } from './Config.types'; -import { ConfigError, errorFromJSON } from './Errors'; -import { serializeAndEvaluate } from './Serialize'; +import { ConfigError } from './Errors'; import { DynamicConfigResults, evalConfig } from './evalConfig'; function isMissingFileCode(code: string): boolean { @@ -14,9 +12,6 @@ function isMissingFileCode(code: string): boolean { // If they don't add support for async Webpack configs then we may need to pull support for Next.js. function readConfigFile(configFile: string, context: ConfigContext): null | DynamicConfigResults { try { - if (context.useHotEval) { - return spawnAndEvalConfig(configFile, context); - } return evalConfig(configFile, context); } catch (error) { // If the file doesn't exist then we should skip it and continue searching. @@ -49,35 +44,3 @@ export function getStaticConfig(configPath: string): AppJSONConfig | ExpoConfig } throw new ConfigError(`Failed to read config at: ${configPath}`, 'INVALID_CONFIG'); } - -function spawnAndEvalConfig(configFile: string, request: ConfigContext): DynamicConfigResults { - const spawnResults = spawnSync( - 'node', - [ - require.resolve('@expo/config/build/scripts/read-config.js'), - '--colors', - configFile, - JSON.stringify({ ...request, config: serializeAndEvaluate(request.config) }), - ], - { cwd: request.projectRoot || process.cwd() } - ); - - if (spawnResults.status === 0) { - const spawnResultString = spawnResults.stdout.toString('utf8').trim(); - const logs = spawnResultString.split('\n'); - // Get the last console log to prevent parsing anything logged in the config. - const lastLog = logs.pop()!; - for (const log of logs) { - // Log out the logs from the config - console.log(log); - } - // Parse the final log of the script, it's the serialized config and exported object type. - const results = JSON.parse(lastLog); - return results; - } else { - // Parse the error data and throw it as expected - const errorData = JSON.parse(spawnResults.stderr.toString('utf8')); - const error = errorFromJSON(errorData); - throw error; - } -} diff --git a/packages/config/src/scripts/read-config.ts b/packages/config/src/scripts/read-config.ts deleted file mode 100644 index 5528a3244b..0000000000 --- a/packages/config/src/scripts/read-config.ts +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -import path from 'path'; - -import { ConfigContext } from '../Config.types'; -import { errorToJSON } from '../Errors'; -import { evalConfig } from '../evalConfig'; - -// Makes the script crash on unhandled rejections instead of silently -// ignoring them. In the future, promise rejections that are not handled will -// terminate the Node.js process with a non-zero exit code. -process.on('unhandledRejection', err => { - throw err; -}); - -const { 3: configFileArg, 4: requestArg } = process.argv; - -let request: ConfigContext | null = null; - -if (typeof requestArg === 'string') { - try { - request = JSON.parse(requestArg) as ConfigContext; - } catch { - // do nothing if the request fails to parse. - // TODO: document a case for why we should do nothing, perhaps it would make sense to have an error classification for this in the future. - } -} - -try { - // TODO: do we need to resolve here? - const configFile = path.resolve(configFileArg); - const result = evalConfig(configFile, request); - console.log(JSON.stringify(result)); - process.exit(0); -} catch (error) { - console.error(JSON.stringify(errorToJSON(error))); - process.exit(-1); -} From 42f77712d4fea7f02f49e0fc7f1a3fc1607f9253 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 14 Sep 2020 19:19:53 +0200 Subject: [PATCH 19/19] revert error formatting --- packages/config/src/Config.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 2fe47ee1a8..80d0736d27 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -150,13 +150,7 @@ function getPackageJsonAndPath( config: Pick = {} ): [PackageJSONConfig, string] { const packageJsonPath = getRootPackageJsonPath(projectRoot, config); - - try { - return [JsonFile.read(packageJsonPath), packageJsonPath]; - } catch (error) { - error.message = `Failed to parse package.json at ${packageJsonPath}\n${error.message}`; - throw error; - } + return [JsonFile.read(packageJsonPath), packageJsonPath]; } export function readConfigJson(