Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add check-filename-webpack-plugin to warn on JSX file extension #353

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var autoprefixer = require('autoprefixer');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
var CheckFilenamePlugin = require('check-filename-webpack-plugin');
var WatchMissingNodeModulesPlugin = require('../scripts/utils/WatchMissingNodeModulesPlugin');
var paths = require('./paths');
var env = require('./env');
Expand All @@ -32,7 +33,7 @@ module.exports = {
publicPath: '/'
},
resolve: {
extensions: ['', '.js', '.json'],
extensions: ['.jsx', '.js', '.json', ''],
alias: {
// This `alias` section can be safely removed after ejection.
// We do this because `babel-runtime` may be inside `react-scripts`,
Expand Down Expand Up @@ -98,7 +99,16 @@ module.exports = {
useEslintrc: false
},
postcss: function() {
return [autoprefixer];
return [
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9',
]
}),
];
},
plugins: [
new HtmlWebpackPlugin({
Expand All @@ -110,6 +120,12 @@ module.exports = {
// Note: only CSS is currently hot reloaded
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new CheckFilenamePlugin({
regex: /\.jsx$/,
error: function(filename) {
return 'Module load aborted: .jsx extensions are not allowed, use .js extensions only. See create-react-app/issues/290 for more info.\n\tFor: ' + filename;
}
}),
new WatchMissingNodeModulesPlugin(paths.appNodeModules)
]
};
13 changes: 11 additions & 2 deletions config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ module.exports = {
publicPath: publicPath
},
resolve: {
extensions: ['', '.js', '.json'],
extensions: ['.js', '.json', ''],
alias: {
// This `alias` section can be safely removed after ejection.
// We do this because `babel-runtime` may be inside `react-scripts`,
Expand Down Expand Up @@ -108,7 +108,16 @@ module.exports = {
useEslintrc: false
},
postcss: function() {
return [autoprefixer];
return [
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9',
]
}),
];
},
plugins: [
new HtmlWebpackPlugin({
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
"babel-runtime": "6.11.6",
"case-sensitive-paths-webpack-plugin": "1.1.2",
"chalk": "1.1.3",
"check-filename-webpack-plugin": "^1.0.0",
"connect-history-api-fallback": "1.2.0",
"cross-spawn": "4.0.0",
"css-loader": "0.23.1",
"detect-port": "1.0.0",
Expand All @@ -61,12 +63,15 @@
"fs-extra": "0.30.0",
"gzip-size": "3.0.0",
"html-webpack-plugin": "2.22.0",
"http-proxy-middleware": "0.17.0",
"jest": "14.1.0",
"json-loader": "0.5.4",
"opn": "4.0.2",
"postcss-loader": "0.9.1",
"promise": "7.1.1",
"recursive-readdir": "2.0.0",
"rimraf": "2.5.4",
"strip-ansi": "3.0.1",
"style-loader": "0.13.1",
"url-loader": "0.5.7",
"webpack": "1.13.1",
Expand Down
158 changes: 114 additions & 44 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,79 +18,149 @@ var rimrafSync = require('rimraf').sync;
var webpack = require('webpack');
var config = require('../config/webpack.config.prod');
var paths = require('../config/paths');
var recursive = require('recursive-readdir');
var stripAnsi = require('strip-ansi');

// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
rimrafSync(paths.appBuild + '/*');
function removeFileNameHash(fileName) {
return fileName
.replace(paths.appBuild, '')
.replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
}

console.log('Creating an optimized production build...');
webpack(config).run(function(err, stats) {
if (err) {
console.error('Failed to create a production build. Reason:');
console.error(err.message || err);
process.exit(1);
function getDifferenceLabel(currentSize, previousSize) {
var FIFTY_KILOBYTES = 1024 * 50;
var difference = currentSize - previousSize;
var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
if (difference >= FIFTY_KILOBYTES) {
return chalk.red('+' + fileSize);
} else if (difference < FIFTY_KILOBYTES && difference > 0) {
return chalk.yellow('+' + fileSize);
} else if (difference < 0) {
return chalk.green(fileSize);
} else {
return '';
}
}

// First, read the current file sizes in build directory.
// This lets us display how much they changed later.
recursive(paths.appBuild, (err, fileNames) => {
var previousSizeMap = (fileNames || [])
.filter(fileName => /\.(js|css)$/.test(fileName))
.reduce((memo, fileName) => {
var contents = fs.readFileSync(fileName);
var key = removeFileNameHash(fileName);
memo[key] = gzipSize(contents);
return memo;
}, {});

console.log(chalk.green('Compiled successfully.'));
console.log();
// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
rimrafSync(paths.appBuild + '/*');

// Start the webpack build
build(previousSizeMap);
});

console.log('File sizes after gzip:');
console.log();
function printFileSizes(stats, previousSizeMap) {
var assets = stats.toJson().assets
.filter(asset => /\.(js|css)$/.test(asset.name))
.map(asset => {
var fileContents = fs.readFileSync(paths.appBuild + '/' + asset.name);
var size = gzipSize(fileContents);
var previousSize = previousSizeMap[removeFileNameHash(asset.name)];
var difference = getDifferenceLabel(size, previousSize);
return {
folder: path.join('build', path.dirname(asset.name)),
name: path.basename(asset.name),
size: size,
sizeLabel: filesize(size)
sizeLabel: filesize(size) + (difference ? ' (' + difference + ')' : '')
};
});
assets.sort((a, b) => b.size - a.size);

var longestSizeLabelLength = Math.max.apply(null,
assets.map(a => a.sizeLabel.length)
assets.map(a => stripAnsi(a.sizeLabel).length)
);
assets.forEach(asset => {
var sizeLabel = asset.sizeLabel;
if (sizeLabel.length < longestSizeLabelLength) {
var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLabel.length);
var sizeLength = stripAnsi(sizeLabel).length;
if (sizeLength < longestSizeLabelLength) {
var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength);
sizeLabel += rightPadding;
}
console.log(
' ' + chalk.green(sizeLabel) +
' ' + sizeLabel +
' ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name)
);
});
console.log();
}

var openCommand = process.platform === 'win32' ? 'start' : 'open';
var homepagePath = require(paths.appPackageJson).homepage;
if (homepagePath) {
console.log('You can now publish them at ' + homepagePath + '.');
console.log('For example, if you use GitHub Pages:');
console.log();
console.log(' git commit -am "Save local changes"');
console.log(' git checkout -B gh-pages');
console.log(' git add -f build');
console.log(' git commit -am "Rebuild website"');
console.log(' git filter-branch -f --prune-empty --subdirectory-filter build');
console.log(' git push -f origin gh-pages');
console.log(' git checkout -');
function build(previousSizeMap) {
console.log('Creating an optimized production build...');
webpack(config).run((err, stats) => {
if (err) {
console.error('Failed to create a production build. Reason:');
console.error(err.message || err);
process.exit(1);
}

console.log(chalk.green('Compiled successfully.'));
console.log();
} else {
console.log('You can now serve them with any static server.');
console.log('For example:');

console.log('File sizes after gzip:');
console.log();
console.log(' npm install -g pushstate-server');
console.log(' pushstate-server build');
console.log(' ' + openCommand + ' http://localhost:9000');
printFileSizes(stats, previousSizeMap);
console.log();
console.log(chalk.dim('The project was built assuming it is hosted at the root.'));
console.log(chalk.dim('Set the "homepage" field in package.json to override this.'));
console.log(chalk.dim('For example, "homepage": "http://user.github.io/project".'));
}
console.log();
});

var openCommand = process.platform === 'win32' ? 'start' : 'open';
var homepagePath = require(paths.appPackageJson).homepage;
var publicPath = config.output.publicPath;
if (homepagePath && homepagePath.indexOf('.github.io/') !== -1) {
// "homepage": "http://user.github.io/project"
console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.');
console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
console.log();
console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
console.log('To publish it at ' + chalk.green(homepagePath) + ', run:');
console.log();
console.log(' ' + chalk.blue('git') + chalk.cyan(' commit -am ') + chalk.yellow('"Save local changes"'));
console.log(' ' + chalk.blue('git') + chalk.cyan(' checkout -B gh-pages'));
console.log(' ' + chalk.blue('git') + chalk.cyan(' add -f build'));
console.log(' ' + chalk.blue('git') + chalk.cyan(' commit -am ' + chalk.yellow('"Rebuild website"')));
console.log(' ' + chalk.blue('git') + chalk.cyan(' filter-branch -f --prune-empty --subdirectory-filter build'));
console.log(' ' + chalk.blue('git') + chalk.cyan(' push -f origin gh-pages'));
console.log(' ' + chalk.blue('git') + chalk.cyan(' checkout -'));
console.log();
} else if (publicPath !== '/') {
// "homepage": "http://mywebsite.com/project"
console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.');
console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
console.log();
console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
console.log();
} else {
// no homepage or "homepage": "http://mywebsite.com"
console.log('The project was built assuming it is hosted at the server root.');
if (homepagePath) {
// "homepage": "http://mywebsite.com"
console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
console.log();
} else {
// no homepage
console.log('To override this, specify the ' + chalk.green('homepage') + ' in your ' + chalk.cyan('package.json') + '.');
console.log('For example, add this to build it for GitHub Pages:')
console.log();
console.log(' ' + chalk.green('"homepage"') + chalk.cyan(': ') + chalk.green('"http://myname.github.io/myapp"') + chalk.cyan(','));
console.log();
}
console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
console.log('You may also serve it locally with a static server:')
console.log();
console.log(' ' + chalk.blue('npm') + chalk.cyan(' install -g pushstate-server'));
console.log(' ' + chalk.blue('pushstate-server') + chalk.cyan(' build'));
console.log(' ' + chalk.blue(openCommand) + chalk.cyan(' http://localhost:9000'));
console.log();
}
});
}
2 changes: 1 addition & 1 deletion scripts/eject.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/

var createJestConfig = require('./utils/create-jest-config');
var createJestConfig = require('./utils/createJestConfig');
var fs = require('fs');
var path = require('path');
var prompt = require('./utils/prompt');
Expand Down
57 changes: 54 additions & 3 deletions scripts/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ var path = require('path');
var chalk = require('chalk');
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var historyApiFallback = require('connect-history-api-fallback');
var httpProxyMiddleware = require('http-proxy-middleware');
var execSync = require('child_process').execSync;
var opn = require('opn');
var detect = require('detect-port');
var prompt = require('./utils/prompt');
var config = require('../config/webpack.config.dev');
var paths = require('../config/paths');

// Tools like Cloud9 rely on this
var DEFAULT_PORT = process.env.PORT || 3000;
Expand Down Expand Up @@ -155,16 +158,64 @@ function openBrowser(port) {
opn('http://localhost:' + port + '/');
}

function addMiddleware(devServer) {
// `proxy` lets you to specify a fallback server during development.
// Every unrecognized request will be forwarded to it.
var proxy = require(paths.appPackageJson).proxy;
devServer.use(historyApiFallback({
// For single page apps, we generally want to fallback to /index.html.
// However we also want to respect `proxy` for API calls.
// So if `proxy` is specified, we need to decide which fallback to use.
// We use a heuristic: if request `accept`s text/html, we pick /index.html.
// Modern browsers include text/html into `accept` header when navigating.
// However API calls like `fetch()` won’t generally won’t accept text/html.
// If this heuristic doesn’t work well for you, don’t use `proxy`.
htmlAcceptHeaders: proxy ?
['text/html'] :
['text/html', '*/*']
}));
if (proxy) {
if (typeof proxy !== 'string') {
console.log(chalk.red('When specified, "proxy" in package.json must be a string.'));
console.log(chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".'));
console.log(chalk.red('Either remove "proxy" from package.json, or make it a string.'));
process.exit(1);
}

// Otherwise, if proxy is specified, we will let it handle any request.
// There are a few exceptions which we won't send to the proxy:
// - /index.html (served as HTML5 history API fallback)
// - /*.hot-update.json (WebpackDevServer uses this too for hot reloading)
// - /sockjs-node/* (WebpackDevServer uses this for hot reloading)
// Tip: use https://www.debuggex.com/ to visualize the regex
var mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/;
devServer.use(mayProxy,
// Pass the scope regex both to Express and to the middleware for proxying
// of both HTTP and WebSockets to work without false positives.
httpProxyMiddleware(pathname => mayProxy.test(pathname), {
target: proxy,
logLevel: 'silent',
secure: false,
changeOrigin: true
})
);
}
// Finally, by now we have certainly resolved the URL.
// It may be /index.html, so let the dev server try serving it again.
devServer.use(devServer.middleware);
}

function runDevServer(port) {
new WebpackDevServer(compiler, {
historyApiFallback: true,
var devServer = new WebpackDevServer(compiler, {
hot: true, // Note: only CSS is currently hot reloaded
publicPath: config.output.publicPath,
quiet: true,
watchOptions: {
ignored: /node_modules/
}
}).listen(port, (err, result) => {
});
addMiddleware(devServer);
devServer.listen(port, (err, result) => {
if (err) {
return console.log(err);
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

process.env.NODE_ENV = 'test';

const createJestConfig = require('./utils/create-jest-config');
const createJestConfig = require('./utils/createJestConfig');
const jest = require('jest');
const path = require('path');
const paths = require('../config/paths');
Expand Down
File renamed without changes.
Loading