Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da834083cc | ||
|
|
7554ab7672 | ||
|
|
2186beb775 | ||
|
|
82cf50ab3a |
@@ -11,9 +11,9 @@ aliases:
|
||||
restore_cache:
|
||||
name: Restore node_modules cache
|
||||
keys:
|
||||
- v2-node-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
- v2-node-{{ arch }}-{{ .Branch }}-
|
||||
- v2-node-{{ arch }}-
|
||||
- v1-node-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
- v1-node-{{ arch }}-{{ .Branch }}-
|
||||
- v1-node-{{ arch }}-
|
||||
- &run_yarn
|
||||
run:
|
||||
name: Install Packages
|
||||
@@ -31,6 +31,7 @@ aliases:
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: node ./scripts/rollup/consolidateBundleSizes.js
|
||||
- run: ./scripts/circleci/upload_build.sh
|
||||
- run: ./scripts/circleci/pack_and_store_artifact.sh
|
||||
- store_artifacts:
|
||||
path: ./node_modules.tgz
|
||||
@@ -61,11 +62,11 @@ jobs:
|
||||
- *run_yarn
|
||||
- save_cache:
|
||||
name: Save node_modules cache
|
||||
key: v2-node-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
key: v1-node-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
|
||||
yarn_lint:
|
||||
lint:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
@@ -79,7 +80,7 @@ jobs:
|
||||
- run: ./scripts/circleci/check_modules.sh
|
||||
- run: ./scripts/circleci/test_print_warnings.sh
|
||||
|
||||
yarn_flow:
|
||||
flow:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
@@ -89,7 +90,7 @@ jobs:
|
||||
- *run_yarn
|
||||
- run: node ./scripts/tasks/flow-ci
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test:
|
||||
test_source:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
@@ -97,90 +98,24 @@ jobs:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=stable --ci
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: stable
|
||||
command: yarn test --maxWorkers=2
|
||||
|
||||
yarn_test:
|
||||
test_source_experimental:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --ci
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: yarn test --maxWorkers=2
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_www:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=www-classic --ci
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_www_variant:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=www-classic --variant --ci
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_prod_www:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=www-classic --prod --ci
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_prod_www_variant:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=www-classic --prod --variant --ci
|
||||
|
||||
yarn_test_www:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=www-modern --ci
|
||||
|
||||
yarn_test_www_variant:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=www-modern --variant --ci
|
||||
|
||||
yarn_test_prod_www:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=www-modern --prod --ci
|
||||
|
||||
yarn_test_prod_www_variant:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=www-modern --prod --variant --ci
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_persistent:
|
||||
test_source_persistent:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
@@ -188,9 +123,12 @@ jobs:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=stable --persistent --ci
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: stable
|
||||
command: yarn test-persistent --maxWorkers=2
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_prod:
|
||||
test_source_prod:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
@@ -198,18 +136,12 @@ jobs:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=stable --prod --ci
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: stable
|
||||
command: yarn test-prod --maxWorkers=2
|
||||
|
||||
yarn_test_prod:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=experimental --prod --ci
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_build:
|
||||
build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: 20
|
||||
@@ -230,13 +162,12 @@ jobs:
|
||||
paths:
|
||||
- RELEASE_CHANNEL
|
||||
- facebook-www
|
||||
- facebook-react-native
|
||||
- node_modules
|
||||
- react-native
|
||||
- dist
|
||||
- sizes/*.json
|
||||
|
||||
yarn_build:
|
||||
build_experimental:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: 20
|
||||
@@ -257,7 +188,6 @@ jobs:
|
||||
paths:
|
||||
- RELEASE_CHANNEL
|
||||
- facebook-www
|
||||
- facebook-react-native
|
||||
- node_modules
|
||||
- react-native
|
||||
- dist
|
||||
@@ -284,7 +214,7 @@ jobs:
|
||||
process_artifacts: *process_artifacts
|
||||
process_artifacts_experimental: *process_artifacts
|
||||
|
||||
sizebot_stable:
|
||||
sizebot:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
@@ -316,7 +246,7 @@ jobs:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: node ./scripts/tasks/danger
|
||||
|
||||
yarn_lint_build:
|
||||
lint_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
@@ -327,7 +257,7 @@ jobs:
|
||||
- run: yarn lint-build
|
||||
- run: scripts/circleci/check_minified_errors.sh
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_lint_build:
|
||||
test_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
@@ -338,10 +268,9 @@ jobs:
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: stable
|
||||
command: yarn lint-build
|
||||
- run: scripts/circleci/check_minified_errors.sh
|
||||
command: yarn test-build --maxWorkers=2
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_build:
|
||||
test_build_experimental:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
@@ -349,9 +278,12 @@ jobs:
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=stable --build --ci
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: yarn test-build --maxWorkers=2
|
||||
|
||||
yarn_test_build:
|
||||
test_devtools:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
@@ -359,19 +291,12 @@ jobs:
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=experimental --build --ci
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: yarn test-build-devtools --maxWorkers=2
|
||||
|
||||
yarn_test_build_devtools:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --project=devtools --build --ci
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
|
||||
test_dom_fixtures:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
@@ -398,10 +323,10 @@ jobs:
|
||||
- run:
|
||||
name: Run fuzz tests
|
||||
command: |
|
||||
FUZZ_TEST_SEED=$RANDOM yarn test fuzz --ci
|
||||
FUZZ_TEST_SEED=$RANDOM yarn test --prod fuzz --ci
|
||||
FUZZ_TEST_SEED=$RANDOM yarn test fuzz --maxWorkers=2
|
||||
FUZZ_TEST_SEED=$RANDOM yarn test-prod fuzz --maxWorkers=2
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_build_prod:
|
||||
test_build_prod:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
@@ -409,9 +334,12 @@ jobs:
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=stable --build --prod --ci
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: stable
|
||||
command: yarn test-build-prod --maxWorkers=2
|
||||
|
||||
yarn_test_build_prod:
|
||||
test_build_prod_experimental:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
@@ -419,108 +347,83 @@ jobs:
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --release-channel=experimental --build --prod --ci
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: yarn test-build-prod --maxWorkers=2
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
stable:
|
||||
jobs:
|
||||
- setup
|
||||
- yarn_lint:
|
||||
- lint:
|
||||
requires:
|
||||
- setup
|
||||
- yarn_flow:
|
||||
- flow:
|
||||
requires:
|
||||
- setup
|
||||
- RELEASE_CHANNEL_stable_yarn_test:
|
||||
- test_source:
|
||||
requires:
|
||||
- setup
|
||||
- RELEASE_CHANNEL_stable_yarn_test_prod:
|
||||
- test_source_prod:
|
||||
requires:
|
||||
- setup
|
||||
- RELEASE_CHANNEL_stable_yarn_test_persistent:
|
||||
- test_source_persistent:
|
||||
requires:
|
||||
- setup
|
||||
- RELEASE_CHANNEL_stable_yarn_test_www:
|
||||
requires:
|
||||
- setup
|
||||
- RELEASE_CHANNEL_stable_yarn_test_www_variant:
|
||||
requires:
|
||||
- setup
|
||||
- RELEASE_CHANNEL_stable_yarn_test_prod_www:
|
||||
requires:
|
||||
- setup
|
||||
- RELEASE_CHANNEL_stable_yarn_test_prod_www_variant:
|
||||
requires:
|
||||
- setup
|
||||
- RELEASE_CHANNEL_stable_yarn_build:
|
||||
- build:
|
||||
requires:
|
||||
- setup
|
||||
- process_artifacts:
|
||||
requires:
|
||||
- RELEASE_CHANNEL_stable_yarn_build
|
||||
- sizebot_stable:
|
||||
- build
|
||||
- sizebot:
|
||||
requires:
|
||||
- RELEASE_CHANNEL_stable_yarn_build
|
||||
- RELEASE_CHANNEL_stable_yarn_lint_build:
|
||||
- build
|
||||
- lint_build:
|
||||
requires:
|
||||
- RELEASE_CHANNEL_stable_yarn_build
|
||||
- RELEASE_CHANNEL_stable_yarn_test_build:
|
||||
- build
|
||||
- test_build:
|
||||
requires:
|
||||
- RELEASE_CHANNEL_stable_yarn_build
|
||||
- RELEASE_CHANNEL_stable_yarn_test_build_prod:
|
||||
- build
|
||||
- test_build_prod:
|
||||
requires:
|
||||
- RELEASE_CHANNEL_stable_yarn_build
|
||||
- RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
|
||||
- build
|
||||
- test_dom_fixtures:
|
||||
requires:
|
||||
- RELEASE_CHANNEL_stable_yarn_build
|
||||
- build
|
||||
|
||||
experimental:
|
||||
jobs:
|
||||
- setup
|
||||
- yarn_test:
|
||||
- test_source_experimental:
|
||||
requires:
|
||||
- setup
|
||||
- yarn_test_prod:
|
||||
requires:
|
||||
- setup
|
||||
- yarn_test_www:
|
||||
requires:
|
||||
- setup
|
||||
- yarn_test_www_variant:
|
||||
requires:
|
||||
- setup
|
||||
- yarn_test_prod_www:
|
||||
requires:
|
||||
- setup
|
||||
- yarn_test_prod_www_variant:
|
||||
requires:
|
||||
- setup
|
||||
- yarn_build:
|
||||
- build_experimental:
|
||||
requires:
|
||||
- setup
|
||||
- process_artifacts_experimental:
|
||||
requires:
|
||||
- yarn_build
|
||||
- build_experimental
|
||||
- sizebot_experimental:
|
||||
requires:
|
||||
- yarn_build
|
||||
- yarn_test_build:
|
||||
- build_experimental
|
||||
- test_build_experimental:
|
||||
requires:
|
||||
- yarn_build
|
||||
- yarn_test_build_prod:
|
||||
- build_experimental
|
||||
- test_build_prod_experimental:
|
||||
requires:
|
||||
- yarn_build
|
||||
- yarn_lint_build:
|
||||
- build_experimental
|
||||
- lint_build:
|
||||
requires:
|
||||
- yarn_build
|
||||
- yarn_test_build_devtools:
|
||||
- build_experimental
|
||||
- test_devtools:
|
||||
requires:
|
||||
- yarn_build
|
||||
# FIXME: Temporarily disabled to unblock master.
|
||||
# - build_devtools_and_process_artifacts:
|
||||
# requires:
|
||||
# - yarn_build
|
||||
- build_experimental
|
||||
- build_devtools_and_process_artifacts:
|
||||
requires:
|
||||
- build_experimental
|
||||
|
||||
fuzz_tests:
|
||||
triggers:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"packages": ["packages/react", "packages/react-dom", "packages/scheduler"],
|
||||
"buildCommand": "build --type=NODE react/index,react-dom/index,react-dom/server,scheduler/index,scheduler/tracing",
|
||||
"buildCommand": "build --type=NODE react/index,react-dom/index,scheduler/index,scheduler/tracing",
|
||||
"publishDirectory": {
|
||||
"react": "build/node_modules/react",
|
||||
"react-dom": "build/node_modules/react-dom",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# https://editorconfig.org
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
||||
18
.eslintrc.js
18
.eslintrc.js
@@ -11,7 +11,10 @@ const OFF = 0;
|
||||
const ERROR = 2;
|
||||
|
||||
module.exports = {
|
||||
extends: ['fbjs', 'prettier'],
|
||||
extends: [
|
||||
'fbjs',
|
||||
'prettier'
|
||||
],
|
||||
|
||||
// Stop ESLint from looking for a configuration file in parent folders
|
||||
root: true,
|
||||
@@ -98,7 +101,6 @@ module.exports = {
|
||||
'react-internal/invariant-args': ERROR,
|
||||
'react-internal/warning-args': ERROR,
|
||||
'react-internal/no-production-logging': ERROR,
|
||||
'react-internal/no-cross-fork-imports': ERROR,
|
||||
},
|
||||
|
||||
overrides: [
|
||||
@@ -127,7 +129,6 @@ module.exports = {
|
||||
},
|
||||
rules: {
|
||||
'no-var': ERROR,
|
||||
'prefer-const': ERROR,
|
||||
strict: OFF,
|
||||
},
|
||||
},
|
||||
@@ -146,7 +147,7 @@ module.exports = {
|
||||
'scripts/**/*.js',
|
||||
'packages/*/npm/**/*.js',
|
||||
'packages/dom-event-testing-library/**/*.js',
|
||||
'packages/react-devtools*/**/*.js',
|
||||
'packages/react-devtools*/**/*.js'
|
||||
],
|
||||
rules: {
|
||||
'react-internal/no-production-logging': OFF,
|
||||
@@ -159,13 +160,6 @@ module.exports = {
|
||||
nativeFabricUIManager: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/react-transport-dom-webpack/**/*.js'],
|
||||
globals: {
|
||||
__webpack_chunk_load__: true,
|
||||
__webpack_require__: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
globals: {
|
||||
@@ -177,8 +171,6 @@ module.exports = {
|
||||
__PROFILE__: true,
|
||||
__UMD__: true,
|
||||
__EXPERIMENTAL__: true,
|
||||
__VARIANT__: true,
|
||||
gate: true,
|
||||
trustedTypes: true,
|
||||
},
|
||||
};
|
||||
|
||||
4
.github/ISSUE_TEMPLATE.md
vendored
Normal file
4
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
👉 Please follow one of these issue templates:
|
||||
- https://github.com/facebook/react/issues/new/choose
|
||||
|
||||
Note: to keep the backlog clean and actionable, issues may be immediately closed if they do not follow one of the above issue templates.
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -12,7 +12,7 @@
|
||||
6. If you need a debugger, run `yarn debug-test --watch TestName`, open `chrome://inspect`, and press "Inspect".
|
||||
7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`).
|
||||
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files.
|
||||
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
|
||||
9. Run the [Flow](https://flowtype.org/) typechecks (`yarn flow`).
|
||||
10. If you haven't already, complete the CLA.
|
||||
|
||||
Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html
|
||||
|
||||
11
.github/stale.yml
vendored
11
.github/stale.yml
vendored
@@ -10,25 +10,22 @@ exemptLabels:
|
||||
- "Resolution: Backlog"
|
||||
- "Type: Bug"
|
||||
- "Type: Discussion"
|
||||
- "Type: Needs Investigation"
|
||||
- "Type: Regression"
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: "Resolution: Stale"
|
||||
issues:
|
||||
# Comment to post when marking an issue as stale.
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale.
|
||||
**If this issue is still affecting you, please leave any comment** (for example, "bump"), and we'll keep it open.
|
||||
We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
|
||||
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
|
||||
Thank you for your contribution.
|
||||
# Comment to post when closing a stale issue.
|
||||
closeComment: >
|
||||
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!
|
||||
pulls:
|
||||
# Comment to post when marking a pull request as stale.
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale.
|
||||
**If this pull request is still relevant, please leave any comment** (for example, "bump"), and we'll keep it open.
|
||||
We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.
|
||||
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
|
||||
Thank you for your contribution.
|
||||
# Comment to post when closing a stale pull request.
|
||||
closeComment: >
|
||||
Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you!
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,7 +1,6 @@
|
||||
.DS_STORE
|
||||
node_modules
|
||||
scripts/flow/*/.flowconfig
|
||||
.flowconfig
|
||||
*~
|
||||
*.pyc
|
||||
.grunt
|
||||
@@ -32,6 +31,5 @@ packages/react-devtools-extensions/firefox/build
|
||||
packages/react-devtools-extensions/firefox/*.xpi
|
||||
packages/react-devtools-extensions/firefox/*.pem
|
||||
packages/react-devtools-extensions/shared/build
|
||||
packages/react-devtools-extensions/.tempUserDataDir
|
||||
packages/react-devtools-inline/dist
|
||||
packages/react-devtools-shell/dist
|
||||
39
CHANGELOG.md
39
CHANGELOG.md
@@ -1,40 +1,3 @@
|
||||
## 16.13.1 (March 19, 2020)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix bug in legacy mode Suspense where effect clean-up functions are not fired. This only affects users who use Suspense for data fetching in legacy mode, which is not technically supported. ([@acdlite](https://github.com/acdlite) in [#18238](https://github.com/facebook/react/pull/18238))
|
||||
* Revert warning for cross-component updates that happen inside class render lifecycles (`componentWillReceiveProps`, `shouldComponentUpdate`, and so on). ([@gaearon](https://github.com/gaearon) in [#18330](https://github.com/facebook/react/pull/18330))
|
||||
|
||||
## 16.13.0 (February 26, 2020)
|
||||
|
||||
### React
|
||||
|
||||
* Warn when a string ref is used in a manner that's not amenable to a future codemod ([@lunaruan](https://github.com/lunaruan) in [#17864](https://github.com/facebook/react/pull/17864))
|
||||
* Deprecate `React.createFactory()` ([@trueadm](https://github.com/trueadm) in [#17878](https://github.com/facebook/react/pull/17878))
|
||||
|
||||
### React DOM
|
||||
|
||||
* Warn when changes in `style` may cause an unexpected collision ([@sophiebits](https://github.com/sophiebits) in [#14181](https://github.com/facebook/react/pull/14181), [#18002](https://github.com/facebook/react/pull/18002))
|
||||
* Warn when a function component is updated during another component's render phase ([@acdlite](https://github.com/acdlite) in [#17099](https://github.com/facebook/react/pull/17099))
|
||||
* Deprecate `unstable_createPortal` ([@trueadm](https://github.com/trueadm) in [#17880](https://github.com/facebook/react/pull/17880))
|
||||
* Fix `onMouseEnter` being fired on disabled buttons ([@AlfredoGJ](https://github.com/AlfredoGJ) in [#17675](https://github.com/facebook/react/pull/17675))
|
||||
* Call `shouldComponentUpdate` twice when developing in `StrictMode` ([@bvaughn](https://github.com/bvaughn) in [#17942](https://github.com/facebook/react/pull/17942))
|
||||
* Add `version` property to ReactDOM ([@ealush](https://github.com/ealush) in [#15780](https://github.com/facebook/react/pull/15780))
|
||||
* Don't call `toString()` of `dangerouslySetInnerHTML` ([@sebmarkbage](https://github.com/sebmarkbage) in [#17773](https://github.com/facebook/react/pull/17773))
|
||||
* Show component stacks in more warnings ([@gaearon](https://github.com/gaearon) in [#17922](https://github.com/facebook/react/pull/17922), [#17586](https://github.com/facebook/react/pull/17586))
|
||||
|
||||
### Concurrent Mode (Experimental)
|
||||
|
||||
* Warn for problematic usages of `ReactDOM.createRoot()` ([@trueadm](https://github.com/trueadm) in [#17937](https://github.com/facebook/react/pull/17937))
|
||||
* Remove `ReactDOM.createRoot()` callback params and added warnings on usage ([@bvaughn](https://github.com/bvaughn) in [#17916](https://github.com/facebook/react/pull/17916))
|
||||
* Don't group Idle/Offscreen work with other work ([@sebmarkbage](https://github.com/sebmarkbage) in [#17456](https://github.com/facebook/react/pull/17456))
|
||||
* Adjust `SuspenseList` CPU bound heuristic ([@sebmarkbage](https://github.com/sebmarkbage) in [#17455](https://github.com/facebook/react/pull/17455))
|
||||
* Add missing event plugin priorities ([@trueadm](https://github.com/trueadm) in [#17914](https://github.com/facebook/react/pull/17914))
|
||||
* Fix `isPending` only being true when transitioning from inside an input event ([@acdlite](https://github.com/acdlite) in [#17382](https://github.com/facebook/react/pull/17382))
|
||||
* Fix `React.memo` components dropping updates when interrupted by a higher priority update ([@acdlite]((https://github.com/acdlite)) in [#18091](https://github.com/facebook/react/pull/18091))
|
||||
* Don't warn when suspending at the wrong priority ([@gaearon](https://github.com/gaearon) in [#17971](https://github.com/facebook/react/pull/17971))
|
||||
* Fix a bug with rebasing updates ([@acdlite](https://github.com/acdlite) and [@sebmarkbage](https://github.com/sebmarkbage) in [#17560](https://github.com/facebook/react/pull/17560), [#17510](https://github.com/facebook/react/pull/17510), [#17483](https://github.com/facebook/react/pull/17483), [#17480](https://github.com/facebook/react/pull/17480))
|
||||
|
||||
## 16.12.0 (November 14, 2019)
|
||||
|
||||
### React DOM
|
||||
@@ -782,7 +745,7 @@ Starting with 16.1.0, we will no longer be publishing new releases on Bower. You
|
||||
* Fix bug in QtWebKit when wrapping synthetic events in proxies. ([@walrusfruitcake](https://github.com/walrusfruitcake) in [#10115](https://github.com/facebook/react/pull/10011))
|
||||
* Prevent event handlers from receiving extra argument in development. ([@aweary](https://github.com/aweary) in [#10115](https://github.com/facebook/react/pull/8363))
|
||||
* Fix cases where `onChange` would not fire with `defaultChecked` on radio inputs. ([@jquense](https://github.com/jquense) in [#10156](https://github.com/facebook/react/pull/10156))
|
||||
* Add support for `controlList` attribute to allowed DOM properties ([@nhunzaker](https://github.com/nhunzaker) in [#9940](https://github.com/facebook/react/pull/9940))
|
||||
* Add support for `controlList` attribute to DOM property whitelist ([@nhunzaker](https://github.com/nhunzaker) in [#9940](https://github.com/facebook/react/pull/9940))
|
||||
* Fix a bug where creating an element with a ref in a constructor did not throw an error in development. ([@iansu](https://github.com/iansu) in [#10025](https://github.com/facebook/react/pull/10025))
|
||||
|
||||
## 15.6.1 (June 14, 2017)
|
||||
|
||||
@@ -4,7 +4,7 @@ React is a JavaScript library for building user interfaces.
|
||||
|
||||
* **Declarative:** React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes. Declarative views make your code more predictable, simpler to understand, and easier to debug.
|
||||
* **Component-Based:** Build encapsulated components that manage their own state, then compose them to make complex UIs. Since component logic is written in JavaScript instead of templates, you can easily pass rich data through your app and keep state out of the DOM.
|
||||
* **Learn Once, Write Anywhere:** We don't make assumptions about the rest of your technology stack, so you can develop new features in React without rewriting existing code. React can also render on the server using Node and power mobile apps using [React Native](https://reactnative.dev/).
|
||||
* **Learn Once, Write Anywhere:** We don't make assumptions about the rest of your technology stack, so you can develop new features in React without rewriting existing code. React can also render on the server using Node and power mobile apps using [React Native](https://facebook.github.io/react-native/).
|
||||
|
||||
[Learn how to use React in your own project](https://reactjs.org/docs/getting-started.html).
|
||||
|
||||
@@ -16,7 +16,7 @@ React has been designed for gradual adoption from the start, and **you can use a
|
||||
* [Add React to a Website](https://reactjs.org/docs/add-react-to-a-website.html) as a `<script>` tag in one minute.
|
||||
* [Create a New React App](https://reactjs.org/docs/create-a-new-react-app.html) if you're looking for a powerful JavaScript toolchain.
|
||||
|
||||
You can use React as a `<script>` tag from a [CDN](https://reactjs.org/docs/cdn-links.html), or as a `react` package on [npm](https://www.npmjs.com/package/react).
|
||||
You can use React as a `<script>` tag from a [CDN](https://reactjs.org/docs/cdn-links.html), or as a `react` package on [npm](https://www.npmjs.com/).
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ const attributes = [
|
||||
{
|
||||
name: 'cite',
|
||||
tagName: 'blockquote',
|
||||
overrideStringValue: 'https://reactjs.com/',
|
||||
overrideStringValue: 'http://reactjs.com/',
|
||||
},
|
||||
{name: 'class', read: getAttribute('class')},
|
||||
{name: 'classID', tagName: 'object', read: getAttribute('classid')},
|
||||
@@ -453,11 +453,6 @@ const attributes = [
|
||||
tagName: 'video',
|
||||
read: getProperty('disablepictureinpicture'),
|
||||
},
|
||||
{
|
||||
name: 'disableRemotePlayback',
|
||||
tagName: 'video',
|
||||
read: getProperty('disableremoteplayback'),
|
||||
},
|
||||
{
|
||||
name: 'display',
|
||||
tagName: 'svg',
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
@@ -1,77 +0,0 @@
|
||||
{
|
||||
"posts": [
|
||||
{
|
||||
"id": 1,
|
||||
"userId": 2,
|
||||
"title": "Welcome",
|
||||
"body": "Hello, world!"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"userId": 3,
|
||||
"title": "A Guide to useEffect",
|
||||
"body": "Let me tell you everything about useEffect"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"userId": 1,
|
||||
"title": "Here and There",
|
||||
"body": "Browsers are smart"
|
||||
}
|
||||
],
|
||||
"comments": [
|
||||
{
|
||||
"id": 1,
|
||||
"body": "Hey there",
|
||||
"postId": 1,
|
||||
"userId": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"body": "Welcome to the chat",
|
||||
"postId": 1,
|
||||
"userId": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"body": "What editor/font are you using?",
|
||||
"postId": 2,
|
||||
"userId": 2
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"body": "It's always been hard",
|
||||
"postId": 3,
|
||||
"userId": 1
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"body": "It's still easy",
|
||||
"postId": 3,
|
||||
"userId": 2
|
||||
}
|
||||
],
|
||||
"users": [{
|
||||
"id": 1,
|
||||
"name": "Sebastian",
|
||||
"bioId": 10
|
||||
}, {
|
||||
"id": 2,
|
||||
"name": "Sophie",
|
||||
"bioId": 20
|
||||
}, {
|
||||
"id": 3,
|
||||
"name": "Dan",
|
||||
"bioId": 30
|
||||
}],
|
||||
"bios": [{
|
||||
"id": 10,
|
||||
"text": "I like European movies"
|
||||
}, {
|
||||
"id": 20,
|
||||
"text": "I like math puzzles"
|
||||
}, {
|
||||
"id": 30,
|
||||
"text": "I like reading twitter"
|
||||
}]
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"name": "blocks",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"proxy": "http://localhost:3001/",
|
||||
"dependencies": {
|
||||
"concurrently": "^5.2.0",
|
||||
"json-server": "^0.16.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-scripts": "3.4.1"
|
||||
},
|
||||
"scripts": {
|
||||
"prestart": "cp -r ../../build/node_modules/* ./node_modules/",
|
||||
"prebuild": "cp -r ../../build/node_modules/* ./node_modules/",
|
||||
"start": "concurrently \"npm run start:client\" \"npm run start:api\"",
|
||||
"start:api": "json-server --watch db.json --port 3001 --delay 1000 --middlewares delay.js",
|
||||
"start:client": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Blocks Fixture</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,105 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
useReducer,
|
||||
useEffect,
|
||||
unstable_useTransition as useTransition,
|
||||
useCallback,
|
||||
useMemo,
|
||||
Suspense,
|
||||
} from 'react';
|
||||
import {createCache, CacheProvider} from 'react/unstable-cache';
|
||||
import {RouterProvider} from './client/RouterContext';
|
||||
// TODO: can't really import a server component on the client.
|
||||
import App from './server/App';
|
||||
|
||||
const initialUrl = window.location.pathname;
|
||||
const initialState = {
|
||||
// TODO: use this for invalidation.
|
||||
cache: createCache(),
|
||||
url: initialUrl,
|
||||
pendingUrl: initialUrl,
|
||||
root: <App route={initialUrl} />,
|
||||
};
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'startNavigation':
|
||||
return {
|
||||
...state,
|
||||
pendingUrl: action.url,
|
||||
};
|
||||
case 'completeNavigation':
|
||||
// TODO: cancel previous fetch?
|
||||
return {
|
||||
...state,
|
||||
url: action.url,
|
||||
pendingUrl: action.url,
|
||||
root: action.root,
|
||||
};
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
function Router() {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const [startTransition, isPending] = useTransition({
|
||||
timeoutMs: 1500,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.cursor = isPending ? 'wait' : '';
|
||||
}, [isPending]);
|
||||
|
||||
const navigate = useCallback(
|
||||
url => {
|
||||
startTransition(() => {
|
||||
// TODO: Here, There, and Everywhere.
|
||||
// TODO: Instant Transitions, somehow.
|
||||
dispatch({
|
||||
type: 'completeNavigation',
|
||||
root: <App route={url} />,
|
||||
url,
|
||||
});
|
||||
});
|
||||
dispatch({
|
||||
type: 'startNavigation',
|
||||
url,
|
||||
});
|
||||
},
|
||||
[startTransition]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = () => {
|
||||
navigate(window.location.pathname);
|
||||
};
|
||||
window.addEventListener('popstate', listener);
|
||||
return () => window.removeEventListener('popstate', listener);
|
||||
}, [navigate]);
|
||||
|
||||
const routeContext = useMemo(
|
||||
() => ({
|
||||
pendingUrl: state.pendingUrl,
|
||||
url: state.url,
|
||||
navigate,
|
||||
}),
|
||||
[state.url, state.pendingUrl, navigate]
|
||||
);
|
||||
|
||||
return (
|
||||
<Suspense fallback={<h2>Loading...</h2>}>
|
||||
<CacheProvider value={state.cache}>
|
||||
<RouterProvider value={routeContext}>{state.root}</RouterProvider>
|
||||
</CacheProvider>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
export default Router;
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {useRouter} from './RouterContext';
|
||||
|
||||
export default function Link({to, children, ...rest}) {
|
||||
const {navigate} = useRouter();
|
||||
return (
|
||||
<a
|
||||
href={to}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
window.history.pushState(null, null, to);
|
||||
navigate(to);
|
||||
}}
|
||||
{...rest}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import {TabBar, TabLink} from '../client/TabNav';
|
||||
|
||||
export default function ProfileNav({userId}) {
|
||||
// TODO: Don't hardcode ID.
|
||||
return (
|
||||
<TabBar>
|
||||
<TabLink to={`/profile/${userId}`}>Timeline</TabLink>
|
||||
<TabLink to={`/profile/${userId}/bio`}>Bio</TabLink>
|
||||
</TabBar>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import {createContext, useContext} from 'react';
|
||||
|
||||
const RouterContext = createContext(null);
|
||||
|
||||
export const RouterProvider = RouterContext.Provider;
|
||||
|
||||
export function useRouter() {
|
||||
return useContext(RouterContext);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {TabBar, TabLink} from './TabNav';
|
||||
|
||||
// TODO: Error Boundaries.
|
||||
|
||||
function MainTabNav() {
|
||||
return (
|
||||
<TabBar>
|
||||
<TabLink to="/">Home</TabLink>
|
||||
<TabLink to="/profile/3" partial={true}>
|
||||
Profile
|
||||
</TabLink>
|
||||
</TabBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Shell({children}) {
|
||||
return (
|
||||
<>
|
||||
<MainTabNav />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import Link from './Link';
|
||||
import {useRouter} from './RouterContext';
|
||||
|
||||
export function TabBar({children}) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid #aaa',
|
||||
padding: 20,
|
||||
marginBottom: 20,
|
||||
width: 500,
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TabLink({to, partial, children}) {
|
||||
const {pendingUrl: activeUrl} = useRouter();
|
||||
const active = partial ? activeUrl.startsWith(to) : activeUrl === to;
|
||||
if (active) {
|
||||
return (
|
||||
<b
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
marginRight: 20,
|
||||
}}>
|
||||
{children}
|
||||
</b>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
marginRight: 20,
|
||||
}}
|
||||
to={to}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
body {
|
||||
font-family: Helvetica;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {unstable_createRoot as createRoot} from 'react-dom';
|
||||
import './index.css';
|
||||
import Router from './Router';
|
||||
|
||||
createRoot(document.getElementById('root')).render(<Router />);
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {matchRoute} from './ServerRouter';
|
||||
import FeedPage from './FeedPage';
|
||||
import ProfilePage from './ProfilePage';
|
||||
|
||||
// TODO: Replace with asset reference.
|
||||
import Shell from '../client/Shell';
|
||||
|
||||
// TODO: Router component?
|
||||
const AppRoutes = {
|
||||
'/': props => <FeedPage {...props} key="home" />,
|
||||
'/profile/:userId/*': props => (
|
||||
<ProfilePage {...props} key={`profile-${props.userId}`} />
|
||||
),
|
||||
};
|
||||
|
||||
export default function App(props) {
|
||||
const match = matchRoute(props, AppRoutes);
|
||||
return <Shell>{match}</Shell>;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {fetch} from 'react-fetch';
|
||||
|
||||
// TODO: Replace with asset reference.
|
||||
import Link from '../client/Link';
|
||||
|
||||
export default function Comments({postId}) {
|
||||
const comments = fetch(`/comments?postId=${postId}&_expand=user`).json();
|
||||
return (
|
||||
<>
|
||||
<h5>Comments</h5>
|
||||
<ul>
|
||||
{comments.slice(0, 5).map(comment => (
|
||||
<li key={comment.id}>
|
||||
{comment.body}
|
||||
{' • '}
|
||||
<Link to={`/profile/${comment.user.id}`}>{comment.user.name}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {fetch} from 'react-fetch';
|
||||
import PostList from './PostList';
|
||||
|
||||
export default function Feed() {
|
||||
const posts = fetch('/posts?_expand=user').json();
|
||||
return (
|
||||
<>
|
||||
<h2>Feed</h2>
|
||||
<PostList posts={posts} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {Suspense} from 'react';
|
||||
import PostGlimmer from './PostGlimmer';
|
||||
import Feed from './Feed';
|
||||
|
||||
export default function FeedPage() {
|
||||
return (
|
||||
<Suspense fallback={<PostGlimmer />}>
|
||||
<Feed />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {Suspense} from 'react';
|
||||
import Comments from './Comments';
|
||||
|
||||
// TODO: Replace with asset reference.
|
||||
import Link from '../client/Link';
|
||||
|
||||
export default function Post({post}) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid #aaa',
|
||||
borderRadius: 10,
|
||||
marginBottom: 20,
|
||||
padding: 20,
|
||||
maxWidth: 500,
|
||||
}}>
|
||||
<h4 style={{marginTop: 0}}>
|
||||
{post.title}
|
||||
{' by '}
|
||||
<Link to={`/profile/${post.user.id}`}>{post.user.name}</Link>
|
||||
</h4>
|
||||
<p>{post.body}</p>
|
||||
<Suspense
|
||||
fallback={<h5>Loading comments...</h5>}
|
||||
unstable_avoidThisFallback={true}>
|
||||
<Comments postId={post.id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
export default function PostGlimmer() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid #aaa',
|
||||
borderRadius: 10,
|
||||
marginBottom: 20,
|
||||
padding: 20,
|
||||
maxWidth: 500,
|
||||
height: 180,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 20,
|
||||
width: '50%',
|
||||
height: 20,
|
||||
background: '#ddd',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 20,
|
||||
width: '60%',
|
||||
height: 20,
|
||||
background: '#eee',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 20,
|
||||
width: '50%',
|
||||
height: 20,
|
||||
background: '#eee',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 20,
|
||||
width: '60%',
|
||||
height: 20,
|
||||
background: '#eee',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {Suspense, unstable_SuspenseList as SuspenseList} from 'react';
|
||||
import {preload} from 'react-fetch';
|
||||
import PostGlimmer from './PostGlimmer';
|
||||
import Post from './Post';
|
||||
|
||||
export default function PostList({posts}) {
|
||||
return (
|
||||
<SuspenseList revealOrder="forwards" tail="collapsed">
|
||||
{posts.map(post => {
|
||||
preload(`/comments?postId=${post.id}&_expand=user`);
|
||||
return (
|
||||
<Suspense key={post.id} fallback={<PostGlimmer />}>
|
||||
<Post post={post} />
|
||||
</Suspense>
|
||||
);
|
||||
})}
|
||||
</SuspenseList>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {fetch} from 'react-fetch';
|
||||
|
||||
export default function ProfileBio({userId}) {
|
||||
const user = fetch(`/users/${userId}`).json();
|
||||
const bio = fetch(`/bios/${user.bioId}`).json().text;
|
||||
return (
|
||||
<>
|
||||
<h3>{user.name}'s Bio</h3>
|
||||
<p>{bio}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Suspense} from 'react';
|
||||
import {fetch} from 'react-fetch';
|
||||
import {matchRoute} from './ServerRouter';
|
||||
import ProfileTimeline from './ProfileTimeline';
|
||||
import ProfileBio from './ProfileBio';
|
||||
|
||||
// TODO: Replace with asset reference.
|
||||
import ProfileNav from '../client/ProfileNav';
|
||||
|
||||
// TODO: Router component?
|
||||
const ProfileRoutes = {
|
||||
'/': props => <ProfileTimeline {...props} key="timeline" />,
|
||||
'/bio': props => <ProfileBio {...props} key="bio" />,
|
||||
};
|
||||
|
||||
export default function ProfilePage(props) {
|
||||
const user = fetch(`/users/${props.userId}`).json();
|
||||
const match = matchRoute(props, ProfileRoutes);
|
||||
return (
|
||||
<>
|
||||
<h2>{user.name}</h2>
|
||||
<ProfileNav userId={user.id} />
|
||||
<Suspense fallback={<h3>Loading...</h3>}>{match}</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {fetch} from 'react-fetch';
|
||||
import PostList from './PostList';
|
||||
|
||||
export default function ProfileTimeline({userId}) {
|
||||
const posts = fetch(`/posts?userId=${userId}&_expand=user`).json();
|
||||
return <PostList posts={posts} />;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
function tryMatch(props, def) {
|
||||
const defSegments = def.split('/').filter(Boolean);
|
||||
const routeSegments = props.route.split('/').filter(Boolean);
|
||||
let innerProps = {...props};
|
||||
while (routeSegments.length > 0) {
|
||||
if (defSegments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const urlSegment = routeSegments.shift();
|
||||
const defSegment = defSegments.shift();
|
||||
if (urlSegment === defSegment) {
|
||||
continue;
|
||||
}
|
||||
if (defSegment[0] === ':') {
|
||||
innerProps[defSegment.slice(1)] = urlSegment;
|
||||
continue;
|
||||
}
|
||||
if (defSegment === '*') {
|
||||
innerProps.route = '/' + urlSegment + routeSegments.join('/');
|
||||
return innerProps;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (defSegments.length === 0) {
|
||||
return innerProps;
|
||||
}
|
||||
if (defSegments.length === 1 && defSegments[0] === '*') {
|
||||
innerProps.route = '/';
|
||||
return innerProps;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function matchRoute(props, defs) {
|
||||
for (let def in defs) {
|
||||
if (!defs.hasOwnProperty(def)) {
|
||||
continue;
|
||||
}
|
||||
const innerProps = tryMatch(props, def);
|
||||
if (innerProps) {
|
||||
const match = defs[def](innerProps);
|
||||
return match;
|
||||
}
|
||||
}
|
||||
throw Error('Not found.');
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -118,9 +118,7 @@ it('warns when using the wrong act version - test + dom: updates', () => {
|
||||
TestRenderer.act(() => {
|
||||
setCtr(1);
|
||||
});
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"], {
|
||||
withoutStack: true,
|
||||
});
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"]);
|
||||
});
|
||||
|
||||
it('warns when using the wrong act version - dom + test: .create()', () => {
|
||||
@@ -156,9 +154,7 @@ it('warns when using the wrong act version - dom + test: updates', () => {
|
||||
TestUtils.act(() => {
|
||||
setCtr(1);
|
||||
});
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"], {
|
||||
withoutStack: true,
|
||||
});
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"]);
|
||||
});
|
||||
|
||||
it('does not warn when nesting react-act inside react-dom', () => {
|
||||
@@ -183,7 +179,7 @@ it("doesn't warn if you use nested acts from different renderers", () => {
|
||||
|
||||
if (__EXPERIMENTAL__) {
|
||||
it('warns when using createRoot() + .render', () => {
|
||||
const root = ReactDOM.unstable_createRoot(document.createElement('div'));
|
||||
const root = ReactDOM.createRoot(document.createElement('div'));
|
||||
expect(() => {
|
||||
TestRenderer.act(() => {
|
||||
root.render(<App />);
|
||||
|
||||
@@ -71,10 +71,8 @@ class Header extends React.Component {
|
||||
<option value="/text-inputs">Text Inputs</option>
|
||||
<option value="/number-inputs">Number Input</option>
|
||||
<option value="/password-inputs">Password Input</option>
|
||||
<option value="/email-inputs">Email Input</option>
|
||||
<option value="/selects">Selects</option>
|
||||
<option value="/textareas">Textareas</option>
|
||||
<option value="/progress">Progress</option>
|
||||
<option value="/input-change-events">
|
||||
Input change events
|
||||
</option>
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import Fixture from '../../Fixture';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class EmailDisabledAttributesTestCase extends React.Component {
|
||||
state = {value: 'a@fb.com'};
|
||||
onChange = event => {
|
||||
this.setState({value: event.target.value});
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<Fixture>
|
||||
<div>{this.props.children}</div>
|
||||
|
||||
<div className="control-box">
|
||||
<fieldset>
|
||||
<legend>Controlled</legend>
|
||||
<input
|
||||
type="email"
|
||||
value={this.state.value}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
<span className="hint">
|
||||
{' '}
|
||||
Value: {JSON.stringify(this.state.value)}
|
||||
</span>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Uncontrolled</legend>
|
||||
<input type="email" defaultValue="" />
|
||||
</fieldset>
|
||||
</div>
|
||||
</Fixture>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EmailDisabledAttributesTestCase;
|
||||
@@ -1,48 +0,0 @@
|
||||
import Fixture from '../../Fixture';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class EmailAttributesTestCase extends React.Component {
|
||||
state = {value: 'a@fb.com'};
|
||||
onChange = event => {
|
||||
this.setState({value: event.target.value});
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<Fixture>
|
||||
<div>{this.props.children}</div>
|
||||
|
||||
<div className="control-box">
|
||||
<fieldset>
|
||||
<legend>Controlled</legend>
|
||||
<input
|
||||
type="email"
|
||||
pattern=".+@fb.com"
|
||||
maxlength={17}
|
||||
multiple={true}
|
||||
value={this.state.value}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
<span className="hint">
|
||||
{' '}
|
||||
Value: {JSON.stringify(this.state.value)}
|
||||
</span>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Uncontrolled</legend>
|
||||
<input
|
||||
type="email"
|
||||
defaultValue=""
|
||||
pattern=".+@fb.com"
|
||||
maxlength={17}
|
||||
multiple={true}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
</Fixture>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EmailAttributesTestCase;
|
||||
@@ -1,39 +0,0 @@
|
||||
import Fixture from '../../Fixture';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class JumpingCursorTestCase extends React.Component {
|
||||
state = {value: ''};
|
||||
onChange = event => {
|
||||
this.setState({value: event.target.value});
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<Fixture>
|
||||
<div>{this.props.children}</div>
|
||||
|
||||
<div className="control-box">
|
||||
<fieldset>
|
||||
<legend>Controlled</legend>
|
||||
<input
|
||||
type="email"
|
||||
value={this.state.value}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
<span className="hint">
|
||||
{' '}
|
||||
Value: {JSON.stringify(this.state.value)}
|
||||
</span>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Uncontrolled</legend>
|
||||
<input type="email" defaultValue="" />
|
||||
</fieldset>
|
||||
</div>
|
||||
</Fixture>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default JumpingCursorTestCase;
|
||||
@@ -1,68 +0,0 @@
|
||||
import FixtureSet from '../../FixtureSet';
|
||||
import TestCase from '../../TestCase';
|
||||
import JumpingCursorTestCase from './JumpingCursorTestCase';
|
||||
import EmailEnabledAttributesTestCase from './EmailEnabledAttributesTestCase';
|
||||
import EmailDisabledAttributesTestCase from './EmailDisabledAttributesTestCase';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
function EmailInputs() {
|
||||
return (
|
||||
<FixtureSet title="Email inputs">
|
||||
<TestCase
|
||||
title="Spaces in email inputs"
|
||||
description={`
|
||||
Some browsers are trying to remove spaces from email inputs and after
|
||||
doing this place cursor to the beginning.
|
||||
`}
|
||||
affectedBrowsers="Chrome">
|
||||
<TestCase.Steps>
|
||||
<li>Type space and character</li>
|
||||
<li>Type character, space, character, delete last character</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>Cursor not moving.</TestCase.ExpectedResult>
|
||||
|
||||
<JumpingCursorTestCase />
|
||||
</TestCase>
|
||||
|
||||
<TestCase
|
||||
title="Attributes enabled"
|
||||
description={`
|
||||
Test enabled pattern, maxlength, multiple attributes.
|
||||
`}>
|
||||
<TestCase.Steps>
|
||||
<li>Type after existing text ',b@tt.com'</li>
|
||||
<li>Try to type spaces after typed text</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
Spaces not added. When cursor hovered over input, popup "Please match
|
||||
the requested format." is showed.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<EmailEnabledAttributesTestCase />
|
||||
</TestCase>
|
||||
|
||||
<TestCase
|
||||
title="Attributes disabled"
|
||||
description={`
|
||||
Test disabled maxlength, multiple attributes.
|
||||
`}>
|
||||
<TestCase.Steps>
|
||||
<li>Type after existing text ',b@tt.com'</li>
|
||||
<li>Try to type spaces after typed text</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
Spaces are added freely. When cursor hovered over input, popup "A part
|
||||
following '@' should not contain the symbol ','." is showed.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<EmailDisabledAttributesTestCase />
|
||||
</TestCase>
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmailInputs;
|
||||
@@ -105,7 +105,7 @@ class Hydration extends React.Component {
|
||||
Version:
|
||||
<VersionPicker
|
||||
id="hydration_version"
|
||||
name="hydration_version"
|
||||
name="hyration_version"
|
||||
version={version}
|
||||
onChange={this.setVersion}
|
||||
/>
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import Fixture from '../../Fixture';
|
||||
import FixtureSet from '../../FixtureSet';
|
||||
import TestCase from '../../TestCase';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class ProgressFixture extends React.Component {
|
||||
state = {value: 0, max: 1, enabled: false, backwards: false};
|
||||
|
||||
startTest = () => {
|
||||
this.setState({enabled: true}, () => {
|
||||
this.progressIntervalId = setInterval(() => {
|
||||
if (this.state.backwards) {
|
||||
if (this.state.value > 0) {
|
||||
this.setState({value: this.state.value - this.state.max / 100});
|
||||
} else {
|
||||
if (this.state.max === 10000) {
|
||||
this.resetTest();
|
||||
} else {
|
||||
this.setState({max: this.state.max * 100, backwards: false});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.state.value < this.state.max) {
|
||||
this.setState({value: this.state.value + this.state.max / 100});
|
||||
} else {
|
||||
this.setState({backwards: true});
|
||||
}
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
};
|
||||
|
||||
resetTest = () => {
|
||||
clearInterval(this.progressIntervalId);
|
||||
this.setState({value: 0, max: 1, enabled: false, backwards: false});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FixtureSet title="Progress">
|
||||
<TestCase title="Fill and reset progress bar">
|
||||
<TestCase.Steps>
|
||||
<li>Press enable button</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
When enabled, bar value should increase from 0% to 100% and
|
||||
backwards during three step loop: 0-1, 0-100, 0-10000. Reset button
|
||||
stops loop, sets value to 0 and max to 1.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<Fixture>
|
||||
<div className="control-box">
|
||||
<fieldset>
|
||||
<legend>Controlled</legend>
|
||||
<progress
|
||||
value={this.state.value}
|
||||
max={this.state.max}></progress>
|
||||
<button
|
||||
onClick={
|
||||
this.state.enabled ? this.resetTest : this.startTest
|
||||
}>
|
||||
{this.state.enabled ? 'Reset' : 'Enable'}
|
||||
</button>
|
||||
<br />
|
||||
<span className="hint">
|
||||
{' '}
|
||||
max: {JSON.stringify(this.state.max)}
|
||||
</span>
|
||||
<span className="hint">
|
||||
{' '}
|
||||
value:{' '}
|
||||
{JSON.stringify(
|
||||
Math.round((this.state.value + Number.EPSILON) * 100) / 100
|
||||
)}
|
||||
</span>
|
||||
</fieldset>
|
||||
</div>
|
||||
</Fixture>
|
||||
</TestCase>
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ProgressFixture;
|
||||
@@ -16,8 +16,8 @@
|
||||
If you checked out the source from GitHub make sure to run <code>npm run build</code>.
|
||||
</p>
|
||||
</div>
|
||||
<script src="../../build/node_modules/react/umd/react.development.js"></script>
|
||||
<script src="../../build/node_modules/react-dom/umd/react-dom-unstable-fizz.browser.development.js"></script>
|
||||
<script src="../../build/dist/react.development.js"></script>
|
||||
<script src="../../build/dist/react-dom-unstable-fizz.browser.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<script type="text/babel">
|
||||
let stream = ReactDOMFizzServer.renderToReadableStream(<body>Success</body>);
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
If you checked out the source from GitHub make sure to run <code>npm run build</code>.
|
||||
</p>
|
||||
</div>
|
||||
<script src="../../build/node_modules/react/umd/react.development.js"></script>
|
||||
<script src="../../build/node_modules/react-dom/umd/react-dom.development.js"></script>
|
||||
<script src="../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js"></script>
|
||||
<script src="../../build/node_modules/react-transport-dom-webpack/umd/react-transport-dom-webpack-server.browser.development.js"></script>
|
||||
<script src="../../build/node_modules/react-transport-dom-webpack/umd/react-transport-dom-webpack.development.js"></script>
|
||||
<script src="../../build/dist/react.development.js"></script>
|
||||
<script src="../../build/dist/react-dom.development.js"></script>
|
||||
<script src="../../build/dist/react-dom-server.browser.development.js"></script>
|
||||
<script src="../../build/dist/react-flight-dom-webpack-server.browser.development.js"></script>
|
||||
<script src="../../build/dist/react-flight-dom-webpack.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<script type="text/babel">
|
||||
let Suspense = React.Suspense;
|
||||
@@ -57,10 +57,12 @@
|
||||
|
||||
let model = {
|
||||
title: <Title />,
|
||||
content: <HTML />,
|
||||
content: {
|
||||
__html: <HTML />,
|
||||
}
|
||||
};
|
||||
|
||||
let stream = ReactTransportDOMServer.renderToReadableStream(model);
|
||||
let stream = ReactFlightDOMServer.renderToReadableStream(model);
|
||||
let response = new Response(stream, {
|
||||
headers: {'Content-Type': 'text/html'},
|
||||
});
|
||||
@@ -70,25 +72,25 @@
|
||||
let blob = await responseToDisplay.blob();
|
||||
let url = URL.createObjectURL(blob);
|
||||
|
||||
let data = ReactTransportDOMClient.createFromFetch(
|
||||
let data = ReactFlightDOMClient.readFromFetch(
|
||||
fetch(url)
|
||||
);
|
||||
// The client also supports XHR streaming.
|
||||
// var xhr = new XMLHttpRequest();
|
||||
// xhr.open('GET', url);
|
||||
// let data = ReactTransportDOMClient.createFromXHR(xhr);
|
||||
// let data = ReactFlightDOMClient.readFromXHR(xhr);
|
||||
// xhr.send();
|
||||
|
||||
renderResult(data);
|
||||
}
|
||||
|
||||
function Shell({ data }) {
|
||||
let model = data.readRoot();
|
||||
let model = data.model;
|
||||
return <div>
|
||||
<Suspense fallback="...">
|
||||
<h1>{model.title}</h1>
|
||||
</Suspense>
|
||||
{model.content}
|
||||
<div dangerouslySetInnerHTML={model.content} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
|
||||
// Make sure that including paths.js after env.js will read .env variables.
|
||||
delete require.cache[require.resolve('./paths')];
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
if (!NODE_ENV) {
|
||||
throw new Error(
|
||||
'The NODE_ENV environment variable is required but was not specified.'
|
||||
);
|
||||
}
|
||||
|
||||
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
|
||||
const dotenvFiles = [
|
||||
`${paths.dotenv}.${NODE_ENV}.local`,
|
||||
`${paths.dotenv}.${NODE_ENV}`,
|
||||
// Don't include `.env.local` for `test` environment
|
||||
// since normally you expect tests to produce the same
|
||||
// results for everyone
|
||||
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
|
||||
paths.dotenv,
|
||||
].filter(Boolean);
|
||||
|
||||
// Load environment variables from .env* files. Suppress warnings using silent
|
||||
// if this file is missing. dotenv will never modify any environment variables
|
||||
// that have already been set. Variable expansion is supported in .env files.
|
||||
// https://github.com/motdotla/dotenv
|
||||
// https://github.com/motdotla/dotenv-expand
|
||||
dotenvFiles.forEach(dotenvFile => {
|
||||
if (fs.existsSync(dotenvFile)) {
|
||||
require('dotenv-expand')(
|
||||
require('dotenv').config({
|
||||
path: dotenvFile,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// We support resolving modules according to `NODE_PATH`.
|
||||
// This lets you use absolute paths in imports inside large monorepos:
|
||||
// https://github.com/facebook/create-react-app/issues/253.
|
||||
// It works similar to `NODE_PATH` in Node itself:
|
||||
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
|
||||
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
|
||||
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
|
||||
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
|
||||
// We also resolve them to make sure all tools using them work consistently.
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
process.env.NODE_PATH = (process.env.NODE_PATH || '')
|
||||
.split(path.delimiter)
|
||||
.filter(folder => folder && !path.isAbsolute(folder))
|
||||
.map(folder => path.resolve(appDirectory, folder))
|
||||
.join(path.delimiter);
|
||||
|
||||
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
|
||||
// injected into the application via DefinePlugin in Webpack configuration.
|
||||
const REACT_APP = /^REACT_APP_/i;
|
||||
|
||||
function getClientEnvironment(publicUrl) {
|
||||
const raw = Object.keys(process.env)
|
||||
.filter(key => REACT_APP.test(key))
|
||||
.reduce(
|
||||
(env, key) => {
|
||||
env[key] = process.env[key];
|
||||
return env;
|
||||
},
|
||||
{
|
||||
// Useful for determining whether we’re running in production mode.
|
||||
// Most importantly, it switches React into the correct mode.
|
||||
NODE_ENV: process.env.NODE_ENV || 'development',
|
||||
// Useful for resolving the correct path to static assets in `public`.
|
||||
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
|
||||
// This should only be used as an escape hatch. Normally you would put
|
||||
// images into the `src` and `import` them in code to get their paths.
|
||||
PUBLIC_URL: publicUrl,
|
||||
}
|
||||
);
|
||||
// Stringify all values so we can feed into Webpack DefinePlugin
|
||||
const stringified = {
|
||||
'process.env': Object.keys(raw).reduce((env, key) => {
|
||||
env[key] = JSON.stringify(raw[key]);
|
||||
return env;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
return {raw, stringified};
|
||||
}
|
||||
|
||||
module.exports = getClientEnvironment;
|
||||
@@ -1,14 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// This is a custom Jest transformer turning style imports into empty objects.
|
||||
// http://facebook.github.io/jest/docs/en/webpack.html
|
||||
|
||||
module.exports = {
|
||||
process() {
|
||||
return 'module.exports = {};';
|
||||
},
|
||||
getCacheKey() {
|
||||
// The output is always the same.
|
||||
return 'cssTransform';
|
||||
},
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const camelcase = require('camelcase');
|
||||
|
||||
// This is a custom Jest transformer turning file imports into filenames.
|
||||
// http://facebook.github.io/jest/docs/en/webpack.html
|
||||
|
||||
module.exports = {
|
||||
process(src, filename) {
|
||||
const assetFilename = JSON.stringify(path.basename(filename));
|
||||
|
||||
if (filename.match(/\.svg$/)) {
|
||||
// Based on how SVGR generates a component name:
|
||||
// https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
|
||||
const pascalCaseFilename = camelcase(path.parse(filename).name, {
|
||||
pascalCase: true,
|
||||
});
|
||||
const componentName = `Svg${pascalCaseFilename}`;
|
||||
return `const React = require('react');
|
||||
module.exports = {
|
||||
__esModule: true,
|
||||
default: ${assetFilename},
|
||||
ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
|
||||
return {
|
||||
$$typeof: Symbol.for('react.element'),
|
||||
type: 'svg',
|
||||
ref: ref,
|
||||
key: null,
|
||||
props: Object.assign({}, props, {
|
||||
children: ${assetFilename}
|
||||
})
|
||||
};
|
||||
}),
|
||||
};`;
|
||||
}
|
||||
|
||||
return `module.exports = ${assetFilename};`;
|
||||
},
|
||||
};
|
||||
@@ -1,141 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
const chalk = require('react-dev-utils/chalk');
|
||||
const resolve = require('resolve');
|
||||
|
||||
/**
|
||||
* Get additional module paths based on the baseUrl of a compilerOptions object.
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
function getAdditionalModulePaths(options = {}) {
|
||||
const baseUrl = options.baseUrl;
|
||||
|
||||
// We need to explicitly check for null and undefined (and not a falsy value) because
|
||||
// TypeScript treats an empty string as `.`.
|
||||
if (baseUrl == null) {
|
||||
// If there's no baseUrl set we respect NODE_PATH
|
||||
// Note that NODE_PATH is deprecated and will be removed
|
||||
// in the next major release of create-react-app.
|
||||
|
||||
const nodePath = process.env.NODE_PATH || '';
|
||||
return nodePath.split(path.delimiter).filter(Boolean);
|
||||
}
|
||||
|
||||
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
|
||||
|
||||
// We don't need to do anything if `baseUrl` is set to `node_modules`. This is
|
||||
// the default behavior.
|
||||
if (path.relative(paths.appNodeModules, baseUrlResolved) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Allow the user set the `baseUrl` to `appSrc`.
|
||||
if (path.relative(paths.appSrc, baseUrlResolved) === '') {
|
||||
return [paths.appSrc];
|
||||
}
|
||||
|
||||
// If the path is equal to the root directory we ignore it here.
|
||||
// We don't want to allow importing from the root directly as source files are
|
||||
// not transpiled outside of `src`. We do allow importing them with the
|
||||
// absolute path (e.g. `src/Components/Button.js`) but we set that up with
|
||||
// an alias.
|
||||
if (path.relative(paths.appPath, baseUrlResolved) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise, throw an error.
|
||||
throw new Error(
|
||||
chalk.red.bold(
|
||||
"Your project's `baseUrl` can only be set to `src` or `node_modules`." +
|
||||
' Create React App does not support other values at this time.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get webpack aliases based on the baseUrl of a compilerOptions object.
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
function getWebpackAliases(options = {}) {
|
||||
const baseUrl = options.baseUrl;
|
||||
|
||||
if (!baseUrl) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
|
||||
|
||||
if (path.relative(paths.appPath, baseUrlResolved) === '') {
|
||||
return {
|
||||
src: paths.appSrc,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get jest aliases based on the baseUrl of a compilerOptions object.
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
function getJestAliases(options = {}) {
|
||||
const baseUrl = options.baseUrl;
|
||||
|
||||
if (!baseUrl) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
|
||||
|
||||
if (path.relative(paths.appPath, baseUrlResolved) === '') {
|
||||
return {
|
||||
'src/(.*)$': '<rootDir>/src/$1',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getModules() {
|
||||
// Check if TypeScript is setup
|
||||
const hasTsConfig = fs.existsSync(paths.appTsConfig);
|
||||
const hasJsConfig = fs.existsSync(paths.appJsConfig);
|
||||
|
||||
if (hasTsConfig && hasJsConfig) {
|
||||
throw new Error(
|
||||
'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.'
|
||||
);
|
||||
}
|
||||
|
||||
let config;
|
||||
|
||||
// If there's a tsconfig.json we assume it's a
|
||||
// TypeScript project and set up the config
|
||||
// based on tsconfig.json
|
||||
if (hasTsConfig) {
|
||||
const ts = require(resolve.sync('typescript', {
|
||||
basedir: paths.appNodeModules,
|
||||
}));
|
||||
config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config;
|
||||
// Otherwise we'll check if there is jsconfig.json
|
||||
// for non TS projects.
|
||||
} else if (hasJsConfig) {
|
||||
config = require(paths.appJsConfig);
|
||||
}
|
||||
|
||||
config = config || {};
|
||||
const options = config.compilerOptions || {};
|
||||
|
||||
const additionalModulePaths = getAdditionalModulePaths(options);
|
||||
|
||||
return {
|
||||
additionalModulePaths: additionalModulePaths,
|
||||
webpackAliases: getWebpackAliases(options),
|
||||
jestAliases: getJestAliases(options),
|
||||
hasTsConfig,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = getModules();
|
||||
@@ -1,88 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const url = require('url');
|
||||
|
||||
// Make sure any symlinks in the project folder are resolved:
|
||||
// https://github.com/facebook/create-react-app/issues/637
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
|
||||
|
||||
const envPublicUrl = process.env.PUBLIC_URL;
|
||||
|
||||
function ensureSlash(inputPath, needsSlash) {
|
||||
const hasSlash = inputPath.endsWith('/');
|
||||
if (hasSlash && !needsSlash) {
|
||||
return inputPath.substr(0, inputPath.length - 1);
|
||||
} else if (!hasSlash && needsSlash) {
|
||||
return `${inputPath}/`;
|
||||
} else {
|
||||
return inputPath;
|
||||
}
|
||||
}
|
||||
|
||||
const getPublicUrl = appPackageJson =>
|
||||
envPublicUrl || require(appPackageJson).homepage;
|
||||
|
||||
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
|
||||
// "public path" at which the app is served.
|
||||
// Webpack needs to know it to put the right <script> hrefs into HTML even in
|
||||
// single-page apps that may serve index.html for nested URLs like /todos/42.
|
||||
// We can't use a relative path in HTML because we don't want to load something
|
||||
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
|
||||
function getServedPath(appPackageJson) {
|
||||
const publicUrl = getPublicUrl(appPackageJson);
|
||||
const servedUrl =
|
||||
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
|
||||
return ensureSlash(servedUrl, true);
|
||||
}
|
||||
|
||||
const moduleFileExtensions = [
|
||||
'web.mjs',
|
||||
'mjs',
|
||||
'web.js',
|
||||
'js',
|
||||
'web.ts',
|
||||
'ts',
|
||||
'web.tsx',
|
||||
'tsx',
|
||||
'json',
|
||||
'web.jsx',
|
||||
'jsx',
|
||||
];
|
||||
|
||||
// Resolve file paths in the same order as webpack
|
||||
const resolveModule = (resolveFn, filePath) => {
|
||||
const extension = moduleFileExtensions.find(extension =>
|
||||
fs.existsSync(resolveFn(`${filePath}.${extension}`))
|
||||
);
|
||||
|
||||
if (extension) {
|
||||
return resolveFn(`${filePath}.${extension}`);
|
||||
}
|
||||
|
||||
return resolveFn(`${filePath}.js`);
|
||||
};
|
||||
|
||||
// config after eject: we're in ./config/
|
||||
module.exports = {
|
||||
dotenv: resolveApp('.env'),
|
||||
appPath: resolveApp('.'),
|
||||
appBuild: resolveApp('build'),
|
||||
appPublic: resolveApp('public'),
|
||||
appHtml: resolveApp('public/index.html'),
|
||||
appIndexJs: resolveModule(resolveApp, 'src/index'),
|
||||
appPackageJson: resolveApp('package.json'),
|
||||
appSrc: resolveApp('src'),
|
||||
appTsConfig: resolveApp('tsconfig.json'),
|
||||
appJsConfig: resolveApp('jsconfig.json'),
|
||||
yarnLockFile: resolveApp('yarn.lock'),
|
||||
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
|
||||
proxySetup: resolveApp('src/setupProxy.js'),
|
||||
appNodeModules: resolveApp('node_modules'),
|
||||
publicUrl: getPublicUrl(resolveApp('package.json')),
|
||||
servedPath: getServedPath(resolveApp('package.json')),
|
||||
};
|
||||
|
||||
module.exports.moduleFileExtensions = moduleFileExtensions;
|
||||
@@ -1,35 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const {resolveModuleName} = require('ts-pnp');
|
||||
|
||||
exports.resolveModuleName = (
|
||||
typescript,
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost
|
||||
) => {
|
||||
return resolveModuleName(
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost,
|
||||
typescript.resolveModuleName
|
||||
);
|
||||
};
|
||||
|
||||
exports.resolveTypeReferenceDirective = (
|
||||
typescript,
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost
|
||||
) => {
|
||||
return resolveModuleName(
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost,
|
||||
typescript.resolveTypeReferenceDirective
|
||||
);
|
||||
};
|
||||
@@ -1,686 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// Fork Start
|
||||
const ReactFlightWebpackPlugin = require('react-transport-dom-webpack/plugin');
|
||||
// Fork End
|
||||
|
||||
const fs = require('fs');
|
||||
const isWsl = require('is-wsl');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const resolve = require('resolve');
|
||||
const PnpWebpackPlugin = require('pnp-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
|
||||
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const safePostCssParser = require('postcss-safe-parser');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
|
||||
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
|
||||
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
|
||||
const paths = require('./paths');
|
||||
const modules = require('./modules');
|
||||
const getClientEnvironment = require('./env');
|
||||
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
|
||||
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
|
||||
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
|
||||
const eslint = require('eslint');
|
||||
|
||||
const postcssNormalize = require('postcss-normalize');
|
||||
|
||||
const appPackageJson = require(paths.appPackageJson);
|
||||
|
||||
// Source maps are resource heavy and can cause out of memory issue for large source files.
|
||||
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
|
||||
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
|
||||
// makes for a smoother build process.
|
||||
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
|
||||
|
||||
const imageInlineSizeLimit = parseInt(
|
||||
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
|
||||
);
|
||||
|
||||
// Check if TypeScript is setup
|
||||
const useTypeScript = fs.existsSync(paths.appTsConfig);
|
||||
|
||||
// style files regexes
|
||||
const cssRegex = /\.css$/;
|
||||
const cssModuleRegex = /\.module\.css$/;
|
||||
const sassRegex = /\.(scss|sass)$/;
|
||||
const sassModuleRegex = /\.module\.(scss|sass)$/;
|
||||
|
||||
// This is the production and development configuration.
|
||||
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
|
||||
module.exports = function(webpackEnv) {
|
||||
const isEnvDevelopment = webpackEnv === 'development';
|
||||
const isEnvProduction = webpackEnv === 'production';
|
||||
|
||||
// Variable used for enabling profiling in Production
|
||||
// passed into alias object. Uses a flag if passed into the build command
|
||||
const isEnvProductionProfile =
|
||||
isEnvProduction && process.argv.includes('--profile');
|
||||
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// It requires a trailing slash, or the file assets will get an incorrect path.
|
||||
// In development, we always serve from the root. This makes config easier.
|
||||
const publicPath = isEnvProduction
|
||||
? paths.servedPath
|
||||
: isEnvDevelopment && '/';
|
||||
// Some apps do not use client-side routing with pushState.
|
||||
// For these, "homepage" can be set to "." to enable relative asset paths.
|
||||
const shouldUseRelativeAssetPaths = publicPath === './';
|
||||
|
||||
// `publicUrl` is just like `publicPath`, but we will provide it to our app
|
||||
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
|
||||
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
|
||||
const publicUrl = isEnvProduction
|
||||
? publicPath.slice(0, -1)
|
||||
: isEnvDevelopment && '';
|
||||
// Get environment variables to inject into our app.
|
||||
const env = getClientEnvironment(publicUrl);
|
||||
|
||||
// common function to get style loaders
|
||||
const getStyleLoaders = (cssOptions, preProcessor) => {
|
||||
const loaders = [
|
||||
isEnvDevelopment && require.resolve('style-loader'),
|
||||
isEnvProduction && {
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: shouldUseRelativeAssetPaths ? {publicPath: '../../'} : {},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
options: cssOptions,
|
||||
},
|
||||
{
|
||||
// Options for PostCSS as we reference these options twice
|
||||
// Adds vendor prefixing based on your specified browser support in
|
||||
// package.json
|
||||
loader: require.resolve('postcss-loader'),
|
||||
options: {
|
||||
// Necessary for external CSS imports to work
|
||||
// https://github.com/facebook/create-react-app/issues/2677
|
||||
ident: 'postcss',
|
||||
plugins: () => [
|
||||
require('postcss-flexbugs-fixes'),
|
||||
require('postcss-preset-env')({
|
||||
autoprefixer: {
|
||||
flexbox: 'no-2009',
|
||||
},
|
||||
stage: 3,
|
||||
}),
|
||||
// Adds PostCSS Normalize as the reset css with default options,
|
||||
// so that it honors browserslist config in package.json
|
||||
// which in turn let's users customize the target behavior as per their needs.
|
||||
postcssNormalize(),
|
||||
],
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
},
|
||||
},
|
||||
].filter(Boolean);
|
||||
if (preProcessor) {
|
||||
loaders.push(
|
||||
{
|
||||
loader: require.resolve('resolve-url-loader'),
|
||||
options: {
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve(preProcessor),
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
return loaders;
|
||||
};
|
||||
|
||||
return {
|
||||
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
|
||||
// Stop compilation early in production
|
||||
bail: isEnvProduction,
|
||||
devtool: isEnvProduction
|
||||
? shouldUseSourceMap
|
||||
? 'source-map'
|
||||
: false
|
||||
: isEnvDevelopment && 'cheap-module-source-map',
|
||||
// These are the "entry points" to our application.
|
||||
// This means they will be the "root" imports that are included in JS bundle.
|
||||
entry: [
|
||||
// Include an alternative client for WebpackDevServer. A client's job is to
|
||||
// connect to WebpackDevServer by a socket and get notified about changes.
|
||||
// When you save a file, the client will either apply hot updates (in case
|
||||
// of CSS changes), or refresh the page (in case of JS changes). When you
|
||||
// make a syntax error, this client will display a syntax error overlay.
|
||||
// Note: instead of the default WebpackDevServer client, we use a custom one
|
||||
// to bring better experience for Create React App users. You can replace
|
||||
// the line below with these two lines if you prefer the stock client:
|
||||
// require.resolve('webpack-dev-server/client') + '?/',
|
||||
// require.resolve('webpack/hot/dev-server'),
|
||||
isEnvDevelopment &&
|
||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||
// Finally, this is your app's code:
|
||||
paths.appIndexJs,
|
||||
// We include the app code last so that if there is a runtime error during
|
||||
// initialization, it doesn't blow up the WebpackDevServer client, and
|
||||
// changing JS code would still trigger a refresh.
|
||||
].filter(Boolean),
|
||||
output: {
|
||||
// The build folder.
|
||||
path: isEnvProduction ? paths.appBuild : undefined,
|
||||
// Add /* filename */ comments to generated require()s in the output.
|
||||
pathinfo: isEnvDevelopment,
|
||||
// There will be one main bundle, and one file per asynchronous chunk.
|
||||
// In development, it does not produce real files.
|
||||
filename: isEnvProduction
|
||||
? 'static/js/[name].[contenthash:8].js'
|
||||
: isEnvDevelopment && 'static/js/bundle.js',
|
||||
// TODO: remove this when upgrading to webpack 5
|
||||
futureEmitAssets: true,
|
||||
// There are also additional JS chunk files if you use code splitting.
|
||||
chunkFilename: isEnvProduction
|
||||
? 'static/js/[name].[contenthash:8].chunk.js'
|
||||
: isEnvDevelopment && 'static/js/[name].chunk.js',
|
||||
// We inferred the "public path" (such as / or /my-project) from homepage.
|
||||
// We use "/" in development.
|
||||
publicPath: publicPath,
|
||||
// Point sourcemap entries to original disk location (format as URL on Windows)
|
||||
devtoolModuleFilenameTemplate: isEnvProduction
|
||||
? info =>
|
||||
path
|
||||
.relative(paths.appSrc, info.absoluteResourcePath)
|
||||
.replace(/\\/g, '/')
|
||||
: isEnvDevelopment &&
|
||||
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
|
||||
// Prevents conflicts when multiple Webpack runtimes (from different apps)
|
||||
// are used on the same page.
|
||||
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
|
||||
// this defaults to 'window', but by setting it to 'this' then
|
||||
// module chunks which are built will work in web workers as well.
|
||||
globalObject: 'this',
|
||||
},
|
||||
optimization: {
|
||||
minimize: isEnvProduction,
|
||||
minimizer: [
|
||||
// This is only used in production mode
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
parse: {
|
||||
// We want terser to parse ecma 8 code. However, we don't want it
|
||||
// to apply any minification steps that turns valid ecma 5 code
|
||||
// into invalid ecma 5 code. This is why the 'compress' and 'output'
|
||||
// sections only apply transformations that are ecma 5 safe
|
||||
// https://github.com/facebook/create-react-app/pull/4234
|
||||
ecma: 8,
|
||||
},
|
||||
compress: {
|
||||
ecma: 5,
|
||||
warnings: false,
|
||||
// Disabled because of an issue with Uglify breaking seemingly valid code:
|
||||
// https://github.com/facebook/create-react-app/issues/2376
|
||||
// Pending further investigation:
|
||||
// https://github.com/mishoo/UglifyJS2/issues/2011
|
||||
comparisons: false,
|
||||
// Disabled because of an issue with Terser breaking valid code:
|
||||
// https://github.com/facebook/create-react-app/issues/5250
|
||||
// Pending further investigation:
|
||||
// https://github.com/terser-js/terser/issues/120
|
||||
inline: 2,
|
||||
},
|
||||
mangle: {
|
||||
safari10: true,
|
||||
},
|
||||
// Added for profiling in devtools
|
||||
keep_classnames: isEnvProductionProfile,
|
||||
keep_fnames: isEnvProductionProfile,
|
||||
output: {
|
||||
ecma: 5,
|
||||
comments: false,
|
||||
// Turned on because emoji and regex is not minified properly using default
|
||||
// https://github.com/facebook/create-react-app/issues/2488
|
||||
ascii_only: true,
|
||||
},
|
||||
},
|
||||
// Use multi-process parallel running to improve the build speed
|
||||
// Default number of concurrent runs: os.cpus().length - 1
|
||||
// Disabled on WSL (Windows Subsystem for Linux) due to an issue with Terser
|
||||
// https://github.com/webpack-contrib/terser-webpack-plugin/issues/21
|
||||
parallel: !isWsl,
|
||||
// Enable file caching
|
||||
cache: true,
|
||||
sourceMap: shouldUseSourceMap,
|
||||
}),
|
||||
// This is only used in production mode
|
||||
new OptimizeCSSAssetsPlugin({
|
||||
cssProcessorOptions: {
|
||||
parser: safePostCssParser,
|
||||
map: shouldUseSourceMap
|
||||
? {
|
||||
// `inline: false` forces the sourcemap to be output into a
|
||||
// separate file
|
||||
inline: false,
|
||||
// `annotation: true` appends the sourceMappingURL to the end of
|
||||
// the css file, helping the browser find the sourcemap
|
||||
annotation: true,
|
||||
}
|
||||
: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
// Automatically split vendor and commons
|
||||
// https://twitter.com/wSokra/status/969633336732905474
|
||||
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
name: false,
|
||||
},
|
||||
// Keep the runtime chunk separated to enable long term caching
|
||||
// https://twitter.com/wSokra/status/969679223278505985
|
||||
// https://github.com/facebook/create-react-app/issues/5358
|
||||
runtimeChunk: {
|
||||
name: entrypoint => `runtime-${entrypoint.name}`,
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
// This allows you to set a fallback for where Webpack should look for modules.
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebook/create-react-app/issues/253
|
||||
modules: ['node_modules', paths.appNodeModules].concat(
|
||||
modules.additionalModulePaths || []
|
||||
),
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
// https://github.com/facebook/create-react-app/issues/290
|
||||
// `web` extension prefixes have been added for better support
|
||||
// for React Native Web.
|
||||
extensions: paths.moduleFileExtensions
|
||||
.map(ext => `.${ext}`)
|
||||
.filter(ext => useTypeScript || !ext.includes('ts')),
|
||||
alias: {
|
||||
// Support React Native Web
|
||||
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
|
||||
'react-native': 'react-native-web',
|
||||
// Allows for better profiling with ReactDevTools
|
||||
...(isEnvProductionProfile && {
|
||||
'react-dom$': 'react-dom/profiling',
|
||||
'scheduler/tracing': 'scheduler/tracing-profiling',
|
||||
}),
|
||||
...(modules.webpackAliases || {}),
|
||||
},
|
||||
plugins: [
|
||||
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
|
||||
// guards against forgotten dependencies and such.
|
||||
PnpWebpackPlugin,
|
||||
// Prevents users from importing files from outside of src/ (or node_modules/).
|
||||
// This often causes confusion because we only process files within src/ with babel.
|
||||
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
|
||||
// please link the files into your node_modules/ and let module-resolution kick in.
|
||||
// Make sure your source files are compiled, as they will not be processed in any way.
|
||||
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
|
||||
],
|
||||
},
|
||||
resolveLoader: {
|
||||
plugins: [
|
||||
// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
|
||||
// from the current package.
|
||||
PnpWebpackPlugin.moduleLoader(module),
|
||||
],
|
||||
},
|
||||
module: {
|
||||
strictExportPresence: true,
|
||||
rules: [
|
||||
// Disable require.ensure as it's not a standard language feature.
|
||||
{parser: {requireEnsure: false}},
|
||||
|
||||
// First, run the linter.
|
||||
// It's important to do this before Babel processes the JS.
|
||||
{
|
||||
test: /\.(js|mjs|jsx|ts|tsx)$/,
|
||||
enforce: 'pre',
|
||||
use: [
|
||||
{
|
||||
options: {
|
||||
cache: true,
|
||||
formatter: require.resolve('react-dev-utils/eslintFormatter'),
|
||||
eslintPath: require.resolve('eslint'),
|
||||
resolvePluginsRelativeTo: __dirname,
|
||||
},
|
||||
loader: require.resolve('eslint-loader'),
|
||||
},
|
||||
],
|
||||
include: paths.appSrc,
|
||||
},
|
||||
{
|
||||
// "oneOf" will traverse all following loaders until one will
|
||||
// match the requirements. When no loader matches it will fall
|
||||
// back to the "file" loader at the end of the loader list.
|
||||
oneOf: [
|
||||
// "url" loader works like "file" loader except that it embeds assets
|
||||
// smaller than specified limit in bytes as data URLs to avoid requests.
|
||||
// A missing `test` is equivalent to a match.
|
||||
{
|
||||
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: imageInlineSizeLimit,
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// Process application JS with Babel.
|
||||
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
|
||||
{
|
||||
test: /\.(js|mjs|jsx|ts|tsx)$/,
|
||||
include: paths.appSrc,
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: {
|
||||
customize: require.resolve(
|
||||
'babel-preset-react-app/webpack-overrides'
|
||||
),
|
||||
|
||||
plugins: [
|
||||
[
|
||||
require.resolve('babel-plugin-named-asset-import'),
|
||||
{
|
||||
loaderMap: {
|
||||
svg: {
|
||||
ReactComponent:
|
||||
'@svgr/webpack?-svgo,+titleProp,+ref![path]',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables caching results in ./node_modules/.cache/babel-loader/
|
||||
// directory for faster rebuilds.
|
||||
cacheDirectory: true,
|
||||
// See #6846 for context on why cacheCompression is disabled
|
||||
cacheCompression: false,
|
||||
compact: isEnvProduction,
|
||||
},
|
||||
},
|
||||
// Process any JS outside of the app with Babel.
|
||||
// Unlike the application JS, we only compile the standard ES features.
|
||||
{
|
||||
test: /\.(js|mjs)$/,
|
||||
exclude: /@babel(?:\/|\\{1,2})runtime/,
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
compact: false,
|
||||
presets: [
|
||||
[
|
||||
require.resolve('babel-preset-react-app/dependencies'),
|
||||
{helpers: true},
|
||||
],
|
||||
],
|
||||
cacheDirectory: true,
|
||||
// See #6846 for context on why cacheCompression is disabled
|
||||
cacheCompression: false,
|
||||
|
||||
// If an error happens in a package, it's possible to be
|
||||
// because it was compiled. Thus, we don't want the browser
|
||||
// debugger to show the original code. Instead, the code
|
||||
// being evaluated would be much more helpful.
|
||||
sourceMaps: false,
|
||||
},
|
||||
},
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader turns CSS into JS modules that inject <style> tags.
|
||||
// In production, we use MiniCSSExtractPlugin to extract that CSS
|
||||
// to a file, but in development "style" loader enables hot editing
|
||||
// of CSS.
|
||||
// By default we support CSS Modules with the extension .module.css
|
||||
{
|
||||
test: cssRegex,
|
||||
exclude: cssModuleRegex,
|
||||
use: getStyleLoaders({
|
||||
importLoaders: 1,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
}),
|
||||
// Don't consider CSS imports dead code even if the
|
||||
// containing package claims to have no side effects.
|
||||
// Remove this when webpack adds a warning or an error for this.
|
||||
// See https://github.com/webpack/webpack/issues/6571
|
||||
sideEffects: true,
|
||||
},
|
||||
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
|
||||
// using the extension .module.css
|
||||
{
|
||||
test: cssModuleRegex,
|
||||
use: getStyleLoaders({
|
||||
importLoaders: 1,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
modules: true,
|
||||
getLocalIdent: getCSSModuleLocalIdent,
|
||||
}),
|
||||
},
|
||||
// Opt-in support for SASS (using .scss or .sass extensions).
|
||||
// By default we support SASS Modules with the
|
||||
// extensions .module.scss or .module.sass
|
||||
{
|
||||
test: sassRegex,
|
||||
exclude: sassModuleRegex,
|
||||
use: getStyleLoaders(
|
||||
{
|
||||
importLoaders: 2,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
},
|
||||
'sass-loader'
|
||||
),
|
||||
// Don't consider CSS imports dead code even if the
|
||||
// containing package claims to have no side effects.
|
||||
// Remove this when webpack adds a warning or an error for this.
|
||||
// See https://github.com/webpack/webpack/issues/6571
|
||||
sideEffects: true,
|
||||
},
|
||||
// Adds support for CSS Modules, but using SASS
|
||||
// using the extension .module.scss or .module.sass
|
||||
{
|
||||
test: sassModuleRegex,
|
||||
use: getStyleLoaders(
|
||||
{
|
||||
importLoaders: 2,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
modules: true,
|
||||
getLocalIdent: getCSSModuleLocalIdent,
|
||||
},
|
||||
'sass-loader'
|
||||
),
|
||||
},
|
||||
// "file" loader makes sure those assets get served by WebpackDevServer.
|
||||
// When you `import` an asset, you get its (virtual) filename.
|
||||
// In production, they would get copied to the `build` folder.
|
||||
// This loader doesn't use a "test" so it will catch all modules
|
||||
// that fall through the other loaders.
|
||||
{
|
||||
loader: require.resolve('file-loader'),
|
||||
// Exclude `js` files to keep "css" loader working as it injects
|
||||
// its runtime that would otherwise be processed through "file" loader.
|
||||
// Also exclude `html` and `json` extensions so they get processed
|
||||
// by webpacks internal loaders.
|
||||
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
|
||||
options: {
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// ** STOP ** Are you adding a new loader?
|
||||
// Make sure to add the new loader(s) before the "file" loader.
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin(
|
||||
Object.assign(
|
||||
{},
|
||||
{
|
||||
inject: true,
|
||||
template: paths.appHtml,
|
||||
},
|
||||
isEnvProduction
|
||||
? {
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true,
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
),
|
||||
// Inlines the webpack runtime script. This script is too small to warrant
|
||||
// a network request.
|
||||
// https://github.com/facebook/create-react-app/issues/5358
|
||||
isEnvProduction &&
|
||||
shouldInlineRuntimeChunk &&
|
||||
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
|
||||
// Makes some environment variables available in index.html.
|
||||
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In production, it will be an empty string unless you specify "homepage"
|
||||
// in `package.json`, in which case it will be the pathname of that URL.
|
||||
// In development, this will be an empty string.
|
||||
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
|
||||
// This gives some necessary context to module not found errors, such as
|
||||
// the requesting resource.
|
||||
new ModuleNotFoundPlugin(paths.appPath),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
|
||||
// It is absolutely essential that NODE_ENV is set to production
|
||||
// during a production build.
|
||||
// Otherwise React will be compiled in the very slow development mode.
|
||||
new webpack.DefinePlugin(env.stringified),
|
||||
// This is necessary to emit hot updates (currently CSS only):
|
||||
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
|
||||
// Watcher doesn't work well if you mistype casing in a path so we use
|
||||
// a plugin that prints an error when you attempt to do this.
|
||||
// See https://github.com/facebook/create-react-app/issues/240
|
||||
isEnvDevelopment && new CaseSensitivePathsPlugin(),
|
||||
// If you require a missing module and then `npm install` it, you still have
|
||||
// to restart the development server for Webpack to discover it. This plugin
|
||||
// makes the discovery automatic so you don't have to restart.
|
||||
// See https://github.com/facebook/create-react-app/issues/186
|
||||
isEnvDevelopment &&
|
||||
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
|
||||
isEnvProduction &&
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
// both options are optional
|
||||
filename: 'static/css/[name].[contenthash:8].css',
|
||||
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
|
||||
}),
|
||||
// Generate an asset manifest file with the following content:
|
||||
// - "files" key: Mapping of all asset filenames to their corresponding
|
||||
// output file so that tools can pick it up without having to parse
|
||||
// `index.html`
|
||||
// - "entrypoints" key: Array of files which are included in `index.html`,
|
||||
// can be used to reconstruct the HTML if necessary
|
||||
new ManifestPlugin({
|
||||
fileName: 'asset-manifest.json',
|
||||
publicPath: publicPath,
|
||||
generate: (seed, files, entrypoints) => {
|
||||
const manifestFiles = files.reduce((manifest, file) => {
|
||||
manifest[file.name] = file.path;
|
||||
return manifest;
|
||||
}, seed);
|
||||
const entrypointFiles = entrypoints.main.filter(
|
||||
fileName => !fileName.endsWith('.map')
|
||||
);
|
||||
|
||||
return {
|
||||
files: manifestFiles,
|
||||
entrypoints: entrypointFiles,
|
||||
};
|
||||
},
|
||||
}),
|
||||
// Moment.js is an extremely popular library that bundles large locale files
|
||||
// by default due to how Webpack interprets its code. This is a practical
|
||||
// solution that requires the user to opt into importing specific locales.
|
||||
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
|
||||
// You can remove this if you don't use Moment.js:
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
// Generate a service worker script that will precache, and keep up to date,
|
||||
// the HTML & assets that are part of the Webpack build.
|
||||
isEnvProduction &&
|
||||
new WorkboxWebpackPlugin.GenerateSW({
|
||||
clientsClaim: true,
|
||||
exclude: [/\.map$/, /asset-manifest\.json$/],
|
||||
importWorkboxFrom: 'cdn',
|
||||
navigateFallback: publicUrl + '/index.html',
|
||||
navigateFallbackBlacklist: [
|
||||
// Exclude URLs starting with /_, as they're likely an API call
|
||||
new RegExp('^/_'),
|
||||
// Exclude any URLs whose last part seems to be a file extension
|
||||
// as they're likely a resource and not a SPA route.
|
||||
// URLs containing a "?" character won't be blacklisted as they're likely
|
||||
// a route with query params (e.g. auth callbacks).
|
||||
new RegExp('/[^/?]+\\.[^/]+$'),
|
||||
],
|
||||
}),
|
||||
// TypeScript type checking
|
||||
useTypeScript &&
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
typescript: resolve.sync('typescript', {
|
||||
basedir: paths.appNodeModules,
|
||||
}),
|
||||
async: isEnvDevelopment,
|
||||
useTypescriptIncrementalApi: true,
|
||||
checkSyntacticErrors: true,
|
||||
resolveModuleNameModule: process.versions.pnp
|
||||
? `${__dirname}/pnpTs.js`
|
||||
: undefined,
|
||||
resolveTypeReferenceDirectiveModule: process.versions.pnp
|
||||
? `${__dirname}/pnpTs.js`
|
||||
: undefined,
|
||||
tsconfig: paths.appTsConfig,
|
||||
reportFiles: [
|
||||
'**',
|
||||
'!**/__tests__/**',
|
||||
'!**/?(*.)(spec|test).*',
|
||||
'!**/src/setupProxy.*',
|
||||
'!**/src/setupTests.*',
|
||||
],
|
||||
silent: true,
|
||||
// The formatter is invoked directly in WebpackDevServerUtils during development
|
||||
formatter: isEnvProduction ? typescriptFormatter : undefined,
|
||||
}),
|
||||
// Fork Start
|
||||
new ReactFlightWebpackPlugin({isServer: false}),
|
||||
// Fork End
|
||||
].filter(Boolean),
|
||||
// Some libraries import Node modules but don't use them in the browser.
|
||||
// Tell Webpack to provide empty mocks for them so importing them works.
|
||||
node: {
|
||||
module: 'empty',
|
||||
dgram: 'empty',
|
||||
dns: 'mock',
|
||||
fs: 'empty',
|
||||
http2: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
child_process: 'empty',
|
||||
},
|
||||
// Turn off performance processing because we utilize
|
||||
// our own hints via the FileSizeReporter
|
||||
performance: false,
|
||||
};
|
||||
};
|
||||
@@ -1,104 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
|
||||
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
|
||||
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
|
||||
const ignoredFiles = require('react-dev-utils/ignoredFiles');
|
||||
const paths = require('./paths');
|
||||
const fs = require('fs');
|
||||
|
||||
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||
const host = process.env.HOST || '0.0.0.0';
|
||||
|
||||
module.exports = function(proxy, allowedHost) {
|
||||
return {
|
||||
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
|
||||
// websites from potentially accessing local content through DNS rebinding:
|
||||
// https://github.com/webpack/webpack-dev-server/issues/887
|
||||
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
|
||||
// However, it made several existing use cases such as development in cloud
|
||||
// environment or subdomains in development significantly more complicated:
|
||||
// https://github.com/facebook/create-react-app/issues/2271
|
||||
// https://github.com/facebook/create-react-app/issues/2233
|
||||
// While we're investigating better solutions, for now we will take a
|
||||
// compromise. Since our WDS configuration only serves files in the `public`
|
||||
// folder we won't consider accessing them a vulnerability. However, if you
|
||||
// use the `proxy` feature, it gets more dangerous because it can expose
|
||||
// remote code execution vulnerabilities in backends like Django and Rails.
|
||||
// So we will disable the host check normally, but enable it if you have
|
||||
// specified the `proxy` setting. Finally, we let you override it if you
|
||||
// really know what you're doing with a special environment variable.
|
||||
disableHostCheck:
|
||||
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
|
||||
// Enable gzip compression of generated files.
|
||||
compress: true,
|
||||
// Silence WebpackDevServer's own logs since they're generally not useful.
|
||||
// It will still show compile warnings and errors with this setting.
|
||||
clientLogLevel: 'none',
|
||||
// By default WebpackDevServer serves physical files from current directory
|
||||
// in addition to all the virtual build products that it serves from memory.
|
||||
// This is confusing because those files won’t automatically be available in
|
||||
// production build folder unless we copy them. However, copying the whole
|
||||
// project directory is dangerous because we may expose sensitive files.
|
||||
// Instead, we establish a convention that only files in `public` directory
|
||||
// get served. Our build script will copy `public` into the `build` folder.
|
||||
// In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
|
||||
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
|
||||
// Note that we only recommend to use `public` folder as an escape hatch
|
||||
// for files like `favicon.ico`, `manifest.json`, and libraries that are
|
||||
// for some reason broken when imported through Webpack. If you just want to
|
||||
// use an image, put it in `src` and `import` it from JavaScript instead.
|
||||
contentBase: paths.appPublic,
|
||||
// By default files from `contentBase` will not trigger a page reload.
|
||||
watchContentBase: true,
|
||||
// Enable hot reloading server. It will provide /sockjs-node/ endpoint
|
||||
// for the WebpackDevServer client so it can learn when the files were
|
||||
// updated. The WebpackDevServer client is included as an entry point
|
||||
// in the Webpack development configuration. Note that only changes
|
||||
// to CSS are currently hot reloaded. JS changes will refresh the browser.
|
||||
hot: true,
|
||||
// It is important to tell WebpackDevServer to use the same "root" path
|
||||
// as we specified in the config. In development, we always serve from /.
|
||||
publicPath: '/',
|
||||
// WebpackDevServer is noisy by default so we emit custom message instead
|
||||
// by listening to the compiler events with `compiler.hooks[...].tap` calls above.
|
||||
quiet: true,
|
||||
// Reportedly, this avoids CPU overload on some systems.
|
||||
// https://github.com/facebook/create-react-app/issues/293
|
||||
// src/node_modules is not ignored to support absolute imports
|
||||
// https://github.com/facebook/create-react-app/issues/1065
|
||||
watchOptions: {
|
||||
ignored: ignoredFiles(paths.appSrc),
|
||||
},
|
||||
// Enable HTTPS if the HTTPS environment variable is set to 'true'
|
||||
https: protocol === 'https',
|
||||
host,
|
||||
overlay: false,
|
||||
historyApiFallback: {
|
||||
// Paths with dots should still use the history fallback.
|
||||
// See https://github.com/facebook/create-react-app/issues/387.
|
||||
disableDotRule: true,
|
||||
},
|
||||
public: allowedHost,
|
||||
proxy,
|
||||
before(app, server) {
|
||||
if (fs.existsSync(paths.proxySetup)) {
|
||||
// This registers user provided middleware for proxy reasons
|
||||
require(paths.proxySetup)(app);
|
||||
}
|
||||
|
||||
// This lets us fetch source contents from webpack for the error overlay
|
||||
app.use(evalSourceMapMiddleware(server));
|
||||
// This lets us open files from the runtime error overlay.
|
||||
app.use(errorOverlayMiddleware());
|
||||
|
||||
// This service worker file is effectively a 'no-op' that will reset any
|
||||
// previous service worker registered for the same host:port combination.
|
||||
// We do this in development to avoid hitting the production cache if
|
||||
// it used the same host and port.
|
||||
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
|
||||
app.use(noopServiceWorkerMiddleware());
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -3,72 +3,21 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.6.0",
|
||||
"@babel/register": "^7.7.0",
|
||||
"@svgr/webpack": "4.3.2",
|
||||
"@typescript-eslint/eslint-plugin": "^2.2.0",
|
||||
"@typescript-eslint/parser": "^2.2.0",
|
||||
"babel-eslint": "10.0.3",
|
||||
"babel-jest": "^24.9.0",
|
||||
"babel-loader": "8.0.6",
|
||||
"babel-plugin-named-asset-import": "^0.3.4",
|
||||
"babel-preset-react-app": "^9.0.2",
|
||||
"camelcase": "^5.2.0",
|
||||
"case-sensitive-paths-webpack-plugin": "2.2.0",
|
||||
"concurrently": "^5.0.0",
|
||||
"css-loader": "2.1.1",
|
||||
"dotenv": "6.2.0",
|
||||
"dotenv-expand": "5.1.0",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint-config-react-app": "^5.0.2",
|
||||
"eslint-loader": "3.0.2",
|
||||
"eslint-plugin-flowtype": "3.13.0",
|
||||
"eslint-plugin-import": "2.18.2",
|
||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||
"eslint-plugin-react": "7.14.3",
|
||||
"eslint-plugin-react-hooks": "^1.6.1",
|
||||
"express": "^4.17.1",
|
||||
"file-loader": "3.0.1",
|
||||
"fs-extra": "7.0.1",
|
||||
"html-webpack-plugin": "4.0.0-beta.5",
|
||||
"identity-obj-proxy": "3.0.0",
|
||||
"is-wsl": "^1.1.0",
|
||||
"jest": "24.9.0",
|
||||
"jest-environment-jsdom-fourteen": "0.1.0",
|
||||
"jest-resolve": "24.9.0",
|
||||
"jest-watch-typeahead": "0.4.0",
|
||||
"mini-css-extract-plugin": "0.8.0",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||
"pnp-webpack-plugin": "1.5.0",
|
||||
"postcss-flexbugs-fixes": "4.1.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
"postcss-normalize": "7.0.1",
|
||||
"postcss-preset-env": "6.7.0",
|
||||
"postcss-safe-parser": "4.0.1",
|
||||
"react-app-polyfill": "^1.0.4",
|
||||
"react-dev-utils": "^9.1.0",
|
||||
"resolve": "1.12.0",
|
||||
"resolve-url-loader": "3.1.0",
|
||||
"sass-loader": "7.2.0",
|
||||
"semver": "6.3.0",
|
||||
"style-loader": "1.0.0",
|
||||
"terser-webpack-plugin": "1.4.1",
|
||||
"ts-pnp": "1.1.4",
|
||||
"url-loader": "2.1.0",
|
||||
"webpack": "4.41.0",
|
||||
"webpack-dev-server": "3.2.1",
|
||||
"webpack-manifest-plugin": "2.1.1",
|
||||
"workbox-webpack-plugin": "4.3.1"
|
||||
"react-scripts": "3.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prestart": "cp -r ../../build/node_modules/* ./node_modules/",
|
||||
"prebuild": "cp -r ../../build/node_modules/* ./node_modules/",
|
||||
"start": "concurrently \"npm run start:server\" \"npm run start:client\"",
|
||||
"start:client": "node scripts/start.js",
|
||||
"start:client": "react-scripts start",
|
||||
"start:server": "NODE_ENV=development node server",
|
||||
"start:prod": "node scripts/build.js && NODE_ENV=production node server",
|
||||
"build": "node scripts/build.js",
|
||||
"test": "node scripts/test.js --env=jsdom"
|
||||
"start:prod": "react-scripts build && NODE_ENV=production node server",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
@@ -84,58 +33,5 @@
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"jest": {
|
||||
"roots": [
|
||||
"<rootDir>/src"
|
||||
],
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.{js,jsx,ts,tsx}",
|
||||
"!src/**/*.d.ts"
|
||||
],
|
||||
"setupFiles": [
|
||||
"react-app-polyfill/jsdom"
|
||||
],
|
||||
"setupFilesAfterEnv": [],
|
||||
"testMatch": [
|
||||
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
|
||||
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
|
||||
],
|
||||
"testEnvironment": "jest-environment-jsdom-fourteen",
|
||||
"transform": {
|
||||
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
|
||||
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
|
||||
"^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
|
||||
"^.+\\.module\\.(css|sass|scss)$"
|
||||
],
|
||||
"modulePaths": [],
|
||||
"moduleNameMapper": {
|
||||
"^react-native$": "react-native-web",
|
||||
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
|
||||
},
|
||||
"moduleFileExtensions": [
|
||||
"web.js",
|
||||
"js",
|
||||
"web.ts",
|
||||
"ts",
|
||||
"web.tsx",
|
||||
"tsx",
|
||||
"json",
|
||||
"web.jsx",
|
||||
"jsx",
|
||||
"node"
|
||||
],
|
||||
"watchPlugins": [
|
||||
"jest-watch-typeahead/filename",
|
||||
"jest-watch-typeahead/testname"
|
||||
]
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"react-app"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'production';
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
const path = require('path');
|
||||
const chalk = require('react-dev-utils/chalk');
|
||||
const fs = require('fs-extra');
|
||||
const webpack = require('webpack');
|
||||
const configFactory = require('../config/webpack.config');
|
||||
const paths = require('../config/paths');
|
||||
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
|
||||
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
|
||||
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
|
||||
const printBuildError = require('react-dev-utils/printBuildError');
|
||||
|
||||
const measureFileSizesBeforeBuild =
|
||||
FileSizeReporter.measureFileSizesBeforeBuild;
|
||||
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
|
||||
const useYarn = fs.existsSync(paths.yarnLockFile);
|
||||
|
||||
// These sizes are pretty large. We'll warn for bundles exceeding them.
|
||||
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
|
||||
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
|
||||
|
||||
const isInteractive = process.stdout.isTTY;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Generate configuration
|
||||
const config = configFactory('production');
|
||||
|
||||
// We require that you explicitly set browsers and do not fall back to
|
||||
// browserslist defaults.
|
||||
const {checkBrowsers} = require('react-dev-utils/browsersHelper');
|
||||
checkBrowsers(paths.appPath, isInteractive)
|
||||
.then(() => {
|
||||
// First, read the current file sizes in build directory.
|
||||
// This lets us display how much they changed later.
|
||||
return measureFileSizesBeforeBuild(paths.appBuild);
|
||||
})
|
||||
.then(previousFileSizes => {
|
||||
// Remove all content but keep the directory so that
|
||||
// if you're in it, you don't end up in Trash
|
||||
fs.emptyDirSync(paths.appBuild);
|
||||
// Merge with the public folder
|
||||
copyPublicFolder();
|
||||
// Start the webpack build
|
||||
return build(previousFileSizes);
|
||||
})
|
||||
.then(
|
||||
({stats, previousFileSizes, warnings}) => {
|
||||
if (warnings.length) {
|
||||
console.log(chalk.yellow('Compiled with warnings.\n'));
|
||||
console.log(warnings.join('\n\n'));
|
||||
console.log(
|
||||
'\nSearch for the ' +
|
||||
chalk.underline(chalk.yellow('keywords')) +
|
||||
' to learn more about each warning.'
|
||||
);
|
||||
console.log(
|
||||
'To ignore, add ' +
|
||||
chalk.cyan('// eslint-disable-next-line') +
|
||||
' to the line before.\n'
|
||||
);
|
||||
} else {
|
||||
console.log(chalk.green('Compiled successfully.\n'));
|
||||
}
|
||||
|
||||
console.log('File sizes after gzip:\n');
|
||||
printFileSizesAfterBuild(
|
||||
stats,
|
||||
previousFileSizes,
|
||||
paths.appBuild,
|
||||
WARN_AFTER_BUNDLE_GZIP_SIZE,
|
||||
WARN_AFTER_CHUNK_GZIP_SIZE
|
||||
);
|
||||
console.log();
|
||||
|
||||
const appPackage = require(paths.appPackageJson);
|
||||
const publicUrl = paths.publicUrl;
|
||||
const publicPath = config.output.publicPath;
|
||||
const buildFolder = path.relative(process.cwd(), paths.appBuild);
|
||||
printHostingInstructions(
|
||||
appPackage,
|
||||
publicUrl,
|
||||
publicPath,
|
||||
buildFolder,
|
||||
useYarn
|
||||
);
|
||||
},
|
||||
err => {
|
||||
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
|
||||
if (tscCompileOnError) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Compiled with the following type errors (you may want to check these before deploying your app):\n'
|
||||
)
|
||||
);
|
||||
printBuildError(err);
|
||||
} else {
|
||||
console.log(chalk.red('Failed to compile.\n'));
|
||||
printBuildError(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
)
|
||||
.catch(err => {
|
||||
if (err && err.message) {
|
||||
console.log(err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Create the production build and print the deployment instructions.
|
||||
function build(previousFileSizes) {
|
||||
// We used to support resolving modules according to `NODE_PATH`.
|
||||
// This now has been deprecated in favor of jsconfig/tsconfig.json
|
||||
// This lets you use absolute paths in imports inside large monorepos:
|
||||
if (process.env.NODE_PATH) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
|
||||
)
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
console.log('Creating an optimized production build...');
|
||||
|
||||
const compiler = webpack(config);
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
let messages;
|
||||
if (err) {
|
||||
if (!err.message) {
|
||||
return reject(err);
|
||||
}
|
||||
messages = formatWebpackMessages({
|
||||
errors: [err.message],
|
||||
warnings: [],
|
||||
});
|
||||
} else {
|
||||
messages = formatWebpackMessages(
|
||||
stats.toJson({all: false, warnings: true, errors: true})
|
||||
);
|
||||
}
|
||||
if (messages.errors.length) {
|
||||
// Only keep the first error. Others are often indicative
|
||||
// of the same problem, but confuse the reader with noise.
|
||||
if (messages.errors.length > 1) {
|
||||
messages.errors.length = 1;
|
||||
}
|
||||
return reject(new Error(messages.errors.join('\n\n')));
|
||||
}
|
||||
if (
|
||||
process.env.CI &&
|
||||
(typeof process.env.CI !== 'string' ||
|
||||
process.env.CI.toLowerCase() !== 'false') &&
|
||||
messages.warnings.length
|
||||
) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'\nTreating warnings as errors because process.env.CI = true.\n' +
|
||||
'Most CI servers set it automatically.\n'
|
||||
)
|
||||
);
|
||||
return reject(new Error(messages.warnings.join('\n\n')));
|
||||
}
|
||||
|
||||
return resolve({
|
||||
stats,
|
||||
previousFileSizes,
|
||||
warnings: messages.warnings,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function copyPublicFolder() {
|
||||
fs.copySync(paths.appPublic, paths.appBuild, {
|
||||
dereference: true,
|
||||
filter: file => file !== paths.appHtml,
|
||||
});
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'development';
|
||||
process.env.NODE_ENV = 'development';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
const fs = require('fs');
|
||||
const chalk = require('react-dev-utils/chalk');
|
||||
const webpack = require('webpack');
|
||||
const WebpackDevServer = require('webpack-dev-server');
|
||||
const clearConsole = require('react-dev-utils/clearConsole');
|
||||
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
const {
|
||||
choosePort,
|
||||
createCompiler,
|
||||
prepareProxy,
|
||||
prepareUrls,
|
||||
} = require('react-dev-utils/WebpackDevServerUtils');
|
||||
const openBrowser = require('react-dev-utils/openBrowser');
|
||||
const paths = require('../config/paths');
|
||||
const configFactory = require('../config/webpack.config');
|
||||
const createDevServerConfig = require('../config/webpackDevServer.config');
|
||||
|
||||
const useYarn = fs.existsSync(paths.yarnLockFile);
|
||||
const isInteractive = process.stdout.isTTY;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Tools like Cloud9 rely on this.
|
||||
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
|
||||
const HOST = process.env.HOST || '0.0.0.0';
|
||||
|
||||
if (process.env.HOST) {
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
`Attempting to bind to HOST environment variable: ${chalk.yellow(
|
||||
chalk.bold(process.env.HOST)
|
||||
)}`
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
|
||||
);
|
||||
console.log(
|
||||
`Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}`
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
// We require that you explicitly set browsers and do not fall back to
|
||||
// browserslist defaults.
|
||||
const {checkBrowsers} = require('react-dev-utils/browsersHelper');
|
||||
checkBrowsers(paths.appPath, isInteractive)
|
||||
.then(() => {
|
||||
// We attempt to use the default port but if it is busy, we offer the user to
|
||||
// run on a different port. `choosePort()` Promise resolves to the next free port.
|
||||
return choosePort(HOST, DEFAULT_PORT);
|
||||
})
|
||||
.then(port => {
|
||||
if (port == null) {
|
||||
// We have not found a port.
|
||||
return;
|
||||
}
|
||||
const config = configFactory('development');
|
||||
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||
const appName = require(paths.appPackageJson).name;
|
||||
const useTypeScript = fs.existsSync(paths.appTsConfig);
|
||||
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
|
||||
const urls = prepareUrls(protocol, HOST, port);
|
||||
const devSocket = {
|
||||
warnings: warnings =>
|
||||
devServer.sockWrite(devServer.sockets, 'warnings', warnings),
|
||||
errors: errors =>
|
||||
devServer.sockWrite(devServer.sockets, 'errors', errors),
|
||||
};
|
||||
// Create a webpack compiler that is configured with custom messages.
|
||||
const compiler = createCompiler({
|
||||
appName,
|
||||
config,
|
||||
devSocket,
|
||||
urls,
|
||||
useYarn,
|
||||
useTypeScript,
|
||||
tscCompileOnError,
|
||||
webpack,
|
||||
});
|
||||
// Load proxy config
|
||||
const proxySetting = require(paths.appPackageJson).proxy;
|
||||
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
|
||||
// Serve webpack assets generated by the compiler over a web server.
|
||||
const serverConfig = createDevServerConfig(
|
||||
proxyConfig,
|
||||
urls.lanUrlForConfig
|
||||
);
|
||||
const devServer = new WebpackDevServer(compiler, serverConfig);
|
||||
// Launch WebpackDevServer.
|
||||
devServer.listen(port, HOST, err => {
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
if (isInteractive) {
|
||||
clearConsole();
|
||||
}
|
||||
|
||||
// We used to support resolving modules according to `NODE_PATH`.
|
||||
// This now has been deprecated in favor of jsconfig/tsconfig.json
|
||||
// This lets you use absolute paths in imports inside large monorepos:
|
||||
if (process.env.NODE_PATH) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
|
||||
)
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
console.log(chalk.cyan('Starting the development server...\n'));
|
||||
openBrowser(urls.localUrlForBrowser);
|
||||
});
|
||||
|
||||
['SIGINT', 'SIGTERM'].forEach(function(sig) {
|
||||
process.on(sig, function() {
|
||||
devServer.close();
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err.message) {
|
||||
console.log(err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'test';
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.PUBLIC_URL = '';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
const jest = require('jest');
|
||||
const execSync = require('child_process').execSync;
|
||||
let argv = process.argv.slice(2);
|
||||
|
||||
function isInGitRepository() {
|
||||
try {
|
||||
execSync('git rev-parse --is-inside-work-tree', {stdio: 'ignore'});
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isInMercurialRepository() {
|
||||
try {
|
||||
execSync('hg --cwd . root', {stdio: 'ignore'});
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Watch unless on CI or explicitly running all tests
|
||||
if (
|
||||
!process.env.CI &&
|
||||
argv.indexOf('--watchAll') === -1 &&
|
||||
argv.indexOf('--watchAll=false') === -1
|
||||
) {
|
||||
// https://github.com/facebook/create-react-app/issues/5210
|
||||
const hasSourceControl = isInGitRepository() || isInMercurialRepository();
|
||||
argv.push(hasSourceControl ? '--watch' : '--watchAll');
|
||||
}
|
||||
|
||||
jest.run(argv);
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const ReactTransportDOMServer = require('react-transport-dom-webpack/server');
|
||||
const ReactFlightDOMServer = require('react-flight-dom-webpack/server');
|
||||
const React = require('react');
|
||||
const Stream = require('stream');
|
||||
|
||||
@@ -20,7 +20,9 @@ function HTML() {
|
||||
module.exports = function(req, res) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
let model = {
|
||||
content: <HTML />,
|
||||
content: {
|
||||
__html: <HTML />,
|
||||
},
|
||||
};
|
||||
ReactTransportDOMServer.pipeToNodeWritable(model, res);
|
||||
ReactFlightDOMServer.pipeToNodeWritable(model, res);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {Suspense} from 'react';
|
||||
|
||||
function Content({data}) {
|
||||
return data.readRoot().content;
|
||||
return <p dangerouslySetInnerHTML={data.model.content} />;
|
||||
}
|
||||
|
||||
function App({data}) {
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactTransportDOMClient from 'react-transport-dom-webpack';
|
||||
import ReactFlightDOMClient from 'react-flight-dom-webpack';
|
||||
import App from './App';
|
||||
|
||||
let data = ReactTransportDOMClient.createFromFetch(
|
||||
fetch('http://localhost:3001')
|
||||
);
|
||||
|
||||
let data = ReactFlightDOMClient.readFromFetch(fetch('http://localhost:3001'));
|
||||
ReactDOM.render(<App data={data} />, document.getElementById('root'));
|
||||
|
||||
@@ -4267,6 +4267,10 @@ fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
|
||||
fsevents@2.0.7:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a"
|
||||
|
||||
fsevents@^1.2.2:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426"
|
||||
@@ -7802,6 +7806,66 @@ react-is@^16.8.1, react-is@^16.8.4:
|
||||
version "16.11.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa"
|
||||
|
||||
react-scripts@3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.2.0.tgz#58ccd6b4ffa27f1b4d2986cbdcaa916660e9e33c"
|
||||
dependencies:
|
||||
"@babel/core" "7.6.0"
|
||||
"@svgr/webpack" "4.3.2"
|
||||
"@typescript-eslint/eslint-plugin" "^2.2.0"
|
||||
"@typescript-eslint/parser" "^2.2.0"
|
||||
babel-eslint "10.0.3"
|
||||
babel-jest "^24.9.0"
|
||||
babel-loader "8.0.6"
|
||||
babel-plugin-named-asset-import "^0.3.4"
|
||||
babel-preset-react-app "^9.0.2"
|
||||
camelcase "^5.2.0"
|
||||
case-sensitive-paths-webpack-plugin "2.2.0"
|
||||
css-loader "2.1.1"
|
||||
dotenv "6.2.0"
|
||||
dotenv-expand "5.1.0"
|
||||
eslint "^6.1.0"
|
||||
eslint-config-react-app "^5.0.2"
|
||||
eslint-loader "3.0.2"
|
||||
eslint-plugin-flowtype "3.13.0"
|
||||
eslint-plugin-import "2.18.2"
|
||||
eslint-plugin-jsx-a11y "6.2.3"
|
||||
eslint-plugin-react "7.14.3"
|
||||
eslint-plugin-react-hooks "^1.6.1"
|
||||
file-loader "3.0.1"
|
||||
fs-extra "7.0.1"
|
||||
html-webpack-plugin "4.0.0-beta.5"
|
||||
identity-obj-proxy "3.0.0"
|
||||
is-wsl "^1.1.0"
|
||||
jest "24.9.0"
|
||||
jest-environment-jsdom-fourteen "0.1.0"
|
||||
jest-resolve "24.9.0"
|
||||
jest-watch-typeahead "0.4.0"
|
||||
mini-css-extract-plugin "0.8.0"
|
||||
optimize-css-assets-webpack-plugin "5.0.3"
|
||||
pnp-webpack-plugin "1.5.0"
|
||||
postcss-flexbugs-fixes "4.1.0"
|
||||
postcss-loader "3.0.0"
|
||||
postcss-normalize "7.0.1"
|
||||
postcss-preset-env "6.7.0"
|
||||
postcss-safe-parser "4.0.1"
|
||||
react-app-polyfill "^1.0.4"
|
||||
react-dev-utils "^9.1.0"
|
||||
resolve "1.12.0"
|
||||
resolve-url-loader "3.1.0"
|
||||
sass-loader "7.2.0"
|
||||
semver "6.3.0"
|
||||
style-loader "1.0.0"
|
||||
terser-webpack-plugin "1.4.1"
|
||||
ts-pnp "1.1.4"
|
||||
url-loader "2.1.0"
|
||||
webpack "4.41.0"
|
||||
webpack-dev-server "3.2.1"
|
||||
webpack-manifest-plugin "2.1.1"
|
||||
workbox-webpack-plugin "4.3.1"
|
||||
optionalDependencies:
|
||||
fsevents "2.0.7"
|
||||
|
||||
read-pkg-up@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
|
||||
|
||||
@@ -7,7 +7,6 @@ module.exports = {
|
||||
react: '../../../../build/node_modules/react/umd/react.development',
|
||||
'react-dom':
|
||||
'../../../../build/node_modules/react-dom/umd/react-dom.development',
|
||||
schedule:
|
||||
'../../../../build/node_modules/scheduler/umd/schedule.development',
|
||||
schedule: '../../../../build/dist/schedule.development',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@ module.exports = {
|
||||
react: '../../../../build/node_modules/react/umd/react.production.min',
|
||||
'react-dom':
|
||||
'../../../../build/node_modules/react-dom/umd/react-dom.production.min',
|
||||
schedule:
|
||||
'../../../../build/node_modules/scheduler/umd/schedule.development',
|
||||
schedule: '../../../../build/dist/schedule.development',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@ System.config({
|
||||
react: '../../../../build/node_modules/react/umd/react.development.js',
|
||||
'react-dom':
|
||||
'../../../../build/node_modules/react-dom/umd/react-dom.development.js',
|
||||
schedule:
|
||||
'../../../../build/node_modules/scheduler/umd/schedule.development',
|
||||
schedule: '../../../../build/dist/schedule.development',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ System.config({
|
||||
react: '../../../../build/node_modules/react/umd/react.production.min.js',
|
||||
'react-dom':
|
||||
'../../../../build/node_modules/react-dom/umd/react-dom.production.min.js',
|
||||
schedule:
|
||||
'../../../../build/node_modules/scheduler/umd/schedule.development',
|
||||
schedule: '../../../../build/dist/schedule.development',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
function _assertThisInitialized(self) {
|
||||
if (self === void 0) {
|
||||
throw new ReferenceError(
|
||||
"this hasn't been initialised - super() hasn't been called"
|
||||
);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
function _defineProperty(obj, key, value) {
|
||||
if (key in obj) {
|
||||
Object.defineProperty(obj, key, {
|
||||
value: value,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
} else {
|
||||
obj[key] = value;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function _inheritsLoose(subClass, superClass) {
|
||||
subClass.prototype = Object.create(superClass.prototype);
|
||||
subClass.prototype.constructor = subClass;
|
||||
subClass.__proto__ = superClass;
|
||||
}
|
||||
|
||||
// Compile this with Babel.
|
||||
// babel --config-file ./babel.config.json BabelClasses.js --out-file BabelClasses-compiled.js --source-maps
|
||||
let BabelClass = /*#__PURE__*/ (function(_React$Component) {
|
||||
_inheritsLoose(BabelClass, _React$Component);
|
||||
|
||||
function BabelClass() {
|
||||
return _React$Component.apply(this, arguments) || this;
|
||||
}
|
||||
|
||||
var _proto = BabelClass.prototype;
|
||||
|
||||
_proto.render = function render() {
|
||||
return this.props.children;
|
||||
};
|
||||
|
||||
return BabelClass;
|
||||
})(React.Component);
|
||||
|
||||
let BabelClassWithFields = /*#__PURE__*/ (function(_React$Component2) {
|
||||
_inheritsLoose(BabelClassWithFields, _React$Component2);
|
||||
|
||||
function BabelClassWithFields(...args) {
|
||||
var _this;
|
||||
|
||||
_this = _React$Component2.call(this, ...args) || this;
|
||||
|
||||
_defineProperty(
|
||||
_assertThisInitialized(_assertThisInitialized(_this)),
|
||||
'props',
|
||||
void 0
|
||||
);
|
||||
|
||||
_defineProperty(
|
||||
_assertThisInitialized(_assertThisInitialized(_this)),
|
||||
'state',
|
||||
{}
|
||||
);
|
||||
|
||||
return _this;
|
||||
}
|
||||
|
||||
var _proto2 = BabelClassWithFields.prototype;
|
||||
|
||||
_proto2.render = function render() {
|
||||
return this.props.children;
|
||||
};
|
||||
|
||||
return BabelClassWithFields;
|
||||
})(React.Component);
|
||||
|
||||
//# sourceMappingURL=BabelClasses-compiled.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["BabelClasses.js"],"names":[],"mappings":";;;;;;AAAA;AACA;IAEM,U;;;;;;;;;SACJ,M,qBAAS;AACP,WAAO,KAAK,KAAL,CAAW,QAAlB;AACD,G;;;EAHsB,KAAK,CAAC,S;;IAMzB,oB;;;;;;;;;;oFAGI,E;;;;;;;UACR,M,qBAAS;AACP,WAAO,KAAK,KAAL,CAAW,QAAlB;AACD,G;;;EANgC,KAAK,CAAC,S","file":"BabelClasses-compiled.js","sourcesContent":["// Compile this with Babel.\n// babel --config-file ./babel.config.json BabelClasses.js --out-file BabelClasses-compiled.js --source-maps\n\nclass BabelClass extends React.Component {\n render() {\n return this.props.children;\n }\n}\n\nclass BabelClassWithFields extends React.Component {\n // These compile to defineProperty which can break some interception techniques.\n props;\n state = {};\n render() {\n return this.props.children;\n }\n}\n"]}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Compile this with Babel.
|
||||
// babel --config-file ./babel.config.json BabelClasses.js --out-file BabelClasses-compiled.js --source-maps
|
||||
|
||||
class BabelClass extends React.Component {
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
class BabelClassWithFields extends React.Component {
|
||||
// These compile to defineProperty which can break some interception techniques.
|
||||
props;
|
||||
state = {};
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Example
|
||||
|
||||
const Throw = React.lazy(() => {
|
||||
throw new Error('Example');
|
||||
});
|
||||
|
||||
const Component = React.memo(function Component({children}) {
|
||||
return children;
|
||||
});
|
||||
|
||||
function DisplayName({children}) {
|
||||
return children;
|
||||
}
|
||||
DisplayName.displayName = 'Custom Name';
|
||||
|
||||
class NativeClass extends React.Component {
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
class FrozenClass extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
Object.freeze(FrozenClass.prototype);
|
||||
@@ -1,67 +0,0 @@
|
||||
// Example
|
||||
|
||||
const x = React.createElement;
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
static getDerivedStateFromError(error) {
|
||||
return {
|
||||
error: error,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
console.log(error.message, errorInfo.componentStack);
|
||||
this.setState({
|
||||
componentStack: errorInfo.componentStack,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state && this.state.error) {
|
||||
return x(
|
||||
'div',
|
||||
null,
|
||||
x('h3', null, this.state.error.message),
|
||||
x('pre', null, this.state.componentStack)
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
function Example() {
|
||||
let state = React.useState(false);
|
||||
return x(
|
||||
ErrorBoundary,
|
||||
null,
|
||||
x(
|
||||
DisplayName,
|
||||
null,
|
||||
x(
|
||||
React.SuspenseList,
|
||||
null,
|
||||
x(
|
||||
NativeClass,
|
||||
null,
|
||||
x(
|
||||
FrozenClass,
|
||||
null,
|
||||
x(
|
||||
BabelClass,
|
||||
null,
|
||||
x(
|
||||
BabelClassWithFields,
|
||||
null,
|
||||
x(
|
||||
React.Suspense,
|
||||
null,
|
||||
x('div', null, x(Component, null, x(Throw)))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
["@babel/plugin-proposal-class-properties", {"loose": false}],
|
||||
["@babel/plugin-transform-classes", {"loose": true}]
|
||||
]
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Component Stacks</title>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 20px;
|
||||
}
|
||||
pre {
|
||||
background: #eee;
|
||||
border: 1px solid #ccc;
|
||||
padding: 2px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<p>
|
||||
To install React, follow the instructions on
|
||||
<a href="https://github.com/facebook/react/">GitHub</a>.
|
||||
</p>
|
||||
<p>
|
||||
If you can see this, React is <strong>not</strong> working right.
|
||||
If you checked out the source from GitHub make sure to run <code>npm run build</code>.
|
||||
</p>
|
||||
</div>
|
||||
<script src="../../build/node_modules/react/umd/react.production.min.js"></script>
|
||||
<script src="../../build/node_modules/react-dom/umd/react-dom.production.min.js"></script>
|
||||
<script src="./Components.js"></script>
|
||||
<script src="./BabelClasses-compiled.js"></script>
|
||||
<script src="./Example.js"></script>
|
||||
<script>
|
||||
const container = document.getElementById("container");
|
||||
ReactDOM.render(React.createElement(Example), container);
|
||||
</script>
|
||||
<h3>The above stack should look something like this:</h3>
|
||||
<pre>
|
||||
at Lazy
|
||||
at Component (/stacks/Component.js:7:1)
|
||||
at div
|
||||
at Suspense
|
||||
at BabelClassWithFields (/stacks/BabelClasses-compiled.js:31:31)
|
||||
at BabelClass (/stacks/BabelClass-compiled.js:13:29)
|
||||
at FrozenClass (/stacks/Components.js:22:1)
|
||||
at NativeClass (/stacks/Component.js:16:1)
|
||||
at SuspenseList
|
||||
at Custom Name (/stacks/Component.js:11:1)
|
||||
at ErrorBoundary (/stacks/Example.js:5:1)
|
||||
at Example (/stacks/Example.js:32:1)</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
<script>
|
||||
if (window.location.search.includes('puppeteer=true')) {
|
||||
// Collocated calls to performance.now() often yield different values in Puppeteer.
|
||||
// Colocated calls to performance.now() often yield different values in Puppeteer.
|
||||
// This causes the Scheduler API test to fail.
|
||||
// For the purposes of our automated release scripts,
|
||||
// Coerce tests to use Date.now() instead to reduce the chances of a false positive.
|
||||
|
||||
50
package.json
50
package.json
@@ -44,9 +44,10 @@
|
||||
"core-js": "^3.6.4",
|
||||
"coveralls": "^3.0.9",
|
||||
"create-react-class": "^15.6.3",
|
||||
"cross-env": "^6.0.3",
|
||||
"danger": "^9.2.10",
|
||||
"error-stack-parser": "^2.0.6",
|
||||
"eslint": "^7.0.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-fbjs": "^1.1.1",
|
||||
"eslint-config-prettier": "^6.9.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
@@ -55,20 +56,19 @@
|
||||
"eslint-plugin-no-for-of-loops": "^1.0.0",
|
||||
"eslint-plugin-react": "^6.7.1",
|
||||
"eslint-plugin-react-internal": "link:./scripts/eslint-rules",
|
||||
"fbjs-scripts": "1.2.0",
|
||||
"fbjs-scripts": "0.8.3",
|
||||
"filesize": "^6.0.1",
|
||||
"flow-bin": "0.97",
|
||||
"glob": "^7.1.6",
|
||||
"glob-stream": "^6.1.0",
|
||||
"google-closure-compiler": "^20200517.0.0",
|
||||
"google-closure-compiler": "^20200112.0.0",
|
||||
"gzip-size": "^5.1.1",
|
||||
"jasmine-check": "^1.0.0-rc.0",
|
||||
"jest": "^25.2.7",
|
||||
"jest-cli": "^25.2.7",
|
||||
"jest-diff": "^25.2.6",
|
||||
"jest": "^25.1.0",
|
||||
"jest-diff": "^25.1.0",
|
||||
"jest-snapshot-serializer-raw": "^1.1.0",
|
||||
"minimatch": "^3.0.4",
|
||||
"minimist": "^1.2.3",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"ncp": "^2.0.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@@ -90,45 +90,37 @@
|
||||
"through2": "^3.0.1",
|
||||
"tmp": "^0.1.0",
|
||||
"typescript": "^3.7.5",
|
||||
"webpack": "^4.41.2",
|
||||
"yargs": "^15.3.1"
|
||||
"webpack": "^4.41.2"
|
||||
},
|
||||
"devEngines": {
|
||||
"node": "8.x || 9.x || 10.x || 11.x || 12.x || 13.x || 14.x"
|
||||
"node": "8.x || 9.x || 10.x || 11.x || 12.x || 13.x"
|
||||
},
|
||||
"jest": {
|
||||
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node ./scripts/rollup/build.js",
|
||||
"build-for-devtools": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react-dom,react-is,react-debug-tools,scheduler,react-test-renderer --type=NODE",
|
||||
"build-for-devtools": "cross-env RELEASE_CHANNEL=experimental yarn build -- react/index,react-dom,react-is,react-debug-tools,scheduler,react-test-renderer --type=NODE",
|
||||
"linc": "node ./scripts/tasks/linc.js",
|
||||
"lint": "node ./scripts/tasks/eslint.js",
|
||||
"lint-build": "node ./scripts/rollup/validate/index.js",
|
||||
"extract-errors": "yarn build --type=dev --extract-errors",
|
||||
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json && node ./scripts/flow/createFlowConfigs.js && node ./scripts/yarn/downloadReactIsForPrettyFormat.js",
|
||||
"debug-test": "yarn test --debug",
|
||||
"test": "node ./scripts/jest/jest-cli.js",
|
||||
"test-www": "yarn test --release-channel=www-modern",
|
||||
"test-www-variant": "yarn test --release-channel=www-modern --variant",
|
||||
"test-prod-www": "yarn test --prod --release-channel=www-modern",
|
||||
"test-prod-www-variant": "yarn test --prod --release-channel=www-modern --variant",
|
||||
"test-persistent": "yarn test --persistent",
|
||||
"debug-test-persistent": "yarn test --debug --persistent",
|
||||
"test-prod": "yarn test --prod",
|
||||
"debug-test-prod": "yarn test --debug --prod",
|
||||
"test-prod-build": "yarn test --prod --build",
|
||||
"test-build": "yarn test --build",
|
||||
"test-build-prod": "yarn test --build --prod",
|
||||
"test-build-devtools": "yarn test --build --project devtools",
|
||||
"debug-test-build-devtools": "yarn test --debug --build --project devtools",
|
||||
"debug-test": "cross-env NODE_ENV=development node --inspect-brk node_modules/jest/bin/jest.js --config ./scripts/jest/config.source.js --runInBand",
|
||||
"test": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source.js",
|
||||
"test-persistent": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source-persistent.js",
|
||||
"debug-test-persistent": "cross-env NODE_ENV=development node --inspect-brk node_modules/jest/bin/jest.js --config ./scripts/jest/config.source-persistent.js --runInBand",
|
||||
"test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js",
|
||||
"test-prod-build": "yarn test-build-prod",
|
||||
"test-build": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build.js",
|
||||
"test-build-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.build.js",
|
||||
"test-build-devtools": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build-devtools.js",
|
||||
"debug-test-build-devtools": "cross-env NODE_ENV=development node --inspect-brk node_modules/jest/bin/jest.js --config ./scripts/jest/config.build-devtools.js",
|
||||
"test-dom-fixture": "cd fixtures/dom && yarn && yarn prestart && yarn test",
|
||||
"flow": "node ./scripts/tasks/flow.js",
|
||||
"flow-ci": "node ./scripts/tasks/flow-ci.js",
|
||||
"prettier": "node ./scripts/prettier/index.js write-changed",
|
||||
"prettier-all": "node ./scripts/prettier/index.js write",
|
||||
"version-check": "node ./scripts/tasks/version-check.js",
|
||||
"merge-fork": "node ./scripts/merge-fork/merge-fork.js",
|
||||
"replace-fork": "node ./scripts/merge-fork/replace-fork.js"
|
||||
"version-check": "node ./scripts/tasks/version-check.js"
|
||||
}
|
||||
}
|
||||
|
||||
5
packages/babel-plugin-react-jsx/README.md
Normal file
5
packages/babel-plugin-react-jsx/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
This package is intended to eventually replace the current `@babel/plugin-transform-react-jsx`, changing the JSX transform from targeting `React.createElement(type, props, children)` to `React.jsx(types, props, key)`.
|
||||
|
||||
https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md
|
||||
|
||||
**This is experimental and not intended to be used directly.**
|
||||
@@ -0,0 +1,870 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable quotes */
|
||||
'use strict';
|
||||
|
||||
const babel = require('@babel/core');
|
||||
const codeFrame = require('@babel/code-frame');
|
||||
const {wrap} = require('jest-snapshot-serializer-raw');
|
||||
|
||||
function transform(input, pluginOpts, babelOpts) {
|
||||
return wrap(
|
||||
babel.transform(input, {
|
||||
configFile: false,
|
||||
sourceType: 'module',
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-jsx',
|
||||
'@babel/plugin-transform-arrow-functions',
|
||||
...(pluginOpts && pluginOpts.development
|
||||
? [
|
||||
'@babel/plugin-transform-react-jsx-source',
|
||||
'@babel/plugin-transform-react-jsx-self',
|
||||
]
|
||||
: []),
|
||||
[
|
||||
'./packages/babel-plugin-react-jsx',
|
||||
{
|
||||
useBuiltIns: true,
|
||||
useCreateElement: false,
|
||||
...pluginOpts,
|
||||
},
|
||||
],
|
||||
],
|
||||
...babelOpts,
|
||||
}).code
|
||||
);
|
||||
}
|
||||
|
||||
describe('transform react to jsx', () => {
|
||||
it('auto import pragma overrides regular pragma', () => {
|
||||
expect(
|
||||
transform(
|
||||
`/** @jsxAutoImport defaultExport */
|
||||
var x = <div><span /></div>
|
||||
`,
|
||||
{
|
||||
autoImport: 'namespace',
|
||||
importSource: 'foobar',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('import source pragma overrides regular pragma', () => {
|
||||
expect(
|
||||
transform(
|
||||
`/** @jsxImportSource baz */
|
||||
var x = <div><span /></div>
|
||||
`,
|
||||
{
|
||||
autoImport: 'namespace',
|
||||
importSource: 'foobar',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('multiple pragmas work', () => {
|
||||
expect(
|
||||
transform(
|
||||
`/** Some comment here
|
||||
* @jsxImportSource baz
|
||||
* @jsxAutoImport defaultExport
|
||||
*/
|
||||
var x = <div><span /></div>
|
||||
`,
|
||||
{
|
||||
autoImport: 'namespace',
|
||||
importSource: 'foobar',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('throws error when sourceType is module and autoImport is require', () => {
|
||||
const code = `var x = <div><span /></div>`;
|
||||
expect(() => {
|
||||
transform(code, {
|
||||
autoImport: 'require',
|
||||
});
|
||||
}).toThrow(
|
||||
'Babel `sourceType` must be set to `script` for autoImport ' +
|
||||
'to use `require` syntax. See Babel `sourceType` for details.\n' +
|
||||
codeFrame.codeFrameColumns(
|
||||
code,
|
||||
{start: {line: 1, column: 1}, end: {line: 1, column: 28}},
|
||||
{highlightCode: true}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('throws error when sourceType is script and autoImport is not require', () => {
|
||||
const code = `var x = <div><span /></div>`;
|
||||
expect(() => {
|
||||
transform(
|
||||
code,
|
||||
{
|
||||
autoImport: 'namespace',
|
||||
},
|
||||
{sourceType: 'script'}
|
||||
);
|
||||
}).toThrow(
|
||||
'Babel `sourceType` must be set to `module` for autoImport ' +
|
||||
'to use `namespace` syntax. See Babel `sourceType` for details.\n' +
|
||||
codeFrame.codeFrameColumns(
|
||||
code,
|
||||
{start: {line: 1, column: 1}, end: {line: 1, column: 28}},
|
||||
{highlightCode: true}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("auto import that doesn't exist should throw error", () => {
|
||||
const code = `var x = <div><span /></div>`;
|
||||
expect(() => {
|
||||
transform(code, {
|
||||
autoImport: 'foo',
|
||||
});
|
||||
}).toThrow(
|
||||
'autoImport must be one of the following: none, require, namespace, defaultExport, namedExports\n' +
|
||||
codeFrame.codeFrameColumns(
|
||||
code,
|
||||
{start: {line: 1, column: 1}, end: {line: 1, column: 28}},
|
||||
{highlightCode: true}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('auto import can specify source', () => {
|
||||
expect(
|
||||
transform(`var x = <div><span /></div>`, {
|
||||
autoImport: 'namespace',
|
||||
importSource: 'foobar',
|
||||
})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('auto import require', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var x = (
|
||||
<>
|
||||
<div>
|
||||
<div key="1" />
|
||||
<div key="2" meow="wolf" />
|
||||
<div key="3" />
|
||||
<div {...props} key="4" />
|
||||
</div>
|
||||
</>
|
||||
);`,
|
||||
{
|
||||
autoImport: 'require',
|
||||
},
|
||||
{
|
||||
sourceType: 'script',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('auto import namespace', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var x = (
|
||||
<>
|
||||
<div>
|
||||
<div key="1" />
|
||||
<div key="2" meow="wolf" />
|
||||
<div key="3" />
|
||||
<div {...props} key="4" />
|
||||
</div>
|
||||
</>
|
||||
);`,
|
||||
{
|
||||
autoImport: 'namespace',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('auto import default', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var x = (
|
||||
<>
|
||||
<div>
|
||||
<div key="1" />
|
||||
<div key="2" meow="wolf" />
|
||||
<div key="3" />
|
||||
<div {...props} key="4" />
|
||||
</div>
|
||||
</>
|
||||
);`,
|
||||
{
|
||||
autoImport: 'defaultExport',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('auto import named exports', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var x = (
|
||||
<>
|
||||
<div>
|
||||
<div key="1" />
|
||||
<div key="2" meow="wolf" />
|
||||
<div key="3" />
|
||||
<div {...props} key="4" />
|
||||
</div>
|
||||
</>
|
||||
);`,
|
||||
{
|
||||
autoImport: 'namedExports',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('auto import with no JSX', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var foo = "<div></div>"`,
|
||||
{
|
||||
autoImport: 'require',
|
||||
},
|
||||
{
|
||||
sourceType: 'script',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('complicated scope require', () => {
|
||||
expect(
|
||||
transform(
|
||||
`
|
||||
const Bar = () => {
|
||||
const Foo = () => {
|
||||
const Component = ({thing, ..._react}) => {
|
||||
if (!thing) {
|
||||
var _react2 = "something useless";
|
||||
var b = _react3();
|
||||
var c = _react5();
|
||||
var jsx = 1;
|
||||
var _jsx = 2;
|
||||
return <div />;
|
||||
};
|
||||
return <span />;
|
||||
};
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
autoImport: 'require',
|
||||
},
|
||||
{
|
||||
sourceType: 'script',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('complicated scope named exports', () => {
|
||||
expect(
|
||||
transform(
|
||||
`
|
||||
const Bar = () => {
|
||||
const Foo = () => {
|
||||
const Component = ({thing, ..._react}) => {
|
||||
if (!thing) {
|
||||
var _react2 = "something useless";
|
||||
var b = _react3();
|
||||
var jsx = 1;
|
||||
var _jsx = 2;
|
||||
return <div />;
|
||||
};
|
||||
return <span />;
|
||||
};
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
autoImport: 'namedExports',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('auto import in dev', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var x = (
|
||||
<>
|
||||
<div>
|
||||
<div key="1" />
|
||||
<div key="2" meow="wolf" />
|
||||
<div key="3" />
|
||||
<div {...props} key="4" />
|
||||
</div>
|
||||
</>
|
||||
);`,
|
||||
{
|
||||
autoImport: 'namedExports',
|
||||
development: true,
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('auto import none', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var x = (
|
||||
<>
|
||||
<div>
|
||||
<div key="1" />
|
||||
<div key="2" meow="wolf" />
|
||||
<div key="3" />
|
||||
<div {...props} key="4" />
|
||||
</div>
|
||||
</>
|
||||
);`,
|
||||
{
|
||||
autoImport: 'none',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('auto import undefined', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var x = (
|
||||
<>
|
||||
<div>
|
||||
<div key="1" />
|
||||
<div key="2" meow="wolf" />
|
||||
<div key="3" />
|
||||
<div {...props} key="4" />
|
||||
</div>
|
||||
</>
|
||||
);`
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('auto import with namespaces already defined', () => {
|
||||
expect(
|
||||
transform(
|
||||
`
|
||||
import * as _react from "foo";
|
||||
const react = _react(1);
|
||||
const _react1 = react;
|
||||
const _react2 = react;
|
||||
var x = (
|
||||
<div>
|
||||
<div key="1" />
|
||||
<div key="2" meow="wolf" />
|
||||
<div key="3" />
|
||||
<div {...props} key="4" />
|
||||
</div>
|
||||
);`,
|
||||
{
|
||||
autoImport: 'namespace',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('auto import with react already defined', () => {
|
||||
expect(
|
||||
transform(
|
||||
`
|
||||
import * as react from "react";
|
||||
var y = react.createElement("div", {foo: 1});
|
||||
var x = (
|
||||
<div>
|
||||
<div key="1" />
|
||||
<div key="2" meow="wolf" />
|
||||
<div key="3" />
|
||||
<div {...props} key="4" />
|
||||
</div>
|
||||
);`,
|
||||
|
||||
{
|
||||
autoImport: 'namespace',
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('fragment with no children', () => {
|
||||
expect(transform(`var x = <></>`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('fragments', () => {
|
||||
expect(transform(`var x = <><div /></>`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('fragments to set keys', () => {
|
||||
expect(
|
||||
transform(`var x = <React.Fragment key="foo"></React.Fragment>`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('React.fragment to set keys and source', () => {
|
||||
expect(
|
||||
transform(`var x = <React.Fragment key='foo'></React.Fragment>`, {
|
||||
development: true,
|
||||
})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('fragments in dev mode (no key and source)', () => {
|
||||
expect(
|
||||
transform(`var x = <><div /></>`, {
|
||||
development: true,
|
||||
})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('nonStatic children', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var x = (
|
||||
<div>
|
||||
{[<span key={'0'} />, <span key={'1'} />]}
|
||||
</div>
|
||||
);
|
||||
`,
|
||||
{
|
||||
development: true,
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('static children', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var x = (
|
||||
<div>
|
||||
<span />
|
||||
{[<span key={'0'} />, <span key={'1'} />]}
|
||||
</div>
|
||||
);
|
||||
`,
|
||||
{
|
||||
development: true,
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('uses jsxDEV instead of jsx in dev mode', () => {
|
||||
expect(
|
||||
transform(`var x = <span propOne="one">Hi</span>`, {development: true})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('properly passes in source and self', () => {
|
||||
expect(
|
||||
transform(`var x = <div />;`, {development: true})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should properly handle potentially null variables', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var foo = null;
|
||||
var x = <div {...foo} />;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('properly handles keys', () => {
|
||||
expect(
|
||||
transform(`var x = (
|
||||
<div>
|
||||
<div key="1" />
|
||||
<div key="2" meow="wolf" />
|
||||
<div key="3" />
|
||||
</div>
|
||||
);`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('uses createElement when the key comes after a spread', () => {
|
||||
expect(
|
||||
transform(`var x = (
|
||||
<div {...props} key="1" foo="bar" />
|
||||
);`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('uses jsx when the key comes before a spread', () => {
|
||||
expect(
|
||||
transform(`var x = (
|
||||
<div key="1" {...props} foo="bar" />
|
||||
);`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should properly handle comments adjacent to children', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = (
|
||||
<div>
|
||||
{/* A comment at the beginning */}
|
||||
{/* A second comment at the beginning */}
|
||||
<span>
|
||||
{/* A nested comment */}
|
||||
</span>
|
||||
{/* A sandwiched comment */}
|
||||
<br />
|
||||
{/* A comment at the end */}
|
||||
{/* A second comment at the end */}
|
||||
</div>
|
||||
);
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('adds appropriate new lines when using spread attribute', () => {
|
||||
expect(transform(`<Component {...props} sound="moo" />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('arrow functions', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var foo = function () {
|
||||
return () => <this />;
|
||||
};
|
||||
|
||||
var bar = function () {
|
||||
return () => <this.foo />;
|
||||
};
|
||||
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('assignment', () => {
|
||||
expect(
|
||||
transform(`var div = <Component {...props} foo="bar" />`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('concatenates adjacent string literals', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x =
|
||||
<div>
|
||||
foo
|
||||
{"bar"}
|
||||
baz
|
||||
<div>
|
||||
buz
|
||||
bang
|
||||
</div>
|
||||
qux
|
||||
{null}
|
||||
quack
|
||||
</div>
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow constructor as prop', () => {
|
||||
expect(transform(`<Component constructor="foo" />;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow deeper js namespacing', () => {
|
||||
expect(
|
||||
transform(`<Namespace.DeepNamespace.Component />;`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow elements as attributes', () => {
|
||||
expect(transform(`<div attr=<div /> />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow js namespacing', () => {
|
||||
expect(transform(`<Namespace.Component />;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow nested fragments', () => {
|
||||
expect(
|
||||
transform(`
|
||||
<div>
|
||||
< >
|
||||
<>
|
||||
<span>Hello</span>
|
||||
<span>world</span>
|
||||
</>
|
||||
<>
|
||||
<span>Goodbye</span>
|
||||
<span>world</span>
|
||||
</>
|
||||
</>
|
||||
</div>
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should avoid wrapping in extra parens if not needed', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = <div>
|
||||
<Component />
|
||||
</div>;
|
||||
|
||||
var x = <div>
|
||||
{props.children}
|
||||
</div>;
|
||||
|
||||
var x = <Composite>
|
||||
{props.children}
|
||||
</Composite>;
|
||||
|
||||
var x = <Composite>
|
||||
<Composite2 />
|
||||
</Composite>;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should convert simple tags', () => {
|
||||
expect(transform(`var x = <div></div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should convert simple text', () => {
|
||||
expect(transform(`var x = <div>text</div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should disallow spread children', () => {
|
||||
let _error;
|
||||
const code = `<div>{...children}</div>;`;
|
||||
try {
|
||||
transform(code);
|
||||
} catch (error) {
|
||||
_error = error;
|
||||
}
|
||||
expect(_error).toEqual(
|
||||
new SyntaxError(
|
||||
'unknown: Spread children are not supported in React.' +
|
||||
'\n' +
|
||||
codeFrame.codeFrameColumns(
|
||||
code,
|
||||
{start: {line: 1, column: 6}, end: {line: 1, column: 19}},
|
||||
{highlightCode: true}
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should escape xhtml jsxattribute', () => {
|
||||
expect(
|
||||
transform(`
|
||||
<div id="wôw" />;
|
||||
<div id="\w" />;
|
||||
<div id="w < w" />;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should escape xhtml jsxtext', () => {
|
||||
/* eslint-disable no-irregular-whitespace */
|
||||
expect(
|
||||
transform(`
|
||||
<div>wow</div>;
|
||||
<div>wôw</div>;
|
||||
|
||||
<div>w & w</div>;
|
||||
<div>w & w</div>;
|
||||
|
||||
<div>w w</div>;
|
||||
<div>this should not parse as unicode: \u00a0</div>;
|
||||
<div>this should parse as nbsp: </div>;
|
||||
<div>this should parse as unicode: {'\u00a0 '}</div>;
|
||||
|
||||
<div>w < w</div>;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
/*eslint-enable */
|
||||
});
|
||||
|
||||
it('should handle attributed elements', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var HelloMessage = React.createClass({
|
||||
render: function() {
|
||||
return <div>Hello {this.props.name}</div>;
|
||||
}
|
||||
});
|
||||
|
||||
React.render(<HelloMessage name={
|
||||
<span>
|
||||
Sebastian
|
||||
</span>
|
||||
} />, mountNode);
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should handle has own property correctly', () => {
|
||||
expect(
|
||||
transform(`<hasOwnProperty>testing</hasOwnProperty>;`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should have correct comma in nested children', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = <div>
|
||||
<div><br /></div>
|
||||
<Component>{foo}<br />{bar}</Component>
|
||||
<br />
|
||||
</div>;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should insert commas after expressions before whitespace', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x =
|
||||
<div
|
||||
attr1={
|
||||
"foo" + "bar"
|
||||
}
|
||||
attr2={
|
||||
"foo" + "bar" +
|
||||
|
||||
"baz" + "bug"
|
||||
}
|
||||
attr3={
|
||||
"foo" + "bar" +
|
||||
"baz" + "bug"
|
||||
}
|
||||
attr4="baz">
|
||||
</div>
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not add quotes to identifier names', () => {
|
||||
expect(
|
||||
transform(`var e = <F aaa new const var default foo-bar/>;`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not strip nbsp even couple with other whitespace', () => {
|
||||
expect(transform(`<div> </div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not strip tags with a single child of nbsp', () => {
|
||||
expect(transform(`<div> </div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should properly handle comments between props', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = (
|
||||
<div
|
||||
/* a multi-line
|
||||
comment */
|
||||
attr1="foo">
|
||||
<span // a double-slash comment
|
||||
attr2="bar"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should quote jsx attributes', () => {
|
||||
expect(
|
||||
transform(`<button data-value='a value'>Button</button>`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should support xml namespaces if flag', () => {
|
||||
expect(
|
||||
transform('<f:image n:attr />', {throwIfNamespace: false})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should throw error namespaces if not flag', () => {
|
||||
let _error;
|
||||
const code = `<f:image />`;
|
||||
try {
|
||||
transform(code);
|
||||
} catch (error) {
|
||||
_error = error;
|
||||
}
|
||||
expect(_error).toEqual(
|
||||
new SyntaxError(
|
||||
"unknown: Namespace tags are not supported by default. React's " +
|
||||
"JSX doesn't support namespace tags. You can turn on the " +
|
||||
"'throwIfNamespace' flag to bypass this warning." +
|
||||
'\n' +
|
||||
codeFrame.codeFrameColumns(
|
||||
code,
|
||||
{start: {line: 1, column: 2}, end: {line: 1, column: 9}},
|
||||
{highlightCode: true}
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should transform known hyphenated tags', () => {
|
||||
expect(transform(`<font-face />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('wraps props in react spread for first spread attributes', () => {
|
||||
expect(transform(`<Component {...x} y={2} z />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('wraps props in react spread for last spread attributes', () => {
|
||||
expect(transform(`<Component y={2} z { ... x } />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('wraps props in react spread for middle spread attributes', () => {
|
||||
expect(transform(`<Component y={2} { ... x } z />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('useBuiltIns false uses extend instead of Object.assign', () => {
|
||||
expect(
|
||||
transform(`<Component y={2} {...x} />`, {useBuiltIns: false})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('duplicate children prop should transform into sequence expression with actual children', () => {
|
||||
expect(
|
||||
transform(`<Component children={1}>2</Component>`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
it('duplicate children prop should transform into sequence expression with next prop', () => {
|
||||
expect(
|
||||
transform(`<Component children={1} foo={3}>2</Component>`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
it('duplicate children props should transform into sequence expression with next prop', () => {
|
||||
expect(
|
||||
transform(`<Component children={1} children={4} foo={3}>2</Component>`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
it('duplicate children prop should transform into sequence expression with spread', () => {
|
||||
expect(
|
||||
transform(`<Component children={1} {...x}>2</Component>`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,645 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`transform react to jsx React.fragment to set keys and source 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV(React.Fragment, {}, "foo", false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1
|
||||
}, this);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = `
|
||||
React.jsx(Component, Object.assign({}, props, {
|
||||
sound: "moo"
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx arrow functions 1`] = `
|
||||
var foo = function () {
|
||||
var _this = this;
|
||||
|
||||
return function () {
|
||||
return React.jsx(_this, {});
|
||||
};
|
||||
};
|
||||
|
||||
var bar = function () {
|
||||
var _this2 = this;
|
||||
|
||||
return function () {
|
||||
return React.jsx(_this2.foo, {});
|
||||
};
|
||||
};
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx assignment 1`] = `
|
||||
var div = React.jsx(Component, Object.assign({}, props, {
|
||||
foo: "bar"
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx auto import can specify source 1`] = `
|
||||
import * as _foobar from "foobar";
|
||||
|
||||
var x = _foobar.jsx("div", {
|
||||
children: _foobar.jsx("span", {})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx auto import default 1`] = `
|
||||
import _default from "react";
|
||||
|
||||
var x = _default.jsx(_default.Fragment, {
|
||||
children: _default.jsxs("div", {
|
||||
children: [_default.jsx("div", {}, "1"), _default.jsx("div", {
|
||||
meow: "wolf"
|
||||
}, "2"), _default.jsx("div", {}, "3"), _default.createElement("div", Object.assign({}, props, {
|
||||
key: "4"
|
||||
}))]
|
||||
})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx auto import in dev 1`] = `
|
||||
import { createElement as _createElement } from "react";
|
||||
import { jsxDEV as _jsxDEV } from "react";
|
||||
import { Fragment as _Fragment } from "react";
|
||||
var _jsxFileName = "";
|
||||
|
||||
var x = _jsxDEV(_Fragment, {
|
||||
children: _jsxDEV("div", {
|
||||
children: [_jsxDEV("div", {}, "1", false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 4
|
||||
}, this), _jsxDEV("div", {
|
||||
meow: "wolf"
|
||||
}, "2", false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 5
|
||||
}, this), _jsxDEV("div", {}, "3", false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 6
|
||||
}, this), _createElement("div", Object.assign({}, props, {
|
||||
key: "4",
|
||||
__source: {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 7
|
||||
},
|
||||
__self: this
|
||||
}))]
|
||||
}, undefined, true, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 3
|
||||
}, this)
|
||||
}, undefined, false);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx auto import named exports 1`] = `
|
||||
import { createElement as _createElement } from "react";
|
||||
import { jsx as _jsx } from "react";
|
||||
import { jsxs as _jsxs } from "react";
|
||||
import { Fragment as _Fragment } from "react";
|
||||
|
||||
var x = _jsx(_Fragment, {
|
||||
children: _jsxs("div", {
|
||||
children: [_jsx("div", {}, "1"), _jsx("div", {
|
||||
meow: "wolf"
|
||||
}, "2"), _jsx("div", {}, "3"), _createElement("div", Object.assign({}, props, {
|
||||
key: "4"
|
||||
}))]
|
||||
})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx auto import namespace 1`] = `
|
||||
import * as _react from "react";
|
||||
|
||||
var x = _react.jsx(_react.Fragment, {
|
||||
children: _react.jsxs("div", {
|
||||
children: [_react.jsx("div", {}, "1"), _react.jsx("div", {
|
||||
meow: "wolf"
|
||||
}, "2"), _react.jsx("div", {}, "3"), _react.createElement("div", Object.assign({}, props, {
|
||||
key: "4"
|
||||
}))]
|
||||
})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx auto import none 1`] = `
|
||||
var x = React.jsx(React.Fragment, {
|
||||
children: React.jsxs("div", {
|
||||
children: [React.jsx("div", {}, "1"), React.jsx("div", {
|
||||
meow: "wolf"
|
||||
}, "2"), React.jsx("div", {}, "3"), React.createElement("div", Object.assign({}, props, {
|
||||
key: "4"
|
||||
}))]
|
||||
})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx auto import pragma overrides regular pragma 1`] = `
|
||||
import _default from "foobar";
|
||||
|
||||
/** @jsxAutoImport defaultExport */
|
||||
var x = _default.jsx("div", {
|
||||
children: _default.jsx("span", {})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx auto import require 1`] = `
|
||||
var _react = require("react");
|
||||
|
||||
var x = _react.jsx(_react.Fragment, {
|
||||
children: _react.jsxs("div", {
|
||||
children: [_react.jsx("div", {}, "1"), _react.jsx("div", {
|
||||
meow: "wolf"
|
||||
}, "2"), _react.jsx("div", {}, "3"), _react.createElement("div", Object.assign({}, props, {
|
||||
key: "4"
|
||||
}))]
|
||||
})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx auto import undefined 1`] = `
|
||||
var x = React.jsx(React.Fragment, {
|
||||
children: React.jsxs("div", {
|
||||
children: [React.jsx("div", {}, "1"), React.jsx("div", {
|
||||
meow: "wolf"
|
||||
}, "2"), React.jsx("div", {}, "3"), React.createElement("div", Object.assign({}, props, {
|
||||
key: "4"
|
||||
}))]
|
||||
})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx auto import with namespaces already defined 1`] = `
|
||||
import * as _react3 from "react";
|
||||
import * as _react from "foo";
|
||||
|
||||
const react = _react(1);
|
||||
|
||||
const _react1 = react;
|
||||
const _react2 = react;
|
||||
|
||||
var x = _react3.jsxs("div", {
|
||||
children: [_react3.jsx("div", {}, "1"), _react3.jsx("div", {
|
||||
meow: "wolf"
|
||||
}, "2"), _react3.jsx("div", {}, "3"), _react3.createElement("div", Object.assign({}, props, {
|
||||
key: "4"
|
||||
}))]
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx auto import with no JSX 1`] = `var foo = "<div></div>";`;
|
||||
|
||||
exports[`transform react to jsx auto import with react already defined 1`] = `
|
||||
import * as _react from "react";
|
||||
import * as react from "react";
|
||||
var y = react.createElement("div", {
|
||||
foo: 1
|
||||
});
|
||||
|
||||
var x = _react.jsxs("div", {
|
||||
children: [_react.jsx("div", {}, "1"), _react.jsx("div", {
|
||||
meow: "wolf"
|
||||
}, "2"), _react.jsx("div", {}, "3"), _react.createElement("div", Object.assign({}, props, {
|
||||
key: "4"
|
||||
}))]
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx complicated scope named exports 1`] = `
|
||||
import { jsx as _jsx2 } from "react";
|
||||
|
||||
const Bar = function () {
|
||||
const Foo = function () {
|
||||
const Component = function ({
|
||||
thing,
|
||||
..._react
|
||||
}) {
|
||||
if (!thing) {
|
||||
var _react2 = "something useless";
|
||||
|
||||
var b = _react3();
|
||||
|
||||
var jsx = 1;
|
||||
var _jsx = 2;
|
||||
return _jsx2("div", {});
|
||||
}
|
||||
|
||||
;
|
||||
return _jsx2("span", {});
|
||||
};
|
||||
};
|
||||
};
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx complicated scope require 1`] = `
|
||||
var _react4 = require("react");
|
||||
|
||||
const Bar = function () {
|
||||
const Foo = function () {
|
||||
const Component = function ({
|
||||
thing,
|
||||
..._react
|
||||
}) {
|
||||
if (!thing) {
|
||||
var _react2 = "something useless";
|
||||
|
||||
var b = _react3();
|
||||
|
||||
var c = _react5();
|
||||
|
||||
var jsx = 1;
|
||||
var _jsx = 2;
|
||||
return _react4.jsx("div", {});
|
||||
}
|
||||
|
||||
;
|
||||
return _react4.jsx("span", {});
|
||||
};
|
||||
};
|
||||
};
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx concatenates adjacent string literals 1`] = `
|
||||
var x = React.jsxs("div", {
|
||||
children: ["foo", "bar", "baz", React.jsx("div", {
|
||||
children: "buz bang"
|
||||
}), "qux", null, "quack"]
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx duplicate children prop should transform into sequence expression with actual children 1`] = `
|
||||
React.jsx(Component, {
|
||||
children: (1, "2")
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx duplicate children prop should transform into sequence expression with next prop 1`] = `
|
||||
React.jsx(Component, {
|
||||
foo: (1, 3),
|
||||
children: "2"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx duplicate children prop should transform into sequence expression with spread 1`] = `
|
||||
React.jsx(Component, Object.assign({}, (1, x), {
|
||||
children: "2"
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx duplicate children props should transform into sequence expression with next prop 1`] = `
|
||||
React.jsx(Component, {
|
||||
foo: (1, 4, 3),
|
||||
children: "2"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx fragment with no children 1`] = `var x = React.jsx(React.Fragment, {});`;
|
||||
|
||||
exports[`transform react to jsx fragments 1`] = `
|
||||
var x = React.jsx(React.Fragment, {
|
||||
children: React.jsx("div", {})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx fragments in dev mode (no key and source) 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV(React.Fragment, {
|
||||
children: React.jsxDEV("div", {}, undefined, false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1
|
||||
}, this)
|
||||
}, undefined, false);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx fragments to set keys 1`] = `var x = React.jsx(React.Fragment, {}, "foo");`;
|
||||
|
||||
exports[`transform react to jsx import source pragma overrides regular pragma 1`] = `
|
||||
import * as _baz from "baz";
|
||||
|
||||
/** @jsxImportSource baz */
|
||||
var x = _baz.jsx("div", {
|
||||
children: _baz.jsx("span", {})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx multiple pragmas work 1`] = `
|
||||
import _default from "baz";
|
||||
|
||||
/** Some comment here
|
||||
* @jsxImportSource baz
|
||||
* @jsxAutoImport defaultExport
|
||||
*/
|
||||
var x = _default.jsx("div", {
|
||||
children: _default.jsx("span", {})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx nonStatic children 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV("div", {
|
||||
children: [React.jsxDEV("span", {}, '0', false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 3
|
||||
}, this), React.jsxDEV("span", {}, '1', false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 3
|
||||
}, this)]
|
||||
}, undefined, false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 2
|
||||
}, this);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx properly handles keys 1`] = `
|
||||
var x = React.jsxs("div", {
|
||||
children: [React.jsx("div", {}, "1"), React.jsx("div", {
|
||||
meow: "wolf"
|
||||
}, "2"), React.jsx("div", {}, "3")]
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx properly passes in source and self 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV("div", {}, undefined, false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1
|
||||
}, this);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should allow constructor as prop 1`] = `
|
||||
React.jsx(Component, {
|
||||
constructor: "foo"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.jsx(Namespace.DeepNamespace.Component, {});`;
|
||||
|
||||
exports[`transform react to jsx should allow elements as attributes 1`] = `
|
||||
React.jsx("div", {
|
||||
attr: React.jsx("div", {})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should allow js namespacing 1`] = `React.jsx(Namespace.Component, {});`;
|
||||
|
||||
exports[`transform react to jsx should allow nested fragments 1`] = `
|
||||
React.jsx("div", {
|
||||
children: React.jsxs(React.Fragment, {
|
||||
children: [React.jsxs(React.Fragment, {
|
||||
children: [React.jsx("span", {
|
||||
children: "Hello"
|
||||
}), React.jsx("span", {
|
||||
children: "world"
|
||||
})]
|
||||
}), React.jsxs(React.Fragment, {
|
||||
children: [React.jsx("span", {
|
||||
children: "Goodbye"
|
||||
}), React.jsx("span", {
|
||||
children: "world"
|
||||
})]
|
||||
})]
|
||||
})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = `
|
||||
var x = React.jsx("div", {
|
||||
children: React.jsx(Component, {})
|
||||
});
|
||||
var x = React.jsx("div", {
|
||||
children: props.children
|
||||
});
|
||||
var x = React.jsx(Composite, {
|
||||
children: props.children
|
||||
});
|
||||
var x = React.jsx(Composite, {
|
||||
children: React.jsx(Composite2, {})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should convert simple tags 1`] = `var x = React.jsx("div", {});`;
|
||||
|
||||
exports[`transform react to jsx should convert simple text 1`] = `
|
||||
var x = React.jsx("div", {
|
||||
children: "text"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should escape xhtml jsxattribute 1`] = `
|
||||
React.jsx("div", {
|
||||
id: "w\\xF4w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
id: "w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
id: "w < w"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should escape xhtml jsxtext 1`] = `
|
||||
React.jsx("div", {
|
||||
children: "wow"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "w\\xF4w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "w & w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "w & w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "w \\xA0 w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "this should not parse as unicode: \\xA0"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "this should parse as nbsp: \\xA0 "
|
||||
});
|
||||
React.jsxs("div", {
|
||||
children: ["this should parse as unicode: ", ' ']
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "w < w"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should handle attributed elements 1`] = `
|
||||
var HelloMessage = React.createClass({
|
||||
render: function () {
|
||||
return React.jsxs("div", {
|
||||
children: ["Hello ", this.props.name]
|
||||
});
|
||||
}
|
||||
});
|
||||
React.render(React.jsx(HelloMessage, {
|
||||
name: React.jsx("span", {
|
||||
children: "Sebastian"
|
||||
})
|
||||
}), mountNode);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should handle has own property correctly 1`] = `
|
||||
React.jsx("hasOwnProperty", {
|
||||
children: "testing"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should have correct comma in nested children 1`] = `
|
||||
var x = React.jsxs("div", {
|
||||
children: [React.jsx("div", {
|
||||
children: React.jsx("br", {})
|
||||
}), React.jsxs(Component, {
|
||||
children: [foo, React.jsx("br", {}), bar]
|
||||
}), React.jsx("br", {})]
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = `
|
||||
var x = React.jsx("div", {
|
||||
attr1: "foo" + "bar",
|
||||
attr2: "foo" + "bar" + "baz" + "bug",
|
||||
attr3: "foo" + "bar" + "baz" + "bug",
|
||||
attr4: "baz"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should not add quotes to identifier names 1`] = `
|
||||
var e = React.jsx(F, {
|
||||
aaa: true,
|
||||
new: true,
|
||||
const: true,
|
||||
var: true,
|
||||
default: true,
|
||||
"foo-bar": true
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `
|
||||
React.jsx("div", {
|
||||
children: "\\xA0 "
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `
|
||||
React.jsx("div", {
|
||||
children: "\\xA0"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `
|
||||
var x = React.jsxs("div", {
|
||||
children: [React.jsx("span", {}), React.jsx("br", {})]
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should properly handle comments between props 1`] = `
|
||||
var x = React.jsx("div", {
|
||||
/* a multi-line
|
||||
comment */
|
||||
attr1: "foo",
|
||||
children: React.jsx("span", {
|
||||
// a double-slash comment
|
||||
attr2: "bar"
|
||||
})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should properly handle potentially null variables 1`] = `
|
||||
var foo = null;
|
||||
var x = React.jsx("div", Object.assign({}, foo));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should quote jsx attributes 1`] = `
|
||||
React.jsx("button", {
|
||||
"data-value": "a value",
|
||||
children: "Button"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should support xml namespaces if flag 1`] = `
|
||||
React.jsx("f:image", {
|
||||
"n:attr": true
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.jsx("font-face", {});`;
|
||||
|
||||
exports[`transform react to jsx static children 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV("div", {
|
||||
children: [React.jsxDEV("span", {}, undefined, false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 3
|
||||
}, this), [React.jsxDEV("span", {}, '0', false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 4
|
||||
}, this), React.jsxDEV("span", {}, '1', false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 4
|
||||
}, this)]]
|
||||
}, undefined, true, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 2
|
||||
}, this);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = `
|
||||
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
|
||||
React.jsx(Component, _extends({
|
||||
y: 2
|
||||
}, x));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx uses createElement when the key comes after a spread 1`] = `
|
||||
var x = React.createElement("div", Object.assign({}, props, {
|
||||
key: "1",
|
||||
foo: "bar"
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx uses jsx when the key comes before a spread 1`] = `
|
||||
var x = React.jsx("div", Object.assign({}, props, {
|
||||
foo: "bar"
|
||||
}), "1");
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx uses jsxDEV instead of jsx in dev mode 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV("span", {
|
||||
propOne: "one",
|
||||
children: "Hi"
|
||||
}, undefined, false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1
|
||||
}, this);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = `
|
||||
React.jsx(Component, Object.assign({}, x, {
|
||||
y: 2,
|
||||
z: true
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = `
|
||||
React.jsx(Component, Object.assign({
|
||||
y: 2,
|
||||
z: true
|
||||
}, x));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = `
|
||||
React.jsx(Component, Object.assign({
|
||||
y: 2
|
||||
}, x, {
|
||||
z: true
|
||||
}));
|
||||
`;
|
||||
3
packages/babel-plugin-react-jsx/index.js
Normal file
3
packages/babel-plugin-react-jsx/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./src/TransformJSXToReactBabelPlugin');
|
||||
7
packages/babel-plugin-react-jsx/npm/index.js
Normal file
7
packages/babel-plugin-react-jsx/npm/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-jsx-babel.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-jsx-babel.development.js');
|
||||
}
|
||||
18
packages/babel-plugin-react-jsx/package.json
Normal file
18
packages/babel-plugin-react-jsx/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "babel-plugin-react-jsx",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "@babel/plugin-transform-react-jsx",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.0.0",
|
||||
"esutils": "^2.0.0"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"index.js",
|
||||
"build-info.json",
|
||||
"cjs/",
|
||||
"umd/"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,799 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
||||
|
||||
// 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.
|
||||
// © 2019 GitHub, Inc.
|
||||
'use strict';
|
||||
|
||||
const esutils = require('esutils');
|
||||
const {
|
||||
isModule,
|
||||
addNamespace,
|
||||
addNamed,
|
||||
addDefault,
|
||||
} = require('@babel/helper-module-imports');
|
||||
|
||||
// These are all the valid auto import types (under the config autoImport)
|
||||
// that a user can specific
|
||||
const IMPORT_TYPES = {
|
||||
none: 'none', // default option. Will not import anything
|
||||
require: 'require', // var _react = require("react");
|
||||
namespace: 'namespace', // import * as _react from "react";
|
||||
defaultExport: 'defaultExport', // import _default from "react";
|
||||
namedExports: 'namedExports', // import { jsx } from "react";
|
||||
};
|
||||
|
||||
const JSX_AUTO_IMPORT_ANNOTATION_REGEX = /\*?\s*@jsxAutoImport\s+([^\s]+)/;
|
||||
const JSX_IMPORT_SOURCE_ANNOTATION_REGEX = /\*?\s*@jsxImportSource\s+([^\s]+)/;
|
||||
|
||||
// We want to use React.createElement, even in the case of
|
||||
// jsx, for <div {...props} key={key} /> to distinguish it
|
||||
// from <div key={key} {...props} />. This is an intermediary
|
||||
// step while we deprecate key spread from props. Afterwards,
|
||||
// we will remove createElement entirely
|
||||
function shouldUseCreateElement(path, types) {
|
||||
const openingPath = path.get('openingElement');
|
||||
const attributes = openingPath.node.attributes;
|
||||
|
||||
let seenPropsSpread = false;
|
||||
for (let i = 0; i < attributes.length; i++) {
|
||||
const attr = attributes[i];
|
||||
if (
|
||||
seenPropsSpread &&
|
||||
types.isJSXAttribute(attr) &&
|
||||
attr.name.name === 'key'
|
||||
) {
|
||||
return true;
|
||||
} else if (types.isJSXSpreadAttribute(attr)) {
|
||||
seenPropsSpread = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function helper(babel, opts) {
|
||||
const {types: t} = babel;
|
||||
|
||||
const visitor = {};
|
||||
|
||||
visitor.JSXNamespacedName = function(path, state) {
|
||||
const throwIfNamespace =
|
||||
state.opts.throwIfNamespace === undefined
|
||||
? true
|
||||
: !!state.opts.throwIfNamespace;
|
||||
if (throwIfNamespace) {
|
||||
throw path.buildCodeFrameError(
|
||||
`Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
|
||||
You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
visitor.JSXSpreadChild = function(path) {
|
||||
throw path.buildCodeFrameError(
|
||||
'Spread children are not supported in React.',
|
||||
);
|
||||
};
|
||||
|
||||
visitor.JSXElement = {
|
||||
exit(path, file) {
|
||||
let callExpr;
|
||||
if (shouldUseCreateElement(path, t)) {
|
||||
callExpr = buildCreateElementCall(path, file);
|
||||
} else {
|
||||
callExpr = buildJSXElementCall(path, file);
|
||||
}
|
||||
|
||||
if (callExpr) {
|
||||
path.replaceWith(t.inherits(callExpr, path.node));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
visitor.JSXFragment = {
|
||||
exit(path, file) {
|
||||
if (opts.compat) {
|
||||
throw path.buildCodeFrameError(
|
||||
'Fragment tags are only supported in React 16 and up.',
|
||||
);
|
||||
}
|
||||
let callExpr = buildJSXFragmentCall(path, file);
|
||||
|
||||
if (callExpr) {
|
||||
path.replaceWith(t.inherits(callExpr, path.node));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return visitor;
|
||||
|
||||
function convertJSXIdentifier(node, parent) {
|
||||
if (t.isJSXIdentifier(node)) {
|
||||
if (node.name === 'this' && t.isReferenced(node, parent)) {
|
||||
return t.thisExpression();
|
||||
} else if (esutils.keyword.isIdentifierNameES6(node.name)) {
|
||||
node.type = 'Identifier';
|
||||
} else {
|
||||
return t.stringLiteral(node.name);
|
||||
}
|
||||
} else if (t.isJSXMemberExpression(node)) {
|
||||
return t.memberExpression(
|
||||
convertJSXIdentifier(node.object, node),
|
||||
convertJSXIdentifier(node.property, node),
|
||||
);
|
||||
} else if (t.isJSXNamespacedName(node)) {
|
||||
/**
|
||||
* If there is flag "throwIfNamespace"
|
||||
* print XMLNamespace like string literal
|
||||
*/
|
||||
return t.stringLiteral(`${node.namespace.name}:${node.name.name}`);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function convertAttributeValue(node) {
|
||||
if (t.isJSXExpressionContainer(node)) {
|
||||
return node.expression;
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
function convertAttribute(node, duplicateChildren) {
|
||||
let value = convertAttributeValue(node.value || t.booleanLiteral(true));
|
||||
|
||||
if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
|
||||
value.value = value.value.replace(/\n\s+/g, ' ');
|
||||
|
||||
// "raw" JSXText should not be used from a StringLiteral because it needs to be escaped.
|
||||
if (value.extra && value.extra.raw) {
|
||||
delete value.extra.raw;
|
||||
}
|
||||
}
|
||||
if (duplicateChildren && duplicateChildren.length > 0) {
|
||||
value = t.sequenceExpression([...duplicateChildren, value]);
|
||||
}
|
||||
|
||||
if (t.isJSXNamespacedName(node.name)) {
|
||||
node.name = t.stringLiteral(
|
||||
node.name.namespace.name + ':' + node.name.name.name,
|
||||
);
|
||||
} else if (esutils.keyword.isIdentifierNameES6(node.name.name)) {
|
||||
node.name.type = 'Identifier';
|
||||
} else {
|
||||
node.name = t.stringLiteral(node.name.name);
|
||||
}
|
||||
|
||||
return t.inherits(t.objectProperty(node.name, value), node);
|
||||
}
|
||||
|
||||
// Builds JSX into:
|
||||
// Production: React.jsx(type, arguments, key)
|
||||
// Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self)
|
||||
function buildJSXElementCall(path, file) {
|
||||
if (opts.filter && !opts.filter(path.node, file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openingPath = path.get('openingElement');
|
||||
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
||||
|
||||
const tagExpr = convertJSXIdentifier(
|
||||
openingPath.node.name,
|
||||
openingPath.node,
|
||||
);
|
||||
const args = [];
|
||||
|
||||
let tagName;
|
||||
if (t.isIdentifier(tagExpr)) {
|
||||
tagName = tagExpr.name;
|
||||
} else if (t.isLiteral(tagExpr)) {
|
||||
tagName = tagExpr.value;
|
||||
}
|
||||
|
||||
const state = {
|
||||
tagExpr: tagExpr,
|
||||
tagName: tagName,
|
||||
args: args,
|
||||
};
|
||||
|
||||
if (opts.pre) {
|
||||
opts.pre(state, file);
|
||||
}
|
||||
|
||||
let attribs = [];
|
||||
let key;
|
||||
let source;
|
||||
let self;
|
||||
|
||||
// for React.jsx, key, __source (dev), and __self (dev) is passed in as
|
||||
// a separate argument rather than in the args object. We go through the
|
||||
// props and filter out these three keywords so we can pass them in
|
||||
// as separate arguments later
|
||||
for (let i = 0; i < openingPath.node.attributes.length; i++) {
|
||||
const attr = openingPath.node.attributes[i];
|
||||
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
|
||||
if (attr.name.name === 'key') {
|
||||
key = convertAttribute(attr).value;
|
||||
} else if (attr.name.name === '__source') {
|
||||
source = convertAttribute(attr).value;
|
||||
} else if (attr.name.name === '__self') {
|
||||
self = convertAttribute(attr).value;
|
||||
} else {
|
||||
attribs.push(attr);
|
||||
}
|
||||
} else {
|
||||
attribs.push(attr);
|
||||
}
|
||||
}
|
||||
|
||||
if (attribs.length || path.node.children.length) {
|
||||
attribs = buildJSXOpeningElementAttributes(
|
||||
attribs,
|
||||
file,
|
||||
path.node.children,
|
||||
);
|
||||
} else {
|
||||
// attributes should never be null
|
||||
attribs = t.objectExpression([]);
|
||||
}
|
||||
|
||||
args.push(attribs);
|
||||
|
||||
if (!file.opts.development) {
|
||||
if (key !== undefined) {
|
||||
args.push(key);
|
||||
}
|
||||
} else {
|
||||
// isStaticChildren, __source, and __self are only used in development
|
||||
args.push(
|
||||
key === undefined ? t.identifier('undefined') : key,
|
||||
t.booleanLiteral(path.node.children.length > 1),
|
||||
source === undefined ? t.identifier('undefined') : source,
|
||||
self === undefined ? t.identifier('undefined') : self,
|
||||
);
|
||||
}
|
||||
|
||||
if (opts.post) {
|
||||
opts.post(state, file);
|
||||
}
|
||||
|
||||
return (
|
||||
state.call ||
|
||||
t.callExpression(
|
||||
path.node.children.length > 1 ? state.staticCallee : state.callee,
|
||||
args,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function isChildrenProp(prop) {
|
||||
return (
|
||||
t.isJSXAttribute(prop) &&
|
||||
t.isJSXIdentifier(prop.name) &&
|
||||
prop.name.name === 'children'
|
||||
);
|
||||
}
|
||||
|
||||
// Builds props for React.jsx. This function adds children into the props
|
||||
// and ensures that props is always an object
|
||||
function buildJSXOpeningElementAttributes(attribs, file, children) {
|
||||
let _props = [];
|
||||
const objs = [];
|
||||
|
||||
// In order to avoid having duplicate "children" keys, we avoid
|
||||
// pushing the "children" prop if we have actual children. However,
|
||||
// the children prop may have side effects, so to be certain
|
||||
// these side effects are evaluated, we add them to the following prop
|
||||
// as a sequence expression to preserve order. So:
|
||||
// <div children={x++} foo={y}>{child}</div> becomes
|
||||
// React.jsx('div', {foo: (x++, y), children: child});
|
||||
// duplicateChildren contains the extra children prop values
|
||||
let duplicateChildren = [];
|
||||
|
||||
const hasChildren = children && children.length > 0;
|
||||
|
||||
const useBuiltIns = file.opts.useBuiltIns || false;
|
||||
if (typeof useBuiltIns !== 'boolean') {
|
||||
throw new Error(
|
||||
'transform-react-jsx currently only accepts a boolean option for ' +
|
||||
'useBuiltIns (defaults to false)',
|
||||
);
|
||||
}
|
||||
|
||||
while (attribs.length) {
|
||||
const prop = attribs.shift();
|
||||
if (hasChildren && isChildrenProp(prop)) {
|
||||
duplicateChildren.push(convertAttributeValue(prop.value));
|
||||
} else if (t.isJSXSpreadAttribute(prop)) {
|
||||
_props = pushProps(_props, objs);
|
||||
if (duplicateChildren.length > 0) {
|
||||
objs.push(
|
||||
t.sequenceExpression([...duplicateChildren, prop.argument]),
|
||||
);
|
||||
duplicateChildren = [];
|
||||
} else {
|
||||
objs.push(prop.argument);
|
||||
}
|
||||
} else {
|
||||
_props.push(convertAttribute(prop, duplicateChildren));
|
||||
if (duplicateChildren.length > 0) {
|
||||
duplicateChildren = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In React.JSX, children is no longer a separate argument, but passed in
|
||||
// through the argument object
|
||||
if (hasChildren) {
|
||||
if (children.length === 1) {
|
||||
_props.push(
|
||||
t.objectProperty(
|
||||
t.identifier('children'),
|
||||
duplicateChildren.length > 0
|
||||
? t.sequenceExpression([...duplicateChildren, children[0]])
|
||||
: children[0],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
_props.push(
|
||||
t.objectProperty(
|
||||
t.identifier('children'),
|
||||
duplicateChildren.length > 0
|
||||
? t.sequenceExpression([
|
||||
...duplicateChildren,
|
||||
t.arrayExpression(children),
|
||||
])
|
||||
: t.arrayExpression(children),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pushProps(_props, objs);
|
||||
|
||||
if (objs.length === 1) {
|
||||
// only one object
|
||||
if (!t.isObjectExpression(objs[0])) {
|
||||
// if the prop object isn't an object, use Object.assign or _extends
|
||||
// to ensure that the prop will always be an object (as opposed to a variable
|
||||
// that could be null at some point)
|
||||
const expressionHelper = useBuiltIns
|
||||
? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
|
||||
: file.addHelper('extends');
|
||||
|
||||
attribs = t.callExpression(expressionHelper, [
|
||||
t.objectExpression([]),
|
||||
objs[0],
|
||||
]);
|
||||
} else {
|
||||
attribs = objs[0];
|
||||
}
|
||||
} else {
|
||||
// looks like we have multiple objects
|
||||
if (!t.isObjectExpression(objs[0])) {
|
||||
objs.unshift(t.objectExpression([]));
|
||||
}
|
||||
|
||||
const expressionHelper = useBuiltIns
|
||||
? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
|
||||
: file.addHelper('extends');
|
||||
|
||||
// spread it
|
||||
attribs = t.callExpression(expressionHelper, objs);
|
||||
}
|
||||
|
||||
return attribs;
|
||||
}
|
||||
|
||||
// Builds JSX Fragment <></> into
|
||||
// Production: React.jsx(type, arguments)
|
||||
// Development: React.jsxDEV(type, { children})
|
||||
function buildJSXFragmentCall(path, file) {
|
||||
if (opts.filter && !opts.filter(path.node, file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openingPath = path.get('openingElement');
|
||||
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
||||
|
||||
const args = [];
|
||||
const tagName = null;
|
||||
const tagExpr = file.get('jsxFragIdentifier')();
|
||||
|
||||
const state = {
|
||||
tagExpr: tagExpr,
|
||||
tagName: tagName,
|
||||
args: args,
|
||||
};
|
||||
|
||||
if (opts.pre) {
|
||||
opts.pre(state, file);
|
||||
}
|
||||
|
||||
let childrenNode;
|
||||
if (path.node.children.length > 0) {
|
||||
if (path.node.children.length === 1) {
|
||||
childrenNode = path.node.children[0];
|
||||
} else {
|
||||
childrenNode = t.arrayExpression(path.node.children);
|
||||
}
|
||||
}
|
||||
|
||||
args.push(
|
||||
t.objectExpression(
|
||||
childrenNode !== undefined
|
||||
? [t.objectProperty(t.identifier('children'), childrenNode)]
|
||||
: [],
|
||||
),
|
||||
);
|
||||
|
||||
if (file.opts.development) {
|
||||
args.push(
|
||||
t.identifier('undefined'),
|
||||
t.booleanLiteral(path.node.children.length > 1),
|
||||
);
|
||||
}
|
||||
|
||||
if (opts.post) {
|
||||
opts.post(state, file);
|
||||
}
|
||||
|
||||
return (
|
||||
state.call ||
|
||||
t.callExpression(
|
||||
path.node.children.length > 1 ? state.staticCallee : state.callee,
|
||||
args,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Builds JSX into:
|
||||
// Production: React.createElement(type, arguments, children)
|
||||
// Development: React.createElement(type, arguments, children, source, self)
|
||||
function buildCreateElementCall(path, file) {
|
||||
if (opts.filter && !opts.filter(path.node, file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openingPath = path.get('openingElement');
|
||||
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
||||
|
||||
const tagExpr = convertJSXIdentifier(
|
||||
openingPath.node.name,
|
||||
openingPath.node,
|
||||
);
|
||||
const args = [];
|
||||
|
||||
let tagName;
|
||||
if (t.isIdentifier(tagExpr)) {
|
||||
tagName = tagExpr.name;
|
||||
} else if (t.isLiteral(tagExpr)) {
|
||||
tagName = tagExpr.value;
|
||||
}
|
||||
|
||||
const state = {
|
||||
tagExpr: tagExpr,
|
||||
tagName: tagName,
|
||||
args: args,
|
||||
};
|
||||
|
||||
if (opts.pre) {
|
||||
opts.pre(state, file);
|
||||
}
|
||||
|
||||
let attribs = openingPath.node.attributes;
|
||||
if (attribs.length) {
|
||||
attribs = buildCreateElementOpeningElementAttributes(attribs, file);
|
||||
} else {
|
||||
attribs = t.nullLiteral();
|
||||
}
|
||||
|
||||
args.push(attribs, ...path.node.children);
|
||||
|
||||
if (opts.post) {
|
||||
opts.post(state, file);
|
||||
}
|
||||
|
||||
return state.call || t.callExpression(state.oldCallee, args);
|
||||
}
|
||||
|
||||
function pushProps(_props, objs) {
|
||||
if (!_props.length) {
|
||||
return _props;
|
||||
}
|
||||
|
||||
objs.push(t.objectExpression(_props));
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* The logic for this is quite terse. It's because we need to
|
||||
* support spread elements. We loop over all attributes,
|
||||
* breaking on spreads, we then push a new object containing
|
||||
* all prior attributes to an array for later processing.
|
||||
*/
|
||||
function buildCreateElementOpeningElementAttributes(attribs, file) {
|
||||
let _props = [];
|
||||
const objs = [];
|
||||
|
||||
const useBuiltIns = file.opts.useBuiltIns || false;
|
||||
if (typeof useBuiltIns !== 'boolean') {
|
||||
throw new Error(
|
||||
'transform-react-jsx currently only accepts a boolean option for ' +
|
||||
'useBuiltIns (defaults to false)',
|
||||
);
|
||||
}
|
||||
|
||||
while (attribs.length) {
|
||||
const prop = attribs.shift();
|
||||
if (t.isJSXSpreadAttribute(prop)) {
|
||||
_props = pushProps(_props, objs);
|
||||
objs.push(prop.argument);
|
||||
} else {
|
||||
const attr = convertAttribute(prop);
|
||||
_props.push(attr);
|
||||
}
|
||||
}
|
||||
|
||||
pushProps(_props, objs);
|
||||
|
||||
if (objs.length === 1) {
|
||||
// only one object
|
||||
attribs = objs[0];
|
||||
} else {
|
||||
// looks like we have multiple objects
|
||||
if (!t.isObjectExpression(objs[0])) {
|
||||
objs.unshift(t.objectExpression([]));
|
||||
}
|
||||
|
||||
const expressionHelper = useBuiltIns
|
||||
? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
|
||||
: file.addHelper('extends');
|
||||
|
||||
// spread it
|
||||
attribs = t.callExpression(expressionHelper, objs);
|
||||
}
|
||||
|
||||
return attribs;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function(babel) {
|
||||
const {types: t} = babel;
|
||||
|
||||
const createIdentifierParser = id => () => {
|
||||
return id
|
||||
.split('.')
|
||||
.map(name => t.identifier(name))
|
||||
.reduce((object, property) => t.memberExpression(object, property));
|
||||
};
|
||||
|
||||
const visitor = helper(babel, {
|
||||
pre(state) {
|
||||
const tagName = state.tagName;
|
||||
const args = state.args;
|
||||
if (t.react.isCompatTag(tagName)) {
|
||||
args.push(t.stringLiteral(tagName));
|
||||
} else {
|
||||
args.push(state.tagExpr);
|
||||
}
|
||||
},
|
||||
|
||||
post(state, pass) {
|
||||
state.callee = pass.get('jsxIdentifier')();
|
||||
state.staticCallee = pass.get('jsxStaticIdentifier')();
|
||||
state.oldCallee = pass.get('oldJSXIdentifier')();
|
||||
},
|
||||
});
|
||||
|
||||
const createIdentifierName = (path, autoImport, name, importName) => {
|
||||
if (autoImport === IMPORT_TYPES.none) {
|
||||
return `React.${name}`;
|
||||
} else if (autoImport === IMPORT_TYPES.namedExports) {
|
||||
if (importName) {
|
||||
const identifierName = `${importName[name]}`;
|
||||
return identifierName;
|
||||
}
|
||||
} else {
|
||||
return `${importName}.${name}`;
|
||||
}
|
||||
};
|
||||
|
||||
function getImportNames(parentPath, state) {
|
||||
const imports = {};
|
||||
parentPath.traverse({
|
||||
JSXElement(path) {
|
||||
if (shouldUseCreateElement(path, t)) {
|
||||
imports.createElement = true;
|
||||
} else if (path.node.children.length > 1) {
|
||||
const importName = state.development ? 'jsxDEV' : 'jsxs';
|
||||
imports[importName] = true;
|
||||
} else {
|
||||
const importName = state.development ? 'jsxDEV' : 'jsx';
|
||||
imports[importName] = true;
|
||||
}
|
||||
},
|
||||
|
||||
JSXFragment(path) {
|
||||
imports.Fragment = true;
|
||||
},
|
||||
});
|
||||
return imports;
|
||||
}
|
||||
|
||||
function hasJSX(parentPath) {
|
||||
let fileHasJSX = false;
|
||||
parentPath.traverse({
|
||||
JSXElement(path) {
|
||||
fileHasJSX = true;
|
||||
path.stop();
|
||||
},
|
||||
|
||||
JSXFragment(path) {
|
||||
fileHasJSX = true;
|
||||
path.stop();
|
||||
},
|
||||
});
|
||||
|
||||
return fileHasJSX;
|
||||
}
|
||||
|
||||
function addAutoImports(path, state) {
|
||||
if (state.autoImport === IMPORT_TYPES.none) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IMPORT_TYPES[state.autoImport] === undefined) {
|
||||
throw path.buildCodeFrameError(
|
||||
'autoImport must be one of the following: ' +
|
||||
Object.keys(IMPORT_TYPES).join(', '),
|
||||
);
|
||||
}
|
||||
if (state.autoImport === IMPORT_TYPES.require && isModule(path)) {
|
||||
throw path.buildCodeFrameError(
|
||||
'Babel `sourceType` must be set to `script` for autoImport ' +
|
||||
'to use `require` syntax. See Babel `sourceType` for details.',
|
||||
);
|
||||
}
|
||||
if (state.autoImport !== IMPORT_TYPES.require && !isModule(path)) {
|
||||
throw path.buildCodeFrameError(
|
||||
'Babel `sourceType` must be set to `module` for autoImport to use `' +
|
||||
state.autoImport +
|
||||
'` syntax. See Babel `sourceType` for details.',
|
||||
);
|
||||
}
|
||||
|
||||
// import {jsx} from "react";
|
||||
// import {createElement} from "react";
|
||||
if (state.autoImport === IMPORT_TYPES.namedExports) {
|
||||
const imports = getImportNames(path, state);
|
||||
const importMap = {};
|
||||
|
||||
Object.keys(imports).forEach(importName => {
|
||||
importMap[importName] = addNamed(path, importName, state.source).name;
|
||||
});
|
||||
|
||||
return importMap;
|
||||
}
|
||||
|
||||
// add import to file and get the import name
|
||||
let name;
|
||||
if (state.autoImport === IMPORT_TYPES.require) {
|
||||
// var _react = require("react");
|
||||
name = addNamespace(path, state.source, {
|
||||
importedInterop: 'uncompiled',
|
||||
}).name;
|
||||
} else if (state.autoImport === IMPORT_TYPES.namespace) {
|
||||
// import * as _react from "react";
|
||||
name = addNamespace(path, state.source).name;
|
||||
} else if (state.autoImport === IMPORT_TYPES.defaultExport) {
|
||||
// import _default from "react";
|
||||
name = addDefault(path, state.source).name;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
visitor.Program = {
|
||||
enter(path, state) {
|
||||
if (hasJSX(path)) {
|
||||
let autoImport = state.opts.autoImport || IMPORT_TYPES.none;
|
||||
let source = state.opts.importSource || 'react';
|
||||
const {file} = state;
|
||||
|
||||
if (file.ast.comments) {
|
||||
for (let i = 0; i < file.ast.comments.length; i++) {
|
||||
const comment = file.ast.comments[i];
|
||||
const jsxAutoImportMatches = JSX_AUTO_IMPORT_ANNOTATION_REGEX.exec(
|
||||
comment.value,
|
||||
);
|
||||
if (jsxAutoImportMatches) {
|
||||
autoImport = jsxAutoImportMatches[1];
|
||||
}
|
||||
const jsxImportSourceMatches = JSX_IMPORT_SOURCE_ANNOTATION_REGEX.exec(
|
||||
comment.value,
|
||||
);
|
||||
if (jsxImportSourceMatches) {
|
||||
source = jsxImportSourceMatches[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const importName = addAutoImports(path, {
|
||||
...state.opts,
|
||||
autoImport,
|
||||
source,
|
||||
});
|
||||
|
||||
state.set(
|
||||
'oldJSXIdentifier',
|
||||
createIdentifierParser(
|
||||
createIdentifierName(path, autoImport, 'createElement', importName),
|
||||
),
|
||||
);
|
||||
|
||||
state.set(
|
||||
'jsxIdentifier',
|
||||
createIdentifierParser(
|
||||
createIdentifierName(
|
||||
path,
|
||||
autoImport,
|
||||
state.opts.development ? 'jsxDEV' : 'jsx',
|
||||
importName,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
state.set(
|
||||
'jsxStaticIdentifier',
|
||||
createIdentifierParser(
|
||||
createIdentifierName(
|
||||
path,
|
||||
autoImport,
|
||||
state.opts.development ? 'jsxDEV' : 'jsxs',
|
||||
importName,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
state.set(
|
||||
'jsxFragIdentifier',
|
||||
createIdentifierParser(
|
||||
createIdentifierName(path, autoImport, 'Fragment', importName),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
visitor.JSXAttribute = function(path) {
|
||||
if (t.isJSXElement(path.node.value)) {
|
||||
path.node.value = t.jsxExpressionContainer(path.node.value);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
name: 'transform-react-jsx',
|
||||
visitor,
|
||||
};
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "create-subscription",
|
||||
"description": "utility for subscribing to external data sources inside React components",
|
||||
"version": "16.13.1",
|
||||
"version": "16.12.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
let createSubscription;
|
||||
let BehaviorSubject;
|
||||
let ReactFeatureFlags;
|
||||
let React;
|
||||
let ReactNoop;
|
||||
let Scheduler;
|
||||
@@ -20,7 +21,8 @@ describe('createSubscription', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
createSubscription = require('create-subscription').createSubscription;
|
||||
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
@@ -1,43 +0,0 @@
|
||||
## 4.0.3
|
||||
* Remove the heuristic that checks all Hooks ending with `Effect` due to too many false positives. ([@gaearon](https://github.com/gaearon) in [#19004](https://github.com/facebook/react/pull/19004))
|
||||
|
||||
## 4.0.2
|
||||
* Prevent Hooks that have `Effect` in the middle from being considered effects. ([@surgeboris](https://github.com/surgeboris) in [#18907](https://github.com/facebook/react/pull/18907))
|
||||
|
||||
## 4.0.1
|
||||
* Declare support for ESLint 7. ([@MichaelDeBoey](https://github.com/MichaelDeBoey) in [#18878](https://github.com/facebook/react/pull/18878))
|
||||
|
||||
## 4.0.0
|
||||
|
||||
* **New Violations:** Consider `PascalCase.useFoo()` calls as Hooks. ([@cyan33](https://github.com/cyan33) in [#18722](https://github.com/facebook/react/pull/18722))
|
||||
* **New Violations:** Check callback body when it's not written inline. ([@gaearon](https://github.com/gaearon) in [#18435](https://github.com/facebook/react/pull/18435))
|
||||
* **New Violations:** Check dependencies for all Hooks ending with `Effect`. ([@airjp73](https://github.com/airjp73) in [#18580](https://github.com/facebook/react/pull/18580))
|
||||
* Add a way to enable the dangerous autofix. ([@gaearon](https://github.com/gaearon) in [#18437](https://github.com/facebook/react/pull/18437))
|
||||
* Offer a more sensible suggestion when encountering an assignment. ([@Zzzen](https://github.com/Zzzen) in [#16784](https://github.com/facebook/react/pull/16784))
|
||||
* Consider TypeScript casts of `useRef` as constant. ([@sophiebits](https://github.com/sophiebits) in [#18496](https://github.com/facebook/react/pull/18496))
|
||||
* Add documentation. ([@ghmcadams](https://github.com/ghmcadams) in [#16607](https://github.com/facebook/react/pull/16607))
|
||||
|
||||
## 3.0.0
|
||||
|
||||
* **New Violations:** Forbid calling Hooks from classes. ([@ianobermiller](https://github.com/ianobermiller) in [#18341](https://github.com/facebook/react/pull/18341))
|
||||
* Add a recommended config. ([@SimenB](https://github.com/SimenB) in [#14762](https://github.com/facebook/react/pull/14762))
|
||||
|
||||
## 2.5.0
|
||||
|
||||
* Fix a misleading error message in loops. ([@M-Izadmehr](https://github.com/M-Izadmehr) in [#16853](https://github.com/facebook/react/pull/16853))
|
||||
|
||||
## 2.4.0
|
||||
|
||||
* **New Violations:** Run checks for functions passed to `forwardRef`. ([@dprgarner](https://github.com/dprgarner) in [#17255](https://github.com/facebook/react/pull/17255))
|
||||
* **New Violations:** Check for ref usage in any Hook containing the word `Effect`. ([@gaearon](https://github.com/gaearon) in [#17663](https://github.com/facebook/react/pull/17663))
|
||||
* Disable dangerous autofix and use ESLint Suggestions API instead. ([@wdoug](https://github.com/wdoug) in [#17385](https://github.com/facebook/react/pull/17385))
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* **New Violations:** Forbid calling Hooks at the top level. ([@gaearon](https://github.com/gaearon) in [#16455](https://github.com/facebook/react/pull/16455))
|
||||
* Fix a crash when referencing arguments in arrow functions. ([@hristo-kanchev](https://github.com/hristo-kanchev) in [#16356](https://github.com/facebook/react/pull/16356))
|
||||
|
||||
|
||||
## 1.x
|
||||
|
||||
The 1.x releases aren’t noted in this changelog, but you can find them in the [commit history](https://github.com/facebook/react/commits/master/packages/eslint-plugin-react-hooks).
|
||||
@@ -18,20 +18,7 @@ npm install eslint-plugin-react-hooks --save-dev
|
||||
yarn add eslint-plugin-react-hooks --dev
|
||||
```
|
||||
|
||||
Then extend the recommended eslint config:
|
||||
|
||||
```js
|
||||
{
|
||||
"extends": [
|
||||
// ...
|
||||
"plugin:react-hooks/recommended"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
If you want more fine-grained configuration, you can instead add a snippet like this to your ESLint configuration file:
|
||||
Then add it to your ESLint configuration:
|
||||
|
||||
```js
|
||||
{
|
||||
@@ -47,29 +34,12 @@ If you want more fine-grained configuration, you can instead add a snippet like
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
`exhaustive-deps` can be configured to validate dependencies of custom Hooks with the `additionalHooks` option.
|
||||
This option accepts a regex to match the names of custom Hooks that have dependencies.
|
||||
|
||||
```js
|
||||
{
|
||||
"rules": {
|
||||
// ...
|
||||
"react-hooks/exhaustive-deps": ["warn", {
|
||||
"additionalHooks": "(useMyCustomHook|useMyOtherCustomHook)"
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We suggest to use this option **very sparingly, if at all**. Generally saying, we recommend most custom Hooks to not use the dependencies argument, and instead provide a higher-level API that is more focused around a specific use case.
|
||||
|
||||
## Valid and Invalid Examples
|
||||
|
||||
Please refer to the [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html) documentation and the [Hooks FAQ](https://reactjs.org/docs/hooks-faq.html#what-exactly-do-the-lint-rules-enforce) to learn more about this rule.
|
||||
|
||||
For feedback about the `exhaustive-deps` rule, please post in [this thread](https://github.com/facebook/react/issues/14920).
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -11,6 +11,14 @@ const ESLintTester = require('eslint').RuleTester;
|
||||
const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks');
|
||||
const ReactHooksESLintRule = ReactHooksESLintPlugin.rules['exhaustive-deps'];
|
||||
|
||||
ESLintTester.setDefaultConfig({
|
||||
parser: require.resolve('babel-eslint'),
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* A string template tag that removes padding from the left side of multi-line strings
|
||||
* @param {Array} strings array of code strings (only one expected)
|
||||
@@ -251,182 +259,6 @@ const tests = {
|
||||
}
|
||||
`,
|
||||
},
|
||||
// Nullish coalescing and optional chaining
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useEffect(() => {
|
||||
console.log(props.foo?.bar?.baz ?? null);
|
||||
}, [props.foo]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useEffect(() => {
|
||||
console.log(props.foo?.bar);
|
||||
}, [props.foo?.bar]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useEffect(() => {
|
||||
console.log(props.foo);
|
||||
console.log(props.foo?.bar);
|
||||
}, [props.foo]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useEffect(() => {
|
||||
console.log(props.foo?.toString());
|
||||
}, [props.foo]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useMemo(() => {
|
||||
console.log(props.foo?.toString());
|
||||
}, [props.foo]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useCallback(() => {
|
||||
console.log(props.foo?.toString());
|
||||
}, [props.foo]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const myEffect = () => {
|
||||
// Doesn't use anything
|
||||
};
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
const local = {};
|
||||
function MyComponent() {
|
||||
const myEffect = () => {
|
||||
console.log(local);
|
||||
};
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
const local = {};
|
||||
function MyComponent() {
|
||||
function myEffect() {
|
||||
console.log(local);
|
||||
}
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
function myEffect() {
|
||||
console.log(local);
|
||||
}
|
||||
useEffect(myEffect, [local]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
function myEffect() {
|
||||
console.log(global);
|
||||
}
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
const local = {};
|
||||
function MyComponent() {
|
||||
const myEffect = () => {
|
||||
otherThing()
|
||||
}
|
||||
const otherThing = () => {
|
||||
console.log(local);
|
||||
}
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
// Valid because even though we don't inspect the function itself,
|
||||
// at least it's passed as a dependency.
|
||||
code: normalizeIndent`
|
||||
function MyComponent({delay}) {
|
||||
const local = {};
|
||||
const myEffect = debounce(() => {
|
||||
console.log(local);
|
||||
}, delay);
|
||||
useEffect(myEffect, [myEffect]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent({myEffect}) {
|
||||
useEffect(myEffect, [,myEffect]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent({myEffect}) {
|
||||
useEffect(myEffect, [,myEffect,,]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
let local = {};
|
||||
function myEffect() {
|
||||
console.log(local);
|
||||
}
|
||||
function MyComponent() {
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent({myEffect}) {
|
||||
useEffect(myEffect, [myEffect]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
// Valid because has no deps.
|
||||
code: normalizeIndent`
|
||||
function MyComponent({myEffect}) {
|
||||
useEffect(myEffect);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
@@ -457,24 +289,6 @@ const tests = {
|
||||
`,
|
||||
options: [{additionalHooks: 'useAnotherEffect'}],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useWithoutEffectSuffix(() => {
|
||||
console.log(props.foo);
|
||||
}, []);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
return renderHelperConfusedWithEffect(() => {
|
||||
console.log(props.foo);
|
||||
}, []);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
// Valid because we don't care about hooks outside of components.
|
||||
code: normalizeIndent`
|
||||
@@ -664,16 +478,6 @@ const tests = {
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
let foo = {}
|
||||
useEffect(() => {
|
||||
foo.bar.baz = 43;
|
||||
}, [foo.bar]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
// Valid because we assign ref.current
|
||||
// ourselves. Therefore it's likely not
|
||||
@@ -1253,35 +1057,6 @@ const tests = {
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useCallback(() => {
|
||||
console.log(props.foo?.toString());
|
||||
}, []);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useCallback has a missing dependency: 'props.foo?.toString'. " +
|
||||
'Either include it or remove the dependency array.',
|
||||
suggestions: [
|
||||
{
|
||||
desc:
|
||||
'Update the dependencies array to be: [props.foo?.toString]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useCallback(() => {
|
||||
console.log(props.foo?.toString());
|
||||
}, [props.foo?.toString]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
@@ -1400,29 +1175,6 @@ const tests = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// Invalid because they don't have a meaning without deps.
|
||||
code: normalizeIndent`
|
||||
function MyComponent({ fn1, fn2 }) {
|
||||
const value = useMemo(fn1);
|
||||
const fn = useCallback(fn2);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'React Hook useMemo does nothing when called with only one argument. ' +
|
||||
'Did you forget to pass an array of dependencies?',
|
||||
suggestions: undefined,
|
||||
},
|
||||
{
|
||||
message:
|
||||
'React Hook useCallback does nothing when called with only one argument. ' +
|
||||
'Did you forget to pass an array of dependencies?',
|
||||
suggestions: undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// Regression test
|
||||
code: normalizeIndent`
|
||||
@@ -1854,38 +1606,6 @@ const tests = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent({ history }) {
|
||||
useEffect(() => {
|
||||
return [
|
||||
history?.foo
|
||||
];
|
||||
}, []);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useEffect has a missing dependency: 'history?.foo'. " +
|
||||
'Either include it or remove the dependency array.',
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [history?.foo]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent({ history }) {
|
||||
useEffect(() => {
|
||||
return [
|
||||
history?.foo
|
||||
];
|
||||
}, [history?.foo]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
@@ -6278,356 +5998,6 @@ const tests = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
function myEffect() {
|
||||
console.log(local);
|
||||
}
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useEffect has a missing dependency: 'local'. " +
|
||||
'Either include it or remove the dependency array.',
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [local]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
function myEffect() {
|
||||
console.log(local);
|
||||
}
|
||||
useEffect(myEffect, [local]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
const myEffect = () => {
|
||||
console.log(local);
|
||||
};
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useEffect has a missing dependency: 'local'. " +
|
||||
'Either include it or remove the dependency array.',
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [local]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
const myEffect = () => {
|
||||
console.log(local);
|
||||
};
|
||||
useEffect(myEffect, [local]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
const myEffect = function() {
|
||||
console.log(local);
|
||||
};
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useEffect has a missing dependency: 'local'. " +
|
||||
'Either include it or remove the dependency array.',
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [local]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
const myEffect = function() {
|
||||
console.log(local);
|
||||
};
|
||||
useEffect(myEffect, [local]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
const myEffect = () => {
|
||||
otherThing();
|
||||
};
|
||||
const otherThing = () => {
|
||||
console.log(local);
|
||||
};
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useEffect has a missing dependency: 'otherThing'. " +
|
||||
'Either include it or remove the dependency array.',
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [otherThing]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
const myEffect = () => {
|
||||
otherThing();
|
||||
};
|
||||
const otherThing = () => {
|
||||
console.log(local);
|
||||
};
|
||||
useEffect(myEffect, [otherThing]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
const myEffect = debounce(() => {
|
||||
console.log(local);
|
||||
}, delay);
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useEffect has a missing dependency: 'myEffect'. " +
|
||||
'Either include it or remove the dependency array.',
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [myEffect]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
const myEffect = debounce(() => {
|
||||
console.log(local);
|
||||
}, delay);
|
||||
useEffect(myEffect, [myEffect]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
const myEffect = debounce(() => {
|
||||
console.log(local);
|
||||
}, delay);
|
||||
useEffect(myEffect, [local]);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useEffect has a missing dependency: 'myEffect'. " +
|
||||
'Either include it or remove the dependency array.',
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [myEffect]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
const myEffect = debounce(() => {
|
||||
console.log(local);
|
||||
}, delay);
|
||||
useEffect(myEffect, [myEffect]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent({myEffect}) {
|
||||
useEffect(myEffect, []);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useEffect has a missing dependency: 'myEffect'. " +
|
||||
'Either include it or remove the dependency array.',
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [myEffect]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent({myEffect}) {
|
||||
useEffect(myEffect, [myEffect]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
useEffect(debounce(() => {
|
||||
console.log(local);
|
||||
}, delay), []);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'React Hook useEffect received a function whose dependencies ' +
|
||||
'are unknown. Pass an inline function instead.',
|
||||
suggestions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
useEffect(() => {
|
||||
console.log(local);
|
||||
}, []);
|
||||
}
|
||||
`,
|
||||
// Dangerous autofix is enabled due to the option:
|
||||
output: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {};
|
||||
useEffect(() => {
|
||||
console.log(local);
|
||||
}, [local]);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useEffect has a missing dependency: 'local'. " +
|
||||
'Either include it or remove the dependency array.',
|
||||
},
|
||||
],
|
||||
// Keep this until major IDEs and VS Code FB ESLint plugin support Suggestions API.
|
||||
options: [{enableDangerousAutofixThisMayCauseInfiniteLoops: true}],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
let foo = {}
|
||||
useEffect(() => {
|
||||
foo.bar.baz = 43;
|
||||
props.foo.bar.baz = 1;
|
||||
}, []);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useEffect has missing dependencies: 'foo.bar' and 'props.foo.bar'. " +
|
||||
'Either include them or remove the dependency array.',
|
||||
suggestions: [
|
||||
{
|
||||
desc:
|
||||
'Update the dependencies array to be: [foo.bar, props.foo.bar]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
let foo = {}
|
||||
useEffect(() => {
|
||||
foo.bar.baz = 43;
|
||||
props.foo.bar.baz = 1;
|
||||
}, [foo.bar, props.foo.bar]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const testsTypescript = {
|
||||
valid: [
|
||||
{
|
||||
// `ref` is still constant, despite the cast.
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const ref = useRef() as React.MutableRefObject<HTMLDivElement>;
|
||||
useEffect(() => {
|
||||
console.log(ref.current);
|
||||
}, []);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
// `local` is still non-constant, despite the cast.
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {} as string;
|
||||
useEffect(() => {
|
||||
console.log(local);
|
||||
}, []);
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useEffect has a missing dependency: 'local'. " +
|
||||
'Either include it or remove the dependency array.',
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [local]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const local = {} as string;
|
||||
useEffect(() => {
|
||||
console.log(local);
|
||||
}, [local]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -6635,12 +6005,7 @@ const testsTypescript = {
|
||||
if (!process.env.CI) {
|
||||
let only = [];
|
||||
let skipped = [];
|
||||
[
|
||||
...tests.valid,
|
||||
...tests.invalid,
|
||||
...testsTypescript.valid,
|
||||
...testsTypescript.invalid,
|
||||
].forEach(t => {
|
||||
[...tests.valid, ...tests.invalid].forEach(t => {
|
||||
if (t.skip) {
|
||||
delete t.skip;
|
||||
skipped.push(t);
|
||||
@@ -6661,21 +6026,7 @@ if (!process.env.CI) {
|
||||
};
|
||||
tests.valid = tests.valid.filter(predicate);
|
||||
tests.invalid = tests.invalid.filter(predicate);
|
||||
testsTypescript.valid = testsTypescript.valid.filter(predicate);
|
||||
testsTypescript.invalid = testsTypescript.invalid.filter(predicate);
|
||||
}
|
||||
|
||||
const parserOptions = {
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module',
|
||||
};
|
||||
|
||||
new ESLintTester({
|
||||
parser: require.resolve('babel-eslint'),
|
||||
parserOptions,
|
||||
}).run('react-hooks', ReactHooksESLintRule, tests);
|
||||
|
||||
new ESLintTester({
|
||||
parser: require.resolve('@typescript-eslint/parser'),
|
||||
parserOptions,
|
||||
}).run('react-hooks', ReactHooksESLintRule, testsTypescript);
|
||||
const eslintTester = new ESLintTester();
|
||||
eslintTester.run('react-hooks', ReactHooksESLintRule, tests);
|
||||
|
||||
@@ -106,7 +106,6 @@ const tests = {
|
||||
({useHook() { useState(); }});
|
||||
const {useHook3 = () => { useState(); }} = {};
|
||||
({useHook = () => { useState(); }} = {});
|
||||
Namespace.useHook = () => { useState(); };
|
||||
`,
|
||||
`
|
||||
// Valid because hooks can call hooks.
|
||||
@@ -192,6 +191,29 @@ const tests = {
|
||||
}
|
||||
}
|
||||
`,
|
||||
`
|
||||
// Currently valid.
|
||||
// We *could* make this invalid if we want, but it creates false positives
|
||||
// (see the FooStore case).
|
||||
class C {
|
||||
m() {
|
||||
This.useHook();
|
||||
Super.useHook();
|
||||
}
|
||||
}
|
||||
`,
|
||||
`
|
||||
// Valid although we *could* consider these invalid.
|
||||
// But it doesn't bring much benefit since it's an immediate runtime error anyway.
|
||||
// So might as well allow it.
|
||||
Hook.use();
|
||||
Hook._use();
|
||||
Hook.useState();
|
||||
Hook._useState();
|
||||
Hook.use42();
|
||||
Hook.useHook();
|
||||
Hook.use_hook();
|
||||
`,
|
||||
`
|
||||
// Valid -- this is a regression test.
|
||||
jest.useFakeTimers();
|
||||
@@ -199,6 +221,56 @@ const tests = {
|
||||
jest.useRealTimers();
|
||||
})
|
||||
`,
|
||||
`
|
||||
// Valid because that's a false positive we've seen quite a bit.
|
||||
// This is a regression test.
|
||||
class Foo extends Component {
|
||||
render() {
|
||||
if (cond) {
|
||||
FooStore.useFeatureFlag();
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
`
|
||||
// Currently valid because we found this to be a common pattern
|
||||
// for feature flag checks in existing components.
|
||||
// We *could* make it invalid but that produces quite a few false positives.
|
||||
// Why does it make sense to ignore it? Firstly, because using
|
||||
// hooks in a class would cause a runtime error anyway.
|
||||
// But why don't we care about the same kind of false positive in a functional
|
||||
// component? Because even if it was a false positive, it would be confusing
|
||||
// anyway. So it might make sense to rename a feature flag check in that case.
|
||||
class ClassComponentWithFeatureFlag extends React.Component {
|
||||
render() {
|
||||
if (foo) {
|
||||
useFeatureFlag();
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
`
|
||||
// Currently valid because we don't check for hooks in classes.
|
||||
// See ClassComponentWithFeatureFlag for rationale.
|
||||
// We *could* make it invalid if we don't regress that false positive.
|
||||
class ClassComponentWithHook extends React.Component {
|
||||
render() {
|
||||
React.useState();
|
||||
}
|
||||
}
|
||||
`,
|
||||
`
|
||||
// Currently valid.
|
||||
// These are variations capturing the current heuristic--
|
||||
// we only allow hooks in PascalCase, useFoo functions,
|
||||
// or classes (due to common false positives and because they error anyway).
|
||||
// We *could* make some of these invalid.
|
||||
// They probably don't matter much.
|
||||
(class {useHook = () => { useState(); }});
|
||||
(class {useHook() { useState(); }});
|
||||
(class {h = () => { useState(); }});
|
||||
(class {i() { useState(); }});
|
||||
`,
|
||||
`
|
||||
// Valid because they're not matching use[A-Z].
|
||||
fooState();
|
||||
@@ -206,8 +278,16 @@ const tests = {
|
||||
_use();
|
||||
_useState();
|
||||
use_hook();
|
||||
// also valid because it's not matching the PascalCase namespace
|
||||
jest.useFakeTimer()
|
||||
`,
|
||||
`
|
||||
// This is grey area.
|
||||
// Currently it's valid (although React.useCallback would fail here).
|
||||
// We could also get stricter and disallow it, just like we did
|
||||
// with non-namespace use*() top-level calls.
|
||||
const History = require('history-2.1.2');
|
||||
const browserHistory = History.useBasename(History.createHistory)({
|
||||
basename: '/',
|
||||
});
|
||||
`,
|
||||
`
|
||||
// Regression test for some internal code.
|
||||
@@ -351,59 +431,6 @@ const tests = {
|
||||
`,
|
||||
errors: [conditionalError('useConditionalHook')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
Hook.use();
|
||||
Hook._use();
|
||||
Hook.useState();
|
||||
Hook._useState();
|
||||
Hook.use42();
|
||||
Hook.useHook();
|
||||
Hook.use_hook();
|
||||
`,
|
||||
errors: [
|
||||
topLevelError('Hook.useState'),
|
||||
topLevelError('Hook.use42'),
|
||||
topLevelError('Hook.useHook'),
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
class C {
|
||||
m() {
|
||||
This.useHook();
|
||||
Super.useHook();
|
||||
}
|
||||
}
|
||||
`,
|
||||
errors: [classError('This.useHook'), classError('Super.useHook')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// This is a false positive (it's valid) that unfortunately
|
||||
// we cannot avoid. Prefer to rename it to not start with "use"
|
||||
class Foo extends Component {
|
||||
render() {
|
||||
if (cond) {
|
||||
FooStore.useFeatureFlag();
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
errors: [classError('FooStore.useFeatureFlag')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Invalid because it's dangerous and might not warn otherwise.
|
||||
// This *must* be invalid.
|
||||
function ComponentWithConditionalHook() {
|
||||
if (cond) {
|
||||
Namespace.useConditionalHook();
|
||||
}
|
||||
}
|
||||
`,
|
||||
errors: [conditionalError('Namespace.useConditionalHook')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Invalid because it's dangerous and might not warn otherwise.
|
||||
@@ -843,52 +870,6 @@ const tests = {
|
||||
`,
|
||||
errors: [topLevelError('useBasename')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
class ClassComponentWithFeatureFlag extends React.Component {
|
||||
render() {
|
||||
if (foo) {
|
||||
useFeatureFlag();
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
errors: [classError('useFeatureFlag')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
class ClassComponentWithHook extends React.Component {
|
||||
render() {
|
||||
React.useState();
|
||||
}
|
||||
}
|
||||
`,
|
||||
errors: [classError('React.useState')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
(class {useHook = () => { useState(); }});
|
||||
`,
|
||||
errors: [classError('useState')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
(class {useHook() { useState(); }});
|
||||
`,
|
||||
errors: [classError('useState')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
(class {h = () => { useState(); }});
|
||||
`,
|
||||
errors: [classError('useState')],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
(class {i() { useState(); }});
|
||||
`,
|
||||
errors: [classError('useState')],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -916,8 +897,7 @@ function functionError(hook, fn) {
|
||||
return {
|
||||
message:
|
||||
`React Hook "${hook}" is called in function "${fn}" that is neither ` +
|
||||
'a React function component nor a custom React Hook function.' +
|
||||
' React component names must start with an uppercase letter.',
|
||||
'a React function component nor a custom React Hook function.',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -939,15 +919,6 @@ function topLevelError(hook) {
|
||||
};
|
||||
}
|
||||
|
||||
function classError(hook) {
|
||||
return {
|
||||
message:
|
||||
`React Hook "${hook}" cannot be called in a class component. React Hooks ` +
|
||||
'must be called in a React function component or a custom React ' +
|
||||
'Hook function.',
|
||||
};
|
||||
}
|
||||
|
||||
// For easier local testing
|
||||
if (!process.env.CI) {
|
||||
let only = [];
|
||||
|
||||
@@ -5,4 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export * from './src/index';
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./src/index');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eslint-plugin-react-hooks",
|
||||
"description": "ESLint rules for React Hooks",
|
||||
"version": "4.0.5",
|
||||
"version": "2.4.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
@@ -25,13 +25,10 @@
|
||||
"url": "https://github.com/facebook/react/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=7"
|
||||
},
|
||||
"homepage": "https://reactjs.org/",
|
||||
"peerDependencies": {
|
||||
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "^2.26.0"
|
||||
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,27 +11,14 @@
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description:
|
||||
'verifies the list of dependencies for Hooks like useEffect and similar',
|
||||
category: 'Best Practices',
|
||||
recommended: true,
|
||||
url: 'https://github.com/facebook/react/issues/14920',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
enableDangerousAutofixThisMayCauseInfiniteLoops: false,
|
||||
properties: {
|
||||
additionalHooks: {
|
||||
type: 'string',
|
||||
},
|
||||
enableDangerousAutofixThisMayCauseInfiniteLoops: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -44,36 +31,13 @@ export default {
|
||||
context.options[0].additionalHooks
|
||||
? new RegExp(context.options[0].additionalHooks)
|
||||
: undefined;
|
||||
|
||||
const enableDangerousAutofixThisMayCauseInfiniteLoops =
|
||||
(context.options &&
|
||||
context.options[0] &&
|
||||
context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops) ||
|
||||
false;
|
||||
|
||||
const options = {
|
||||
additionalHooks,
|
||||
enableDangerousAutofixThisMayCauseInfiniteLoops,
|
||||
};
|
||||
|
||||
function reportProblem(problem) {
|
||||
if (enableDangerousAutofixThisMayCauseInfiniteLoops) {
|
||||
// Used to enable legacy behavior. Dangerous.
|
||||
// Keep this as an option until major IDEs upgrade (including VSCode FB ESLint extension).
|
||||
if (Array.isArray(problem.suggest) && problem.suggest.length > 0) {
|
||||
problem.fix = problem.suggest[0].fix;
|
||||
}
|
||||
}
|
||||
context.report(problem);
|
||||
}
|
||||
|
||||
const scopeManager = context.getSourceCode().scopeManager;
|
||||
const options = {additionalHooks};
|
||||
|
||||
// Should be shared between visitors.
|
||||
const setStateCallSites = new WeakMap();
|
||||
const stateVariables = new WeakSet();
|
||||
const staticKnownValueCache = new WeakMap();
|
||||
const functionWithoutCapturedValueCache = new WeakMap();
|
||||
let setStateCallSites = new WeakMap();
|
||||
let stateVariables = new WeakSet();
|
||||
let staticKnownValueCache = new WeakMap();
|
||||
let functionWithoutCapturedValueCache = new WeakMap();
|
||||
function memoizeWithWeakMap(fn, map) {
|
||||
return function(arg) {
|
||||
if (map.has(arg)) {
|
||||
@@ -88,24 +52,41 @@ export default {
|
||||
}
|
||||
|
||||
return {
|
||||
CallExpression: visitCallExpression,
|
||||
FunctionExpression: visitFunctionExpression,
|
||||
ArrowFunctionExpression: visitFunctionExpression,
|
||||
};
|
||||
|
||||
function visitCallExpression(node) {
|
||||
const callbackIndex = getReactiveHookCallbackIndex(node.callee, options);
|
||||
if (callbackIndex === -1) {
|
||||
// Not a React Hook call that needs deps.
|
||||
/**
|
||||
* Visitor for both function expressions and arrow function expressions.
|
||||
*/
|
||||
function visitFunctionExpression(node) {
|
||||
// We only want to lint nodes which are reactive hook callbacks.
|
||||
if (
|
||||
(node.type !== 'FunctionExpression' &&
|
||||
node.type !== 'ArrowFunctionExpression') ||
|
||||
node.parent.type !== 'CallExpression'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const callback = node.arguments[callbackIndex];
|
||||
const reactiveHook = node.callee;
|
||||
|
||||
const callbackIndex = getReactiveHookCallbackIndex(
|
||||
node.parent.callee,
|
||||
options,
|
||||
);
|
||||
if (node.parent.arguments[callbackIndex] !== node) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the reactive hook node.
|
||||
const reactiveHook = node.parent.callee;
|
||||
const reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name;
|
||||
const declaredDependenciesNode = node.arguments[callbackIndex + 1];
|
||||
const isEffect = /Effect($|[^a-z])/g.test(reactiveHookName);
|
||||
|
||||
// Check the declared dependencies for this reactive hook. If there is no
|
||||
// Get the declared dependencies for this reactive hook. If there is no
|
||||
// second argument then the reactive callback will re-run on every render.
|
||||
// So no need to check for dependency inclusion.
|
||||
const depsIndex = callbackIndex + 1;
|
||||
const declaredDependenciesNode = node.parent.arguments[depsIndex];
|
||||
if (!declaredDependenciesNode && !isEffect) {
|
||||
// These are only used for optimization.
|
||||
if (
|
||||
@@ -113,8 +94,8 @@ export default {
|
||||
reactiveHookName === 'useCallback'
|
||||
) {
|
||||
// TODO: Can this have a suggestion?
|
||||
reportProblem({
|
||||
node: reactiveHook,
|
||||
context.report({
|
||||
node: node.parent.callee,
|
||||
message:
|
||||
`React Hook ${reactiveHookName} does nothing when called with ` +
|
||||
`only one argument. Did you forget to pass an array of ` +
|
||||
@@ -124,128 +105,8 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (callback.type) {
|
||||
case 'FunctionExpression':
|
||||
case 'ArrowFunctionExpression':
|
||||
visitFunctionWithDependencies(
|
||||
callback,
|
||||
declaredDependenciesNode,
|
||||
reactiveHook,
|
||||
reactiveHookName,
|
||||
isEffect,
|
||||
);
|
||||
return; // Handled
|
||||
case 'Identifier':
|
||||
if (!declaredDependenciesNode) {
|
||||
// No deps, no problems.
|
||||
return; // Handled
|
||||
}
|
||||
// The function passed as a callback is not written inline.
|
||||
// But perhaps it's in the dependencies array?
|
||||
if (
|
||||
declaredDependenciesNode.elements &&
|
||||
declaredDependenciesNode.elements.some(
|
||||
el => el && el.type === 'Identifier' && el.name === callback.name,
|
||||
)
|
||||
) {
|
||||
// If it's already in the list of deps, we don't care because
|
||||
// this is valid regardless.
|
||||
return; // Handled
|
||||
}
|
||||
// We'll do our best effort to find it, complain otherwise.
|
||||
const variable = context.getScope().set.get(callback.name);
|
||||
if (variable == null || variable.defs == null) {
|
||||
// If it's not in scope, we don't care.
|
||||
return; // Handled
|
||||
}
|
||||
// The function passed as a callback is not written inline.
|
||||
// But it's defined somewhere in the render scope.
|
||||
// We'll do our best effort to find and check it, complain otherwise.
|
||||
const def = variable.defs[0];
|
||||
if (!def || !def.node) {
|
||||
break; // Unhandled
|
||||
}
|
||||
if (def.type !== 'Variable' && def.type !== 'FunctionName') {
|
||||
// Parameter or an unusual pattern. Bail out.
|
||||
break; // Unhandled
|
||||
}
|
||||
switch (def.node.type) {
|
||||
case 'FunctionDeclaration':
|
||||
// useEffect(() => { ... }, []);
|
||||
visitFunctionWithDependencies(
|
||||
def.node,
|
||||
declaredDependenciesNode,
|
||||
reactiveHook,
|
||||
reactiveHookName,
|
||||
isEffect,
|
||||
);
|
||||
return; // Handled
|
||||
case 'VariableDeclarator':
|
||||
const init = def.node.init;
|
||||
if (!init) {
|
||||
break; // Unhandled
|
||||
}
|
||||
switch (init.type) {
|
||||
// const effectBody = () => {...};
|
||||
// useEffect(effectBody, []);
|
||||
case 'ArrowFunctionExpression':
|
||||
case 'FunctionExpression':
|
||||
// We can inspect this function as if it were inline.
|
||||
visitFunctionWithDependencies(
|
||||
init,
|
||||
declaredDependenciesNode,
|
||||
reactiveHook,
|
||||
reactiveHookName,
|
||||
isEffect,
|
||||
);
|
||||
return; // Handled
|
||||
}
|
||||
break; // Unhandled
|
||||
}
|
||||
break; // Unhandled
|
||||
default:
|
||||
// useEffect(generateEffectBody(), []);
|
||||
reportProblem({
|
||||
node: reactiveHook,
|
||||
message:
|
||||
`React Hook ${reactiveHookName} received a function whose dependencies ` +
|
||||
`are unknown. Pass an inline function instead.`,
|
||||
});
|
||||
return; // Handled
|
||||
}
|
||||
|
||||
// Something unusual. Fall back to suggesting to add the body itself as a dep.
|
||||
reportProblem({
|
||||
node: reactiveHook,
|
||||
message:
|
||||
`React Hook ${reactiveHookName} has a missing dependency: '${callback.name}'. ` +
|
||||
`Either include it or remove the dependency array.`,
|
||||
suggest: [
|
||||
{
|
||||
desc: `Update the dependencies array to be: [${callback.name}]`,
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(
|
||||
declaredDependenciesNode,
|
||||
`[${callback.name}]`,
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor for both function expressions and arrow function expressions.
|
||||
*/
|
||||
function visitFunctionWithDependencies(
|
||||
node,
|
||||
declaredDependenciesNode,
|
||||
reactiveHook,
|
||||
reactiveHookName,
|
||||
isEffect,
|
||||
) {
|
||||
if (isEffect && node.async) {
|
||||
reportProblem({
|
||||
context.report({
|
||||
node: node,
|
||||
message:
|
||||
`Effect callbacks are synchronous to prevent race conditions. ` +
|
||||
@@ -263,7 +124,7 @@ export default {
|
||||
}
|
||||
|
||||
// Get the current scope.
|
||||
const scope = scopeManager.acquire(node);
|
||||
const scope = context.getScope();
|
||||
|
||||
// Find all our "pure scopes". On every re-render of a component these
|
||||
// pure scopes may have changes to the variables declared within. So all
|
||||
@@ -316,13 +177,10 @@ export default {
|
||||
if (def.node.type !== 'VariableDeclarator') {
|
||||
return false;
|
||||
}
|
||||
let init = def.node.init;
|
||||
const init = def.node.init;
|
||||
if (init == null) {
|
||||
return false;
|
||||
}
|
||||
while (init.type === 'TSAsExpression') {
|
||||
init = init.expression;
|
||||
}
|
||||
// Detect primitive constants
|
||||
// const foo = 42
|
||||
let declaration = def.node.parent;
|
||||
@@ -420,12 +278,12 @@ export default {
|
||||
// Search the direct component subscopes for
|
||||
// top-level function definitions matching this reference.
|
||||
const fnNode = def.node;
|
||||
const childScopes = componentScope.childScopes;
|
||||
let childScopes = componentScope.childScopes;
|
||||
let fnScope = null;
|
||||
let i;
|
||||
for (i = 0; i < childScopes.length; i++) {
|
||||
const childScope = childScopes[i];
|
||||
const childScopeBlock = childScope.block;
|
||||
let childScope = childScopes[i];
|
||||
let childScopeBlock = childScope.block;
|
||||
if (
|
||||
// function handleChange() {}
|
||||
(fnNode.type === 'FunctionDeclaration' &&
|
||||
@@ -603,7 +461,7 @@ export default {
|
||||
if (foundCurrentAssignment) {
|
||||
return;
|
||||
}
|
||||
reportProblem({
|
||||
context.report({
|
||||
node: dependencyNode.parent.property,
|
||||
message:
|
||||
`The ref value '${dependency}.current' will likely have ` +
|
||||
@@ -617,13 +475,13 @@ export default {
|
||||
|
||||
// Warn about assigning to variables in the outer scope.
|
||||
// Those are usually bugs.
|
||||
const staleAssignments = new Set();
|
||||
let staleAssignments = new Set();
|
||||
function reportStaleAssignment(writeExpr, key) {
|
||||
if (staleAssignments.has(key)) {
|
||||
return;
|
||||
}
|
||||
staleAssignments.add(key);
|
||||
reportProblem({
|
||||
context.report({
|
||||
node: writeExpr,
|
||||
message:
|
||||
`Assignments to the '${key}' variable from inside React Hook ` +
|
||||
@@ -684,15 +542,15 @@ export default {
|
||||
});
|
||||
});
|
||||
if (setStateInsideEffectWithoutDeps) {
|
||||
const {suggestedDependencies} = collectRecommendations({
|
||||
let {suggestedDependencies} = collectRecommendations({
|
||||
dependencies,
|
||||
declaredDependencies: [],
|
||||
optionalDependencies,
|
||||
externalDependencies: new Set(),
|
||||
isEffect: true,
|
||||
});
|
||||
reportProblem({
|
||||
node: reactiveHook,
|
||||
context.report({
|
||||
node: node.parent.callee,
|
||||
message:
|
||||
`React Hook ${reactiveHookName} contains a call to '${setStateInsideEffectWithoutDeps}'. ` +
|
||||
`Without a list of dependencies, this can lead to an infinite chain of updates. ` +
|
||||
@@ -723,7 +581,7 @@ export default {
|
||||
// If the declared dependencies are not an array expression then we
|
||||
// can't verify that the user provided the correct dependencies. Tell
|
||||
// the user this in an error.
|
||||
reportProblem({
|
||||
context.report({
|
||||
node: declaredDependenciesNode,
|
||||
message:
|
||||
`React Hook ${context.getSource(reactiveHook)} was passed a ` +
|
||||
@@ -739,7 +597,7 @@ export default {
|
||||
}
|
||||
// If we see a spread element then add a special warning.
|
||||
if (declaredDependencyNode.type === 'SpreadElement') {
|
||||
reportProblem({
|
||||
context.report({
|
||||
node: declaredDependencyNode,
|
||||
message:
|
||||
`React Hook ${context.getSource(reactiveHook)} has a spread ` +
|
||||
@@ -758,7 +616,7 @@ export default {
|
||||
if (/Unsupported node type/.test(error.message)) {
|
||||
if (declaredDependencyNode.type === 'Literal') {
|
||||
if (dependencies.has(declaredDependencyNode.value)) {
|
||||
reportProblem({
|
||||
context.report({
|
||||
node: declaredDependencyNode,
|
||||
message:
|
||||
`The ${declaredDependencyNode.raw} literal is not a valid dependency ` +
|
||||
@@ -766,7 +624,7 @@ export default {
|
||||
`Did you mean to include ${declaredDependencyNode.value} in the array instead?`,
|
||||
});
|
||||
} else {
|
||||
reportProblem({
|
||||
context.report({
|
||||
node: declaredDependencyNode,
|
||||
message:
|
||||
`The ${declaredDependencyNode.raw} literal is not a valid dependency ` +
|
||||
@@ -774,7 +632,7 @@ export default {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
reportProblem({
|
||||
context.report({
|
||||
node: declaredDependencyNode,
|
||||
message:
|
||||
`React Hook ${context.getSource(reactiveHook)} has a ` +
|
||||
@@ -809,7 +667,7 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
let {
|
||||
suggestedDependencies,
|
||||
unnecessaryDependencies,
|
||||
missingDependencies,
|
||||
@@ -822,8 +680,6 @@ export default {
|
||||
isEffect,
|
||||
});
|
||||
|
||||
let suggestedDeps = suggestedDependencies;
|
||||
|
||||
const problemCount =
|
||||
duplicateDependencies.size +
|
||||
missingDependencies.size +
|
||||
@@ -876,7 +732,7 @@ export default {
|
||||
}
|
||||
// TODO: What if the function needs to change on every render anyway?
|
||||
// Should we suggest removing effect deps as an appropriate fix too?
|
||||
reportProblem({
|
||||
context.report({
|
||||
// TODO: Why not report this at the dependency site?
|
||||
node: fn.node,
|
||||
message,
|
||||
@@ -893,7 +749,7 @@ export default {
|
||||
// for effects though because those have legit
|
||||
// use cases for over-specifying deps.
|
||||
if (!isEffect && missingDependencies.size > 0) {
|
||||
suggestedDeps = collectRecommendations({
|
||||
suggestedDependencies = collectRecommendations({
|
||||
dependencies,
|
||||
declaredDependencies: [], // Pretend we don't know
|
||||
optionalDependencies,
|
||||
@@ -912,7 +768,7 @@ export default {
|
||||
return declaredDepKeys.join(',') === sortedDeclaredDepKeys.join(',');
|
||||
}
|
||||
if (areDeclaredDepsAlphabetized()) {
|
||||
suggestedDeps.sort();
|
||||
suggestedDependencies.sort();
|
||||
}
|
||||
|
||||
function getWarningMessage(deps, singlePrefix, label, fixVerb) {
|
||||
@@ -967,7 +823,7 @@ export default {
|
||||
// a `this` value. This warning can be confusing.
|
||||
// So if we're going to show it, append a clarification.
|
||||
if (!extraWarning && missingDependencies.has('props')) {
|
||||
const propDep = dependencies.get('props');
|
||||
let propDep = dependencies.get('props');
|
||||
if (propDep == null) {
|
||||
return;
|
||||
}
|
||||
@@ -1147,7 +1003,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
reportProblem({
|
||||
context.report({
|
||||
node: declaredDependenciesNode,
|
||||
message:
|
||||
`React Hook ${context.getSource(reactiveHook)} has ` +
|
||||
@@ -1168,14 +1024,14 @@ export default {
|
||||
extraWarning,
|
||||
suggest: [
|
||||
{
|
||||
desc: `Update the dependencies array to be: [${suggestedDeps.join(
|
||||
desc: `Update the dependencies array to be: [${suggestedDependencies.join(
|
||||
', ',
|
||||
)}]`,
|
||||
fix(fixer) {
|
||||
// TODO: consider preserving the comments or formatting?
|
||||
return fixer.replaceText(
|
||||
declaredDependenciesNode,
|
||||
`[${suggestedDeps.join(', ')}]`,
|
||||
`[${suggestedDependencies.join(', ')}]`,
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -1235,10 +1091,10 @@ function collectRecommendations({
|
||||
|
||||
// Tree manipulation helpers.
|
||||
function getOrCreateNodeByPath(rootNode, path) {
|
||||
const keys = path.split('.');
|
||||
let keys = path.split('.');
|
||||
let node = rootNode;
|
||||
for (const key of keys) {
|
||||
let child = getChildByKey(node, key);
|
||||
for (let key of keys) {
|
||||
let child = node.children.get(key);
|
||||
if (!child) {
|
||||
child = createDepTree();
|
||||
node.children.set(key, child);
|
||||
@@ -1248,10 +1104,10 @@ function collectRecommendations({
|
||||
return node;
|
||||
}
|
||||
function markAllParentsByPath(rootNode, path, fn) {
|
||||
const keys = path.split('.');
|
||||
let keys = path.split('.');
|
||||
let node = rootNode;
|
||||
for (const key of keys) {
|
||||
const child = getChildByKey(node, key);
|
||||
for (let key of keys) {
|
||||
let child = node.children.get(key);
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
@@ -1260,24 +1116,9 @@ function collectRecommendations({
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Match key with optional chaining
|
||||
* key -> key
|
||||
* key? -> key
|
||||
* key -> key?
|
||||
* Otherwise undefined.
|
||||
*/
|
||||
function getChildByKey(node, key) {
|
||||
return (
|
||||
node.children.get(key) ||
|
||||
node.children.get(key.split('?')[0]) ||
|
||||
node.children.get(key + '?')
|
||||
);
|
||||
}
|
||||
|
||||
// Now we can learn which dependencies are missing or necessary.
|
||||
const missingDependencies = new Set();
|
||||
const satisfyingDependencies = new Set();
|
||||
let missingDependencies = new Set();
|
||||
let satisfyingDependencies = new Set();
|
||||
scanTreeRecursively(
|
||||
depTree,
|
||||
missingDependencies,
|
||||
@@ -1287,13 +1128,10 @@ function collectRecommendations({
|
||||
function scanTreeRecursively(node, missingPaths, satisfyingPaths, keyToPath) {
|
||||
node.children.forEach((child, key) => {
|
||||
const path = keyToPath(key);
|
||||
// For analyzing dependencies, we want the "normalized" path, without any optional chaining ("?.") operator
|
||||
// foo?.bar -> foo.bar
|
||||
const normalizedPath = path.replace(/\?$/, '');
|
||||
if (child.isSatisfiedRecursively) {
|
||||
if (child.hasRequiredNodesBelow) {
|
||||
// Remember this dep actually satisfied something.
|
||||
satisfyingPaths.add(normalizedPath);
|
||||
satisfyingPaths.add(path);
|
||||
}
|
||||
// It doesn't matter if there's something deeper.
|
||||
// It would be transitively satisfied since we assume immutability.
|
||||
@@ -1302,7 +1140,7 @@ function collectRecommendations({
|
||||
}
|
||||
if (child.isRequired) {
|
||||
// Remember that no declared deps satisfied this node.
|
||||
missingPaths.add(normalizedPath);
|
||||
missingPaths.add(path);
|
||||
// If we got here, nothing in its subtree was satisfied.
|
||||
// No need to search further.
|
||||
return;
|
||||
@@ -1317,9 +1155,9 @@ function collectRecommendations({
|
||||
}
|
||||
|
||||
// Collect suggestions in the order they were originally specified.
|
||||
const suggestedDependencies = [];
|
||||
const unnecessaryDependencies = new Set();
|
||||
const duplicateDependencies = new Set();
|
||||
let suggestedDependencies = [];
|
||||
let unnecessaryDependencies = new Set();
|
||||
let duplicateDependencies = new Set();
|
||||
declaredDependencies.forEach(({key}) => {
|
||||
// Does this declared dep satisfy a real need?
|
||||
if (satisfyingDependencies.has(key)) {
|
||||
@@ -1377,7 +1215,7 @@ function scanForDeclaredBareFunctions({
|
||||
if (fnRef == null) {
|
||||
return null;
|
||||
}
|
||||
const fnNode = fnRef.defs[0];
|
||||
let fnNode = fnRef.defs[0];
|
||||
if (fnNode == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -1447,8 +1285,7 @@ function scanForDeclaredBareFunctions({
|
||||
*/
|
||||
function getDependency(node) {
|
||||
if (
|
||||
(node.parent.type === 'MemberExpression' ||
|
||||
node.parent.type === 'OptionalMemberExpression') &&
|
||||
node.parent.type === 'MemberExpression' &&
|
||||
node.parent.object === node &&
|
||||
node.parent.property.name !== 'current' &&
|
||||
!node.parent.computed &&
|
||||
@@ -1459,12 +1296,6 @@ function getDependency(node) {
|
||||
)
|
||||
) {
|
||||
return getDependency(node.parent);
|
||||
} else if (
|
||||
node.type === 'MemberExpression' &&
|
||||
node.parent &&
|
||||
node.parent.type === 'AssignmentExpression'
|
||||
) {
|
||||
return node.object;
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
@@ -1475,7 +1306,6 @@ function getDependency(node) {
|
||||
* (foo) -> 'foo'
|
||||
* foo.(bar) -> 'foo.bar'
|
||||
* foo.bar.(baz) -> 'foo.bar.baz'
|
||||
* foo?.(bar) -> 'foo?.bar'
|
||||
* Otherwise throw.
|
||||
*/
|
||||
function toPropertyAccessString(node) {
|
||||
@@ -1485,10 +1315,6 @@ function toPropertyAccessString(node) {
|
||||
const object = toPropertyAccessString(node.object);
|
||||
const property = toPropertyAccessString(node.property);
|
||||
return `${object}.${property}`;
|
||||
} else if (node.type === 'OptionalMemberExpression' && !node.computed) {
|
||||
const object = toPropertyAccessString(node.object);
|
||||
const property = toPropertyAccessString(node.property);
|
||||
return `${object}?.${property}`;
|
||||
} else {
|
||||
throw new Error(`Unsupported node type: ${node.type}`);
|
||||
}
|
||||
@@ -1513,9 +1339,9 @@ function getNodeWithoutReactNamespace(node, options) {
|
||||
// 1 for useImperativeHandle(ref, fn).
|
||||
// For additionally configured Hooks, assume that they're like useEffect (0).
|
||||
function getReactiveHookCallbackIndex(calleeNode, options) {
|
||||
const node = getNodeWithoutReactNamespace(calleeNode);
|
||||
let node = getNodeWithoutReactNamespace(calleeNode);
|
||||
if (node.type !== 'Identifier') {
|
||||
return -1;
|
||||
return null;
|
||||
}
|
||||
switch (node.name) {
|
||||
case 'useEffect':
|
||||
@@ -1559,7 +1385,7 @@ function getReactiveHookCallbackIndex(calleeNode, options) {
|
||||
* - agnostic to AST node types, it looks for `{ type: string, ... }`
|
||||
*/
|
||||
function fastFindReferenceWithParent(start, target) {
|
||||
const queue = [start];
|
||||
let queue = [start];
|
||||
let item = null;
|
||||
|
||||
while (queue.length) {
|
||||
@@ -1573,7 +1399,7 @@ function fastFindReferenceWithParent(start, target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(item)) {
|
||||
for (let [key, value] of Object.entries(item)) {
|
||||
if (key === 'parent') {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -31,9 +31,10 @@ function isHook(node) {
|
||||
!node.computed &&
|
||||
isHook(node.property)
|
||||
) {
|
||||
// Only consider React.useFoo() to be namespace hooks for now to avoid false positives.
|
||||
// We can expand this check later.
|
||||
const obj = node.object;
|
||||
const isPascalCaseNameSpace = /^[A-Z].*/;
|
||||
return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name);
|
||||
return obj.type === 'Identifier' && obj.name === 'React';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -105,15 +106,6 @@ function isInsideComponentOrHook(node) {
|
||||
}
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'enforces the Rules of Hooks',
|
||||
category: 'Possible Errors',
|
||||
recommended: true,
|
||||
url: 'https://reactjs.org/docs/hooks-rules.html',
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const codePathReactHooksMapStack = [];
|
||||
const codePathSegmentStack = [];
|
||||
@@ -234,7 +226,7 @@ export default {
|
||||
function countPathsToEnd(segment, pathHistory) {
|
||||
const {cache} = countPathsToEnd;
|
||||
let paths = cache.get(segment.id);
|
||||
const pathList = new Set(pathHistory);
|
||||
let pathList = new Set(pathHistory);
|
||||
|
||||
// If `pathList` includes the current segment then we've found a cycle!
|
||||
// We need to fill `cyclic` with all segments inside cycle
|
||||
@@ -469,20 +461,18 @@ export default {
|
||||
codePathNode.parent.type === 'ClassProperty') &&
|
||||
codePathNode.parent.value === codePathNode
|
||||
) {
|
||||
// Custom message for hooks inside a class
|
||||
const message =
|
||||
`React Hook "${context.getSource(hook)}" cannot be called ` +
|
||||
'in a class component. React Hooks must be called in a ' +
|
||||
'React function component or a custom React Hook function.';
|
||||
context.report({node: hook, message});
|
||||
// Ignore class methods for now because they produce too many
|
||||
// false positives due to feature flag checks. We're less
|
||||
// sensitive to them in classes because hooks would produce
|
||||
// runtime errors in classes anyway, and because a use*()
|
||||
// call in a class, if it works, is unambiguously *not* a hook.
|
||||
} else if (codePathFunctionName) {
|
||||
// Custom message if we found an invalid function name.
|
||||
const message =
|
||||
`React Hook "${context.getSource(hook)}" is called in ` +
|
||||
`function "${context.getSource(codePathFunctionName)}" ` +
|
||||
'that is neither a React function component nor a custom ' +
|
||||
'React Hook function.' +
|
||||
' React component names must start with an uppercase letter.';
|
||||
'React Hook function.';
|
||||
context.report({node: hook, message});
|
||||
} else if (codePathNode.type === 'Program') {
|
||||
// These are dangerous if you have inline requires enabled.
|
||||
@@ -536,7 +526,7 @@ export default {
|
||||
* easy. For anonymous function expressions it is much harder. If you search for
|
||||
* `IsAnonymousFunctionDefinition()` in the ECMAScript spec you'll find places
|
||||
* where JS gives anonymous function expressions names. We roughly detect the
|
||||
* same AST nodes with some exceptions to better fit our use case.
|
||||
* same AST nodes with some exceptions to better fit our usecase.
|
||||
*/
|
||||
|
||||
function getFunctionName(node) {
|
||||
|
||||
@@ -7,20 +7,10 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import RulesOfHooks from './RulesOfHooks';
|
||||
import RuleOfHooks from './RulesOfHooks';
|
||||
import ExhaustiveDeps from './ExhaustiveDeps';
|
||||
|
||||
export const configs = {
|
||||
recommended: {
|
||||
plugins: ['react-hooks'],
|
||||
rules: {
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const rules = {
|
||||
'rules-of-hooks': RulesOfHooks,
|
||||
'rules-of-hooks': RuleOfHooks,
|
||||
'exhaustive-deps': ExhaustiveDeps,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jest-react",
|
||||
"version": "0.11.1",
|
||||
"version": "0.10.0",
|
||||
"description": "Jest matchers and utilities for testing React components.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -23,9 +23,6 @@
|
||||
"react": "^16.0.0",
|
||||
"react-test-renderer": "^16.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"object-assign": "^4.1.1"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
|
||||
@@ -99,7 +99,7 @@ function jsonChildrenToJSXChildren(jsonChildren) {
|
||||
if (jsonChildren.length === 1) {
|
||||
return jsonChildToJSXChild(jsonChildren[0]);
|
||||
} else if (jsonChildren.length > 1) {
|
||||
const jsxChildren = [];
|
||||
let jsxChildren = [];
|
||||
let allJSXChildrenAreStrings = true;
|
||||
let jsxChildrenString = '';
|
||||
for (let i = 0; i < jsonChildren.length; i++) {
|
||||
|
||||
@@ -11,18 +11,12 @@ import type {DispatchConfig} from './ReactSyntheticEventType';
|
||||
import type {
|
||||
AnyNativeEvent,
|
||||
PluginName,
|
||||
LegacyPluginModule,
|
||||
ModernPluginModule,
|
||||
PluginModule,
|
||||
} from './PluginModuleType';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
type NamesToPlugins = {
|
||||
[key: PluginName]:
|
||||
| LegacyPluginModule<AnyNativeEvent>
|
||||
| ModernPluginModule<AnyNativeEvent>,
|
||||
...,
|
||||
};
|
||||
type NamesToPlugins = {[key: PluginName]: PluginModule<AnyNativeEvent>, ...};
|
||||
type EventPluginOrder = null | Array<PluginName>;
|
||||
|
||||
/**
|
||||
@@ -90,9 +84,7 @@ function recomputePluginOrdering(): void {
|
||||
*/
|
||||
function publishEventForPlugin(
|
||||
dispatchConfig: DispatchConfig,
|
||||
pluginModule:
|
||||
| LegacyPluginModule<AnyNativeEvent>
|
||||
| ModernPluginModule<AnyNativeEvent>,
|
||||
pluginModule: PluginModule<AnyNativeEvent>,
|
||||
eventName: string,
|
||||
): boolean {
|
||||
invariant(
|
||||
@@ -136,9 +128,7 @@ function publishEventForPlugin(
|
||||
*/
|
||||
function publishRegistrationName(
|
||||
registrationName: string,
|
||||
pluginModule:
|
||||
| LegacyPluginModule<AnyNativeEvent>
|
||||
| ModernPluginModule<AnyNativeEvent>,
|
||||
pluginModule: PluginModule<AnyNativeEvent>,
|
||||
eventName: string,
|
||||
): void {
|
||||
invariant(
|
||||
@@ -251,20 +241,3 @@ export function injectEventPluginsByName(
|
||||
recomputePluginOrdering();
|
||||
}
|
||||
}
|
||||
|
||||
export function injectEventPlugins(
|
||||
eventPlugins: [ModernPluginModule<AnyNativeEvent>],
|
||||
): void {
|
||||
for (let i = 0; i < eventPlugins.length; i++) {
|
||||
const pluginModule = eventPlugins[i];
|
||||
plugins.push(pluginModule);
|
||||
const publishedEvents = pluginModule.eventTypes;
|
||||
for (const eventName in publishedEvents) {
|
||||
publishEventForPlugin(
|
||||
publishedEvents[eventName],
|
||||
pluginModule,
|
||||
eventName,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
packages/legacy-events/EventPropagators.js
Normal file
130
packages/legacy-events/EventPropagators.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
getParentInstance,
|
||||
traverseTwoPhase,
|
||||
traverseEnterLeave,
|
||||
} from 'shared/ReactTreeTraversal';
|
||||
|
||||
import getListener from 'legacy-events/getListener';
|
||||
import accumulateInto from './accumulateInto';
|
||||
import forEachAccumulated from './forEachAccumulated';
|
||||
|
||||
type PropagationPhases = 'bubbled' | 'captured';
|
||||
|
||||
/**
|
||||
* Some event types have a notion of different registration names for different
|
||||
* "phases" of propagation. This finds listeners by a given phase.
|
||||
*/
|
||||
function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
|
||||
const registrationName =
|
||||
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
|
||||
return getListener(inst, registrationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* A small set of propagation patterns, each of which will accept a small amount
|
||||
* of information, and generate a set of "dispatch ready event objects" - which
|
||||
* are sets of events that have already been annotated with a set of dispatched
|
||||
* listener functions/ids. The API is designed this way to discourage these
|
||||
* propagation strategies from actually executing the dispatches, since we
|
||||
* always want to collect the entire set of dispatches before executing even a
|
||||
* single one.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tags a `SyntheticEvent` with dispatched listeners. Creating this function
|
||||
* here, allows us to not have to bind or create functions for each event.
|
||||
* Mutating the event's members allows us to not have to create a wrapping
|
||||
* "dispatch" object that pairs the event with the listener.
|
||||
*/
|
||||
function accumulateDirectionalDispatches(inst, phase, event) {
|
||||
if (__DEV__) {
|
||||
if (!inst) {
|
||||
console.error('Dispatching inst must not be null');
|
||||
}
|
||||
}
|
||||
const listener = listenerAtPhase(inst, event, phase);
|
||||
if (listener) {
|
||||
event._dispatchListeners = accumulateInto(
|
||||
event._dispatchListeners,
|
||||
listener,
|
||||
);
|
||||
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect dispatches (must be entirely collected before dispatching - see unit
|
||||
* tests). Lazily allocate the array to conserve memory. We must loop through
|
||||
* each event and perform the traversal for each one. We cannot perform a
|
||||
* single traversal for the entire collection of events because each event may
|
||||
* have a different target.
|
||||
*/
|
||||
function accumulateTwoPhaseDispatchesSingle(event) {
|
||||
if (event && event.dispatchConfig.phasedRegistrationNames) {
|
||||
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `accumulateTwoPhaseDispatchesSingle`, but skips over the targetID.
|
||||
*/
|
||||
function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
|
||||
if (event && event.dispatchConfig.phasedRegistrationNames) {
|
||||
const targetInst = event._targetInst;
|
||||
const parentInst = targetInst ? getParentInstance(targetInst) : null;
|
||||
traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulates without regard to direction, does not look for phased
|
||||
* registration names. Same as `accumulateDirectDispatchesSingle` but without
|
||||
* requiring that the `dispatchMarker` be the same as the dispatched ID.
|
||||
*/
|
||||
function accumulateDispatches(inst, ignoredDirection, event) {
|
||||
if (inst && event && event.dispatchConfig.registrationName) {
|
||||
const registrationName = event.dispatchConfig.registrationName;
|
||||
const listener = getListener(inst, registrationName);
|
||||
if (listener) {
|
||||
event._dispatchListeners = accumulateInto(
|
||||
event._dispatchListeners,
|
||||
listener,
|
||||
);
|
||||
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulates dispatches on an `SyntheticEvent`, but only for the
|
||||
* `dispatchMarker`.
|
||||
* @param {SyntheticEvent} event
|
||||
*/
|
||||
function accumulateDirectDispatchesSingle(event) {
|
||||
if (event && event.dispatchConfig.registrationName) {
|
||||
accumulateDispatches(event._targetInst, null, event);
|
||||
}
|
||||
}
|
||||
|
||||
export function accumulateTwoPhaseDispatches(events) {
|
||||
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
|
||||
}
|
||||
|
||||
export function accumulateTwoPhaseDispatchesSkipTarget(events) {
|
||||
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget);
|
||||
}
|
||||
|
||||
export function accumulateEnterLeaveDispatches(leave, enter, from, to) {
|
||||
traverseEnterLeave(from, to, accumulateDispatches, leave, enter);
|
||||
}
|
||||
|
||||
export function accumulateDirectDispatches(events) {
|
||||
forEachAccumulated(events, accumulateDirectDispatchesSingle);
|
||||
}
|
||||
@@ -11,9 +11,8 @@ export type EventSystemFlags = number;
|
||||
|
||||
export const PLUGIN_EVENT_SYSTEM = 1;
|
||||
export const RESPONDER_EVENT_SYSTEM = 1 << 1;
|
||||
export const IS_TARGET_PHASE_ONLY = 1 << 2;
|
||||
export const IS_PASSIVE = 1 << 3;
|
||||
export const IS_PASSIVE = 1 << 2;
|
||||
export const IS_ACTIVE = 1 << 3;
|
||||
export const PASSIVE_NOT_SUPPORTED = 1 << 4;
|
||||
export const IS_REPLAYED = 1 << 5;
|
||||
export const IS_FIRST_ANCESTOR = 1 << 6;
|
||||
export const LEGACY_FB_SUPPORT = 1 << 7;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user