diff --git a/Gruntfile.js b/Gruntfile.js index e2f2faa59c296..369c6a7ef110e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -11,7 +11,7 @@ module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), - browserify: require('./grunt/config/browserify'), + rollup: require('./grunt/config/rollup'), npm: require('./grunt/config/npm'), clean: [ './build', @@ -69,8 +69,8 @@ module.exports = function(grunt) { spawnGulp(['react:clean'], null, this.async()); }); - // Our own browserify-based tasks to build a single JS file build. - grunt.registerMultiTask('browserify', require('./grunt/tasks/browserify')); + // Our own Rollup-based tasks to build a single JS file build. + grunt.registerMultiTask('rollup', require('./grunt/tasks/rollup')); grunt.registerMultiTask('npm', require('./grunt/tasks/npm')); @@ -98,20 +98,20 @@ module.exports = function(grunt) { grunt.registerTask('build:basic', [ 'build-modules', 'version-check', - 'browserify:basic', + 'rollup:basic', ]); grunt.registerTask('build:addons', [ 'build-modules', - 'browserify:addons', + 'rollup:addons', ]); grunt.registerTask('build:min', [ 'build-modules', 'version-check', - 'browserify:min', + 'rollup:min', ]); grunt.registerTask('build:addons-min', [ 'build-modules', - 'browserify:addonsMin', + 'rollup:addonsMin', ]); grunt.registerTask('build:npm-react', [ 'version-check', @@ -133,10 +133,10 @@ module.exports = function(grunt) { 'delete-build-modules', 'build-modules', 'version-check', - 'browserify:basic', - 'browserify:addons', - 'browserify:min', - 'browserify:addonsMin', + 'rollup:basic', + 'rollup:addons', + 'rollup:min', + 'rollup:addonsMin', 'build:react-dom', 'npm-react:release', 'npm-react:pack', diff --git a/grunt/config/browserify.js b/grunt/config/browserify.js deleted file mode 100644 index bfcb01ddda4e6..0000000000000 --- a/grunt/config/browserify.js +++ /dev/null @@ -1,117 +0,0 @@ -/*eslint-disable no-multi-str */ - -'use strict'; - -var envify = require('loose-envify/custom'); -var grunt = require('grunt'); -var UglifyJS = require('uglify-js'); -var uglifyify = require('uglifyify'); -var derequire = require('derequire'); -var collapser = require('bundle-collapser/plugin'); - -var envifyDev = envify({NODE_ENV: process.env.NODE_ENV || 'development'}); -var envifyProd = envify({NODE_ENV: process.env.NODE_ENV || 'production'}); - -var SIMPLE_TEMPLATE = - grunt.file.read('./grunt/data/header-template-short.txt'); - -var LICENSE_TEMPLATE = - grunt.file.read('./grunt/data/header-template-extended.txt'); - -function minify(src) { - return UglifyJS.minify(src, {fromString: true}).code; -} - -// TODO: move this out to another build step maybe. -function bannerify(src) { - var version = grunt.config.data.pkg.version; - var packageName = this.data.packageName || this.data.standalone; - return ( - grunt.template.process( - LICENSE_TEMPLATE, - {data: {package: packageName, version: version}} - ) + - src - ); -} - -function simpleBannerify(src) { - var version = grunt.config.data.pkg.version; - var packageName = this.data.packageName || this.data.standalone; - return ( - grunt.template.process( - SIMPLE_TEMPLATE, - {data: {package: packageName, version: version}} - ) + - src - ); -} - -// Our basic config which we'll add to to make our other builds -var basic = { - entries: [ - './build/modules/ReactUMDEntry.js', - ], - outfile: './build/react.js', - debug: false, - standalone: 'React', - // Apply as global transform so that we also envify fbjs and any other deps - globalTransforms: [envifyDev], - plugins: [collapser], - after: [derequire, simpleBannerify], -}; - -var min = { - entries: [ - './build/modules/ReactUMDEntry.js', - ], - outfile: './build/react.min.js', - debug: false, - standalone: 'React', - // Envify twice. The first ensures that when we uglifyify, we have the right - // conditions to exclude requires. The global transform runs on deps. - transforms: [envifyProd, uglifyify], - globalTransforms: [envifyProd], - plugins: [collapser], - // No need to derequire because the minifier will mangle - // the "require" calls. - - after: [minify, bannerify], -}; - -var addons = { - entries: [ - './build/modules/ReactWithAddonsUMDEntry.js', - ], - outfile: './build/react-with-addons.js', - debug: false, - standalone: 'React', - packageName: 'React (with addons)', - globalTransforms: [envifyDev], - plugins: [collapser], - after: [derequire, simpleBannerify], -}; - -var addonsMin = { - entries: [ - './build/modules/ReactWithAddonsUMDEntry.js', - ], - outfile: './build/react-with-addons.min.js', - debug: false, - standalone: 'React', - packageName: 'React (with addons)', - transforms: [envifyProd, uglifyify], - globalTransforms: [envifyProd], - plugins: [collapser], - // No need to derequire because the minifier will mangle - // the "require" calls. - - after: [minify, bannerify], -}; - -module.exports = { - basic: basic, - min: min, - addons: addons, - addonsMin: addonsMin, -}; diff --git a/grunt/config/rollup.js b/grunt/config/rollup.js new file mode 100644 index 0000000000000..3438fc778ceca --- /dev/null +++ b/grunt/config/rollup.js @@ -0,0 +1,88 @@ +/** + * Copyright (c) 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. + */ +'use strict'; + +var grunt = require('grunt'); +var rollupBabel = require('rollup-plugin-babel'); +var rollupReplace = require('rollup-plugin-replace'); +var rollupResolve = require('rollup-plugin-node-resolve'); +var UglifyJS = require('uglify-js'); + +var babelEs6ModulifyTransform = require('../../scripts/babel/transform-es6-modulify'); + +var LICENSE_TEMPLATE = + grunt.file.read('./grunt/data/header-template.txt'); + +function minify(src) { + return UglifyJS.minify(src, {fromString: true}).code; +} + +function bannerify(src) { + var version = grunt.config.data.pkg.version; + var packageName = this.data.packageName || this.data.moduleName; + return ( + grunt.template.process( + LICENSE_TEMPLATE, + {data: {package: packageName, version: version}} + ) + + src + ); +} + +function getPlugins(replaceConfig) { + return [ + rollupBabel({ + babelrc: false, + plugins: [babelEs6ModulifyTransform], + }), + rollupReplace(replaceConfig), + rollupResolve({ + main: true, + }), + ]; +} + +var buildConfigs = { + basic: { + after: [bannerify], + dest: 'build/react.js', + entry: 'build/modules/ReactUMDEntry.js', + format: 'umd', + moduleName: 'React', + plugins: getPlugins({'process.env.NODE_ENV': JSON.stringify('development')}), + }, + min: { + after: [minify, bannerify], + dest: 'build/react.min.js', + entry: 'build/modules/ReactUMDEntry.js', + format: 'umd', + moduleName: 'React', + plugins: getPlugins({'process.env.NODE_ENV': JSON.stringify('production')}), + }, + addons: { + after: [bannerify], + dest: 'build/react-with-addons.js', + entry: 'build/modules/ReactWithAddonsUMDEntry.js', + format: 'umd', + moduleName: 'React', + packageName: 'React (with addons)', + plugins: getPlugins({'process.env.NODE_ENV': JSON.stringify('development')}), + }, + addonsMin: { + after: [minify, bannerify], + dest: 'build/react-with-addons.min.js', + entry: 'build/modules/ReactWithAddonsUMDEntry.js', + format: 'umd', + moduleName: 'React', + packageName: 'React (with addons)', + plugins: getPlugins({'process.env.NODE_ENV': JSON.stringify('production')}), + }, +}; + +module.exports = buildConfigs; diff --git a/grunt/data/header-template-short.txt b/grunt/data/header-template-short.txt deleted file mode 100644 index ec08497d13a90..0000000000000 --- a/grunt/data/header-template-short.txt +++ /dev/null @@ -1,3 +0,0 @@ - /** - * <%= package %> v<%= version %> - */ diff --git a/grunt/data/header-template-extended.txt b/grunt/data/header-template.txt similarity index 100% rename from grunt/data/header-template-extended.txt rename to grunt/data/header-template.txt diff --git a/grunt/tasks/react-dom.js b/grunt/tasks/react-dom.js index 77a68d0656ace..999e629a5e9f6 100644 --- a/grunt/tasks/react-dom.js +++ b/grunt/tasks/react-dom.js @@ -4,7 +4,7 @@ var grunt = require('grunt'); var UglifyJS = require('uglify-js'); var LICENSE_TEMPLATE = - grunt.file.read('./grunt/data/header-template-extended.txt'); + grunt.file.read('./grunt/data/header-template.txt'); function build(name, filename) { var srcFile = `vendor/${filename}.js`; diff --git a/grunt/tasks/rollup.js b/grunt/tasks/rollup.js new file mode 100644 index 0000000000000..afec14833338d --- /dev/null +++ b/grunt/tasks/rollup.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) 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. + */ +'use strict'; + +var grunt = require('grunt'); +var rollup = require('rollup'); + +module.exports = function() { + /* + config fields: + { + after: Array<(code: string) => string>, + dest: string, + entry: string, + format: string, + moduleName: string, + packageName?: string, + plugins: Array, + } + */ + + var config = this.data; + config.after = config.after || []; + + // This task is async... + var done = this.async(); + + var ctx = this; + + rollup.rollup({ + entry: config.entry, + plugins: config.plugins, + }).then(function(bundle) { + var code = bundle.generate({ + exports: 'default', + dest: config.dest, + format: config.format, + moduleName: config.moduleName, + sourceMap: false, + useStrict: 'true', + }).code; + + code = config.after.reduce(function(src, postProcessor) { + // minify and bannerify + return postProcessor.call(ctx, src); + }, code); + + grunt.file.write(config.dest, code); + done(); + }).catch(function(err) { + grunt.log.error(err); + done(); + }); +}; diff --git a/package.json b/package.json index 85bc4a1101a54..9ba6a54ccc2a4 100644 --- a/package.json +++ b/package.json @@ -32,13 +32,10 @@ "babel-preset-react": "^6.5.0", "babel-traverse": "^6.9.0", "babylon": "6.8.0", - "browserify": "^13.0.0", - "bundle-collapser": "^1.1.1", "coffee-script": "^1.8.0", "core-js": "^2.2.1", "coveralls": "^2.11.6", "del": "^2.0.2", - "derequire": "^2.0.3", "eslint": "1.10.3", "eslint-plugin-react": "4.1.0", "eslint-plugin-react-internal": "file:eslint-rules", @@ -61,12 +58,16 @@ "loose-envify": "^1.1.0", "object-assign": "^4.1.0", "platform": "^1.1.0", + "rollup": "^0.34.1", + "rollup-plugin-babel": "^2.6.1", + "rollup-plugin-node-resolve": "^1.7.1", + "rollup-plugin-replace": "^1.1.1", + "rollup-plugin-uglify": "^1.0.1", "run-sequence": "^1.1.4", "through2": "^2.0.0", "tmp": "~0.0.28", "typescript": "~1.8.10", - "uglify-js": "^2.5.0", - "uglifyify": "^3.0.1" + "uglify-js": "^2.5.0" }, "devEngines": { "node": "4.x || 5.x || 6.x", diff --git a/packages/react/package.json b/packages/react/package.json index 0c7f04eed8dec..516bf8567aaa9 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -26,10 +26,5 @@ "fbjs": "^0.8.1", "loose-envify": "^1.1.0", "object-assign": "^4.1.0" - }, - "browserify": { - "transform": [ - "loose-envify" - ] } } diff --git a/scripts/babel/__tests__/transform-es6-modulify-test.js b/scripts/babel/__tests__/transform-es6-modulify-test.js new file mode 100644 index 0000000000000..f106ea35a980e --- /dev/null +++ b/scripts/babel/__tests__/transform-es6-modulify-test.js @@ -0,0 +1,184 @@ +/** + * Copyright (c) 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. + */ +/* eslint-disable quotes */ +'use strict'; + +const babel = require('babel-core'); +const babelEs6ModulifyPlugin = require('../transform-es6-modulify'); + +function transform(input) { + return babel.transform(input, { + plugins: [babelEs6ModulifyPlugin], + }).code; +} + +function compare(input, output) { + var compiled = transform(input); + expect(compiled).toEqual(output); +} + +describe('transform-es6-modulify', () => { + // var ... = require('...'); -> import ... from '...'; + it('should rewrite `require` to `import`', () => { + compare( + "var React = require('react');", + + "import React from 'react';" + ); + + compare( + "var React = require('react');", + + "import React from 'react';" + ); + }); + + it('rewrites multi requires inserted by Babel', () => { + compare( +`var _prodInvariant = require('./reactProdInvariant'), + _assign = require('object-assign');`, + +`import _prodInvariant from './reactProdInvariant'; +import _assign from 'object-assign';` + ); + }); + + it('should throw when the RHS is a MemberExpression', () => { + expect(() => { + transform("var createElement = require('react').createElement;"); + }).toThrowError(/Invalid require: `require\(\)` must be in the form of `var ... = require\(...\);`/); + }); + + it('should throw for non-top level requires', () => { + expect(() => { + transform( +` +if (3 > 2) { + var React = require('react'); +} +` + ); + }).toThrowError(/Invalid require: `require\(\)` must be on the top-level/); + }); + + it('should throw when `require()` is not in a VariableDeclarator', () => { + expect(() => { + transform( +` +var React; +React = require('react'); +`); + }).toThrowError(/Invalid require: `require\(\)` must be directly in a variable declarator/); + + expect(() => { + transform("var React = require('react') || null;"); + }).toThrowError(/Invalid require: `require\(\)` must be directly in a variable declarator/); + + expect(() => { + transform("require('inject-something');"); + }).toThrowError(/Invalid require: `require\(\)` must be directly in a variable declarator/); + }); + + it('should throw when the parameter passed to `require()` is not a literal string', () => { + expect(() => { + transform("var React = require('re' + 'act');"); + }).toThrowError(/Invalid require: `require\(\)` must take a literal string as argument/); + }); + + it('should throw when it sees destructuring', () => { + expect(() => { + transform("var {createElement} = require('react');"); + }).toThrowError(/Invalid require: left hand side of `require\(\)` must be an identifier/); + }); + + + // module.exports = ...; -> exports default ...; + it('should rewrite `module.exports` to `export default`', () => { + compare( + "module.exports = {};", + + "export default {};" + ); + + compare( + "module.exports = React;", + + "export default React;" + ); + + compare( + "module.exports = 3 > 2 ? React : ReactDOM;", + + "export default 3 > 2 ? React : ReactDOM;" + ); + + compare( + "module.exports = class Foo {};", + + "export default (class Foo {});" + ); + + compare( + "module.exports = class {};", + + "export default (class {});" + ); + + compare( + "module.exports = function foo() {};", + + "export default (function foo() {});" + ); + + compare( + "module.exports = function() {};", + + "export default (function () {});" + ); + }); + + it('should throw when it sees module.exports.foo = ...', () => { + expect(() => { + transform("module.exports.createElement = createElement;"); + }).toThrowError(/Invalid exports: `module.exports` must be in the form of `module.exports = ...;`/); + }); + + it('should throw when it sees module.exports on the right hand side', () => { + expect(() => { + transform('foo = module.exports;'); + }).toThrowError(/Invalid exports: `module.exports` must be in the form of `module.exports = ...;`/); + + expect(() => { + transform(`var foo = module.exports;`); + }).toThrowError(/Invalid exports: `module.exports` must be in the form of `module.exports = ...;`/); + }); + + it('should throw for non-top level exports', () => { + expect(() => { + transform( +` +if (3 > 2) { + module.exports = React; +} +` + ); + }).toThrowError(/Invalid exports: `module.exports = ...` must be on the top-level/); + }); + + it('should throw when there are more than one module.exports', () => { + expect(() => { + transform( +` +module.exports = React; +module.exports = ReactDOM; +` + ); + }).toThrowError(/Invalid exports: `module.exports = ...` can only happen once in a module/); + }); +}); diff --git a/scripts/babel/transform-es6-modulify.js b/scripts/babel/transform-es6-modulify.js new file mode 100644 index 0000000000000..2e9ea38d998fc --- /dev/null +++ b/scripts/babel/transform-es6-modulify.js @@ -0,0 +1,122 @@ +/** + * Copyright (c) 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. + */ +'use strict'; + + +/* + * This Babel transform should _ONLY_ be used with Rollup. + * It transforms our CommonJS require/export statements to ES6 imports/exports + * so that we can use Rollup to bundle our modules to UMD. + * + * NOTE It requires all require/export statements to be on the top-level. + */ +var basename = require('path').basename; + +function getFileLoc(path, file) { + if (file.file.opts.filename === 'unknown') { + return ''; + } + var startLinePos = path.node.loc.start.line; + var fileName = file.file.opts.filename; + var moduleName = basename(fileName); + return ( + 'Check line ' + startLinePos + ' at `/build/modules/' + fileName + + '` and the original `' + moduleName + '` module under `/src`.' + ); +} + +module.exports = function(babel) { + var t = babel.types; + + return { + visitor: { + CallExpression: function(path, file) { + if (!t.isIdentifier(path.node.callee, {name: 'require'})) { + return; + } + + var sourceLocText = getFileLoc(path, file); + if (t.isMemberExpression(path.parent)) { + // `var createElement = require('react').createElement;` + throw new Error( + 'Invalid require: `require()` must be in the form of `var ... = require(...);`. ' + sourceLocText + ); + } else if (!t.isVariableDeclarator(path.parent)) { + // is not directly in a VariableDeclarator + throw new Error( + 'Invalid require: `require()` must be directly in a variable declarator. ' + sourceLocText + ); + } else if (!t.isProgram(path.scope.block)) { + // is not on the top-level + throw new Error( + 'Invalid require: `require()` must be on the top-level. ' + sourceLocText + ); + } else if (!t.isStringLiteral(path.node.arguments[0])) { + // the argument is not a StringLiteral + throw new Error( + 'Invalid require: `require()` must take a literal string as argument. ' + sourceLocText + ); + } else if (!t.isIdentifier(path.parent.id)) { + // LHS is not an Identifier; presumably an ObjectPattern (destructuring) + throw new Error( + 'Invalid require: left hand side of `require()` must be an identifier. ' + sourceLocText + ); + } + + // instead of calling `replaceWith`, this handles cases like + // `var m0 = require('m0'), m1 = require('m1');` + // we don't have this pattern in the code base but + // it happens when Babel inserts `require`s + path.parentPath.parentPath.insertBefore( + t.importDeclaration( + [t.importDefaultSpecifier(path.parent.id)], + path.node.arguments[0] + ) + ); + path.parentPath.remove(); + }, + + MemberExpression: function(path, file) { + if (!path.matchesPattern('module.exports')) { + return; + } + + var sourceLocText = getFileLoc(path, file); + if ( + !t.isAssignmentExpression(path.parent) || + path.node !== path.parent.left + ) { + // `module.exports` is on the RHS, or the LHS looks like `module.exports.foo` + throw new Error( + 'Invalid exports: `module.exports` must be in the form of `module.exports = ...;`. ' + sourceLocText + ); + } else if (!t.isProgram(path.scope.block)) { + // is not on the top-level + throw new Error( + 'Invalid exports: `module.exports = ...` must be on the top-level. ' + sourceLocText + ); + } + + this.numberOfExports = this.numberOfExports ? this.numberOfExports + 1 : 1; + if (this.numberOfExports > 1) { + throw new Error( + 'Invalid exports: `module.exports = ...` can only happen once in a module. ' + sourceLocText + ); + } + + path.parentPath.parentPath.replaceWith( + // for now, it's not necessary to convert the RHS to + // a FunctionDeclaration or a ClassDeclaration since we only export default + // (i.e., only named exports resolve the `id` field for naming) + t.exportDefaultDeclaration(path.parent.right) + ); + }, + }, + }; +}; diff --git a/src/addons/ReactWithAddons.js b/src/addons/ReactWithAddons.js index 94102c5c2e8b6..155772e608132 100644 --- a/src/addons/ReactWithAddons.js +++ b/src/addons/ReactWithAddons.js @@ -17,6 +17,8 @@ var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); var ReactCSSTransitionGroup = require('ReactCSSTransitionGroup'); var ReactFragment = require('ReactFragment'); +var ReactPerfDev = require('ReactPerfDev'); +var ReactTestUtils = require('ReactTestUtils'); var ReactTransitionGroup = require('ReactTransitionGroup'); var shallowCompare = require('shallowCompare'); @@ -34,8 +36,8 @@ React.addons = { }; if (__DEV__) { - React.addons.Perf = require('ReactPerf'); - React.addons.TestUtils = require('ReactTestUtils'); + React.addons.Perf = ReactPerfDev; + React.addons.TestUtils = ReactTestUtils; } module.exports = React; diff --git a/src/isomorphic/React.js b/src/isomorphic/React.js index c791b30e08cf0..0223fa2554d15 100644 --- a/src/isomorphic/React.js +++ b/src/isomorphic/React.js @@ -17,6 +17,7 @@ var ReactPureComponent = require('ReactPureComponent'); var ReactClass = require('ReactClass'); var ReactDOMFactories = require('ReactDOMFactories'); var ReactElement = require('ReactElement'); +var ReactElementValidatorDev = require('ReactElementValidatorDev'); var ReactPropTypes = require('ReactPropTypes'); var ReactVersion = require('ReactVersion'); @@ -28,10 +29,9 @@ var createFactory = ReactElement.createFactory; var cloneElement = ReactElement.cloneElement; if (__DEV__) { - var ReactElementValidator = require('ReactElementValidator'); - createElement = ReactElementValidator.createElement; - createFactory = ReactElementValidator.createFactory; - cloneElement = ReactElementValidator.cloneElement; + createElement = ReactElementValidatorDev.createElement; + createFactory = ReactElementValidatorDev.createFactory; + cloneElement = ReactElementValidatorDev.cloneElement; } var __spread = Object.assign; diff --git a/src/isomorphic/classic/element/ReactDOMFactories.js b/src/isomorphic/classic/element/ReactDOMFactories.js index 44b2a6f997fd3..c7a6cc1fb0f89 100644 --- a/src/isomorphic/classic/element/ReactDOMFactories.js +++ b/src/isomorphic/classic/element/ReactDOMFactories.js @@ -12,6 +12,7 @@ 'use strict'; var ReactElement = require('ReactElement'); +var ReactElementValidatorDev = require('ReactElementValidatorDev'); var mapObject = require('mapObject'); @@ -23,8 +24,7 @@ var mapObject = require('mapObject'); */ function createDOMFactory(tag) { if (__DEV__) { - var ReactElementValidator = require('ReactElementValidator'); - return ReactElementValidator.createFactory(tag); + return ReactElementValidatorDev.createFactory(tag); } return ReactElement.createFactory(tag); } diff --git a/src/isomorphic/classic/element/ReactElement.js b/src/isomorphic/classic/element/ReactElement.js index 710bcaa532546..bdaba78d7e2c9 100644 --- a/src/isomorphic/classic/element/ReactElement.js +++ b/src/isomorphic/classic/element/ReactElement.js @@ -14,7 +14,7 @@ var ReactCurrentOwner = require('ReactCurrentOwner'); var warning = require('warning'); -var canDefineProperty = require('canDefineProperty'); +var canDefinePropertyDev = require('canDefinePropertyDev'); var hasOwnProperty = Object.prototype.hasOwnProperty; // The Symbol used to tag the ReactElement type. If there is no native Symbol @@ -103,7 +103,7 @@ var ReactElement = function(type, key, ref, self, source, owner, props) { // the validation flag non-enumerable (where possible, which should // include every environment we run tests in), so the test framework // ignores it. - if (canDefineProperty) { + if (canDefinePropertyDev) { Object.defineProperty(element._store, 'validated', { configurable: false, enumerable: false, diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js deleted file mode 100644 index 4d4121f9810c3..0000000000000 --- a/src/isomorphic/classic/element/ReactElementValidator.js +++ /dev/null @@ -1,269 +0,0 @@ -/** - * Copyright 2014-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. - * - * @providesModule ReactElementValidator - */ - -/** - * ReactElementValidator provides a wrapper around a element factory - * which validates the props passed to the element. This is intended to be - * used only in DEV and could be replaced by a static type checker for languages - * that support it. - */ - -'use strict'; - -var ReactCurrentOwner = require('ReactCurrentOwner'); -var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool'); -var ReactElement = require('ReactElement'); -var ReactPropTypeLocations = require('ReactPropTypeLocations'); - -var checkReactTypeSpec = require('checkReactTypeSpec'); - -var canDefineProperty = require('canDefineProperty'); -var getIteratorFn = require('getIteratorFn'); -var warning = require('warning'); - -function getDeclarationErrorAddendum() { - if (ReactCurrentOwner.current) { - var name = ReactCurrentOwner.current.getName(); - if (name) { - return ' Check the render method of `' + name + '`.'; - } - } - return ''; -} - -/** - * Warn if there's no key explicitly set on dynamic arrays of children or - * object keys are not valid. This allows us to keep track of children between - * updates. - */ -var ownerHasKeyUseWarning = {}; - -function getCurrentComponentErrorInfo(parentType) { - var info = getDeclarationErrorAddendum(); - - if (!info) { - var parentName = typeof parentType === 'string' ? - parentType : parentType.displayName || parentType.name; - if (parentName) { - info = ` Check the top-level render call using <${parentName}>.`; - } - } - return info; -} - -/** - * Warn if the element doesn't have an explicit key assigned to it. - * This element is in an array. The array could grow and shrink or be - * reordered. All children that haven't already been validated are required to - * have a "key" property assigned to it. Error statuses are cached so a warning - * will only be shown once. - * - * @internal - * @param {ReactElement} element Element that requires a key. - * @param {*} parentType element's parent's type. - */ -function validateExplicitKey(element, parentType) { - if (!element._store || element._store.validated || element.key != null) { - return; - } - element._store.validated = true; - - var memoizer = ownerHasKeyUseWarning.uniqueKey || ( - ownerHasKeyUseWarning.uniqueKey = {} - ); - - var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType); - if (memoizer[currentComponentErrorInfo]) { - return; - } - memoizer[currentComponentErrorInfo] = true; - - // Usually the current owner is the offender, but if it accepts children as a - // property, it may be the creator of the child that's responsible for - // assigning it a key. - var childOwner = ''; - if (element && - element._owner && - element._owner !== ReactCurrentOwner.current) { - // Give the component that originally created this child. - childOwner = - ` It was passed a child from ${element._owner.getName()}.`; - } - - warning( - false, - 'Each child in an array or iterator should have a unique "key" prop.' + - '%s%s See https://fb.me/react-warning-keys for more information.%s', - currentComponentErrorInfo, - childOwner, - ReactComponentTreeDevtool.getCurrentStackAddendum(element) - ); -} - -/** - * Ensure that every element either is passed in a static location, in an - * array with an explicit keys property defined, or in an object literal - * with valid key property. - * - * @internal - * @param {ReactNode} node Statically passed child of any type. - * @param {*} parentType node's parent's type. - */ -function validateChildKeys(node, parentType) { - if (typeof node !== 'object') { - return; - } - if (Array.isArray(node)) { - for (var i = 0; i < node.length; i++) { - var child = node[i]; - if (ReactElement.isValidElement(child)) { - validateExplicitKey(child, parentType); - } - } - } else if (ReactElement.isValidElement(node)) { - // This element was passed in a valid location. - if (node._store) { - node._store.validated = true; - } - } else if (node) { - var iteratorFn = getIteratorFn(node); - // Entry iterators provide implicit keys. - if (iteratorFn) { - if (iteratorFn !== node.entries) { - var iterator = iteratorFn.call(node); - var step; - while (!(step = iterator.next()).done) { - if (ReactElement.isValidElement(step.value)) { - validateExplicitKey(step.value, parentType); - } - } - } - } - } -} - -/** - * Given an element, validate that its props follow the propTypes definition, - * provided by the type. - * - * @param {ReactElement} element - */ -function validatePropTypes(element) { - var componentClass = element.type; - if (typeof componentClass !== 'function') { - return; - } - var name = componentClass.displayName || componentClass.name; - if (componentClass.propTypes) { - checkReactTypeSpec( - componentClass.propTypes, - element.props, - ReactPropTypeLocations.prop, - name, - element, - null - ); - } - if (typeof componentClass.getDefaultProps === 'function') { - warning( - componentClass.getDefaultProps.isReactClassApproved, - 'getDefaultProps is only used on classic React.createClass ' + - 'definitions. Use a static property named `defaultProps` instead.' - ); - } -} - -var ReactElementValidator = { - - createElement: function(type, props, children) { - var validType = typeof type === 'string' || typeof type === 'function' || - (type !== null && typeof type === 'object'); - // We warn in this case but don't throw. We expect the element creation to - // succeed and there will likely be errors in render. - warning( - validType, - 'React.createElement: type should not be null, undefined, boolean, or ' + - 'number. It should be a string (for DOM elements) or a ReactClass ' + - '(for composite components).%s', - getDeclarationErrorAddendum() - ); - - var element = ReactElement.createElement.apply(this, arguments); - - // The result can be nullish if a mock or a custom function is used. - // TODO: Drop this when these are no longer allowed as the type argument. - if (element == null) { - return element; - } - - // Skip key warning if the type isn't valid since our key validation logic - // doesn't expect a non-string/function type and can throw confusing errors. - // We don't want exception behavior to differ between dev and prod. - // (Rendering will throw with a helpful message and as soon as the type is - // fixed, the key warnings will appear.) - if (validType) { - for (var i = 2; i < arguments.length; i++) { - validateChildKeys(arguments[i], type); - } - } - - validatePropTypes(element); - - return element; - }, - - createFactory: function(type) { - var validatedFactory = ReactElementValidator.createElement.bind( - null, - type - ); - // Legacy hook TODO: Warn if this is accessed - validatedFactory.type = type; - - if (__DEV__) { - if (canDefineProperty) { - Object.defineProperty( - validatedFactory, - 'type', - { - enumerable: false, - get: function() { - warning( - false, - 'Factory.type is deprecated. Access the class directly ' + - 'before passing it to createFactory.' - ); - Object.defineProperty(this, 'type', { - value: type, - }); - return type; - }, - } - ); - } - } - - - return validatedFactory; - }, - - cloneElement: function(element, props, children) { - var newElement = ReactElement.cloneElement.apply(this, arguments); - for (var i = 2; i < arguments.length; i++) { - validateChildKeys(arguments[i], newElement.type); - } - validatePropTypes(newElement); - return newElement; - }, - -}; - -module.exports = ReactElementValidator; diff --git a/src/isomorphic/classic/element/ReactElementValidatorDev.js b/src/isomorphic/classic/element/ReactElementValidatorDev.js new file mode 100644 index 0000000000000..1f52e37599fb1 --- /dev/null +++ b/src/isomorphic/classic/element/ReactElementValidatorDev.js @@ -0,0 +1,270 @@ +/** + * Copyright 2014-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. + * + * @providesModule ReactElementValidatorDev + */ + +/** + * ReactElementValidator provides a wrapper around a element factory + * which validates the props passed to the element. This is intended to be + * used only in DEV and could be replaced by a static type checker for languages + * that support it. + */ + +'use strict'; + +var ReactCurrentOwner = require('ReactCurrentOwner'); +var ReactComponentTreeDevtoolDev = require('ReactComponentTreeDevtoolDev'); +var ReactElement = require('ReactElement'); +var ReactPropTypeLocations = require('ReactPropTypeLocations'); + +var checkReactTypeSpec = require('checkReactTypeSpec'); + +var canDefinePropertyDev = require('canDefinePropertyDev'); +var getIteratorFn = require('getIteratorFn'); +var warning = require('warning'); + +var ReactElementValidatorDev = {}; + +if (__DEV__) { + var getDeclarationErrorAddendum = function() { + if (ReactCurrentOwner.current) { + var name = ReactCurrentOwner.current.getName(); + if (name) { + return ' Check the render method of `' + name + '`.'; + } + } + return ''; + }; + + /** + * Warn if there's no key explicitly set on dynamic arrays of children or + * object keys are not valid. This allows us to keep track of children between + * updates. + */ + var ownerHasKeyUseWarning = {}; + + var getCurrentComponentErrorInfo = function(parentType) { + var info = getDeclarationErrorAddendum(); + + if (!info) { + var parentName = typeof parentType === 'string' ? + parentType : parentType.displayName || parentType.name; + if (parentName) { + info = ` Check the top-level render call using <${parentName}>.`; + } + } + return info; + }; + + /** + * Warn if the element doesn't have an explicit key assigned to it. + * This element is in an array. The array could grow and shrink or be + * reordered. All children that haven't already been validated are required to + * have a "key" property assigned to it. Error statuses are cached so a warning + * will only be shown once. + * + * @internal + * @param {ReactElement} element Element that requires a key. + * @param {*} parentType element's parent's type. + */ + var validateExplicitKey = function(element, parentType) { + if (!element._store || element._store.validated || element.key != null) { + return; + } + element._store.validated = true; + + var memoizer = ownerHasKeyUseWarning.uniqueKey || ( + ownerHasKeyUseWarning.uniqueKey = {} + ); + + var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType); + if (memoizer[currentComponentErrorInfo]) { + return; + } + memoizer[currentComponentErrorInfo] = true; + + // Usually the current owner is the offender, but if it accepts children as a + // property, it may be the creator of the child that's responsible for + // assigning it a key. + var childOwner = ''; + if (element && + element._owner && + element._owner !== ReactCurrentOwner.current) { + // Give the component that originally created this child. + childOwner = + ` It was passed a child from ${element._owner.getName()}.`; + } + + warning( + false, + 'Each child in an array or iterator should have a unique "key" prop.' + + '%s%s See https://fb.me/react-warning-keys for more information.%s', + currentComponentErrorInfo, + childOwner, + ReactComponentTreeDevtoolDev.getCurrentStackAddendum(element) + ); + }; + + /** + * Ensure that every element either is passed in a static location, in an + * array with an explicit keys property defined, or in an object literal + * with valid key property. + * + * @internal + * @param {ReactNode} node Statically passed child of any type. + * @param {*} parentType node's parent's type. + */ + var validateChildKeys = function(node, parentType) { + if (typeof node !== 'object') { + return; + } + if (Array.isArray(node)) { + for (var i = 0; i < node.length; i++) { + var child = node[i]; + if (ReactElement.isValidElement(child)) { + validateExplicitKey(child, parentType); + } + } + } else if (ReactElement.isValidElement(node)) { + // This element was passed in a valid location. + if (node._store) { + node._store.validated = true; + } + } else if (node) { + var iteratorFn = getIteratorFn(node); + // Entry iterators provide implicit keys. + if (iteratorFn) { + if (iteratorFn !== node.entries) { + var iterator = iteratorFn.call(node); + var step; + while (!(step = iterator.next()).done) { + if (ReactElement.isValidElement(step.value)) { + validateExplicitKey(step.value, parentType); + } + } + } + } + } + }; + + /** + * Given an element, validate that its props follow the propTypes definition, + * provided by the type. + * + * @param {ReactElement} element + */ + var validatePropTypes = function(element) { + var componentClass = element.type; + if (typeof componentClass !== 'function') { + return; + } + var name = componentClass.displayName || componentClass.name; + if (componentClass.propTypes) { + checkReactTypeSpec( + componentClass.propTypes, + element.props, + ReactPropTypeLocations.prop, + name, + element, + null + ); + } + if (typeof componentClass.getDefaultProps === 'function') { + warning( + componentClass.getDefaultProps.isReactClassApproved, + 'getDefaultProps is only used on classic React.createClass ' + + 'definitions. Use a static property named `defaultProps` instead.' + ); + } + }; + + ReactElementValidatorDev = { + + createElement: function(type, props, children) { + var validType = typeof type === 'string' || typeof type === 'function' || + (type !== null && typeof type === 'object'); + // We warn in this case but don't throw. We expect the element creation to + // succeed and there will likely be errors in render. + warning( + validType, + 'React.createElement: type should not be null, undefined, boolean, or ' + + 'number. It should be a string (for DOM elements) or a ReactClass ' + + '(for composite components).%s', + getDeclarationErrorAddendum() + ); + + var element = ReactElement.createElement.apply(this, arguments); + + // The result can be nullish if a mock or a custom function is used. + // TODO: Drop this when these are no longer allowed as the type argument. + if (element == null) { + return element; + } + + // Skip key warning if the type isn't valid since our key validation logic + // doesn't expect a non-string/function type and can throw confusing errors. + // We don't want exception behavior to differ between dev and prod. + // (Rendering will throw with a helpful message and as soon as the type is + // fixed, the key warnings will appear.) + if (validType) { + for (var i = 2; i < arguments.length; i++) { + validateChildKeys(arguments[i], type); + } + } + + validatePropTypes(element); + + return element; + }, + + createFactory: function(type) { + var validatedFactory = ReactElementValidatorDev.createElement.bind( + null, + type + ); + // Legacy hook TODO: Warn if this is accessed + validatedFactory.type = type; + + if (canDefinePropertyDev) { + Object.defineProperty( + validatedFactory, + 'type', + { + enumerable: false, + get: function() { + warning( + false, + 'Factory.type is deprecated. Access the class directly ' + + 'before passing it to createFactory.' + ); + Object.defineProperty(this, 'type', { + value: type, + }); + return type; + }, + } + ); + } + + return validatedFactory; + }, + + cloneElement: function(element, props, children) { + var newElement = ReactElement.cloneElement.apply(this, arguments); + for (var i = 2; i < arguments.length; i++) { + validateChildKeys(arguments[i], newElement.type); + } + validatePropTypes(newElement); + return newElement; + }, + + }; +} + +module.exports = ReactElementValidatorDev; diff --git a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js b/src/isomorphic/classic/element/__tests__/ReactElementValidatorDev-test.js similarity index 99% rename from src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js rename to src/isomorphic/classic/element/__tests__/ReactElementValidatorDev-test.js index fe7acf3e60930..c5a8ce09b0de7 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElementValidatorDev-test.js @@ -18,7 +18,7 @@ var React; var ReactDOM; var ReactTestUtils; -describe('ReactElementValidator', function() { +describe('ReactElementValidatorDev', function() { function normalizeCodeLocInfo(str) { return str.replace(/\(at .+?:\d+\)/g, '(at **)'); } diff --git a/src/isomorphic/classic/types/checkReactTypeSpec.js b/src/isomorphic/classic/types/checkReactTypeSpec.js index 84211727fa977..196d387a9cbbb 100644 --- a/src/isomorphic/classic/types/checkReactTypeSpec.js +++ b/src/isomorphic/classic/types/checkReactTypeSpec.js @@ -11,27 +11,13 @@ 'use strict'; +var ReactComponentTreeDevtoolDev = require('ReactComponentTreeDevtoolDev'); var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); var ReactPropTypesSecret = require('ReactPropTypesSecret'); var invariant = require('invariant'); var warning = require('warning'); -var ReactComponentTreeDevtool; - -if ( - typeof process !== 'undefined' && - process.env && - process.env.NODE_ENV === 'test' -) { - // Temporary hack. - // Inline requires don't work well with Jest: - // https://github.com/facebook/react/issues/7240 - // Remove the inline requires when we don't need them anymore: - // https://github.com/facebook/react/pull/7178 - ReactComponentTreeDevtool = require('ReactComponentTreeDevtool') -} - var loggedTypeFailures = {}; /** @@ -88,13 +74,10 @@ function checkReactTypeSpec(typeSpecs, values, location, componentName, element, var componentStackInfo = ''; if (__DEV__) { - if (!ReactComponentTreeDevtool) { - ReactComponentTreeDevtool = require('ReactComponentTreeDevtool'); - } if (debugID !== null) { - componentStackInfo = ReactComponentTreeDevtool.getStackAddendumByID(debugID); + componentStackInfo = ReactComponentTreeDevtoolDev.getStackAddendumByID(debugID); } else if (element !== null) { - componentStackInfo = ReactComponentTreeDevtool.getCurrentStackAddendum(element); + componentStackInfo = ReactComponentTreeDevtoolDev.getCurrentStackAddendum(element); } } diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js deleted file mode 100644 index 622bad984749f..0000000000000 --- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js +++ /dev/null @@ -1,262 +0,0 @@ -/** - * Copyright 2016-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. - * - * @providesModule ReactComponentTreeDevtool - */ - -'use strict'; - -var ReactCurrentOwner = require('ReactCurrentOwner'); - -var invariant = require('invariant'); -var warning = require('warning'); - -var tree = {}; -var unmountedIDs = {}; -var rootIDs = {}; - -function updateTree(id, update) { - if (!tree[id]) { - tree[id] = { - element: null, - parentID: null, - ownerID: null, - text: null, - childIDs: [], - displayName: 'Unknown', - isMounted: false, - updateCount: 0, - }; - } - update(tree[id]); -} - -function purgeDeep(id) { - var item = tree[id]; - if (item) { - var {childIDs} = item; - delete tree[id]; - childIDs.forEach(purgeDeep); - } -} - -function describeComponentFrame(name, source, ownerName) { - return '\n in ' + name + ( - source ? - ' (at ' + source.fileName.replace(/^.*[\\\/]/, '') + ':' + - source.lineNumber + ')' : - ownerName ? - ' (created by ' + ownerName + ')' : - '' - ); -} - -function describeID(id) { - var name = ReactComponentTreeDevtool.getDisplayName(id); - var element = ReactComponentTreeDevtool.getElement(id); - var ownerID = ReactComponentTreeDevtool.getOwnerID(id); - var ownerName; - if (ownerID) { - ownerName = ReactComponentTreeDevtool.getDisplayName(ownerID); - } - warning( - element, - 'ReactComponentTreeDevtool: Missing React element for debugID %s when ' + - 'building stack', - id - ); - return describeComponentFrame(name, element && element._source, ownerName); -} - -var ReactComponentTreeDevtool = { - onSetDisplayName(id, displayName) { - updateTree(id, item => item.displayName = displayName); - }, - - onSetChildren(id, nextChildIDs) { - updateTree(id, item => { - item.childIDs = nextChildIDs; - - nextChildIDs.forEach(nextChildID => { - var nextChild = tree[nextChildID]; - invariant( - nextChild, - 'Expected devtool events to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - invariant( - nextChild.displayName != null, - 'Expected onSetDisplayName() to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - invariant( - nextChild.childIDs != null || nextChild.text != null, - 'Expected onSetChildren() or onSetText() to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - invariant( - nextChild.isMounted, - 'Expected onMountComponent() to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - if (nextChild.parentID == null) { - nextChild.parentID = id; - // TODO: This shouldn't be necessary but mounting a new root during in - // componentWillMount currently causes not-yet-mounted components to - // be purged from our tree data so their parent ID is missing. - } - invariant( - nextChild.parentID === id, - 'Expected onSetParent() and onSetChildren() to be consistent (%s ' + - 'has parents %s and %s).', - nextChildID, - nextChild.parentID, - id - ); - }); - }); - }, - - onSetOwner(id, ownerID) { - updateTree(id, item => item.ownerID = ownerID); - }, - - onSetParent(id, parentID) { - updateTree(id, item => item.parentID = parentID); - }, - - onSetText(id, text) { - updateTree(id, item => item.text = text); - }, - - onBeforeMountComponent(id, element) { - updateTree(id, item => item.element = element); - }, - - onBeforeUpdateComponent(id, element) { - updateTree(id, item => item.element = element); - }, - - onMountComponent(id) { - updateTree(id, item => item.isMounted = true); - }, - - onMountRootComponent(id) { - rootIDs[id] = true; - }, - - onUpdateComponent(id) { - updateTree(id, item => item.updateCount++); - }, - - onUnmountComponent(id) { - updateTree(id, item => item.isMounted = false); - unmountedIDs[id] = true; - delete rootIDs[id]; - }, - - purgeUnmountedComponents() { - if (ReactComponentTreeDevtool._preventPurging) { - // Should only be used for testing. - return; - } - - for (var id in unmountedIDs) { - purgeDeep(id); - } - unmountedIDs = {}; - }, - - isMounted(id) { - var item = tree[id]; - return item ? item.isMounted : false; - }, - - getCurrentStackAddendum(topElement) { - var info = ''; - if (topElement) { - var type = topElement.type; - var name = typeof type === 'function' ? - type.displayName || type.name : - type; - var owner = topElement._owner; - info += describeComponentFrame( - name || 'Unknown', - topElement._source, - owner && owner.getName() - ); - } - - var currentOwner = ReactCurrentOwner.current; - var id = currentOwner && currentOwner._debugID; - - info += ReactComponentTreeDevtool.getStackAddendumByID(id); - return info; - }, - - getStackAddendumByID(id) { - var info = ''; - while (id) { - info += describeID(id); - id = ReactComponentTreeDevtool.getParentID(id); - } - return info; - }, - - getChildIDs(id) { - var item = tree[id]; - return item ? item.childIDs : []; - }, - - getDisplayName(id) { - var item = tree[id]; - return item ? item.displayName : 'Unknown'; - }, - - getElement(id) { - var item = tree[id]; - return item ? item.element : null; - }, - - getOwnerID(id) { - var item = tree[id]; - return item ? item.ownerID : null; - }, - - getParentID(id) { - var item = tree[id]; - return item ? item.parentID : null; - }, - - getSource(id) { - var item = tree[id]; - var element = item ? item.element : null; - var source = element != null ? element._source : null; - return source; - }, - - getText(id) { - var item = tree[id]; - return item ? item.text : null; - }, - - getUpdateCount(id) { - var item = tree[id]; - return item ? item.updateCount : 0; - }, - - getRootIDs() { - return Object.keys(rootIDs); - }, - - getRegisteredIDs() { - return Object.keys(tree); - }, -}; - -module.exports = ReactComponentTreeDevtool; diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtoolDev.js b/src/isomorphic/devtools/ReactComponentTreeDevtoolDev.js new file mode 100644 index 0000000000000..0fa62a0ddd6aa --- /dev/null +++ b/src/isomorphic/devtools/ReactComponentTreeDevtoolDev.js @@ -0,0 +1,266 @@ +/** + * Copyright 2016-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. + * + * @providesModule ReactComponentTreeDevtoolDev + */ + +'use strict'; + +var ReactCurrentOwner = require('ReactCurrentOwner'); + +var invariant = require('invariant'); +var warning = require('warning'); + +var ReactComponentTreeDevtoolDev = {}; + +if (__DEV__) { + var tree = {}; + var unmountedIDs = {}; + var rootIDs = {}; + + var updateTree = function(id, update) { + if (!tree[id]) { + tree[id] = { + element: null, + parentID: null, + ownerID: null, + text: null, + childIDs: [], + displayName: 'Unknown', + isMounted: false, + updateCount: 0, + }; + } + update(tree[id]); + }; + + var purgeDeep = function(id) { + var item = tree[id]; + if (item) { + var {childIDs} = item; + delete tree[id]; + childIDs.forEach(purgeDeep); + } + }; + + var describeComponentFrame = function(name, source, ownerName) { + return '\n in ' + name + ( + source ? + ' (at ' + source.fileName.replace(/^.*[\\\/]/, '') + ':' + + source.lineNumber + ')' : + ownerName ? + ' (created by ' + ownerName + ')' : + '' + ); + }; + + var describeID = function(id) { + var name = ReactComponentTreeDevtoolDev.getDisplayName(id); + var element = ReactComponentTreeDevtoolDev.getElement(id); + var ownerID = ReactComponentTreeDevtoolDev.getOwnerID(id); + var ownerName; + if (ownerID) { + ownerName = ReactComponentTreeDevtoolDev.getDisplayName(ownerID); + } + warning( + element, + 'ReactComponentTreeDevtool: Missing React element for debugID %s when ' + + 'building stack', + id + ); + return describeComponentFrame(name, element && element._source, ownerName); + }; + + ReactComponentTreeDevtoolDev = { + onSetDisplayName(id, displayName) { + updateTree(id, item => item.displayName = displayName); + }, + + onSetChildren(id, nextChildIDs) { + updateTree(id, item => { + item.childIDs = nextChildIDs; + + nextChildIDs.forEach(nextChildID => { + var nextChild = tree[nextChildID]; + invariant( + nextChild, + 'Expected devtool events to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.displayName != null, + 'Expected onSetDisplayName() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.childIDs != null || nextChild.text != null, + 'Expected onSetChildren() or onSetText() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.isMounted, + 'Expected onMountComponent() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + if (nextChild.parentID == null) { + nextChild.parentID = id; + // TODO: This shouldn't be necessary but mounting a new root during in + // componentWillMount currently causes not-yet-mounted components to + // be purged from our tree data so their parent ID is missing. + } + invariant( + nextChild.parentID === id, + 'Expected onSetParent() and onSetChildren() to be consistent (%s ' + + 'has parents %s and %s).', + nextChildID, + nextChild.parentID, + id + ); + }); + }); + }, + + onSetOwner(id, ownerID) { + updateTree(id, item => item.ownerID = ownerID); + }, + + onSetParent(id, parentID) { + updateTree(id, item => item.parentID = parentID); + }, + + onSetText(id, text) { + updateTree(id, item => item.text = text); + }, + + onBeforeMountComponent(id, element) { + updateTree(id, item => item.element = element); + }, + + onBeforeUpdateComponent(id, element) { + updateTree(id, item => item.element = element); + }, + + onMountComponent(id) { + updateTree(id, item => item.isMounted = true); + }, + + onMountRootComponent(id) { + rootIDs[id] = true; + }, + + onUpdateComponent(id) { + updateTree(id, item => item.updateCount++); + }, + + onUnmountComponent(id) { + updateTree(id, item => item.isMounted = false); + unmountedIDs[id] = true; + delete rootIDs[id]; + }, + + purgeUnmountedComponents() { + if (ReactComponentTreeDevtoolDev._preventPurging) { + // Should only be used for testing. + return; + } + + for (var id in unmountedIDs) { + purgeDeep(id); + } + unmountedIDs = {}; + }, + + isMounted(id) { + var item = tree[id]; + return item ? item.isMounted : false; + }, + + getCurrentStackAddendum(topElement) { + var info = ''; + if (topElement) { + var type = topElement.type; + var name = typeof type === 'function' ? + type.displayName || type.name : + type; + var owner = topElement._owner; + info += describeComponentFrame( + name || 'Unknown', + topElement._source, + owner && owner.getName() + ); + } + + var currentOwner = ReactCurrentOwner.current; + var id = currentOwner && currentOwner._debugID; + + info += ReactComponentTreeDevtoolDev.getStackAddendumByID(id); + return info; + }, + + getStackAddendumByID(id) { + var info = ''; + while (id) { + info += describeID(id); + id = ReactComponentTreeDevtoolDev.getParentID(id); + } + return info; + }, + + getChildIDs(id) { + var item = tree[id]; + return item ? item.childIDs : []; + }, + + getDisplayName(id) { + var item = tree[id]; + return item ? item.displayName : 'Unknown'; + }, + + getElement(id) { + var item = tree[id]; + return item ? item.element : null; + }, + + getOwnerID(id) { + var item = tree[id]; + return item ? item.ownerID : null; + }, + + getParentID(id) { + var item = tree[id]; + return item ? item.parentID : null; + }, + + getSource(id) { + var item = tree[id]; + var element = item ? item.element : null; + var source = element != null ? element._source : null; + return source; + }, + + getText(id) { + var item = tree[id]; + return item ? item.text : null; + }, + + getUpdateCount(id) { + var item = tree[id]; + return item ? item.updateCount : 0; + }, + + getRootIDs() { + return Object.keys(rootIDs); + }, + + getRegisteredIDs() { + return Object.keys(tree); + }, + }; +} + +module.exports = ReactComponentTreeDevtoolDev; diff --git a/src/isomorphic/modern/class/ReactComponent.js b/src/isomorphic/modern/class/ReactComponent.js index b93da63e94cf9..81547ba92b6c6 100644 --- a/src/isomorphic/modern/class/ReactComponent.js +++ b/src/isomorphic/modern/class/ReactComponent.js @@ -13,7 +13,7 @@ var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue'); -var canDefineProperty = require('canDefineProperty'); +var canDefinePropertyDev = require('canDefinePropertyDev'); var emptyObject = require('emptyObject'); var invariant = require('invariant'); var warning = require('warning'); @@ -111,7 +111,7 @@ if (__DEV__) { ], }; var defineDeprecationWarning = function(methodName, info) { - if (canDefineProperty) { + if (canDefinePropertyDev) { Object.defineProperty(ReactComponent.prototype, methodName, { get: function() { warning( diff --git a/src/renderers/dom/ReactDOM.js b/src/renderers/dom/ReactDOM.js index b546e46948300..afd46fa0a4b60 100644 --- a/src/renderers/dom/ReactDOM.js +++ b/src/renderers/dom/ReactDOM.js @@ -13,6 +13,7 @@ 'use strict'; +var ExecutionEnvironment = require('ExecutionEnvironment'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactDefaultInjection = require('ReactDefaultInjection'); var ReactMount = require('ReactMount'); @@ -66,7 +67,6 @@ if ( } if (__DEV__) { - var ExecutionEnvironment = require('ExecutionEnvironment'); if (ExecutionEnvironment.canUseDOM && window.top === window.self) { // First check if devtools is not installed diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index 9a0d15ba272af..69cb3b24486c2 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -21,7 +21,7 @@ var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); var ReactElement = require('ReactElement'); var ReactFeatureFlags = require('ReactFeatureFlags'); var ReactInstanceMap = require('ReactInstanceMap'); -var ReactInstrumentation = require('ReactInstrumentation'); +var ReactInstrumentationDev = require('ReactInstrumentationDev'); var ReactMarkupChecksum = require('ReactMarkupChecksum'); var ReactReconciler = require('ReactReconciler'); var ReactUpdateQueue = require('ReactUpdateQueue'); @@ -171,11 +171,11 @@ function batchedMountComponentIntoNode( */ function unmountComponentFromNode(instance, container, safely) { if (__DEV__) { - ReactInstrumentation.debugTool.onBeginFlush(); + ReactInstrumentationDev.debugTool.onBeginFlush(); } ReactReconciler.unmountComponent(instance, safely); if (__DEV__) { - ReactInstrumentation.debugTool.onEndFlush(); + ReactInstrumentationDev.debugTool.onEndFlush(); } if (container.nodeType === DOC_NODE_TYPE) { @@ -357,7 +357,7 @@ var ReactMount = { if (__DEV__) { // The instance here is TopLevelWrapper so we report mount for its child. - ReactInstrumentation.debugTool.onMountRootComponent( + ReactInstrumentationDev.debugTool.onMountRootComponent( componentInstance._renderedComponent._debugID ); } @@ -705,7 +705,7 @@ var ReactMount = { if (__DEV__) { var hostNode = ReactDOMComponentTree.getInstanceFromNode(container.firstChild); if (hostNode._debugID !== 0) { - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( hostNode._debugID, 'mount', markup.toString() diff --git a/src/renderers/dom/client/ReactReconcileTransaction.js b/src/renderers/dom/client/ReactReconcileTransaction.js index 833e415f8023d..ff6b32101359a 100644 --- a/src/renderers/dom/client/ReactReconcileTransaction.js +++ b/src/renderers/dom/client/ReactReconcileTransaction.js @@ -15,7 +15,7 @@ var CallbackQueue = require('CallbackQueue'); var PooledClass = require('PooledClass'); var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter'); var ReactInputSelection = require('ReactInputSelection'); -var ReactInstrumentation = require('ReactInstrumentation'); +var ReactInstrumentationDev = require('ReactInstrumentationDev'); var Transaction = require('Transaction'); var ReactUpdateQueue = require('ReactUpdateQueue'); @@ -94,8 +94,8 @@ var TRANSACTION_WRAPPERS = [ if (__DEV__) { TRANSACTION_WRAPPERS.push({ - initialize: ReactInstrumentation.debugTool.onBeginFlush, - close: ReactInstrumentation.debugTool.onEndFlush, + initialize: ReactInstrumentationDev.debugTool.onBeginFlush, + close: ReactInstrumentationDev.debugTool.onEndFlush, }); } diff --git a/src/renderers/dom/client/__tests__/validateDOMNesting-test.js b/src/renderers/dom/client/__tests__/validateDOMNestingDev-test.js similarity index 91% rename from src/renderers/dom/client/__tests__/validateDOMNesting-test.js rename to src/renderers/dom/client/__tests__/validateDOMNestingDev-test.js index dec17ae4ba288..3bda33290e45f 100644 --- a/src/renderers/dom/client/__tests__/validateDOMNesting-test.js +++ b/src/renderers/dom/client/__tests__/validateDOMNestingDev-test.js @@ -11,7 +11,7 @@ 'use strict'; -var validateDOMNesting; +var validateDOMNestingDev; // https://html.spec.whatwg.org/multipage/syntax.html#special var specialTags = [ @@ -36,11 +36,11 @@ var formattingTags = [ function isTagStackValid(stack) { var ancestorInfo = null; for (var i = 0; i < stack.length; i++) { - if (!validateDOMNesting.isTagValidInContext(stack[i], ancestorInfo)) { + if (!validateDOMNestingDev.isTagValidInContext(stack[i], ancestorInfo)) { return false; } ancestorInfo = - validateDOMNesting.updatedAncestorInfo(ancestorInfo, stack[i], null); + validateDOMNestingDev.updatedAncestorInfo(ancestorInfo, stack[i], null); } return true; } @@ -49,7 +49,7 @@ describe('ReactContextValidator', function() { beforeEach(function() { jest.resetModuleRegistry(); - validateDOMNesting = require('validateDOMNesting'); + validateDOMNestingDev = require('validateDOMNestingDev'); }); it('allows any tag with no context', function() { @@ -57,7 +57,7 @@ describe('ReactContextValidator', function() { // tag so we must err on the side of leniency. var allTags = [].concat(specialTags, formattingTags, ['mysterytag']); allTags.forEach(function(tag) { - expect(validateDOMNesting.isTagValidInContext(tag, null)).toBe(true); + expect(validateDOMNestingDev.isTagValidInContext(tag, null)).toBe(true); }); }); diff --git a/src/renderers/dom/client/utils/DOMChildrenOperations.js b/src/renderers/dom/client/utils/DOMChildrenOperations.js index a4541646d1900..f799fd7b00210 100644 --- a/src/renderers/dom/client/utils/DOMChildrenOperations.js +++ b/src/renderers/dom/client/utils/DOMChildrenOperations.js @@ -15,7 +15,7 @@ var DOMLazyTree = require('DOMLazyTree'); var Danger = require('Danger'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); -var ReactInstrumentation = require('ReactInstrumentation'); +var ReactInstrumentationDev = require('ReactInstrumentationDev'); var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction'); var setInnerHTML = require('setInnerHTML'); @@ -123,7 +123,7 @@ function replaceDelimitedText(openingComment, closingComment, stringText) { } if (__DEV__) { - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( ReactDOMComponentTree.getInstanceFromNode(openingComment)._debugID, 'replace text', stringText @@ -136,7 +136,7 @@ if (__DEV__) { dangerouslyReplaceNodeWithMarkup = function(oldChild, markup, prevInstance) { Danger.dangerouslyReplaceNodeWithMarkup(oldChild, markup); if (prevInstance._debugID !== 0) { - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( prevInstance._debugID, 'replace with', markup.toString() @@ -144,7 +144,7 @@ if (__DEV__) { } else { var nextInstance = ReactDOMComponentTree.getInstanceFromNode(markup.node); if (nextInstance._debugID !== 0) { - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( nextInstance._debugID, 'mount', markup.toString() @@ -186,7 +186,7 @@ var DOMChildrenOperations = { getNodeAfter(parentNode, update.afterNode) ); if (__DEV__) { - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( parentNodeDebugID, 'insert child', {toIndex: update.toIndex, content: update.content.toString()} @@ -200,7 +200,7 @@ var DOMChildrenOperations = { getNodeAfter(parentNode, update.afterNode) ); if (__DEV__) { - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( parentNodeDebugID, 'move child', {fromIndex: update.fromIndex, toIndex: update.toIndex} @@ -213,7 +213,7 @@ var DOMChildrenOperations = { update.content ); if (__DEV__) { - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( parentNodeDebugID, 'replace children', update.content.toString() @@ -226,7 +226,7 @@ var DOMChildrenOperations = { update.content ); if (__DEV__) { - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( parentNodeDebugID, 'replace text', update.content.toString() @@ -236,7 +236,7 @@ var DOMChildrenOperations = { case ReactMultiChildUpdateTypes.REMOVE_NODE: removeChild(parentNode, update.fromNode); if (__DEV__) { - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( parentNodeDebugID, 'remove child', {fromIndex: update.fromIndex} diff --git a/src/renderers/dom/client/validateDOMNesting.js b/src/renderers/dom/client/validateDOMNestingDev.js similarity index 97% rename from src/renderers/dom/client/validateDOMNesting.js rename to src/renderers/dom/client/validateDOMNestingDev.js index 9422a079e1462..0647416f07f49 100644 --- a/src/renderers/dom/client/validateDOMNesting.js +++ b/src/renderers/dom/client/validateDOMNestingDev.js @@ -6,7 +6,7 @@ * 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. * - * @providesModule validateDOMNesting + * @providesModule validateDOMNestingDev */ 'use strict'; @@ -14,7 +14,7 @@ var emptyFunction = require('emptyFunction'); var warning = require('warning'); -var validateDOMNesting = emptyFunction; +var validateDOMNestingDev = emptyFunction; if (__DEV__) { // This validation code was written based on the HTML5 parsing spec: @@ -322,7 +322,7 @@ if (__DEV__) { var didWarn = {}; - validateDOMNesting = function(childTag, childInstance, ancestorInfo) { + validateDOMNestingDev = function(childTag, childInstance, ancestorInfo) { ancestorInfo = ancestorInfo || emptyAncestorInfo; var parentInfo = ancestorInfo.current; var parentTag = parentInfo && parentInfo.tag; @@ -418,10 +418,10 @@ if (__DEV__) { } }; - validateDOMNesting.updatedAncestorInfo = updatedAncestorInfo; + validateDOMNestingDev.updatedAncestorInfo = updatedAncestorInfo; // For testing - validateDOMNesting.isTagValidInContext = function(tag, ancestorInfo) { + validateDOMNestingDev.isTagValidInContext = function(tag, ancestorInfo) { ancestorInfo = ancestorInfo || emptyAncestorInfo; var parentInfo = ancestorInfo.current; var parentTag = parentInfo && parentInfo.tag; @@ -432,4 +432,4 @@ if (__DEV__) { }; } -module.exports = validateDOMNesting; +module.exports = validateDOMNestingDev; diff --git a/src/renderers/dom/client/wrappers/LinkedValueUtils.js b/src/renderers/dom/client/wrappers/LinkedValueUtils.js index d13630d6f563a..b73b76e902d79 100644 --- a/src/renderers/dom/client/wrappers/LinkedValueUtils.js +++ b/src/renderers/dom/client/wrappers/LinkedValueUtils.js @@ -103,29 +103,6 @@ function getDeclarationErrorAddendum(owner) { * this outside of the ReactDOM controlled form components. */ var LinkedValueUtils = { - checkPropTypes: function(tagName, props, owner) { - for (var propName in propTypes) { - if (propTypes.hasOwnProperty(propName)) { - var error = propTypes[propName]( - props, - propName, - tagName, - ReactPropTypeLocations.prop, - null, - ReactPropTypesSecret - ); - } - if (error instanceof Error && !(error.message in loggedTypeFailures)) { - // Only monitor this failure once because there tends to be a lot of the - // same error. - loggedTypeFailures[error.message] = true; - - var addendum = getDeclarationErrorAddendum(owner); - warning(false, 'Failed form propType: %s%s', error.message, addendum); - } - } - }, - /** * @param {object} inputProps Props for form component * @return {*} current value of the input either from value prop or link. @@ -168,4 +145,29 @@ var LinkedValueUtils = { }, }; +if (__DEV__) { + LinkedValueUtils.checkPropTypes = function(tagName, props, owner) { + for (var propName in propTypes) { + if (propTypes.hasOwnProperty(propName)) { + var error = propTypes[propName]( + props, + propName, + tagName, + ReactPropTypeLocations.prop, + null, + ReactPropTypesSecret + ); + } + if (error instanceof Error && !(error.message in loggedTypeFailures)) { + // Only monitor this failure once because there tends to be a lot of the + // same error. + loggedTypeFailures[error.message] = true; + + var addendum = getDeclarationErrorAddendum(owner); + warning(false, 'Failed form propType: %s%s', error.message, addendum); + } + } + }; +} + module.exports = LinkedValueUtils; diff --git a/src/renderers/dom/server/ReactServerRendering.js b/src/renderers/dom/server/ReactServerRendering.js index fefb9af9d5cd5..d2f2dc6217da6 100644 --- a/src/renderers/dom/server/ReactServerRendering.js +++ b/src/renderers/dom/server/ReactServerRendering.js @@ -13,7 +13,7 @@ var ReactDOMContainerInfo = require('ReactDOMContainerInfo'); var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); var ReactElement = require('ReactElement'); -var ReactInstrumentation = require('ReactInstrumentation'); +var ReactInstrumentationDev = require('ReactInstrumentationDev'); var ReactMarkupChecksum = require('ReactMarkupChecksum'); var ReactReconciler = require('ReactReconciler'); var ReactServerBatchingStrategy = require('ReactServerBatchingStrategy'); @@ -50,7 +50,7 @@ function renderToStringImpl(element, makeStaticMarkup) { emptyObject ); if (__DEV__) { - ReactInstrumentation.debugTool.onUnmountComponent( + ReactInstrumentationDev.debugTool.onUnmountComponent( componentInstance._debugID ); } diff --git a/src/renderers/dom/server/ReactServerRenderingTransaction.js b/src/renderers/dom/server/ReactServerRenderingTransaction.js index ddb93b6311dd0..79a26447654c1 100644 --- a/src/renderers/dom/server/ReactServerRenderingTransaction.js +++ b/src/renderers/dom/server/ReactServerRenderingTransaction.js @@ -13,7 +13,7 @@ var PooledClass = require('PooledClass'); var Transaction = require('Transaction'); -var ReactInstrumentation = require('ReactInstrumentation'); +var ReactInstrumentationDev = require('ReactInstrumentationDev'); var ReactServerUpdateQueue = require('ReactServerUpdateQueue'); @@ -26,8 +26,8 @@ var TRANSACTION_WRAPPERS = []; if (__DEV__) { TRANSACTION_WRAPPERS.push({ - initialize: ReactInstrumentation.debugTool.onBeginFlush, - close: ReactInstrumentation.debugTool.onEndFlush, + initialize: ReactInstrumentationDev.debugTool.onBeginFlush, + close: ReactInstrumentationDev.debugTool.onEndFlush, }); } diff --git a/src/renderers/dom/shared/CSSPropertyOperations.js b/src/renderers/dom/shared/CSSPropertyOperations.js index c3222c202f147..54be9df0b0873 100644 --- a/src/renderers/dom/shared/CSSPropertyOperations.js +++ b/src/renderers/dom/shared/CSSPropertyOperations.js @@ -13,7 +13,7 @@ var CSSProperty = require('CSSProperty'); var ExecutionEnvironment = require('ExecutionEnvironment'); -var ReactInstrumentation = require('ReactInstrumentation'); +var ReactInstrumentationDev = require('ReactInstrumentationDev'); var camelizeStyleName = require('camelizeStyleName'); var dangerousStyleValue = require('dangerousStyleValue'); @@ -193,7 +193,7 @@ var CSSPropertyOperations = { */ setValueForStyles: function(node, styles, component) { if (__DEV__) { - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( component._debugID, 'update styles', styles diff --git a/src/renderers/dom/shared/DOMPropertyOperations.js b/src/renderers/dom/shared/DOMPropertyOperations.js index d0cce18dcf9b2..ef86257426600 100644 --- a/src/renderers/dom/shared/DOMPropertyOperations.js +++ b/src/renderers/dom/shared/DOMPropertyOperations.js @@ -13,8 +13,8 @@ var DOMProperty = require('DOMProperty'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); -var ReactDOMInstrumentation = require('ReactDOMInstrumentation'); -var ReactInstrumentation = require('ReactInstrumentation'); +var ReactDOMInstrumentationDev = require('ReactDOMInstrumentationDev'); +var ReactInstrumentationDev = require('ReactInstrumentationDev'); var quoteAttributeValueForBrowser = require('quoteAttributeValueForBrowser'); var warning = require('warning'); @@ -90,7 +90,7 @@ var DOMPropertyOperations = { */ createMarkupForProperty: function(name, value) { if (__DEV__) { - ReactDOMInstrumentation.debugTool.onCreateMarkupForProperty(name, value); + ReactDOMInstrumentationDev.debugTool.onCreateMarkupForProperty(name, value); } var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; @@ -168,10 +168,10 @@ var DOMPropertyOperations = { } if (__DEV__) { - ReactDOMInstrumentation.debugTool.onSetValueForProperty(node, name, value); + ReactDOMInstrumentationDev.debugTool.onSetValueForProperty(node, name, value); var payload = {}; payload[name] = value; - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( ReactDOMComponentTree.getInstanceFromNode(node)._debugID, 'update attribute', payload @@ -192,7 +192,7 @@ var DOMPropertyOperations = { if (__DEV__) { var payload = {}; payload[name] = value; - ReactInstrumentation.debugTool.onHostOperation( + ReactInstrumentationDev.debugTool.onHostOperation( ReactDOMComponentTree.getInstanceFromNode(node)._debugID, 'update attribute', payload @@ -209,8 +209,8 @@ var DOMPropertyOperations = { deleteValueForAttribute: function(node, name) { node.removeAttribute(name); if (__DEV__) { - ReactDOMInstrumentation.debugTool.onDeleteValueForProperty(node, name); - ReactInstrumentation.debugTool.onHostOperation( + ReactDOMInstrumentationDev.debugTool.onDeleteValueForProperty(node, name); + ReactInstrumentationDev.debugTool.onHostOperation( ReactDOMComponentTree.getInstanceFromNode(node)._debugID, 'remove attribute', name @@ -246,8 +246,8 @@ var DOMPropertyOperations = { } if (__DEV__) { - ReactDOMInstrumentation.debugTool.onDeleteValueForProperty(node, name); - ReactInstrumentation.debugTool.onHostOperation( + ReactDOMInstrumentationDev.debugTool.onDeleteValueForProperty(node, name); + ReactInstrumentationDev.debugTool.onHostOperation( ReactDOMComponentTree.getInstanceFromNode(node)._debugID, 'remove attribute', name diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index c4de8639b74ca..215bcc50335f8 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -32,7 +32,7 @@ var ReactDOMInput = require('ReactDOMInput'); var ReactDOMOption = require('ReactDOMOption'); var ReactDOMSelect = require('ReactDOMSelect'); var ReactDOMTextarea = require('ReactDOMTextarea'); -var ReactInstrumentation = require('ReactInstrumentation'); +var ReactInstrumentationDev = require('ReactInstrumentationDev'); var ReactMultiChild = require('ReactMultiChild'); var ReactServerRenderingTransaction = require('ReactServerRenderingTransaction'); @@ -43,7 +43,7 @@ var isEventSupported = require('isEventSupported'); var keyOf = require('keyOf'); var shallowEqual = require('shallowEqual'); var inputValueTracking = require('inputValueTracking'); -var validateDOMNesting = require('validateDOMNesting'); +var validateDOMNestingDev = require('validateDOMNestingDev'); var warning = require('warning'); var Flags = ReactDOMComponentFlags; @@ -265,7 +265,7 @@ if (__DEV__) { if (content == null) { if (hasExistingContent) { - ReactInstrumentation.debugTool.onUnmountComponent(this._contentDebugID); + ReactInstrumentationDev.debugTool.onUnmountComponent(this._contentDebugID); } this._contentDebugID = null; return; @@ -274,17 +274,17 @@ if (__DEV__) { this._contentDebugID = contentDebugID; var text = '' + content; - ReactInstrumentation.debugTool.onSetDisplayName(contentDebugID, '#text'); - ReactInstrumentation.debugTool.onSetParent(contentDebugID, debugID); - ReactInstrumentation.debugTool.onSetText(contentDebugID, text); + ReactInstrumentationDev.debugTool.onSetDisplayName(contentDebugID, '#text'); + ReactInstrumentationDev.debugTool.onSetParent(contentDebugID, debugID); + ReactInstrumentationDev.debugTool.onSetText(contentDebugID, text); if (hasExistingContent) { - ReactInstrumentation.debugTool.onBeforeUpdateComponent(contentDebugID, content); - ReactInstrumentation.debugTool.onUpdateComponent(contentDebugID); + ReactInstrumentationDev.debugTool.onBeforeUpdateComponent(contentDebugID, content); + ReactInstrumentationDev.debugTool.onUpdateComponent(contentDebugID); } else { - ReactInstrumentation.debugTool.onBeforeMountComponent(contentDebugID, content); - ReactInstrumentation.debugTool.onMountComponent(contentDebugID); - ReactInstrumentation.debugTool.onSetChildren(debugID, [contentDebugID]); + ReactInstrumentationDev.debugTool.onBeforeMountComponent(contentDebugID, content); + ReactInstrumentationDev.debugTool.onMountComponent(contentDebugID); + ReactInstrumentationDev.debugTool.onSetChildren(debugID, [contentDebugID]); } }; } @@ -612,10 +612,10 @@ ReactDOMComponent.Mixin = { if (parentInfo) { // parentInfo should always be present except for the top-level // component when server rendering - validateDOMNesting(this._tag, this, parentInfo); + validateDOMNestingDev(this._tag, this, parentInfo); } this._ancestorInfo = - validateDOMNesting.updatedAncestorInfo(parentInfo, this._tag, this); + validateDOMNestingDev.updatedAncestorInfo(parentInfo, this._tag, this); } var mountImage; @@ -715,7 +715,7 @@ ReactDOMComponent.Mixin = { if (__DEV__) { if (this._debugID) { - var callback = () => ReactInstrumentation.debugTool.onComponentHasMounted(this._debugID); + var callback = () => ReactInstrumentationDev.debugTool.onComponentHasMounted(this._debugID); transaction.getReactMountReady().enqueue(callback, this); } } @@ -952,7 +952,7 @@ ReactDOMComponent.Mixin = { if (__DEV__) { if (this._debugID) { - var callback = () => ReactInstrumentation.debugTool.onComponentHasUpdated(this._debugID); + var callback = () => ReactInstrumentationDev.debugTool.onComponentHasUpdated(this._debugID); transaction.getReactMountReady().enqueue(callback, this); } } @@ -1130,7 +1130,7 @@ ReactDOMComponent.Mixin = { } else if (lastHasContentOrHtml && !nextHasContentOrHtml) { this.updateTextContent(''); if (__DEV__) { - ReactInstrumentation.debugTool.onSetChildren(this._debugID, []); + ReactInstrumentationDev.debugTool.onSetChildren(this._debugID, []); } } @@ -1146,7 +1146,7 @@ ReactDOMComponent.Mixin = { this.updateMarkup('' + nextHtml); } if (__DEV__) { - ReactInstrumentation.debugTool.onSetChildren(this._debugID, []); + ReactInstrumentationDev.debugTool.onSetChildren(this._debugID, []); } } else if (nextChildren != null) { if (__DEV__) { diff --git a/src/renderers/dom/shared/ReactDOMContainerInfo.js b/src/renderers/dom/shared/ReactDOMContainerInfo.js index 0f74f733a7acb..dea344080ce6c 100644 --- a/src/renderers/dom/shared/ReactDOMContainerInfo.js +++ b/src/renderers/dom/shared/ReactDOMContainerInfo.js @@ -11,7 +11,7 @@ 'use strict'; -var validateDOMNesting = require('validateDOMNesting'); +var validateDOMNestingDev = require('validateDOMNestingDev'); var DOC_NODE_TYPE = 9; @@ -28,7 +28,7 @@ function ReactDOMContainerInfo(topLevelWrapper, node) { }; if (__DEV__) { info._ancestorInfo = node ? - validateDOMNesting.updatedAncestorInfo(null, info._tag, null) : null; + validateDOMNestingDev.updatedAncestorInfo(null, info._tag, null) : null; } return info; } diff --git a/src/renderers/dom/shared/ReactDOMDebugTool.js b/src/renderers/dom/shared/ReactDOMDebugTool.js deleted file mode 100644 index aae63af9164c9..0000000000000 --- a/src/renderers/dom/shared/ReactDOMDebugTool.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * 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. - * - * @providesModule ReactDOMDebugTool - */ - -'use strict'; - -var ReactDOMNullInputValuePropDevtool = require('ReactDOMNullInputValuePropDevtool'); -var ReactDOMUnknownPropertyDevtool = require('ReactDOMUnknownPropertyDevtool'); -var ReactDebugTool = require('ReactDebugTool'); - -var warning = require('warning'); - -var eventHandlers = []; -var handlerDoesThrowForEvent = {}; - -function emitEvent(handlerFunctionName, arg1, arg2, arg3, arg4, arg5) { - eventHandlers.forEach(function(handler) { - try { - if (handler[handlerFunctionName]) { - handler[handlerFunctionName](arg1, arg2, arg3, arg4, arg5); - } - } catch (e) { - warning( - handlerDoesThrowForEvent[handlerFunctionName], - 'exception thrown by devtool while handling %s: %s', - handlerFunctionName, - e + '\n' + e.stack - ); - handlerDoesThrowForEvent[handlerFunctionName] = true; - } - }); -} - -var ReactDOMDebugTool = { - addDevtool(devtool) { - ReactDebugTool.addDevtool(devtool); - eventHandlers.push(devtool); - }, - removeDevtool(devtool) { - ReactDebugTool.removeDevtool(devtool); - for (var i = 0; i < eventHandlers.length; i++) { - if (eventHandlers[i] === devtool) { - eventHandlers.splice(i, 1); - i--; - } - } - }, - onCreateMarkupForProperty(name, value) { - emitEvent('onCreateMarkupForProperty', name, value); - }, - onSetValueForProperty(node, name, value) { - emitEvent('onSetValueForProperty', node, name, value); - }, - onDeleteValueForProperty(node, name) { - emitEvent('onDeleteValueForProperty', node, name); - }, - onTestEvent() { - emitEvent('onTestEvent'); - }, -}; - -ReactDOMDebugTool.addDevtool(ReactDOMUnknownPropertyDevtool); -ReactDOMDebugTool.addDevtool(ReactDOMNullInputValuePropDevtool); - -module.exports = ReactDOMDebugTool; diff --git a/src/renderers/dom/shared/ReactDOMDebugToolDev.js b/src/renderers/dom/shared/ReactDOMDebugToolDev.js new file mode 100644 index 0000000000000..f13a47f4c57a4 --- /dev/null +++ b/src/renderers/dom/shared/ReactDOMDebugToolDev.js @@ -0,0 +1,76 @@ +/** + * 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. + * + * @providesModule ReactDOMDebugToolDev + */ + +'use strict'; + +var ReactDebugToolDev = require('ReactDebugToolDev'); +var ReactDOMNullInputValuePropDevtoolDev = require('ReactDOMNullInputValuePropDevtoolDev'); +var ReactDOMUnknownPropertyDevtoolDev = require('ReactDOMUnknownPropertyDevtoolDev'); + +var warning = require('warning'); + +var ReactDOMDebugToolDev = {}; + +if (__DEV__) { + var eventHandlers = []; + var handlerDoesThrowForEvent = {}; + + var emitEvent = function(handlerFunctionName, arg1, arg2, arg3, arg4, arg5) { + eventHandlers.forEach(function(handler) { + try { + if (handler[handlerFunctionName]) { + handler[handlerFunctionName](arg1, arg2, arg3, arg4, arg5); + } + } catch (e) { + warning( + handlerDoesThrowForEvent[handlerFunctionName], + 'exception thrown by devtool while handling %s: %s', + handlerFunctionName, + e + '\n' + e.stack + ); + handlerDoesThrowForEvent[handlerFunctionName] = true; + } + }); + }; + + var ReactDOMDebugToolDev = { + addDevtool(devtool) { + ReactDebugToolDev.addDevtool(devtool); + eventHandlers.push(devtool); + }, + removeDevtool(devtool) { + ReactDebugToolDev.removeDevtool(devtool); + for (var i = 0; i < eventHandlers.length; i++) { + if (eventHandlers[i] === devtool) { + eventHandlers.splice(i, 1); + i--; + } + } + }, + onCreateMarkupForProperty(name, value) { + emitEvent('onCreateMarkupForProperty', name, value); + }, + onSetValueForProperty(node, name, value) { + emitEvent('onSetValueForProperty', node, name, value); + }, + onDeleteValueForProperty(node, name) { + emitEvent('onDeleteValueForProperty', node, name); + }, + onTestEvent() { + emitEvent('onTestEvent'); + }, + }; + + ReactDOMDebugToolDev.addDevtool(ReactDOMUnknownPropertyDevtoolDev); + ReactDOMDebugToolDev.addDevtool(ReactDOMNullInputValuePropDevtoolDev); +} + +module.exports = ReactDOMDebugToolDev; diff --git a/src/renderers/dom/shared/ReactDOMInstrumentation.js b/src/renderers/dom/shared/ReactDOMInstrumentationDev.js similarity index 61% rename from src/renderers/dom/shared/ReactDOMInstrumentation.js rename to src/renderers/dom/shared/ReactDOMInstrumentationDev.js index 4f31a7818dd36..0ddae9e81d7c9 100644 --- a/src/renderers/dom/shared/ReactDOMInstrumentation.js +++ b/src/renderers/dom/shared/ReactDOMInstrumentationDev.js @@ -6,16 +6,11 @@ * 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. * - * @providesModule ReactDOMInstrumentation + * @providesModule ReactDOMInstrumentationDev */ 'use strict'; -var debugTool = null; +var ReactDOMDebugToolDev = require('ReactDOMDebugToolDev'); -if (__DEV__) { - var ReactDOMDebugTool = require('ReactDOMDebugTool'); - debugTool = ReactDOMDebugTool; -} - -module.exports = {debugTool}; +module.exports = {debugTool: ReactDOMDebugToolDev}; diff --git a/src/renderers/dom/shared/ReactDOMTextComponent.js b/src/renderers/dom/shared/ReactDOMTextComponent.js index 95eacc9c2c06a..8ae952073352e 100644 --- a/src/renderers/dom/shared/ReactDOMTextComponent.js +++ b/src/renderers/dom/shared/ReactDOMTextComponent.js @@ -14,11 +14,11 @@ var DOMChildrenOperations = require('DOMChildrenOperations'); var DOMLazyTree = require('DOMLazyTree'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); -var ReactInstrumentation = require('ReactInstrumentation'); +var ReactInstrumentationDev = require('ReactInstrumentationDev'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); var invariant = require('invariant'); -var validateDOMNesting = require('validateDOMNesting'); +var validateDOMNestingDev = require('validateDOMNestingDev'); /** * Text nodes violate a couple assumptions that React makes about components: @@ -67,7 +67,7 @@ Object.assign(ReactDOMTextComponent.prototype, { context ) { if (__DEV__) { - ReactInstrumentation.debugTool.onSetText(this._debugID, this._stringText); + ReactInstrumentationDev.debugTool.onSetText(this._debugID, this._stringText); var parentInfo; if (hostParent != null) { @@ -78,7 +78,7 @@ Object.assign(ReactDOMTextComponent.prototype, { if (parentInfo) { // parentInfo should always be present except for the top-level // component when server rendering - validateDOMNesting('#text', this, parentInfo); + validateDOMNestingDev('#text', this, parentInfo); } } @@ -144,7 +144,7 @@ Object.assign(ReactDOMTextComponent.prototype, { ); if (__DEV__) { - ReactInstrumentation.debugTool.onSetText( + ReactInstrumentationDev.debugTool.onSetText( this._debugID, nextStringText ); diff --git a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js index be0b0269ab3d1..f03fdc5521677 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js @@ -736,7 +736,7 @@ describe('ReactDOMComponent', function() { }); it('should work error event on element', function() { - spyOn(console, 'error'); + spyOn(console, 'error'); var container = document.createElement('div'); ReactDOM.render(