mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-22 09:36:43 +03:00
Move StackTraceTab and react-error-overlay
From zalmoxisus/remotedev-app/pull/43
This commit is contained in:
parent
671b26157e
commit
d6a0e13d3b
9
packages/redux-devtools-trace-monitor/.babelrc
Normal file
9
packages/redux-devtools-trace-monitor/.babelrc
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"presets": [ ["env", {
|
||||||
|
"targets": {
|
||||||
|
"firefox" : 56,
|
||||||
|
"chrome" : 64
|
||||||
|
}
|
||||||
|
}], "react", "flow"],
|
||||||
|
"plugins": [ "transform-class-properties", "transform-object-rest-spread", "add-module-exports", "transform-decorators-legacy" ]
|
||||||
|
}
|
5
packages/redux-devtools-trace-monitor/.eslintignore
Normal file
5
packages/redux-devtools-trace-monitor/.eslintignore
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
build
|
||||||
|
dev
|
||||||
|
dist
|
||||||
|
lib
|
36
packages/redux-devtools-trace-monitor/.eslintrc
Normal file
36
packages/redux-devtools-trace-monitor/.eslintrc
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"extends": "eslint-config-airbnb",
|
||||||
|
"globals": {
|
||||||
|
"chrome": true
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"jest": true,
|
||||||
|
"browser": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"rules": {
|
||||||
|
"react/jsx-uses-react": 2,
|
||||||
|
"react/jsx-uses-vars": 2,
|
||||||
|
"react/react-in-jsx-scope": 2,
|
||||||
|
"react/sort-comp": 0,
|
||||||
|
"react/jsx-quotes": 0,
|
||||||
|
"block-scoped-var": 0,
|
||||||
|
"padded-blocks": 0,
|
||||||
|
"quotes": [ 1, "single" ],
|
||||||
|
"comma-style": [ 2, "last" ],
|
||||||
|
"eol-last": 0,
|
||||||
|
"no-unused-vars": 0,
|
||||||
|
"no-console": 0,
|
||||||
|
"func-names": 0,
|
||||||
|
"prefer-const": 0,
|
||||||
|
"comma-dangle": 0,
|
||||||
|
"id-length": 0,
|
||||||
|
"no-use-before-define": 0,
|
||||||
|
"indent": [2, 2, {"SwitchCase": 1}],
|
||||||
|
"new-cap": [2, { "capIsNewExceptions": ["Test"] }]
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"react"
|
||||||
|
]
|
||||||
|
}
|
19
packages/redux-devtools-trace-monitor/LICENSE
Normal file
19
packages/redux-devtools-trace-monitor/LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
36
packages/redux-devtools-trace-monitor/README.md
Normal file
36
packages/redux-devtools-trace-monitor/README.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
Redux DevTools Stack Trace Monitor
|
||||||
|
==================================
|
||||||
|
|
||||||
|
Submonitor for Redux DevTools inspector to show stack traces. Based on [`react-error-overlay`](https://github.com/facebook/create-react-app/tree/master/packages/react-error-overlay) and the contribution of [Mark Erikson](https://github.com/markerikson) in [the PR from `remotedev-app`](https://github.com/zalmoxisus/remotedev-app/pull/43/).
|
||||||
|
|
||||||
|
It's integrated in Redux DevTools browser extension. To use it separately with [`redux-devtools`](https://github.com/reduxjs/redux-devtools/packages/redux-devtools) and [`redux-devtools-inspector`](https://github.com/reduxjs/redux-devtools/packages/redux-devtools-inspector) according to [Walkthrough](https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md):
|
||||||
|
|
||||||
|
##### `containers/DevTools.js`
|
||||||
|
|
||||||
|
```js
|
||||||
|
import React from 'react';
|
||||||
|
import { createDevTools } from 'redux-devtools';
|
||||||
|
import Inspector from 'redux-devtools-inspector';
|
||||||
|
import TraceMonitor from 'redux-devtools-trace-monitor';
|
||||||
|
|
||||||
|
export default createDevTools(
|
||||||
|
<Inspector
|
||||||
|
tabs: defaultTabs => [...defaultTabs, { name: 'Trace', component: TraceMonitor }]
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `store/configureStore.js`
|
||||||
|
|
||||||
|
```js
|
||||||
|
// ...
|
||||||
|
const enhancer = compose(
|
||||||
|
// ...
|
||||||
|
DevTools.instrument({ trace: true })
|
||||||
|
);
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
MIT
|
57
packages/redux-devtools-trace-monitor/index.js
Normal file
57
packages/redux-devtools-trace-monitor/index.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import InspectorMonitor from 'remotedev-inspector-monitor';
|
||||||
|
import StackTraceTab from './StackTraceTab';
|
||||||
|
import { DATA_TYPE_KEY } from '../../../constants/dataTypes';
|
||||||
|
import SubTabs from './SubTabs';
|
||||||
|
import TestTab from './TestTab';
|
||||||
|
|
||||||
|
const DEFAULT_TABS = [{
|
||||||
|
name: 'Action',
|
||||||
|
component: SubTabs
|
||||||
|
}, {
|
||||||
|
name: 'State',
|
||||||
|
component: SubTabs
|
||||||
|
}, {
|
||||||
|
name: 'Diff',
|
||||||
|
component: SubTabs
|
||||||
|
}];
|
||||||
|
|
||||||
|
const NON_INIT_TABS = [
|
||||||
|
{ name: 'Trace', component: StackTraceTab }
|
||||||
|
];
|
||||||
|
|
||||||
|
class InspectorWrapper extends Component {
|
||||||
|
static update = InspectorMonitor.update;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { lib, ...rest } = this.props;
|
||||||
|
console.log(rest);
|
||||||
|
let tabs;
|
||||||
|
if (lib === 'redux') {
|
||||||
|
tabs = () => [
|
||||||
|
...DEFAULT_TABS,
|
||||||
|
...(!rest.monitorState || rest.monitorState.selectedActionId === null ? NON_INIT_TABS : []),
|
||||||
|
{ name: 'Test', component: TestTab }
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
tabs = () => DEFAULT_TABS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InspectorMonitor
|
||||||
|
dataTypeKey={DATA_TYPE_KEY}
|
||||||
|
shouldPersistState={false}
|
||||||
|
invertTheme={false}
|
||||||
|
theme="nicinabox"
|
||||||
|
tabs={tabs}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InspectorWrapper.propTypes = {
|
||||||
|
lib: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InspectorWrapper;
|
60
packages/redux-devtools-trace-monitor/package.json
Normal file
60
packages/redux-devtools-trace-monitor/package.json
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
{
|
||||||
|
"name": "redux-devtools-trace-monitor",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Submonitor for Redux DevTools inspector to show stack traces.",
|
||||||
|
"repository": "https://github.com/reduxjs/redux-devtools",
|
||||||
|
"homepage": "https://github.com/reduxjs/redux-devtools",
|
||||||
|
"author": "Mark Erikson <mark@isquaredsoftware.com>",
|
||||||
|
"contributors": [
|
||||||
|
"Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "lib/StackTraceTab.js",
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf lib",
|
||||||
|
"build": "babel src --out-dir lib",
|
||||||
|
"lint": "eslint src test",
|
||||||
|
"lint:fix": "eslint --fix src test",
|
||||||
|
"test": "jest --no-cache",
|
||||||
|
"postinstall": "npm run build",
|
||||||
|
"prepublishOnly": "npm run lint && npm run test && npm run clean && npm run build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.10.1",
|
||||||
|
"babel-core": "^6.10.4",
|
||||||
|
"babel-eslint": "^6.0.5",
|
||||||
|
"babel-loader": "^6.2.4",
|
||||||
|
"babel-plugin-add-module-exports": "^0.2.1",
|
||||||
|
"babel-plugin-react-transform": "^2.0.0",
|
||||||
|
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||||
|
"babel-preset-env": "^1.7.0",
|
||||||
|
"babel-preset-es2015": "^6.9.0",
|
||||||
|
"babel-preset-es2015-loose": "^7.0.0",
|
||||||
|
"babel-preset-react": "^6.5.0",
|
||||||
|
"babel-preset-react-app": "^3.1.2",
|
||||||
|
"babel-preset-stage-0": "^6.5.0",
|
||||||
|
"babel-register": "^6.11.6",
|
||||||
|
"enzyme": "^2.6.0",
|
||||||
|
"enzyme-to-json": "^1.3.0",
|
||||||
|
"eslint": "^2.13.1",
|
||||||
|
"eslint-config-airbnb": "^9.0.1",
|
||||||
|
"eslint-plugin-import": "^1.9.2",
|
||||||
|
"eslint-plugin-jsx-a11y": "^1.5.3",
|
||||||
|
"eslint-plugin-react": "^5.2.2",
|
||||||
|
"jest": "^17.0.3",
|
||||||
|
"react-addons-test-utils": "^15.4.0",
|
||||||
|
"react-dom": "^15.4.0",
|
||||||
|
"rimraf": "^2.5.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.0.0",
|
||||||
|
"anser": "^1.4.7",
|
||||||
|
"chalk": "^2.4.1",
|
||||||
|
"html-entities": "^1.2.1",
|
||||||
|
"react": "^15.4.0",
|
||||||
|
"settle-promise": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
141
packages/redux-devtools-trace-monitor/src/StackTraceTab.js
Normal file
141
packages/redux-devtools-trace-monitor/src/StackTraceTab.js
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
// import ErrorStackParser from "error-stack-parser";
|
||||||
|
|
||||||
|
import {getStackFrames} from "./react-error-overlay/utils/getStackFrames";
|
||||||
|
import StackTrace from "./react-error-overlay/containers/StackTrace";
|
||||||
|
|
||||||
|
export default class StackTraceTab extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
stackFrames : []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
//console.log("StackTraceTab mounted");
|
||||||
|
this.checkForStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const {action, actions} = prevProps;
|
||||||
|
|
||||||
|
if(action !== this.props.action || actions !== this.props.actions) {
|
||||||
|
this.checkForStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkForStackTrace() {
|
||||||
|
const {action, actions : liftedActionsById} = this.props;
|
||||||
|
|
||||||
|
if(!action) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const liftedActions = Object.values(liftedActionsById);
|
||||||
|
const liftedAction = liftedActions.find(liftedAction => liftedAction.action === action);
|
||||||
|
|
||||||
|
|
||||||
|
if(liftedAction && typeof liftedAction.stack === 'string') {
|
||||||
|
const deserializedError = Object.assign(new Error(), {stack: liftedAction.stack});
|
||||||
|
|
||||||
|
getStackFrames(deserializedError)
|
||||||
|
.then(stackFrames => {
|
||||||
|
console.log("Stack frames: ", stackFrames);
|
||||||
|
this.setState({stackFrames, currentError : deserializedError});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.setState({stackFrames : []})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
onStackFrameClicked = (i) => {
|
||||||
|
const stackFrame = this.state.stackFrames[i];
|
||||||
|
|
||||||
|
if(stackFrame) {
|
||||||
|
const parsedFramesNoSourcemaps = ErrorStackParser.parse(this.state.currentError)
|
||||||
|
console.log("Parsed stack frames: ", parsedFramesNoSourcemaps);
|
||||||
|
|
||||||
|
if(chrome && chrome.devtools.panels.openResource) {
|
||||||
|
const frameWithoutSourcemap = parsedFramesNoSourcemaps[i];
|
||||||
|
const {fileName, lineNumber} = frameWithoutSourcemap;
|
||||||
|
console.log("Parsed stack frame: ", stackFrame);
|
||||||
|
console.log("Original stack frame: ", frameWithoutSourcemap);
|
||||||
|
|
||||||
|
const adjustedLineNumber = Math.max(lineNumber - 1, 0);
|
||||||
|
|
||||||
|
|
||||||
|
chrome.devtools.panels.openResource(fileName, adjustedLineNumber, (...callbackArgs) => {
|
||||||
|
console.log("openResource callback args: ", callbackArgs);
|
||||||
|
//console.log("Testing");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
onStackLocationClicked = (fileLocation = {}) => {
|
||||||
|
//console.log("Stack location args: ", ...args);
|
||||||
|
|
||||||
|
// const parsedFramesNoSourcemaps = ErrorStackParser.parse(this.state.currentError)
|
||||||
|
//console.log("Parsed stack frames: ", parsedFramesNoSourcemaps);
|
||||||
|
|
||||||
|
const {fileName, lineNumber} = fileLocation;
|
||||||
|
|
||||||
|
if(fileName && lineNumber) {
|
||||||
|
const matchingStackFrame = this.state.stackFrames.find(stackFrame => {
|
||||||
|
const matches = (
|
||||||
|
(stackFrame._originalFileName === fileName && stackFrame._originalLineNumber === lineNumber) ||
|
||||||
|
(stackFrame.fileName === fileName && stackFrame.lineNumber === lineNumber)
|
||||||
|
);
|
||||||
|
return matches;
|
||||||
|
})
|
||||||
|
|
||||||
|
//console.log("Matching stack frame: ", matchingStackFrame);
|
||||||
|
|
||||||
|
if(matchingStackFrame) {
|
||||||
|
/*
|
||||||
|
const frameIndex = this.state.stackFrames.indexOf(matchingStackFrame);
|
||||||
|
const originalStackFrame = parsedFramesNoSourcemaps[frameIndex];
|
||||||
|
console.log("Original stack frame: ", originalStackFrame);
|
||||||
|
*/
|
||||||
|
const adjustedLineNumber = Math.max(lineNumber - 1, 0);
|
||||||
|
|
||||||
|
|
||||||
|
chrome.devtools.panels.openResource(fileName, adjustedLineNumber, (result) => {
|
||||||
|
//console.log("openResource callback args: ", callbackArgs);
|
||||||
|
//console.log("Testing");
|
||||||
|
if(result.isError) {
|
||||||
|
const {fileName : finalFileName, lineNumber : finalLineNumber} = matchingStackFrame;
|
||||||
|
const adjustedLineNumber = Math.max(finalLineNumber - 1, 0);
|
||||||
|
chrome.devtools.panels.openResource(finalFileName, adjustedLineNumber, (result) => {
|
||||||
|
//console.log("openResource result: ", result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {stackFrames} = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{backgroundColor : "white", color : "black"}}>
|
||||||
|
<h2>Dispatched Action Stack Trace</h2>
|
||||||
|
<div style={{display : "flex", flexDirection : "column"}}>
|
||||||
|
<StackTrace
|
||||||
|
stackFrames={stackFrames}
|
||||||
|
errorName={"N/A"}
|
||||||
|
contextSize={3}
|
||||||
|
editorHandler={this.onStackLocationClicked}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
53
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/CodeBlock.js
vendored
Normal file
53
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/CodeBlock.js
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import React from 'react';
|
||||||
|
import { redTransparent, yellowTransparent } from '../styles';
|
||||||
|
|
||||||
|
const _preStyle = {
|
||||||
|
position: 'relative',
|
||||||
|
display: 'block',
|
||||||
|
padding: '0.5em',
|
||||||
|
marginTop: '0.5em',
|
||||||
|
marginBottom: '0.5em',
|
||||||
|
overflowX: 'auto',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
borderRadius: '0.25rem',
|
||||||
|
};
|
||||||
|
|
||||||
|
const primaryPreStyle = {
|
||||||
|
..._preStyle,
|
||||||
|
backgroundColor: redTransparent,
|
||||||
|
};
|
||||||
|
|
||||||
|
const secondaryPreStyle = {
|
||||||
|
..._preStyle,
|
||||||
|
backgroundColor: yellowTransparent,
|
||||||
|
};
|
||||||
|
|
||||||
|
const codeStyle = {
|
||||||
|
fontFamily: 'Consolas, Menlo, monospace',
|
||||||
|
};
|
||||||
|
|
||||||
|
type CodeBlockPropsType = {|
|
||||||
|
main: boolean,
|
||||||
|
codeHTML: string,
|
||||||
|
|};
|
||||||
|
|
||||||
|
function CodeBlock(props: CodeBlockPropsType) {
|
||||||
|
const preStyle = props.main ? primaryPreStyle : secondaryPreStyle;
|
||||||
|
const codeBlock = { __html: props.codeHTML };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<pre style={preStyle}>
|
||||||
|
<code style={codeStyle} dangerouslySetInnerHTML={codeBlock} />
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CodeBlock;
|
86
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/Collapsible.js
vendored
Normal file
86
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/Collapsible.js
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { black } from '../styles';
|
||||||
|
|
||||||
|
import type { Element as ReactElement } from 'react';
|
||||||
|
|
||||||
|
const _collapsibleStyle = {
|
||||||
|
color: black,
|
||||||
|
cursor: 'pointer',
|
||||||
|
border: 'none',
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
textAlign: 'left',
|
||||||
|
background: '#fff',
|
||||||
|
fontFamily: 'Consolas, Menlo, monospace',
|
||||||
|
fontSize: '1em',
|
||||||
|
padding: '0px',
|
||||||
|
lineHeight: '1.5',
|
||||||
|
};
|
||||||
|
|
||||||
|
const collapsibleCollapsedStyle = {
|
||||||
|
..._collapsibleStyle,
|
||||||
|
marginBottom: '1.5em',
|
||||||
|
};
|
||||||
|
|
||||||
|
const collapsibleExpandedStyle = {
|
||||||
|
..._collapsibleStyle,
|
||||||
|
marginBottom: '0.6em',
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {|
|
||||||
|
children: ReactElement<any>[],
|
||||||
|
|};
|
||||||
|
|
||||||
|
type State = {|
|
||||||
|
collapsed: boolean,
|
||||||
|
|};
|
||||||
|
|
||||||
|
class Collapsible extends Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
collapsed: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleCollaped = () => {
|
||||||
|
this.setState(state => ({
|
||||||
|
collapsed: !state.collapsed,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const count = this.props.children.length;
|
||||||
|
const collapsed = this.state.collapsed;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={this.toggleCollaped}
|
||||||
|
style={
|
||||||
|
collapsed ? collapsibleCollapsedStyle : collapsibleExpandedStyle
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(collapsed ? '▶' : '▼') +
|
||||||
|
` ${count} stack frames were ` +
|
||||||
|
(collapsed ? 'collapsed.' : 'expanded.')}
|
||||||
|
</button>
|
||||||
|
<div style={{ display: collapsed ? 'none' : 'block' }}>
|
||||||
|
{this.props.children}
|
||||||
|
<button
|
||||||
|
onClick={this.toggleCollaped}
|
||||||
|
style={collapsibleExpandedStyle}
|
||||||
|
>
|
||||||
|
{`▲ ${count} stack frames were expanded.`}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Collapsible;
|
191
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackFrame.js
vendored
Normal file
191
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackFrame.js
vendored
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import CodeBlock from './StackFrameCodeBlock';
|
||||||
|
import { getPrettyURL } from '../utils/getPrettyURL';
|
||||||
|
import { darkGray } from '../styles';
|
||||||
|
|
||||||
|
import type { StackFrame as StackFrameType } from '../utils/stack-frame';
|
||||||
|
import type { ErrorLocation } from '../utils/parseCompileError';
|
||||||
|
|
||||||
|
const linkStyle = {
|
||||||
|
fontSize: '0.9em',
|
||||||
|
marginBottom: '0.9em',
|
||||||
|
};
|
||||||
|
|
||||||
|
const anchorStyle = {
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: darkGray,
|
||||||
|
cursor: 'pointer',
|
||||||
|
};
|
||||||
|
|
||||||
|
const codeAnchorStyle = {
|
||||||
|
cursor: 'pointer',
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleStyle = {
|
||||||
|
marginBottom: '1.5em',
|
||||||
|
color: darkGray,
|
||||||
|
cursor: 'pointer',
|
||||||
|
border: 'none',
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
textAlign: 'left',
|
||||||
|
background: '#fff',
|
||||||
|
fontFamily: 'Consolas, Menlo, monospace',
|
||||||
|
fontSize: '1em',
|
||||||
|
padding: '0px',
|
||||||
|
lineHeight: '1.5',
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {|
|
||||||
|
frame: StackFrameType,
|
||||||
|
contextSize: number,
|
||||||
|
critical: boolean,
|
||||||
|
showCode: boolean,
|
||||||
|
editorHandler: (errorLoc: ErrorLocation) => void,
|
||||||
|
|};
|
||||||
|
|
||||||
|
type State = {|
|
||||||
|
compiled: boolean,
|
||||||
|
|};
|
||||||
|
|
||||||
|
class StackFrame extends Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
compiled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleCompiled = () => {
|
||||||
|
this.setState(state => ({
|
||||||
|
compiled: !state.compiled,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
getErrorLocation(): ErrorLocation | null {
|
||||||
|
const {
|
||||||
|
_originalFileName: fileName,
|
||||||
|
_originalLineNumber: lineNumber,
|
||||||
|
} = this.props.frame;
|
||||||
|
// Unknown file
|
||||||
|
if (!fileName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// e.g. "/path-to-my-app/webpack/bootstrap eaddeb46b67d75e4dfc1"
|
||||||
|
const isInternalWebpackBootstrapCode = fileName.trim().indexOf(' ') !== -1;
|
||||||
|
if (isInternalWebpackBootstrapCode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Code is in a real file
|
||||||
|
return { fileName, lineNumber: lineNumber || 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
editorHandler = () => {
|
||||||
|
const errorLoc = this.getErrorLocation();
|
||||||
|
if (!errorLoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.editorHandler(errorLoc);
|
||||||
|
};
|
||||||
|
|
||||||
|
onKeyDown = (e: SyntheticKeyboardEvent<>) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
this.editorHandler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { frame, contextSize, critical, showCode } = this.props;
|
||||||
|
const {
|
||||||
|
fileName,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
_scriptCode: scriptLines,
|
||||||
|
_originalFileName: sourceFileName,
|
||||||
|
_originalLineNumber: sourceLineNumber,
|
||||||
|
_originalColumnNumber: sourceColumnNumber,
|
||||||
|
_originalScriptCode: sourceLines,
|
||||||
|
} = frame;
|
||||||
|
const functionName = frame.getFunctionName();
|
||||||
|
|
||||||
|
const compiled = this.state.compiled;
|
||||||
|
const url = getPrettyURL(
|
||||||
|
sourceFileName,
|
||||||
|
sourceLineNumber,
|
||||||
|
sourceColumnNumber,
|
||||||
|
fileName,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
compiled
|
||||||
|
);
|
||||||
|
|
||||||
|
let codeBlockProps = null;
|
||||||
|
if (showCode) {
|
||||||
|
if (
|
||||||
|
compiled &&
|
||||||
|
scriptLines &&
|
||||||
|
scriptLines.length !== 0 &&
|
||||||
|
lineNumber != null
|
||||||
|
) {
|
||||||
|
codeBlockProps = {
|
||||||
|
lines: scriptLines,
|
||||||
|
lineNum: lineNumber,
|
||||||
|
columnNum: columnNumber,
|
||||||
|
contextSize,
|
||||||
|
main: critical,
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
!compiled &&
|
||||||
|
sourceLines &&
|
||||||
|
sourceLines.length !== 0 &&
|
||||||
|
sourceLineNumber != null
|
||||||
|
) {
|
||||||
|
codeBlockProps = {
|
||||||
|
lines: sourceLines,
|
||||||
|
lineNum: sourceLineNumber,
|
||||||
|
columnNum: sourceColumnNumber,
|
||||||
|
contextSize,
|
||||||
|
main: critical,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const canOpenInEditor =
|
||||||
|
this.getErrorLocation() !== null && this.props.editorHandler !== null;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>{functionName}</div>
|
||||||
|
<div style={linkStyle}>
|
||||||
|
<span
|
||||||
|
style={canOpenInEditor ? anchorStyle : null}
|
||||||
|
onClick={canOpenInEditor ? this.editorHandler : null}
|
||||||
|
onKeyDown={canOpenInEditor ? this.onKeyDown : null}
|
||||||
|
tabIndex={canOpenInEditor ? '0' : null}
|
||||||
|
>
|
||||||
|
{url}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{codeBlockProps && (
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
onClick={canOpenInEditor ? this.editorHandler : null}
|
||||||
|
style={canOpenInEditor ? codeAnchorStyle : null}
|
||||||
|
>
|
||||||
|
<CodeBlock {...codeBlockProps} />
|
||||||
|
</span>
|
||||||
|
<button style={toggleStyle} onClick={this.toggleCompiled}>
|
||||||
|
{'View ' + (compiled ? 'source' : 'compiled')}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StackFrame;
|
102
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackFrameCodeBlock.js
vendored
Normal file
102
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackFrameCodeBlock.js
vendored
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import React from 'react';
|
||||||
|
import CodeBlock from '../components/CodeBlock';
|
||||||
|
import { applyStyles } from '../utils/dom/css';
|
||||||
|
import { absolutifyCaret } from '../utils/dom/absolutifyCaret';
|
||||||
|
import type { ScriptLine } from '../utils/stack-frame';
|
||||||
|
import { primaryErrorStyle, secondaryErrorStyle } from '../styles';
|
||||||
|
import generateAnsiHTML from '../utils/generateAnsiHTML';
|
||||||
|
|
||||||
|
import { codeFrameColumns } from '@babel/code-frame';
|
||||||
|
|
||||||
|
type StackFrameCodeBlockPropsType = {|
|
||||||
|
lines: ScriptLine[],
|
||||||
|
lineNum: number,
|
||||||
|
columnNum: ?number,
|
||||||
|
contextSize: number,
|
||||||
|
main: boolean,
|
||||||
|
|};
|
||||||
|
|
||||||
|
// Exact type workaround for spread operator.
|
||||||
|
// See: https://github.com/facebook/flow/issues/2405
|
||||||
|
type Exact<T> = $Shape<T>;
|
||||||
|
|
||||||
|
function StackFrameCodeBlock(props: Exact<StackFrameCodeBlockPropsType>) {
|
||||||
|
const { lines, lineNum, columnNum, contextSize, main } = props;
|
||||||
|
const sourceCode = [];
|
||||||
|
let whiteSpace = Infinity;
|
||||||
|
lines.forEach(function(e) {
|
||||||
|
const { content: text } = e;
|
||||||
|
const m = text.match(/^\s*/);
|
||||||
|
if (text === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m && m[0]) {
|
||||||
|
whiteSpace = Math.min(whiteSpace, m[0].length);
|
||||||
|
} else {
|
||||||
|
whiteSpace = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
lines.forEach(function(e) {
|
||||||
|
let { content: text } = e;
|
||||||
|
const { lineNumber: line } = e;
|
||||||
|
|
||||||
|
if (isFinite(whiteSpace)) {
|
||||||
|
text = text.substring(whiteSpace);
|
||||||
|
}
|
||||||
|
sourceCode[line - 1] = text;
|
||||||
|
});
|
||||||
|
const ansiHighlight = codeFrameColumns(
|
||||||
|
sourceCode.join('\n'),
|
||||||
|
{
|
||||||
|
start: {
|
||||||
|
line: lineNum,
|
||||||
|
column:
|
||||||
|
columnNum == null
|
||||||
|
? 0
|
||||||
|
: columnNum - (isFinite(whiteSpace) ? whiteSpace : 0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
forceColor: true,
|
||||||
|
linesAbove: contextSize,
|
||||||
|
linesBelow: contextSize,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const htmlHighlight = generateAnsiHTML(ansiHighlight);
|
||||||
|
const code = document.createElement('code');
|
||||||
|
code.innerHTML = htmlHighlight;
|
||||||
|
absolutifyCaret(code);
|
||||||
|
|
||||||
|
const ccn = code.childNodes;
|
||||||
|
// eslint-disable-next-line
|
||||||
|
oLoop: for (let index = 0; index < ccn.length; ++index) {
|
||||||
|
const node = ccn[index];
|
||||||
|
const ccn2 = node.childNodes;
|
||||||
|
for (let index2 = 0; index2 < ccn2.length; ++index2) {
|
||||||
|
const lineNode = ccn2[index2];
|
||||||
|
const text = lineNode.innerText;
|
||||||
|
if (text == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (text.indexOf(' ' + lineNum + ' |') === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// $FlowFixMe
|
||||||
|
applyStyles(node, main ? primaryErrorStyle : secondaryErrorStyle);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
break oLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <CodeBlock main={main} codeHTML={code.innerHTML} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StackFrameCodeBlock;
|
94
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackTrace.js
vendored
Normal file
94
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackTrace.js
vendored
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import StackFrame from './StackFrame';
|
||||||
|
import Collapsible from '../components/Collapsible';
|
||||||
|
import { isInternalFile } from '../utils/isInternalFile';
|
||||||
|
import { isBultinErrorName } from '../utils/isBultinErrorName';
|
||||||
|
|
||||||
|
import type { StackFrame as StackFrameType } from '../utils/stack-frame';
|
||||||
|
import type { ErrorLocation } from '../utils/parseCompileError';
|
||||||
|
|
||||||
|
const traceStyle = {
|
||||||
|
fontSize: '1em',
|
||||||
|
flex: '0 1 auto',
|
||||||
|
minHeight: '0px',
|
||||||
|
overflow: 'auto',
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {|
|
||||||
|
stackFrames: StackFrameType[],
|
||||||
|
errorName: string,
|
||||||
|
contextSize: number,
|
||||||
|
editorHandler: (errorLoc: ErrorLocation) => void,
|
||||||
|
|};
|
||||||
|
|
||||||
|
class StackTrace extends Component<Props> {
|
||||||
|
renderFrames() {
|
||||||
|
const { stackFrames, errorName, contextSize, editorHandler } = this.props;
|
||||||
|
const renderedFrames = [];
|
||||||
|
let hasReachedAppCode = false,
|
||||||
|
currentBundle = [],
|
||||||
|
bundleCount = 0;
|
||||||
|
|
||||||
|
stackFrames.forEach((frame, index) => {
|
||||||
|
const { fileName, _originalFileName: sourceFileName } = frame;
|
||||||
|
const isInternalUrl = isInternalFile(sourceFileName, fileName);
|
||||||
|
const isThrownIntentionally = !isBultinErrorName(errorName);
|
||||||
|
const shouldCollapse =
|
||||||
|
isInternalUrl && (isThrownIntentionally || hasReachedAppCode);
|
||||||
|
|
||||||
|
if (!isInternalUrl) {
|
||||||
|
hasReachedAppCode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const frameEle = (
|
||||||
|
<StackFrame
|
||||||
|
key={'frame-' + index}
|
||||||
|
frame={frame}
|
||||||
|
contextSize={contextSize}
|
||||||
|
critical={index === 0}
|
||||||
|
showCode={!shouldCollapse}
|
||||||
|
editorHandler={editorHandler}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const lastElement = index === stackFrames.length - 1;
|
||||||
|
|
||||||
|
if (shouldCollapse) {
|
||||||
|
currentBundle.push(frameEle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldCollapse || lastElement) {
|
||||||
|
if (currentBundle.length === 1) {
|
||||||
|
renderedFrames.push(currentBundle[0]);
|
||||||
|
} else if (currentBundle.length > 1) {
|
||||||
|
bundleCount++;
|
||||||
|
renderedFrames.push(
|
||||||
|
<Collapsible key={'bundle-' + bundleCount}>
|
||||||
|
{currentBundle}
|
||||||
|
</Collapsible>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
currentBundle = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldCollapse) {
|
||||||
|
renderedFrames.push(frameEle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return renderedFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div style={traceStyle}>{this.renderFrames()}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StackTrace;
|
54
packages/redux-devtools-trace-monitor/src/react-error-overlay/styles.js
vendored
Normal file
54
packages/redux-devtools-trace-monitor/src/react-error-overlay/styles.js
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
const black = '#293238',
|
||||||
|
darkGray = '#878e91',
|
||||||
|
red = '#ce1126',
|
||||||
|
redTransparent = 'rgba(206, 17, 38, 0.05)',
|
||||||
|
lightRed = '#fccfcf',
|
||||||
|
yellow = '#fbf5b4',
|
||||||
|
yellowTransparent = 'rgba(251, 245, 180, 0.3)',
|
||||||
|
white = '#ffffff';
|
||||||
|
|
||||||
|
const iframeStyle = {
|
||||||
|
position: 'fixed',
|
||||||
|
top: '0',
|
||||||
|
left: '0',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
border: 'none',
|
||||||
|
'z-index': 2147483647,
|
||||||
|
};
|
||||||
|
|
||||||
|
const overlayStyle = {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
'box-sizing': 'border-box',
|
||||||
|
'text-align': 'center',
|
||||||
|
'background-color': white,
|
||||||
|
};
|
||||||
|
|
||||||
|
const primaryErrorStyle = {
|
||||||
|
'background-color': lightRed,
|
||||||
|
};
|
||||||
|
|
||||||
|
const secondaryErrorStyle = {
|
||||||
|
'background-color': yellow,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
iframeStyle,
|
||||||
|
overlayStyle,
|
||||||
|
primaryErrorStyle,
|
||||||
|
secondaryErrorStyle,
|
||||||
|
black,
|
||||||
|
darkGray,
|
||||||
|
red,
|
||||||
|
redTransparent,
|
||||||
|
yellowTransparent,
|
||||||
|
};
|
41
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/dom/absolutifyCaret.js
vendored
Normal file
41
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/dom/absolutifyCaret.js
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
function removeNextBr(parent, component: ?Element) {
|
||||||
|
while (component != null && component.tagName.toLowerCase() !== 'br') {
|
||||||
|
component = component.nextElementSibling;
|
||||||
|
}
|
||||||
|
if (component != null) {
|
||||||
|
parent.removeChild(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function absolutifyCaret(component: Node) {
|
||||||
|
const ccn = component.childNodes;
|
||||||
|
for (let index = 0; index < ccn.length; ++index) {
|
||||||
|
const c = ccn[index];
|
||||||
|
// $FlowFixMe
|
||||||
|
if (c.tagName.toLowerCase() !== 'span') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const _text = c.innerText;
|
||||||
|
if (_text == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const text = _text.replace(/\s/g, '');
|
||||||
|
if (text !== '|^') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// $FlowFixMe
|
||||||
|
c.style.position = 'absolute';
|
||||||
|
// $FlowFixMe
|
||||||
|
removeNextBr(component, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { absolutifyCaret };
|
47
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/dom/css.js
vendored
Normal file
47
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/dom/css.js
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
let injectedCount = 0;
|
||||||
|
const injectedCache = {};
|
||||||
|
|
||||||
|
function getHead(document: Document) {
|
||||||
|
return document.head || document.getElementsByTagName('head')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectCss(document: Document, css: string): number {
|
||||||
|
const head = getHead(document);
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.type = 'text/css';
|
||||||
|
style.appendChild(document.createTextNode(css));
|
||||||
|
head.appendChild(style);
|
||||||
|
|
||||||
|
injectedCache[++injectedCount] = style;
|
||||||
|
return injectedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeCss(document: Document, ref: number) {
|
||||||
|
if (injectedCache[ref] == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const head = getHead(document);
|
||||||
|
head.removeChild(injectedCache[ref]);
|
||||||
|
delete injectedCache[ref];
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyStyles(element: HTMLElement, styles: Object) {
|
||||||
|
element.setAttribute('style', '');
|
||||||
|
for (const key in styles) {
|
||||||
|
if (!styles.hasOwnProperty(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// $FlowFixMe
|
||||||
|
element.style[key] = styles[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getHead, injectCss, removeCss, applyStyles };
|
107
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/generateAnsiHTML.js
vendored
Normal file
107
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/generateAnsiHTML.js
vendored
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import Anser from 'anser';
|
||||||
|
import { AllHtmlEntities as Entities } from 'html-entities';
|
||||||
|
|
||||||
|
var entities = new Entities();
|
||||||
|
|
||||||
|
// Color scheme inspired by https://chriskempson.github.io/base16/css/base16-github.css
|
||||||
|
// var base00 = 'ffffff'; // Default Background
|
||||||
|
//var base01 = 'f5f5f5'; // Lighter Background (Used for status bars)
|
||||||
|
var base01 = "red";
|
||||||
|
// var base02 = 'c8c8fa'; // Selection Background
|
||||||
|
var base03 = '6e6e6e'; // Comments, Invisibles, Line Highlighting
|
||||||
|
// var base04 = 'e8e8e8'; // Dark Foreground (Used for status bars)
|
||||||
|
var base05 = '333333'; // Default Foreground, Caret, Delimiters, Operators
|
||||||
|
// var base06 = 'ffffff'; // Light Foreground (Not often used)
|
||||||
|
// var base07 = 'ffffff'; // Light Background (Not often used)
|
||||||
|
var base08 = '881280'; // Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted
|
||||||
|
// var base09 = '0086b3'; // Integers, Boolean, Constants, XML Attributes, Markup Link Url
|
||||||
|
// var base0A = '795da3'; // Classes, Markup Bold, Search Text Background
|
||||||
|
var base0B = '1155cc'; // Strings, Inherited Class, Markup Code, Diff Inserted
|
||||||
|
var base0C = '994500'; // Support, Regular Expressions, Escape Characters, Markup Quotes
|
||||||
|
// var base0D = '795da3'; // Functions, Methods, Attribute IDs, Headings
|
||||||
|
var base0E = 'c80000'; // Keywords, Storage, Selector, Markup Italic, Diff Changed
|
||||||
|
// var base0F = '333333'; // Deprecated, Opening/Closing Embedded Language Tags e.g. <?php ?>
|
||||||
|
|
||||||
|
// Map ANSI colors from what babel-code-frame uses to base16-github
|
||||||
|
// See: https://github.com/babel/babel/blob/e86f62b304d280d0bab52c38d61842b853848ba6/packages/babel-code-frame/src/index.js#L9-L22
|
||||||
|
var colors = {
|
||||||
|
reset: [base05, 'transparent'],
|
||||||
|
black: base05,
|
||||||
|
red: base08 /* marker, bg-invalid */,
|
||||||
|
green: base0B /* string */,
|
||||||
|
yellow: base08 /* capitalized, jsx_tag, punctuator */,
|
||||||
|
blue: base0C,
|
||||||
|
magenta: base0C /* regex */,
|
||||||
|
cyan: base0E /* keyword */,
|
||||||
|
gray: base03 /* comment, gutter */,
|
||||||
|
lightgrey: base01,
|
||||||
|
darkgrey: base03,
|
||||||
|
};
|
||||||
|
|
||||||
|
var anserMap = {
|
||||||
|
'ansi-bright-black': 'black',
|
||||||
|
'ansi-bright-yellow': 'yellow',
|
||||||
|
'ansi-yellow': 'yellow',
|
||||||
|
'ansi-bright-green': 'green',
|
||||||
|
'ansi-green': 'green',
|
||||||
|
'ansi-bright-cyan': 'cyan',
|
||||||
|
'ansi-cyan': 'cyan',
|
||||||
|
'ansi-bright-red': 'red',
|
||||||
|
'ansi-red': 'red',
|
||||||
|
'ansi-bright-magenta': 'magenta',
|
||||||
|
'ansi-magenta': 'magenta',
|
||||||
|
'ansi-white': 'darkgrey',
|
||||||
|
};
|
||||||
|
|
||||||
|
function generateAnsiHTML(txt: string): string {
|
||||||
|
var arr = new Anser().ansiToJson(entities.encode(txt), {
|
||||||
|
use_classes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = '';
|
||||||
|
var open = false;
|
||||||
|
for (var index = 0; index < arr.length; ++index) {
|
||||||
|
var c = arr[index];
|
||||||
|
var content = c.content,
|
||||||
|
fg = c.fg;
|
||||||
|
|
||||||
|
var contentParts = content.split('\n');
|
||||||
|
for (var _index = 0; _index < contentParts.length; ++_index) {
|
||||||
|
if (!open) {
|
||||||
|
result += '<span data-ansi-line="true">';
|
||||||
|
open = true;
|
||||||
|
}
|
||||||
|
var part = contentParts[_index].replace('\r', '');
|
||||||
|
var color = colors[anserMap[fg]];
|
||||||
|
if (color != null) {
|
||||||
|
result += '<span style="color: #' + color + ';">' + part + '</span>';
|
||||||
|
} else {
|
||||||
|
if (fg != null) {
|
||||||
|
console.log('Missing color mapping: ', fg);
|
||||||
|
}
|
||||||
|
result += '<span>' + part + '</span>';
|
||||||
|
}
|
||||||
|
if (_index < contentParts.length - 1) {
|
||||||
|
result += '</span>';
|
||||||
|
open = false;
|
||||||
|
result += '<br/>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (open) {
|
||||||
|
result += '</span>';
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default generateAnsiHTML;
|
37
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getLinesAround.js
vendored
Normal file
37
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getLinesAround.js
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import { ScriptLine } from './stack-frame';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} line The line number to provide context around.
|
||||||
|
* @param {number} count The number of lines you'd like for context.
|
||||||
|
* @param {string[] | string} lines The source code.
|
||||||
|
*/
|
||||||
|
function getLinesAround(
|
||||||
|
line: number,
|
||||||
|
count: number,
|
||||||
|
lines: string[] | string
|
||||||
|
): ScriptLine[] {
|
||||||
|
if (typeof lines === 'string') {
|
||||||
|
lines = lines.split('\n');
|
||||||
|
}
|
||||||
|
const result = [];
|
||||||
|
for (
|
||||||
|
let index = Math.max(0, line - 1 - count);
|
||||||
|
index <= Math.min(lines.length - 1, line - 1 + count);
|
||||||
|
++index
|
||||||
|
) {
|
||||||
|
result.push(new ScriptLine(index + 1, lines[index], index === line - 1));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getLinesAround };
|
||||||
|
export default getLinesAround;
|
47
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getPrettyURL.js
vendored
Normal file
47
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getPrettyURL.js
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
function getPrettyURL(
|
||||||
|
sourceFileName: ?string,
|
||||||
|
sourceLineNumber: ?number,
|
||||||
|
sourceColumnNumber: ?number,
|
||||||
|
fileName: ?string,
|
||||||
|
lineNumber: ?number,
|
||||||
|
columnNumber: ?number,
|
||||||
|
compiled: boolean
|
||||||
|
): string {
|
||||||
|
let prettyURL;
|
||||||
|
if (!compiled && sourceFileName && typeof sourceLineNumber === 'number') {
|
||||||
|
// Remove everything up to the first /src/ or /node_modules/
|
||||||
|
const trimMatch = /^[/|\\].*?[/|\\]((src|node_modules)[/|\\].*)/.exec(
|
||||||
|
sourceFileName
|
||||||
|
);
|
||||||
|
if (trimMatch && trimMatch[1]) {
|
||||||
|
prettyURL = trimMatch[1];
|
||||||
|
} else {
|
||||||
|
prettyURL = sourceFileName;
|
||||||
|
}
|
||||||
|
prettyURL += ':' + sourceLineNumber;
|
||||||
|
// Note: we intentionally skip 0's because they're produced by cheap Webpack maps
|
||||||
|
if (sourceColumnNumber) {
|
||||||
|
prettyURL += ':' + sourceColumnNumber;
|
||||||
|
}
|
||||||
|
} else if (fileName && typeof lineNumber === 'number') {
|
||||||
|
prettyURL = fileName + ':' + lineNumber;
|
||||||
|
// Note: we intentionally skip 0's because they're produced by cheap Webpack maps
|
||||||
|
if (columnNumber) {
|
||||||
|
prettyURL += ':' + columnNumber;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prettyURL = 'unknown';
|
||||||
|
}
|
||||||
|
return prettyURL.replace('webpack://', '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getPrettyURL };
|
||||||
|
export default getPrettyURL;
|
160
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getSourceMap.js
vendored
Normal file
160
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getSourceMap.js
vendored
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import { SourceMapConsumer } from 'source-map';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapped instance of a <code>{@link https://github.com/mozilla/source-map SourceMapConsumer}</code>.
|
||||||
|
*
|
||||||
|
* This exposes methods which will be indifferent to changes made in <code>{@link https://github.com/mozilla/source-map source-map}</code>.
|
||||||
|
*/
|
||||||
|
class SourceMap {
|
||||||
|
__source_map: SourceMapConsumer;
|
||||||
|
|
||||||
|
constructor(sourceMap) {
|
||||||
|
this.__source_map = sourceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the original code position for a generated code position.
|
||||||
|
* @param {number} line The line of the generated code position.
|
||||||
|
* @param {number} column The column of the generated code position.
|
||||||
|
*/
|
||||||
|
getOriginalPosition(
|
||||||
|
line: number,
|
||||||
|
column: number
|
||||||
|
): { source: string, line: number, column: number } {
|
||||||
|
const {
|
||||||
|
line: l,
|
||||||
|
column: c,
|
||||||
|
source: s,
|
||||||
|
} = this.__source_map.originalPositionFor({
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
});
|
||||||
|
return { line: l, column: c, source: s };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the generated code position for an original position.
|
||||||
|
* @param {string} source The source file of the original code position.
|
||||||
|
* @param {number} line The line of the original code position.
|
||||||
|
* @param {number} column The column of the original code position.
|
||||||
|
*/
|
||||||
|
getGeneratedPosition(
|
||||||
|
source: string,
|
||||||
|
line: number,
|
||||||
|
column: number
|
||||||
|
): { line: number, column: number } {
|
||||||
|
const { line: l, column: c } = this.__source_map.generatedPositionFor({
|
||||||
|
source,
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
line: l,
|
||||||
|
column: c,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the code for a given source file name.
|
||||||
|
* @param {string} sourceName The name of the source file.
|
||||||
|
*/
|
||||||
|
getSource(sourceName: string): string {
|
||||||
|
return this.__source_map.sourceContentFor(sourceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSources(): string[] {
|
||||||
|
return this.__source_map.sources;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractSourceMapUrl(
|
||||||
|
fileUri: string,
|
||||||
|
fileContents: string
|
||||||
|
): Promise<string> {
|
||||||
|
const regex = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/gm;
|
||||||
|
let match = null;
|
||||||
|
for (;;) {
|
||||||
|
let next = regex.exec(fileContents);
|
||||||
|
if (next == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match = next;
|
||||||
|
}
|
||||||
|
if (!(match && match[1])) {
|
||||||
|
return Promise.reject(`Cannot find a source map directive for ${fileUri}.`);
|
||||||
|
}
|
||||||
|
return Promise.resolve(match[1].toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of <code>{@link SourceMap}</code> for a given fileUri and fileContents.
|
||||||
|
* @param {string} fileUri The URI of the source file.
|
||||||
|
* @param {string} fileContents The contents of the source file.
|
||||||
|
*/
|
||||||
|
async function getSourceMap(
|
||||||
|
//function getSourceMap(
|
||||||
|
fileUri: string,
|
||||||
|
fileContents: string
|
||||||
|
): Promise<SourceMap> {
|
||||||
|
|
||||||
|
let sm = await extractSourceMapUrl(fileUri, fileContents);
|
||||||
|
if (sm.indexOf('data:') === 0) {
|
||||||
|
const base64 = /^data:application\/json;([\w=:"-]+;)*base64,/;
|
||||||
|
const match2 = sm.match(base64);
|
||||||
|
if (!match2) {
|
||||||
|
throw new Error(
|
||||||
|
'Sorry, non-base64 inline source-map encoding is not supported.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sm = sm.substring(match2[0].length);
|
||||||
|
sm = window.atob(sm);
|
||||||
|
sm = JSON.parse(sm);
|
||||||
|
return new SourceMap(new SourceMapConsumer(sm));
|
||||||
|
} else {
|
||||||
|
const index = fileUri.lastIndexOf('/');
|
||||||
|
const url = fileUri.substring(0, index + 1) + sm;
|
||||||
|
const obj = await fetch(url).then(res => res.json());
|
||||||
|
return new SourceMap(new SourceMapConsumer(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
return extractSourceMapUrl(fileUri, fileContents)
|
||||||
|
.then(sm => {
|
||||||
|
if (sm.indexOf('data:') === 0) {
|
||||||
|
const base64 = /^data:application\/json;([\w=:"-]+;)*base64,/;
|
||||||
|
const match2 = sm.match(base64);
|
||||||
|
if (!match2) {
|
||||||
|
throw new Error(
|
||||||
|
'Sorry, non-base64 inline source-map encoding is not supported.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sm = sm.substring(match2[0].length);
|
||||||
|
sm = window.atob(sm);
|
||||||
|
sm = JSON.parse(sm);
|
||||||
|
return new SourceMap(new SourceMapConsumer(sm));
|
||||||
|
} else {
|
||||||
|
const index = fileUri.lastIndexOf('/');
|
||||||
|
const url = fileUri.substring(0, index + 1) + sm;
|
||||||
|
|
||||||
|
return fetch(url).then(res => res.json())
|
||||||
|
.then(obj => {
|
||||||
|
return new SourceMap(new SourceMapConsumer(obj))
|
||||||
|
})
|
||||||
|
//const obj = await fetch(url).then(res => res.json());
|
||||||
|
//return new SourceMap(new SourceMapConsumer(obj));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export { extractSourceMapUrl, getSourceMap };
|
||||||
|
export default getSourceMap;
|
48
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getStackFrames.js
vendored
Normal file
48
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getStackFrames.js
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import type { StackFrame } from './stack-frame';
|
||||||
|
import { parse } from './parser';
|
||||||
|
import { map } from './mapper';
|
||||||
|
import { unmap } from './unmapper';
|
||||||
|
|
||||||
|
function getStackFrames(
|
||||||
|
error: Error,
|
||||||
|
unhandledRejection: boolean = false,
|
||||||
|
contextSize: number = 3
|
||||||
|
): Promise<StackFrame[] | null> {
|
||||||
|
const parsedFrames = parse(error);
|
||||||
|
let enhancedFramesPromise;
|
||||||
|
if (error.__unmap_source) {
|
||||||
|
enhancedFramesPromise = unmap(
|
||||||
|
// $FlowFixMe
|
||||||
|
error.__unmap_source,
|
||||||
|
parsedFrames,
|
||||||
|
contextSize
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
enhancedFramesPromise = map(parsedFrames, contextSize);
|
||||||
|
}
|
||||||
|
return enhancedFramesPromise.then(enhancedFrames => {
|
||||||
|
if (
|
||||||
|
enhancedFrames
|
||||||
|
.map(f => f._originalFileName)
|
||||||
|
.filter(f => f != null && f.indexOf('node_modules') === -1).length === 0
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return enhancedFrames.filter(
|
||||||
|
({ functionName }) =>
|
||||||
|
functionName == null ||
|
||||||
|
functionName.indexOf('__stack_frame_overlay_proxy_console__') === -1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getStackFrames;
|
||||||
|
export { getStackFrames };
|
25
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/isBultinErrorName.js
vendored
Normal file
25
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/isBultinErrorName.js
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
function isBultinErrorName(errorName: ?string) {
|
||||||
|
switch (errorName) {
|
||||||
|
case 'EvalError':
|
||||||
|
case 'InternalError':
|
||||||
|
case 'RangeError':
|
||||||
|
case 'ReferenceError':
|
||||||
|
case 'SyntaxError':
|
||||||
|
case 'TypeError':
|
||||||
|
case 'URIError':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { isBultinErrorName };
|
||||||
|
export default isBultinErrorName;
|
21
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/isInternalFile.js
vendored
Normal file
21
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/isInternalFile.js
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
function isInternalFile(sourceFileName: ?string, fileName: ?string) {
|
||||||
|
return (
|
||||||
|
sourceFileName == null ||
|
||||||
|
sourceFileName === '' ||
|
||||||
|
sourceFileName.indexOf('/~/') !== -1 ||
|
||||||
|
sourceFileName.indexOf('/node_modules/') !== -1 ||
|
||||||
|
sourceFileName.trim().indexOf(' ') !== -1 ||
|
||||||
|
fileName == null ||
|
||||||
|
fileName === ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { isInternalFile };
|
69
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/mapper.js
vendored
Normal file
69
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/mapper.js
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import StackFrame from './stack-frame';
|
||||||
|
import { getSourceMap } from './getSourceMap';
|
||||||
|
import { getLinesAround } from './getLinesAround';
|
||||||
|
import { settle } from 'settle-promise';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhances a set of <code>StackFrame</code>s with their original positions and code (when available).
|
||||||
|
* @param {StackFrame[]} frames A set of <code>StackFrame</code>s which contain (generated) code positions.
|
||||||
|
* @param {number} [contextLines=3] The number of lines to provide before and after the line specified in the <code>StackFrame</code>.
|
||||||
|
*/
|
||||||
|
async function map(
|
||||||
|
frames: StackFrame[],
|
||||||
|
contextLines: number = 3
|
||||||
|
): Promise<StackFrame[]> {
|
||||||
|
const cache: any = {};
|
||||||
|
const files: string[] = [];
|
||||||
|
frames.forEach(frame => {
|
||||||
|
const { fileName } = frame;
|
||||||
|
if (fileName == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (files.indexOf(fileName) !== -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
files.push(fileName);
|
||||||
|
});
|
||||||
|
await settle(
|
||||||
|
files.map(async fileName => {
|
||||||
|
const fileSource = await fetch(fileName).then(r => r.text());
|
||||||
|
const map = await getSourceMap(fileName, fileSource);
|
||||||
|
cache[fileName] = { fileSource, map };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return frames.map(frame => {
|
||||||
|
const { functionName, fileName, lineNumber, columnNumber } = frame;
|
||||||
|
let { map, fileSource } = cache[fileName] || {};
|
||||||
|
if (map == null || lineNumber == null) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
const { source, line, column } = map.getOriginalPosition(
|
||||||
|
lineNumber,
|
||||||
|
columnNumber
|
||||||
|
);
|
||||||
|
const originalSource = source == null ? [] : map.getSource(source) || [];
|
||||||
|
return new StackFrame(
|
||||||
|
functionName,
|
||||||
|
fileName,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
getLinesAround(lineNumber, contextLines, fileSource),
|
||||||
|
functionName,
|
||||||
|
source,
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
getLinesAround(line, contextLines, originalSource)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { map };
|
||||||
|
export default map;
|
61
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/parseCompileError.js
vendored
Normal file
61
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/parseCompileError.js
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// @flow
|
||||||
|
import Anser from 'anser';
|
||||||
|
|
||||||
|
export type ErrorLocation = {|
|
||||||
|
fileName: string,
|
||||||
|
lineNumber: number,
|
||||||
|
colNumber?: number,
|
||||||
|
|};
|
||||||
|
|
||||||
|
const filePathRegex = /^\.(\/[^/\n ]+)+\.[^/\n ]+$/;
|
||||||
|
|
||||||
|
const lineNumberRegexes = [
|
||||||
|
// Babel syntax errors
|
||||||
|
// Based on syntax error formating of babylon parser
|
||||||
|
// https://github.com/babel/babylon/blob/v7.0.0-beta.22/src/parser/location.js#L19
|
||||||
|
/^.*\((\d+):(\d+)\)$/,
|
||||||
|
|
||||||
|
// ESLint errors
|
||||||
|
// Based on eslintFormatter in react-dev-utils
|
||||||
|
/^Line (\d+):.+$/,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Based on error formatting of webpack
|
||||||
|
// https://github.com/webpack/webpack/blob/v3.5.5/lib/Stats.js#L183-L217
|
||||||
|
function parseCompileError(message: string): ?ErrorLocation {
|
||||||
|
const lines: Array<string> = message.split('\n');
|
||||||
|
let fileName: string = '';
|
||||||
|
let lineNumber: number = 0;
|
||||||
|
let colNumber: number = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line: string = Anser.ansiToText(lines[i]).trim();
|
||||||
|
if (!line) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileName && line.match(filePathRegex)) {
|
||||||
|
fileName = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
let k = 0;
|
||||||
|
while (k < lineNumberRegexes.length) {
|
||||||
|
const match: ?Array<string> = line.match(lineNumberRegexes[k]);
|
||||||
|
if (match) {
|
||||||
|
lineNumber = parseInt(match[1], 10);
|
||||||
|
// colNumber starts with 0 and hence add 1
|
||||||
|
colNumber = parseInt(match[2], 10) + 1 || 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName && lineNumber) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileName && lineNumber ? { fileName, lineNumber, colNumber } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseCompileError;
|
91
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/parser.js
vendored
Normal file
91
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/parser.js
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import StackFrame from './stack-frame';
|
||||||
|
|
||||||
|
const regexExtractLocation = /\(?(.+?)(?::(\d+))?(?::(\d+))?\)?$/;
|
||||||
|
|
||||||
|
function extractLocation(token: string): [string, number, number] {
|
||||||
|
return regexExtractLocation
|
||||||
|
.exec(token)
|
||||||
|
.slice(1)
|
||||||
|
.map(v => {
|
||||||
|
const p = Number(v);
|
||||||
|
if (!isNaN(p)) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const regexValidFrame_Chrome = /^\s*(at|in)\s.+(:\d+)/;
|
||||||
|
const regexValidFrame_FireFox = /(^|@)\S+:\d+|.+line\s+\d+\s+>\s+(eval|Function).+/;
|
||||||
|
|
||||||
|
function parseStack(stack: string[]): StackFrame[] {
|
||||||
|
const frames = stack
|
||||||
|
.filter(
|
||||||
|
e => regexValidFrame_Chrome.test(e) || regexValidFrame_FireFox.test(e)
|
||||||
|
)
|
||||||
|
.map(e => {
|
||||||
|
if (regexValidFrame_FireFox.test(e)) {
|
||||||
|
// Strip eval, we don't care about it
|
||||||
|
let isEval = false;
|
||||||
|
if (/ > (eval|Function)/.test(e)) {
|
||||||
|
e = e.replace(
|
||||||
|
/ line (\d+)(?: > eval line \d+)* > (eval|Function):\d+:\d+/g,
|
||||||
|
':$1'
|
||||||
|
);
|
||||||
|
isEval = true;
|
||||||
|
}
|
||||||
|
const data = e.split(/[@]/g);
|
||||||
|
const last = data.pop();
|
||||||
|
return new StackFrame(
|
||||||
|
data.join('@') || (isEval ? 'eval' : null),
|
||||||
|
...extractLocation(last)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Strip eval, we don't care about it
|
||||||
|
if (e.indexOf('(eval ') !== -1) {
|
||||||
|
e = e.replace(/(\(eval at [^()]*)|(\),.*$)/g, '');
|
||||||
|
}
|
||||||
|
if (e.indexOf('(at ') !== -1) {
|
||||||
|
e = e.replace(/\(at /, '(');
|
||||||
|
}
|
||||||
|
const data = e
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/g)
|
||||||
|
.slice(1);
|
||||||
|
const last = data.pop();
|
||||||
|
return new StackFrame(data.join(' ') || null, ...extractLocation(last));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns an <code>Error</code>, or similar object, into a set of <code>StackFrame</code>s.
|
||||||
|
* @alias parse
|
||||||
|
*/
|
||||||
|
function parseError(error: Error | string | string[]): StackFrame[] {
|
||||||
|
if (error == null) {
|
||||||
|
throw new Error('You cannot pass a null object.');
|
||||||
|
}
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
return parseStack(error.split('\n'));
|
||||||
|
}
|
||||||
|
if (Array.isArray(error)) {
|
||||||
|
return parseStack(error);
|
||||||
|
}
|
||||||
|
if (typeof error.stack === 'string') {
|
||||||
|
return parseStack(error.stack.split('\n'));
|
||||||
|
}
|
||||||
|
throw new Error('The error you provided does not contain a stack trace.');
|
||||||
|
}
|
||||||
|
|
||||||
|
export { parseError as parse };
|
||||||
|
export default parseError;
|
18
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/pollyfills.js
vendored
Normal file
18
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/pollyfills.js
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (typeof Promise === 'undefined') {
|
||||||
|
// Rejection tracking prevents a common issue where React gets into an
|
||||||
|
// inconsistent state due to an error, but it gets swallowed by a Promise,
|
||||||
|
// and the user has no idea what causes React's erratic future behavior.
|
||||||
|
require('promise/lib/rejection-tracking').enable();
|
||||||
|
window.Promise = require('promise/lib/es6-extensions.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object.assign() is commonly used with React.
|
||||||
|
// It will use the native implementation if it's present and isn't buggy.
|
||||||
|
Object.assign = require('object-assign');
|
120
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/stack-frame.js
vendored
Normal file
120
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/stack-frame.js
vendored
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
/** A container holding a script line. */
|
||||||
|
class ScriptLine {
|
||||||
|
/** The line number of this line of source. */
|
||||||
|
lineNumber: number;
|
||||||
|
/** The content (or value) of this line of source. */
|
||||||
|
content: string;
|
||||||
|
/** Whether or not this line should be highlighted. Particularly useful for error reporting with context. */
|
||||||
|
highlight: boolean;
|
||||||
|
|
||||||
|
constructor(lineNumber: number, content: string, highlight: boolean = false) {
|
||||||
|
this.lineNumber = lineNumber;
|
||||||
|
this.content = content;
|
||||||
|
this.highlight = highlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a stack frame.
|
||||||
|
*/
|
||||||
|
class StackFrame {
|
||||||
|
functionName: string | null;
|
||||||
|
fileName: string | null;
|
||||||
|
lineNumber: number | null;
|
||||||
|
columnNumber: number | null;
|
||||||
|
|
||||||
|
_originalFunctionName: string | null;
|
||||||
|
_originalFileName: string | null;
|
||||||
|
_originalLineNumber: number | null;
|
||||||
|
_originalColumnNumber: number | null;
|
||||||
|
|
||||||
|
_scriptCode: ScriptLine[] | null;
|
||||||
|
_originalScriptCode: ScriptLine[] | null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
functionName: string | null = null,
|
||||||
|
fileName: string | null = null,
|
||||||
|
lineNumber: number | null = null,
|
||||||
|
columnNumber: number | null = null,
|
||||||
|
scriptCode: ScriptLine[] | null = null,
|
||||||
|
sourceFunctionName: string | null = null,
|
||||||
|
sourceFileName: string | null = null,
|
||||||
|
sourceLineNumber: number | null = null,
|
||||||
|
sourceColumnNumber: number | null = null,
|
||||||
|
sourceScriptCode: ScriptLine[] | null = null
|
||||||
|
) {
|
||||||
|
if (functionName && functionName.indexOf('Object.') === 0) {
|
||||||
|
functionName = functionName.slice('Object.'.length);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
// Chrome has a bug with inferring function.name:
|
||||||
|
// https://github.com/facebook/create-react-app/issues/2097
|
||||||
|
// Let's ignore a meaningless name we get for top-level modules.
|
||||||
|
functionName === 'friendlySyntaxErrorLabel' ||
|
||||||
|
functionName === 'exports.__esModule' ||
|
||||||
|
functionName === '<anonymous>' ||
|
||||||
|
!functionName
|
||||||
|
) {
|
||||||
|
functionName = null;
|
||||||
|
}
|
||||||
|
this.functionName = functionName;
|
||||||
|
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.lineNumber = lineNumber;
|
||||||
|
this.columnNumber = columnNumber;
|
||||||
|
|
||||||
|
this._originalFunctionName = sourceFunctionName;
|
||||||
|
this._originalFileName = sourceFileName;
|
||||||
|
this._originalLineNumber = sourceLineNumber;
|
||||||
|
this._originalColumnNumber = sourceColumnNumber;
|
||||||
|
|
||||||
|
this._scriptCode = scriptCode;
|
||||||
|
this._originalScriptCode = sourceScriptCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of this function.
|
||||||
|
*/
|
||||||
|
getFunctionName(): string {
|
||||||
|
return this.functionName || '(anonymous function)';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the source of the frame.
|
||||||
|
* This contains the file name, line number, and column number when available.
|
||||||
|
*/
|
||||||
|
getSource(): string {
|
||||||
|
let str = '';
|
||||||
|
if (this.fileName != null) {
|
||||||
|
str += this.fileName + ':';
|
||||||
|
}
|
||||||
|
if (this.lineNumber != null) {
|
||||||
|
str += this.lineNumber + ':';
|
||||||
|
}
|
||||||
|
if (this.columnNumber != null) {
|
||||||
|
str += this.columnNumber + ':';
|
||||||
|
}
|
||||||
|
return str.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pretty version of this stack frame.
|
||||||
|
*/
|
||||||
|
toString(): string {
|
||||||
|
const functionName = this.getFunctionName();
|
||||||
|
const source = this.getSource();
|
||||||
|
return `${functionName}${source ? ` (${source})` : ``}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { StackFrame, ScriptLine };
|
||||||
|
export default StackFrame;
|
126
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/unmapper.js
vendored
Normal file
126
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/unmapper.js
vendored
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import StackFrame from './stack-frame';
|
||||||
|
import { getSourceMap } from './getSourceMap';
|
||||||
|
import { getLinesAround } from './getLinesAround';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
function count(search: string, string: string): number {
|
||||||
|
// Count starts at -1 becuse a do-while loop always runs at least once
|
||||||
|
let count = -1,
|
||||||
|
index = -1;
|
||||||
|
do {
|
||||||
|
// First call or the while case evaluated true, meaning we have to make
|
||||||
|
// count 0 or we found a character
|
||||||
|
++count;
|
||||||
|
// Find the index of our search string, starting after the previous index
|
||||||
|
index = string.indexOf(search, index + 1);
|
||||||
|
} while (index !== -1);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns a set of mapped <code>StackFrame</code>s back into their generated code position and enhances them with code.
|
||||||
|
* @param {string} fileUri The URI of the <code>bundle.js</code> file.
|
||||||
|
* @param {StackFrame[]} frames A set of <code>StackFrame</code>s which are already mapped and missing their generated positions.
|
||||||
|
* @param {number} [fileContents=3] The number of lines to provide before and after the line specified in the <code>StackFrame</code>.
|
||||||
|
*/
|
||||||
|
async function unmap(
|
||||||
|
_fileUri: string | { uri: string, contents: string },
|
||||||
|
frames: StackFrame[],
|
||||||
|
contextLines: number = 3
|
||||||
|
): Promise<StackFrame[]> {
|
||||||
|
let fileContents = typeof _fileUri === 'object' ? _fileUri.contents : null;
|
||||||
|
let fileUri = typeof _fileUri === 'object' ? _fileUri.uri : _fileUri;
|
||||||
|
if (fileContents == null) {
|
||||||
|
fileContents = await fetch(fileUri).then(res => res.text());
|
||||||
|
}
|
||||||
|
const map = await getSourceMap(fileUri, fileContents);
|
||||||
|
return frames.map(frame => {
|
||||||
|
const {
|
||||||
|
functionName,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
_originalLineNumber,
|
||||||
|
} = frame;
|
||||||
|
if (_originalLineNumber != null) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
let { fileName } = frame;
|
||||||
|
if (fileName) {
|
||||||
|
// The web version of this module only provides POSIX support, so Windows
|
||||||
|
// paths like C:\foo\\baz\..\\bar\ cannot be normalized.
|
||||||
|
// A simple solution to this is to replace all `\` with `/`, then
|
||||||
|
// normalize afterwards.
|
||||||
|
fileName = path.normalize(fileName.replace(/[\\]+/g, '/'));
|
||||||
|
}
|
||||||
|
if (fileName == null) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
const fN: string = fileName;
|
||||||
|
const source = map
|
||||||
|
.getSources()
|
||||||
|
// Prepare path for normalization; see comment above for reasoning.
|
||||||
|
.map(s => s.replace(/[\\]+/g, '/'))
|
||||||
|
.filter(p => {
|
||||||
|
p = path.normalize(p);
|
||||||
|
const i = p.lastIndexOf(fN);
|
||||||
|
return i !== -1 && i === p.length - fN.length;
|
||||||
|
})
|
||||||
|
.map(p => ({
|
||||||
|
token: p,
|
||||||
|
seps: count(path.sep, path.normalize(p)),
|
||||||
|
penalties: count('node_modules', p) + count('~', p),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => {
|
||||||
|
const s = Math.sign(a.seps - b.seps);
|
||||||
|
if (s !== 0) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return Math.sign(a.penalties - b.penalties);
|
||||||
|
});
|
||||||
|
if (source.length < 1 || lineNumber == null) {
|
||||||
|
return new StackFrame(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
functionName,
|
||||||
|
fN,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const sourceT = source[0].token;
|
||||||
|
const { line, column } = map.getGeneratedPosition(
|
||||||
|
sourceT,
|
||||||
|
lineNumber,
|
||||||
|
// $FlowFixMe
|
||||||
|
columnNumber
|
||||||
|
);
|
||||||
|
const originalSource = map.getSource(sourceT);
|
||||||
|
return new StackFrame(
|
||||||
|
functionName,
|
||||||
|
fileUri,
|
||||||
|
line,
|
||||||
|
column || null,
|
||||||
|
getLinesAround(line, contextLines, fileContents || []),
|
||||||
|
functionName,
|
||||||
|
fN,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
getLinesAround(lineNumber, contextLines, originalSource)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { unmap };
|
||||||
|
export default unmap;
|
Loading…
Reference in New Issue
Block a user