diff --git a/examples/styled-components/src/App.js b/examples/styled-components/src/App.js index 80602f2d6..fcc01c9e3 100644 --- a/examples/styled-components/src/App.js +++ b/examples/styled-components/src/App.js @@ -51,11 +51,13 @@ const Hook = () => { () => { console.log('hot effected 0'); setState(state => ({ - x: state.x + 0.1, + x: state.x + 0.5, })); }, ['hot'], ); + + //React.useState(0); return (
hook state 1: {state.x} diff --git a/examples/styled-components/webpack.config.babel.js b/examples/styled-components/webpack.config.babel.js index 86f5ba2b1..1a771f952 100644 --- a/examples/styled-components/webpack.config.babel.js +++ b/examples/styled-components/webpack.config.babel.js @@ -6,7 +6,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: ['./src/index'], mode: process.env.NODE_ENV || 'development', - devtool: false, + //devtool: false, output: { path: path.join(__dirname, 'dist'), filename: 'bundle.js', diff --git a/src/babel.dev.js b/src/babel.dev.js index 470193511..5672bb859 100644 --- a/src/babel.dev.js +++ b/src/babel.dev.js @@ -1,4 +1,5 @@ import { REGENERATE_METHOD } from './internal/constants'; +import fresh from './fresh/babel'; const templateOptions = { placeholderPattern: /^([A-Z0-9]+)([A-Z0-9_]+)$/, @@ -10,9 +11,10 @@ const shouldIgnoreFile = file => .split('\\') .join('/') .match(/node_modules\/(react|react-hot-loader)([\/]|$)/); + /* eslint-enable */ -module.exports = function plugin(args, options = {}) { +function plugin(args, options = {}) { // This is a Babel plugin, but the user put it in the Webpack config. if (this && this.callback) { throw new Error( @@ -30,6 +32,12 @@ module.exports = function plugin(args, options = {}) { const { safetyNet = true } = options; const buildRegistration = template('reactHotLoader.register(ID, NAME, FILENAME);', templateOptions); + + const signatureHeader = template( + `var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) {return a;}`, + templateOptions, + ); + const headerTemplate = template( `(function () { var enterModule = (typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal : require('react-hot-loader')).enterModule; @@ -53,7 +61,7 @@ module.exports = function plugin(args, options = {}) { ` (function () { - var reactHotLoader = (typeof reactHotLoaderGlobal !== 'undefined' ?reactHotLoaderGlobal : require('react-hot-loader')).default; + var reactHotLoader = (typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal : require('react-hot-loader')).default; if (!reactHotLoader) { return; @@ -119,10 +127,12 @@ module.exports = function plugin(args, options = {}) { }, Program: { - enter({ scope }, state) { + enter({ scope, node }, state) { const { file } = state; state[REGISTRATIONS] = []; // eslint-disable-line no-param-reassign + node.body.unshift(signatureHeader()); + // Everything in the top level scope, when reasonable, // is going to get tagged with __source. /* eslint-disable guard-for-in,no-restricted-syntax */ @@ -208,6 +218,54 @@ module.exports = function plugin(args, options = {}) { }, }, }; +} + +const mergeRecord = (sourceRecord, newRecord) => { + Object.keys(newRecord).forEach(key => { + const action = newRecord[key]; + if (typeof action === 'function') { + if (!sourceRecord[key]) { + sourceRecord[key] = () => ({}); + } + const prev = sourceRecord[key]; + sourceRecord[key] = (...args) => { + prev(...args); + action(...args); + }; + } else if (typeof action === 'object') { + if (!sourceRecord[key]) { + sourceRecord[key] = {}; + } + mergeRecord(sourceRecord[key], action); + } + }); +}; + +const composePlugins = plugins => (...args) => { + const result = {}; + plugins.forEach(creator => { + const plugin = creator(...args); + mergeRecord(result, plugin); + }); + return result; }; +module.exports = composePlugins([ + plugin, + (...args) => { + const p = fresh(...args); + // removing everything we dont want right now + + // registration + delete p.visitor.Program; + + // registrations + delete p.visitor.FunctionDeclaration.enter; + delete p.visitor.FunctionDeclaration.leave; + delete p.visitor.VariableDeclaration; + + return p; + }, +]); + module.exports.shouldIgnoreFile = shouldIgnoreFile; diff --git a/src/fresh/babel.js b/src/fresh/babel.js new file mode 100644 index 000000000..a5925c38e --- /dev/null +++ b/src/fresh/babel.js @@ -0,0 +1,579 @@ +/** + * 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 SIGNATURE = '__signature__'; + +export default function(babel) { + const {types: t} = babel; + + const registrationsByProgramPath = new Map(); + function createRegistration(programPath, persistentID) { + const handle = programPath.scope.generateUidIdentifier('c'); + if (!registrationsByProgramPath.has(programPath)) { + registrationsByProgramPath.set(programPath, []); + } + const registrations = registrationsByProgramPath.get(programPath); + registrations.push({ + handle, + persistentID, + }); + return handle; + } + + function isComponentishName(name) { + return typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z'; + } + + function findInnerComponents(inferredName, path, callback) { + const node = path.node; + switch (node.type) { + case 'Identifier': { + if (!isComponentishName(node.name)) { + return false; + } + // export default hoc(Foo) + // const X = hoc(Foo) + callback(inferredName, node, null); + return true; + } + case 'FunctionDeclaration': { + // function Foo() {} + // export function Foo() {} + // export default function Foo() {} + callback(inferredName, node.id, null); + return true; + } + case 'ArrowFunctionExpression': { + if (node.body.type === 'ArrowFunctionExpression') { + return false; + } + // let Foo = () => {} + // export default hoc1(hoc2(() => {})) + callback(inferredName, node, path); + return true; + } + case 'FunctionExpression': { + // let Foo = function() {} + // const Foo = hoc1(forwardRef(function renderFoo() {})) + // export default memo(function() {}) + callback(inferredName, node, path); + return true; + } + case 'CallExpression': { + const argsPath = path.get('arguments'); + if (argsPath === undefined || argsPath.length === 0) { + return false; + } + const calleePath = path.get('callee'); + switch (calleePath.node.type) { + case 'MemberExpression': + case 'Identifier': { + const calleeSource = calleePath.getSource(); + const firstArgPath = argsPath[0]; + const innerName = inferredName + '$' + calleeSource; + const foundInside = findInnerComponents( + innerName, + firstArgPath, + callback, + ); + if (!foundInside) { + return false; + } + // const Foo = hoc1(hoc2(() => {})) + // export default memo(React.forwardRef(function() {})) + callback(inferredName, node, path); + return true; + } + default: { + return false; + } + } + } + case 'VariableDeclarator': { + const init = node.init; + if (init === null) { + return false; + } + const name = node.id.name; + if (!isComponentishName(name)) { + return false; + } + if (init.type === 'Identifier' || init.type === 'MemberExpression') { + return false; + } + const initPath = path.get('init'); + const foundInside = findInnerComponents( + inferredName, + initPath, + callback, + ); + if (foundInside) { + return true; + } + // See if this identifier is used in JSX. Then it's a component. + const binding = path.scope.getBinding(name); + if (binding === undefined) { + return; + } + let isLikelyUsedAsType = false; + const referencePaths = binding.referencePaths; + for (let i = 0; i < referencePaths.length; i++) { + const ref = referencePaths[i]; + if ( + ref.node.type !== 'JSXIdentifier' && + ref.node.type !== 'Identifier' + ) { + continue; + } + const refParent = ref.parent; + if (refParent.type === 'JSXOpeningElement') { + isLikelyUsedAsType = true; + } else if (refParent.type === 'CallExpression') { + const callee = refParent.callee; + let fnName; + switch (callee.type) { + case 'Identifier': + fnName = callee.name; + break; + case 'MemberExpression': + fnName = callee.property.name; + break; + } + switch (fnName) { + case 'createElement': + case 'jsx': + case 'jsxDEV': + case 'jsxs': + isLikelyUsedAsType = true; + break; + } + } + if (isLikelyUsedAsType) { + // const X = ... + later + callback(inferredName, init, initPath); + return true; + } + } + } + } + return false; + } + + let hookCalls = new WeakMap(); + + function recordHookCall(functionNode, hookCallPath, hookName) { + if (!hookCalls.has(functionNode)) { + hookCalls.set(functionNode, []); + } + let hookCallsForFn = hookCalls.get(functionNode); + let key = ''; + if (hookCallPath.parent.type === 'VariableDeclarator') { + // TODO: if there is no LHS, consider some other heuristic. + key = hookCallPath.parentPath.get('id').getSource(); + } + hookCallsForFn.push({ + name: hookName, + callee: hookCallPath.node.callee, + key, + }); + } + + function isBuiltinHook(hookName) { + switch (hookName) { + case 'useState': + case 'React.useState': + case 'useReducer': + case 'React.useReducer': + case 'useEffect': + case 'React.useEffect': + case 'useLayoutEffect': + case 'React.useLayoutEffect': + case 'useMemo': + case 'React.useMemo': + case 'useCallback': + case 'React.useCallback': + case 'useRef': + case 'React.useRef': + case 'useContext': + case 'React.useContext': + case 'useImperativeMethods': + case 'React.useImperativeMethods': + case 'useDebugValue': + case 'React.useDebugValue': + return true; + default: + return false; + } + } + + function getHookCallsSignature(functionNode) { + const fnHookCalls = hookCalls.get(functionNode); + if (fnHookCalls === undefined) { + return null; + } + return { + key: fnHookCalls.map(call => call.name + '{' + call.key + '}').join('\n'), + customHooks: fnHookCalls + .filter(call => !isBuiltinHook(call.name)) + .map(call => call.callee), + }; + } + + function createArgumentsForSignature(node, signature) { + const {key, customHooks} = signature; + const args = [node, t.stringLiteral(key)]; + if (customHooks.length > 0) { + args.push(t.arrowFunctionExpression([], t.arrayExpression(customHooks))); + } + return args; + } + + let seenForRegistration = new WeakSet(); + let seenForSignature = new WeakSet(); + let seenForHookCalls = new WeakSet(); + let seenForOutro = new WeakSet(); + + return { + visitor: { + ExportDefaultDeclaration(path) { + const node = path.node; + const decl = node.declaration; + const declPath = path.get('declaration'); + if (decl.type !== 'CallExpression') { + // For now, we only support possible HOC calls here. + // Named function declarations are handled in FunctionDeclaration. + // Anonymous direct exports like export default function() {} + // are currently ignored. + return; + } + + // Make sure we're not mutating the same tree twice. + // This can happen if another Babel plugin replaces parents. + if (seenForRegistration.has(node)) { + return; + } + seenForRegistration.add(node); + // Don't mutate the tree above this point. + + // This code path handles nested cases like: + // export default memo(() => {}) + // In those cases it is more plausible people will omit names + // so they're worth handling despite possible false positives. + // More importantly, it handles the named case: + // export default memo(function Named() {}) + const inferredName = '%default%'; + const programPath = path.parentPath; + findInnerComponents( + inferredName, + declPath, + (persistentID, targetExpr, targetPath) => { + if (targetPath === null) { + // For case like: + // export default hoc(Foo) + // we don't want to wrap Foo inside the call. + // Instead we assume it's registered at definition. + return; + } + const handle = createRegistration(programPath, persistentID); + targetPath.replaceWith( + t.assignmentExpression('=', handle, targetExpr), + ); + }, + ); + }, + FunctionDeclaration: { + enter(path) { + const node = path.node; + let programPath; + let insertAfterPath; + switch (path.parent.type) { + case 'Program': + insertAfterPath = path; + programPath = path.parentPath; + break; + case 'ExportNamedDeclaration': + insertAfterPath = path.parentPath; + programPath = insertAfterPath.parentPath; + break; + case 'ExportDefaultDeclaration': + insertAfterPath = path.parentPath; + programPath = insertAfterPath.parentPath; + break; + default: + return; + } + const id = node.id; + if (id === null) { + // We don't currently handle anonymous default exports. + return; + } + const inferredName = id.name; + if (!isComponentishName(inferredName)) { + return; + } + + // Make sure we're not mutating the same tree twice. + // This can happen if another Babel plugin replaces parents. + if (seenForRegistration.has(node)) { + return; + } + seenForRegistration.add(node); + // Don't mutate the tree above this point. + + // export function Named() {} + // function Named() {} + findInnerComponents( + inferredName, + path, + (persistentID, targetExpr) => { + const handle = createRegistration(programPath, persistentID); + insertAfterPath.insertAfter( + t.expressionStatement( + t.assignmentExpression('=', handle, targetExpr), + ), + ); + }, + ); + }, + exit(path) { + const node = path.node; + const id = node.id; + if (id === null) { + return; + } + const signature = getHookCallsSignature(node); + if (signature === null) { + return; + } + + // Make sure we're not mutating the same tree twice. + // This can happen if another Babel plugin replaces parents. + if (seenForSignature.has(node)) { + return; + } + seenForSignature.add(node); + // Don't muatte the tree above this point. + + // Unlike with __register__, this needs to work for nested + // declarations too. So we need to search for a path where + // we can insert a statement rather than hardcoding it. + let insertAfterPath = null; + path.find(p => { + if (p.parentPath.isBlock()) { + insertAfterPath = p; + return true; + } + }); + if (insertAfterPath === null) { + return; + } + + insertAfterPath.insertAfter( + t.expressionStatement( + t.callExpression( + t.identifier(SIGNATURE), + createArgumentsForSignature(id, signature), + ), + ), + ); + }, + }, + 'ArrowFunctionExpression|FunctionExpression': { + exit(path) { + const node = path.node; + const signature = getHookCallsSignature(node); + if (signature === null) { + return; + } + + // Make sure we're not mutating the same tree twice. + // This can happen if another Babel plugin replaces parents. + if (seenForSignature.has(node)) { + return; + } + seenForSignature.add(node); + // Don't mutate the tree above this point. + + if (path.parent.type === 'VariableDeclarator') { + let insertAfterPath = null; + path.find(p => { + if (p.parentPath.isBlock()) { + insertAfterPath = p; + return true; + } + }); + if (insertAfterPath === null) { + return; + } + // Special case when a function would get an inferred name: + // let Foo = () => {} + // let Foo = function() {} + // We'll add signature it on next line so that + // we don't mess up the inferred 'Foo' function name. + insertAfterPath.insertAfter( + t.expressionStatement( + t.callExpression( + t.identifier(SIGNATURE), + createArgumentsForSignature(path.parent.id, signature), + ), + ), + ); + // Result: let Foo = () => {}; __signature(Foo, ...); + } else { + // let Foo = hoc(() => {}) + path.replaceWith( + t.callExpression( + t.identifier(SIGNATURE), + createArgumentsForSignature(node, signature), + ), + ); + // Result: let Foo = hoc(__signature(() => {}, ...)) + } + }, + }, + VariableDeclaration(path) { + const node = path.node; + let programPath; + let insertAfterPath; + switch (path.parent.type) { + case 'Program': + insertAfterPath = path; + programPath = path.parentPath; + break; + case 'ExportNamedDeclaration': + insertAfterPath = path.parentPath; + programPath = insertAfterPath.parentPath; + break; + case 'ExportDefaultDeclaration': + insertAfterPath = path.parentPath; + programPath = insertAfterPath.parentPath; + break; + default: + return; + } + + // Make sure we're not mutating the same tree twice. + // This can happen if another Babel plugin replaces parents. + if (seenForRegistration.has(node)) { + return; + } + seenForRegistration.add(node); + // Don't mutate the tree above this point. + + const declPaths = path.get('declarations'); + if (declPaths.length !== 1) { + return; + } + const declPath = declPaths[0]; + const inferredName = declPath.node.id.name; + findInnerComponents( + inferredName, + declPath, + (persistentID, targetExpr, targetPath) => { + if (targetPath === null) { + // For case like: + // export const Something = hoc(Foo) + // we don't want to wrap Foo inside the call. + // Instead we assume it's registered at definition. + return; + } + const handle = createRegistration(programPath, persistentID); + if ( + (targetExpr.type === 'ArrowFunctionExpression' || + targetExpr.type === 'FunctionExpression') && + targetPath.parent.type === 'VariableDeclarator' + ) { + // Special case when a function would get an inferred name: + // let Foo = () => {} + // let Foo = function() {} + // We'll register it on next line so that + // we don't mess up the inferred 'Foo' function name. + insertAfterPath.insertAfter( + t.expressionStatement( + t.assignmentExpression('=', handle, declPath.node.id), + ), + ); + // Result: let Foo = () => {}; _c1 = Foo; + } else { + // let Foo = hoc(() => {}) + targetPath.replaceWith( + t.assignmentExpression('=', handle, targetExpr), + ); + // Result: let Foo = _c1 = hoc(() => {}) + } + }, + ); + }, + CallExpression(path) { + const node = path.node; + const callee = node.callee; + + let name = null; + switch (callee.type) { + case 'Identifier': + name = callee.name; + break; + case 'MemberExpression': + name = callee.property.name; + break; + } + if (name === null || !/^use[A-Z]/.test(name)) { + return; + } + + // Make sure we're not recording the same calls twice. + // This can happen if another Babel plugin replaces parents. + if (seenForHookCalls.has(node)) { + return; + } + seenForHookCalls.add(node); + // Don't mutate the tree above this point. + + const fn = path.scope.getFunctionParent(); + if (fn === null) { + return; + } + recordHookCall(fn.block, path, name); + }, + Program: { + exit(path) { + const registrations = registrationsByProgramPath.get(path); + if (registrations === undefined) { + return; + } + + // Make sure we're not mutating the same tree twice. + // This can happen if another Babel plugin replaces parents. + const node = path.node; + if (seenForOutro.has(node)) { + return; + } + seenForOutro.add(node); + // Don't mutate the tree above this point. + + registrationsByProgramPath.delete(path); + const declarators = []; + path.pushContainer('body', t.variableDeclaration('var', declarators)); + registrations.forEach(({handle, persistentID}) => { + path.pushContainer( + 'body', + t.expressionStatement( + t.callExpression(t.identifier('__register__'), [ + handle, + t.stringLiteral(persistentID), + ]), + ), + ); + declarators.push(t.variableDeclarator(handle)); + }); + }, + }, + }, + }; +} \ No newline at end of file diff --git a/src/reactHotLoader.js b/src/reactHotLoader.js index d682a53a9..602e52aa2 100644 --- a/src/reactHotLoader.js +++ b/src/reactHotLoader.js @@ -15,6 +15,7 @@ import { isTypeBlacklisted, registerComponent, updateFunctionProxyById, + addSignature, } from './reconciler/proxies'; import configuration from './configuration'; import logger from './logger'; @@ -33,8 +34,14 @@ const hookWrapper = hook => (cb, deps) => { return hook(cb, deps); }; +const noDeps = () => []; + const reactHotLoader = { IS_REACT_MERGE_ENABLED: false, + signature(type, key, getCustomHooks = noDeps) { + addSignature(type, { key, getCustomHooks }); + return type; + }, register(type, uniqueLocalName, fileName, options = {}) { const id = `${fileName}#${uniqueLocalName}`; diff --git a/src/reconciler/componentComparator.js b/src/reconciler/componentComparator.js index 72ee0c7db..f2de14757 100644 --- a/src/reconciler/componentComparator.js +++ b/src/reconciler/componentComparator.js @@ -1,4 +1,11 @@ -import { getIdByType, getProxyByType, isColdType, isRegisteredComponent, updateProxyById } from './proxies'; +import { + getIdByType, + getProxyByType, + getSignature, + isColdType, + isRegisteredComponent, + updateProxyById, +} from './proxies'; import { hotComparisonOpen } from '../global/generation'; import { isForwardType, isMemoType, isReactClass, isReloadableComponent } from '../internal/reactUtils'; import { areSwappable } from './utils'; @@ -11,12 +18,53 @@ const getInnerComponentType = component => { return unwrapper ? unwrapper() : component; }; +function haveEqualSignatures(prevType, nextType) { + const prevSignature = getSignature(prevType); + const nextSignature = getSignature(nextType); + + if (prevSignature === undefined && nextSignature === undefined) { + return true; + } + if (prevSignature === undefined || nextSignature === undefined) { + return false; + } + if (prevSignature.key !== nextSignature.key) { + return false; + } + + // TODO: we might need to calculate previous signature earlier in practice, + // such as during the first time a component is resolved. We'll revisit this. + const prevCustomHooks = prevSignature.getCustomHooks(); + const nextCustomHooks = nextSignature.getCustomHooks(); + if (prevCustomHooks.length !== nextCustomHooks.length) { + return false; + } + + for (let i = 0; i < nextCustomHooks.length; i++) { + if (!haveEqualSignatures(prevCustomHooks[i], nextCustomHooks[i])) { + return false; + } + } + + return true; +} + const compareRegistered = (a, b) => { if (isRegisteredComponent(a) || isRegisteredComponent(b)) { if (resolveType(a) !== resolveType(b)) { return false; } } + + // compare signatures of two components + // non-equal component have to remount and there is two options to do it + // - fail the comparison, remounting all tree below + // - fulfill it, but set `_debugNeedsRemount` on a fiber to drop only local state + // the second way is not published yet, so going with the first one + if (!haveEqualSignatures(a, b)) { + logger.warn('Hook order change detected:', a, 'state has been reset'); + return false; + } return true; }; diff --git a/src/reconciler/proxies.js b/src/reconciler/proxies.js index c5dc64d13..6411737e8 100644 --- a/src/reconciler/proxies.js +++ b/src/reconciler/proxies.js @@ -6,6 +6,7 @@ import { incrementHotGeneration } from '../global/generation'; const merge = require('lodash/merge'); +let signatures; let proxiesByID; let blackListedProxies; let registeredComponents; @@ -78,12 +79,16 @@ export const blacklistByType = type => blackListedProxies.set(type, true); export const setComponentOptions = (component, options) => componentOptions.set(component, options); +export const addSignature = (type, signature) => signatures.set(type, signature); +export const getSignature = type => signatures.get(type); + export const resetProxies = () => { proxiesByID = {}; idsByType = new WeakMap(); blackListedProxies = new WeakMap(); registeredComponents = new WeakMap(); componentOptions = new WeakMap(); + signatures = new WeakMap(); resetClassProxies(); }; diff --git a/test/__babel_fixtures__/hooks.js b/test/__babel_fixtures__/hooks.js new file mode 100644 index 000000000..13c997a46 --- /dev/null +++ b/test/__babel_fixtures__/hooks.js @@ -0,0 +1,40 @@ +import React, {useState} from 'react'; + +const NoHooks = () =>
no hooks
; + +const UseStateHook = () => { + useState(42); +}; + +const UseStateHooks = () => { + useState(42); + useState(24); +}; + +const useEffectHook = () => { + React.useEffect(() => ({})); + useState(24); + React.useState(42); +}; + +const useForwardRefHook = React.forwardRef(() => { + React.useEffect(() => ({})); + useState(24); + React.useState(42); +}); + +const useForwardRefFunctionHook = React.forwardRef(function () { + React.useEffect(() => ({})); + useState(24); + React.useState(42); +}); + +const useCustomHook = () => { + useState(42); + useEffectHook(); +}; + +function useFunc () { + useState(42); + useEffectHook(); +}; \ No newline at end of file diff --git a/test/__snapshots__/babel.test.js.snap b/test/__snapshots__/babel.test.js.snap index 05f476cb2..d8868f1f0 100644 --- a/test/__snapshots__/babel.test.js.snap +++ b/test/__snapshots__/babel.test.js.snap @@ -12,6 +12,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { var _arguments = arguments; @@ -69,6 +73,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { _classCallCheck(this, Foo); @@ -124,6 +132,10 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { var _this = this; @@ -202,6 +214,10 @@ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, a function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { var _this = this; @@ -278,6 +294,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { _classCallCheck(this, Foo); @@ -331,6 +351,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { _classCallCheck(this, Foo); @@ -386,6 +410,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { _classCallCheck(this, Foo); @@ -442,6 +470,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { _classCallCheck(this, Foo); @@ -495,6 +527,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { var _arguments = arguments; @@ -554,6 +590,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { _classCallCheck(this, Foo); @@ -611,6 +651,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { _classCallCheck(this, Foo); @@ -666,6 +710,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { _classCallCheck(this, Foo); @@ -717,6 +765,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { _classCallCheck(this, Foo); @@ -770,6 +822,10 @@ var _createClass = function () { function defineProperties(target, props) { for function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function () { function Foo() { _classCallCheck(this, Foo); @@ -826,6 +882,10 @@ exports[`babel Targetting "es2015" copies arrow function body block onto hidden function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Foo = function Foo() { _classCallCheck(this, Foo); }; @@ -877,6 +937,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var A = 42; function B() { function R() {} @@ -967,6 +1031,10 @@ Object.defineProperty(exports, \\"__esModule\\", { function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } } +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var Counter = function Counter() { _classCallCheck(this, Counter); }; @@ -1058,6 +1126,116 @@ exports.e = e; exports.z = z;" `; +exports[`babel Targetting "es2015" tags potential React components hooks.js 1`] = ` +"'use strict'; + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +(function () { + var enterModule = (typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal : require('react-hot-loader')).enterModule; + enterModule && enterModule(module); +})(); + +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + +var NoHooks = function NoHooks() { + return _react2.default.createElement( + 'div', + null, + 'no hooks' + ); +}; + +var UseStateHook = function UseStateHook() { + (0, _react.useState)(42); +}; + +__signature__(UseStateHook, 'useState{}'); + +var UseStateHooks = function UseStateHooks() { + (0, _react.useState)(42); + (0, _react.useState)(24); +}; + +__signature__(UseStateHooks, 'useState{}\\\\nuseState{}'); + +var useEffectHook = function useEffectHook() { + _react2.default.useEffect(function () { + return {}; + }); + (0, _react.useState)(24); + _react2.default.useState(42); +}; + +__signature__(useEffectHook, 'useEffect{}\\\\nuseState{}\\\\nuseState{}'); + +var useForwardRefHook = _react2.default.forwardRef(__signature__(function () { + _react2.default.useEffect(function () { + return {}; + }); + (0, _react.useState)(24); + _react2.default.useState(42); +}, 'useEffect{}\\\\nuseState{}\\\\nuseState{}')); + +var useForwardRefFunctionHook = _react2.default.forwardRef(__signature__(function () { + _react2.default.useEffect(function () { + return {}; + }); + (0, _react.useState)(24); + _react2.default.useState(42); +}, 'useEffect{}\\\\nuseState{}\\\\nuseState{}')); + +var useCustomHook = function useCustomHook() { + (0, _react.useState)(42); + useEffectHook(); +}; + +__signature__(useCustomHook, 'useState{}\\\\nuseEffectHook{}', function () { + return [useEffectHook]; +}); + +function useFunc() { + (0, _react.useState)(42); + useEffectHook(); +} +__signature__(useFunc, 'useState{}\\\\nuseEffectHook{}', function () { + return [useEffectHook]; +}); + +; +; + +(function () { + var reactHotLoader = (typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal : require('react-hot-loader')).default; + + if (!reactHotLoader) { + return; + } + + reactHotLoader.register(NoHooks, 'NoHooks', __FILENAME__); + reactHotLoader.register(UseStateHook, 'UseStateHook', __FILENAME__); + reactHotLoader.register(UseStateHooks, 'UseStateHooks', __FILENAME__); + reactHotLoader.register(useEffectHook, 'useEffectHook', __FILENAME__); + reactHotLoader.register(useForwardRefHook, 'useForwardRefHook', __FILENAME__); + reactHotLoader.register(useForwardRefFunctionHook, 'useForwardRefFunctionHook', __FILENAME__); + reactHotLoader.register(useCustomHook, 'useCustomHook', __FILENAME__); + reactHotLoader.register(useFunc, 'useFunc', __FILENAME__); +})(); + +; + +(function () { + var leaveModule = (typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal : require('react-hot-loader')).leaveModule; + leaveModule && leaveModule(module); +})();" +`; + exports[`babel Targetting "es2015" tags potential React components issue 246.js 1`] = ` "\\"use strict\\"; @@ -1071,6 +1249,10 @@ exports.spread = spread; enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + function spread() { for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; @@ -1110,6 +1292,10 @@ Object.defineProperty(exports, \\"__esModule\\", { enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + var _default = 10; var _default2 = 42; exports.default = _default2; @@ -1142,6 +1328,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = (a, b) => { @@ -1186,6 +1376,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.onClick = e => e.target.value; @@ -1226,6 +1420,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = async (a, b) => await b(a); @@ -1266,6 +1464,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = async (a, b) => { @@ -1308,6 +1510,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = (a, b) => { @@ -1350,6 +1556,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = (a = \\"foo\\") => { @@ -1392,6 +1602,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = ({ a }, { b }) => { @@ -1434,6 +1648,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.onClick = e => e.target.value; @@ -1474,6 +1692,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = (a, b) => { @@ -1520,6 +1742,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = (a, b) => { @@ -1566,6 +1792,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = (a, b) => { @@ -1610,6 +1840,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = 2; @@ -1650,6 +1884,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = function (a, b) { @@ -1692,6 +1930,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo { constructor() { this.bar = (a, b) => { @@ -1738,6 +1980,10 @@ exports[`babel Targetting "modern" copies arrow function body block onto hidden enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Foo {} Foo.bar = (a, b) => { @@ -1783,6 +2029,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + const A = 42; function B() { function R() {} @@ -1851,6 +2101,10 @@ Object.defineProperty(exports, \\"__esModule\\", { enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + class Counter {} const _default = () => React.createElement( @@ -1936,6 +2190,104 @@ exports.e = e; exports.z = z;" `; +exports[`babel Targetting "modern" tags potential React components hooks.js 1`] = ` +"'use strict'; + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +(function () { + var enterModule = (typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal : require('react-hot-loader')).enterModule; + enterModule && enterModule(module); +})(); + +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + +const NoHooks = () => _react2.default.createElement( + 'div', + null, + 'no hooks' +); + +const UseStateHook = () => { + (0, _react.useState)(42); +}; + +__signature__(UseStateHook, 'useState{}'); + +const UseStateHooks = () => { + (0, _react.useState)(42); + (0, _react.useState)(24); +}; + +__signature__(UseStateHooks, 'useState{}\\\\nuseState{}'); + +const useEffectHook = () => { + _react2.default.useEffect(() => ({})); + (0, _react.useState)(24); + _react2.default.useState(42); +}; + +__signature__(useEffectHook, 'useEffect{}\\\\nuseState{}\\\\nuseState{}'); + +const useForwardRefHook = _react2.default.forwardRef(__signature__(() => { + _react2.default.useEffect(() => ({})); + (0, _react.useState)(24); + _react2.default.useState(42); +}, 'useEffect{}\\\\nuseState{}\\\\nuseState{}')); + +const useForwardRefFunctionHook = _react2.default.forwardRef(__signature__(function () { + _react2.default.useEffect(() => ({})); + (0, _react.useState)(24); + _react2.default.useState(42); +}, 'useEffect{}\\\\nuseState{}\\\\nuseState{}')); + +const useCustomHook = () => { + (0, _react.useState)(42); + useEffectHook(); +}; + +__signature__(useCustomHook, 'useState{}\\\\nuseEffectHook{}', () => [useEffectHook]); + +function useFunc() { + (0, _react.useState)(42); + useEffectHook(); +} +__signature__(useFunc, 'useState{}\\\\nuseEffectHook{}', () => [useEffectHook]); + +; +; + +(function () { + var reactHotLoader = (typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal : require('react-hot-loader')).default; + + if (!reactHotLoader) { + return; + } + + reactHotLoader.register(NoHooks, 'NoHooks', __FILENAME__); + reactHotLoader.register(UseStateHook, 'UseStateHook', __FILENAME__); + reactHotLoader.register(UseStateHooks, 'UseStateHooks', __FILENAME__); + reactHotLoader.register(useEffectHook, 'useEffectHook', __FILENAME__); + reactHotLoader.register(useForwardRefHook, 'useForwardRefHook', __FILENAME__); + reactHotLoader.register(useForwardRefFunctionHook, 'useForwardRefFunctionHook', __FILENAME__); + reactHotLoader.register(useCustomHook, 'useCustomHook', __FILENAME__); + reactHotLoader.register(useFunc, 'useFunc', __FILENAME__); +})(); + +; + +(function () { + var leaveModule = (typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal : require('react-hot-loader')).leaveModule; + leaveModule && leaveModule(module); +})();" +`; + exports[`babel Targetting "modern" tags potential React components issue 246.js 1`] = ` "\\"use strict\\"; @@ -1949,6 +2301,10 @@ exports.spread = spread; enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + function spread(...args) { return args.push(1); } @@ -1984,6 +2340,10 @@ Object.defineProperty(exports, \\"__esModule\\", { enterModule && enterModule(module); })(); +var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) { + return a; +}; + const _default = 10; const _default2 = 42; exports.default = _default2;