diff --git a/.eslintignore b/.eslintignore index 4eaf46c6d7d..27c694cea55 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,4 @@ build my-app* packages/react-scripts/template packages/react-scripts/fixtures +fixtures/ diff --git a/.eslintrc b/.eslintrc index 44a504e4080..b642bbc5c14 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,7 +7,7 @@ "es6": true }, "parserOptions": { - "ecmaVersion": 6 + "ecmaVersion": 2018 }, "rules": { "no-console": "off", diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 4ef50efcac3..d2612759e2f 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -85,19 +85,18 @@ ### Environment + To help identify if a problem is specific to a platform, browser, or module version, information about your environment is required. + This enables the maintainers quickly reproduce the issue and give feedback. -1. `node -v`: -2. `npm -v`: -3. `yarn --version` (if you use Yarn): -4. `npm ls react-scripts` (if you haven’t ejected): + Run the following command in your React app's folder in terminal. + Note: The result is copied to your clipboard directly. -Then, specify: + `npx create-react-app --info` -1. Operating system: -2. Browser and version (if relevant): + Paste the output of the command in the section below. +--> +(paste the output of the command here) ### Steps to Reproduce diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000000..8ff820d3bc0 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "trailingComma": "es5", + "singleQuote": true, + "semi": true +} diff --git a/.travis.yml b/.travis.yml index f27e0e9a2b8..d5fdc156413 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,28 +3,33 @@ dist: trusty language: node_js node_js: - 8 - - 9 + - 10 cache: + yarn: true directories: - - node_modules - - packages/create-react-app/node_modules - - packages/react-scripts/node_modules + - .npm +before_install: + - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --nightly + - export PATH="$HOME/.yarn/bin:$PATH" install: true script: - - 'if [ $TEST_SUITE = "simple" ]; then tasks/e2e-simple.sh; fi' - - 'if [ $TEST_SUITE = "installs" ]; then tasks/e2e-installs.sh; fi' - - 'if [ $TEST_SUITE = "kitchensink" ]; then tasks/e2e-kitchensink.sh; fi' - - 'if [ $TEST_SUITE = "old-node" ]; then tasks/e2e-old-node.sh; fi' - - 'if [ $TEST_SUITE = "monorepos" ]; then tasks/e2e-monorepos.sh; fi' + - 'if [ $TEST_SUITE = "simple" ]; then tasks/e2e-simple.sh; fi' + - 'if [ $TEST_SUITE = "installs" ]; then tasks/e2e-installs.sh; fi' + - 'if [ $TEST_SUITE = "kitchensink" ]; then tasks/e2e-kitchensink.sh; fi' + - 'if [ $TEST_SUITE = "kitchensink-eject" ]; then tasks/e2e-kitchensink-eject.sh; fi' + - 'if [ $TEST_SUITE = "old-node" ]; then tasks/e2e-old-node.sh; fi' + - 'if [ $TEST_SUITE = "behavior" ]; then tasks/e2e-behavior.sh; fi' env: matrix: - TEST_SUITE=simple - TEST_SUITE=installs - TEST_SUITE=kitchensink - - TEST_SUITE=monorepos + - TEST_SUITE=kitchensink-eject + - TEST_SUITE=behavior matrix: include: - - node_js: 0.10 + - os: osx + node_js: 8 + env: TEST_SUITE=behavior + - node_js: 4 env: TEST_SUITE=old-node - - node_js: 6 - env: TEST_SUITE=kitchensink diff --git a/.yarnrc b/.yarnrc deleted file mode 100644 index 07e44a9a5b2..00000000000 --- a/.yarnrc +++ /dev/null @@ -1,3 +0,0 @@ ---install.no-lockfile true ---install.check-files true ---add.no-lockfile true diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c63d42f570..5b2c643d3e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,35 @@ +## 2.0.2 (October 1, 2018) + +TODO (work in progress) + +## 1.1.5 (August 24, 2018) + +* `react-scripts` + + * Update the `webpack-dev-server` dependency + +* `react-dev-utils` + + * [#4866](https://github.com/facebook/create-react-app/pull/4866) Fix a Windows-only vulnerability (`CVE-2018-6342`) in the development server ([@acdlite](https://github.com/acdlite)) + * Update the `sockjs-client` dependency + +#### Committers: 1 +- Andrew Clark ([acdlite](https://github.com/acdlite)) + +### Migrating from 1.1.4 to 1.1.5 + +Inside any created project that has not been ejected, run: + +``` +npm install --save --save-exact react-scripts@1.1.5 +``` + +or + +``` +yarn add --exact react-scripts@1.1.5 +``` + ## 1.1.4 (April 3, 2018) #### :bug: Bug Fix diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 55203be746a..0fb245803d8 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,3 @@ # Code of Conduct -Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.facebook.com/pages/876921332402685/open-source-code-of-conduct) so that you can understand what actions will and will not be tolerated. +Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.fb.com/codeofconduct/) so that you can understand what actions will and will not be tolerated. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e69f729fd27..a51391c44bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,7 +57,7 @@ packages/ ### Package Descriptions #### [babel-preset-react-app](https://github.com/facebook/create-react-app/tree/master/packages/babel-preset-react-app) This package is a babel preset intended to be used with `react-scripts`.
-It targets platforms that React is designed to support (IE 9+) and enables experimental features used heavily at Facebook.
+It targets platforms that React is designed to support (IE 11+) and enables experimental features used heavily at Facebook.
This package is enabled by default for all `create-react-app` scaffolded applications. #### [create-react-app](https://github.com/facebook/create-react-app/tree/master/packages/create-react-app) The global CLI command code can be found in this directory, and shouldn't often be changed. It should run on Node 0.10+. diff --git a/README.md b/README.md index 655aeff4955..c4390b59748 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,9 @@ Just create a project, and you’re good to go. **You’ll need to have Node >= 6 on your local development machine** (but it’s not required on the server). You can use [nvm](https://github.com/creationix/nvm#installation) (macOS/Linux) or [nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows) to easily switch Node versions between different projects. -To create a new app, run a single command: +To create a new app, you may choose one of the following methods: + +### npx ```sh npx create-react-app my-app @@ -44,6 +46,20 @@ npx create-react-app my-app *([npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b) comes with npm 5.2+ and higher, see [instructions for older npm versions](https://gist.github.com/gaearon/4064d3c23a77c74a3614c498a8bb1c5f))* +### npm + +```sh +npm init react-app my-app +``` +*`npm init ` is available in npm 6+* + +### Yarn + +```sh +yarn create react-app my-app +``` +*`yarn create` is available in Yarn 0.25+* + It will create a directory called `my-app` inside the current folder.
Inside that directory, it will generate the initial project structure and install the transitive dependencies: @@ -54,16 +70,16 @@ my-app ├── package.json ├── .gitignore ├── public -│ └── favicon.ico -│ └── index.html +│ ├── favicon.ico +│ ├── index.html │ └── manifest.json └── src - └── App.css - └── App.js - └── App.test.js - └── index.css - └── index.js - └── logo.svg + ├── App.css + ├── App.js + ├── App.test.js + ├── index.css + ├── index.js + ├── logo.svg └── registerServiceWorker.js ``` @@ -101,7 +117,6 @@ 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.
-By default, it also [includes a service worker](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app) so that your app loads from local cache on future visits. Your app is ready to be deployed. @@ -147,7 +162,7 @@ The [User Guide](https://github.com/facebook/create-react-app/blob/master/packag - [Analyzing the Bundle Size](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#analyzing-the-bundle-size) - [Deployment](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#deployment) - [Advanced Configuration](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#advanced-configuration) -- [Troubleshooting](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#troubleshooting) +- [Troubleshooting](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#troubleshooting-1) A copy of the user guide will be created as `README.md` in your project folder. @@ -173,7 +188,7 @@ Your environment will have everything you need to build a modern single-page Rea * A fast interactive unit test runner with built-in support for coverage reporting. * A live development server that warns about common mistakes. * A build script to bundle JS, CSS, and images for production, with hashes and 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/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app) criteria. +* 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/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app) criteria. (*Note: Using the service worker is opt-in as of `react-scripts@2.0.0` and higher*) * Hassle-free updates for the above tools with a single dependency. Check out [this guide](https://github.com/nitishdayal/cra_closer_look) for an overview of how these tools fit together. @@ -202,6 +217,8 @@ Here’s a few common cases where you might want to try something else: * If you want to use **TypeScript**, consider using [create-react-app-typescript](https://github.com/wmonk/create-react-app-typescript). +* If you want to use **Parcel** instead of **Webpack** as your bundler, consider using [create-react-app-parcel](https://github.com/sw-yx/create-react-app-parcel). + * Finally, if you need **more customization**, check out [Neutrino](https://neutrino.js.org/) and its [React preset](https://neutrino.js.org/packages/react/). All of the above tools can work with little to no configuration. diff --git a/appveyor.cleanup-cache.txt b/appveyor.cleanup-cache.txt index 200d97c3aac..d48a91fdf35 100644 --- a/appveyor.cleanup-cache.txt +++ b/appveyor.cleanup-cache.txt @@ -2,5 +2,3 @@ Edit this file to trigger a cache rebuild. http://help.appveyor.com/discussions/questions/1310-delete-cache ---- - -bump diff --git a/appveyor.yml b/appveyor.yml index db1d640caa1..5f5143167d6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,37 +1,44 @@ image: Visual Studio 2017 environment: + APPVEYOR_SAVE_CACHE_ON_ERROR: true + APPVEYOR_BUILD_WORKER_CLOUD: 'GCE' matrix: + - nodejs_version: 10 + test_suite: 'simple' + - nodejs_version: 10 + test_suite: 'installs' + - nodejs_version: 10 + test_suite: 'kitchensink' + - nodejs_version: 10 + test_suite: 'kitchensink-eject' - nodejs_version: 8 - test_suite: "simple" + test_suite: 'simple' - nodejs_version: 8 - test_suite: "installs" + test_suite: 'installs' - nodejs_version: 8 - test_suite: "kitchensink" + test_suite: 'kitchensink' - nodejs_version: 8 - test_suite: "monorepos" - - nodejs_version: 6 - test_suite: "simple" - - nodejs_version: 6 - test_suite: "installs" - - nodejs_version: 6 - test_suite: "kitchensink" - - nodejs_version: 6 - test_suite: "monorepos" + test_suite: 'kitchensink-eject' cache: - - node_modules -> appveyor.cleanup-cache.txt - - packages\react-scripts\node_modules -> appveyor.cleanup-cache.txt + - '%APPDATA%\npm-cache -> appveyor.cleanup-cache.txt' + - '%LOCALAPPDATA%\Yarn\Cache -> appveyor.cleanup-cache.txt' clone_depth: 50 matrix: fast_finish: true + allow_failures: + - test_suite: 'installs' platform: - x64 install: - ps: Install-Product node $env:nodejs_version $env:platform + - ps: | + (New-Object Net.WebClient).DownloadFile("https://nightly.yarnpkg.com/latest.msi", "$env:temp\yarn.msi") + cmd /c start /wait msiexec.exe /i $env:temp\yarn.msi /quiet /qn /norestart build: off @@ -42,4 +49,6 @@ skip_commits: test_script: - node --version - npm --version + - yarn --version + - yarn cache dir - bash tasks/e2e-%test_suite%.sh diff --git a/fixtures/output/jest.config.js b/fixtures/output/jest.config.js new file mode 100644 index 00000000000..fa718fa3ea3 --- /dev/null +++ b/fixtures/output/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + testEnvironment: 'node', + testMatch: ['**/*.test.js'], + setupTestFrameworkScriptFile: './setupOutputTests.js', +}; diff --git a/fixtures/output/setupOutputTests.js b/fixtures/output/setupOutputTests.js new file mode 100644 index 00000000000..b709406980f --- /dev/null +++ b/fixtures/output/setupOutputTests.js @@ -0,0 +1,6 @@ +beforeAll(() => { + jest.setTimeout(1000 * 60 * 5); +}); +beforeEach(() => { + jest.setTimeout(1000 * 60 * 5); +}); diff --git a/fixtures/output/webpack-message-formatting/__snapshots__/index.test.js.snap b/fixtures/output/webpack-message-formatting/__snapshots__/index.test.js.snap new file mode 100644 index 00000000000..6a510ef70b9 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/__snapshots__/index.test.js.snap @@ -0,0 +1,177 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`webpack message formatting formats aliased unknown export 1`] = ` +Object { + "stderr": "Creating an optimized production build... +Failed to compile. + +./src/App.js +Attempted import error: 'bar' is not exported from './AppUnknownExport' (imported as 'bar2'). + + +", + "stdout": "", +} +`; + +exports[`webpack message formatting formats babel syntax error 1`] = ` +Object { + "stderr": "Creating an optimized production build... +Failed to compile. + +./src/App.js +Syntax error: Unterminated JSX contents (8:13) + + 6 |
+ 7 | +> 8 |
+ | ^ + 9 | ); + 10 | } + 11 | } + + +", + "stdout": "", +} +`; + +exports[`webpack message formatting formats css syntax error 1`] = ` +Object { + "stderr": "Creating an optimized production build... +Failed to compile. + +./src/AppCss.css +Syntax error: Unexpected } (3:2) + + 1 | .App { + 2 | color: red; +> 3 | }} + | ^ + 4 | + + +", + "stdout": "", +} +`; + +exports[`webpack message formatting formats eslint error 1`] = ` +Object { + "stderr": "Creating an optimized production build... +Failed to compile. + +./src/App.js + Line 4: 'b' is not defined no-undef + +Search for the keywords to learn more about each error. + + +", + "stdout": "", +} +`; + +exports[`webpack message formatting formats eslint warning 1`] = ` +Object { + "stderr": "", + "stdout": "Creating an optimized production build... +Compiled with warnings. + +./src/App.js + Line 3: 'foo' is defined but never used no-unused-vars + +Search for the keywords to learn more about each warning. +To ignore, add // eslint-disable-next-line to the line before. + +", +} +`; + +exports[`webpack message formatting formats file not found error 1`] = ` +Object { + "stderr": "Creating an optimized production build... +Failed to compile. + +./src/App.js +Cannot find file './ThisFileSouldNotExist' in './src'. + + +", + "stdout": "", +} +`; + +exports[`webpack message formatting formats missing package 1`] = ` +Object { + "stderr": "Creating an optimized production build... +Failed to compile. + +./src/App.js +Cannot find module: 'unknown-package'. Make sure this package is installed. + +You can install this package by running: yarn add unknown-package. + + +", + "stdout": "", +} +`; + +exports[`webpack message formatting formats no default export 1`] = ` +Object { + "stderr": "Creating an optimized production build... +Failed to compile. + +./src/App.js +Attempted import error: './ExportNoDefault' does not contain a default export (imported as 'myImport'). + + +", + "stdout": "", +} +`; + +exports[`webpack message formatting formats out of scope error 1`] = ` +Object { + "stderr": "Creating an optimized production build... +Failed to compile. + +./src/App.js +You attempted to import ../OutOfScopeImport which falls outside of the project src/ directory. Relative imports outside of src/ are not supported. +You can either move it inside src/, or add a symlink to it from project's node_modules/. + + +", + "stdout": "", +} +`; + +exports[`webpack message formatting formats unknown export 1`] = ` +Object { + "stderr": "Creating an optimized production build... +Failed to compile. + +./src/App.js +Attempted import error: 'bar' is not exported from './AppUnknownExport'. + + +", + "stdout": "", +} +`; + +exports[`webpack message formatting helps when users tries to use sass 1`] = ` +Object { + "stderr": "Creating an optimized production build... +Failed to compile. + +./src/AppSass.scss +To import Sass files, you first need to install node-sass. +Run \`npm install node-sass\` or \`yarn add node-sass\` inside your workspace. + + +", + "stdout": "", +} +`; diff --git a/fixtures/output/webpack-message-formatting/index.test.js b/fixtures/output/webpack-message-formatting/index.test.js new file mode 100644 index 00000000000..475fb108536 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/index.test.js @@ -0,0 +1,157 @@ +const { + bootstrap, + getOutputDevelopment, + getOutputProduction, +} = require('../../utils'); +const fs = require('fs-extra'); +const path = require('path'); +const Semaphore = require('async-sema'); +const tempy = require('tempy'); + +describe('webpack message formatting', () => { + const semaphore = new Semaphore(1, { capacity: Infinity }); + let testDirectory; + beforeAll(async () => { + testDirectory = tempy.directory(); + await bootstrap({ directory: testDirectory, template: __dirname }); + }); + beforeEach(async () => { + await semaphore.acquire(); + }); + afterEach(async () => { + fs.removeSync(path.join(testDirectory, 'src', 'App.js')); + semaphore.release(); + }); + + it('formats babel syntax error', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppBabel.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputProduction({ directory: testDirectory }); + expect(response).toMatchSnapshot(); + }); + + it('formats css syntax error', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppCss.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputProduction({ directory: testDirectory }); + expect(response).toMatchSnapshot(); + }); + + it('formats unknown export', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppUnknownExport.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputProduction({ directory: testDirectory }); + expect(response).toMatchSnapshot(); + }); + + it('formats aliased unknown export', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppAliasUnknownExport.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputProduction({ directory: testDirectory }); + expect(response).toMatchSnapshot(); + }); + + it('formats no default export', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppNoDefault.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputProduction({ directory: testDirectory }); + expect(response).toMatchSnapshot(); + }); + + it('formats missing package', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppMissingPackage.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputProduction({ directory: testDirectory }); + expect(response).toMatchSnapshot(); + }); + + it('formats eslint warning', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppLintWarning.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputProduction({ directory: testDirectory }); + const sizeIndex = response.stdout.indexOf('File sizes after gzip'); + if (sizeIndex !== -1) { + response.stdout = response.stdout.substring(0, sizeIndex); + } + expect(response).toMatchSnapshot(); + }); + + it('formats eslint error', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppLintError.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputProduction({ directory: testDirectory }); + expect(response).toMatchSnapshot(); + }); + + it('helps when users tries to use sass', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppSass.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputProduction({ directory: testDirectory }); + expect(response).toMatchSnapshot(); + }); + + it('formats file not found error', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppUnknownFile.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputProduction({ directory: testDirectory }); + expect(response).toMatchSnapshot(); + }); + + it('formats case sensitive path error', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppIncorrectCase.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputDevelopment({ directory: testDirectory }); + if (process.platform === 'darwin') { + expect(response.stderr).toMatch( + `Cannot find file: 'export5.js' does not match the corresponding name on disk: './src/Export5.js'.` + ); + } else { + expect(response.stderr).not.toEqual(''); // TODO: figure out how we can test this on Linux/Windows + // I believe getting this working requires we tap into enhanced-resolve + // pipeline, which is debt we don't want to take on right now. + } + }); + + it('formats out of scope error', async () => { + fs.copySync( + path.join(__dirname, 'src', 'AppOutOfScopeImport.js'), + path.join(testDirectory, 'src', 'App.js') + ); + + const response = await getOutputProduction({ directory: testDirectory }); + expect(response).toMatchSnapshot(); + }); +}); diff --git a/fixtures/output/webpack-message-formatting/package.json b/fixtures/output/webpack-message-formatting/package.json new file mode 100644 index 00000000000..4fda3598df5 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "react": "latest", + "react-dom": "latest" + }, + "browserslist": [ + ">0.2%" + ] +} diff --git a/fixtures/output/webpack-message-formatting/public/index.html b/fixtures/output/webpack-message-formatting/public/index.html new file mode 100644 index 00000000000..86010b24067 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/public/index.html @@ -0,0 +1,9 @@ + + + + React App + + +
+ + diff --git a/fixtures/output/webpack-message-formatting/src/AppAliasUnknownExport.js b/fixtures/output/webpack-message-formatting/src/AppAliasUnknownExport.js new file mode 100644 index 00000000000..df2716efc78 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppAliasUnknownExport.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react'; +import { bar as bar2 } from './AppUnknownExport'; + +class App extends Component { + componentDidMount() { + bar2(); + } + render() { + return
; + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/AppBabel.js b/fixtures/output/webpack-message-formatting/src/AppBabel.js new file mode 100644 index 00000000000..b6799d38e5f --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppBabel.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react'; + +class App extends Component { + render() { + return ( +
+ +
+ ); + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/AppCss.css b/fixtures/output/webpack-message-formatting/src/AppCss.css new file mode 100644 index 00000000000..530380750ec --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppCss.css @@ -0,0 +1,3 @@ +.App { + color: red; +}} diff --git a/fixtures/output/webpack-message-formatting/src/AppCss.js b/fixtures/output/webpack-message-formatting/src/AppCss.js new file mode 100644 index 00000000000..af04f1c60b5 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppCss.js @@ -0,0 +1,10 @@ +import React, { Component } from 'react'; +import './AppCss.css'; + +class App extends Component { + render() { + return
; + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/AppIncorrectCase.js b/fixtures/output/webpack-message-formatting/src/AppIncorrectCase.js new file mode 100644 index 00000000000..40f69d43938 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppIncorrectCase.js @@ -0,0 +1,10 @@ +import React, { Component } from 'react'; +import five from './export5'; + +class App extends Component { + render() { + return
{five}
; + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/AppLintError.js b/fixtures/output/webpack-message-formatting/src/AppLintError.js new file mode 100644 index 00000000000..6015c397d47 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppLintError.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react'; + +function foo() { + const a = b; +} + +class App extends Component { + render() { + return
; + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/AppLintWarning.js b/fixtures/output/webpack-message-formatting/src/AppLintWarning.js new file mode 100644 index 00000000000..0918e42b739 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppLintWarning.js @@ -0,0 +1,11 @@ +import React, { Component } from 'react'; + +function foo() {} + +class App extends Component { + render() { + return
; + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/AppMissingPackage.js b/fixtures/output/webpack-message-formatting/src/AppMissingPackage.js new file mode 100644 index 00000000000..5e62c967430 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppMissingPackage.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react'; +import { bar } from 'unknown-package'; + +class App extends Component { + componentDidMount() { + bar(); + } + render() { + return
; + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/AppNoDefault.js b/fixtures/output/webpack-message-formatting/src/AppNoDefault.js new file mode 100644 index 00000000000..9087eed49d0 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppNoDefault.js @@ -0,0 +1,10 @@ +import React, { Component } from 'react'; +import myImport from './ExportNoDefault'; + +class App extends Component { + render() { + return
{myImport}
; + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/AppOutOfScopeImport.js b/fixtures/output/webpack-message-formatting/src/AppOutOfScopeImport.js new file mode 100644 index 00000000000..a8717e23082 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppOutOfScopeImport.js @@ -0,0 +1,10 @@ +import React, { Component } from 'react'; +import myImport from '../OutOfScopeImport'; + +class App extends Component { + render() { + return
{myImport}
; + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/AppSass.js b/fixtures/output/webpack-message-formatting/src/AppSass.js new file mode 100644 index 00000000000..20ac0e211a5 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppSass.js @@ -0,0 +1,10 @@ +import React, { Component } from 'react'; +import './AppSass.scss'; + +class App extends Component { + render() { + return
; + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/AppSass.scss b/fixtures/output/webpack-message-formatting/src/AppSass.scss new file mode 100644 index 00000000000..724638c3a7b --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppSass.scss @@ -0,0 +1,3 @@ +.App { + color: red; +} diff --git a/fixtures/output/webpack-message-formatting/src/AppUnknownExport.js b/fixtures/output/webpack-message-formatting/src/AppUnknownExport.js new file mode 100644 index 00000000000..482a545240b --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppUnknownExport.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react'; +import { bar } from './AppUnknownExport'; + +class App extends Component { + componentDidMount() { + bar(); + } + render() { + return
; + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/AppUnknownFile.js b/fixtures/output/webpack-message-formatting/src/AppUnknownFile.js new file mode 100644 index 00000000000..d3b2ce26730 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/AppUnknownFile.js @@ -0,0 +1,10 @@ +import React, { Component } from 'react'; +import DefaultExport from './ThisFileSouldNotExist'; + +class App extends Component { + render() { + return
; + } +} + +export default App; diff --git a/fixtures/output/webpack-message-formatting/src/Export5.js b/fixtures/output/webpack-message-formatting/src/Export5.js new file mode 100644 index 00000000000..ba35c21b821 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/Export5.js @@ -0,0 +1 @@ +export default 5; diff --git a/fixtures/output/webpack-message-formatting/src/ExportNoDefault.js b/fixtures/output/webpack-message-formatting/src/ExportNoDefault.js new file mode 100644 index 00000000000..1608f67ee7a --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/ExportNoDefault.js @@ -0,0 +1 @@ +export const six = 6; diff --git a/fixtures/output/webpack-message-formatting/src/FooExport.js b/fixtures/output/webpack-message-formatting/src/FooExport.js new file mode 100644 index 00000000000..acf7c881810 --- /dev/null +++ b/fixtures/output/webpack-message-formatting/src/FooExport.js @@ -0,0 +1,3 @@ +export function foo() { + console.log('bar'); +} diff --git a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/index.js b/fixtures/output/webpack-message-formatting/src/index.js similarity index 86% rename from packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/index.js rename to fixtures/output/webpack-message-formatting/src/index.js index 395b74997b2..b597a44232c 100644 --- a/packages/react-scripts/fixtures/monorepos/packages/cra-app1/src/index.js +++ b/fixtures/output/webpack-message-formatting/src/index.js @@ -1,6 +1,5 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; import App from './App'; ReactDOM.render(, document.getElementById('root')); diff --git a/fixtures/smoke/boostrap-sass/index.test.js b/fixtures/smoke/boostrap-sass/index.test.js new file mode 100644 index 00000000000..44f3a6c9d13 --- /dev/null +++ b/fixtures/smoke/boostrap-sass/index.test.js @@ -0,0 +1,17 @@ +const { + bootstrap, + isSuccessfulDevelopment, + isSuccessfulProduction, +} = require('../../utils'); +beforeEach(async () => { + await bootstrap({ directory: global.testDirectory, template: __dirname }); +}); + +describe('bootstrap sass', () => { + it('builds in development', async () => { + await isSuccessfulDevelopment({ directory: global.testDirectory }); + }); + it('builds in production', async () => { + await isSuccessfulProduction({ directory: global.testDirectory }); + }); +}); diff --git a/fixtures/smoke/boostrap-sass/package.json b/fixtures/smoke/boostrap-sass/package.json new file mode 100644 index 00000000000..adb19270b6b --- /dev/null +++ b/fixtures/smoke/boostrap-sass/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "bootstrap": "4.x", + "node-sass": "4.x", + "react": "latest", + "react-dom": "latest" + } +} diff --git a/fixtures/smoke/boostrap-sass/public/index.html b/fixtures/smoke/boostrap-sass/public/index.html new file mode 100644 index 00000000000..86010b24067 --- /dev/null +++ b/fixtures/smoke/boostrap-sass/public/index.html @@ -0,0 +1,9 @@ + + + + React App + + +
+ + diff --git a/fixtures/smoke/boostrap-sass/src/index.js b/fixtures/smoke/boostrap-sass/src/index.js new file mode 100644 index 00000000000..44f52927b5a --- /dev/null +++ b/fixtures/smoke/boostrap-sass/src/index.js @@ -0,0 +1,5 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.sass'; + +ReactDOM.render(
, document.getElementById('root')); diff --git a/fixtures/smoke/boostrap-sass/src/index.sass b/fixtures/smoke/boostrap-sass/src/index.sass new file mode 100644 index 00000000000..48fa0ce0d6e --- /dev/null +++ b/fixtures/smoke/boostrap-sass/src/index.sass @@ -0,0 +1 @@ +@import "~bootstrap/scss/bootstrap.scss"; diff --git a/fixtures/smoke/builds-with-multiple-runtimes/index.test.js b/fixtures/smoke/builds-with-multiple-runtimes/index.test.js new file mode 100644 index 00000000000..990ea7b357c --- /dev/null +++ b/fixtures/smoke/builds-with-multiple-runtimes/index.test.js @@ -0,0 +1,17 @@ +const { + bootstrap, + isSuccessfulDevelopment, + isSuccessfulProduction, +} = require('../../utils'); +beforeEach(async () => { + await bootstrap({ directory: global.testDirectory, template: __dirname }); +}); + +describe('builds-with-multiple-runtimes', () => { + it('builds in development', async () => { + await isSuccessfulDevelopment({ directory: global.testDirectory }); + }); + it('builds in production', async () => { + await isSuccessfulProduction({ directory: global.testDirectory }); + }); +}); diff --git a/fixtures/smoke/builds-with-multiple-runtimes/package.json b/fixtures/smoke/builds-with-multiple-runtimes/package.json new file mode 100644 index 00000000000..b2792516c6e --- /dev/null +++ b/fixtures/smoke/builds-with-multiple-runtimes/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "dva": "2.4.0", + "ky": "0.3.0", + "react": "latest", + "react-dom": "latest" + } +} diff --git a/fixtures/smoke/builds-with-multiple-runtimes/public/index.html b/fixtures/smoke/builds-with-multiple-runtimes/public/index.html new file mode 100644 index 00000000000..86010b24067 --- /dev/null +++ b/fixtures/smoke/builds-with-multiple-runtimes/public/index.html @@ -0,0 +1,9 @@ + + + + React App + + +
+ + diff --git a/fixtures/smoke/builds-with-multiple-runtimes/src/index.js b/fixtures/smoke/builds-with-multiple-runtimes/src/index.js new file mode 100644 index 00000000000..b0603469a80 --- /dev/null +++ b/fixtures/smoke/builds-with-multiple-runtimes/src/index.js @@ -0,0 +1,14 @@ +import React from 'react'; +import dva from 'dva'; +import createHistory from 'history/createHashHistory'; +import ky from 'ky'; + +const app = dva({ history: createHistory() }); +app.router(() => { + ky.get('https://canihazip.com/s') + .then(r => r.text()) + .then(console.log, console.error) + .then(() => console.log('ok')); + return
Test
; +}); +app.start('#root'); diff --git a/fixtures/smoke/graphql-with-mjs/index.test.js b/fixtures/smoke/graphql-with-mjs/index.test.js new file mode 100644 index 00000000000..1f1d2bd078a --- /dev/null +++ b/fixtures/smoke/graphql-with-mjs/index.test.js @@ -0,0 +1,17 @@ +const { + bootstrap, + isSuccessfulDevelopment, + isSuccessfulProduction, +} = require('../../utils'); +beforeEach(async () => { + await bootstrap({ directory: global.testDirectory, template: __dirname }); +}); + +describe('graphql with mjs entrypoint', () => { + it('builds in development', async () => { + await isSuccessfulDevelopment({ directory: global.testDirectory }); + }); + it('builds in production', async () => { + await isSuccessfulProduction({ directory: global.testDirectory }); + }); +}); diff --git a/fixtures/smoke/graphql-with-mjs/package.json b/fixtures/smoke/graphql-with-mjs/package.json new file mode 100644 index 00000000000..c97e6b8d013 --- /dev/null +++ b/fixtures/smoke/graphql-with-mjs/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "apollo-boost": "0.1.16", + "graphql": "14.0.2", + "react-apollo": "2.2.1", + "react": "latest", + "react-dom": "latest" + } +} diff --git a/fixtures/smoke/graphql-with-mjs/public/index.html b/fixtures/smoke/graphql-with-mjs/public/index.html new file mode 100644 index 00000000000..86010b24067 --- /dev/null +++ b/fixtures/smoke/graphql-with-mjs/public/index.html @@ -0,0 +1,9 @@ + + + + React App + + +
+ + diff --git a/fixtures/smoke/graphql-with-mjs/src/App.js b/fixtures/smoke/graphql-with-mjs/src/App.js new file mode 100644 index 00000000000..1ba9891424a --- /dev/null +++ b/fixtures/smoke/graphql-with-mjs/src/App.js @@ -0,0 +1,20 @@ +import React, { Component } from 'react'; + +import ApolloClient from 'apollo-boost'; +import { ApolloProvider } from 'react-apollo'; + +const client = new ApolloClient({ + uri: '/whatever', +}); + +class App extends Component { + render() { + return ( + +
+ + ); + } +} + +export default App; diff --git a/fixtures/smoke/graphql-with-mjs/src/index.js b/fixtures/smoke/graphql-with-mjs/src/index.js new file mode 100644 index 00000000000..b597a44232c --- /dev/null +++ b/fixtures/smoke/graphql-with-mjs/src/index.js @@ -0,0 +1,5 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +ReactDOM.render(, document.getElementById('root')); diff --git a/fixtures/smoke/issue-5176-flow-class-properties/index.test.js b/fixtures/smoke/issue-5176-flow-class-properties/index.test.js new file mode 100644 index 00000000000..72a7a3daf0e --- /dev/null +++ b/fixtures/smoke/issue-5176-flow-class-properties/index.test.js @@ -0,0 +1,13 @@ +const { bootstrap, isSuccessfulTest } = require('../../utils'); +beforeEach(async () => { + await bootstrap({ directory: global.testDirectory, template: __dirname }); +}); + +describe('issue #5176 (flow class properties interaction)', () => { + it('passes tests', async () => { + await isSuccessfulTest({ + directory: global.testDirectory, + jestEnvironment: 'node', + }); + }); +}); diff --git a/fixtures/smoke/issue-5176-flow-class-properties/package.json b/fixtures/smoke/issue-5176-flow-class-properties/package.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/fixtures/smoke/issue-5176-flow-class-properties/package.json @@ -0,0 +1 @@ +{} diff --git a/fixtures/smoke/issue-5176-flow-class-properties/public/index.html b/fixtures/smoke/issue-5176-flow-class-properties/public/index.html new file mode 100644 index 00000000000..86010b24067 --- /dev/null +++ b/fixtures/smoke/issue-5176-flow-class-properties/public/index.html @@ -0,0 +1,9 @@ + + + + React App + + +
+ + diff --git a/fixtures/smoke/issue-5176-flow-class-properties/src/App.js b/fixtures/smoke/issue-5176-flow-class-properties/src/App.js new file mode 100644 index 00000000000..c6a68613b58 --- /dev/null +++ b/fixtures/smoke/issue-5176-flow-class-properties/src/App.js @@ -0,0 +1,11 @@ +class App { + constructor() { + this.foo = this.foo.bind(this); + } + foo: void => void; + foo() { + return 'bar'; + } +} + +export default App; diff --git a/fixtures/smoke/issue-5176-flow-class-properties/src/App.test.js b/fixtures/smoke/issue-5176-flow-class-properties/src/App.test.js new file mode 100644 index 00000000000..4991b756f29 --- /dev/null +++ b/fixtures/smoke/issue-5176-flow-class-properties/src/App.test.js @@ -0,0 +1,6 @@ +import App from './App'; + +it('creates instance without', () => { + const app = new App(); + expect(app.foo()).toBe('bar'); +}); diff --git a/fixtures/smoke/jest.config.js b/fixtures/smoke/jest.config.js new file mode 100644 index 00000000000..b2f8182ebd9 --- /dev/null +++ b/fixtures/smoke/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + testEnvironment: 'node', + testMatch: ['**/*.test.js'], + testPathIgnorePatterns: ['/src/', 'node_modules'], + setupTestFrameworkScriptFile: './setupSmokeTests.js', +}; diff --git a/fixtures/smoke/relative-paths/index.test.js b/fixtures/smoke/relative-paths/index.test.js new file mode 100644 index 00000000000..1f367f64350 --- /dev/null +++ b/fixtures/smoke/relative-paths/index.test.js @@ -0,0 +1,37 @@ +const fs = require('fs-extra'); +const globby = require('globby'); +const path = require('path'); +const { + bootstrap, + isSuccessfulDevelopment, + isSuccessfulProduction, +} = require('../../utils'); +beforeEach(async () => { + await bootstrap({ directory: global.testDirectory, template: __dirname }); +}); + +describe('relative paths', () => { + // TODO: enable when development relative paths are supported + xit('builds in development', async () => { + await isSuccessfulDevelopment({ directory: global.testDirectory }); + }); + it('builds in production', async () => { + await isSuccessfulProduction({ directory: global.testDirectory }); + + const buildDir = path.join(global.testDirectory, 'build'); + const cssFile = path.join( + buildDir, + globby.sync('**/*.css', { cwd: buildDir }).pop() + ); + const svgFile = path.join( + buildDir, + globby.sync('**/*.svg', { cwd: buildDir }).pop() + ); + const desiredPath = /url\((.+?)\)/ + .exec(fs.readFileSync(cssFile, 'utf8')) + .pop(); + expect(path.resolve(path.join(path.dirname(cssFile), desiredPath))).toBe( + path.resolve(svgFile) + ); + }); +}); diff --git a/fixtures/smoke/relative-paths/package.json b/fixtures/smoke/relative-paths/package.json new file mode 100644 index 00000000000..64d2bb6dd27 --- /dev/null +++ b/fixtures/smoke/relative-paths/package.json @@ -0,0 +1,4 @@ +{ + "dependencies": {}, + "homepage": "." +} diff --git a/fixtures/smoke/relative-paths/public/index.html b/fixtures/smoke/relative-paths/public/index.html new file mode 100644 index 00000000000..86010b24067 --- /dev/null +++ b/fixtures/smoke/relative-paths/public/index.html @@ -0,0 +1,9 @@ + + + + React App + + +
+ + diff --git a/fixtures/smoke/relative-paths/src/index.css b/fixtures/smoke/relative-paths/src/index.css new file mode 100644 index 00000000000..244889b10aa --- /dev/null +++ b/fixtures/smoke/relative-paths/src/index.css @@ -0,0 +1,8 @@ +.RootSvg:before { + display: block; + content: ' '; + background-image: url(./logo.svg); + background-size: 28px 28px; + height: 28px; + width: 28px; +} diff --git a/fixtures/smoke/relative-paths/src/index.js b/fixtures/smoke/relative-paths/src/index.js new file mode 100644 index 00000000000..6a9a4b13285 --- /dev/null +++ b/fixtures/smoke/relative-paths/src/index.js @@ -0,0 +1 @@ +import './index.css'; diff --git a/fixtures/smoke/relative-paths/src/logo.svg b/fixtures/smoke/relative-paths/src/logo.svg new file mode 100644 index 00000000000..5e53156653f --- /dev/null +++ b/fixtures/smoke/relative-paths/src/logo.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/fixtures/smoke/setupSmokeTests.js b/fixtures/smoke/setupSmokeTests.js new file mode 100644 index 00000000000..1d4038de417 --- /dev/null +++ b/fixtures/smoke/setupSmokeTests.js @@ -0,0 +1,10 @@ +const fs = require('fs-extra'); +const tempy = require('tempy'); + +beforeEach(() => { + global.testDirectory = tempy.directory(); + jest.setTimeout(1000 * 60 * 5); +}); +afterEach(() => { + fs.removeSync(global.testDirectory); +}); diff --git a/fixtures/utils.js b/fixtures/utils.js new file mode 100644 index 00000000000..bde092f6e3e --- /dev/null +++ b/fixtures/utils.js @@ -0,0 +1,145 @@ +const execa = require('execa'); +const fs = require('fs-extra'); +const getPort = require('get-port'); +const path = require('path'); +const os = require('os'); +const stripAnsi = require('strip-ansi'); + +async function bootstrap({ directory, template }) { + const shouldInstallScripts = process.env.CI && process.env.CI !== 'false'; + await Promise.all( + ['public/', 'src/', 'package.json'].map(async file => + fs.copy(path.join(template, file), path.join(directory, file)) + ) + ); + if (shouldInstallScripts) { + const packageJson = fs.readJsonSync(path.join(directory, 'package.json')); + packageJson.dependencies = Object.assign({}, packageJson.dependencies, { + 'react-scripts': 'latest', + }); + fs.writeJsonSync(path.join(directory, 'package.json'), packageJson); + } + await execa('yarnpkg', ['install', '--mutex', 'network'], { cwd: directory }); + if (!shouldInstallScripts) { + fs.ensureSymlinkSync( + path.resolve( + path.join( + __dirname, + '..', + 'packages', + 'react-scripts', + 'bin', + 'react-scripts.js' + ) + ), + path.join(directory, 'node_modules', '.bin', 'react-scripts') + ); + await execa('yarnpkg', ['link', 'react-scripts'], { cwd: directory }); + } +} + +async function isSuccessfulDevelopment({ directory }) { + const { stdout, stderr } = await execa( + './node_modules/.bin/react-scripts', + ['start', '--smoke-test'], + { + cwd: directory, + env: { BROWSER: 'none', PORT: await getPort() }, + } + ); + + if (!/Compiled successfully/.test(stdout)) { + throw new Error(`stdout: ${stdout}${os.EOL + os.EOL}stderr: ${stderr}`); + } +} + +async function isSuccessfulProduction({ directory }) { + const { stdout, stderr } = await execa( + './node_modules/.bin/react-scripts', + ['build'], + { + cwd: directory, + } + ); + + if (!/Compiled successfully/.test(stdout)) { + throw new Error(`stdout: ${stdout}${os.EOL + os.EOL}stderr: ${stderr}`); + } +} + +async function isSuccessfulTest({ directory, jestEnvironment = 'jsdom' }) { + await execa( + './node_modules/.bin/react-scripts', + ['test', '--env', jestEnvironment, '--ci'], + { + cwd: directory, + env: { CI: 'true' }, + } + ); +} + +async function getOutputDevelopment({ directory, env = {} }) { + try { + const { stdout, stderr } = await execa( + './node_modules/.bin/react-scripts', + ['start', '--smoke-test'], + { + cwd: directory, + env: Object.assign( + {}, + { + BROWSER: 'none', + PORT: await getPort(), + CI: 'false', + FORCE_COLOR: '0', + }, + env + ), + } + ); + return { stdout: stripAnsi(stdout), stderr: stripAnsi(stderr) }; + } catch (err) { + return { + stdout: '', + stderr: stripAnsi( + err.message + .split(os.EOL) + .slice(2) + .join(os.EOL) + ), + }; + } +} + +async function getOutputProduction({ directory, env = {} }) { + try { + const { stdout, stderr } = await execa( + './node_modules/.bin/react-scripts', + ['build'], + { + cwd: directory, + env: Object.assign({}, { CI: 'false', FORCE_COLOR: '0' }, env), + } + ); + return { stdout: stripAnsi(stdout), stderr: stripAnsi(stderr) }; + } catch (err) { + return { + stdout: '', + stderr: stripAnsi( + err.message + .split(os.EOL) + .slice(2) + .join(os.EOL) + ), + }; + } +} + +module.exports = { + bootstrap, + isSuccessfulDevelopment, + isSuccessfulProduction, + isSuccessfulTest, + getOutputDevelopment, + getOutputProduction, +}; diff --git a/package.json b/package.json index 9a590471c3c..96d63b4f224 100644 --- a/package.json +++ b/package.json @@ -14,25 +14,35 @@ "start": "cd packages/react-scripts && node bin/react-scripts.js start", "screencast": "node ./tasks/screencast.js", "screencast:error": "svg-term --cast jyu19xGl88FQ3poMY8Hbmfw8y --out screencast-error.svg --window --at 12000 --no-cursor", - "test": "cd packages/react-scripts && node bin/react-scripts.js test --env=jsdom", - "format": "prettier --trailing-comma es5 --single-quote --write 'packages/*/*.js' 'packages/*/!(node_modules)/**/*.js'", - "precommit": "lint-staged" + "test": "cd packages/react-scripts && node bin/react-scripts.js test", + "format": "prettier --trailing-comma es5 --single-quote --write 'packages/*/*.js' 'packages/*/!(node_modules)/**/*.js'" }, "devDependencies": { - "eslint": "4.15.0", - "execa": "^0.9.0", - "husky": "^0.13.2", - "lerna": "2.6.0", - "lerna-changelog": "^0.6.0", - "lint-staged": "^3.3.1", - "meow": "^4.0.0", + "async-sema": "^2.1.3", + "eslint": "5.6.0", + "execa": "1.0.0", + "fs-extra": "^7.0.0", + "get-port": "^4.0.0", + "globby": "^8.0.1", + "husky": "1.0.0-rc.15", + "jest": "^23.6.0", + "lerna": "2.9.1", + "lerna-changelog": "^0.8.0", + "lint-staged": "^7.0.5", + "meow": "^5.0.0", "multimatch": "^2.1.0", - "prettier": "1.6.1", - "svg-term-cli": "^2.0.3", + "prettier": "1.14.3", + "strip-ansi": "^4.0.0", + "svg-term-cli": "^2.1.1", "tempy": "^0.2.1" }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, "lint-staged": { - "*.js": [ + "*.{js,md,css}": [ "prettier --trailing-comma es5 --single-quote --write", "git add" ], diff --git a/packages/babel-plugin-named-asset-import/LICENSE b/packages/babel-plugin-named-asset-import/LICENSE new file mode 100644 index 00000000000..188fb2b0bd8 --- /dev/null +++ b/packages/babel-plugin-named-asset-import/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/babel-plugin-named-asset-import/index.js b/packages/babel-plugin-named-asset-import/index.js index 6fd919bc676..f1989e5ee06 100644 --- a/packages/babel-plugin-named-asset-import/index.js +++ b/packages/babel-plugin-named-asset-import/index.js @@ -7,7 +7,12 @@ function namedAssetImportPlugin({ types: t }) { return { visitor: { - ImportDeclaration(path, { opts: { loaderMap } }) { + ImportDeclaration( + path, + { + opts: { loaderMap }, + } + ) { const sourcePath = path.node.source.value; const ext = extname(sourcePath).substr(1); diff --git a/packages/babel-plugin-named-asset-import/package.json b/packages/babel-plugin-named-asset-import/package.json index 27bfae4b0e2..192e141f750 100644 --- a/packages/babel-plugin-named-asset-import/package.json +++ b/packages/babel-plugin-named-asset-import/package.json @@ -1,6 +1,6 @@ { "name": "babel-plugin-named-asset-import", - "version": "0.1.0", + "version": "0.2.1", "description": "Babel plugin for named asset imports in Create React App", "repository": "facebookincubator/create-react-app", "license": "MIT", @@ -12,6 +12,6 @@ "index.js" ], "peerDependencies": { - "@babel/core": "7.0.0-beta.44" + "@babel/core": "^7.1.0" } } diff --git a/packages/babel-preset-react-app/LICENSE b/packages/babel-preset-react-app/LICENSE new file mode 100644 index 00000000000..188fb2b0bd8 --- /dev/null +++ b/packages/babel-preset-react-app/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/babel-preset-react-app/create.js b/packages/babel-preset-react-app/create.js new file mode 100644 index 00000000000..52f0fa14bb7 --- /dev/null +++ b/packages/babel-preset-react-app/create.js @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const path = require('path'); + +const validateBoolOption = (name, value, defaultValue) => { + if (typeof value === 'undefined') { + value = defaultValue; + } + + if (typeof value !== 'boolean') { + throw new Error(`Preset react-app: '${name}' option must be a boolean.`); + } + + return value; +}; + +module.exports = function(api, opts, env) { + if (!opts) { + opts = {}; + } + + var isEnvDevelopment = env === 'development'; + var isEnvProduction = env === 'production'; + var isEnvTest = env === 'test'; + + var isFlowEnabled = validateBoolOption('flow', opts.flow, true); + var areHelpersEnabled = validateBoolOption('helpers', opts.helpers, true); + var useAbsoluteRuntime = validateBoolOption( + 'absoluteRuntime', + opts.absoluteRuntime, + true + ); + + var absoluteRuntimePath = undefined; + if (useAbsoluteRuntime) { + absoluteRuntimePath = path.dirname( + require.resolve('@babel/runtime/package.json') + ); + } + + if (!isEnvDevelopment && !isEnvProduction && !isEnvTest) { + throw new Error( + 'Using `babel-preset-react-app` requires that you specify `NODE_ENV` or ' + + '`BABEL_ENV` environment variables. Valid values are "development", ' + + '"test", and "production". Instead, received: ' + + JSON.stringify(env) + + '.' + ); + } + + return { + presets: [ + isEnvTest && [ + // ES features necessary for user's Node version + require('@babel/preset-env').default, + { + targets: { + node: 'current', + }, + }, + ], + (isEnvProduction || isEnvDevelopment) && [ + // Latest stable ECMAScript features + require('@babel/preset-env').default, + { + // We want Create React App to be IE 9 compatible until React itself + // no longer works with IE 9 + targets: { + ie: 9, + }, + // Users cannot override this behavior because this Babel + // configuration is highly tuned for ES5 support + ignoreBrowserslistConfig: true, + // If users import all core-js they're probably not concerned with + // bundle size. We shouldn't rely on magic to try and shrink it. + useBuiltIns: false, + // Do not transform modules to CJS + modules: false, + }, + ], + [ + require('@babel/preset-react').default, + { + // Adds component stack to warning messages + // Adds __self attribute to JSX which React will use for some warnings + development: isEnvDevelopment || isEnvTest, + // Will use the native built-in instead of trying to polyfill + // behavior for any plugins that require one. + useBuiltIns: true, + }, + ], + ].filter(Boolean), + plugins: [ + // Strip flow types before any other transform, emulating the behavior + // order as-if the browser supported all of the succeeding features + // https://github.com/facebook/create-react-app/pull/5182 + isFlowEnabled && + require('@babel/plugin-transform-flow-strip-types').default, + // Experimental macros support. Will be documented after it's had some time + // in the wild. + require('babel-plugin-macros'), + // Necessary to include regardless of the environment because + // in practice some other transforms (such as object-rest-spread) + // don't work without it: https://github.com/babel/babel/issues/7215 + require('@babel/plugin-transform-destructuring').default, + // class { handleClick = () => { } } + // Enable loose mode to use assignment instead of defineProperty + // See discussion in https://github.com/facebook/create-react-app/issues/4263 + [ + require('@babel/plugin-proposal-class-properties').default, + { + loose: true, + }, + ], + // The following two plugins use Object.assign directly, instead of Babel's + // extends helper. Note that this assumes `Object.assign` is available. + // { ...todo, completed: true } + [ + require('@babel/plugin-proposal-object-rest-spread').default, + { + useBuiltIns: true, + }, + ], + // Polyfills the runtime needed for async/await, generators, and friends + // https://babeljs.io/docs/en/babel-plugin-transform-runtime + [ + require('@babel/plugin-transform-runtime').default, + { + corejs: false, + helpers: areHelpersEnabled, + regenerator: true, + // https://babeljs.io/docs/en/babel-plugin-transform-runtime#useesmodules + // We should turn this on once the lowest version of Node LTS + // supports ES Modules. + useESModules: isEnvDevelopment || isEnvProduction, + // Undocumented option that lets us encapsulate our runtime, ensuring + // the correct version is used + // https://github.com/babel/babel/blob/090c364a90fe73d36a30707fc612ce037bdbbb24/packages/babel-plugin-transform-runtime/src/index.js#L35-L42 + absoluteRuntime: absoluteRuntimePath, + }, + ], + isEnvProduction && [ + // Remove PropTypes from production build + require('babel-plugin-transform-react-remove-prop-types').default, + { + removeImport: true, + }, + ], + // Adds syntax support for import() + require('@babel/plugin-syntax-dynamic-import').default, + isEnvTest && + // Transform dynamic import to require + require('babel-plugin-transform-dynamic-import').default, + ].filter(Boolean), + }; +}; diff --git a/packages/babel-preset-react-app/dependencies.js b/packages/babel-preset-react-app/dependencies.js index b676945a311..19a6e74557b 100644 --- a/packages/babel-preset-react-app/dependencies.js +++ b/packages/babel-preset-react-app/dependencies.js @@ -6,6 +6,20 @@ */ 'use strict'; +const path = require('path'); + +const validateBoolOption = (name, value, defaultValue) => { + if (typeof value === 'undefined') { + value = defaultValue; + } + + if (typeof value !== 'boolean') { + throw new Error(`Preset react-app: '${name}' option must be a boolean.`); + } + + return value; +}; + module.exports = function(api, opts) { if (!opts) { opts = {}; @@ -21,6 +35,21 @@ module.exports = function(api, opts) { var isEnvDevelopment = env === 'development'; var isEnvProduction = env === 'production'; var isEnvTest = env === 'test'; + + var areHelpersEnabled = validateBoolOption('helpers', opts.helpers, false); + var useAbsoluteRuntime = validateBoolOption( + 'absoluteRuntime', + opts.absoluteRuntime, + true + ); + + var absoluteRuntimePath = undefined; + if (useAbsoluteRuntime) { + absoluteRuntimePath = path.dirname( + require.resolve('@babel/runtime/package.json') + ); + } + if (!isEnvDevelopment && !isEnvProduction && !isEnvTest) { throw new Error( 'Using `babel-preset-react-app` requires that you specify `NODE_ENV` or ' + @@ -32,6 +61,11 @@ module.exports = function(api, opts) { } return { + // Babel assumes ES Modules, which isn't safe until CommonJS + // dies. This changes the behavior to assume CommonJS unless + // an `import` or `export` is present in the file. + // https://github.com/webpack/webpack/issues/4039#issuecomment-419284940 + sourceType: 'unambiguous', presets: [ isEnvTest && [ // ES features necessary for user's Node version @@ -48,10 +82,46 @@ module.exports = function(api, opts) { // Latest stable ECMAScript features require('@babel/preset-env').default, { + // We want Create React App to be IE 9 compatible until React itself + // no longer works with IE 9 + targets: { + ie: 9, + }, + // Users cannot override this behavior because this Babel + // configuration is highly tuned for ES5 support + ignoreBrowserslistConfig: true, + // If users import all core-js they're probably not concerned with + // bundle size. We shouldn't rely on magic to try and shrink it. + useBuiltIns: false, // Do not transform modules to CJS modules: false, }, ], ].filter(Boolean), + plugins: [ + // Polyfills the runtime needed for async/await, generators, and friends + // https://babeljs.io/docs/en/babel-plugin-transform-runtime + [ + require('@babel/plugin-transform-runtime').default, + { + corejs: false, + helpers: areHelpersEnabled, + regenerator: true, + // https://babeljs.io/docs/en/babel-plugin-transform-runtime#useesmodules + // We should turn this on once the lowest version of Node LTS + // supports ES Modules. + useESModules: isEnvDevelopment || isEnvProduction, + // Undocumented option that lets us encapsulate our runtime, ensuring + // the correct version is used + // https://github.com/babel/babel/blob/090c364a90fe73d36a30707fc612ce037bdbbb24/packages/babel-plugin-transform-runtime/src/index.js#L35-L42 + absoluteRuntime: absoluteRuntimePath, + }, + ], + // Adds syntax support for import() + require('@babel/plugin-syntax-dynamic-import').default, + isEnvTest && + // Transform dynamic import to require + require('babel-plugin-transform-dynamic-import').default, + ].filter(Boolean), }; }; diff --git a/packages/babel-preset-react-app/dev.js b/packages/babel-preset-react-app/dev.js new file mode 100644 index 00000000000..ce87adea4fa --- /dev/null +++ b/packages/babel-preset-react-app/dev.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const create = require('./create'); + +module.exports = function(api, opts) { + return create(api, Object.assign({ helpers: false }, opts), 'development'); +}; diff --git a/packages/babel-preset-react-app/index.js b/packages/babel-preset-react-app/index.js index 72089b9b78b..621af951619 100644 --- a/packages/babel-preset-react-app/index.js +++ b/packages/babel-preset-react-app/index.js @@ -6,138 +6,15 @@ */ 'use strict'; -const validateBoolOption = (name, value, defaultValue) => { - if (typeof value === 'undefined') { - value = defaultValue; - } - - if (typeof value !== 'boolean') { - throw new Error(`Preset react-app: '${name}' option must be a boolean.`); - } - - return value; -}; +const create = require('./create'); module.exports = function(api, opts) { - if (!opts) { - opts = {}; - } - // This is similar to how `env` works in Babel: // https://babeljs.io/docs/usage/babelrc/#env-option // We are not using `env` because it’s ignored in versions > babel-core@6.10.4: // https://github.com/babel/babel/issues/4539 // https://github.com/facebook/create-react-app/issues/720 // It’s also nice that we can enforce `NODE_ENV` being specified. - var env = process.env.BABEL_ENV || process.env.NODE_ENV; - var isEnvDevelopment = env === 'development'; - var isEnvProduction = env === 'production'; - var isEnvTest = env === 'test'; - var isFlowEnabled = validateBoolOption('flow', opts.flow, true); - - if (!isEnvDevelopment && !isEnvProduction && !isEnvTest) { - throw new Error( - 'Using `babel-preset-react-app` requires that you specify `NODE_ENV` or ' + - '`BABEL_ENV` environment variables. Valid values are "development", ' + - '"test", and "production". Instead, received: ' + - JSON.stringify(env) + - '.' - ); - } - - return { - presets: [ - isEnvTest && [ - // ES features necessary for user's Node version - require('@babel/preset-env').default, - { - targets: { - node: '6.12', - }, - }, - ], - (isEnvProduction || isEnvDevelopment) && [ - // Latest stable ECMAScript features - require('@babel/preset-env').default, - { - // `entry` transforms `@babel/polyfill` into individual requires for - // the targeted browsers. This is safer than `usage` which performs - // static code analysis to determine what's required. - // This is probably a fine default to help trim down bundles when - // end-users inevitably import '@babel/polyfill'. - useBuiltIns: 'entry', - // Do not transform modules to CJS - modules: false, - }, - ], - [ - require('@babel/preset-react').default, - { - // Adds component stack to warning messages - // Adds __self attribute to JSX which React will use for some warnings - development: isEnvDevelopment || isEnvTest, - // Will use the native built-in instead of trying to polyfill - // behavior for any plugins that require one. - useBuiltIns: true, - }, - ], - isFlowEnabled && [require('@babel/preset-flow').default], - ].filter(Boolean), - plugins: [ - // Experimental macros support. Will be documented after it's had some time - // in the wild. - require('babel-plugin-macros'), - // Necessary to include regardless of the environment because - // in practice some other transforms (such as object-rest-spread) - // don't work without it: https://github.com/babel/babel/issues/7215 - require('@babel/plugin-transform-destructuring').default, - // class { handleClick = () => { } } - // Enable loose mode to use assignment instead of defineProperty - // See discussion in https://github.com/facebook/create-react-app/issues/4263 - [ - require('@babel/plugin-proposal-class-properties').default, - { - loose: true, - }, - ], - // The following two plugins use Object.assign directly, instead of Babel's - // extends helper. Note that this assumes `Object.assign` is available. - // { ...todo, completed: true } - [ - require('@babel/plugin-proposal-object-rest-spread').default, - { - useBuiltIns: true, - }, - ], - // Polyfills the runtime needed for async/await and generators - [ - require('@babel/plugin-transform-runtime').default, - { - helpers: false, - polyfill: false, - regenerator: true, - }, - ], - isEnvProduction && [ - // Remove PropTypes from production build - require('babel-plugin-transform-react-remove-prop-types').default, - { - removeImport: true, - }, - ], - // function* () { yield 42; yield 43; } - !isEnvTest && [ - require('@babel/plugin-transform-regenerator').default, - { - // Async functions are converted to generators by @babel/preset-env - async: false, - }, - ], - // Adds syntax support for import() - require('@babel/plugin-syntax-dynamic-import').default, - isEnvTest && - // Transform dynamic import to require - require('babel-plugin-transform-dynamic-import').default, - ].filter(Boolean), - }; + const env = process.env.BABEL_ENV || process.env.NODE_ENV; + return create(api, opts, env); }; diff --git a/packages/babel-preset-react-app/package.json b/packages/babel-preset-react-app/package.json index be38c048947..10593914103 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": "3.1.1", + "version": "5.0.1", "description": "Babel preset used by Create React App", "repository": "facebook/create-react-app", "license": "MIT", @@ -8,25 +8,31 @@ "url": "https://github.com/facebook/create-react-app/issues" }, "files": [ + "create.js", + "dependencies.js", + "dev.js", "index.js", - "dependencies.js" + "webpack-overrides.js", + "prod.js", + "test.js" ], "dependencies": { - "@babel/core": "7.0.0-beta.44", - "@babel/plugin-proposal-class-properties": "7.0.0-beta.44", - "@babel/plugin-proposal-object-rest-spread": "7.0.0-beta.44", - "@babel/plugin-syntax-dynamic-import": "7.0.0-beta.44", - "@babel/plugin-transform-classes": "7.0.0-beta.44", - "@babel/plugin-transform-destructuring": "7.0.0-beta.44", - "@babel/plugin-transform-react-constant-elements": "7.0.0-beta.44", - "@babel/plugin-transform-react-display-name": "7.0.0-beta.44", - "@babel/plugin-transform-regenerator": "7.0.0-beta.44", - "@babel/plugin-transform-runtime": "7.0.0-beta.44", - "@babel/preset-env": "7.0.0-beta.44", - "@babel/preset-flow": "7.0.0-beta.44", - "@babel/preset-react": "7.0.0-beta.44", - "babel-plugin-macros": "2.0.0", - "babel-plugin-transform-dynamic-import": "2.0.0", - "babel-plugin-transform-react-remove-prop-types": "0.4.12" + "@babel/core": "7.1.0", + "@babel/plugin-proposal-class-properties": "7.1.0", + "@babel/plugin-proposal-object-rest-spread": "7.0.0", + "@babel/plugin-syntax-dynamic-import": "7.0.0", + "@babel/plugin-transform-classes": "7.1.0", + "@babel/plugin-transform-destructuring": "7.0.0", + "@babel/plugin-transform-flow-strip-types": "7.0.0", + "@babel/plugin-transform-react-constant-elements": "7.0.0", + "@babel/plugin-transform-react-display-name": "7.0.0", + "@babel/plugin-transform-runtime": "7.1.0", + "@babel/preset-env": "7.1.0", + "@babel/preset-react": "7.0.0", + "@babel/runtime": "7.0.0", + "babel-loader": "8.0.4", + "babel-plugin-macros": "2.4.2", + "babel-plugin-transform-dynamic-import": "2.1.0", + "babel-plugin-transform-react-remove-prop-types": "0.4.18" } } diff --git a/packages/babel-preset-react-app/prod.js b/packages/babel-preset-react-app/prod.js new file mode 100644 index 00000000000..0873aee7258 --- /dev/null +++ b/packages/babel-preset-react-app/prod.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const create = require('./create'); + +module.exports = function(api, opts) { + return create(api, Object.assign({ helpers: false }, opts), 'production'); +}; diff --git a/packages/babel-preset-react-app/test.js b/packages/babel-preset-react-app/test.js new file mode 100644 index 00000000000..be189fc3265 --- /dev/null +++ b/packages/babel-preset-react-app/test.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const create = require('./create'); + +module.exports = function(api, opts) { + return create(api, Object.assign({ helpers: false }, opts), 'test'); +}; diff --git a/packages/babel-preset-react-app/webpack-overrides.js b/packages/babel-preset-react-app/webpack-overrides.js new file mode 100644 index 00000000000..95a87eeb66f --- /dev/null +++ b/packages/babel-preset-react-app/webpack-overrides.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const crypto = require('crypto'); + +const macroCheck = new RegExp('[./]macro'); + +module.exports = function() { + return { + // This function transforms the Babel configuration on a per-file basis + config(config, { source }) { + // Babel Macros are notoriously hard to cache, so they shouldn't be + // https://github.com/babel/babel/issues/8497 + // We naively detect macros using their package suffix and add a random token + // to the caller, a valid option accepted by Babel, to compose a one-time + // cacheIdentifier for the file. We cannot tune the loader options on a per + // file basis. + if (macroCheck.test(source)) { + return Object.assign({}, config.options, { + caller: Object.assign({}, config.options.caller, { + craInvalidationToken: crypto.randomBytes(32).toString('hex'), + }), + }); + } + return config.options; + }, + }; +}; diff --git a/packages/confusing-browser-globals/LICENSE b/packages/confusing-browser-globals/LICENSE new file mode 100644 index 00000000000..188fb2b0bd8 --- /dev/null +++ b/packages/confusing-browser-globals/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/confusing-browser-globals/package.json b/packages/confusing-browser-globals/package.json index b96cff9ef93..f2d9d87e2ad 100644 --- a/packages/confusing-browser-globals/package.json +++ b/packages/confusing-browser-globals/package.json @@ -1,6 +1,6 @@ { "name": "confusing-browser-globals", - "version": "1.0.0", + "version": "1.0.2", "description": "A list of browser globals that are often used by mistake instead of local variables", "license": "MIT", "main": "index.js", @@ -16,6 +16,6 @@ "index.js" ], "devDependencies": { - "jest": "22.1.2" + "jest": "23.6.0" } } diff --git a/packages/create-react-app/LICENSE b/packages/create-react-app/LICENSE new file mode 100644 index 00000000000..188fb2b0bd8 --- /dev/null +++ b/packages/create-react-app/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/create-react-app/createReactApp.js b/packages/create-react-app/createReactApp.js index cbe9699e2c3..132d55a8373 100755 --- a/packages/create-react-app/createReactApp.js +++ b/packages/create-react-app/createReactApp.js @@ -49,7 +49,7 @@ const url = require('url'); const hyperquest = require('hyperquest'); const envinfo = require('envinfo'); const os = require('os'); -const findMonorepo = require('react-dev-utils/workspaceUtils').findMonorepo; + const packageJson = require('./package.json'); // These files should be allowed to remain on a failed install, @@ -76,6 +76,7 @@ const program = new commander.Command(packageJson.name) 'use a non-standard version of react-scripts' ) .option('--use-npm') + .option('--use-pnp') .allowUnknownOption() .on('--help', () => { console.log(` Only ${chalk.green('')} is required.`); @@ -84,6 +85,7 @@ const program = new commander.Command(packageJson.name) ` A custom ${chalk.cyan('--scripts-version')} can be one of:` ); console.log(` - a specific npm version: ${chalk.green('0.8.2')}`); + console.log(` - a specific npm tag: ${chalk.green('@next')}`); console.log( ` - a custom fork published on npm: ${chalk.green( 'my-react-scripts' @@ -120,15 +122,28 @@ const program = new commander.Command(packageJson.name) }) .parse(process.argv); +if (program.info) { + console.log(chalk.bold('\nEnvironment Info:')); + return envinfo + .run( + { + System: ['OS', 'CPU'], + Binaries: ['Node', 'npm', 'Yarn'], + Browsers: ['Chrome', 'Edge', 'Internet Explorer', 'Firefox', 'Safari'], + npmPackages: ['react', 'react-dom', 'react-scripts'], + npmGlobalPackages: ['create-react-app'], + }, + { + clipboard: true, + duplicates: true, + showNotFound: true, + } + ) + .then(console.log) + .then(() => console.log(chalk.green('Copied To Clipboard!\n'))); +} + if (typeof projectName === 'undefined') { - if (program.info) { - envinfo.print({ - packages: ['react', 'react-dom', 'react-scripts'], - noNativeIDE: true, - duplicates: true, - }); - process.exit(0); - } console.error('Please specify the project directory:'); console.log( ` ${chalk.cyan(program.name())} ${chalk.green('')}` @@ -164,10 +179,11 @@ createApp( program.verbose, program.scriptsVersion, program.useNpm, + program.usePnp, hiddenProgram.internalTestingTemplate ); -function createApp(name, verbose, version, useNpm, template) { +function createApp(name, verbose, version, useNpm, usePnp, template) { const root = path.resolve(name); const appName = path.basename(root); @@ -190,7 +206,7 @@ function createApp(name, verbose, version, useNpm, template) { JSON.stringify(packageJson, null, 2) + os.EOL ); - const useYarn = useNpm ? false : shouldUseYarn(root); + const useYarn = useNpm ? false : shouldUseYarn(); const originalDirectory = process.cwd(); process.chdir(root); if (!useYarn && !checkThatNpmCanReadCwd()) { @@ -200,7 +216,9 @@ function createApp(name, verbose, version, useNpm, template) { if (!semver.satisfies(process.version, '>=6.0.0')) { console.log( chalk.yellow( - `You are using Node ${process.version} so the project will be bootstrapped with an old unsupported version of tools.\n\n` + + `You are using Node ${ + process.version + } so the project will be bootstrapped with an old unsupported version of tools.\n\n` + `Please update to Node 6 or higher for a better, fully supported experience.\n` ) ); @@ -214,7 +232,9 @@ function createApp(name, verbose, version, useNpm, template) { if (npmInfo.npmVersion) { console.log( chalk.yellow( - `You are using npm ${npmInfo.npmVersion} so the project will be boostrapped with an old unsupported version of tools.\n\n` + + `You are using npm ${ + npmInfo.npmVersion + } so the project will be boostrapped with an old unsupported version of tools.\n\n` + `Please update to npm 3 or higher for a better, fully supported experience.\n` ) ); @@ -223,10 +243,19 @@ function createApp(name, verbose, version, useNpm, template) { version = 'react-scripts@0.9.x'; } } - run(root, appName, version, verbose, originalDirectory, template, useYarn); + run( + root, + appName, + version, + verbose, + originalDirectory, + template, + useYarn, + usePnp + ); } -function isYarnAvailable() { +function shouldUseYarn() { try { execSync('yarnpkg --version', { stdio: 'ignore' }); return true; @@ -235,12 +264,7 @@ function isYarnAvailable() { } } -function shouldUseYarn(appDir) { - const mono = findMonorepo(appDir); - return (mono.isYarnWs && mono.isAppIncluded) || isYarnAvailable(); -} - -function install(root, useYarn, dependencies, verbose, isOnline) { +function install(root, useYarn, usePnp, dependencies, verbose, isOnline) { return new Promise((resolve, reject) => { let command; let args; @@ -250,6 +274,9 @@ function install(root, useYarn, dependencies, verbose, isOnline) { if (!isOnline) { args.push('--offline'); } + if (usePnp) { + args.push('--enable-pnp'); + } [].push.apply(args, dependencies); // Explicitly set cwd() to work around issues like @@ -274,6 +301,12 @@ function install(root, useYarn, dependencies, verbose, isOnline) { '--loglevel', 'error', ].concat(dependencies); + + if (usePnp) { + console.log(chalk.yellow("NPM doesn't support PnP.")); + console.log(chalk.yellow('Falling back to the regular installs.')); + console.log(); + } } if (verbose) { @@ -300,7 +333,8 @@ function run( verbose, originalDirectory, template, - useYarn + useYarn, + usePnp ) { const packageToInstall = getInstallPackage(version, originalDirectory); const allDependencies = ['react', 'react-dom', packageToInstall]; @@ -323,28 +357,39 @@ function run( ); console.log(); - return install(root, useYarn, allDependencies, verbose, isOnline).then( - () => packageName - ); + return install( + root, + useYarn, + usePnp, + allDependencies, + verbose, + isOnline + ).then(() => packageName); }) - .then(packageName => { + .then(async packageName => { checkNodeVersion(packageName); setCaretRangeForRuntimeDeps(packageName); - const scriptsPath = path.resolve( - process.cwd(), - 'node_modules', - packageName, - 'scripts', - 'init.js' + const pnpPath = path.resolve(process.cwd(), '.pnp.js'); + + const nodeArgs = fs.existsSync(pnpPath) ? ['--require', pnpPath] : []; + + await executeNodeScript( + { + cwd: process.cwd(), + args: nodeArgs, + }, + [root, appName, verbose, originalDirectory, template], + ` + var init = require('${packageName}/scripts/init.js'); + init.apply(null, JSON.parse(process.argv[1])); + ` ); - const init = require(scriptsPath); - init(root, appName, verbose, originalDirectory, template); if (version === 'react-scripts@0.9.x') { console.log( chalk.yellow( - `\nNote: the project was boostrapped with an old unsupported version of tools.\n` + + `\nNote: the project was bootstrapped with an old unsupported version of tools.\n` + `Please update to Node >=6 and npm >=3 to get supported tools in new projects.\n` ) ); @@ -394,14 +439,18 @@ function getInstallPackage(version, originalDirectory) { const validSemver = semver.valid(version); if (validSemver) { packageToInstall += `@${validSemver}`; - } else if (version && version.match(/^file:/)) { - packageToInstall = `file:${path.resolve( - originalDirectory, - version.match(/^file:(.*)?$/)[1] - )}`; } else if (version) { - // for tar.gz or alternative paths - packageToInstall = version; + if (version[0] === '@' && version.indexOf('/') === -1) { + packageToInstall += version; + } else if (version.match(/^file:/)) { + packageToInstall = `file:${path.resolve( + originalDirectory, + version.match(/^file:(.*)?$/)[1] + )}`; + } else { + // for tar.gz or alternative paths + packageToInstall = version; + } } return packageToInstall; } @@ -523,6 +572,11 @@ function checkNodeVersion(packageName) { packageName, 'package.json' ); + + if (!fs.existsSync(packageJsonPath)) { + return; + } + const packageJson = require(packageJsonPath); if (!packageJson.engines || !packageJson.engines.node) { return; @@ -777,3 +831,23 @@ function checkIfOnline(useYarn) { }); }); } + +function executeNodeScript({ cwd, args }, data, source) { + return new Promise((resolve, reject) => { + const child = spawn( + process.execPath, + [...args, '-e', source, '--', JSON.stringify(data)], + { cwd, stdio: 'inherit' } + ); + + child.on('close', code => { + if (code !== 0) { + reject({ + command: `node ${args.join(' ')}`, + }); + return; + } + resolve(); + }); + }); +} diff --git a/packages/create-react-app/index.js b/packages/create-react-app/index.js index a093636609e..808ebcfe69c 100755 --- a/packages/create-react-app/index.js +++ b/packages/create-react-app/index.js @@ -42,13 +42,13 @@ var currentNodeVersion = process.versions.node; var semver = currentNodeVersion.split('.'); var major = semver[0]; -if (major < 4) { +if (major < 8) { console.error( chalk.red( 'You are running Node ' + currentNodeVersion + '.\n' + - 'Create React App requires Node 4 or higher. \n' + + 'Create React App requires Node 8 or higher. \n' + 'Please update your version of Node.' ) ); diff --git a/packages/create-react-app/package.json b/packages/create-react-app/package.json index 86bd258f736..18c098e0b81 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.5.1", + "version": "2.0.1", "keywords": [ "react" ], @@ -21,16 +21,15 @@ "create-react-app": "./index.js" }, "dependencies": { - "chalk": "^1.1.3", - "commander": "^2.9.0", - "cross-spawn": "^4.0.0", - "envinfo": "3.4.2", - "fs-extra": "^5.0.0", - "hyperquest": "^2.1.2", - "react-dev-utils": "^5.0.0", - "semver": "^5.0.3", - "tar-pack": "^3.4.0", + "chalk": "1.1.3", + "commander": "2.18.0", + "cross-spawn": "4.0.2", + "envinfo": "5.10.0", + "fs-extra": "5.0.0", + "hyperquest": "2.1.3", + "semver": "5.5.1", + "tar-pack": "3.4.1", "tmp": "0.0.33", - "validate-npm-package-name": "^3.0.0" + "validate-npm-package-name": "3.0.0" } } diff --git a/packages/eslint-config-react-app/LICENSE b/packages/eslint-config-react-app/LICENSE new file mode 100644 index 00000000000..188fb2b0bd8 --- /dev/null +++ b/packages/eslint-config-react-app/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/eslint-config-react-app/README.md b/packages/eslint-config-react-app/README.md index b85a69bb5ac..1cf00fb1564 100644 --- a/packages/eslint-config-react-app/README.md +++ b/packages/eslint-config-react-app/README.md @@ -19,7 +19,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.2.3 eslint@^4.1.1 eslint-plugin-flowtype@^2.34.1 eslint-plugin-import@^2.6.0 eslint-plugin-jsx-a11y@^5.1.1 eslint-plugin-react@^7.1.0 + npm install --save-dev eslint-config-react-app babel-eslint@^9.0.0 eslint@^5.0.0 eslint-plugin-flowtype@^2.34.1 eslint-plugin-import@^2.6.0 eslint-plugin-jsx-a11y@^6.0.0 eslint-plugin-react@^7.1.0 ``` 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 7d2e716956a..92b65234a81 100644 --- a/packages/eslint-config-react-app/index.js +++ b/packages/eslint-config-react-app/index.js @@ -39,12 +39,10 @@ module.exports = { }, parserOptions: { - ecmaVersion: 6, + ecmaVersion: 2018, sourceType: 'module', ecmaFeatures: { jsx: true, - generators: true, - experimentalObjectRestSpread: true, }, }, @@ -53,7 +51,7 @@ module.exports = { 'array-callback-return': 'warn', 'default-case': ['warn', { commentPattern: '^no default$' }], 'dot-location': ['warn', 'property'], - eqeqeq: ['warn', 'allow-null'], + eqeqeq: ['warn', 'smart'], 'new-parens': 'warn', 'no-array-constructor': 'warn', 'no-caller': 'warn', @@ -201,7 +199,10 @@ module.exports = { 'react/jsx-uses-react': 'warn', 'react/jsx-uses-vars': 'warn', 'react/no-danger-with-children': 'warn', - 'react/no-deprecated': 'warn', + // Disabled because of undesirable warnings + // See https://github.com/facebook/create-react-app/issues/5204 for + // blockers until its re-enabled + // 'react/no-deprecated': 'warn', 'react/no-direct-mutation-state': 'warn', 'react/no-is-mounted': 'warn', 'react/react-in-jsx-scope': 'error', diff --git a/packages/eslint-config-react-app/package.json b/packages/eslint-config-react-app/package.json index c4b51925e69..e3ed2213f54 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": "2.1.0", + "version": "3.0.2", "description": "ESLint configuration used by Create React App", "repository": "facebook/create-react-app", "license": "MIT", @@ -11,14 +11,14 @@ "index.js" ], "peerDependencies": { - "babel-eslint": "^8.2.2", - "eslint": "^4.1.1", - "eslint-plugin-flowtype": "^2.34.1", - "eslint-plugin-import": "^2.6.0", - "eslint-plugin-jsx-a11y": "^6.0.2", - "eslint-plugin-react": "^7.7.0" + "babel-eslint": "9.x", + "eslint": "5.x", + "eslint-plugin-flowtype": "2.x", + "eslint-plugin-import": "2.x", + "eslint-plugin-jsx-a11y": "6.x", + "eslint-plugin-react": "7.x" }, "dependencies": { - "confusing-browser-globals": "^1.0.0" + "confusing-browser-globals": "^1.0.2" } } diff --git a/packages/react-app-polyfill/LICENSE b/packages/react-app-polyfill/LICENSE new file mode 100644 index 00000000000..188fb2b0bd8 --- /dev/null +++ b/packages/react-app-polyfill/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/react-app-polyfill/README.md b/packages/react-app-polyfill/README.md new file mode 100644 index 00000000000..3243ad7b16e --- /dev/null +++ b/packages/react-app-polyfill/README.md @@ -0,0 +1,48 @@ +# react-app-polyfill + +This package includes polyfills for various browsers. +It includes minimum requirements and commonly used language features used by [Create React App](https://github.com/facebook/create-react-app) projects. + +### Features + +Each polyfill ensures the following language features are present: + +1. `Promise` (for `async` / `await` support) +1. `window.fetch` (a Promise-based way to make web requests in the browser) +1. `Object.assign` (a helper required for Object Spread, i.e. `{ ...a, ...b }`) +1. `Symbol` (a built-in object used by `for...of` syntax and friends) +1. `Array.from` (a built-in static method used by array spread, i.e. `[...arr]`) + +### Usage + +First, install the package using Yarn or npm: + +```bash +npm install react-app-polyfill +``` + +or + +```bash +yarn add react-app-polyfill +``` + +Now, you can import the entry point for the minimal version you intend to support. For example, if you import the IE9 entry point, this will include IE10 and IE11 support. + +#### Internet Explorer 9 + +```js +// This must be the first line in src/index.js +import 'react-app-polyfill/ie9'; + +// ... +``` + +#### Internet Explorer 11 + +```js +// This must be the first line in src/index.js +import 'react-app-polyfill/ie11'; + +// ... +``` diff --git a/packages/react-scripts/config/polyfills.js b/packages/react-app-polyfill/ie11.js similarity index 74% rename from packages/react-scripts/config/polyfills.js rename to packages/react-app-polyfill/ie11.js index 8d97fb4ac39..15a7a9a085b 100644 --- a/packages/react-scripts/config/polyfills.js +++ b/packages/react-app-polyfill/ie11.js @@ -1,11 +1,9 @@ -// @remove-on-eject-begin /** * Copyright (c) 2015-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -// @remove-on-eject-end 'use strict'; if (typeof Promise === 'undefined') { @@ -23,8 +21,7 @@ require('whatwg-fetch'); // It will use the native implementation if it's present and isn't buggy. Object.assign = require('object-assign'); -// In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. -// We don't polyfill it in the browser--this is user's responsibility. -if (process.env.NODE_ENV === 'test') { - require('raf').polyfill(global); -} +// Support for...of (a commonly used syntax feature that requires Symbols) +require('core-js/es6/symbol'); +// Support iterable spread (...Set, ...Map) +require('core-js/fn/array/from'); diff --git a/packages/react-error-overlay/src/utils/pollyfills.js b/packages/react-app-polyfill/ie9.js similarity index 63% rename from packages/react-error-overlay/src/utils/pollyfills.js rename to packages/react-app-polyfill/ie9.js index ddd5aeb9651..ef89d14fa40 100644 --- a/packages/react-error-overlay/src/utils/pollyfills.js +++ b/packages/react-app-polyfill/ie9.js @@ -4,6 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +'use strict'; if (typeof Promise === 'undefined') { // Rejection tracking prevents a common issue where React gets into an @@ -13,6 +14,19 @@ if (typeof Promise === 'undefined') { window.Promise = require('promise/lib/es6-extensions.js'); } +// fetch() polyfill for making API calls. +require('whatwg-fetch'); + // Object.assign() is commonly used with React. // It will use the native implementation if it's present and isn't buggy. Object.assign = require('object-assign'); + +// Support for...of (a commonly used syntax feature that requires Symbols) +require('core-js/es6/symbol'); +// Support iterable spread (...Set, ...Map) +require('core-js/fn/array/from'); + +// React 16+ relies on Map, Set, and requestAnimationFrame +require('core-js/es6/map'); +require('core-js/es6/set'); +require('raf').polyfill(window); diff --git a/packages/react-app-polyfill/jsdom.js b/packages/react-app-polyfill/jsdom.js new file mode 100644 index 00000000000..1fa4c99623c --- /dev/null +++ b/packages/react-app-polyfill/jsdom.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +// Make sure we're in a Browser-like environment before importing polyfills +// This prevents `fetch()` from being imported in a Node test environment +if (typeof window !== 'undefined') { + // fetch() polyfill for making API calls. + require('whatwg-fetch'); +} diff --git a/packages/react-app-polyfill/package.json b/packages/react-app-polyfill/package.json new file mode 100644 index 00000000000..206239e7b0e --- /dev/null +++ b/packages/react-app-polyfill/package.json @@ -0,0 +1,25 @@ +{ + "name": "react-app-polyfill", + "version": "0.1.2", + "description": "Polyfills for various browsers including commonly used language features", + "repository": "facebook/create-react-app", + "license": "MIT", + "bugs": { + "url": "https://github.com/facebook/create-react-app/issues" + }, + "engines": { + "node": ">=6" + }, + "files": [ + "ie9.js", + "ie11.js", + "jsdom.js" + ], + "dependencies": { + "core-js": "2.5.7", + "object-assign": "4.1.1", + "promise": "8.0.2", + "raf": "3.4.0", + "whatwg-fetch": "3.0.0" + } +} diff --git a/packages/react-dev-utils/FileSizeReporter.js b/packages/react-dev-utils/FileSizeReporter.js index 68aae411f12..c718e6e3823 100644 --- a/packages/react-dev-utils/FileSizeReporter.js +++ b/packages/react-dev-utils/FileSizeReporter.js @@ -15,6 +15,14 @@ var recursive = require('recursive-readdir'); var stripAnsi = require('strip-ansi'); var gzipSize = require('gzip-size').sync; +function canReadAsset(asset) { + return ( + /\.(js|css)$/.test(asset.name) && + !/service-worker\.js/.test(asset.name) && + !/precache-manifest\.[0-9a-f]+\.js/.test(asset.name) + ); +} + // Prints a detailed summary of build files. function printFileSizesAfterBuild( webpackStats, @@ -28,8 +36,8 @@ function printFileSizesAfterBuild( var assets = (webpackStats.stats || [webpackStats]) .map(stats => stats - .toJson() - .assets.filter(asset => /\.(js|css)$/.test(asset.name)) + .toJson({ all: false, assets: true }) + .assets.filter(canReadAsset) .map(asset => { var fileContents = fs.readFileSync(path.join(root, asset.name)); var size = gzipSize(fileContents); @@ -43,7 +51,7 @@ function printFileSizesAfterBuild( name: path.basename(asset.name), size: size, sizeLabel: - filesize(size) + (difference ? ' (' + difference + ')' : '') + filesize(size) + (difference ? ' (' + difference + ')' : ''), }; }) ) @@ -98,6 +106,7 @@ function printFileSizesAfterBuild( function removeFileNameHash(buildFolder, fileName) { return fileName .replace(buildFolder, '') + .replace(/\\/g, '/') .replace( /\/?(.*)(\.[0-9a-f]+)(\.chunk)?(\.js|\.css)/, (match, p1, p2, p3, p4) => p1 + p4 @@ -126,14 +135,12 @@ function measureFileSizesBeforeBuild(buildFolder) { recursive(buildFolder, (err, fileNames) => { var sizes; if (!err && fileNames) { - sizes = fileNames - .filter(fileName => /\.(js|css)$/.test(fileName)) - .reduce((memo, fileName) => { - var contents = fs.readFileSync(fileName); - var key = removeFileNameHash(buildFolder, fileName); - memo[key] = gzipSize(contents); - return memo; - }, {}); + sizes = fileNames.filter(canReadAsset).reduce((memo, fileName) => { + var contents = fs.readFileSync(fileName); + var key = removeFileNameHash(buildFolder, fileName); + memo[key] = gzipSize(contents); + return memo; + }, {}); } resolve({ root: buildFolder, diff --git a/packages/react-dev-utils/InlineChunkHtmlPlugin.js b/packages/react-dev-utils/InlineChunkHtmlPlugin.js new file mode 100644 index 00000000000..e38ed53d3fb --- /dev/null +++ b/packages/react-dev-utils/InlineChunkHtmlPlugin.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +class InlineChunkHtmlPlugin { + constructor(htmlWebpackPlugin, tests) { + this.htmlWebpackPlugin = htmlWebpackPlugin; + this.tests = tests; + } + + getInlinedTag(publicPath, assets, tag) { + if (tag.tagName !== 'script' || !(tag.attributes && tag.attributes.src)) { + return tag; + } + const scriptName = tag.attributes.src.replace(publicPath, ''); + if (!this.tests.some(test => scriptName.match(test))) { + return tag; + } + const asset = assets[scriptName]; + if (asset == null) { + return tag; + } + return { tagName: 'script', innerHTML: asset.source(), closeTag: true }; + } + + apply(compiler) { + let publicPath = compiler.options.output.publicPath; + if (!publicPath.endsWith('/')) { + publicPath += '/'; + } + + compiler.hooks.compilation.tap('InlineChunkHtmlPlugin', compilation => { + const tagFunction = tag => + this.getInlinedTag(publicPath, compilation.assets, tag); + + const hooks = this.htmlWebpackPlugin.getHooks(compilation); + hooks.alterAssetTagGroups.tap('InlineChunkHtmlPlugin', assets => { + assets.headTags = assets.headTags.map(tagFunction); + assets.bodyTags = assets.bodyTags.map(tagFunction); + }); + + // Still emit the runtime chunk for users who do not use our generated + // index.html file. + // hooks.afterEmit.tap('InlineChunkHtmlPlugin', () => { + // Object.keys(compilation.assets).forEach(assetName => { + // if (this.tests.some(test => assetName.match(test))) { + // delete compilation.assets[assetName]; + // } + // }); + // }); + }); + } +} + +module.exports = InlineChunkHtmlPlugin; diff --git a/packages/react-dev-utils/InterpolateHtmlPlugin.js b/packages/react-dev-utils/InterpolateHtmlPlugin.js index 9233bdefa3c..30438f0b8e0 100644 --- a/packages/react-dev-utils/InterpolateHtmlPlugin.js +++ b/packages/react-dev-utils/InterpolateHtmlPlugin.js @@ -6,7 +6,7 @@ */ // This Webpack plugin lets us interpolate custom variables into `index.html`. -// Usage: `new InterpolateHtmlPlugin({ 'MY_VARIABLE': 42 })` +// Usage: `new InterpolateHtmlPlugin(HtmlWebpackPlugin, { 'MY_VARIABLE': 42 })` // Then, you can use %MY_VARIABLE% in your `index.html`. // It works in tandem with HtmlWebpackPlugin. @@ -17,15 +17,16 @@ const escapeStringRegexp = require('escape-string-regexp'); class InterpolateHtmlPlugin { - constructor(replacements) { + constructor(htmlWebpackPlugin, replacements) { + this.htmlWebpackPlugin = htmlWebpackPlugin; this.replacements = replacements; } apply(compiler) { - compiler.plugin('compilation', compilation => { - compilation.plugin( - 'html-webpack-plugin-before-html-processing', - (data, callback) => { + compiler.hooks.compilation.tap('InterpolateHtmlPlugin', compilation => { + this.htmlWebpackPlugin + .getHooks(compilation) + .beforeEmit.tap('InterpolateHtmlPlugin', data => { // Run HTML through a series of user-specified string replacements. Object.keys(this.replacements).forEach(key => { const value = this.replacements[key]; @@ -34,9 +35,7 @@ class InterpolateHtmlPlugin { value ); }); - callback(null, data); - } - ); + }); }); } } diff --git a/packages/react-dev-utils/LICENSE b/packages/react-dev-utils/LICENSE new file mode 100644 index 00000000000..188fb2b0bd8 --- /dev/null +++ b/packages/react-dev-utils/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/react-dev-utils/ModuleNotFoundPlugin.js b/packages/react-dev-utils/ModuleNotFoundPlugin.js new file mode 100644 index 00000000000..55705fd3ea9 --- /dev/null +++ b/packages/react-dev-utils/ModuleNotFoundPlugin.js @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const chalk = require('chalk'); +const findUp = require('find-up'); +const path = require('path'); + +class ModuleNotFoundPlugin { + constructor(appPath, yarnLockFile) { + this.appPath = appPath; + this.yarnLockFile = yarnLockFile; + + this.useYarnCommand = this.useYarnCommand.bind(this); + this.getRelativePath = this.getRelativePath.bind(this); + this.prettierError = this.prettierError.bind(this); + } + + useYarnCommand() { + try { + return findUp.sync('yarn.lock', { cwd: this.appPath }) != null; + } catch (_) { + return false; + } + } + + getRelativePath(_file) { + let file = path.relative(this.appPath, _file); + if (file.startsWith('..')) { + file = _file; + } else if (!file.startsWith('.')) { + file = '.' + path.sep + file; + } + return file; + } + + prettierError(err) { + let { details: _details = '', origin } = err; + + if (origin == null) { + const caseSensitivity = + err.message && + /\[CaseSensitivePathsPlugin\] `(.*?)` .* `(.*?)`/.exec(err.message); + if (caseSensitivity) { + const [, incorrectPath, actualName] = caseSensitivity; + const actualFile = this.getRelativePath( + path.join(path.dirname(incorrectPath), actualName) + ); + const incorrectName = path.basename(incorrectPath); + err.message = `Cannot find file: '${incorrectName}' does not match the corresponding name on disk: '${actualFile}'.`; + } + return err; + } + + const file = this.getRelativePath(origin.resource); + let details = _details.split('\n'); + + const request = /resolve '(.*?)' in '(.*?)'/.exec(details); + if (request) { + const isModule = details[1] && details[1].includes('module'); + const isFile = details[1] && details[1].includes('file'); + + let [, target, context] = request; + context = this.getRelativePath(context); + if (isModule) { + const isYarn = this.useYarnCommand(); + details = [ + `Cannot find module: '${target}'. Make sure this package is installed.`, + '', + 'You can install this package by running: ' + + (isYarn + ? chalk.bold(`yarn add ${target}`) + : chalk.bold(`npm install ${target}`)) + + '.', + ]; + } else if (isFile) { + details = [`Cannot find file '${target}' in '${context}'.`]; + } else { + details = [err.message]; + } + } else { + details = [err.message]; + } + err.message = [file, ...details].join('\n').replace('Error: ', ''); + + const isModuleScopePluginError = + err.error && err.error.__module_scope_plugin; + if (isModuleScopePluginError) { + err.message = err.message.replace('Module not found: ', ''); + } + return err; + } + + apply(compiler) { + const { prettierError } = this; + compiler.hooks.make.intercept({ + register(tap) { + if ( + !(tap.name === 'MultiEntryPlugin' || tap.name === 'SingleEntryPlugin') + ) { + return tap; + } + return Object.assign({}, tap, { + fn: (compilation, callback) => { + tap.fn(compilation, (err, ...args) => { + if (err && err.name === 'ModuleNotFoundError') { + err = prettierError(err); + } + callback(err, ...args); + }); + }, + }); + }, + }); + compiler.hooks.normalModuleFactory.tap('ModuleNotFoundPlugin', nmf => { + nmf.hooks.afterResolve.intercept({ + register(tap) { + if (tap.name !== 'CaseSensitivePathsPlugin') { + return tap; + } + return Object.assign({}, tap, { + fn: (compilation, callback) => { + tap.fn(compilation, (err, ...args) => { + if ( + err && + err.message && + err.message.includes('CaseSensitivePathsPlugin') + ) { + err = prettierError(err); + } + callback(err, ...args); + }); + }, + }); + }, + }); + }); + } +} + +module.exports = ModuleNotFoundPlugin; diff --git a/packages/react-dev-utils/ModuleScopePlugin.js b/packages/react-dev-utils/ModuleScopePlugin.js index 695426331b9..e84d2b38aab 100644 --- a/packages/react-dev-utils/ModuleScopePlugin.js +++ b/packages/react-dev-utils/ModuleScopePlugin.js @@ -9,6 +9,7 @@ const chalk = require('chalk'); const path = require('path'); +const os = require('os'); class ModuleScopePlugin { constructor(appSrc, allowedFiles = []) { @@ -18,51 +19,52 @@ class ModuleScopePlugin { apply(resolver) { const { appSrcs } = this; - resolver.plugin('file', (request, callback) => { - // Unknown issuer, probably webpack internals - if (!request.context.issuer) { - return callback(); - } - if ( - // If this resolves to a node_module, we don't care what happens next - request.descriptionFileRoot.indexOf('/node_modules/') !== -1 || - request.descriptionFileRoot.indexOf('\\node_modules\\') !== -1 || - // Make sure this request was manual - !request.__innerRequest_request - ) { - return callback(); - } - // Resolve the issuer from our appSrc and make sure it's one of our files - // Maybe an indexOf === 0 would be better? - if ( - appSrcs.every(appSrc => { - const relative = path.relative(appSrc, request.context.issuer); - // If it's not in one of our app src or a subdirectory, not our request! - return relative.startsWith('../') || relative.startsWith('..\\'); - }) - ) { - return callback(); - } - const requestFullPath = path.resolve( - path.dirname(request.context.issuer), - request.__innerRequest_request - ); - if (this.allowedFiles.has(requestFullPath)) { - return callback(); - } - // Find path from src to the requested file - // Error if in a parent directory of all given appSrcs - if ( - appSrcs.every(appSrc => { - const requestRelative = path.relative(appSrc, requestFullPath); - return ( - requestRelative.startsWith('../') || - requestRelative.startsWith('..\\') - ); - }) - ) { - callback( - new Error( + resolver.hooks.file.tapAsync( + 'ModuleScopePlugin', + (request, contextResolver, callback) => { + // Unknown issuer, probably webpack internals + if (!request.context.issuer) { + return callback(); + } + if ( + // If this resolves to a node_module, we don't care what happens next + request.descriptionFileRoot.indexOf('/node_modules/') !== -1 || + request.descriptionFileRoot.indexOf('\\node_modules\\') !== -1 || + // Make sure this request was manual + !request.__innerRequest_request + ) { + return callback(); + } + // Resolve the issuer from our appSrc and make sure it's one of our files + // Maybe an indexOf === 0 would be better? + if ( + appSrcs.every(appSrc => { + const relative = path.relative(appSrc, request.context.issuer); + // If it's not in one of our app src or a subdirectory, not our request! + return relative.startsWith('../') || relative.startsWith('..\\'); + }) + ) { + return callback(); + } + const requestFullPath = path.resolve( + path.dirname(request.context.issuer), + request.__innerRequest_request + ); + if (this.allowedFiles.has(requestFullPath)) { + return callback(); + } + // Find path from src to the requested file + // Error if in a parent directory of all given appSrcs + if ( + appSrcs.every(appSrc => { + const requestRelative = path.relative(appSrc, requestFullPath); + return ( + requestRelative.startsWith('../') || + requestRelative.startsWith('..\\') + ); + }) + ) { + const scopeError = new Error( `You attempted to import ${chalk.cyan( request.__innerRequest_request )} which falls outside of the project ${chalk.cyan( @@ -70,19 +72,25 @@ class ModuleScopePlugin { )} directory. ` + `Relative imports outside of ${chalk.cyan( 'src/' - )} are not supported. ` + + )} are not supported.` + + os.EOL + `You can either move it inside ${chalk.cyan( 'src/' )}, or add a symlink to it from project's ${chalk.cyan( 'node_modules/' )}.` - ), - request - ); - } else { - callback(); + ); + Object.defineProperty(scopeError, '__module_scope_plugin', { + value: true, + writable: false, + enumerable: false, + }); + callback(scopeError, request); + } else { + callback(); + } } - }); + ); } } diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md index f63febe7c65..d615f66b5cf 100644 --- a/packages/react-dev-utils/README.md +++ b/packages/react-dev-utils/README.md @@ -3,8 +3,8 @@ This package includes some utilities used by [Create React App](https://github.com/facebook/create-react-app).
Please refer to its documentation: -* [Getting Started](https://github.com/facebook/create-react-app/blob/master/README.md#getting-started) – How to create a new app. -* [User Guide](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md) – How to develop apps bootstrapped with Create React App. +- [Getting Started](https://github.com/facebook/create-react-app/blob/master/README.md#getting-started) – How to create a new app. +- [User Guide](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md) – How to develop apps bootstrapped with Create React App. ## Usage in Create React App Projects @@ -18,14 +18,14 @@ If you don’t use Create React App, or if you [ejected](https://github.com/face There is no single entry point. You can only import individual top-level modules. -#### `new InterpolateHtmlPlugin(replacements: {[key:string]: string})` +#### `new InterpolateHtmlPlugin(htmlWebpackPlugin: HtmlWebpackPlugin, replacements: {[key:string]: string})` This Webpack plugin lets us interpolate custom variables into `index.html`.
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 var path = require('path'); -var HtmlWebpackPlugin = require('html-dev-plugin'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); // Webpack config @@ -34,29 +34,61 @@ var publicUrl = '/my-custom-url'; module.exports = { output: { // ... - publicPath: publicUrl + '/' + publicPath: publicUrl + '/', }, // ... plugins: [ + // Generates an `index.html` file with the