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

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