Skip to content

Commit 2e59c54

Browse files
committed
Offer to set default browsers (facebook#3792)
* Offer to set browser defaults * Catch error on no * Add ending newlines * Ensure we re-check to prevent defaults from leaking * Reduce nesting * Add defaults message * More explicit
1 parent 0ff2349 commit 2e59c54

File tree

9 files changed

+138
-59
lines changed

9 files changed

+138
-59
lines changed

packages/create-react-app/createReactApp.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const unpack = require('tar-pack').unpack;
4848
const url = require('url');
4949
const hyperquest = require('hyperquest');
5050
const envinfo = require('envinfo');
51+
const os = require('os');
5152

5253
const packageJson = require('./package.json');
5354

@@ -173,7 +174,7 @@ function createApp(name, verbose, version, useNpm, template) {
173174
};
174175
fs.writeFileSync(
175176
path.join(root, 'package.json'),
176-
JSON.stringify(packageJson, null, 2)
177+
JSON.stringify(packageJson, null, 2) + os.EOL
177178
);
178179

179180
const useYarn = useNpm ? false : shouldUseYarn();
@@ -481,7 +482,10 @@ function getPackageName(installPackage) {
481482
);
482483
} else if (installPackage.match(/^file:/)) {
483484
const installPackagePath = installPackage.match(/^file:(.*)?$/)[1];
484-
const installPackageJson = require(path.join(installPackagePath, 'package.json'));
485+
const installPackageJson = require(path.join(
486+
installPackagePath,
487+
'package.json'
488+
));
485489
return Promise.resolve(installPackageJson.name);
486490
}
487491
return Promise.resolve(installPackage);
@@ -600,7 +604,7 @@ function setCaretRangeForRuntimeDeps(packageName) {
600604
makeCaretRange(packageJson.dependencies, 'react');
601605
makeCaretRange(packageJson.dependencies, 'react-dom');
602606

603-
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
607+
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + os.EOL);
604608
}
605609

606610
// If project only contains files generated by GH, it’s safe.

packages/react-dev-utils/WebpackDevServerUtils.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ function prepareProxy(proxy, appPublicFolder) {
318318
// However we also want to respect `proxy` for API calls.
319319
// So if `proxy` is specified as a string, we need to decide which fallback to use.
320320
// We use a heuristic: We want to proxy all the requests that are not meant
321-
// for static assets and as all the requests for static assets will be using
321+
// for static assets and as all the requests for static assets will be using
322322
// `GET` method, we can proxy all non-`GET` requests.
323323
// For `GET` requests, if request `accept`s text/html, we pick /index.html.
324324
// Modern browsers include text/html into `accept` header when navigating.

packages/react-dev-utils/browsersHelper.js

+87-24
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,99 @@
99
const browserslist = require('browserslist');
1010
const chalk = require('chalk');
1111
const os = require('os');
12+
const inquirer = require('inquirer');
13+
const pkgUp = require('pkg-up');
14+
const fs = require('fs');
1215

13-
function checkBrowsers(dir) {
14-
const found = browserslist.findConfig(dir);
16+
const defaultBrowsers = {
17+
development: ['chrome', 'firefox', 'edge'].map(
18+
browser => `last 2 ${browser} versions`
19+
),
20+
production: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 11'],
21+
};
1522

16-
if (found == null) {
17-
console.log(
18-
chalk.red('As of react-scripts >=2 you must specify targeted browsers.') +
19-
os.EOL +
20-
`Please add a ${chalk.underline(
21-
'browserslist'
22-
)} key to your ${chalk.bold('package.json')}.`
23+
function checkBrowsers(dir, retry = true) {
24+
const current = browserslist.findConfig(dir);
25+
if (current != null) {
26+
return Promise.resolve(current);
27+
}
28+
29+
if (!retry) {
30+
return Promise.reject(
31+
new Error(
32+
chalk.red(
33+
'As of react-scripts >=2 you must specify targeted browsers.'
34+
) +
35+
os.EOL +
36+
`Please add a ${chalk.underline(
37+
'browserslist'
38+
)} key to your ${chalk.bold('package.json')}.`
39+
)
2340
);
24-
return null;
2541
}
26-
return found;
42+
43+
const question = {
44+
type: 'confirm',
45+
name: 'shouldSetBrowsers',
46+
message:
47+
chalk.yellow("We're unable to detect target browsers.") +
48+
`\n\nWould you like to add the defaults to your ${chalk.bold(
49+
'package.json'
50+
)}?`,
51+
default: true,
52+
};
53+
return inquirer.prompt(question).then(answer => {
54+
if (answer.shouldSetBrowsers) {
55+
return (
56+
pkgUp(dir)
57+
.then(filePath => {
58+
if (filePath == null) {
59+
return Promise.reject();
60+
}
61+
const pkg = JSON.parse(fs.readFileSync(filePath));
62+
pkg['browserslist'] = defaultBrowsers;
63+
fs.writeFileSync(filePath, JSON.stringify(pkg, null, 2) + os.EOL);
64+
65+
browserslist.clearCaches();
66+
console.log();
67+
console.log(chalk.green('Set target browsers:'));
68+
console.log();
69+
console.log(
70+
`\t${chalk.bold('Production')}: ${chalk.cyan(
71+
defaultBrowsers.production.join(', ')
72+
)}`
73+
);
74+
console.log(
75+
`\t${chalk.bold('Development')}: ${chalk.cyan(
76+
defaultBrowsers.development.join(', ')
77+
)}`
78+
);
79+
console.log();
80+
})
81+
// Swallow any error
82+
.catch(() => {})
83+
.then(() => checkBrowsers(dir, false))
84+
);
85+
} else {
86+
return checkBrowsers(dir, false);
87+
}
88+
});
2789
}
2890

2991
function printBrowsers(dir) {
30-
let browsers = checkBrowsers(dir);
31-
if (browsers == null) {
32-
console.log('Built the bundle with default browser support.');
33-
return;
34-
}
35-
browsers = browsers[process.env.NODE_ENV] || browsers;
36-
if (Array.isArray(browsers)) {
37-
browsers = browsers.join(', ');
38-
}
39-
console.log(
40-
`Built the bundle with browser support for ${chalk.cyan(browsers)}.`
41-
);
92+
return checkBrowsers(dir).then(browsers => {
93+
if (browsers == null) {
94+
console.log('Built the bundle with default browser support.');
95+
return;
96+
}
97+
browsers = browsers[process.env.NODE_ENV] || browsers;
98+
if (Array.isArray(browsers)) {
99+
browsers = browsers.join(', ');
100+
}
101+
console.log(
102+
`Built the bundle with browser support for ${chalk.cyan(browsers)}.`
103+
);
104+
});
42105
}
43106

44-
module.exports = { checkBrowsers, printBrowsers };
107+
module.exports = { defaultBrowsers, checkBrowsers, printBrowsers };

packages/react-dev-utils/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"inquirer": "5.0.0",
5151
"is-root": "1.0.0",
5252
"opn": "5.2.0",
53+
"pkg-up": "2.0.0",
5354
"react-error-overlay": "^4.0.0",
5455
"recursive-readdir": "2.2.1",
5556
"shell-quote": "1.6.1",

packages/react-scripts/package.json

+11-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,16 @@
7070
"fsevents": "1.1.2"
7171
},
7272
"browserslist": {
73-
"development": "last 2 chrome versions",
74-
"production": [">1%", "last 4 versions", "Firefox ESR", "not ie < 11"]
73+
"development": [
74+
"last 2 chrome versions",
75+
"last 2 firefox versions",
76+
"last 2 edge versions"
77+
],
78+
"production": [
79+
">1%",
80+
"last 4 versions",
81+
"Firefox ESR",
82+
"not ie < 11"
83+
]
7584
}
7685
}

packages/react-scripts/scripts/build.js

+16-11
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,6 @@ const printHostingInstructions = require('react-dev-utils/printHostingInstructio
4141
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
4242
const printBuildError = require('react-dev-utils/printBuildError');
4343
const { printBrowsers } = require('react-dev-utils/browsersHelper');
44-
// @remove-on-eject-begin
45-
// Require browsers to be specified before you eject
46-
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
47-
if (!checkBrowsers(paths.appPath)) {
48-
process.exit(1);
49-
}
50-
// @remove-on-eject-end
5144

5245
const measureFileSizesBeforeBuild =
5346
FileSizeReporter.measureFileSizesBeforeBuild;
@@ -63,9 +56,15 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
6356
process.exit(1);
6457
}
6558

66-
// First, read the current file sizes in build directory.
67-
// This lets us display how much they changed later.
68-
measureFileSizesBeforeBuild(paths.appBuild)
59+
// We require that you explictly set browsers and do not fall back to
60+
// browserslist defaults.
61+
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
62+
checkBrowsers(paths.appPath)
63+
.then(() => {
64+
// First, read the current file sizes in build directory.
65+
// This lets us display how much they changed later.
66+
return measureFileSizesBeforeBuild(paths.appBuild);
67+
})
6968
.then(previousFileSizes => {
7069
// Remove all content but keep the directory so that
7170
// if you're in it, you don't end up in Trash
@@ -122,7 +121,13 @@ measureFileSizesBeforeBuild(paths.appBuild)
122121
printBuildError(err);
123122
process.exit(1);
124123
}
125-
);
124+
)
125+
.catch(err => {
126+
if (err && err.message) {
127+
console.log(err.message);
128+
}
129+
process.exit(1);
130+
});
126131

127132
// Create the production build and print the deployment instructions.
128133
function build(previousFileSizes) {

packages/react-scripts/scripts/eject.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const paths = require('../config/paths');
2222
const createJestConfig = require('./utils/createJestConfig');
2323
const inquirer = require('react-dev-utils/inquirer');
2424
const spawnSync = require('react-dev-utils/crossSpawn').sync;
25+
const os = require('os');
2526

2627
const green = chalk.green;
2728
const cyan = chalk.cyan;
@@ -218,7 +219,7 @@ inquirer
218219

219220
fs.writeFileSync(
220221
path.join(appPath, 'package.json'),
221-
JSON.stringify(appPackage, null, 2) + '\n'
222+
JSON.stringify(appPackage, null, 2) + os.EOL
222223
);
223224
console.log();
224225

packages/react-scripts/scripts/init.js

+4-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const fs = require('fs-extra');
1818
const path = require('path');
1919
const chalk = require('chalk');
2020
const spawn = require('react-dev-utils/crossSpawn');
21+
const { defaultBrowsers } = require('react-dev-utils/browsersHelper');
22+
const os = require('os');
2123

2224
module.exports = function(
2325
appPath,
@@ -43,16 +45,11 @@ module.exports = function(
4345
eject: 'react-scripts eject',
4446
};
4547

46-
appPackage.browserslist = {
47-
development: ['chrome', 'firefox', 'edge'].map(
48-
browser => `last 2 ${browser} versions`
49-
),
50-
production: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 11'],
51-
};
48+
appPackage.browserslist = defaultBrowsers;
5249

5350
fs.writeFileSync(
5451
path.join(appPath, 'package.json'),
55-
JSON.stringify(appPackage, null, 2)
52+
JSON.stringify(appPackage, null, 2) + os.EOL
5653
);
5754

5855
const readmeExists = fs.existsSync(path.join(appPath, 'README.md'));

packages/react-scripts/scripts/start.js

+9-10
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,6 @@ const createDevServerConfig = require('../config/webpackDevServer.config');
4848

4949
const useYarn = fs.existsSync(paths.yarnLockFile);
5050
const isInteractive = process.stdout.isTTY;
51-
// @remove-on-eject-begin
52-
// Require browsers to be specified before you eject
53-
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
54-
if (!checkBrowsers(paths.appPath)) {
55-
process.exit(1);
56-
}
57-
// @remove-on-eject-end
5851

5952
// Warn and crash if required files are missing
6053
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
@@ -80,9 +73,15 @@ if (process.env.HOST) {
8073
console.log();
8174
}
8275

83-
// We attempt to use the default port but if it is busy, we offer the user to
84-
// run on a different port. `choosePort()` Promise resolves to the next free port.
85-
choosePort(HOST, DEFAULT_PORT)
76+
// We require that you explictly set browsers and do not fall back to
77+
// browserslist defaults.
78+
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
79+
checkBrowsers(paths.appPath)
80+
.then(() => {
81+
// We attempt to use the default port but if it is busy, we offer the user to
82+
// run on a different port. `choosePort()` Promise resolves to the next free port.
83+
return choosePort(HOST, DEFAULT_PORT);
84+
})
8685
.then(port => {
8786
if (port == null) {
8887
// We have not found a port.

0 commit comments

Comments
 (0)