diff --git a/.circleci/config.yml b/.circleci/config.yml index 535563556ee95..bfd2cc916b90d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,6 +22,32 @@ aliases: - &attach_workspace at: build + - &process_artifacts + docker: *docker + environment: *environment + steps: + - checkout + - attach_workspace: *attach_workspace + - *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 + - store_artifacts: + path: ./build.tgz + - store_artifacts: + path: ./build/bundle-sizes.json + - store_artifacts: + # TODO: Update release script to use local file instead of pulling + # from artifacts. + path: ./scripts/error-codes/codes.json + - persist_to_workspace: + root: build + paths: + - bundle-sizes.json + jobs: setup: docker: *docker @@ -74,6 +100,18 @@ jobs: - *run_yarn - run: yarn test --maxWorkers=2 + test_source_experimental: + docker: *docker + environment: *environment + steps: + - checkout + - *restore_yarn_cache + - *run_yarn + - run: + environment: + RELEASE_CHANNEL: experimental + command: yarn test --maxWorkers=2 + test_source_persistent: docker: *docker environment: *environment @@ -105,16 +143,49 @@ jobs: - run: ./scripts/circleci/add_build_info_json.sh - run: ./scripts/circleci/update_package_versions.sh - run: yarn build + - run: echo "stable" >> build/RELEASE_CHANNEL + - persist_to_workspace: + root: build + paths: + - RELEASE_CHANNEL + - facebook-www + - node_modules + - react-native + - dist + - sizes/*.json + + build_experimental: + docker: *docker + environment: *environment + parallelism: 20 + steps: + - checkout + - *restore_yarn_cache + - *run_yarn + - run: + environment: + RELEASE_CHANNEL: experimental + command: | + ./scripts/circleci/add_build_info_json.sh + ./scripts/circleci/update_package_versions.sh + yarn build + - run: echo "experimental" >> build/RELEASE_CHANNEL - persist_to_workspace: root: build paths: + - RELEASE_CHANNEL - facebook-www - node_modules - react-native - dist - sizes/*.json - process_artifacts: + # These jobs are named differently so we can distinguish the stable and + # and experimental artifacts + process_artifacts: *process_artifacts + process_artifacts_experimental: *process_artifacts + + sizebot: docker: *docker environment: *environment steps: @@ -122,20 +193,10 @@ jobs: - attach_workspace: *attach_workspace - *restore_yarn_cache - *run_yarn + # This runs in the process_artifacts job, too, but it's faster to run + # this step in both jobs instead of running the jobs sequentially - run: node ./scripts/rollup/consolidateBundleSizes.js - run: node ./scripts/tasks/danger - - run: ./scripts/circleci/upload_build.sh - - run: ./scripts/circleci/pack_and_store_artifact.sh - - store_artifacts: - path: ./node_modules.tgz - - store_artifacts: - path: ./build.tgz - - store_artifacts: - path: ./build/bundle-sizes.json - - store_artifacts: - # TODO: Update release script to use local file instead of pulling - # from artifacts. - path: ./scripts/error-codes/codes.json lint_build: docker: *docker @@ -208,7 +269,7 @@ jobs: workflows: version: 2 - commit: + stable: jobs: - setup - lint: @@ -232,6 +293,9 @@ workflows: - process_artifacts: requires: - build + - sizebot: + requires: + - build - lint_build: requires: - build @@ -247,9 +311,24 @@ workflows: - test_dom_fixtures: requires: - build - hourly: + + experimental: + jobs: + - setup + - test_source_experimental: + requires: + - setup + - build_experimental: + requires: + - setup + - process_artifacts_experimental: + requires: + - build_experimental + + fuzz_tests: triggers: - schedule: + # Fuzz tests run hourly cron: "0 * * * *" filters: branches: diff --git a/.eslintrc.js b/.eslintrc.js index 603f20a3cacba..df147c90bd3e6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -149,6 +149,7 @@ module.exports = { spyOnProd: true, __PROFILE__: true, __UMD__: true, + __EXPERIMENTAL__: true, trustedTypes: true, }, }; diff --git a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js index 532030d0b262a..b58ae3508b696 100644 --- a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js @@ -103,9 +103,7 @@ describe('ReactDOMRoot', () => { it('throws a good message on invalid containers', () => { expect(() => { ReactDOM.unstable_createRoot(
Hi
); - }).toThrow( - 'unstable_createRoot(...): Target container is not a DOM element.', - ); + }).toThrow('createRoot(...): Target container is not a DOM element.'); }); it('warns when rendering with legacy API into createRoot() container', () => { @@ -119,7 +117,7 @@ describe('ReactDOMRoot', () => { [ // We care about this warning: 'You are calling ReactDOM.render() on a container that was previously ' + - 'passed to ReactDOM.unstable_createRoot(). This is not supported. ' + + 'passed to ReactDOM.createRoot(). This is not supported. ' + 'Did you mean to call root.render(element)?', // This is more of a symptom but restructuring the code to avoid it isn't worth it: 'Replacing React-rendered children with a new root component.', @@ -142,7 +140,7 @@ describe('ReactDOMRoot', () => { [ // We care about this warning: 'You are calling ReactDOM.hydrate() on a container that was previously ' + - 'passed to ReactDOM.unstable_createRoot(). This is not supported. ' + + 'passed to ReactDOM.createRoot(). This is not supported. ' + 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?', // This is more of a symptom but restructuring the code to avoid it isn't worth it: 'Replacing React-rendered children with a new root component.', @@ -163,7 +161,7 @@ describe('ReactDOMRoot', () => { [ // We care about this warning: 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' + - 'passed to ReactDOM.unstable_createRoot(). This is not supported. Did you mean to call root.unmount()?', + 'passed to ReactDOM.createRoot(). This is not supported. Did you mean to call root.unmount()?', // This is more of a symptom but restructuring the code to avoid it isn't worth it: "The node you're attempting to unmount was rendered by React and is not a top-level container.", ], @@ -202,7 +200,7 @@ describe('ReactDOMRoot', () => { expect(() => { ReactDOM.unstable_createRoot(container); }).toWarnDev( - 'You are calling ReactDOM.unstable_createRoot() on a container that was previously ' + + 'You are calling ReactDOM.createRoot() on a container that was previously ' + 'passed to ReactDOM.render(). This is not supported.', {withoutStack: true}, ); diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index 37f67e0ce8534..b0fa8e2179daa 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOM.js @@ -451,9 +451,8 @@ const ReactDOM: Object = { warningWithoutStack( !container._reactHasBeenPassedToCreateRootDEV, 'You are calling ReactDOM.hydrate() on a container that was previously ' + - 'passed to ReactDOM.%s(). This is not supported. ' + + 'passed to ReactDOM.createRoot(). This is not supported. ' + 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?', - enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot', ); } // TODO: throw or warn if we couldn't hydrate? @@ -479,9 +478,8 @@ const ReactDOM: Object = { warningWithoutStack( !container._reactHasBeenPassedToCreateRootDEV, 'You are calling ReactDOM.render() on a container that was previously ' + - 'passed to ReactDOM.%s(). This is not supported. ' + + 'passed to ReactDOM.createRoot(). This is not supported. ' + 'Did you mean to call root.render(element)?', - enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot', ); } return legacyRenderSubtreeIntoContainer( @@ -526,8 +524,7 @@ const ReactDOM: Object = { warningWithoutStack( !container._reactHasBeenPassedToCreateRootDEV, 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' + - 'passed to ReactDOM.%s(). This is not supported. Did you mean to call root.unmount()?', - enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot', + 'passed to ReactDOM.createRoot(). This is not supported. Did you mean to call root.unmount()?', ); } @@ -650,13 +647,9 @@ function createRoot( container: DOMContainer, options?: RootOptions, ): _ReactRoot { - const functionName = enableStableConcurrentModeAPIs - ? 'createRoot' - : 'unstable_createRoot'; invariant( isValidContainer(container), - '%s(...): Target container is not a DOM element.', - functionName, + 'createRoot(...): Target container is not a DOM element.', ); warnIfReactDOMContainerInDEV(container); return new ReactRoot(container, options); @@ -666,13 +659,9 @@ function createSyncRoot( container: DOMContainer, options?: RootOptions, ): _ReactRoot { - const functionName = enableStableConcurrentModeAPIs - ? 'createRoot' - : 'unstable_createRoot'; invariant( isValidContainer(container), - '%s(...): Target container is not a DOM element.', - functionName, + 'createRoot(...): Target container is not a DOM element.', ); warnIfReactDOMContainerInDEV(container); return new ReactSyncRoot(container, BatchedRoot, options); @@ -682,9 +671,8 @@ function warnIfReactDOMContainerInDEV(container) { if (__DEV__) { warningWithoutStack( !container._reactRootContainer, - 'You are calling ReactDOM.%s() on a container that was previously ' + + 'You are calling ReactDOM.createRoot() on a container that was previously ' + 'passed to ReactDOM.render(). This is not supported.', - enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot', ); container._reactHasBeenPassedToCreateRootDEV = true; } diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index c1645f0497674..ddb12f71b7993 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -52,7 +52,7 @@ export const disableInputAttributeSyncing = false; // These APIs will no longer be "unstable" in the upcoming 16.7 release, // Control this behavior with a flag to support 16.6 minor releases in the meanwhile. -export const enableStableConcurrentModeAPIs = false; +export const enableStableConcurrentModeAPIs = __EXPERIMENTAL__; export const warnAboutShorthandPropertyCollision = false; diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index a770a31c16c08..f0f93e9b6949d 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -297,7 +297,7 @@ "296": "Log of yielded values is not empty. Call expect(ReactTestRenderer).unstable_toHaveYielded(...) first.", "297": "The matcher `unstable_toHaveYielded` expects an instance of React Test Renderer.\n\nTry: expect(ReactTestRenderer).unstable_toHaveYielded(expectedYields)", "298": "Hooks can only be called inside the body of a function component.", - "299": "%s(...): Target container is not a DOM element.", + "299": "createRoot(...): Target container is not a DOM element.", "300": "Rendered fewer hooks than expected. This may be caused by an accidental early return statement.", "301": "Too many re-renders. React limits the number of renders to prevent an infinite loop.", "302": "It is not supported to run the profiling version of a renderer (for example, `react-dom/profiling`) without also replacing the `scheduler/tracing` module with `scheduler/tracing-profiling`. Your bundler might have a setting for aliasing both modules. Learn more at http://fb.me/react-profiling", diff --git a/scripts/eslint-rules/__tests__/warning-and-invariant-args-test.internal.js b/scripts/eslint-rules/__tests__/warning-and-invariant-args-test.internal.js index 3bf12ae0282f9..4e408113e6a93 100644 --- a/scripts/eslint-rules/__tests__/warning-and-invariant-args-test.internal.js +++ b/scripts/eslint-rules/__tests__/warning-and-invariant-args-test.internal.js @@ -20,7 +20,7 @@ ruleTester.run('eslint-rules/warning-and-invariant-args', rule, { 'arbitraryFunction(a, b)', // These messages are in the error code map "invariant(false, 'Do not override existing functions.')", - "invariant(false, '%s(...): Target container is not a DOM element.', str)", + "invariant(false, 'createRoot(...): Target container is not a DOM element.')", ], invalid: [ { diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js index 8eeda9784000d..0a742aad4022f 100644 --- a/scripts/flow/environment.js +++ b/scripts/flow/environment.js @@ -11,6 +11,7 @@ declare var __PROFILE__: boolean; declare var __UMD__: boolean; +declare var __EXPERIMENTAL__: boolean; declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{ inject: ?((stuff: Object) => void) diff --git a/scripts/jest/setupEnvironment.js b/scripts/jest/setupEnvironment.js index 10ff1234b4c72..1e694b2d31575 100644 --- a/scripts/jest/setupEnvironment.js +++ b/scripts/jest/setupEnvironment.js @@ -7,6 +7,7 @@ if (NODE_ENV !== 'development' && NODE_ENV !== 'production') { global.__DEV__ = NODE_ENV === 'development'; global.__PROFILE__ = NODE_ENV === 'development'; global.__UMD__ = false; +global.__EXPERIMENTAL__ = process.env.RELEASE_CHANNEL === 'experimental'; if (typeof window !== 'undefined') { global.requestIdleCallback = function(callback) { diff --git a/scripts/release/utils.js b/scripts/release/utils.js index 861b83ad34d6c..816f0fdfc2853 100644 --- a/scripts/release/utils.js +++ b/scripts/release/utils.js @@ -78,12 +78,16 @@ const getArtifactsList = async buildID => { const getBuildInfo = async () => { const cwd = join(__dirname, '..', '..'); + const isExperimental = process.env.RELEASE_CHANNEL === 'experimental'; + const branch = await execRead('git branch | grep \\* | cut -d " " -f2', { cwd, }); const commit = await execRead('git show -s --format=%h', {cwd}); const checksum = await getChecksumForCurrentRevision(cwd); - const version = `0.0.0-${commit}`; + const version = isExperimental + ? `0.0.0-experimental-${commit}` + : `0.0.0-${commit}`; // Only available for Circle CI builds. // https://circleci.com/docs/2.0/env-vars/ @@ -94,7 +98,9 @@ const getBuildInfo = async () => { const packageJSON = await readJson( join(cwd, 'packages', 'react', 'package.json') ); - const reactVersion = `${packageJSON.version}-canary-${commit}`; + const reactVersion = isExperimental + ? `${packageJSON.version}-experimental-canary-${commit}` + : `${packageJSON.version}-canary-${commit}`; return {branch, buildNumber, checksum, commit, reactVersion, version}; }; diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index 92f1f3a754648..34184efab63e1 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -304,7 +304,8 @@ function getPlugins( bundleType, globalName, moduleType, - pureExternalModules + pureExternalModules, + isExperimentalBuild ) { const findAndRecordErrorCodes = extractErrorCodes(errorCodeOpts); const forks = Modules.getForks(bundleType, entry, moduleType); @@ -362,6 +363,7 @@ function getPlugins( __PROFILE__: isProfiling || !isProduction ? 'true' : 'false', __UMD__: isUMDBundle ? 'true' : 'false', 'process.env.NODE_ENV': isProduction ? "'production'" : "'development'", + __EXPERIMENTAL__: isExperimentalBuild, }), // We still need CommonJS for external deps like object-assign. commonjs(), @@ -485,6 +487,8 @@ async function createBundle(bundle, bundleType) { module => !importSideEffects[module] ); + const isExperimentalBuild = process.env.RELEASE_CHANNEL === 'experimental'; + const rollupConfig = { input: resolvedEntry, treeshake: { @@ -508,7 +512,8 @@ async function createBundle(bundle, bundleType) { bundleType, bundle.global, bundle.moduleType, - pureExternalModules + pureExternalModules, + isExperimentalBuild ), // We can't use getters in www. legacy: