From 07c1f954386edb072fd6d5dbc6d55ee5acd83217 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Thu, 3 Jan 2019 16:04:16 +0100 Subject: [PATCH 01/34] Set TS base url based on node_path --- packages/react-scripts/config/webpack.config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 3696597fb44..fd23e4b6829 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -629,6 +629,8 @@ module.exports = function(webpackEnv) { isolatedModules: true, noEmit: true, jsx: 'preserve', + // Typescript expects the baseUrl to be relative from the root directory (e.g. src) + baseUrl: process.env.NODE_PATH.replace(paths.appPath + '/', ''), }, reportFiles: [ '**', From ec03a780dc79a059037d6ef7ebcc60058544acc4 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Thu, 3 Jan 2019 17:05:53 +0100 Subject: [PATCH 02/34] Remove TS messages for now --- .../react-scripts/scripts/utils/verifyTypeScriptSetup.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js b/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js index d8da57a3509..7467a7016c6 100644 --- a/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js +++ b/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js @@ -121,13 +121,6 @@ function verifyTypeScriptSetup() { value: 'preserve', reason: 'JSX is compiled by Babel', }, - // We do not support absolute imports, though this may come as a future - // enhancement - baseUrl: { - value: undefined, - reason: 'absolute imports are not supported (yet)', - }, - paths: { value: undefined, reason: 'aliased imports are not supported' }, }; const formatDiagnosticHost = { From ebd765076f3c466c4154a7aabd191da9dcaa3f87 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Thu, 3 Jan 2019 17:15:34 +0100 Subject: [PATCH 03/34] Resole baseUrl for tsconfig/jsconfig.json --- packages/react-scripts/config/config.js | 81 +++++++++++++++++++ packages/react-scripts/config/paths.js | 3 + .../react-scripts/config/webpack.config.js | 9 +-- 3 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 packages/react-scripts/config/config.js diff --git a/packages/react-scripts/config/config.js b/packages/react-scripts/config/config.js new file mode 100644 index 00000000000..24e8e08b1cc --- /dev/null +++ b/packages/react-scripts/config/config.js @@ -0,0 +1,81 @@ +// @remove-on-eject-begin +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +// @remove-on-eject-end +'use strict'; + +const fs = require('fs'); +const chalk = require('chalk'); +const paths = require('./paths'); + +/** + * Get the baseUrl of a compilerOptions object. + * + * @param {Object} options + */ +function getBaseUrl(options = {}) { + const baseUrl = options.baseUrl; + + if (!baseUrl) { + return null; + } + + if (baseUrl !== 'src' && baseUrl !== './src') { + console.error( + chalk.red.bold( + "You tried to set baseUrl to anything other than 'src'. This is not supported in create-react-app and will be ignored." + ) + ); + + return null; + } + + return 'src'; +} + +/** + * Get the paths of a compilerOptions object. + * + * @param {Object} options + */ +function getPaths(options = {}) { + const paths = options.paths; + + if (!paths) { + return []; + } +} + +function getConfig() { + // Check if TypeScript is setup + const useTypeScript = fs.existsSync(paths.appTsConfig); + const hasJsConfig = fs.existsSync(paths.appJsConfig); + + let config; + + // If there's a tsconfig.json we assume it's a + // Typescript project and set up the config + // based on tsconfig.json + if (useTypeScript) { + config = require(paths.appTsConfig); + // Otherwise we'll check if there is jsconfig.json + // for non TS projects. + } else if (hasJsConfig) { + config = require(paths.appJsConfig); + } + + config = config || {}; + const options = config.compilerOptions || {}; + + return { + paths: getPaths(options), + baseUrl: getBaseUrl(options), + useTypeScript, + }; +} + +module.exports = getConfig(); diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index b719054583b..e5a3e0b5374 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -84,6 +84,7 @@ module.exports = { appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), appTsConfig: resolveApp('tsconfig.json'), + appJsConfig: resolveApp('jsconfig.json'), yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), @@ -106,6 +107,7 @@ module.exports = { appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), appTsConfig: resolveApp('tsconfig.json'), + appJsConfig: resolveApp('jsconfig.json'), yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), @@ -140,6 +142,7 @@ if ( appPackageJson: resolveOwn('package.json'), appSrc: resolveOwn('template/src'), appTsConfig: resolveOwn('template/tsconfig.json'), + appJsConfig: resolveOwn('template/jsconfig.json'), yarnLockFile: resolveOwn('template/yarn.lock'), testsSetup: resolveModule(resolveOwn, 'template/src/setupTests'), proxySetup: resolveOwn('template/src/setupProxy.js'), diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index fd23e4b6829..bc59410fc34 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -30,6 +30,7 @@ const paths = require('./paths'); const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt'); +const config = require('./config'); const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); // @remove-on-eject-begin const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); @@ -258,10 +259,7 @@ module.exports = function(webpackEnv) { // We placed these paths second because we want `node_modules` to "win" // if there are any conflicts. This matches Node resolution mechanism. // https://github.com/facebook/create-react-app/issues/253 - modules: ['node_modules'].concat( - // It is guaranteed to exist because we tweak it in `env.js` - process.env.NODE_PATH.split(path.delimiter).filter(Boolean) - ), + modules: ['node_modules'].concat(config.baseUrl ? [config.baseUrl] : []), // These are the reasonable defaults supported by the Node ecosystem. // We also include JSX as a common component filename extension to support // some tools, although we do not recommend using it, see: @@ -629,8 +627,7 @@ module.exports = function(webpackEnv) { isolatedModules: true, noEmit: true, jsx: 'preserve', - // Typescript expects the baseUrl to be relative from the root directory (e.g. src) - baseUrl: process.env.NODE_PATH.replace(paths.appPath + '/', ''), + baseUrl: config.baseUrl, }, reportFiles: [ '**', From 4de79d5e23ee08a9c7780ef6577434a499478d89 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Thu, 3 Jan 2019 18:43:44 +0100 Subject: [PATCH 04/34] Add baseUrl test --- .../kitchensink/integration/config.test.js | 19 +++++++++++++++++++ .../src/features/config/BaseUrl.test.js | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 packages/react-scripts/fixtures/kitchensink/integration/config.test.js create mode 100644 packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.test.js diff --git a/packages/react-scripts/fixtures/kitchensink/integration/config.test.js b/packages/react-scripts/fixtures/kitchensink/integration/config.test.js new file mode 100644 index 00000000000..6d09b56c481 --- /dev/null +++ b/packages/react-scripts/fixtures/kitchensink/integration/config.test.js @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import initDOM from './initDOM'; + +describe('Integration', () => { + describe('jsconfig.json/tsconfig.json', () => { + it('Supports setting baseUrl to src', async () => { + const doc = await initDOM('base-url'); + + expect(doc.getElementById('feature-base-url').childElementCount).toBe(4); + doc.defaultView.close(); + }); + }); +}); diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.test.js b/packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.test.js new file mode 100644 index 00000000000..378ec9d2fa9 --- /dev/null +++ b/packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.test.js @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import BaseUrl from './BaseUrl'; + +describe('baseUrl', () => { + it('renders without crashing', () => { + const div = document.createElement('div'); + return new Promise(resolve => { + ReactDOM.render(, div); + }); + }); +}); From 839a2a33241863960e188c6414a14c8f9f56328e Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Thu, 3 Jan 2019 19:29:47 +0100 Subject: [PATCH 05/34] Add jsconfig.json to kitchensink --- packages/react-scripts/fixtures/kitchensink/jsconfig.json | 5 +++++ tasks/e2e-kitchensink.sh | 6 +----- 2 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 packages/react-scripts/fixtures/kitchensink/jsconfig.json diff --git a/packages/react-scripts/fixtures/kitchensink/jsconfig.json b/packages/react-scripts/fixtures/kitchensink/jsconfig.json new file mode 100644 index 00000000000..ec2332eb49c --- /dev/null +++ b/packages/react-scripts/fixtures/kitchensink/jsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "baseUrl": "src" + } +} diff --git a/tasks/e2e-kitchensink.sh b/tasks/e2e-kitchensink.sh index 2b886a1791a..e38da60c8df 100755 --- a/tasks/e2e-kitchensink.sh +++ b/tasks/e2e-kitchensink.sh @@ -122,7 +122,6 @@ npm link "$temp_module_path/node_modules/test-integrity" # Test the build REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ - NODE_PATH=src \ PUBLIC_URL=http://www.example.org/spa/ \ yarn build @@ -134,7 +133,6 @@ exists build/static/js/main.*.js # https://facebook.github.io/jest/docs/en/troubleshooting.html#tests-are-extremely-slow-on-docker-and-or-continuous-integration-ci-server REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ CI=true \ - NODE_PATH=src \ NODE_ENV=test \ yarn test --no-cache --runInBand --testPathPattern=src @@ -142,21 +140,19 @@ REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ tmp_server_log=`mktemp` PORT=3001 \ REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ - NODE_PATH=src \ nohup yarn start &>$tmp_server_log & grep -q 'You can now view' <(tail -f $tmp_server_log) # Test "development" environment E2E_URL="http://localhost:3001" \ REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ - CI=true NODE_PATH=src \ + CI=true \ NODE_ENV=development \ BABEL_ENV=test \ node_modules/.bin/jest --no-cache --runInBand --config='jest.integration.config.js' # Test "production" environment E2E_FILE=./build/index.html \ CI=true \ - NODE_PATH=src \ NODE_ENV=production \ BABEL_ENV=test \ PUBLIC_URL=http://www.example.org/spa/ \ From 91f7794deb4b23d17595c0a8fbe9a4190cf26c94 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Thu, 3 Jan 2019 20:05:15 +0100 Subject: [PATCH 06/34] Add BaseUrl component --- .../src/features/config/BaseUrl.js | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.js diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.js b/packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.js new file mode 100644 index 00000000000..c17c750be6a --- /dev/null +++ b/packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.js @@ -0,0 +1,41 @@ +i; +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import load from 'absoluteLoad'; + +export default class extends Component { + static propTypes = { + onReady: PropTypes.func.isRequired, + }; + + constructor(props) { + super(props); + this.state = { users: [] }; + } + + async componentDidMount() { + const users = load(); + this.setState({ users }); + } + + componentDidUpdate() { + this.props.onReady(); + } + + render() { + return ( +
+ {this.state.users.map(user => ( +
{user.name}
+ ))} +
+ ); + } +} From be875380b249636910e09ccf475b25db22fe410f Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Thu, 3 Jan 2019 20:55:55 +0100 Subject: [PATCH 07/34] Add baseUrl as modulePath for jest --- packages/react-scripts/scripts/utils/createJestConfig.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-scripts/scripts/utils/createJestConfig.js b/packages/react-scripts/scripts/utils/createJestConfig.js index 58c2ad48812..1bd5725a4e5 100644 --- a/packages/react-scripts/scripts/utils/createJestConfig.js +++ b/packages/react-scripts/scripts/utils/createJestConfig.js @@ -10,6 +10,7 @@ const fs = require('fs'); const chalk = require('chalk'); const paths = require('../../config/paths'); +const jsConfig = require('../../config/config'); module.exports = (resolve, rootDir, isEjecting) => { // Use this instead of `paths.testsSetup` to avoid putting @@ -57,6 +58,7 @@ module.exports = (resolve, rootDir, isEjecting) => { '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', '^.+\\.module\\.(css|sass|scss)$', ], + modulePaths: jsConfig.baseUrl ? ['/' + jsConfig.baseUrl] : [], moduleNameMapper: { '^react-native$': 'react-native-web', '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', From e0d3ebf84242bafc468f5cc6655f1110c9539f70 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 19:49:24 +0100 Subject: [PATCH 08/34] =?UTF-8?q?Remove=20NODE=5FPATH=20resolving=20and=20?= =?UTF-8?q?inform=20user=20about=20it=E2=80=99s=20deprecation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/react-scripts/config/env.js | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/react-scripts/config/env.js b/packages/react-scripts/config/env.js index 7565cecd001..61e2992cc2d 100644 --- a/packages/react-scripts/config/env.js +++ b/packages/react-scripts/config/env.js @@ -48,21 +48,14 @@ dotenvFiles.forEach(dotenvFile => { } }); -// We support resolving modules according to `NODE_PATH`. +// We used to support resolving modules according to `NODE_PATH`. +// This now has been deprecated in favor of jsconfig/tsconfig.json // This lets you use absolute paths in imports inside large monorepos: -// https://github.com/facebook/create-react-app/issues/253. -// It works similar to `NODE_PATH` in Node itself: -// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders -// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. -// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. -// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 -// We also resolve them to make sure all tools using them work consistently. -const appDirectory = fs.realpathSync(process.cwd()); -process.env.NODE_PATH = (process.env.NODE_PATH || '') - .split(path.delimiter) - .filter(folder => folder && !path.isAbsolute(folder)) - .map(folder => path.resolve(appDirectory, folder)) - .join(path.delimiter); +if (process.env.NODE_PATH) { + console.log( + 'Setting NODE_PATH to resolves modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using Typescript) to achieve the same behaviour.' + ); +} // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be // injected into the application via DefinePlugin in Webpack configuration. From 5d67018bdaff4f68da1fff3f40e43e03a6f388a7 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 20:01:03 +0100 Subject: [PATCH 09/34] Remove unused variable --- packages/react-scripts/config/env.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-scripts/config/env.js b/packages/react-scripts/config/env.js index 61e2992cc2d..4c0fd54656a 100644 --- a/packages/react-scripts/config/env.js +++ b/packages/react-scripts/config/env.js @@ -9,7 +9,6 @@ 'use strict'; const fs = require('fs'); -const path = require('path'); const paths = require('./paths'); // Make sure that including paths.js after env.js will read .env variables. From bb49cf351a7f3bbe02b13d72207163f5d99fceb0 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 20:08:58 +0100 Subject: [PATCH 10/34] Remove accident typo --- .../fixtures/kitchensink/src/features/config/BaseUrl.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.js b/packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.js index c17c750be6a..818d4db271b 100644 --- a/packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.js +++ b/packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.js @@ -1,4 +1,3 @@ -i; /** * Copyright (c) 2015-present, Facebook, Inc. * From f5821c21785eda13586f61c3349c9df2c49f8f4e Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 20:27:00 +0100 Subject: [PATCH 11/34] Remove unused tests & properly load baseUrl test --- .../kitchensink/integration/env.test.js | 7 ---- .../fixtures/kitchensink/src/App.js | 4 +- .../kitchensink/src/features/env/NodePath.js | 40 ------------------- .../src/features/env/NodePath.test.js | 19 --------- 4 files changed, 2 insertions(+), 68 deletions(-) delete mode 100644 packages/react-scripts/fixtures/kitchensink/src/features/env/NodePath.js delete mode 100644 packages/react-scripts/fixtures/kitchensink/src/features/env/NodePath.test.js diff --git a/packages/react-scripts/fixtures/kitchensink/integration/env.test.js b/packages/react-scripts/fixtures/kitchensink/integration/env.test.js index 79de16706dc..90e0e631e88 100644 --- a/packages/react-scripts/fixtures/kitchensink/integration/env.test.js +++ b/packages/react-scripts/fixtures/kitchensink/integration/env.test.js @@ -37,13 +37,6 @@ describe('Integration', () => { doc.defaultView.close(); }); - it('NODE_PATH', async () => { - const doc = await initDOM('node-path'); - - expect(doc.getElementById('feature-node-path').childElementCount).toBe(4); - doc.defaultView.close(); - }); - it('PUBLIC_URL', async () => { const doc = await initDOM('public-url'); diff --git a/packages/react-scripts/fixtures/kitchensink/src/App.js b/packages/react-scripts/fixtures/kitchensink/src/App.js index 380a49fc639..66387d0397a 100644 --- a/packages/react-scripts/fixtures/kitchensink/src/App.js +++ b/packages/react-scripts/fixtures/kitchensink/src/App.js @@ -149,8 +149,8 @@ class App extends Component { this.setFeature(f.default) ); break; - case 'node-path': - import('./features/env/NodePath').then(f => this.setFeature(f.default)); + case 'base-url': + import('./features/env/BaseUrl').then(f => this.setFeature(f.default)); break; case 'no-ext-inclusion': import('./features/webpack/NoExtInclusion').then(f => diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/env/NodePath.js b/packages/react-scripts/fixtures/kitchensink/src/features/env/NodePath.js deleted file mode 100644 index e89228e20b0..00000000000 --- a/packages/react-scripts/fixtures/kitchensink/src/features/env/NodePath.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import load from 'absoluteLoad'; - -export default class extends Component { - static propTypes = { - onReady: PropTypes.func.isRequired, - }; - - constructor(props) { - super(props); - this.state = { users: [] }; - } - - async componentDidMount() { - const users = load(); - this.setState({ users }); - } - - componentDidUpdate() { - this.props.onReady(); - } - - render() { - return ( -
- {this.state.users.map(user => ( -
{user.name}
- ))} -
- ); - } -} diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/env/NodePath.test.js b/packages/react-scripts/fixtures/kitchensink/src/features/env/NodePath.test.js deleted file mode 100644 index 1de025d2f2f..00000000000 --- a/packages/react-scripts/fixtures/kitchensink/src/features/env/NodePath.test.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import NodePath from './NodePath'; - -describe('NODE_PATH', () => { - it('renders without crashing', () => { - const div = document.createElement('div'); - return new Promise(resolve => { - ReactDOM.render(, div); - }); - }); -}); From 832cad2d6408be4f62f0ac820e19643d702a8c7c Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 20:44:57 +0100 Subject: [PATCH 12/34] Fix wrong path to BaseUrl test --- packages/react-scripts/fixtures/kitchensink/src/App.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-scripts/fixtures/kitchensink/src/App.js b/packages/react-scripts/fixtures/kitchensink/src/App.js index 66387d0397a..76a4a79fe4b 100644 --- a/packages/react-scripts/fixtures/kitchensink/src/App.js +++ b/packages/react-scripts/fixtures/kitchensink/src/App.js @@ -150,7 +150,9 @@ class App extends Component { ); break; case 'base-url': - import('./features/env/BaseUrl').then(f => this.setFeature(f.default)); + import('./features/config/BaseUrl').then(f => + this.setFeature(f.default) + ); break; case 'no-ext-inclusion': import('./features/webpack/NoExtInclusion').then(f => From 401b500e7543b529219763eb97b7bca7336082c2 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 22:38:18 +0100 Subject: [PATCH 13/34] Add support for aliasing @ to src --- packages/react-scripts/config/config.js | 43 ++++++++++++++++--- .../react-scripts/config/webpack.config.js | 1 + .../fixtures/kitchensink/jsconfig.json | 5 ++- .../fixtures/kitchensink/src/App.js | 3 ++ .../kitchensink/src/features/config/Alias.js | 40 +++++++++++++++++ .../src/features/config/Alias.test.js | 19 ++++++++ 6 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 packages/react-scripts/fixtures/kitchensink/src/features/config/Alias.js create mode 100644 packages/react-scripts/fixtures/kitchensink/src/features/config/Alias.test.js diff --git a/packages/react-scripts/config/config.js b/packages/react-scripts/config/config.js index 24e8e08b1cc..f80c3e0cdfb 100644 --- a/packages/react-scripts/config/config.js +++ b/packages/react-scripts/config/config.js @@ -9,9 +9,12 @@ 'use strict'; const fs = require('fs'); +const path = require('path'); const chalk = require('chalk'); const paths = require('./paths'); +const appDirectory = fs.realpathSync(process.cwd()); + /** * Get the baseUrl of a compilerOptions object. * @@ -34,20 +37,46 @@ function getBaseUrl(options = {}) { return null; } - return 'src'; + return path.resolve(appDirectory, 'src'); } /** - * Get the paths of a compilerOptions object. + * Get the alias of a compilerOptions object. * * @param {Object} options */ -function getPaths(options = {}) { - const paths = options.paths; +function getAlias(options = {}) { + const paths = options.paths || {}; + + const alias = paths['@']; + + const others = Object.keys(paths).filter(function(value) { + return value !== '@'; + }); + + if (others.length) { + console.error( + chalk.red.bold( + 'You tried to set one or more paths with an alias other than "@", this is currently not supported in create-react-app and will be ignored.' + ) + ); + } - if (!paths) { - return []; + if (!alias) { + return {}; } + + if (alias.toString() !== 'src') { + console.error( + chalk.red.bold( + "You tried to set a path with alias '@' to anything other than ['src']. This is not supported in create-react-app and will be ignored." + ) + ); + } + + return { + '@': path.resolve(appDirectory, 'src'), + }; } function getConfig() { @@ -72,7 +101,7 @@ function getConfig() { const options = config.compilerOptions || {}; return { - paths: getPaths(options), + alias: getAlias(options), baseUrl: getBaseUrl(options), useTypeScript, }; diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index bc59410fc34..21d3d2d8412 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -273,6 +273,7 @@ module.exports = function(webpackEnv) { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', + ...config.alias, }, plugins: [ // Adds support for installing with Plug'n'Play, leading to faster installs and adding diff --git a/packages/react-scripts/fixtures/kitchensink/jsconfig.json b/packages/react-scripts/fixtures/kitchensink/jsconfig.json index ec2332eb49c..f0d9b148297 100644 --- a/packages/react-scripts/fixtures/kitchensink/jsconfig.json +++ b/packages/react-scripts/fixtures/kitchensink/jsconfig.json @@ -1,5 +1,8 @@ { "compilerOptions": { - "baseUrl": "src" + "baseUrl": "src", + "paths": { + "@": ["src"] + } } } diff --git a/packages/react-scripts/fixtures/kitchensink/src/App.js b/packages/react-scripts/fixtures/kitchensink/src/App.js index 76a4a79fe4b..82fe9da3779 100644 --- a/packages/react-scripts/fixtures/kitchensink/src/App.js +++ b/packages/react-scripts/fixtures/kitchensink/src/App.js @@ -154,6 +154,9 @@ class App extends Component { this.setFeature(f.default) ); break; + case 'alias': + import('./features/config/Alias').then(f => this.setFeature(f.default)); + break; case 'no-ext-inclusion': import('./features/webpack/NoExtInclusion').then(f => this.setFeature(f.default) diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/config/Alias.js b/packages/react-scripts/fixtures/kitchensink/src/features/config/Alias.js new file mode 100644 index 00000000000..f803de8ef6c --- /dev/null +++ b/packages/react-scripts/fixtures/kitchensink/src/features/config/Alias.js @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import load from '@/absoluteLoad'; + +export default class extends Component { + static propTypes = { + onReady: PropTypes.func.isRequired, + }; + + constructor(props) { + super(props); + this.state = { users: [] }; + } + + async componentDidMount() { + const users = load(); + this.setState({ users }); + } + + componentDidUpdate() { + this.props.onReady(); + } + + render() { + return ( +
+ {this.state.users.map(user => ( +
{user.name}
+ ))} +
+ ); + } +} diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/config/Alias.test.js b/packages/react-scripts/fixtures/kitchensink/src/features/config/Alias.test.js new file mode 100644 index 00000000000..94288459b25 --- /dev/null +++ b/packages/react-scripts/fixtures/kitchensink/src/features/config/Alias.test.js @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import Alias from './Alias'; + +describe('alias', () => { + it('renders without crashing', () => { + const div = document.createElement('div'); + return new Promise(resolve => { + ReactDOM.render(, div); + }); + }); +}); From 234e361eb2fb8f8c70e5b897a68a42e96a35b0cc Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 22:51:43 +0100 Subject: [PATCH 14/34] Add integration test for alias --- .../fixtures/kitchensink/integration/config.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react-scripts/fixtures/kitchensink/integration/config.test.js b/packages/react-scripts/fixtures/kitchensink/integration/config.test.js index 6d09b56c481..07831724aa4 100644 --- a/packages/react-scripts/fixtures/kitchensink/integration/config.test.js +++ b/packages/react-scripts/fixtures/kitchensink/integration/config.test.js @@ -15,5 +15,12 @@ describe('Integration', () => { expect(doc.getElementById('feature-base-url').childElementCount).toBe(4); doc.defaultView.close(); }); + + it('Supports setting @ as alias to src', async () => { + const doc = await initDOM('alias'); + + expect(doc.getElementById('feature-alias').childElementCount).toBe(4); + doc.defaultView.close(); + }); }); }); From 97ea8541a605d2db806aed68a54a8a3424667ab3 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 23:09:31 +0100 Subject: [PATCH 15/34] Log to debug tests --- packages/react-scripts/config/webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 21d3d2d8412..c35e4833f04 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -31,6 +31,7 @@ const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt'); const config = require('./config'); +console.log(JSON.stringify(config)); const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); // @remove-on-eject-begin const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); From 2aa5605ec3f11858c9f9732bf1fb3aa88e57a87c Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 23:34:17 +0100 Subject: [PATCH 16/34] Allow setting the baseUrl to node_modules --- packages/react-scripts/config/config.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/react-scripts/config/config.js b/packages/react-scripts/config/config.js index f80c3e0cdfb..2b8fd123b47 100644 --- a/packages/react-scripts/config/config.js +++ b/packages/react-scripts/config/config.js @@ -13,7 +13,12 @@ const path = require('path'); const chalk = require('chalk'); const paths = require('./paths'); -const appDirectory = fs.realpathSync(process.cwd()); +function isValidPath(path) { + return ( + paths.relative(paths.appSrc, path) === '.' || + paths.relative(paths.appNodeModules, path) === '.' + ); +} /** * Get the baseUrl of a compilerOptions object. @@ -27,17 +32,19 @@ function getBaseUrl(options = {}) { return null; } - if (baseUrl !== 'src' && baseUrl !== './src') { + const baseUrlResolved = path.resolve(appDirectory, baseUrl); + + if (!isValidPath) { console.error( chalk.red.bold( - "You tried to set baseUrl to anything other than 'src'. This is not supported in create-react-app and will be ignored." + "You tried to set baseUrl to anything other than 'src' or 'node_modules'.This is not supported in create-react-app and will be ignored." ) ); return null; } - return path.resolve(appDirectory, 'src'); + return path.resolve(paths.appDirectory, 'src'); } /** @@ -48,8 +55,6 @@ function getBaseUrl(options = {}) { function getAlias(options = {}) { const paths = options.paths || {}; - const alias = paths['@']; - const others = Object.keys(paths).filter(function(value) { return value !== '@'; }); From a0ed8582c44c5bfff1548ec7e80f5c34c4eb3a48 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 23:35:18 +0100 Subject: [PATCH 17/34] Remove unnecessary prefix for Jest module paths --- packages/react-scripts/scripts/utils/createJestConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-scripts/scripts/utils/createJestConfig.js b/packages/react-scripts/scripts/utils/createJestConfig.js index 1bd5725a4e5..a4570a3421b 100644 --- a/packages/react-scripts/scripts/utils/createJestConfig.js +++ b/packages/react-scripts/scripts/utils/createJestConfig.js @@ -58,7 +58,7 @@ module.exports = (resolve, rootDir, isEjecting) => { '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', '^.+\\.module\\.(css|sass|scss)$', ], - modulePaths: jsConfig.baseUrl ? ['/' + jsConfig.baseUrl] : [], + modulePaths: jsConfig.baseUrl ? [jsConfig.baseUrl] : [], moduleNameMapper: { '^react-native$': 'react-native-web', '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', From e52182dd17774a40e6cad844f29eddc1a38d8110 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 23:37:56 +0100 Subject: [PATCH 18/34] Whoops. --- packages/react-scripts/config/config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-scripts/config/config.js b/packages/react-scripts/config/config.js index 2b8fd123b47..3d4be0ebf1f 100644 --- a/packages/react-scripts/config/config.js +++ b/packages/react-scripts/config/config.js @@ -55,6 +55,8 @@ function getBaseUrl(options = {}) { function getAlias(options = {}) { const paths = options.paths || {}; + const alias = paths['@']; + const others = Object.keys(paths).filter(function(value) { return value !== '@'; }); From bf5329f837a531b0b01695a54f4b47c5b18207e8 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 23:39:41 +0100 Subject: [PATCH 19/34] Call isValidPath method to check if the baseUrl is valid --- packages/react-scripts/config/config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-scripts/config/config.js b/packages/react-scripts/config/config.js index 3d4be0ebf1f..4e254819973 100644 --- a/packages/react-scripts/config/config.js +++ b/packages/react-scripts/config/config.js @@ -32,9 +32,9 @@ function getBaseUrl(options = {}) { return null; } - const baseUrlResolved = path.resolve(appDirectory, baseUrl); + const baseUrlResolved = path.resolve(paths.appDirectory, baseUrl); - if (!isValidPath) { + if (!isValidPath(baseUrlResolved)) { console.error( chalk.red.bold( "You tried to set baseUrl to anything other than 'src' or 'node_modules'.This is not supported in create-react-app and will be ignored." From 2c19adc5e67953c7bb5c67f1c79d5862a394fa7a Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 23:40:05 +0100 Subject: [PATCH 20/34] Use appDirectory from paths --- packages/react-scripts/config/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-scripts/config/config.js b/packages/react-scripts/config/config.js index 4e254819973..7e30130ec26 100644 --- a/packages/react-scripts/config/config.js +++ b/packages/react-scripts/config/config.js @@ -82,7 +82,7 @@ function getAlias(options = {}) { } return { - '@': path.resolve(appDirectory, 'src'), + '@': path.resolve(paths.appDirectory, 'src'), }; } From ac8dd39019ddcdc841c663cf2c35201780ea6f7a Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Sun, 6 Jan 2019 23:42:30 +0100 Subject: [PATCH 21/34] Throw error if there are both a tsconfig.json and jsconfig.json file --- packages/react-scripts/config/config.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/react-scripts/config/config.js b/packages/react-scripts/config/config.js index 7e30130ec26..ba9d8c02842 100644 --- a/packages/react-scripts/config/config.js +++ b/packages/react-scripts/config/config.js @@ -91,6 +91,12 @@ function getConfig() { const useTypeScript = fs.existsSync(paths.appTsConfig); const hasJsConfig = fs.existsSync(paths.appJsConfig); + if (useTypeScript && hasJsConfig) { + throw new Error( + 'You have both a tsconfig.json and a jsconfig.json. If you are using Typescript please remove your jsconfig.json file.' + ); + } + let config; // If there's a tsconfig.json we assume it's a From a9b60b11c1c6e30b19b71875cef9e83884b1e27a Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 09:27:26 +0100 Subject: [PATCH 22/34] Add better path checking & add support for different aliases for src --- packages/react-scripts/config/config.js | 123 ++++++++++-------- .../react-scripts/config/webpack.config.js | 6 +- .../scripts/utils/createJestConfig.js | 5 +- 3 files changed, 80 insertions(+), 54 deletions(-) diff --git a/packages/react-scripts/config/config.js b/packages/react-scripts/config/config.js index ba9d8c02842..f16f69643f7 100644 --- a/packages/react-scripts/config/config.js +++ b/packages/react-scripts/config/config.js @@ -13,38 +13,40 @@ const path = require('path'); const chalk = require('chalk'); const paths = require('./paths'); -function isValidPath(path) { - return ( - paths.relative(paths.appSrc, path) === '.' || - paths.relative(paths.appNodeModules, path) === '.' - ); -} - /** * Get the baseUrl of a compilerOptions object. * * @param {Object} options */ -function getBaseUrl(options = {}) { +function getAdditionalModulePath(options = {}) { const baseUrl = options.baseUrl; - if (!baseUrl) { + // We need to explicitly check for null and undefined (and not a falsy value) because + // TypeScript treats an empty string as `.`. + if (baseUrl == null) { return null; } - const baseUrlResolved = path.resolve(paths.appDirectory, baseUrl); - - if (!isValidPath(baseUrlResolved)) { - console.error( - chalk.red.bold( - "You tried to set baseUrl to anything other than 'src' or 'node_modules'.This is not supported in create-react-app and will be ignored." - ) - ); + const baseUrlResolved = path.resolve(paths.appPath, baseUrl); + // We don't need to do anything if `baseUrl` is set to `node_modules`. This is + // the default behavior. + if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { return null; } - return path.resolve(paths.appDirectory, 'src'); + // Allow the user set the `baseUrl` to `appSrc`. + if (path.relative(paths.appSrc, baseUrlResolved) === '') { + return paths.appSrc; + } + + // Otherwise, throw an error. + throw new Error( + chalk.red.bold( + "Your project's `baseUrl` can only be set to `src` or `node_modules`." + + ' Create React App does not support other values at this time.' + ) + ); } /** @@ -52,38 +54,52 @@ function getBaseUrl(options = {}) { * * @param {Object} options */ -function getAlias(options = {}) { - const paths = options.paths || {}; - - const alias = paths['@']; - - const others = Object.keys(paths).filter(function(value) { - return value !== '@'; - }); - - if (others.length) { - console.error( - chalk.red.bold( - 'You tried to set one or more paths with an alias other than "@", this is currently not supported in create-react-app and will be ignored.' - ) - ); - } - - if (!alias) { - return {}; - } - - if (alias.toString() !== 'src') { - console.error( - chalk.red.bold( - "You tried to set a path with alias '@' to anything other than ['src']. This is not supported in create-react-app and will be ignored." - ) - ); - } +function getAliases(options = {}) { + // This is an object with the alias as key + // and an array of paths as value. + // e.g. '@': ['src'] + const aliases = options.paths || {}; + + return Object.keys(aliases).reduce(function(prev, alias) { + // Value contains the paths of the alias. + const value = aliases[alias]; + + // The value should be an array but we have to verify + // that because it's user input. + if (!Array.isArray(value) || value.length > 1) { + throw new Error( + chalk.red.bold( + "Your project's `alias` can only be set to an array containing `src`." + + ' Create React App does not support other values at this time.' + ) + ); + } + + const aliasPath = value[0]; + const resolvedAliasPath = path.resolve(paths.appPath, aliasPath); + + if (path.relative(paths.appSrc, resolvedAliasPath) !== '') { + throw new Error( + chalk.red.bold( + "Your project's `alias` can only be set to ['src']." + + ' Create React App does not support other values at this time.' + ) + ); + } + + prev[alias] = resolvedAliasPath; + return prev; + }, {}); +} - return { - '@': path.resolve(paths.appDirectory, 'src'), - }; +function getJestAliases(aliases) { + return Object.keys(aliases).reduce(function(prev, alias) { + const aliasPath = aliases[alias]; + const relativeAliasPath = path.relative(paths.appPath, aliasPath); + const match = alias + '(.*)$'; + const target = '/' + relativeAliasPath + '/$1'; + prev[match] = target; + }, {}); } function getConfig() { @@ -113,9 +129,14 @@ function getConfig() { config = config || {}; const options = config.compilerOptions || {}; + const aliases = getAliases(options); + const jestAliases = getJestAliases(aliases); + const additionalModulePath = getAdditionalModulePath(options); + return { - alias: getAlias(options), - baseUrl: getBaseUrl(options), + aliases, + jestAliases, + additionalModulePath, useTypeScript, }; } diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index c35e4833f04..219bd5ce537 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -260,7 +260,9 @@ module.exports = function(webpackEnv) { // We placed these paths second because we want `node_modules` to "win" // if there are any conflicts. This matches Node resolution mechanism. // https://github.com/facebook/create-react-app/issues/253 - modules: ['node_modules'].concat(config.baseUrl ? [config.baseUrl] : []), + modules: ['node_modules'].concat( + config.additionalModulePath ? [config.additionalModulePath] : [] + ), // These are the reasonable defaults supported by the Node ecosystem. // We also include JSX as a common component filename extension to support // some tools, although we do not recommend using it, see: @@ -274,7 +276,7 @@ module.exports = function(webpackEnv) { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', - ...config.alias, + ...config.aliases, }, plugins: [ // Adds support for installing with Plug'n'Play, leading to faster installs and adding diff --git a/packages/react-scripts/scripts/utils/createJestConfig.js b/packages/react-scripts/scripts/utils/createJestConfig.js index a4570a3421b..28c5cbf0a03 100644 --- a/packages/react-scripts/scripts/utils/createJestConfig.js +++ b/packages/react-scripts/scripts/utils/createJestConfig.js @@ -58,10 +58,13 @@ module.exports = (resolve, rootDir, isEjecting) => { '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', '^.+\\.module\\.(css|sass|scss)$', ], - modulePaths: jsConfig.baseUrl ? [jsConfig.baseUrl] : [], + modulePaths: jsConfig.additionalModulePath + ? [jsConfig.additionalModulePath] + : [], moduleNameMapper: { '^react-native$': 'react-native-web', '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', + ...config.jestAliases, }, moduleFileExtensions: [...paths.moduleFileExtensions, 'node'].filter( ext => !ext.includes('mjs') From 637e404b7cc021556b372d64347f5c45c2282424 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 10:12:00 +0100 Subject: [PATCH 23/34] Rename config to modules --- packages/react-scripts/config/{config.js => modules.js} | 4 ++-- packages/react-scripts/config/webpack.config.js | 7 +++---- packages/react-scripts/scripts/utils/createJestConfig.js | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) rename packages/react-scripts/config/{config.js => modules.js} (98%) diff --git a/packages/react-scripts/config/config.js b/packages/react-scripts/config/modules.js similarity index 98% rename from packages/react-scripts/config/config.js rename to packages/react-scripts/config/modules.js index f16f69643f7..44baf1dd633 100644 --- a/packages/react-scripts/config/config.js +++ b/packages/react-scripts/config/modules.js @@ -102,7 +102,7 @@ function getJestAliases(aliases) { }, {}); } -function getConfig() { +function getModules() { // Check if TypeScript is setup const useTypeScript = fs.existsSync(paths.appTsConfig); const hasJsConfig = fs.existsSync(paths.appJsConfig); @@ -141,4 +141,4 @@ function getConfig() { }; } -module.exports = getConfig(); +module.exports = getModules(); diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 219bd5ce537..21cc50e61d5 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -30,8 +30,7 @@ const paths = require('./paths'); const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt'); -const config = require('./config'); -console.log(JSON.stringify(config)); +const modules = require('./modules'); const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); // @remove-on-eject-begin const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); @@ -261,7 +260,7 @@ module.exports = function(webpackEnv) { // if there are any conflicts. This matches Node resolution mechanism. // https://github.com/facebook/create-react-app/issues/253 modules: ['node_modules'].concat( - config.additionalModulePath ? [config.additionalModulePath] : [] + modules.additionalModulePath ? [modules.additionalModulePath] : [] ), // These are the reasonable defaults supported by the Node ecosystem. // We also include JSX as a common component filename extension to support @@ -276,7 +275,7 @@ module.exports = function(webpackEnv) { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', - ...config.aliases, + ...modules.aliases, }, plugins: [ // Adds support for installing with Plug'n'Play, leading to faster installs and adding diff --git a/packages/react-scripts/scripts/utils/createJestConfig.js b/packages/react-scripts/scripts/utils/createJestConfig.js index 28c5cbf0a03..272d22fb74d 100644 --- a/packages/react-scripts/scripts/utils/createJestConfig.js +++ b/packages/react-scripts/scripts/utils/createJestConfig.js @@ -10,7 +10,7 @@ const fs = require('fs'); const chalk = require('chalk'); const paths = require('../../config/paths'); -const jsConfig = require('../../config/config'); +const modules = require('../../config/modules'); module.exports = (resolve, rootDir, isEjecting) => { // Use this instead of `paths.testsSetup` to avoid putting @@ -58,13 +58,13 @@ module.exports = (resolve, rootDir, isEjecting) => { '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', '^.+\\.module\\.(css|sass|scss)$', ], - modulePaths: jsConfig.additionalModulePath + modulePaths: modules.additionalModulePath ? [jsConfig.additionalModulePath] : [], moduleNameMapper: { '^react-native$': 'react-native-web', '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', - ...config.jestAliases, + ...modules.jestAliases, }, moduleFileExtensions: [...paths.moduleFileExtensions, 'node'].filter( ext => !ext.includes('mjs') From 830415a6425bec9a3e99e4fd153fb553dab255c3 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 10:35:44 +0100 Subject: [PATCH 24/34] Fix old reference --- packages/react-scripts/scripts/utils/createJestConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-scripts/scripts/utils/createJestConfig.js b/packages/react-scripts/scripts/utils/createJestConfig.js index 272d22fb74d..cf4e86495cd 100644 --- a/packages/react-scripts/scripts/utils/createJestConfig.js +++ b/packages/react-scripts/scripts/utils/createJestConfig.js @@ -59,7 +59,7 @@ module.exports = (resolve, rootDir, isEjecting) => { '^.+\\.module\\.(css|sass|scss)$', ], modulePaths: modules.additionalModulePath - ? [jsConfig.additionalModulePath] + ? [modules.additionalModulePath] : [], moduleNameMapper: { '^react-native$': 'react-native-web', From 09d98456bff6c01196fac48ff84ef5c8aa2c0976 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 10:57:58 +0100 Subject: [PATCH 25/34] Update baseUrl in Typescript compiler options --- packages/react-scripts/config/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 21cc50e61d5..6b42e17906f 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -630,7 +630,7 @@ module.exports = function(webpackEnv) { isolatedModules: true, noEmit: true, jsx: 'preserve', - baseUrl: config.baseUrl, + baseUrl: modules.baseUrl, }, reportFiles: [ '**', From db01cb99a778ea2cf78b5d5ff04ae43c3d7e22ca Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 11:22:49 +0100 Subject: [PATCH 26/34] Log modules to debug --- packages/react-scripts/config/webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 6b42e17906f..0619442907b 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -31,6 +31,7 @@ const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt'); const modules = require('./modules'); +console.log(modules); const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); // @remove-on-eject-begin const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); From 47b7099a1c8d6f3cb9691381a71fb20d1ea07f74 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 11:48:16 +0100 Subject: [PATCH 27/34] Correctly return prev in reducer --- packages/react-scripts/config/modules.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/react-scripts/config/modules.js b/packages/react-scripts/config/modules.js index 44baf1dd633..17566aa40d9 100644 --- a/packages/react-scripts/config/modules.js +++ b/packages/react-scripts/config/modules.js @@ -99,6 +99,7 @@ function getJestAliases(aliases) { const match = alias + '(.*)$'; const target = '/' + relativeAliasPath + '/$1'; prev[match] = target; + return prev; }, {}); } @@ -134,9 +135,9 @@ function getModules() { const additionalModulePath = getAdditionalModulePath(options); return { - aliases, - jestAliases, - additionalModulePath, + aliases: aliases, + jestAliases: jestAliases, + additionalModulePath: additionalModulePath, useTypeScript, }; } From 50e9c6043a15670afda236320dff528ce3351565 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 12:57:51 +0100 Subject: [PATCH 28/34] Suffix alias with a forward slash to prevent conflicts with node_modules vendors --- packages/react-scripts/config/modules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-scripts/config/modules.js b/packages/react-scripts/config/modules.js index 17566aa40d9..f4277ee3edb 100644 --- a/packages/react-scripts/config/modules.js +++ b/packages/react-scripts/config/modules.js @@ -96,7 +96,7 @@ function getJestAliases(aliases) { return Object.keys(aliases).reduce(function(prev, alias) { const aliasPath = aliases[alias]; const relativeAliasPath = path.relative(paths.appPath, aliasPath); - const match = alias + '(.*)$'; + const match = alias + '/(.*)$'; const target = '/' + relativeAliasPath + '/$1'; prev[match] = target; return prev; From 229457abe0a4dac062a26ebaecb5b00843df75a9 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 13:40:10 +0100 Subject: [PATCH 29/34] Remove console.log --- packages/react-scripts/config/webpack.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 0619442907b..6b42e17906f 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -31,7 +31,6 @@ const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt'); const modules = require('./modules'); -console.log(modules); const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); // @remove-on-eject-begin const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); From 216eaff0cf34af85214692c793d3439a7695b822 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 14:55:32 +0100 Subject: [PATCH 30/34] Attempt to test baseUrl for Typescript --- test/fixtures/typescript/src/App.test.ts | 10 ++++++++++ test/fixtures/typescript/src/App.ts | 3 +++ test/fixtures/typescript/src/absoluteLoad.ts | 13 +++++++++++++ test/fixtures/typescript/tsconfig.json | 4 ++++ 4 files changed, 30 insertions(+) create mode 100644 test/fixtures/typescript/src/absoluteLoad.ts diff --git a/test/fixtures/typescript/src/App.test.ts b/test/fixtures/typescript/src/App.test.ts index f02c462b553..636c014031b 100644 --- a/test/fixtures/typescript/src/App.test.ts +++ b/test/fixtures/typescript/src/App.test.ts @@ -13,3 +13,13 @@ it('supports decorators', () => { const app = new App(); expect(app.decorated).toBe(42); }); + +it('supports loading modules with baseUrl', () => { + const app = new App(); + expect(app.users).toEqual([ + { id: 1, name: '1' }, + { id: 2, name: '2' }, + { id: 3, name: '3' }, + { id: 4, name: '4' }, + ]); +}); diff --git a/test/fixtures/typescript/src/App.ts b/test/fixtures/typescript/src/App.ts index d803c92d199..f7c9f63f225 100644 --- a/test/fixtures/typescript/src/App.ts +++ b/test/fixtures/typescript/src/App.ts @@ -1,3 +1,5 @@ +import absoluteLoad from 'absoluteLoad'; + interface MyType { foo: number; bar: boolean; @@ -12,6 +14,7 @@ class App { n = App.foo.baz!.n; @propertyDecorator decorated = 5; + users = absoluteLoad; } function annotation(target: any) { diff --git a/test/fixtures/typescript/src/absoluteLoad.ts b/test/fixtures/typescript/src/absoluteLoad.ts new file mode 100644 index 00000000000..5c4f7842e28 --- /dev/null +++ b/test/fixtures/typescript/src/absoluteLoad.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +export default () => [ + { id: 1, name: '1' }, + { id: 2, name: '2' }, + { id: 3, name: '3' }, + { id: 4, name: '4' }, +]; diff --git a/test/fixtures/typescript/tsconfig.json b/test/fixtures/typescript/tsconfig.json index 504cd646e14..3c894ccf627 100644 --- a/test/fixtures/typescript/tsconfig.json +++ b/test/fixtures/typescript/tsconfig.json @@ -1,5 +1,9 @@ { "compilerOptions": { + "baseUrl": "src", + "paths": { + "@": ["src"] + }, "experimentalDecorators": true } } From 26d6bdb745f0575b36ac1e51afd6d6312e559bd3 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 15:45:55 +0100 Subject: [PATCH 31/34] Add test to verify aliases work in TypeScript as well --- test/fixtures/typescript/src/App.test.ts | 10 ++++++++++ test/fixtures/typescript/src/App.ts | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/test/fixtures/typescript/src/App.test.ts b/test/fixtures/typescript/src/App.test.ts index 636c014031b..41958542474 100644 --- a/test/fixtures/typescript/src/App.test.ts +++ b/test/fixtures/typescript/src/App.test.ts @@ -23,3 +23,13 @@ it('supports loading modules with baseUrl', () => { { id: 4, name: '4' }, ]); }); + +it('supports loading modules with alias', () => { + const app = new App(); + expect(app.usersWithAlias).toEqual([ + { id: 1, name: '1' }, + { id: 2, name: '2' }, + { id: 3, name: '3' }, + { id: 4, name: '4' }, + ]); +}); diff --git a/test/fixtures/typescript/src/App.ts b/test/fixtures/typescript/src/App.ts index f7c9f63f225..f04166f9453 100644 --- a/test/fixtures/typescript/src/App.ts +++ b/test/fixtures/typescript/src/App.ts @@ -1,4 +1,5 @@ import absoluteLoad from 'absoluteLoad'; +import absoluteLoadWithAlias from '@/absoluteLoad'; interface MyType { foo: number; @@ -14,7 +15,8 @@ class App { n = App.foo.baz!.n; @propertyDecorator decorated = 5; - users = absoluteLoad; + users = absoluteLoad(); + usersWithAlias = absoluteLoadWithAlias(); } function annotation(target: any) { From 74ad80aaddafdea9327759bd15a29fb1ddbb2a90 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 16:52:39 +0100 Subject: [PATCH 32/34] Remove baseUrl overwrite --- packages/react-scripts/config/webpack.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 6b42e17906f..3ec62626f0a 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -630,7 +630,6 @@ module.exports = function(webpackEnv) { isolatedModules: true, noEmit: true, jsx: 'preserve', - baseUrl: modules.baseUrl, }, reportFiles: [ '**', From 116c9c8ba66372e3eb2db964cf17b38dc3908905 Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Mon, 7 Jan 2019 17:11:38 +0100 Subject: [PATCH 33/34] Allow setting the path of an alias to a subfolder of src --- packages/react-scripts/config/modules.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/react-scripts/config/modules.js b/packages/react-scripts/config/modules.js index f4277ee3edb..ef25e3e8bf0 100644 --- a/packages/react-scripts/config/modules.js +++ b/packages/react-scripts/config/modules.js @@ -69,7 +69,7 @@ function getAliases(options = {}) { if (!Array.isArray(value) || value.length > 1) { throw new Error( chalk.red.bold( - "Your project's `alias` can only be set to an array containing `src`." + + "Your project's `alias` can only be set to an array containing `src` or a subfolder of `src`." + ' Create React App does not support other values at this time.' ) ); @@ -78,10 +78,15 @@ function getAliases(options = {}) { const aliasPath = value[0]; const resolvedAliasPath = path.resolve(paths.appPath, aliasPath); - if (path.relative(paths.appSrc, resolvedAliasPath) !== '') { + const relativePath = path.relative(paths.appSrc, resolvedAliasPath); + const isSrc = relativePath === ''; + const isSubfolderOfSrc = + !relativePath.startsWith('../') && !relativePath.startsWith('..\\'); + + if (!isSrc && !isSubfolderOfSrc) { throw new Error( chalk.red.bold( - "Your project's `alias` can only be set to ['src']." + + "Your project's `alias` can only be set to ['src'] or a subfolder of `src`." + ' Create React App does not support other values at this time.' ) ); From ccc6b894740c764274c4dd273d215e72a23fafcb Mon Sep 17 00:00:00 2001 From: Robert van Steen Date: Wed, 9 Jan 2019 23:13:29 +0100 Subject: [PATCH 34/34] Work on matching jsconfig path behavior with webpack/jest --- packages/react-scripts/config/modules.js | 43 ++++++++++++++++--- .../react-scripts/config/webpack.config.js | 2 +- .../fixtures/kitchensink/jsconfig.json | 2 +- test/fixtures/typescript/tsconfig.json | 2 +- 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/react-scripts/config/modules.js b/packages/react-scripts/config/modules.js index ef25e3e8bf0..b9147cb2855 100644 --- a/packages/react-scripts/config/modules.js +++ b/packages/react-scripts/config/modules.js @@ -76,8 +76,15 @@ function getAliases(options = {}) { } const aliasPath = value[0]; - const resolvedAliasPath = path.resolve(paths.appPath, aliasPath); + // Alias paths are relative to the baseurl. + // If there is no baseUrl set, it will default to the root of the app. + const baseUrl = options.baseUrl + ? path.resolve(paths.appPath, options.baseUrl) + : paths.appPath; + const resolvedAliasPath = path.resolve(baseUrl, aliasPath); + + // We then check if the resolved alias path is src or a sub folder of src. const relativePath = path.relative(paths.appSrc, resolvedAliasPath); const isSrc = relativePath === ''; const isSubfolderOfSrc = @@ -97,13 +104,37 @@ function getAliases(options = {}) { }, {}); } +function getWebpackAliases(aliases) { + return Object.keys(aliases).reduce(function(prev, alias) { + let aliasPath = aliases[alias]; + const endsWithWilcard = alias.endsWith('*'); + // Remove trailing wildcards (/*) + alias = alias.replace(/\/?\*$/, ''); + aliasPath = aliasPath.replace(/\/\*$/, ''); + // Webpack aliases work a little bit different than jsconfig/tsconfig.json paths + // By default webpack aliases act as a wildcard and for an exact match you have + // to suffix it with a dollar sign. + // tsconfig/jsconfig.json work the other way around and are an exact match unless + // suffixed by a wildcard. + const webpackAlias = endsWithWilcard ? alias : alias + '$'; + prev[webpackAlias] = aliasPath; + return prev; + }, {}); +} + function getJestAliases(aliases) { return Object.keys(aliases).reduce(function(prev, alias) { - const aliasPath = aliases[alias]; + const endsWithWilcard = alias.endsWith('*'); + let aliasPath = aliases[alias]; + + alias = alias.replace(/\/?\*$/, ''); + const match = endsWithWilcard ? alias + '(.*)$' : alias; + + aliasPath = aliasPath.replace(/\*$/, ''); const relativeAliasPath = path.relative(paths.appPath, aliasPath); - const match = alias + '/(.*)$'; - const target = '/' + relativeAliasPath + '/$1'; - prev[match] = target; + const target = '/' + relativeAliasPath; + + prev[match] = target + (endsWithWilcard ? '/$1' : ''); return prev; }, {}); } @@ -137,11 +168,13 @@ function getModules() { const aliases = getAliases(options); const jestAliases = getJestAliases(aliases); + const webpackAliases = getWebpackAliases(aliases); const additionalModulePath = getAdditionalModulePath(options); return { aliases: aliases, jestAliases: jestAliases, + webpackAliases: webpackAliases, additionalModulePath: additionalModulePath, useTypeScript, }; diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 3ec62626f0a..2c02bbffcf0 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -275,7 +275,7 @@ module.exports = function(webpackEnv) { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', - ...modules.aliases, + ...modules.webpackAliases, }, plugins: [ // Adds support for installing with Plug'n'Play, leading to faster installs and adding diff --git a/packages/react-scripts/fixtures/kitchensink/jsconfig.json b/packages/react-scripts/fixtures/kitchensink/jsconfig.json index f0d9b148297..344028e7fc7 100644 --- a/packages/react-scripts/fixtures/kitchensink/jsconfig.json +++ b/packages/react-scripts/fixtures/kitchensink/jsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "baseUrl": "src", "paths": { - "@": ["src"] + "@*": ["*"] } } } diff --git a/test/fixtures/typescript/tsconfig.json b/test/fixtures/typescript/tsconfig.json index 3c894ccf627..7ce41dc24cb 100644 --- a/test/fixtures/typescript/tsconfig.json +++ b/test/fixtures/typescript/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "baseUrl": "src", "paths": { - "@": ["src"] + "@*": ["*"] }, "experimentalDecorators": true }