Skip to content

Commit e7a2d61

Browse files
robertvansteeniansumrmckebJack Zhao
committed
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 <[email protected]> Co-authored-by: Brody McKee <[email protected]> Co-authored-by: Jack Zhao <[email protected]>
1 parent ced3fd4 commit e7a2d61

29 files changed

+290
-28
lines changed

docusaurus/docs/deployment.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,9 @@ The AWS Amplify Console provides continuous deployment and hosting for modern we
167167
1. Login to the Amplify Console [here](https://console.aws.amazon.com/amplify/home).
168168
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.
169169
1. The Amplify Console automatically detects the build settings. Choose Next.
170-
1. Choose *Save and deploy*.
170+
1. Choose _Save and deploy_.
171171

172-
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.
172+
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.
173173

174174
## [Azure](https://azure.microsoft.com/)
175175

docusaurus/docs/getting-started.md

-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ yarn create react-app my-app
6262

6363
_`yarn create` is available in Yarn 0.25+_
6464

65-
6665
### Creating a TypeScript app
6766

6867
Follow our [Adding TypeScript](adding-typescript.md) documentation to create a TypeScript app.
+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// @remove-on-eject-begin
2+
/**
3+
* Copyright (c) 2015-present, Facebook, Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
// @remove-on-eject-end
9+
'use strict';
10+
11+
const fs = require('fs');
12+
const path = require('path');
13+
const paths = require('./paths');
14+
const chalk = require('react-dev-utils/chalk');
15+
16+
/**
17+
* Get the baseUrl of a compilerOptions object.
18+
*
19+
* @param {Object} options
20+
*/
21+
function getAdditionalModulePaths(options = {}) {
22+
const baseUrl = options.baseUrl;
23+
24+
// We need to explicitly check for null and undefined (and not a falsy value) because
25+
// TypeScript treats an empty string as `.`.
26+
if (baseUrl == null) {
27+
// If there's no baseUrl set we respect NODE_PATH
28+
// Note that NODE_PATH is deprecated and will be removed
29+
// in the next major release of create-react-app.
30+
31+
const nodePath = process.env.NODE_PATH || '';
32+
return nodePath.split(path.delimiter).filter(Boolean);
33+
}
34+
35+
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
36+
37+
// We don't need to do anything if `baseUrl` is set to `node_modules`. This is
38+
// the default behavior.
39+
if (path.relative(paths.appNodeModules, baseUrlResolved) === '') {
40+
return null;
41+
}
42+
43+
// Allow the user set the `baseUrl` to `appSrc`.
44+
if (path.relative(paths.appSrc, baseUrlResolved) === '') {
45+
return [paths.appSrc];
46+
}
47+
48+
// Otherwise, throw an error.
49+
throw new Error(
50+
chalk.red.bold(
51+
"Your project's `baseUrl` can only be set to `src` or `node_modules`." +
52+
' Create React App does not support other values at this time.'
53+
)
54+
);
55+
}
56+
57+
function getModules() {
58+
// Check if TypeScript is setup
59+
const hasTsConfig = fs.existsSync(paths.appTsConfig);
60+
const hasJsConfig = fs.existsSync(paths.appJsConfig);
61+
62+
if (hasTsConfig && hasJsConfig) {
63+
throw new Error(
64+
'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.'
65+
);
66+
}
67+
68+
let config;
69+
70+
// If there's a tsconfig.json we assume it's a
71+
// TypeScript project and set up the config
72+
// based on tsconfig.json
73+
if (hasTsConfig) {
74+
config = require(paths.appTsConfig);
75+
// Otherwise we'll check if there is jsconfig.json
76+
// for non TS projects.
77+
} else if (hasJsConfig) {
78+
config = require(paths.appJsConfig);
79+
}
80+
81+
config = config || {};
82+
const options = config.compilerOptions || {};
83+
84+
const additionalModulePaths = getAdditionalModulePaths(options);
85+
86+
return {
87+
additionalModulePaths: additionalModulePaths,
88+
hasTsConfig,
89+
};
90+
}
91+
92+
module.exports = getModules();

packages/react-scripts/config/paths.js

+3
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ module.exports = {
8484
appPackageJson: resolveApp('package.json'),
8585
appSrc: resolveApp('src'),
8686
appTsConfig: resolveApp('tsconfig.json'),
87+
appJsConfig: resolveApp('jsconfig.json'),
8788
yarnLockFile: resolveApp('yarn.lock'),
8889
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
8990
proxySetup: resolveApp('src/setupProxy.js'),
@@ -106,6 +107,7 @@ module.exports = {
106107
appPackageJson: resolveApp('package.json'),
107108
appSrc: resolveApp('src'),
108109
appTsConfig: resolveApp('tsconfig.json'),
110+
appJsConfig: resolveApp('jsconfig.json'),
109111
yarnLockFile: resolveApp('yarn.lock'),
110112
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
111113
proxySetup: resolveApp('src/setupProxy.js'),
@@ -140,6 +142,7 @@ if (
140142
appPackageJson: resolveOwn('package.json'),
141143
appSrc: resolveOwn('template/src'),
142144
appTsConfig: resolveOwn('template/tsconfig.json'),
145+
appJsConfig: resolveOwn('template/jsconfig.json'),
143146
yarnLockFile: resolveOwn('template/yarn.lock'),
144147
testsSetup: resolveModule(resolveOwn, 'template/src/setupTests'),
145148
proxySetup: resolveOwn('template/src/setupProxy.js'),

packages/react-scripts/config/webpack.config.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeM
2828
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
2929
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
3030
const paths = require('./paths');
31+
const modules = require('./modules');
3132
const getClientEnvironment = require('./env');
3233
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
3334
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
@@ -268,10 +269,7 @@ module.exports = function(webpackEnv) {
268269
// We placed these paths second because we want `node_modules` to "win"
269270
// if there are any conflicts. This matches Node resolution mechanism.
270271
// https://github.com/facebook/create-react-app/issues/253
271-
modules: ['node_modules', paths.appNodeModules].concat(
272-
// It is guaranteed to exist because we tweak it in `env.js`
273-
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
274-
),
272+
modules: ['node_modules', paths.appNodeModules].concat(modules.additionalModulePaths || []),
275273
// These are the reasonable defaults supported by the Node ecosystem.
276274
// We also include JSX as a common component filename extension to support
277275
// some tools, although we do not recommend using it, see:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import initDOM from './initDOM';
9+
10+
describe('Integration', () => {
11+
describe('jsconfig.json/tsconfig.json', () => {
12+
it('Supports setting baseUrl to src', async () => {
13+
const doc = await initDOM('base-url');
14+
15+
expect(doc.getElementById('feature-base-url').childElementCount).toBe(4);
16+
doc.defaultView.close();
17+
});
18+
});
19+
});

packages/react-scripts/fixtures/kitchensink/integration/env.test.js

-6
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,6 @@ describe('Integration', () => {
4343
}
4444
});
4545

46-
it('NODE_PATH', async () => {
47-
doc = await initDOM('node-path');
48-
49-
expect(doc.getElementById('feature-node-path').childElementCount).toBe(4);
50-
});
51-
5246
it('PUBLIC_URL', async () => {
5347
doc = await initDOM('public-url');
5448

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": "src"
4+
}
5+
}

packages/react-scripts/fixtures/kitchensink/src/App.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,6 @@ class App extends Component {
166166
this.setFeature(f.default)
167167
);
168168
break;
169-
case 'node-path':
170-
import('./features/env/NodePath').then(f => this.setFeature(f.default));
171-
break;
172169
case 'no-ext-inclusion':
173170
import('./features/webpack/NoExtInclusion').then(f =>
174171
this.setFeature(f.default)
@@ -239,6 +236,11 @@ class App extends Component {
239236
this.setFeature(f.default)
240237
);
241238
break;
239+
case 'base-url':
240+
import('./features/config/BaseUrl').then(f =>
241+
this.setFeature(f.default)
242+
);
243+
break;
242244
default:
243245
this.setState({ error: `Missing feature "${feature}"` });
244246
}

packages/react-scripts/fixtures/kitchensink/src/features/env/NodePath.js packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default class extends Component {
3030

3131
render() {
3232
return (
33-
<div id="feature-node-path">
33+
<div id="feature-base-url">
3434
{this.state.users.map(user => (
3535
<div key={user.id}>{user.name}</div>
3636
))}

packages/react-scripts/fixtures/kitchensink/src/features/env/NodePath.test.js packages/react-scripts/fixtures/kitchensink/src/features/config/BaseUrl.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
import React from 'react';
99
import ReactDOM from 'react-dom';
10-
import NodePath from './NodePath';
10+
import NodePath from './BaseUrl';
1111

12-
describe('NODE_PATH', () => {
12+
describe('BASE_URL', () => {
1313
it('renders without crashing', () => {
1414
const div = document.createElement('div');
1515
return new Promise(resolve => {

packages/react-scripts/scripts/build.js

+12
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,18 @@ checkBrowsers(paths.appPath, isInteractive)
136136

137137
// Create the production build and print the deployment instructions.
138138
function build(previousFileSizes) {
139+
// We used to support resolving modules according to `NODE_PATH`.
140+
// This now has been deprecated in favor of jsconfig/tsconfig.json
141+
// This lets you use absolute paths in imports inside large monorepos:
142+
if (process.env.NODE_PATH) {
143+
console.log(
144+
chalk.yellow(
145+
'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.'
146+
)
147+
);
148+
console.log();
149+
}
150+
139151
console.log('Creating an optimized production build...');
140152

141153
const compiler = webpack(config);

packages/react-scripts/scripts/start.js

+13
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@ checkBrowsers(paths.appPath, isInteractive)
129129
if (isInteractive) {
130130
clearConsole();
131131
}
132+
133+
// We used to support resolving modules according to `NODE_PATH`.
134+
// This now has been deprecated in favor of jsconfig/tsconfig.json
135+
// This lets you use absolute paths in imports inside large monorepos:
136+
if (process.env.NODE_PATH) {
137+
console.log(
138+
chalk.yellow(
139+
'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.'
140+
)
141+
);
142+
console.log();
143+
}
144+
132145
console.log(chalk.cyan('Starting the development server...\n'));
133146
openBrowser(urls.localUrlForBrowser);
134147
});

packages/react-scripts/scripts/utils/createJestConfig.js

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
const fs = require('fs');
1111
const chalk = require('react-dev-utils/chalk');
1212
const paths = require('../../config/paths');
13+
const modules = require('../../config/modules');
1314

1415
module.exports = (resolve, rootDir, isEjecting) => {
1516
// Use this instead of `paths.testsSetup` to avoid putting
@@ -49,6 +50,7 @@ module.exports = (resolve, rootDir, isEjecting) => {
4950
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$',
5051
'^.+\\.module\\.(css|sass|scss)$',
5152
],
53+
modulePaths: modules.additionalModulePaths || [],
5254
moduleNameMapper: {
5355
'^react-native$': 'react-native-web',
5456
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',

packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js

+4-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ const immer = require('react-dev-utils/immer').produce;
1818
const globby = require('react-dev-utils/globby').sync;
1919

2020
function writeJson(fileName, object) {
21-
fs.writeFileSync(fileName, JSON.stringify(object, null, 2).replace(/\n/g, os.EOL) + os.EOL);
21+
fs.writeFileSync(
22+
fileName,
23+
JSON.stringify(object, null, 2).replace(/\n/g, os.EOL) + os.EOL
24+
);
2225
}
2326

2427
function verifyNoTypeScript() {
@@ -124,12 +127,6 @@ function verifyTypeScriptSetup() {
124127
value: 'preserve',
125128
reason: 'JSX is compiled by Babel',
126129
},
127-
// We do not support absolute imports, though this may come as a future
128-
// enhancement
129-
baseUrl: {
130-
value: undefined,
131-
reason: 'absolute imports are not supported (yet)',
132-
},
133130
paths: { value: undefined, reason: 'aliased imports are not supported' },
134131
};
135132

tasks/e2e-kitchensink.sh

-2
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ npm link "$temp_module_path/node_modules/test-integrity"
123123

124124
# Test the build
125125
REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \
126-
NODE_PATH=src \
127126
PUBLIC_URL=http://www.example.org/spa/ \
128127
yarn build
129128

@@ -135,7 +134,6 @@ exists build/static/js/main.*.js
135134
# https://facebook.github.io/jest/docs/en/troubleshooting.html#tests-are-extremely-slow-on-docker-and-or-continuous-integration-ci-server
136135
REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \
137136
CI=true \
138-
NODE_PATH=src \
139137
NODE_ENV=test \
140138
yarn test --no-cache --runInBand --testPathPattern=src
141139

tasks/local-test.sh

+3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ case ${test_suite} in
6565
"installs")
6666
test_command="./tasks/e2e-installs.sh"
6767
;;
68+
"behavior")
69+
test_command="./tasks/e2e-behavior.sh"
70+
;;
6871
*)
6972
;;
7073
esac

test/fixtures/node_path/.disable-pnp

Whitespace-only changes.

test/fixtures/node_path/.env

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
NODE_PATH=src

test/fixtures/node_path/index.test.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const testSetup = require('../__shared__/test-setup');
2+
3+
test('builds in development', async () => {
4+
const { fulfilled } = await testSetup.scripts.start({ smoke: true });
5+
expect(fulfilled).toBe(true);
6+
});
7+
test('builds in production', async () => {
8+
const { fulfilled } = await testSetup.scripts.build();
9+
expect(fulfilled).toBe(true);
10+
});
11+
test('passes tests', async () => {
12+
const { fulfilled } = await testSetup.scripts.test();
13+
expect(fulfilled).toBe(true);
14+
});

test/fixtures/node_path/package.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"dependencies": {
3+
"prop-types": "^15.7.2",
4+
"react": "latest",
5+
"react-dom": "latest"
6+
}
7+
}

0 commit comments

Comments
 (0)