diff --git a/config/paths.js b/config/paths.js index 1d50c38b9f8..1c154c36164 100644 --- a/config/paths.js +++ b/config/paths.js @@ -70,17 +70,18 @@ module.exports = { }; // @remove-on-eject-end -// @remove-on-publish-begin -module.exports = { - appBuild: resolveOwn('../../../build'), - appPublic: resolveOwn('../template/public'), - appHtml: resolveOwn('../template/public/index.html'), - appIndexJs: resolveOwn('../template/src/index.js'), - appPackageJson: resolveOwn('../package.json'), - appSrc: resolveOwn('../template/src'), - testsSetup: resolveOwn('../template/src/setupTests.js'), - appNodeModules: resolveOwn('../node_modules'), - ownNodeModules: resolveOwn('../node_modules'), - nodePaths: nodePaths -}; -// @remove-on-publish-end +// config before publish: we're in ./packages/react-scripts/config/ +if (__dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1) { + module.exports = { + appBuild: resolveOwn('../../../build'), + appPublic: resolveOwn('../template/public'), + appHtml: resolveOwn('../template/public/index.html'), + appIndexJs: resolveOwn('../template/src/index.js'), + appPackageJson: resolveOwn('../package.json'), + appSrc: resolveOwn('../template/src'), + testsSetup: resolveOwn('../template/src/setupTests.js'), + appNodeModules: resolveOwn('../node_modules'), + ownNodeModules: resolveOwn('../node_modules'), + nodePaths: nodePaths + }; +} diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index cd5e27df6fb..ad5a699daf1 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -12,7 +12,6 @@ var path = require('path'); var autoprefixer = require('autoprefixer'); var webpack = require('webpack'); -var findCacheDir = require('find-cache-dir'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); @@ -126,12 +125,9 @@ module.exports = { plugins: [].concat(customConfig.babelPlugins), // @remove-on-eject-end // This is a feature of `babel-loader` for webpack (not Babel itself). - // It enables caching results in ./node_modules/.cache/react-scripts/ - // directory for faster rebuilds. We use findCacheDir() because of: - // https://github.com/facebookincubator/create-react-app/issues/483 - cacheDirectory: findCacheDir({ - name: 'react-scripts' - }) + // It enables caching results in ./node_modules/.cache/babel-loader/ + // directory for faster rebuilds. + cacheDirectory: true } }, // "postcss" loader applies autoprefixer to our CSS. @@ -141,7 +137,7 @@ module.exports = { // in development "style" loader enables hot editing of CSS. { test: /\.css$/, - loader: customConfig.values.CSS_MODULES ? customConfig.values.CSS_MODULES.dev : 'style!css!postcss' + loader: customConfig.values.CSS_MODULES ? customConfig.values.CSS_MODULES.dev : 'style!css?importLoaders=1!postcss' }, // JSON is not enabled by default in Webpack but both Node and Browserify // allow it implicitly so we also enable it. @@ -179,7 +175,7 @@ module.exports = { }, // @remove-on-eject-end // We use PostCSS for autoprefixing only. - postcss: function () { + postcss: function() { return [ autoprefixer({ browsers: [ diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js index 8c11bae02b8..a316e75cd23 100644 --- a/config/webpack.config.prod.js +++ b/config/webpack.config.prod.js @@ -14,6 +14,7 @@ var autoprefixer = require('autoprefixer'); var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); +var ManifestPlugin = require('webpack-manifest-plugin'); var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); var url = require('url'); var paths = require('./paths'); @@ -153,10 +154,9 @@ module.exports = { // Webpack 1.x uses Uglify plugin as a signal to minify *all* the assets // including CSS. This is confusing and will be removed in Webpack 2: // https://github.com/webpack/webpack/issues/283 - loader: ExtractTextPlugin.extract(customConfig.values.CSS_MODULES ? 'style!css?modules&-autoprefixer&importLoaders=1!postcss' : 'style!css?-autoprefixer!postcss') + loader: ExtractTextPlugin.extract(customConfig.values.CSS_MODULES ? 'style!css?modules&-autoprefixer&importLoaders=1!postcss' : 'style!css?importLoaders=1&-autoprefixer!postcss') // Note: this won't work without `new ExtractTextPlugin()` in `plugins`. }, - // JSON is not enabled by default in Webpack but both Node and Browserify // allow it implicitly so we also enable it. { @@ -194,7 +194,7 @@ module.exports = { }, // @remove-on-eject-end // We use PostCSS for autoprefixing only. - postcss: function () { + postcss: function() { return [ autoprefixer({ browsers: [ @@ -255,7 +255,13 @@ module.exports = { } }), // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`. - new ExtractTextPlugin('static/css/[name].[contenthash:8].css') + new ExtractTextPlugin('static/css/[name].[contenthash:8].css'), + // Generate a manifest file which contains a mapping of all asset filenames + // to their corresponding output file so that tools can pick it up without + // having to parse `index.html`. + new ManifestPlugin({ + fileName: 'asset-manifest.json' + }) ].concat(customConfig.plugins), // Some libraries import Node modules but don't use them in the browser. // Tell Webpack to provide empty mocks for them so importing them works. diff --git a/package.json b/package.json index 4a2dd1febb0..6d8e38e3d63 100644 --- a/package.json +++ b/package.json @@ -23,46 +23,45 @@ "react-scripts": "./bin/react-scripts.js" }, "dependencies": { - "autoprefixer": "6.4.1", - "babel-core": "6.14.0", - "babel-eslint": "6.1.2", - "babel-jest": "15.0.0", - "babel-loader": "6.2.5", - "babel-plugin-transform-decorators-legacy": "^1.3.4", - "babel-preset-react-app": "^0.2.1", + "autoprefixer": "6.5.1", + "babel-core": "6.17.0", + "babel-eslint": "7.0.0", + "babel-jest": "16.0.0", + "babel-loader": "6.2.7", + "babel-preset-react-app": "^1.0.0", "babel-preset-stage-0": "^6.5.0", + "babel-plugin-transform-decorators-legacy": "^1.3.4", "case-sensitive-paths-webpack-plugin": "1.1.4", "chalk": "1.1.3", "connect-history-api-fallback": "1.3.0", - "cross-spawn": "4.0.0", - "css-loader": "0.24.0", - "detect-port": "1.0.0", + "cross-spawn": "4.0.2", + "css-loader": "0.25.0", + "detect-port": "1.0.1", "dotenv": "2.0.0", - "eslint": "3.5.0", - "eslint-config-react-app": "^0.2.1", - "eslint-loader": "1.5.0", - "eslint-plugin-flowtype": "2.18.1", - "eslint-plugin-import": "1.12.0", - "eslint-plugin-jsx-a11y": "2.2.2", - "eslint-plugin-react": "6.3.0", + "eslint": "3.8.1", + "eslint-config-react-app": "^0.3.0", + "eslint-loader": "1.6.0", + "eslint-plugin-flowtype": "2.21.0", + "eslint-plugin-import": "2.0.1", + "eslint-plugin-jsx-a11y": "2.2.3", + "eslint-plugin-react": "6.4.1", "extract-text-webpack-plugin": "1.0.1", "file-loader": "0.9.0", "filesize": "3.3.0", - "find-cache-dir": "0.1.1", "fs-extra": "0.30.0", "gzip-size": "3.0.0", - "html-webpack-plugin": "2.22.0", - "http-proxy-middleware": "0.17.1", - "jest": "15.1.1", + "html-webpack-plugin": "2.24.0", + "http-proxy-middleware": "0.17.2", + "jest": "16.0.2", "json-loader": "0.5.4", "less": "^2.7.1", "less-loader": "^2.2.3", "node-sass": "^3.10.0", "object-assign": "4.1.0", "path-exists": "2.1.0", - "postcss-loader": "0.13.0", + "postcss-loader": "1.0.0", "promise": "7.1.1", - "react-dev-utils": "^0.2.1", + "react-dev-utils": "^0.3.0", "recursive-readdir": "2.1.0", "rimraf": "2.5.4", "sass-loader": "^4.0.2", @@ -72,7 +71,8 @@ "stylus-loader": "^2.3.1", "url-loader": "0.5.7", "webpack": "1.13.2", - "webpack-dev-server": "1.16.1", + "webpack-dev-server": "1.16.2", + "webpack-manifest-plugin": "1.1.0", "whatwg-fetch": "1.0.0" }, "devDependencies": { @@ -134,6 +134,7 @@ "url-loader", "webpack", "webpack-dev-server", + "webpack-manifest-plugin", "whatwg-fetch" ] } diff --git a/scripts/build.js b/scripts/build.js index 4c61dc93ba7..d0b92f6a73b 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -118,13 +118,27 @@ function printFileSizes(stats, previousSizeMap) { }); } +// Print out errors +function printErrors(summary, errors) { + console.log(chalk.red(summary)); + console.log(); + errors.forEach(err => { + console.log(err.message || err); + console.log(); + }); +} + // Create the production build and print the deployment instructions. 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); + printErrors('Failed to compile.', [err]); + process.exit(1); + } + + if (stats.compilation.errors.length) { + printErrors('Failed to compile.', stats.compilation.errors); process.exit(1); } diff --git a/scripts/eject.js b/scripts/eject.js index be1350e6e3c..5e7c568b572 100644 --- a/scripts/eject.js +++ b/scripts/eject.js @@ -30,6 +30,25 @@ prompt( var ownPath = path.join(__dirname, '..'); var appPath = path.join(ownPath, '..', '..'); + + function verifyAbsent(file) { + if (fs.existsSync(path.join(appPath, file))) { + console.error( + '`' + file + '` already exists in your app folder. We cannot ' + + 'continue as you would lose all the changes in that file or directory. ' + + 'Please move or delete it (maybe make a copy for backup) and run this ' + + 'command again.' + ); + process.exit(1); + } + } + + var folders = [ + 'config', + path.join('config', 'jest'), + 'scripts' + ]; + var files = [ path.join('config', 'customizers.js'), path.join('config', 'env.js'), @@ -46,22 +65,13 @@ prompt( ]; // Ensure that the app folder is clean and we won't override any files - files.forEach(function(file) { - if (fs.existsSync(path.join(appPath, file))) { - console.error( - '`' + file + '` already exists in your app folder. We cannot ' + - 'continue as you would lose all the changes in that file or directory. ' + - 'Please delete it (maybe make a copy for backup) and run this ' + - 'command again.' - ); - process.exit(1); - } - }); + folders.forEach(verifyAbsent); + files.forEach(verifyAbsent); // Copy the files over - fs.mkdirSync(path.join(appPath, 'config')); - fs.mkdirSync(path.join(appPath, 'config', 'jest')); - fs.mkdirSync(path.join(appPath, 'scripts')); + folders.forEach(function(folder) { + fs.mkdirSync(path.join(appPath, folder)) + }); console.log(); console.log(cyan('Copying files into ' + appPath)); diff --git a/scripts/start.js b/scripts/start.js index 53877dd6a32..f887dd20407 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -195,6 +195,8 @@ function addMiddleware(devServer) { function runDevServer(host, port, protocol) { var devServer = new WebpackDevServer(compiler, { + // Enable gzip compression of generated files. + compress: true, // Silence WebpackDevServer's own logs since they're generally not useful. // It will still show compile warnings and errors with this setting. clientLogLevel: 'none', diff --git a/template/README.md b/template/README.md index 13f63c2e23f..3203c5fcbb9 100644 --- a/template/README.md +++ b/template/README.md @@ -174,7 +174,7 @@ Then add this block to the `package.json` file of your project: Finally, you will need to install some packages *globally*: ```sh -npm install -g eslint-config-react-app@0.2.1 eslint@3.5.0 babel-eslint@6.1.2 eslint-plugin-react@6.3.0 eslint-plugin-import@1.12.0 eslint-plugin-jsx-a11y@2.2.2 eslint-plugin-flowtype@2.18.1 +npm install -g eslint-config-react-app@0.3.0 eslint@3.8.1 babel-eslint@7.0.0 eslint-plugin-react@6.4.1 eslint-plugin-import@2.0.1 eslint-plugin-jsx-a11y@2.2.3 eslint-plugin-flowtype@2.21.0 ``` We recognize that this is suboptimal, but it is currently required due to the way we hide the ESLint dependency. The ESLint team is already [working on a solution to this](https://github.com/eslint/eslint/issues/3458) so this may become unnecessary in a couple of months. @@ -320,7 +320,7 @@ function Header() { return Logo; } -export default function Header; +export default Header; ``` This ensures that when the project is built, Webpack will correctly move the images into the build folder, and provide us with correct paths. @@ -444,6 +444,8 @@ default you will have `NODE_ENV` defined for you, and any other environment vari variable named `REACT_APP_SECRET_CODE` will be exposed in your JS as `process.env.REACT_APP_SECRET_CODE`, in addition to `process.env.NODE_ENV`. +>Note: Changing any environment variables will require you to restart the development server if it is running. + These environment variables can be useful for displaying information conditionally based on where the project is deployed or consuming sensitive data that lives outside of version control. @@ -800,8 +802,8 @@ node_js: - 6 cache: directories: - - node_modules -script + - node_modules +script: - npm test ``` 1. Trigger your first build with a git push. @@ -879,15 +881,15 @@ This will let Create React App correctly infer the root path to use in the gener Open your `package.json` and add a `homepage` field: ```js - "homepage": "http://myusername.github.io/my-app", + "homepage": "https://myusername.github.io/my-app", ``` **The above step is important!**
Create React App uses the `homepage` field to determine the root URL in the built HTML file. -Now, whenever you run `npm run build`, you will see a cheat sheet with instructions on how to deploy to GitHub pages. +Now, whenever you run `npm run build`, you will see a cheat sheet with instructions on how to deploy to GitHub Pages. -To publish it at [http://myusername.github.io/my-app](http://myusername.github.io/my-app), run: +To publish it at [https://myusername.github.io/my-app](https://myusername.github.io/my-app), run: ```sh npm install --save-dev gh-pages @@ -899,16 +901,20 @@ Add the following script in your `package.json`: // ... "scripts": { // ... - "deploy": "gh-pages -d build" + "deploy": "npm run build&&gh-pages -d build" } ``` +(Note: the lack of whitespace is intentional.) + Then run: ```sh npm run deploy ``` +You can configure a custom domain with GitHub Pages by adding a `CNAME` file to the `public/` folder. + Note that GitHub Pages doesn't support routers that use the HTML5 `pushState` history API under the hood (for example, React Router using `browserHistory`). This is because when there is a fresh page load for a url like `http://user.github.io/todomvc/todos/42`, where `/todos/42` is a frontend route, the GitHub Pages server returns 404 because it knows nothing of `/todos/42`. If you want to add a router to a project hosted on GitHub Pages, here are a couple of solutions: * You could switch from using HTML5 history API to routing with hashes. If you use React Router, you can switch to `hashHistory` for this effect, but the URL will be longer and more verbose (for example, `http://user.github.io/todomvc/#/todos/42?_k=yknaj`). [Read more](https://github.com/reactjs/react-router/blob/master/docs/guides/Histories.md#histories) about different history implementations in React Router. * Alternatively, you can use a trick to teach GitHub Pages to handle 404 by redirecting to your `index.html` page with a special redirect parameter. You would need to add a `404.html` file with the redirection code to the `build` folder before deploying your project, and you’ll need to add code handling the redirect parameter to `index.html`. You can find a detailed explanation of this technique [in this guide](https://github.com/rafrex/spa-github-pages). diff --git a/utils/createJestConfig.js b/utils/createJestConfig.js index 7eb9f9775c1..df0238f2587 100644 --- a/utils/createJestConfig.js +++ b/utils/createJestConfig.js @@ -18,16 +18,16 @@ module.exports = (resolve, rootDir, isEjecting) => { const setupTestsFile = pathExists.sync(paths.testsSetup) ? '/src/setupTests.js' : undefined; const config = { + collectCoverageFrom: ['src/**/*.{js,jsx}'], moduleFileExtensions: ['jsx', 'js', 'json'], moduleNameMapper: { - '^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': resolve('config/jest/FileStub.js'), + '^.+\\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': resolve('config/jest/FileStub.js'), '^.+\\.css$': resolve('config/jest/CSSStub.js') }, setupFiles: [resolve('config/polyfills.js')], setupTestFrameworkScriptFile: setupTestsFile, testPathIgnorePatterns: ['/(build|docs|node_modules)/'], testEnvironment: 'node', - testRegex: '(/__tests__/.*|\\.(test|spec))\\.(js|jsx)$', }; if (rootDir) { config.rootDir = rootDir;