diff --git a/.gitignore b/.gitignore index 0aa0ed568ba..79ce88915d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.idea/ +.vscode/ node_modules/ build .DS_Store diff --git a/.travis.yml b/.travis.yml index aa61d9eb976..524224e9f6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ cache: - node_modules - packages/create-react-app/node_modules - packages/react-scripts/node_modules +install: true script: - 'if [ $TEST_SUITE = "simple" ]; then tasks/e2e-simple.sh; fi' - 'if [ $TEST_SUITE = "installs" ]; then tasks/e2e-installs.sh; fi' @@ -21,5 +22,7 @@ env: - TEST_SUITE=kitchensink matrix: include: + - node_js: 0.10 + env: TEST_SUITE=simple - node_js: 6 env: USE_YARN=yes TEST_SUITE=simple diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d4225d0755..2391b014ee7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,199 @@ +## 0.9.3 (February 28, 2017) + +#### :rocket: New Feature +* `create-react-app` + * [#1423](/~https://github.com/facebookincubator/create-react-app/pull/1423) **Fall back to Yarn offline cache when creating a new project.** ([@voxsim](/~https://github.com/voxsim)) + + If you are using Yarn, and you have created at least one app previously, Create React App now works offline. + + Yarn offline installation demo + +#### :bug: Bug Fix + +* `react-scripts` + + * [#1665](/~https://github.com/facebookincubator/create-react-app/pull/1665) Temporarily disable ESLint caching because of a bug. ([@gaearon](/~https://github.com/gaearon)) + +* `create-react-app` + * [#1675](/~https://github.com/facebookincubator/create-react-app/pull/1675) Delete project folder on failed installation on Windows. ([@johann-sonntagbauer](/~https://github.com/johann-sonntagbauer)) + * [#1662](/~https://github.com/facebookincubator/create-react-app/pull/1662) Validate project name before creating a project. ([@johann-sonntagbauer](/~https://github.com/johann-sonntagbauer)) + * [#1669](/~https://github.com/facebookincubator/create-react-app/pull/1669) Make sure React dependencies aren’t pinned in new projects. ([@johann-sonntagbauer](/~https://github.com/johann-sonntagbauer)) + +#### :nail_care: Enhancement +* `react-scripts` + + * [#1677](/~https://github.com/facebookincubator/create-react-app/pull/1677) Add `X-FORWARDED` headers for proxy requests. ([@johann-sonntagbauer](/~https://github.com/johann-sonntagbauer)) + +#### :memo: Documentation +* `react-scripts` + + * [#1657](/~https://github.com/facebookincubator/create-react-app/pull/1657) Tweak the Visual Studio Code debugging guide. ([@ryansully](/~https://github.com/ryansully)) + +#### :house: Internal +* End-to-end Tests + + * [#1648](/~https://github.com/facebookincubator/create-react-app/pull/1648) Add Windows CI tests for better stability. ([@Timer](/~https://github.com/Timer)) + +#### Committers: 5 +- Dan Abramov ([gaearon](/~https://github.com/gaearon)) +- Joe Haddad ([Timer](/~https://github.com/Timer)) +- Johann Hubert Sonntagbauer ([johann-sonntagbauer](/~https://github.com/johann-sonntagbauer)) +- Ryan Sullivan ([ryansully](/~https://github.com/ryansully)) +- Simon Vocella ([voxsim](/~https://github.com/voxsim)) + +### Migrating from 0.9.2 to 0.9.3 + +Inside any created project that has not been ejected, run: + +``` +npm install --save-dev --save-exact react-scripts@0.9.3 +``` + +You may also optionally update the global command-line utility for offline Yarn cache support: + +``` +npm install -g create-react-app@1.2.1 +``` + +## 0.9.2 (February 26, 2017) + +#### :nail_care: Enhancement + +* `create-react-app` + * [#1253](/~https://github.com/facebookincubator/create-react-app/pull/1253) **Install time optimization.** ([@n3tr](/~https://github.com/n3tr)) + + React, ReactDOM, and `react-scripts` are now installed in the same install instead of two different installs. This reduces app creation time by a noticeable amount. + + * [#1512](/~https://github.com/facebookincubator/create-react-app/pull/1512) **Graceful error handling.** ([@chitchu](/~https://github.com/chitchu)) + + If an error occurs while `create-react-app` is running, it will now clean up and not leave a broken project to reduce confusion. + + * [#1193](/~https://github.com/facebookincubator/create-react-app/pull/1193) Suggest upgrading to NPM >= 3 for faster install times. ([@mobinni](/~https://github.com/mobinni)) + + * [#1603](/~https://github.com/facebookincubator/create-react-app/pull/1603) Allow app creation in a WebStorm project. ([@driquelme](/~https://github.com/driquelme)) + + * [#1570](/~https://github.com/facebookincubator/create-react-app/pull/1570) Allow git urls in `--scripts-version`. ([@tomconroy](/~https://github.com/tomconroy)) + +* `react-scripts` + * [#1578](/~https://github.com/facebookincubator/create-react-app/pull/1578) Enable lint caching in development. ([@viankakrisna](/~https://github.com/viankakrisna)) + + * [#1478](/~https://github.com/facebookincubator/create-react-app/pull/1478) Update the build script message to show the correct port. ([@chyipin](/~https://github.com/chyipin)) + + * [#1567](/~https://github.com/facebookincubator/create-react-app/pull/1567) Remove .bin files after eject. ([@tuchk4](/~https://github.com/tuchk4)) + + * [#1560](/~https://github.com/facebookincubator/create-react-app/pull/1560) Bump `recursive-readdir`. ([@wtgtybhertgeghgtwtg](/~https://github.com/wtgtybhertgeghgtwtg)) + +#### :bug: Bug Fix +* `react-scripts` + + * [#1635](/~https://github.com/facebookincubator/create-react-app/pull/1635) **Fix Jest configuration.** ([@Timer](/~https://github.com/Timer)) + + Fixes ejecting on Windows for macOS and Linux machines. + + * [#1356](/~https://github.com/facebookincubator/create-react-app/pull/1356) Fix workflow if react-scripts package is linked via npm-link. ([@tuchk4](/~https://github.com/tuchk4)) + + Advanced users may opt to fork `react-scripts` instead of ejecting so they still receive upstream updates.
+ `react-scripts` will now function as expected when linking to a development version.
+ Previously, you could not test changes with an existing application via linking. + + * [#1585](/~https://github.com/facebookincubator/create-react-app/pull/1585) Ensure PORT environment variable is an integer. ([@matoilic](/~https://github.com/matoilic)) + + * [#1628](/~https://github.com/facebookincubator/create-react-app/pull/1628) Show correct port for pushstate-server URL text. ([@mattccrampton](/~https://github.com/mattccrampton)) + + * [#1647](/~https://github.com/facebookincubator/create-react-app/pull/1647) Fix `npm test` on Windows ([@gaearon](/~https://github.com/gaearon)) + + +#### :memo: Documentation +* User Guides + * [#1391](/~https://github.com/facebookincubator/create-react-app/pull/1391) Add note how to resolve missing required files for Heroku. ([@sbritoig](/~https://github.com/sbritoig)) + * [#1577](/~https://github.com/facebookincubator/create-react-app/pull/1577) Add a how-to on `react-snapshot`. ([@superhighfives](/~https://github.com/superhighfives)) + * [#1121](/~https://github.com/facebookincubator/create-react-app/pull/1121) Add documentation for customizing Bootstrap theme. ([@myappincome](/~https://github.com/myappincome)) + * [#1540](/~https://github.com/facebookincubator/create-react-app/pull/1540) Document debugging in Visual Studio Code. ([@bondz](/~https://github.com/bondz)) + * [#1618](/~https://github.com/facebookincubator/create-react-app/pull/1618) Add note about when to import Bootstrap CSS. ([@joewoodhouse](/~https://github.com/joewoodhouse)) + * [#1518](/~https://github.com/facebookincubator/create-react-app/pull/1518) Update flow configuration documentation. ([@SBrown52](/~https://github.com/SBrown52)) + * [#1625](/~https://github.com/facebookincubator/create-react-app/pull/1625) Specify that NODE_ENV is set to 'production' during the build step. ([@mderazon](/~https://github.com/mderazon)) + * [#1573](/~https://github.com/facebookincubator/create-react-app/pull/1573) Update Jest documentation links. ([@mkermani144](/~https://github.com/mkermani144)) + * [#1564](/~https://github.com/facebookincubator/create-react-app/pull/1564) Add --recursive to Sass watch script. ([@aleburato](/~https://github.com/aleburato)) + * [#1561](/~https://github.com/facebookincubator/create-react-app/pull/1561) Use https in link in documentation. ([@dariocravero](/~https://github.com/dariocravero)) + * [#1562](/~https://github.com/facebookincubator/create-react-app/pull/1562) Update `jest-enzyme` documentation. ([@kiranps](/~https://github.com/kiranps)) + * [#1543](/~https://github.com/facebookincubator/create-react-app/pull/1543) Update CSS preprocessor instructions. ([@aleburato](/~https://github.com/aleburato)) + * [#1338](/~https://github.com/facebookincubator/create-react-app/pull/1338) Add link to Azure deployment tutorial. ([@tpetrina](/~https://github.com/tpetrina)) + * [#1320](/~https://github.com/facebookincubator/create-react-app/pull/1320) Document how to disable autoprefix feature. ([@rrubas](/~https://github.com/rrubas)) + * [#1313](/~https://github.com/facebookincubator/create-react-app/pull/1313) List features beyond ES6 supported by create-react-app. ([@jonathanconway](/~https://github.com/jonathanconway)) + * [#1008](/~https://github.com/facebookincubator/create-react-app/pull/1008) Add Saas support documentation. ([@tsironis](/~https://github.com/tsironis)) + * [#994](/~https://github.com/facebookincubator/create-react-app/pull/994) Suggest `jest-enzyme` for simplifying test matchers. ([@blainekasten](/~https://github.com/blainekasten)) + * [#1608](/~https://github.com/facebookincubator/create-react-app/pull/1608) Add note for using CHOKIDAR_USEPOLLING in virtual machines to enable HMR. ([@AJamesPhillips](/~https://github.com/AJamesPhillips)) + * [#1495](/~https://github.com/facebookincubator/create-react-app/pull/1495) Add useful link to react-scripts. ([@pd4d10](/~https://github.com/pd4d10)) +* READMEs + * [#1576](/~https://github.com/facebookincubator/create-react-app/pull/1576) Switch from Neo to Neutrino. ([@eliperelman](/~https://github.com/eliperelman)) + * [#1275](/~https://github.com/facebookincubator/create-react-app/pull/1275) Suggest yarn commands in addition to npm. ([@lifez](/~https://github.com/lifez)) + +#### :house: Internal +* `babel-preset-react-app` + * [#1598](/~https://github.com/facebookincubator/create-react-app/pull/1598) Remove redundant babel-plugin-transform-es2015-parameters. ([@christophehurpeau](/~https://github.com/christophehurpeau)) +* Other + * [#1534](/~https://github.com/facebookincubator/create-react-app/pull/1534) Use yarn@latest in e2e. ([@gaearon](/~https://github.com/gaearon)) + * [#1295](/~https://github.com/facebookincubator/create-react-app/pull/1295) Make node version check more robust in e2e. ([@pugnascotia](/~https://github.com/pugnascotia)) + * [#1503](/~https://github.com/facebookincubator/create-react-app/pull/1503) Fix `test -e` in e2e. ([@igetgames](/~https://github.com/igetgames)) + +#### Committers: 36 +- Ade Viankakrisna Fadlil ([viankakrisna](/~https://github.com/viankakrisna)) +- Alessandro Burato ([aleburato](/~https://github.com/aleburato)) +- Alexander James Phillips ([AJamesPhillips](/~https://github.com/AJamesPhillips)) +- Blaine Kasten ([blainekasten](/~https://github.com/blainekasten)) +- Bond ([bondz](/~https://github.com/bondz)) +- Charlie Gleason ([superhighfives](/~https://github.com/superhighfives)) +- Christophe Hurpeau ([christophehurpeau](/~https://github.com/christophehurpeau)) +- Dan Abramov ([gaearon](/~https://github.com/gaearon)) +- Daniel Riquelme ([driquelme](/~https://github.com/driquelme)) +- Darío Javier Cravero ([dariocravero](/~https://github.com/dariocravero)) +- Dimitris Tsironis ([tsironis](/~https://github.com/tsironis)) +- Eli Perelman ([eliperelman](/~https://github.com/eliperelman)) +- Jirat Ki. ([n3tr](/~https://github.com/n3tr)) +- Joe Haddad ([Timer](/~https://github.com/Timer)) +- Joe Woodhouse ([joewoodhouse](/~https://github.com/joewoodhouse)) +- Jonathan Conway ([jonathanconway](/~https://github.com/jonathanconway)) +- Marcus R. Brown ([igetgames](/~https://github.com/igetgames)) +- Mato Ilic ([matoilic](/~https://github.com/matoilic)) +- Matt Crampton ([mattccrampton](/~https://github.com/mattccrampton)) +- Michael DeRazon ([mderazon](/~https://github.com/mderazon)) +- Mo Binni ([mobinni](/~https://github.com/mobinni)) +- Mohammad Kermani ([mkermani144](/~https://github.com/mkermani144)) +- Phawin Khongkhasawan ([lifez](/~https://github.com/lifez)) +- Roman Rubas ([rrubas](/~https://github.com/rrubas)) +- Rory Hunter ([pugnascotia](/~https://github.com/pugnascotia)) +- Tom Conroy ([tomconroy](/~https://github.com/tomconroy)) +- Toni Petrina ([tpetrina](/~https://github.com/tpetrina)) +- Valerii Sorokobatko ([tuchk4](/~https://github.com/tuchk4)) +- Vicente Jr Yuchitcho ([chitchu](/~https://github.com/chitchu)) +- [SBrown52](/~https://github.com/SBrown52) +- [chyipin](/~https://github.com/chyipin) +- [myappincome](/~https://github.com/myappincome) +- [sbritoig](/~https://github.com/sbritoig) +- [wtgtybhertgeghgtwtg](/~https://github.com/wtgtybhertgeghgtwtg) +- kiran ps ([kiranps](/~https://github.com/kiranps)) +- pd4d10 ([pd4d10](/~https://github.com/pd4d10)) + +### Migrating from 0.9.0 to 0.9.2 + +**Note:** 0.9.1 had known issues so you should skip it. + +Inside any created project that has not been ejected, run: + +``` +npm install --save-dev --save-exact react-scripts@0.9.2 +``` + +You may also optionally update the global command-line utility for more efficient installs (thanks [@n3tr](/~https://github.com/n3tr)): + +``` +npm install -g create-react-app@1.1.0 +``` + +## 0.9.1 (February 25, 2017) + +This release has known issues and you should skip it. Update directly to 0.9.2 instead. + ## 0.9.0 (February 11, 2017) Thanks to [@Timer](/~https://github.com/timer) for cutting this release. @@ -7,23 +203,23 @@ Thanks to [@Timer](/~https://github.com/timer) for cutting this release. * `react-scripts` * [#1489](/~https://github.com/facebookincubator/create-react-app/pull/1489) Support setting `"homepage"` to `"."` to generate relative asset paths. ([@tibdex](/~https://github.com/tibdex)) - + Applications that don’t use the HTML5 `pushState` API can now be built to be served from any relative URL. To enable this, specify `"."` as your `homepage` setting in `package.json`. It used to be possible before with a few known bugs, but they should be fixed now. See [Serving the Same Build from Different Paths](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#serving-the-same-build-from-different-paths). * [#937](/~https://github.com/facebookincubator/create-react-app/pull/1504) Add `PUBLIC_URL` environment variable for advanced use. ([@EnoahNetzach](/~https://github.com/EnoahNetzach)) - + If you use a CDN to serve the app, you can now specify `PUBLIC_URL` environment variable to override the base URL (including the hostname) for resources referenced from the built code. This new variable is mentioned in the new [Advanced Configuration](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#advanced-configuration) section. * [#1440](/~https://github.com/facebookincubator/create-react-app/pull/1440) Make all `REACT_APP_*` environment variables accessible in `index.html`. ([@jihchi](/~https://github.com/jihchi)) - + This makes all environment variables previously available in JS, also available in the HTML file, for example `%REACT_APP_MY_VARIABLE%`. See [Referencing Environment Variables in HTML](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#referencing-environment-variables-in-the-html). * `react-dev-utils` * [#1148](/~https://github.com/facebookincubator/create-react-app/pull/1148) Configure which browser to open with `npm start`. ([@GAumala](/~https://github.com/GAumala)) - + You can now disable the automatic browser launching by setting the `BROWSER` environment variable to `none`. You can also specify a different browser (or an arbitrary script) to open by default, [as supported by `opn` command](/~https://github.com/sindresorhus/opn#app) that we use under the hood. See [Advanced Configuration](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#advanced-configuration). - + #### :boom: Breaking Change * `react-scripts` @@ -31,7 +227,7 @@ Thanks to [@Timer](/~https://github.com/timer) for cutting this release. * [#1522](/~https://github.com/facebookincubator/create-react-app/pull/1522) Upgrade dependencies. ([@Timer](/~https://github.com/Timer)) * [#1432](/~https://github.com/facebookincubator/create-react-app/pull/1432) Bump Jest version. ([@gaearon](/~https://github.com/gaearon)) * [#1311](/~https://github.com/facebookincubator/create-react-app/pull/1311) Updated `babel-jest` and `jest` packages to 18.0.0. ([@lopezator](/~https://github.com/lopezator)) - + Jest has been updated to 18 and has introduced some [breaking changes and new features](https://facebook.github.io/jest/blog/2016/12/15/2016-in-jest.html). * `react-scripts`, `react-dev-utils` @@ -45,7 +241,7 @@ Thanks to [@Timer](/~https://github.com/timer) for cutting this release. * `react-scripts` * [#1441](/~https://github.com/facebookincubator/create-react-app/pull/1441) Added `babel-runtime` dependency to deduplicate dependencies when using Yarn. ([@jkimbo](/~https://github.com/jkimbo)) - + This works around a bug in Yarn that caused newly created projects to be over 400MB. Now they are down to 126MB, just like with npm 3. * [#1522](/~https://github.com/facebookincubator/create-react-app/pull/1522) Upgrade dependencies. ([@Timer](/~https://github.com/Timer)) @@ -110,7 +306,7 @@ Thanks to [@Timer](/~https://github.com/timer) for cutting this release. * [#1463](/~https://github.com/facebookincubator/create-react-app/pull/1463) Minor code style and wrong expect. ([@tuchk4](/~https://github.com/tuchk4)) * [#1470](/~https://github.com/facebookincubator/create-react-app/pull/1470) E2e jsdom fix. ([@EnoahNetzach](/~https://github.com/EnoahNetzach)) * [#1187](/~https://github.com/facebookincubator/create-react-app/pull/1187) Use a more sophisticated template for end-to-end testing.. ([@EnoahNetzach](/~https://github.com/EnoahNetzach)) - + * Other * [#1289](/~https://github.com/facebookincubator/create-react-app/pull/1289) Remove path-exists from dependencies and replace it with fs.existsSync. ([@halfzebra](/~https://github.com/halfzebra)) @@ -197,60 +393,60 @@ npm install -g create-react-app@1.0.3 * `react-scripts` * [#1233](/~https://github.com/facebookincubator/create-react-app/pull/1233) Disable subresource integrity temporarily. ([@Timer](/~https://github.com/Timer)) - + We added [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) checks to the build output in 0.8.2 but it turns out that they may fail in browsers using special compression proxies, such as Chrome on Android, when served over HTTP. We disabled the checks until we can find a safe way to add them. - + * `react-dev-utils` * [#1226](/~https://github.com/facebookincubator/create-react-app/pull/1226) Fix weird lint output. ([@n3tr](/~https://github.com/n3tr)) - + Fixes strange lint message formatting in some edge cases. - + * [#1215](/~https://github.com/facebookincubator/create-react-app/pull/1215) Fix - openChrome won't open default browser (using Canary). ([@n3tr](/~https://github.com/n3tr)) - + Fixes a regression that caused stable Google Chrome to be opened even if you are using Canary as the default browser. - + * `create-react-app` * [#1223](/~https://github.com/facebookincubator/create-react-app/pull/1223) Clean up Yarn detection and install code. ([@fson](/~https://github.com/fson)) - + Fixes noisy output on Windows when Yarn is not installed. * [#1224](/~https://github.com/facebookincubator/create-react-app/pull/1224) Exit with an error code when npm/yarn install fails. ([@fson](/~https://github.com/fson)) - + #### :nail_care: Enhancement * `react-scripts` * [#1237](/~https://github.com/facebookincubator/create-react-app/pull/1237) Clear scrollback in test mode. ([@gaearon](/~https://github.com/gaearon)) - + Ensures test watcher clears the console before running. - + * [#1229](/~https://github.com/facebookincubator/create-react-app/pull/1229) Disable jest watch mode when --coverage flag is present [#1207]. ([@BenoitAverty](/~https://github.com/BenoitAverty)) - + Since coverage doesn't work well with watch mode, we don’t run the watcher on `npm test -- --coverage` anymore. - + * [#1212](/~https://github.com/facebookincubator/create-react-app/pull/1212) Proxy rewrites Origin header to match the target server URL. ([@koles](/~https://github.com/koles)) - + Makes sure more API endpoints can work with the `proxy` setting. - + * [#1222](/~https://github.com/facebookincubator/create-react-app/pull/1222) Disable gh-page setup instruction if scripts.deploy has been added. ([@n3tr](/~https://github.com/n3tr)) - + Suppresses the instructions printed at the end of `npm run build` if `npm run deploy` already exists. * `create-react-app` * [#1236](/~https://github.com/facebookincubator/create-react-app/pull/1236) Tweak console messages. ([@gaearon](/~https://github.com/gaearon)) - + Makes error messages more friendly. - + * [#1195](/~https://github.com/facebookincubator/create-react-app/pull/1195) Use "commander" for cli argv handling. ([@EnoahNetzach](/~https://github.com/EnoahNetzach)) - + Adds `create-react-app --help` with a list of options. * `react-dev-utils` * [#1211](/~https://github.com/facebookincubator/create-react-app/pull/1211) Use a better clear console sequence. ([@gaearon](/~https://github.com/gaearon)) - + Ensures the development server clears the terminal when files are changed. #### :memo: Documentation @@ -300,14 +496,14 @@ npm install -g create-react-app@1.0.2 * [#1204](/~https://github.com/facebookincubator/create-react-app/pull/1204) Catch synchronous errors from spawning yarn. ([@gaearon](/~https://github.com/gaearon)) Fixes a crash when running `create-react-app` in some cases. - + * `react-scripts` * [#1203](/~https://github.com/facebookincubator/create-react-app/pull/1203) Update webpack-subresource-integrity to fix Windows builds. ([@gaearon](/~https://github.com/gaearon)) - + Fixes a crash when running `npm run build` on Windows. - + * [#1201](/~https://github.com/facebookincubator/create-react-app/pull/1201) Instruct Jest to load native components from RNW instead of RN. ([@remon-georgy](/~https://github.com/remon-georgy)) - + Fixes tests for users of React Native Web. #### :memo: Documentation @@ -771,7 +967,7 @@ This ensures it become a part of the build output, and resolves correctly both w ## 0.4.3 (September 18, 2016) -This is a hotfix release for a broken package. +This is a hotfix release for a broken package.
It contained no changes to the code. ### Build Dependency (`react-scripts`) @@ -913,7 +1109,7 @@ npm install --save-dev --save-exact react-scripts@0.3.0 #### Breaking Change -Now `favicon.ico` is not treated specially anymore. +Now `favicon.ico` is not treated specially anymore.
If you use it, move it to `src` and add the following line to `` in your HTML: ```html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b6c4e69bbfd..3a6402606db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Following these guidelines helps to communicate that you respect the time of the As much as possible, we try to avoid adding configuration and flags. The purpose of this tool is to provide the best experience for people getting started with React, and this will always be our first priority. This means that sometimes we [sacrifice additional functionality](https://gettingreal.37signals.com/ch05_Half_Not_Half_Assed.php) (such as server rendering) because it is too hard to solve it in a way that wouldn’t require any configuration. -We prefer **convention, heuristics, or interactivity** over configuration. +We prefer **convention, heuristics, or interactivity** over configuration.
Here’s a few examples of them in action. ### Convention diff --git a/README.md b/README.md index 52843b572ed..87ddd4ee5d1 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ Create React apps with no build configuration. * [Getting Started](#getting-started) – How to create a new app. * [User Guide](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md) – How to develop apps bootstrapped with Create React App. +Create React App works on macOS, Windows, and Linux.
+If something doesn’t work please [file an issue](/~https://github.com/facebookincubator/create-react-app/issues/new). + ## tl;dr ```sh @@ -13,7 +16,6 @@ npm install -g create-react-app create-react-app my-app cd my-app/ npm start - ``` Then open [http://localhost:3000/](http://localhost:3000/) to see your app.
@@ -70,7 +72,7 @@ my-app/ No configuration or complicated folder structures, just the files you need to build your app.
Once the installation is done, you can run some commands inside the project folder: -### `npm start` +### `npm start` or `yarn start` Runs the app in development mode.
Open [http://localhost:3000](http://localhost:3000) to view it in the browser. @@ -80,20 +82,21 @@ You will see the build errors and lint warnings in the console. Build errors -### `npm test` +### `npm test` or `yarn test` Runs the test watcher in an interactive mode.
By default, runs tests related to files changes since the last commit. [Read more about testing.](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#running-tests) -### `npm run build` +### `npm run build` or `yarn build` Builds the app for production to the `build` folder.
It correctly bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! +A [service worker](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers) using an [offline-first caching strategy](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network) is automatically generated.
+Your ([progressive web](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app)) app is ready to be deployed! ## User Guide @@ -102,13 +105,16 @@ The [User Guide](/~https://github.com/facebookincubator/create-react-app/blob/mast - [Updating to New Releases](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#updating-to-new-releases) - [Folder Structure](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#folder-structure) - [Available Scripts](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#available-scripts) +- [Supported Language Features and Polyfills](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#supported-language-features-and-polyfills) - [Syntax Highlighting in the Editor](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#syntax-highlighting-in-the-editor) - [Displaying Lint Output in the Editor](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#displaying-lint-output-in-the-editor) +- [Debugging in the Editor](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#debugging-in-the-editor) - [Changing the Page ``](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#changing-the-page-title) - [Installing a Dependency](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#installing-a-dependency) - [Importing a Component](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#importing-a-component) - [Adding a Stylesheet](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-a-stylesheet) - [Post-Processing CSS](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#post-processing-css) +- [Adding a CSS Preprocessor (Sass, Less etc.)](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-a-css-preprocessor-sass-less-etc) - [Adding Images and Fonts](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-images-and-fonts) - [Using the `public` Folder](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#using-the-public-folder) - [Using Global Variables](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#using-global-variables) @@ -120,6 +126,7 @@ The [User Guide](/~https://github.com/facebookincubator/create-react-app/blob/mast - [Proxying API Requests in Development](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#proxying-api-requests-in-development) - [Using HTTPS in Development](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#using-https-in-development) - [Generating Dynamic `<meta>` Tags on the Server](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#generating-dynamic-meta-tags-on-the-server) +- [Pre-Rendering into Static HTML Files](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#pre-rendering-into-static-html-files) - [Running Tests](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#running-tests) - [Developing Components in Isolation](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#developing-components-in-isolation) - [Making a Progressive Web App](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app) @@ -151,6 +158,7 @@ Please refer to the [User Guide](/~https://github.com/facebookincubator/create-rea * Import CSS and image files directly from JavaScript. * Autoprefixed CSS, so you don’t need `-webkit` or other prefixes. * A `build` script to bundle JS, CSS, and images for production, with sourcemaps. +* An offline-first [service worker](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers) and a [web app manifest](https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/), meeting all the [Progressive Web App](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app) criteria. **The feature set is intentionally limited**. It doesn’t support advanced features such as server rendering or CSS modules. The tool is also **non-configurable** because it is hard to provide a cohesive experience and easy updates across a set of tools when the user can tweak anything. @@ -173,7 +181,7 @@ Some features are currently **not supported**: * Server rendering. * Some experimental syntax extensions (e.g. decorators). * CSS Modules. -* LESS or Sass. +* Importing LESS or Sass directly ([but you still can use them](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-a-css-preprocessor-sass-less-etc)). * Hot reloading of components. Some of them might get added in the future if they are stable, are useful to majority of React apps, don’t conflict with existing tools, and don’t introduce additional configuration. @@ -210,7 +218,7 @@ If you don’t agree with the choices made in this project, you might want to ex Some of the more popular and actively maintained ones are: * [insin/nwb](/~https://github.com/insin/nwb) -* [mozilla/neo](/~https://github.com/mozilla/neo) +* [mozilla-neutrino/neutrino-dev](/~https://github.com/mozilla-neutrino/neutrino-dev) * [NYTimes/kyt](/~https://github.com/NYTimes/kyt) * [zeit/next.js](/~https://github.com/zeit/next.js) * [gatsbyjs/gatsby](/~https://github.com/gatsbyjs/gatsby) diff --git a/appveyor.cleanup-cache.txt b/appveyor.cleanup-cache.txt new file mode 100644 index 00000000000..0656d74ac41 --- /dev/null +++ b/appveyor.cleanup-cache.txt @@ -0,0 +1,6 @@ +Edit this file to trigger a cache rebuild. +http://help.appveyor.com/discussions/questions/1310-delete-cache + +---- +Just testing if this works. +Hello, world. diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000000..bc2ac1b3f88 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,40 @@ +environment: + matrix: + - nodejs_version: 6 + test_suite: "simple" + - nodejs_version: 6 + test_suite: "installs" + - nodejs_version: 6 + test_suite: "kitchensink" + - nodejs_version: 4 + test_suite: "simple" + - nodejs_version: 4 + test_suite: "installs" + - nodejs_version: 4 + test_suite: "kitchensink" + +cache: + - node_modules -> appveyor.cleanup-cache.txt + - packages\react-scripts\node_modules -> appveyor.cleanup-cache.txt + +clone_depth: 50 + +matrix: + fast_finish: true + +platform: + - x64 + +install: + - ps: Install-Product node $env:nodejs_version $env:platform + +build: off + +skip_commits: + files: + - '**/*.md' + +test_script: + - node --version + - npm --version + - sh tasks/e2e-%test_suite%.sh diff --git a/lerna.json b/lerna.json index f648aac48a1..e2429777df4 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "lerna": "2.0.0-beta.37", + "lerna": "2.0.0-beta.38", "version": "independent", "changelog": { "repo": "facebookincubator/create-react-app", @@ -12,5 +12,8 @@ "tag: internal": ":house: Internal" }, "cacheDir": ".changelog" - } + }, + "packages": [ + "packages/*" + ] } diff --git a/package.json b/package.json index a9262568d25..1398c949cf9 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,14 @@ "test": "node packages/react-scripts/scripts/test.js --env=jsdom" }, "devDependencies": { - "babel-eslint": "6.1.2", - "eslint": "3.5.0", - "eslint-config-react-app": "0.2.1", - "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", - "lerna": "^2.0.0-beta.37", + "babel-eslint": "7.1.0", + "eslint": "3.16.1", + "eslint-config-react-app": "0.5.1", + "eslint-plugin-flowtype": "2.21.0", + "eslint-plugin-import": "2.0.1", + "eslint-plugin-jsx-a11y": "4.0.0", + "eslint-plugin-react": "6.4.1", + "lerna": "2.0.0-beta.38", "lerna-changelog": "^0.2.3" } } diff --git a/packages/babel-preset-react-app/README.md b/packages/babel-preset-react-app/README.md index 5221c41af59..4dc9fb9b168 100644 --- a/packages/babel-preset-react-app/README.md +++ b/packages/babel-preset-react-app/README.md @@ -1,6 +1,6 @@ # babel-preset-react-app -This package includes the Babel preset used by [Create React App](/~https://github.com/facebookincubator/create-react-app). +This package includes the Babel preset used by [Create React App](/~https://github.com/facebookincubator/create-react-app).<br> Please refer to its documentation: * [Getting Started](/~https://github.com/facebookincubator/create-react-app/blob/master/README.md#getting-started) – How to create a new app. diff --git a/packages/babel-preset-react-app/index.js b/packages/babel-preset-react-app/index.js index 8a42f885e31..3760ec4e1ae 100644 --- a/packages/babel-preset-react-app/index.js +++ b/packages/babel-preset-react-app/index.js @@ -64,13 +64,6 @@ if (env === 'development' || env === 'test') { } if (env === 'test') { - plugins.push.apply(plugins, [ - // We always include this plugin regardless of environment - // because of a Babel bug that breaks object rest/spread without it: - // /~https://github.com/babel/babel/issues/4851 - require.resolve('babel-plugin-transform-es2015-parameters') - ]); - module.exports = { presets: [ // ES features necessary for user's Node version @@ -82,7 +75,10 @@ if (env === 'test') { // JSX, Flow require.resolve('babel-preset-react') ], - plugins: plugins + plugins: plugins.concat([ + // Compiles import() to a deferred require() + require.resolve('babel-plugin-dynamic-import-node') + ]) }; } else { module.exports = { @@ -102,6 +98,8 @@ if (env === 'test') { // Async functions are converted to generators by babel-preset-latest async: false }], + // Adds syntax support for import() + require.resolve('babel-plugin-syntax-dynamic-import'), ]) }; diff --git a/packages/babel-preset-react-app/package.json b/packages/babel-preset-react-app/package.json index ae5ab93be65..049d75db3ee 100644 --- a/packages/babel-preset-react-app/package.json +++ b/packages/babel-preset-react-app/package.json @@ -1,6 +1,6 @@ { "name": "babel-preset-react-app", - "version": "2.1.0", + "version": "2.1.1", "description": "Babel preset used by Create React App", "repository": "facebookincubator/create-react-app", "license": "BSD-3-Clause", @@ -11,8 +11,9 @@ "index.js" ], "dependencies": { + "babel-plugin-dynamic-import-node": "1.0.0", + "babel-plugin-syntax-dynamic-import": "6.18.0", "babel-plugin-transform-class-properties": "6.22.0", - "babel-plugin-transform-es2015-parameters": "6.22.0", "babel-plugin-transform-object-rest-spread": "6.22.0", "babel-plugin-transform-react-constant-elements": "6.22.0", "babel-plugin-transform-react-jsx": "6.22.0", diff --git a/packages/create-react-app/README.md b/packages/create-react-app/README.md index 062a320250d..c8fed06489f 100644 --- a/packages/create-react-app/README.md +++ b/packages/create-react-app/README.md @@ -1,6 +1,6 @@ # create-react-app -This package includes the global command for [Create React App](/~https://github.com/facebookincubator/create-react-app). +This package includes the global command for [Create React App](/~https://github.com/facebookincubator/create-react-app).<br> Please refer to its documentation: * [Getting Started](/~https://github.com/facebookincubator/create-react-app/blob/master/README.md#getting-started) – How to create a new app. diff --git a/packages/create-react-app/index.js b/packages/create-react-app/index.js old mode 100644 new mode 100755 index 4dbb37f4969..5c2205dc6fc --- a/packages/create-react-app/index.js +++ b/packages/create-react-app/index.js @@ -39,8 +39,9 @@ 'use strict'; var chalk = require('chalk'); +var validateProjectName = require("validate-npm-package-name"); -var currentNodeVersion = process.versions.node +var currentNodeVersion = process.versions.node; if (currentNodeVersion.split('.')[0] < 4) { console.error( chalk.red( @@ -58,6 +59,7 @@ var path = require('path'); var execSync = require('child_process').execSync; var spawn = require('cross-spawn'); var semver = require('semver'); +var dns = require('dns'); var projectName; @@ -97,6 +99,14 @@ if (typeof projectName === 'undefined') { process.exit(1); } +function printValidationResults(results) { + if (typeof results !== 'undefined') { + results.forEach(function (error) { + console.error(chalk.red(' * ' + error)); + }); + } +} + var hiddenProgram = new commander.Command() .option('--internal-testing-template <path-to-template>', '(internal usage only, DO NOT RELY ON THIS) ' + 'use a non-standard application template') @@ -124,7 +134,7 @@ function createApp(name, verbose, version, template) { var packageJson = { name: appName, version: '0.1.0', - private: true, + private: true }; fs.writeFileSync( path.join(root, 'package.json'), @@ -133,10 +143,6 @@ function createApp(name, verbose, version, template) { var originalDirectory = process.cwd(); process.chdir(root); - console.log('Installing packages. This might take a couple minutes.'); - console.log('Installing ' + chalk.cyan('react-scripts') + '...'); - console.log(); - run(root, appName, version, verbose, originalDirectory, template); } @@ -149,24 +155,47 @@ function shouldUseYarn() { } } -function install(packageToInstall, verbose, callback) { - var command; - var args; - if (shouldUseYarn()) { - command = 'yarnpkg'; - args = [ 'add', '--dev', '--exact', packageToInstall]; - } else { - command = 'npm'; - args = ['install', '--save-dev', '--save-exact', packageToInstall]; - } +function install(useYarn, dependencies, verbose, isOnline) { + return new Promise(function(resolve, reject) { + var command; + var args; + if (useYarn) { + command = 'yarnpkg'; + args = [ + 'add', + '--exact', + ]; + if (!isOnline) { + args.push('--offline'); + } + [].push.apply(args, dependencies); + + if (!isOnline) { + console.log(chalk.yellow('You appear to be offline.')); + console.log(chalk.yellow('Falling back to the local Yarn cache.')); + console.log(); + } + + } else { + checkNpmVersion(); + command = 'npm'; + args = ['install', '--save', '--save-exact'].concat(dependencies); + } - if (verbose) { - args.push('--verbose'); - } + if (verbose) { + args.push('--verbose'); + } - var child = spawn(command, args, {stdio: 'inherit'}); - child.on('close', function(code) { - callback(code, command, args); + var child = spawn(command, args, {stdio: 'inherit'}); + child.on('close', function(code) { + if (code !== 0) { + reject({ + command: command + ' ' + args.join(' ') + }); + return; + } + resolve(); + }); }); } @@ -174,24 +203,74 @@ function run(root, appName, version, verbose, originalDirectory, template) { var packageToInstall = getInstallPackage(version); var packageName = getPackageName(packageToInstall); - install(packageToInstall, verbose, function(code, command, args) { - if (code !== 0) { - console.error(chalk.cyan(command + ' ' + args.join(' ')) + ' failed'); - process.exit(1); - } - - checkNodeVersion(packageName); + var allDependencies = ['react', 'react-dom', packageToInstall]; - var scriptsPath = path.resolve( - process.cwd(), - 'node_modules', - packageName, - 'scripts', - 'init.js' - ); - var init = require(scriptsPath); - init(root, appName, verbose, originalDirectory, template); - }); + console.log('Installing packages. This might take a couple minutes.'); + console.log( + 'Installing ' + chalk.cyan('react') + ', ' + chalk.cyan('react-dom') + + ', and ' + chalk.cyan(packageName) + '...' + ); + console.log(); + + var useYarn = shouldUseYarn(); + checkIfOnline(useYarn) + .then(function(isOnline) { + return install(useYarn, allDependencies, verbose, isOnline); + }) + .then(function() { + checkNodeVersion(packageName); + + // Since react-scripts has been installed with --save + // we need to move it into devDependencies and rewrite package.json + // also ensure react dependencies have caret version range + fixDependencies(packageName); + + var scriptsPath = path.resolve( + process.cwd(), + 'node_modules', + packageName, + 'scripts', + 'init.js' + ); + var init = require(scriptsPath); + init(root, appName, verbose, originalDirectory, template); + }) + .catch(function(reason) { + console.log(); + console.log('Aborting installation.'); + if (reason.command) { + console.log(' ' + chalk.cyan(reason.command), 'has failed.') + } else { + console.log(chalk.red('Unexpected error. Please report it as a bug:')); + console.log(reason); + } + console.log(); + + // On 'exit' we will delete these files from target directory. + var knownGeneratedFiles = [ + 'package.json', 'npm-debug.log', 'yarn-error.log', 'yarn-debug.log', 'node_modules' + ]; + var currentFiles = fs.readdirSync(path.join(root)); + currentFiles.forEach(function (file) { + knownGeneratedFiles.forEach(function (fileToMatch) { + // This will catch `(npm-debug|yarn-error|yarn-debug).log*` files + // and the rest of knownGeneratedFiles. + if ((fileToMatch.match(/.log/g) && file.indexOf(fileToMatch) === 0) || file === fileToMatch) { + console.log('Deleting generated file...', chalk.cyan(file)); + fs.removeSync(path.join(root, file)); + } + }); + }); + var remainingFiles = fs.readdirSync(path.join(root)); + if (!remainingFiles.length) { + // Delete target folder if empty + console.log('Deleting', chalk.cyan(appName + '/'), 'from', chalk.cyan(path.resolve(root, '..'))); + process.chdir(path.resolve(root, '..')); + fs.removeSync(path.join(root)); + } + console.log('Done.'); + process.exit(1); + }); } function getInstallPackage(version) { @@ -212,6 +291,11 @@ function getPackageName(installPackage) { // The package name could be with or without semver version, e.g. react-scripts-0.2.0-alpha.1.tgz // However, this function returns package name only without semver version. return installPackage.match(/^.+\/(.+?)(?:-\d+.+)?\.tgz$/)[1]; + } else if (installPackage.indexOf('git+') === 0) { + // Pull package name out of git urls e.g: + // git+/~https://github.com/mycompany/react-scripts.git + // git+ssh://github.com/mycompany/react-scripts.git#v1.2.3 + return installPackage.match(/([^\/]+)\.git(#.*)?$/)[1]; } else if (installPackage.indexOf('@') > 0) { // Do not match @scope/ when stripping off @version or @tag return installPackage.charAt(0) + installPackage.substr(1).split('@')[0]; @@ -219,6 +303,25 @@ function getPackageName(installPackage) { return installPackage; } +function checkNpmVersion() { + var isNpm2 = false; + try { + var npmVersion = execSync('npm --version').toString(); + isNpm2 = semver.lt(npmVersion, '3.0.0'); + } catch (err) { + return; + } + if (!isNpm2) { + return; + } + console.log(chalk.yellow('It looks like you are using npm 2.')); + console.log(chalk.yellow( + 'We suggest using npm 3 or Yarn for faster install times ' + + 'and less disk space usage.' + )); + console.log(); +} + function checkNodeVersion(packageName) { var packageJsonPath = path.resolve( process.cwd(), @@ -246,11 +349,18 @@ function checkNodeVersion(packageName) { } function checkAppName(appName) { + var validationResult = validateProjectName(appName); + if (!validationResult.validForNewPackages) { + console.error('Could not create a project called ' + chalk.red('"' + appName + '"') + ' because of npm naming restrictions:'); + printValidationResults(validationResult.errors); + printValidationResults(validationResult.warnings); + process.exit(1); + } + // TODO: there should be a single place that holds the dependencies var dependencies = ['react', 'react-dom']; var devDependencies = ['react-scripts']; var allDependencies = dependencies.concat(devDependencies).sort(); - if (allDependencies.indexOf(appName) >= 0) { console.error( chalk.red( @@ -268,15 +378,81 @@ function checkAppName(appName) { } } +function makeCaretRange(dependencies, name) { + var version = dependencies[name]; + + if (typeof version === 'undefined') { + console.error( + chalk.red('Missing ' + name + ' dependency in package.json') + ); + process.exit(1); + } + + var patchedVersion = '^' + version; + + if (!semver.validRange(patchedVersion)) { + console.error( + 'Unable to patch ' + name + ' dependency version because version ' + chalk.red(version) + ' will become invalid ' + chalk.red(patchedVersion) + ); + patchedVersion = version; + } + + dependencies[name] = patchedVersion; +} + +function fixDependencies(packageName) { + var packagePath = path.join(process.cwd(), 'package.json'); + var packageJson = require(packagePath); + + if (typeof packageJson.dependencies === 'undefined') { + console.error( + chalk.red('Missing dependencies in package.json') + ); + process.exit(1); + } + + var packageVersion = packageJson.dependencies[packageName]; + + if (typeof packageVersion === 'undefined') { + console.error( + chalk.red('Unable to find ' + packageName + ' in package.json') + ); + process.exit(1); + } + + packageJson.devDependencies = packageJson.devDependencies || {}; + packageJson.devDependencies[packageName] = packageVersion; + delete packageJson.dependencies[packageName]; + + makeCaretRange(packageJson.dependencies, 'react'); + makeCaretRange(packageJson.dependencies, 'react-dom'); + + fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2)); +} + // If project only contains files generated by GH, it’s safe. // We also special case IJ-based products .idea because it integrates with CRA: // /~https://github.com/facebookincubator/create-react-app/pull/368#issuecomment-243446094 function isSafeToCreateProjectIn(root) { var validFiles = [ - '.DS_Store', 'Thumbs.db', '.git', '.gitignore', '.idea', 'README.md', 'LICENSE' + '.DS_Store', 'Thumbs.db', '.git', '.gitignore', '.idea', 'README.md', 'LICENSE', 'web.iml' ]; return fs.readdirSync(root) .every(function(file) { return validFiles.indexOf(file) >= 0; }); } + +function checkIfOnline(useYarn) { + if (!useYarn) { + // Don't ping the Yarn registry. + // We'll just assume the best case. + return Promise.resolve(true); + } + + return new Promise(function(resolve) { + dns.resolve('registry.yarnpkg.com', function(err) { + resolve(err === null); + }); + }); +} diff --git a/packages/create-react-app/package.json b/packages/create-react-app/package.json index 93df8da55b8..add92d005ca 100644 --- a/packages/create-react-app/package.json +++ b/packages/create-react-app/package.json @@ -1,6 +1,6 @@ { "name": "create-react-app", - "version": "1.0.4", + "version": "1.2.1", "keywords": [ "react" ], @@ -24,6 +24,7 @@ "commander": "^2.9.0", "cross-spawn": "^4.0.0", "fs-extra": "^1.0.0", - "semver": "^5.0.3" + "semver": "^5.0.3", + "validate-npm-package-name": "^3.0.0" } } diff --git a/packages/eslint-config-react-app/README.md b/packages/eslint-config-react-app/README.md index 5c20f50ca2e..8eace6efffc 100644 --- a/packages/eslint-config-react-app/README.md +++ b/packages/eslint-config-react-app/README.md @@ -1,6 +1,6 @@ # eslint-config-react-app -This package includes the shareable ESLint configuration used by [Create React App](/~https://github.com/facebookincubator/create-react-app). +This package includes the shareable ESLint configuration used by [Create React App](/~https://github.com/facebookincubator/create-react-app).<br> Please refer to its documentation: * [Getting Started](/~https://github.com/facebookincubator/create-react-app/blob/master/README.md#getting-started) – How to create a new app. @@ -17,7 +17,7 @@ If you want to use this ESLint configuration in a project not built with Create First, install this package, ESLint and the necessary plugins. ```sh - npm install --save-dev eslint-config-react-app babel-eslint@7.0.0 eslint@3.8.1 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 + npm install --save-dev eslint-config-react-app babel-eslint@7.1.1 eslint@3.16.1 eslint-plugin-flowtype@2.21.0 eslint-plugin-import@2.0.1 eslint-plugin-jsx-a11y@4.0.0 eslint-plugin-react@6.4.1 ``` Then create a file named `.eslintrc` with following contents in the root folder of your project: diff --git a/packages/eslint-config-react-app/index.js b/packages/eslint-config-react-app/index.js index d93478bc8c9..f4d33baffa3 100644 --- a/packages/eslint-config-react-app/index.js +++ b/packages/eslint-config-react-app/index.js @@ -123,6 +123,7 @@ module.exports = { 'no-this-before-super': 'warn', 'no-throw-literal': 'warn', 'no-undef': 'error', + 'no-restricted-globals': ['error', 'event'], 'no-unexpected-multiline': 'warn', 'no-unreachable': 'warn', 'no-unused-expressions': ['warn', { @@ -133,7 +134,8 @@ module.exports = { 'no-unused-vars': ['warn', { vars: 'local', varsIgnorePattern: '^_', - args: 'none' + args: 'none', + ignoreRestSiblings: true, }], 'no-use-before-define': ['warn', 'nofunc'], 'no-useless-computed-key': 'warn', @@ -155,6 +157,15 @@ module.exports = { 'unicode-bom': ['warn', 'never'], 'use-isnan': 'warn', 'valid-typeof': 'warn', + 'no-restricted-properties': ['error', { + object: 'require', + property: 'ensure', + message: 'Please use import() instead. More info: https://webpack.js.org/guides/code-splitting-import/#dynamic-import' + }, { + object: 'System', + property: 'import', + message: 'Please use import() instead. More info: https://webpack.js.org/guides/code-splitting-import/#dynamic-import' + }], // /~https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/ diff --git a/packages/eslint-config-react-app/package.json b/packages/eslint-config-react-app/package.json index 87c7e8742f7..9f8db175b2b 100644 --- a/packages/eslint-config-react-app/package.json +++ b/packages/eslint-config-react-app/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-react-app", - "version": "0.5.1", + "version": "0.5.2", "description": "ESLint configuration used by Create React App", "repository": "facebookincubator/create-react-app", "license": "BSD-3-Clause", @@ -12,10 +12,10 @@ ], "peerDependencies": { "babel-eslint": "^7.0.0", - "eslint": "^3.8.1", + "eslint": "^3.16.1", "eslint-plugin-flowtype": "^2.21.0", "eslint-plugin-import": "^2.0.1", - "eslint-plugin-jsx-a11y": "^2.2.3", + "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.4.1" } } diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md index 455996fcc59..2d430e2a299 100644 --- a/packages/react-dev-utils/README.md +++ b/packages/react-dev-utils/README.md @@ -1,6 +1,6 @@ # react-dev-utils -This package includes some utilities used by [Create React App](/~https://github.com/facebookincubator/create-react-app). +This package includes some utilities used by [Create React App](/~https://github.com/facebookincubator/create-react-app).<br> Please refer to its documentation: * [Getting Started](/~https://github.com/facebookincubator/create-react-app/blob/master/README.md#getting-started) – How to create a new app. @@ -20,7 +20,7 @@ There is no single entry point. You can only import individual top-level modules #### `new InterpolateHtmlPlugin(replacements: {[key:string]: string})` -This Webpack plugin lets us interpolate custom variables into `index.html`. +This Webpack plugin lets us interpolate custom variables into `index.html`.<br> It works in tandem with [HtmlWebpackPlugin](/~https://github.com/ampedandwired/html-webpack-plugin) 2.x via its [events](/~https://github.com/ampedandwired/html-webpack-plugin#events). ```js @@ -58,8 +58,8 @@ module.exports = { #### `new WatchMissingNodeModulesPlugin(nodeModulesPath: string)` -This Webpack plugin ensures `npm install <library>` forces a project rebuild. -We’re not sure why this isn't Webpack's default behavior. +This Webpack plugin ensures `npm install <library>` forces a project rebuild.<br> +We’re not sure why this isn't Webpack's default behavior.<br> See [#186](/~https://github.com/facebookincubator/create-react-app/issues/186) for details. ```js @@ -83,8 +83,8 @@ module.exports = { #### `checkRequiredFiles(files: Array<string>): boolean` -Makes sure that all passed files exist. -Filenames are expected to be absolute. +Makes sure that all passed files exist.<br> +Filenames are expected to be absolute.<br> If a file is not found, prints a warning message and returns `false`. ```js @@ -161,8 +161,8 @@ getProcessForPort(3000); #### `openBrowser(url: string): boolean` -Attempts to open the browser with a given URL. -On Mac OS X, attempts to reuse an existing Chrome tab via AppleScript. +Attempts to open the browser with a given URL.<br> +On Mac OS X, attempts to reuse an existing Chrome tab via AppleScript.<br> Otherwise, falls back to [opn](/~https://github.com/sindresorhus/opn) behavior. @@ -179,8 +179,8 @@ if (openBrowser('http://localhost:3000')) { This function displays a console prompt to the user. -By convention, "no" should be the conservative choice. -If you mistype the answer, we'll always take it as a "no". +By convention, "no" should be the conservative choice.<br> +If you mistype the answer, we'll always take it as a "no".<br> You can control the behavior on `<Enter>` with `isYesDefault`. ```js diff --git a/packages/react-dev-utils/getProcessForPort.js b/packages/react-dev-utils/getProcessForPort.js index 5540fbad47a..d412b0c7a72 100644 --- a/packages/react-dev-utils/getProcessForPort.js +++ b/packages/react-dev-utils/getProcessForPort.js @@ -43,7 +43,7 @@ function getProcessCommand(processId, processDirectory) { } function getDirectoryOfProcessById(processId) { - return execSync('lsof -p '+ processId + ' | grep cwd | awk \'{print $9}\'', execOptions).trim(); + return execSync('lsof -p '+ processId + ' | awk \'$4=="cwd" {print $9}\'', execOptions).trim(); } function getProcessForPort(port) { diff --git a/packages/react-dev-utils/openBrowser.js b/packages/react-dev-utils/openBrowser.js index a3623515e0a..2e66716439b 100644 --- a/packages/react-dev-utils/openBrowser.js +++ b/packages/react-dev-utils/openBrowser.js @@ -17,7 +17,7 @@ function openBrowser(url) { // Attempt to honor this environment variable. // It is specific to the operating system. // See /~https://github.com/sindresorhus/opn#app for documentation. - const browser = process.env.BROWSER; + var browser = process.env.BROWSER; // Special case: BROWSER="none" will prevent opening completely. if (browser === 'none') { @@ -50,6 +50,14 @@ function openBrowser(url) { } } + // Another special case: on OS X, check if BROWSER has been set to "open". + // In this case, instead of passing `open` to `opn` (which won't work), + // just ignore it (thus ensuring the intended behavior, i.e. opening the system browser): + // /~https://github.com/facebookincubator/create-react-app/pull/1690#issuecomment-283518768 + if (process.platform === 'darwin' && browser === 'open') { + browser = undefined; + } + // Fallback to opn // (It will always open new tab) try { diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 9870e1ecc34..aae3b83ff01 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "react-dev-utils", - "version": "0.5.0", + "version": "0.5.1", "description": "Webpack utilities used by Create React App", "repository": "facebookincubator/create-react-app", "license": "BSD-3-Clause", @@ -28,7 +28,7 @@ "escape-string-regexp": "1.0.5", "html-entities": "1.2.0", "opn": "4.0.2", - "sockjs-client": "1.0.1", + "sockjs-client": "1.1.2", "strip-ansi": "3.0.1" } } diff --git a/packages/react-dev-utils/webpackHotDevClient.js b/packages/react-dev-utils/webpackHotDevClient.js index 7b1768d8fa0..903861a65d1 100644 --- a/packages/react-dev-utils/webpackHotDevClient.js +++ b/packages/react-dev-utils/webpackHotDevClient.js @@ -252,6 +252,10 @@ connection.onmessage = function(e) { case 'ok': handleSuccess(); break; + case 'content-changed': + // Triggered when a file from `contentBase` changed. + window.location.reload(); + break; case 'warnings': handleWarnings(message.data); break; diff --git a/packages/react-scripts/README.md b/packages/react-scripts/README.md index 845e546c67c..8004b887004 100644 --- a/packages/react-scripts/README.md +++ b/packages/react-scripts/README.md @@ -1,6 +1,6 @@ # react-scripts -This package includes scripts and configuration used by [Create React App](/~https://github.com/facebookincubator/create-react-app). +This package includes scripts and configuration used by [Create React App](/~https://github.com/facebookincubator/create-react-app).<br> Please refer to its documentation: * [Getting Started](/~https://github.com/facebookincubator/create-react-app/blob/master/README.md#getting-started) – How to create a new app. diff --git a/packages/react-scripts/bin/react-scripts.js b/packages/react-scripts/bin/react-scripts.js index a3ae830f45b..5ed9b244ca6 100755 --- a/packages/react-scripts/bin/react-scripts.js +++ b/packages/react-scripts/bin/react-scripts.js @@ -34,5 +34,6 @@ case 'test': default: console.log('Unknown script "' + script + '".'); console.log('Perhaps you need to update react-scripts?'); + console.log('See: /~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#updating-to-new-releases'); break; } diff --git a/packages/react-scripts/config/jest/babelTransform.js b/packages/react-scripts/config/jest/babelTransform.js index 145bd86cc9a..064fbf10290 100644 --- a/packages/react-scripts/config/jest/babelTransform.js +++ b/packages/react-scripts/config/jest/babelTransform.js @@ -1,3 +1,4 @@ +// @remove-file-on-eject /** * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. * diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 7d114080f7c..7ad1fc95c89 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -91,11 +91,13 @@ module.exports = { // @remove-on-eject-begin function resolveOwn(relativePath) { - return path.resolve(__dirname, relativePath); + return path.resolve(__dirname, '..', relativePath); } // config before eject: we're in ./node_modules/react-scripts/config/ module.exports = { + appPath: resolveApp('.'), + ownPath: resolveApp('node_modules/react-scripts'), appBuild: resolveApp('build'), appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), @@ -106,28 +108,33 @@ module.exports = { testsSetup: resolveApp('src/setupTests.js'), appNodeModules: resolveApp('node_modules'), // this is empty with npm3 but node resolution searches higher anyway: - ownNodeModules: resolveOwn('../node_modules'), + ownNodeModules: resolveOwn('node_modules'), nodePaths: nodePaths, publicUrl: getPublicUrl(resolveApp('package.json')), servedPath: getServedPath(resolveApp('package.json')) }; +var reactScriptsPath = path.resolve('node_modules/react-scripts'); +var reactScriptsLinked = fs.existsSync(reactScriptsPath) && fs.lstatSync(reactScriptsPath).isSymbolicLink(); + // config before publish: we're in ./packages/react-scripts/config/ -if (__dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1) { +if (!reactScriptsLinked && __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'), - yarnLockFile: resolveOwn('../template/yarn.lock'), - testsSetup: resolveOwn('../template/src/setupTests.js'), - appNodeModules: resolveOwn('../node_modules'), - ownNodeModules: resolveOwn('../node_modules'), + appPath: resolveApp('.'), + ownPath: resolveOwn('.'), + 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'), + yarnLockFile: resolveOwn('template/yarn.lock'), + testsSetup: resolveOwn('template/src/setupTests.js'), + appNodeModules: resolveOwn('node_modules'), + ownNodeModules: resolveOwn('node_modules'), nodePaths: nodePaths, - publicUrl: getPublicUrl(resolveOwn('../package.json')), - servedPath: getServedPath(resolveOwn('../package.json')) + publicUrl: getPublicUrl(resolveOwn('package.json')), + servedPath: getServedPath(resolveOwn('package.json')) }; } // @remove-on-eject-end diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index e26a5a25d81..642d73741a8 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -106,6 +106,8 @@ module.exports = { // @remove-on-eject-end module: { rules: [ + // Disable require.ensure as it's not a standard language feature. + { parser: { requireEnsure: false } }, // First, run the linter. // It's important to do this before Babel processes the JS. { diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 86d87d39211..b9ed97f787d 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -15,7 +15,7 @@ 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 SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); var paths = require('./paths'); var getClientEnvironment = require('./env'); @@ -111,6 +111,8 @@ module.exports = { // @remove-on-eject-end module: { rules: [ + // Disable require.ensure as it's not a standard language feature. + { parser: { requireEnsure: false } }, // First, run the linter. // It's important to do this before Babel processes the JS. { @@ -273,6 +275,13 @@ module.exports = { // having to parse `index.html`. new ManifestPlugin({ fileName: 'asset-manifest.json' + }), + new SWPrecacheWebpackPlugin({ + dontCacheBustUrlsMatching: /\.\w{8}\./, + filename: 'service-worker.js', + minify: true, + navigateFallback: publicUrl + '/index.html', + staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/] }) ], // Some libraries import Node modules but don't use them in the browser. diff --git a/packages/react-scripts/config/webpackDevServer.config.js b/packages/react-scripts/config/webpackDevServer.config.js new file mode 100644 index 00000000000..4d65567bd7d --- /dev/null +++ b/packages/react-scripts/config/webpackDevServer.config.js @@ -0,0 +1,51 @@ +var config = require('./webpack.config.dev'); +var paths = require('./paths'); + +var protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; +var host = process.env.HOST || 'localhost'; + +module.exports = { + // 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', + // By default WebpackDevServer serves physical files from current directory + // in addition to all the virtual build products that it serves from memory. + // This is confusing because those files won’t automatically be available in + // production build folder unless we copy them. However, copying the whole + // project directory is dangerous because we may expose sensitive files. + // Instead, we establish a convention that only files in `public` directory + // get served. Our build script will copy `public` into the `build` folder. + // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: + // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> + // In JavaScript code, you can access it with `process.env.PUBLIC_URL`. + // Note that we only recommend to use `public` folder as an escape hatch + // for files like `favicon.ico`, `manifest.json`, and libraries that are + // for some reason broken when imported through Webpack. If you just want to + // use an image, put it in `src` and `import` it from JavaScript instead. + contentBase: paths.appPublic, + // By default files from `contentBase` will not trigger a page reload. + watchContentBase: true, + // Enable hot reloading server. It will provide /sockjs-node/ endpoint + // for the WebpackDevServer client so it can learn when the files were + // updated. The WebpackDevServer client is included as an entry point + // in the Webpack development configuration. Note that only changes + // to CSS are currently hot reloaded. JS changes will refresh the browser. + hot: true, + // It is important to tell WebpackDevServer to use the same "root" path + // as we specified in the config. In development, we always serve from /. + publicPath: config.output.publicPath, + // WebpackDevServer is noisy by default so we emit custom message instead + // by listening to the compiler events with `compiler.plugin` calls above. + quiet: true, + // Reportedly, this avoids CPU overload on some systems. + // /~https://github.com/facebookincubator/create-react-app/issues/293 + watchOptions: { + ignored: /node_modules/ + }, + // Enable HTTPS if the HTTPS environment variable is set to 'true' + https: protocol === 'https', + host: host, + overlay: false, +}; diff --git a/packages/react-scripts/fixtures/kitchensink/src/App.js b/packages/react-scripts/fixtures/kitchensink/src/App.js index 36abe50d87a..587ac5f6f79 100644 --- a/packages/react-scripts/fixtures/kitchensink/src/App.js +++ b/packages/react-scripts/fixtures/kitchensink/src/App.js @@ -47,79 +47,79 @@ class App extends Component { const feature = location.hash.slice(1); switch (feature) { case 'array-destructuring': - require.ensure([], () => this.setFeature(require('./features/syntax/ArrayDestructuring').default)); + import('./features/syntax/ArrayDestructuring').then(f => this.setFeature(f.default)); break; case 'array-spread': - require.ensure([], () => this.setFeature(require('./features/syntax/ArraySpread').default)); + import('./features/syntax/ArraySpread').then(f => this.setFeature(f.default)); break; case 'async-await': - require.ensure([], () => this.setFeature(require('./features/syntax/AsyncAwait').default)); + import('./features/syntax/AsyncAwait').then(f => this.setFeature(f.default)); break; case 'class-properties': - require.ensure([], () => this.setFeature(require('./features/syntax/ClassProperties').default)); + import('./features/syntax/ClassProperties').then(f => this.setFeature(f.default)); break; case 'computed-properties': - require.ensure([], () => this.setFeature(require('./features/syntax/ComputedProperties').default)); + import('./features/syntax/ComputedProperties').then(f => this.setFeature(f.default)); break; case 'css-inclusion': - require.ensure([], () => this.setFeature(require('./features/webpack/CssInclusion').default)); + import('./features/webpack/CssInclusion').then(f => this.setFeature(f.default)); break; case 'custom-interpolation': - require.ensure([], () => this.setFeature(require('./features/syntax/CustomInterpolation').default)); + import('./features/syntax/CustomInterpolation').then(f => this.setFeature(f.default)); break; case 'default-parameters': - require.ensure([], () => this.setFeature(require('./features/syntax/DefaultParameters').default)); + import('./features/syntax/DefaultParameters').then(f => this.setFeature(f.default)); break; case 'destructuring-and-await': - require.ensure([], () => this.setFeature(require('./features/syntax/DestructuringAndAwait').default)); + import('./features/syntax/DestructuringAndAwait').then(f => this.setFeature(f.default)); break; case 'file-env-variables': - require.ensure([], () => this.setFeature(require('./features/env/FileEnvVariables').default)); + import('./features/env/FileEnvVariables').then(f => this.setFeature(f.default)); break; case 'generators': - require.ensure([], () => this.setFeature(require('./features/syntax/Generators').default)); + import('./features/syntax/Generators').then(f => this.setFeature(f.default)); break; case 'image-inclusion': - require.ensure([], () => this.setFeature(require('./features/webpack/ImageInclusion').default)); + import('./features/webpack/ImageInclusion').then(f => this.setFeature(f.default)); break; case 'json-inclusion': - require.ensure([], () => this.setFeature(require('./features/webpack/JsonInclusion').default)); + import('./features/webpack/JsonInclusion').then(f => this.setFeature(f.default)); break; case 'node-path': - require.ensure([], () => this.setFeature(require('./features/env/NodePath').default)); + import('./features/env/NodePath').then(f => this.setFeature(f.default)); break; case 'no-ext-inclusion': - require.ensure([], () => this.setFeature(require('./features/webpack/NoExtInclusion').default)); + import('./features/webpack/NoExtInclusion').then(f => this.setFeature(f.default)); break; case 'object-destructuring': - require.ensure([], () => this.setFeature(require('./features/syntax/ObjectDestructuring').default)); + import('./features/syntax/ObjectDestructuring').then(f => this.setFeature(f.default)); break; case 'object-spread': - require.ensure([], () => this.setFeature(require('./features/syntax/ObjectSpread').default)); + import('./features/syntax/ObjectSpread').then(f => this.setFeature(f.default)); break; case 'promises': - require.ensure([], () => this.setFeature(require('./features/syntax/Promises').default)); + import('./features/syntax/Promises').then(f => this.setFeature(f.default)); break; case 'public-url': - require.ensure([], () => this.setFeature(require('./features/env/PublicUrl').default)); + import('./features/env/PublicUrl').then(f => this.setFeature(f.default)); break; case 'rest-and-default': - require.ensure([], () => this.setFeature(require('./features/syntax/RestAndDefault').default)); + import('./features/syntax/RestAndDefault').then(f => this.setFeature(f.default)); break; case 'rest-parameters': - require.ensure([], () => this.setFeature(require('./features/syntax/RestParameters').default)); + import('./features/syntax/RestParameters').then(f => this.setFeature(f.default)); break; case 'shell-env-variables': - require.ensure([], () => this.setFeature(require('./features/env/ShellEnvVariables').default)); + import('./features/env/ShellEnvVariables').then(f => this.setFeature(f.default)); break; case 'svg-inclusion': - require.ensure([], () => this.setFeature(require('./features/webpack/SvgInclusion').default)); + import('./features/webpack/SvgInclusion').then(f => this.setFeature(f.default)); break; case 'template-interpolation': - require.ensure([], () => this.setFeature(require('./features/syntax/TemplateInterpolation').default)); + import('./features/syntax/TemplateInterpolation').then(f => this.setFeature(f.default)); break; case 'unknown-ext-inclusion': - require.ensure([], () => this.setFeature(require('./features/webpack/UnknownExtInclusion').default)); + import('./features/webpack/UnknownExtInclusion').then(f => this.setFeature(f.default)); break; default: throw new Error(`Missing feature "${feature}"`); } diff --git a/packages/react-scripts/fixtures/kitchensink/src/features/syntax/Promises.test.js b/packages/react-scripts/fixtures/kitchensink/src/features/syntax/Promises.test.js index 36c5984a4b5..3783cca0a54 100644 --- a/packages/react-scripts/fixtures/kitchensink/src/features/syntax/Promises.test.js +++ b/packages/react-scripts/fixtures/kitchensink/src/features/syntax/Promises.test.js @@ -1,12 +1,13 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import Promises from './Promises'; describe('promises', () => { it('renders without crashing', () => { const div = document.createElement('div'); - return new Promise(resolve => { - ReactDOM.render(<Promises onReady={resolve} />, div); + return import('./Promises').then(({ default: Promises }) => { + return new Promise(resolve => { + ReactDOM.render(<Promises onReady={resolve} />, div); + }); }); }); }); diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 371b952c8c5..3b3711e172c 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -1,6 +1,6 @@ { "name": "react-scripts", - "version": "0.9.0", + "version": "0.9.3", "description": "Configuration and scripts for Create React App.", "repository": "facebookincubator/create-react-app", "license": "BSD-3-Clause", @@ -23,12 +23,12 @@ "react-scripts": "./bin/react-scripts.js" }, "dependencies": { - "autoprefixer": "6.7.2", + "autoprefixer": "6.7.5", "babel-core": "6.22.1", "babel-eslint": "7.1.1", "babel-jest": "18.0.0", - "babel-loader": "6.2.10", - "babel-preset-react-app": "^2.1.0", + "babel-loader": "6.3.2", + "babel-preset-react-app": "^2.1.1", "babel-runtime": "^6.20.0", "case-sensitive-paths-webpack-plugin": "1.1.4", "chalk": "1.1.3", @@ -37,14 +37,14 @@ "css-loader": "0.26.1", "detect-port": "1.0.1", "dotenv": "2.0.0", - "eslint": "3.8.1", - "eslint-config-react-app": "^0.5.1", - "eslint-loader": "1.6.1", + "eslint": "3.16.1", + "eslint-config-react-app": "^0.5.2", + "eslint-loader": "1.6.3", "eslint-plugin-flowtype": "2.21.0", "eslint-plugin-import": "2.0.1", - "eslint-plugin-jsx-a11y": "2.2.3", + "eslint-plugin-jsx-a11y": "4.0.0", "eslint-plugin-react": "6.4.1", - "extract-text-webpack-plugin": "2.0.0-rc.3", + "extract-text-webpack-plugin": "2.0.0", "file-loader": "0.10.0", "filesize": "3.3.0", "fs-extra": "0.30.0", @@ -53,15 +53,16 @@ "http-proxy-middleware": "0.17.3", "jest": "18.1.0", "object-assign": "4.1.1", - "postcss-loader": "1.2.2", + "postcss-loader": "1.3.1", "promise": "7.1.1", - "react-dev-utils": "^0.5.0", - "recursive-readdir": "2.1.0", + "react-dev-utils": "^0.5.1", + "recursive-readdir": "2.1.1", "strip-ansi": "3.0.1", "style-loader": "0.13.1", + "sw-precache-webpack-plugin": "^0.9.0", "url-loader": "0.5.7", "webpack": "2.2.1", - "webpack-dev-server": "2.3.0", + "webpack-dev-server": "2.4.1", "webpack-manifest-plugin": "1.1.0", "whatwg-fetch": "2.0.2" }, diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js index 7c953c3544e..6892fcd6aff 100644 --- a/packages/react-scripts/scripts/build.js +++ b/packages/react-scripts/scripts/build.js @@ -133,7 +133,16 @@ function printErrors(summary, errors) { // Create the production build and print the deployment instructions. function build(previousSizeMap) { console.log('Creating an optimized production build...'); - webpack(config).run((err, stats) => { + + var compiler; + try { + compiler = webpack(config); + } catch (err) { + printErrors('Failed to compile.', [err]); + process.exit(1); + } + + compiler.run((err, stats) => { if (err) { printErrors('Failed to compile.', [err]); process.exit(1); @@ -214,7 +223,8 @@ function build(previousSizeMap) { 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.'); + var build = path.relative(process.cwd(), paths.appBuild); + 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(); if (useYarn) { @@ -222,8 +232,8 @@ function build(previousSizeMap) { } else { console.log(' ' + chalk.cyan('npm') + ' install -g pushstate-server'); } - console.log(' ' + chalk.cyan('pushstate-server') + ' build'); - console.log(' ' + chalk.cyan(openCommand) + ' http://localhost:9000'); + console.log(' ' + chalk.cyan('pushstate-server') + ' ' + build); + console.log(' ' + chalk.cyan(openCommand) + ' http://localhost:' + (process.env.PORT || 9000)); console.log(); } }); diff --git a/packages/react-scripts/scripts/eject.js b/packages/react-scripts/scripts/eject.js index a8ee0fc3102..0358d4256f4 100644 --- a/packages/react-scripts/scripts/eject.js +++ b/packages/react-scripts/scripts/eject.js @@ -1,3 +1,4 @@ +// @remove-file-on-eject /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -7,13 +8,14 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -var createJestConfig = require('../utils/createJestConfig'); var fs = require('fs-extra'); var path = require('path'); -var paths = require('../config/paths'); -var prompt = require('react-dev-utils/prompt'); var spawnSync = require('cross-spawn').sync; var chalk = require('chalk'); +var prompt = require('react-dev-utils/prompt'); +var paths = require('../config/paths'); +var createJestConfig = require('./utils/createJestConfig'); + var green = chalk.green; var cyan = chalk.cyan; @@ -28,8 +30,8 @@ prompt( console.log('Ejecting...'); - var ownPath = path.join(__dirname, '..'); - var appPath = path.join(ownPath, '..', '..'); + var ownPath = paths.ownPath; + var appPath = paths.appPath; function verifyAbsent(file) { if (fs.existsSync(path.join(appPath, file))) { @@ -45,44 +47,48 @@ prompt( var folders = [ 'config', - path.join('config', 'jest'), - 'scripts' + 'config/jest', + 'scripts', + 'scripts/utils', ]; - var files = [ - path.join('config', 'env.js'), - path.join('config', 'paths.js'), - path.join('config', 'polyfills.js'), - path.join('config', 'webpack.config.dev.js'), - path.join('config', 'webpack.config.prod.js'), - path.join('config', 'jest', 'cssTransform.js'), - path.join('config', 'jest', 'fileTransform.js'), - path.join('scripts', 'build.js'), - path.join('scripts', 'start.js'), - path.join('scripts', 'test.js') - ]; + // Make shallow array of files paths + var files = folders.reduce(function (files, folder) { + return files.concat( + fs.readdirSync(path.join(ownPath, folder)) + // set full path + .map(file => path.join(ownPath, folder, file)) + // omit dirs from file list + .filter(file => fs.lstatSync(file).isFile()) + ); + }, []); // Ensure that the app folder is clean and we won't override any files folders.forEach(verifyAbsent); files.forEach(verifyAbsent); - // Copy the files over + console.log(); + console.log(cyan('Copying files into ' + appPath)); + folders.forEach(function(folder) { fs.mkdirSync(path.join(appPath, folder)) }); - console.log(); - console.log(cyan('Copying files into ' + appPath)); files.forEach(function(file) { - console.log(' Adding ' + cyan(file) + ' to the project'); - var content = fs - .readFileSync(path.join(ownPath, file), 'utf8') + var content = fs.readFileSync(file, 'utf8'); + + // Skip flagged files + if (content.match(/\/\/ @remove-file-on-eject/)) { + return; + } + content = content // Remove dead code from .js files on eject .replace(/\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/mg, '') // Remove dead code from .applescript files on eject .replace(/-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/mg, '') .trim() + '\n'; - fs.writeFileSync(path.join(appPath, file), content); + console.log(' Adding ' + cyan(file.replace(ownPath, '')) + ' to the project'); + fs.writeFileSync(file.replace(ownPath, appPath), content); }); console.log(); @@ -129,13 +135,12 @@ prompt( // Add Jest config console.log(' Adding ' + cyan('Jest') + ' configuration'); appPackage.jest = createJestConfig( - filePath => path.join('<rootDir>', filePath), + filePath => path.posix.join('<rootDir>', filePath), null, true ); // Add Babel config - console.log(' Adding ' + cyan('Babel') + ' preset'); appPackage.babel = babelConfig; @@ -149,13 +154,19 @@ prompt( ); console.log(); + try { + // remove react-scripts and react-scripts binaries from app node_modules + Object.keys(ownPackage.bin).forEach(function(binKey) { + fs.removeSync(path.join(appPath, 'node_modules', '.bin', binKey)); + }); + fs.removeSync(ownPath); + } catch(e) {} + if (fs.existsSync(paths.yarnLockFile)) { console.log(cyan('Running yarn...')); - fs.removeSync(ownPath); spawnSync('yarnpkg', [], {stdio: 'inherit'}); } else { console.log(cyan('Running npm install...')); - fs.removeSync(ownPath); spawnSync('npm', ['install'], {stdio: 'inherit'}); } console.log(green('Ejected successfully!')); diff --git a/packages/react-scripts/scripts/init.js b/packages/react-scripts/scripts/init.js index 864005ecac2..33c0777ef41 100644 --- a/packages/react-scripts/scripts/init.js +++ b/packages/react-scripts/scripts/init.js @@ -1,3 +1,4 @@ +// @remove-file-on-eject /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -64,8 +65,6 @@ module.exports = function(appPath, appName, verbose, originalDirectory, template } }); - // Run yarn or npm for react and react-dom - // TODO: having to do two npm/yarn installs is bad, can we avoid it? var command; var args; @@ -92,53 +91,65 @@ module.exports = function(appPath, appName, verbose, originalDirectory, template fs.unlinkSync(templateDependenciesPath); } - console.log('Installing react and react-dom using ' + command + '...'); - console.log(); + // Install react and react-dom for backward compatibility with old CRA cli + // which doesn't install react and react-dom along with react-scripts + // or template is presetend (via --internal-testing-template) + if (!isReactInstalled(appPackage) || template) { + console.log('Installing react and react-dom using ' + command + '...'); + console.log(); - var proc = spawn(command, args, {stdio: 'inherit'}); - proc.on('close', function (code) { - if (code !== 0) { + var proc = spawn.sync(command, args, {stdio: 'inherit'}); + if (proc.status !== 0) { console.error('`' + command + ' ' + args.join(' ') + '` failed'); return; } + } - // Display the most elegant way to cd. - // This needs to handle an undefined originalDirectory for - // backward compatibility with old global-cli's. - var cdpath; - if (originalDirectory && - path.join(originalDirectory, appName) === appPath) { - cdpath = appName; - } else { - cdpath = appPath; - } + // Display the most elegant way to cd. + // This needs to handle an undefined originalDirectory for + // backward compatibility with old global-cli's. + var cdpath; + if (originalDirectory && + path.join(originalDirectory, appName) === appPath) { + cdpath = appName; + } else { + cdpath = appPath; + } + console.log(); + console.log('Success! Created ' + appName + ' at ' + appPath); + console.log('Inside that directory, you can run several commands:'); + console.log(); + console.log(chalk.cyan(' ' + command + ' start')); + console.log(' Starts the development server.'); + console.log(); + console.log(chalk.cyan(' ' + command + ' run build')); + console.log(' Bundles the app into static files for production.'); + console.log(); + console.log(chalk.cyan(' ' + command + ' test')); + console.log(' Starts the test runner.'); + console.log(); + console.log(chalk.cyan(' ' + command + ' run eject')); + console.log(' Removes this tool and copies build dependencies, configuration files'); + console.log(' and scripts into the app directory. If you do this, you can’t go back!'); + console.log(); + console.log('We suggest that you begin by typing:'); + console.log(); + console.log(chalk.cyan(' cd'), cdpath); + console.log(' ' + chalk.cyan(command + ' start')); + if (readmeExists) { console.log(); - console.log('Success! Created ' + appName + ' at ' + appPath); - console.log('Inside that directory, you can run several commands:'); - console.log(); - console.log(chalk.cyan(' ' + command + ' start')); - console.log(' Starts the development server.'); - console.log(); - console.log(chalk.cyan(' ' + command + ' run build')); - console.log(' Bundles the app into static files for production.'); - console.log(); - console.log(chalk.cyan(' ' + command + ' test')); - console.log(' Starts the test runner.'); - console.log(); - console.log(chalk.cyan(' ' + command + ' run eject')); - console.log(' Removes this tool and copies build dependencies, configuration files'); - console.log(' and scripts into the app directory. If you do this, you can’t go back!'); - console.log(); - console.log('We suggest that you begin by typing:'); - console.log(); - console.log(chalk.cyan(' cd'), cdpath); - console.log(' ' + chalk.cyan(command + ' start')); - if (readmeExists) { - console.log(); - console.log(chalk.yellow('You had a `README.md` file, we renamed it to `README.old.md`')); - } - console.log(); - console.log('Happy hacking!'); - }); + console.log(chalk.yellow('You had a `README.md` file, we renamed it to `README.old.md`')); + } + console.log(); + console.log('Happy hacking!'); }; + +function isReactInstalled(appPackage) { + var dependencies = appPackage.dependencies || {}; + + return ( + typeof dependencies.react !== 'undefined' && + typeof dependencies['react-dom'] !== 'undefined' + ) +} diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 6943f6bb95b..9f6f645af10 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -17,21 +17,20 @@ process.env.NODE_ENV = 'development'; // /~https://github.com/motdotla/dotenv require('dotenv').config({silent: true}); +var fs = require('fs'); 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 detect = require('detect-port'); +var WebpackDevServer = require('webpack-dev-server'); var clearConsole = require('react-dev-utils/clearConsole'); var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); -var formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); var getProcessForPort = require('react-dev-utils/getProcessForPort'); var openBrowser = require('react-dev-utils/openBrowser'); var prompt = require('react-dev-utils/prompt'); -var fs = require('fs'); -var config = require('../config/webpack.config.dev'); var paths = require('../config/paths'); +var config = require('../config/webpack.config.dev'); +var devServerConfig = require('../config/webpackDevServer.config'); +var createWebpackCompiler = require('./utils/createWebpackCompiler'); +var addWebpackMiddleware = require('./utils/addWebpackMiddleware'); var useYarn = fs.existsSync(paths.yarnLockFile); var cli = useYarn ? 'yarn' : 'npm'; @@ -43,237 +42,32 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { } // Tools like Cloud9 rely on this. -var DEFAULT_PORT = process.env.PORT || 3000; -var compiler; -var handleCompile; - -// You can safely remove this after ejecting. -// We only use this block for testing of Create React App itself: -var isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1); -if (isSmokeTest) { - handleCompile = function (err, stats) { - if (err || stats.hasErrors() || stats.hasWarnings()) { - process.exit(1); - } else { - process.exit(0); - } - }; -} +var DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; -function setupCompiler(host, port, protocol) { - // "Compiler" is a low-level interface to Webpack. - // It lets us listen to some events and provide our own custom messages. - compiler = webpack(config, handleCompile); - - // "invalid" event fires when you have changed a file, and Webpack is - // recompiling a bundle. WebpackDevServer takes care to pause serving the - // bundle, so if you refresh, it'll wait instead of serving the old one. - // "invalid" is short for "bundle invalidated", it doesn't imply any errors. - compiler.plugin('invalid', function() { - if (isInteractive) { - clearConsole(); - } - console.log('Compiling...'); - }); - - var isFirstCompile = true; - - // "done" event fires when Webpack has finished recompiling the bundle. - // Whether or not you have warnings or errors, you will get this event. - compiler.plugin('done', function(stats) { - if (isInteractive) { - clearConsole(); - } - - // We have switched off the default Webpack output in WebpackDevServer - // options so we are going to "massage" the warnings and errors and present - // them in a readable focused way. - var messages = formatWebpackMessages(stats.toJson({}, true)); - var isSuccessful = !messages.errors.length && !messages.warnings.length; - var showInstructions = isSuccessful && (isInteractive || isFirstCompile); - - if (isSuccessful) { - console.log(chalk.green('Compiled successfully!')); - } - - if (showInstructions) { - console.log(); - console.log('The app is running at:'); - console.log(); - console.log(' ' + chalk.cyan(protocol + '://' + host + ':' + port + '/')); - console.log(); - console.log('Note that the development build is not optimized.'); - console.log('To create a production build, use ' + chalk.cyan(cli + ' run build') + '.'); - console.log(); - isFirstCompile = false; - } +function run(port) { + var protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; + var host = process.env.HOST || 'localhost'; - // If errors exist, only show errors. - if (messages.errors.length) { - console.log(chalk.red('Failed to compile.')); - console.log(); - messages.errors.forEach(message => { - console.log(message); - console.log(); - }); + // Create a webpack compiler that is configured with custom messages. + var compiler = createWebpackCompiler(config, function onReady(showInstructions) { + if (!showInstructions) { return; } - - // Show warnings if no errors were found. - if (messages.warnings.length) { - console.log(chalk.yellow('Compiled with warnings.')); - console.log(); - messages.warnings.forEach(message => { - console.log(message); - console.log(); - }); - // Teach some ESLint tricks. - console.log('You may use special comments to disable some warnings.'); - console.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.'); - console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.'); - } - }); -} - -// We need to provide a custom onError function for httpProxyMiddleware. -// It allows us to log custom error messages on the console. -function onProxyError(proxy) { - return function(err, req, res){ - var host = req.headers && req.headers.host; - console.log( - chalk.red('Proxy error:') + ' Could not proxy request ' + chalk.cyan(req.url) + - ' from ' + chalk.cyan(host) + ' to ' + chalk.cyan(proxy) + '.' - ); - console.log( - 'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' + - chalk.cyan(err.code) + ').' - ); console.log(); - - // And immediately send the proper error response to the client. - // Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side. - if (res.writeHead && !res.headersSent) { - res.writeHead(500); - } - res.end('Proxy error: Could not proxy request ' + req.url + ' from ' + - host + ' to ' + proxy + ' (' + err.code + ').' - ); - } -} - -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({ - // Paths with dots should still use the history fallback. - // See /~https://github.com/facebookincubator/create-react-app/issues/387. - disableDotRule: true, - // 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 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://jex.im/regulex/ to visualize the regex - var mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/; - - // Pass the scope regex both to Express and to the middleware for proxying - // of both HTTP and WebSockets to work without false positives. - var hpm = httpProxyMiddleware(pathname => mayProxy.test(pathname), { - target: proxy, - logLevel: 'silent', - onProxyReq: function(proxyReq, req, res) { - // Browers may send Origin headers even with same-origin - // requests. To prevent CORS issues, we have to change - // the Origin to match the target URL. - if (proxyReq.getHeader('origin')) { - proxyReq.setHeader('origin', proxy); - } - }, - onError: onProxyError(proxy), - secure: false, - changeOrigin: true, - ws: true - }); - devServer.use(mayProxy, hpm); - - // Listen for the websocket 'upgrade' event and upgrade the connection. - // If this is not done, httpProxyMiddleware will not try to upgrade until - // an initial plain HTTP request is made. - devServer.listeningApp.on('upgrade', hpm.upgrade); - } - - // 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(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', - // By default WebpackDevServer serves physical files from current directory - // in addition to all the virtual build products that it serves from memory. - // This is confusing because those files won’t automatically be available in - // production build folder unless we copy them. However, copying the whole - // project directory is dangerous because we may expose sensitive files. - // Instead, we establish a convention that only files in `public` directory - // get served. Our build script will copy `public` into the `build` folder. - // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: - // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> - // In JavaScript code, you can access it with `process.env.PUBLIC_URL`. - // Note that we only recommend to use `public` folder as an escape hatch - // for files like `favicon.ico`, `manifest.json`, and libraries that are - // for some reason broken when imported through Webpack. If you just want to - // use an image, put it in `src` and `import` it from JavaScript instead. - contentBase: paths.appPublic, - // Enable hot reloading server. It will provide /sockjs-node/ endpoint - // for the WebpackDevServer client so it can learn when the files were - // updated. The WebpackDevServer client is included as an entry point - // in the Webpack development configuration. Note that only changes - // to CSS are currently hot reloaded. JS changes will refresh the browser. - hot: true, - // It is important to tell WebpackDevServer to use the same "root" path - // as we specified in the config. In development, we always serve from /. - publicPath: config.output.publicPath, - // WebpackDevServer is noisy by default so we emit custom message instead - // by listening to the compiler events with `compiler.plugin` calls above. - quiet: true, - // Reportedly, this avoids CPU overload on some systems. - // /~https://github.com/facebookincubator/create-react-app/issues/293 - watchOptions: { - ignored: /node_modules/ - }, - // Enable HTTPS if the HTTPS environment variable is set to 'true' - https: protocol === "https", - host: host, - overlay: false, + console.log('The app is running at:'); + console.log(); + console.log(' ' + chalk.cyan(protocol + '://' + host + ':' + port + '/')); + console.log(); + console.log('Note that the development build is not optimized.'); + console.log('To create a production build, use ' + chalk.cyan(cli + ' run build') + '.'); + console.log(); }); + // Serve webpack assets generated by the compiler over a web sever. + var devServer = new WebpackDevServer(compiler, devServerConfig); + // Our custom middleware proxies requests to /index.html or a remote API. - addMiddleware(devServer); + addWebpackMiddleware(devServer); // Launch WebpackDevServer. devServer.listen(port, (err, result) => { @@ -291,13 +85,6 @@ function runDevServer(host, port, protocol) { }); } -function run(port) { - var protocol = process.env.HTTPS === 'true' ? "https" : "http"; - var host = process.env.HOST || 'localhost'; - setupCompiler(host, port, protocol); - runDevServer(host, port, protocol); -} - // We attempt to use the default port but if it is busy, we offer the user to // run on a different port. `detect()` Promise resolves to the next free port. detect(DEFAULT_PORT).then(port => { diff --git a/packages/react-scripts/scripts/test.js b/packages/react-scripts/scripts/test.js index 9de5181d739..dfd2c75c728 100644 --- a/packages/react-scripts/scripts/test.js +++ b/packages/react-scripts/scripts/test.js @@ -28,7 +28,7 @@ if (!process.env.CI && argv.indexOf('--coverage') < 0) { // @remove-on-eject-begin // This is not necessary after eject because we embed config into package.json. -const createJestConfig = require('../utils/createJestConfig'); +const createJestConfig = require('./utils/createJestConfig'); const path = require('path'); const paths = require('../config/paths'); argv.push('--config', JSON.stringify(createJestConfig( diff --git a/packages/react-scripts/scripts/utils/addWebpackMiddleware.js b/packages/react-scripts/scripts/utils/addWebpackMiddleware.js new file mode 100644 index 00000000000..16bf40c3709 --- /dev/null +++ b/packages/react-scripts/scripts/utils/addWebpackMiddleware.js @@ -0,0 +1,97 @@ +var chalk = require('chalk'); +var historyApiFallback = require('connect-history-api-fallback'); +var httpProxyMiddleware = require('http-proxy-middleware'); +var paths = require('../../config/paths'); + +// We need to provide a custom onError function for httpProxyMiddleware. +// It allows us to log custom error messages on the console. +function onProxyError(proxy) { + return function(err, req, res){ + var host = req.headers && req.headers.host; + console.log( + chalk.red('Proxy error:') + ' Could not proxy request ' + chalk.cyan(req.url) + + ' from ' + chalk.cyan(host) + ' to ' + chalk.cyan(proxy) + '.' + ); + console.log( + 'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' + + chalk.cyan(err.code) + ').' + ); + console.log(); + + // And immediately send the proper error response to the client. + // Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side. + if (res.writeHead && !res.headersSent) { + res.writeHead(500); + } + res.end('Proxy error: Could not proxy request ' + req.url + ' from ' + + host + ' to ' + proxy + ' (' + err.code + ').' + ); + } +} + +module.exports = function addWebpackMiddleware(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({ + // Paths with dots should still use the history fallback. + // See /~https://github.com/facebookincubator/create-react-app/issues/387. + disableDotRule: true, + // 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 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://jex.im/regulex/ to visualize the regex + var mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/; + + // Pass the scope regex both to Express and to the middleware for proxying + // of both HTTP and WebSockets to work without false positives. + var hpm = httpProxyMiddleware(pathname => mayProxy.test(pathname), { + target: proxy, + logLevel: 'silent', + onProxyReq: function(proxyReq, req, res) { + // Browers may send Origin headers even with same-origin + // requests. To prevent CORS issues, we have to change + // the Origin to match the target URL. + if (proxyReq.getHeader('origin')) { + proxyReq.setHeader('origin', proxy); + } + }, + onError: onProxyError(proxy), + secure: false, + changeOrigin: true, + ws: true, + xfwd: true + }); + devServer.use(mayProxy, hpm); + + // Listen for the websocket 'upgrade' event and upgrade the connection. + // If this is not done, httpProxyMiddleware will not try to upgrade until + // an initial plain HTTP request is made. + devServer.listeningApp.on('upgrade', hpm.upgrade); + } + + // 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); +}; diff --git a/packages/react-scripts/utils/createJestConfig.js b/packages/react-scripts/scripts/utils/createJestConfig.js similarity index 94% rename from packages/react-scripts/utils/createJestConfig.js rename to packages/react-scripts/scripts/utils/createJestConfig.js index f1c67c018f1..c99345b858a 100644 --- a/packages/react-scripts/utils/createJestConfig.js +++ b/packages/react-scripts/scripts/utils/createJestConfig.js @@ -1,3 +1,4 @@ +// @remove-file-on-eject /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -7,10 +8,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -// Note: this file does not exist after ejecting. - const fs = require('fs'); -const paths = require('../config/paths'); +const paths = require('../../config/paths'); module.exports = (resolve, rootDir, isEjecting) => { // Use this instead of `paths.testsSetup` to avoid putting diff --git a/packages/react-scripts/scripts/utils/createWebpackCompiler.js b/packages/react-scripts/scripts/utils/createWebpackCompiler.js new file mode 100644 index 00000000000..932bfd54034 --- /dev/null +++ b/packages/react-scripts/scripts/utils/createWebpackCompiler.js @@ -0,0 +1,98 @@ +var chalk = require('chalk'); +var webpack = require('webpack'); +var clearConsole = require('react-dev-utils/clearConsole'); +var formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); + +var isInteractive = process.stdout.isTTY; +var handleCompile; + +// You can safely remove this after ejecting. +// We only use this block for testing of Create React App itself: +var isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1); +if (isSmokeTest) { + handleCompile = function (err, stats) { + if (err || stats.hasErrors() || stats.hasWarnings()) { + process.exit(1); + } else { + process.exit(0); + } + }; +} + +module.exports = function createWebpackCompiler(config, onReadyCallback) { + // "Compiler" is a low-level interface to Webpack. + // It lets us listen to some events and provide our own custom messages. + try { + var compiler = webpack(config, handleCompile); + } catch (err) { + console.log(chalk.red('Failed to compile.')); + console.log(); + console.log(err.message || err); + console.log(); + process.exit(1); + } + + // "invalid" event fires when you have changed a file, and Webpack is + // recompiling a bundle. WebpackDevServer takes care to pause serving the + // bundle, so if you refresh, it'll wait instead of serving the old one. + // "invalid" is short for "bundle invalidated", it doesn't imply any errors. + compiler.plugin('invalid', function() { + if (isInteractive) { + clearConsole(); + } + console.log('Compiling...'); + }); + + var isFirstCompile = true; + + // "done" event fires when Webpack has finished recompiling the bundle. + // Whether or not you have warnings or errors, you will get this event. + compiler.plugin('done', function(stats) { + if (isInteractive) { + clearConsole(); + } + + // We have switched off the default Webpack output in WebpackDevServer + // options so we are going to "massage" the warnings and errors and present + // them in a readable focused way. + var messages = formatWebpackMessages(stats.toJson({}, true)); + var isSuccessful = !messages.errors.length && !messages.warnings.length; + var showInstructions = isSuccessful && (isInteractive || isFirstCompile); + + if (isSuccessful) { + console.log(chalk.green('Compiled successfully!')); + } + + if (typeof onReadyCallback === 'function') { + onReadyCallback(showInstructions); + } + isFirstCompile = false; + + // If errors exist, only show errors. + if (messages.errors.length) { + console.log(chalk.red('Failed to compile.')); + console.log(); + messages.errors.forEach(message => { + console.log(message); + console.log(); + }); + return; + } + + // Show warnings if no errors were found. + if (messages.warnings.length) { + console.log(chalk.yellow('Compiled with warnings.')); + console.log(); + messages.warnings.forEach(message => { + console.log(message); + console.log(); + }); + // Teach some ESLint tricks. + console.log('You may use special comments to disable some warnings.'); + console.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.'); + console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.'); + } + }); + + return compiler; +}; diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md index c49da3e8d34..db06a0e0e5a 100644 --- a/packages/react-scripts/template/README.md +++ b/packages/react-scripts/template/README.md @@ -13,13 +13,16 @@ You can find the most recent version of this guide [here](/~https://github.com/fac - [npm test](#npm-test) - [npm run build](#npm-run-build) - [npm run eject](#npm-run-eject) +- [Supported Language Features and Polyfills](#supported-language-features-and-polyfills) - [Syntax Highlighting in the Editor](#syntax-highlighting-in-the-editor) - [Displaying Lint Output in the Editor](#displaying-lint-output-in-the-editor) +- [Debugging in the Editor](#debugging-in-the-editor) - [Changing the Page `<title>`](#changing-the-page-title) - [Installing a Dependency](#installing-a-dependency) - [Importing a Component](#importing-a-component) - [Adding a Stylesheet](#adding-a-stylesheet) - [Post-Processing CSS](#post-processing-css) +- [Adding a CSS Preprocessor (Sass, Less etc.)](#adding-a-css-preprocessor-sass-less-etc) - [Adding Images and Fonts](#adding-images-and-fonts) - [Using the `public` Folder](#using-the-public-folder) - [Changing the HTML](#changing-the-html) @@ -27,6 +30,7 @@ You can find the most recent version of this guide [here](/~https://github.com/fac - [When to Use the `public` Folder](#when-to-use-the-public-folder) - [Using Global Variables](#using-global-variables) - [Adding Bootstrap](#adding-bootstrap) + - [Using a Custom Theme](#using-a-custom-theme) - [Adding Flow](#adding-flow) - [Adding Custom Environment Variables](#adding-custom-environment-variables) - [Referencing Environment Variables in the HTML](#referencing-environment-variables-in-the-html) @@ -39,6 +43,7 @@ You can find the most recent version of this guide [here](/~https://github.com/fac - [Proxying API Requests in Development](#proxying-api-requests-in-development) - [Using HTTPS in Development](#using-https-in-development) - [Generating Dynamic `<meta>` Tags on the Server](#generating-dynamic-meta-tags-on-the-server) +- [Pre-Rendering into Static HTML Files](#pre-rendering-into-static-html-files) - [Injecting Data from the Server into the Page](#injecting-data-from-the-server-into-the-page) - [Running Tests](#running-tests) - [Filename Conventions](#filename-conventions) @@ -59,6 +64,7 @@ You can find the most recent version of this guide [here](/~https://github.com/fac - [Deployment](#deployment) - [Serving Apps with Client-Side Routing](#serving-apps-with-client-side-routing) - [Building for Relative Paths](#building-for-relative-paths) + - [Azure](#azure) - [Firebase](#firebase) - [GitHub Pages](#github-pages) - [Heroku](#heroku) @@ -170,6 +176,29 @@ Instead, it will copy all the configuration files and the transitive dependencie You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. +## Supported Language Features and Polyfills + +This project supports a superset of the latest JavaScript standard.<br> +In addition to [ES6](/~https://github.com/lukehoban/es6features) syntax features, it also supports: + +* [Exponentiation Operator](/~https://github.com/rwaldron/exponentiation-operator) (ES2016). +* [Async/await](/~https://github.com/tc39/ecmascript-asyncawait) (ES2017). +* [Object Rest/Spread Properties](/~https://github.com/sebmarkbage/ecmascript-rest-spread) (stage 3 proposal). +* [Class Fields and Static Properties](/~https://github.com/tc39/proposal-class-public-fields) (stage 2 proposal). +* [JSX](https://facebook.github.io/react/docs/introducing-jsx.html) and [Flow](https://flowtype.org/) syntax. + +Learn more about [different proposal stages](https://babeljs.io/docs/plugins/#presets-stage-x-experimental-presets-). + +While we recommend to use experimental proposals with some caution, Facebook heavily uses these features in the product code, so we intend to provide [codemods](https://medium.com/@cpojer/effective-javascript-codemods-5a6686bb46fb) if any of these proposals change in the future. + +Note that **the project only includes a few ES6 [polyfills](https://en.wikipedia.org/wiki/Polyfill)**: + +* [`Object.assign()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) via [`object-assign`](/~https://github.com/sindresorhus/object-assign). +* [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) via [`promise`](/~https://github.com/then/promise). +* [`fetch()`](https://developer.mozilla.org/en/docs/Web/API/Fetch_API) via [`whatwg-fetch`](/~https://github.com/github/fetch). + +If you use any other ES6+ features that need **runtime support** (such as `Array.from()` or `Symbol`), make sure you are including the appropriate polyfills manually, or that the browsers you are targeting already support them. + ## Syntax Highlighting in the Editor To configure the syntax highlighting in your favorite text editor, head to the [relevant Babel documentation page](https://babeljs.io/docs/editors) and follow the instructions. Some of the most popular editors are covered. @@ -216,20 +245,49 @@ 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.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 +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@4.0.0 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. +## Debugging in the Editor + +**This feature is currently only supported by [Visual Studio Code](https://code.visualstudio.com) editor.** + +Visual Studio Code supports live-editing and debugging out of the box with Create React App. This enables you as a developer to write and debug your React code without leaving the editor, and most importantly it enables you to have a continuous development workflow, where context switching is minimal, as you don’t have to switch between tools. + +You would need to have the latest version of [VS Code](https://code.visualstudio.com) and VS Code [Chrome Debugger Extension](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) installed. + +Then add the block below to your `launch.json` file and put it inside the `.vscode` folder in your app’s root directory. + +```json +{ + "version": "0.2.0", + "configurations": [{ + "name": "Chrome", + "type": "chrome", + "request": "launch", + "url": "http://localhost:3000", + "webRoot": "${workspaceRoot}/src", + "userDataDir": "${workspaceRoot}/.vscode/chrome", + "sourceMapPathOverrides": { + "webpack:///src/*": "${webRoot}/*" + } + }] +} +``` + +Start your app by running `npm start`, and start debugging in VS Code by pressing `F5` or by clicking the green debug icon. You can now write code, set breakpoints, make changes to the code, and debug your newly modified code—all from your editor. + ## Changing the Page `<title>` You can find the source HTML file in the `public` folder of the generated project. You may edit the `<title>` tag in it to change the title from “React App” to anything else. -Note that normally you wouldn't edit files in the `public` folder very often. For example, [adding a stylesheet](#adding-a-stylesheet) is done without touching the HTML. +Note that normally you wouldn’t edit files in the `public` folder very often. For example, [adding a stylesheet](#adding-a-stylesheet) is done without touching the HTML. If you need to dynamically update the page title based on the content, you can use the browser [`document.title`](https://developer.mozilla.org/en-US/docs/Web/API/Document/title) API. For more complex scenarios when you want to change the title from React components, you can use [React Helmet](/~https://github.com/nfl/react-helmet), a third party library. -Finally, if you use a custom server for your app in production and want to modify the title before it gets sent to the browser, you can follow advice in [this section](#generating-dynamic-meta-tags-on-the-server). +If you use a custom server for your app in production and want to modify the title before it gets sent to the browser, you can follow advice in [this section](#generating-dynamic-meta-tags-on-the-server). Alternatively, you can pre-build each page as a static HTML file which then loads the JavaScript bundle, which is covered [here](#pre-rendering-into-static-html-files). ## Installing a Dependency @@ -351,7 +409,62 @@ becomes this: } ``` -There is currently no support for preprocessors such as Less, or for sharing variables across CSS files. +If you need to disable autoprefixing for some reason, [follow this section](/~https://github.com/postcss/autoprefixer#disabling). + +## Adding a CSS Preprocessor (Sass, Less etc.) + +Generally, we recommend that you don’t reuse the same CSS classes across different components. For example, instead of using a `.Button` CSS class in `<AcceptButton>` and `<RejectButton>` components, we recommend creating a `<Button>` component with its own `.Button` styles, that both `<AcceptButton>` and `<RejectButton>` can render (but [not inherit](https://facebook.github.io/react/docs/composition-vs-inheritance.html)). + +Following this rule often makes CSS preprocessors less useful, as features like mixins and nesting are replaced by component composition. You can, however, integrate a CSS preprocessor if you find it valuable. In this walkthrough, we will be using Sass, but you can also use Less, or another alternative. + +First, let’s install the command-line interface for Sass: + +``` +npm install node-sass --save-dev +``` + +Then in `package.json`, add the following lines to `scripts`: + +```diff + "scripts": { ++ "build-css": "node-sass src/ -o src/", ++ "watch-css": "npm run build-css && node-sass src/ -o src/ --watch --recursive", + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", +``` + +>Note: To use a different preprocessor, replace `build-css` and `watch-css` commands according to your preprocessor’s documentation. + +Now you can rename `src/App.css` to `src/App.scss` and run `npm run watch-css`. The watcher will find every Sass file in `src` subdirectories, and create a corresponding CSS file next to it, in our case overwriting `src/App.css`. Since `src/App.js` still imports `src/App.css`, the styles become a part of your application. You can now edit `src/App.scss`, and `src/App.css` will be regenerated. + +To share variables between Sass files, you can use Sass imports. For example, `src/App.scss` and other component style files could include `@import "./shared.scss";` with variable definitions. + +At this point you might want to remove all CSS files from the source control, and add `src/**/*.css` to your `.gitignore` file. It is generally a good practice to keep the build products outside of the source control. + +As a final step, you may find it convenient to run `watch-css` automatically with `npm start`, and run `build-css` as a part of `npm run build`. You can use the `&&` operator to execute two scripts sequentially. However, there is no cross-platform way to run two scripts in parallel, so we will install a package for this: + +``` +npm install --save-dev npm-run-all +``` + +Then we can change `start` and `build` scripts to include the CSS preprocessor commands: + +```diff + "scripts": { + "build-css": "node-sass src/ -o src/", + "watch-css": "npm run build-css && node-sass src/ -o src/ --watch --recursive", +- "start": "react-scripts start", +- "build": "react-scripts build", ++ "start-js": "react-scripts start", ++ "start": "npm-run-all -p watch-css start-js", ++ "build": "npm run build-css && react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +``` + +Now running `npm start` and `npm run build` also builds Sass files. Note that `node-sass` seems to have an [issue recognizing newly created files on some systems](/~https://github.com/sass/node-sass/issues/1891) so you might need to restart the watcher when you create a file until it’s resolved. ## Adding Images and Fonts @@ -474,18 +587,20 @@ Alternatively, you can force the linter to ignore any line by adding `// eslint- You don’t have to use [React Bootstrap](https://react-bootstrap.github.io) together with React but it is a popular library for integrating Bootstrap with React apps. If you need it, you can integrate it with Create React App by following these steps: -Install React Bootstrap and Bootstrap from NPM. React Bootstrap does not include Bootstrap CSS so this needs to be installed as well: +Install React Bootstrap and Bootstrap from npm. React Bootstrap does not include Bootstrap CSS so this needs to be installed as well: ``` npm install react-bootstrap --save npm install bootstrap@3 --save ``` -Import Bootstrap CSS and optionally Bootstrap theme CSS in the ```src/index.js``` file: +Import Bootstrap CSS and optionally Bootstrap theme CSS in the beginning of your ```src/index.js``` file: ```js import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap/dist/css/bootstrap-theme.css'; +// Put any other imports below so that CSS from your +// components takes precedence over default styles. ``` Import required React Bootstrap components within ```src/App.js``` file or your custom component files: @@ -496,6 +611,17 @@ import { Navbar, Jumbotron, Button } from 'react-bootstrap'; Now you are ready to use the imported React Bootstrap components within your component hierarchy defined in the render method. Here is an example [`App.js`](https://gist.githubusercontent.com/gaearon/85d8c067f6af1e56277c82d19fd4da7b/raw/6158dd991b67284e9fc8d70b9d973efe87659d72/App.js) redone using React Bootstrap. +### Using a Custom Theme + +Sometimes you might need to tweak the visual styles of Bootstrap (or equivalent package).<br> +We suggest the following approach: + +* Create a new package that depends on the package you wish to customize, e.g. Bootstrap. +* Add the necessary build steps to tweak the theme, and publish your package on npm. +* Install your own theme npm package as a dependency of your app. + +Here is an example of adding a [customized Bootstrap](https://medium.com/@tacomanator/customizing-create-react-app-aa9ffb88165) that follows these steps. + ## Adding Flow Flow is a static type checker that helps you write code with fewer bugs. Check out this [introduction to using static types in JavaScript](https://medium.com/@preethikasireddy/why-use-static-types-in-javascript-part-1-8382da1e0adb) if you are new to this concept. @@ -506,7 +632,8 @@ To add Flow to a Create React App project, follow these steps: 1. Run `npm install --save-dev flow-bin`. 2. Add `"flow": "flow"` to the `scripts` section of your `package.json`. -3. Add `// @flow` to any files you want to type check (for example, to `src/App.js`). +3. Run `npm run flow -- init` to create a [`.flowconfig` file](https://flowtype.org/docs/advanced-configuration.html) in the root directory. +4. Add `// @flow` to any files you want to type check (for example, to `src/App.js`). Now you can run `npm run flow` to check the files for type errors. You can optionally use an IDE like [Nuclide](https://nuclide.io/docs/languages/flow/) for a better integrated experience. @@ -524,13 +651,12 @@ default you will have `NODE_ENV` defined for you, and any other environment vari **The environment variables are embedded during the build time**. Since Create React App produces a static HTML/CSS/JS bundle, it can’t possibly read them at runtime. To read them at runtime, you would need to load HTML into memory on the server and replace placeholders in runtime, just like [described here](#injecting-data-from-the-server-into-the-page). Alternatively you can rebuild the app on the server anytime you change them. ->Note: You must create custom environment variables beginning with `REACT_APP_`. Any other variables except `NODE_ENV` will be ignored to avoid accidentally [exposing a private key on the machine that could have the same name](/~https://github.com/facebookincubator/create-react-app/issues/865#issuecomment-252199527). +>Note: You must create custom environment variables beginning with `REACT_APP_`. Any other variables except `NODE_ENV` will be ignored to avoid accidentally [exposing a private key on the machine that could have the same name](/~https://github.com/facebookincubator/create-react-app/issues/865#issuecomment-252199527). Changing any environment variables will require you to restart the development server if it is running. These environment variables will be defined for you on `process.env`. For example, having an environment -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`. +variable named `REACT_APP_SECRET_CODE` will be exposed in your JS as `process.env.REACT_APP_SECRET_CODE`. ->Note: Changing any environment variables will require you to restart the development server if it is running. +There is also a special built-in environment variable called `NODE_ENV`. You can read it from `process.env.NODE_ENV`. When you run `npm start`, it is always equal to `'development'`, when you run `npm test` it is always equal to `'test'`, and when you run `npm run build` to make a production bundle, it is always equal to `'production'`. **You cannot override `NODE_ENV` manually.** This prevents developers from accidentally deploying a slow development build to production. 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. @@ -564,6 +690,10 @@ When you load the app in the browser and inspect the `<input>`, you will see its </div> ``` +The above form is looking for a variable called `REACT_APP_SECRET_CODE` from the environment. In order to consume this +value, we need to have it defined in the environment. This can be done using two ways: either in your shell or in +a `.env` file. Both of these ways are described in the next few sections. + Having access to the `NODE_ENV` is also useful for performing actions conditionally: ```js @@ -572,9 +702,7 @@ if (process.env.NODE_ENV !== 'production') { } ``` -The above form is looking for a variable called `REACT_APP_SECRET_CODE` from the environment. In order to consume this -value, we need to have it defined in the environment. This can be done using two ways: either in your shell or in -a `.env` file. +When you compile the app with `npm run build`, the minification step will strip out this condition, and the resulting bundle will be smaller. ### Referencing Environment Variables in the HTML @@ -593,7 +721,7 @@ Note that the caveats from the above section apply: ### Adding Temporary Environment Variables In Your Shell -Defining environment variables can vary between OSes. It's also important to know that this manner is temporary for the +Defining environment variables can vary between OSes. It’s also important to know that this manner is temporary for the life of the shell session. #### Windows (cmd.exe) @@ -734,6 +862,16 @@ Then, on the server, regardless of the backend you use, you can read `index.html If you use a Node server, you can even share the route matching logic between the client and the server. However duplicating it also works fine in simple cases. +## Pre-Rendering into Static HTML Files + +If you’re hosting your `build` with a static hosting provider you can use [react-snapshot](https://www.npmjs.com/package/react-snapshot) to generate HTML pages for each route, or relative link, in your application. These pages will then seamlessly become active, or “hydrated”, when the JavaScript bundle has loaded. + +There are also opportunities to use this outside of static hosting, to take the pressure off the server when generating and caching routes. + +The primary benefit of pre-rendering is that you get the core content of each page _with_ the HTML payload—regardless of whether or not your JavaScript bundle successfully downloads. It also increases the likelihood that each route of your application will be picked up by search engines. + +You can read more about [zero-configuration pre-rendering (also called snapshotting) here](https://medium.com/superhighfives/an-almost-static-stack-6df0a2791319). + ## Injecting Data from the Server into the Page Similarly to the previous section, you can leave some placeholders in the HTML that inject global variables, for example: @@ -805,8 +943,8 @@ it('sums numbers', () => { }); ``` -All `expect()` matchers supported by Jest are [extensively documented here](http://facebook.github.io/jest/docs/api.html#expect-value).<br> -You can also use [`jest.fn()` and `expect(fn).toBeCalled()`](http://facebook.github.io/jest/docs/api.html#tobecalled) to create “spies” or mock functions. +All `expect()` matchers supported by Jest are [extensively documented here](http://facebook.github.io/jest/docs/expect.html).<br> +You can also use [`jest.fn()` and `expect(fn).toBeCalled()`](http://facebook.github.io/jest/docs/expect.html#tohavebeencalled) to create “spies” or mock functions. ### Testing Components @@ -864,9 +1002,27 @@ it('renders welcome message', () => { }); ``` -All Jest matchers are [extensively documented here](http://facebook.github.io/jest/docs/api.html#expect-value).<br> +All Jest matchers are [extensively documented here](http://facebook.github.io/jest/docs/expect.html).<br> Nevertheless you can use a third-party assertion library like [Chai](http://chaijs.com/) if you want to, as described below. +Additionally, you might find [jest-enzyme](/~https://github.com/blainekasten/enzyme-matchers) helpful to simplify your tests with readable matchers. The above `contains` code can be written simpler with jest-enzyme. + +```js +expect(wrapper).toContainReact(welcome) +``` + +To setup jest-enzyme with Create React App, follow the instructions for [initializing your test environment](#initializing-test-environment) to import `jest-enzyme`. + +```sh +npm install --save-dev jest-enzyme +``` + +```js +// src/setupTests.js +import 'jest-enzyme'; +``` + + ### Using Third Party Assertion Libraries We recommend that you use `expect()` for assertions and `jest.fn()` for spies. If you are having issues with them please [file those against Jest](/~https://github.com/facebook/jest/issues/new), and we’ll fix them. We intend to keep making them better for React, supporting, for example, [pretty-printing React elements as JSX](/~https://github.com/facebook/jest/pull/1566). @@ -1017,7 +1173,7 @@ For an example, a simple button component could have following states: Usually, it’s hard to see these states without running a sample app or some examples. -Create React App doesn't include any tools for this by default, but you can easily add [React Storybook](/~https://github.com/kadirahq/react-storybook) to your project. **It is a third-party tool that lets you develop components and see all their states in isolation from your app**. +Create React App doesn’t include any tools for this by default, but you can easily add [React Storybook](/~https://github.com/kadirahq/react-storybook) to your project. **It is a third-party tool that lets you develop components and see all their states in isolation from your app**. ![React Storybook Demo](http://i.imgur.com/7CIAWpB.gif) @@ -1048,7 +1204,90 @@ Learn more about React Storybook: ## Making a Progressive Web App -You can turn your React app into a [Progressive Web App](https://developers.google.com/web/progressive-web-apps/) by following the steps in [this repository](/~https://github.com/jeffposnick/create-react-pwa). +By default, the production build is a fully functional, offline-first +[Progressive Web App](https://developers.google.com/web/progressive-web-apps/). + +The [`sw-precache-webpack-plugin`](/~https://github.com/goldhand/sw-precache-webpack-plugin) +is integrated into [`webpack.config.prod.js`](../config/webpack.config.prod.js), +and it will take care of generating a service worker file that will automatically +precache all of your local assets and keep them up to date as you deploy updates. +The service worker will use a [cache-first strategy](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network) +for handling all requests for local assets, including the initial HTML, ensuring +that you web app is reliably fast, even on a slow or unreliable network. + +It includes a [web app manifest](https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/), +located at [`public/manifest.json`](public/manifest.json), that you might want +to customize with metadata specific to your web application, such as its name +and branding colors. + +If you would prefer not to enable service workers prior to your initial +production deployment, then remove the call to `serviceWorkerRegistration.register()` +from [`src/index.js`](src/index.js). + +If you had previously enabled service workers in your production deployment and +have decided that you would like to disable them for all your existing users, +you can swap out the call to `serviceWorkerRegistration.register()` in +[`src/index.js`](src/index.js) with a call to `serviceWorkerRegistration.unregister()`. +After the user visits a page that has `serviceWorkerRegistration.unregister()`, +the service worker will be uninstalled. + +### Offline-First Considerations + +1. Service workers [require HTTPS](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers#you_need_https), +although to facilitate local testing, that policy +[does not apply to `localhost`](http://stackoverflow.com/questions/34160509/options-for-testing-service-workers-via-http/34161385#34161385). +If your production web server does not support HTTPS, then the service worker +registration will fail, but the rest of your web app will remain functional. + +1. Service workers are [not currently supported](https://jakearchibald.github.io/isserviceworkerready/) +in all web browsers. Service worker registration [won't be attempted](src/service-worker-registration.js) +on browsers that lack support. + +1. The service worker is only enabled in the [production environment](#deployment), +e.g. the output of `npm run build`. It's recommended that you do not enable an +offline-first service worker in a development environment, as it can lead to +frustration when previously cached assets are used and do not include the latest +changes you've made locally. + +1. If you *need* to test your offline-first service worker locally, build +the application (using `npm run build`) and run a simple http server from your +build directory. After running the build script, `create-react-app` will give +instructions for one way to test your production build locally using +`pushstate-server` and the [deployment instructions](#deployment) have +instructions for using the python `SimpleHTTPServer`. *Be sure to always use an +incognito window to avoid complications with your browser cache.* + +1. If possible,configure your production environment to serve the generated +`service-worker.js` [with HTTP caching disabled](http://stackoverflow.com/questions/38843970/service-worker-javascript-update-frequency-every-24-hours). +If that's not possible—[GitHub Pages](#github-pages), for instance, does not +allow you to change the default 10 minute HTTP cache lifetime—then be aware +that if you visit your production site, and then revisit again before +`service-worker.js` has expired from your HTTP cache, you'll continue to get +the previously cached assets from the service worker. If you have an immediate +need to view your updated production deployment, performing a shift-refresh +will temporarily disable the service worker and retrieve all assets from the +network. + +1. Users aren't always familiar with offline-first web apps. It can be useful to +[let the user know](https://developers.google.com/web/fundamentals/instant-and-offline/offline-ux#inform_the_user_when_the_app_is_ready_for_offline_consumption) +when the service worker has finished populating your caches (showing a "This web +app works offline!" message) and also let them know when the service worker has +fetched the latest updates that will be available the next time they load the +page (showing a "New content is available; please refresh." message). Showing +this messages is currently left as an exercise to the developer, but as a +starting point, you can make use of the logic included in [`src/service-worker-registration.js`](src/service-worker-registration.js), which +demonstrates which service worker lifecycle events to listen for to detect each +scenario, and which as a default, just logs appropriate messages to the +JavaScript console. + +1. By default, the generated service worker file will not intercept or cache any +cross-origin traffic, like HTTP [API requests](#integrating-with-an-api-backend), +images, or embeds loaded from a different domain. If you would like to use a +runtime caching strategy for those requests, you can [`eject`](#npm-run-eject) +and then configure the +[`runtimeCaching`](/~https://github.com/GoogleChrome/sw-precache#runtimecaching-arrayobject) +option in the `SWPrecacheWebpackPlugin` section of +[`webpack.config.prod.js`](../config/webpack.config.prod.js). ## Deployment @@ -1059,7 +1298,7 @@ cd build python -m SimpleHTTPServer 9000 ``` -If you're using [Node](https://nodejs.org/) and [Express](http://expressjs.com/) as a server, it might look like this: +If you’re using [Node](https://nodejs.org/) and [Express](http://expressjs.com/) as a server, it might look like this: ```javascript const express = require('express'); @@ -1094,8 +1333,28 @@ This is because when there is a fresh page load for a `/todos/42`, the server lo }); ``` +If you’re using [Apache](https://httpd.apache.org/), you need to create a `.htaccess` file in the `public` folder that looks like this: + +``` + Options -MultiViews + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.html [QSA,L] +``` + +It will get copied to the `build` folder when you run `npm run build`. + Now requests to `/todos/42` will be handled correctly both in development and in production. +On a production build, and in a browser that supports [service workers](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers), +the service worker will automatically handle all navigation requests, like for +`/todos/42`, by serving the cached copy of your `index.html`. This +service worker navigation routing can be configured or disabled by +[`eject`ing](#npm-run-eject) and then modifying the +[`navigateFallback`](/~https://github.com/GoogleChrome/sw-precache#navigatefallback-string) +and [`navigateFallbackWhitelist`](/~https://github.com/GoogleChrome/sw-precache#navigatefallbackwhitelist-arrayregexp) +options of the `SWPreachePlugin` [configuration](../config/webpack.config.prod.js). + ### Building for Relative Paths By default, Create React App produces a build assuming your app is hosted at the server root.<br> @@ -1119,11 +1378,15 @@ If you are not using the HTML5 `pushState` history API or not using client-side This will make sure that all the asset paths are relative to `index.html`. You will then be able to move your app from `http://mywebsite.com` to `http://mywebsite.com/relativepath` or even `http://mywebsite.com/relative/path` without having to rebuild it. +### Azure + +See [this](https://medium.com/@to_pe/deploying-create-react-app-on-microsoft-azure-c0f6686a4321) blog post on how to deploy your React app to [Microsoft Azure](https://azure.microsoft.com/). + ### Firebase -Install the Firebase CLI if you haven't already by running `npm install -g firebase-tools`. Sign up for a [Firebase account](https://console.firebase.google.com/) and create a new project. Run `firebase login` and login with your previous created Firebase account. +Install the Firebase CLI if you haven’t already by running `npm install -g firebase-tools`. Sign up for a [Firebase account](https://console.firebase.google.com/) and create a new project. Run `firebase login` and login with your previous created Firebase account. -Then run the `firebase init` command from your project's root. You need to choose the **Hosting: Configure and deploy Firebase Hosting sites** and choose the Firebase project you created in the previous step. You will need to agree with `database.rules.json` being created, choose `build` as the public directory, and also agree to **Configure as a single-page app** by replying with `y`. +Then run the `firebase init` command from your project’s root. You need to choose the **Hosting: Configure and deploy Firebase Hosting sites** and choose the Firebase project you created in the previous step. You will need to agree with `database.rules.json` being created, choose `build` as the public directory, and also agree to **Configure as a single-page app** by replying with `y`. ```sh === Project Setup @@ -1228,7 +1491,7 @@ Then run: npm run deploy ``` -#### Step 4: Ensure your project's settings use `gh-pages` +#### Step 4: Ensure your project’s settings use `gh-pages` Finally, make sure **GitHub Pages** option in your GitHub project settings is set to use the `gh-pages` branch: @@ -1240,7 +1503,7 @@ You can configure a custom domain with GitHub Pages by adding a `CNAME` file to #### Notes on client-side routing -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: +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). @@ -1250,27 +1513,46 @@ GitHub Pages doesn't support routers that use the HTML5 `pushState` history API Use the [Heroku Buildpack for Create React App](/~https://github.com/mars/create-react-app-buildpack).<br> You can find instructions in [Deploying React with Zero Configuration](https://blog.heroku.com/deploying-react-with-zero-configuration). -#### Resolving "Module not found: Error: Cannot resolve 'file' or 'directory'" +#### Resolving Heroku Deployment Errors + +Sometimes `npm run build` works locally but fails during deploy via Heroku. Following are the most common cases. + +##### "Module not found: Error: Cannot resolve 'file' or 'directory'" -Sometimes `npm run build` works locally but fails during deploy via Heroku with an error like this: +If you get something like this: -``` +``` remote: Failed to create a production build. Reason: remote: Module not found: Error: Cannot resolve 'file' or 'directory' -MyDirectory in /tmp/build_1234/src +MyDirectory in /tmp/build_1234/src ``` -This means you need to ensure that the lettercase of the file or directory you `import` matches the one you see on your filesystem or on GitHub. +It means you need to ensure that the lettercase of the file or directory you `import` matches the one you see on your filesystem or on GitHub. This is important because Linux (the operating system used by Heroku) is case sensitive. So `MyDirectory` and `mydirectory` are two distinct directories and thus, even though the project builds locally, the difference in case breaks the `import` statements on Heroku remotes. +##### "Could not find a required file." + +If you exclude or ignore necessary files from the package you will see a error similar this one: + +``` +remote: Could not find a required file. +remote: Name: `index.html` +remote: Searched in: /tmp/build_a2875fc163b209225122d68916f1d4df/public +remote: +remote: npm ERR! Linux 3.13.0-105-generic +remote: npm ERR! argv "/tmp/build_a2875fc163b209225122d68916f1d4df/.heroku/node/bin/node" "/tmp/build_a2875fc163b209225122d68916f1d4df/.heroku/node/bin/npm" "run" "build" +``` + +In this case, ensure that the file is there with the proper lettercase and that’s not ignored on your local `.gitignore` or `~/.gitignore_global`. + ### Modulus See the [Modulus blog post](http://blog.modulus.io/deploying-react-apps-on-modulus) on how to deploy your react app to Modulus. ## Netlify -**To do a manual deploy to Netlify's CDN:** +**To do a manual deploy to Netlify’s CDN:** ```sh npm install netlify-cli @@ -1299,7 +1581,27 @@ When you build the project, Create React App will place the `public` folder cont ### Now -See [this example](/~https://github.com/xkawi/create-react-app-now) for a zero-configuration single-command deployment with [now](https://zeit.co/now). +[now](https://zeit.co/now) offers a zero-configuration single-command deployment. + +1. Install the `now` command-line tool either via the recommended [desktop tool](https://zeit.co/download) or via node with `npm install -g now`. + +2. Install `serve` by running `npm install --save serve`. + +3. Add this line to `scripts` in `package.json`: + + ``` + "now-start": "serve build/", + ``` + +4. Run `now` from your project directory. You will see a **now.sh** URL in your output like this: + + ``` + > Ready! https://your-project-dirname-tpspyhtdtk.now.sh (copied to clipboard) + ``` + + Paste that URL into your browser when the build is complete, and you will see your deployed app. + +Details are available in [this article.](https://zeit.co/blog/now-static) ### S3 and CloudFront @@ -1307,7 +1609,7 @@ See this [blog post](https://medium.com/@omgwtfmarc/deploying-create-react-app-t ### Surge -Install the Surge CLI if you haven't already by running `npm install -g surge`. Run the `surge` command and log in you or create a new account. You just need to specify the *build* folder and your custom domain, and you are done. +Install the Surge CLI if you haven’t already by running `npm install -g surge`. Run the `surge` command and log in you or create a new account. You just need to specify the *build* folder and your custom domain, and you are done. ```sh email: email@domain.com @@ -1351,6 +1653,7 @@ If this doesn’t happen, try one of the following workarounds: * Some editors like Vim and IntelliJ have a “safe write” feature that currently breaks the watcher. You will need to disable it. Follow the instructions in [“Working with editors supporting safe write”](https://webpack.github.io/docs/webpack-dev-server.html#working-with-editors-ides-supporting-safe-write). * If your project path contains parentheses, try moving the project to a path without them. This is caused by a [Webpack watcher bug](/~https://github.com/webpack/watchpack/issues/42). * On Linux and macOS, you might need to [tweak system settings](https://webpack.github.io/docs/troubleshooting.html#not-enough-watchers) to allow more watchers. +* If the project runs inside a virtual machine such as (a Vagrant provisioned) VirtualBox, create an `.env` file in your project directory if it doesn’t exist, and add `CHOKIDAR_USEPOLLING=true` to it. This ensures that the next time you run `npm start`, the watcher uses the polling mode, as necessary inside a VM. If none of these solutions help please leave a comment [in this thread](/~https://github.com/facebookincubator/create-react-app/issues/659). @@ -1374,7 +1677,7 @@ brew reinstall watchman You can find [other installation methods](https://facebook.github.io/watchman/docs/install.html#build-install) on the Watchman documentation page. -If this still doesn't help, try running `launchctl unload -F ~/Library/LaunchAgents/com.github.facebook.watchman.plist`. +If this still doesn’t help, try running `launchctl unload -F ~/Library/LaunchAgents/com.github.facebook.watchman.plist`. There are also reports that *uninstalling* Watchman fixes the issue. So if nothing else helps, remove it from your system and try again. @@ -1385,7 +1688,7 @@ It is reported that `npm run build` can fail on machines with no swap space, whi ### `npm run build` fails on Heroku This may be a problem with case sensitive filenames. -Please refer to [this section](#resolving-module-not-found-error-cannot-resolve-file-or-directory). +Please refer to [this section](#resolving-heroku-deployment-errors). ## Something Missing? diff --git a/packages/react-scripts/template/gitignore b/packages/react-scripts/template/gitignore index 4fa4a53764b..927d17bb9c5 100644 --- a/packages/react-scripts/template/gitignore +++ b/packages/react-scripts/template/gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://help.github.com/ignore-files/ for more about ignoring files. # dependencies /node_modules diff --git a/packages/react-scripts/template/public/index.html b/packages/react-scripts/template/public/index.html index aab5e3b00ce..d0da717c6cf 100644 --- a/packages/react-scripts/template/public/index.html +++ b/packages/react-scripts/template/public/index.html @@ -3,6 +3,8 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="theme-color" content="#000000"> + <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> <!-- Notice the use of %PUBLIC_URL% in the tag above. diff --git a/packages/react-scripts/template/public/manifest.json b/packages/react-scripts/template/public/manifest.json new file mode 100644 index 00000000000..5f5d33d2c7d --- /dev/null +++ b/packages/react-scripts/template/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "React App", + "icons": [ + { + "src": "favicon.ico", + "sizes": "192x192", + "type": "image/png" + } + ], + "start_url": "./", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/packages/react-scripts/template/src/index.js b/packages/react-scripts/template/src/index.js index 54c5ef1a427..88e2c4ef3cb 100644 --- a/packages/react-scripts/template/src/index.js +++ b/packages/react-scripts/template/src/index.js @@ -1,9 +1,12 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; +import registerServiceWorker from './service-worker-registration'; import './index.css'; ReactDOM.render( <App />, document.getElementById('root') ); + +registerServiceWorker(); diff --git a/packages/react-scripts/template/src/service-worker-registration.js b/packages/react-scripts/template/src/service-worker-registration.js new file mode 100644 index 00000000000..c3a5d878c64 --- /dev/null +++ b/packages/react-scripts/template/src/service-worker-registration.js @@ -0,0 +1,38 @@ +export default function register() { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + navigator.serviceWorker.register(swUrl).then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a "New content is + // available; please refresh." message in your web app. + console.log('New content is available; please refresh.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + } + } + }; + }; + }).catch(error => { + console.error('Error during service worker registration:', error); + }); + }); + } +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} diff --git a/tasks/cra.sh b/tasks/cra.sh index f63fd464b0c..c94559406d2 100755 --- a/tasks/cra.sh +++ b/tasks/cra.sh @@ -52,7 +52,7 @@ root_path=$PWD # ****************************************************************************** # Install all our packages -$root_path/node_modules/.bin/lerna bootstrap +"$root_path"/node_modules/.bin/lerna bootstrap cd packages/react-scripts @@ -61,10 +61,10 @@ cp package.json package.json.orig # Replace own dependencies (those in the `packages` dir) with the local paths # of those packages. -node $root_path/tasks/replace-own-deps.js +node "$root_path"/tasks/replace-own-deps.js # Finally, pack react-scripts -scripts_path=$root_path/packages/react-scripts/`npm pack` +scripts_path="$root_path"/packages/react-scripts/`npm pack` # Restore package.json rm package.json @@ -79,8 +79,8 @@ mv package.json.orig package.json yarn cache clean || true # Go back to the root directory and run the command from here -cd $root_path -node packages/create-react-app/index.js --scripts-version=$scripts_path "$@" +cd "$root_path" +node packages/create-react-app/index.js --scripts-version="$scripts_path" "$@" # Cleanup cleanup diff --git a/tasks/e2e-installs.sh b/tasks/e2e-installs.sh index 7bb4c0392f2..35ec3521b4c 100755 --- a/tasks/e2e-installs.sh +++ b/tasks/e2e-installs.sh @@ -21,8 +21,8 @@ temp_app_path=`mktemp -d 2>/dev/null || mktemp -d -t 'temp_app_path'` function cleanup { echo 'Cleaning up.' - cd $root_path - rm -rf $temp_cli_path $temp_app_path + cd "$root_path" + rm -rf "$temp_cli_path" "$temp_app_path" } # Error messages are redirected to stderr @@ -39,6 +39,13 @@ function handle_exit { exit } +# Check for the existence of one or more files. +function exists { + for f in $*; do + test -e "$f" + done +} + function create_react_app { node "$temp_cli_path"/node_modules/create-react-app/index.js $* } @@ -61,7 +68,7 @@ npm install if [ "$USE_YARN" = "yes" ] then # Install Yarn so that the test can use it to install packages. - npm install -g yarn@0.17.10 # TODO: remove version when /~https://github.com/yarnpkg/yarn/issues/2142 is fixed. + npm install -g yarn yarn cache clean fi @@ -70,54 +77,80 @@ fi # ****************************************************************************** # Pack CLI -cd $root_path/packages/create-react-app +cd "$root_path"/packages/create-react-app cli_path=$PWD/`npm pack` # Install the CLI in a temporary location -cd $temp_cli_path -npm install $cli_path +cd "$temp_cli_path" +npm install "$cli_path" # ****************************************************************************** # Test --scripts-version with a version number # ****************************************************************************** -cd $temp_app_path +cd "$temp_app_path" create_react_app --scripts-version=0.4.0 test-app-version-number cd test-app-version-number # Check corresponding scripts version is installed. -test -e node_modules/react-scripts +exists node_modules/react-scripts grep '"version": "0.4.0"' node_modules/react-scripts/package.json # ****************************************************************************** # Test --scripts-version with a tarball url # ****************************************************************************** -cd $temp_app_path +cd "$temp_app_path" create_react_app --scripts-version=https://registry.npmjs.org/react-scripts/-/react-scripts-0.4.0.tgz test-app-tarball-url cd test-app-tarball-url # Check corresponding scripts version is installed. -test -e node_modules/react-scripts +exists node_modules/react-scripts grep '"version": "0.4.0"' node_modules/react-scripts/package.json # ****************************************************************************** # Test --scripts-version with a custom fork of react-scripts # ****************************************************************************** -cd $temp_app_path +cd "$temp_app_path" create_react_app --scripts-version=react-scripts-fork test-app-fork cd test-app-fork # Check corresponding scripts version is installed. -test -e node_modules/react-scripts-fork +exists node_modules/react-scripts-fork + +# ****************************************************************************** +# Test project folder is deleted on failing package installation +# ****************************************************************************** + +cd "$temp_app_path" +# we will install a non-existing package to simulate a failed installataion. +create_react_app --scripts-version=`date +%s` test-app-should-not-exist || true +# confirm that the project folder was deleted +test ! -d test-app-should-not-exist + +# ****************************************************************************** +# Test project folder is not deleted when creating app over existing folder +# ****************************************************************************** + +cd "$temp_app_path" +mkdir test-app-should-remain +echo '## Hello' > ./test-app-should-remain/README.md +# we will install a non-existing package to simulate a failed installataion. +create_react_app --scripts-version=`date +%s` test-app-should-remain || true +# confirm the file exist +test -e test-app-should-remain/README.md +# confirm only README.md is the only file in the directory +if [ "$(ls -1 ./test-app-should-remain | wc -l | tr -d '[:space:]')" != "1" ]; then + false +fi # ****************************************************************************** # Test nested folder path as the project name # ****************************************************************************** #Testing a path that exists -cd $temp_app_path +cd "$temp_app_path" mkdir test-app-nested-paths-t1 cd test-app-nested-paths-t1 mkdir -p test-app-nested-paths-t1/aa/bb/cc/dd @@ -126,13 +159,13 @@ cd test-app-nested-paths-t1/aa/bb/cc/dd npm start -- --smoke-test #Testing a path that does not exist -cd $temp_app_path +cd "$temp_app_path" create_react_app test-app-nested-paths-t2/aa/bb/cc/dd cd test-app-nested-paths-t2/aa/bb/cc/dd npm start -- --smoke-test #Testing a path that is half exists -cd $temp_app_path +cd "$temp_app_path" mkdir -p test-app-nested-paths-t3/aa create_react_app test-app-nested-paths-t3/aa/bb/cc/dd cd test-app-nested-paths-t3/aa/bb/cc/dd diff --git a/tasks/e2e-kitchensink.sh b/tasks/e2e-kitchensink.sh index 4bb45d72853..becd88282c1 100755 --- a/tasks/e2e-kitchensink.sh +++ b/tasks/e2e-kitchensink.sh @@ -21,8 +21,10 @@ temp_app_path=`mktemp -d 2>/dev/null || mktemp -d -t 'temp_app_path'` function cleanup { echo 'Cleaning up.' - cd $root_path - rm -rf $temp_cli_path $temp_app_path + ps -ef | grep 'react-scripts' | grep -v grep | awk '{print $2}' | xargs kill -s 9 + cd "$root_path" + # TODO: fix "Device or resource busy" and remove ``|| $CI` + rm -rf "$temp_cli_path" $temp_app_path || $CI } # Error messages are redirected to stderr @@ -40,7 +42,14 @@ function handle_exit { } function create_react_app { - node "$temp_cli_path"/node_modules/create-react-app/index.js $* + node "$temp_cli_path"/node_modules/create-react-app/index.js "$@" +} + +# Check for the existence of one or more files. +function exists { + for f in $*; do + test -e "$f" + done } # Exit the script with a helpful error message when any error is encountered @@ -61,7 +70,7 @@ npm install if [ "$USE_YARN" = "yes" ] then # Install Yarn so that the test can use it to install packages. - npm install -g yarn@0.17.10 # TODO: remove version when /~https://github.com/yarnpkg/yarn/issues/2142 is fixed. + npm install -g yarn yarn cache clean fi @@ -70,21 +79,21 @@ fi # ****************************************************************************** # Pack CLI -cd $root_path/packages/create-react-app +cd "$root_path"/packages/create-react-app cli_path=$PWD/`npm pack` # Go to react-scripts -cd $root_path/packages/react-scripts +cd "$root_path"/packages/react-scripts # Save package.json because we're going to touch it cp package.json package.json.orig # Replace own dependencies (those in the `packages` dir) with the local paths # of those packages. -node $root_path/tasks/replace-own-deps.js +node "$root_path"/tasks/replace-own-deps.js # Finally, pack react-scripts -scripts_path=$root_path/packages/react-scripts/`npm pack` +scripts_path="$root_path"/packages/react-scripts/`npm pack` # Restore package.json rm package.json @@ -95,12 +104,12 @@ mv package.json.orig package.json # ****************************************************************************** # Install the CLI in a temporary location -cd $temp_cli_path -npm install $cli_path +cd "$temp_cli_path" +npm install "$cli_path" # Install the app in a temporary location cd $temp_app_path -create_react_app --scripts-version=$scripts_path --internal-testing-template=$root_path/packages/react-scripts/fixtures/kitchensink test-kitchensink +create_react_app --scripts-version="$scripts_path" --internal-testing-template="$root_path"/packages/react-scripts/fixtures/kitchensink test-kitchensink # ****************************************************************************** # Now that we used create-react-app to create an app depending on react-scripts, @@ -111,7 +120,7 @@ create_react_app --scripts-version=$scripts_path --internal-testing-template=$ro cd test-kitchensink # Link to our preset -npm link $root_path/packages/babel-preset-react-app +npm link "$root_path"/packages/babel-preset-react-app # Test the build REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ @@ -120,8 +129,8 @@ REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ npm run build # Check for expected output -test -e build/*.html -test -e build/static/js/main.*.js +exists build/*.html +exists build/static/js/main.*.js # Unit tests REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ @@ -136,12 +145,19 @@ PORT=3001 \ REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ NODE_PATH=src \ nohup npm start &>$tmp_server_log & -grep -q 'The app is running at:' <(tail -f $tmp_server_log) +while true +do + if grep -q 'The app is running at:' $tmp_server_log; then + break + else + sleep 1 + fi +done E2E_URL="http://localhost:3001" \ REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ CI=true NODE_PATH=src \ NODE_ENV=development \ - node node_modules/.bin/mocha --require babel-register --require babel-polyfill integration/*.test.js + node_modules/.bin/mocha --require babel-register --require babel-polyfill integration/*.test.js # Test "production" environment E2E_FILE=./build/index.html \ @@ -156,16 +172,16 @@ E2E_FILE=./build/index.html \ # ****************************************************************************** # Unlink our preset -npm unlink $root_path/packages/babel-preset-react-app +npm unlink "$root_path"/packages/babel-preset-react-app # Eject... echo yes | npm run eject # ...but still link to the local packages -npm link $root_path/packages/babel-preset-react-app -npm link $root_path/packages/eslint-config-react-app -npm link $root_path/packages/react-dev-utils -npm link $root_path/packages/react-scripts +npm link "$root_path"/packages/babel-preset-react-app +npm link "$root_path"/packages/eslint-config-react-app +npm link "$root_path"/packages/react-dev-utils +npm link "$root_path"/packages/react-scripts # Test the build REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ @@ -174,15 +190,15 @@ REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ npm run build # Check for expected output -test -e build/*.html -test -e build/static/js/main.*.js +exists build/*.html +exists build/static/js/main.*.js # Unit tests REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ CI=true \ NODE_PATH=src \ NODE_ENV=test \ - npm test -- --no-cache --testPathPattern="/src/" + npm test -- --no-cache --testPathPattern='/src/' # Test "development" environment tmp_server_log=`mktemp` @@ -190,7 +206,14 @@ PORT=3002 \ REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ NODE_PATH=src \ nohup npm start &>$tmp_server_log & -grep -q 'The app is running at:' <(tail -f $tmp_server_log) +while true +do + if grep -q 'The app is running at:' $tmp_server_log; then + break + else + sleep 1 + fi +done E2E_URL="http://localhost:3002" \ REACT_APP_SHELL_ENV_MESSAGE=fromtheshell \ CI=true NODE_PATH=src \ diff --git a/tasks/e2e-simple.sh b/tasks/e2e-simple.sh index d0acb16b412..594f4604085 100755 --- a/tasks/e2e-simple.sh +++ b/tasks/e2e-simple.sh @@ -21,10 +21,10 @@ temp_app_path=`mktemp -d 2>/dev/null || mktemp -d -t 'temp_app_path'` function cleanup { echo 'Cleaning up.' - cd $root_path + cd "$root_path" # Uncomment when snapshot testing is enabled by default: # rm ./packages/react-scripts/template/src/__snapshots__/App.test.js.snap - rm -rf $temp_cli_path $temp_app_path + rm -rf "$temp_cli_path" $temp_app_path } # Error messages are redirected to stderr @@ -42,7 +42,14 @@ function handle_exit { } function create_react_app { - node "$temp_cli_path"/node_modules/create-react-app/index.js $* + node "$temp_cli_path"/node_modules/create-react-app/index.js "$@" +} + +# Check for the existence of one or more files. +function exists { + for f in $*; do + test -e "$f" + done } # Exit the script with a helpful error message when any error is encountered @@ -58,20 +65,34 @@ set -x cd .. root_path=$PWD +# Prevent lerna bootstrap, we only want top-level dependencies +cp package.json package.json.bak +grep -v "lerna bootstrap" package.json > temp && mv temp package.json +npm install +mv package.json.bak package.json + +# We need to install create-react-app deps to test it +cd "$root_path"/packages/create-react-app npm install +cd "$root_path" # If the node version is < 4, the script should just give an error. -if [ `node --version | sed -e 's/^v//' -e 's/\..\+//g'` -lt 4 ] +if [[ `node --version | sed -e 's/^v//' -e 's/\..*//g'` -lt 4 ]] then cd $temp_app_path err_output=`node "$root_path"/packages/create-react-app/index.js test-node-version 2>&1 > /dev/null || echo ''` [[ $err_output =~ You\ are\ running\ Node ]] && exit 0 || exit 1 fi +# Still use npm install instead of directly calling lerna bootstrap to test +# postinstall script functionality (one npm install should result in a working +# project) +npm install + if [ "$USE_YARN" = "yes" ] then # Install Yarn so that the test can use it to install packages. - npm install -g yarn@0.17.10 # TODO: remove version when /~https://github.com/yarnpkg/yarn/issues/2142 is fixed. + npm install -g yarn yarn cache clean fi @@ -86,16 +107,16 @@ fi # Test local build command npm run build # Check for expected output -test -e build/*.html -test -e build/static/js/*.js -test -e build/static/css/*.css -test -e build/static/media/*.svg -test -e build/favicon.ico +exists build/*.html +exists build/static/js/*.js +exists build/static/css/*.css +exists build/static/media/*.svg +exists build/favicon.ico # Run tests with CI flag CI=true npm test # Uncomment when snapshot testing is enabled by default: -# test -e template/src/__snapshots__/App.test.js.snap +# exists template/src/__snapshots__/App.test.js.snap # Test local start command npm start -- --smoke-test @@ -105,21 +126,21 @@ npm start -- --smoke-test # ****************************************************************************** # Pack CLI -cd $root_path/packages/create-react-app +cd "$root_path"/packages/create-react-app cli_path=$PWD/`npm pack` # Go to react-scripts -cd $root_path/packages/react-scripts +cd "$root_path"/packages/react-scripts # Save package.json because we're going to touch it cp package.json package.json.orig # Replace own dependencies (those in the `packages` dir) with the local paths # of those packages. -node $root_path/tasks/replace-own-deps.js +node "$root_path"/tasks/replace-own-deps.js # Finally, pack react-scripts -scripts_path=$root_path/packages/react-scripts/`npm pack` +scripts_path="$root_path"/packages/react-scripts/`npm pack` # Restore package.json rm package.json @@ -130,12 +151,18 @@ mv package.json.orig package.json # ****************************************************************************** # Install the CLI in a temporary location -cd $temp_cli_path -npm install $cli_path +cd "$temp_cli_path" + +# Initialize package.json before installing the CLI because npm will not install +# the CLI properly in the temporary location if it is missing. +npm init --yes + +# Now we can install the CLI from the local package. +npm install "$cli_path" # Install the app in a temporary location cd $temp_app_path -create_react_app --scripts-version=$scripts_path test-app +create_react_app --scripts-version="$scripts_path" test-app # ****************************************************************************** # Now that we used create-react-app to create an app depending on react-scripts, @@ -197,16 +224,16 @@ cd test-app # Test the build npm run build # Check for expected output -test -e build/*.html -test -e build/static/js/*.js -test -e build/static/css/*.css -test -e build/static/media/*.svg -test -e build/favicon.ico +exists build/*.html +exists build/static/js/*.js +exists build/static/css/*.css +exists build/static/media/*.svg +exists build/favicon.ico # Run tests with CI flag CI=true npm test # Uncomment when snapshot testing is enabled by default: -# test -e src/__snapshots__/App.test.js.snap +# exists src/__snapshots__/App.test.js.snap # Test the server npm start -- --smoke-test @@ -222,19 +249,19 @@ verify_env_url echo yes | npm run eject # ...but still link to the local packages -npm link $root_path/packages/babel-preset-react-app -npm link $root_path/packages/eslint-config-react-app -npm link $root_path/packages/react-dev-utils -npm link $root_path/packages/react-scripts +npm link "$root_path"/packages/babel-preset-react-app +npm link "$root_path"/packages/eslint-config-react-app +npm link "$root_path"/packages/react-dev-utils +npm link "$root_path"/packages/react-scripts # Test the build npm run build # Check for expected output -test -e build/*.html -test -e build/static/js/*.js -test -e build/static/css/*.css -test -e build/static/media/*.svg -test -e build/favicon.ico +exists build/*.html +exists build/static/js/*.js +exists build/static/css/*.css +exists build/static/media/*.svg +exists build/favicon.ico # Run tests, overring the watch option to disable it. # `CI=true npm test` won't work here because `npm test` becomes just `jest`. @@ -242,7 +269,7 @@ test -e build/favicon.ico # `scripts/test.js` survive ejection (right now it doesn't). npm test -- --watch=no # Uncomment when snapshot testing is enabled by default: -# test -e src/__snapshots__/App.test.js.snap +# exists src/__snapshots__/App.test.js.snap # Test the server npm start -- --smoke-test diff --git a/tasks/release.sh b/tasks/release.sh index 1520a5f0785..49a7302852f 100755 --- a/tasks/release.sh +++ b/tasks/release.sh @@ -39,6 +39,6 @@ if [ -n "$(git status --porcelain)" ]; then exit 1; fi -cd $root_path +cd "$root_path" # Go! ./node_modules/.bin/lerna publish --independent "$@" diff --git a/template/README.md b/template/README.md index 8f918c1979f..32efd00ff82 100644 --- a/template/README.md +++ b/template/README.md @@ -1,4 +1,4 @@ -This page has moved! +This page has moved!<br> Please update your link to point [here](/~https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md) instead. Sorry for the inconvenience!