From 66735151ed97d44f0f4533e5df13ece7afa64e84 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Fri, 2 Aug 2019 10:44:24 -0700 Subject: [PATCH 01/33] initial babel --- package.json | 1 + .../babel/__tests__/fixtures/basic/input.js | 1 + .../babel/__tests__/fixtures/basic/output.js | 1 + .../__tests__/transform-react-jsx-test.js | 50 ++++ scripts/babel/transform-react-to-jsx.js | 273 ++++++++++++++++++ yarn.lock | 12 + 6 files changed, 338 insertions(+) create mode 100644 scripts/babel/__tests__/fixtures/basic/input.js create mode 100644 scripts/babel/__tests__/fixtures/basic/output.js create mode 100644 scripts/babel/__tests__/transform-react-jsx-test.js create mode 100644 scripts/babel/transform-react-to-jsx.js diff --git a/package.json b/package.json index 13b23b070a0bb..1c0176dcb4b59 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "packages/*" ], "devDependencies": { + "@babel/plugin-syntax-jsx": "^7.2.0", "@mattiasbuelens/web-streams-polyfill": "0.1.0", "art": "^0.10.1", "babel-cli": "^6.6.5", diff --git a/scripts/babel/__tests__/fixtures/basic/input.js b/scripts/babel/__tests__/fixtures/basic/input.js new file mode 100644 index 0000000000000..5818db6bc16b8 --- /dev/null +++ b/scripts/babel/__tests__/fixtures/basic/input.js @@ -0,0 +1 @@ +const a =
; diff --git a/scripts/babel/__tests__/fixtures/basic/output.js b/scripts/babel/__tests__/fixtures/basic/output.js new file mode 100644 index 0000000000000..81719c3db0fc0 --- /dev/null +++ b/scripts/babel/__tests__/fixtures/basic/output.js @@ -0,0 +1 @@ +const a = React.createElement('div', {className: 'foo'}); diff --git a/scripts/babel/__tests__/transform-react-jsx-test.js b/scripts/babel/__tests__/transform-react-jsx-test.js new file mode 100644 index 0000000000000..472bf93d3b818 --- /dev/null +++ b/scripts/babel/__tests__/transform-react-jsx-test.js @@ -0,0 +1,50 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* eslint-disable quotes */ +'use strict'; + +const babel = require('babel-core'); +// const transformReactJSX = require('../transform-react-jsx'); +const fs = require('fs'); +const path = require('path'); + +function transform(input, isDev) { + return babel.transform(input, { + plugins: [ + [ + './scripts/babel/transform-react-to-jsx', + { + module: 'bluebird', + method: 'coroutine', + }, + ], + ], + }).code; +} + +function compare(input, output) { + const compiled = transform(input); + expect(compiled).toEqual(output); +} + +const TEST_DIR = './scripts/babel/__tests__/fixtures'; +function makeTests() { + fs.readdirSync(TEST_DIR).forEach(filename => { + const testLoc = path.join(TEST_DIR, filename); + const inputLoc = path.join(testLoc, 'input.js'); + const outputLoc = path.join(testLoc, 'output.js'); + const input = fs.readFileSync(inputLoc, 'utf8'); + const output = fs.readFileSync(outputLoc, 'utf8'); + it(filename, () => { + compare(input, output); + }); + }); +} + +describe('transform react to jsx', () => { + makeTests(); +}); diff --git a/scripts/babel/transform-react-to-jsx.js b/scripts/babel/transform-react-to-jsx.js new file mode 100644 index 0000000000000..2993eaa55f7b0 --- /dev/null +++ b/scripts/babel/transform-react-to-jsx.js @@ -0,0 +1,273 @@ +'use strict'; + +const t = require('@babel/types'); +const esutils = require('esutils'); +const jsx = require('@babel/plugin-syntax-jsx'); + +function helper(opts) { + console.log(jsx); + const visitor = {}; + + visitor.JSXSpreadChild = function(path) { + throw path.buildCodeFrameError( + 'Spread children are not supported in React.' + ); + }; + + visitor.JSXElement = { + exit(path, file) { + const callExpr = buildElementCall(path, file); + if (callExpr) { + path.replaceWith(t.inherits(callExpr, path.node)); + } + }, + }; + + visitor.JSXFragment = { + exit(path, file) { + if (opts.compat) { + throw path.buildCodeFrameError( + 'Fragment tags are only supported in React 16 and up.' + ); + } + const callExpr = buildFragmentCall(path, file); + if (callExpr) { + path.replaceWith(t.inherits(callExpr, path.node)); + } + }, + }; + + return visitor; + + function convertJSXIdentifier(node, parent) { + if (t.isJSXIdentifier(node)) { + if (node.name === 'this' && t.isReferenced(node, parent)) { + return t.thisExpression(); + } else if (esutils.keyword.isIdentifierNameES6(node.name)) { + node.type = 'Identifier'; + } else { + return t.stringLiteral(node.name); + } + } else if (t.isJSXMemberExpression(node)) { + return t.memberExpression( + convertJSXIdentifier(node.object, node), + convertJSXIdentifier(node.property, node) + ); + } + + return node; + } + + function convertAttributeValue(node) { + if (t.isJSXExpressionContainer(node)) { + return node.expression; + } else { + return node; + } + } + + function convertAttribute(node) { + const value = convertAttributeValue(node.value || t.booleanLiteral(true)); + + if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) { + value.value = value.value.replace(/\n\s+/g, ' '); + + // "raw" JSXText should not be used from a StringLiteral because it needs to be escaped. + if (value.extra && value.extra.raw) { + delete value.extra.raw; + } + } + + if (t.isJSXNamespacedName(node.name)) { + node.name = t.stringLiteral( + node.name.namespace.name + ':' + node.name.name.name + ); + } else if (esutils.keyword.isIdentifierNameES6(node.name.name)) { + node.name.type = 'Identifier'; + } else { + node.name = t.stringLiteral(node.name.name); + } + + return t.inherits(t.objectProperty(node.name, value), node); + } + + // //
should use React.createElement + // function isPropsSpreadFollowedByKey(node) { + + // } + + function buildElementCall(path, file) { + if (opts.filter && !opts.filter(path.node, file)) return; + + const openingPath = path.get('openingElement'); + openingPath.parent.children = t.react.buildChildren(openingPath.parent); + + const tagExpr = convertJSXIdentifier( + openingPath.node.name, + openingPath.node + ); + const args = []; + + let tagName; + if (t.isIdentifier(tagExpr)) { + tagName = tagExpr.name; + } else if (t.isLiteral(tagExpr)) { + tagName = tagExpr.value; + } + + const state = { + tagExpr: tagExpr, + tagName: tagName, + args: args, + }; + + if (opts.pre) { + opts.pre(state, file); + } + + let attribs = openingPath.node.attributes; + if (attribs.length) { + attribs = buildOpeningElementAttributes(attribs, file); + } else { + attribs = t.nullLiteral(); + } + + args.push(attribs, ...path.node.children); + + if (opts.post) { + opts.post(state, file); + } + + return state.call || t.callExpression(state.callee, args); + } + + function pushProps(_props, objs) { + if (!_props.length) return _props; + + objs.push(t.objectExpression(_props)); + return []; + } + + /** + * The logic for this is quite terse. It's because we need to + * support spread elements. We loop over all attributes, + * breaking on spreads, we then push a new object containing + * all prior attributes to an array for later processing. + */ + + function buildOpeningElementAttributes(attribs, file) { + let _props = []; + const objs = []; + + const useBuiltIns = file.opts.useBuiltIns || false; + if (typeof useBuiltIns !== 'boolean') { + throw new Error( + 'transform-react-jsx currently only accepts a boolean option for ' + + 'useBuiltIns (defaults to false)' + ); + } + + while (attribs.length) { + const prop = attribs.shift(); + if (t.isJSXSpreadAttribute(prop)) { + _props = pushProps(_props, objs); + objs.push(prop.argument); + } else { + _props.push(convertAttribute(prop)); + } + } + + pushProps(_props, objs); + + if (objs.length === 1) { + // only one object + attribs = objs[0]; + } else { + // looks like we have multiple objects + if (!t.isObjectExpression(objs[0])) { + objs.unshift(t.objectExpression([])); + } + + const expressionHelper = useBuiltIns + ? t.memberExpression(t.identifier('Object'), t.identifier('assign')) + : file.addHelper('extends'); + + // spread it + attribs = t.callExpression(expressionHelper, objs); + } + + return attribs; + } + + function buildFragmentCall(path, file) { + if (opts.filter && !opts.filter(path.node, file)) return; + + const openingPath = path.get('openingElement'); + openingPath.parent.children = t.react.buildChildren(openingPath.parent); + + const args = []; + const tagName = null; + const tagExpr = file.get('jsxFragIdentifier')(); + + const state = { + tagExpr: tagExpr, + tagName: tagName, + args: args, + }; + + if (opts.pre) { + opts.pre(state, file); + } + + // no attributes are allowed with <> syntax + args.push(t.nullLiteral(), ...path.node.children); + + if (opts.post) { + opts.post(state, file); + } + + file.set('usedFragment', true); + return state.call || t.callExpression(state.callee, args); + } +} + +module.exports = function(babel, options) { + // const NEW_PRAGMA = options.development ? 'React.jsx' : 'React.jsxDEV'; + const NEW_PRAGMA = 'React.jsx'; + const OLD_PRAGMA = 'React.createElement'; + + const createIdentifierParser = id => () => { + return id + .split('.') + .map(name => t.identifier(name)) + .reduce((object, property) => t.memberExpression(object, property)); + }; + + const visitor = helper({ + pre(state) { + const tagName = state.tagName; + const args = state.args; + if (t.react.isCompatTag(tagName)) { + args.push(t.stringLiteral(tagName)); + } else { + args.push(state.tagExpr); + } + }, + + post(state, pass) { + state.callee = pass.get('jsxIdentifier')(); + }, + }); + + visitor.JSXAttribute = function(path) { + if (t.isJSXElement(path.node.value)) { + path.node.value = t.v(path.node.value); + } + }; + + return { + name: 'transform-react-jsx', + inherits: jsx, + visitor, + }; +}; diff --git a/yarn.lock b/yarn.lock index 6605e61fa9cdb..1a457be41e621 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,6 +38,11 @@ dependencies: "@babel/types" "^7.0.0" +"@babel/helper-plugin-utils@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" + integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== + "@babel/helper-split-export-declaration@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813" @@ -64,6 +69,13 @@ version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.0.tgz#a7cd42cb3c12aec52e24375189a47b39759b783e" +"@babel/plugin-syntax-jsx@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz#0b85a3b4bc7cdf4cc4b8bf236335b907ca22e7c7" + integrity sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/template@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.0.tgz#58cc9572e1bfe24fe1537fdf99d839d53e517e22" From bef39280a4d636ba000322da2b5d8fed9615ac1c Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Fri, 9 Aug 2019 13:05:06 -0700 Subject: [PATCH 02/33] added more stuff --- .../react-jsx-babel-plugin}/__tests__/transform-react-jsx-test.js | 0 .../react-jsx-babel-plugin}/transform-react-to-jsx.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {scripts/babel => packages/react-jsx-babel-plugin}/__tests__/transform-react-jsx-test.js (100%) rename {scripts/babel => packages/react-jsx-babel-plugin}/transform-react-to-jsx.js (100%) diff --git a/scripts/babel/__tests__/transform-react-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-test.js similarity index 100% rename from scripts/babel/__tests__/transform-react-jsx-test.js rename to packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-test.js diff --git a/scripts/babel/transform-react-to-jsx.js b/packages/react-jsx-babel-plugin/transform-react-to-jsx.js similarity index 100% rename from scripts/babel/transform-react-to-jsx.js rename to packages/react-jsx-babel-plugin/transform-react-to-jsx.js From d773b2b229403c8fac22b944ec30f9c0023fb8b5 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Fri, 9 Aug 2019 16:59:57 -0700 Subject: [PATCH 03/33] wrote the tests/snapshots --- babel.config.js | 2 +- .../transform-react-jsx-test.js.snap | 232 ++++++++++ .../__tests__/transform-react-jsx-test.js | 417 +++++++++++++++++- .../transform-react-to-jsx.js | 71 ++- .../babel/__tests__/fixtures/basic/input.js | 1 - .../babel/__tests__/fixtures/basic/output.js | 1 - 6 files changed, 682 insertions(+), 42 deletions(-) create mode 100644 packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-test.js.snap delete mode 100644 scripts/babel/__tests__/fixtures/basic/input.js delete mode 100644 scripts/babel/__tests__/fixtures/basic/output.js diff --git a/babel.config.js b/babel.config.js index d4d1e3213c574..30114aab44568 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,7 +3,7 @@ module.exports = { plugins: [ '@babel/plugin-syntax-jsx', - '@babel/plugin-transform-react-jsx', + // '@babel/plugin-transform-react-jsx', '@babel/plugin-transform-flow-strip-types', ['@babel/plugin-proposal-class-properties', {loose: true}], 'syntax-trailing-function-commas', diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-test.js.snap new file mode 100644 index 0000000000000..cdb9eebd249d8 --- /dev/null +++ b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-test.js.snap @@ -0,0 +1,232 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = ` +"React.createElement(Component, Object.assign({}, props, { + sound: \\"moo\\" +}));" +`; + +exports[`transform react to jsx arrow functions 1`] = ` +"var foo = function () { + var _this = this; + + return function () { + return React.createElement(_this, null); + }; +}; + +var bar = function () { + var _this2 = this; + + return function () { + return React.createElement(_this2.foo, null); + }; +};" +`; + +exports[`transform react to jsx assignment 1`] = ` +"var div = React.createElement(Component, Object.assign({}, props, { + foo: \\"bar\\" +}));" +`; + +exports[`transform react to jsx concatenates adjacent string literals 1`] = `"var x = React.createElement(\\"div\\", null, \\"foo\\", \\"bar\\", \\"baz\\", React.createElement(\\"div\\", null, \\"buz bang\\"), \\"qux\\", null, \\"quack\\");"`; + +exports[`transform react to jsx display name assignment expression 1`] = ` +"var Component; +Component = React.createClass({ + render: function render() { + return null; + } +});" +`; + +exports[`transform react to jsx display name export default 1`] = ` +"export default React.createClass({ + render: function render() { + return null; + } +});" +`; + +exports[`transform react to jsx display name if missing 1`] = ` +"var Whateva = React.createClass({ + displayName: \\"Whatever\\", + render: function render() { + return null; + } +}); +var Bar = React.createClass({ + \\"displayName\\": \\"Ba\\", + render: function render() { + return null; + } +});" +`; + +exports[`transform react to jsx display name object declaration 1`] = ` +"exports = { + Component: React.createClass({ + render: function render() { + return null; + } + }) +};" +`; + +exports[`transform react to jsx display name property assignment 1`] = ` +"exports.Component = React.createClass({ + render: function render() { + return null; + } +});" +`; + +exports[`transform react to jsx display name variable declaration 1`] = ` +"var Component = React.createClass({ + render: function render() { + return null; + } +});" +`; + +exports[`transform react to jsx should allow constructor as prop 1`] = ` +"React.createElement(Component, { + constructor: \\"foo\\" +});" +`; + +exports[`transform react to jsx should allow deeper js namespacing 1`] = `"React.createElement(Namespace.DeepNamespace.Component, null);"`; + +exports[`transform react to jsx should allow elements as attributes 1`] = ` +"React.createElement(\\"div\\", { + attr: React.createElement(\\"div\\", null) +});" +`; + +exports[`transform react to jsx should allow js namespacing 1`] = `"React.createElement(Namespace.Component, null);"`; + +exports[`transform react to jsx should allow nested fragments 1`] = `"React.createElement(\\"div\\", null, React.createElement(React.Fragment, null, React.createElement(React.Fragment, null, React.createElement(\\"span\\", null, \\"Hello\\"), React.createElement(\\"span\\", null, \\"world\\")), React.createElement(React.Fragment, null, React.createElement(\\"span\\", null, \\"Goodbye\\"), React.createElement(\\"span\\", null, \\"world\\"))));"`; + +exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = ` +"var x = React.createElement(\\"div\\", null, React.createElement(Component, null)); +var x = React.createElement(\\"div\\", null, props.children); +var x = React.createElement(Composite, null, props.children); +var x = React.createElement(Composite, null, React.createElement(Composite2, null));" +`; + +exports[`transform react to jsx should convert simple tags 1`] = `"var x = React.createElement(\\"div\\", null);"`; + +exports[`transform react to jsx should convert simple text 1`] = `"var x = React.createElement(\\"div\\", null, \\"text\\");"`; + +exports[`transform react to jsx should escape xhtml jsxattribute 1`] = ` +"React.createElement(\\"div\\", { + id: \\"w\\\\xF4w\\" +}); +React.createElement(\\"div\\", { + id: \\"w\\" +}); +React.createElement(\\"div\\", { + id: \\"w < w\\" +});" +`; + +exports[`transform react to jsx should escape xhtml jsxtext 1`] = ` +"React.createElement(\\"div\\", null, \\"wow\\"); +React.createElement(\\"div\\", null, \\"w\\\\xF4w\\"); +React.createElement(\\"div\\", null, \\"w & w\\"); +React.createElement(\\"div\\", null, \\"w & w\\"); +React.createElement(\\"div\\", null, \\"w \\\\xA0 w\\"); +React.createElement(\\"div\\", null, \\"this should not parse as unicode: \\\\xA0\\"); +React.createElement(\\"div\\", null, \\"this should parse as nbsp: \\\\xA0 \\"); +React.createElement(\\"div\\", null, \\"this should parse as unicode: \\", '  '); +React.createElement(\\"div\\", null, \\"w < w\\");" +`; + +exports[`transform react to jsx should handle attributed elements 1`] = ` +"var HelloMessage = React.createClass({ + render: function () { + return React.createElement(\\"div\\", null, \\"Hello \\", this.props.name); + } +}); +React.render(React.createElement(HelloMessage, { + name: React.createElement(\\"span\\", null, \\"Sebastian\\") +}), mountNode);" +`; + +exports[`transform react to jsx should handle has own property correctly 1`] = `"React.createElement(\\"hasOwnProperty\\", null, \\"testing\\");"`; + +exports[`transform react to jsx should have correct comma in nested children 1`] = `"var x = React.createElement(\\"div\\", null, React.createElement(\\"div\\", null, React.createElement(\\"br\\", null)), React.createElement(Component, null, foo, React.createElement(\\"br\\", null), bar), React.createElement(\\"br\\", null));"`; + +exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = ` +"var x = React.createElement(\\"div\\", { + attr1: \\"foo\\" + \\"bar\\", + attr2: \\"foo\\" + \\"bar\\" + \\"baz\\" + \\"bug\\", + attr3: \\"foo\\" + \\"bar\\" + \\"baz\\" + \\"bug\\", + attr4: \\"baz\\" +});" +`; + +exports[`transform react to jsx should not add quotes to identifier names 1`] = ` +"var e = React.createElement(F, { + aaa: true, + new: true, + const: true, + var: true, + default: true, + \\"foo-bar\\": true +});" +`; + +exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `"React.createElement(\\"div\\", null, \\"\\\\xA0 \\");"`; + +exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `"React.createElement(\\"div\\", null, \\"\\\\xA0\\");"`; + +exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `"var x = React.createElement(\\"div\\", null, React.createElement(\\"span\\", null), React.createElement(\\"br\\", null));"`; + +exports[`transform react to jsx should properly handle comments between props 1`] = ` +"var x = React.createElement(\\"div\\", { + /* a multi-line + comment */ + attr1: \\"foo\\" +}, React.createElement(\\"span\\", { + // a double-slash comment + attr2: \\"bar\\" +}));" +`; + +exports[`transform react to jsx should quote jsx attributes 1`] = ` +"React.createElement(\\"button\\", { + \\"data-value\\": \\"a value\\" +}, \\"Button\\");" +`; + +exports[`transform react to jsx should support xml namespaces if flag 1`] = ` +"React.createElement(\\"f:image\\", { + \\"n:attr\\": true +});" +`; + +exports[`transform react to jsx should transform known hyphenated tags 1`] = `"React.createElement(\\"font-face\\", null);"`; + +exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = ` +"React.createElement(Component, Object.assign({}, x, { + y: 2, + z: true +}));" +`; + +exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = ` +"React.createElement(Component, Object.assign({ + y: 2, + z: true +}, x));" +`; + +exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = ` +"React.createElement(Component, Object.assign({ + y: 2 +}, x, { + z: true +}));" +`; diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-test.js index 472bf93d3b818..afbc65a32c087 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-test.js @@ -7,44 +7,415 @@ /* eslint-disable quotes */ 'use strict'; -const babel = require('babel-core'); +const babel = require('@babel/core'); // const transformReactJSX = require('../transform-react-jsx'); -const fs = require('fs'); -const path = require('path'); +const codeFrame = require('@babel/code-frame'); -function transform(input, isDev) { +function transform(input, options) { return babel.transform(input, { plugins: [ [ - './scripts/babel/transform-react-to-jsx', + './packages/react-jsx-babel-plugin/transform-react-to-jsx', + // '@babel/plugin-transform-react-jsx', { module: 'bluebird', method: 'coroutine', + development: __DEV__, + useBuiltIns: true, + ...options, }, ], ], }).code; } -function compare(input, output) { - const compiled = transform(input); - expect(compiled).toEqual(output); -} +// function compare(input, output) { +// const compiled = transform(input); +// expect(compiled).toEqual(output); +// } -const TEST_DIR = './scripts/babel/__tests__/fixtures'; -function makeTests() { - fs.readdirSync(TEST_DIR).forEach(filename => { - const testLoc = path.join(TEST_DIR, filename); - const inputLoc = path.join(testLoc, 'input.js'); - const outputLoc = path.join(testLoc, 'output.js'); - const input = fs.readFileSync(inputLoc, 'utf8'); - const output = fs.readFileSync(outputLoc, 'utf8'); - it(filename, () => { - compare(input, output); - }); - }); -} +// const TEST_DIR = './packages/react-jsx-babel-plugin/__tests__/fixtures'; +// function makeTests() { +// fs.readdirSync(TEST_DIR).forEach(filename => { +// const testLoc = path.join(TEST_DIR, filename); +// const inputLoc = path.join(testLoc, 'input.js'); +// const outputLoc = path.join(testLoc, 'output.js'); +// const input = fs.readFileSync(inputLoc, 'utf8'); +// const output = fs.readFileSync(outputLoc, 'utf8'); +// it(filename, () => { +// compare(input, output); +// }); +// }); +// } describe('transform react to jsx', () => { - makeTests(); + it('should properly handle comments adjacent to children', () => { + expect( + transform(` + var x = ( +
+ {/* A comment at the beginning */} + {/* A second comment at the beginning */} + + {/* A nested comment */} + + {/* A sandwiched comment */} +
+ {/* A comment at the end */} + {/* A second comment at the end */} +
+ ); + `) + ).toMatchSnapshot(); + }); + it('adds appropriate new lines when using spread attribute', () => { + expect(transform(``)).toMatchSnapshot(); + }); + it('arrow functions', () => { + expect( + transform(` + var foo = function () { + return () => ; + }; + + var bar = function () { + return () => ; + }; + + `) + ).toMatchSnapshot(); + }); + it('assignment', () => { + expect( + transform(`var div = `) + ).toMatchSnapshot(); + }); + it('concatenates adjacent string literals', () => { + expect( + transform(` + var x = +
+ foo + {"bar"} + baz +
+ buz + bang +
+ qux + {null} + quack +
+ `) + ).toMatchSnapshot(); + }); + it('display name assignment expression', () => { + expect( + transform(` + var Component; + Component = React.createClass({ + render: function render() { + return null; + } + }); + `) + ).toMatchSnapshot(); + }); + it('display name export default', () => { + expect( + transform(` + export default React.createClass({ + render: function render() { + return null; + } + }); + `) + ).toMatchSnapshot(); + }); + it('display name if missing', () => { + expect( + transform(` + var Whateva = React.createClass({ + displayName: "Whatever", + render: function render() { + return null; + } + }); + + var Bar = React.createClass({ + "displayName": "Ba", + render: function render() { + return null; + } + }); + `) + ).toMatchSnapshot(); + }); + it('display name object declaration', () => { + expect( + transform(` + exports = { + Component: React.createClass({ + render: function render() { + return null; + } + }) + }; + `) + ).toMatchSnapshot(); + }); + it('display name property assignment', () => { + expect( + transform(` + exports.Component = React.createClass({ + render: function render() { + return null; + } + }); + `) + ).toMatchSnapshot(); + }); + it('display name variable declaration', () => { + expect( + transform(` + var Component = React.createClass({ + render: function render() { + return null; + } + }); + `) + ).toMatchSnapshot(); + }); + it('should allow constructor as prop', () => { + expect(transform(`;`)).toMatchSnapshot(); + }); + it('should allow deeper js namespacing', () => { + expect( + transform(`;`) + ).toMatchSnapshot(); + }); + it('should allow elements as attributes', () => { + expect(transform(`
/>`)).toMatchSnapshot(); + }); + it('should allow js namespacing', () => { + expect(transform(`;`)).toMatchSnapshot(); + }); + it('should allow nested fragments', () => { + expect( + transform(` +
+ < > + <> + Hello + world + + <> + Goodbye + world + + +
+ `) + ).toMatchSnapshot(); + }); + it('should avoid wrapping in extra parens if not needed', () => { + expect( + transform(` + var x =
+ +
; + + var x =
+ {props.children} +
; + + var x = + {props.children} + ; + + var x = + + ; + `) + ).toMatchSnapshot(); + }); + it('should convert simple tags', () => { + expect(transform(`var x =
;`)).toMatchSnapshot(); + }); + it('should convert simple text', () => { + expect(transform(`var x =
text
;`)).toMatchSnapshot(); + }); + it('should disallow spread children', () => { + let _error; + const code = `
{...children}
;`; + try { + transform(code); + } catch (error) { + _error = error; + } + expect(_error).toEqual( + new SyntaxError( + 'undefined: Spread children are not supported in React.' + + '\n' + + codeFrame.codeFrameColumns( + code, + {start: {line: 1, column: 6}}, + {highlightCode: true} + ) + ) + ); + }); + it('should escape xhtml jsxattribute', () => { + expect( + transform(` +
; +
; +
; + `) + ).toMatchSnapshot(); + }); + it('should escape xhtml jsxtext', () => { + expect( + transform(` +
wow
; +
wôw
; + +
w & w
; +
w & w
; + +
w   w
; +
this should not parse as unicode: \u00a0
; +
this should parse as nbsp:  
; +
this should parse as unicode: {'\u00a0 '}
; + +
w < w
; + `) + ).toMatchSnapshot(); + }); + it('should handle attributed elements', () => { + expect( + transform(` + var HelloMessage = React.createClass({ + render: function() { + return
Hello {this.props.name}
; + } + }); + + React.render( + Sebastian + + } />, mountNode); + `) + ).toMatchSnapshot(); + }); + it('should handle has own property correctly', () => { + expect( + transform(`testing;`) + ).toMatchSnapshot(); + }); + it('should have correct comma in nested children', () => { + expect( + transform(` + var x =
+

+ {foo}
{bar}
+
+
; + `) + ).toMatchSnapshot(); + }); + it('should insert commas after expressions before whitespace', () => { + expect( + transform(` + var x = +
+
+ `) + ).toMatchSnapshot(); + }); + it('should not add quotes to identifier names', () => { + expect( + transform(`var e = ;`) + ).toMatchSnapshot(); + }); + it('should not strip nbsp even couple with other whitespace', () => { + expect(transform(`
 
;`)).toMatchSnapshot(); + }); + it('should not strip tags with a single child of nbsp', () => { + expect(transform(`
 
;`)).toMatchSnapshot(); + }); + it('should properly handle comments between props', () => { + expect( + transform(` + var x = ( +
+ +
+ ); + `) + ).toMatchSnapshot(); + }); + it('should quote jsx attributes', () => { + expect( + transform(``) + ).toMatchSnapshot(); + }); + it('should support xml namespaces if flag', () => { + expect( + transform('', {throwIfNamespace: false}) + ).toMatchSnapshot(); + }); + it('should throw error namespaces if not flag', () => { + let _error; + const code = ``; + try { + transform(code); + } catch (error) { + _error = error; + } + expect(_error).toEqual( + new SyntaxError( + "undefined: Namespace tags are not supported by default. React's " + + "JSX doesn't support namespace tags. You can turn on the " + + "'throwIfNamespace' flag to bypass this warning." + + '\n' + + codeFrame.codeFrameColumns( + code, + {start: {line: 1, column: 2}}, + {highlightCode: true} + ) + ) + ); + }); + it('should transform known hyphenated tags', () => { + expect(transform(``)).toMatchSnapshot(); + }); + it('wraps props in react spread for first spread attributes', () => { + expect(transform(``)).toMatchSnapshot(); + }); + it('wraps props in react spread for last spread attributes', () => { + expect(transform(``)).toMatchSnapshot(); + }); + it('wraps props in react spread for middle spread attributes', () => { + expect(transform(``)).toMatchSnapshot(); + }); }); diff --git a/packages/react-jsx-babel-plugin/transform-react-to-jsx.js b/packages/react-jsx-babel-plugin/transform-react-to-jsx.js index 2993eaa55f7b0..aac9013e39407 100644 --- a/packages/react-jsx-babel-plugin/transform-react-to-jsx.js +++ b/packages/react-jsx-babel-plugin/transform-react-to-jsx.js @@ -2,19 +2,34 @@ const t = require('@babel/types'); const esutils = require('esutils'); -const jsx = require('@babel/plugin-syntax-jsx'); +// const jsx = '@babel/plugin-syntax-jsx'; function helper(opts) { - console.log(jsx); const visitor = {}; + visitor.JSXNamespacedName = function(path, state) { + const throwIfNamespace = + state.opts.throwIfNamespace === undefined + ? true + : !!state.opts.throwIfNamespace; + if (throwIfNamespace) { + throw path.buildCodeFrameError( + `Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \ +You can turn on the 'throwIfNamespace' flag to bypass this warning.`, + ); + } + }; + visitor.JSXSpreadChild = function(path) { throw path.buildCodeFrameError( - 'Spread children are not supported in React.' + 'Spread children are not supported in React.', ); }; visitor.JSXElement = { + enter(path, state) { + // console.log(path, state); + }, exit(path, file) { const callExpr = buildElementCall(path, file); if (callExpr) { @@ -27,7 +42,7 @@ function helper(opts) { exit(path, file) { if (opts.compat) { throw path.buildCodeFrameError( - 'Fragment tags are only supported in React 16 and up.' + 'Fragment tags are only supported in React 16 and up.', ); } const callExpr = buildFragmentCall(path, file); @@ -36,7 +51,6 @@ function helper(opts) { } }, }; - return visitor; function convertJSXIdentifier(node, parent) { @@ -51,8 +65,14 @@ function helper(opts) { } else if (t.isJSXMemberExpression(node)) { return t.memberExpression( convertJSXIdentifier(node.object, node), - convertJSXIdentifier(node.property, node) + convertJSXIdentifier(node.property, node), ); + } else if (t.isJSXNamespacedName(node)) { + /** + * If there is flag "throwIfNamespace" + * print XMLNamespace like string literal + */ + return t.stringLiteral(`${node.namespace.name}:${node.name.name}`); } return node; @@ -80,7 +100,7 @@ function helper(opts) { if (t.isJSXNamespacedName(node.name)) { node.name = t.stringLiteral( - node.name.namespace.name + ':' + node.name.name.name + node.name.namespace.name + ':' + node.name.name.name, ); } else if (esutils.keyword.isIdentifierNameES6(node.name.name)) { node.name.type = 'Identifier'; @@ -104,7 +124,7 @@ function helper(opts) { const tagExpr = convertJSXIdentifier( openingPath.node.name, - openingPath.node + openingPath.node, ); const args = []; @@ -163,7 +183,7 @@ function helper(opts) { if (typeof useBuiltIns !== 'boolean') { throw new Error( 'transform-react-jsx currently only accepts a boolean option for ' + - 'useBuiltIns (defaults to false)' + 'useBuiltIns (defaults to false)', ); } @@ -231,11 +251,7 @@ function helper(opts) { } } -module.exports = function(babel, options) { - // const NEW_PRAGMA = options.development ? 'React.jsx' : 'React.jsxDEV'; - const NEW_PRAGMA = 'React.jsx'; - const OLD_PRAGMA = 'React.createElement'; - +module.exports = function(babel) { const createIdentifierParser = id => () => { return id .split('.') @@ -259,15 +275,38 @@ module.exports = function(babel, options) { }, }); + visitor.Program = { + enter(path, state) { + const pragma = state.opts.development ? 'React.jsxDEV' : 'React.jsx'; + const pragmaFrag = 'React.Fragment'; + state.set('jsxIdentifier', createIdentifierParser(pragma)); + state.set('jsxFragIdentifier', createIdentifierParser(pragmaFrag)); + state.set('usedFragment', false); + state.set('pragmaSet', true); + state.set('pragmaFragSet', true); + }, + exit(path, state) { + if ( + state.get('pragmaSet') && + state.get('usedFragment') && + !state.get('pragmaFragSet') + ) { + throw new Error( + 'transform-react-jsx: pragma has been set but ' + + 'pragmafrag has not been set', + ); + } + }, + }; + visitor.JSXAttribute = function(path) { if (t.isJSXElement(path.node.value)) { - path.node.value = t.v(path.node.value); + path.node.value = t.jsxExpressionContainer(path.node.value); } }; return { name: 'transform-react-jsx', - inherits: jsx, visitor, }; }; diff --git a/scripts/babel/__tests__/fixtures/basic/input.js b/scripts/babel/__tests__/fixtures/basic/input.js deleted file mode 100644 index 5818db6bc16b8..0000000000000 --- a/scripts/babel/__tests__/fixtures/basic/input.js +++ /dev/null @@ -1 +0,0 @@ -const a =
; diff --git a/scripts/babel/__tests__/fixtures/basic/output.js b/scripts/babel/__tests__/fixtures/basic/output.js deleted file mode 100644 index 81719c3db0fc0..0000000000000 --- a/scripts/babel/__tests__/fixtures/basic/output.js +++ /dev/null @@ -1 +0,0 @@ -const a = React.createElement('div', {className: 'foo'}); From d9450ab16162da038a3dd50c7163c43bb6c2bb30 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Mon, 12 Aug 2019 18:16:28 -0700 Subject: [PATCH 04/33] wrote plugin and added tests --- ...form-react-jsx-createElement-test.js.snap} | 0 .../transform-react-jsx-jsx-test.js.snap | 344 ++++++++++++++ ...transform-react-jsx-createElement-test.js} | 21 +- .../__tests__/transform-react-jsx-jsx-test.js | 432 ++++++++++++++++++ .../transform-react-to-jsx.js | 171 +++++-- 5 files changed, 920 insertions(+), 48 deletions(-) rename packages/react-jsx-babel-plugin/__tests__/__snapshots__/{transform-react-jsx-test.js.snap => transform-react-jsx-createElement-test.js.snap} (100%) create mode 100644 packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-jsx-test.js.snap rename packages/react-jsx-babel-plugin/__tests__/{transform-react-jsx-test.js => transform-react-jsx-createElement-test.js} (93%) create mode 100644 packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-jsx-test.js diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-createElement-test.js.snap similarity index 100% rename from packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-test.js.snap rename to packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-createElement-test.js.snap diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-jsx-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-jsx-test.js.snap new file mode 100644 index 0000000000000..5e15051de5b2a --- /dev/null +++ b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-jsx-test.js.snap @@ -0,0 +1,344 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = ` +"React.jsx(Component, Object.assign({}, props, { + sound: \\"moo\\" +}));" +`; + +exports[`transform react to jsx arrow functions 1`] = ` +"var foo = function () { + var _this = this; + + return function () { + return React.jsx(_this, null); + }; +}; + +var bar = function () { + var _this2 = this; + + return function () { + return React.jsx(_this2.foo, null); + }; +};" +`; + +exports[`transform react to jsx assignment 1`] = ` +"var div = React.jsx(Component, Object.assign({}, props, { + foo: \\"bar\\" +}));" +`; + +exports[`transform react to jsx concatenates adjacent string literals 1`] = ` +"var x = React.jsx(\\"div\\", { + children: [\\"foo\\", \\"bar\\", \\"baz\\", React.jsx(\\"div\\", { + children: \\"buz bang\\" + }), \\"qux\\", null, \\"quack\\"] +});" +`; + +exports[`transform react to jsx display name assignment expression 1`] = ` +"var Component; +Component = React.createClass({ + render: function render() { + return null; + } +});" +`; + +exports[`transform react to jsx display name export default 1`] = ` +"export default React.createClass({ + render: function render() { + return null; + } +});" +`; + +exports[`transform react to jsx display name if missing 1`] = ` +"var Whateva = React.createClass({ + displayName: \\"Whatever\\", + render: function render() { + return null; + } +}); +var Bar = React.createClass({ + \\"displayName\\": \\"Ba\\", + render: function render() { + return null; + } +});" +`; + +exports[`transform react to jsx display name object declaration 1`] = ` +"exports = { + Component: React.createClass({ + render: function render() { + return null; + } + }) +};" +`; + +exports[`transform react to jsx display name property assignment 1`] = ` +"exports.Component = React.createClass({ + render: function render() { + return null; + } +});" +`; + +exports[`transform react to jsx display name variable declaration 1`] = ` +"var Component = React.createClass({ + render: function render() { + return null; + } +});" +`; + +exports[`transform react to jsx properly handles keys 1`] = ` +"var x = React.jsx(\\"div\\", { + children: [React.jsx(\\"div\\", Object.assign({}), \\"1\\"), React.jsx(\\"div\\", { + meow: \\"wolf\\" + }, \\"2\\"), React.jsx(\\"div\\", Object.assign({}), \\"3\\")] +});" +`; + +exports[`transform react to jsx should allow constructor as prop 1`] = ` +"React.jsx(Component, { + constructor: \\"foo\\" +});" +`; + +exports[`transform react to jsx should allow deeper js namespacing 1`] = `"React.jsx(Namespace.DeepNamespace.Component, null);"`; + +exports[`transform react to jsx should allow elements as attributes 1`] = ` +"React.jsx(\\"div\\", { + attr: React.jsx(\\"div\\", null) +});" +`; + +exports[`transform react to jsx should allow js namespacing 1`] = `"React.jsx(Namespace.Component, null);"`; + +exports[`transform react to jsx should allow nested fragments 1`] = ` +"React.jsx(\\"div\\", { + children: React.jsx(React.Fragment, { + children: [React.jsx(React.Fragment, { + children: [React.jsx(\\"span\\", { + children: \\"Hello\\" + }), React.jsx(\\"span\\", { + children: \\"world\\" + })] + }), React.jsx(React.Fragment, { + children: [React.jsx(\\"span\\", { + children: \\"Goodbye\\" + }), React.jsx(\\"span\\", { + children: \\"world\\" + })] + })] + }) +});" +`; + +exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = ` +"var x = React.jsx(\\"div\\", { + children: React.jsx(Component, null) +}); +var x = React.jsx(\\"div\\", { + children: props.children +}); +var x = React.jsx(Composite, { + children: props.children +}); +var x = React.jsx(Composite, { + children: React.jsx(Composite2, null) +});" +`; + +exports[`transform react to jsx should convert simple tags 1`] = `"var x = React.jsx(\\"div\\", null);"`; + +exports[`transform react to jsx should convert simple text 1`] = ` +"var x = React.jsx(\\"div\\", { + children: \\"text\\" +});" +`; + +exports[`transform react to jsx should escape xhtml jsxattribute 1`] = ` +"React.jsx(\\"div\\", { + id: \\"w\\\\xF4w\\" +}); +React.jsx(\\"div\\", { + id: \\"w\\" +}); +React.jsx(\\"div\\", { + id: \\"w < w\\" +});" +`; + +exports[`transform react to jsx should escape xhtml jsxtext 1`] = ` +"React.jsx(\\"div\\", { + children: \\"wow\\" +}); +React.jsx(\\"div\\", { + children: \\"w\\\\xF4w\\" +}); +React.jsx(\\"div\\", { + children: \\"w & w\\" +}); +React.jsx(\\"div\\", { + children: \\"w & w\\" +}); +React.jsx(\\"div\\", { + children: \\"w \\\\xA0 w\\" +}); +React.jsx(\\"div\\", { + children: \\"this should not parse as unicode: \\\\xA0\\" +}); +React.jsx(\\"div\\", { + children: \\"this should parse as nbsp: \\\\xA0 \\" +}); +React.jsx(\\"div\\", { + children: [\\"this should parse as unicode: \\", '  '] +}); +React.jsx(\\"div\\", { + children: \\"w < w\\" +});" +`; + +exports[`transform react to jsx should handle attributed elements 1`] = ` +"var HelloMessage = React.createClass({ + render: function () { + return React.jsx(\\"div\\", { + children: [\\"Hello \\", this.props.name] + }); + } +}); +React.render(React.jsx(HelloMessage, { + name: React.jsx(\\"span\\", { + children: \\"Sebastian\\" + }) +}), mountNode);" +`; + +exports[`transform react to jsx should handle has own property correctly 1`] = ` +"React.jsx(\\"hasOwnProperty\\", { + children: \\"testing\\" +});" +`; + +exports[`transform react to jsx should have correct comma in nested children 1`] = ` +"var x = React.jsx(\\"div\\", { + children: [React.jsx(\\"div\\", { + children: React.jsx(\\"br\\", null) + }), React.jsx(Component, { + children: [foo, React.jsx(\\"br\\", null), bar] + }), React.jsx(\\"br\\", null)] +});" +`; + +exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = ` +"var x = React.jsx(\\"div\\", { + attr1: \\"foo\\" + \\"bar\\", + attr2: \\"foo\\" + \\"bar\\" + \\"baz\\" + \\"bug\\", + attr3: \\"foo\\" + \\"bar\\" + \\"baz\\" + \\"bug\\", + attr4: \\"baz\\" +});" +`; + +exports[`transform react to jsx should not add quotes to identifier names 1`] = ` +"var e = React.jsx(F, { + aaa: true, + new: true, + const: true, + var: true, + default: true, + \\"foo-bar\\": true +});" +`; + +exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = ` +"React.jsx(\\"div\\", { + children: \\"\\\\xA0 \\" +});" +`; + +exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = ` +"React.jsx(\\"div\\", { + children: \\"\\\\xA0\\" +});" +`; + +exports[`transform react to jsx should properly handle comments adjacent to children 1`] = ` +"var x = React.jsx(\\"div\\", { + children: [React.jsx(\\"span\\", null), React.jsx(\\"br\\", null)] +});" +`; + +exports[`transform react to jsx should properly handle comments between props 1`] = ` +"var x = React.jsx(\\"div\\", { + /* a multi-line + comment */ + attr1: \\"foo\\", + children: React.jsx(\\"span\\", { + // a double-slash comment + attr2: \\"bar\\" + }) +});" +`; + +exports[`transform react to jsx should quote jsx attributes 1`] = ` +"React.jsx(\\"button\\", { + \\"data-value\\": \\"a value\\", + children: \\"Button\\" +});" +`; + +exports[`transform react to jsx should support xml namespaces if flag 1`] = ` +"React.jsx(\\"f:image\\", { + \\"n:attr\\": true +});" +`; + +exports[`transform react to jsx should transform known hyphenated tags 1`] = `"React.jsx(\\"font-face\\", null);"`; + +exports[`transform react to jsx uses createElement when the key comes after a spread 1`] = ` +"var x = React.createElement(\\"div\\", Object.assign({}, props, { + key: \\"1\\", + foo: \\"bar\\" +}));" +`; + +exports[`transform react to jsx uses jsx when the key comes before a spread 1`] = ` +"var x = React.jsx(\\"div\\", Object.assign({}, props, { + foo: \\"bar\\" +}), \\"1\\");" +`; + +exports[`transform react to jsx uses jsxDEV instead of jsx in dev mode 1`] = ` +"var x = React.jsxDEV(\\"span\\", { + propOne: \\"one\\", + children: \\"Hi\\" +});" +`; + +exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = ` +"React.jsx(Component, Object.assign({}, x, { + y: 2, + z: true +}));" +`; + +exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = ` +"React.jsx(Component, Object.assign({ + y: 2, + z: true +}, x));" +`; + +exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = ` +"React.jsx(Component, Object.assign({ + y: 2 +}, x, { + z: true +}));" +`; diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-createElement-test.js similarity index 93% rename from packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-test.js rename to packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-createElement-test.js index afbc65a32c087..14919db1c399c 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-createElement-test.js @@ -8,7 +8,6 @@ 'use strict'; const babel = require('@babel/core'); -// const transformReactJSX = require('../transform-react-jsx'); const codeFrame = require('@babel/code-frame'); function transform(input, options) { @@ -22,6 +21,7 @@ function transform(input, options) { method: 'coroutine', development: __DEV__, useBuiltIns: true, + useCreateElement: true, ...options, }, ], @@ -29,25 +29,6 @@ function transform(input, options) { }).code; } -// function compare(input, output) { -// const compiled = transform(input); -// expect(compiled).toEqual(output); -// } - -// const TEST_DIR = './packages/react-jsx-babel-plugin/__tests__/fixtures'; -// function makeTests() { -// fs.readdirSync(TEST_DIR).forEach(filename => { -// const testLoc = path.join(TEST_DIR, filename); -// const inputLoc = path.join(testLoc, 'input.js'); -// const outputLoc = path.join(testLoc, 'output.js'); -// const input = fs.readFileSync(inputLoc, 'utf8'); -// const output = fs.readFileSync(outputLoc, 'utf8'); -// it(filename, () => { -// compare(input, output); -// }); -// }); -// } - describe('transform react to jsx', () => { it('should properly handle comments adjacent to children', () => { expect( diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-jsx-test.js new file mode 100644 index 0000000000000..43a4beb5f9f34 --- /dev/null +++ b/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-jsx-test.js @@ -0,0 +1,432 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* eslint-disable quotes */ +'use strict'; + +const babel = require('@babel/core'); +const codeFrame = require('@babel/code-frame'); + +function transform(input, options) { + return babel.transform(input, { + plugins: [ + [ + './packages/react-jsx-babel-plugin/transform-react-to-jsx', + // '@babel/plugin-transform-react-jsx', + { + module: 'bluebird', + method: 'coroutine', + development: false, + useBuiltIns: true, + useCreateElement: false, + ...options, + }, + ], + ], + }).code; +} + +describe('transform react to jsx', () => { + it('uses jsxDEV instead of jsx in dev mode', () => { + expect( + transform(`var x = Hi`, {development: true}) + ).toMatchSnapshot(); + }); + it('properly handles keys', () => { + expect( + transform(`var x = ( +
+
+
+
+
+ );`) + ).toMatchSnapshot(); + }); + it('uses createElement when the key comes after a spread', () => { + expect( + transform(`var x = ( +
+ );`) + ).toMatchSnapshot(); + }); + it('uses jsx when the key comes before a spread', () => { + expect( + transform(`var x = ( +
+ );`) + ).toMatchSnapshot(); + }); + it('should properly handle comments adjacent to children', () => { + expect( + transform(` + var x = ( +
+ {/* A comment at the beginning */} + {/* A second comment at the beginning */} + + {/* A nested comment */} + + {/* A sandwiched comment */} +
+ {/* A comment at the end */} + {/* A second comment at the end */} +
+ ); + `) + ).toMatchSnapshot(); + }); + it('adds appropriate new lines when using spread attribute', () => { + expect(transform(``)).toMatchSnapshot(); + }); + it('arrow functions', () => { + expect( + transform(` + var foo = function () { + return () => ; + }; + + var bar = function () { + return () => ; + }; + + `) + ).toMatchSnapshot(); + }); + it('assignment', () => { + expect( + transform(`var div = `) + ).toMatchSnapshot(); + }); + it('concatenates adjacent string literals', () => { + expect( + transform(` + var x = +
+ foo + {"bar"} + baz +
+ buz + bang +
+ qux + {null} + quack +
+ `) + ).toMatchSnapshot(); + }); + it('display name assignment expression', () => { + expect( + transform(` + var Component; + Component = React.createClass({ + render: function render() { + return null; + } + }); + `) + ).toMatchSnapshot(); + }); + it('display name export default', () => { + expect( + transform(` + export default React.createClass({ + render: function render() { + return null; + } + }); + `) + ).toMatchSnapshot(); + }); + it('display name if missing', () => { + expect( + transform(` + var Whateva = React.createClass({ + displayName: "Whatever", + render: function render() { + return null; + } + }); + + var Bar = React.createClass({ + "displayName": "Ba", + render: function render() { + return null; + } + }); + `) + ).toMatchSnapshot(); + }); + it('display name object declaration', () => { + expect( + transform(` + exports = { + Component: React.createClass({ + render: function render() { + return null; + } + }) + }; + `) + ).toMatchSnapshot(); + }); + it('display name property assignment', () => { + expect( + transform(` + exports.Component = React.createClass({ + render: function render() { + return null; + } + }); + `) + ).toMatchSnapshot(); + }); + it('display name variable declaration', () => { + expect( + transform(` + var Component = React.createClass({ + render: function render() { + return null; + } + }); + `) + ).toMatchSnapshot(); + }); + it('should allow constructor as prop', () => { + expect(transform(`;`)).toMatchSnapshot(); + }); + it('should allow deeper js namespacing', () => { + expect( + transform(`;`) + ).toMatchSnapshot(); + }); + it('should allow elements as attributes', () => { + expect(transform(`
/>`)).toMatchSnapshot(); + }); + it('should allow js namespacing', () => { + expect(transform(`;`)).toMatchSnapshot(); + }); + it('should allow nested fragments', () => { + expect( + transform(` +
+ < > + <> + Hello + world + + <> + Goodbye + world + + +
+ `) + ).toMatchSnapshot(); + }); + it('should avoid wrapping in extra parens if not needed', () => { + expect( + transform(` + var x =
+ +
; + + var x =
+ {props.children} +
; + + var x = + {props.children} + ; + + var x = + + ; + `) + ).toMatchSnapshot(); + }); + it('should convert simple tags', () => { + expect(transform(`var x =
;`)).toMatchSnapshot(); + }); + it('should convert simple text', () => { + expect(transform(`var x =
text
;`)).toMatchSnapshot(); + }); + it('should disallow spread children', () => { + let _error; + const code = `
{...children}
;`; + try { + transform(code); + } catch (error) { + _error = error; + } + expect(_error).toEqual( + new SyntaxError( + 'undefined: Spread children are not supported in React.' + + '\n' + + codeFrame.codeFrameColumns( + code, + {start: {line: 1, column: 6}}, + {highlightCode: true} + ) + ) + ); + }); + it('should escape xhtml jsxattribute', () => { + expect( + transform(` +
; +
; +
; + `) + ).toMatchSnapshot(); + }); + it('should escape xhtml jsxtext', () => { + expect( + transform(` +
wow
; +
wôw
; + +
w & w
; +
w & w
; + +
w   w
; +
this should not parse as unicode: \u00a0
; +
this should parse as nbsp:  
; +
this should parse as unicode: {'\u00a0 '}
; + +
w < w
; + `) + ).toMatchSnapshot(); + }); + it('should handle attributed elements', () => { + expect( + transform(` + var HelloMessage = React.createClass({ + render: function() { + return
Hello {this.props.name}
; + } + }); + + React.render( + Sebastian + + } />, mountNode); + `) + ).toMatchSnapshot(); + }); + it('should handle has own property correctly', () => { + expect( + transform(`testing;`) + ).toMatchSnapshot(); + }); + it('should have correct comma in nested children', () => { + expect( + transform(` + var x =
+

+ {foo}
{bar}
+
+
; + `) + ).toMatchSnapshot(); + }); + it('should insert commas after expressions before whitespace', () => { + expect( + transform(` + var x = +
+
+ `) + ).toMatchSnapshot(); + }); + it('should not add quotes to identifier names', () => { + expect( + transform(`var e = ;`) + ).toMatchSnapshot(); + }); + it('should not strip nbsp even couple with other whitespace', () => { + expect(transform(`
 
;`)).toMatchSnapshot(); + }); + it('should not strip tags with a single child of nbsp', () => { + expect(transform(`
 
;`)).toMatchSnapshot(); + }); + it('should properly handle comments between props', () => { + expect( + transform(` + var x = ( +
+ +
+ ); + `) + ).toMatchSnapshot(); + }); + it('should quote jsx attributes', () => { + expect( + transform(``) + ).toMatchSnapshot(); + }); + it('should support xml namespaces if flag', () => { + expect( + transform('', {throwIfNamespace: false}) + ).toMatchSnapshot(); + }); + it('should throw error namespaces if not flag', () => { + let _error; + const code = ``; + try { + transform(code); + } catch (error) { + _error = error; + } + expect(_error).toEqual( + new SyntaxError( + "undefined: Namespace tags are not supported by default. React's " + + "JSX doesn't support namespace tags. You can turn on the " + + "'throwIfNamespace' flag to bypass this warning." + + '\n' + + codeFrame.codeFrameColumns( + code, + {start: {line: 1, column: 2}}, + {highlightCode: true} + ) + ) + ); + }); + it('should transform known hyphenated tags', () => { + expect(transform(``)).toMatchSnapshot(); + }); + it('wraps props in react spread for first spread attributes', () => { + expect(transform(``)).toMatchSnapshot(); + }); + it('wraps props in react spread for last spread attributes', () => { + expect(transform(``)).toMatchSnapshot(); + }); + it('wraps props in react spread for middle spread attributes', () => { + expect(transform(``)).toMatchSnapshot(); + }); +}); diff --git a/packages/react-jsx-babel-plugin/transform-react-to-jsx.js b/packages/react-jsx-babel-plugin/transform-react-to-jsx.js index aac9013e39407..fd2dfb260b287 100644 --- a/packages/react-jsx-babel-plugin/transform-react-to-jsx.js +++ b/packages/react-jsx-babel-plugin/transform-react-to-jsx.js @@ -27,11 +27,15 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, }; visitor.JSXElement = { - enter(path, state) { - // console.log(path, state); - }, + enter(path, state) {}, exit(path, file) { - const callExpr = buildElementCall(path, file); + let callExpr; + if (file.opts.useCreateElement || useCreateElement(path)) { + callExpr = buildElementCall(path, file); + } else { + callExpr = buildJSXElementCall(path, file); + } + if (callExpr) { path.replaceWith(t.inherits(callExpr, path.node)); } @@ -45,6 +49,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, 'Fragment tags are only supported in React 16 and up.', ); } + const callExpr = buildFragmentCall(path, file); if (callExpr) { path.replaceWith(t.inherits(callExpr, path.node)); @@ -111,10 +116,70 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, return t.inherits(t.objectProperty(node.name, value), node); } - // //
should use React.createElement - // function isPropsSpreadFollowedByKey(node) { + function buildJSXElementCall(path, file) { + if (opts.filter && !opts.filter(path.node, file)) return; + + const openingPath = path.get('openingElement'); + openingPath.parent.children = t.react.buildChildren(openingPath.parent); + + const tagExpr = convertJSXIdentifier( + openingPath.node.name, + openingPath.node, + ); + + const args = []; + + let tagName; + if (t.isIdentifier(tagExpr)) { + tagName = tagExpr.name; + } else if (t.isLiteral(tagExpr)) { + tagName = tagExpr.value; + } + + const state = { + tagExpr: tagExpr, + tagName: tagName, + args: args, + }; + + if (opts.pre) { + opts.pre(state, file); + } + + let attribs = openingPath.node.attributes; + let keyValue; + for (let i = 0, attrLength = attribs.length; i < attrLength; i++) { + const attr = attribs[i]; + if (t.isJSXAttribute(attr)) { + if (t.isJSXIdentifier(attr.name) && attr.name.name === 'key') { + keyValue = attr.value; + } + } + } + + if (attribs.length || path.node.children.length) { + attribs = buildOpeningElementAttributes( + attribs, + file, + true, + path.node.children, + ); + } else { + attribs = t.nullLiteral(); + } + + args.push(attribs); + + if (keyValue !== undefined) { + args.push(keyValue); + } + + if (opts.post) { + opts.post(state, file); + } - // } + return state.call || t.callExpression(state.callee, args); + } function buildElementCall(path, file) { if (opts.filter && !opts.filter(path.node, file)) return; @@ -158,7 +223,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, opts.post(state, file); } - return state.call || t.callExpression(state.callee, args); + return state.call || t.callExpression(state.oldCallee, args); } function pushProps(_props, objs) { @@ -175,7 +240,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, * all prior attributes to an array for later processing. */ - function buildOpeningElementAttributes(attribs, file) { + function buildOpeningElementAttributes(attribs, file, isReactJSX, children) { let _props = []; const objs = []; @@ -193,7 +258,28 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, _props = pushProps(_props, objs); objs.push(prop.argument); } else { - _props.push(convertAttribute(prop)); + const attr = convertAttribute(prop); + // if we are using React.JSX, we don't want to pass 'key' as a prop + // so don't add it to the list + if (!isReactJSX || attr.key.name !== 'key') { + _props.push(attr); + } + } + } + + // if we are using React.JSX, children is now a prop, so add it to the list + + console.log(children); + if (isReactJSX && children && children.length > 0) { + if (children.length === 1) { + _props.push(t.objectProperty(t.identifier('children'), children[0])); + } else { + _props.push( + t.objectProperty( + t.identifier('children'), + t.arrayExpression(children), + ), + ); } } @@ -240,14 +326,53 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } // no attributes are allowed with <> syntax - args.push(t.nullLiteral(), ...path.node.children); + // React.createElement uses different syntax than React.jsx + // createElement passes in children as a separate argument, + // whereas jsx passes children in as a prop + if (file.opts.useCreateElement) { + args.push(t.nullLiteral(), ...path.node.children); + } else { + args.push( + t.objectExpression([ + t.objectProperty( + t.identifier('children'), + t.arrayExpression(path.node.children), + ), + ]), + ); + } if (opts.post) { opts.post(state, file); } - file.set('usedFragment', true); - return state.call || t.callExpression(state.callee, args); + return ( + state.call || + t.callExpression( + file.opts.useCreateElement ? state.oldCallee : state.callee, + args, + ) + ); + } + + function useCreateElement(path) { + const openingPath = path.get('openingElement'); + const attributes = openingPath.node.attributes; + + let seenPropsSpread = false; + for (let i = 0, length = attributes.length; i < length; i++) { + const attr = attributes[i]; + if ( + seenPropsSpread && + t.isJSXAttribute(attr) && + attr.name.name === 'key' + ) { + return true; + } else if (t.isJSXSpreadAttribute(attr)) { + seenPropsSpread = true; + } + } + return false; } } @@ -272,6 +397,7 @@ module.exports = function(babel) { post(state, pass) { state.callee = pass.get('jsxIdentifier')(); + state.oldCallee = pass.get('oldJSXIdentifier')(); }, }); @@ -279,23 +405,12 @@ module.exports = function(babel) { enter(path, state) { const pragma = state.opts.development ? 'React.jsxDEV' : 'React.jsx'; const pragmaFrag = 'React.Fragment'; + state.set( + 'oldJSXIdentifier', + createIdentifierParser('React.createElement'), + ); state.set('jsxIdentifier', createIdentifierParser(pragma)); state.set('jsxFragIdentifier', createIdentifierParser(pragmaFrag)); - state.set('usedFragment', false); - state.set('pragmaSet', true); - state.set('pragmaFragSet', true); - }, - exit(path, state) { - if ( - state.get('pragmaSet') && - state.get('usedFragment') && - !state.get('pragmaFragSet') - ) { - throw new Error( - 'transform-react-jsx: pragma has been set but ' + - 'pragmafrag has not been set', - ); - } }, }; From 79ac078bc368e4902a1cd1b1d949cb9d0690666e Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Tue, 13 Aug 2019 10:57:38 -0700 Subject: [PATCH 05/33] some more stuff --- .../transform-react-jsx-jsx-test.js.snap | 21 +++++- .../__tests__/transform-react-jsx-jsx-test.js | 19 ++++++ .../transform-react-to-jsx.js | 67 ++++++++++++------- 3 files changed, 80 insertions(+), 27 deletions(-) diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-jsx-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-jsx-test.js.snap index 5e15051de5b2a..084875a736072 100644 --- a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-jsx-test.js.snap +++ b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-jsx-test.js.snap @@ -98,12 +98,27 @@ exports[`transform react to jsx display name variable declaration 1`] = ` exports[`transform react to jsx properly handles keys 1`] = ` "var x = React.jsx(\\"div\\", { - children: [React.jsx(\\"div\\", Object.assign({}), \\"1\\"), React.jsx(\\"div\\", { + children: [React.jsx(\\"div\\", null, \\"1\\"), React.jsx(\\"div\\", { meow: \\"wolf\\" - }, \\"2\\"), React.jsx(\\"div\\", Object.assign({}), \\"3\\")] + }, \\"2\\"), React.jsx(\\"div\\", null, \\"3\\")] });" `; +exports[`transform react to jsx properly passes in source and self 1`] = ` +"var x = React.jsxDEV(\\"div\\", null, undefined, { + fileName: 'this/file.js', + lineNumber: 10 +}, this); +var y = React.jsxDEV(\\"div\\", null, undefined, undefined, this);" +`; + +exports[`transform react to jsx properly passes in source and self 2`] = ` +"var x = React.jsxDEV(\\"div\\", null, undefined, { + fileName: 'this/file.js', + lineNumber: 10 +}, this);" +`; + exports[`transform react to jsx should allow constructor as prop 1`] = ` "React.jsx(Component, { constructor: \\"foo\\" @@ -318,7 +333,7 @@ exports[`transform react to jsx uses jsxDEV instead of jsx in dev mode 1`] = ` "var x = React.jsxDEV(\\"span\\", { propOne: \\"one\\", children: \\"Hi\\" -});" +}, undefined, undefined, undefined);" `; exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = ` diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-jsx-test.js index 43a4beb5f9f34..7a89abeb6a73a 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-jsx-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-jsx-test.js @@ -35,6 +35,25 @@ describe('transform react to jsx', () => { transform(`var x = Hi`, {development: true}) ).toMatchSnapshot(); }); + it('properly passes in source and self', () => { + expect( + transform( + `var x = ( +
+ ); + var y = ( +
+ );`, + {development: true} + ) + ).toMatchSnapshot(); + }); + it('properly handles keys', () => { expect( transform(`var x = ( diff --git a/packages/react-jsx-babel-plugin/transform-react-to-jsx.js b/packages/react-jsx-babel-plugin/transform-react-to-jsx.js index fd2dfb260b287..aae77b1f8282c 100644 --- a/packages/react-jsx-babel-plugin/transform-react-to-jsx.js +++ b/packages/react-jsx-babel-plugin/transform-react-to-jsx.js @@ -126,7 +126,6 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, openingPath.node.name, openingPath.node, ); - const args = []; let tagName; @@ -146,14 +145,29 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, opts.pre(state, file); } - let attribs = openingPath.node.attributes; - let keyValue; - for (let i = 0, attrLength = attribs.length; i < attrLength; i++) { - const attr = attribs[i]; - if (t.isJSXAttribute(attr)) { - if (t.isJSXIdentifier(attr.name) && attr.name.name === 'key') { - keyValue = attr.value; + let attribs = []; + let key; + let source; + let self; + + // for React.jsx, key, __source (dev), and __self (dev) is passed in as + //a separate argument rather than in the args object. We go through the + // props and filter out these three keywords so we can pass them in + // as separate arguments later + for (let i = 0, len = openingPath.node.attributes.length; i < len; i++) { + const attr = openingPath.node.attributes[i]; + if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) { + if (attr.name.name === 'key') { + key = convertAttribute(attr).value; + } else if (attr.name.name === '__source') { + source = convertAttribute(attr).value; + } else if (attr.name.name === '__self') { + self = convertAttribute(attr).value; + } else { + attribs.push(attr); } + } else { + attribs.push(attr); } } @@ -170,14 +184,22 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, args.push(attribs); - if (keyValue !== undefined) { - args.push(keyValue); + // __source and __self are only used in development + if (!file.opts.development) { + if (key !== undefined) { + args.push(key); + } + } else { + args.push( + key === undefined ? t.identifier('undefined') : key, + source === undefined ? t.identifier('undefined') : source, + self === undefined ? t.identifier('undefined') : self, + ); } if (opts.post) { opts.post(state, file); } - return state.call || t.callExpression(state.callee, args); } @@ -259,17 +281,12 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, objs.push(prop.argument); } else { const attr = convertAttribute(prop); - // if we are using React.JSX, we don't want to pass 'key' as a prop - // so don't add it to the list - if (!isReactJSX || attr.key.name !== 'key') { - _props.push(attr); - } + _props.push(attr); } } - // if we are using React.JSX, children is now a prop, so add it to the list - - console.log(children); + // In React.JSX, children is no longer a separate argument, but passed in + // through the argument object if (isReactJSX && children && children.length > 0) { if (children.length === 1) { _props.push(t.objectProperty(t.identifier('children'), children[0])); @@ -282,7 +299,6 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, ); } } - pushProps(_props, objs); if (objs.length === 1) { @@ -403,14 +419,17 @@ module.exports = function(babel) { visitor.Program = { enter(path, state) { - const pragma = state.opts.development ? 'React.jsxDEV' : 'React.jsx'; - const pragmaFrag = 'React.Fragment'; state.set( 'oldJSXIdentifier', createIdentifierParser('React.createElement'), ); - state.set('jsxIdentifier', createIdentifierParser(pragma)); - state.set('jsxFragIdentifier', createIdentifierParser(pragmaFrag)); + state.set( + 'jsxIdentifier', + createIdentifierParser( + state.opts.development ? 'React.jsxDEV' : 'React.jsx', + ), + ); + state.set('jsxFragIdentifier', createIdentifierParser('React.Fragment')); }, }; From 8c27b24c2681b66bf4ff98dbb32c18aa9c756225 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Thu, 15 Aug 2019 17:27:36 -0700 Subject: [PATCH 06/33] fixed all the tests --- ...m-jsx-to-react-createElement-test.js.snap} | 0 ...> transform-jsx-to-react-jsx-test.js.snap} | 106 +++++++++---- ...nsform-jsx-to-react-createElement-test.js} | 6 +- ....js => transform-jsx-to-react-jsx-test.js} | 72 +++++++-- ...o-jsx.js => transform-jsx-to-react-jsx.js} | 140 ++++++++++++++---- packages/react/src/ReactElement.js | 47 +++--- packages/react/src/ReactElementValidator.js | 15 +- packages/shared/ReactFeatureFlags.js | 2 +- .../forks/ReactFeatureFlags.native-oss.js | 2 +- scripts/jest/preprocessor.js | 11 ++ 10 files changed, 301 insertions(+), 100 deletions(-) rename packages/react-jsx-babel-plugin/__tests__/__snapshots__/{transform-react-jsx-createElement-test.js.snap => transform-jsx-to-react-createElement-test.js.snap} (100%) rename packages/react-jsx-babel-plugin/__tests__/__snapshots__/{transform-react-jsx-jsx-test.js.snap => transform-jsx-to-react-jsx-test.js.snap} (76%) rename packages/react-jsx-babel-plugin/__tests__/{transform-react-jsx-createElement-test.js => transform-jsx-to-react-createElement-test.js} (98%) rename packages/react-jsx-babel-plugin/__tests__/{transform-react-jsx-jsx-test.js => transform-jsx-to-react-jsx-test.js} (89%) rename packages/react-jsx-babel-plugin/{transform-react-to-jsx.js => transform-jsx-to-react-jsx.js} (78%) diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-createElement-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap similarity index 100% rename from packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-createElement-test.js.snap rename to packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-jsx-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap similarity index 76% rename from packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-jsx-test.js.snap rename to packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap index 084875a736072..24412cd51abd9 100644 --- a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-react-jsx-jsx-test.js.snap +++ b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap @@ -1,5 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`transform react to jsx fragments 1`] = ` +"var x = React.jsx(React.Fragment, { + children: React.jsx(\\"div\\", {}) +});" +`; + exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = ` "React.jsx(Component, Object.assign({}, props, { sound: \\"moo\\" @@ -11,7 +17,7 @@ exports[`transform react to jsx arrow functions 1`] = ` var _this = this; return function () { - return React.jsx(_this, null); + return React.jsx(_this, {}); }; }; @@ -19,7 +25,7 @@ var bar = function () { var _this2 = this; return function () { - return React.jsx(_this2.foo, null); + return React.jsx(_this2.foo, {}); }; };" `; @@ -96,26 +102,47 @@ exports[`transform react to jsx display name variable declaration 1`] = ` });" `; +exports[`transform react to jsx fragments in dev mode 1`] = ` +"var _jsxFileName = \\"\\"; +var x = React.jsxDEV(React.Fragment, { + children: React.jsxDEV(\\"div\\", {}, undefined, true, { + fileName: _jsxFileName, + lineNumber: 1 + }, this) +}, undefined, true);" +`; + +exports[`transform react to jsx fragments to set keys 1`] = `"var x = React.jsx(React.Fragment, {}, 'foo');"`; + +exports[`transform react to jsx nonStatic children 1`] = ` +"var _jsxFileName = \\"\\"; +var x = React.jsxDEV(\\"div\\", { + children: [React.jsxDEV(\\"span\\", {}, '0', true, { + fileName: _jsxFileName, + lineNumber: 3 + }, this), React.jsxDEV(\\"span\\", {}, '1', true, { + fileName: _jsxFileName, + lineNumber: 3 + }, this)] +}, undefined, false, { + fileName: _jsxFileName, + lineNumber: 2 +}, this);" +`; + exports[`transform react to jsx properly handles keys 1`] = ` "var x = React.jsx(\\"div\\", { - children: [React.jsx(\\"div\\", null, \\"1\\"), React.jsx(\\"div\\", { + children: [React.jsx(\\"div\\", {}, \\"1\\"), React.jsx(\\"div\\", { meow: \\"wolf\\" - }, \\"2\\"), React.jsx(\\"div\\", null, \\"3\\")] + }, \\"2\\"), React.jsx(\\"div\\", {}, \\"3\\")] });" `; exports[`transform react to jsx properly passes in source and self 1`] = ` -"var x = React.jsxDEV(\\"div\\", null, undefined, { - fileName: 'this/file.js', - lineNumber: 10 -}, this); -var y = React.jsxDEV(\\"div\\", null, undefined, undefined, this);" -`; - -exports[`transform react to jsx properly passes in source and self 2`] = ` -"var x = React.jsxDEV(\\"div\\", null, undefined, { - fileName: 'this/file.js', - lineNumber: 10 +"var _jsxFileName = \\"\\"; +var x = React.jsxDEV(\\"div\\", {}, undefined, true, { + fileName: _jsxFileName, + lineNumber: 1 }, this);" `; @@ -125,15 +152,15 @@ exports[`transform react to jsx should allow constructor as prop 1`] = ` });" `; -exports[`transform react to jsx should allow deeper js namespacing 1`] = `"React.jsx(Namespace.DeepNamespace.Component, null);"`; +exports[`transform react to jsx should allow deeper js namespacing 1`] = `"React.jsx(Namespace.DeepNamespace.Component, {});"`; exports[`transform react to jsx should allow elements as attributes 1`] = ` "React.jsx(\\"div\\", { - attr: React.jsx(\\"div\\", null) + attr: React.jsx(\\"div\\", {}) });" `; -exports[`transform react to jsx should allow js namespacing 1`] = `"React.jsx(Namespace.Component, null);"`; +exports[`transform react to jsx should allow js namespacing 1`] = `"React.jsx(Namespace.Component, {});"`; exports[`transform react to jsx should allow nested fragments 1`] = ` "React.jsx(\\"div\\", { @@ -157,7 +184,7 @@ exports[`transform react to jsx should allow nested fragments 1`] = ` exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = ` "var x = React.jsx(\\"div\\", { - children: React.jsx(Component, null) + children: React.jsx(Component, {}) }); var x = React.jsx(\\"div\\", { children: props.children @@ -166,11 +193,11 @@ var x = React.jsx(Composite, { children: props.children }); var x = React.jsx(Composite, { - children: React.jsx(Composite2, null) + children: React.jsx(Composite2, {}) });" `; -exports[`transform react to jsx should convert simple tags 1`] = `"var x = React.jsx(\\"div\\", null);"`; +exports[`transform react to jsx should convert simple tags 1`] = `"var x = React.jsx(\\"div\\", {});"`; exports[`transform react to jsx should convert simple text 1`] = ` "var x = React.jsx(\\"div\\", { @@ -244,10 +271,10 @@ exports[`transform react to jsx should handle has own property correctly 1`] = ` exports[`transform react to jsx should have correct comma in nested children 1`] = ` "var x = React.jsx(\\"div\\", { children: [React.jsx(\\"div\\", { - children: React.jsx(\\"br\\", null) + children: React.jsx(\\"br\\", {}) }), React.jsx(Component, { - children: [foo, React.jsx(\\"br\\", null), bar] - }), React.jsx(\\"br\\", null)] + children: [foo, React.jsx(\\"br\\", {}), bar] + }), React.jsx(\\"br\\", {})] });" `; @@ -285,7 +312,7 @@ exports[`transform react to jsx should not strip tags with a single child of nbs exports[`transform react to jsx should properly handle comments adjacent to children 1`] = ` "var x = React.jsx(\\"div\\", { - children: [React.jsx(\\"span\\", null), React.jsx(\\"br\\", null)] + children: [React.jsx(\\"span\\", {}), React.jsx(\\"br\\", {})] });" `; @@ -314,7 +341,26 @@ exports[`transform react to jsx should support xml namespaces if flag 1`] = ` });" `; -exports[`transform react to jsx should transform known hyphenated tags 1`] = `"React.jsx(\\"font-face\\", null);"`; +exports[`transform react to jsx should transform known hyphenated tags 1`] = `"React.jsx(\\"font-face\\", {});"`; + +exports[`transform react to jsx static children 1`] = ` +"var _jsxFileName = \\"\\"; +var x = React.jsxDEV(\\"div\\", { + children: [React.jsxDEV(\\"span\\", {}, undefined, true, { + fileName: _jsxFileName, + lineNumber: 3 + }, this), [React.jsxDEV(\\"span\\", {}, '0', true, { + fileName: _jsxFileName, + lineNumber: 4 + }, this), React.jsxDEV(\\"span\\", {}, '1', true, { + fileName: _jsxFileName, + lineNumber: 4 + }, this)]] +}, undefined, true, { + fileName: _jsxFileName, + lineNumber: 2 +}, this);" +`; exports[`transform react to jsx uses createElement when the key comes after a spread 1`] = ` "var x = React.createElement(\\"div\\", Object.assign({}, props, { @@ -330,10 +376,14 @@ exports[`transform react to jsx uses jsx when the key comes before a spread 1`] `; exports[`transform react to jsx uses jsxDEV instead of jsx in dev mode 1`] = ` -"var x = React.jsxDEV(\\"span\\", { +"var _jsxFileName = \\"\\"; +var x = React.jsxDEV(\\"span\\", { propOne: \\"one\\", children: \\"Hi\\" -}, undefined, undefined, undefined);" +}, undefined, true, { + fileName: _jsxFileName, + lineNumber: 1 +}, this);" `; exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = ` diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-createElement-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js similarity index 98% rename from packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-createElement-test.js rename to packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js index 14919db1c399c..df5f917191458 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-createElement-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js @@ -12,10 +12,12 @@ const codeFrame = require('@babel/code-frame'); function transform(input, options) { return babel.transform(input, { + configFile: false, plugins: [ + '@babel/plugin-syntax-jsx', + '@babel/plugin-transform-arrow-functions', [ - './packages/react-jsx-babel-plugin/transform-react-to-jsx', - // '@babel/plugin-transform-react-jsx', + './packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx', { module: 'bluebird', method: 'coroutine', diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js similarity index 89% rename from packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-jsx-test.js rename to packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js index 7a89abeb6a73a..1197e1f79080c 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-react-jsx-jsx-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js @@ -12,14 +12,21 @@ const codeFrame = require('@babel/code-frame'); function transform(input, options) { return babel.transform(input, { + configFile: false, plugins: [ + '@babel/plugin-syntax-jsx', + '@babel/plugin-transform-arrow-functions', + ...(options && options.development + ? [ + '@babel/plugin-transform-react-jsx-source', + '@babel/plugin-transform-react-jsx-self', + ] + : []), [ - './packages/react-jsx-babel-plugin/transform-react-to-jsx', - // '@babel/plugin-transform-react-jsx', + './packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx', { module: 'bluebird', method: 'coroutine', - development: false, useBuiltIns: true, useCreateElement: false, ...options, @@ -30,29 +37,62 @@ function transform(input, options) { } describe('transform react to jsx', () => { - it('uses jsxDEV instead of jsx in dev mode', () => { + it(' fragments', () => { + expect(transform(`var x = <>
`)).toMatchSnapshot(); + }); + it('fragments to set keys', () => { expect( - transform(`var x = Hi`, {development: true}) + transform(`var x = `) ).toMatchSnapshot(); }); - it('properly passes in source and self', () => { + it('fragments in dev mode', () => { + expect( + transform(`var x = <>
`, { + development: true, + }) + ).toMatchSnapshot(); + }); + it('nonStatic children', () => { expect( transform( `var x = ( -
+
+ {[, ]} +
); - var y = ( -
- );`, - {development: true} + `, + { + development: true, + } ) ).toMatchSnapshot(); }); + it('static children', () => { + expect( + transform( + `var x = ( +
+ + {[, ]} +
+ ); + `, + { + development: true, + } + ) + ).toMatchSnapshot(); + }); + it('uses jsxDEV instead of jsx in dev mode', () => { + expect( + transform(`var x = Hi`, {development: true}) + ).toMatchSnapshot(); + }); + it('properly passes in source and self', () => { + expect( + transform(`var x =
;`, {development: true}) + ).toMatchSnapshot(); + }); it('properly handles keys', () => { expect( diff --git a/packages/react-jsx-babel-plugin/transform-react-to-jsx.js b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js similarity index 78% rename from packages/react-jsx-babel-plugin/transform-react-to-jsx.js rename to packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js index aae77b1f8282c..c0cab605c0a55 100644 --- a/packages/react-jsx-babel-plugin/transform-react-to-jsx.js +++ b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js @@ -2,7 +2,6 @@ const t = require('@babel/types'); const esutils = require('esutils'); -// const jsx = '@babel/plugin-syntax-jsx'; function helper(opts) { const visitor = {}; @@ -31,7 +30,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, exit(path, file) { let callExpr; if (file.opts.useCreateElement || useCreateElement(path)) { - callExpr = buildElementCall(path, file); + callExpr = buildCreateElementCall(path, file); } else { callExpr = buildJSXElementCall(path, file); } @@ -56,6 +55,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } }, }; + return visitor; function convertJSXIdentifier(node, parent) { @@ -91,6 +91,15 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } } + function isStaticChildren(children) { + return !( + children.length === 1 && + (t.isArrayExpression(children[0]) || + t.isIdentifier(children[0]) || + t.isMemberExpression(children[0])) + ); + } + function convertAttribute(node) { const value = convertAttributeValue(node.value || t.booleanLiteral(true)); @@ -172,14 +181,13 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } if (attribs.length || path.node.children.length) { - attribs = buildOpeningElementAttributes( + attribs = buildJSXOpeningElementAttributes( attribs, file, - true, path.node.children, ); } else { - attribs = t.nullLiteral(); + attribs = t.objectExpression([]); } args.push(attribs); @@ -192,6 +200,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } else { args.push( key === undefined ? t.identifier('undefined') : key, + t.booleanLiteral(isStaticChildren(path.node.children)), source === undefined ? t.identifier('undefined') : source, self === undefined ? t.identifier('undefined') : self, ); @@ -203,7 +212,85 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, return state.call || t.callExpression(state.callee, args); } - function buildElementCall(path, file) { + /** + * The logic for this is quite terse. It's because we need to + * support spread elements. We loop over all attributes, + * breaking on spreads, we then push a new object containing + * all prior attributes to an array for later processing. + */ + function buildJSXOpeningElementAttributes(attribs, file, children) { + let _props = []; + const objs = []; + + const useBuiltIns = file.opts.useBuiltIns || false; + if (typeof useBuiltIns !== 'boolean') { + throw new Error( + 'transform-react-jsx currently only accepts a boolean option for ' + + 'useBuiltIns (defaults to false)', + ); + } + + while (attribs.length) { + const prop = attribs.shift(); + if (t.isJSXSpreadAttribute(prop)) { + _props = pushProps(_props, objs); + objs.push(prop.argument); + } else { + const attr = convertAttribute(prop); + _props.push(attr); + } + } + + // In React.JSX, children is no longer a separate argument, but passed in + // through the argument object + if (children && children.length > 0) { + if (children.length === 1) { + _props.push(t.objectProperty(t.identifier('children'), children[0])); + } else { + _props.push( + t.objectProperty( + t.identifier('children'), + t.arrayExpression(children), + ), + ); + } + } + pushProps(_props, objs); + + if (objs.length === 1) { + // only one object + if (!t.isObjectExpression(objs[0])) { + // this could be null, and jsx expects props to be non-null + const expressionHelper = useBuiltIns + ? t.memberExpression(t.identifier('Object'), t.identifier('assign')) + : file.addHelper('extends'); + + // spread it + attribs = t.callExpression(expressionHelper, [ + t.objectExpression([]), + ...objs, + ]); + } else { + attribs = objs[0]; + } + } else { + // looks like we have multiple objects + if (!t.isObjectExpression(objs[0])) { + objs.unshift(t.objectExpression([])); + } + + const expressionHelper = useBuiltIns + ? t.memberExpression(t.identifier('Object'), t.identifier('assign')) + : file.addHelper('extends'); + + // spread it + attribs = t.callExpression(expressionHelper, objs); + } + + return attribs; + } + + function buildCreateElementCall(path, file) { if (opts.filter && !opts.filter(path.node, file)) return; const openingPath = path.get('openingElement'); @@ -261,8 +348,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, * breaking on spreads, we then push a new object containing * all prior attributes to an array for later processing. */ - - function buildOpeningElementAttributes(attribs, file, isReactJSX, children) { + function buildOpeningElementAttributes(attribs, file) { let _props = []; const objs = []; @@ -285,20 +371,6 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } } - // In React.JSX, children is no longer a separate argument, but passed in - // through the argument object - if (isReactJSX && children && children.length > 0) { - if (children.length === 1) { - _props.push(t.objectProperty(t.identifier('children'), children[0])); - } else { - _props.push( - t.objectProperty( - t.identifier('children'), - t.arrayExpression(children), - ), - ); - } - } pushProps(_props, objs); if (objs.length === 1) { @@ -341,23 +413,29 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, opts.pre(state, file); } - // no attributes are allowed with <> syntax - // React.createElement uses different syntax than React.jsx - // createElement passes in children as a separate argument, - // whereas jsx passes children in as a prop if (file.opts.useCreateElement) { args.push(t.nullLiteral(), ...path.node.children); } else { + let childrenNode; + if (path.node.children.length === 0) { + childrenNode = t.nullLiteral(); + } else if (path.node.children.length === 1) { + childrenNode = path.node.children[0]; + } else { + childrenNode = t.arrayExpression(path.node.children); + } args.push( t.objectExpression([ - t.objectProperty( - t.identifier('children'), - t.arrayExpression(path.node.children), - ), + t.objectProperty(t.identifier('children'), childrenNode), ]), ); + if (file.opts.development) { + args.push( + t.identifier('undefined'), + t.booleanLiteral(isStaticChildren(path.node.children)), + ); + } } - if (opts.post) { opts.post(state, file); } diff --git a/packages/react/src/ReactElement.js b/packages/react/src/ReactElement.js index 9416a313b9648..858844c8e28e1 100644 --- a/packages/react/src/ReactElement.js +++ b/packages/react/src/ReactElement.js @@ -12,6 +12,7 @@ import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; import ReactCurrentOwner from './ReactCurrentOwner'; const hasOwnProperty = Object.prototype.hasOwnProperty; +const freeze = Object.freeze; const RESERVED_PROPS = { key: true, @@ -155,9 +156,9 @@ const ReactElement = function(type, key, ref, self, source, owner, props) { writable: false, value: source, }); - if (Object.freeze) { - Object.freeze(element.props); - Object.freeze(element); + if (freeze) { + freeze(element.props); + freeze(element); } } @@ -179,6 +180,16 @@ export function jsx(type, config, maybeKey) { let key = null; let ref = null; + // Currently, key can be spread in as a prop. This causes a potential + // issue if key is also explicitly declared (ie.
+ // or
). We want to deprecate key spread, + // but as an intermediary step, we will use jsxDEV for everything except + //
, because we aren't currently able to tell if + // key is explicitly declared to be undefined or not. + if (maybeKey !== undefined) { + key = '' + maybeKey; + } + if (hasValidRef(config)) { ref = config.ref; } @@ -197,12 +208,6 @@ export function jsx(type, config, maybeKey) { } } - // intentionally not checking if key was set above - // this key is higher priority as it's static - if (maybeKey !== undefined) { - key = '' + maybeKey; - } - // Resolve default props if (type && type.defaultProps) { const defaultProps = type.defaultProps; @@ -239,6 +244,16 @@ export function jsxDEV(type, config, maybeKey, source, self) { let key = null; let ref = null; + // Currently, key can be spread in as a prop. This causes a potential + // issue if key is also explicitly declared (ie.
+ // or
). We want to deprecate key spread, + // but as an intermediary step, we will use jsxDEV for everything except + //
, because we aren't currently able to tell if + // key is explicitly declared to be undefined or not. + if (maybeKey !== undefined) { + key = '' + maybeKey; + } + if (hasValidRef(config)) { ref = config.ref; } @@ -257,12 +272,6 @@ export function jsxDEV(type, config, maybeKey, source, self) { } } - // intentionally not checking if key was set above - // this key is higher priority as it's static - if (maybeKey !== undefined) { - key = '' + maybeKey; - } - // Resolve default props if (type && type.defaultProps) { const defaultProps = type.defaultProps; @@ -273,6 +282,10 @@ export function jsxDEV(type, config, maybeKey, source, self) { } } + if (freeze && Array.isArray(props.children)) { + freeze(props.children); + } + if (key || ref) { const displayName = typeof type === 'function' @@ -344,8 +357,8 @@ export function createElement(type, config, children) { childArray[i] = arguments[i + 2]; } if (__DEV__) { - if (Object.freeze) { - Object.freeze(childArray); + if (freeze) { + freeze(childArray); } } props.children = childArray; diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js index 6a1626a2e27e8..5f523c2611e0d 100644 --- a/packages/react/src/ReactElementValidator.js +++ b/packages/react/src/ReactElementValidator.js @@ -43,6 +43,8 @@ if (__DEV__) { propTypesMisspellWarningShown = false; } +const hasOwnProperty = Object.prototype.hasOwnProperty; + function getDeclarationErrorAddendum() { if (ReactCurrentOwner.current) { const name = getComponentName(ReactCurrentOwner.current.type); @@ -313,7 +315,7 @@ export function jsxWithValidation( warning( false, - 'React.jsx: type is invalid -- expected a string (for ' + + 'React.createElement: type is invalid -- expected a string (for ' + 'built-in components) or a class/function (for composite ' + 'components) but got: %s.%s', typeString, @@ -334,12 +336,17 @@ export function jsxWithValidation( // 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) { const children = props.children; if (children !== undefined) { if (isStaticChildren) { - for (let i = 0; i < children.length; i++) { - validateChildKeys(children[i], type); + if (Array.isArray(children)) { + for (let i = 0; i < children.length; i++) { + validateChildKeys(children[i], type); + } + } else { + validateChildKeys(children, type); } } else { validateChildKeys(children, type); @@ -347,7 +354,7 @@ export function jsxWithValidation( } } - if (props.key !== undefined) { + if (hasOwnProperty.call(props, 'key')) { warning( false, 'React.jsx: Spreading a key to JSX is a deprecated pattern. ' + diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index f3ebeaa96c48e..e66457bf9f033 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -66,7 +66,7 @@ export const enableFlareAPI = false; export const enableFundamentalAPI = false; // New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107 -export const enableJSXTransformAPI = false; +export const enableJSXTransformAPI = true; // We will enforce mocking scheduler with scheduler/unstable_mock at some point. (v17?) // Till then, we warn about the missing mock, but still fallback to a sync mode compatible version diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 7db7b5728a246..5b33457e5f3b1 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -28,7 +28,7 @@ export const enableSchedulerDebugging = false; export const warnAboutDeprecatedSetNativeProps = false; export const enableFlareAPI = false; export const enableFundamentalAPI = false; -export const enableJSXTransformAPI = false; +export const enableJSXTransformAPI = true; export const warnAboutUnmockedScheduler = false; export const revertPassiveEffectsChange = false; export const flushSuspenseFallbacksInTests = true; diff --git a/scripts/jest/preprocessor.js b/scripts/jest/preprocessor.js index d97b61e5e6234..20d621b5550a0 100644 --- a/scripts/jest/preprocessor.js +++ b/scripts/jest/preprocessor.js @@ -22,6 +22,9 @@ const pathToBabelPluginWrapWarning = require.resolve( const pathToBabelPluginAsyncToGenerator = require.resolve( '@babel/plugin-transform-async-to-generator' ); +const pathToBabelReactJSXTransform = require.resolve( + '../../packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx' +); const pathToBabelrc = path.join(__dirname, '..', '..', 'babel.config.js'); const pathToErrorCodes = require.resolve('../error-codes/codes.json'); @@ -39,6 +42,14 @@ const babelOptions = { // TODO: I have not verified that this actually works. require.resolve('@babel/plugin-transform-react-jsx-source'), + [ + pathToBabelReactJSXTransform, + { + development: process.env.NODE_ENV === 'development', + useCreateElement: false, + }, + ], + require.resolve('../babel/transform-prevent-infinite-loops'), ], retainLines: true, From 574c6611000976dbf768f0d23f41eb503f7b56bc Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Thu, 15 Aug 2019 17:34:25 -0700 Subject: [PATCH 07/33] cleaned up code some --- babel.config.js | 2 +- packages/react/src/ReactElementValidator.js | 2 +- packages/shared/ReactFeatureFlags.js | 2 +- packages/shared/forks/ReactFeatureFlags.native-oss.js | 4 ++-- scripts/jest/preprocessor.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/babel.config.js b/babel.config.js index 30114aab44568..d4d1e3213c574 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,7 +3,7 @@ module.exports = { plugins: [ '@babel/plugin-syntax-jsx', - // '@babel/plugin-transform-react-jsx', + '@babel/plugin-transform-react-jsx', '@babel/plugin-transform-flow-strip-types', ['@babel/plugin-proposal-class-properties', {loose: true}], 'syntax-trailing-function-commas', diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js index 5f523c2611e0d..15957988c7cb6 100644 --- a/packages/react/src/ReactElementValidator.js +++ b/packages/react/src/ReactElementValidator.js @@ -315,7 +315,7 @@ export function jsxWithValidation( warning( false, - 'React.createElement: type is invalid -- expected a string (for ' + + 'React.jsx: type is invalid -- expected a string (for ' + 'built-in components) or a class/function (for composite ' + 'components) but got: %s.%s', typeString, diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index e66457bf9f033..f3ebeaa96c48e 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -66,7 +66,7 @@ export const enableFlareAPI = false; export const enableFundamentalAPI = false; // New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107 -export const enableJSXTransformAPI = true; +export const enableJSXTransformAPI = false; // We will enforce mocking scheduler with scheduler/unstable_mock at some point. (v17?) // Till then, we warn about the missing mock, but still fallback to a sync mode compatible version diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 5b33457e5f3b1..f8907cd1c9f85 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -28,10 +28,10 @@ export const enableSchedulerDebugging = false; export const warnAboutDeprecatedSetNativeProps = false; export const enableFlareAPI = false; export const enableFundamentalAPI = false; -export const enableJSXTransformAPI = true; +export const enableJSXTransformAPI = false; export const warnAboutUnmockedScheduler = false; export const revertPassiveEffectsChange = false; -export const flushSuspenseFallbacksInTests = true; +export const flushSuspenseFallbacksInTests = false; export const enableUserBlockingEvents = false; export const enableSuspenseCallback = false; export const warnAboutDefaultPropsOnFunctionComponents = false; diff --git a/scripts/jest/preprocessor.js b/scripts/jest/preprocessor.js index 20d621b5550a0..f049b089bad34 100644 --- a/scripts/jest/preprocessor.js +++ b/scripts/jest/preprocessor.js @@ -46,7 +46,7 @@ const babelOptions = { pathToBabelReactJSXTransform, { development: process.env.NODE_ENV === 'development', - useCreateElement: false, + useCreateElement: true, }, ], From 68640913261bf353e7f46f7a61adc4fba74c5a16 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Fri, 16 Aug 2019 11:29:36 -0700 Subject: [PATCH 08/33] refactored code --- ...rm-jsx-to-react-createElement-test.js.snap | 31 ++++ .../transform-jsx-to-react-jsx-test.js.snap | 26 ++- ...ansform-jsx-to-react-createElement-test.js | 25 ++- .../transform-jsx-to-react-jsx-test.js | 18 +- .../transform-jsx-to-react-jsx.js | 173 +++++++++++------- scripts/jest/preprocessor.js | 8 - 6 files changed, 191 insertions(+), 90 deletions(-) diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap index cdb9eebd249d8..6acbaacb4d0da 100644 --- a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap +++ b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap @@ -1,5 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`transform react to jsx React.Fragment to set keys and source 1`] = ` +"var _jsxFileName = \\"\\"; +var x = React.createElement(React.Fragment, { + key: \\"foo\\", + __source: { + fileName: _jsxFileName, + lineNumber: 1 + }, + __self: this +}, React.createElement(\\"div\\", { + __source: { + fileName: _jsxFileName, + lineNumber: 1 + }, + __self: this +}));" +`; + exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = ` "React.createElement(Component, Object.assign({}, props, { sound: \\"moo\\" @@ -90,6 +108,19 @@ exports[`transform react to jsx display name variable declaration 1`] = ` });" `; +exports[`transform react to jsx fragment with no children 1`] = `"var x = React.createElement(React.Fragment, null);"`; + +exports[`transform react to jsx normal fragments not to set key and source 1`] = ` +"var _jsxFileName = \\"\\"; +var x = React.createElement(React.Fragment, null, React.createElement(\\"div\\", { + __source: { + fileName: _jsxFileName, + lineNumber: 1 + }, + __self: this +}));" +`; + exports[`transform react to jsx should allow constructor as prop 1`] = ` "React.createElement(Component, { constructor: \\"foo\\" diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap index 24412cd51abd9..833e4b199dd96 100644 --- a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap +++ b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap @@ -1,9 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`transform react to jsx fragments 1`] = ` -"var x = React.jsx(React.Fragment, { - children: React.jsx(\\"div\\", {}) -});" +exports[`transform react to jsx React.fragment to set keys and source 1`] = ` +"var _jsxFileName = \\"\\"; +var x = React.jsxDEV(React.Fragment, {}, \\"foo\\", true, { + fileName: _jsxFileName, + lineNumber: 1 +}, this);" `; exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = ` @@ -102,17 +104,25 @@ exports[`transform react to jsx display name variable declaration 1`] = ` });" `; -exports[`transform react to jsx fragments in dev mode 1`] = ` +exports[`transform react to jsx fragment with no children 1`] = `"var x = React.jsx(React.Fragment, {});"`; + +exports[`transform react to jsx fragments 1`] = ` +"var x = React.jsx(React.Fragment, { + children: React.jsx(\\"div\\", {}) +});" +`; + +exports[`transform react to jsx fragments in dev mode (no key and source) 1`] = ` "var _jsxFileName = \\"\\"; var x = React.jsxDEV(React.Fragment, { children: React.jsxDEV(\\"div\\", {}, undefined, true, { fileName: _jsxFileName, lineNumber: 1 }, this) -}, undefined, true);" +}, undefined, false);" `; -exports[`transform react to jsx fragments to set keys 1`] = `"var x = React.jsx(React.Fragment, {}, 'foo');"`; +exports[`transform react to jsx fragments to set keys 1`] = `"var x = React.jsx(React.Fragment, {}, \\"foo\\");"`; exports[`transform react to jsx nonStatic children 1`] = ` "var _jsxFileName = \\"\\"; @@ -380,7 +390,7 @@ exports[`transform react to jsx uses jsxDEV instead of jsx in dev mode 1`] = ` var x = React.jsxDEV(\\"span\\", { propOne: \\"one\\", children: \\"Hi\\" -}, undefined, true, { +}, undefined, false, { fileName: _jsxFileName, lineNumber: 1 }, this);" diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js index df5f917191458..11c120d7bc7e3 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js @@ -16,11 +16,15 @@ function transform(input, options) { plugins: [ '@babel/plugin-syntax-jsx', '@babel/plugin-transform-arrow-functions', + ...(options && options.development + ? [ + '@babel/plugin-transform-react-jsx-source', + '@babel/plugin-transform-react-jsx-self', + ] + : []), [ './packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx', { - module: 'bluebird', - method: 'coroutine', development: __DEV__, useBuiltIns: true, useCreateElement: true, @@ -32,6 +36,23 @@ function transform(input, options) { } describe('transform react to jsx', () => { + it('fragment with no children', () => { + expect(transform(`var x = <>`)).toMatchSnapshot(); + }); + it('React.Fragment to set keys and source', () => { + expect( + transform(`var x =
`, { + development: true, + }) + ).toMatchSnapshot(); + }); + it('normal fragments not to set key and source', () => { + expect( + transform(`var x = <>
`, { + development: true, + }) + ).toMatchSnapshot(); + }); it('should properly handle comments adjacent to children', () => { expect( transform(` diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js index 1197e1f79080c..8e6e269b036df 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js @@ -25,8 +25,6 @@ function transform(input, options) { [ './packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx', { - module: 'bluebird', - method: 'coroutine', useBuiltIns: true, useCreateElement: false, ...options, @@ -37,15 +35,25 @@ function transform(input, options) { } describe('transform react to jsx', () => { - it(' fragments', () => { + it('fragment with no children', () => { + expect(transform(`var x = <>`)).toMatchSnapshot(); + }); + it('fragments', () => { expect(transform(`var x = <>
`)).toMatchSnapshot(); }); it('fragments to set keys', () => { expect( - transform(`var x = `) + transform(`var x = `) + ).toMatchSnapshot(); + }); + it('React.fragment to set keys and source', () => { + expect( + transform(`var x = `, { + development: true, + }) ).toMatchSnapshot(); }); - it('fragments in dev mode', () => { + it('fragments in dev mode (no key and source)', () => { expect( transform(`var x = <>
`, { development: true, diff --git a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js index c0cab605c0a55..a0650f13cbf21 100644 --- a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js +++ b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js @@ -26,7 +26,6 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, }; visitor.JSXElement = { - enter(path, state) {}, exit(path, file) { let callExpr; if (file.opts.useCreateElement || useCreateElement(path)) { @@ -48,8 +47,13 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, 'Fragment tags are only supported in React 16 and up.', ); } + let callExpr; + if (file.opts.useCreateElement) { + callExpr = buildCreateElementFragmentCall(path, file); + } else { + callExpr = buildJSXFragmentCall(path, file); + } - const callExpr = buildFragmentCall(path, file); if (callExpr) { path.replaceWith(t.inherits(callExpr, path.node)); } @@ -91,15 +95,6 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } } - function isStaticChildren(children) { - return !( - children.length === 1 && - (t.isArrayExpression(children[0]) || - t.isIdentifier(children[0]) || - t.isMemberExpression(children[0])) - ); - } - function convertAttribute(node) { const value = convertAttributeValue(node.value || t.booleanLiteral(true)); @@ -125,6 +120,34 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, return t.inherits(t.objectProperty(node.name, value), node); } + // We want to use React.createElement, even in the case of + // jsx, for
to distinguish it + // from
. This is an intermediary + // step while we deprecate key spread from props. Afterwards, + // we will remove createElement entirely + function useCreateElement(path) { + const openingPath = path.get('openingElement'); + const attributes = openingPath.node.attributes; + + let seenPropsSpread = false; + for (let i = 0, length = attributes.length; i < length; i++) { + const attr = attributes[i]; + if ( + seenPropsSpread && + t.isJSXAttribute(attr) && + attr.name.name === 'key' + ) { + return true; + } else if (t.isJSXSpreadAttribute(attr)) { + seenPropsSpread = true; + } + } + return false; + } + + // Builds JSX into: + // Production: React.jsx(type, arguments, key) + // Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self) function buildJSXElementCall(path, file) { if (opts.filter && !opts.filter(path.node, file)) return; @@ -187,20 +210,21 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, path.node.children, ); } else { + // attributes should never be null attribs = t.objectExpression([]); } args.push(attribs); - // __source and __self are only used in development if (!file.opts.development) { if (key !== undefined) { args.push(key); } } else { + // isStaticChildren, __source, and __self are only used in development args.push( key === undefined ? t.identifier('undefined') : key, - t.booleanLiteral(isStaticChildren(path.node.children)), + t.booleanLiteral(path.node.children.length !== 1), source === undefined ? t.identifier('undefined') : source, self === undefined ? t.identifier('undefined') : self, ); @@ -212,12 +236,6 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, return state.call || t.callExpression(state.callee, args); } - /** - * The logic for this is quite terse. It's because we need to - * support spread elements. We loop over all attributes, - * breaking on spreads, we then push a new object containing - * all prior attributes to an array for later processing. - */ function buildJSXOpeningElementAttributes(attribs, file, children) { let _props = []; const objs = []; @@ -236,8 +254,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, _props = pushProps(_props, objs); objs.push(prop.argument); } else { - const attr = convertAttribute(prop); - _props.push(attr); + _props.push(convertAttribute(prop)); } } @@ -255,6 +272,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, ); } } + pushProps(_props, objs); if (objs.length === 1) { @@ -265,10 +283,9 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, ? t.memberExpression(t.identifier('Object'), t.identifier('assign')) : file.addHelper('extends'); - // spread it attribs = t.callExpression(expressionHelper, [ t.objectExpression([]), - ...objs, + objs[0], ]); } else { attribs = objs[0]; @@ -290,6 +307,69 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, return attribs; } + // Builds JSX Fragment <> into + // Production: React.jsx(type, arguments) + // Development: React.jsxDEV(type, { children}) + function buildJSXFragmentCall(path, file) { + if (opts.filter && !opts.filter(path.node, file)) return; + + const openingPath = path.get('openingElement'); + openingPath.parent.children = t.react.buildChildren(openingPath.parent); + + const args = []; + const tagName = null; + const tagExpr = file.get('jsxFragIdentifier')(); + + const state = { + tagExpr: tagExpr, + tagName: tagName, + args: args, + }; + + if (opts.pre) { + opts.pre(state, file); + } + + let childrenNode; + if (path.node.children.length > 0) { + if (path.node.children.length === 1) { + childrenNode = path.node.children[0]; + } else { + childrenNode = t.arrayExpression(path.node.children); + } + } + + args.push( + t.objectExpression( + childrenNode !== undefined + ? [t.objectProperty(t.identifier('children'), childrenNode)] + : [], + ), + ); + + if (file.opts.development) { + args.push( + t.identifier('undefined'), + t.booleanLiteral(path.node.children.length !== 1), + ); + } + + if (opts.post) { + opts.post(state, file); + } + + return ( + state.call || + t.callExpression( + file.opts.useCreateElement ? state.oldCallee : state.callee, + args, + ) + ); + } + + // Builds JSX into: + // Production: React.createElement(type, arguments, children) + // Development: React.createElement(type, arguments, children, source, self) function buildCreateElementCall(path, file) { if (opts.filter && !opts.filter(path.node, file)) return; @@ -393,7 +473,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, return attribs; } - function buildFragmentCall(path, file) { + function buildCreateElementFragmentCall(path, file) { if (opts.filter && !opts.filter(path.node, file)) return; const openingPath = path.get('openingElement'); @@ -413,29 +493,8 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, opts.pre(state, file); } - if (file.opts.useCreateElement) { - args.push(t.nullLiteral(), ...path.node.children); - } else { - let childrenNode; - if (path.node.children.length === 0) { - childrenNode = t.nullLiteral(); - } else if (path.node.children.length === 1) { - childrenNode = path.node.children[0]; - } else { - childrenNode = t.arrayExpression(path.node.children); - } - args.push( - t.objectExpression([ - t.objectProperty(t.identifier('children'), childrenNode), - ]), - ); - if (file.opts.development) { - args.push( - t.identifier('undefined'), - t.booleanLiteral(isStaticChildren(path.node.children)), - ); - } - } + args.push(t.nullLiteral(), ...path.node.children); + if (opts.post) { opts.post(state, file); } @@ -448,26 +507,6 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, ) ); } - - function useCreateElement(path) { - const openingPath = path.get('openingElement'); - const attributes = openingPath.node.attributes; - - let seenPropsSpread = false; - for (let i = 0, length = attributes.length; i < length; i++) { - const attr = attributes[i]; - if ( - seenPropsSpread && - t.isJSXAttribute(attr) && - attr.name.name === 'key' - ) { - return true; - } else if (t.isJSXSpreadAttribute(attr)) { - seenPropsSpread = true; - } - } - return false; - } } module.exports = function(babel) { diff --git a/scripts/jest/preprocessor.js b/scripts/jest/preprocessor.js index f049b089bad34..0bd11adf68b49 100644 --- a/scripts/jest/preprocessor.js +++ b/scripts/jest/preprocessor.js @@ -42,14 +42,6 @@ const babelOptions = { // TODO: I have not verified that this actually works. require.resolve('@babel/plugin-transform-react-jsx-source'), - [ - pathToBabelReactJSXTransform, - { - development: process.env.NODE_ENV === 'development', - useCreateElement: true, - }, - ], - require.resolve('../babel/transform-prevent-infinite-loops'), ], retainLines: true, From 0645bbb8a7d9144d372871b14520ae6cae4564d2 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Fri, 16 Aug 2019 11:38:22 -0700 Subject: [PATCH 09/33] added another test --- .../transform-jsx-to-react-jsx-test.js.snap | 5 +++++ .../transform-jsx-to-react-jsx-test.js | 9 ++++++++- .../transform-jsx-to-react-jsx.js | 19 +++++-------------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap index 833e4b199dd96..e94281c6acc70 100644 --- a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap +++ b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap @@ -338,6 +338,11 @@ exports[`transform react to jsx should properly handle comments between props 1` });" `; +exports[`transform react to jsx should properly handle potentially null variables 1`] = ` +"var foo = null; +var x = React.jsx(\\"div\\", Object.assign({}, foo));" +`; + exports[`transform react to jsx should quote jsx attributes 1`] = ` "React.jsx(\\"button\\", { \\"data-value\\": \\"a value\\", diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js index 8e6e269b036df..69c47aafb1438 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js @@ -101,7 +101,14 @@ describe('transform react to jsx', () => { transform(`var x =
;`, {development: true}) ).toMatchSnapshot(); }); - + it('should properly handle potentially null variables', () => { + expect( + transform(` + var foo = null; + var x =
; + `) + ).toMatchSnapshot(); + }); it('properly handles keys', () => { expect( transform(`var x = ( diff --git a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js index a0650f13cbf21..5ea4aab1bcd32 100644 --- a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js +++ b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js @@ -236,6 +236,8 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, return state.call || t.callExpression(state.callee, args); } + // Builds props for React.jsx. This function adds children into the props + // and ensures that props is always an object function buildJSXOpeningElementAttributes(attribs, file, children) { let _props = []; const objs = []; @@ -358,13 +360,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, opts.post(state, file); } - return ( - state.call || - t.callExpression( - file.opts.useCreateElement ? state.oldCallee : state.callee, - args, - ) - ); + return state.call || t.callExpression(state.callee, args); } // Builds JSX into: @@ -493,19 +489,14 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, opts.pre(state, file); } + // no attributes are allowed with <> syntax args.push(t.nullLiteral(), ...path.node.children); if (opts.post) { opts.post(state, file); } - return ( - state.call || - t.callExpression( - file.opts.useCreateElement ? state.oldCallee : state.callee, - args, - ) - ); + return state.call || t.callExpression(state.oldCallee, args); } } From 518c1512dc9166b86f3d008e55fd76806359f277 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Fri, 16 Aug 2019 13:16:06 -0700 Subject: [PATCH 10/33] cleaned up more stuff --- packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js | 4 ++-- packages/react/src/ReactElementValidator.js | 1 - scripts/jest/preprocessor.js | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js index 5ea4aab1bcd32..5f7d0dd4be35e 100644 --- a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js +++ b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js @@ -397,7 +397,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, let attribs = openingPath.node.attributes; if (attribs.length) { - attribs = buildOpeningElementAttributes(attribs, file); + attribs = buildCreateElementOpeningElementAttributes(attribs, file); } else { attribs = t.nullLiteral(); } @@ -424,7 +424,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, * breaking on spreads, we then push a new object containing * all prior attributes to an array for later processing. */ - function buildOpeningElementAttributes(attribs, file) { + function buildCreateElementOpeningElementAttributes(attribs, file) { let _props = []; const objs = []; diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js index 15957988c7cb6..324c2449bddd0 100644 --- a/packages/react/src/ReactElementValidator.js +++ b/packages/react/src/ReactElementValidator.js @@ -336,7 +336,6 @@ export function jsxWithValidation( // 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) { const children = props.children; if (children !== undefined) { diff --git a/scripts/jest/preprocessor.js b/scripts/jest/preprocessor.js index 7a8b63942b315..44642ec91888d 100644 --- a/scripts/jest/preprocessor.js +++ b/scripts/jest/preprocessor.js @@ -22,9 +22,6 @@ const pathToBabelPluginWrapWarning = require.resolve( const pathToBabelPluginAsyncToGenerator = require.resolve( '@babel/plugin-transform-async-to-generator' ); -const pathToBabelReactJSXTransform = require.resolve( - '../../packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx' -); const pathToBabelrc = path.join(__dirname, '..', '..', 'babel.config.js'); const pathToErrorCodes = require.resolve('../error-codes/codes.json'); From 159c7f1834bc73c1b67c4515c44271c81d0525a7 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Fri, 16 Aug 2019 14:00:45 -0700 Subject: [PATCH 11/33] added package.json --- packages/react-jsx-babel-plugin/package.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 packages/react-jsx-babel-plugin/package.json diff --git a/packages/react-jsx-babel-plugin/package.json b/packages/react-jsx-babel-plugin/package.json new file mode 100644 index 0000000000000..0d7185b0fc572 --- /dev/null +++ b/packages/react-jsx-babel-plugin/package.json @@ -0,0 +1,8 @@ +{ + "name": "react-jsx-babel-plugin", + "version": "1.0.0", + "description": "@babel/plugin-transform-react-jsx", + "main": "transform-jsx-to-react-jsx.js", + "repository": "https://github.com/facebook/react.git", + "license": "MIT" +} From 938d1638343ae69d9a6b5a5e140e82edf8e064a3 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Fri, 16 Aug 2019 14:17:31 -0700 Subject: [PATCH 12/33] fixed linter --- ...ansform-jsx-to-react-createElement-test.js | 2 ++ .../transform-jsx-to-react-jsx-test.js | 2 ++ .../transform-jsx-to-react-jsx.js | 20 ++++++++++++++----- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js index 11c120d7bc7e3..8f4cc3abbd616 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js @@ -279,6 +279,7 @@ describe('transform react to jsx', () => { ).toMatchSnapshot(); }); it('should escape xhtml jsxtext', () => { + /* eslint-disable no-irregular-whitespace */ expect( transform(`
wow
; @@ -295,6 +296,7 @@ describe('transform react to jsx', () => {
w < w
; `) ).toMatchSnapshot(); + /*eslint-enable */ }); it('should handle attributed elements', () => { expect( diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js index 69c47aafb1438..08a48617c6809 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js @@ -360,6 +360,7 @@ describe('transform react to jsx', () => { ).toMatchSnapshot(); }); it('should escape xhtml jsxtext', () => { + /* eslint-disable no-irregular-whitespace */ expect( transform(`
wow
; @@ -376,6 +377,7 @@ describe('transform react to jsx', () => {
w < w
; `) ).toMatchSnapshot(); + /*eslint-enable */ }); it('should handle attributed elements', () => { expect( diff --git a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js index 5f7d0dd4be35e..d0a1029d3e0a1 100644 --- a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js +++ b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js @@ -149,7 +149,9 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, // Production: React.jsx(type, arguments, key) // Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self) function buildJSXElementCall(path, file) { - if (opts.filter && !opts.filter(path.node, file)) return; + if (opts.filter && !opts.filter(path.node, file)) { + return; + } const openingPath = path.get('openingElement'); openingPath.parent.children = t.react.buildChildren(openingPath.parent); @@ -313,7 +315,9 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, // Production: React.jsx(type, arguments) // Development: React.jsxDEV(type, { children}) function buildJSXFragmentCall(path, file) { - if (opts.filter && !opts.filter(path.node, file)) return; + if (opts.filter && !opts.filter(path.node, file)) { + return; + } const openingPath = path.get('openingElement'); openingPath.parent.children = t.react.buildChildren(openingPath.parent); @@ -367,7 +371,9 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, // Production: React.createElement(type, arguments, children) // Development: React.createElement(type, arguments, children, source, self) function buildCreateElementCall(path, file) { - if (opts.filter && !opts.filter(path.node, file)) return; + if (opts.filter && !opts.filter(path.node, file)) { + return; + } const openingPath = path.get('openingElement'); openingPath.parent.children = t.react.buildChildren(openingPath.parent); @@ -412,7 +418,9 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } function pushProps(_props, objs) { - if (!_props.length) return _props; + if (!_props.length) { + return _props; + } objs.push(t.objectExpression(_props)); return []; @@ -470,7 +478,9 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } function buildCreateElementFragmentCall(path, file) { - if (opts.filter && !opts.filter(path.node, file)) return; + if (opts.filter && !opts.filter(path.node, file)) { + return; + } const openingPath = path.get('openingElement'); openingPath.parent.children = t.react.buildChildren(openingPath.parent); From ad5b0eda559bc6d0596a39aa57b2ee25fe2b4a35 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Mon, 19 Aug 2019 15:55:45 -0700 Subject: [PATCH 13/33] updated tests and addressed some comments --- ...rm-jsx-to-react-createElement-test.js.snap | 66 ++------------- .../transform-jsx-to-react-jsx-test.js.snap | 66 ++------------- ...ansform-jsx-to-react-createElement-test.js | 82 ++----------------- .../transform-jsx-to-react-jsx-test.js | 82 ++----------------- .../transform-jsx-to-react-jsx.js | 2 +- packages/react/src/ReactElement.js | 31 ++++--- 6 files changed, 42 insertions(+), 287 deletions(-) diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap index 6acbaacb4d0da..ac3405810ff48 100644 --- a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap +++ b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap @@ -50,64 +50,6 @@ exports[`transform react to jsx assignment 1`] = ` exports[`transform react to jsx concatenates adjacent string literals 1`] = `"var x = React.createElement(\\"div\\", null, \\"foo\\", \\"bar\\", \\"baz\\", React.createElement(\\"div\\", null, \\"buz bang\\"), \\"qux\\", null, \\"quack\\");"`; -exports[`transform react to jsx display name assignment expression 1`] = ` -"var Component; -Component = React.createClass({ - render: function render() { - return null; - } -});" -`; - -exports[`transform react to jsx display name export default 1`] = ` -"export default React.createClass({ - render: function render() { - return null; - } -});" -`; - -exports[`transform react to jsx display name if missing 1`] = ` -"var Whateva = React.createClass({ - displayName: \\"Whatever\\", - render: function render() { - return null; - } -}); -var Bar = React.createClass({ - \\"displayName\\": \\"Ba\\", - render: function render() { - return null; - } -});" -`; - -exports[`transform react to jsx display name object declaration 1`] = ` -"exports = { - Component: React.createClass({ - render: function render() { - return null; - } - }) -};" -`; - -exports[`transform react to jsx display name property assignment 1`] = ` -"exports.Component = React.createClass({ - render: function render() { - return null; - } -});" -`; - -exports[`transform react to jsx display name variable declaration 1`] = ` -"var Component = React.createClass({ - render: function render() { - return null; - } -});" -`; - exports[`transform react to jsx fragment with no children 1`] = `"var x = React.createElement(React.Fragment, null);"`; exports[`transform react to jsx normal fragments not to set key and source 1`] = ` @@ -240,6 +182,14 @@ exports[`transform react to jsx should support xml namespaces if flag 1`] = ` exports[`transform react to jsx should transform known hyphenated tags 1`] = `"React.createElement(\\"font-face\\", null);"`; +exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = ` +"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +React.createElement(Component, _extends({ + y: 2 +}, x));" +`; + exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = ` "React.createElement(Component, Object.assign({}, x, { y: 2, diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap index e94281c6acc70..8530a3c375528 100644 --- a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap +++ b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap @@ -46,64 +46,6 @@ exports[`transform react to jsx concatenates adjacent string literals 1`] = ` });" `; -exports[`transform react to jsx display name assignment expression 1`] = ` -"var Component; -Component = React.createClass({ - render: function render() { - return null; - } -});" -`; - -exports[`transform react to jsx display name export default 1`] = ` -"export default React.createClass({ - render: function render() { - return null; - } -});" -`; - -exports[`transform react to jsx display name if missing 1`] = ` -"var Whateva = React.createClass({ - displayName: \\"Whatever\\", - render: function render() { - return null; - } -}); -var Bar = React.createClass({ - \\"displayName\\": \\"Ba\\", - render: function render() { - return null; - } -});" -`; - -exports[`transform react to jsx display name object declaration 1`] = ` -"exports = { - Component: React.createClass({ - render: function render() { - return null; - } - }) -};" -`; - -exports[`transform react to jsx display name property assignment 1`] = ` -"exports.Component = React.createClass({ - render: function render() { - return null; - } -});" -`; - -exports[`transform react to jsx display name variable declaration 1`] = ` -"var Component = React.createClass({ - render: function render() { - return null; - } -});" -`; - exports[`transform react to jsx fragment with no children 1`] = `"var x = React.jsx(React.Fragment, {});"`; exports[`transform react to jsx fragments 1`] = ` @@ -377,6 +319,14 @@ var x = React.jsxDEV(\\"div\\", { }, this);" `; +exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = ` +"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +React.jsx(Component, _extends({ + y: 2 +}, x));" +`; + exports[`transform react to jsx uses createElement when the key comes after a spread 1`] = ` "var x = React.createElement(\\"div\\", Object.assign({}, props, { key: \\"1\\", diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js index 8f4cc3abbd616..49d3c7fa07cd5 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js @@ -113,83 +113,6 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); - it('display name assignment expression', () => { - expect( - transform(` - var Component; - Component = React.createClass({ - render: function render() { - return null; - } - }); - `) - ).toMatchSnapshot(); - }); - it('display name export default', () => { - expect( - transform(` - export default React.createClass({ - render: function render() { - return null; - } - }); - `) - ).toMatchSnapshot(); - }); - it('display name if missing', () => { - expect( - transform(` - var Whateva = React.createClass({ - displayName: "Whatever", - render: function render() { - return null; - } - }); - - var Bar = React.createClass({ - "displayName": "Ba", - render: function render() { - return null; - } - }); - `) - ).toMatchSnapshot(); - }); - it('display name object declaration', () => { - expect( - transform(` - exports = { - Component: React.createClass({ - render: function render() { - return null; - } - }) - }; - `) - ).toMatchSnapshot(); - }); - it('display name property assignment', () => { - expect( - transform(` - exports.Component = React.createClass({ - render: function render() { - return null; - } - }); - `) - ).toMatchSnapshot(); - }); - it('display name variable declaration', () => { - expect( - transform(` - var Component = React.createClass({ - render: function render() { - return null; - } - }); - `) - ).toMatchSnapshot(); - }); it('should allow constructor as prop', () => { expect(transform(`;`)).toMatchSnapshot(); }); @@ -424,4 +347,9 @@ describe('transform react to jsx', () => { it('wraps props in react spread for middle spread attributes', () => { expect(transform(``)).toMatchSnapshot(); }); + it('useBuiltIns false uses extend instead of Object.assign', () => { + expect( + transform(``, {useBuiltIns: false}) + ).toMatchSnapshot(); + }); }); diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js index 08a48617c6809..8cfcf23f64098 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js @@ -194,83 +194,6 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); - it('display name assignment expression', () => { - expect( - transform(` - var Component; - Component = React.createClass({ - render: function render() { - return null; - } - }); - `) - ).toMatchSnapshot(); - }); - it('display name export default', () => { - expect( - transform(` - export default React.createClass({ - render: function render() { - return null; - } - }); - `) - ).toMatchSnapshot(); - }); - it('display name if missing', () => { - expect( - transform(` - var Whateva = React.createClass({ - displayName: "Whatever", - render: function render() { - return null; - } - }); - - var Bar = React.createClass({ - "displayName": "Ba", - render: function render() { - return null; - } - }); - `) - ).toMatchSnapshot(); - }); - it('display name object declaration', () => { - expect( - transform(` - exports = { - Component: React.createClass({ - render: function render() { - return null; - } - }) - }; - `) - ).toMatchSnapshot(); - }); - it('display name property assignment', () => { - expect( - transform(` - exports.Component = React.createClass({ - render: function render() { - return null; - } - }); - `) - ).toMatchSnapshot(); - }); - it('display name variable declaration', () => { - expect( - transform(` - var Component = React.createClass({ - render: function render() { - return null; - } - }); - `) - ).toMatchSnapshot(); - }); it('should allow constructor as prop', () => { expect(transform(`;`)).toMatchSnapshot(); }); @@ -505,4 +428,9 @@ describe('transform react to jsx', () => { it('wraps props in react spread for middle spread attributes', () => { expect(transform(``)).toMatchSnapshot(); }); + it('useBuiltIns false uses extend instead of Object.assign', () => { + expect( + transform(``, {useBuiltIns: false}) + ).toMatchSnapshot(); + }); }); diff --git a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js index d0a1029d3e0a1..dfc043cced4ea 100644 --- a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js +++ b/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js @@ -185,7 +185,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, let self; // for React.jsx, key, __source (dev), and __self (dev) is passed in as - //a separate argument rather than in the args object. We go through the + // a separate argument rather than in the args object. We go through the // props and filter out these three keywords so we can pass them in // as separate arguments later for (let i = 0, len = openingPath.node.attributes.length; i < len; i++) { diff --git a/packages/react/src/ReactElement.js b/packages/react/src/ReactElement.js index 858844c8e28e1..84e9d888328ff 100644 --- a/packages/react/src/ReactElement.js +++ b/packages/react/src/ReactElement.js @@ -12,7 +12,6 @@ import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; import ReactCurrentOwner from './ReactCurrentOwner'; const hasOwnProperty = Object.prototype.hasOwnProperty; -const freeze = Object.freeze; const RESERVED_PROPS = { key: true, @@ -156,9 +155,9 @@ const ReactElement = function(type, key, ref, self, source, owner, props) { writable: false, value: source, }); - if (freeze) { - freeze(element.props); - freeze(element); + if (Object.freeze) { + Object.freeze(element.props); + Object.freeze(element); } } @@ -190,14 +189,14 @@ export function jsx(type, config, maybeKey) { key = '' + maybeKey; } - if (hasValidRef(config)) { - ref = config.ref; - } - if (hasValidKey(config)) { key = '' + config.key; } + if (hasValidRef(config)) { + ref = config.ref; + } + // Remaining properties are added to a new props object for (propName in config) { if ( @@ -254,14 +253,14 @@ export function jsxDEV(type, config, maybeKey, source, self) { key = '' + maybeKey; } - if (hasValidRef(config)) { - ref = config.ref; - } - if (hasValidKey(config)) { key = '' + config.key; } + if (hasValidRef(config)) { + ref = config.ref; + } + // Remaining properties are added to a new props object for (propName in config) { if ( @@ -282,8 +281,8 @@ export function jsxDEV(type, config, maybeKey, source, self) { } } - if (freeze && Array.isArray(props.children)) { - freeze(props.children); + if (Object.freeze && Array.isArray(props.children)) { + Object.freeze(props.children); } if (key || ref) { @@ -357,8 +356,8 @@ export function createElement(type, config, children) { childArray[i] = arguments[i + 2]; } if (__DEV__) { - if (freeze) { - freeze(childArray); + if (Object.freeze) { + Object.freeze(childArray); } } props.children = childArray; From 1b07249c3bd6e628f28755135396a39e5e261f88 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Tue, 20 Aug 2019 10:59:09 -0700 Subject: [PATCH 14/33] components with static children use jsxs. made babel plugin part of the build --- packages/react-jsx-babel-plugin/README.md | 5 ++ .../transform-jsx-to-react-jsx-test.js.snap | 54 +++++++++---------- ...ansform-jsx-to-react-createElement-test.js | 2 +- .../transform-jsx-to-react-jsx-test.js | 2 +- packages/react-jsx-babel-plugin/index.js | 3 ++ packages/react-jsx-babel-plugin/npm/index.js | 7 +++ packages/react-jsx-babel-plugin/package.json | 20 +++++-- .../TransformJSXToReactBabelPlugin.js} | 32 +++++++++-- scripts/rollup/bundles.js | 8 +++ 9 files changed, 96 insertions(+), 37 deletions(-) create mode 100644 packages/react-jsx-babel-plugin/README.md create mode 100644 packages/react-jsx-babel-plugin/index.js create mode 100644 packages/react-jsx-babel-plugin/npm/index.js rename packages/react-jsx-babel-plugin/{transform-jsx-to-react-jsx.js => src/TransformJSXToReactBabelPlugin.js} (95%) diff --git a/packages/react-jsx-babel-plugin/README.md b/packages/react-jsx-babel-plugin/README.md new file mode 100644 index 0000000000000..3e2858d310c65 --- /dev/null +++ b/packages/react-jsx-babel-plugin/README.md @@ -0,0 +1,5 @@ +This package is intended to eventually replace the current `@babel/plugin-transform-react-jsx`, changing the JSX transform from targeting `React.createElement(type, props, children)` to `React.jsx(types, props, key)`. + +https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md + +**This is experimental and not intended to be used directly.** diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap index 8530a3c375528..510de731536f4 100644 --- a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap +++ b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap @@ -40,7 +40,7 @@ exports[`transform react to jsx assignment 1`] = ` exports[`transform react to jsx concatenates adjacent string literals 1`] = ` "var x = React.jsx(\\"div\\", { - children: [\\"foo\\", \\"bar\\", \\"baz\\", React.jsx(\\"div\\", { + children: [\\"foo\\", \\"bar\\", \\"baz\\", React.jsxs(\\"div\\", { children: \\"buz bang\\" }), \\"qux\\", null, \\"quack\\"] });" @@ -49,7 +49,7 @@ exports[`transform react to jsx concatenates adjacent string literals 1`] = ` exports[`transform react to jsx fragment with no children 1`] = `"var x = React.jsx(React.Fragment, {});"`; exports[`transform react to jsx fragments 1`] = ` -"var x = React.jsx(React.Fragment, { +"var x = React.jsxs(React.Fragment, { children: React.jsx(\\"div\\", {}) });" `; @@ -115,18 +115,18 @@ exports[`transform react to jsx should allow elements as attributes 1`] = ` exports[`transform react to jsx should allow js namespacing 1`] = `"React.jsx(Namespace.Component, {});"`; exports[`transform react to jsx should allow nested fragments 1`] = ` -"React.jsx(\\"div\\", { +"React.jsxs(\\"div\\", { children: React.jsx(React.Fragment, { children: [React.jsx(React.Fragment, { - children: [React.jsx(\\"span\\", { + children: [React.jsxs(\\"span\\", { children: \\"Hello\\" - }), React.jsx(\\"span\\", { + }), React.jsxs(\\"span\\", { children: \\"world\\" })] }), React.jsx(React.Fragment, { - children: [React.jsx(\\"span\\", { + children: [React.jsxs(\\"span\\", { children: \\"Goodbye\\" - }), React.jsx(\\"span\\", { + }), React.jsxs(\\"span\\", { children: \\"world\\" })] })] @@ -135,16 +135,16 @@ exports[`transform react to jsx should allow nested fragments 1`] = ` `; exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = ` -"var x = React.jsx(\\"div\\", { +"var x = React.jsxs(\\"div\\", { children: React.jsx(Component, {}) }); -var x = React.jsx(\\"div\\", { +var x = React.jsxs(\\"div\\", { children: props.children }); -var x = React.jsx(Composite, { +var x = React.jsxs(Composite, { children: props.children }); -var x = React.jsx(Composite, { +var x = React.jsxs(Composite, { children: React.jsx(Composite2, {}) });" `; @@ -152,7 +152,7 @@ var x = React.jsx(Composite, { exports[`transform react to jsx should convert simple tags 1`] = `"var x = React.jsx(\\"div\\", {});"`; exports[`transform react to jsx should convert simple text 1`] = ` -"var x = React.jsx(\\"div\\", { +"var x = React.jsxs(\\"div\\", { children: \\"text\\" });" `; @@ -170,31 +170,31 @@ React.jsx(\\"div\\", { `; exports[`transform react to jsx should escape xhtml jsxtext 1`] = ` -"React.jsx(\\"div\\", { +"React.jsxs(\\"div\\", { children: \\"wow\\" }); -React.jsx(\\"div\\", { +React.jsxs(\\"div\\", { children: \\"w\\\\xF4w\\" }); -React.jsx(\\"div\\", { +React.jsxs(\\"div\\", { children: \\"w & w\\" }); -React.jsx(\\"div\\", { +React.jsxs(\\"div\\", { children: \\"w & w\\" }); -React.jsx(\\"div\\", { +React.jsxs(\\"div\\", { children: \\"w \\\\xA0 w\\" }); -React.jsx(\\"div\\", { +React.jsxs(\\"div\\", { children: \\"this should not parse as unicode: \\\\xA0\\" }); -React.jsx(\\"div\\", { +React.jsxs(\\"div\\", { children: \\"this should parse as nbsp: \\\\xA0 \\" }); React.jsx(\\"div\\", { children: [\\"this should parse as unicode: \\", '  '] }); -React.jsx(\\"div\\", { +React.jsxs(\\"div\\", { children: \\"w < w\\" });" `; @@ -208,21 +208,21 @@ exports[`transform react to jsx should handle attributed elements 1`] = ` } }); React.render(React.jsx(HelloMessage, { - name: React.jsx(\\"span\\", { + name: React.jsxs(\\"span\\", { children: \\"Sebastian\\" }) }), mountNode);" `; exports[`transform react to jsx should handle has own property correctly 1`] = ` -"React.jsx(\\"hasOwnProperty\\", { +"React.jsxs(\\"hasOwnProperty\\", { children: \\"testing\\" });" `; exports[`transform react to jsx should have correct comma in nested children 1`] = ` "var x = React.jsx(\\"div\\", { - children: [React.jsx(\\"div\\", { + children: [React.jsxs(\\"div\\", { children: React.jsx(\\"br\\", {}) }), React.jsx(Component, { children: [foo, React.jsx(\\"br\\", {}), bar] @@ -251,13 +251,13 @@ exports[`transform react to jsx should not add quotes to identifier names 1`] = `; exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = ` -"React.jsx(\\"div\\", { +"React.jsxs(\\"div\\", { children: \\"\\\\xA0 \\" });" `; exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = ` -"React.jsx(\\"div\\", { +"React.jsxs(\\"div\\", { children: \\"\\\\xA0\\" });" `; @@ -269,7 +269,7 @@ exports[`transform react to jsx should properly handle comments adjacent to chil `; exports[`transform react to jsx should properly handle comments between props 1`] = ` -"var x = React.jsx(\\"div\\", { +"var x = React.jsxs(\\"div\\", { /* a multi-line comment */ attr1: \\"foo\\", @@ -286,7 +286,7 @@ var x = React.jsx(\\"div\\", Object.assign({}, foo));" `; exports[`transform react to jsx should quote jsx attributes 1`] = ` -"React.jsx(\\"button\\", { +"React.jsxs(\\"button\\", { \\"data-value\\": \\"a value\\", children: \\"Button\\" });" diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js index 49d3c7fa07cd5..1c77f630d0af2 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js @@ -23,7 +23,7 @@ function transform(input, options) { ] : []), [ - './packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx', + './packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin', { development: __DEV__, useBuiltIns: true, diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js index 8cfcf23f64098..fc92beabc4cd2 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js @@ -23,7 +23,7 @@ function transform(input, options) { ] : []), [ - './packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx', + './packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin', { useBuiltIns: true, useCreateElement: false, diff --git a/packages/react-jsx-babel-plugin/index.js b/packages/react-jsx-babel-plugin/index.js new file mode 100644 index 0000000000000..7401bda105727 --- /dev/null +++ b/packages/react-jsx-babel-plugin/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./src/TransformJSXToReactBabelPlugin'); diff --git a/packages/react-jsx-babel-plugin/npm/index.js b/packages/react-jsx-babel-plugin/npm/index.js new file mode 100644 index 0000000000000..af4ea6a44bb9a --- /dev/null +++ b/packages/react-jsx-babel-plugin/npm/index.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-jsx-babel.production.min.js'); +} else { + module.exports = require('./cjs/react-jsx-babel.development.js'); +} diff --git a/packages/react-jsx-babel-plugin/package.json b/packages/react-jsx-babel-plugin/package.json index 0d7185b0fc572..22b4780fd1f0f 100644 --- a/packages/react-jsx-babel-plugin/package.json +++ b/packages/react-jsx-babel-plugin/package.json @@ -2,7 +2,21 @@ "name": "react-jsx-babel-plugin", "version": "1.0.0", "description": "@babel/plugin-transform-react-jsx", - "main": "transform-jsx-to-react-jsx.js", - "repository": "https://github.com/facebook/react.git", - "license": "MIT" + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/facebook/react.git", + "directory": "packages/react-jsx-babel-plugin" + }, + "license": "MIT", + "dependencies": { + "esutils": "^2.0.0" + }, + "files": [ + "README.md", + "index.js", + "build-info.json", + "cjs/", + "umd/" + ] } diff --git a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js b/packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin.js similarity index 95% rename from packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js rename to packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin.js index dfc043cced4ea..0b39356ca8ed6 100644 --- a/packages/react-jsx-babel-plugin/transform-jsx-to-react-jsx.js +++ b/packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin.js @@ -1,9 +1,10 @@ 'use strict'; -const t = require('@babel/types'); const esutils = require('esutils'); -function helper(opts) { +function helper(babel, opts) { + const {types: t} = babel; + const visitor = {}; visitor.JSXNamespacedName = function(path, state) { @@ -235,7 +236,13 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, if (opts.post) { opts.post(state, file); } - return state.call || t.callExpression(state.callee, args); + return ( + state.call || + t.callExpression( + path.node.children.length === 1 ? state.staticCallee : state.callee, + args, + ) + ); } // Builds props for React.jsx. This function adds children into the props @@ -364,7 +371,13 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, opts.post(state, file); } - return state.call || t.callExpression(state.callee, args); + return ( + state.call || + t.callExpression( + path.node.children.length === 1 ? state.staticCallee : state.callee, + args, + ) + ); } // Builds JSX into: @@ -511,6 +524,8 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } module.exports = function(babel) { + const {types: t} = babel; + const createIdentifierParser = id => () => { return id .split('.') @@ -518,7 +533,7 @@ module.exports = function(babel) { .reduce((object, property) => t.memberExpression(object, property)); }; - const visitor = helper({ + const visitor = helper(babel, { pre(state) { const tagName = state.tagName; const args = state.args; @@ -531,6 +546,7 @@ module.exports = function(babel) { post(state, pass) { state.callee = pass.get('jsxIdentifier')(); + state.staticCallee = pass.get('jsxStaticIdentifier')(); state.oldCallee = pass.get('oldJSXIdentifier')(); }, }); @@ -547,6 +563,12 @@ module.exports = function(babel) { state.opts.development ? 'React.jsxDEV' : 'React.jsx', ), ); + state.set( + 'jsxStaticIdentifier', + createIdentifierParser( + state.opts.development ? 'React.jsxDEV' : 'React.jsxs', + ), + ); state.set('jsxFragIdentifier', createIdentifierParser('React.Fragment')); }, }; diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index fb36d65dde452..9da0f484c2dfe 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -455,6 +455,14 @@ const bundles = [ global: 'ESLintPluginReactHooks', externals: [], }, + /******** JSX to React.JSX Babel Plugin ********/ + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: ISOMORPHIC, + entry: 'react-jsx-babel-plugin', + global: 'TransformJSXToReactBabelPlugin', + externals: [], + }, /******* React Fresh *******/ { From 87e2dc5b60bf1c6b8bc16c3e071a4d5ece9e0e8d Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Tue, 20 Aug 2019 14:06:20 -0700 Subject: [PATCH 15/33] fixed isStaticChildren --- .../transform-jsx-to-react-jsx-test.js.snap | 90 +++++++++---------- .../src/TransformJSXToReactBabelPlugin.js | 8 +- packages/react/src/ReactElementValidator.js | 8 +- .../ReactElementJSX-test.internal.js | 12 +++ 4 files changed, 68 insertions(+), 50 deletions(-) diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap index 510de731536f4..8f48cd25af71c 100644 --- a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap +++ b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap @@ -2,7 +2,7 @@ exports[`transform react to jsx React.fragment to set keys and source 1`] = ` "var _jsxFileName = \\"\\"; -var x = React.jsxDEV(React.Fragment, {}, \\"foo\\", true, { +var x = React.jsxDEV(React.Fragment, {}, \\"foo\\", false, { fileName: _jsxFileName, lineNumber: 1 }, this);" @@ -39,8 +39,8 @@ exports[`transform react to jsx assignment 1`] = ` `; exports[`transform react to jsx concatenates adjacent string literals 1`] = ` -"var x = React.jsx(\\"div\\", { - children: [\\"foo\\", \\"bar\\", \\"baz\\", React.jsxs(\\"div\\", { +"var x = React.jsxs(\\"div\\", { + children: [\\"foo\\", \\"bar\\", \\"baz\\", React.jsx(\\"div\\", { children: \\"buz bang\\" }), \\"qux\\", null, \\"quack\\"] });" @@ -49,7 +49,7 @@ exports[`transform react to jsx concatenates adjacent string literals 1`] = ` exports[`transform react to jsx fragment with no children 1`] = `"var x = React.jsx(React.Fragment, {});"`; exports[`transform react to jsx fragments 1`] = ` -"var x = React.jsxs(React.Fragment, { +"var x = React.jsx(React.Fragment, { children: React.jsx(\\"div\\", {}) });" `; @@ -57,7 +57,7 @@ exports[`transform react to jsx fragments 1`] = ` exports[`transform react to jsx fragments in dev mode (no key and source) 1`] = ` "var _jsxFileName = \\"\\"; var x = React.jsxDEV(React.Fragment, { - children: React.jsxDEV(\\"div\\", {}, undefined, true, { + children: React.jsxDEV(\\"div\\", {}, undefined, false, { fileName: _jsxFileName, lineNumber: 1 }, this) @@ -69,10 +69,10 @@ exports[`transform react to jsx fragments to set keys 1`] = `"var x = React.jsx( exports[`transform react to jsx nonStatic children 1`] = ` "var _jsxFileName = \\"\\"; var x = React.jsxDEV(\\"div\\", { - children: [React.jsxDEV(\\"span\\", {}, '0', true, { + children: [React.jsxDEV(\\"span\\", {}, '0', false, { fileName: _jsxFileName, lineNumber: 3 - }, this), React.jsxDEV(\\"span\\", {}, '1', true, { + }, this), React.jsxDEV(\\"span\\", {}, '1', false, { fileName: _jsxFileName, lineNumber: 3 }, this)] @@ -83,7 +83,7 @@ var x = React.jsxDEV(\\"div\\", { `; exports[`transform react to jsx properly handles keys 1`] = ` -"var x = React.jsx(\\"div\\", { +"var x = React.jsxs(\\"div\\", { children: [React.jsx(\\"div\\", {}, \\"1\\"), React.jsx(\\"div\\", { meow: \\"wolf\\" }, \\"2\\"), React.jsx(\\"div\\", {}, \\"3\\")] @@ -92,7 +92,7 @@ exports[`transform react to jsx properly handles keys 1`] = ` exports[`transform react to jsx properly passes in source and self 1`] = ` "var _jsxFileName = \\"\\"; -var x = React.jsxDEV(\\"div\\", {}, undefined, true, { +var x = React.jsxDEV(\\"div\\", {}, undefined, false, { fileName: _jsxFileName, lineNumber: 1 }, this);" @@ -115,18 +115,18 @@ exports[`transform react to jsx should allow elements as attributes 1`] = ` exports[`transform react to jsx should allow js namespacing 1`] = `"React.jsx(Namespace.Component, {});"`; exports[`transform react to jsx should allow nested fragments 1`] = ` -"React.jsxs(\\"div\\", { - children: React.jsx(React.Fragment, { - children: [React.jsx(React.Fragment, { - children: [React.jsxs(\\"span\\", { +"React.jsx(\\"div\\", { + children: React.jsxs(React.Fragment, { + children: [React.jsxs(React.Fragment, { + children: [React.jsx(\\"span\\", { children: \\"Hello\\" - }), React.jsxs(\\"span\\", { + }), React.jsx(\\"span\\", { children: \\"world\\" })] - }), React.jsx(React.Fragment, { - children: [React.jsxs(\\"span\\", { + }), React.jsxs(React.Fragment, { + children: [React.jsx(\\"span\\", { children: \\"Goodbye\\" - }), React.jsxs(\\"span\\", { + }), React.jsx(\\"span\\", { children: \\"world\\" })] })] @@ -135,16 +135,16 @@ exports[`transform react to jsx should allow nested fragments 1`] = ` `; exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = ` -"var x = React.jsxs(\\"div\\", { +"var x = React.jsx(\\"div\\", { children: React.jsx(Component, {}) }); -var x = React.jsxs(\\"div\\", { +var x = React.jsx(\\"div\\", { children: props.children }); -var x = React.jsxs(Composite, { +var x = React.jsx(Composite, { children: props.children }); -var x = React.jsxs(Composite, { +var x = React.jsx(Composite, { children: React.jsx(Composite2, {}) });" `; @@ -152,7 +152,7 @@ var x = React.jsxs(Composite, { exports[`transform react to jsx should convert simple tags 1`] = `"var x = React.jsx(\\"div\\", {});"`; exports[`transform react to jsx should convert simple text 1`] = ` -"var x = React.jsxs(\\"div\\", { +"var x = React.jsx(\\"div\\", { children: \\"text\\" });" `; @@ -170,31 +170,31 @@ React.jsx(\\"div\\", { `; exports[`transform react to jsx should escape xhtml jsxtext 1`] = ` -"React.jsxs(\\"div\\", { +"React.jsx(\\"div\\", { children: \\"wow\\" }); -React.jsxs(\\"div\\", { +React.jsx(\\"div\\", { children: \\"w\\\\xF4w\\" }); -React.jsxs(\\"div\\", { +React.jsx(\\"div\\", { children: \\"w & w\\" }); -React.jsxs(\\"div\\", { +React.jsx(\\"div\\", { children: \\"w & w\\" }); -React.jsxs(\\"div\\", { +React.jsx(\\"div\\", { children: \\"w \\\\xA0 w\\" }); -React.jsxs(\\"div\\", { +React.jsx(\\"div\\", { children: \\"this should not parse as unicode: \\\\xA0\\" }); -React.jsxs(\\"div\\", { +React.jsx(\\"div\\", { children: \\"this should parse as nbsp: \\\\xA0 \\" }); -React.jsx(\\"div\\", { +React.jsxs(\\"div\\", { children: [\\"this should parse as unicode: \\", '  '] }); -React.jsxs(\\"div\\", { +React.jsx(\\"div\\", { children: \\"w < w\\" });" `; @@ -202,29 +202,29 @@ React.jsxs(\\"div\\", { exports[`transform react to jsx should handle attributed elements 1`] = ` "var HelloMessage = React.createClass({ render: function () { - return React.jsx(\\"div\\", { + return React.jsxs(\\"div\\", { children: [\\"Hello \\", this.props.name] }); } }); React.render(React.jsx(HelloMessage, { - name: React.jsxs(\\"span\\", { + name: React.jsx(\\"span\\", { children: \\"Sebastian\\" }) }), mountNode);" `; exports[`transform react to jsx should handle has own property correctly 1`] = ` -"React.jsxs(\\"hasOwnProperty\\", { +"React.jsx(\\"hasOwnProperty\\", { children: \\"testing\\" });" `; exports[`transform react to jsx should have correct comma in nested children 1`] = ` -"var x = React.jsx(\\"div\\", { - children: [React.jsxs(\\"div\\", { +"var x = React.jsxs(\\"div\\", { + children: [React.jsx(\\"div\\", { children: React.jsx(\\"br\\", {}) - }), React.jsx(Component, { + }), React.jsxs(Component, { children: [foo, React.jsx(\\"br\\", {}), bar] }), React.jsx(\\"br\\", {})] });" @@ -251,25 +251,25 @@ exports[`transform react to jsx should not add quotes to identifier names 1`] = `; exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = ` -"React.jsxs(\\"div\\", { +"React.jsx(\\"div\\", { children: \\"\\\\xA0 \\" });" `; exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = ` -"React.jsxs(\\"div\\", { +"React.jsx(\\"div\\", { children: \\"\\\\xA0\\" });" `; exports[`transform react to jsx should properly handle comments adjacent to children 1`] = ` -"var x = React.jsx(\\"div\\", { +"var x = React.jsxs(\\"div\\", { children: [React.jsx(\\"span\\", {}), React.jsx(\\"br\\", {})] });" `; exports[`transform react to jsx should properly handle comments between props 1`] = ` -"var x = React.jsxs(\\"div\\", { +"var x = React.jsx(\\"div\\", { /* a multi-line comment */ attr1: \\"foo\\", @@ -286,7 +286,7 @@ var x = React.jsx(\\"div\\", Object.assign({}, foo));" `; exports[`transform react to jsx should quote jsx attributes 1`] = ` -"React.jsxs(\\"button\\", { +"React.jsx(\\"button\\", { \\"data-value\\": \\"a value\\", children: \\"Button\\" });" @@ -303,13 +303,13 @@ exports[`transform react to jsx should transform known hyphenated tags 1`] = `"R exports[`transform react to jsx static children 1`] = ` "var _jsxFileName = \\"\\"; var x = React.jsxDEV(\\"div\\", { - children: [React.jsxDEV(\\"span\\", {}, undefined, true, { + children: [React.jsxDEV(\\"span\\", {}, undefined, false, { fileName: _jsxFileName, lineNumber: 3 - }, this), [React.jsxDEV(\\"span\\", {}, '0', true, { + }, this), [React.jsxDEV(\\"span\\", {}, '0', false, { fileName: _jsxFileName, lineNumber: 4 - }, this), React.jsxDEV(\\"span\\", {}, '1', true, { + }, this), React.jsxDEV(\\"span\\", {}, '1', false, { fileName: _jsxFileName, lineNumber: 4 }, this)]] diff --git a/packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin.js b/packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin.js index 0b39356ca8ed6..186a8288cf428 100644 --- a/packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin.js +++ b/packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin.js @@ -227,7 +227,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, // isStaticChildren, __source, and __self are only used in development args.push( key === undefined ? t.identifier('undefined') : key, - t.booleanLiteral(path.node.children.length !== 1), + t.booleanLiteral(path.node.children.length > 1), source === undefined ? t.identifier('undefined') : source, self === undefined ? t.identifier('undefined') : self, ); @@ -239,7 +239,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, return ( state.call || t.callExpression( - path.node.children.length === 1 ? state.staticCallee : state.callee, + path.node.children.length > 1 ? state.staticCallee : state.callee, args, ) ); @@ -363,7 +363,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, if (file.opts.development) { args.push( t.identifier('undefined'), - t.booleanLiteral(path.node.children.length !== 1), + t.booleanLiteral(path.node.children.length > 1), ); } @@ -374,7 +374,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, return ( state.call || t.callExpression( - path.node.children.length === 1 ? state.staticCallee : state.callee, + path.node.children.length > 1 ? state.staticCallee : state.callee, args, ) ); diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js index 324c2449bddd0..44ac18239d408 100644 --- a/packages/react/src/ReactElementValidator.js +++ b/packages/react/src/ReactElementValidator.js @@ -336,6 +336,7 @@ export function jsxWithValidation( // 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) { const children = props.children; if (children !== undefined) { @@ -345,7 +346,12 @@ export function jsxWithValidation( validateChildKeys(children[i], type); } } else { - validateChildKeys(children, type); + warning( + false, + 'React.jsx: Static children should always be an array. ' + + 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' + + 'Use the babel transform instead.', + ); } } else { validateChildKeys(children, type); diff --git a/packages/react/src/__tests__/ReactElementJSX-test.internal.js b/packages/react/src/__tests__/ReactElementJSX-test.internal.js index db62a2c51ec15..602cc3024a3f6 100644 --- a/packages/react/src/__tests__/ReactElementJSX-test.internal.js +++ b/packages/react/src/__tests__/ReactElementJSX-test.internal.js @@ -215,6 +215,18 @@ describe('ReactElement.jsx', () => { ); }); + it('warns when a jsxs is passed something that is not an array', () => { + const container = document.createElement('div'); + expect(() => + ReactDOM.render(React.jsxs('div', {children: 'foo'}, null), container), + ).toWarnDev( + 'React.jsx: Static children should always be an array. ' + + 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' + + 'Use the babel transform instead.', + {withoutStack: true}, + ); + }); + it('should warn when `key` is being accessed on a host element', () => { const element = React.jsxs('div', {}, '3'); expect(() => void element.props.key).toWarnDev( From 737ebb547a86bce4e621c2e2b44a1541aa0a7d2d Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Tue, 20 Aug 2019 14:16:43 -0700 Subject: [PATCH 16/33] modified tests to not import source and removed bundle code for separate PR --- .../transform-jsx-to-react-createElement-test.js | 2 +- .../__tests__/transform-jsx-to-react-jsx-test.js | 2 +- scripts/rollup/bundles.js | 8 -------- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js index 1c77f630d0af2..46fcbbe307e85 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js @@ -23,7 +23,7 @@ function transform(input, options) { ] : []), [ - './packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin', + './packages/react-jsx-babel-plugin', { development: __DEV__, useBuiltIns: true, diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js index fc92beabc4cd2..409f8b59294ad 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js +++ b/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js @@ -23,7 +23,7 @@ function transform(input, options) { ] : []), [ - './packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin', + './packages/react-jsx-babel-plugin', { useBuiltIns: true, useCreateElement: false, diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 9da0f484c2dfe..fb36d65dde452 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -455,14 +455,6 @@ const bundles = [ global: 'ESLintPluginReactHooks', externals: [], }, - /******** JSX to React.JSX Babel Plugin ********/ - { - bundleTypes: [NODE_DEV, NODE_PROD], - moduleType: ISOMORPHIC, - entry: 'react-jsx-babel-plugin', - global: 'TransformJSXToReactBabelPlugin', - externals: [], - }, /******* React Fresh *******/ { From aa83e963adb1b2ce662ebdc8cbc29a6585632112 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Thu, 22 Aug 2019 16:34:12 -0700 Subject: [PATCH 17/33] edits from comments --- .../README.md | 0 .../TransformJSXToReactCreateElement-test.js} | 79 +++-- .../__tests__/TransformJSXToReactJSX-test.js} | 87 +++-- ...nsformJSXToReactCreateElement-test.js.snap | 213 ++++++++++++ .../TransformJSXToReactJSX-test.js.snap} | 324 +++++++++--------- .../index.js | 0 .../npm/index.js | 0 .../package.json | 12 +- .../src/TransformJSXToReactBabelPlugin.js | 38 +- ...rm-jsx-to-react-createElement-test.js.snap | 213 ------------ packages/react/src/ReactElementValidator.js | 2 +- .../ReactElementJSX-test.internal.js | 2 +- 12 files changed, 540 insertions(+), 430 deletions(-) rename packages/{react-jsx-babel-plugin => babel-plugin-react-jsx}/README.md (100%) rename packages/{react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js => babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js} (92%) rename packages/{react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js => babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js} (94%) create mode 100644 packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactCreateElement-test.js.snap rename packages/{react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap => babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap} (50%) rename packages/{react-jsx-babel-plugin => babel-plugin-react-jsx}/index.js (100%) rename packages/{react-jsx-babel-plugin => babel-plugin-react-jsx}/npm/index.js (100%) rename packages/{react-jsx-babel-plugin => babel-plugin-react-jsx}/package.json (50%) rename packages/{react-jsx-babel-plugin => babel-plugin-react-jsx}/src/TransformJSXToReactBabelPlugin.js (90%) delete mode 100644 packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap diff --git a/packages/react-jsx-babel-plugin/README.md b/packages/babel-plugin-react-jsx/README.md similarity index 100% rename from packages/react-jsx-babel-plugin/README.md rename to packages/babel-plugin-react-jsx/README.md diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js similarity index 92% rename from packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js rename to packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js index 46fcbbe307e85..6e44eaeaec95b 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-createElement-test.js +++ b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js @@ -9,36 +9,40 @@ const babel = require('@babel/core'); const codeFrame = require('@babel/code-frame'); +const {wrap} = require('jest-snapshot-serializer-raw'); function transform(input, options) { - return babel.transform(input, { - configFile: false, - plugins: [ - '@babel/plugin-syntax-jsx', - '@babel/plugin-transform-arrow-functions', - ...(options && options.development - ? [ - '@babel/plugin-transform-react-jsx-source', - '@babel/plugin-transform-react-jsx-self', - ] - : []), - [ - './packages/react-jsx-babel-plugin', - { - development: __DEV__, - useBuiltIns: true, - useCreateElement: true, - ...options, - }, + return wrap( + babel.transform(input, { + configFile: false, + plugins: [ + '@babel/plugin-syntax-jsx', + '@babel/plugin-transform-arrow-functions', + ...(options && options.development + ? [ + '@babel/plugin-transform-react-jsx-source', + '@babel/plugin-transform-react-jsx-self', + ] + : []), + [ + './packages/babel-plugin-react-jsx', + { + development: __DEV__, + useBuiltIns: true, + useCreateElement: true, + ...options, + }, + ], ], - ], - }).code; + }).code + ); } describe('transform react to jsx', () => { it('fragment with no children', () => { expect(transform(`var x = <>`)).toMatchSnapshot(); }); + it('React.Fragment to set keys and source', () => { expect( transform(`var x =
`, { @@ -46,6 +50,7 @@ describe('transform react to jsx', () => { }) ).toMatchSnapshot(); }); + it('normal fragments not to set key and source', () => { expect( transform(`var x = <>
`, { @@ -53,6 +58,7 @@ describe('transform react to jsx', () => { }) ).toMatchSnapshot(); }); + it('should properly handle comments adjacent to children', () => { expect( transform(` @@ -72,9 +78,11 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('adds appropriate new lines when using spread attribute', () => { expect(transform(``)).toMatchSnapshot(); }); + it('arrow functions', () => { expect( transform(` @@ -89,11 +97,13 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('assignment', () => { expect( transform(`var div = `) ).toMatchSnapshot(); }); + it('concatenates adjacent string literals', () => { expect( transform(` @@ -113,20 +123,25 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should allow constructor as prop', () => { expect(transform(`;`)).toMatchSnapshot(); }); + it('should allow deeper js namespacing', () => { expect( transform(`;`) ).toMatchSnapshot(); }); + it('should allow elements as attributes', () => { expect(transform(`
/>`)).toMatchSnapshot(); }); + it('should allow js namespacing', () => { expect(transform(`;`)).toMatchSnapshot(); }); + it('should allow nested fragments', () => { expect( transform(` @@ -145,6 +160,7 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should avoid wrapping in extra parens if not needed', () => { expect( transform(` @@ -166,12 +182,15 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should convert simple tags', () => { expect(transform(`var x =
;`)).toMatchSnapshot(); }); + it('should convert simple text', () => { expect(transform(`var x =
text
;`)).toMatchSnapshot(); }); + it('should disallow spread children', () => { let _error; const code = `
{...children}
;`; @@ -192,6 +211,7 @@ describe('transform react to jsx', () => { ) ); }); + it('should escape xhtml jsxattribute', () => { expect( transform(` @@ -201,6 +221,7 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should escape xhtml jsxtext', () => { /* eslint-disable no-irregular-whitespace */ expect( @@ -221,6 +242,7 @@ describe('transform react to jsx', () => { ).toMatchSnapshot(); /*eslint-enable */ }); + it('should handle attributed elements', () => { expect( transform(` @@ -238,11 +260,13 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should handle has own property correctly', () => { expect( transform(`testing;`) ).toMatchSnapshot(); }); + it('should have correct comma in nested children', () => { expect( transform(` @@ -254,6 +278,7 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should insert commas after expressions before whitespace', () => { expect( transform(` @@ -276,17 +301,21 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should not add quotes to identifier names', () => { expect( transform(`var e = ;`) ).toMatchSnapshot(); }); + it('should not strip nbsp even couple with other whitespace', () => { expect(transform(`
 
;`)).toMatchSnapshot(); }); + it('should not strip tags with a single child of nbsp', () => { expect(transform(`
 
;`)).toMatchSnapshot(); }); + it('should properly handle comments between props', () => { expect( transform(` @@ -303,16 +332,19 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should quote jsx attributes', () => { expect( transform(``) ).toMatchSnapshot(); }); + it('should support xml namespaces if flag', () => { expect( transform('', {throwIfNamespace: false}) ).toMatchSnapshot(); }); + it('should throw error namespaces if not flag', () => { let _error; const code = ``; @@ -335,18 +367,23 @@ describe('transform react to jsx', () => { ) ); }); + it('should transform known hyphenated tags', () => { expect(transform(``)).toMatchSnapshot(); }); + it('wraps props in react spread for first spread attributes', () => { expect(transform(``)).toMatchSnapshot(); }); + it('wraps props in react spread for last spread attributes', () => { expect(transform(``)).toMatchSnapshot(); }); + it('wraps props in react spread for middle spread attributes', () => { expect(transform(``)).toMatchSnapshot(); }); + it('useBuiltIns false uses extend instead of Object.assign', () => { expect( transform(``, {useBuiltIns: false}) diff --git a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js similarity index 94% rename from packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js rename to packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js index 409f8b59294ad..86ee84f1fa260 100644 --- a/packages/react-jsx-babel-plugin/__tests__/transform-jsx-to-react-jsx-test.js +++ b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js @@ -9,43 +9,49 @@ const babel = require('@babel/core'); const codeFrame = require('@babel/code-frame'); +const {wrap} = require('jest-snapshot-serializer-raw'); function transform(input, options) { - return babel.transform(input, { - configFile: false, - plugins: [ - '@babel/plugin-syntax-jsx', - '@babel/plugin-transform-arrow-functions', - ...(options && options.development - ? [ - '@babel/plugin-transform-react-jsx-source', - '@babel/plugin-transform-react-jsx-self', - ] - : []), - [ - './packages/react-jsx-babel-plugin', - { - useBuiltIns: true, - useCreateElement: false, - ...options, - }, + return wrap( + babel.transform(input, { + configFile: false, + plugins: [ + '@babel/plugin-syntax-jsx', + '@babel/plugin-transform-arrow-functions', + ...(options && options.development + ? [ + '@babel/plugin-transform-react-jsx-source', + '@babel/plugin-transform-react-jsx-self', + ] + : []), + [ + './packages/babel-plugin-react-jsx', + { + useBuiltIns: true, + useCreateElement: false, + ...options, + }, + ], ], - ], - }).code; + }).code + ); } describe('transform react to jsx', () => { it('fragment with no children', () => { expect(transform(`var x = <>`)).toMatchSnapshot(); }); + it('fragments', () => { expect(transform(`var x = <>
`)).toMatchSnapshot(); }); + it('fragments to set keys', () => { expect( transform(`var x = `) ).toMatchSnapshot(); }); + it('React.fragment to set keys and source', () => { expect( transform(`var x = `, { @@ -53,6 +59,7 @@ describe('transform react to jsx', () => { }) ).toMatchSnapshot(); }); + it('fragments in dev mode (no key and source)', () => { expect( transform(`var x = <>
`, { @@ -60,6 +67,7 @@ describe('transform react to jsx', () => { }) ).toMatchSnapshot(); }); + it('nonStatic children', () => { expect( transform( @@ -75,6 +83,7 @@ describe('transform react to jsx', () => { ) ).toMatchSnapshot(); }); + it('static children', () => { expect( transform( @@ -91,16 +100,19 @@ describe('transform react to jsx', () => { ) ).toMatchSnapshot(); }); + it('uses jsxDEV instead of jsx in dev mode', () => { expect( transform(`var x = Hi`, {development: true}) ).toMatchSnapshot(); }); + it('properly passes in source and self', () => { expect( transform(`var x =
;`, {development: true}) ).toMatchSnapshot(); }); + it('should properly handle potentially null variables', () => { expect( transform(` @@ -109,6 +121,7 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('properly handles keys', () => { expect( transform(`var x = ( @@ -120,6 +133,7 @@ describe('transform react to jsx', () => { );`) ).toMatchSnapshot(); }); + it('uses createElement when the key comes after a spread', () => { expect( transform(`var x = ( @@ -127,6 +141,7 @@ describe('transform react to jsx', () => { );`) ).toMatchSnapshot(); }); + it('uses jsx when the key comes before a spread', () => { expect( transform(`var x = ( @@ -134,6 +149,7 @@ describe('transform react to jsx', () => { );`) ).toMatchSnapshot(); }); + it('should properly handle comments adjacent to children', () => { expect( transform(` @@ -153,9 +169,11 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('adds appropriate new lines when using spread attribute', () => { expect(transform(``)).toMatchSnapshot(); }); + it('arrow functions', () => { expect( transform(` @@ -170,11 +188,13 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('assignment', () => { expect( transform(`var div = `) ).toMatchSnapshot(); }); + it('concatenates adjacent string literals', () => { expect( transform(` @@ -194,20 +214,25 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should allow constructor as prop', () => { expect(transform(`;`)).toMatchSnapshot(); }); + it('should allow deeper js namespacing', () => { expect( transform(`;`) ).toMatchSnapshot(); }); + it('should allow elements as attributes', () => { expect(transform(`
/>`)).toMatchSnapshot(); }); + it('should allow js namespacing', () => { expect(transform(`;`)).toMatchSnapshot(); }); + it('should allow nested fragments', () => { expect( transform(` @@ -226,6 +251,7 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should avoid wrapping in extra parens if not needed', () => { expect( transform(` @@ -247,12 +273,15 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should convert simple tags', () => { expect(transform(`var x =
;`)).toMatchSnapshot(); }); + it('should convert simple text', () => { expect(transform(`var x =
text
;`)).toMatchSnapshot(); }); + it('should disallow spread children', () => { let _error; const code = `
{...children}
;`; @@ -273,6 +302,7 @@ describe('transform react to jsx', () => { ) ); }); + it('should escape xhtml jsxattribute', () => { expect( transform(` @@ -282,6 +312,7 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should escape xhtml jsxtext', () => { /* eslint-disable no-irregular-whitespace */ expect( @@ -302,6 +333,7 @@ describe('transform react to jsx', () => { ).toMatchSnapshot(); /*eslint-enable */ }); + it('should handle attributed elements', () => { expect( transform(` @@ -319,11 +351,13 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should handle has own property correctly', () => { expect( transform(`testing;`) ).toMatchSnapshot(); }); + it('should have correct comma in nested children', () => { expect( transform(` @@ -335,6 +369,7 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should insert commas after expressions before whitespace', () => { expect( transform(` @@ -357,17 +392,21 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should not add quotes to identifier names', () => { expect( transform(`var e = ;`) ).toMatchSnapshot(); }); + it('should not strip nbsp even couple with other whitespace', () => { expect(transform(`
 
;`)).toMatchSnapshot(); }); + it('should not strip tags with a single child of nbsp', () => { expect(transform(`
 
;`)).toMatchSnapshot(); }); + it('should properly handle comments between props', () => { expect( transform(` @@ -384,16 +423,19 @@ describe('transform react to jsx', () => { `) ).toMatchSnapshot(); }); + it('should quote jsx attributes', () => { expect( transform(``) ).toMatchSnapshot(); }); + it('should support xml namespaces if flag', () => { expect( transform('', {throwIfNamespace: false}) ).toMatchSnapshot(); }); + it('should throw error namespaces if not flag', () => { let _error; const code = ``; @@ -416,18 +458,23 @@ describe('transform react to jsx', () => { ) ); }); + it('should transform known hyphenated tags', () => { expect(transform(``)).toMatchSnapshot(); }); + it('wraps props in react spread for first spread attributes', () => { expect(transform(``)).toMatchSnapshot(); }); + it('wraps props in react spread for last spread attributes', () => { expect(transform(``)).toMatchSnapshot(); }); + it('wraps props in react spread for middle spread attributes', () => { expect(transform(``)).toMatchSnapshot(); }); + it('useBuiltIns false uses extend instead of Object.assign', () => { expect( transform(``, {useBuiltIns: false}) diff --git a/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactCreateElement-test.js.snap b/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactCreateElement-test.js.snap new file mode 100644 index 0000000000000..a74c7e1d15e82 --- /dev/null +++ b/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactCreateElement-test.js.snap @@ -0,0 +1,213 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`transform react to jsx React.Fragment to set keys and source 1`] = ` +var _jsxFileName = ""; +var x = React.createElement(React.Fragment, { + key: "foo", + __source: { + fileName: _jsxFileName, + lineNumber: 1 + }, + __self: this +}, React.createElement("div", { + __source: { + fileName: _jsxFileName, + lineNumber: 1 + }, + __self: this +})); +`; + +exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = ` +React.createElement(Component, Object.assign({}, props, { + sound: "moo" +})); +`; + +exports[`transform react to jsx arrow functions 1`] = ` +var foo = function () { + var _this = this; + + return function () { + return React.createElement(_this, null); + }; +}; + +var bar = function () { + var _this2 = this; + + return function () { + return React.createElement(_this2.foo, null); + }; +}; +`; + +exports[`transform react to jsx assignment 1`] = ` +var div = React.createElement(Component, Object.assign({}, props, { + foo: "bar" +})); +`; + +exports[`transform react to jsx concatenates adjacent string literals 1`] = `var x = React.createElement("div", null, "foo", "bar", "baz", React.createElement("div", null, "buz bang"), "qux", null, "quack");`; + +exports[`transform react to jsx fragment with no children 1`] = `var x = React.createElement(React.Fragment, null);`; + +exports[`transform react to jsx normal fragments not to set key and source 1`] = ` +var _jsxFileName = ""; +var x = React.createElement(React.Fragment, null, React.createElement("div", { + __source: { + fileName: _jsxFileName, + lineNumber: 1 + }, + __self: this +})); +`; + +exports[`transform react to jsx should allow constructor as prop 1`] = ` +React.createElement(Component, { + constructor: "foo" +}); +`; + +exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.createElement(Namespace.DeepNamespace.Component, null);`; + +exports[`transform react to jsx should allow elements as attributes 1`] = ` +React.createElement("div", { + attr: React.createElement("div", null) +}); +`; + +exports[`transform react to jsx should allow js namespacing 1`] = `React.createElement(Namespace.Component, null);`; + +exports[`transform react to jsx should allow nested fragments 1`] = `React.createElement("div", null, React.createElement(React.Fragment, null, React.createElement(React.Fragment, null, React.createElement("span", null, "Hello"), React.createElement("span", null, "world")), React.createElement(React.Fragment, null, React.createElement("span", null, "Goodbye"), React.createElement("span", null, "world"))));`; + +exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = ` +var x = React.createElement("div", null, React.createElement(Component, null)); +var x = React.createElement("div", null, props.children); +var x = React.createElement(Composite, null, props.children); +var x = React.createElement(Composite, null, React.createElement(Composite2, null)); +`; + +exports[`transform react to jsx should convert simple tags 1`] = `var x = React.createElement("div", null);`; + +exports[`transform react to jsx should convert simple text 1`] = `var x = React.createElement("div", null, "text");`; + +exports[`transform react to jsx should escape xhtml jsxattribute 1`] = ` +React.createElement("div", { + id: "w\\xF4w" +}); +React.createElement("div", { + id: "w" +}); +React.createElement("div", { + id: "w < w" +}); +`; + +exports[`transform react to jsx should escape xhtml jsxtext 1`] = ` +React.createElement("div", null, "wow"); +React.createElement("div", null, "w\\xF4w"); +React.createElement("div", null, "w & w"); +React.createElement("div", null, "w & w"); +React.createElement("div", null, "w \\xA0 w"); +React.createElement("div", null, "this should not parse as unicode: \\xA0"); +React.createElement("div", null, "this should parse as nbsp: \\xA0 "); +React.createElement("div", null, "this should parse as unicode: ", '  '); +React.createElement("div", null, "w < w"); +`; + +exports[`transform react to jsx should handle attributed elements 1`] = ` +var HelloMessage = React.createClass({ + render: function () { + return React.createElement("div", null, "Hello ", this.props.name); + } +}); +React.render(React.createElement(HelloMessage, { + name: React.createElement("span", null, "Sebastian") +}), mountNode); +`; + +exports[`transform react to jsx should handle has own property correctly 1`] = `React.createElement("hasOwnProperty", null, "testing");`; + +exports[`transform react to jsx should have correct comma in nested children 1`] = `var x = React.createElement("div", null, React.createElement("div", null, React.createElement("br", null)), React.createElement(Component, null, foo, React.createElement("br", null), bar), React.createElement("br", null));`; + +exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = ` +var x = React.createElement("div", { + attr1: "foo" + "bar", + attr2: "foo" + "bar" + "baz" + "bug", + attr3: "foo" + "bar" + "baz" + "bug", + attr4: "baz" +}); +`; + +exports[`transform react to jsx should not add quotes to identifier names 1`] = ` +var e = React.createElement(F, { + aaa: true, + new: true, + const: true, + var: true, + default: true, + "foo-bar": true +}); +`; + +exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `React.createElement("div", null, "\\xA0 ");`; + +exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `React.createElement("div", null, "\\xA0");`; + +exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `var x = React.createElement("div", null, React.createElement("span", null), React.createElement("br", null));`; + +exports[`transform react to jsx should properly handle comments between props 1`] = ` +var x = React.createElement("div", { + /* a multi-line + comment */ + attr1: "foo" +}, React.createElement("span", { + // a double-slash comment + attr2: "bar" +})); +`; + +exports[`transform react to jsx should quote jsx attributes 1`] = ` +React.createElement("button", { + "data-value": "a value" +}, "Button"); +`; + +exports[`transform react to jsx should support xml namespaces if flag 1`] = ` +React.createElement("f:image", { + "n:attr": true +}); +`; + +exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.createElement("font-face", null);`; + +exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = ` +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +React.createElement(Component, _extends({ + y: 2 +}, x)); +`; + +exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = ` +React.createElement(Component, Object.assign({}, x, { + y: 2, + z: true +})); +`; + +exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = ` +React.createElement(Component, Object.assign({ + y: 2, + z: true +}, x)); +`; + +exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = ` +React.createElement(Component, Object.assign({ + y: 2 +}, x, { + z: true +})); +`; diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap b/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap similarity index 50% rename from packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap rename to packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap index 8f48cd25af71c..e5b844b12fc0d 100644 --- a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-jsx-test.js.snap +++ b/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap @@ -1,21 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`transform react to jsx React.fragment to set keys and source 1`] = ` -"var _jsxFileName = \\"\\"; -var x = React.jsxDEV(React.Fragment, {}, \\"foo\\", false, { +var _jsxFileName = ""; +var x = React.jsxDEV(React.Fragment, {}, "foo", false, { fileName: _jsxFileName, lineNumber: 1 -}, this);" +}, this); `; exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = ` -"React.jsx(Component, Object.assign({}, props, { - sound: \\"moo\\" -}));" +React.jsx(Component, Object.assign({}, props, { + sound: "moo" +})); `; exports[`transform react to jsx arrow functions 1`] = ` -"var foo = function () { +var foo = function () { var _this = this; return function () { @@ -29,116 +29,116 @@ var bar = function () { return function () { return React.jsx(_this2.foo, {}); }; -};" +}; `; exports[`transform react to jsx assignment 1`] = ` -"var div = React.jsx(Component, Object.assign({}, props, { - foo: \\"bar\\" -}));" +var div = React.jsx(Component, Object.assign({}, props, { + foo: "bar" +})); `; exports[`transform react to jsx concatenates adjacent string literals 1`] = ` -"var x = React.jsxs(\\"div\\", { - children: [\\"foo\\", \\"bar\\", \\"baz\\", React.jsx(\\"div\\", { - children: \\"buz bang\\" - }), \\"qux\\", null, \\"quack\\"] -});" +var x = React.jsxs("div", { + children: ["foo", "bar", "baz", React.jsx("div", { + children: "buz bang" + }), "qux", null, "quack"] +}); `; -exports[`transform react to jsx fragment with no children 1`] = `"var x = React.jsx(React.Fragment, {});"`; +exports[`transform react to jsx fragment with no children 1`] = `var x = React.jsx(React.Fragment, {});`; exports[`transform react to jsx fragments 1`] = ` -"var x = React.jsx(React.Fragment, { - children: React.jsx(\\"div\\", {}) -});" +var x = React.jsx(React.Fragment, { + children: React.jsx("div", {}) +}); `; exports[`transform react to jsx fragments in dev mode (no key and source) 1`] = ` -"var _jsxFileName = \\"\\"; +var _jsxFileName = ""; var x = React.jsxDEV(React.Fragment, { - children: React.jsxDEV(\\"div\\", {}, undefined, false, { + children: React.jsxDEV("div", {}, undefined, false, { fileName: _jsxFileName, lineNumber: 1 }, this) -}, undefined, false);" +}, undefined, false); `; -exports[`transform react to jsx fragments to set keys 1`] = `"var x = React.jsx(React.Fragment, {}, \\"foo\\");"`; +exports[`transform react to jsx fragments to set keys 1`] = `var x = React.jsx(React.Fragment, {}, "foo");`; exports[`transform react to jsx nonStatic children 1`] = ` -"var _jsxFileName = \\"\\"; -var x = React.jsxDEV(\\"div\\", { - children: [React.jsxDEV(\\"span\\", {}, '0', false, { +var _jsxFileName = ""; +var x = React.jsxDEV("div", { + children: [React.jsxDEV("span", {}, '0', false, { fileName: _jsxFileName, lineNumber: 3 - }, this), React.jsxDEV(\\"span\\", {}, '1', false, { + }, this), React.jsxDEV("span", {}, '1', false, { fileName: _jsxFileName, lineNumber: 3 }, this)] }, undefined, false, { fileName: _jsxFileName, lineNumber: 2 -}, this);" +}, this); `; exports[`transform react to jsx properly handles keys 1`] = ` -"var x = React.jsxs(\\"div\\", { - children: [React.jsx(\\"div\\", {}, \\"1\\"), React.jsx(\\"div\\", { - meow: \\"wolf\\" - }, \\"2\\"), React.jsx(\\"div\\", {}, \\"3\\")] -});" +var x = React.jsxs("div", { + children: [React.jsx("div", {}, "1"), React.jsx("div", { + meow: "wolf" + }, "2"), React.jsx("div", {}, "3")] +}); `; exports[`transform react to jsx properly passes in source and self 1`] = ` -"var _jsxFileName = \\"\\"; -var x = React.jsxDEV(\\"div\\", {}, undefined, false, { +var _jsxFileName = ""; +var x = React.jsxDEV("div", {}, undefined, false, { fileName: _jsxFileName, lineNumber: 1 -}, this);" +}, this); `; exports[`transform react to jsx should allow constructor as prop 1`] = ` -"React.jsx(Component, { - constructor: \\"foo\\" -});" +React.jsx(Component, { + constructor: "foo" +}); `; -exports[`transform react to jsx should allow deeper js namespacing 1`] = `"React.jsx(Namespace.DeepNamespace.Component, {});"`; +exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.jsx(Namespace.DeepNamespace.Component, {});`; exports[`transform react to jsx should allow elements as attributes 1`] = ` -"React.jsx(\\"div\\", { - attr: React.jsx(\\"div\\", {}) -});" +React.jsx("div", { + attr: React.jsx("div", {}) +}); `; -exports[`transform react to jsx should allow js namespacing 1`] = `"React.jsx(Namespace.Component, {});"`; +exports[`transform react to jsx should allow js namespacing 1`] = `React.jsx(Namespace.Component, {});`; exports[`transform react to jsx should allow nested fragments 1`] = ` -"React.jsx(\\"div\\", { +React.jsx("div", { children: React.jsxs(React.Fragment, { children: [React.jsxs(React.Fragment, { - children: [React.jsx(\\"span\\", { - children: \\"Hello\\" - }), React.jsx(\\"span\\", { - children: \\"world\\" + children: [React.jsx("span", { + children: "Hello" + }), React.jsx("span", { + children: "world" })] }), React.jsxs(React.Fragment, { - children: [React.jsx(\\"span\\", { - children: \\"Goodbye\\" - }), React.jsx(\\"span\\", { - children: \\"world\\" + children: [React.jsx("span", { + children: "Goodbye" + }), React.jsx("span", { + children: "world" })] })] }) -});" +}); `; exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = ` -"var x = React.jsx(\\"div\\", { +var x = React.jsx("div", { children: React.jsx(Component, {}) }); -var x = React.jsx(\\"div\\", { +var x = React.jsx("div", { children: props.children }); var x = React.jsx(Composite, { @@ -146,229 +146,229 @@ var x = React.jsx(Composite, { }); var x = React.jsx(Composite, { children: React.jsx(Composite2, {}) -});" +}); `; -exports[`transform react to jsx should convert simple tags 1`] = `"var x = React.jsx(\\"div\\", {});"`; +exports[`transform react to jsx should convert simple tags 1`] = `var x = React.jsx("div", {});`; exports[`transform react to jsx should convert simple text 1`] = ` -"var x = React.jsx(\\"div\\", { - children: \\"text\\" -});" +var x = React.jsx("div", { + children: "text" +}); `; exports[`transform react to jsx should escape xhtml jsxattribute 1`] = ` -"React.jsx(\\"div\\", { - id: \\"w\\\\xF4w\\" +React.jsx("div", { + id: "w\\xF4w" }); -React.jsx(\\"div\\", { - id: \\"w\\" +React.jsx("div", { + id: "w" +}); +React.jsx("div", { + id: "w < w" }); -React.jsx(\\"div\\", { - id: \\"w < w\\" -});" `; exports[`transform react to jsx should escape xhtml jsxtext 1`] = ` -"React.jsx(\\"div\\", { - children: \\"wow\\" +React.jsx("div", { + children: "wow" +}); +React.jsx("div", { + children: "w\\xF4w" }); -React.jsx(\\"div\\", { - children: \\"w\\\\xF4w\\" +React.jsx("div", { + children: "w & w" }); -React.jsx(\\"div\\", { - children: \\"w & w\\" +React.jsx("div", { + children: "w & w" }); -React.jsx(\\"div\\", { - children: \\"w & w\\" +React.jsx("div", { + children: "w \\xA0 w" }); -React.jsx(\\"div\\", { - children: \\"w \\\\xA0 w\\" +React.jsx("div", { + children: "this should not parse as unicode: \\xA0" }); -React.jsx(\\"div\\", { - children: \\"this should not parse as unicode: \\\\xA0\\" +React.jsx("div", { + children: "this should parse as nbsp: \\xA0 " }); -React.jsx(\\"div\\", { - children: \\"this should parse as nbsp: \\\\xA0 \\" +React.jsxs("div", { + children: ["this should parse as unicode: ", '  '] }); -React.jsxs(\\"div\\", { - children: [\\"this should parse as unicode: \\", '  '] +React.jsx("div", { + children: "w < w" }); -React.jsx(\\"div\\", { - children: \\"w < w\\" -});" `; exports[`transform react to jsx should handle attributed elements 1`] = ` -"var HelloMessage = React.createClass({ +var HelloMessage = React.createClass({ render: function () { - return React.jsxs(\\"div\\", { - children: [\\"Hello \\", this.props.name] + return React.jsxs("div", { + children: ["Hello ", this.props.name] }); } }); React.render(React.jsx(HelloMessage, { - name: React.jsx(\\"span\\", { - children: \\"Sebastian\\" + name: React.jsx("span", { + children: "Sebastian" }) -}), mountNode);" +}), mountNode); `; exports[`transform react to jsx should handle has own property correctly 1`] = ` -"React.jsx(\\"hasOwnProperty\\", { - children: \\"testing\\" -});" +React.jsx("hasOwnProperty", { + children: "testing" +}); `; exports[`transform react to jsx should have correct comma in nested children 1`] = ` -"var x = React.jsxs(\\"div\\", { - children: [React.jsx(\\"div\\", { - children: React.jsx(\\"br\\", {}) +var x = React.jsxs("div", { + children: [React.jsx("div", { + children: React.jsx("br", {}) }), React.jsxs(Component, { - children: [foo, React.jsx(\\"br\\", {}), bar] - }), React.jsx(\\"br\\", {})] -});" + children: [foo, React.jsx("br", {}), bar] + }), React.jsx("br", {})] +}); `; exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = ` -"var x = React.jsx(\\"div\\", { - attr1: \\"foo\\" + \\"bar\\", - attr2: \\"foo\\" + \\"bar\\" + \\"baz\\" + \\"bug\\", - attr3: \\"foo\\" + \\"bar\\" + \\"baz\\" + \\"bug\\", - attr4: \\"baz\\" -});" +var x = React.jsx("div", { + attr1: "foo" + "bar", + attr2: "foo" + "bar" + "baz" + "bug", + attr3: "foo" + "bar" + "baz" + "bug", + attr4: "baz" +}); `; exports[`transform react to jsx should not add quotes to identifier names 1`] = ` -"var e = React.jsx(F, { +var e = React.jsx(F, { aaa: true, new: true, const: true, var: true, default: true, - \\"foo-bar\\": true -});" + "foo-bar": true +}); `; exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = ` -"React.jsx(\\"div\\", { - children: \\"\\\\xA0 \\" -});" +React.jsx("div", { + children: "\\xA0 " +}); `; exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = ` -"React.jsx(\\"div\\", { - children: \\"\\\\xA0\\" -});" +React.jsx("div", { + children: "\\xA0" +}); `; exports[`transform react to jsx should properly handle comments adjacent to children 1`] = ` -"var x = React.jsxs(\\"div\\", { - children: [React.jsx(\\"span\\", {}), React.jsx(\\"br\\", {})] -});" +var x = React.jsxs("div", { + children: [React.jsx("span", {}), React.jsx("br", {})] +}); `; exports[`transform react to jsx should properly handle comments between props 1`] = ` -"var x = React.jsx(\\"div\\", { +var x = React.jsx("div", { /* a multi-line comment */ - attr1: \\"foo\\", - children: React.jsx(\\"span\\", { + attr1: "foo", + children: React.jsx("span", { // a double-slash comment - attr2: \\"bar\\" + attr2: "bar" }) -});" +}); `; exports[`transform react to jsx should properly handle potentially null variables 1`] = ` -"var foo = null; -var x = React.jsx(\\"div\\", Object.assign({}, foo));" +var foo = null; +var x = React.jsx("div", Object.assign({}, foo)); `; exports[`transform react to jsx should quote jsx attributes 1`] = ` -"React.jsx(\\"button\\", { - \\"data-value\\": \\"a value\\", - children: \\"Button\\" -});" +React.jsx("button", { + "data-value": "a value", + children: "Button" +}); `; exports[`transform react to jsx should support xml namespaces if flag 1`] = ` -"React.jsx(\\"f:image\\", { - \\"n:attr\\": true -});" +React.jsx("f:image", { + "n:attr": true +}); `; -exports[`transform react to jsx should transform known hyphenated tags 1`] = `"React.jsx(\\"font-face\\", {});"`; +exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.jsx("font-face", {});`; exports[`transform react to jsx static children 1`] = ` -"var _jsxFileName = \\"\\"; -var x = React.jsxDEV(\\"div\\", { - children: [React.jsxDEV(\\"span\\", {}, undefined, false, { +var _jsxFileName = ""; +var x = React.jsxDEV("div", { + children: [React.jsxDEV("span", {}, undefined, false, { fileName: _jsxFileName, lineNumber: 3 - }, this), [React.jsxDEV(\\"span\\", {}, '0', false, { + }, this), [React.jsxDEV("span", {}, '0', false, { fileName: _jsxFileName, lineNumber: 4 - }, this), React.jsxDEV(\\"span\\", {}, '1', false, { + }, this), React.jsxDEV("span", {}, '1', false, { fileName: _jsxFileName, lineNumber: 4 }, this)]] }, undefined, true, { fileName: _jsxFileName, lineNumber: 2 -}, this);" +}, this); `; exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = ` -"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } React.jsx(Component, _extends({ y: 2 -}, x));" +}, x)); `; exports[`transform react to jsx uses createElement when the key comes after a spread 1`] = ` -"var x = React.createElement(\\"div\\", Object.assign({}, props, { - key: \\"1\\", - foo: \\"bar\\" -}));" +var x = React.createElement("div", Object.assign({}, props, { + key: "1", + foo: "bar" +})); `; exports[`transform react to jsx uses jsx when the key comes before a spread 1`] = ` -"var x = React.jsx(\\"div\\", Object.assign({}, props, { - foo: \\"bar\\" -}), \\"1\\");" +var x = React.jsx("div", Object.assign({}, props, { + foo: "bar" +}), "1"); `; exports[`transform react to jsx uses jsxDEV instead of jsx in dev mode 1`] = ` -"var _jsxFileName = \\"\\"; -var x = React.jsxDEV(\\"span\\", { - propOne: \\"one\\", - children: \\"Hi\\" +var _jsxFileName = ""; +var x = React.jsxDEV("span", { + propOne: "one", + children: "Hi" }, undefined, false, { fileName: _jsxFileName, lineNumber: 1 -}, this);" +}, this); `; exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = ` -"React.jsx(Component, Object.assign({}, x, { +React.jsx(Component, Object.assign({}, x, { y: 2, z: true -}));" +})); `; exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = ` -"React.jsx(Component, Object.assign({ +React.jsx(Component, Object.assign({ y: 2, z: true -}, x));" +}, x)); `; exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = ` -"React.jsx(Component, Object.assign({ +React.jsx(Component, Object.assign({ y: 2 }, x, { z: true -}));" +})); `; diff --git a/packages/react-jsx-babel-plugin/index.js b/packages/babel-plugin-react-jsx/index.js similarity index 100% rename from packages/react-jsx-babel-plugin/index.js rename to packages/babel-plugin-react-jsx/index.js diff --git a/packages/react-jsx-babel-plugin/npm/index.js b/packages/babel-plugin-react-jsx/npm/index.js similarity index 100% rename from packages/react-jsx-babel-plugin/npm/index.js rename to packages/babel-plugin-react-jsx/npm/index.js diff --git a/packages/react-jsx-babel-plugin/package.json b/packages/babel-plugin-react-jsx/package.json similarity index 50% rename from packages/react-jsx-babel-plugin/package.json rename to packages/babel-plugin-react-jsx/package.json index 22b4780fd1f0f..41243452d4f17 100644 --- a/packages/react-jsx-babel-plugin/package.json +++ b/packages/babel-plugin-react-jsx/package.json @@ -1,16 +1,12 @@ { - "name": "react-jsx-babel-plugin", - "version": "1.0.0", + "name": "babel-plugin-react-jsx", + "version": "0.1.0", + "private": true, "description": "@babel/plugin-transform-react-jsx", "main": "index.js", - "repository": { - "type": "git", - "url": "https://github.com/facebook/react.git", - "directory": "packages/react-jsx-babel-plugin" - }, - "license": "MIT", "dependencies": { "esutils": "^2.0.0" + }, "files": [ "README.md", diff --git a/packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin.js b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js similarity index 90% rename from packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin.js rename to packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js index 186a8288cf428..669fbe8de3bc3 100644 --- a/packages/react-jsx-babel-plugin/src/TransformJSXToReactBabelPlugin.js +++ b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js @@ -1,3 +1,33 @@ +// MIT License + +// Copyright (c) 2014-present Sebastian McKenzie and other contributors + +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: + +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// © 2019 GitHub, Inc. + +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ 'use strict'; const esutils = require('esutils'); @@ -29,7 +59,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, visitor.JSXElement = { exit(path, file) { let callExpr; - if (file.opts.useCreateElement || useCreateElement(path)) { + if (file.opts.useCreateElement || shouldUseCreateElement(path)) { callExpr = buildCreateElementCall(path, file); } else { callExpr = buildJSXElementCall(path, file); @@ -126,12 +156,12 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, // from
. This is an intermediary // step while we deprecate key spread from props. Afterwards, // we will remove createElement entirely - function useCreateElement(path) { + function shouldUseCreateElement(path) { const openingPath = path.get('openingElement'); const attributes = openingPath.node.attributes; let seenPropsSpread = false; - for (let i = 0, length = attributes.length; i < length; i++) { + for (let i = 0; i < attributes.length; i++) { const attr = attributes[i]; if ( seenPropsSpread && @@ -189,7 +219,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, // a separate argument rather than in the args object. We go through the // props and filter out these three keywords so we can pass them in // as separate arguments later - for (let i = 0, len = openingPath.node.attributes.length; i < len; i++) { + for (let i = 0; i < openingPath.node.attributes.length; i++) { const attr = openingPath.node.attributes[i]; if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) { if (attr.name.name === 'key') { diff --git a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap b/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap deleted file mode 100644 index ac3405810ff48..0000000000000 --- a/packages/react-jsx-babel-plugin/__tests__/__snapshots__/transform-jsx-to-react-createElement-test.js.snap +++ /dev/null @@ -1,213 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`transform react to jsx React.Fragment to set keys and source 1`] = ` -"var _jsxFileName = \\"\\"; -var x = React.createElement(React.Fragment, { - key: \\"foo\\", - __source: { - fileName: _jsxFileName, - lineNumber: 1 - }, - __self: this -}, React.createElement(\\"div\\", { - __source: { - fileName: _jsxFileName, - lineNumber: 1 - }, - __self: this -}));" -`; - -exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = ` -"React.createElement(Component, Object.assign({}, props, { - sound: \\"moo\\" -}));" -`; - -exports[`transform react to jsx arrow functions 1`] = ` -"var foo = function () { - var _this = this; - - return function () { - return React.createElement(_this, null); - }; -}; - -var bar = function () { - var _this2 = this; - - return function () { - return React.createElement(_this2.foo, null); - }; -};" -`; - -exports[`transform react to jsx assignment 1`] = ` -"var div = React.createElement(Component, Object.assign({}, props, { - foo: \\"bar\\" -}));" -`; - -exports[`transform react to jsx concatenates adjacent string literals 1`] = `"var x = React.createElement(\\"div\\", null, \\"foo\\", \\"bar\\", \\"baz\\", React.createElement(\\"div\\", null, \\"buz bang\\"), \\"qux\\", null, \\"quack\\");"`; - -exports[`transform react to jsx fragment with no children 1`] = `"var x = React.createElement(React.Fragment, null);"`; - -exports[`transform react to jsx normal fragments not to set key and source 1`] = ` -"var _jsxFileName = \\"\\"; -var x = React.createElement(React.Fragment, null, React.createElement(\\"div\\", { - __source: { - fileName: _jsxFileName, - lineNumber: 1 - }, - __self: this -}));" -`; - -exports[`transform react to jsx should allow constructor as prop 1`] = ` -"React.createElement(Component, { - constructor: \\"foo\\" -});" -`; - -exports[`transform react to jsx should allow deeper js namespacing 1`] = `"React.createElement(Namespace.DeepNamespace.Component, null);"`; - -exports[`transform react to jsx should allow elements as attributes 1`] = ` -"React.createElement(\\"div\\", { - attr: React.createElement(\\"div\\", null) -});" -`; - -exports[`transform react to jsx should allow js namespacing 1`] = `"React.createElement(Namespace.Component, null);"`; - -exports[`transform react to jsx should allow nested fragments 1`] = `"React.createElement(\\"div\\", null, React.createElement(React.Fragment, null, React.createElement(React.Fragment, null, React.createElement(\\"span\\", null, \\"Hello\\"), React.createElement(\\"span\\", null, \\"world\\")), React.createElement(React.Fragment, null, React.createElement(\\"span\\", null, \\"Goodbye\\"), React.createElement(\\"span\\", null, \\"world\\"))));"`; - -exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = ` -"var x = React.createElement(\\"div\\", null, React.createElement(Component, null)); -var x = React.createElement(\\"div\\", null, props.children); -var x = React.createElement(Composite, null, props.children); -var x = React.createElement(Composite, null, React.createElement(Composite2, null));" -`; - -exports[`transform react to jsx should convert simple tags 1`] = `"var x = React.createElement(\\"div\\", null);"`; - -exports[`transform react to jsx should convert simple text 1`] = `"var x = React.createElement(\\"div\\", null, \\"text\\");"`; - -exports[`transform react to jsx should escape xhtml jsxattribute 1`] = ` -"React.createElement(\\"div\\", { - id: \\"w\\\\xF4w\\" -}); -React.createElement(\\"div\\", { - id: \\"w\\" -}); -React.createElement(\\"div\\", { - id: \\"w < w\\" -});" -`; - -exports[`transform react to jsx should escape xhtml jsxtext 1`] = ` -"React.createElement(\\"div\\", null, \\"wow\\"); -React.createElement(\\"div\\", null, \\"w\\\\xF4w\\"); -React.createElement(\\"div\\", null, \\"w & w\\"); -React.createElement(\\"div\\", null, \\"w & w\\"); -React.createElement(\\"div\\", null, \\"w \\\\xA0 w\\"); -React.createElement(\\"div\\", null, \\"this should not parse as unicode: \\\\xA0\\"); -React.createElement(\\"div\\", null, \\"this should parse as nbsp: \\\\xA0 \\"); -React.createElement(\\"div\\", null, \\"this should parse as unicode: \\", '  '); -React.createElement(\\"div\\", null, \\"w < w\\");" -`; - -exports[`transform react to jsx should handle attributed elements 1`] = ` -"var HelloMessage = React.createClass({ - render: function () { - return React.createElement(\\"div\\", null, \\"Hello \\", this.props.name); - } -}); -React.render(React.createElement(HelloMessage, { - name: React.createElement(\\"span\\", null, \\"Sebastian\\") -}), mountNode);" -`; - -exports[`transform react to jsx should handle has own property correctly 1`] = `"React.createElement(\\"hasOwnProperty\\", null, \\"testing\\");"`; - -exports[`transform react to jsx should have correct comma in nested children 1`] = `"var x = React.createElement(\\"div\\", null, React.createElement(\\"div\\", null, React.createElement(\\"br\\", null)), React.createElement(Component, null, foo, React.createElement(\\"br\\", null), bar), React.createElement(\\"br\\", null));"`; - -exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = ` -"var x = React.createElement(\\"div\\", { - attr1: \\"foo\\" + \\"bar\\", - attr2: \\"foo\\" + \\"bar\\" + \\"baz\\" + \\"bug\\", - attr3: \\"foo\\" + \\"bar\\" + \\"baz\\" + \\"bug\\", - attr4: \\"baz\\" -});" -`; - -exports[`transform react to jsx should not add quotes to identifier names 1`] = ` -"var e = React.createElement(F, { - aaa: true, - new: true, - const: true, - var: true, - default: true, - \\"foo-bar\\": true -});" -`; - -exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `"React.createElement(\\"div\\", null, \\"\\\\xA0 \\");"`; - -exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `"React.createElement(\\"div\\", null, \\"\\\\xA0\\");"`; - -exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `"var x = React.createElement(\\"div\\", null, React.createElement(\\"span\\", null), React.createElement(\\"br\\", null));"`; - -exports[`transform react to jsx should properly handle comments between props 1`] = ` -"var x = React.createElement(\\"div\\", { - /* a multi-line - comment */ - attr1: \\"foo\\" -}, React.createElement(\\"span\\", { - // a double-slash comment - attr2: \\"bar\\" -}));" -`; - -exports[`transform react to jsx should quote jsx attributes 1`] = ` -"React.createElement(\\"button\\", { - \\"data-value\\": \\"a value\\" -}, \\"Button\\");" -`; - -exports[`transform react to jsx should support xml namespaces if flag 1`] = ` -"React.createElement(\\"f:image\\", { - \\"n:attr\\": true -});" -`; - -exports[`transform react to jsx should transform known hyphenated tags 1`] = `"React.createElement(\\"font-face\\", null);"`; - -exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = ` -"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } - -React.createElement(Component, _extends({ - y: 2 -}, x));" -`; - -exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = ` -"React.createElement(Component, Object.assign({}, x, { - y: 2, - z: true -}));" -`; - -exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = ` -"React.createElement(Component, Object.assign({ - y: 2, - z: true -}, x));" -`; - -exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = ` -"React.createElement(Component, Object.assign({ - y: 2 -}, x, { - z: true -}));" -`; diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js index 44ac18239d408..e3b1351b53ab9 100644 --- a/packages/react/src/ReactElementValidator.js +++ b/packages/react/src/ReactElementValidator.js @@ -350,7 +350,7 @@ export function jsxWithValidation( false, 'React.jsx: Static children should always be an array. ' + 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' + - 'Use the babel transform instead.', + 'Use the Babel transform instead.', ); } } else { diff --git a/packages/react/src/__tests__/ReactElementJSX-test.internal.js b/packages/react/src/__tests__/ReactElementJSX-test.internal.js index 602cc3024a3f6..23135d15de8e3 100644 --- a/packages/react/src/__tests__/ReactElementJSX-test.internal.js +++ b/packages/react/src/__tests__/ReactElementJSX-test.internal.js @@ -222,7 +222,7 @@ describe('ReactElement.jsx', () => { ).toWarnDev( 'React.jsx: Static children should always be an array. ' + 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' + - 'Use the babel transform instead.', + 'Use the Babel transform instead.', {withoutStack: true}, ); }); From 208b29109119f6593118eceab85fc6f6911d90c4 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Fri, 23 Aug 2019 14:28:44 -0700 Subject: [PATCH 18/33] modified comments --- .../src/TransformJSXToReactBabelPlugin.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js index 669fbe8de3bc3..e26b65fcf7c88 100644 --- a/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js +++ b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js @@ -319,7 +319,9 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, if (objs.length === 1) { // only one object if (!t.isObjectExpression(objs[0])) { - // this could be null, and jsx expects props to be non-null + // if the prop object isn't an object, use Object.assign or _extends + // to ensure that the prop will always be an object (as opposed to a variable + // that could be null at some point) const expressionHelper = useBuiltIns ? t.memberExpression(t.identifier('Object'), t.identifier('assign')) : file.addHelper('extends'); From 215e16cd23b16b51bf921d1e33f0f9379274a480 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Tue, 27 Aug 2019 16:07:56 -0700 Subject: [PATCH 19/33] packages/ --- .../TransformJSXToReactCreateElement-test.js | 1 + .../__tests__/TransformJSXToReactJSX-test.js | 19 +- packages/babel-plugin-react-jsx/package.json | 1 + .../src/TransformJSXToReactBabelPlugin.js | 210 +++++++++++++++--- 4 files changed, 193 insertions(+), 38 deletions(-) diff --git a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js index 6e44eaeaec95b..94f117c6cdb63 100644 --- a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js +++ b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js @@ -33,6 +33,7 @@ function transform(input, options) { ...options, }, ], + '@babel/plugin-transform-modules-commonjs', ], }).code ); diff --git a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js index 86ee84f1fa260..85e0d7b24ee62 100644 --- a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js +++ b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js @@ -15,6 +15,7 @@ function transform(input, options) { return wrap( babel.transform(input, { configFile: false, + sourceType: 'module', plugins: [ '@babel/plugin-syntax-jsx', '@babel/plugin-transform-arrow-functions', @@ -30,8 +31,10 @@ function transform(input, options) { useBuiltIns: true, useCreateElement: false, ...options, + autoImport: 'namespace', // none | default | namedExports / namespace }, ], + // '@babel/transform-modules-commonjs', ], }).code ); @@ -68,17 +71,27 @@ describe('transform react to jsx', () => { ).toMatchSnapshot(); }); - it('nonStatic children', () => { + fit('nonStatic children', () => { expect( transform( - `var x = ( + ` + import {_jsx} from 'foo'; + const _$React1 = 1; + createElement.foo = _jsx(); + var x = ( +
+
+
+
+
{[, ]}
+
); `, { - development: true, + development: false, } ) ).toMatchSnapshot(); diff --git a/packages/babel-plugin-react-jsx/package.json b/packages/babel-plugin-react-jsx/package.json index 41243452d4f17..fcc519362b6aa 100644 --- a/packages/babel-plugin-react-jsx/package.json +++ b/packages/babel-plugin-react-jsx/package.json @@ -5,6 +5,7 @@ "description": "@babel/plugin-transform-react-jsx", "main": "index.js", "dependencies": { + "@babel/helper-module-imports": "^7.0.0", "esutils": "^2.0.0" }, diff --git a/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js index e26b65fcf7c88..4ff49e10bacf8 100644 --- a/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js +++ b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js @@ -31,6 +31,7 @@ 'use strict'; const esutils = require('esutils'); +const helperModuleImports = require('@babel/helper-module-imports'); function helper(babel, opts) { const {types: t} = babel; @@ -266,6 +267,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, if (opts.post) { opts.post(state, file); } + return ( state.call || t.callExpression( @@ -555,6 +557,103 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } } +function addAutoImports(babel, onAutoImportsAdded) { + const {types: t} = babel; + + const imports = {}; + + const visitor = { + JSXElement(path, state) { + console.log(state); + if (shouldUseCreateElement(path)) { + buildAutoImport(path, state, 'createElement'); + } else if (path.node.children.length > 1) { + buildAutoImport(path, state, 'jsxs'); + } else { + buildAutoImport(path, state, 'jsx'); + } + }, + + JSXFragment(path, state) { + console.log(state); + if (!state.opts.useCreateElement) { + buildAutoImport(path, state, 'Fragment'); + } + }, + + Program: { + exit(path, file) { + onAutoImportsAdded(imports); + }, + }, + }; + + const buildAutoImport = (path, state, importName) => { + if (state.opts.autoImport === 'namespace' && imports.react === undefined) { + const {name} = buildAutoImportNamespace(path, state); + imports.react = name; + } else if ( + state.opts.autoImport === 'namedExports' && + imports[importName] === undefined + ) { + const {name} = buildAutoImportNamedExports(path, state, importName); + imports[name] = name; + } + }; + + const buildAutoImportNamedExports = (path, state, identifierName) => { + const source = state.opts.importSource || 'react'; + const {name} = helperModuleImports.addNamed(path, identifierName, source, { + nameHint: identifierName, + }); + return name; + }; + + const buildAutoImportNamespace = (path, state) => { + console.log(path.node); + const source = state.opts.importSource || 'react'; + // if namespace exists then don't import + const {name} = helperModuleImports.addNamespace(path, source, { + nameHint: state.reactName, + }); + return name; + }; + + // const buildAutoImportDefault = (path, state) => { + // const source = state.opts.importSource || 'react'; + // helperModuleImports.addDefault(path, source, { + // nameHint: state.reactName, + // }); + // }; + + // We want to use React.createElement, even in the case of + // jsx, for
to distinguish it + // from
. This is an intermediary + // step while we deprecate key spread from props. Afterwards, + // we will remove createElement entirely + function shouldUseCreateElement(path) { + const openingPath = path.get('openingElement'); + const attributes = openingPath.node.attributes; + + let seenPropsSpread = false; + for (let i = 0; i < attributes.length; i++) { + const attr = attributes[i]; + if ( + seenPropsSpread && + t.isJSXAttribute(attr) && + attr.name.name === 'key' + ) { + return true; + } else if (t.isJSXSpreadAttribute(attr)) { + seenPropsSpread = true; + } + } + return false; + } + + return visitor; +} + module.exports = function(babel) { const {types: t} = babel; @@ -565,43 +664,84 @@ module.exports = function(babel) { .reduce((object, property) => t.memberExpression(object, property)); }; - const visitor = helper(babel, { - pre(state) { - const tagName = state.tagName; - const args = state.args; - if (t.react.isCompatTag(tagName)) { - args.push(t.stringLiteral(tagName)); - } else { - args.push(state.tagExpr); - } - }, - - post(state, pass) { - state.callee = pass.get('jsxIdentifier')(); - state.staticCallee = pass.get('jsxStaticIdentifier')(); - state.oldCallee = pass.get('oldJSXIdentifier')(); - }, - }); - + // const visitor = helper(babel, { + // pre(state) { + // const tagName = state.tagName; + // const args = state.args; + // if (t.react.isCompatTag(tagName)) { + // args.push(t.stringLiteral(tagName)); + // } else { + // args.push(state.tagExpr); + // } + // }, + + // post(state, pass) { + // state.callee = pass.get('jsxIdentifier')(); + // state.staticCallee = pass.get('jsxStaticIdentifier')(); + // state.oldCallee = pass.get('oldJSXIdentifier')(); + // state.reactName = pass.get('reactIdentifierName'); + // }, + // }); + + const createIdentifierName = (path, state, name, importNames) => { + if (state.opts.autoImport === 'none') { + return `React.${name}`; + } else if (state.opts.autoImport === 'namedExports') { + const identifierName = `${importNames[name]}`; + return identifierName; + } else { + return `${importNames.react}.${name}`; + } + }; + const visitor = {}; visitor.Program = { enter(path, state) { - state.set( - 'oldJSXIdentifier', - createIdentifierParser('React.createElement'), - ); - state.set( - 'jsxIdentifier', - createIdentifierParser( - state.opts.development ? 'React.jsxDEV' : 'React.jsx', - ), - ); - state.set( - 'jsxStaticIdentifier', - createIdentifierParser( - state.opts.development ? 'React.jsxDEV' : 'React.jsxs', - ), - ); - state.set('jsxFragIdentifier', createIdentifierParser('React.Fragment')); + let importNames; + const autoImportVisitor = addAutoImports(babel, imports => { + importNames = imports; + }); + console.log(state.opts); + path.traverse(autoImportVisitor, state); + console.log(importNames); + // const reactIdentifierName = path.scope.generateUidIdentifier('React'); + // state.set('reactIdentifierName', reactIdentifierName); + // state.set( + // 'oldJSXIdentifier', + // createIdentifierParser( + // createIdentifierName(path, state, 'createElement', importNames), + // ), + // ); + + // state.set( + // 'jsxIdentifier', + // createIdentifierParser( + // createIdentifierName( + // path, + // state, + // state.opts.development ? 'jsxDEV' : 'jsx', + // importNames, + // ), + // ), + // ); + + // state.set( + // 'jsxStaticIdentifier', + // createIdentifierParser( + // createIdentifierName( + // path, + // state, + // state.opts.development ? 'jsxDEV' : 'jsxs', + // importNames, + // ), + // ), + // ); + + // state.set( + // 'jsxFragIdentifier', + // createIdentifierParser( + // createIdentifierName(path, state, 'Fragment', importNames), + // ), + // ); }, }; From c693c09d0587711b1d5e3e4cd3c087828604aac2 Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Wed, 28 Aug 2019 10:56:30 -0700 Subject: [PATCH 20/33] code --- .../__tests__/TransformJSXToReactJSX-test.js | 11 +- .../src/TransformJSXToReactBabelPlugin.js | 238 ++++++++---------- 2 files changed, 113 insertions(+), 136 deletions(-) diff --git a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js index 85e0d7b24ee62..c7a798bfb69e4 100644 --- a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js +++ b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js @@ -15,7 +15,7 @@ function transform(input, options) { return wrap( babel.transform(input, { configFile: false, - sourceType: 'module', + sourceType: 'script', plugins: [ '@babel/plugin-syntax-jsx', '@babel/plugin-transform-arrow-functions', @@ -31,7 +31,8 @@ function transform(input, options) { useBuiltIns: true, useCreateElement: false, ...options, - autoImport: 'namespace', // none | default | namedExports / namespace + importSource: 'React', + autoImport: 'require', // none | default | namedExports / namespace / require }, ], // '@babel/transform-modules-commonjs', @@ -75,10 +76,9 @@ describe('transform react to jsx', () => { expect( transform( ` - import {_jsx} from 'foo'; - const _$React1 = 1; - createElement.foo = _jsx(); var x = ( + <> +
@@ -88,6 +88,7 @@ describe('transform react to jsx', () => { {[, ]}
+ ); `, { diff --git a/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js index 0c553fb4ebafd..b3764c3b54e13 100644 --- a/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js +++ b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js @@ -25,6 +25,8 @@ const esutils = require('esutils'); const helperModuleImports = require('@babel/helper-module-imports'); +const isModule = helperModuleImports.isModule; +const addNamespace = helperModuleImports.addNamespace; function helper(babel, opts) { const {types: t} = babel; @@ -87,6 +89,31 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, return visitor; + // We want to use React.createElement, even in the case of + // jsx, for
to distinguish it + // from
. This is an intermediary + // step while we deprecate key spread from props. Afterwards, + // we will remove createElement entirely + function shouldUseCreateElement(path) { + const openingPath = path.get('openingElement'); + const attributes = openingPath.node.attributes; + + let seenPropsSpread = false; + for (let i = 0; i < attributes.length; i++) { + const attr = attributes[i]; + if ( + seenPropsSpread && + t.isJSXAttribute(attr) && + attr.name.name === 'key' + ) { + return true; + } else if (t.isJSXSpreadAttribute(attr)) { + seenPropsSpread = true; + } + } + return false; + } + function convertJSXIdentifier(node, parent) { if (t.isJSXIdentifier(node)) { if (node.name === 'this' && t.isReferenced(node, parent)) { @@ -145,31 +172,6 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, return t.inherits(t.objectProperty(node.name, value), node); } - // We want to use React.createElement, even in the case of - // jsx, for
to distinguish it - // from
. This is an intermediary - // step while we deprecate key spread from props. Afterwards, - // we will remove createElement entirely - function shouldUseCreateElement(path) { - const openingPath = path.get('openingElement'); - const attributes = openingPath.node.attributes; - - let seenPropsSpread = false; - for (let i = 0; i < attributes.length; i++) { - const attr = attributes[i]; - if ( - seenPropsSpread && - t.isJSXAttribute(attr) && - attr.name.name === 'key' - ) { - return true; - } else if (t.isJSXSpreadAttribute(attr)) { - seenPropsSpread = true; - } - } - return false; - } - // Builds JSX into: // Production: React.jsx(type, arguments, key) // Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self) @@ -550,14 +552,9 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`, } } -function addAutoImports(babel, onAutoImportsAdded) { - const {types: t} = babel; - - const imports = {}; - +function addAutoImports({types: t}, imports) { const visitor = { JSXElement(path, state) { - console.log(state); if (shouldUseCreateElement(path)) { buildAutoImport(path, state, 'createElement'); } else if (path.node.children.length > 1) { @@ -568,57 +565,36 @@ function addAutoImports(babel, onAutoImportsAdded) { }, JSXFragment(path, state) { - console.log(state); if (!state.opts.useCreateElement) { buildAutoImport(path, state, 'Fragment'); } }, - - Program: { - exit(path, file) { - onAutoImportsAdded(imports); - }, - }, }; const buildAutoImport = (path, state, importName) => { + const source = state.opts.importSource || 'react'; + if (state.opts.autoImport === 'require') { + const {name} = helperModuleImports.addNamespace(path, source); + imports.react = name; + } if (state.opts.autoImport === 'namespace' && imports.react === undefined) { - const {name} = buildAutoImportNamespace(path, state); + const {name} = helperModuleImports.addNamespace(path, source); imports.react = name; } else if ( state.opts.autoImport === 'namedExports' && imports[importName] === undefined ) { - const {name} = buildAutoImportNamedExports(path, state, importName); - imports[name] = name; + const {name} = helperModuleImports.addNamed(path, importName, source); + imports[importName] = name; + } else if ( + state.opts.autoImport === 'default' && + imports.react === undefined + ) { + const {name} = helperModuleImports.addDefault(path, source); + imports.react = name; } }; - const buildAutoImportNamedExports = (path, state, identifierName) => { - const source = state.opts.importSource || 'react'; - const {name} = helperModuleImports.addNamed(path, identifierName, source, { - nameHint: identifierName, - }); - return name; - }; - - const buildAutoImportNamespace = (path, state) => { - console.log(path.node); - const source = state.opts.importSource || 'react'; - // if namespace exists then don't import - const {name} = helperModuleImports.addNamespace(path, source, { - nameHint: state.reactName, - }); - return name; - }; - - // const buildAutoImportDefault = (path, state) => { - // const source = state.opts.importSource || 'react'; - // helperModuleImports.addDefault(path, source, { - // nameHint: state.reactName, - // }); - // }; - // We want to use React.createElement, even in the case of // jsx, for
to distinguish it // from
. This is an intermediary @@ -657,24 +633,23 @@ module.exports = function(babel) { .reduce((object, property) => t.memberExpression(object, property)); }; - // const visitor = helper(babel, { - // pre(state) { - // const tagName = state.tagName; - // const args = state.args; - // if (t.react.isCompatTag(tagName)) { - // args.push(t.stringLiteral(tagName)); - // } else { - // args.push(state.tagExpr); - // } - // }, - - // post(state, pass) { - // state.callee = pass.get('jsxIdentifier')(); - // state.staticCallee = pass.get('jsxStaticIdentifier')(); - // state.oldCallee = pass.get('oldJSXIdentifier')(); - // state.reactName = pass.get('reactIdentifierName'); - // }, - // }); + const visitor = helper(babel, { + pre(state) { + const tagName = state.tagName; + const args = state.args; + if (t.react.isCompatTag(tagName)) { + args.push(t.stringLiteral(tagName)); + } else { + args.push(state.tagExpr); + } + }, + + post(state, pass) { + state.callee = pass.get('jsxIdentifier')(); + state.staticCallee = pass.get('jsxStaticIdentifier')(); + state.oldCallee = pass.get('oldJSXIdentifier')(); + }, + }); const createIdentifierName = (path, state, name, importNames) => { if (state.opts.autoImport === 'none') { @@ -686,55 +661,56 @@ module.exports = function(babel) { return `${importNames.react}.${name}`; } }; - const visitor = {}; + visitor.Program = { enter(path, state) { - let importNames; - const autoImportVisitor = addAutoImports(babel, imports => { - importNames = imports; - }); - console.log(state.opts); - path.traverse(autoImportVisitor, state); - console.log(importNames); - // const reactIdentifierName = path.scope.generateUidIdentifier('React'); - // state.set('reactIdentifierName', reactIdentifierName); - // state.set( - // 'oldJSXIdentifier', - // createIdentifierParser( - // createIdentifierName(path, state, 'createElement', importNames), - // ), - // ); - - // state.set( - // 'jsxIdentifier', - // createIdentifierParser( - // createIdentifierName( - // path, - // state, - // state.opts.development ? 'jsxDEV' : 'jsx', - // importNames, - // ), - // ), - // ); - - // state.set( - // 'jsxStaticIdentifier', - // createIdentifierParser( - // createIdentifierName( - // path, - // state, - // state.opts.development ? 'jsxDEV' : 'jsxs', - // importNames, - // ), - // ), - // ); - - // state.set( - // 'jsxFragIdentifier', - // createIdentifierParser( - // createIdentifierName(path, state, 'Fragment', importNames), - // ), - // ); + if (state.opts.autoImport === 'require' && isModule(path)) { + throw path.buildCodeFrameError(`Somethinga bout require.`); + } else if (state.opts.autoImport === 'require' && !isModule(path)) { + throw path.buildCodeFrameError(`Something about import.`); + } + + const importNames = {}; + path.traverse(addAutoImports(babel, importNames), state); + const reactIdentifierName = path.scope.generateUidIdentifier('React'); + state.set('reactIdentifierName', reactIdentifierName); + state.set( + 'oldJSXIdentifier', + createIdentifierParser( + createIdentifierName(path, state, 'createElement', importNames), + ), + ); + + state.set( + 'jsxIdentifier', + createIdentifierParser( + createIdentifierName( + path, + state, + state.opts.development ? 'jsxDEV' : 'jsx', + importNames, + ), + ), + ); + + state.set( + 'jsxStaticIdentifier', + createIdentifierParser( + createIdentifierName( + path, + state, + state.opts.development ? 'jsxDEV' : 'jsxs', + importNames, + ), + ), + ); + + state.set( + 'jsxFragIdentifier', + createIdentifierParser( + createIdentifierName(path, state, 'Fragment', importNames), + ), + ); }, }; From 7e88cb19f411d9fa4ecacafb386ac92ce049e0cc Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Thu, 29 Aug 2019 15:59:15 -0700 Subject: [PATCH 21/33] merge --- .circleci/config.yml | 13 + .eslintignore | 7 + .gitignore | 11 + .prettierignore | 6 + fixtures/devtools/regression/14.9.html | 38 + fixtures/devtools/regression/15.0.html | 38 + fixtures/devtools/regression/15.1.html | 38 + fixtures/devtools/regression/15.2.html | 38 + fixtures/devtools/regression/15.3.html | 38 + fixtures/devtools/regression/15.4.html | 38 + fixtures/devtools/regression/15.5.html | 38 + fixtures/devtools/regression/15.6.html | 38 + fixtures/devtools/regression/16.0.html | 38 + fixtures/devtools/regression/16.1.html | 38 + fixtures/devtools/regression/16.2.html | 38 + fixtures/devtools/regression/16.3.html | 38 + fixtures/devtools/regression/16.4.html | 38 + fixtures/devtools/regression/16.5.html | 40 + fixtures/devtools/regression/16.6.html | 41 + fixtures/devtools/regression/16.7.html | 41 + fixtures/devtools/regression/canary.html | 41 + fixtures/devtools/regression/index.html | 28 + fixtures/devtools/regression/next.html | 41 + fixtures/devtools/regression/server.js | 16 + fixtures/devtools/regression/shared.js | 328 + fixtures/devtools/regression/styles.css | 37 + fixtures/devtools/standalone/index.html | 313 + package.json | 8 +- .../react-debug-tools/src/ReactDebugHooks.js | 4 +- packages/react-devtools-core/README.md | 23 +- packages/react-devtools-core/backend.js | 1 + packages/react-devtools-core/package.json | 33 +- packages/react-devtools-core/src/backend.js | 284 + packages/react-devtools-core/src/editor.js | 190 + .../react-devtools-core/src/standalone.js | 326 + packages/react-devtools-core/standalone.js | 1 + .../react-devtools-core/webpack.backend.js | 67 + .../react-devtools-core/webpack.standalone.js | 93 + .../.circleci/config.yml | 58 + packages/react-devtools-extensions/README.md | 22 + packages/react-devtools-extensions/build.js | 120 + .../chrome/README.md | 12 + .../react-devtools-extensions/chrome/build.js | 43 + .../chrome/deploy.js | 9 + .../chrome/manifest.json | 52 + .../react-devtools-extensions/chrome/now.json | 5 + .../react-devtools-extensions/chrome/test.js | 13 + .../deploy.chrome.html | 8 + .../deploy.firefox.html | 7 + .../react-devtools-extensions/deploy.html | 40 + packages/react-devtools-extensions/deploy.js | 58 + .../firefox/README.md | 12 + .../firefox/build.js | 37 + .../firefox/deploy.js | 9 + .../firefox/manifest.json | 56 + .../firefox/now.json | 5 + .../react-devtools-extensions/firefox/test.js | 48 + .../flow-typed/jest.js | 1190 +++ .../npm/react-test-renderer_v16.x.x.js | 84 + .../icons/128-deadcode.png | Bin 0 -> 5201 bytes .../icons/128-development.png | Bin 0 -> 5201 bytes .../icons/128-disabled.png | Bin 0 -> 4792 bytes .../icons/128-outdated.png | Bin 0 -> 4162 bytes .../icons/128-production.png | Bin 0 -> 4577 bytes .../icons/128-unminified.png | Bin 0 -> 5201 bytes .../icons/16-deadcode.png | Bin 0 -> 638 bytes .../icons/16-development.png | Bin 0 -> 638 bytes .../icons/16-disabled.png | Bin 0 -> 494 bytes .../icons/16-outdated.png | Bin 0 -> 558 bytes .../icons/16-production.png | Bin 0 -> 546 bytes .../icons/16-unminified.png | Bin 0 -> 638 bytes .../icons/32-deadcode.png | Bin 0 -> 1318 bytes .../icons/32-development.png | Bin 0 -> 1318 bytes .../icons/32-disabled.png | Bin 0 -> 1092 bytes .../icons/32-outdated.png | Bin 0 -> 1095 bytes .../icons/32-production.png | Bin 0 -> 1068 bytes .../icons/32-unminified.png | Bin 0 -> 1318 bytes .../icons/48-deadcode.png | Bin 0 -> 2001 bytes .../icons/48-development.png | Bin 0 -> 2001 bytes .../icons/48-disabled.png | Bin 0 -> 1564 bytes .../icons/48-outdated.png | Bin 0 -> 1570 bytes .../icons/48-production.png | Bin 0 -> 1674 bytes .../icons/48-unminified.png | Bin 0 -> 2001 bytes .../icons/deadcode.svg | 1 + .../icons/development.svg | 1 + .../icons/disabled.svg | 1 + .../icons/outdated.svg | 1 + .../icons/production.svg | 1 + packages/react-devtools-extensions/main.html | 9 + .../react-devtools-extensions/package.json | 43 + packages/react-devtools-extensions/panel.html | 32 + .../popups/deadcode.html | 32 + .../popups/development.html | 28 + .../popups/disabled.html | 21 + .../popups/outdated.html | 29 + .../popups/production.html | 21 + .../popups/shared.js | 24 + .../popups/unminified.html | 31 + .../react-devtools-extensions/src/backend.js | 79 + .../src/background.js | 115 + .../src/contentScript.js | 79 + .../react-devtools-extensions/src/inject.js | 24 + .../src/injectGlobalHook.js | 89 + .../react-devtools-extensions/src/main.js | 322 + .../react-devtools-extensions/src/panel.js | 19 + .../react-devtools-extensions/src/renderer.js | 26 + .../react-devtools-extensions/src/utils.js | 51 + packages/react-devtools-extensions/utils.js | 39 + .../webpack.backend.js | 62 + .../webpack.config.js | 84 + packages/react-devtools-inline/README.md | 25 +- packages/react-devtools-inline/backend.js | 1 + packages/react-devtools-inline/frontend.js | 1 + packages/react-devtools-inline/package.json | 45 +- packages/react-devtools-inline/src/backend.js | 96 + .../react-devtools-inline/src/constants.js | 6 + .../react-devtools-inline/src/frontend.js | 75 + .../react-devtools-inline/webpack.config.js | 80 + .../react-devtools-shared/babel.config.js | 46 + packages/react-devtools-shared/package.json | 20 + .../src/__tests__/__mocks__/cssMock.js | 1 + .../inspectedElementContext-test.js.snap | 561 ++ .../ownersListContext-test.js.snap | 95 + .../profilerContext-test.js.snap | 41 + .../__snapshots__/profilingCache-test.js.snap | 3632 ++++++++ .../profilingCharts-test.js.snap | 452 + .../profilingCommitTreeBuilder-test.js.snap | 176 + .../__snapshots__/store-test.js.snap | 590 ++ .../storeComponentFilters-test.js.snap | 120 + .../__snapshots__/storeOwners-test.js.snap | 121 + .../storeStressSync-test.js.snap | 493 ++ .../storeStressTestConcurrent-test.js.snap | 493 ++ .../__snapshots__/treeContext-test.js.snap | 1204 +++ .../src/__tests__/bridge-test.js | 49 + .../src/__tests__/console-test.js | 344 + .../__tests__/inspectedElementContext-test.js | 1012 +++ .../__tests__/inspectedElementSerializer.js | 28 + .../__snapshots__/inspectElement-test.js.snap | 211 + .../storeLegacy-v15-test.js.snap | 499 ++ .../__tests__/legacy/inspectElement-test.js | 353 + .../__tests__/legacy/storeLegacy-v15-test.js | 500 ++ .../src/__tests__/ownersListContext-test.js | 209 + .../src/__tests__/profilerContext-test.js | 354 + .../src/__tests__/profilerStore-test.js | 82 + .../src/__tests__/profilingCache-test.js | 575 ++ .../src/__tests__/profilingCharts-test.js | 249 + .../profilingCommitTreeBuilder-test.js | 82 + .../src/__tests__/profilingUtils-test.js | 27 + .../src/__tests__/setupEnv.js | 9 + .../src/__tests__/setupTests.js | 94 + .../src/__tests__/store-test.js | 807 ++ .../__tests__/storeComponentFilters-test.js | 230 + .../src/__tests__/storeOwners-test.js | 171 + .../src/__tests__/storeSerializer.js | 21 + .../src/__tests__/storeStressSync-test.js | 1100 +++ .../storeStressTestConcurrent-test.js | 1099 +++ .../src/__tests__/treeContext-test.js | 613 ++ .../src/__tests__/utils.js | 212 + .../NativeStyleEditor/resolveBoxStyle.js | 93 + .../setupNativeStyleEditor.js | 325 + .../src/backend/NativeStyleEditor/types.js | 34 + .../src/backend/agent.js | 492 ++ .../src/backend/console.js | 152 + .../src/backend/describeComponentFrame.js | 48 + .../src/backend/index.js | 102 + .../src/backend/legacy/renderer.js | 950 ++ .../src/backend/legacy/utils.js | 46 + .../src/backend/renderer.js | 3032 +++++++ .../src/backend/types.js | 282 + .../src/backend/utils.js | 55 + .../backend/views/Highlighter/Highlighter.js | 53 + .../src/backend/views/Highlighter/Overlay.js | 461 + .../src/backend/views/Highlighter/index.js | 195 + packages/react-devtools-shared/src/bridge.js | 248 + .../react-devtools-shared/src/constants.js | 67 + .../src/devtools/ProfilerStore.js | 347 + .../src/devtools/ProfilingCache.js | 129 + .../src/devtools/cache.js | 203 + .../src/devtools/index.js | 21 + .../src/devtools/store.js | 1012 +++ .../src/devtools/utils.js | 84 + .../src/devtools/views/Button.css | 37 + .../src/devtools/views/Button.js | 45 + .../src/devtools/views/ButtonIcon.css | 5 + .../src/devtools/views/ButtonIcon.js | 246 + .../src/devtools/views/Components/Badge.css | 17 + .../src/devtools/views/Components/Badge.js | 57 + .../devtools/views/Components/Components.css | 45 + .../devtools/views/Components/Components.js | 53 + .../views/Components/EditableValue.css | 30 + .../views/Components/EditableValue.js | 149 + .../src/devtools/views/Components/Element.css | 75 + .../src/devtools/views/Components/Element.js | 234 + .../views/Components/ExpandCollapseToggle.css | 7 + .../views/Components/ExpandCollapseToggle.js | 33 + .../devtools/views/Components/HocBadges.css | 16 + .../devtools/views/Components/HocBadges.js | 48 + .../devtools/views/Components/HooksTree.css | 69 + .../devtools/views/Components/HooksTree.js | 290 + .../Components/InspectHostNodesToggle.js | 50 + .../Components/InspectedElementContext.js | 325 + .../views/Components/InspectedElementTree.css | 48 + .../views/Components/InspectedElementTree.js | 79 + .../devtools/views/Components/KeyValue.css | 41 + .../src/devtools/views/Components/KeyValue.js | 229 + .../NativeStyleEditor/AutoSizeInput.css | 26 + .../NativeStyleEditor/AutoSizeInput.js | 107 + .../NativeStyleEditor/LayoutViewer.css | 50 + .../NativeStyleEditor/LayoutViewer.js | 71 + .../NativeStyleEditor/StyleEditor.css | 58 + .../NativeStyleEditor/StyleEditor.js | 305 + .../Components/NativeStyleEditor/context.js | 207 + .../Components/NativeStyleEditor/index.js | 72 + .../Components/NativeStyleEditor/types.js | 20 + .../views/Components/OwnersListContext.js | 138 + .../devtools/views/Components/OwnersStack.css | 104 + .../devtools/views/Components/OwnersStack.js | 310 + .../devtools/views/Components/SearchInput.css | 42 + .../devtools/views/Components/SearchInput.js | 136 + .../views/Components/SelectedElement.css | 89 + .../views/Components/SelectedElement.js | 476 + .../Components/SelectedTreeHighlight.css | 16 + .../views/Components/SelectedTreeHighlight.js | 107 + .../src/devtools/views/Components/Tree.css | 60 + .../src/devtools/views/Components/Tree.js | 518 ++ .../devtools/views/Components/TreeContext.js | 824 ++ .../views/Components/TreeFocusedContext.js | 14 + .../Components/ViewElementSourceContext.js | 25 + .../src/devtools/views/Components/types.js | 101 + .../src/devtools/views/DevTools.css | 54 + .../src/devtools/views/DevTools.js | 177 + .../src/devtools/views/ErrorBoundary.css | 24 + .../src/devtools/views/ErrorBoundary.js | 114 + .../src/devtools/views/Icon.css | 5 + .../src/devtools/views/Icon.js | 117 + .../src/devtools/views/ModalDialog.css | 40 + .../src/devtools/views/ModalDialog.js | 162 + .../src/devtools/views/Profiler/ChartNode.css | 31 + .../src/devtools/views/Profiler/ChartNode.js | 74 + .../Profiler/ClearProfilingDataButton.js | 31 + .../views/Profiler/CommitFlamegraph.css | 10 + .../views/Profiler/CommitFlamegraph.js | 172 + .../Profiler/CommitFlamegraphListItem.js | 117 + .../devtools/views/Profiler/CommitRanked.css | 5 + .../devtools/views/Profiler/CommitRanked.js | 136 + .../views/Profiler/CommitRankedListItem.js | 61 + .../views/Profiler/CommitTreeBuilder.js | 371 + .../views/Profiler/FlamegraphChartBuilder.js | 189 + .../views/Profiler/InteractionListItem.css | 43 + .../views/Profiler/InteractionListItem.js | 106 + .../devtools/views/Profiler/Interactions.css | 9 + .../devtools/views/Profiler/Interactions.js | 148 + .../Profiler/InteractionsChartBuilder.js | 62 + .../devtools/views/Profiler/NoCommitData.css | 18 + .../devtools/views/Profiler/NoCommitData.js | 25 + .../views/Profiler/NoInteractions.css | 16 + .../devtools/views/Profiler/NoInteractions.js | 35 + .../src/devtools/views/Profiler/Profiler.css | 112 + .../src/devtools/views/Profiler/Profiler.js | 203 + .../views/Profiler/ProfilerContext.js | 297 + .../Profiler/ProfilingImportExportButtons.css | 25 + .../Profiler/ProfilingImportExportButtons.js | 131 + .../views/Profiler/RankedChartBuilder.js | 106 + .../devtools/views/Profiler/RecordToggle.css | 38 + .../devtools/views/Profiler/RecordToggle.js | 42 + .../views/Profiler/ReloadAndProfileButton.js | 71 + .../devtools/views/Profiler/RootSelector.css | 3 + .../devtools/views/Profiler/RootSelector.js | 49 + .../views/Profiler/SidebarCommitInfo.css | 65 + .../views/Profiler/SidebarCommitInfo.js | 91 + .../views/Profiler/SidebarInteractions.css | 55 + .../views/Profiler/SidebarInteractions.js | 97 + .../Profiler/SidebarSelectedFiberInfo.css | 76 + .../Profiler/SidebarSelectedFiberInfo.js | 221 + .../views/Profiler/SnapshotCommitList.css | 9 + .../views/Profiler/SnapshotCommitList.js | 179 + .../views/Profiler/SnapshotCommitListItem.css | 16 + .../views/Profiler/SnapshotCommitListItem.js | 80 + .../views/Profiler/SnapshotSelector.css | 30 + .../views/Profiler/SnapshotSelector.js | 186 + .../src/devtools/views/Profiler/constants.js | 14 + .../src/devtools/views/Profiler/types.js | 139 + .../src/devtools/views/Profiler/utils.js | 236 + .../src/devtools/views/ReactLogo.css | 6 + .../src/devtools/views/ReactLogo.js | 32 + .../views/Settings/ComponentsSettings.js | 401 + .../views/Settings/GeneralSettings.js | 77 + .../views/Settings/ProfilerSettings.js | 103 + .../views/Settings/SettingsContext.js | 345 + .../devtools/views/Settings/SettingsModal.css | 45 + .../devtools/views/Settings/SettingsModal.js | 140 + .../views/Settings/SettingsModalContext.js | 38 + .../Settings/SettingsModalContextToggle.js | 48 + .../views/Settings/SettingsShared.css | 140 + .../ShowWelcomeToTheNewDevToolsDialog.css | 26 + .../ShowWelcomeToTheNewDevToolsDialog.js | 73 + .../src/devtools/views/TabBar.css | 110 + .../src/devtools/views/TabBar.js | 132 + .../src/devtools/views/Toggle.css | 54 + .../src/devtools/views/Toggle.js | 67 + .../src/devtools/views/Tooltip.css | 11 + .../views/WarnIfLegacyBackendDetected.css | 6 + .../views/WarnIfLegacyBackendDetected.js | 86 + .../src/devtools/views/context.js | 21 + .../src/devtools/views/hooks.js | 226 + .../src/devtools/views/portaledContent.js | 32 + .../src/devtools/views/root.css | 178 + .../src/devtools/views/utils.js | 212 + packages/react-devtools-shared/src/hook.js | 314 + .../react-devtools-shared/src/hydration.js | 531 ++ .../node_modules/react-window/CHANGELOG.md | 96 + .../src/node_modules/react-window/LICENSE.md | 21 + .../src/node_modules/react-window/README.md | 88 + .../react-window/dist/index-dev.umd.js | 2 + .../react-window/dist/index-prod.umd.js | 2 + .../react-window/dist/index.cjs.js | 2079 +++++ .../react-window/dist/index.cjs.js.flow | 3 + .../react-window/dist/index.esm.js | 2078 +++++ .../react-window/dist/index.esm.js.flow | 3 + .../node_modules/react-window/package.json | 110 + .../react-window/src/FixedSizeGrid.js | 244 + .../react-window/src/FixedSizeList.js | 130 + .../react-window/src/VariableSizeGrid.js | 507 ++ .../react-window/src/VariableSizeList.js | 316 + .../node_modules/react-window/src/areEqual.js | 18 + .../react-window/src/createGridComponent.js | 916 ++ .../react-window/src/createListComponent.js | 722 ++ .../react-window/src/domHelpers.js | 72 + .../node_modules/react-window/src/index.js | 9 + .../react-window/src/shallowDiffers.js | 17 + .../react-window/src/shouldComponentUpdate.js | 16 + .../node_modules/react-window/src/timer.js | 37 + packages/react-devtools-shared/src/storage.js | 48 + packages/react-devtools-shared/src/types.js | 77 + packages/react-devtools-shared/src/utils.js | 309 + packages/react-devtools-shell/README.md | 17 + packages/react-devtools-shell/index.html | 68 + packages/react-devtools-shell/now.json | 5 + packages/react-devtools-shell/package.json | 34 + .../src/app/DeeplyNestedComponents/index.js | 45 + .../src/app/EditableProps/index.js | 169 + .../src/app/ElementTypes/index.js | 67 + .../src/app/Hydration/index.js | 140 + .../src/app/Iframe/index.js | 69 + .../src/app/InspectableElements/Contexts.js | 120 + .../app/InspectableElements/CustomHooks.js | 132 + .../app/InspectableElements/CustomObject.js | 25 + .../InspectableElements.js | 32 + .../app/InspectableElements/NestedProps.js | 64 + .../app/InspectableElements/SimpleValues.js | 30 + .../UnserializableProps.js | 42 + .../src/app/InspectableElements/index.js | 12 + .../src/app/InteractionTracing/index.js | 92 + .../src/app/PriorityLevels/index.js | 53 + .../src/app/ReactNativeWeb/index.js | 41 + .../src/app/SuspenseTree/index.js | 143 + .../src/app/ToDoList/List.css | 16 + .../src/app/ToDoList/List.js | 120 + .../src/app/ToDoList/ListItem.css | 23 + .../src/app/ToDoList/ListItem.js | 54 + .../src/app/ToDoList/index.js | 12 + .../src/app/Toggle/index.js | 21 + .../react-devtools-shell/src/app/console.js | 34 + .../react-devtools-shell/src/app/index.js | 71 + .../react-devtools-shell/src/app/styles.css | 13 + packages/react-devtools-shell/src/devtools.js | 77 + .../react-devtools-shell/webpack.config.js | 99 + packages/react-devtools/README.md | 9 +- packages/react-devtools/app.html | 129 + packages/react-devtools/app.js | 48 + packages/react-devtools/bin.js | 25 + packages/react-devtools/icons/icon128.png | Bin 0 -> 4577 bytes packages/react-devtools/index.js | 14 + packages/react-devtools/package.json | 32 +- packages/react-events/src/dom/Press.js | 5 + .../src/dom/__tests__/Press-test.internal.js | 4 +- .../react-reconciler/src/ReactFiberHooks.js | 5 + .../src/__tests__/ReactFresh-test.js | 43 + scripts/error-codes/README.md | 2 +- scripts/flow/config/flowconfig | 2 + scripts/flow/react-devtools.js | 25 + scripts/jest/config.build-devtools.js | 67 + scripts/jest/config.build.js | 4 + scripts/jest/config.source-persistent.js | 2 + scripts/jest/config.source.js | 4 + scripts/jest/preprocessor.js | 7 + scripts/print-warnings/print-warnings.js | 1 + yarn.lock | 7705 ++++++++++++++++- 388 files changed, 63603 insertions(+), 245 deletions(-) create mode 100644 .prettierignore create mode 100644 fixtures/devtools/regression/14.9.html create mode 100644 fixtures/devtools/regression/15.0.html create mode 100644 fixtures/devtools/regression/15.1.html create mode 100644 fixtures/devtools/regression/15.2.html create mode 100644 fixtures/devtools/regression/15.3.html create mode 100644 fixtures/devtools/regression/15.4.html create mode 100644 fixtures/devtools/regression/15.5.html create mode 100644 fixtures/devtools/regression/15.6.html create mode 100644 fixtures/devtools/regression/16.0.html create mode 100644 fixtures/devtools/regression/16.1.html create mode 100644 fixtures/devtools/regression/16.2.html create mode 100644 fixtures/devtools/regression/16.3.html create mode 100644 fixtures/devtools/regression/16.4.html create mode 100644 fixtures/devtools/regression/16.5.html create mode 100644 fixtures/devtools/regression/16.6.html create mode 100644 fixtures/devtools/regression/16.7.html create mode 100644 fixtures/devtools/regression/canary.html create mode 100644 fixtures/devtools/regression/index.html create mode 100644 fixtures/devtools/regression/next.html create mode 100755 fixtures/devtools/regression/server.js create mode 100644 fixtures/devtools/regression/shared.js create mode 100644 fixtures/devtools/regression/styles.css create mode 100644 fixtures/devtools/standalone/index.html create mode 100644 packages/react-devtools-core/backend.js create mode 100644 packages/react-devtools-core/src/backend.js create mode 100644 packages/react-devtools-core/src/editor.js create mode 100644 packages/react-devtools-core/src/standalone.js create mode 100644 packages/react-devtools-core/standalone.js create mode 100644 packages/react-devtools-core/webpack.backend.js create mode 100644 packages/react-devtools-core/webpack.standalone.js create mode 100644 packages/react-devtools-extensions/.circleci/config.yml create mode 100644 packages/react-devtools-extensions/README.md create mode 100644 packages/react-devtools-extensions/build.js create mode 100644 packages/react-devtools-extensions/chrome/README.md create mode 100644 packages/react-devtools-extensions/chrome/build.js create mode 100644 packages/react-devtools-extensions/chrome/deploy.js create mode 100644 packages/react-devtools-extensions/chrome/manifest.json create mode 100644 packages/react-devtools-extensions/chrome/now.json create mode 100644 packages/react-devtools-extensions/chrome/test.js create mode 100644 packages/react-devtools-extensions/deploy.chrome.html create mode 100644 packages/react-devtools-extensions/deploy.firefox.html create mode 100644 packages/react-devtools-extensions/deploy.html create mode 100644 packages/react-devtools-extensions/deploy.js create mode 100644 packages/react-devtools-extensions/firefox/README.md create mode 100644 packages/react-devtools-extensions/firefox/build.js create mode 100644 packages/react-devtools-extensions/firefox/deploy.js create mode 100644 packages/react-devtools-extensions/firefox/manifest.json create mode 100644 packages/react-devtools-extensions/firefox/now.json create mode 100644 packages/react-devtools-extensions/firefox/test.js create mode 100644 packages/react-devtools-extensions/flow-typed/jest.js create mode 100644 packages/react-devtools-extensions/flow-typed/npm/react-test-renderer_v16.x.x.js create mode 100644 packages/react-devtools-extensions/icons/128-deadcode.png create mode 100644 packages/react-devtools-extensions/icons/128-development.png create mode 100644 packages/react-devtools-extensions/icons/128-disabled.png create mode 100644 packages/react-devtools-extensions/icons/128-outdated.png create mode 100644 packages/react-devtools-extensions/icons/128-production.png create mode 100644 packages/react-devtools-extensions/icons/128-unminified.png create mode 100644 packages/react-devtools-extensions/icons/16-deadcode.png create mode 100644 packages/react-devtools-extensions/icons/16-development.png create mode 100644 packages/react-devtools-extensions/icons/16-disabled.png create mode 100644 packages/react-devtools-extensions/icons/16-outdated.png create mode 100644 packages/react-devtools-extensions/icons/16-production.png create mode 100644 packages/react-devtools-extensions/icons/16-unminified.png create mode 100644 packages/react-devtools-extensions/icons/32-deadcode.png create mode 100644 packages/react-devtools-extensions/icons/32-development.png create mode 100644 packages/react-devtools-extensions/icons/32-disabled.png create mode 100644 packages/react-devtools-extensions/icons/32-outdated.png create mode 100644 packages/react-devtools-extensions/icons/32-production.png create mode 100644 packages/react-devtools-extensions/icons/32-unminified.png create mode 100644 packages/react-devtools-extensions/icons/48-deadcode.png create mode 100644 packages/react-devtools-extensions/icons/48-development.png create mode 100644 packages/react-devtools-extensions/icons/48-disabled.png create mode 100644 packages/react-devtools-extensions/icons/48-outdated.png create mode 100644 packages/react-devtools-extensions/icons/48-production.png create mode 100644 packages/react-devtools-extensions/icons/48-unminified.png create mode 100644 packages/react-devtools-extensions/icons/deadcode.svg create mode 100644 packages/react-devtools-extensions/icons/development.svg create mode 100644 packages/react-devtools-extensions/icons/disabled.svg create mode 100644 packages/react-devtools-extensions/icons/outdated.svg create mode 100644 packages/react-devtools-extensions/icons/production.svg create mode 100644 packages/react-devtools-extensions/main.html create mode 100644 packages/react-devtools-extensions/package.json create mode 100644 packages/react-devtools-extensions/panel.html create mode 100644 packages/react-devtools-extensions/popups/deadcode.html create mode 100644 packages/react-devtools-extensions/popups/development.html create mode 100644 packages/react-devtools-extensions/popups/disabled.html create mode 100644 packages/react-devtools-extensions/popups/outdated.html create mode 100644 packages/react-devtools-extensions/popups/production.html create mode 100644 packages/react-devtools-extensions/popups/shared.js create mode 100644 packages/react-devtools-extensions/popups/unminified.html create mode 100644 packages/react-devtools-extensions/src/backend.js create mode 100644 packages/react-devtools-extensions/src/background.js create mode 100644 packages/react-devtools-extensions/src/contentScript.js create mode 100644 packages/react-devtools-extensions/src/inject.js create mode 100644 packages/react-devtools-extensions/src/injectGlobalHook.js create mode 100644 packages/react-devtools-extensions/src/main.js create mode 100644 packages/react-devtools-extensions/src/panel.js create mode 100644 packages/react-devtools-extensions/src/renderer.js create mode 100644 packages/react-devtools-extensions/src/utils.js create mode 100644 packages/react-devtools-extensions/utils.js create mode 100644 packages/react-devtools-extensions/webpack.backend.js create mode 100644 packages/react-devtools-extensions/webpack.config.js create mode 100644 packages/react-devtools-inline/backend.js create mode 100644 packages/react-devtools-inline/frontend.js create mode 100644 packages/react-devtools-inline/src/backend.js create mode 100644 packages/react-devtools-inline/src/constants.js create mode 100644 packages/react-devtools-inline/src/frontend.js create mode 100644 packages/react-devtools-inline/webpack.config.js create mode 100644 packages/react-devtools-shared/babel.config.js create mode 100644 packages/react-devtools-shared/package.json create mode 100644 packages/react-devtools-shared/src/__tests__/__mocks__/cssMock.js create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/inspectedElementContext-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/ownersListContext-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/profilerContext-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCharts-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCommitTreeBuilder-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/store-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/storeComponentFilters-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/storeOwners-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/storeStressSync-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/storeStressTestConcurrent-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/__snapshots__/treeContext-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/bridge-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/console-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/inspectedElementSerializer.js create mode 100644 packages/react-devtools-shared/src/__tests__/legacy/__snapshots__/inspectElement-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/legacy/__snapshots__/storeLegacy-v15-test.js.snap create mode 100644 packages/react-devtools-shared/src/__tests__/legacy/inspectElement-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/legacy/storeLegacy-v15-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/ownersListContext-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/profilerContext-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/profilerStore-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/profilingCache-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/profilingCharts-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/profilingUtils-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/setupEnv.js create mode 100644 packages/react-devtools-shared/src/__tests__/setupTests.js create mode 100644 packages/react-devtools-shared/src/__tests__/store-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/storeOwners-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/storeSerializer.js create mode 100644 packages/react-devtools-shared/src/__tests__/storeStressSync-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/treeContext-test.js create mode 100644 packages/react-devtools-shared/src/__tests__/utils.js create mode 100644 packages/react-devtools-shared/src/backend/NativeStyleEditor/resolveBoxStyle.js create mode 100644 packages/react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor.js create mode 100644 packages/react-devtools-shared/src/backend/NativeStyleEditor/types.js create mode 100644 packages/react-devtools-shared/src/backend/agent.js create mode 100644 packages/react-devtools-shared/src/backend/console.js create mode 100644 packages/react-devtools-shared/src/backend/describeComponentFrame.js create mode 100644 packages/react-devtools-shared/src/backend/index.js create mode 100644 packages/react-devtools-shared/src/backend/legacy/renderer.js create mode 100644 packages/react-devtools-shared/src/backend/legacy/utils.js create mode 100644 packages/react-devtools-shared/src/backend/renderer.js create mode 100644 packages/react-devtools-shared/src/backend/types.js create mode 100644 packages/react-devtools-shared/src/backend/utils.js create mode 100644 packages/react-devtools-shared/src/backend/views/Highlighter/Highlighter.js create mode 100644 packages/react-devtools-shared/src/backend/views/Highlighter/Overlay.js create mode 100644 packages/react-devtools-shared/src/backend/views/Highlighter/index.js create mode 100644 packages/react-devtools-shared/src/bridge.js create mode 100644 packages/react-devtools-shared/src/constants.js create mode 100644 packages/react-devtools-shared/src/devtools/ProfilerStore.js create mode 100644 packages/react-devtools-shared/src/devtools/ProfilingCache.js create mode 100644 packages/react-devtools-shared/src/devtools/cache.js create mode 100644 packages/react-devtools-shared/src/devtools/index.js create mode 100644 packages/react-devtools-shared/src/devtools/store.js create mode 100644 packages/react-devtools-shared/src/devtools/utils.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Button.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Button.js create mode 100644 packages/react-devtools-shared/src/devtools/views/ButtonIcon.css create mode 100644 packages/react-devtools-shared/src/devtools/views/ButtonIcon.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/Badge.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/Badge.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/Components.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/Components.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/EditableValue.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/EditableValue.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/Element.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/Element.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/ExpandCollapseToggle.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/ExpandCollapseToggle.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/HocBadges.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/HocBadges.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/HooksTree.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/HooksTree.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/InspectHostNodesToggle.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/InspectedElementTree.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/InspectedElementTree.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/KeyValue.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/AutoSizeInput.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/AutoSizeInput.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/LayoutViewer.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/LayoutViewer.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/StyleEditor.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/StyleEditor.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/context.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/index.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/types.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/SearchInput.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/SearchInput.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/SelectedTreeHighlight.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/SelectedTreeHighlight.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/Tree.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/Tree.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/TreeFocusedContext.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/ViewElementSourceContext.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/types.js create mode 100644 packages/react-devtools-shared/src/devtools/views/DevTools.css create mode 100644 packages/react-devtools-shared/src/devtools/views/DevTools.js create mode 100644 packages/react-devtools-shared/src/devtools/views/ErrorBoundary.css create mode 100644 packages/react-devtools-shared/src/devtools/views/ErrorBoundary.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Icon.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Icon.js create mode 100644 packages/react-devtools-shared/src/devtools/views/ModalDialog.css create mode 100644 packages/react-devtools-shared/src/devtools/views/ModalDialog.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/ChartNode.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/ChartNode.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/ClearProfilingDataButton.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraph.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraph.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraphListItem.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/CommitRanked.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/CommitRanked.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/CommitRankedListItem.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/FlamegraphChartBuilder.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/InteractionListItem.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/InteractionListItem.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/Interactions.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/Interactions.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/InteractionsChartBuilder.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/NoCommitData.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/NoCommitData.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/NoInteractions.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/NoInteractions.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/Profiler.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/RankedChartBuilder.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/RecordToggle.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/RecordToggle.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/ReloadAndProfileButton.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/RootSelector.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/RootSelector.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SidebarCommitInfo.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SidebarInteractions.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SidebarInteractions.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitList.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitList.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitListItem.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitListItem.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotSelector.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotSelector.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/constants.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/types.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Profiler/utils.js create mode 100644 packages/react-devtools-shared/src/devtools/views/ReactLogo.css create mode 100644 packages/react-devtools-shared/src/devtools/views/ReactLogo.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Settings/ComponentsSettings.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Settings/GeneralSettings.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Settings/ProfilerSettings.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Settings/SettingsModal.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Settings/SettingsModal.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Settings/SettingsModalContext.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Settings/SettingsShared.css create mode 100644 packages/react-devtools-shared/src/devtools/views/ShowWelcomeToTheNewDevToolsDialog.css create mode 100644 packages/react-devtools-shared/src/devtools/views/ShowWelcomeToTheNewDevToolsDialog.js create mode 100644 packages/react-devtools-shared/src/devtools/views/TabBar.css create mode 100644 packages/react-devtools-shared/src/devtools/views/TabBar.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Toggle.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Toggle.js create mode 100644 packages/react-devtools-shared/src/devtools/views/Tooltip.css create mode 100644 packages/react-devtools-shared/src/devtools/views/WarnIfLegacyBackendDetected.css create mode 100644 packages/react-devtools-shared/src/devtools/views/WarnIfLegacyBackendDetected.js create mode 100644 packages/react-devtools-shared/src/devtools/views/context.js create mode 100644 packages/react-devtools-shared/src/devtools/views/hooks.js create mode 100644 packages/react-devtools-shared/src/devtools/views/portaledContent.js create mode 100644 packages/react-devtools-shared/src/devtools/views/root.css create mode 100644 packages/react-devtools-shared/src/devtools/views/utils.js create mode 100644 packages/react-devtools-shared/src/hook.js create mode 100644 packages/react-devtools-shared/src/hydration.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/CHANGELOG.md create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/LICENSE.md create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/README.md create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/dist/index-dev.umd.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/dist/index-prod.umd.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/dist/index.cjs.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/dist/index.cjs.js.flow create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/dist/index.esm.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/dist/index.esm.js.flow create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/package.json create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/FixedSizeGrid.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/FixedSizeList.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/VariableSizeGrid.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/VariableSizeList.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/areEqual.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/createGridComponent.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/createListComponent.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/domHelpers.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/index.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/shallowDiffers.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/shouldComponentUpdate.js create mode 100644 packages/react-devtools-shared/src/node_modules/react-window/src/timer.js create mode 100644 packages/react-devtools-shared/src/storage.js create mode 100644 packages/react-devtools-shared/src/types.js create mode 100644 packages/react-devtools-shared/src/utils.js create mode 100644 packages/react-devtools-shell/README.md create mode 100644 packages/react-devtools-shell/index.html create mode 100644 packages/react-devtools-shell/now.json create mode 100644 packages/react-devtools-shell/package.json create mode 100644 packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js create mode 100644 packages/react-devtools-shell/src/app/EditableProps/index.js create mode 100644 packages/react-devtools-shell/src/app/ElementTypes/index.js create mode 100644 packages/react-devtools-shell/src/app/Hydration/index.js create mode 100644 packages/react-devtools-shell/src/app/Iframe/index.js create mode 100644 packages/react-devtools-shell/src/app/InspectableElements/Contexts.js create mode 100644 packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js create mode 100644 packages/react-devtools-shell/src/app/InspectableElements/CustomObject.js create mode 100644 packages/react-devtools-shell/src/app/InspectableElements/InspectableElements.js create mode 100644 packages/react-devtools-shell/src/app/InspectableElements/NestedProps.js create mode 100644 packages/react-devtools-shell/src/app/InspectableElements/SimpleValues.js create mode 100644 packages/react-devtools-shell/src/app/InspectableElements/UnserializableProps.js create mode 100644 packages/react-devtools-shell/src/app/InspectableElements/index.js create mode 100644 packages/react-devtools-shell/src/app/InteractionTracing/index.js create mode 100644 packages/react-devtools-shell/src/app/PriorityLevels/index.js create mode 100644 packages/react-devtools-shell/src/app/ReactNativeWeb/index.js create mode 100644 packages/react-devtools-shell/src/app/SuspenseTree/index.js create mode 100644 packages/react-devtools-shell/src/app/ToDoList/List.css create mode 100644 packages/react-devtools-shell/src/app/ToDoList/List.js create mode 100644 packages/react-devtools-shell/src/app/ToDoList/ListItem.css create mode 100644 packages/react-devtools-shell/src/app/ToDoList/ListItem.js create mode 100644 packages/react-devtools-shell/src/app/ToDoList/index.js create mode 100644 packages/react-devtools-shell/src/app/Toggle/index.js create mode 100644 packages/react-devtools-shell/src/app/console.js create mode 100644 packages/react-devtools-shell/src/app/index.js create mode 100644 packages/react-devtools-shell/src/app/styles.css create mode 100644 packages/react-devtools-shell/src/devtools.js create mode 100644 packages/react-devtools-shell/webpack.config.js create mode 100644 packages/react-devtools/app.html create mode 100644 packages/react-devtools/app.js create mode 100755 packages/react-devtools/bin.js create mode 100644 packages/react-devtools/icons/icon128.png create mode 100644 packages/react-devtools/index.js create mode 100644 scripts/flow/react-devtools.js create mode 100644 scripts/jest/config.build-devtools.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 311665d0f3df3..535563556ee95 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -158,6 +158,16 @@ jobs: - *run_yarn - run: yarn test-build --maxWorkers=2 + test_build_devtools: + docker: *docker + environment: *environment + steps: + - checkout + - attach_workspace: *attach_workspace + - *restore_yarn_cache + - *run_yarn + - run: yarn test-build-devtools --maxWorkers=2 + test_dom_fixtures: docker: *docker environment: *environment @@ -231,6 +241,9 @@ workflows: - test_build_prod: requires: - build + - test_build_devtools: + requires: + - build - test_dom_fixtures: requires: - build diff --git a/.eslintignore b/.eslintignore index d95f2bf247e3c..62ca593965173 100644 --- a/.eslintignore +++ b/.eslintignore @@ -12,3 +12,10 @@ scripts/bench/benchmarks/**/*.js # React repository clone scripts/bench/remote-repo/ + +packages/react-devtools-core/dist +packages/react-devtools-extensions/chrome/build +packages/react-devtools-extensions/firefox/build +packages/react-devtools-extensions/shared/build +packages/react-devtools-inline/dist +packages/react-devtools-shell/dist \ No newline at end of file diff --git a/.gitignore b/.gitignore index 53de0c289077e..902e6c9c8838b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,14 @@ chrome-user-data .vscode *.swp *.swo + +packages/react-devtools-core/dist +packages/react-devtools-extensions/chrome/build +packages/react-devtools-extensions/chrome/*.crx +packages/react-devtools-extensions/chrome/*.pem +packages/react-devtools-extensions/firefox/build +packages/react-devtools-extensions/firefox/*.xpi +packages/react-devtools-extensions/firefox/*.pem +packages/react-devtools-extensions/shared/build +packages/react-devtools-inline/dist +packages/react-devtools-shell/dist \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000000..bea24210eca5e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +packages/react-devtools-core/dist +packages/react-devtools-extensions/chrome/build +packages/react-devtools-extensions/firefox/build +packages/react-devtools-extensions/shared/build +packages/react-devtools-inline/dist +packages/react-devtools-shell/dist \ No newline at end of file diff --git a/fixtures/devtools/regression/14.9.html b/fixtures/devtools/regression/14.9.html new file mode 100644 index 0000000000000..524ff196881f5 --- /dev/null +++ b/fixtures/devtools/regression/14.9.html @@ -0,0 +1,38 @@ + + + + + React 14.9 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/14.9.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/15.0.html b/fixtures/devtools/regression/15.0.html new file mode 100644 index 0000000000000..fde26012c6bbe --- /dev/null +++ b/fixtures/devtools/regression/15.0.html @@ -0,0 +1,38 @@ + + + + + React 15.0 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/15.0.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/15.1.html b/fixtures/devtools/regression/15.1.html new file mode 100644 index 0000000000000..49813d80a19d6 --- /dev/null +++ b/fixtures/devtools/regression/15.1.html @@ -0,0 +1,38 @@ + + + + + React 15.1 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/15.1.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/15.2.html b/fixtures/devtools/regression/15.2.html new file mode 100644 index 0000000000000..2be6f7c41aae3 --- /dev/null +++ b/fixtures/devtools/regression/15.2.html @@ -0,0 +1,38 @@ + + + + + React 15.2 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/15.2.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/15.3.html b/fixtures/devtools/regression/15.3.html new file mode 100644 index 0000000000000..cc5c3960d2a44 --- /dev/null +++ b/fixtures/devtools/regression/15.3.html @@ -0,0 +1,38 @@ + + + + + React 15.3 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/15.3.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/15.4.html b/fixtures/devtools/regression/15.4.html new file mode 100644 index 0000000000000..93b425a644b6f --- /dev/null +++ b/fixtures/devtools/regression/15.4.html @@ -0,0 +1,38 @@ + + + + + React 15.4 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/15.4.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/15.5.html b/fixtures/devtools/regression/15.5.html new file mode 100644 index 0000000000000..75f722ccabfe6 --- /dev/null +++ b/fixtures/devtools/regression/15.5.html @@ -0,0 +1,38 @@ + + + + + React 15.5 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/15.5.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/15.6.html b/fixtures/devtools/regression/15.6.html new file mode 100644 index 0000000000000..0ec03ee51d123 --- /dev/null +++ b/fixtures/devtools/regression/15.6.html @@ -0,0 +1,38 @@ + + + + + React 15.6 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/15.6.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/16.0.html b/fixtures/devtools/regression/16.0.html new file mode 100644 index 0000000000000..1e1403372ca43 --- /dev/null +++ b/fixtures/devtools/regression/16.0.html @@ -0,0 +1,38 @@ + + + + + React 16.0 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/16.0.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/16.1.html b/fixtures/devtools/regression/16.1.html new file mode 100644 index 0000000000000..a6131e9ae1ce9 --- /dev/null +++ b/fixtures/devtools/regression/16.1.html @@ -0,0 +1,38 @@ + + + + + React 16.1 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/16.1.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/16.2.html b/fixtures/devtools/regression/16.2.html new file mode 100644 index 0000000000000..4d0468d343428 --- /dev/null +++ b/fixtures/devtools/regression/16.2.html @@ -0,0 +1,38 @@ + + + + + React 16.2 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/16.2.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/16.3.html b/fixtures/devtools/regression/16.3.html new file mode 100644 index 0000000000000..335adaeffead1 --- /dev/null +++ b/fixtures/devtools/regression/16.3.html @@ -0,0 +1,38 @@ + + + + + React 16.3 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/16.3.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/16.4.html b/fixtures/devtools/regression/16.4.html new file mode 100644 index 0000000000000..8a881ea3fe059 --- /dev/null +++ b/fixtures/devtools/regression/16.4.html @@ -0,0 +1,38 @@ + + + + + React 16.4 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/16.4.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/16.5.html b/fixtures/devtools/regression/16.5.html new file mode 100644 index 0000000000000..ad91102b1b609 --- /dev/null +++ b/fixtures/devtools/regression/16.5.html @@ -0,0 +1,40 @@ + + + + + React 16.5 + + + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/16.5.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/16.6.html b/fixtures/devtools/regression/16.6.html new file mode 100644 index 0000000000000..aa8ae33837e57 --- /dev/null +++ b/fixtures/devtools/regression/16.6.html @@ -0,0 +1,41 @@ + + + + + React 16.6 + + + + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/16.6.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/16.7.html b/fixtures/devtools/regression/16.7.html new file mode 100644 index 0000000000000..76fb9823014c2 --- /dev/null +++ b/fixtures/devtools/regression/16.7.html @@ -0,0 +1,41 @@ + + + + + React 16.7 + + + + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/16.7.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/canary.html b/fixtures/devtools/regression/canary.html new file mode 100644 index 0000000000000..88cadef4ccaf6 --- /dev/null +++ b/fixtures/devtools/regression/canary.html @@ -0,0 +1,41 @@ + + + + + React canary + + + + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/canary.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/index.html b/fixtures/devtools/regression/index.html new file mode 100644 index 0000000000000..125a8e8baf9b9 --- /dev/null +++ b/fixtures/devtools/regression/index.html @@ -0,0 +1,28 @@ + + + + + React DevTools regression test + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/next.html b/fixtures/devtools/regression/next.html new file mode 100644 index 0000000000000..4ac0751a0c22d --- /dev/null +++ b/fixtures/devtools/regression/next.html @@ -0,0 +1,41 @@ + + + + + React next + + + + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/devtools/regression/server.js
+ open http://localhost:3000/next.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/devtools/regression/server.js b/fixtures/devtools/regression/server.js new file mode 100755 index 0000000000000..34853173a01ee --- /dev/null +++ b/fixtures/devtools/regression/server.js @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +const finalhandler = require('finalhandler'); +const http = require('http'); +const serveStatic = require('serve-static'); + +// Serve fixtures folder +const serve = serveStatic(__dirname, {index: 'index.html'}); + +// Create server +const server = http.createServer(function onRequest(req, res) { + serve(req, res, finalhandler(req, res)); +}); + +// Listen +server.listen(3000); diff --git a/fixtures/devtools/regression/shared.js b/fixtures/devtools/regression/shared.js new file mode 100644 index 0000000000000..055ba3d65e553 --- /dev/null +++ b/fixtures/devtools/regression/shared.js @@ -0,0 +1,328 @@ +/* eslint-disable no-fallthrough, react/react-in-jsx-scope, react/jsx-no-undef */ +/* global React ReactCache ReactDOM SchedulerTracing ScheduleTracing */ + +const apps = []; + +const pieces = React.version.split('.'); +const major = + pieces[0] === '0' ? parseInt(pieces[1], 10) : parseInt(pieces[0], 10); +const minor = + pieces[0] === '0' ? parseInt(pieces[2], 10) : parseInt(pieces[1], 10); + +// Convenience wrapper to organize API features in DevTools. +function Feature({children, label, version}) { + return ( +
+
+ {label} + {version} +
+ {children} +
+ ); +} + +// Simplify interaction tracing for tests below. +let trace = null; +if (typeof SchedulerTracing !== 'undefined') { + trace = SchedulerTracing.unstable_trace; +} else if (typeof ScheduleTracing !== 'undefined') { + trace = ScheduleTracing.unstable_trace; +} else { + trace = (_, __, callback) => callback(); +} + +// https://github.com/facebook/react/blob/master/CHANGELOG.md +switch (major) { + case 16: + switch (minor) { + case 7: + if (typeof React.useState === 'function') { + // Hooks + function Hooks() { + const [count, setCount] = React.useState(0); + const incrementCount = React.useCallback( + () => setCount(count + 1), + [count] + ); + return ( +
+ count: {count}{' '} + +
+ ); + } + apps.push( + + + + ); + } + case 6: + // memo + function LabelComponent({label}) { + return ; + } + const AnonymousMemoized = React.memo(({label}) => ( + + )); + const Memoized = React.memo(LabelComponent); + const CustomMemoized = React.memo(LabelComponent); + CustomMemoized.displayName = 'MemoizedLabelFunction'; + apps.push( + + + + + + ); + + // Suspense + const loadResource = ([text, ms]) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(text); + }, ms); + }); + }; + const getResourceKey = ([text, ms]) => text; + const Resource = ReactCache.unstable_createResource( + loadResource, + getResourceKey + ); + class Suspending extends React.Component { + state = {useSuspense: false}; + useSuspense = () => this.setState({useSuspense: true}); + render() { + if (this.state.useSuspense) { + const text = Resource.read(['loaded', 2000]); + return text; + } else { + return ; + } + } + } + apps.push( + + loading...
}> + + + + ); + + // lazy + const LazyWithDefaultProps = React.lazy( + () => + new Promise(resolve => { + function FooWithDefaultProps(props) { + return ( +

+ {props.greeting}, {props.name} +

+ ); + } + FooWithDefaultProps.defaultProps = { + name: 'World', + greeting: 'Bonjour', + }; + resolve({ + default: FooWithDefaultProps, + }); + }) + ); + apps.push( + + loading...
}> + + + + ); + case 5: + case 4: + // unstable_Profiler + class ProfilerChild extends React.Component { + state = {count: 0}; + incrementCount = () => + this.setState(prevState => ({count: prevState.count + 1})); + render() { + return ( +
+ count: {this.state.count}{' '} + +
+ ); + } + } + const onRender = (...args) => {}; + const Profiler = React.unstable_Profiler || React.Profiler; + apps.push( + + +
+ +
+
+
+ ); + case 3: + // createContext() + const LocaleContext = React.createContext(); + LocaleContext.displayName = 'LocaleContext'; + const ThemeContext = React.createContext(); + apps.push( + + + + {theme =>
theme: {theme}
} +
+
+ + + {locale =>
locale: {locale}
} +
+
+
+ ); + + // forwardRef() + const AnonymousFunction = React.forwardRef((props, ref) => ( +
{props.children}
+ )); + const NamedFunction = React.forwardRef(function named(props, ref) { + return
{props.children}
; + }); + const CustomName = React.forwardRef((props, ref) => ( +
{props.children}
+ )); + CustomName.displayName = 'CustomNameForwardRef'; + apps.push( + + AnonymousFunction + NamedFunction + CustomName + + ); + + // StrictMode + class StrictModeChild extends React.Component { + render() { + return 'StrictModeChild'; + } + } + apps.push( + + + + + + ); + + // unstable_AsyncMode (later renamed to unstable_ConcurrentMode, then ConcurrentMode) + const ConcurrentMode = + React.ConcurrentMode || + React.unstable_ConcurrentMode || + React.unstable_AsyncMode; + apps.push( + + +
+ unstable_AsyncMode was added in 16.3, renamed to + unstable_ConcurrentMode in 16.5, and then renamed to + ConcurrentMode in 16.7 +
+
+
+ ); + case 2: + // Fragment + apps.push( + + +
one
+
two
+
+
+ ); + case 1: + case 0: + default: + break; + } + break; + case 15: + break; + case 14: + break; + default: + break; +} + +function Even() { + return (even); +} + +// Simple stateful app shared by all React versions +class SimpleApp extends React.Component { + state = {count: 0}; + incrementCount = () => { + const updaterFn = prevState => ({count: prevState.count + 1}); + trace('Updating count', performance.now(), () => this.setState(updaterFn)); + }; + render() { + const {count} = this.state; + return ( +
+ {count % 2 === 0 ? ( + + count: {count} + + ) : ( + count: {count} + )}{' '} + +
+ ); + } +} +apps.push( + + + +); + +// This component, with the version prop, helps organize DevTools at a glance. +function TopLevelWrapperForDevTools({version}) { + let header =

React {version}

; + if (version.includes('canary')) { + const commitSha = version.match(/.+canary-(.+)/)[1]; + header = ( +

+ React canary{' '} + + {commitSha} + +

+ ); + } else if (version.includes('alpha')) { + header =

React next

; + } + + return ( +
+ {header} + {apps} +
+ ); +} +TopLevelWrapperForDevTools.displayName = 'React'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/fixtures/devtools/regression/styles.css b/fixtures/devtools/regression/styles.css new file mode 100644 index 0000000000000..6cbaaa5c0149a --- /dev/null +++ b/fixtures/devtools/regression/styles.css @@ -0,0 +1,37 @@ +body { + font-family: sans-serif; + font-size: 12px; +} + +h1 { + margin: 0; + font-size: 20px; +} + +h2 { + margin: 1rem 0 0; +} + +iframe { + border: 1px solid #ddd; + border-radius: 0.5rem; +} + +code { + white-space: nowrap; +} + +.Feature { + margin: 1rem 0; + border-bottom: 1px solid #eee; + padding-bottom: 1rem; +} +.FeatureHeader { + font-size: 16px; + margin-bottom: 0.5rem; +} +.FeatureCode { + background-color: #eee; + padding: 0.25rem; + border-radius: 0.25rem; +} diff --git a/fixtures/devtools/standalone/index.html b/fixtures/devtools/standalone/index.html new file mode 100644 index 0000000000000..28255cb67ee6c --- /dev/null +++ b/fixtures/devtools/standalone/index.html @@ -0,0 +1,313 @@ + + + + + TODO List + + + + + + + + + + + + + + +
+ + + diff --git a/package.json b/package.json index 28cc06d5dda9b..2f2bda553eaa7 100644 --- a/package.json +++ b/package.json @@ -48,10 +48,10 @@ "error-stack-parser": "^2.0.2", "eslint": "^6.1.0", "eslint-config-fbjs": "^1.1.1", + "eslint-plugin-babel": "^5.3.0", "eslint-plugin-flowtype": "^2.25.0", "eslint-plugin-jest": "^22.15.0", "eslint-plugin-no-for-of-loops": "^1.0.0", - "eslint-plugin-babel": "^5.3.0", "eslint-plugin-react": "^6.7.1", "eslint-plugin-react-internal": "link:./scripts/eslint-rules", "fbjs-scripts": "^0.8.3", @@ -62,8 +62,8 @@ "google-closure-compiler": "20190301.0.0", "gzip-size": "^3.0.0", "jasmine-check": "^1.0.0-rc.0", - "jest": "^23.1.0", - "jest-diff": "^23.0.1", + "jest": "^23", + "jest-diff": "^23", "jest-snapshot-serializer-raw": "^1.1.0", "minimatch": "^3.0.4", "minimist": "^1.2.0", @@ -109,6 +109,8 @@ "test-prod-build": "yarn test-build-prod", "test-build": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build.js", "test-build-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.build.js", + "test-build-devtools": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build-devtools.js", + "debug-test-build-devtools": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.build-devtools.js", "test-dom-fixture": "cd fixtures/dom && yarn && yarn prestart && yarn test", "flow": "node ./scripts/tasks/flow.js", "flow-ci": "node ./scripts/tasks/flow-ci.js", diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index c4a65ac5ba37d..994745b8a75ae 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -253,14 +253,14 @@ const Dispatcher: DispatcherType = { // Inspect -type HooksNode = { +export type HooksNode = { id: number | null, isStateEditable: boolean, name: string, value: mixed, subHooks: Array, }; -type HooksTree = Array; +export type HooksTree = Array; // Don't assume // diff --git a/packages/react-devtools-core/README.md b/packages/react-devtools-core/README.md index 6f45a6f2ea44b..73d05c351f159 100644 --- a/packages/react-devtools-core/README.md +++ b/packages/react-devtools-core/README.md @@ -12,20 +12,17 @@ This is similar requiring the `react-devtools` package, but provides several con ```js const { connectToDevTools } = require("react-devtools-core"); -connectToDevTools({ - // Config options -}); - +connectToDevTools(config); ``` Run `connectToDevTools()` in the same context as React to set up a connection to DevTools. Be sure to run this function *before* importing e.g. `react`, `react-dom`, `react-native`. -The `options` object may contain: +The `config` object may contain: * `host: string` (defaults to "localhost") - Websocket will connect to this host. * `port: number` (defaults to `8097`) - Websocket will connect to this port. * `websocket: Websocket` - Custom websocked to use. Overrides `host` and `port` settings if provided. -* `resolveNativeStyle: (style: number) => ?Object` - Used by the React Native style plug-in. +* `resolveRNStyle: (style: number) => ?Object` - Used by the React Native style plug-in. * `isAppActive: () => boolean` - If provided, DevTools will poll this method and wait until it returns true before connecting to React. ## `react-devtools-core/standalone` @@ -41,4 +38,16 @@ require("react-devtools-core/standalone") .startServer(port); ``` -Reference the `react-devtools` package for a complete integration example. \ No newline at end of file +Reference the `react-devtools` package for a complete integration example. + +## Development + +Watch for changes made to the backend entry point and rebuild: +```sh +yarn start:backend +``` + +Watch for changes made to the standalone UI entry point and rebuild: +```sh +yarn start:standalone +``` \ No newline at end of file diff --git a/packages/react-devtools-core/backend.js b/packages/react-devtools-core/backend.js new file mode 100644 index 0000000000000..2c2a32d45125b --- /dev/null +++ b/packages/react-devtools-core/backend.js @@ -0,0 +1 @@ +module.exports = require('./dist/backend'); diff --git a/packages/react-devtools-core/package.json b/packages/react-devtools-core/package.json index d71b62e8248d4..4970dcf263977 100644 --- a/packages/react-devtools-core/package.json +++ b/packages/react-devtools-core/package.json @@ -1,5 +1,34 @@ { - "private": true, "name": "react-devtools-core", - "version": "0.0.0" + "version": "4.0.6", + "description": "Use react-devtools outside of the browser", + "license": "MIT", + "main": "./dist/backend.js", + "repository": { + "type": "git", + "url": "https://github.com/facebook/react.git", + "directory": "packages/react-devtools-core" + }, + "files": [ + "dist", + "backend.js", + "build-info.json", + "standalone.js" + ], + "scripts": { + "build": "yarn build:backend && yarn build:standalone", + "build:backend": "cross-env NODE_ENV=production webpack --config webpack.backend.js", + "build:standalone": "cross-env NODE_ENV=production webpack --config webpack.standalone.js", + "prepublish": "yarn run build", + "start:backend": "cross-env NODE_ENV=development webpack --config webpack.backend.js --watch", + "start:standalone": "cross-env NODE_ENV=development webpack --config webpack.standalone.js --watch" + }, + "dependencies": { + "es6-symbol": "^3", + "shell-quote": "^1.6.1", + "ws": "^7" + }, + "devDependencies": { + "cross-env": "^3.1.4" + } } diff --git a/packages/react-devtools-core/src/backend.js b/packages/react-devtools-core/src/backend.js new file mode 100644 index 0000000000000..e84a07fd3e16f --- /dev/null +++ b/packages/react-devtools-core/src/backend.js @@ -0,0 +1,284 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import Agent from 'react-devtools-shared/src/backend/agent'; +import Bridge from 'react-devtools-shared/src/bridge'; +import {installHook} from 'react-devtools-shared/src/hook'; +import {initBackend} from 'react-devtools-shared/src/backend'; +import {__DEBUG__} from 'react-devtools-shared/src/constants'; +import setupNativeStyleEditor from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor'; +import {getDefaultComponentFilters} from 'react-devtools-shared/src/utils'; + +import type {BackendBridge} from 'react-devtools-shared/src/bridge'; +import type {ComponentFilter} from 'react-devtools-shared/src/types'; +import type {DevToolsHook} from 'react-devtools-shared/src/backend/types'; +import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor'; + +type ConnectOptions = { + host?: string, + nativeStyleEditorValidAttributes?: $ReadOnlyArray, + port?: number, + resolveRNStyle?: ResolveNativeStyle, + isAppActive?: () => boolean, + websocket?: ?WebSocket, +}; + +installHook(window); + +const hook: DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; + +let savedComponentFilters: Array< + ComponentFilter, +> = getDefaultComponentFilters(); + +function debug(methodName: string, ...args) { + if (__DEBUG__) { + console.log( + `%c[core/backend] %c${methodName}`, + 'color: teal; font-weight: bold;', + 'font-weight: bold;', + ...args, + ); + } +} + +export function connectToDevTools(options: ?ConnectOptions) { + const { + host = 'localhost', + nativeStyleEditorValidAttributes, + port = 8097, + websocket, + resolveRNStyle = null, + isAppActive = () => true, + } = + options || {}; + + let retryTimeoutID: TimeoutID | null = null; + + function scheduleRetry() { + if (retryTimeoutID === null) { + // Two seconds because RN had issues with quick retries. + retryTimeoutID = setTimeout(() => connectToDevTools(options), 2000); + } + } + + if (!isAppActive()) { + // If the app is in background, maybe retry later. + // Don't actually attempt to connect until we're in foreground. + scheduleRetry(); + return; + } + + let bridge: BackendBridge | null = null; + + const messageListeners = []; + const uri = 'ws://' + host + ':' + port; + + // If existing websocket is passed, use it. + // This is necessary to support our custom integrations. + // See D6251744. + const ws = websocket ? websocket : new window.WebSocket(uri); + ws.onclose = handleClose; + ws.onerror = handleFailed; + ws.onmessage = handleMessage; + ws.onopen = function() { + bridge = new Bridge({ + listen(fn) { + messageListeners.push(fn); + return () => { + const index = messageListeners.indexOf(fn); + if (index >= 0) { + messageListeners.splice(index, 1); + } + }; + }, + send(event: string, payload: any, transferable?: Array) { + if (ws.readyState === ws.OPEN) { + if (__DEBUG__) { + debug('wall.send()', event, payload); + } + + ws.send(JSON.stringify({event, payload})); + } else { + if (__DEBUG__) { + debug( + 'wall.send()', + 'Shutting down bridge because of closed WebSocket connection', + ); + } + + if (bridge !== null) { + bridge.emit('shutdown'); + } + + scheduleRetry(); + } + }, + }); + bridge.addListener( + 'inspectElement', + ({id, rendererID}: {id: number, rendererID: number}) => { + const renderer = agent.rendererInterfaces[rendererID]; + if (renderer != null) { + // Send event for RN to highlight. + const nodes: ?Array = renderer.findNativeNodesForFiberID( + id, + ); + if (nodes != null && nodes[0] != null) { + agent.emit('showNativeHighlight', nodes[0]); + } + } + }, + ); + bridge.addListener( + 'updateComponentFilters', + (componentFilters: Array) => { + // Save filter changes in memory, in case DevTools is reloaded. + // In that case, the renderer will already be using the updated values. + // We'll lose these in between backend reloads but that can't be helped. + savedComponentFilters = componentFilters; + }, + ); + + // The renderer interface doesn't read saved component filters directly, + // because they are generally stored in localStorage within the context of the extension. + // Because of this it relies on the extension to pass filters. + // In the case of the standalone DevTools being used with a website, + // saved filters are injected along with the backend script tag so we shouldn't override them here. + // This injection strategy doesn't work for React Native though. + // Ideally the backend would save the filters itself, but RN doesn't provide a sync storage solution. + // So for now we just fall back to using the default filters... + if (window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ == null) { + bridge.send('overrideComponentFilters', savedComponentFilters); + } + + // TODO (npm-packages) Warn if "isBackendStorageAPISupported" + const agent = new Agent(bridge); + agent.addListener('shutdown', () => { + // If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down, + // and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here. + hook.emit('shutdown'); + }); + + initBackend(hook, agent, window); + + // Setup React Native style editor if the environment supports it. + if (resolveRNStyle != null || hook.resolveRNStyle != null) { + setupNativeStyleEditor( + bridge, + agent, + ((resolveRNStyle || hook.resolveRNStyle: any): ResolveNativeStyle), + nativeStyleEditorValidAttributes || + hook.nativeStyleEditorValidAttributes || + null, + ); + } else { + // Otherwise listen to detect if the environment later supports it. + // For example, Flipper does not eagerly inject these values. + // Instead it relies on the React Native Inspector to lazily inject them. + let lazyResolveRNStyle; + let lazyNativeStyleEditorValidAttributes; + + const initAfterTick = () => { + if (bridge !== null) { + setupNativeStyleEditor( + bridge, + agent, + lazyResolveRNStyle, + lazyNativeStyleEditorValidAttributes, + ); + } + }; + + if (!hook.hasOwnProperty('resolveRNStyle')) { + Object.defineProperty( + hook, + 'resolveRNStyle', + ({ + enumerable: false, + get() { + return lazyResolveRNStyle; + }, + set(value) { + lazyResolveRNStyle = value; + initAfterTick(); + }, + }: Object), + ); + } + if (!hook.hasOwnProperty('nativeStyleEditorValidAttributes')) { + Object.defineProperty( + hook, + 'nativeStyleEditorValidAttributes', + ({ + enumerable: false, + get() { + return lazyNativeStyleEditorValidAttributes; + }, + set(value) { + lazyNativeStyleEditorValidAttributes = value; + initAfterTick(); + }, + }: Object), + ); + } + } + }; + + function handleClose() { + if (__DEBUG__) { + debug('WebSocket.onclose'); + } + + if (bridge !== null) { + bridge.emit('shutdown'); + } + + scheduleRetry(); + } + + function handleFailed() { + if (__DEBUG__) { + debug('WebSocket.onerror'); + } + + scheduleRetry(); + } + + function handleMessage(event) { + let data; + try { + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + if (__DEBUG__) { + debug('WebSocket.onmessage', data); + } + } else { + throw Error(); + } + } catch (e) { + console.error( + '[React DevTools] Failed to parse JSON: ' + (event.data: any), + ); + return; + } + messageListeners.forEach(fn => { + try { + fn(data); + } catch (error) { + // jsc doesn't play so well with tracebacks that go into eval'd code, + // so the stack trace here will stop at the `eval()` call. Getting the + // message that caused the error is the best we can do for now. + console.log('[React DevTools] Error calling listener', data); + console.log('error:', error); + throw error; + } + }); + } +} diff --git a/packages/react-devtools-core/src/editor.js b/packages/react-devtools-core/src/editor.js new file mode 100644 index 0000000000000..bbe3fa63d9857 --- /dev/null +++ b/packages/react-devtools-core/src/editor.js @@ -0,0 +1,190 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import {existsSync} from 'fs'; +import {basename, join, isAbsolute} from 'path'; +import {execSync, spawn} from 'child_process'; +import {parse} from 'shell-quote'; + +function isTerminalEditor(editor: string): boolean { + switch (editor) { + case 'vim': + case 'emacs': + case 'nano': + return true; + default: + return false; + } +} + +// Map from full process name to binary that starts the process +// We can't just re-use full process name, because it will spawn a new instance +// of the app every time +const COMMON_EDITORS = { + '/Applications/Atom.app/Contents/MacOS/Atom': 'atom', + '/Applications/Atom Beta.app/Contents/MacOS/Atom Beta': + '/Applications/Atom Beta.app/Contents/MacOS/Atom Beta', + '/Applications/Sublime Text.app/Contents/MacOS/Sublime Text': + '/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl', + '/Applications/Sublime Text 2.app/Contents/MacOS/Sublime Text 2': + '/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl', + '/Applications/Visual Studio Code.app/Contents/MacOS/Electron': 'code', +}; + +function getArgumentsForLineNumber( + editor: string, + filePath: string, + lineNumber: number, +): Array { + switch (basename(editor)) { + case 'vim': + case 'mvim': + return [filePath, '+' + lineNumber]; + case 'atom': + case 'Atom': + case 'Atom Beta': + case 'subl': + case 'sublime': + case 'wstorm': + case 'appcode': + case 'charm': + case 'idea': + return [filePath + ':' + lineNumber]; + case 'joe': + case 'emacs': + case 'emacsclient': + return ['+' + lineNumber, filePath]; + case 'rmate': + case 'mate': + case 'mine': + return ['--line', lineNumber + '', filePath]; + case 'code': + return ['-g', filePath + ':' + lineNumber]; + default: + // For all others, drop the lineNumber until we have + // a mapping above, since providing the lineNumber incorrectly + // can result in errors or confusing behavior. + return [filePath]; + } +} + +function guessEditor(): Array { + // Explicit config always wins + if (process.env.REACT_EDITOR) { + return parse(process.env.REACT_EDITOR); + } + + // Using `ps x` on OSX we can find out which editor is currently running. + // Potentially we could use similar technique for Windows and Linux + if (process.platform === 'darwin') { + try { + const output = execSync('ps x').toString(); + const processNames = Object.keys(COMMON_EDITORS); + for (let i = 0; i < processNames.length; i++) { + const processName = processNames[i]; + if (output.indexOf(processName) !== -1) { + return [COMMON_EDITORS[processName]]; + } + } + } catch (error) { + // Ignore... + } + } + + // Last resort, use old skool env vars + if (process.env.VISUAL) { + return [process.env.VISUAL]; + } else if (process.env.EDITOR) { + return [process.env.EDITOR]; + } + + return []; +} + +let childProcess = null; + +export function getValidFilePath( + maybeRelativePath: string, + absoluteProjectRoots: Array, +): string | null { + // We use relative paths at Facebook with deterministic builds. + // This is why our internal tooling calls React DevTools with absoluteProjectRoots. + // If the filename is absolute then we don't need to care about this. + if (isAbsolute(maybeRelativePath)) { + if (existsSync(maybeRelativePath)) { + return maybeRelativePath; + } + } else { + for (let i = 0; i < absoluteProjectRoots.length; i++) { + const projectRoot = absoluteProjectRoots[i]; + const joinedPath = join(projectRoot, maybeRelativePath); + if (existsSync(joinedPath)) { + return joinedPath; + } + } + } + + return null; +} + +export function doesFilePathExist( + maybeRelativePath: string, + absoluteProjectRoots: Array, +): boolean { + return getValidFilePath(maybeRelativePath, absoluteProjectRoots) !== null; +} + +export function launchEditor( + maybeRelativePath: string, + lineNumber: number, + absoluteProjectRoots: Array, +) { + const filePath = getValidFilePath(maybeRelativePath, absoluteProjectRoots); + if (filePath === null) { + return; + } + + // Sanitize lineNumber to prevent malicious use on win32 + // via: https://github.com/nodejs/node/blob/c3bb4b1aa5e907d489619fb43d233c3336bfc03d/lib/child_process.js#L333 + if (lineNumber && isNaN(lineNumber)) { + return; + } + + let [editor, ...args] = guessEditor(); + if (!editor) { + return; + } + + if (lineNumber) { + args = args.concat(getArgumentsForLineNumber(editor, filePath, lineNumber)); + } else { + args.push(filePath); + } + + if (childProcess && isTerminalEditor(editor)) { + // There's an existing editor process already and it's attached + // to the terminal, so go kill it. Otherwise two separate editor + // instances attach to the stdin/stdout which gets confusing. + childProcess.kill('SIGKILL'); + } + + if (process.platform === 'win32') { + // On Windows, launch the editor in a shell because spawn can only + // launch .exe files. + childProcess = spawn('cmd.exe', ['/C', editor].concat(args), { + stdio: 'inherit', + }); + } else { + childProcess = spawn(editor, args, {stdio: 'inherit'}); + } + childProcess.on('error', function() {}); + childProcess.on('exit', function(errorCode) { + childProcess = null; + }); +} diff --git a/packages/react-devtools-core/src/standalone.js b/packages/react-devtools-core/src/standalone.js new file mode 100644 index 0000000000000..1516fa1c77b51 --- /dev/null +++ b/packages/react-devtools-core/src/standalone.js @@ -0,0 +1,326 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import {createElement} from 'react'; +import { + // $FlowFixMe Flow does not yet know about flushSync() + flushSync, + // $FlowFixMe Flow does not yet know about createRoot() + unstable_createRoot as createRoot, +} from 'react-dom'; +import Bridge from 'react-devtools-shared/src/bridge'; +import Store from 'react-devtools-shared/src/devtools/store'; +import { + getSavedComponentFilters, + getAppendComponentStack, +} from 'react-devtools-shared/src/utils'; +import {Server} from 'ws'; +import {join} from 'path'; +import {readFileSync} from 'fs'; +import {installHook} from 'react-devtools-shared/src/hook'; +import DevTools from 'react-devtools-shared/src/devtools/views/DevTools'; +import {doesFilePathExist, launchEditor} from './editor'; +import {__DEBUG__} from 'react-devtools-shared/src/constants'; + +import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; +import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types'; + +installHook(window); + +export type StatusListener = (message: string) => void; + +let node: HTMLElement = ((null: any): HTMLElement); +let nodeWaitingToConnectHTML: string = ''; +let projectRoots: Array = []; +let statusListener: StatusListener = (message: string) => {}; + +// Unlike browser extension users, people using the standalone have actively installed version 4, +// So we probably don't need to show them a changelog notice. +// We should give embedded users (e.g. Nuclide, Sonar) a way of showing this dialog though. +let showWelcomeToTheNewDevToolsDialog: boolean = false; + +function setContentDOMNode(value: HTMLElement) { + node = value; + + // Save so we can restore the exact waiting message between sessions. + nodeWaitingToConnectHTML = node.innerHTML; + + return DevtoolsUI; +} + +function setProjectRoots(value: Array) { + projectRoots = value; +} + +function setStatusListener(value: StatusListener) { + statusListener = value; + return DevtoolsUI; +} + +function setShowWelcomeToTheNewDevToolsDialog(value: boolean) { + showWelcomeToTheNewDevToolsDialog = value; + return DevtoolsUI; +} + +let bridge: FrontendBridge | null = null; +let store: Store | null = null; +let root = null; + +const log = (...args) => console.log('[React DevTools]', ...args); +log.warn = (...args) => console.warn('[React DevTools]', ...args); +log.error = (...args) => console.error('[React DevTools]', ...args); + +function debug(methodName: string, ...args) { + if (__DEBUG__) { + console.log( + `%c[core/standalone] %c${methodName}`, + 'color: teal; font-weight: bold;', + 'font-weight: bold;', + ...args, + ); + } +} + +function safeUnmount() { + flushSync(() => { + if (root !== null) { + root.unmount(); + } + }); + root = null; +} + +function reload() { + safeUnmount(); + + node.innerHTML = ''; + + setTimeout(() => { + root = createRoot(node); + root.render( + createElement(DevTools, { + bridge: ((bridge: any): FrontendBridge), + canViewElementSourceFunction, + showTabBar: true, + showWelcomeToTheNewDevToolsDialog, + store: ((store: any): Store), + warnIfLegacyBackendDetected: true, + viewElementSourceFunction, + }), + ); + }, 100); +} + +function canViewElementSourceFunction( + inspectedElement: InspectedElement, +): boolean { + if ( + inspectedElement.canViewSource === false || + inspectedElement.source === null + ) { + return false; + } + + const {source} = inspectedElement; + + return doesFilePathExist(source.fileName, projectRoots); +} + +function viewElementSourceFunction( + id: number, + inspectedElement: InspectedElement, +): void { + const {source} = inspectedElement; + if (source !== null) { + launchEditor(source.fileName, source.lineNumber, projectRoots); + } else { + log.error('Cannot inspect element', id); + } +} + +function onDisconnected() { + safeUnmount(); + + node.innerHTML = nodeWaitingToConnectHTML; +} + +function onError({code, message}) { + safeUnmount(); + + if (code === 'EADDRINUSE') { + node.innerHTML = `

Another instance of DevTools is running

`; + } else { + node.innerHTML = `

Unknown error (${message})

`; + } +} + +function initialize(socket: WebSocket) { + const listeners = []; + socket.onmessage = event => { + let data; + try { + if (typeof event.data === 'string') { + data = JSON.parse(event.data); + + if (__DEBUG__) { + debug('WebSocket.onmessage', data); + } + } else { + throw Error(); + } + } catch (e) { + log.error('Failed to parse JSON', event.data); + return; + } + listeners.forEach(fn => { + try { + fn(data); + } catch (error) { + log.error('Error calling listener', data); + throw error; + } + }); + }; + + bridge = new Bridge({ + listen(fn) { + listeners.push(fn); + return () => { + const index = listeners.indexOf(fn); + if (index >= 0) { + listeners.splice(index, 1); + } + }; + }, + send(event: string, payload: any, transferable?: Array) { + if (socket.readyState === socket.OPEN) { + socket.send(JSON.stringify({event, payload})); + } + }, + }); + ((bridge: any): FrontendBridge).addListener('shutdown', () => { + socket.close(); + }); + + store = new Store(bridge, {supportsNativeInspection: false}); + + log('Connected'); + reload(); +} + +let startServerTimeoutID: TimeoutID | null = null; + +function connectToSocket(socket: WebSocket) { + socket.onerror = err => { + onDisconnected(); + log.error('Error with websocket connection', err); + }; + socket.onclose = () => { + onDisconnected(); + log('Connection to RN closed'); + }; + initialize(socket); + + return { + close: function() { + onDisconnected(); + }, + }; +} + +function startServer(port?: number = 8097) { + const httpServer = require('http').createServer(); + const server = new Server({server: httpServer}); + let connected: WebSocket | null = null; + server.on('connection', (socket: WebSocket) => { + if (connected !== null) { + connected.close(); + log.warn( + 'Only one connection allowed at a time.', + 'Closing the previous connection', + ); + } + connected = socket; + socket.onerror = error => { + connected = null; + onDisconnected(); + log.error('Error with websocket connection', error); + }; + socket.onclose = () => { + connected = null; + onDisconnected(); + log('Connection to RN closed'); + }; + initialize(socket); + }); + + server.on('error', event => { + onError(event); + log.error('Failed to start the DevTools server', event); + startServerTimeoutID = setTimeout(() => startServer(port), 1000); + }); + + httpServer.on('request', (request, response) => { + // Serve a file that immediately sets up the connection. + const backendFile = readFileSync(join(__dirname, 'backend.js')); + + // The renderer interface doesn't read saved component filters directly, + // because they are generally stored in localStorage within the context of the extension. + // Because of this it relies on the extension to pass filters, so include them wth the response here. + // This will ensure that saved filters are shared across different web pages. + const savedPreferencesString = ` + window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = ${JSON.stringify( + getSavedComponentFilters(), + )}; + window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = ${JSON.stringify( + getAppendComponentStack(), + )};`; + + response.end( + savedPreferencesString + + '\n;' + + backendFile.toString() + + '\n;' + + 'ReactDevToolsBackend.connectToDevTools();', + ); + }); + + httpServer.on('error', event => { + onError(event); + statusListener('Failed to start the server.'); + startServerTimeoutID = setTimeout(() => startServer(port), 1000); + }); + + httpServer.listen(port, () => { + statusListener('The server is listening on the port ' + port + '.'); + }); + + return { + close: function() { + connected = null; + onDisconnected(); + if (startServerTimeoutID !== null) { + clearTimeout(startServerTimeoutID); + } + server.close(); + httpServer.close(); + }, + }; +} + +const DevtoolsUI = { + connectToSocket, + setContentDOMNode, + setProjectRoots, + setShowWelcomeToTheNewDevToolsDialog, + setStatusListener, + startServer, +}; + +export default DevtoolsUI; diff --git a/packages/react-devtools-core/standalone.js b/packages/react-devtools-core/standalone.js new file mode 100644 index 0000000000000..fe55aa11d5d41 --- /dev/null +++ b/packages/react-devtools-core/standalone.js @@ -0,0 +1 @@ +module.exports = require('./dist/standalone'); diff --git a/packages/react-devtools-core/webpack.backend.js b/packages/react-devtools-core/webpack.backend.js new file mode 100644 index 0000000000000..3a27421d94e84 --- /dev/null +++ b/packages/react-devtools-core/webpack.backend.js @@ -0,0 +1,67 @@ +const {resolve} = require('path'); +const {DefinePlugin} = require('webpack'); +const { + getGitHubURL, + getVersionString, +} = require('react-devtools-extensions/utils'); + +const NODE_ENV = process.env.NODE_ENV; +if (!NODE_ENV) { + console.error('NODE_ENV not set'); + process.exit(1); +} + +const builtModulesDir = resolve(__dirname, '..', '..', 'build', 'node_modules'); + +const __DEV__ = NODE_ENV === 'development'; + +const GITHUB_URL = getGitHubURL(); +const DEVTOOLS_VERSION = getVersionString(); + +module.exports = { + mode: __DEV__ ? 'development' : 'production', + devtool: __DEV__ ? 'cheap-module-eval-source-map' : false, + entry: { + backend: './src/backend.js', + }, + output: { + path: __dirname + '/dist', + filename: '[name].js', + + // This name is important; standalone references it in order to connect. + library: 'ReactDevToolsBackend', + libraryTarget: 'umd', + }, + resolve: { + alias: { + react: resolve(builtModulesDir, 'react'), + 'react-dom': resolve(builtModulesDir, 'react-dom'), + 'react-debug-tools': resolve(builtModulesDir, 'react-debug-tools'), + 'react-is': resolve(builtModulesDir, 'react-is'), + scheduler: resolve(builtModulesDir, 'scheduler'), + }, + }, + plugins: [ + new DefinePlugin({ + __DEV__: true, + 'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`, + 'process.env.GITHUB_URL': `"${GITHUB_URL}"`, + }), + ], + module: { + rules: [ + { + test: /\.js$/, + loader: 'babel-loader', + options: { + configFile: resolve( + __dirname, + '..', + 'react-devtools-shared', + 'babel.config.js', + ), + }, + }, + ], + }, +}; diff --git a/packages/react-devtools-core/webpack.standalone.js b/packages/react-devtools-core/webpack.standalone.js new file mode 100644 index 0000000000000..d14e6514ba934 --- /dev/null +++ b/packages/react-devtools-core/webpack.standalone.js @@ -0,0 +1,93 @@ +const {resolve} = require('path'); +const {DefinePlugin} = require('webpack'); +const { + getGitHubURL, + getVersionString, +} = require('react-devtools-extensions/utils'); + +const NODE_ENV = process.env.NODE_ENV; +if (!NODE_ENV) { + console.error('NODE_ENV not set'); + process.exit(1); +} + +const builtModulesDir = resolve(__dirname, '..', '..', 'build', 'node_modules'); + +const __DEV__ = NODE_ENV === 'development'; + +const GITHUB_URL = getGitHubURL(); +const DEVTOOLS_VERSION = getVersionString(); + +module.exports = { + mode: __DEV__ ? 'development' : 'production', + devtool: __DEV__ ? 'cheap-module-eval-source-map' : false, + target: 'electron-main', + entry: { + standalone: './src/standalone.js', + }, + output: { + path: __dirname + '/dist', + filename: '[name].js', + library: '[name]', + libraryTarget: 'commonjs2', + }, + resolve: { + alias: { + react: resolve(builtModulesDir, 'react'), + 'react-dom': resolve(builtModulesDir, 'react-dom'), + 'react-debug-tools': resolve(builtModulesDir, 'react-debug-tools'), + 'react-is': resolve(builtModulesDir, 'react-is'), + scheduler: resolve(builtModulesDir, 'scheduler'), + }, + }, + node: { + // Don't replace __dirname! + // This would break the standalone DevTools ability to load the backend. + // see https://github.com/facebook/react-devtools/issues/1269 + __dirname: false, + }, + plugins: [ + new DefinePlugin({ + __DEV__: false, + 'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`, + 'process.env.GITHUB_URL': `"${GITHUB_URL}"`, + 'process.env.NODE_ENV': `"${NODE_ENV}"`, + }), + ], + module: { + rules: [ + { + test: /\.js$/, + loader: 'babel-loader', + options: { + configFile: resolve( + __dirname, + '..', + 'react-devtools-shared', + 'babel.config.js', + ), + }, + }, + { + test: /\.css$/, + use: [ + { + loader: 'style-loader', + }, + { + loader: 'css-loader', + options: { + // WARNING It's important that we disable CSS source maps for production builds. + // This causes style-loader to insert styles via a + + +

+ React DevTools pre-release +

+ +

+ Created on %date% from + %commit% +

+ +

+ This is a preview build of the React DevTools extension. +

+ +

Installation instructions

+ %installation% +

+ If you already have the React DevTools extension installed, you will need to temporarily disable or remove it in order to install this prerelease build. +

+ +

Bug reports

+

+ Please report bugs as GitHub issues. + Please include all of the info required to reproduce the bug (e.g. links, code, instructions). +

+ + diff --git a/packages/react-devtools-extensions/deploy.js b/packages/react-devtools-extensions/deploy.js new file mode 100644 index 0000000000000..c9bfdaf6af73f --- /dev/null +++ b/packages/react-devtools-extensions/deploy.js @@ -0,0 +1,58 @@ +#!/usr/bin/env node + +'use strict'; + +const {exec, execSync} = require('child_process'); +const {readFileSync, writeFileSync} = require('fs'); +const {join} = require('path'); + +const main = async buildId => { + const root = join(__dirname, buildId); + const buildPath = join(root, 'build'); + + execSync(`node ${join(root, './build')}`, { + cwd: __dirname, + env: { + ...process.env, + NODE_ENV: 'production', + }, + stdio: 'inherit', + }); + + await exec(`cp ${join(root, 'now.json')} ${join(buildPath, 'now.json')}`, { + cwd: root, + }); + + const file = readFileSync(join(root, 'now.json')); + const json = JSON.parse(file); + const alias = json.alias[0]; + + const commit = execSync('git rev-parse HEAD') + .toString() + .trim() + .substr(0, 7); + + let date = new Date(); + date = `${date.toLocaleDateString()} – ${date.toLocaleTimeString()}`; + + const installationInstructions = + buildId === 'chrome' + ? readFileSync(join(__dirname, 'deploy.chrome.html')) + : readFileSync(join(__dirname, 'deploy.firefox.html')); + + let html = readFileSync(join(__dirname, 'deploy.html')).toString(); + html = html.replace(/%commit%/g, commit); + html = html.replace(/%date%/g, date); + html = html.replace(/%installation%/, installationInstructions); + + writeFileSync(join(buildPath, 'index.html'), html); + + await exec(`now deploy && now alias ${alias}`, { + cwd: buildPath, + stdio: 'inherit', + }); + + console.log(`Deployed to https://${alias}.now.sh`); +}; + +module.exports = main; diff --git a/packages/react-devtools-extensions/firefox/README.md b/packages/react-devtools-extensions/firefox/README.md new file mode 100644 index 0000000000000..c4bbc8d2de6c6 --- /dev/null +++ b/packages/react-devtools-extensions/firefox/README.md @@ -0,0 +1,12 @@ +# The Firefox extension + +The source code for this extension has moved to `shells/webextension`. + +Modify the source code there and then rebuild this extension by running `node build` from this directory or `yarn run build:extension:firefox` from the root directory. + +## Testing in Firefox + + 1. Build the extension: `node build` + 1. Follow the on-screen instructions. + +You can test upcoming releases of Firefox by downloading the Beta or Nightly build from the [Firefox releases](https://www.mozilla.org/en-US/firefox/channel/desktop/) page and then following the on-screen instructions after building. diff --git a/packages/react-devtools-extensions/firefox/build.js b/packages/react-devtools-extensions/firefox/build.js new file mode 100644 index 0000000000000..3ff45547a90f2 --- /dev/null +++ b/packages/react-devtools-extensions/firefox/build.js @@ -0,0 +1,37 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const build = require('../build'); + +const main = async () => { + await build('firefox'); + + console.log(chalk.green('\nThe Firefox extension has been built!')); + console.log(chalk.green('You can test this build by running:')); + console.log(chalk.gray('\n# From the react-devtools root directory:')); + console.log('yarn run test:firefox'); + console.log( + chalk.gray('\n# You can also test against upcoming Firefox releases.') + ); + console.log( + chalk.gray( + '# First download a release from https://www.mozilla.org/en-US/firefox/channel/desktop/' + ) + ); + console.log( + chalk.gray( + '# And then tell web-ext which release to use (eg firefoxdeveloperedition, nightly, beta):' + ) + ); + console.log('WEB_EXT_FIREFOX=nightly yarn run test:firefox'); + console.log(chalk.gray('\n# You can test against older versions too:')); + console.log( + 'WEB_EXT_FIREFOX=/Applications/Firefox52.app/Contents/MacOS/firefox-bin yarn run test:firefox' + ); +}; + +main(); + +module.exports = {main}; diff --git a/packages/react-devtools-extensions/firefox/deploy.js b/packages/react-devtools-extensions/firefox/deploy.js new file mode 100644 index 0000000000000..49d83ead46123 --- /dev/null +++ b/packages/react-devtools-extensions/firefox/deploy.js @@ -0,0 +1,9 @@ +#!/usr/bin/env node + +'use strict'; + +const deploy = require('../deploy'); + +const main = async () => await deploy('firefox'); + +main(); diff --git a/packages/react-devtools-extensions/firefox/manifest.json b/packages/react-devtools-extensions/firefox/manifest.json new file mode 100644 index 0000000000000..77ea161a7a632 --- /dev/null +++ b/packages/react-devtools-extensions/firefox/manifest.json @@ -0,0 +1,56 @@ +{ + "manifest_version": 2, + "name": "React Developer Tools", + "description": "Adds React debugging tools to the Firefox Developer Tools.", + "version": "4.0.6", + + "applications": { + "gecko": { + "id": "@react-devtools", + "strict_min_version": "54.0" + } + }, + + "icons": { + "16": "icons/16-production.png", + "32": "icons/32-production.png", + "48": "icons/48-production.png", + "128": "icons/128-production.png" + }, + + "browser_action": { + "default_icon": { + "16": "icons/16-disabled.png", + "32": "icons/32-disabled.png", + "48": "icons/48-disabled.png", + "128": "icons/128-disabled.png" + }, + + "default_popup": "popups/disabled.html", + "browser_style": true + }, + + "devtools_page": "main.html", + + "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", + "web_accessible_resources": [ + "main.html", + "panel.html", + "build/backend.js", + "build/renderer.js" + ], + + "background": { + "scripts": ["build/background.js"] + }, + + "permissions": ["file:///*", "http://*/*", "https://*/*"], + + "content_scripts": [ + { + "matches": [""], + "js": ["build/injectGlobalHook.js"], + "run_at": "document_start" + } + ] +} diff --git a/packages/react-devtools-extensions/firefox/now.json b/packages/react-devtools-extensions/firefox/now.json new file mode 100644 index 0000000000000..5e61bb442f567 --- /dev/null +++ b/packages/react-devtools-extensions/firefox/now.json @@ -0,0 +1,5 @@ +{ + "name": "react-devtools-experimental-firefox", + "alias": ["react-devtools-experimental-firefox"], + "files": ["index.html", "ReactDevTools.zip"] +} diff --git a/packages/react-devtools-extensions/firefox/test.js b/packages/react-devtools-extensions/firefox/test.js new file mode 100644 index 0000000000000..754c2f05e79b7 --- /dev/null +++ b/packages/react-devtools-extensions/firefox/test.js @@ -0,0 +1,48 @@ +#!/usr/bin/env node + +'use strict'; + +const {exec} = require('child-process-promise'); +const {Finder} = require('firefox-profile'); +const {resolve} = require('path'); + +const EXTENSION_PATH = resolve('./firefox/build/unpacked'); +const START_URL = 'https://facebook.github.io/react/'; + +const main = async () => { + const finder = new Finder(); + + // Use default Firefox profile for testing purposes. + // This prevents users from having to re-login-to sites before testing. + const findPathPromise = new Promise((resolvePromise, rejectPromise) => { + finder.getPath('default', (error, profile) => { + if (error) { + rejectPromise(error); + } else { + resolvePromise(profile); + } + }); + }); + + const options = [ + `--source-dir=${EXTENSION_PATH}`, + `--start-url=${START_URL}`, + '--browser-console', + ]; + + try { + const path = await findPathPromise; + const trimmedPath = path.replace(' ', '\\ '); + options.push(`--firefox-profile=${trimmedPath}`); + } catch (err) { + console.warn('Could not find default profile, using temporary profile.'); + } + + try { + await exec(`web-ext run ${options.join(' ')}`); + } catch (err) { + console.error('`web-ext run` failed', err.stdout, err.stderr); + } +}; + +main(); diff --git a/packages/react-devtools-extensions/flow-typed/jest.js b/packages/react-devtools-extensions/flow-typed/jest.js new file mode 100644 index 0000000000000..f8cd1b90be611 --- /dev/null +++ b/packages/react-devtools-extensions/flow-typed/jest.js @@ -0,0 +1,1190 @@ +'use strict'; + +type JestMockFn, TReturn> = { + (...args: TArguments): TReturn, + /** + * An object for introspecting mock calls + */ + mock: { + /** + * An array that represents all calls that have been made into this mock + * function. Each call is represented by an array of arguments that were + * passed during the call. + */ + calls: Array, + /** + * An array that contains all the object instances that have been + * instantiated from this mock function. + */ + instances: Array, + /** + * An array that contains all the object results that have been + * returned by this mock function call + */ + results: Array<{isThrow: boolean, value: TReturn}>, + }, + /** + * Resets all information stored in the mockFn.mock.calls and + * mockFn.mock.instances arrays. Often this is useful when you want to clean + * up a mock's usage data between two assertions. + */ + mockClear(): void, + /** + * Resets all information stored in the mock. This is useful when you want to + * completely restore a mock back to its initial state. + */ + mockReset(): void, + /** + * Removes the mock and restores the initial implementation. This is useful + * when you want to mock functions in certain test cases and restore the + * original implementation in others. Beware that mockFn.mockRestore only + * works when mock was created with jest.spyOn. Thus you have to take care of + * restoration yourself when manually assigning jest.fn(). + */ + mockRestore(): void, + /** + * Accepts a function that should be used as the implementation of the mock. + * The mock itself will still record all calls that go into and instances + * that come from itself -- the only difference is that the implementation + * will also be executed when the mock is called. + */ + mockImplementation( + fn: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Accepts a function that will be used as an implementation of the mock for + * one call to the mocked function. Can be chained so that multiple function + * calls produce different results. + */ + mockImplementationOnce( + fn: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Accepts a string to use in test result output in place of "jest.fn()" to + * indicate which mock function is being referenced. + */ + mockName(name: string): JestMockFn, + /** + * Just a simple sugar function for returning `this` + */ + mockReturnThis(): void, + /** + * Accepts a value that will be returned whenever the mock function is called. + */ + mockReturnValue(value: TReturn): JestMockFn, + /** + * Sugar for only returning a value once inside your mock + */ + mockReturnValueOnce(value: TReturn): JestMockFn, + /** + * Sugar for jest.fn().mockImplementation(() => Promise.resolve(value)) + */ + mockResolvedValue(value: TReturn): JestMockFn>, + /** + * Sugar for jest.fn().mockImplementationOnce(() => Promise.resolve(value)) + */ + mockResolvedValueOnce( + value: TReturn + ): JestMockFn>, + /** + * Sugar for jest.fn().mockImplementation(() => Promise.reject(value)) + */ + mockRejectedValue(value: TReturn): JestMockFn>, + /** + * Sugar for jest.fn().mockImplementationOnce(() => Promise.reject(value)) + */ + mockRejectedValueOnce(value: TReturn): JestMockFn>, +}; + +type JestAsymmetricEqualityType = { + /** + * A custom Jasmine equality tester + */ + asymmetricMatch(value: mixed): boolean, +}; + +type JestCallsType = { + allArgs(): mixed, + all(): mixed, + any(): boolean, + count(): number, + first(): mixed, + mostRecent(): mixed, + reset(): void, +}; + +type JestClockType = { + install(): void, + mockDate(date: Date): void, + tick(milliseconds?: number): void, + uninstall(): void, +}; + +type JestMatcherResult = { + message?: string | (() => string), + pass: boolean, +}; + +type JestMatcher = ( + actual: any, + expected: any +) => JestMatcherResult | Promise; + +type JestPromiseType = { + /** + * Use rejects to unwrap the reason of a rejected promise so any other + * matcher can be chained. If the promise is fulfilled the assertion fails. + */ + // eslint-disable-next-line no-use-before-define + rejects: JestExpectType, + /** + * Use resolves to unwrap the value of a fulfilled promise so any other + * matcher can be chained. If the promise is rejected the assertion fails. + */ + // eslint-disable-next-line no-use-before-define + resolves: JestExpectType, +}; + +/** + * Jest allows functions and classes to be used as test names in test() and + * describe() + */ +type JestTestName = string | Function; + +/** + * Plugin: jest-styled-components + */ + +type JestStyledComponentsMatcherValue = + | string + | JestAsymmetricEqualityType + | RegExp + | typeof undefined; + +type JestStyledComponentsMatcherOptions = { + media?: string, + modifier?: string, + supports?: string, +}; + +type JestStyledComponentsMatchersType = { + toHaveStyleRule( + property: string, + value: JestStyledComponentsMatcherValue, + options?: JestStyledComponentsMatcherOptions + ): void, +}; + +/** + * Plugin: jest-enzyme + */ +type EnzymeMatchersType = { + // 5.x + toBeEmpty(): void, + toBePresent(): void, + // 6.x + toBeChecked(): void, + toBeDisabled(): void, + toBeEmptyRender(): void, + toContainMatchingElement(selector: string): void, + toContainMatchingElements(n: number, selector: string): void, + toContainExactlyOneMatchingElement(selector: string): void, + toContainReact(element: React$Element): void, + toExist(): void, + toHaveClassName(className: string): void, + toHaveHTML(html: string): void, + toHaveProp: ((propKey: string, propValue?: any) => void) & + ((props: {}) => void), + toHaveRef(refName: string): void, + toHaveState: ((stateKey: string, stateValue?: any) => void) & + ((state: {}) => void), + toHaveStyle: ((styleKey: string, styleValue?: any) => void) & + ((style: {}) => void), + toHaveTagName(tagName: string): void, + toHaveText(text: string): void, + toHaveValue(value: any): void, + toIncludeText(text: string): void, + toMatchElement( + element: React$Element, + options?: {|ignoreProps?: boolean, verbose?: boolean|} + ): void, + toMatchSelector(selector: string): void, + // 7.x + toHaveDisplayName(name: string): void, +}; + +// DOM testing library extensions https://github.com/kentcdodds/dom-testing-library#custom-jest-matchers +type DomTestingLibraryType = { + toBeDisabled(): void, + toBeEmpty(): void, + toBeInTheDocument(): void, + toBeVisible(): void, + toContainElement(element: HTMLElement | null): void, + toContainHTML(htmlText: string): void, + toHaveAttribute(name: string, expectedValue?: string): void, + toHaveClass(...classNames: string[]): void, + toHaveFocus(): void, + toHaveFormValues(expectedValues: {[name: string]: any}): void, + toHaveStyle(css: string): void, + toHaveTextContent( + content: string | RegExp, + options?: {normalizeWhitespace: boolean} + ): void, + toBeInTheDOM(): void, +}; + +// Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers +type JestJQueryMatchersType = { + toExist(): void, + toHaveLength(len: number): void, + toHaveId(id: string): void, + toHaveClass(className: string): void, + toHaveTag(tag: string): void, + toHaveAttr(key: string, val?: any): void, + toHaveProp(key: string, val?: any): void, + toHaveText(text: string | RegExp): void, + toHaveData(key: string, val?: any): void, + toHaveValue(val: any): void, + toHaveCss(css: {[key: string]: any}): void, + toBeChecked(): void, + toBeDisabled(): void, + toBeEmpty(): void, + toBeHidden(): void, + toBeSelected(): void, + toBeVisible(): void, + toBeFocused(): void, + toBeInDom(): void, + toBeMatchedBy(sel: string): void, + toHaveDescendant(sel: string): void, + toHaveDescendantWithText(sel: string, text: string | RegExp): void, +}; + +// Jest Extended Matchers: https://github.com/jest-community/jest-extended +type JestExtendedMatchersType = { + /** + * Note: Currently unimplemented + * Passing assertion + * + * @param {String} message + */ + // pass(message: string): void; + + /** + * Note: Currently unimplemented + * Failing assertion + * + * @param {String} message + */ + // fail(message: string): void; + + /** + * Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty. + */ + toBeEmpty(): void, + + /** + * Use .toBeOneOf when checking if a value is a member of a given Array. + * @param {Array.<*>} members + */ + toBeOneOf(members: any[]): void, + + /** + * Use `.toBeNil` when checking a value is `null` or `undefined`. + */ + toBeNil(): void, + + /** + * Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`. + * @param {Function} predicate + */ + toSatisfy(predicate: (n: any) => boolean): void, + + /** + * Use `.toBeArray` when checking if a value is an `Array`. + */ + toBeArray(): void, + + /** + * Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x. + * @param {Number} x + */ + toBeArrayOfSize(x: number): void, + + /** + * Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set. + * @param {Array.<*>} members + */ + toIncludeAllMembers(members: any[]): void, + + /** + * Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set. + * @param {Array.<*>} members + */ + toIncludeAnyMembers(members: any[]): void, + + /** + * Use `.toSatisfyAll` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean` for all values in an array. + * @param {Function} predicate + */ + toSatisfyAll(predicate: (n: any) => boolean): void, + + /** + * Use `.toBeBoolean` when checking if a value is a `Boolean`. + */ + toBeBoolean(): void, + + /** + * Use `.toBeTrue` when checking a value is equal (===) to `true`. + */ + toBeTrue(): void, + + /** + * Use `.toBeFalse` when checking a value is equal (===) to `false`. + */ + toBeFalse(): void, + + /** + * Use .toBeDate when checking if a value is a Date. + */ + toBeDate(): void, + + /** + * Use `.toBeFunction` when checking if a value is a `Function`. + */ + toBeFunction(): void, + + /** + * Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`. + * + * Note: Required Jest version >22 + * Note: Your mock functions will have to be asynchronous to cause the timestamps inside of Jest to occur in a differentJS event loop, otherwise the mock timestamps will all be the same + * + * @param {Mock} mock + */ + toHaveBeenCalledBefore(mock: JestMockFn): void, + + /** + * Use `.toBeNumber` when checking if a value is a `Number`. + */ + toBeNumber(): void, + + /** + * Use `.toBeNaN` when checking a value is `NaN`. + */ + toBeNaN(): void, + + /** + * Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`. + */ + toBeFinite(): void, + + /** + * Use `.toBePositive` when checking if a value is a positive `Number`. + */ + toBePositive(): void, + + /** + * Use `.toBeNegative` when checking if a value is a negative `Number`. + */ + toBeNegative(): void, + + /** + * Use `.toBeEven` when checking if a value is an even `Number`. + */ + toBeEven(): void, + + /** + * Use `.toBeOdd` when checking if a value is an odd `Number`. + */ + toBeOdd(): void, + + /** + * Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive). + * + * @param {Number} start + * @param {Number} end + */ + toBeWithin(start: number, end: number): void, + + /** + * Use `.toBeObject` when checking if a value is an `Object`. + */ + toBeObject(): void, + + /** + * Use `.toContainKey` when checking if an object contains the provided key. + * + * @param {String} key + */ + toContainKey(key: string): void, + + /** + * Use `.toContainKeys` when checking if an object has all of the provided keys. + * + * @param {Array.} keys + */ + toContainKeys(keys: string[]): void, + + /** + * Use `.toContainAllKeys` when checking if an object only contains all of the provided keys. + * + * @param {Array.} keys + */ + toContainAllKeys(keys: string[]): void, + + /** + * Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys. + * + * @param {Array.} keys + */ + toContainAnyKeys(keys: string[]): void, + + /** + * Use `.toContainValue` when checking if an object contains the provided value. + * + * @param {*} value + */ + toContainValue(value: any): void, + + /** + * Use `.toContainValues` when checking if an object contains all of the provided values. + * + * @param {Array.<*>} values + */ + toContainValues(values: any[]): void, + + /** + * Use `.toContainAllValues` when checking if an object only contains all of the provided values. + * + * @param {Array.<*>} values + */ + toContainAllValues(values: any[]): void, + + /** + * Use `.toContainAnyValues` when checking if an object contains at least one of the provided values. + * + * @param {Array.<*>} values + */ + toContainAnyValues(values: any[]): void, + + /** + * Use `.toContainEntry` when checking if an object contains the provided entry. + * + * @param {Array.} entry + */ + toContainEntry(entry: [string, string]): void, + + /** + * Use `.toContainEntries` when checking if an object contains all of the provided entries. + * + * @param {Array.>} entries + */ + toContainEntries(entries: [string, string][]): void, + + /** + * Use `.toContainAllEntries` when checking if an object only contains all of the provided entries. + * + * @param {Array.>} entries + */ + toContainAllEntries(entries: [string, string][]): void, + + /** + * Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries. + * + * @param {Array.>} entries + */ + toContainAnyEntries(entries: [string, string][]): void, + + /** + * Use `.toBeExtensible` when checking if an object is extensible. + */ + toBeExtensible(): void, + + /** + * Use `.toBeFrozen` when checking if an object is frozen. + */ + toBeFrozen(): void, + + /** + * Use `.toBeSealed` when checking if an object is sealed. + */ + toBeSealed(): void, + + /** + * Use `.toBeString` when checking if a value is a `String`. + */ + toBeString(): void, + + /** + * Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings. + * + * @param {String} string + */ + toEqualCaseInsensitive(string: string): void, + + /** + * Use `.toStartWith` when checking if a `String` starts with a given `String` prefix. + * + * @param {String} prefix + */ + toStartWith(prefix: string): void, + + /** + * Use `.toEndWith` when checking if a `String` ends with a given `String` suffix. + * + * @param {String} suffix + */ + toEndWith(suffix: string): void, + + /** + * Use `.toInclude` when checking if a `String` includes the given `String` substring. + * + * @param {String} substring + */ + toInclude(substring: string): void, + + /** + * Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times. + * + * @param {String} substring + * @param {Number} times + */ + toIncludeRepeated(substring: string, times: number): void, + + /** + * Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings. + * + * @param {Array.} substring + */ + toIncludeMultiple(substring: string[]): void, +}; + +interface JestExpectType { + not: JestExpectType & + EnzymeMatchersType & + DomTestingLibraryType & + JestJQueryMatchersType & + JestStyledComponentsMatchersType & + JestExtendedMatchersType; + /** + * If you have a mock function, you can use .lastCalledWith to test what + * arguments it was last called with. + */ + lastCalledWith(...args: Array): void; + /** + * toBe just checks that a value is what you expect. It uses === to check + * strict equality. + */ + toBe(value: any): void; + /** + * Use .toBeCalledWith to ensure that a mock function was called with + * specific arguments. + */ + toBeCalledWith(...args: Array): void; + /** + * Using exact equality with floating point numbers is a bad idea. Rounding + * means that intuitive things fail. + */ + toBeCloseTo(num: number, delta: any): void; + /** + * Use .toBeDefined to check that a variable is not undefined. + */ + toBeDefined(): void; + /** + * Use .toBeFalsy when you don't care what a value is, you just want to + * ensure a value is false in a boolean context. + */ + toBeFalsy(): void; + /** + * To compare floating point numbers, you can use toBeGreaterThan. + */ + toBeGreaterThan(number: number): void; + /** + * To compare floating point numbers, you can use toBeGreaterThanOrEqual. + */ + toBeGreaterThanOrEqual(number: number): void; + /** + * To compare floating point numbers, you can use toBeLessThan. + */ + toBeLessThan(number: number): void; + /** + * To compare floating point numbers, you can use toBeLessThanOrEqual. + */ + toBeLessThanOrEqual(number: number): void; + /** + * Use .toBeInstanceOf(Class) to check that an object is an instance of a + * class. + */ + toBeInstanceOf(cls: Class<*>): void; + /** + * .toBeNull() is the same as .toBe(null) but the error messages are a bit + * nicer. + */ + toBeNull(): void; + /** + * Use .toBeTruthy when you don't care what a value is, you just want to + * ensure a value is true in a boolean context. + */ + toBeTruthy(): void; + /** + * Use .toBeUndefined to check that a variable is undefined. + */ + toBeUndefined(): void; + /** + * Use .toContain when you want to check that an item is in a list. For + * testing the items in the list, this uses ===, a strict equality check. + */ + toContain(item: any): void; + /** + * Use .toContainEqual when you want to check that an item is in a list. For + * testing the items in the list, this matcher recursively checks the + * equality of all fields, rather than checking for object identity. + */ + toContainEqual(item: any): void; + /** + * Use .toEqual when you want to check that two objects have the same value. + * This matcher recursively checks the equality of all fields, rather than + * checking for object identity. + */ + toEqual(value: any): void; + /** + * Use .toHaveBeenCalled to ensure that a mock function got called. + */ + toHaveBeenCalled(): void; + toBeCalled(): void; + /** + * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact + * number of times. + */ + toHaveBeenCalledTimes(number: number): void; + toBeCalledTimes(number: number): void; + /** + * + */ + toHaveBeenNthCalledWith(nthCall: number, ...args: Array): void; + nthCalledWith(nthCall: number, ...args: Array): void; + /** + * + */ + toHaveReturned(): void; + toReturn(): void; + /** + * + */ + toHaveReturnedTimes(number: number): void; + toReturnTimes(number: number): void; + /** + * + */ + toHaveReturnedWith(value: any): void; + toReturnWith(value: any): void; + /** + * + */ + toHaveLastReturnedWith(value: any): void; + lastReturnedWith(value: any): void; + /** + * + */ + toHaveNthReturnedWith(nthCall: number, value: any): void; + nthReturnedWith(nthCall: number, value: any): void; + /** + * Use .toHaveBeenCalledWith to ensure that a mock function was called with + * specific arguments. + */ + toHaveBeenCalledWith(...args: Array): void; + toBeCalledWith(...args: Array): void; + /** + * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called + * with specific arguments. + */ + toHaveBeenLastCalledWith(...args: Array): void; + lastCalledWith(...args: Array): void; + /** + * Check that an object has a .length property and it is set to a certain + * numeric value. + */ + toHaveLength(number: number): void; + /** + * + */ + toHaveProperty(propPath: string, value?: any): void; + /** + * Use .toMatch to check that a string matches a regular expression or string. + */ + toMatch(regexpOrString: RegExp | string): void; + /** + * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. + */ + toMatchObject(object: Object | Array): void; + /** + * Use .toStrictEqual to check that a javascript object matches a subset of the properties of an object. + */ + toStrictEqual(value: any): void; + /** + * This ensures that an Object matches the most recent snapshot. + */ + toMatchSnapshot(propertyMatchers?: any, name?: string): void; + /** + * This ensures that an Object matches the most recent snapshot. + */ + toMatchSnapshot(name: string): void; + + toMatchInlineSnapshot(snapshot?: string): void; + toMatchInlineSnapshot(propertyMatchers?: any, snapshot?: string): void; + /** + * Use .toThrow to test that a function throws when it is called. + * If you want to test that a specific error gets thrown, you can provide an + * argument to toThrow. The argument can be a string for the error message, + * a class for the error, or a regex that should match the error. + * + * Alias: .toThrowError + */ + toThrow(message?: string | Error | Class | RegExp): void; + toThrowError(message?: string | Error | Class | RegExp): void; + /** + * Use .toThrowErrorMatchingSnapshot to test that a function throws a error + * matching the most recent snapshot when it is called. + */ + toThrowErrorMatchingSnapshot(): void; + toThrowErrorMatchingInlineSnapshot(snapshot?: string): void; +} + +type JestObjectType = { + /** + * Disables automatic mocking in the module loader. + * + * After this method is called, all `require()`s will return the real + * versions of each module (rather than a mocked version). + */ + disableAutomock(): JestObjectType, + /** + * An un-hoisted version of disableAutomock + */ + autoMockOff(): JestObjectType, + /** + * Enables automatic mocking in the module loader. + */ + enableAutomock(): JestObjectType, + /** + * An un-hoisted version of enableAutomock + */ + autoMockOn(): JestObjectType, + /** + * Clears the mock.calls and mock.instances properties of all mocks. + * Equivalent to calling .mockClear() on every mocked function. + */ + clearAllMocks(): JestObjectType, + /** + * Resets the state of all mocks. Equivalent to calling .mockReset() on every + * mocked function. + */ + resetAllMocks(): JestObjectType, + /** + * Restores all mocks back to their original value. + */ + restoreAllMocks(): JestObjectType, + /** + * Removes any pending timers from the timer system. + */ + clearAllTimers(): void, + /** + * Returns the number of fake timers still left to run. + */ + getTimerCount(): number, + /** + * The same as `mock` but not moved to the top of the expectation by + * babel-jest. + */ + doMock(moduleName: string, moduleFactory?: any): JestObjectType, + /** + * The same as `unmock` but not moved to the top of the expectation by + * babel-jest. + */ + dontMock(moduleName: string): JestObjectType, + /** + * Returns a new, unused mock function. Optionally takes a mock + * implementation. + */ + fn, TReturn>( + implementation?: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Determines if the given function is a mocked function. + */ + isMockFunction(fn: Function): boolean, + /** + * Given the name of a module, use the automatic mocking system to generate a + * mocked version of the module for you. + */ + genMockFromModule(moduleName: string): any, + /** + * Mocks a module with an auto-mocked version when it is being required. + * + * The second argument can be used to specify an explicit module factory that + * is being run instead of using Jest's automocking feature. + * + * The third argument can be used to create virtual mocks -- mocks of modules + * that don't exist anywhere in the system. + */ + mock( + moduleName: string, + moduleFactory?: any, + options?: Object + ): JestObjectType, + /** + * Returns the actual module instead of a mock, bypassing all checks on + * whether the module should receive a mock implementation or not. + */ + requireActual(moduleName: string): any, + /** + * Returns a mock module instead of the actual module, bypassing all checks + * on whether the module should be required normally or not. + */ + requireMock(moduleName: string): any, + /** + * Resets the module registry - the cache of all required modules. This is + * useful to isolate modules where local state might conflict between tests. + */ + resetModules(): JestObjectType, + + /** + * Creates a sandbox registry for the modules that are loaded inside the + * callback function. This is useful to isolate specific modules for every + * test so that local module state doesn't conflict between tests. + */ + isolateModules(fn: () => void): JestObjectType, + + /** + * Exhausts the micro-task queue (usually interfaced in node via + * process.nextTick). + */ + runAllTicks(): void, + /** + * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), + * setInterval(), and setImmediate()). + */ + runAllTimers(): void, + /** + * Exhausts all tasks queued by setImmediate(). + */ + runAllImmediates(): void, + /** + * Executes only the macro task queue (i.e. all tasks queued by setTimeout() + * or setInterval() and setImmediate()). + */ + advanceTimersByTime(msToRun: number): void, + /** + * Executes only the macro task queue (i.e. all tasks queued by setTimeout() + * or setInterval() and setImmediate()). + * + * Renamed to `advanceTimersByTime`. + */ + runTimersToTime(msToRun: number): void, + /** + * Executes only the macro-tasks that are currently pending (i.e., only the + * tasks that have been queued by setTimeout() or setInterval() up to this + * point) + */ + runOnlyPendingTimers(): void, + /** + * Explicitly supplies the mock object that the module system should return + * for the specified module. Note: It is recommended to use jest.mock() + * instead. + */ + setMock(moduleName: string, moduleExports: any): JestObjectType, + /** + * Indicates that the module system should never return a mocked version of + * the specified module from require() (e.g. that it should always return the + * real module). + */ + unmock(moduleName: string): JestObjectType, + /** + * Instructs Jest to use fake versions of the standard timer functions + * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, + * setImmediate and clearImmediate). + */ + useFakeTimers(): JestObjectType, + /** + * Instructs Jest to use the real versions of the standard timer functions. + */ + useRealTimers(): JestObjectType, + /** + * Creates a mock function similar to jest.fn but also tracks calls to + * object[methodName]. + */ + spyOn( + object: Object, + methodName: string, + accessType?: 'get' | 'set' + ): JestMockFn, + /** + * Set the default timeout interval for tests and before/after hooks in milliseconds. + * Note: The default timeout interval is 5 seconds if this method is not called. + */ + setTimeout(timeout: number): JestObjectType, +}; + +type JestSpyType = { + calls: JestCallsType, +}; + +/** Runs this function after every test inside this context */ +declare function afterEach( + fn: (done: () => void) => ?Promise, + timeout?: number +): void; +/** Runs this function before every test inside this context */ +declare function beforeEach( + fn: (done: () => void) => ?Promise, + timeout?: number +): void; +/** Runs this function after all tests have finished inside this context */ +declare function afterAll( + fn: (done: () => void) => ?Promise, + timeout?: number +): void; +/** Runs this function before any tests have started inside this context */ +declare function beforeAll( + fn: (done: () => void) => ?Promise, + timeout?: number +): void; + +/** A context for grouping tests together */ +declare var describe: { + /** + * Creates a block that groups together several related tests in one "test suite" + */ + (name: JestTestName, fn: () => void): void, + + /** + * Only run this describe block + */ + only(name: JestTestName, fn: () => void): void, + + /** + * Skip running this describe block + */ + skip(name: JestTestName, fn: () => void): void, + + /** + * each runs this test against array of argument arrays per each run + * + * @param {table} table of Test + */ + each( + ...table: Array | mixed> | [Array, string] + ): ( + name: JestTestName, + fn?: (...args: Array) => ?Promise, + timeout?: number + ) => void, +}; + +/** An individual test unit */ +declare var it: { + /** + * An individual test unit + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + ( + name: JestTestName, + fn?: (done: () => void) => ?Promise, + timeout?: number + ): void, + + /** + * Only run this test + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + only( + name: JestTestName, + fn?: (done: () => void) => ?Promise, + timeout?: number + ): { + each( + ...table: Array | mixed> | [Array, string] + ): ( + name: JestTestName, + fn?: (...args: Array) => ?Promise, + timeout?: number + ) => void, + }, + + /** + * Skip running this test + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + skip( + name: JestTestName, + fn?: (done: () => void) => ?Promise, + timeout?: number + ): void, + + /** + * Highlight planned tests in the summary output + * + * @param {String} Name of Test to do + */ + todo(name: string): void, + + /** + * Run the test concurrently + * + * @param {JestTestName} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + concurrent( + name: JestTestName, + fn?: (done: () => void) => ?Promise, + timeout?: number + ): void, + + /** + * each runs this test against array of argument arrays per each run + * + * @param {table} table of Test + */ + each( + ...table: Array | mixed> | [Array, string] + ): ( + name: JestTestName, + fn?: (...args: Array) => ?Promise, + timeout?: number + ) => void, +}; + +declare function fit( + name: JestTestName, + fn: (done: () => void) => ?Promise, + timeout?: number +): void; +/** An individual test unit */ +declare var test: typeof it; +/** A disabled group of tests */ +declare var xdescribe: typeof describe; +/** A focused group of tests */ +declare var fdescribe: typeof describe; +/** A disabled individual test */ +declare var xit: typeof it; +/** A disabled individual test */ +declare var xtest: typeof it; + +type JestPrettyFormatColors = { + comment: {close: string, open: string}, + content: {close: string, open: string}, + prop: {close: string, open: string}, + tag: {close: string, open: string}, + value: {close: string, open: string}, +}; + +type JestPrettyFormatIndent = string => string; +// eslint-disable-next-line no-unused-vars +type JestPrettyFormatRefs = Array; +type JestPrettyFormatPrint = any => string; +// eslint-disable-next-line no-unused-vars +type JestPrettyFormatStringOrNull = string | null; + +type JestPrettyFormatOptions = {| + callToJSON: boolean, + edgeSpacing: string, + escapeRegex: boolean, + highlight: boolean, + indent: number, + maxDepth: number, + min: boolean, + // eslint-disable-next-line no-use-before-define + plugins: JestPrettyFormatPlugins, + printFunctionName: boolean, + spacing: string, + theme: {| + comment: string, + content: string, + prop: string, + tag: string, + value: string, + |}, +|}; + +type JestPrettyFormatPlugin = { + print: ( + val: any, + serialize: JestPrettyFormatPrint, + indent: JestPrettyFormatIndent, + opts: JestPrettyFormatOptions, + colors: JestPrettyFormatColors + ) => string, + test: any => boolean, +}; + +type JestPrettyFormatPlugins = Array; + +/** The expect function is used every time you want to test a value */ +declare var expect: { + /** The object that you want to make assertions against */ + ( + value: any + ): JestExpectType & + JestPromiseType & + EnzymeMatchersType & + DomTestingLibraryType & + JestJQueryMatchersType & + JestStyledComponentsMatchersType & + JestExtendedMatchersType, + + /** Add additional Jasmine matchers to Jest's roster */ + extend(matchers: {[name: string]: JestMatcher}): void, + /** Add a module that formats application-specific data structures. */ + addSnapshotSerializer(pluginModule: JestPrettyFormatPlugin): void, + assertions(expectedAssertions: number): void, + hasAssertions(): void, + any(value: mixed): JestAsymmetricEqualityType, + anything(): any, + arrayContaining(value: Array): Array, + objectContaining(value: Object): Object, + /** Matches any received string that contains the exact expected string. */ + stringContaining(value: string): string, + stringMatching(value: string | RegExp): string, + not: { + arrayContaining: (value: $ReadOnlyArray) => Array, + objectContaining: (value: {}) => Object, + stringContaining: (value: string) => string, + stringMatching: (value: string | RegExp) => string, + }, +}; + +// TODO handle return type +// http://jasmine.github.io/2.4/introduction.html#section-Spies +declare function spyOn(value: mixed, method: string): Object; + +/** Holds all functions related to manipulating test runner */ +declare var jest: JestObjectType; + +/** + * The global Jasmine object, this is generally not exposed as the public API, + * using features inside here could break in later versions of Jest. + */ +declare var jasmine: { + DEFAULT_TIMEOUT_INTERVAL: number, + any(value: mixed): JestAsymmetricEqualityType, + anything(): any, + arrayContaining(value: Array): Array, + clock(): JestClockType, + createSpy(name: string): JestSpyType, + createSpyObj( + baseName: string, + methodNames: Array + ): {[methodName: string]: JestSpyType}, + objectContaining(value: Object): Object, + stringMatching(value: string): string, +}; diff --git a/packages/react-devtools-extensions/flow-typed/npm/react-test-renderer_v16.x.x.js b/packages/react-devtools-extensions/flow-typed/npm/react-test-renderer_v16.x.x.js new file mode 100644 index 0000000000000..cd2ae506d2ef5 --- /dev/null +++ b/packages/react-devtools-extensions/flow-typed/npm/react-test-renderer_v16.x.x.js @@ -0,0 +1,84 @@ +// flow-typed signature: b6bb53397d83d2d821e258cc73818d1b +// flow-typed version: 9c71eca8ef/react-test-renderer_v16.x.x/flow_>=v0.47.x + +// Type definitions for react-test-renderer 16.x.x +// Ported from: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-test-renderer + +'use strict'; + +type ReactComponentInstance = React$Component; + +type ReactTestRendererJSON = { + type: string, + props: {[propName: string]: any}, + children: null | ReactTestRendererJSON[], +}; + +type ReactTestRendererTree = ReactTestRendererJSON & { + nodeType: 'component' | 'host', + instance: ?ReactComponentInstance, + rendered: null | ReactTestRendererTree, +}; + +type ReactTestInstance = { + instance: ?ReactComponentInstance, + type: string, + props: {[propName: string]: any}, + parent: null | ReactTestInstance, + children: Array, + + find(predicate: (node: ReactTestInstance) => boolean): ReactTestInstance, + findByType(type: React$ElementType): ReactTestInstance, + findByProps(props: {[propName: string]: any}): ReactTestInstance, + + findAll( + predicate: (node: ReactTestInstance) => boolean, + options?: {deep: boolean} + ): ReactTestInstance[], + findAllByType( + type: React$ElementType, + options?: {deep: boolean} + ): ReactTestInstance[], + findAllByProps( + props: {[propName: string]: any}, + options?: {deep: boolean} + ): ReactTestInstance[], +}; + +type TestRendererOptions = { + createNodeMock(element: React$Element): any, +}; + +declare module 'react-test-renderer' { + // eslint-disable-next-line no-inner-declarations + declare export type ReactTestRenderer = { + toJSON(): null | ReactTestRendererJSON, + toTree(): null | ReactTestRendererTree, + unmount(nextElement?: React$Element): void, + update(nextElement: React$Element): void, + getInstance(): ?ReactComponentInstance, + root: ReactTestInstance, + }; + + declare type Thenable = { + then(resolve: () => mixed, reject?: () => mixed): mixed, + }; + + declare function create( + nextElement: React$Element, + options?: TestRendererOptions + ): ReactTestRenderer; + + declare function act(callback: () => ?Thenable): Thenable; +} + +declare module 'react-test-renderer/shallow' { + declare export default class ShallowRenderer { + static createRenderer(): ShallowRenderer; + getMountedInstance(): ReactTestInstance; + getRenderOutput>(): E; + getRenderOutput(): React$Element; + render(element: React$Element, context?: any): void; + unmount(): void; + } +} diff --git a/packages/react-devtools-extensions/icons/128-deadcode.png b/packages/react-devtools-extensions/icons/128-deadcode.png new file mode 100644 index 0000000000000000000000000000000000000000..b6ecc88e13ac082da3b82b48179b8a665fef6214 GIT binary patch literal 5201 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 LNkvXXu0mjfgeJQI literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/128-development.png b/packages/react-devtools-extensions/icons/128-development.png new file mode 100644 index 0000000000000000000000000000000000000000..b6ecc88e13ac082da3b82b48179b8a665fef6214 GIT binary patch literal 5201 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 LNkvXXu0mjfgeJQI literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/128-disabled.png b/packages/react-devtools-extensions/icons/128-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..67b1c9a31a6dfe22567ce26e385b0be06022e3c2 GIT binary patch literal 4792 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 Sp@}{K0000W>yK& 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 M07*qoM6N<$f@qJob^rhX literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/128-production.png b/packages/react-devtools-extensions/icons/128-production.png new file mode 100644 index 0000000000000000000000000000000000000000..b9327946441f529cd5510f8ee14b788f0aaea1df GIT binary patch literal 4577 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 LNkvXXu0mjfgeJQI literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/16-deadcode.png b/packages/react-devtools-extensions/icons/16-deadcode.png new file mode 100644 index 0000000000000000000000000000000000000000..33d99798e07b27bd48862e9d5779206bc96f9e4b GIT binary patch literal 638 zcmV-^0)hRBP)0 z5Ee=iwzklA@y)=lrIw46Om=qOn|a^&y?M58v8zh}>hXUTmx_zswGHq$HAC`vt(+rt z5oOnJ@LG}8OU1>(+Z|9M)wuj(gd0&I#gf;DaNhv06`H;9$5q#WMK8*HAt+4rwLM`cMUdq%=7LO-CU}b*ZqMO%`w2<$H1O2(= zHYz%M8Gazvii01k@Fg^yOKPa~D40&Bxk>1Z3@w{S^nMQ#!>^GQRBd7s56!M+EjQtB zj6whM8Ql*bVQz09sl0)AH9gg-kg%(aRvV9NQnZ~o9 zzZk{fm2!Aw+ld1$4NRk1mpwjY)bC77&E&8_!ugbvH$({Hg)g#j&dDN9JNs$R`Huht Y0J(_O53sM9y8r+H07*qoM6N<$g5#(xe*gdg literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/16-development.png b/packages/react-devtools-extensions/icons/16-development.png new file mode 100644 index 0000000000000000000000000000000000000000..33d99798e07b27bd48862e9d5779206bc96f9e4b GIT binary patch literal 638 zcmV-^0)hRBP)0 z5Ee=iwzklA@y)=lrIw46Om=qOn|a^&y?M58v8zh}>hXUTmx_zswGHq$HAC`vt(+rt z5oOnJ@LG}8OU1>(+Z|9M)wuj(gd0&I#gf;DaNhv06`H;9$5q#WMK8*HAt+4rwLM`cMUdq%=7LO-CU}b*ZqMO%`w2<$H1O2(= zHYz%M8Gazvii01k@Fg^yOKPa~D40&Bxk>1Z3@w{S^nMQ#!>^GQRBd7s56!M+EjQtB zj6whM8Ql*bVQz09sl0)AH9gg-kg%(aRvV9NQnZ~o9 zzZk{fm2!Aw+ld1$4NRk1mpwjY)bC77&E&8_!ugbvH$({Hg)g#j&dDN9JNs$R`Huht Y0J(_O53sM9y8r+H07*qoM6N<$g5#(xe*gdg literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/16-disabled.png b/packages/react-devtools-extensions/icons/16-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..2f0317ea3fd6b7933988fb03624d7fa0d1981d36 GIT binary patch literal 494 zcmVQ9n z-goZ3=iI!SBuOXCkEFj@Dp$d->$WD7$#u8eRVij!*4cDAy+f{2fMZX|*@dg!Zuc5R zk)5XL8!L3rfc-qrSM&M&0bj>)3=wls0G9y5nRUHD5jdOXyqYJMtE3b~;iDUd(JG2! z4e#}Omz0m=cwNQtO6YuFh($ literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/16-outdated.png b/packages/react-devtools-extensions/icons/16-outdated.png new file mode 100644 index 0000000000000000000000000000000000000000..aa42bfe0b4e0c791fa1bb7f48e989729028bf481 GIT binary patch literal 558 zcmV+}0@3}6P)KQz112mgW=yWf z7&`QNy@ewN4F-c$iJeRVkbvYwB0*U&Gjn7;z&lx%DS;ClE0<|!-%WkN#ya8VIPON% zG^kdqq*an6(mtEbpj<9PtJQ*7EJoGo?+O&I{qXSkwTysG#1#&Qtwy6^>AG(9`+duf z$z($J+M8sV-?DXiabz(bXMY}iXPkpnDh1hWmMX!?!~MzeHi!ovxT}4_`Pl&oJMPVM zz<^}bj=I&?Ar!9sWIs*sQlLzFZ+#N6%k6fX#ulHhR4S1zGbgc(hVest40K!jSPl{c wHj)hy8y->g%(L=qRXz<33h=Mr`B#7e0LAj*R5Z^J3jhEB07*qoM6N<$f@F>Jr2qf` literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/16-production.png b/packages/react-devtools-extensions/icons/16-production.png new file mode 100644 index 0000000000000000000000000000000000000000..1c253dca5d56d6a309c5854b8a82266dfce01aba GIT binary patch literal 546 zcmV+-0^R+IP)Kjq`cRJc0luV*{T{`ATe)vFcW8~a^U z&1IW8S~VsC9cYk*rG)-jtNe~YB-#QCyfAn9CdEELe=9ukUS~A3Um(&+k`%_QL#q+K zB>6_EcAw=DDz_ObB@^^s%=6`TyOU$!2?em%qZ1rrP9PER2bOm@F|#qpgubK_lsk%W zaS#ixnj8R{(gft+VrU|`(3#tG;g8oMcQaTW)H4}mXt?ljx=+s+G3xS-F((~^UNSnI z4cOH>7UfQo=xG$0N|06!l;X?+F3D&~5O9a6V{f_i5|To~O@pf4C| z(Q=Cp>eVuTAr|qpNX$Kp30 z5Ee=iwzklA@y)=lrIw46Om=qOn|a^&y?M58v8zh}>hXUTmx_zswGHq$HAC`vt(+rt z5oOnJ@LG}8OU1>(+Z|9M)wuj(gd0&I#gf;DaNhv06`H;9$5q#WMK8*HAt+4rwLM`cMUdq%=7LO-CU}b*ZqMO%`w2<$H1O2(= zHYz%M8Gazvii01k@Fg^yOKPa~D40&Bxk>1Z3@w{S^nMQ#!>^GQRBd7s56!M+EjQtB zj6whM8Ql*bVQz09sl0)AH9gg-kg%(aRvV9NQnZ~o9 zzZk{fm2!Aw+ld1$4NRk1mpwjY)bC77&E&8_!ugbvH$({Hg)g#j&dDN9JNs$R`Huht Y0J(_O53sM9y8r+H07*qoM6N<$g5#(xe*gdg literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/32-deadcode.png b/packages/react-devtools-extensions/icons/32-deadcode.png new file mode 100644 index 0000000000000000000000000000000000000000..c4a6bda3e6a6d0df9388f3beff37e254a91df25c GIT binary patch literal 1318 zcmV+>1=;$EP)_r`D3!8(9ZZPOzH)}VTUMvIY zO?R^j>(!KY%-^(KY@6hyO=1$i=S{zOPI^pY9P@?4NxppN{dwN!eZCXhUxyAf5z(Z$ zUZ?-pu8V6xc|pNxGv=i7f`XTluvgpRXltjYH;<5`wVl@IW-0mmG-VebSnl=g*+<@R zFS)zAX=C9Q#U?IOUQbr^Zxb9SR0{sm7vGWM)hI0-x`boc%1=L-oMT_;<5Sci62KY~ z#9rM0Ir(nIBq}0+$`Kq+kN`dh2ai!etfSiK*jZA&KH7fz9PeMgG|tJv8V?@kq_l7! z#UsDemjIvy>lJ2rV> zPPB)zOVNt{?bYB1;WRN?aOUdl=IUi%JHPp{v=T9U!GuGNuHKV+KnKwxfDHyd;Q*3Sm~WEj2^Ve#huV zvJ3G-0`Os_A29V~_s%oY!OqXdO9AhX4Aa`RNrv9N`&Ck0F8*zCe1x6O=^2Wwt#+F`A#dP{)ho9k}ilu0mNM?`MxkW$Lgg@Ehn#QnJYjdi!n9cEZ zDH^AFfs&P}WqP4VrUG86@kT=nB&%Bl60oq(y~Q!s#_p}rdpDQ)``)tZVek)s^1LvS zu*mp6YJTe-=0f`KqRIu29wVF6Ne@MP@GykMb9BKA|44D7E`Gi>0kE>j&Ma#H5MJ3dSWcSOWGjXQ zLYR=6r5MlyDg;Locg4tHO5}vF2CU{x&qP+&slVAvk(G5OBFrlgINcEtcUygj5wyQ3 zyR%$9?qQ#czU^t*oO?%U@7K`;-v?Wtuw2jO>CaTA{?ky^G|DBdU#9@+@sQ6H3@td= z)L@!`3t}?P-d<(Y>N<{bx>=c?D{#PaF#`O9Et+9GU8LvprM5g%0;nEmffY%o9_lPM zAehmh1yDdB(dJND9Y@~ literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/32-development.png b/packages/react-devtools-extensions/icons/32-development.png new file mode 100644 index 0000000000000000000000000000000000000000..c4a6bda3e6a6d0df9388f3beff37e254a91df25c GIT binary patch literal 1318 zcmV+>1=;$EP)_r`D3!8(9ZZPOzH)}VTUMvIY zO?R^j>(!KY%-^(KY@6hyO=1$i=S{zOPI^pY9P@?4NxppN{dwN!eZCXhUxyAf5z(Z$ zUZ?-pu8V6xc|pNxGv=i7f`XTluvgpRXltjYH;<5`wVl@IW-0mmG-VebSnl=g*+<@R zFS)zAX=C9Q#U?IOUQbr^Zxb9SR0{sm7vGWM)hI0-x`boc%1=L-oMT_;<5Sci62KY~ z#9rM0Ir(nIBq}0+$`Kq+kN`dh2ai!etfSiK*jZA&KH7fz9PeMgG|tJv8V?@kq_l7! z#UsDemjIvy>lJ2rV> zPPB)zOVNt{?bYB1;WRN?aOUdl=IUi%JHPp{v=T9U!GuGNuHKV+KnKwxfDHyd;Q*3Sm~WEj2^Ve#huV zvJ3G-0`Os_A29V~_s%oY!OqXdO9AhX4Aa`RNrv9N`&Ck0F8*zCe1x6O=^2Wwt#+F`A#dP{)ho9k}ilu0mNM?`MxkW$Lgg@Ehn#QnJYjdi!n9cEZ zDH^AFfs&P}WqP4VrUG86@kT=nB&%Bl60oq(y~Q!s#_p}rdpDQ)``)tZVek)s^1LvS zu*mp6YJTe-=0f`KqRIu29wVF6Ne@MP@GykMb9BKA|44D7E`Gi>0kE>j&Ma#H5MJ3dSWcSOWGjXQ zLYR=6r5MlyDg;Locg4tHO5}vF2CU{x&qP+&slVAvk(G5OBFrlgINcEtcUygj5wyQ3 zyR%$9?qQ#czU^t*oO?%U@7K`;-v?Wtuw2jO>CaTA{?ky^G|DBdU#9@+@sQ6H3@td= z)L@!`3t}?P-d<(Y>N<{bx>=c?D{#PaF#`O9Et+9GU8LvprM5g%0;nEmffY%o9_lPM zAehmh1yDdB(dJND9Y@~ literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/32-disabled.png b/packages/react-devtools-extensions/icons/32-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..7c750453238885d1cba45da238abc82ae0fcfa06 GIT binary patch literal 1092 zcmV-K1iSl*P)({HVg{3dAJt$@ z#3X9$`B{(LT;F@q28zzC44$pwO_=FdH71k~AWD^z$SK%5RKNa;O|+ zRx)u+O-(&LI5-%O#bPtt+uL3Euf@BvveE$F)6>&=JiEKQOYUNYO>2;{=NA_je=ms= ziNw##Ya-!Z67CTBNa;7MfuDKL&d!1&pT^K$gdzx9TwHvC{}9G=P=a~#`ZbhdP=v;K z@8;&_JMiI|wE^%7L!K9vvN4E)%!df<@>-9z{{Ea=f;- z=I-t74at1x=jY+Fva$v}`56t!&(FVANECXf-EJRDCX>HXsnl^}W8F** zxY#NFhoaWj))~C}e7?6!OH1vD#N_1U?5!R%o1K)xu%3KVrtp`Smv?P!Y_wEVR5&db z%d>)l0;kDjx@yIqo}TZ_TUc1wK;C{qM6fR!jfP+to_ad+xDsVAM@L6HY&KgLD+56| zgvR4>zg!zutMwH;W}X|UqclY#kvbHT+0xLkM61fkJL82;g?Y8l~e6=JCWDc+r-a0}g!Zu%TD~(9GEp=JD=RApR##WstE#G;gmk4M;sNCt^F@RN{O;}Tg_@e0 z=7YhYySlpCm(`63p;v1sP7OlMm;hciKB?gDcONhsjitz=Jm1&X*NzCZwYAOH)zwWP z68%InA9&=`^xnrY<@#8j^Lo99i>EJ6m71>06ef z?tK~c)XoeMKV;<88%FxEXj!(tzMk0K-JK(k|4=vlpo_)w@bGYdXJ_YEnb+g-_{oU( z4u>N^M*jxy$jFErGVXmNdQ4KTzfyBWNu(ft{ef!C#pn4y0R{m73@I68j+emz0000< KMNUMnLSTXgoDP}* literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/32-outdated.png b/packages/react-devtools-extensions/icons/32-outdated.png new file mode 100644 index 0000000000000000000000000000000000000000..6eae901bf058833312c3ccd677300e26f08bc3fb GIT binary patch literal 1095 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 literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/48-deadcode.png b/packages/react-devtools-extensions/icons/48-deadcode.png new file mode 100644 index 0000000000000000000000000000000000000000..f3224021a560c7d543178c3159c05cee9eebac9f GIT binary patch literal 2001 zcmV;?2QK)DP)So#v3TzJq(Wsh2i& zG*EU}yE;NUI=Ua^#KHHdqnNVGCe=}=#Ur0>;3YW+duL9bgXdyAqwo>N50zKe81@MAWi+;L1Q<+bnF0{c*V&D zTR-`lJXO`EYcpM)H2TG524rS|`h8UP&KY*x%qDJqN26CSXD$OJhu_e_kA3qMh40=W zEfC;na#ZVMQ5{hrkP&0nDnl*24ugY}3M?`w_r>*ib&^S#>%HpDAEkS8#p!0sEeSGs zq&qq?NRh{X(M)$Imq1?mMs^TD5oLUi*UjfIvt#1;-xr%{zVA0$ni%H;=V2Y^u0$g= z_e*zXG9b~PX#X~84N(O!M6v9|8IGu@YCD5M29{@M=+W6zmeED|T&K#RwhLU>phYkb49)Gc2mt6fpxWMm!iyGC0vSUYNiY^i22FbkYZz{r)>g74r2_yPJbH|q&ln}C ze>A9+5%duZItFvV&X76u3GRj2fLHVmzDu%T>@n#fD@Qad%>z&Z9ybBtKVCn;pkvY8 z&adV1h@y^DftTi%Kmd4*f$H=ji0{C6iIxHeV6kGbBrOEKE`9AS%5l3H?2`{e>Si6R zc{pWmPfY8mi5XF#1;kM62l@b;U0R_lgHv>Ce2#8UhUxBXglhdBa%(xPbygw~^6%M8 z`8#*16713MF{~&T&Y9^eoG<#y(gEB^JOzLMbN#cV!Gcu6L06za&k!6P|KWxX{_Xxz z>I+9{IJ(5g!2BY0OoZuBiI1Itki)gOho&Yxg0(m@MC0GzNNViq&fEM72axrJKl}Ay zu3{l0JB%jC;l!I_&Da~*1BiV-G^LzaPPz`CVeS3riu7nNDOn;K$YUeoP+GYv`YaR` z14H%+@N#p`MGp@rKV}p&Th+CO)4U_$0-|y6PZ&2gsyAXK7EO7s)%R$|h)BI-&uhP@J-BK}8RbEFp3Cw&Sv!4yj6Qzyl)9!C`0rZ3 zJEa4`qsR5RW0?WjRD+YPXbahq`j^rPHXhU0PgWL^-<6zLe1v3F7M^K!IIq!~L z>^yiLR9Ro>W#BmQzH@4xS{_cAGSuV9BA+MDfPyRBE*_G=0MHn2VH2TeDJ5i57AXhJ zni^CKXP1=tLX?m_?3JaDt`1G>&3RwV0W;u61HEG4bxkkuxvkqu>E**mRx!|;#2moG ziP$FHoU26xfeukJRo+}W`(T^_8SUEkG74xK4>~)C>I=N=5Xy1gavBlk=&A#Kf#yaj z$);U&5SsI<8dTD-rgI>&L=}D)HI@bRbB$Kz&7+p*i>bnf$=0WY8@pAeIy#` znLCgw`o`hZrfS~I+Ex8SVsoYvN)4akKo+{wC@06i4|vjeb_sCJibfG_VWdRb1PfhK zT;EXY=Sa)?r58$d2hwS1Z7tw^^-YXo2fbR>ssVrm;{cUelQa zjvlW=F1WXB+*OE=I<_kU&ZC=bXAyqBGs(fa11bfDNrJAHXHySze8 z6$O006LaJMp5R}k4W1oMAPBsj)AWAx#?M&<6kNNZw4A|&tm-Ta3Dmnf1JoR1Lly1e_2s) j>ar{oz*-0F{|YbwKA#E$9zpcP00000NkvXXu0mjf*nPMD literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/48-development.png b/packages/react-devtools-extensions/icons/48-development.png new file mode 100644 index 0000000000000000000000000000000000000000..f3224021a560c7d543178c3159c05cee9eebac9f GIT binary patch literal 2001 zcmV;?2QK)DP)So#v3TzJq(Wsh2i& zG*EU}yE;NUI=Ua^#KHHdqnNVGCe=}=#Ur0>;3YW+duL9bgXdyAqwo>N50zKe81@MAWi+;L1Q<+bnF0{c*V&D zTR-`lJXO`EYcpM)H2TG524rS|`h8UP&KY*x%qDJqN26CSXD$OJhu_e_kA3qMh40=W zEfC;na#ZVMQ5{hrkP&0nDnl*24ugY}3M?`w_r>*ib&^S#>%HpDAEkS8#p!0sEeSGs zq&qq?NRh{X(M)$Imq1?mMs^TD5oLUi*UjfIvt#1;-xr%{zVA0$ni%H;=V2Y^u0$g= z_e*zXG9b~PX#X~84N(O!M6v9|8IGu@YCD5M29{@M=+W6zmeED|T&K#RwhLU>phYkb49)Gc2mt6fpxWMm!iyGC0vSUYNiY^i22FbkYZz{r)>g74r2_yPJbH|q&ln}C ze>A9+5%duZItFvV&X76u3GRj2fLHVmzDu%T>@n#fD@Qad%>z&Z9ybBtKVCn;pkvY8 z&adV1h@y^DftTi%Kmd4*f$H=ji0{C6iIxHeV6kGbBrOEKE`9AS%5l3H?2`{e>Si6R zc{pWmPfY8mi5XF#1;kM62l@b;U0R_lgHv>Ce2#8UhUxBXglhdBa%(xPbygw~^6%M8 z`8#*16713MF{~&T&Y9^eoG<#y(gEB^JOzLMbN#cV!Gcu6L06za&k!6P|KWxX{_Xxz z>I+9{IJ(5g!2BY0OoZuBiI1Itki)gOho&Yxg0(m@MC0GzNNViq&fEM72axrJKl}Ay zu3{l0JB%jC;l!I_&Da~*1BiV-G^LzaPPz`CVeS3riu7nNDOn;K$YUeoP+GYv`YaR` z14H%+@N#p`MGp@rKV}p&Th+CO)4U_$0-|y6PZ&2gsyAXK7EO7s)%R$|h)BI-&uhP@J-BK}8RbEFp3Cw&Sv!4yj6Qzyl)9!C`0rZ3 zJEa4`qsR5RW0?WjRD+YPXbahq`j^rPHXhU0PgWL^-<6zLe1v3F7M^K!IIq!~L z>^yiLR9Ro>W#BmQzH@4xS{_cAGSuV9BA+MDfPyRBE*_G=0MHn2VH2TeDJ5i57AXhJ zni^CKXP1=tLX?m_?3JaDt`1G>&3RwV0W;u61HEG4bxkkuxvkqu>E**mRx!|;#2moG ziP$FHoU26xfeukJRo+}W`(T^_8SUEkG74xK4>~)C>I=N=5Xy1gavBlk=&A#Kf#yaj z$);U&5SsI<8dTD-rgI>&L=}D)HI@bRbB$Kz&7+p*i>bnf$=0WY8@pAeIy#` znLCgw`o`hZrfS~I+Ex8SVsoYvN)4akKo+{wC@06i4|vjeb_sCJibfG_VWdRb1PfhK zT;EXY=Sa)?r58$d2hwS1Z7tw^^-YXo2fbR>ssVrm;{cUelQa zjvlW=F1WXB+*OE=I<_kU&ZC=bXAyqBGs(fa11bfDNrJAHXHySze8 z6$O006LaJMp5R}k4W1oMAPBsj)AWAx#?M&<6kNNZw4A|&tm-Ta3Dmnf1JoR1Lly1e_2s) j>ar{oz*-0F{|YbwKA#E$9zpcP00000NkvXXu0mjf*nPMD literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/48-disabled.png b/packages/react-devtools-extensions/icons/48-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..372b6e00e88a85c08ccfa020e8db75b31b429a5b GIT binary patch literal 1564 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 U2JL{>NdN!<07*qoM6N<$f|%kP{{R30 literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/48-unminified.png b/packages/react-devtools-extensions/icons/48-unminified.png new file mode 100644 index 0000000000000000000000000000000000000000..f3224021a560c7d543178c3159c05cee9eebac9f GIT binary patch literal 2001 zcmV;?2QK)DP)So#v3TzJq(Wsh2i& zG*EU}yE;NUI=Ua^#KHHdqnNVGCe=}=#Ur0>;3YW+duL9bgXdyAqwo>N50zKe81@MAWi+;L1Q<+bnF0{c*V&D zTR-`lJXO`EYcpM)H2TG524rS|`h8UP&KY*x%qDJqN26CSXD$OJhu_e_kA3qMh40=W zEfC;na#ZVMQ5{hrkP&0nDnl*24ugY}3M?`w_r>*ib&^S#>%HpDAEkS8#p!0sEeSGs zq&qq?NRh{X(M)$Imq1?mMs^TD5oLUi*UjfIvt#1;-xr%{zVA0$ni%H;=V2Y^u0$g= z_e*zXG9b~PX#X~84N(O!M6v9|8IGu@YCD5M29{@M=+W6zmeED|T&K#RwhLU>phYkb49)Gc2mt6fpxWMm!iyGC0vSUYNiY^i22FbkYZz{r)>g74r2_yPJbH|q&ln}C ze>A9+5%duZItFvV&X76u3GRj2fLHVmzDu%T>@n#fD@Qad%>z&Z9ybBtKVCn;pkvY8 z&adV1h@y^DftTi%Kmd4*f$H=ji0{C6iIxHeV6kGbBrOEKE`9AS%5l3H?2`{e>Si6R zc{pWmPfY8mi5XF#1;kM62l@b;U0R_lgHv>Ce2#8UhUxBXglhdBa%(xPbygw~^6%M8 z`8#*16713MF{~&T&Y9^eoG<#y(gEB^JOzLMbN#cV!Gcu6L06za&k!6P|KWxX{_Xxz z>I+9{IJ(5g!2BY0OoZuBiI1Itki)gOho&Yxg0(m@MC0GzNNViq&fEM72axrJKl}Ay zu3{l0JB%jC;l!I_&Da~*1BiV-G^LzaPPz`CVeS3riu7nNDOn;K$YUeoP+GYv`YaR` z14H%+@N#p`MGp@rKV}p&Th+CO)4U_$0-|y6PZ&2gsyAXK7EO7s)%R$|h)BI-&uhP@J-BK}8RbEFp3Cw&Sv!4yj6Qzyl)9!C`0rZ3 zJEa4`qsR5RW0?WjRD+YPXbahq`j^rPHXhU0PgWL^-<6zLe1v3F7M^K!IIq!~L z>^yiLR9Ro>W#BmQzH@4xS{_cAGSuV9BA+MDfPyRBE*_G=0MHn2VH2TeDJ5i57AXhJ zni^CKXP1=tLX?m_?3JaDt`1G>&3RwV0W;u61HEG4bxkkuxvkqu>E**mRx!|;#2moG ziP$FHoU26xfeukJRo+}W`(T^_8SUEkG74xK4>~)C>I=N=5Xy1gavBlk=&A#Kf#yaj z$);U&5SsI<8dTD-rgI>&L=}D)HI@bRbB$Kz&7+p*i>bnf$=0WY8@pAeIy#` znLCgw`o`hZrfS~I+Ex8SVsoYvN)4akKo+{wC@06i4|vjeb_sCJibfG_VWdRb1PfhK zT;EXY=Sa)?r58$d2hwS1Z7tw^^-YXo2fbR>ssVrm;{cUelQa zjvlW=F1WXB+*OE=I<_kU&ZC=bXAyqBGs(fa11bfDNrJAHXHySze8 z6$O006LaJMp5R}k4W1oMAPBsj)AWAx#?M&<6kNNZw4A|&tm-Ta3Dmnf1JoR1Lly1e_2s) j>ar{oz*-0F{|YbwKA#E$9zpcP00000NkvXXu0mjf*nPMD literal 0 HcmV?d00001 diff --git a/packages/react-devtools-extensions/icons/deadcode.svg b/packages/react-devtools-extensions/icons/deadcode.svg new file mode 100644 index 0000000000000..ccd6e669061f7 --- /dev/null +++ b/packages/react-devtools-extensions/icons/deadcode.svg @@ -0,0 +1 @@ +development780780 \ No newline at end of file diff --git a/packages/react-devtools-extensions/icons/development.svg b/packages/react-devtools-extensions/icons/development.svg new file mode 100644 index 0000000000000..ccd6e669061f7 --- /dev/null +++ b/packages/react-devtools-extensions/icons/development.svg @@ -0,0 +1 @@ +development780780 \ No newline at end of file diff --git a/packages/react-devtools-extensions/icons/disabled.svg b/packages/react-devtools-extensions/icons/disabled.svg new file mode 100644 index 0000000000000..73c2bb51cdbbc --- /dev/null +++ b/packages/react-devtools-extensions/icons/disabled.svg @@ -0,0 +1 @@ +disabled \ No newline at end of file diff --git a/packages/react-devtools-extensions/icons/outdated.svg b/packages/react-devtools-extensions/icons/outdated.svg new file mode 100644 index 0000000000000..03b83c1eb559b --- /dev/null +++ b/packages/react-devtools-extensions/icons/outdated.svg @@ -0,0 +1 @@ +outdated \ No newline at end of file diff --git a/packages/react-devtools-extensions/icons/production.svg b/packages/react-devtools-extensions/icons/production.svg new file mode 100644 index 0000000000000..1e974f5131012 --- /dev/null +++ b/packages/react-devtools-extensions/icons/production.svg @@ -0,0 +1 @@ +production \ No newline at end of file diff --git a/packages/react-devtools-extensions/main.html b/packages/react-devtools-extensions/main.html new file mode 100644 index 0000000000000..f1c96d4a7a094 --- /dev/null +++ b/packages/react-devtools-extensions/main.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/react-devtools-extensions/package.json b/packages/react-devtools-extensions/package.json new file mode 100644 index 0000000000000..06a2dcd2df344 --- /dev/null +++ b/packages/react-devtools-extensions/package.json @@ -0,0 +1,43 @@ +{ + + "name": "react-devtools-extensions", + "version": "0.0.0", + "private": true, + "scripts": { + "build": "cross-env NODE_ENV=production yarn run build:chrome && yarn run build:firefox", + "build:dev": "cross-env NODE_ENV=development yarn run build:chrome && yarn run build:firefox", + "build:chrome": "cross-env NODE_ENV=production node ./chrome/build", + "build:chrome:crx": "cross-env NODE_ENV=production node ./chrome/build --crx", + "build:chrome:dev": "cross-env NODE_ENV=development node ./chrome/build", + "build:firefox": "cross-env NODE_ENV=production node ./firefox/build", + "build:firefox:dev": "cross-env NODE_ENV=development node ./firefox/build", + "test:chrome": "node ./chrome/test", + "test:firefox": "node ./firefox/test" + }, + "devDependencies": { + "@babel/core": "^7.1.6", + "@babel/plugin-proposal-class-properties": "^7.1.0", + "@babel/plugin-transform-flow-strip-types": "^7.1.6", + "@babel/plugin-transform-react-jsx-source": "^7.2.0", + "@babel/preset-env": "^7.1.6", + "@babel/preset-flow": "^7.0.0", + "@babel/preset-react": "^7.0.0", + "archiver": "^3.0.0", + "babel-core": "^7.0.0-bridge", + "babel-eslint": "^9.0.0", + "babel-jest": "^24.7.1", + "babel-loader": "^8.0.4", + "chrome-launch": "^1.1.4", + "child-process-promise": "^2.2.1", + "css-loader": "^1.0.1", + "firefox-profile": "^1.0.2", + "node-libs-browser": "0.5.3", + "nullthrows": "^1.0.0", + "raw-loader": "^3.1.0", + "style-loader": "^0.23.1", + "web-ext": "^3.0.0", + "webpack": "^4.26.0", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.3.1" + } +} diff --git a/packages/react-devtools-extensions/panel.html b/packages/react-devtools-extensions/panel.html new file mode 100644 index 0000000000000..60fd1bdf13ff2 --- /dev/null +++ b/packages/react-devtools-extensions/panel.html @@ -0,0 +1,32 @@ + + + + + + + + +
Unable to find React on the page.
+ + + diff --git a/packages/react-devtools-extensions/popups/deadcode.html b/packages/react-devtools-extensions/popups/deadcode.html new file mode 100644 index 0000000000000..cdd4c278e86cc --- /dev/null +++ b/packages/react-devtools-extensions/popups/deadcode.html @@ -0,0 +1,32 @@ + + +

+ This page includes an extra development build of React. 🚧 +

+

+ The React build on this page includes both development and production versions because dead code elimination has not been applied correctly. +
+
+ This makes its size larger, and causes React to run slower. +
+
+ Make sure to set up dead code elimination before deployment. +

+
+

+ Open the developer tools, and "Components" and "Profiler" tabs will appear to the right. +

diff --git a/packages/react-devtools-extensions/popups/development.html b/packages/react-devtools-extensions/popups/development.html new file mode 100644 index 0000000000000..3d092a5eb69f8 --- /dev/null +++ b/packages/react-devtools-extensions/popups/development.html @@ -0,0 +1,28 @@ + + +

+ This page is using the development build of React. 🚧 +

+

+ Note that the development build is not suitable for production. +
+ Make sure to use the production build before deployment. +

+
+

+ Open the developer tools, and "Components" and "Profiler" tabs will appear to the right. +

diff --git a/packages/react-devtools-extensions/popups/disabled.html b/packages/react-devtools-extensions/popups/disabled.html new file mode 100644 index 0000000000000..a89b178d49cf8 --- /dev/null +++ b/packages/react-devtools-extensions/popups/disabled.html @@ -0,0 +1,21 @@ + + +

+ This page doesn’t appear to be using React. +
+ If this seems wrong, follow the troubleshooting instructions. +

diff --git a/packages/react-devtools-extensions/popups/outdated.html b/packages/react-devtools-extensions/popups/outdated.html new file mode 100644 index 0000000000000..8f3f3f9508766 --- /dev/null +++ b/packages/react-devtools-extensions/popups/outdated.html @@ -0,0 +1,29 @@ + + +

+ This page is using an outdated version of React. ⌛ +

+

+ We recommend updating React to ensure that you receive important bugfixes and performance improvements. +
+
+ You can find the upgrade instructions on the React blog. +

+
+

+ Open the developer tools, and "Components" and "Profiler" tabs will appear to the right. +

diff --git a/packages/react-devtools-extensions/popups/production.html b/packages/react-devtools-extensions/popups/production.html new file mode 100644 index 0000000000000..7b92841c6c75c --- /dev/null +++ b/packages/react-devtools-extensions/popups/production.html @@ -0,0 +1,21 @@ + + +

+ This page is using the production build of React. ✅ +
+ Open the developer tools, and "Components" and "Profiler" tabs will appear to the right. +

diff --git a/packages/react-devtools-extensions/popups/shared.js b/packages/react-devtools-extensions/popups/shared.js new file mode 100644 index 0000000000000..ddb0456e5bbf3 --- /dev/null +++ b/packages/react-devtools-extensions/popups/shared.js @@ -0,0 +1,24 @@ +/* globals chrome */ + +'use strict'; + +document.addEventListener('DOMContentLoaded', function() { + // Make links work + const links = document.getElementsByTagName('a'); + for (let i = 0; i < links.length; i++) { + (function() { + const ln = links[i]; + const location = ln.href; + ln.onclick = function() { + chrome.tabs.create({active: true, url: location}); + }; + })(); + } + + // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=428044 + document.body.style.opacity = 0; + document.body.style.transition = 'opacity ease-out .4s'; + requestAnimationFrame(function() { + document.body.style.opacity = 1; + }); +}); diff --git a/packages/react-devtools-extensions/popups/unminified.html b/packages/react-devtools-extensions/popups/unminified.html new file mode 100644 index 0000000000000..c53885f5098c3 --- /dev/null +++ b/packages/react-devtools-extensions/popups/unminified.html @@ -0,0 +1,31 @@ + + +

+ This page is using an unminified build of React. 🚧 +

+

+ The React build on this page appears to be unminified. +
+ This makes its size larger, and causes React to run slower. +
+
+ Make sure to set up minification before deployment. +

+
+

+ Open the developer tools, and "Components" and "Profiler" tabs will appear to the right. +

diff --git a/packages/react-devtools-extensions/src/backend.js b/packages/react-devtools-extensions/src/backend.js new file mode 100644 index 0000000000000..072e8de09f0ef --- /dev/null +++ b/packages/react-devtools-extensions/src/backend.js @@ -0,0 +1,79 @@ +// Do not use imports or top-level requires here! +// Running module factories is intentionally delayed until we know the hook exists. +// This is to avoid issues like: https://github.com/facebook/react-devtools/issues/1039 + +/** @flow */ + +'use strict'; + +function welcome(event) { + if ( + event.source !== window || + event.data.source !== 'react-devtools-content-script' + ) { + return; + } + + window.removeEventListener('message', welcome); + + setup(window.__REACT_DEVTOOLS_GLOBAL_HOOK__); +} + +window.addEventListener('message', welcome); + +function setup(hook) { + const Agent = require('react-devtools-shared/src/backend/agent').default; + const Bridge = require('react-devtools-shared/src/bridge').default; + const {initBackend} = require('react-devtools-shared/src/backend'); + const setupNativeStyleEditor = require('react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor') + .default; + + const bridge = new Bridge({ + listen(fn) { + const listener = event => { + if ( + event.source !== window || + !event.data || + event.data.source !== 'react-devtools-content-script' || + !event.data.payload + ) { + return; + } + fn(event.data.payload); + }; + window.addEventListener('message', listener); + return () => { + window.removeEventListener('message', listener); + }; + }, + send(event: string, payload: any, transferable?: Array) { + window.postMessage( + { + source: 'react-devtools-bridge', + payload: {event, payload}, + }, + '*', + transferable, + ); + }, + }); + + const agent = new Agent(bridge); + agent.addListener('shutdown', () => { + // If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down, + // and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here. + hook.emit('shutdown'); + }); + + initBackend(hook, agent, window); + + // Setup React Native style editor if a renderer like react-native-web has injected it. + if (hook.resolveRNStyle) { + setupNativeStyleEditor( + bridge, + agent, + hook.resolveRNStyle, + hook.nativeStyleEditorValidAttributes, + ); + } +} diff --git a/packages/react-devtools-extensions/src/background.js b/packages/react-devtools-extensions/src/background.js new file mode 100644 index 0000000000000..389ca1310a910 --- /dev/null +++ b/packages/react-devtools-extensions/src/background.js @@ -0,0 +1,115 @@ +/* global chrome */ + +'use strict'; + +const ports = {}; + +const IS_FIREFOX = navigator.userAgent.indexOf('Firefox') >= 0; + +chrome.runtime.onConnect.addListener(function(port) { + let tab = null; + let name = null; + if (isNumeric(port.name)) { + tab = port.name; + name = 'devtools'; + installContentScript(+port.name); + } else { + tab = port.sender.tab.id; + name = 'content-script'; + } + + if (!ports[tab]) { + ports[tab] = { + devtools: null, + 'content-script': null, + }; + } + ports[tab][name] = port; + + if (ports[tab].devtools && ports[tab]['content-script']) { + doublePipe(ports[tab].devtools, ports[tab]['content-script']); + } +}); + +function isNumeric(str: string): boolean { + return +str + '' === str; +} + +function installContentScript(tabId: number) { + chrome.tabs.executeScript( + tabId, + {file: '/build/contentScript.js'}, + function() {}, + ); +} + +function doublePipe(one, two) { + one.onMessage.addListener(lOne); + function lOne(message) { + two.postMessage(message); + } + two.onMessage.addListener(lTwo); + function lTwo(message) { + one.postMessage(message); + } + function shutdown() { + one.onMessage.removeListener(lOne); + two.onMessage.removeListener(lTwo); + one.disconnect(); + two.disconnect(); + } + one.onDisconnect.addListener(shutdown); + two.onDisconnect.addListener(shutdown); +} + +function setIconAndPopup(reactBuildType, tabId) { + chrome.browserAction.setIcon({ + tabId: tabId, + path: { + '16': 'icons/16-' + reactBuildType + '.png', + '32': 'icons/32-' + reactBuildType + '.png', + '48': 'icons/48-' + reactBuildType + '.png', + '128': 'icons/128-' + reactBuildType + '.png', + }, + }); + chrome.browserAction.setPopup({ + tabId: tabId, + popup: 'popups/' + reactBuildType + '.html', + }); +} + +// Listen to URL changes on the active tab and reset the DevTools icon. +// This prevents non-disabled icons from sticking in Firefox. +// Don't listen to this event in Chrome though. +// It fires more frequently, often after onMessage() has been called. +if (IS_FIREFOX) { + chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { + if (tab.active && changeInfo.status === 'loading') { + setIconAndPopup('disabled', tabId); + } + }); +} + +chrome.runtime.onMessage.addListener((request, sender) => { + if (sender.tab) { + // This is sent from the hook content script. + // It tells us a renderer has attached. + if (request.hasDetectedReact) { + // We use browserAction instead of pageAction because this lets us + // display a custom default popup when React is *not* detected. + // It is specified in the manifest. + let reactBuildType = request.reactBuildType; + if (sender.url.indexOf('facebook.github.io/react') !== -1) { + // Cheat: We use the development version on the website because + // it is better for interactive examples. However we're going + // to get misguided bug reports if the extension highlights it + // as using the dev version. We're just going to special case + // our own documentation and cheat. It is acceptable to use dev + // version of React in React docs, but not in any other case. + reactBuildType = 'production'; + } + + setIconAndPopup(reactBuildType, sender.tab.id); + } + } +}); diff --git a/packages/react-devtools-extensions/src/contentScript.js b/packages/react-devtools-extensions/src/contentScript.js new file mode 100644 index 0000000000000..c914c6e7b3dfc --- /dev/null +++ b/packages/react-devtools-extensions/src/contentScript.js @@ -0,0 +1,79 @@ +/* global chrome */ + +'use strict'; + +let backendDisconnected: boolean = false; +let backendInitialized: boolean = false; + +function sayHelloToBackend() { + window.postMessage( + { + source: 'react-devtools-content-script', + hello: true, + }, + '*', + ); +} + +function handleMessageFromDevtools(message) { + window.postMessage( + { + source: 'react-devtools-content-script', + payload: message, + }, + '*', + ); +} + +function handleMessageFromPage(evt) { + if ( + evt.source === window && + evt.data && + evt.data.source === 'react-devtools-bridge' + ) { + backendInitialized = true; + + port.postMessage(evt.data.payload); + } +} + +function handleDisconnect() { + backendDisconnected = true; + + window.removeEventListener('message', handleMessageFromPage); + + window.postMessage( + { + source: 'react-devtools-content-script', + payload: { + type: 'event', + event: 'shutdown', + }, + }, + '*', + ); +} + +// proxy from main page to devtools (via the background page) +const port = chrome.runtime.connect({ + name: 'content-script', +}); +port.onMessage.addListener(handleMessageFromDevtools); +port.onDisconnect.addListener(handleDisconnect); + +window.addEventListener('message', handleMessageFromPage); + +sayHelloToBackend(); + +// The backend waits to install the global hook until notified by the content script. +// In the event of a page reload, the content script might be loaded before the backend is injected. +// Because of this we need to poll the backend until it has been initialized. +if (!backendInitialized) { + const intervalID = setInterval(() => { + if (backendInitialized || backendDisconnected) { + clearInterval(intervalID); + } else { + sayHelloToBackend(); + } + }, 500); +} diff --git a/packages/react-devtools-extensions/src/inject.js b/packages/react-devtools-extensions/src/inject.js new file mode 100644 index 0000000000000..938e2cf7eb227 --- /dev/null +++ b/packages/react-devtools-extensions/src/inject.js @@ -0,0 +1,24 @@ +/* global chrome */ + +export default function inject(scriptName: string, done: ?Function) { + const source = ` + // the prototype stuff is in case document.createElement has been modified + (function () { + var script = document.constructor.prototype.createElement.call(document, 'script'); + script.src = "${scriptName}"; + script.charset = "utf-8"; + document.documentElement.appendChild(script); + script.parentNode.removeChild(script); + })() + `; + + chrome.devtools.inspectedWindow.eval(source, function(response, error) { + if (error) { + console.log(error); + } + + if (typeof done === 'function') { + done(); + } + }); +} diff --git a/packages/react-devtools-extensions/src/injectGlobalHook.js b/packages/react-devtools-extensions/src/injectGlobalHook.js new file mode 100644 index 0000000000000..b56f9a299b378 --- /dev/null +++ b/packages/react-devtools-extensions/src/injectGlobalHook.js @@ -0,0 +1,89 @@ +/* global chrome */ + +import nullthrows from 'nullthrows'; +import {installHook} from 'react-devtools-shared/src/hook'; +import {SESSION_STORAGE_RELOAD_AND_PROFILE_KEY} from 'react-devtools-shared/src/constants'; +import {sessionStorageGetItem} from 'react-devtools-shared/src/storage'; + +function injectCode(code) { + const script = document.createElement('script'); + script.textContent = code; + + // This script runs before the element is created, + // so we add the script to instead. + nullthrows(document.documentElement).appendChild(script); + nullthrows(script.parentNode).removeChild(script); +} + +let lastDetectionResult; + +// We want to detect when a renderer attaches, and notify the "background page" +// (which is shared between tabs and can highlight the React icon). +// Currently we are in "content script" context, so we can't listen to the hook directly +// (it will be injected directly into the page). +// So instead, the hook will use postMessage() to pass message to us here. +// And when this happens, we'll send a message to the "background page". +window.addEventListener('message', function(evt) { + if ( + evt.source === window && + evt.data && + evt.data.source === 'react-devtools-detector' + ) { + lastDetectionResult = { + hasDetectedReact: true, + reactBuildType: evt.data.reactBuildType, + }; + chrome.runtime.sendMessage(lastDetectionResult); + } +}); + +// NOTE: Firefox WebExtensions content scripts are still alive and not re-injected +// while navigating the history to a document that has not been destroyed yet, +// replay the last detection result if the content script is active and the +// document has been hidden and shown again. +window.addEventListener('pageshow', function(evt) { + if (!lastDetectionResult || evt.target !== window.document) { + return; + } + chrome.runtime.sendMessage(lastDetectionResult); +}); + +const detectReact = ` +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function(evt) { + window.postMessage({ + source: 'react-devtools-detector', + reactBuildType: evt.reactBuildType, + }, '*'); +}); +`; +const saveNativeValues = ` +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeObjectCreate = Object.create; +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeMap = Map; +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeWeakMap = WeakMap; +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set; +`; + +// If we have just reloaded to profile, we need to inject the renderer interface before the app loads. +if (sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') { + const rendererURL = chrome.runtime.getURL('build/renderer.js'); + let rendererCode; + + // We need to inject in time to catch the initial mount. + // This means we need to synchronously read the renderer code itself, + // and synchronously inject it into the page. + // There are very few ways to actually do this. + // This seems to be the best approach. + const request = new XMLHttpRequest(); + request.addEventListener('load', function() { + rendererCode = this.responseText; + }); + request.open('GET', rendererURL, false); + request.send(); + injectCode(rendererCode); +} + +// Inject a `__REACT_DEVTOOLS_GLOBAL_HOOK__` global so that React can detect that the +// devtools are installed (and skip its suggestion to install the devtools). +injectCode( + ';(' + installHook.toString() + '(window))' + saveNativeValues + detectReact, +); diff --git a/packages/react-devtools-extensions/src/main.js b/packages/react-devtools-extensions/src/main.js new file mode 100644 index 0000000000000..7d786e0321917 --- /dev/null +++ b/packages/react-devtools-extensions/src/main.js @@ -0,0 +1,322 @@ +/* global chrome */ + +import {createElement} from 'react'; +import {unstable_createRoot as createRoot, flushSync} from 'react-dom'; +import Bridge from 'react-devtools-shared/src/bridge'; +import Store from 'react-devtools-shared/src/devtools/store'; +import inject from './inject'; +import { + createViewElementSource, + getBrowserName, + getBrowserTheme, +} from './utils'; +import { + getSavedComponentFilters, + getAppendComponentStack, +} from 'react-devtools-shared/src/utils'; +import { + localStorageGetItem, + localStorageRemoveItem, + localStorageSetItem, +} from 'react-devtools-shared/src/storage'; +import DevTools from 'react-devtools-shared/src/devtools/views/DevTools'; + +const LOCAL_STORAGE_SUPPORTS_PROFILING_KEY = + 'React::DevTools::supportsProfiling'; + +const isChrome = getBrowserName() === 'Chrome'; + +let panelCreated = false; + +// The renderer interface can't read saved component filters directly, +// because they are stored in localStorage within the context of the extension. +// Instead it relies on the extension to pass filters through. +function syncSavedPreferences() { + const componentFilters = getSavedComponentFilters(); + chrome.devtools.inspectedWindow.eval( + `window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = ${JSON.stringify( + componentFilters, + )};`, + ); + + const appendComponentStack = getAppendComponentStack(); + chrome.devtools.inspectedWindow.eval( + `window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = ${JSON.stringify( + appendComponentStack, + )};`, + ); +} + +syncSavedPreferences(); + +function createPanelIfReactLoaded() { + if (panelCreated) { + return; + } + + chrome.devtools.inspectedWindow.eval( + 'window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.size > 0', + function(pageHasReact, error) { + if (!pageHasReact || panelCreated) { + return; + } + + panelCreated = true; + + clearInterval(loadCheckInterval); + + let bridge = null; + let store = null; + + let profilingData = null; + + let componentsPortalContainer = null; + let profilerPortalContainer = null; + + let cloneStyleTags = null; + let mostRecentOverrideTab = null; + let render = null; + let root = null; + + const tabId = chrome.devtools.inspectedWindow.tabId; + + function initBridgeAndStore() { + const port = chrome.runtime.connect({ + name: '' + tabId, + }); + // Looks like `port.onDisconnect` does not trigger on in-tab navigation like new URL or back/forward navigation, + // so it makes no sense to handle it here. + + bridge = new Bridge({ + listen(fn) { + const listener = message => fn(message); + // Store the reference so that we unsubscribe from the same object. + const portOnMessage = port.onMessage; + portOnMessage.addListener(listener); + return () => { + portOnMessage.removeListener(listener); + }; + }, + send(event: string, payload: any, transferable?: Array) { + port.postMessage({event, payload}, transferable); + }, + }); + bridge.addListener('reloadAppForProfiling', () => { + localStorageSetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true'); + chrome.devtools.inspectedWindow.eval('window.location.reload();'); + }); + bridge.addListener('syncSelectionToNativeElementsPanel', () => { + setBrowserSelectionFromReact(); + }); + + // This flag lets us tip the Store off early that we expect to be profiling. + // This avoids flashing a temporary "Profiling not supported" message in the Profiler tab, + // after a user has clicked the "reload and profile" button. + let isProfiling = false; + let supportsProfiling = false; + if ( + localStorageGetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY) === 'true' + ) { + supportsProfiling = true; + isProfiling = true; + localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY); + } + + if (store !== null) { + profilingData = store.profilerStore.profilingData; + } + + store = new Store(bridge, { + isProfiling, + supportsReloadAndProfile: isChrome, + supportsProfiling, + }); + store.profilerStore.profilingData = profilingData; + + // Initialize the backend only once the Store has been initialized. + // Otherwise the Store may miss important initial tree op codes. + inject(chrome.runtime.getURL('build/backend.js')); + + const viewElementSourceFunction = createViewElementSource( + bridge, + store, + ); + + root = createRoot(document.createElement('div')); + + render = (overrideTab = mostRecentOverrideTab) => { + mostRecentOverrideTab = overrideTab; + + root.render( + createElement(DevTools, { + bridge, + browserTheme: getBrowserTheme(), + componentsPortalContainer, + overrideTab, + profilerPortalContainer, + showTabBar: false, + showWelcomeToTheNewDevToolsDialog: true, + store, + viewElementSourceFunction, + }), + ); + }; + + render(); + } + + cloneStyleTags = () => { + const linkTags = []; + // eslint-disable-next-line no-for-of-loops/no-for-of-loops + for (let linkTag of document.getElementsByTagName('link')) { + if (linkTag.rel === 'stylesheet') { + const newLinkTag = document.createElement('link'); + // eslint-disable-next-line no-for-of-loops/no-for-of-loops + for (let attribute of linkTag.attributes) { + newLinkTag.setAttribute(attribute.nodeName, attribute.nodeValue); + } + linkTags.push(newLinkTag); + } + } + return linkTags; + }; + + initBridgeAndStore(); + + function ensureInitialHTMLIsCleared(container) { + if (container._hasInitialHTMLBeenCleared) { + return; + } + container.innerHTML = ''; + container._hasInitialHTMLBeenCleared = true; + } + + function setBrowserSelectionFromReact() { + // This is currently only called on demand when you press "view DOM". + // In the future, if Chrome adds an inspect() that doesn't switch tabs, + // we could make this happen automatically when you select another component. + chrome.devtools.inspectedWindow.eval( + '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' + + '(inspect(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0), true) :' + + 'false', + (didSelectionChange, evalError) => { + if (evalError) { + console.error(evalError); + } + }, + ); + } + + function setReactSelectionFromBrowser() { + // When the user chooses a different node in the browser Elements tab, + // copy it over to the hook object so that we can sync the selection. + chrome.devtools.inspectedWindow.eval( + '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' + + '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0, true) :' + + 'false', + (didSelectionChange, evalError) => { + if (evalError) { + console.error(evalError); + } else if (didSelectionChange) { + // Remember to sync the selection next time we show Components tab. + needsToSyncElementSelection = true; + } + }, + ); + } + + setReactSelectionFromBrowser(); + chrome.devtools.panels.elements.onSelectionChanged.addListener(() => { + setReactSelectionFromBrowser(); + }); + + let currentPanel = null; + let needsToSyncElementSelection = false; + + chrome.devtools.panels.create( + isChrome ? '⚛ Components' : 'Components', + '', + 'panel.html', + extensionPanel => { + extensionPanel.onShown.addListener(panel => { + if (needsToSyncElementSelection) { + needsToSyncElementSelection = false; + bridge.send('syncSelectionFromNativeElementsPanel'); + } + + if (currentPanel === panel) { + return; + } + + currentPanel = panel; + componentsPortalContainer = panel.container; + + if (componentsPortalContainer != null) { + ensureInitialHTMLIsCleared(componentsPortalContainer); + render('components'); + panel.injectStyles(cloneStyleTags); + } + }); + extensionPanel.onHidden.addListener(panel => { + // TODO: Stop highlighting and stuff. + }); + }, + ); + + chrome.devtools.panels.create( + isChrome ? '⚛ Profiler' : 'Profiler', + '', + 'panel.html', + extensionPanel => { + extensionPanel.onShown.addListener(panel => { + if (currentPanel === panel) { + return; + } + + currentPanel = panel; + profilerPortalContainer = panel.container; + + if (profilerPortalContainer != null) { + ensureInitialHTMLIsCleared(profilerPortalContainer); + render('profiler'); + panel.injectStyles(cloneStyleTags); + } + }); + }, + ); + + chrome.devtools.network.onNavigated.removeListener(checkPageForReact); + + // Re-initialize DevTools panel when a new page is loaded. + chrome.devtools.network.onNavigated.addListener(function onNavigated() { + // Re-initialize saved filters on navigation, + // since global values stored on window get reset in this case. + syncSavedPreferences(); + + // It's easiest to recreate the DevTools panel (to clean up potential stale state). + // We can revisit this in the future as a small optimization. + flushSync(() => { + root.unmount(() => { + initBridgeAndStore(); + }); + }); + }); + }, + ); +} + +// Load (or reload) the DevTools extension when the user navigates to a new page. +function checkPageForReact() { + syncSavedPreferences(); + createPanelIfReactLoaded(); +} + +chrome.devtools.network.onNavigated.addListener(checkPageForReact); + +// Check to see if React has loaded once per second in case React is added +// after page load +const loadCheckInterval = setInterval(function() { + createPanelIfReactLoaded(); +}, 1000); + +createPanelIfReactLoaded(); diff --git a/packages/react-devtools-extensions/src/panel.js b/packages/react-devtools-extensions/src/panel.js new file mode 100644 index 0000000000000..de7d8a68fc5ff --- /dev/null +++ b/packages/react-devtools-extensions/src/panel.js @@ -0,0 +1,19 @@ +// Portal target container. +window.container = document.getElementById('container'); + +let hasInjectedStyles = false; + +// DevTools styles are injected into the top-level document head (where the main React app is rendered). +// This method copies those styles to the child window where each panel (e.g. Elements, Profiler) is portaled. +window.injectStyles = getLinkTags => { + if (!hasInjectedStyles) { + hasInjectedStyles = true; + + const linkTags = getLinkTags(); + + // eslint-disable-next-line no-for-of-loops/no-for-of-loops + for (let linkTag of linkTags) { + document.head.appendChild(linkTag); + } + } +}; diff --git a/packages/react-devtools-extensions/src/renderer.js b/packages/react-devtools-extensions/src/renderer.js new file mode 100644 index 0000000000000..933fd5d6b8a6a --- /dev/null +++ b/packages/react-devtools-extensions/src/renderer.js @@ -0,0 +1,26 @@ +/** + * In order to support reload-and-profile functionality, the renderer needs to be injected before any other scripts. + * Since it is a complex file (with imports) we can't just toString() it like we do with the hook itself, + * So this entry point (one of the web_accessible_resources) provcides a way to eagerly inject it. + * The hook will look for the presence of a global __REACT_DEVTOOLS_ATTACH__ and attach an injected renderer early. + * The normal case (not a reload-and-profile) will not make use of this entry point though. + * + * @flow + */ + +import {attach} from 'react-devtools-shared/src/backend/renderer'; + +Object.defineProperty( + window, + '__REACT_DEVTOOLS_ATTACH__', + ({ + enumerable: false, + // This property needs to be configurable to allow third-party integrations + // to attach their own renderer. Note that using third-party integrations + // is not officially supported. Use at your own risk. + configurable: true, + get() { + return attach; + }, + }: Object), +); diff --git a/packages/react-devtools-extensions/src/utils.js b/packages/react-devtools-extensions/src/utils.js new file mode 100644 index 0000000000000..c8d4003115a85 --- /dev/null +++ b/packages/react-devtools-extensions/src/utils.js @@ -0,0 +1,51 @@ +/* global chrome */ + +const IS_CHROME = navigator.userAgent.indexOf('Firefox') < 0; + +export function createViewElementSource(bridge: Bridge, store: Store) { + return function viewElementSource(id) { + const rendererID = store.getRendererIDForElement(id); + if (rendererID != null) { + // Ask the renderer interface to determine the component function, + // and store it as a global variable on the window + bridge.send('viewElementSource', {id, rendererID}); + + setTimeout(() => { + // Ask Chrome to display the location of the component function, + // assuming the renderer found one. + chrome.devtools.inspectedWindow.eval(` + if (window.$type != null) { + inspect(window.$type); + } + `); + }, 100); + } + }; +} + +export type BrowserName = 'Chrome' | 'Firefox'; + +export function getBrowserName(): BrowserName { + return IS_CHROME ? 'Chrome' : 'Firefox'; +} + +export type BrowserTheme = 'dark' | 'light'; + +export function getBrowserTheme(): BrowserTheme { + if (IS_CHROME) { + // chrome.devtools.panels added in Chrome 18. + // chrome.devtools.panels.themeName added in Chrome 54. + return chrome.devtools.panels.themeName === 'dark' ? 'dark' : 'light'; + } else { + // chrome.devtools.panels.themeName added in Firefox 55. + // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/devtools.panels/themeName + if (chrome.devtools && chrome.devtools.panels) { + switch (chrome.devtools.panels.themeName) { + case 'dark': + return 'dark'; + default: + return 'light'; + } + } + } +} diff --git a/packages/react-devtools-extensions/utils.js b/packages/react-devtools-extensions/utils.js new file mode 100644 index 0000000000000..1af974e701783 --- /dev/null +++ b/packages/react-devtools-extensions/utils.js @@ -0,0 +1,39 @@ +const {execSync} = require('child_process'); +const {readFileSync} = require('fs'); +const {resolve} = require('path'); + +function getGitCommit() { + return execSync('git show -s --format=%h') + .toString() + .trim(); +} + +function getGitHubURL() { + // TODO potentially replace this with an fb.me URL (assuming it can forward the query params) + const url = execSync('git remote get-url origin') + .toString() + .trim(); + + if (url.startsWith('https://')) { + return url.replace('.git', ''); + } else { + return url + .replace(':', '/') + .replace('git@', 'https://') + .replace('.git', ''); + } +} + +function getVersionString() { + const packageVersion = JSON.parse( + readFileSync( + resolve(__dirname, '..', 'react-devtools-core', './package.json'), + ), + ).version; + + const commit = getGitCommit(); + + return `${packageVersion}-${commit}`; +} + +module.exports = {getGitCommit, getGitHubURL, getVersionString}; diff --git a/packages/react-devtools-extensions/webpack.backend.js b/packages/react-devtools-extensions/webpack.backend.js new file mode 100644 index 0000000000000..03911a31290b2 --- /dev/null +++ b/packages/react-devtools-extensions/webpack.backend.js @@ -0,0 +1,62 @@ +'use strict'; + +const {resolve} = require('path'); +const {DefinePlugin} = require('webpack'); +const {getGitHubURL, getVersionString} = require('./utils'); + +const NODE_ENV = process.env.NODE_ENV; +if (!NODE_ENV) { + console.error('NODE_ENV not set'); + process.exit(1); +} + +const builtModulesDir = resolve(__dirname, '..', '..', 'build', 'node_modules'); + +const __DEV__ = NODE_ENV === 'development'; + +const GITHUB_URL = getGitHubURL(); +const DEVTOOLS_VERSION = getVersionString(); + +module.exports = { + mode: __DEV__ ? 'development' : 'production', + devtool: __DEV__ ? 'cheap-module-eval-source-map' : false, + entry: { + backend: './src/backend.js', + }, + output: { + path: __dirname + '/build', + filename: '[name].js', + }, + resolve: { + alias: { + react: resolve(builtModulesDir, 'react'), + 'react-debug-tools': resolve(builtModulesDir, 'react-debug-tools'), + 'react-dom': resolve(builtModulesDir, 'react-dom'), + 'react-is': resolve(builtModulesDir, 'react-is'), + scheduler: resolve(builtModulesDir, 'scheduler'), + }, + }, + plugins: [ + new DefinePlugin({ + __DEV__: true, + 'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`, + 'process.env.GITHUB_URL': `"${GITHUB_URL}"`, + }), + ], + module: { + rules: [ + { + test: /\.js$/, + loader: 'babel-loader', + options: { + configFile: resolve( + __dirname, + '..', + 'react-devtools-shared', + 'babel.config.js', + ), + }, + }, + ], + }, +}; diff --git a/packages/react-devtools-extensions/webpack.config.js b/packages/react-devtools-extensions/webpack.config.js new file mode 100644 index 0000000000000..8a362f82fd3db --- /dev/null +++ b/packages/react-devtools-extensions/webpack.config.js @@ -0,0 +1,84 @@ +'use strict'; + +const {resolve} = require('path'); +const {DefinePlugin} = require('webpack'); +const {getGitHubURL, getVersionString} = require('./utils'); + +const NODE_ENV = process.env.NODE_ENV; +if (!NODE_ENV) { + console.error('NODE_ENV not set'); + process.exit(1); +} + +const builtModulesDir = resolve(__dirname, '..', '..', 'build', 'node_modules'); + +const __DEV__ = NODE_ENV === 'development'; + +const GITHUB_URL = getGitHubURL(); +const DEVTOOLS_VERSION = getVersionString(); + +module.exports = { + mode: __DEV__ ? 'development' : 'production', + devtool: __DEV__ ? 'cheap-module-eval-source-map' : false, + entry: { + background: './src/background.js', + contentScript: './src/contentScript.js', + injectGlobalHook: './src/injectGlobalHook.js', + main: './src/main.js', + panel: './src/panel.js', + renderer: './src/renderer.js', + }, + output: { + path: __dirname + '/build', + filename: '[name].js', + }, + resolve: { + alias: { + react: resolve(builtModulesDir, 'react'), + 'react-debug-tools': resolve(builtModulesDir, 'react-debug-tools'), + 'react-dom': resolve(builtModulesDir, 'react-dom'), + 'react-is': resolve(builtModulesDir, 'react-is'), + scheduler: resolve(builtModulesDir, 'scheduler'), + }, + }, + plugins: [ + new DefinePlugin({ + __DEV__: false, + 'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`, + 'process.env.GITHUB_URL': `"${GITHUB_URL}"`, + 'process.env.NODE_ENV': `"${NODE_ENV}"`, + }), + ], + module: { + rules: [ + { + test: /\.js$/, + loader: 'babel-loader', + options: { + configFile: resolve( + __dirname, + '..', + 'react-devtools-shared', + 'babel.config.js', + ), + }, + }, + { + test: /\.css$/, + use: [ + { + loader: 'style-loader', + }, + { + loader: 'css-loader', + options: { + sourceMap: true, + modules: true, + localIdentName: '[local]___[hash:base64:5]', + }, + }, + ], + }, + ], + }, +}; diff --git a/packages/react-devtools-inline/README.md b/packages/react-devtools-inline/README.md index 33cc4afa8b3d8..2847bac8a9394 100644 --- a/packages/react-devtools-inline/README.md +++ b/packages/react-devtools-inline/README.md @@ -21,24 +21,32 @@ The frontend and backend can be initialized in any order, but **the backend must ### `react-devtools-inline/backend` * **`initialize(contentWindow)`** - -Installs the global hook on the window. This hook is how React and DevTools communicate. **This method must be called before React is loaded.** (This means before any `import` or `require` statements!) +Installs the global hook on the window. This hook is how React and DevTools communicate. **This method must be called before React is loaded.**2 * **`activate(contentWindow)`** - Lets the backend know when the frontend is ready. It should not be called until after the frontend has been initialized, else the frontend might miss important tree-initialization events. ```js import { activate, initialize } from 'react-devtools-inline/backend'; +// This should be the iframe the React application is running in. +const iframe = document.getElementById(frameID); +const contentWindow = iframe.contentWindow; + // Call this before importing React (or any other packages that might import React). -initialize(); +initialize(contentWindow); + +// Initialize the frontend... // Call this only once the frontend has been initialized. -activate(); +activate(contentWindow); ``` +2 The backend must be initialized before React is loaded. (This means before any `import` or `require` statements or ` + + \ No newline at end of file diff --git a/packages/react-devtools-shell/now.json b/packages/react-devtools-shell/now.json new file mode 100644 index 0000000000000..7c0805c3b1656 --- /dev/null +++ b/packages/react-devtools-shell/now.json @@ -0,0 +1,5 @@ +{ + "name": "react-devtools-experimental", + "alias": ["react-devtools-experimental"], + "files": ["index.html", "dist"] +} diff --git a/packages/react-devtools-shell/package.json b/packages/react-devtools-shell/package.json new file mode 100644 index 0000000000000..b3f1420065c33 --- /dev/null +++ b/packages/react-devtools-shell/package.json @@ -0,0 +1,34 @@ +{ + "private": true, + "name": "react-devtools-shell", + "version": "0.0.0", + "scripts": { + "build": "cross-env NODE_ENV=development cross-env TARGET=remote webpack --config webpack.config.js", + "deploy": "yarn run build && now deploy && now alias react-devtools-experimental", + "start": "cross-env NODE_ENV=development cross-env TARGET=local webpack-dev-server --open" + }, + "dependencies": { + "immutable": "^4.0.0-rc.12", + "react-native-web": "^0.11.5" + }, + "devDependencies": { + "@babel/core": "^7.1.6", + "@babel/plugin-proposal-class-properties": "^7.1.0", + "@babel/plugin-transform-flow-strip-types": "^7.1.6", + "@babel/plugin-transform-react-jsx-source": "^7.2.0", + "@babel/preset-env": "^7.1.6", + "@babel/preset-flow": "^7.0.0", + "@babel/preset-react": "^7.0.0", + "babel-core": "^7.0.0-bridge", + "babel-eslint": "^9.0.0", + "babel-jest": "^24.7.1", + "babel-loader": "^8.0.4", + "cross-env": "^3.1.4", + "css-loader": "^1.0.1", + "raw-loader": "^3.1.0", + "style-loader": "^0.23.1", + "webpack": "^4.26.0", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.3.1" + } +} diff --git a/packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js b/packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js new file mode 100644 index 0000000000000..bc6b05fa9d774 --- /dev/null +++ b/packages/react-devtools-shell/src/app/DeeplyNestedComponents/index.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import React, {Fragment} from 'react'; + +function wrapWithHoc(Component, index) { + function HOC() { + return ; + } + + // $FlowFixMe + const displayName = Component.displayName || Component.name; + + HOC.displayName = `withHoc${index}(${displayName})`; + return HOC; +} + +function wrapWithNested(Component, times) { + for (let i = 0; i < times; i++) { + Component = wrapWithHoc(Component, i); + } + + return Component; +} + +function Nested() { + return
Deeply nested div
; +} + +const DeeplyNested = wrapWithNested(Nested, 100); + +export default function DeeplyNestedComponents() { + return ( + +

Deeply nested component

+ +
+ ); +} diff --git a/packages/react-devtools-shell/src/app/EditableProps/index.js b/packages/react-devtools-shell/src/app/EditableProps/index.js new file mode 100644 index 0000000000000..220a58c17be32 --- /dev/null +++ b/packages/react-devtools-shell/src/app/EditableProps/index.js @@ -0,0 +1,169 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import React, { + createContext, + Component, + forwardRef, + Fragment, + memo, + useCallback, + useDebugValue, + useEffect, + useReducer, + useState, +} from 'react'; + +const initialData = {foo: 'FOO', bar: 'BAR'}; + +function reducer(state, action) { + switch (action.type) { + case 'swap': + return {foo: state.bar, bar: state.foo}; + default: + throw new Error(); + } +} + +type StatefulFunctionProps = {|name: string|}; + +function StatefulFunction({name}: StatefulFunctionProps) { + const [count, updateCount] = useState(0); + const debouncedCount = useDebounce(count, 1000); + const handleUpdateCountClick = useCallback(() => updateCount(count + 1), [ + count, + ]); + + const [data, dispatch] = useReducer(reducer, initialData); + const handleUpdateReducerClick = useCallback( + () => dispatch({type: 'swap'}), + [], + ); + + return ( +
    +
  • Name: {name}
  • +
  • + +
  • +
  • + Reducer state: foo "{data.foo}", bar "{data.bar}" +
  • +
  • + +
  • +
+ ); +} + +const BoolContext = createContext(true); +BoolContext.displayName = 'BoolContext'; + +type Props = {|name: string, toggle: boolean|}; +type State = {|cities: Array, state: string|}; + +class StatefulClass extends Component { + static contextType = BoolContext; + + state: State = { + cities: ['San Francisco', 'San Jose'], + state: 'California', + }; + + handleChange = ({target}) => + this.setState({ + state: target.value, + }); + + render() { + return ( +
    +
  • Name: {this.props.name}
  • +
  • Toggle: {this.props.toggle ? 'true' : 'false'}
  • +
  • + State: +
  • +
  • Cities: {this.state.cities.join(', ')}
  • +
  • Context: {this.context ? 'true' : 'false'}
  • +
+ ); + } +} + +const MemoizedStatefulClass = memo(StatefulClass); +const MemoizedStatefulFunction = memo(StatefulFunction); + +const ForwardRef = forwardRef<{|name: string|}, HTMLUListElement>( + ({name}, ref) => { + const [count, updateCount] = useState(0); + const debouncedCount = useDebounce(count, 1000); + const handleUpdateCountClick = useCallback(() => updateCount(count + 1), [ + count, + ]); + return ( +
    +
  • Name: {name}
  • +
  • + +
  • +
+ ); + }, +); + +export default function EditableProps() { + return ( + +

Editable props

+ Class + + Function + + Memoized Class + + Memoized Function + + Forward Ref + +
+ ); +} + +// Below copied from https://usehooks.com/ +function useDebounce(value, delay) { + // State and setters for debounced value + const [debouncedValue, setDebouncedValue] = useState(value); + + // Show the value in DevTools + useDebugValue(debouncedValue); + + useEffect( + () => { + // Update debounced value after delay + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + // Cancel the timeout if value changes (also on delay change or unmount) + // This is how we prevent debounced value from updating if value is changed ... + // .. within the delay period. Timeout gets cleared and restarted. + return () => { + clearTimeout(handler); + }; + }, + [value, delay], // Only re-call effect if value or delay changes + ); + + return debouncedValue; +} +// Above copied from https://usehooks.com/ diff --git a/packages/react-devtools-shell/src/app/ElementTypes/index.js b/packages/react-devtools-shell/src/app/ElementTypes/index.js new file mode 100644 index 0000000000000..b66d9eb9989fe --- /dev/null +++ b/packages/react-devtools-shell/src/app/ElementTypes/index.js @@ -0,0 +1,67 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import React, { + createContext, + forwardRef, + lazy, + memo, + Component, + Fragment, + // $FlowFixMe Flow doesn't know about the Profiler import yet + Profiler, + StrictMode, + Suspense, +} from 'react'; + +const Context = createContext('abc'); +Context.displayName = 'ExampleContext'; + +class ClassComponent extends Component { + render() { + return null; + } +} + +function FunctionComponent() { + return null; +} + +const MemoFunctionComponent = memo(FunctionComponent); + +const ForwardRefComponent = forwardRef((props, ref) => ( + +)); + +const LazyComponent = lazy(() => + Promise.resolve({ + default: FunctionComponent, + }), +); + +export default function ElementTypes() { + return ( + {}}> + + + {value => null} + + + Loading...}> + + + + + + + + + + ); +} diff --git a/packages/react-devtools-shell/src/app/Hydration/index.js b/packages/react-devtools-shell/src/app/Hydration/index.js new file mode 100644 index 0000000000000..914bf54ccc071 --- /dev/null +++ b/packages/react-devtools-shell/src/app/Hydration/index.js @@ -0,0 +1,140 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import React, {Fragment, useDebugValue, useState} from 'react'; + +const div = document.createElement('div'); +const exmapleFunction = () => {}; +const typedArray = new Uint8Array(3); +typedArray[0] = 1; +typedArray[1] = 2; +typedArray[2] = 3; + +const arrayOfArrays = [ + [['a', 'b', 'c'], ['d', 'e', 'f'], ['h', 'i', 'j']], + [['k', 'l', 'm'], ['n', 'o', 'p'], ['q', 'r', 's']], + [['t', 'u', 'v'], ['w', 'x', 'y'], ['z']], + [], +]; + +const objectOfObjects = { + foo: { + a: 1, + b: 2, + c: 3, + }, + bar: { + e: 4, + f: 5, + g: 6, + }, + baz: { + h: 7, + i: 8, + j: 9, + }, + qux: {}, +}; + +function useOuterFoo() { + useDebugValue({ + debugA: { + debugB: { + debugC: 'abc', + }, + }, + }); + useState({ + valueA: { + valueB: { + valueC: 'abc', + }, + }, + }); + return useInnerFoo(); +} + +function useInnerFoo() { + const [value] = useState([[['a', 'b', 'c']]]); + return value; +} + +function useOuterBar() { + useDebugValue({ + debugA: { + debugB: { + debugC: 'abc', + }, + }, + }); + return useInnerBar(); +} + +function useInnerBar() { + useDebugValue({ + debugA: { + debugB: { + debugC: 'abc', + }, + }, + }); + const [count] = useState(123); + return count; +} + +function useOuterBaz() { + return useInnerBaz(); +} + +function useInnerBaz() { + const [count] = useState(123); + return count; +} + +export default function Hydration() { + return ( + +

Hydration

+ } + array_buffer={typedArray.buffer} + typed_array={typedArray} + date={new Date()} + array={arrayOfArrays} + object={objectOfObjects} + /> + +
+ ); +} + +function DehydratableProps({array, object}: any) { + return ( +
    +
  • array: {JSON.stringify(array, null, 2)}
  • +
  • object: {JSON.stringify(object, null, 2)}
  • +
+ ); +} + +function DeepHooks(props: any) { + const foo = useOuterFoo(); + const bar = useOuterBar(); + const baz = useOuterBaz(); + return ( +
    +
  • foo: {foo}
  • +
  • bar: {bar}
  • +
  • baz: {baz}
  • +
+ ); +} diff --git a/packages/react-devtools-shell/src/app/Iframe/index.js b/packages/react-devtools-shell/src/app/Iframe/index.js new file mode 100644 index 0000000000000..394b0d680f9c4 --- /dev/null +++ b/packages/react-devtools-shell/src/app/Iframe/index.js @@ -0,0 +1,69 @@ +/** @flow */ + +import React, {Fragment} from 'react'; +import ReactDOM from 'react-dom'; + +export default function Iframe() { + return ( + +

Iframe

+
+ + + +
+
+ ); +} + +const iframeStyle = {border: '2px solid #eee', height: 80}; + +function Frame(props) { + const [element, setElement] = React.useState(null); + + const ref = React.useRef(); + + React.useLayoutEffect(function() { + const iframe = ref.current; + + if (iframe) { + const html = ` + + + +
+ + + `; + + const document = iframe.contentDocument; + + document.open(); + document.write(html); + document.close(); + + setElement(document.getElementById('root')); + } + }, []); + + return ( + +