Set baseUrl from jsconfig.json/tsconfig.json (#6656)

* Set baseUrl from jsconfig.json/tsconfig.json

* Resolve the path for loading modules

* Add tests for jsconfig.json

* Add jsconfig.json

* Update packages/react-scripts/scripts/start.js

* Move baseUrl test to config folder

* Remove alias test

* Use chalk from react-dev-utils

* Add lost absolute file for typescript baseUrl test

* Update packages/react-scripts/config/modules.js

* Update other references of useTypeScript to hasTsConfig

* Fix casing of TypeScript

* Keep respecting NODE_PATH for now to support multiple module paths.

* Add test for NODE_PATH

* Add fallback if NODE_PATH is not set.

* Fix node path behavior tests

* Remove debugging code from behavior test suite

* Remove more debugging code

* Show NODE_PATH deprecation warning during build


Co-authored-by: Ian Sutherland <ian@iansutherland.ca>
Co-authored-by: Brody McKee <mrmckeb@users.noreply.github.com>
Co-authored-by: Jack Zhao <jzhao@fb.com>
This commit is contained in:
Robert van Steen
2019-04-16 23:08:24 +02:00
committed by Ian Sutherland
parent ced3fd49ca
commit e7a2d6168a
29 changed files with 290 additions and 28 deletions

View File

@@ -167,9 +167,9 @@ The AWS Amplify Console provides continuous deployment and hosting for modern we
1. Login to the Amplify Console [here](https://console.aws.amazon.com/amplify/home).
1. Connect your Create React App repo and pick a branch. If you're looking for a Create React App+Amplify starter, try the [create-react-app-auth-amplify starter](https://github.com/swaminator/create-react-app-auth-amplify) that demonstrates setting up auth in 10 minutes with Create React App.
1. The Amplify Console automatically detects the build settings. Choose Next.
1. Choose *Save and deploy*.
1. Choose _Save and deploy_.
If the build succeeds, the app is deployed and hosted on a global CDN with an amplifyapp.com domain. You can now continuously deploy changes to your frontend or backend. Continuous deployment allows developers to deploy updates to their frontend and backend on every code commit to their Git repository.
If the build succeeds, the app is deployed and hosted on a global CDN with an amplifyapp.com domain. You can now continuously deploy changes to your frontend or backend. Continuous deployment allows developers to deploy updates to their frontend and backend on every code commit to their Git repository.
## [Azure](https://azure.microsoft.com/)

View File

@@ -62,7 +62,6 @@ yarn create react-app my-app
_`yarn create` is available in Yarn 0.25+_
### Creating a TypeScript app
Follow our [Adding TypeScript](adding-typescript.md) documentation to create a TypeScript app.

View File

@@ -0,0 +1,92 @@
// @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 path = require('path');
const paths = require('./paths');
const chalk = require('react-dev-utils/chalk');
/**
* Get the baseUrl of a compilerOptions object.
*
* @param {Object} options
*/
function getAdditionalModulePaths(options = {}) {
const baseUrl = options.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) {
// If there's no baseUrl set we respect NODE_PATH
// Note that NODE_PATH is deprecated and will be removed
// in the next major release of create-react-app.
const nodePath = process.env.NODE_PATH || '';
return nodePath.split(path.delimiter).filter(Boolean);
}
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;
}
// 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.'
)
);
}
function getModules() {
// Check if TypeScript is setup
const hasTsConfig = fs.existsSync(paths.appTsConfig);
const hasJsConfig = fs.existsSync(paths.appJsConfig);
if (hasTsConfig && 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
// TypeScript project and set up the config
// based on tsconfig.json
if (hasTsConfig) {
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 || {};
const additionalModulePaths = getAdditionalModulePaths(options);
return {
additionalModulePaths: additionalModulePaths,
hasTsConfig,
};
}
module.exports = getModules();

View File

@@ -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'),

View File

@@ -28,6 +28,7 @@ const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeM
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const paths = require('./paths');
const modules = require('./modules');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
@@ -268,10 +269,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', paths.appNodeModules].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', paths.appNodeModules].concat(modules.additionalModulePaths || []),
// 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:

View File

@@ -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();
});
});
});

View File

@@ -43,12 +43,6 @@ describe('Integration', () => {
}
});
it('NODE_PATH', async () => {
doc = await initDOM('node-path');
expect(doc.getElementById('feature-node-path').childElementCount).toBe(4);
});
it('PUBLIC_URL', async () => {
doc = await initDOM('public-url');

View File

@@ -0,0 +1,5 @@
{
"compilerOptions": {
"baseUrl": "src"
}
}

View File

@@ -166,9 +166,6 @@ class App extends Component {
this.setFeature(f.default)
);
break;
case 'node-path':
import('./features/env/NodePath').then(f => this.setFeature(f.default));
break;
case 'no-ext-inclusion':
import('./features/webpack/NoExtInclusion').then(f =>
this.setFeature(f.default)
@@ -239,6 +236,11 @@ class App extends Component {
this.setFeature(f.default)
);
break;
case 'base-url':
import('./features/config/BaseUrl').then(f =>
this.setFeature(f.default)
);
break;
default:
this.setState({ error: `Missing feature "${feature}"` });
}

View File

@@ -30,7 +30,7 @@ export default class extends Component {
render() {
return (
<div id="feature-node-path">
<div id="feature-base-url">
{this.state.users.map(user => (
<div key={user.id}>{user.name}</div>
))}

View File

@@ -7,9 +7,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import NodePath from './NodePath';
import NodePath from './BaseUrl';
describe('NODE_PATH', () => {
describe('BASE_URL', () => {
it('renders without crashing', () => {
const div = document.createElement('div');
return new Promise(resolve => {

View File

@@ -136,6 +136,18 @@ checkBrowsers(paths.appPath, isInteractive)
// Create the production build and print the deployment instructions.
function build(previousFileSizes) {
// 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:
if (process.env.NODE_PATH) {
console.log(
chalk.yellow(
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
)
);
console.log();
}
console.log('Creating an optimized production build...');
const compiler = webpack(config);

View File

@@ -129,6 +129,19 @@ checkBrowsers(paths.appPath, isInteractive)
if (isInteractive) {
clearConsole();
}
// 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:
if (process.env.NODE_PATH) {
console.log(
chalk.yellow(
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
)
);
console.log();
}
console.log(chalk.cyan('Starting the development server...\n'));
openBrowser(urls.localUrlForBrowser);
});

View File

@@ -10,6 +10,7 @@
const fs = require('fs');
const chalk = require('react-dev-utils/chalk');
const paths = require('../../config/paths');
const modules = require('../../config/modules');
module.exports = (resolve, rootDir, isEjecting) => {
// Use this instead of `paths.testsSetup` to avoid putting
@@ -49,6 +50,7 @@ module.exports = (resolve, rootDir, isEjecting) => {
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$',
'^.+\\.module\\.(css|sass|scss)$',
],
modulePaths: modules.additionalModulePaths || [],
moduleNameMapper: {
'^react-native$': 'react-native-web',
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',

View File

@@ -18,7 +18,10 @@ const immer = require('react-dev-utils/immer').produce;
const globby = require('react-dev-utils/globby').sync;
function writeJson(fileName, object) {
fs.writeFileSync(fileName, JSON.stringify(object, null, 2).replace(/\n/g, os.EOL) + os.EOL);
fs.writeFileSync(
fileName,
JSON.stringify(object, null, 2).replace(/\n/g, os.EOL) + os.EOL
);
}
function verifyNoTypeScript() {
@@ -124,12 +127,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' },
};

View File

@@ -123,7 +123,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
@@ -135,7 +134,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

View File

@@ -65,6 +65,9 @@ case ${test_suite} in
"installs")
test_command="./tasks/e2e-installs.sh"
;;
"behavior")
test_command="./tasks/e2e-behavior.sh"
;;
*)
;;
esac

0
test/fixtures/node_path/.disable-pnp vendored Normal file
View File

1
test/fixtures/node_path/.env vendored Normal file
View File

@@ -0,0 +1 @@
NODE_PATH=src

14
test/fixtures/node_path/index.test.js vendored Normal file
View File

@@ -0,0 +1,14 @@
const testSetup = require('../__shared__/test-setup');
test('builds in development', async () => {
const { fulfilled } = await testSetup.scripts.start({ smoke: true });
expect(fulfilled).toBe(true);
});
test('builds in production', async () => {
const { fulfilled } = await testSetup.scripts.build();
expect(fulfilled).toBe(true);
});
test('passes tests', async () => {
const { fulfilled } = await testSetup.scripts.test();
expect(fulfilled).toBe(true);
});

7
test/fixtures/node_path/package.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"prop-types": "^15.7.2",
"react": "latest",
"react-dom": "latest"
}
}

41
test/fixtures/node_path/src/App.js vendored Normal file
View File

@@ -0,0 +1,41 @@
/**
* 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 (
<div>
{this.state.users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}
}

17
test/fixtures/node_path/src/App.test.js vendored Normal file
View File

@@ -0,0 +1,17 @@
/**
* 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 App from './App';
test('loads modules absolutely with NODE_PATH', () => {
const div = document.createElement('div');
return new Promise(resolve => {
ReactDOM.render(<App onReady={resolve} />, div);
});
});

View File

@@ -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' },
];

5
test/fixtures/node_path/src/index.js vendored Normal file
View File

@@ -0,0 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

View File

@@ -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' },
]);
});

View File

@@ -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) {

View File

@@ -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' },
];

View File

@@ -1,5 +1,6 @@
{
"compilerOptions": {
"baseUrl": "src",
"experimentalDecorators": true
}
}