[WIP] DevTools: Support named hooks for >1 module in a bundle (#21790)

* Add named hooks test case built with Rollup

* Fix prepareStackTrace unpatching, remove sourceURL

* Prettier

* Resolve source map URL/path relative to the script

* Add failing tests for multi-module bundle

* Parse hook names from multiple modules in a bundle

* Create a HookSourceData per location key (file, line, column).
* Cache the source map per runtime URL ( = file part of location key).
* Don't store sourceMapContents - only store a consumer instance.
* Look up original source URLs in the source map correctly.
* Cache the code + AST per original URL.
* Fix off-by-one column number lookup.
* Some naming and typing tweaks related to the above.
* Stop storing the consumer outside the with() callback, which is a bug.

* Lint fix for 8d8dd25

* Added devDependencies to react-devtools-extensions package.json

* Added some debug logging and TODO comments

* Added additional DEBUG logging to hook names cache

Co-authored-by: Brian Vaughn <[email protected]>
This commit is contained in:
Moti Zilberman
2021-07-07 13:07:58 -04:00
committed by GitHub
co-authored by Brian Vaughn
parent fe9d9a3842
commit 64cc858d3b
25 changed files with 691 additions and 219 deletions
@@ -19,6 +19,7 @@
"update-mock-source-maps": "node ./src/__tests__/updateMockSourceMaps.js"
},
"devDependencies": {
"acorn-jsx": "^5.2.0",
"@babel/core": "^7.11.1",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-transform-flow-strip-types": "^7.10.4",
@@ -44,6 +45,10 @@
"open": "^7.0.2",
"os-name": "^3.1.0",
"raw-loader": "^3.1.0",
"rollup": "^1.19.4",
"rollup-plugin-babel": "^4.0.1",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-node-resolve": "^2.1.1",
"source-map": "^0.8.0-beta.0",
"style-loader": "^0.23.1",
"web-ext": "^3.0.0",
@@ -0,0 +1,198 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
/**
* 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
*/
function Component() {
const [count, setCount] = React.useState(0);
const isDarkMode = useIsDarkMode();
React.useEffect(() => {// ...
}, []);
const handleClick = () => setCount(count + 1);
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("div", null, "Dark mode? ", isDarkMode), /*#__PURE__*/React__default.createElement("div", null, "Count: ", count), /*#__PURE__*/React__default.createElement("button", {
onClick: handleClick
}, "Update count"));
}
function useIsDarkMode() {
const [isDarkMode] = React.useState(false);
React.useEffect(function useEffectCreate() {// Here is where we may listen to a "theme" event...
}, []);
return isDarkMode;
}
/**
* 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
*/
const ThemeContext = /*#__PURE__*/React.createContext('bright');
function useTheme() {
const theme = React.useContext(ThemeContext);
React.useDebugValue(theme);
return theme;
}
/**
* 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
*/
function Component$1() {
const theme = useTheme();
return /*#__PURE__*/React__default.createElement("div", null, "theme: ", theme);
}
/**
* 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
*/
function Component$2() {
const [count, setCount] = React.useState(0);
return /*#__PURE__*/React__default.createElement("div", null, /*#__PURE__*/React__default.createElement("p", null, "You clicked ", count, " times"), /*#__PURE__*/React__default.createElement("button", {
onClick: () => setCount(count + 1)
}, "Click me"));
}
/**
* 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
*/
function Component$3() {
const [count] = require('react').useState(0);
return count;
}
/**
* 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
*/
function ListItem({
item,
removeItem,
toggleItem
}) {
const handleDelete = React.useCallback(() => {
removeItem(item);
}, [item, removeItem]);
const handleToggle = React.useCallback(() => {
toggleItem(item);
}, [item, toggleItem]);
return /*#__PURE__*/React.createElement("li", null, /*#__PURE__*/React.createElement("button", {
onClick: handleDelete
}, "Delete"), /*#__PURE__*/React.createElement("label", null, /*#__PURE__*/React.createElement("input", {
checked: item.isComplete,
onChange: handleToggle,
type: "checkbox"
}), ' ', item.text));
}
function List(props) {
const [newItemText, setNewItemText] = React.useState('');
const [items, setItems] = React.useState([{
id: 1,
isComplete: true,
text: 'First'
}, {
id: 2,
isComplete: true,
text: 'Second'
}, {
id: 3,
isComplete: false,
text: 'Third'
}]);
const [uid, setUID] = React.useState(4);
const handleClick = React.useCallback(() => {
if (newItemText !== '') {
setItems([...items, {
id: uid,
isComplete: false,
text: newItemText
}]);
setUID(uid + 1);
setNewItemText('');
}
}, [newItemText, items, uid]);
const handleKeyPress = React.useCallback(event => {
if (event.key === 'Enter') {
handleClick();
}
}, [handleClick]);
const handleChange = React.useCallback(event => {
setNewItemText(event.currentTarget.value);
}, [setNewItemText]);
const removeItem = React.useCallback(itemToRemove => setItems(items.filter(item => item !== itemToRemove)), [items]);
const toggleItem = React.useCallback(itemToToggle => {
// Dont use indexOf()
// because editing props in DevTools creates a new Object.
const index = items.findIndex(item => item.id === itemToToggle.id);
setItems(items.slice(0, index).concat({ ...itemToToggle,
isComplete: !itemToToggle.isComplete
}).concat(items.slice(index + 1)));
}, [items]);
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "List"), /*#__PURE__*/React.createElement("input", {
type: "text",
placeholder: "New list item...",
value: newItemText,
onChange: handleChange,
onKeyPress: handleKeyPress
}), /*#__PURE__*/React.createElement("button", {
disabled: newItemText === '',
onClick: handleClick
}, /*#__PURE__*/React.createElement("span", {
role: "img",
"aria-label": "Add item"
}, "Add")), /*#__PURE__*/React.createElement("ul", null, items.map(item => /*#__PURE__*/React.createElement(ListItem, {
key: item.id,
item: item,
removeItem: removeItem,
toggleItem: toggleItem
}))));
}
var ToDoList = /*#__PURE__*/Object.freeze({
__proto__: null,
ListItem: ListItem,
List: List
});
exports.ComponentWithCustomHook = Component;
exports.ComponentWithExternalCustomHooks = Component$1;
exports.Example = Component$2;
exports.InlineRequire = Component$3;
exports.ToDoList = ToDoList;
exports.useTheme = useTheme;
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
@@ -33,10 +33,9 @@ function Component() {
}
function useIsDarkMode() {
const [isDarkMode, setIsDarkMode] = (0, _react.useState)(0);
const [isDarkMode] = (0, _react.useState)(false);
(0, _react.useEffect)(function useEffectCreate() {// Here is where we may listen to a "theme" event...
}, []);
return isDarkMode;
}
//# sourceMappingURL=ComponentWithCustomHook.js.map
//# sourceURL=ComponentWithCustomHook.js
//# sourceMappingURL=ComponentWithCustomHook.js.map
@@ -1 +1 @@
{"version":3,"sources":["ComponentWithCustomHook.js"],"names":["Component","count","setCount","isDarkMode","useIsDarkMode","handleClick","setIsDarkMode","useEffectCreate"],"mappings":";;;;;;;AASA;;;;;;AATA;;;;;;;;AAWO,SAASA,SAAT,GAAqB;AAC1B,QAAM,CAACC,KAAD,EAAQC,QAAR,IAAoB,qBAAS,CAAT,CAA1B;AACA,QAAMC,UAAU,GAAGC,aAAa,EAAhC;AAEA,wBAAU,MAAM,CACd;AACD,GAFD,EAEG,EAFH;;AAIA,QAAMC,WAAW,GAAG,MAAMH,QAAQ,CAACD,KAAK,GAAG,CAAT,CAAlC;;AAEA,sBACE,yEACE,yDAAiBE,UAAjB,CADF,eAEE,qDAAaF,KAAb,CAFF,eAGE;AAAQ,IAAA,OAAO,EAAEI;AAAjB,oBAHF,CADF;AAOD;;AAED,SAASD,aAAT,GAAyB;AACvB,QAAM,CAACD,UAAD,EAAaG,aAAb,IAA8B,qBAAS,CAAT,CAApC;AAEA,wBAAU,SAASC,eAAT,GAA2B,CACnC;AACD,GAFD,EAEG,EAFH;AAIA,SAAOJ,UAAP;AACD","sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @flow\n */\n\nimport React, {useEffect, useState} from 'react';\n\nexport function Component() {\n const [count, setCount] = useState(0);\n const isDarkMode = useIsDarkMode();\n\n useEffect(() => {\n // ...\n }, []);\n\n const handleClick = () => setCount(count + 1);\n\n return (\n <>\n <div>Dark mode? {isDarkMode}</div>\n <div>Count: {count}</div>\n <button onClick={handleClick}>Update count</button>\n </>\n );\n}\n\nfunction useIsDarkMode() {\n const [isDarkMode, setIsDarkMode] = useState(0);\n\n useEffect(function useEffectCreate() {\n // Here is where we may listen to a \"theme\" event...\n }, []);\n\n return isDarkMode;\n}"]}
{"version":3,"sources":["ComponentWithCustomHook.js"],"names":["Component","count","setCount","isDarkMode","useIsDarkMode","handleClick","useEffectCreate"],"mappings":";;;;;;;AASA;;;;;;AATA;;;;;;;;AAWO,SAASA,SAAT,GAAqB;AAC1B,QAAM,CAACC,KAAD,EAAQC,QAAR,IAAoB,qBAAS,CAAT,CAA1B;AACA,QAAMC,UAAU,GAAGC,aAAa,EAAhC;AAEA,wBAAU,MAAM,CACd;AACD,GAFD,EAEG,EAFH;;AAIA,QAAMC,WAAW,GAAG,MAAMH,QAAQ,CAACD,KAAK,GAAG,CAAT,CAAlC;;AAEA,sBACE,yEACE,yDAAiBE,UAAjB,CADF,eAEE,qDAAaF,KAAb,CAFF,eAGE;AAAQ,IAAA,OAAO,EAAEI;AAAjB,oBAHF,CADF;AAOD;;AAED,SAASD,aAAT,GAAyB;AACvB,QAAM,CAACD,UAAD,IAAe,qBAAS,KAAT,CAArB;AAEA,wBAAU,SAASG,eAAT,GAA2B,CACnC;AACD,GAFD,EAEG,EAFH;AAIA,SAAOH,UAAP;AACD","sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @flow\n */\n\nimport React, {useEffect, useState} from 'react';\n\nexport function Component() {\n const [count, setCount] = useState(0);\n const isDarkMode = useIsDarkMode();\n\n useEffect(() => {\n // ...\n }, []);\n\n const handleClick = () => setCount(count + 1);\n\n return (\n <>\n <div>Dark mode? {isDarkMode}</div>\n <div>Count: {count}</div>\n <button onClick={handleClick}>Update count</button>\n </>\n );\n}\n\nfunction useIsDarkMode() {\n const [isDarkMode] = useState(false);\n\n useEffect(function useEffectCreate() {\n // Here is where we may listen to a \"theme\" event...\n }, []);\n\n return isDarkMode;\n}\n"]}
@@ -23,5 +23,4 @@ function Component() {
const theme = (0, _useTheme.default)();
return /*#__PURE__*/_react.default.createElement("div", null, "theme: ", theme);
}
//# sourceMappingURL=ComponentWithExternalCustomHooks.js.map
//# sourceURL=ComponentWithExternalCustomHooks.js
//# sourceMappingURL=ComponentWithExternalCustomHooks.js.map
@@ -25,5 +25,4 @@ function Component() {
onClick: () => setCount(count + 1)
}, "Click me"));
}
//# sourceMappingURL=Example.js.map
//# sourceURL=Example.js
//# sourceMappingURL=Example.js.map
@@ -18,5 +18,4 @@ function Component() {
return count;
}
//# sourceMappingURL=InlineRequire.js.map
//# sourceURL=InlineRequire.js
//# sourceMappingURL=InlineRequire.js.map
@@ -103,5 +103,4 @@ function List(props) {
toggleItem: toggleItem
}))));
}
//# sourceMappingURL=ToDoList.js.map
//# sourceURL=ToDoList.js
//# sourceMappingURL=ToDoList.js.map
@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "ComponentWithCustomHook", {
enumerable: true,
get: function () {
return _ComponentWithCustomHook.Component;
}
});
Object.defineProperty(exports, "ComponentWithExternalCustomHooks", {
enumerable: true,
get: function () {
return _ComponentWithExternalCustomHooks.Component;
}
});
Object.defineProperty(exports, "Example", {
enumerable: true,
get: function () {
return _Example.Component;
}
});
Object.defineProperty(exports, "InlineRequire", {
enumerable: true,
get: function () {
return _InlineRequire.Component;
}
});
Object.defineProperty(exports, "useTheme", {
enumerable: true,
get: function () {
return _useTheme.default;
}
});
exports.ToDoList = void 0;
var _ComponentWithCustomHook = require("./ComponentWithCustomHook");
var _ComponentWithExternalCustomHooks = require("./ComponentWithExternalCustomHooks");
var _Example = require("./Example");
var _InlineRequire = require("./InlineRequire");
var ToDoList = _interopRequireWildcard(require("./ToDoList"));
exports.ToDoList = ToDoList;
var _useTheme = _interopRequireDefault(require("./useTheme"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA;;AACA;;AACA;;AACA;;AACA;;;;AAEA","sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @flow\n */\n\nexport {Component as ComponentWithCustomHook} from './ComponentWithCustomHook';\nexport {Component as ComponentWithExternalCustomHooks} from './ComponentWithExternalCustomHooks';\nexport {Component as Example} from './Example';\nexport {Component as InlineRequire} from './InlineRequire';\nimport * as ToDoList from './ToDoList';\nexport {ToDoList};\nexport {default as useTheme} from './useTheme';\n"]}
@@ -24,5 +24,4 @@ function useTheme() {
(0, _react.useDebugValue)(theme);
return theme;
}
//# sourceMappingURL=useTheme.js.map
//# sourceURL=useTheme.js
//# sourceMappingURL=useTheme.js.map
@@ -33,10 +33,9 @@ function Component() {
}
function useIsDarkMode() {
const [isDarkMode, setIsDarkMode] = (0, _react.useState)(0);
const [isDarkMode] = (0, _react.useState)(false);
(0, _react.useEffect)(function useEffectCreate() {// Here is where we may listen to a "theme" event...
}, []);
return isDarkMode;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkNvbXBvbmVudFdpdGhDdXN0b21Ib29rLmpzIl0sIm5hbWVzIjpbIkNvbXBvbmVudCIsImNvdW50Iiwic2V0Q291bnQiLCJpc0RhcmtNb2RlIiwidXNlSXNEYXJrTW9kZSIsImhhbmRsZUNsaWNrIiwic2V0SXNEYXJrTW9kZSIsInVzZUVmZmVjdENyZWF0ZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQVNBOzs7Ozs7QUFUQTs7Ozs7Ozs7QUFXTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU0sQ0FBQ0MsS0FBRCxFQUFRQyxRQUFSLElBQW9CLHFCQUFTLENBQVQsQ0FBMUI7QUFDQSxRQUFNQyxVQUFVLEdBQUdDLGFBQWEsRUFBaEM7QUFFQSx3QkFBVSxNQUFNLENBQ2Q7QUFDRCxHQUZELEVBRUcsRUFGSDs7QUFJQSxRQUFNQyxXQUFXLEdBQUcsTUFBTUgsUUFBUSxDQUFDRCxLQUFLLEdBQUcsQ0FBVCxDQUFsQzs7QUFFQSxzQkFDRSx5RUFDRSx5REFBaUJFLFVBQWpCLENBREYsZUFFRSxxREFBYUYsS0FBYixDQUZGLGVBR0U7QUFBUSxJQUFBLE9BQU8sRUFBRUk7QUFBakIsb0JBSEYsQ0FERjtBQU9EOztBQUVELFNBQVNELGFBQVQsR0FBeUI7QUFDdkIsUUFBTSxDQUFDRCxVQUFELEVBQWFHLGFBQWIsSUFBOEIscUJBQVMsQ0FBVCxDQUFwQztBQUVBLHdCQUFVLFNBQVNDLGVBQVQsR0FBMkIsQ0FDbkM7QUFDRCxHQUZELEVBRUcsRUFGSDtBQUlBLFNBQU9KLFVBQVA7QUFDRCIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQ29weXJpZ2h0IChjKSBGYWNlYm9vaywgSW5jLiBhbmQgaXRzIGFmZmlsaWF0ZXMuXG4gKlxuICogVGhpcyBzb3VyY2UgY29kZSBpcyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIGxpY2Vuc2UgZm91bmQgaW4gdGhlXG4gKiBMSUNFTlNFIGZpbGUgaW4gdGhlIHJvb3QgZGlyZWN0b3J5IG9mIHRoaXMgc291cmNlIHRyZWUuXG4gKlxuICogQGZsb3dcbiAqL1xuXG5pbXBvcnQgUmVhY3QsIHt1c2VFZmZlY3QsIHVzZVN0YXRlfSBmcm9tICdyZWFjdCc7XG5cbmV4cG9ydCBmdW5jdGlvbiBDb21wb25lbnQoKSB7XG4gIGNvbnN0IFtjb3VudCwgc2V0Q291bnRdID0gdXNlU3RhdGUoMCk7XG4gIGNvbnN0IGlzRGFya01vZGUgPSB1c2VJc0RhcmtNb2RlKCk7XG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICAvLyAuLi5cbiAgfSwgW10pO1xuXG4gIGNvbnN0IGhhbmRsZUNsaWNrID0gKCkgPT4gc2V0Q291bnQoY291bnQgKyAxKTtcblxuICByZXR1cm4gKFxuICAgIDw+XG4gICAgICA8ZGl2PkRhcmsgbW9kZT8ge2lzRGFya01vZGV9PC9kaXY+XG4gICAgICA8ZGl2PkNvdW50OiB7Y291bnR9PC9kaXY+XG4gICAgICA8YnV0dG9uIG9uQ2xpY2s9e2hhbmRsZUNsaWNrfT5VcGRhdGUgY291bnQ8L2J1dHRvbj5cbiAgICA8Lz5cbiAgKTtcbn1cblxuZnVuY3Rpb24gdXNlSXNEYXJrTW9kZSgpIHtcbiAgY29uc3QgW2lzRGFya01vZGUsIHNldElzRGFya01vZGVdID0gdXNlU3RhdGUoMCk7XG5cbiAgdXNlRWZmZWN0KGZ1bmN0aW9uIHVzZUVmZmVjdENyZWF0ZSgpIHtcbiAgICAvLyBIZXJlIGlzIHdoZXJlIHdlIG1heSBsaXN0ZW4gdG8gYSBcInRoZW1lXCIgZXZlbnQuLi5cbiAgfSwgW10pO1xuXG4gIHJldHVybiBpc0RhcmtNb2RlO1xufSJdfQ==
//# sourceURL=ComponentWithCustomHook.js
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkNvbXBvbmVudFdpdGhDdXN0b21Ib29rLmpzIl0sIm5hbWVzIjpbIkNvbXBvbmVudCIsImNvdW50Iiwic2V0Q291bnQiLCJpc0RhcmtNb2RlIiwidXNlSXNEYXJrTW9kZSIsImhhbmRsZUNsaWNrIiwidXNlRWZmZWN0Q3JlYXRlIl0sIm1hcHBpbmdzIjoiOzs7Ozs7O0FBU0E7Ozs7OztBQVRBOzs7Ozs7OztBQVdPLFNBQVNBLFNBQVQsR0FBcUI7QUFDMUIsUUFBTSxDQUFDQyxLQUFELEVBQVFDLFFBQVIsSUFBb0IscUJBQVMsQ0FBVCxDQUExQjtBQUNBLFFBQU1DLFVBQVUsR0FBR0MsYUFBYSxFQUFoQztBQUVBLHdCQUFVLE1BQU0sQ0FDZDtBQUNELEdBRkQsRUFFRyxFQUZIOztBQUlBLFFBQU1DLFdBQVcsR0FBRyxNQUFNSCxRQUFRLENBQUNELEtBQUssR0FBRyxDQUFULENBQWxDOztBQUVBLHNCQUNFLHlFQUNFLHlEQUFpQkUsVUFBakIsQ0FERixlQUVFLHFEQUFhRixLQUFiLENBRkYsZUFHRTtBQUFRLElBQUEsT0FBTyxFQUFFSTtBQUFqQixvQkFIRixDQURGO0FBT0Q7O0FBRUQsU0FBU0QsYUFBVCxHQUF5QjtBQUN2QixRQUFNLENBQUNELFVBQUQsSUFBZSxxQkFBUyxLQUFULENBQXJCO0FBRUEsd0JBQVUsU0FBU0csZUFBVCxHQUEyQixDQUNuQztBQUNELEdBRkQsRUFFRyxFQUZIO0FBSUEsU0FBT0gsVUFBUDtBQUNEIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb3B5cmlnaHQgKGMpIEZhY2Vib29rLCBJbmMuIGFuZCBpdHMgYWZmaWxpYXRlcy5cbiAqXG4gKiBUaGlzIHNvdXJjZSBjb2RlIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZSBmb3VuZCBpbiB0aGVcbiAqIExJQ0VOU0UgZmlsZSBpbiB0aGUgcm9vdCBkaXJlY3Rvcnkgb2YgdGhpcyBzb3VyY2UgdHJlZS5cbiAqXG4gKiBAZmxvd1xuICovXG5cbmltcG9ydCBSZWFjdCwge3VzZUVmZmVjdCwgdXNlU3RhdGV9IGZyb20gJ3JlYWN0JztcblxuZXhwb3J0IGZ1bmN0aW9uIENvbXBvbmVudCgpIHtcbiAgY29uc3QgW2NvdW50LCBzZXRDb3VudF0gPSB1c2VTdGF0ZSgwKTtcbiAgY29uc3QgaXNEYXJrTW9kZSA9IHVzZUlzRGFya01vZGUoKTtcblxuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIC8vIC4uLlxuICB9LCBbXSk7XG5cbiAgY29uc3QgaGFuZGxlQ2xpY2sgPSAoKSA9PiBzZXRDb3VudChjb3VudCArIDEpO1xuXG4gIHJldHVybiAoXG4gICAgPD5cbiAgICAgIDxkaXY+RGFyayBtb2RlPyB7aXNEYXJrTW9kZX08L2Rpdj5cbiAgICAgIDxkaXY+Q291bnQ6IHtjb3VudH08L2Rpdj5cbiAgICAgIDxidXR0b24gb25DbGljaz17aGFuZGxlQ2xpY2t9PlVwZGF0ZSBjb3VudDwvYnV0dG9uPlxuICAgIDwvPlxuICApO1xufVxuXG5mdW5jdGlvbiB1c2VJc0RhcmtNb2RlKCkge1xuICBjb25zdCBbaXNEYXJrTW9kZV0gPSB1c2VTdGF0ZShmYWxzZSk7XG5cbiAgdXNlRWZmZWN0KGZ1bmN0aW9uIHVzZUVmZmVjdENyZWF0ZSgpIHtcbiAgICAvLyBIZXJlIGlzIHdoZXJlIHdlIG1heSBsaXN0ZW4gdG8gYSBcInRoZW1lXCIgZXZlbnQuLi5cbiAgfSwgW10pO1xuXG4gIHJldHVybiBpc0RhcmtNb2RlO1xufVxuIl19
@@ -23,5 +23,4 @@ function Component() {
const theme = (0, _useTheme.default)();
return /*#__PURE__*/_react.default.createElement("div", null, "theme: ", theme);
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkNvbXBvbmVudFdpdGhFeHRlcm5hbEN1c3RvbUhvb2tzLmpzIl0sIm5hbWVzIjpbIkNvbXBvbmVudCIsInRoZW1lIl0sIm1hcHBpbmdzIjoiOzs7Ozs7O0FBU0E7O0FBQ0E7Ozs7QUFWQTs7Ozs7Ozs7QUFZTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU1DLEtBQUssR0FBRyx3QkFBZDtBQUVBLHNCQUFPLHFEQUFhQSxLQUFiLENBQVA7QUFDRCIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQ29weXJpZ2h0IChjKSBGYWNlYm9vaywgSW5jLiBhbmQgaXRzIGFmZmlsaWF0ZXMuXG4gKlxuICogVGhpcyBzb3VyY2UgY29kZSBpcyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIGxpY2Vuc2UgZm91bmQgaW4gdGhlXG4gKiBMSUNFTlNFIGZpbGUgaW4gdGhlIHJvb3QgZGlyZWN0b3J5IG9mIHRoaXMgc291cmNlIHRyZWUuXG4gKlxuICogQGZsb3dcbiAqL1xuXG5pbXBvcnQgUmVhY3QgZnJvbSAncmVhY3QnO1xuaW1wb3J0IHVzZVRoZW1lIGZyb20gJy4vdXNlVGhlbWUnO1xuXG5leHBvcnQgZnVuY3Rpb24gQ29tcG9uZW50KCkge1xuICBjb25zdCB0aGVtZSA9IHVzZVRoZW1lKCk7XG5cbiAgcmV0dXJuIDxkaXY+dGhlbWU6IHt0aGVtZX08L2Rpdj47XG59XG4iXX0=
//# sourceURL=ComponentWithExternalCustomHooks.js
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkNvbXBvbmVudFdpdGhFeHRlcm5hbEN1c3RvbUhvb2tzLmpzIl0sIm5hbWVzIjpbIkNvbXBvbmVudCIsInRoZW1lIl0sIm1hcHBpbmdzIjoiOzs7Ozs7O0FBU0E7O0FBQ0E7Ozs7QUFWQTs7Ozs7Ozs7QUFZTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU1DLEtBQUssR0FBRyx3QkFBZDtBQUVBLHNCQUFPLHFEQUFhQSxLQUFiLENBQVA7QUFDRCIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQ29weXJpZ2h0IChjKSBGYWNlYm9vaywgSW5jLiBhbmQgaXRzIGFmZmlsaWF0ZXMuXG4gKlxuICogVGhpcyBzb3VyY2UgY29kZSBpcyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIGxpY2Vuc2UgZm91bmQgaW4gdGhlXG4gKiBMSUNFTlNFIGZpbGUgaW4gdGhlIHJvb3QgZGlyZWN0b3J5IG9mIHRoaXMgc291cmNlIHRyZWUuXG4gKlxuICogQGZsb3dcbiAqL1xuXG5pbXBvcnQgUmVhY3QgZnJvbSAncmVhY3QnO1xuaW1wb3J0IHVzZVRoZW1lIGZyb20gJy4vdXNlVGhlbWUnO1xuXG5leHBvcnQgZnVuY3Rpb24gQ29tcG9uZW50KCkge1xuICBjb25zdCB0aGVtZSA9IHVzZVRoZW1lKCk7XG5cbiAgcmV0dXJuIDxkaXY+dGhlbWU6IHt0aGVtZX08L2Rpdj47XG59XG4iXX0=
@@ -25,5 +25,4 @@ function Component() {
onClick: () => setCount(count + 1)
}, "Click me"));
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkV4YW1wbGUuanMiXSwibmFtZXMiOlsiQ29tcG9uZW50IiwiY291bnQiLCJzZXRDb3VudCJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQVNBOzs7Ozs7QUFUQTs7Ozs7Ozs7QUFXTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU0sQ0FBQ0MsS0FBRCxFQUFRQyxRQUFSLElBQW9CLHFCQUFTLENBQVQsQ0FBMUI7QUFFQSxzQkFDRSx1REFDRSx3REFBZ0JELEtBQWhCLFdBREYsZUFFRTtBQUFRLElBQUEsT0FBTyxFQUFFLE1BQU1DLFFBQVEsQ0FBQ0QsS0FBSyxHQUFHLENBQVQ7QUFBL0IsZ0JBRkYsQ0FERjtBQU1EIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb3B5cmlnaHQgKGMpIEZhY2Vib29rLCBJbmMuIGFuZCBpdHMgYWZmaWxpYXRlcy5cbiAqXG4gKiBUaGlzIHNvdXJjZSBjb2RlIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZSBmb3VuZCBpbiB0aGVcbiAqIExJQ0VOU0UgZmlsZSBpbiB0aGUgcm9vdCBkaXJlY3Rvcnkgb2YgdGhpcyBzb3VyY2UgdHJlZS5cbiAqXG4gKiBAZmxvd1xuICovXG5cbmltcG9ydCBSZWFjdCwge3VzZVN0YXRlfSBmcm9tICdyZWFjdCc7XG5cbmV4cG9ydCBmdW5jdGlvbiBDb21wb25lbnQoKSB7XG4gIGNvbnN0IFtjb3VudCwgc2V0Q291bnRdID0gdXNlU3RhdGUoMCk7XG5cbiAgcmV0dXJuIChcbiAgICA8ZGl2PlxuICAgICAgPHA+WW91IGNsaWNrZWQge2NvdW50fSB0aW1lczwvcD5cbiAgICAgIDxidXR0b24gb25DbGljaz17KCkgPT4gc2V0Q291bnQoY291bnQgKyAxKX0+Q2xpY2sgbWU8L2J1dHRvbj5cbiAgICA8L2Rpdj5cbiAgKTtcbn1cbiJdfQ==
//# sourceURL=Example.js
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkV4YW1wbGUuanMiXSwibmFtZXMiOlsiQ29tcG9uZW50IiwiY291bnQiLCJzZXRDb3VudCJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQVNBOzs7Ozs7QUFUQTs7Ozs7Ozs7QUFXTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU0sQ0FBQ0MsS0FBRCxFQUFRQyxRQUFSLElBQW9CLHFCQUFTLENBQVQsQ0FBMUI7QUFFQSxzQkFDRSx1REFDRSx3REFBZ0JELEtBQWhCLFdBREYsZUFFRTtBQUFRLElBQUEsT0FBTyxFQUFFLE1BQU1DLFFBQVEsQ0FBQ0QsS0FBSyxHQUFHLENBQVQ7QUFBL0IsZ0JBRkYsQ0FERjtBQU1EIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb3B5cmlnaHQgKGMpIEZhY2Vib29rLCBJbmMuIGFuZCBpdHMgYWZmaWxpYXRlcy5cbiAqXG4gKiBUaGlzIHNvdXJjZSBjb2RlIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZSBmb3VuZCBpbiB0aGVcbiAqIExJQ0VOU0UgZmlsZSBpbiB0aGUgcm9vdCBkaXJlY3Rvcnkgb2YgdGhpcyBzb3VyY2UgdHJlZS5cbiAqXG4gKiBAZmxvd1xuICovXG5cbmltcG9ydCBSZWFjdCwge3VzZVN0YXRlfSBmcm9tICdyZWFjdCc7XG5cbmV4cG9ydCBmdW5jdGlvbiBDb21wb25lbnQoKSB7XG4gIGNvbnN0IFtjb3VudCwgc2V0Q291bnRdID0gdXNlU3RhdGUoMCk7XG5cbiAgcmV0dXJuIChcbiAgICA8ZGl2PlxuICAgICAgPHA+WW91IGNsaWNrZWQge2NvdW50fSB0aW1lczwvcD5cbiAgICAgIDxidXR0b24gb25DbGljaz17KCkgPT4gc2V0Q291bnQoY291bnQgKyAxKX0+Q2xpY2sgbWU8L2J1dHRvbj5cbiAgICA8L2Rpdj5cbiAgKTtcbn1cbiJdfQ==
@@ -18,5 +18,4 @@ function Component() {
return count;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIklubGluZVJlcXVpcmUuanMiXSwibmFtZXMiOlsiQ29tcG9uZW50IiwiY291bnQiLCJyZXF1aXJlIiwidXNlU3RhdGUiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7QUFBQTs7Ozs7Ozs7QUFTTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU0sQ0FBQ0MsS0FBRCxJQUFVQyxPQUFPLENBQUMsT0FBRCxDQUFQLENBQWlCQyxRQUFqQixDQUEwQixDQUExQixDQUFoQjs7QUFFQSxTQUFPRixLQUFQO0FBQ0QiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvcHlyaWdodCAoYykgRmFjZWJvb2ssIEluYy4gYW5kIGl0cyBhZmZpbGlhdGVzLlxuICpcbiAqIFRoaXMgc291cmNlIGNvZGUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlIGZvdW5kIGluIHRoZVxuICogTElDRU5TRSBmaWxlIGluIHRoZSByb290IGRpcmVjdG9yeSBvZiB0aGlzIHNvdXJjZSB0cmVlLlxuICpcbiAqIEBmbG93XG4gKi9cblxuZXhwb3J0IGZ1bmN0aW9uIENvbXBvbmVudCgpIHtcbiAgY29uc3QgW2NvdW50XSA9IHJlcXVpcmUoJ3JlYWN0JykudXNlU3RhdGUoMCk7XG5cbiAgcmV0dXJuIGNvdW50O1xufVxuIl19
//# sourceURL=InlineRequire.js
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIklubGluZVJlcXVpcmUuanMiXSwibmFtZXMiOlsiQ29tcG9uZW50IiwiY291bnQiLCJyZXF1aXJlIiwidXNlU3RhdGUiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7QUFBQTs7Ozs7Ozs7QUFTTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU0sQ0FBQ0MsS0FBRCxJQUFVQyxPQUFPLENBQUMsT0FBRCxDQUFQLENBQWlCQyxRQUFqQixDQUEwQixDQUExQixDQUFoQjs7QUFFQSxTQUFPRixLQUFQO0FBQ0QiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvcHlyaWdodCAoYykgRmFjZWJvb2ssIEluYy4gYW5kIGl0cyBhZmZpbGlhdGVzLlxuICpcbiAqIFRoaXMgc291cmNlIGNvZGUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlIGZvdW5kIGluIHRoZVxuICogTElDRU5TRSBmaWxlIGluIHRoZSByb290IGRpcmVjdG9yeSBvZiB0aGlzIHNvdXJjZSB0cmVlLlxuICpcbiAqIEBmbG93XG4gKi9cblxuZXhwb3J0IGZ1bmN0aW9uIENvbXBvbmVudCgpIHtcbiAgY29uc3QgW2NvdW50XSA9IHJlcXVpcmUoJ3JlYWN0JykudXNlU3RhdGUoMCk7XG5cbiAgcmV0dXJuIGNvdW50O1xufVxuIl19
File diff suppressed because one or more lines are too long
@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "ComponentWithCustomHook", {
enumerable: true,
get: function () {
return _ComponentWithCustomHook.Component;
}
});
Object.defineProperty(exports, "ComponentWithExternalCustomHooks", {
enumerable: true,
get: function () {
return _ComponentWithExternalCustomHooks.Component;
}
});
Object.defineProperty(exports, "Example", {
enumerable: true,
get: function () {
return _Example.Component;
}
});
Object.defineProperty(exports, "InlineRequire", {
enumerable: true,
get: function () {
return _InlineRequire.Component;
}
});
Object.defineProperty(exports, "useTheme", {
enumerable: true,
get: function () {
return _useTheme.default;
}
});
exports.ToDoList = void 0;
var _ComponentWithCustomHook = require("./ComponentWithCustomHook");
var _ComponentWithExternalCustomHooks = require("./ComponentWithExternalCustomHooks");
var _Example = require("./Example");
var _InlineRequire = require("./InlineRequire");
var ToDoList = _interopRequireWildcard(require("./ToDoList"));
exports.ToDoList = ToDoList;
var _useTheme = _interopRequireDefault(require("./useTheme"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImluZGV4LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFTQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFDQTs7OztBQUVBIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb3B5cmlnaHQgKGMpIEZhY2Vib29rLCBJbmMuIGFuZCBpdHMgYWZmaWxpYXRlcy5cbiAqXG4gKiBUaGlzIHNvdXJjZSBjb2RlIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZSBmb3VuZCBpbiB0aGVcbiAqIExJQ0VOU0UgZmlsZSBpbiB0aGUgcm9vdCBkaXJlY3Rvcnkgb2YgdGhpcyBzb3VyY2UgdHJlZS5cbiAqXG4gKiBAZmxvd1xuICovXG5cbmV4cG9ydCB7Q29tcG9uZW50IGFzIENvbXBvbmVudFdpdGhDdXN0b21Ib29rfSBmcm9tICcuL0NvbXBvbmVudFdpdGhDdXN0b21Ib29rJztcbmV4cG9ydCB7Q29tcG9uZW50IGFzIENvbXBvbmVudFdpdGhFeHRlcm5hbEN1c3RvbUhvb2tzfSBmcm9tICcuL0NvbXBvbmVudFdpdGhFeHRlcm5hbEN1c3RvbUhvb2tzJztcbmV4cG9ydCB7Q29tcG9uZW50IGFzIEV4YW1wbGV9IGZyb20gJy4vRXhhbXBsZSc7XG5leHBvcnQge0NvbXBvbmVudCBhcyBJbmxpbmVSZXF1aXJlfSBmcm9tICcuL0lubGluZVJlcXVpcmUnO1xuaW1wb3J0ICogYXMgVG9Eb0xpc3QgZnJvbSAnLi9Ub0RvTGlzdCc7XG5leHBvcnQge1RvRG9MaXN0fTtcbmV4cG9ydCB7ZGVmYXVsdCBhcyB1c2VUaGVtZX0gZnJvbSAnLi91c2VUaGVtZSc7XG4iXX0=
@@ -24,5 +24,4 @@ function useTheme() {
(0, _react.useDebugValue)(theme);
return theme;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInVzZVRoZW1lLmpzIl0sIm5hbWVzIjpbIlRoZW1lQ29udGV4dCIsInVzZVRoZW1lIiwidGhlbWUiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O0FBU0E7O0FBVEE7Ozs7Ozs7O0FBV08sTUFBTUEsWUFBWSxnQkFBRywwQkFBYyxRQUFkLENBQXJCOzs7QUFFUSxTQUFTQyxRQUFULEdBQW9CO0FBQ2pDLFFBQU1DLEtBQUssR0FBRyx1QkFBV0YsWUFBWCxDQUFkO0FBQ0EsNEJBQWNFLEtBQWQ7QUFDQSxTQUFPQSxLQUFQO0FBQ0QiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvcHlyaWdodCAoYykgRmFjZWJvb2ssIEluYy4gYW5kIGl0cyBhZmZpbGlhdGVzLlxuICpcbiAqIFRoaXMgc291cmNlIGNvZGUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlIGZvdW5kIGluIHRoZVxuICogTElDRU5TRSBmaWxlIGluIHRoZSByb290IGRpcmVjdG9yeSBvZiB0aGlzIHNvdXJjZSB0cmVlLlxuICpcbiAqIEBmbG93XG4gKi9cblxuaW1wb3J0IHtjcmVhdGVDb250ZXh0LCB1c2VDb250ZXh0LCB1c2VEZWJ1Z1ZhbHVlfSBmcm9tICdyZWFjdCc7XG5cbmV4cG9ydCBjb25zdCBUaGVtZUNvbnRleHQgPSBjcmVhdGVDb250ZXh0KCdicmlnaHQnKTtcblxuZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gdXNlVGhlbWUoKSB7XG4gIGNvbnN0IHRoZW1lID0gdXNlQ29udGV4dChUaGVtZUNvbnRleHQpO1xuICB1c2VEZWJ1Z1ZhbHVlKHRoZW1lKTtcbiAgcmV0dXJuIHRoZW1lO1xufVxuIl19
//# sourceURL=useTheme.js
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInVzZVRoZW1lLmpzIl0sIm5hbWVzIjpbIlRoZW1lQ29udGV4dCIsInVzZVRoZW1lIiwidGhlbWUiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O0FBU0E7O0FBVEE7Ozs7Ozs7O0FBV08sTUFBTUEsWUFBWSxnQkFBRywwQkFBYyxRQUFkLENBQXJCOzs7QUFFUSxTQUFTQyxRQUFULEdBQW9CO0FBQ2pDLFFBQU1DLEtBQUssR0FBRyx1QkFBV0YsWUFBWCxDQUFkO0FBQ0EsNEJBQWNFLEtBQWQ7QUFDQSxTQUFPQSxLQUFQO0FBQ0QiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvcHlyaWdodCAoYykgRmFjZWJvb2ssIEluYy4gYW5kIGl0cyBhZmZpbGlhdGVzLlxuICpcbiAqIFRoaXMgc291cmNlIGNvZGUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlIGZvdW5kIGluIHRoZVxuICogTElDRU5TRSBmaWxlIGluIHRoZSByb290IGRpcmVjdG9yeSBvZiB0aGlzIHNvdXJjZSB0cmVlLlxuICpcbiAqIEBmbG93XG4gKi9cblxuaW1wb3J0IHtjcmVhdGVDb250ZXh0LCB1c2VDb250ZXh0LCB1c2VEZWJ1Z1ZhbHVlfSBmcm9tICdyZWFjdCc7XG5cbmV4cG9ydCBjb25zdCBUaGVtZUNvbnRleHQgPSBjcmVhdGVDb250ZXh0KCdicmlnaHQnKTtcblxuZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gdXNlVGhlbWUoKSB7XG4gIGNvbnN0IHRoZW1lID0gdXNlQ29udGV4dChUaGVtZUNvbnRleHQpO1xuICB1c2VEZWJ1Z1ZhbHVlKHRoZW1lKTtcbiAgcmV0dXJuIHRoZW1lO1xufVxuIl19
@@ -0,0 +1,16 @@
/**
* 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
*/
export {Component as ComponentWithCustomHook} from './ComponentWithCustomHook';
export {Component as ComponentWithExternalCustomHooks} from './ComponentWithExternalCustomHooks';
export {Component as Example} from './Example';
export {Component as InlineRequire} from './InlineRequire';
import * as ToDoList from './ToDoList';
export {ToDoList};
export {default as useTheme} from './useTheme';
@@ -33,25 +33,18 @@ describe('parseHookNames', () => {
// Jest (jest-runner?) configures Errors to automatically account for source maps.
// This changes behavior between our tests and the browser.
// To "fix" this, clear the prepareStackTrace() method on the Error object.
delete Error.prepareStackTrace;
// Ideally we would clear the prepareStackTrace() method on the Error object,
// but Node falls back to looking for it on the main context's Error constructor,
// which may still be patched.
// To ensure we get the default behavior, override prepareStackTrace ourselves.
// NOTE: prepareStackTrace is called from the error.stack getter, but the getter
// has a recursion breaker which falls back to the default behavior.
Error.prepareStackTrace = (error, trace) => {
return error.stack;
};
fetchMock.mockIf(/.+$/, request => {
const {resolve} = require('path');
const url = request.url;
if (url.endsWith('js.map')) {
// Source maps are relative URLs (e.g. "path/to/Exmaple.js" specifies "Exmaple.js.map").
const sourceMapURL = resolve(
__dirname,
'__source__',
'__compiled__',
'external',
url,
);
return Promise.resolve(requireText(sourceMapURL, 'utf8'));
} else {
return Promise.resolve(requireText(url, 'utf8'));
}
return Promise.resolve(requireText(request.url, 'utf8'));
});
// Mock out portion of browser API used by parseHookNames to initialize "source-map".
@@ -185,10 +178,10 @@ describe('parseHookNames', () => {
]);
});
describe('inline and external source maps', () => {
describe('inline, external and bundle source maps', () => {
it('should work for simple components', async () => {
async function test(path) {
const Component = require(path).Component;
async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState
@@ -198,11 +191,12 @@ describe('parseHookNames', () => {
await test('./__source__/Example'); // original source (uncompiled)
await test('./__source__/__compiled__/inline/Example'); // inline source map
await test('./__source__/__compiled__/external/Example'); // external source map
await test('./__source__/__compiled__/bundle/index', 'Example'); // bundle source map
});
it('should work with more complex files and components', async () => {
async function test(path) {
const components = require(path);
async function test(path, name = undefined) {
const components = name != null ? require(path)[name] : require(path);
let hookNames = await getHookNamesForComponent(components.List);
expectHookNamesToEqual(hookNames, [
@@ -228,11 +222,12 @@ describe('parseHookNames', () => {
await test('./__source__/ToDoList'); // original source (uncompiled)
await test('./__source__/__compiled__/inline/ToDoList'); // inline source map
await test('./__source__/__compiled__/external/ToDoList'); // external source map
await test('./__source__/__compiled__/bundle', 'ToDoList'); // bundle source map
});
it('should work for custom hook', async () => {
async function test(path) {
const Component = require(path).Component;
async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState()
@@ -244,11 +239,12 @@ describe('parseHookNames', () => {
await test('./__source__/ComponentWithCustomHook'); // original source (uncompiled)
await test('./__source__/__compiled__/inline/ComponentWithCustomHook'); // inline source map
await test('./__source__/__compiled__/external/ComponentWithCustomHook'); // external source map
await test('./__source__/__compiled__/bundle', 'ComponentWithCustomHook'); // bundle source map
});
it('should work for external hooks', async () => {
async function test(path) {
const Component = require(path).Component;
async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'theme', // useTheme()
@@ -265,14 +261,18 @@ describe('parseHookNames', () => {
await test(
'./__source__/__compiled__/external/ComponentWithExternalCustomHooks',
); // external source map
await test(
'./__source__/__compiled__/bundle',
'ComponentWithExternalCustomHooks',
); // bundle source map
});
// TODO Inline require (e.g. require("react").useState()) isn't supported yet.
// Maybe this isn't an important use case to support,
// since inline requires are most likely to exist in compiled source (if at all).
xit('should work for inline requires', async () => {
async function test(path) {
const Component = require(path).Component;
async function test(path, name = 'Component') {
const Component = require(path)[name];
const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, [
'count', // useState()
@@ -282,6 +282,7 @@ describe('parseHookNames', () => {
await test('./__source__/InlineRequire'); // original source (uncompiled)
await test('./__source__/__compiled__/inline/InlineRequire'); // inline source map
await test('./__source__/__compiled__/external/InlineRequire'); // external source map
await test('./__source__/__compiled__/bundle', 'InlineRequire'); // bundle source map
});
});
});
@@ -9,16 +9,23 @@ const {
} = require('fs');
const {emptyDirSync} = require('fs-extra');
const {resolve} = require('path');
const rollup = require('rollup');
const babel = require('rollup-plugin-babel');
const commonjs = require('rollup-plugin-commonjs');
const jsx = require('acorn-jsx');
const rollupResolve = require('rollup-plugin-node-resolve');
const sourceDir = resolve(__dirname, '__source__');
const buildRoot = resolve(sourceDir, '__compiled__');
const externalDir = resolve(buildRoot, 'external');
const inlineDir = resolve(buildRoot, 'inline');
const bundleDir = resolve(buildRoot, 'bundle');
// Remove previous builds
emptyDirSync(buildRoot);
mkdirSync(externalDir);
mkdirSync(inlineDir);
mkdirSync(bundleDir);
function compile(fileName) {
const code = readFileSync(resolve(sourceDir, fileName), 'utf8');
@@ -44,9 +51,7 @@ function compile(fileName) {
// Generate compiled output with external source maps
writeFileSync(
resolve(externalDir, fileName),
transformed.code +
`\n//# sourceMappingURL=${fileName}.map` +
`\n//# sourceURL=${fileName}`,
transformed.code + `\n//# sourceMappingURL=${fileName}.map`,
'utf8',
);
writeFileSync(
@@ -60,12 +65,32 @@ function compile(fileName) {
resolve(inlineDir, fileName),
transformed.code +
'\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
btoa(JSON.stringify(sourceMap)) +
`\n//# sourceURL=${fileName}`,
btoa(JSON.stringify(sourceMap)),
'utf8',
);
}
async function bundle() {
const entryFileName = resolve(sourceDir, 'index.js');
// Bundle all modules with rollup
const result = await rollup.rollup({
input: entryFileName,
acornInjectPlugins: [jsx()],
plugins: [
rollupResolve(),
commonjs(),
babel({presets: ['@babel/preset-react'], sourceMap: true}),
],
external: ['react'],
});
await result.write({
file: resolve(bundleDir, 'index.js'),
format: 'cjs',
sourcemap: true,
});
}
// Compile all files in the current directory
const entries = readdirSync(sourceDir);
entries.forEach(entry => {
@@ -74,3 +99,8 @@ entries.forEach(entry => {
compile(entry);
}
});
bundle().catch(e => {
console.error(e);
process.exit(1);
});
-8
View File
@@ -20,14 +20,6 @@ export type SourceFileASTWithHookDetails = {
source: string,
};
export type SourceMap = {|
mappings: string,
names: Array<string>,
sources: Array<string>,
sourcesContent: Array<string>,
version: number,
|};
const AST_NODE_TYPES = Object.freeze({
CALL_EXPRESSION: 'CallExpression',
MEMBER_EXPRESSION: 'MemberExpression',
+263 -150
View File
@@ -24,7 +24,7 @@ import type {
} from 'react-debug-tools/src/ReactDebugHooks';
import type {HookNames, LRUCache} from 'react-devtools-shared/src/types';
import type {Thenable} from 'shared/ReactTypes';
import type {SourceConsumer, SourceMap} from './astUtils';
import type {SourceConsumer} from './astUtils';
const SOURCE_MAP_REGEX = / ?sourceMappingURL=([^\s'"]+)/gm;
const ABSOLUTE_URL_REGEX = /^https?:\/\//i;
@@ -43,36 +43,36 @@ type HookSourceData = {|
// If no source map has been provided, this code will be the same as runtimeSourceCode.
originalSourceCode: string | null,
// Original source URL if there is a source map, or the same as runtimeSourceURL.
originalSourceURL: string | null,
// Compiled code (React components or custom hooks) containing primitive hook calls.
runtimeSourceCode: string | null,
// Same as hookSource.fileName but guaranteed to be non-null.
runtimeSourceURL: string,
// APIs from source-map for parsing source maps (if detected).
sourceConsumer: SourceConsumer | null,
// External URL of source map.
// Sources without source maps (or with inline source maps) won't have this.
sourceMapURL: string | null,
// Parsed source map object.
sourceMapContents: SourceMap | null,
|};
type CachedMetadata = {|
originalSourceAST: AST,
originalSourceCode: string,
type CachedRuntimeCodeMetadata = {|
sourceConsumer: SourceConsumer | null,
|};
// On large trees, encoding takes significant time.
// Try to reuse the already encoded strings.
const fileNameToMetadataCache: LRUCache<string, CachedMetadata> = new LRU({
const runtimeURLToMetadataCache: LRUCache<
string,
CachedRuntimeCodeMetadata,
> = new LRU({
max: 50,
dispose: (fileName: string, metadata: CachedMetadata) => {
dispose: (runtimeSourceURL: string, metadata: CachedRuntimeCodeMetadata) => {
if (__DEBUG__) {
console.log(
'fileNameToHookSourceData.dispose() Evicting cached metadata for "' +
fileName +
'"',
`runtimeURLToMetadataCache.dispose() Evicting cached metadata for "${runtimeSourceURL}"`,
);
}
@@ -83,6 +83,36 @@ const fileNameToMetadataCache: LRUCache<string, CachedMetadata> = new LRU({
},
});
type CachedSourceCodeMetadata = {|
originalSourceAST: AST,
originalSourceCode: string,
|};
const originalURLToMetadataCache: LRUCache<
string,
CachedSourceCodeMetadata,
> = new LRU({
max: 50,
dispose: (originalSourceURL: string, metadata: CachedSourceCodeMetadata) => {
if (__DEBUG__) {
console.log(
`originalURLToMetadataCache.dispose() Evicting cached metadata for "${originalSourceURL}"`,
);
}
},
});
function getLocationKey({
fileName,
lineNumber,
columnNumber,
}: HookSource): string {
if (fileName == null || lineNumber == null || columnNumber == null) {
throw Error('Hook source code location not found.');
}
return `${fileName}:${lineNumber}:${columnNumber}`;
}
export default async function parseHookNames(
hooksTree: HooksTree,
): Thenable<HookNames | null> {
@@ -97,8 +127,8 @@ export default async function parseHookNames(
console.log('parseHookNames() hooksList:', hooksList);
}
// Gather the unique set of source files to load for the built-in hooks.
const fileNameToHookSourceData: Map<string, HookSourceData> = new Map();
// Create map of unique source locations (file names plus line and column numbers) to metadata about hooks.
const locationKeyToHookSourceData: Map<string, HookSourceData> = new Map();
for (let i = 0; i < hooksList.length; i++) {
const hook = hooksList[i];
@@ -109,50 +139,47 @@ export default async function parseHookNames(
throw Error('Hook source code location not found.');
}
const fileName = hookSource.fileName;
if (fileName == null) {
throw Error('Hook source code location not found.');
} else {
if (!fileNameToHookSourceData.has(fileName)) {
const hookSourceData: HookSourceData = {
hookSource,
originalSourceAST: null,
originalSourceCode: null,
runtimeSourceCode: null,
sourceConsumer: null,
sourceMapURL: null,
sourceMapContents: null,
};
const locationKey = getLocationKey(hookSource);
if (!locationKeyToHookSourceData.has(locationKey)) {
// Can't be null because getLocationKey() would have thrown
const runtimeSourceURL = ((hookSource.fileName: any): string);
// If we've already loaded source/source map info for this file,
// we can skip reloading it (and more importantly, re-parsing it).
const metadata = fileNameToMetadataCache.get(fileName);
if (metadata != null) {
if (__DEBUG__) {
console.groupCollapsed(
'parseHookNames() Found cached metadata for file "' +
fileName +
'"',
);
console.log(metadata);
console.groupEnd();
}
const hookSourceData: HookSourceData = {
hookSource,
originalSourceAST: null,
originalSourceCode: null,
originalSourceURL: null,
runtimeSourceCode: null,
runtimeSourceURL,
sourceConsumer: null,
sourceMapURL: null,
};
hookSourceData.originalSourceAST = metadata.originalSourceAST;
hookSourceData.originalSourceCode = metadata.originalSourceCode;
hookSourceData.sourceConsumer = metadata.sourceConsumer;
// If we've already loaded the source map info for this file,
// we can skip reloading it (and more importantly, re-parsing it).
const runtimeMetadata = runtimeURLToMetadataCache.get(
hookSourceData.runtimeSourceURL,
);
if (runtimeMetadata != null) {
if (__DEBUG__) {
console.groupCollapsed(
`parseHookNames() Found cached runtime metadata for file "${hookSourceData.runtimeSourceURL}"`,
);
console.log(runtimeMetadata);
console.groupEnd();
}
fileNameToHookSourceData.set(fileName, hookSourceData);
hookSourceData.sourceConsumer = runtimeMetadata.sourceConsumer;
}
locationKeyToHookSourceData.set(locationKey, hookSourceData);
}
}
return loadSourceFiles(fileNameToHookSourceData)
.then(() => extractAndLoadSourceMaps(fileNameToHookSourceData))
.then(() => parseSourceAST(fileNameToHookSourceData))
.then(() => updateLruCache(fileNameToHookSourceData))
.then(() => findHookNames(hooksList, fileNameToHookSourceData));
return loadSourceFiles(locationKeyToHookSourceData)
.then(() => extractAndLoadSourceMaps(locationKeyToHookSourceData))
.then(() => parseSourceAST(locationKeyToHookSourceData))
.then(() => updateLruCache(locationKeyToHookSourceData))
.then(() => findHookNames(hooksList, locationKeyToHookSourceData));
}
function decodeBase64String(encoded: string): Object {
@@ -170,12 +197,31 @@ function decodeBase64String(encoded: string): Object {
}
function extractAndLoadSourceMaps(
fileNameToHookSourceData: Map<string, HookSourceData>,
locationKeyToHookSourceData: Map<string, HookSourceData>,
): Promise<*> {
const promises = [];
fileNameToHookSourceData.forEach(hookSourceData => {
if (hookSourceData.originalSourceAST !== null) {
// Use cached metadata.
// SourceMapConsumer.initialize() does nothing when running in Node (aka Jest)
// because the wasm file is automatically read from the file system
// so we can avoid triggering a warning message about this.
if (!__TEST__) {
if (__DEBUG__) {
console.log(
'extractAndLoadSourceMaps() Initializing source-map library ...',
);
}
// $FlowFixMe
const wasmMappingsURL = chrome.extension.getURL('mappings.wasm');
SourceMapConsumer.initialize({'lib/mappings.wasm': wasmMappingsURL});
}
// Deduplicate fetches, since there can be multiple location keys per source map.
const fetchPromises = new Map();
const setPromises = [];
locationKeyToHookSourceData.forEach(hookSourceData => {
if (hookSourceData.sourceConsumer != null) {
// Use cached source map consumer.
return;
}
@@ -189,9 +235,13 @@ function extractAndLoadSourceMaps(
}
} else {
for (let i = 0; i < sourceMappingURLs.length; i++) {
const {runtimeSourceURL} = hookSourceData;
const sourceMappingURL = sourceMappingURLs[i];
const index = sourceMappingURL.indexOf('base64,');
if (index >= 0) {
// TODO (named hooks) deduplicate parsing in this branch (similar to fetching in the other branch)
// since there can be multiple location keys per source map.
// Web apps like Code Sandbox embed multiple inline source maps.
// In this case, we need to loop through and find the right one.
// We may also need to trim any part of this string that isn't based64 encoded data.
@@ -211,13 +261,17 @@ function extractAndLoadSourceMaps(
// Hook source might be a URL like "https://4syus.csb.app/src/App.js"
// Parsed source map might be a partial path like "src/App.js"
const fileName = ((hookSourceData.hookSource.fileName: any): string);
const match = parsed.sources.find(
source =>
source === 'Inline Babel script' || fileName.includes(source),
source === 'Inline Babel script' ||
runtimeSourceURL.endsWith(source),
);
if (match) {
hookSourceData.sourceMapContents = parsed;
setPromises.push(
new SourceMapConsumer(parsed).then(sourceConsumer => {
hookSourceData.sourceConsumer = sourceConsumer;
}),
);
break;
}
} else {
@@ -227,7 +281,7 @@ function extractAndLoadSourceMaps(
);
}
let url = sourceMappingURLs[0].split('=')[1];
let url = sourceMappingURLs[i].split('=')[1];
if (ABSOLUTE_URL_REGEX.test(url)) {
const baseURL = url.slice(0, url.lastIndexOf('/'));
url = `${baseURL}/${url}`;
@@ -235,19 +289,38 @@ function extractAndLoadSourceMaps(
if (!isValidUrl(url)) {
throw new Error(`Invalid source map URL "${url}"`);
}
} else if (!url.startsWith('/')) {
// Resolve paths relative to the location of the file name
const lastSlashIdx = runtimeSourceURL.lastIndexOf('/');
if (lastSlashIdx !== -1) {
const baseURL = runtimeSourceURL.slice(
0,
runtimeSourceURL.lastIndexOf('/'),
);
url = `${baseURL}/${url}`;
}
}
hookSourceData.sourceMapURL = url;
if (__DEBUG__) {
console.log(
'extractAndLoadSourceMaps() External source map "' + url + '"',
const fetchPromise =
fetchPromises.get(url) ||
fetchFile(url).then(
sourceMapContents =>
new SourceMapConsumer(JSON.parse(sourceMapContents)),
);
if (__DEBUG__) {
if (!fetchPromises.has(url)) {
console.log(
`extractAndLoadSourceMaps() External source map "${url}"`,
);
}
}
promises.push(
fetchFile(url).then(sourceMapContents => {
hookSourceData.sourceMapContents = JSON.parse(sourceMapContents);
fetchPromises.set(url, fetchPromise);
setPromises.push(
fetchPromise.then(sourceConsumer => {
hookSourceData.sourceConsumer = sourceConsumer;
}),
);
break;
@@ -255,7 +328,7 @@ function extractAndLoadSourceMaps(
}
}
});
return Promise.all(promises);
return Promise.all(setPromises);
}
function fetchFile(url: string): Promise<string> {
@@ -268,9 +341,15 @@ function fetchFile(url: string): Promise<string> {
resolve(text);
})
.catch(error => {
if (__DEBUG__) {
console.log(`fetchFile() Could not read text for url "${url}"`);
}
reject(null);
});
} else {
if (__DEBUG__) {
console.log(`fetchFile() Got bad response for url "${url}"`);
}
reject(null);
}
});
@@ -279,11 +358,13 @@ function fetchFile(url: string): Promise<string> {
function findHookNames(
hooksList: Array<HooksNode>,
fileNameToHookSourceData: Map<string, HookSourceData>,
locationKeyToHookSourceData: Map<string, HookSourceData>,
): HookNames {
const map: HookNames = new Map();
hooksList.map(hook => {
// TODO (named hooks) We should probably filter before this point,
// otherwise we are loading and parsing source maps and ASTs for nothing.
if (isNonDeclarativePrimitiveHook(hook)) {
if (__DEBUG__) {
console.log('findHookNames() Non declarative primitive hook');
@@ -300,7 +381,8 @@ function findHookNames(
return null; // Should not be reachable.
}
const hookSourceData = fileNameToHookSourceData.get(fileName);
const locationKey = getLocationKey(hookSource);
const hookSourceData = locationKeyToHookSourceData.get(locationKey);
if (!hookSourceData) {
return null; // Should not be reachable.
}
@@ -321,7 +403,10 @@ function findHookNames(
} else {
originalSourceLineNumber = sourceConsumer.originalPositionFor({
line: lineNumber,
column: columnNumber,
// Column numbers are representated differently between tools/engines.
// For more info see https://github.com/facebook/react/issues/21792#issuecomment-873171991
column: columnNumber - 1,
}).line;
}
@@ -346,7 +431,7 @@ function findHookNames(
);
if (__DEBUG__) {
console.log('findHookNames() Found name "' + (name || '-') + '"');
console.log(`findHookNames() Found name "${name || '-'}"`);
}
map.set(hook, name);
@@ -366,103 +451,133 @@ function isValidUrl(possibleURL: string): boolean {
}
function loadSourceFiles(
fileNameToHookSourceData: Map<string, HookSourceData>,
locationKeyToHookSourceData: Map<string, HookSourceData>,
): Promise<*> {
const promises = [];
fileNameToHookSourceData.forEach((hookSourceData, fileName) => {
promises.push(
fetchFile(fileName).then(runtimeSourceCode => {
// Deduplicate fetches, since there can be multiple location keys per file.
const fetchPromises = new Map();
const setPromises = [];
locationKeyToHookSourceData.forEach(hookSourceData => {
const {runtimeSourceURL} = hookSourceData;
const fetchPromise =
fetchPromises.get(runtimeSourceURL) ||
fetchFile(runtimeSourceURL).then(runtimeSourceCode => {
if (runtimeSourceCode.length > MAX_SOURCE_LENGTH) {
throw Error('Source code too large to parse');
}
if (__DEBUG__) {
console.groupCollapsed(
'loadSourceFiles() fileName "' + fileName + '"',
`loadSourceFiles() runtimeSourceURL "${runtimeSourceURL}"`,
);
console.log(runtimeSourceCode);
console.groupEnd();
}
return runtimeSourceCode;
});
fetchPromises.set(runtimeSourceURL, fetchPromise);
setPromises.push(
fetchPromise.then(runtimeSourceCode => {
hookSourceData.runtimeSourceCode = runtimeSourceCode;
}),
);
});
return Promise.all(promises);
return Promise.all(setPromises);
}
async function parseSourceAST(
fileNameToHookSourceData: Map<string, HookSourceData>,
locationKeyToHookSourceData: Map<string, HookSourceData>,
): Promise<*> {
// SourceMapConsumer.initialize() does nothing when running in Node (aka Jest)
// because the wasm file is automatically read from the file system
// so we can avoid triggering a warning message about this.
if (!__TEST__) {
if (__DEBUG__) {
console.log('parseSourceAST() Initializing source-map library ...');
}
// $FlowFixMe
const wasmMappingsURL = chrome.extension.getURL('mappings.wasm');
SourceMapConsumer.initialize({'lib/mappings.wasm': wasmMappingsURL});
}
const promises = [];
fileNameToHookSourceData.forEach(hookSourceData => {
locationKeyToHookSourceData.forEach(hookSourceData => {
if (hookSourceData.originalSourceAST !== null) {
// Use cached metadata.
return;
}
const {runtimeSourceCode, sourceMapContents} = hookSourceData;
if (sourceMapContents !== null) {
const {sourceConsumer} = hookSourceData;
const runtimeSourceCode = ((hookSourceData.runtimeSourceCode: any): string);
let originalSourceURL, originalSourceCode;
if (sourceConsumer !== null) {
// Parse and extract the AST from the source map.
promises.push(
SourceMapConsumer.with(
sourceMapContents,
null,
(sourceConsumer: SourceConsumer) => {
hookSourceData.sourceConsumer = sourceConsumer;
const {lineNumber, columnNumber} = hookSourceData.hookSource;
if (lineNumber == null || columnNumber == null) {
throw Error('Hook source code location not found.');
}
// Now that the source map has been loaded,
// extract the original source for later.
const {source} = sourceConsumer.originalPositionFor({
line: lineNumber,
// Now that the source map has been loaded,
// extract the original source for later.
const source = sourceMapContents.sources[0];
const originalSourceCode = sourceConsumer.sourceContentFor(
source,
true,
);
// Column numbers are representated differently between tools/engines.
// For more info see https://github.com/facebook/react/issues/21792#issuecomment-873171991
column: columnNumber - 1,
});
if (__DEBUG__) {
console.groupCollapsed(
'parseSourceAST() Extracted source code from source map',
);
console.log(originalSourceCode);
console.groupEnd();
}
if (source == null) {
// TODO (named hooks) maybe fall back to the runtime source instead of throwing?
throw new Error(
'Could not map hook runtime location to original source location',
);
}
hookSourceData.originalSourceCode = originalSourceCode;
// TODO (named hooks) maybe canonicalize this URL somehow?
// It can be relative if the source map specifies it that way,
// but we use it as a cache key across different source maps and there can be collisions.
originalSourceURL = (source: string);
originalSourceCode = (sourceConsumer.sourceContentFor(
source,
true,
): string);
// TODO Parsing should ideally be done off of the main thread.
hookSourceData.originalSourceAST = parse(originalSourceCode, {
sourceType: 'unambiguous',
plugins: ['jsx', 'typescript'],
});
},
),
);
if (__DEBUG__) {
console.groupCollapsed(
'parseSourceAST() Extracted source code from source map',
);
console.log(originalSourceCode);
console.groupEnd();
}
} else {
// There's no source map to parse here so we can just parse the original source itself.
hookSourceData.originalSourceCode = runtimeSourceCode;
originalSourceCode = runtimeSourceCode;
// TODO (named hooks) This mixes runtimeSourceURLs with source mapped URLs in the same cache key space.
// Namespace them?
originalSourceURL = hookSourceData.runtimeSourceURL;
}
// TODO Parsing should ideally be done off of the main thread.
hookSourceData.originalSourceAST = parse(runtimeSourceCode, {
hookSourceData.originalSourceCode = originalSourceCode;
hookSourceData.originalSourceURL = originalSourceURL;
// The cache also serves to deduplicate parsing by URL in our loop over
// location keys. This may need to change if we switch to async parsing.
const sourceMetadata = originalURLToMetadataCache.get(originalSourceURL);
if (sourceMetadata != null) {
if (__DEBUG__) {
console.groupCollapsed(
`parseSourceAST() Found cached source metadata for "${originalSourceURL}"`,
);
console.log(sourceMetadata);
console.groupEnd();
}
hookSourceData.originalSourceAST = sourceMetadata.originalSourceAST;
hookSourceData.originalSourceCode = sourceMetadata.originalSourceCode;
} else {
// TODO (named hooks) Parsing should ideally be done off of the main thread.
const originalSourceAST = parse(originalSourceCode, {
sourceType: 'unambiguous',
plugins: ['jsx', 'typescript'],
});
hookSourceData.originalSourceAST = originalSourceAST;
if (__DEBUG__) {
console.log(
`parseSourceAST() Caching source metadata for "${originalSourceURL}"`,
);
}
originalURLToMetadataCache.set(originalSourceURL, {
originalSourceAST,
originalSourceCode,
});
}
});
return Promise.all(promises);
return Promise.resolve();
}
function flattenHooksList(
@@ -479,23 +594,21 @@ function flattenHooksList(
}
function updateLruCache(
fileNameToHookSourceData: Map<string, HookSourceData>,
locationKeyToHookSourceData: Map<string, HookSourceData>,
): Promise<*> {
fileNameToHookSourceData.forEach(
({originalSourceAST, originalSourceCode, sourceConsumer}, fileName) => {
// Only set once to avoid triggering eviction/cleanup code.
if (!fileNameToMetadataCache.has(fileName)) {
if (__DEBUG__) {
console.log('updateLruCache() Caching metada for "' + fileName + '"');
}
fileNameToMetadataCache.set(fileName, {
originalSourceAST,
originalSourceCode: ((originalSourceCode: any): string),
sourceConsumer,
});
locationKeyToHookSourceData.forEach(({sourceConsumer, runtimeSourceURL}) => {
// Only set once to avoid triggering eviction/cleanup code.
if (!runtimeURLToMetadataCache.has(runtimeSourceURL)) {
if (__DEBUG__) {
console.log(
`updateLruCache() Caching runtime metadata for "${runtimeSourceURL}"`,
);
}
},
);
runtimeURLToMetadataCache.set(runtimeSourceURL, {
sourceConsumer,
});
}
});
return Promise.resolve();
}
+14 -1
View File
@@ -9,6 +9,7 @@
import {unstable_getCacheForType as getCacheForType} from 'react';
import {enableHookNameParsing} from 'react-devtools-feature-flags';
import {__DEBUG__} from 'react-devtools-shared/src/constants';
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
import type {Thenable, Wakeable} from 'shared/ReactTypes';
@@ -108,6 +109,10 @@ export function loadHookNames(
return;
}
if (__DEBUG__) {
console.log('[hookNamesCache] onSuccess() hookNames:', hookNames);
}
if (hookNames) {
const resolvedRecord = ((newRecord: any): ResolvedRecord<HookNames>);
resolvedRecord.status = Resolved;
@@ -121,6 +126,10 @@ export function loadHookNames(
wake();
},
function onError(error) {
if (__DEBUG__) {
console.log('[hookNamesCache] onError() error:', error);
}
if (didTimeout) {
return;
}
@@ -134,7 +143,11 @@ export function loadHookNames(
);
// Eventually timeout and stop trying to load names.
let timeoutID = setTimeout(() => {
let timeoutID = setTimeout(function onTimeout() {
if (__DEBUG__) {
console.log('[hookNamesCache] onTimeout()');
}
timeoutID = null;
didTimeout = true;