From 265b3c0188d34ac6785e4c615fb5f78aab558307 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Wed, 20 Sep 2017 09:29:36 -0400 Subject: [PATCH 01/25] Initial commit of react-reconciler bundle --- packages/react-reconciler/README.md | 16 ++++++++++++ packages/react-reconciler/index.js | 7 ++++++ packages/react-reconciler/package.json | 35 ++++++++++++++++++++++++++ scripts/rollup/bundles.js | 26 +++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 packages/react-reconciler/README.md create mode 100644 packages/react-reconciler/index.js create mode 100644 packages/react-reconciler/package.json diff --git a/packages/react-reconciler/README.md b/packages/react-reconciler/README.md new file mode 100644 index 0000000000000..740a4f2c064d2 --- /dev/null +++ b/packages/react-reconciler/README.md @@ -0,0 +1,16 @@ +# react + +An npm package to get you immediate access to [React](https://facebook.github.io/react/), +without also requiring the JSX transformer. This is especially useful for cases where you +want to [`browserify`](https://github.com/substack/node-browserify) your module using +`React`. + +**Note:** by default, React will be in development mode. The development version includes extra warnings about common mistakes, whereas the production version includes extra performance optimizations and strips all error messages. + +To use React in production mode, set the environment variable `NODE_ENV` to `production`. A minifier that performs dead-code elimination such as [UglifyJS](https://github.com/mishoo/UglifyJS2) is recommended to completely remove the extra code present in development mode. + +## Example Usage + +```js +var React = require('react'); +``` diff --git a/packages/react-reconciler/index.js b/packages/react-reconciler/index.js new file mode 100644 index 0000000000000..28d36c9d30df1 --- /dev/null +++ b/packages/react-reconciler/index.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-reconciler.production.min.js'); +} else { + module.exports = require('./cjs/react-reconciler.development.js'); +} diff --git a/packages/react-reconciler/package.json b/packages/react-reconciler/package.json new file mode 100644 index 0000000000000..89697d252a390 --- /dev/null +++ b/packages/react-reconciler/package.json @@ -0,0 +1,35 @@ +{ + "name": "react-reconciler", + "description": "React is a JavaScript library for building user interfaces.", + "version": "16.0.0-rc.2", + "keywords": [ + "react" + ], + "homepage": "https://facebook.github.io/react/", + "bugs": "https://github.com/facebook/react/issues", + "license": "BSD-3-Clause", + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "cjs/", + "umd/" + ], + "main": "index.js", + "repository": "facebook/react", + "engines": { + "node": ">=0.10.0" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + } +} diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index a8e536eb11399..9b5ecd52eee41 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -401,6 +401,32 @@ const bundles = [ 'src/shared/**/*.js', ], }, + + /******* React Reconciler *******/ + { + babelOpts: babelOptsReact, + bundleTypes: [NODE_DEV, NODE_PROD], + config: { + destDir: 'build/', + globals: { + react: 'React', + }, + moduleName: 'ReactReconciler', + sourceMap: false, + }, + entry: 'src/renderers/shared/fiber/ReactFiberReconciler', + externals: ['react', 'prop-types/checkPropTypes'], + isRenderer: false, + label: 'react-reconciler', + manglePropertiesOnProd: false, + name: 'react-reconciler', + paths: [ + 'src/renderers/shared/**/*.js', + + 'src/ReactVersion.js', + 'src/shared/**/*.js', + ], + }, ]; // Based on deep-freeze by substack (public domain) From 21853d5e6bbe967037844e348afcfcd84ccf55f5 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Wed, 20 Sep 2017 11:01:31 -0400 Subject: [PATCH 02/25] =?UTF-8?q?I=20think=20it=E2=80=99s=20working=20?= =?UTF-8?q?=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fixtures/reconciler/README.md | 16 ++++ fixtures/reconciler/index.js | 10 +++ fixtures/reconciler/package.json | 13 +++ fixtures/reconciler/yarn.lock | 107 +++++++++++++++++++++++++ packages/react-reconciler/package.json | 3 + scripts/rollup/build.js | 22 +++-- 6 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 fixtures/reconciler/README.md create mode 100644 fixtures/reconciler/index.js create mode 100644 fixtures/reconciler/package.json create mode 100644 fixtures/reconciler/yarn.lock diff --git a/fixtures/reconciler/README.md b/fixtures/reconciler/README.md new file mode 100644 index 0000000000000..6798d09f5f248 --- /dev/null +++ b/fixtures/reconciler/README.md @@ -0,0 +1,16 @@ + +To test, run: +* `yarn install` +* edit node_modules/react-reconciler/cjs/react-reconciler.development.js +* Add the below snipper after `var valueStack = [];` +* `yarn test` + +If everything is okay then you should see `Ok!` printed to the console. + + +``` +if (global.valueStacks === undefined) { + global.valueStacks = []; +} +global.valueStacks.push(valueStack); +``` diff --git a/fixtures/reconciler/index.js b/fixtures/reconciler/index.js new file mode 100644 index 0000000000000..53f93c8a60b18 --- /dev/null +++ b/fixtures/reconciler/index.js @@ -0,0 +1,10 @@ +const React = require('react'); +const Reconciler = require('react-reconciler'); +const assert = require('assert'); +const A = Reconciler({}); +const B = Reconciler({}); + +assert(global.valueStacks.length === 2); +assert(global.valueStacks[0] !== global.valueStacks[1]); +console.log('Ok!'); + diff --git a/fixtures/reconciler/package.json b/fixtures/reconciler/package.json new file mode 100644 index 0000000000000..a288c1e22963b --- /dev/null +++ b/fixtures/reconciler/package.json @@ -0,0 +1,13 @@ +{ + "name": "react-fixtures", + "version": "0.1.0", + "private": true, + "dependencies": { + "react": "^16.0.0-", + "react-reconciler": "file:../../build/packages/react-reconciler" + }, + "scripts": { + "start": "../../node_modules/.bin/babel-node ./index", + "test": "../../node_modules/.bin/babel-node ./index" + } +} diff --git a/fixtures/reconciler/yarn.lock b/fixtures/reconciler/yarn.lock new file mode 100644 index 0000000000000..64bb62652dcfa --- /dev/null +++ b/fixtures/reconciler/yarn.lock @@ -0,0 +1,107 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + dependencies: + iconv-lite "~0.4.13" + +fbjs@^0.8.9: + version "0.8.15" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.15.tgz#4f0695fdfcc16c37c0b07facec8cb4c4091685b9" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.9" + +iconv-lite@~0.4.13: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + +is-stream@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + +js-tokens@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + dependencies: + asap "~2.0.3" + +prop-types@^15.5.6: + version "15.5.10" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + +"react-reconciler@file:../../build/packages/react-reconciler": + version "16.0.0" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.6" + +react@^16.0.0-: + version "16.0.0-rc.3" + resolved "https://registry.yarnpkg.com/react/-/react-16.0.0-rc.3.tgz#4a9df996326ba7185903d9fbed3149765e237e26" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.6" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + +ua-parser-js@^0.7.9: + version "0.7.14" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.14.tgz#110d53fa4c3f326c121292bbeac904d2e03387ca" + +whatwg-fetch@>=0.10.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" diff --git a/packages/react-reconciler/package.json b/packages/react-reconciler/package.json index 89697d252a390..594393cf3f0d2 100644 --- a/packages/react-reconciler/package.json +++ b/packages/react-reconciler/package.json @@ -21,6 +21,9 @@ "engines": { "node": ">=0.10.0" }, + "peerDependencies": { + "react": "^16.0.0-beta.3" + }, "dependencies": { "fbjs": "^0.8.9", "loose-envify": "^1.1.0", diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index de66907c96ff3..aacb53254b52e 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -79,6 +79,7 @@ function getHeaderSanityCheck(bundleType, hasteName) { } function getBanner(bundleType, hasteName, filename) { + const isReconciler = /react-reconciler/.test(filename); switch (bundleType) { // UMDs are not wrapped in conditions. case UMD_DEV: @@ -89,10 +90,15 @@ function getBanner(bundleType, hasteName, filename) { let banner = Header.getHeader(filename, reactVersion); // Wrap the contents of the if-DEV check with an IIFE. // Block-level function definitions can cause problems for strict mode. - banner += `'use strict';\n\n\nif (process.env.NODE_ENV !== "production") {\n(function() {\n`; + banner += isReconciler + ? `'use strict';\n\n\nif (process.env.NODE_ENV !== "production") {\nmodule.exports = function(config) {\n` + : `'use strict';\n\n\nif (process.env.NODE_ENV !== "production") {\n(function() {\n`; return banner; case NODE_PROD: - return Header.getHeader(filename, reactVersion); + return ( + Header.getHeader(filename, reactVersion) + + (isReconciler ? `\n\n'use strict';\n\n\nmodule.exports = function(config) {\n` : '') + ); // All FB and RN bundles need Haste headers. // DEV bundle is guarded to help weak dead code elimination. case FB_DEV: @@ -105,14 +111,20 @@ function getBanner(bundleType, hasteName, filename) { // Block-level function definitions can cause problems for strict mode. return ( Header.getProvidesHeader(hasteFinalName) + - (isDev ? `\n\n'use strict';\n\n\nif (__DEV__) {\n(function() {\n` : '') + isDev ? `\n\n'use strict';\n\n\nif (__DEV__) {\n(function() {\n` : '' ); default: throw new Error('Unknown type.'); } } -function getFooter(bundleType) { +function getFooter(bundleType, filename) { + if (/react-reconciler/.test(filename)) { + return ( + '\nreturn ReactFiberReconciler(config);\n};\n' + + (bundleType === NODE_DEV ? '}\n' : '') + ); + } // Only need a footer if getBanner() has an opening brace. switch (bundleType) { // Non-UMD DEV bundles need conditions to help weak dead code elimination. @@ -125,7 +137,7 @@ function getFooter(bundleType) { } } -function updateBabelConfig(babelOpts, bundleType) { +function updateBabelConfig(babelOpts, bundleType, filename) { switch (bundleType) { case FB_DEV: case FB_PROD: From 703c5124877433ea68c704deda8da34f04146856 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Wed, 20 Sep 2017 11:43:49 -0400 Subject: [PATCH 03/25] React reconciler: slightly better description and README --- packages/react-reconciler/README.md | 37 +++++++++++++++++++------- packages/react-reconciler/package.json | 2 +- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/react-reconciler/README.md b/packages/react-reconciler/README.md index 740a4f2c064d2..8d84ff562b9bd 100644 --- a/packages/react-reconciler/README.md +++ b/packages/react-reconciler/README.md @@ -1,16 +1,33 @@ -# react +# react-reconciler -An npm package to get you immediate access to [React](https://facebook.github.io/react/), -without also requiring the JSX transformer. This is especially useful for cases where you -want to [`browserify`](https://github.com/substack/node-browserify) your module using -`React`. - -**Note:** by default, React will be in development mode. The development version includes extra warnings about common mistakes, whereas the production version includes extra performance optimizations and strips all error messages. - -To use React in production mode, set the environment variable `NODE_ENV` to `production`. A minifier that performs dead-code elimination such as [UglifyJS](https://github.com/mishoo/UglifyJS2) is recommended to completely remove the extra code present in development mode. +An npm package to let you create custom reconcilers. ## Example Usage ```js -var React = require('react'); +var createReconciler = require('react-reconciler'); + +var ReconcilerConfig = { /* ... */ }; +var Reconciler = createReconciler(ReconcilerConfig); +var Renderer = { + render( + element: React$Element, + container: DOMContainer, + callback: ?Function, + ) { + let root = container._reactRootContainer; + if (!root) { + const newRoot = Renderer.createContainer(container); + root = container._reactRootContainer = newRoot; + // Initial mount should not be batched. + Renderer.unbatchedUpdates(() => { + Renderer.updateContainer(element, newRoot, null, callback); + }); + } else { + Renderer.updateContainer(element, root, null, callback); + } + } +}; + +module.exports = Renderer; ``` diff --git a/packages/react-reconciler/package.json b/packages/react-reconciler/package.json index 594393cf3f0d2..dbd74516b0dca 100644 --- a/packages/react-reconciler/package.json +++ b/packages/react-reconciler/package.json @@ -1,6 +1,6 @@ { "name": "react-reconciler", - "description": "React is a JavaScript library for building user interfaces.", + "description": "React package for creating custom reconcilers.", "version": "16.0.0-rc.2", "keywords": [ "react" From d4deee80e61fb1f7ace009d0bef32bd461ed05d9 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Thu, 21 Sep 2017 08:37:37 -0400 Subject: [PATCH 04/25] Drop react-reconciler version to an unstable release number --- packages/react-reconciler/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-reconciler/package.json b/packages/react-reconciler/package.json index dbd74516b0dca..0019bb3748b2f 100644 --- a/packages/react-reconciler/package.json +++ b/packages/react-reconciler/package.json @@ -1,7 +1,7 @@ { "name": "react-reconciler", "description": "React package for creating custom reconcilers.", - "version": "16.0.0-rc.2", + "version": "0.1.0", "keywords": [ "react" ], From a64e9e9e2b4751c12e8f2370eef5ff30db764daa Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Thu, 21 Sep 2017 08:49:13 -0400 Subject: [PATCH 05/25] Convert to moduleType enum and fix packaging --- scripts/rollup/build.js | 37 ++++++++++++++++++++++--------------- scripts/rollup/bundles.js | 35 +++++++++++++++++++++++------------ scripts/rollup/modules.js | 16 +++++++++++----- 3 files changed, 56 insertions(+), 32 deletions(-) diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index aacb53254b52e..575eb3a3bd9e8 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -34,6 +34,10 @@ const FB_PROD = Bundles.bundleTypes.FB_PROD; const RN_DEV = Bundles.bundleTypes.RN_DEV; const RN_PROD = Bundles.bundleTypes.RN_PROD; +const ISOMORPHIC = Bundles.moduleTypes.ISOMORPHIC; +const RENDERER = Bundles.moduleTypes.RENDERER; +const RECONCILER = Bundles.moduleTypes.RECONCILER; + const reactVersion = require('../../package.json').version; const requestedBundleTypes = (argv.type || '') .split(',') @@ -78,8 +82,7 @@ function getHeaderSanityCheck(bundleType, hasteName) { } } -function getBanner(bundleType, hasteName, filename) { - const isReconciler = /react-reconciler/.test(filename); +function getBanner(bundleType, hasteName, filename, moduleType) { switch (bundleType) { // UMDs are not wrapped in conditions. case UMD_DEV: @@ -90,15 +93,18 @@ function getBanner(bundleType, hasteName, filename) { let banner = Header.getHeader(filename, reactVersion); // Wrap the contents of the if-DEV check with an IIFE. // Block-level function definitions can cause problems for strict mode. - banner += isReconciler + banner += moduleType === RECONCILER ? `'use strict';\n\n\nif (process.env.NODE_ENV !== "production") {\nmodule.exports = function(config) {\n` : `'use strict';\n\n\nif (process.env.NODE_ENV !== "production") {\n(function() {\n`; return banner; case NODE_PROD: return ( Header.getHeader(filename, reactVersion) + - (isReconciler ? `\n\n'use strict';\n\n\nmodule.exports = function(config) {\n` : '') + (moduleType === RECONCILER + ? `\n\n'use strict';\n\n\nmodule.exports = function(config) {\n` + : '') ); + // All FB and RN bundles need Haste headers. // DEV bundle is guarded to help weak dead code elimination. case FB_DEV: @@ -111,15 +117,15 @@ function getBanner(bundleType, hasteName, filename) { // Block-level function definitions can cause problems for strict mode. return ( Header.getProvidesHeader(hasteFinalName) + - isDev ? `\n\n'use strict';\n\n\nif (__DEV__) {\n(function() {\n` : '' + (isDev ? `\n\n'use strict';\n\n\nif (__DEV__) {\n(function() {\n` : '') ); default: throw new Error('Unknown type.'); } } -function getFooter(bundleType, filename) { - if (/react-reconciler/.test(filename)) { +function getFooter(bundleType, filename, moduleType) { + if (moduleType === RECONCILER) { return ( '\nreturn ReactFiberReconciler(config);\n};\n' + (bundleType === NODE_DEV ? '}\n' : '') @@ -178,16 +184,16 @@ function handleRollupWarnings(warning) { console.warn(warning.message || warning); } -function updateBundleConfig(config, filename, format, bundleType, hasteName) { +function updateBundleConfig(config, filename, format, bundleType, hasteName, moduleType) { return Object.assign({}, config, { - banner: getBanner(bundleType, hasteName, filename), + banner: getBanner(bundleType, hasteName, filename, moduleType), dest: Packaging.getPackageDestination( config, bundleType, filename, hasteName ), - footer: getFooter(bundleType), + footer: getFooter(bundleType, filename, moduleType), format, interop: false, }); @@ -321,7 +327,7 @@ function getPlugins( filename, bundleType, hasteName, - isRenderer, + moduleType, manglePropertiesOnProd, useFiber, modulesToStub @@ -329,7 +335,7 @@ function getPlugins( const plugins = [ babel(updateBabelConfig(babelOpts, bundleType)), alias( - Modules.getAliases(paths, bundleType, isRenderer, argv['extract-errors']) + Modules.getAliases(paths, bundleType, moduleType, argv['extract-errors']) ), ]; @@ -465,7 +471,7 @@ function createBundle(bundle, bundleType) { external: Modules.getExternalModules( bundle.externals, bundleType, - bundle.isRenderer + bundle.moduleType ), onwarn: handleRollupWarnings, plugins: getPlugins( @@ -475,7 +481,7 @@ function createBundle(bundle, bundleType) { filename, bundleType, bundle.hasteName, - bundle.isRenderer, + bundle.moduleType, bundle.manglePropertiesOnProd, bundle.useFiber, bundle.modulesToStub @@ -488,7 +494,8 @@ function createBundle(bundle, bundleType) { filename, format, bundleType, - bundle.hasteName + bundle.hasteName, + bundle.moduleType ) ) ) diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 9b5ecd52eee41..6ba9f3dd4aee2 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -20,6 +20,16 @@ const FB_PROD = bundleTypes.FB_PROD; const RN_DEV = bundleTypes.RN_DEV; const RN_PROD = bundleTypes.RN_PROD; +const moduleTypes = { + ISOMORPHIC: 'ISOMORPHIC', + RENDERER: 'RENDERER', + RECONCILER: 'RECONCILER', +}; + +const ISOMORPHIC = moduleTypes.ISOMORPHIC; +const RENDERER = moduleTypes.RENDERER; +const RECONCILER = moduleTypes.RECONCILER; + const babelOptsReact = { exclude: 'node_modules/**', presets: [], @@ -51,7 +61,7 @@ const bundles = [ ], fbEntry: 'src/isomorphic/ReactEntry', hasteName: 'React', - isRenderer: false, + moduleType: ISOMORPHIC, label: 'core', manglePropertiesOnProd: false, name: 'react', @@ -79,7 +89,7 @@ const bundles = [ externals: ['prop-types', 'prop-types/checkPropTypes'], fbEntry: 'src/fb/ReactDOMFiberFBEntry', hasteName: 'ReactDOMFiber', - isRenderer: true, + moduleType: RENDERER, label: 'dom-fiber', manglePropertiesOnProd: false, name: 'react-dom', @@ -112,7 +122,7 @@ const bundles = [ ], fbEntry: 'src/renderers/dom/test/ReactTestUtilsEntry', hasteName: 'ReactTestUtils', - isRenderer: true, + moduleType: RENDERER, label: 'test-utils', manglePropertiesOnProd: false, name: 'react-dom/test-utils', @@ -147,7 +157,7 @@ const bundles = [ ], fbEntry: 'src/renderers/dom/shared/ReactDOMUnstableNativeDependenciesEntry', hasteName: 'ReactDOMUnstableNativeDependencies', - isRenderer: false, + moduleType: RENDERER, label: 'dom-unstable-native-dependencies', manglePropertiesOnProd: false, name: 'react-dom/unstable-native-dependencies', @@ -176,7 +186,7 @@ const bundles = [ externals: ['prop-types', 'prop-types/checkPropTypes'], fbEntry: 'src/renderers/dom/ReactDOMServerBrowserEntry', hasteName: 'ReactDOMServer', - isRenderer: true, + moduleType: RENDERER, label: 'dom-server-browser', manglePropertiesOnProd: false, name: 'react-dom/server.browser', @@ -201,7 +211,7 @@ const bundles = [ }, entry: 'src/renderers/dom/ReactDOMServerNodeEntry', externals: ['prop-types', 'prop-types/checkPropTypes', 'stream'], - isRenderer: true, + moduleType: RENDERER, label: 'dom-server-server-node', manglePropertiesOnProd: false, name: 'react-dom/server.node', @@ -237,7 +247,7 @@ const bundles = [ ], fbEntry: 'src/renderers/art/ReactARTFiberEntry', hasteName: 'ReactARTFiber', - isRenderer: true, + moduleType: RENDERER, label: 'art-fiber', manglePropertiesOnProd: false, name: 'react-art', @@ -274,7 +284,7 @@ const bundles = [ 'prop-types/checkPropTypes', ], hasteName: 'ReactNativeFiber', - isRenderer: true, + moduleType: RENDERER, label: 'native-fiber', manglePropertiesOnProd: false, name: 'react-native-renderer', @@ -335,7 +345,7 @@ const bundles = [ externals: ['prop-types/checkPropTypes'], fbEntry: 'src/renderers/testing/ReactTestRendererFiberEntry', hasteName: 'ReactTestRendererFiber', - isRenderer: true, + moduleType: RENDERER, label: 'test-fiber', manglePropertiesOnProd: false, name: 'react-test-renderer', @@ -364,7 +374,7 @@ const bundles = [ ], fbEntry: 'src/renderers/testing/ReactShallowRendererEntry', hasteName: 'ReactShallowRenderer', - isRenderer: true, + moduleType: RENDERER, label: 'shallow-renderer', manglePropertiesOnProd: false, name: 'react-test-renderer/shallow', @@ -389,7 +399,7 @@ const bundles = [ }, entry: 'src/renderers/noop/ReactNoopEntry', externals: ['prop-types/checkPropTypes', 'jest-matchers'], - isRenderer: true, + moduleType: RENDERER, label: 'noop-fiber', manglePropertiesOnProd: false, name: 'react-noop-renderer', @@ -416,7 +426,7 @@ const bundles = [ }, entry: 'src/renderers/shared/fiber/ReactFiberReconciler', externals: ['react', 'prop-types/checkPropTypes'], - isRenderer: false, + moduleType: RECONCILER, label: 'react-reconciler', manglePropertiesOnProd: false, name: 'react-reconciler', @@ -449,5 +459,6 @@ deepFreeze(bundles); module.exports = { bundleTypes, + moduleTypes, bundles, }; diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js index 53918d536a5d5..8d0edfecb261a 100644 --- a/scripts/rollup/modules.js +++ b/scripts/rollup/modules.js @@ -4,6 +4,7 @@ const resolve = require('path').resolve; const basename = require('path').basename; const sync = require('glob').sync; const bundleTypes = require('./bundles').bundleTypes; +const moduleTypes = require('./bundles').moduleTypes; const extractErrorCodes = require('../error-codes/extract-errors'); const exclude = [ @@ -21,6 +22,10 @@ const FB_PROD = bundleTypes.FB_PROD; const RN_DEV = bundleTypes.RN_DEV; const RN_PROD = bundleTypes.RN_PROD; +const ISOMORPHIC = moduleTypes.ISOMORPHIC; +const RENDERER = moduleTypes.RENDERER; +const RECONCILER = moduleTypes.RECONCILER; + const errorCodeOpts = { errorMapFilePath: 'scripts/error-codes/codes.json', }; @@ -87,7 +92,7 @@ function createModuleMap(paths, extractErrors, bundleType) { return moduleMap; } -function getNodeModules(bundleType, isRenderer) { +function getNodeModules(bundleType, moduleType) { // rather than adding the rollup node resolve plugin, // we can instead deal with the only node module that is used // for UMD bundles - object-assign @@ -97,7 +102,7 @@ function getNodeModules(bundleType, isRenderer) { return { // Bundle object-assign once in the isomorphic React, and then use // that from the renderer UMD. Avoids bundling it in both UMDs. - 'object-assign': isRenderer + 'object-assign': moduleType === RENDERER ? resolve('./scripts/rollup/shims/rollup/assign.js') : resolve('./node_modules/object-assign/index.js'), // include the ART package modules directly by aliasing them from node_modules @@ -136,7 +141,7 @@ function ignoreReactNativeModules() { ]; } -function getExternalModules(externals, bundleType, isRenderer) { +function getExternalModules(externals, bundleType, moduleType) { // external modules tell Rollup that we should not attempt // to bundle these modules and instead treat them as // external dependencies to the bundle. so for CJS bundles @@ -144,6 +149,7 @@ function getExternalModules(externals, bundleType, isRenderer) { // the top of the bundle. for UMD bundles this means having // both a require and a global check for them let externalModules = externals.slice(); + const isRenderer = moduleType === RENDERER || moduleType === RECONCILER; switch (bundleType) { case UMD_DEV: @@ -282,7 +288,7 @@ function replaceBundleStubModules(bundleModulesToStub) { return stubbedModules; } -function getAliases(paths, bundleType, isRenderer, extractErrors) { +function getAliases(paths, bundleType, moduleType, extractErrors) { return Object.assign( createModuleMap( paths, @@ -290,7 +296,7 @@ function getAliases(paths, bundleType, isRenderer, extractErrors) { bundleType ), getInternalModules(), - getNodeModules(bundleType, isRenderer), + getNodeModules(bundleType, moduleType), getFbjsModuleAliases(bundleType) ); } From ac1e6034df3146f4969535e38d6558334a3f716b Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Thu, 21 Sep 2017 13:51:13 -0400 Subject: [PATCH 06/25] eslint --- scripts/rollup/build.js | 2 -- scripts/rollup/modules.js | 1 - 2 files changed, 3 deletions(-) diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index 575eb3a3bd9e8..d26e859185db9 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -34,8 +34,6 @@ const FB_PROD = Bundles.bundleTypes.FB_PROD; const RN_DEV = Bundles.bundleTypes.RN_DEV; const RN_PROD = Bundles.bundleTypes.RN_PROD; -const ISOMORPHIC = Bundles.moduleTypes.ISOMORPHIC; -const RENDERER = Bundles.moduleTypes.RENDERER; const RECONCILER = Bundles.moduleTypes.RECONCILER; const reactVersion = require('../../package.json').version; diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js index 8d0edfecb261a..3ab8b668c9814 100644 --- a/scripts/rollup/modules.js +++ b/scripts/rollup/modules.js @@ -22,7 +22,6 @@ const FB_PROD = bundleTypes.FB_PROD; const RN_DEV = bundleTypes.RN_DEV; const RN_PROD = bundleTypes.RN_PROD; -const ISOMORPHIC = moduleTypes.ISOMORPHIC; const RENDERER = moduleTypes.RENDERER; const RECONCILER = moduleTypes.RECONCILER; From 1165ee87f3df178c8ce68bc9ae90482bd4f63824 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Fri, 22 Sep 2017 10:36:23 -0400 Subject: [PATCH 07/25] s/Renderer/Reconciler in docs --- packages/react-reconciler/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-reconciler/README.md b/packages/react-reconciler/README.md index 8d84ff562b9bd..b3d579f50c1a0 100644 --- a/packages/react-reconciler/README.md +++ b/packages/react-reconciler/README.md @@ -17,14 +17,14 @@ var Renderer = { ) { let root = container._reactRootContainer; if (!root) { - const newRoot = Renderer.createContainer(container); + const newRoot = Reconciler.createContainer(container); root = container._reactRootContainer = newRoot; // Initial mount should not be batched. - Renderer.unbatchedUpdates(() => { - Renderer.updateContainer(element, newRoot, null, callback); + Reconciler.unbatchedUpdates(() => { + Reconciler.updateContainer(element, newRoot, null, callback); }); } else { - Renderer.updateContainer(element, root, null, callback); + Reconciler.updateContainer(element, root, null, callback); } } }; From 2018b79ccffdf7534215da67d9a9c3963731ad56 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Fri, 22 Sep 2017 10:38:54 -0400 Subject: [PATCH 08/25] yarn prettier --- fixtures/reconciler/index.js | 1 - scripts/rollup/build.js | 14 ++++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/fixtures/reconciler/index.js b/fixtures/reconciler/index.js index 53f93c8a60b18..89b1630a89a8c 100644 --- a/fixtures/reconciler/index.js +++ b/fixtures/reconciler/index.js @@ -7,4 +7,3 @@ const B = Reconciler({}); assert(global.valueStacks.length === 2); assert(global.valueStacks[0] !== global.valueStacks[1]); console.log('Ok!'); - diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index d26e859185db9..d9c3e0db57f68 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -99,10 +99,9 @@ function getBanner(bundleType, hasteName, filename, moduleType) { return ( Header.getHeader(filename, reactVersion) + (moduleType === RECONCILER - ? `\n\n'use strict';\n\n\nmodule.exports = function(config) {\n` - : '') + ? `\n\n'use strict';\n\n\nmodule.exports = function(config) {\n` + : '') ); - // All FB and RN bundles need Haste headers. // DEV bundle is guarded to help weak dead code elimination. case FB_DEV: @@ -182,7 +181,14 @@ function handleRollupWarnings(warning) { console.warn(warning.message || warning); } -function updateBundleConfig(config, filename, format, bundleType, hasteName, moduleType) { +function updateBundleConfig( + config, + filename, + format, + bundleType, + hasteName, + moduleType +) { return Object.assign({}, config, { banner: getBanner(bundleType, hasteName, filename, moduleType), dest: Packaging.getPackageDestination( From 3f7a6b6801f3b9848c83134b8f370d9cef423528 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Fri, 22 Sep 2017 11:28:19 -0400 Subject: [PATCH 09/25] change names of things in the react-reconciler readme --- packages/react-reconciler/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/react-reconciler/README.md b/packages/react-reconciler/README.md index b3d579f50c1a0..3d4ce8d9f3770 100644 --- a/packages/react-reconciler/README.md +++ b/packages/react-reconciler/README.md @@ -5,11 +5,11 @@ An npm package to let you create custom reconcilers. ## Example Usage ```js -var createReconciler = require('react-reconciler'); +var Reconciler = require('react-reconciler'); var ReconcilerConfig = { /* ... */ }; -var Reconciler = createReconciler(ReconcilerConfig); -var Renderer = { +var Renderer = Reconciler(ReconcilerConfig); +var RendererPublicAPI = { render( element: React$Element, container: DOMContainer, @@ -17,17 +17,17 @@ var Renderer = { ) { let root = container._reactRootContainer; if (!root) { - const newRoot = Reconciler.createContainer(container); + const newRoot = Renderer.createContainer(container); root = container._reactRootContainer = newRoot; // Initial mount should not be batched. - Reconciler.unbatchedUpdates(() => { - Reconciler.updateContainer(element, newRoot, null, callback); + Renderer.unbatchedUpdates(() => { + Renderer.updateContainer(element, newRoot, null, callback); }); } else { - Reconciler.updateContainer(element, root, null, callback); + Renderer.updateContainer(element, root, null, callback); } } }; -module.exports = Renderer; +module.exports = RendererPublicAPI; ``` From b1ace5c8e11db4399c0020e858d1de1d695d3276 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Mon, 25 Sep 2017 10:05:57 -0400 Subject: [PATCH 10/25] change predicate --- scripts/rollup/modules.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js index 3ab8b668c9814..8cc8a27821a86 100644 --- a/scripts/rollup/modules.js +++ b/scripts/rollup/modules.js @@ -23,7 +23,7 @@ const RN_DEV = bundleTypes.RN_DEV; const RN_PROD = bundleTypes.RN_PROD; const RENDERER = moduleTypes.RENDERER; -const RECONCILER = moduleTypes.RECONCILER; +const ISOMORPHIC = moduleTypes.ISOMORPHIC; const errorCodeOpts = { errorMapFilePath: 'scripts/error-codes/codes.json', @@ -148,12 +148,10 @@ function getExternalModules(externals, bundleType, moduleType) { // the top of the bundle. for UMD bundles this means having // both a require and a global check for them let externalModules = externals.slice(); - const isRenderer = moduleType === RENDERER || moduleType === RECONCILER; - switch (bundleType) { case UMD_DEV: case UMD_PROD: - if (isRenderer) { + if (moduleType !== ISOMORPHIC) { externalModules.push('react'); } break; @@ -163,7 +161,7 @@ function getExternalModules(externals, bundleType, moduleType) { case RN_PROD: fbjsModules.forEach(module => externalModules.push(module)); externalModules.push('object-assign'); - if (isRenderer) { + if (moduleType !== ISOMORPHIC) { externalModules.push('react'); } break; @@ -173,7 +171,7 @@ function getExternalModules(externals, bundleType, moduleType) { externalModules.push('object-assign'); externalModules.push('ReactCurrentOwner'); externalModules.push('lowPriorityWarning'); - if (isRenderer) { + if (moduleType !== ISOMORPHIC) { externalModules.push('React'); if (externalModules.indexOf('react-dom') > -1) { externalModules.push('ReactDOM'); From e7d4f44b63366aef614a20fa6a88ef147909545d Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Mon, 25 Sep 2017 10:36:04 -0400 Subject: [PATCH 11/25] rollup: flip object-assign shimming check --- scripts/rollup/modules.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js index 8cc8a27821a86..7d4effb01965f 100644 --- a/scripts/rollup/modules.js +++ b/scripts/rollup/modules.js @@ -22,7 +22,6 @@ const FB_PROD = bundleTypes.FB_PROD; const RN_DEV = bundleTypes.RN_DEV; const RN_PROD = bundleTypes.RN_PROD; -const RENDERER = moduleTypes.RENDERER; const ISOMORPHIC = moduleTypes.ISOMORPHIC; const errorCodeOpts = { @@ -101,9 +100,9 @@ function getNodeModules(bundleType, moduleType) { return { // Bundle object-assign once in the isomorphic React, and then use // that from the renderer UMD. Avoids bundling it in both UMDs. - 'object-assign': moduleType === RENDERER - ? resolve('./scripts/rollup/shims/rollup/assign.js') - : resolve('./node_modules/object-assign/index.js'), + 'object-assign': moduleType === ISOMORPHIC + ? resolve('./node_modules/object-assign/index.js') + : resolve('./scripts/rollup/shims/rollup/assign.js'), // include the ART package modules directly by aliasing them from node_modules 'art/modes/current': resolve('./node_modules/art/modes/current.js'), 'art/modes/fast-noSideEffects': resolve( From 34551f7dbfd46646cb8d89d7e9a88c367dc9b92a Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Mon, 25 Sep 2017 17:27:27 -0400 Subject: [PATCH 12/25] copy noop renderer into react-reconciler fixture --- fixtures/reconciler/NoopRenderer.js | 470 ++++++++++++++++++++++++++++ fixtures/reconciler/index.js | 38 ++- 2 files changed, 499 insertions(+), 9 deletions(-) create mode 100644 fixtures/reconciler/NoopRenderer.js diff --git a/fixtures/reconciler/NoopRenderer.js b/fixtures/reconciler/NoopRenderer.js new file mode 100644 index 0000000000000..05e766ae30e83 --- /dev/null +++ b/fixtures/reconciler/NoopRenderer.js @@ -0,0 +1,470 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ + +/** + * This is a renderer of React that doesn't have a render target output. + * It is useful to demonstrate the internals of the reconciler in isolation + * and for testing semantics of reconciliation separate from the host + * environment. + */ + +'use strict'; + +import type {Fiber} from 'ReactFiber'; +import type {UpdateQueue} from 'ReactFiberUpdateQueue'; +var ReactFiberReconciler = require('react-reconciler'); +var emptyObject = require('fbjs/lib/emptyObject'); + +var assert = require('assert'); + +const UPDATE_SIGNAL = {}; + +var scheduledCallback = null; + +type Container = {rootID: string, children: Array}; +type Props = {prop: any, hidden?: boolean}; +type Instance = {| + type: string, + id: number, + children: Array, + prop: any, +|}; +type TextInstance = {|text: string, id: number|}; + +var instanceCounter = 0; + +var failInBeginPhase = false; + +function appendChild( + parentInstance: Instance | Container, + child: Instance | TextInstance, +): void { + const index = parentInstance.children.indexOf(child); + if (index !== -1) { + parentInstance.children.splice(index, 1); + } + parentInstance.children.push(child); +} + +function insertBefore( + parentInstance: Instance | Container, + child: Instance | TextInstance, + beforeChild: Instance | TextInstance, +): void { + const index = parentInstance.children.indexOf(child); + if (index !== -1) { + parentInstance.children.splice(index, 1); + } + const beforeIndex = parentInstance.children.indexOf(beforeChild); + if (beforeIndex === -1) { + throw new Error('This child does not exist.'); + } + parentInstance.children.splice(beforeIndex, 0, child); +} + +function removeChild( + parentInstance: Instance | Container, + child: Instance | TextInstance, +): void { + const index = parentInstance.children.indexOf(child); + if (index === -1) { + throw new Error('This child does not exist.'); + } + parentInstance.children.splice(index, 1); +} + +var NoopRenderer = ReactFiberReconciler({ + getRootHostContext() { + if (failInBeginPhase) { + throw new Error('Error in host config.'); + } + return emptyObject; + }, + + getChildHostContext() { + return emptyObject; + }, + + getPublicInstance(instance) { + return instance; + }, + + createInstance(type: string, props: Props): Instance { + const inst = { + id: instanceCounter++, + type: type, + children: [], + prop: props.prop, + }; + // Hide from unit tests + Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false}); + return inst; + }, + + appendInitialChild( + parentInstance: Instance, + child: Instance | TextInstance, + ): void { + parentInstance.children.push(child); + }, + + finalizeInitialChildren( + domElement: Instance, + type: string, + props: Props, + ): boolean { + return false; + }, + + prepareUpdate( + instance: Instance, + type: string, + oldProps: Props, + newProps: Props, + ): null | {} { + return UPDATE_SIGNAL; + }, + + commitMount(instance: Instance, type: string, newProps: Props): void { + // Noop + }, + + commitUpdate( + instance: Instance, + updatePayload: Object, + type: string, + oldProps: Props, + newProps: Props, + ): void { + instance.prop = newProps.prop; + }, + + shouldSetTextContent(type: string, props: Props): boolean { + return ( + typeof props.children === 'string' || typeof props.children === 'number' + ); + }, + + resetTextContent(instance: Instance): void {}, + + shouldDeprioritizeSubtree(type: string, props: Props): boolean { + return !!props.hidden; + }, + + createTextInstance( + text: string, + rootContainerInstance: Container, + hostContext: Object, + internalInstanceHandle: Object, + ): TextInstance { + var inst = {text: text, id: instanceCounter++}; + // Hide from unit tests + Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false}); + return inst; + }, + + commitTextUpdate( + textInstance: TextInstance, + oldText: string, + newText: string, + ): void { + textInstance.text = newText; + }, + + appendChild: appendChild, + appendChildToContainer: appendChild, + insertBefore: insertBefore, + insertInContainerBefore: insertBefore, + removeChild: removeChild, + removeChildFromContainer: removeChild, + + scheduleDeferredCallback(callback) { + if (scheduledCallback) { + throw new Error( + 'Scheduling a callback twice is excessive. Instead, keep track of ' + + 'whether the callback has already been scheduled.', + ); + } + scheduledCallback = callback; + }, + + prepareForCommit(): void {}, + + resetAfterCommit(): void {}, +}); + +var rootContainers = new Map(); +var roots = new Map(); +var DEFAULT_ROOT_ID = ''; + +let yieldedValues = null; + +function* flushUnitsOfWork(n: number): Generator, void, void> { + var didStop = false; + while (!didStop && scheduledCallback !== null) { + var cb = scheduledCallback; + scheduledCallback = null; + yieldedValues = null; + var unitsRemaining = n; + cb({ + timeRemaining() { + if (yieldedValues !== null) { + return 0; + } + if (unitsRemaining-- > 0) { + return 999; + } + didStop = true; + return 0; + }, + }); + + if (yieldedValues !== null) { + const values = yieldedValues; + yieldedValues = null; + yield values; + } + } +} + +var ReactNoop = { + getChildren(rootID: string = DEFAULT_ROOT_ID) { + const container = rootContainers.get(rootID); + if (container) { + return container.children; + } else { + return null; + } + }, + + // Shortcut for testing a single root + render(element: React$Element, callback: ?Function) { + ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback); + }, + + renderToRootWithID( + element: React$Element, + rootID: string, + callback: ?Function, + ) { + let root = roots.get(rootID); + if (!root) { + const container = {rootID: rootID, children: []}; + rootContainers.set(rootID, container); + root = NoopRenderer.createContainer(container); + roots.set(rootID, root); + } + NoopRenderer.updateContainer(element, root, null, callback); + }, + + unmountRootWithID(rootID: string) { + const root = roots.get(rootID); + if (root) { + NoopRenderer.updateContainer(null, root, null, () => { + roots.delete(rootID); + rootContainers.delete(rootID); + }); + } + }, + + /* We don’t have access to ReactInstanceMap from the outside + findInstance( + componentOrElement: Element | ?React$Component, + ): null | Instance | TextInstance { + if (componentOrElement == null) { + return null; + } + // Unsound duck typing. + const component = (componentOrElement: any); + if (typeof component.id === 'number') { + return component; + } + const inst = ReactInstanceMap.get(component); + return inst ? NoopRenderer.findHostInstance(inst) : null; + }, + */ + + flushDeferredPri(timeout: number = Infinity): Array { + // The legacy version of this function decremented the timeout before + // returning the new time. + // TODO: Convert tests to use flushUnitsOfWork or flushAndYield instead. + const n = timeout / 5 - 1; + + let values = []; + for (const value of flushUnitsOfWork(n)) { + values.push(...value); + } + return values; + }, + + flush(): Array { + return ReactNoop.flushUnitsOfWork(Infinity); + }, + + flushAndYield( + unitsOfWork: number = Infinity, + ): Generator, void, void> { + return flushUnitsOfWork(unitsOfWork); + }, + + flushUnitsOfWork(n: number): Array { + let values = []; + for (const value of flushUnitsOfWork(n)) { + values.push(...value); + } + return values; + }, + + flushThrough(expected: Array): void { + let actual = []; + if (expected.length !== 0) { + for (const value of flushUnitsOfWork(Infinity)) { + actual.push(...value); + if (actual.length >= expected.length) { + break; + } + } + } + assert.deepEqual( + actual, + expected, + 'Unexpected flush results.\nExpected:\n ' + JSON.stringify(actual, null, 2) + + '\n\nActual:\n ' + JSON.stringify(expected, null, 2) + ); + }, + + yield(value: mixed) { + if (yieldedValues === null) { + yieldedValues = [value]; + } else { + yieldedValues.push(value); + } + }, + + hasScheduledCallback() { + return !!scheduledCallback; + }, + + batchedUpdates: NoopRenderer.batchedUpdates, + + deferredUpdates: NoopRenderer.deferredUpdates, + + unbatchedUpdates: NoopRenderer.unbatchedUpdates, + + flushSync: NoopRenderer.flushSync, + + // Logs the current state of the tree. + dumpTree(rootID: string = DEFAULT_ROOT_ID) { + const root = roots.get(rootID); + const rootContainer = rootContainers.get(rootID); + if (!root || !rootContainer) { + console.log('Nothing rendered yet.'); + return; + } + + var bufferedLog = []; + function log(...args) { + bufferedLog.push(...args, '\n'); + } + + function logHostInstances(children: Array, depth) { + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var indent = ' '.repeat(depth); + if (typeof child.text === 'string') { + log(indent + '- ' + child.text); + } else { + // $FlowFixMe - The child should've been refined now. + log(indent + '- ' + child.type + '#' + child.id); + // $FlowFixMe - The child should've been refined now. + logHostInstances(child.children, depth + 1); + } + } + } + function logContainer(container: Container, depth) { + log(' '.repeat(depth) + '- [root#' + container.rootID + ']'); + logHostInstances(container.children, depth + 1); + } + + function logUpdateQueue(updateQueue: UpdateQueue, depth) { + log(' '.repeat(depth + 1) + 'QUEUED UPDATES'); + const firstUpdate = updateQueue.first; + if (!firstUpdate) { + return; + } + + log( + ' '.repeat(depth + 1) + '~', + firstUpdate && firstUpdate.partialState, + firstUpdate.callback ? 'with callback' : '', + '[' + firstUpdate.priorityLevel + ']', + ); + var next; + while ((next = firstUpdate.next)) { + log( + ' '.repeat(depth + 1) + '~', + next.partialState, + next.callback ? 'with callback' : '', + '[' + firstUpdate.priorityLevel + ']', + ); + } + } + + function logFiber(fiber: Fiber, depth) { + log( + ' '.repeat(depth) + + '- ' + + (fiber.type ? fiber.type.name || fiber.type : '[root]'), + '[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']', + ); + if (fiber.updateQueue) { + logUpdateQueue(fiber.updateQueue, depth); + } + // const childInProgress = fiber.progressedChild; + // if (childInProgress && childInProgress !== fiber.child) { + // log( + // ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority, + // ); + // logFiber(childInProgress, depth + 1); + // if (fiber.child) { + // log(' '.repeat(depth + 1) + 'CURRENT'); + // } + // } else if (fiber.child && fiber.updateQueue) { + // log(' '.repeat(depth + 1) + 'CHILDREN'); + // } + if (fiber.child) { + logFiber(fiber.child, depth + 1); + } + if (fiber.sibling) { + logFiber(fiber.sibling, depth); + } + } + + log('HOST INSTANCES:'); + logContainer(rootContainer, 0); + log('FIBERS:'); + logFiber(root.current, 0); + + console.log(...bufferedLog); + }, + + simulateErrorInHostConfig(fn: () => void) { + failInBeginPhase = true; + try { + fn(); + } finally { + failInBeginPhase = false; + } + }, + +}; + +module.exports = ReactNoop; diff --git a/fixtures/reconciler/index.js b/fixtures/reconciler/index.js index 89b1630a89a8c..de072a22bb094 100644 --- a/fixtures/reconciler/index.js +++ b/fixtures/reconciler/index.js @@ -1,9 +1,29 @@ -const React = require('react'); -const Reconciler = require('react-reconciler'); -const assert = require('assert'); -const A = Reconciler({}); -const B = Reconciler({}); - -assert(global.valueStacks.length === 2); -assert(global.valueStacks[0] !== global.valueStacks[1]); -console.log('Ok!'); + +var React = require('react'); +var Noop = require('./NoopRenderer'); +var assert = require('assert'); + +class Comp extends React.Component { + render() { + return this.props.active ? 'Active Comp' : 'Deactive Comp'; + } +} +const Children = (props) => props.children; +const result = Noop.render( +
+
Hello
+ + Hello world + {'Number '}{42} + + +
+); + +console.log('Noop dumping Tree'); +Noop.dumpTree(); + +Noop.flushDeferredPri(); +console.log('Noop dumping Tree'); +Noop.dumpTree(); + From 8f50bf3de16bbbb7804338d11116e46350fa4c3d Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Thu, 28 Sep 2017 09:28:36 -0400 Subject: [PATCH 13/25] Change reconciler fixture test --- fixtures/reconciler/index.js | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/fixtures/reconciler/index.js b/fixtures/reconciler/index.js index de072a22bb094..36f38d48147ff 100644 --- a/fixtures/reconciler/index.js +++ b/fixtures/reconciler/index.js @@ -9,7 +9,7 @@ class Comp extends React.Component { } } const Children = (props) => props.children; -const result = Noop.render( +Noop.render(
Hello
@@ -19,11 +19,28 @@ const result = Noop.render(
); +Noop.flush(); +assert.deepEqual( + Noop.getChildren(), + [ + { + type: 'main', + children: [ + {type: 'div', children: [], prop: undefined}, + {text: 'Hello world'}, + { + type: 'span', + children: [ + {text: 'Number '}, + {text: '42'}, + ], + prop: undefined, + }, + {text: 'Active Comp'} + ], + prop: undefined, + } + ] +); -console.log('Noop dumping Tree'); -Noop.dumpTree(); - -Noop.flushDeferredPri(); -console.log('Noop dumping Tree'); -Noop.dumpTree(); - +console.log('Reconciler package is Ok!'); From 8dc59e6d4562d51ab07b8f396cc377c5809c884d Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Thu, 28 Sep 2017 09:47:58 -0400 Subject: [PATCH 14/25] prettier --- fixtures/reconciler/NoopRenderer.js | 37 ++++++++++++----------- fixtures/reconciler/index.js | 46 +++++++++++++++-------------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/fixtures/reconciler/NoopRenderer.js b/fixtures/reconciler/NoopRenderer.js index 05e766ae30e83..96381ae5cc518 100644 --- a/fixtures/reconciler/NoopRenderer.js +++ b/fixtures/reconciler/NoopRenderer.js @@ -45,7 +45,7 @@ var failInBeginPhase = false; function appendChild( parentInstance: Instance | Container, - child: Instance | TextInstance, + child: Instance | TextInstance ): void { const index = parentInstance.children.indexOf(child); if (index !== -1) { @@ -57,7 +57,7 @@ function appendChild( function insertBefore( parentInstance: Instance | Container, child: Instance | TextInstance, - beforeChild: Instance | TextInstance, + beforeChild: Instance | TextInstance ): void { const index = parentInstance.children.indexOf(child); if (index !== -1) { @@ -72,7 +72,7 @@ function insertBefore( function removeChild( parentInstance: Instance | Container, - child: Instance | TextInstance, + child: Instance | TextInstance ): void { const index = parentInstance.children.indexOf(child); if (index === -1) { @@ -111,7 +111,7 @@ var NoopRenderer = ReactFiberReconciler({ appendInitialChild( parentInstance: Instance, - child: Instance | TextInstance, + child: Instance | TextInstance ): void { parentInstance.children.push(child); }, @@ -119,7 +119,7 @@ var NoopRenderer = ReactFiberReconciler({ finalizeInitialChildren( domElement: Instance, type: string, - props: Props, + props: Props ): boolean { return false; }, @@ -128,7 +128,7 @@ var NoopRenderer = ReactFiberReconciler({ instance: Instance, type: string, oldProps: Props, - newProps: Props, + newProps: Props ): null | {} { return UPDATE_SIGNAL; }, @@ -142,7 +142,7 @@ var NoopRenderer = ReactFiberReconciler({ updatePayload: Object, type: string, oldProps: Props, - newProps: Props, + newProps: Props ): void { instance.prop = newProps.prop; }, @@ -163,7 +163,7 @@ var NoopRenderer = ReactFiberReconciler({ text: string, rootContainerInstance: Container, hostContext: Object, - internalInstanceHandle: Object, + internalInstanceHandle: Object ): TextInstance { var inst = {text: text, id: instanceCounter++}; // Hide from unit tests @@ -174,7 +174,7 @@ var NoopRenderer = ReactFiberReconciler({ commitTextUpdate( textInstance: TextInstance, oldText: string, - newText: string, + newText: string ): void { textInstance.text = newText; }, @@ -190,7 +190,7 @@ var NoopRenderer = ReactFiberReconciler({ if (scheduledCallback) { throw new Error( 'Scheduling a callback twice is excessive. Instead, keep track of ' + - 'whether the callback has already been scheduled.', + 'whether the callback has already been scheduled.' ); } scheduledCallback = callback; @@ -253,7 +253,7 @@ var ReactNoop = { renderToRootWithID( element: React$Element, rootID: string, - callback: ?Function, + callback: ?Function ) { let root = roots.get(rootID); if (!root) { @@ -310,7 +310,7 @@ var ReactNoop = { }, flushAndYield( - unitsOfWork: number = Infinity, + unitsOfWork: number = Infinity ): Generator, void, void> { return flushUnitsOfWork(unitsOfWork); }, @@ -336,8 +336,10 @@ var ReactNoop = { assert.deepEqual( actual, expected, - 'Unexpected flush results.\nExpected:\n ' + JSON.stringify(actual, null, 2) + - '\n\nActual:\n ' + JSON.stringify(expected, null, 2) + 'Unexpected flush results.\nExpected:\n ' + + JSON.stringify(actual, null, 2) + + '\n\nActual:\n ' + + JSON.stringify(expected, null, 2) ); }, @@ -405,7 +407,7 @@ var ReactNoop = { ' '.repeat(depth + 1) + '~', firstUpdate && firstUpdate.partialState, firstUpdate.callback ? 'with callback' : '', - '[' + firstUpdate.priorityLevel + ']', + '[' + firstUpdate.priorityLevel + ']' ); var next; while ((next = firstUpdate.next)) { @@ -413,7 +415,7 @@ var ReactNoop = { ' '.repeat(depth + 1) + '~', next.partialState, next.callback ? 'with callback' : '', - '[' + firstUpdate.priorityLevel + ']', + '[' + firstUpdate.priorityLevel + ']' ); } } @@ -423,7 +425,7 @@ var ReactNoop = { ' '.repeat(depth) + '- ' + (fiber.type ? fiber.type.name || fiber.type : '[root]'), - '[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']', + '[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']' ); if (fiber.updateQueue) { logUpdateQueue(fiber.updateQueue, depth); @@ -464,7 +466,6 @@ var ReactNoop = { failInBeginPhase = false; } }, - }; module.exports = ReactNoop; diff --git a/fixtures/reconciler/index.js b/fixtures/reconciler/index.js index 36f38d48147ff..d290dac0ff62a 100644 --- a/fixtures/reconciler/index.js +++ b/fixtures/reconciler/index.js @@ -1,4 +1,3 @@ - var React = require('react'); var Noop = require('./NoopRenderer'); var assert = require('assert'); @@ -8,7 +7,7 @@ class Comp extends React.Component { return this.props.active ? 'Active Comp' : 'Deactive Comp'; } } -const Children = (props) => props.children; +const Children = props => props.children; Noop.render(
Hello
@@ -20,27 +19,30 @@ Noop.render(
); Noop.flush(); +const actual = Noop.getChildren(); +const expected = [ + { + type: 'main', + children: [ + {type: 'div', children: [], prop: undefined}, + {text: 'Hello world'}, + { + type: 'span', + children: [{text: 'Number '}, {text: '42'}], + prop: undefined, + }, + {text: 'Active Comp'}, + ], + prop: undefined, + }, +]; assert.deepEqual( - Noop.getChildren(), - [ - { - type: 'main', - children: [ - {type: 'div', children: [], prop: undefined}, - {text: 'Hello world'}, - { - type: 'span', - children: [ - {text: 'Number '}, - {text: '42'}, - ], - prop: undefined, - }, - {text: 'Active Comp'} - ], - prop: undefined, - } - ] + actual, + expected, + 'Error. Noop.getChildren() returned unexpected value.\nExpected:\ ' + + JSON.stringify(expected, null, 2) + + '\n\nActual:\n ' + + JSON.stringify(actual, null, 2) ); console.log('Reconciler package is Ok!'); From 80f4a6b3015ef89e3ee08dc9f47bbda6f1609f84 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Thu, 28 Sep 2017 10:04:12 -0400 Subject: [PATCH 15/25] Remove a bunch of Noop test renderer --- fixtures/reconciler/NoopRenderer.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/fixtures/reconciler/NoopRenderer.js b/fixtures/reconciler/NoopRenderer.js index 96381ae5cc518..d7477bc6fe433 100644 --- a/fixtures/reconciler/NoopRenderer.js +++ b/fixtures/reconciler/NoopRenderer.js @@ -1,12 +1,8 @@ /** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. + * Copyright (c) 2015-present, Facebook, Inc. * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. */ /** From 9d5345d707a1f0b6a17c7cffc556158571fbf395 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Thu, 28 Sep 2017 10:09:15 -0400 Subject: [PATCH 16/25] =?UTF-8?q?Delete=20a=20bunch=20of=20stuff=20we=20do?= =?UTF-8?q?n=E2=80=99t=20care=20about=20for=20reconciler=20teesting.=20Add?= =?UTF-8?q?=20flow=20pragmas=20for=20future=20flow=20pragma=20testing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fixtures/reconciler/NoopRenderer.js | 193 +--------------------------- fixtures/reconciler/index.js | 3 + 2 files changed, 6 insertions(+), 190 deletions(-) diff --git a/fixtures/reconciler/NoopRenderer.js b/fixtures/reconciler/NoopRenderer.js index d7477bc6fe433..11110a7023ba3 100644 --- a/fixtures/reconciler/NoopRenderer.js +++ b/fixtures/reconciler/NoopRenderer.js @@ -1,24 +1,14 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - /** * This is a renderer of React that doesn't have a render target output. - * It is useful to demonstrate the internals of the reconciler in isolation - * and for testing semantics of reconciliation separate from the host - * environment. + * It is used to test that the react-reconciler package doesn't blow up. + * + * @flow */ 'use strict'; -import type {Fiber} from 'ReactFiber'; -import type {UpdateQueue} from 'ReactFiberUpdateQueue'; var ReactFiberReconciler = require('react-reconciler'); var emptyObject = require('fbjs/lib/emptyObject'); - var assert = require('assert'); const UPDATE_SIGNAL = {}; @@ -37,8 +27,6 @@ type TextInstance = {|text: string, id: number|}; var instanceCounter = 0; -var failInBeginPhase = false; - function appendChild( parentInstance: Instance | Container, child: Instance | TextInstance @@ -79,9 +67,6 @@ function removeChild( var NoopRenderer = ReactFiberReconciler({ getRootHostContext() { - if (failInBeginPhase) { - throw new Error('Error in host config.'); - } return emptyObject; }, @@ -271,46 +256,10 @@ var ReactNoop = { } }, - /* We don’t have access to ReactInstanceMap from the outside - findInstance( - componentOrElement: Element | ?React$Component, - ): null | Instance | TextInstance { - if (componentOrElement == null) { - return null; - } - // Unsound duck typing. - const component = (componentOrElement: any); - if (typeof component.id === 'number') { - return component; - } - const inst = ReactInstanceMap.get(component); - return inst ? NoopRenderer.findHostInstance(inst) : null; - }, - */ - - flushDeferredPri(timeout: number = Infinity): Array { - // The legacy version of this function decremented the timeout before - // returning the new time. - // TODO: Convert tests to use flushUnitsOfWork or flushAndYield instead. - const n = timeout / 5 - 1; - - let values = []; - for (const value of flushUnitsOfWork(n)) { - values.push(...value); - } - return values; - }, - flush(): Array { return ReactNoop.flushUnitsOfWork(Infinity); }, - flushAndYield( - unitsOfWork: number = Infinity - ): Generator, void, void> { - return flushUnitsOfWork(unitsOfWork); - }, - flushUnitsOfWork(n: number): Array { let values = []; for (const value of flushUnitsOfWork(n)) { @@ -319,38 +268,6 @@ var ReactNoop = { return values; }, - flushThrough(expected: Array): void { - let actual = []; - if (expected.length !== 0) { - for (const value of flushUnitsOfWork(Infinity)) { - actual.push(...value); - if (actual.length >= expected.length) { - break; - } - } - } - assert.deepEqual( - actual, - expected, - 'Unexpected flush results.\nExpected:\n ' + - JSON.stringify(actual, null, 2) + - '\n\nActual:\n ' + - JSON.stringify(expected, null, 2) - ); - }, - - yield(value: mixed) { - if (yieldedValues === null) { - yieldedValues = [value]; - } else { - yieldedValues.push(value); - } - }, - - hasScheduledCallback() { - return !!scheduledCallback; - }, - batchedUpdates: NoopRenderer.batchedUpdates, deferredUpdates: NoopRenderer.deferredUpdates, @@ -358,110 +275,6 @@ var ReactNoop = { unbatchedUpdates: NoopRenderer.unbatchedUpdates, flushSync: NoopRenderer.flushSync, - - // Logs the current state of the tree. - dumpTree(rootID: string = DEFAULT_ROOT_ID) { - const root = roots.get(rootID); - const rootContainer = rootContainers.get(rootID); - if (!root || !rootContainer) { - console.log('Nothing rendered yet.'); - return; - } - - var bufferedLog = []; - function log(...args) { - bufferedLog.push(...args, '\n'); - } - - function logHostInstances(children: Array, depth) { - for (var i = 0; i < children.length; i++) { - var child = children[i]; - var indent = ' '.repeat(depth); - if (typeof child.text === 'string') { - log(indent + '- ' + child.text); - } else { - // $FlowFixMe - The child should've been refined now. - log(indent + '- ' + child.type + '#' + child.id); - // $FlowFixMe - The child should've been refined now. - logHostInstances(child.children, depth + 1); - } - } - } - function logContainer(container: Container, depth) { - log(' '.repeat(depth) + '- [root#' + container.rootID + ']'); - logHostInstances(container.children, depth + 1); - } - - function logUpdateQueue(updateQueue: UpdateQueue, depth) { - log(' '.repeat(depth + 1) + 'QUEUED UPDATES'); - const firstUpdate = updateQueue.first; - if (!firstUpdate) { - return; - } - - log( - ' '.repeat(depth + 1) + '~', - firstUpdate && firstUpdate.partialState, - firstUpdate.callback ? 'with callback' : '', - '[' + firstUpdate.priorityLevel + ']' - ); - var next; - while ((next = firstUpdate.next)) { - log( - ' '.repeat(depth + 1) + '~', - next.partialState, - next.callback ? 'with callback' : '', - '[' + firstUpdate.priorityLevel + ']' - ); - } - } - - function logFiber(fiber: Fiber, depth) { - log( - ' '.repeat(depth) + - '- ' + - (fiber.type ? fiber.type.name || fiber.type : '[root]'), - '[' + fiber.pendingWorkPriority + (fiber.pendingProps ? '*' : '') + ']' - ); - if (fiber.updateQueue) { - logUpdateQueue(fiber.updateQueue, depth); - } - // const childInProgress = fiber.progressedChild; - // if (childInProgress && childInProgress !== fiber.child) { - // log( - // ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority, - // ); - // logFiber(childInProgress, depth + 1); - // if (fiber.child) { - // log(' '.repeat(depth + 1) + 'CURRENT'); - // } - // } else if (fiber.child && fiber.updateQueue) { - // log(' '.repeat(depth + 1) + 'CHILDREN'); - // } - if (fiber.child) { - logFiber(fiber.child, depth + 1); - } - if (fiber.sibling) { - logFiber(fiber.sibling, depth); - } - } - - log('HOST INSTANCES:'); - logContainer(rootContainer, 0); - log('FIBERS:'); - logFiber(root.current, 0); - - console.log(...bufferedLog); - }, - - simulateErrorInHostConfig(fn: () => void) { - failInBeginPhase = true; - try { - fn(); - } finally { - failInBeginPhase = false; - } - }, }; module.exports = ReactNoop; diff --git a/fixtures/reconciler/index.js b/fixtures/reconciler/index.js index d290dac0ff62a..6be1296c3d85b 100644 --- a/fixtures/reconciler/index.js +++ b/fixtures/reconciler/index.js @@ -1,3 +1,6 @@ +/** + * @flow + */ var React = require('react'); var Noop = require('./NoopRenderer'); var assert = require('assert'); From 6de269f0cd64be1164cd0258deb20082cfe61e03 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 10 Oct 2017 19:11:36 +0100 Subject: [PATCH 17/25] Remove PATENTS --- packages/react-reconciler/package.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/react-reconciler/package.json b/packages/react-reconciler/package.json index 0019bb3748b2f..0a5ef163007eb 100644 --- a/packages/react-reconciler/package.json +++ b/packages/react-reconciler/package.json @@ -1,16 +1,15 @@ { "name": "react-reconciler", - "description": "React package for creating custom reconcilers.", + "description": "React package for creating custom renderers.", "version": "0.1.0", "keywords": [ "react" ], "homepage": "https://facebook.github.io/react/", "bugs": "https://github.com/facebook/react/issues", - "license": "BSD-3-Clause", + "license": "MIT", "files": [ "LICENSE", - "PATENTS", "README.md", "index.js", "cjs/", @@ -22,13 +21,13 @@ "node": ">=0.10.0" }, "peerDependencies": { - "react": "^16.0.0-beta.3" + "react": "^16.0.0" }, "dependencies": { - "fbjs": "^0.8.9", + "fbjs": "^0.8.16", "loose-envify": "^1.1.0", - "object-assign": "^4.1.0", - "prop-types": "^15.5.6" + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" }, "browserify": { "transform": [ From f767f0ef5df513b5baae7058e018b5f8d02ca3a0 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Wed, 11 Oct 2017 09:12:12 -0400 Subject: [PATCH 18/25] Update Reconciler fixture docs --- fixtures/reconciler/README.md | 25 ++++++++++----------- fixtures/reconciler/package.json | 2 +- fixtures/reconciler/yarn.lock | 37 ++++++++++++++++---------------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/fixtures/reconciler/README.md b/fixtures/reconciler/README.md index 6798d09f5f248..ba35e043fd8ba 100644 --- a/fixtures/reconciler/README.md +++ b/fixtures/reconciler/README.md @@ -1,16 +1,17 @@ +# React Reconciler Test Fixture -To test, run: -* `yarn install` -* edit node_modules/react-reconciler/cjs/react-reconciler.development.js -* Add the below snipper after `var valueStack = [];` -* `yarn test` +This folder exists for **React contributors** only. +If you use React you don't need to worry about it. -If everything is okay then you should see `Ok!` printed to the console. +These fixtures are a smoke-screen verification that the built React Reconciler distributions do not +share internal global state. +**They are not running automatically.** (At least not yet, feel free to contribute to automate them.) + +Run them when you make changes to how we package React or perform any release. +To test, from the project root: +* `yarn build` +* `pushd fixtures/reconciler; yarn install && yarn test; popd` + +If everything is okay then you should see `Ok!` printed to the console. -``` -if (global.valueStacks === undefined) { - global.valueStacks = []; -} -global.valueStacks.push(valueStack); -``` diff --git a/fixtures/reconciler/package.json b/fixtures/reconciler/package.json index a288c1e22963b..abe75ffad2dba 100644 --- a/fixtures/reconciler/package.json +++ b/fixtures/reconciler/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "react": "^16.0.0-", + "react": "^16.0.0", "react-reconciler": "file:../../build/packages/react-reconciler" }, "scripts": { diff --git a/fixtures/reconciler/yarn.lock b/fixtures/reconciler/yarn.lock index 64bb62652dcfa..b843eb76e551f 100644 --- a/fixtures/reconciler/yarn.lock +++ b/fixtures/reconciler/yarn.lock @@ -16,9 +16,9 @@ encoding@^0.1.11: dependencies: iconv-lite "~0.4.13" -fbjs@^0.8.9: - version "0.8.15" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.15.tgz#4f0695fdfcc16c37c0b07facec8cb4c4091685b9" +fbjs@^0.8.16: + version "0.8.16" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" dependencies: core-js "^1.0.0" isomorphic-fetch "^2.1.1" @@ -60,7 +60,7 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" -object-assign@^4.1.0: +object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -70,29 +70,30 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.6: - version "15.5.10" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" +prop-types@^15.6.0: + version "15.6.0" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" dependencies: - fbjs "^0.8.9" + fbjs "^0.8.16" loose-envify "^1.3.1" + object-assign "^4.1.1" "react-reconciler@file:../../build/packages/react-reconciler": - version "16.0.0" + version "0.1.0" dependencies: - fbjs "^0.8.9" + fbjs "^0.8.16" loose-envify "^1.1.0" - object-assign "^4.1.0" - prop-types "^15.5.6" + object-assign "^4.1.1" + prop-types "^15.6.0" -react@^16.0.0-: - version "16.0.0-rc.3" - resolved "https://registry.yarnpkg.com/react/-/react-16.0.0-rc.3.tgz#4a9df996326ba7185903d9fbed3149765e237e26" +react@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.0.0.tgz#ce7df8f1941b036f02b2cca9dbd0cb1f0e855e2d" dependencies: - fbjs "^0.8.9" + fbjs "^0.8.16" loose-envify "^1.1.0" - object-assign "^4.1.0" - prop-types "^15.5.6" + object-assign "^4.1.1" + prop-types "^15.6.0" setimmediate@^1.0.5: version "1.0.5" From d4842f075061081b0d97e65ffb5336b25277e774 Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Wed, 11 Oct 2017 09:47:33 -0400 Subject: [PATCH 19/25] ReactDOMUnstableNativeDependencies should be ISOMORPHIC --- scripts/rollup/bundles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 6ba9f3dd4aee2..ef4c229fd1862 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -157,7 +157,7 @@ const bundles = [ ], fbEntry: 'src/renderers/dom/shared/ReactDOMUnstableNativeDependenciesEntry', hasteName: 'ReactDOMUnstableNativeDependencies', - moduleType: RENDERER, + moduleType: ISOMORPHIC, label: 'dom-unstable-native-dependencies', manglePropertiesOnProd: false, name: 'react-dom/unstable-native-dependencies', From d10506bc9053750142292a4d9098f38d215bb1ee Mon Sep 17 00:00:00 2001 From: Dustan Kasten Date: Wed, 11 Oct 2017 10:10:35 -0400 Subject: [PATCH 20/25] Inline fixture renderer --- fixtures/reconciler/NoopRenderer.js | 280 ---------------------------- fixtures/reconciler/README.md | 3 +- fixtures/reconciler/index.js | 277 ++++++++++++++++++++++++++- 3 files changed, 277 insertions(+), 283 deletions(-) delete mode 100644 fixtures/reconciler/NoopRenderer.js diff --git a/fixtures/reconciler/NoopRenderer.js b/fixtures/reconciler/NoopRenderer.js deleted file mode 100644 index 11110a7023ba3..0000000000000 --- a/fixtures/reconciler/NoopRenderer.js +++ /dev/null @@ -1,280 +0,0 @@ -/** - * This is a renderer of React that doesn't have a render target output. - * It is used to test that the react-reconciler package doesn't blow up. - * - * @flow - */ - -'use strict'; - -var ReactFiberReconciler = require('react-reconciler'); -var emptyObject = require('fbjs/lib/emptyObject'); -var assert = require('assert'); - -const UPDATE_SIGNAL = {}; - -var scheduledCallback = null; - -type Container = {rootID: string, children: Array}; -type Props = {prop: any, hidden?: boolean}; -type Instance = {| - type: string, - id: number, - children: Array, - prop: any, -|}; -type TextInstance = {|text: string, id: number|}; - -var instanceCounter = 0; - -function appendChild( - parentInstance: Instance | Container, - child: Instance | TextInstance -): void { - const index = parentInstance.children.indexOf(child); - if (index !== -1) { - parentInstance.children.splice(index, 1); - } - parentInstance.children.push(child); -} - -function insertBefore( - parentInstance: Instance | Container, - child: Instance | TextInstance, - beforeChild: Instance | TextInstance -): void { - const index = parentInstance.children.indexOf(child); - if (index !== -1) { - parentInstance.children.splice(index, 1); - } - const beforeIndex = parentInstance.children.indexOf(beforeChild); - if (beforeIndex === -1) { - throw new Error('This child does not exist.'); - } - parentInstance.children.splice(beforeIndex, 0, child); -} - -function removeChild( - parentInstance: Instance | Container, - child: Instance | TextInstance -): void { - const index = parentInstance.children.indexOf(child); - if (index === -1) { - throw new Error('This child does not exist.'); - } - parentInstance.children.splice(index, 1); -} - -var NoopRenderer = ReactFiberReconciler({ - getRootHostContext() { - return emptyObject; - }, - - getChildHostContext() { - return emptyObject; - }, - - getPublicInstance(instance) { - return instance; - }, - - createInstance(type: string, props: Props): Instance { - const inst = { - id: instanceCounter++, - type: type, - children: [], - prop: props.prop, - }; - // Hide from unit tests - Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false}); - return inst; - }, - - appendInitialChild( - parentInstance: Instance, - child: Instance | TextInstance - ): void { - parentInstance.children.push(child); - }, - - finalizeInitialChildren( - domElement: Instance, - type: string, - props: Props - ): boolean { - return false; - }, - - prepareUpdate( - instance: Instance, - type: string, - oldProps: Props, - newProps: Props - ): null | {} { - return UPDATE_SIGNAL; - }, - - commitMount(instance: Instance, type: string, newProps: Props): void { - // Noop - }, - - commitUpdate( - instance: Instance, - updatePayload: Object, - type: string, - oldProps: Props, - newProps: Props - ): void { - instance.prop = newProps.prop; - }, - - shouldSetTextContent(type: string, props: Props): boolean { - return ( - typeof props.children === 'string' || typeof props.children === 'number' - ); - }, - - resetTextContent(instance: Instance): void {}, - - shouldDeprioritizeSubtree(type: string, props: Props): boolean { - return !!props.hidden; - }, - - createTextInstance( - text: string, - rootContainerInstance: Container, - hostContext: Object, - internalInstanceHandle: Object - ): TextInstance { - var inst = {text: text, id: instanceCounter++}; - // Hide from unit tests - Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false}); - return inst; - }, - - commitTextUpdate( - textInstance: TextInstance, - oldText: string, - newText: string - ): void { - textInstance.text = newText; - }, - - appendChild: appendChild, - appendChildToContainer: appendChild, - insertBefore: insertBefore, - insertInContainerBefore: insertBefore, - removeChild: removeChild, - removeChildFromContainer: removeChild, - - scheduleDeferredCallback(callback) { - if (scheduledCallback) { - throw new Error( - 'Scheduling a callback twice is excessive. Instead, keep track of ' + - 'whether the callback has already been scheduled.' - ); - } - scheduledCallback = callback; - }, - - prepareForCommit(): void {}, - - resetAfterCommit(): void {}, -}); - -var rootContainers = new Map(); -var roots = new Map(); -var DEFAULT_ROOT_ID = ''; - -let yieldedValues = null; - -function* flushUnitsOfWork(n: number): Generator, void, void> { - var didStop = false; - while (!didStop && scheduledCallback !== null) { - var cb = scheduledCallback; - scheduledCallback = null; - yieldedValues = null; - var unitsRemaining = n; - cb({ - timeRemaining() { - if (yieldedValues !== null) { - return 0; - } - if (unitsRemaining-- > 0) { - return 999; - } - didStop = true; - return 0; - }, - }); - - if (yieldedValues !== null) { - const values = yieldedValues; - yieldedValues = null; - yield values; - } - } -} - -var ReactNoop = { - getChildren(rootID: string = DEFAULT_ROOT_ID) { - const container = rootContainers.get(rootID); - if (container) { - return container.children; - } else { - return null; - } - }, - - // Shortcut for testing a single root - render(element: React$Element, callback: ?Function) { - ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback); - }, - - renderToRootWithID( - element: React$Element, - rootID: string, - callback: ?Function - ) { - let root = roots.get(rootID); - if (!root) { - const container = {rootID: rootID, children: []}; - rootContainers.set(rootID, container); - root = NoopRenderer.createContainer(container); - roots.set(rootID, root); - } - NoopRenderer.updateContainer(element, root, null, callback); - }, - - unmountRootWithID(rootID: string) { - const root = roots.get(rootID); - if (root) { - NoopRenderer.updateContainer(null, root, null, () => { - roots.delete(rootID); - rootContainers.delete(rootID); - }); - } - }, - - flush(): Array { - return ReactNoop.flushUnitsOfWork(Infinity); - }, - - flushUnitsOfWork(n: number): Array { - let values = []; - for (const value of flushUnitsOfWork(n)) { - values.push(...value); - } - return values; - }, - - batchedUpdates: NoopRenderer.batchedUpdates, - - deferredUpdates: NoopRenderer.deferredUpdates, - - unbatchedUpdates: NoopRenderer.unbatchedUpdates, - - flushSync: NoopRenderer.flushSync, -}; - -module.exports = ReactNoop; diff --git a/fixtures/reconciler/README.md b/fixtures/reconciler/README.md index ba35e043fd8ba..1b00e827094d5 100644 --- a/fixtures/reconciler/README.md +++ b/fixtures/reconciler/README.md @@ -3,8 +3,7 @@ This folder exists for **React contributors** only. If you use React you don't need to worry about it. -These fixtures are a smoke-screen verification that the built React Reconciler distributions do not -share internal global state. +These fixtures are a smoke-screen verification that the built React Reconciler distribution works. **They are not running automatically.** (At least not yet, feel free to contribute to automate them.) Run them when you make changes to how we package React or perform any release. diff --git a/fixtures/reconciler/index.js b/fixtures/reconciler/index.js index 6be1296c3d85b..248ff0e073d52 100644 --- a/fixtures/reconciler/index.js +++ b/fixtures/reconciler/index.js @@ -1,9 +1,284 @@ /** + * This is a renderer of React that doesn't have a render target output. + * It is used to test that the react-reconciler package doesn't blow up. + * * @flow */ +'use strict'; + var React = require('react'); -var Noop = require('./NoopRenderer'); var assert = require('assert'); +var ReactFiberReconciler = require('react-reconciler'); +var emptyObject = require('fbjs/lib/emptyObject'); +var assert = require('assert'); + +const UPDATE_SIGNAL = {}; + +var scheduledCallback = null; + +type Container = {rootID: string, children: Array}; +type Props = {prop: any, hidden?: boolean}; +type Instance = {| + type: string, + id: number, + children: Array, + prop: any, +|}; +type TextInstance = {|text: string, id: number|}; + +var instanceCounter = 0; + +function appendChild( + parentInstance: Instance | Container, + child: Instance | TextInstance +): void { + const index = parentInstance.children.indexOf(child); + if (index !== -1) { + parentInstance.children.splice(index, 1); + } + parentInstance.children.push(child); +} + +function insertBefore( + parentInstance: Instance | Container, + child: Instance | TextInstance, + beforeChild: Instance | TextInstance +): void { + const index = parentInstance.children.indexOf(child); + if (index !== -1) { + parentInstance.children.splice(index, 1); + } + const beforeIndex = parentInstance.children.indexOf(beforeChild); + if (beforeIndex === -1) { + throw new Error('This child does not exist.'); + } + parentInstance.children.splice(beforeIndex, 0, child); +} + +function removeChild( + parentInstance: Instance | Container, + child: Instance | TextInstance +): void { + const index = parentInstance.children.indexOf(child); + if (index === -1) { + throw new Error('This child does not exist.'); + } + parentInstance.children.splice(index, 1); +} + +var NoopRenderer = ReactFiberReconciler({ + getRootHostContext() { + return emptyObject; + }, + + getChildHostContext() { + return emptyObject; + }, + + getPublicInstance(instance) { + return instance; + }, + + createInstance(type: string, props: Props): Instance { + const inst = { + id: instanceCounter++, + type: type, + children: [], + prop: props.prop, + }; + // Hide from unit tests + Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false}); + return inst; + }, + + appendInitialChild( + parentInstance: Instance, + child: Instance | TextInstance + ): void { + parentInstance.children.push(child); + }, + + finalizeInitialChildren( + domElement: Instance, + type: string, + props: Props + ): boolean { + return false; + }, + + prepareUpdate( + instance: Instance, + type: string, + oldProps: Props, + newProps: Props + ): null | {} { + return UPDATE_SIGNAL; + }, + + commitMount(instance: Instance, type: string, newProps: Props): void { + // Noop + }, + + commitUpdate( + instance: Instance, + updatePayload: Object, + type: string, + oldProps: Props, + newProps: Props + ): void { + instance.prop = newProps.prop; + }, + + shouldSetTextContent(type: string, props: Props): boolean { + return ( + typeof props.children === 'string' || typeof props.children === 'number' + ); + }, + + resetTextContent(instance: Instance): void {}, + + shouldDeprioritizeSubtree(type: string, props: Props): boolean { + return !!props.hidden; + }, + + now: Date.now, + + createTextInstance( + text: string, + rootContainerInstance: Container, + hostContext: Object, + internalInstanceHandle: Object + ): TextInstance { + var inst = {text: text, id: instanceCounter++}; + // Hide from unit tests + Object.defineProperty(inst, 'id', {value: inst.id, enumerable: false}); + return inst; + }, + + commitTextUpdate( + textInstance: TextInstance, + oldText: string, + newText: string + ): void { + textInstance.text = newText; + }, + + appendChild: appendChild, + appendChildToContainer: appendChild, + insertBefore: insertBefore, + insertInContainerBefore: insertBefore, + removeChild: removeChild, + removeChildFromContainer: removeChild, + + scheduleDeferredCallback(callback) { + if (scheduledCallback) { + throw new Error( + 'Scheduling a callback twice is excessive. Instead, keep track of ' + + 'whether the callback has already been scheduled.' + ); + } + scheduledCallback = callback; + }, + + prepareForCommit(): void {}, + + resetAfterCommit(): void {}, +}); + +var rootContainers = new Map(); +var roots = new Map(); +var DEFAULT_ROOT_ID = ''; + +let yieldedValues = null; + +function* flushUnitsOfWork(n: number): Generator, void, void> { + var didStop = false; + while (!didStop && scheduledCallback !== null) { + var cb = scheduledCallback; + scheduledCallback = null; + yieldedValues = null; + var unitsRemaining = n; + cb({ + timeRemaining() { + if (yieldedValues !== null) { + return 0; + } + if (unitsRemaining-- > 0) { + return 999; + } + didStop = true; + return 0; + }, + }); + + if (yieldedValues !== null) { + const values = yieldedValues; + yieldedValues = null; + yield values; + } + } +} + +var Noop = { + getChildren(rootID: string = DEFAULT_ROOT_ID) { + const container = rootContainers.get(rootID); + if (container) { + return container.children; + } else { + return null; + } + }, + + // Shortcut for testing a single root + render(element: React$Element, callback: ?Function) { + Noop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback); + }, + + renderToRootWithID( + element: React$Element, + rootID: string, + callback: ?Function + ) { + let root = roots.get(rootID); + if (!root) { + const container = {rootID: rootID, children: []}; + rootContainers.set(rootID, container); + root = NoopRenderer.createContainer(container); + roots.set(rootID, root); + } + NoopRenderer.updateContainer(element, root, null, callback); + }, + + unmountRootWithID(rootID: string) { + const root = roots.get(rootID); + if (root) { + NoopRenderer.updateContainer(null, root, null, () => { + roots.delete(rootID); + rootContainers.delete(rootID); + }); + } + }, + + flush(): Array { + return Noop.flushUnitsOfWork(Infinity); + }, + + flushUnitsOfWork(n: number): Array { + let values = []; + for (const value of flushUnitsOfWork(n)) { + values.push(...value); + } + return values; + }, + + batchedUpdates: NoopRenderer.batchedUpdates, + + deferredUpdates: NoopRenderer.deferredUpdates, + + unbatchedUpdates: NoopRenderer.unbatchedUpdates, + + flushSync: NoopRenderer.flushSync, +}; class Comp extends React.Component { render() { From ec188633a0dc9cf9b87e871301fe479fd6e12997 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 11 Oct 2017 16:30:37 +0100 Subject: [PATCH 21/25] Make it "RENDERER" --- scripts/rollup/bundles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index ef4c229fd1862..6ba9f3dd4aee2 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -157,7 +157,7 @@ const bundles = [ ], fbEntry: 'src/renderers/dom/shared/ReactDOMUnstableNativeDependenciesEntry', hasteName: 'ReactDOMUnstableNativeDependencies', - moduleType: ISOMORPHIC, + moduleType: RENDERER, label: 'dom-unstable-native-dependencies', manglePropertiesOnProd: false, name: 'react-dom/unstable-native-dependencies', From 5c6510d9767238c928b3c3d3d427b1c061de8d0f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 11 Oct 2017 17:16:11 +0100 Subject: [PATCH 22/25] There is no UMD build. It also doesn't need propTypes. --- packages/react-reconciler/package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/react-reconciler/package.json b/packages/react-reconciler/package.json index 0a5ef163007eb..1432bb2ee6871 100644 --- a/packages/react-reconciler/package.json +++ b/packages/react-reconciler/package.json @@ -12,8 +12,7 @@ "LICENSE", "README.md", "index.js", - "cjs/", - "umd/" + "cjs/" ], "main": "index.js", "repository": "facebook/react", @@ -26,8 +25,7 @@ "dependencies": { "fbjs": "^0.8.16", "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.0" + "object-assign": "^4.1.1" }, "browserify": { "transform": [ From 8a3ffdce69906b587a8272924d1b3dee56de33f9 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 11 Oct 2017 18:49:16 +0100 Subject: [PATCH 23/25] Tweak how the reconciler is built --- fixtures/reconciler/README.md | 9 +-- fixtures/reconciler/index.js | 82 +++++++++++++++++------ fixtures/reconciler/package.json | 9 +-- fixtures/reconciler/yarn.lock | 108 ------------------------------- scripts/circleci/build.sh | 6 +- scripts/rollup/build.js | 64 ++++++++++++++---- 6 files changed, 123 insertions(+), 155 deletions(-) delete mode 100644 fixtures/reconciler/yarn.lock diff --git a/fixtures/reconciler/README.md b/fixtures/reconciler/README.md index 1b00e827094d5..0fbec76424ac0 100644 --- a/fixtures/reconciler/README.md +++ b/fixtures/reconciler/README.md @@ -4,13 +4,10 @@ This folder exists for **React contributors** only. If you use React you don't need to worry about it. These fixtures are a smoke-screen verification that the built React Reconciler distribution works. -**They are not running automatically.** (At least not yet, feel free to contribute to automate them.) - -Run them when you make changes to how we package React or perform any release. To test, from the project root: * `yarn build` -* `pushd fixtures/reconciler; yarn install && yarn test; popd` - -If everything is okay then you should see `Ok!` printed to the console. +* `cd fixtures/reconciler` +* `yarn test` +They should run as part of the CI check. diff --git a/fixtures/reconciler/index.js b/fixtures/reconciler/index.js index 248ff0e073d52..b1780069929d8 100644 --- a/fixtures/reconciler/index.js +++ b/fixtures/reconciler/index.js @@ -249,16 +249,6 @@ var Noop = { NoopRenderer.updateContainer(element, root, null, callback); }, - unmountRootWithID(rootID: string) { - const root = roots.get(rootID); - if (root) { - NoopRenderer.updateContainer(null, root, null, () => { - roots.delete(rootID); - rootContainers.delete(rootID); - }); - } - }, - flush(): Array { return Noop.flushUnitsOfWork(Infinity); }, @@ -280,9 +270,23 @@ var Noop = { flushSync: NoopRenderer.flushSync, }; -class Comp extends React.Component { +type TestProps = {| + active: boolean, +|}; +type TestState = {| + counter: number, +|}; + +let instance = null; +class Test extends React.Component { + state = {counter: 0}; + increment() { + this.setState(({counter}) => ({ + counter: counter + 1, + })); + } render() { - return this.props.active ? 'Active Comp' : 'Deactive Comp'; + return [this.props.active ? 'Active' : 'Inactive', this.state.counter]; } } const Children = props => props.children; @@ -292,13 +296,46 @@ Noop.render( Hello world {'Number '}{42} - + (instance = t)} /> ); Noop.flush(); -const actual = Noop.getChildren(); -const expected = [ +const actual1 = Noop.getChildren(); +const expected1 = [ + { + type: 'main', + children: [ + {type: 'div', children: [], prop: undefined}, + {text: 'Hello world'}, + { + type: 'span', + children: [{text: 'Number '}, {text: '42'}], + prop: undefined, + }, + {text: 'Active'}, + {text: '0'}, + ], + prop: undefined, + }, +]; +assert.deepEqual( + actual1, + expected1, + 'Error. Noop.getChildren() returned unexpected value.\nExpected:\ ' + + JSON.stringify(expected1, null, 2) + + '\n\nActual:\n ' + + JSON.stringify(actual1, null, 2) +); + +if (instance === null) { + throw new Error('Expected instance to exist.'); +} + +instance.increment(); +Noop.flush(); +const actual2 = Noop.getChildren(); +const expected2 = [ { type: 'main', children: [ @@ -309,18 +346,21 @@ const expected = [ children: [{text: 'Number '}, {text: '42'}], prop: undefined, }, - {text: 'Active Comp'}, + {text: 'Active'}, + {text: '1'}, ], prop: undefined, }, ]; assert.deepEqual( - actual, - expected, + actual2, + expected2, 'Error. Noop.getChildren() returned unexpected value.\nExpected:\ ' + - JSON.stringify(expected, null, 2) + + JSON.stringify(expected2, null, 2) + '\n\nActual:\n ' + - JSON.stringify(actual, null, 2) + JSON.stringify(actual2, null, 2) ); -console.log('Reconciler package is Ok!'); +const beginGreen = '\u001b[32m'; +const endGreen = '\u001b[39m'; +console.log(beginGreen + 'Reconciler package is OK!' + endGreen); diff --git a/fixtures/reconciler/package.json b/fixtures/reconciler/package.json index abe75ffad2dba..d036f41ecc7dc 100644 --- a/fixtures/reconciler/package.json +++ b/fixtures/reconciler/package.json @@ -2,12 +2,9 @@ "name": "react-fixtures", "version": "0.1.0", "private": true, - "dependencies": { - "react": "^16.0.0", - "react-reconciler": "file:../../build/packages/react-reconciler" - }, "scripts": { - "start": "../../node_modules/.bin/babel-node ./index", - "test": "../../node_modules/.bin/babel-node ./index" + "test:dev": "NODE_PATH=../../build/packages ../../node_modules/.bin/babel-node ./index", + "test:prod": "NODE_PATH=../../build/packages NODE_ENV=production ../../node_modules/.bin/babel-node ./index", + "test": "npm run test:dev && npm run test:prod" } } diff --git a/fixtures/reconciler/yarn.lock b/fixtures/reconciler/yarn.lock deleted file mode 100644 index b843eb76e551f..0000000000000 --- a/fixtures/reconciler/yarn.lock +++ /dev/null @@ -1,108 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - -encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - dependencies: - iconv-lite "~0.4.13" - -fbjs@^0.8.16: - version "0.8.16" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" - dependencies: - core-js "^1.0.0" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.9" - -iconv-lite@~0.4.13: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" - -is-stream@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - -js-tokens@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" - dependencies: - js-tokens "^3.0.0" - -node-fetch@^1.0.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - -object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - dependencies: - asap "~2.0.3" - -prop-types@^15.6.0: - version "15.6.0" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.3.1" - object-assign "^4.1.1" - -"react-reconciler@file:../../build/packages/react-reconciler": - version "0.1.0" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.0" - -react@^16.0.0: - version "16.0.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.0.0.tgz#ce7df8f1941b036f02b2cca9dbd0cb1f0e855e2d" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.0" - -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - -ua-parser-js@^0.7.9: - version "0.7.14" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.14.tgz#110d53fa4c3f326c121292bbeac904d2e03387ca" - -whatwg-fetch@>=0.10.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" diff --git a/scripts/circleci/build.sh b/scripts/circleci/build.sh index 49bb2128657cf..4f0c1521677c4 100755 --- a/scripts/circleci/build.sh +++ b/scripts/circleci/build.sh @@ -3,4 +3,8 @@ set -e npm run build -- --extract-errors -git checkout -- scripts/error-codes/codes.json \ No newline at end of file +git checkout -- scripts/error-codes/codes.json + +# Check that the standalone reconciler isn't borked +cd fixtures/reconciler +yarn test diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index d9c3e0db57f68..eb9cbe346d0d9 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -81,6 +81,12 @@ function getHeaderSanityCheck(bundleType, hasteName) { } function getBanner(bundleType, hasteName, filename, moduleType) { + if (moduleType === RECONCILER) { + // Standalone reconciler is only used by third-party renderers. + // It is handled separately. + return getReconcilerBanner(bundleType, filename); + } + switch (bundleType) { // UMDs are not wrapped in conditions. case UMD_DEV: @@ -91,17 +97,10 @@ function getBanner(bundleType, hasteName, filename, moduleType) { let banner = Header.getHeader(filename, reactVersion); // Wrap the contents of the if-DEV check with an IIFE. // Block-level function definitions can cause problems for strict mode. - banner += moduleType === RECONCILER - ? `'use strict';\n\n\nif (process.env.NODE_ENV !== "production") {\nmodule.exports = function(config) {\n` - : `'use strict';\n\n\nif (process.env.NODE_ENV !== "production") {\n(function() {\n`; + banner += `'use strict';\n\n\nif (process.env.NODE_ENV !== "production") {\n(function() {\n`; return banner; case NODE_PROD: - return ( - Header.getHeader(filename, reactVersion) + - (moduleType === RECONCILER - ? `\n\n'use strict';\n\n\nmodule.exports = function(config) {\n` - : '') - ); + return Header.getHeader(filename, reactVersion); // All FB and RN bundles need Haste headers. // DEV bundle is guarded to help weak dead code elimination. case FB_DEV: @@ -123,11 +122,11 @@ function getBanner(bundleType, hasteName, filename, moduleType) { function getFooter(bundleType, filename, moduleType) { if (moduleType === RECONCILER) { - return ( - '\nreturn ReactFiberReconciler(config);\n};\n' + - (bundleType === NODE_DEV ? '}\n' : '') - ); + // Standalone reconciler is only used by third-party renderers. + // It is handled separately. + return getReconcilerFooter(bundleType); } + // Only need a footer if getBanner() has an opening brace. switch (bundleType) { // Non-UMD DEV bundles need conditions to help weak dead code elimination. @@ -140,6 +139,45 @@ function getFooter(bundleType, filename, moduleType) { } } +// TODO: this is extremely gross. +// But it only affects the "experimental" standalone reconciler build. +// The goal is to avoid having any shared state between renderers sharing it on npm. +// Ideally we should just remove shared state in all Fiber modules and then lint against it. +// But for now, we store the exported function in a variable, and then put the rest of the code +// into a closure that makes all module-level state private to each call. +const RECONCILER_WRAPPER_INTRO = `var $$$reconciler;\nmodule.exports = function(config) {\n`; +const RECONCILER_WRAPPER_OUTRO = `return ($$$reconciler || ($$$reconciler = module.exports))(config);\n};\n`; + +function getReconcilerBanner(bundleType, filename) { + let banner = `${Header.getHeader(filename, reactVersion)}\n\n'use strict';\n\n\n`; + switch (bundleType) { + case NODE_DEV: + banner += `if (process.env.NODE_ENV !== "production") {\n${RECONCILER_WRAPPER_INTRO}`; + break; + case NODE_PROD: + banner += RECONCILER_WRAPPER_INTRO; + break; + default: + throw new Error( + 'Standalone reconciler does not support ' + bundleType + ' builds.' + ); + } + return banner; +} + +function getReconcilerFooter(bundleType) { + switch (bundleType) { + case NODE_DEV: + return `\n${RECONCILER_WRAPPER_OUTRO}\n}`; + case NODE_PROD: + return `\n${RECONCILER_WRAPPER_OUTRO}`; + default: + throw new Error( + 'Standalone reconciler does not support ' + bundleType + ' builds.' + ); + } +} + function updateBabelConfig(babelOpts, bundleType, filename) { switch (bundleType) { case FB_DEV: From a935605170aa6a8185666238745dc4a49a676821 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 11 Oct 2017 19:09:49 +0100 Subject: [PATCH 24/25] Record sizes --- scripts/rollup/results.json | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index e0808eef81bdc..1e345fb8c752f 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -57,12 +57,12 @@ "gzip": 11028 }, "react-dom-unstable-native-dependencies.development.js (UMD_DEV)": { - "size": 86854, - "gzip": 21985 + "size": 84930, + "gzip": 21288 }, "react-dom-unstable-native-dependencies.production.min.js (UMD_PROD)": { - "size": 15251, - "gzip": 5010 + "size": 14441, + "gzip": 4650 }, "react-dom-unstable-native-dependencies.development.js (NODE_DEV)": { "size": 80723, @@ -207,6 +207,14 @@ "react-dom-test-utils.production.min.js (NODE_PROD)": { "size": 11608, "gzip": 4241 + }, + "react-reconciler.development.js (NODE_DEV)": { + "size": 279927, + "gzip": 58576 + }, + "react-reconciler.production.min.js (NODE_PROD)": { + "size": 38630, + "gzip": 12165 } } } \ No newline at end of file From 04ec04ebcc69a25fb87039b8fd599b82de5dfd45 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 11 Oct 2017 19:11:02 +0100 Subject: [PATCH 25/25] Update README.md --- packages/react-reconciler/README.md | 46 ++++++++++++++++------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/react-reconciler/README.md b/packages/react-reconciler/README.md index 3d4ce8d9f3770..f108413a9e911 100644 --- a/packages/react-reconciler/README.md +++ b/packages/react-reconciler/README.md @@ -1,33 +1,39 @@ # react-reconciler -An npm package to let you create custom reconcilers. +This is an experimental package for creating custom React renderers. -## Example Usage +**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.** + +**Use it at your own risk.** + +## API ```js var Reconciler = require('react-reconciler'); -var ReconcilerConfig = { /* ... */ }; -var Renderer = Reconciler(ReconcilerConfig); +var ReconcilerConfig = { + // You'll need to implement some methods here. + // See below for more information and examples. +}; + +var MyRenderer = Reconciler(ReconcilerConfig); + var RendererPublicAPI = { - render( - element: React$Element, - container: DOMContainer, - callback: ?Function, - ) { - let root = container._reactRootContainer; - if (!root) { - const newRoot = Renderer.createContainer(container); - root = container._reactRootContainer = newRoot; - // Initial mount should not be batched. - Renderer.unbatchedUpdates(() => { - Renderer.updateContainer(element, newRoot, null, callback); - }); - } else { - Renderer.updateContainer(element, root, null, callback); - } + render(element, container, callback) { + // Call MyRenderer.updateContainer() to schedule changes on the roots. + // See ReactDOM, React Native, or React ART for practical examples. } }; module.exports = RendererPublicAPI; ``` + +## Practical Examples + +* [React ART](https://github.com/facebook/react/blob/master/src/renderers/art/ReactARTFiberEntry.js) +* [React DOM](https://github.com/facebook/react/blob/master/src/fb/ReactDOMFiberFBEntry.js) +* [React Native](https://github.com/facebook/react/blob/master/src/renderers/native/ReactNativeFiberRenderer.js) + +If these links break please file an issue and we’ll fix them. They intentionally link to the latest versions since the API is still evolving. + +This [third-party tutorial](https://github.com/nitin42/Making-a-custom-React-renderer) is relatively up-to-date and may be helpful.