├── .editorconfig ├── .gitignore ├── .prettierrc.js ├── CHANGELOG.EN.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.en.md ├── README.md ├── __tests__ ├── __mocks__ │ └── index.js └── unit │ └── utils │ └── shared.test.js ├── config ├── env.js ├── jest │ ├── babelTransform.js │ ├── cssTransform.js │ ├── fileTransform.js │ ├── graphqlTransform.js │ └── setUp.js ├── modules.js ├── paths.js ├── pnpTs.js ├── polyfills.js ├── webpack.config.js └── webpackDevServer.config.js ├── docs ├── asset-manifest.json ├── favicon.ico ├── index.html ├── index.js └── manifest.json ├── jest.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── scripts ├── build.js ├── docs.js ├── start.js └── test.js └── src ├── App.js ├── App.module.css ├── components ├── header │ ├── header.module.css │ └── index.jsx ├── index.js ├── lockscroll │ └── index.jsx ├── panel │ ├── index.jsx │ └── pannel.module.css ├── printer │ ├── index.jsx │ └── printer.module.css ├── protector │ └── index.jsx ├── reactDevTools │ ├── app │ │ ├── App.module.css │ │ └── index.jsx │ ├── browser │ │ ├── browserWindow.jsx │ │ ├── browserWindow.module.css │ │ └── devTools.jsx │ ├── console │ │ ├── console.jsx │ │ └── console.module.css │ ├── icon │ │ ├── icon.jsx │ │ ├── icon.module.css │ │ ├── iconButton.jsx │ │ └── iconButton.module.css │ ├── index.jsx │ └── setupDevToolsBackend.js ├── showmore │ ├── index.jsx │ └── showmore.module.css └── toolbar │ ├── index.jsx │ └── toolbar.module.css ├── constants ├── error │ └── index.js ├── feature │ └── index.js └── index.js ├── index.css ├── index.js ├── modules ├── application │ └── index.js ├── console │ └── index.js ├── header │ └── index.js ├── index.js ├── network │ └── index.js ├── panelCon │ └── index.js ├── performance │ └── index.js ├── proxy │ └── index.js ├── settings │ └── index.js ├── system │ └── index.js └── toolbar │ └── index.js ├── panels ├── about │ ├── about.module.css │ └── index.jsx ├── application │ ├── application.module.css │ └── index.jsx ├── console │ ├── console.module.css │ └── index.jsx ├── detection │ ├── detection.module.css │ └── index.jsx ├── elements │ ├── elements.module.css │ └── index.jsx ├── network │ ├── index.jsx │ └── network.module.css ├── performance │ ├── index.jsx │ └── performance.module.css ├── proxy │ ├── index.jsx │ └── proxy.module.css ├── settings │ ├── index.jsx │ └── settings.module.css └── system │ ├── index.jsx │ └── system.module.css ├── polyfill.js ├── reducers ├── application │ └── index.js ├── console │ └── index.js ├── index.js ├── network │ └── index.js ├── settings │ └── index.js ├── system │ └── index.js └── tab │ └── index.js ├── store └── index.js └── utils ├── ajax └── index.js ├── console └── index.js ├── copy └── index.js ├── dimension └── index.js ├── emitter └── index.js ├── headers └── index.js ├── index.js ├── log └── index.js ├── network └── index.js ├── object └── index.js ├── performance └── index.js ├── resources └── index.js ├── shared └── index.js ├── storage └── index.js ├── ua └── index.js └── url └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [{*.js,*.json,*.yml}] 10 | indent_size = 2 11 | indent_style = space -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | semi: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | bracketSpacing: true, 7 | jsxBracketSameLine: true, 8 | arrowParens: 'avoid', 9 | insertPragma: false, 10 | tabWidth: 2, 11 | useTabs: false, 12 | }; 13 | -------------------------------------------------------------------------------- /CHANGELOG.EN.md: -------------------------------------------------------------------------------- 1 | # 2021.8.24 2 | 3 | + Release v1.0.0 4 | 5 | # 2021.8.25 6 | 7 | + Release v1.0.1 8 | + Add English readme 9 | 10 | # 2021.8.28 11 | 12 | + Release v1.0.2 13 | + Add some unit tests and delete some useless code 14 | 15 | # 2021.8.31 16 | 17 | + Release v1.0.3 18 | + Update readme 19 | 20 | # 2021.9.02 21 | 22 | + Release v1.0.4 23 | + Add Js&&Css latest feature detection 24 | 25 | # 2021.9.03 26 | 27 | + Upgrade related dependencies 28 | + Built-in react developer tools to increase react component debugging capabilities 29 | 30 | # 2021.9.06 31 | 32 | + Add setting panel, support dynamic adjustment of panel height [https://github.com/tnfe/mdebug/pull/19](https://github.com/tnfe/mdebug/pull/19) 33 | + Added support for formatting the response in the web panel [https://github.com/tnfe/mdebug/pull/21](https://github.com/tnfe/mdebug/pull/21) 34 | + Release 2.0.0 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2021.8.24 2 | 3 | + mdebug发布 v1.0.0 4 | 5 | # 2021.8.25 6 | 7 | + mdebug发布 v1.0.1 8 | + 增加英文readme 9 | 10 | # 2021.8.28 11 | 12 | + mdebug发布 v1.0.2 13 | + 增加部分单元测试,删除部分无用代码 14 | 15 | # 2021.8.31 16 | 17 | + mdebug发布 v1.0.3 18 | + 更新readme 19 | 20 | # 2021.9.02 21 | 22 | + mdebug发布 v1.0.4 23 | + 增加Js&&Css最新特性检测 24 | 25 | # 2021.9.03 26 | 27 | + 升级相关依赖 28 | + 内嵌React开发者工具, 增加react组件调试能力 29 | 30 | # 2021.9.06 31 | 32 | + 增加设置面板,支持动态调整面板高度 [https://github.com/tnfe/mdebug/pull/19](https://github.com/tnfe/mdebug/pull/19) 33 | + 网络面板增加支持对response格式化 [https://github.com/tnfe/mdebug/pull/21](https://github.com/tnfe/mdebug/pull/21) 34 | + 发布2.0.0 35 | 36 | # 2021.9.17 37 | 38 | + 新增console.clear [https://github.com/tnfe/mdebug/pull/26](https://github.com/tnfe/mdebug/pull/26) 39 | + 发布2.0.2 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 为mdebug做出贡献 2 | 3 | 欢迎您 [提出问题](https://github.com/tnfe/mdebug/issues) 或 [merge requests](https://github.com/tnfe/mdebug/merge_requests), 建议您在为 mdebug 做出贡献前先阅读以下 mdebug 贡献指南。 4 | 5 | ## issues 6 | 7 | 我们通过 [issues](https://github.com/tnfe/mdebug/issues) 来收集问题和功能相关的需求。 8 | 9 | ### 首先查看已知的问题 10 | 11 | 在您准备提出问题以前,请先查看现有的 [issues](https://github.com/tnfe/mdebug/issues) 是否已有其他人提出过相似的功能或问题,以确保您提出的问题是有效的。 12 | 13 | ### 提交问题 14 | 15 | 问题的表述应当尽可能的详细,可以包含相关的代码块。 16 | 17 | ## Merge Requests 18 | 19 | 我们十分期待您通过 [Merge Requests](https://github.com/tnfe/mdebug/merge_requests) 让 mdebug 变的更加完善。 20 | 21 | ### 分支管理 22 | 23 | mdebug 主仓库只包含 master 分支,其将作为稳定的开发分支,经过测试后会打 Tag 进行发布。 24 | 25 | ### Commit Message 26 | 27 | 我们希望您能使用`npm run commit`来提交代码,保持项目的一致性。 28 | 这样可以方便生成每个版本的 Changelog,很容易地追溯历史。 29 | 30 | ### MR/PR 流程 31 | 32 | TNFE 团队会查看所有的 MR/PR,我们会运行一些代码检查和测试,一经测试通过,我们会接受这次 MR/PR,但不会立即发布外网,会有一些延迟。 33 | 34 | 当您准备 MR 时,请确保已经完成以下几个步骤: 35 | 36 | 1. 将主仓库代码 Fork 到自己名下。 37 | 2. 基于 `master` 分支创建您的开发分支。 38 | 3. 如果您更改了 API(s) 请更新代码及文档。 39 | 4. 检查您的代码语法及格式。 40 | 5. 提一个 MR/PR 到主仓库的 `master` 分支上。 41 | 42 | ### 本地开发 43 | 44 | 首先安装相关依赖 45 | 46 | ```bash 47 | npm i 48 | ``` 49 | 50 | 运行代码 51 | 52 | ```bash 53 | npm run start 54 | ``` 55 | 56 | 使用 [npm link](https://docs.npmjs.com/cli/link.html) 进行测试 57 | 58 | ```bash 59 | npm link 60 | ``` 61 | 62 | ## 许可证 63 | 64 | 通过为 mdebug 做出贡献,代表您同意将其版权归为 mdebug 所有,开源协议为 [MIT LICENSE](https://opensource.org/licenses/MIT) 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 html5@mobile 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | English | [简体中文](./README.md) 2 |

3 | 4 |

5 | 6 |

A Lightweight, Easy To Extend Web Debugging Tool Build With React CHANGELOG

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

Easy to use

Full-featured

Easy to expand

high performance

Use the cdn method, one-click accessChrome devtools-like, built-in React developer tools, support log, network, element, proxy, storage, performance, etc., have better network capture capabilities and rich log display formsExpose rich internal events, which can be seamlessly integrated with react componentsSupport large amount of data display, no lag
21 | 22 |
23 | NPM Version 24 | PRs 25 | Node Version 26 |
27 | 28 | ## Demos 29 | 30 | https://tnfe.github.io/mdebug 31 | 32 | ![image](https://user-images.githubusercontent.com/6822604/131059931-7efb7494-82fe-4a27-bd79-ed2bd9ce2c11.png) 33 | 34 | 35 | 36 | ## Examples 37 | 38 | + Vanilla 39 | 40 | [![Edit crimson-sun-py8x7](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/crimson-sun-py8x7?fontsize=14&hidenavigation=1&theme=dark) 41 | 42 | 43 | ## Installation 44 | 45 | #### Install using npm 46 | ``` 47 | npm install mdebug --save 48 | 49 | ``` 50 | ## Useage 51 | 52 | ### 1. ES6 53 | ```javascript 54 | import mdebug from 'mdebug'; 55 | mdebug.init(); 56 | ``` 57 | 58 | ### 2.CDN 59 | ```javascript 60 | (function() { 61 | var scp = document.createElement('script'); 62 | // Load the latest mdebug version 63 | scp.src = 'https://unpkg.com/mdebug@latest/dist/index.js'; 64 | scp.async = true; 65 | scp.charset = 'utf-8'; 66 | // Successfully loaded and initialized 67 | scp.onload = function() { 68 | mdebug.init(); 69 | }; 70 | // Load state switch callback 71 | scp.onreadystate = function() {}; 72 | // Load failed callback 73 | scp.onerror = function() {}; 74 | document.getElementsByTagName('head')[0].appendChild(scp); 75 | })(); 76 | ``` 77 | ## API 78 | 79 | ### 1. init 80 | ```javascript 81 | mdebug.init({ 82 | containerId: '' // mdebug mounts the container id, if it is empty, a unique id will be automatically generated internally, 83 | plugins: [], // Incoming mdebug plugin 84 | hideToolbar: [], // Pass in the tab id that needs to be hidden 85 | }); 86 | ``` 87 | ### 2. addPlugin 88 | ```javascript 89 | mdebug.addPlugin({ 90 | id: '', // tab id 91 | name: '', // Chinese title corresponding to tab, 92 | enName: '', // English title corresponding to tab 93 | component: () => {}, // React component corresponding to tab 94 | }); 95 | ``` 96 | 97 | ### 3. removePlugin 98 | ```javascript 99 | // Support the id of the panel to be removed 100 | /* 101 | System => system; 102 | Elements => elements; 103 | Console => console 104 | Application => application 105 | NetWork => network 106 | Performance => performance 107 | Settings => settings 108 | */ 109 | mdebug.removePlugin([]); 110 | ``` 111 | 112 | ### 4. exportLog 113 | ```javascript 114 | /* 115 | @returned { 116 | type: '' // Log type 117 | source: [], // Original log 118 | } 119 | @params type 120 | // type is equal to log, return all console logs 121 | // type is equal to net, return all net logs 122 | */ 123 | mdebug.exportLog(type); 124 | 125 | ``` 126 | 127 | ### 5. on 128 | ```javascript 129 | mdebug.on(eventName, callback); 130 | ``` 131 | ### 6. emit 132 | ```javascript 133 | mdebug.emit(eventName, data); 134 | ``` 135 | 136 | ## Event list 137 | | **Event name** | **params** | **Trigger timing** | 138 | | ---------- | :-----------: | :-----------: | 139 | | ready | object | mdebug loaded 140 | | addTab | object | Add panel 141 | | removeTab | array | Remove panel | 142 | | changeTab | object | Panel switch| 143 | 144 | 145 | ## development 146 | 147 | 1. npm i 148 | 2. npm start 149 | 3. npm run build 150 | ## Projects 151 | 1. [eruda](https://github.com/liriliri/eruda) 152 | 2. [vConsole](https://github.com/Tencent/vConsole) 153 | 3. [react-json-tree](https://github.com/alexkuz/react-json-tree) 154 | 4. [React-based mobile debugging solution](https://github.com/abell123456/mdebug) 155 | 5. [a useful debugger for mobile](https://github.com/ghking1/mdebug) 156 | 6. [autoDevTools](https://github.com/chokcoco/autoDevTools) 157 | 7. [react-inspector](https://github.com/xyc/react-inspector) 158 | 8. [web-console](https://github.com/whinc/web-console) 159 | 9. [ChromeDevTools](https://github.com/ChromeDevTools/devtools-frontend) 160 | 161 | ## License 162 | The MIT License (MIT). Please see [License File](./LICENSE) for more information. 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [English](./README.en.md) | 简体中文 2 |

3 | 4 |

5 | 6 |

基于React开发的移动web调试工具 更新日志

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |

简单易用

功能全面

易扩展

高性能

使用cdn方式,一键接入类Chrome devtools, 内嵌React开发者工具,支持日志,网络,元素,代理,存储,性能等, 具有更好的网络捕获能力和丰富的日志展现形式暴露内部丰富的事件, 可以和react组件无缝进行集成支持大数据量展示, 不卡顿
22 | 23 |
24 | NPM Version 25 | PRs 26 | Node Version 27 |
28 | 29 | ## 一、快速体验 30 | 31 | https://ihtml5.github.io/mdebug 32 | 33 | ![image](https://user-images.githubusercontent.com/6822604/178648277-0748f41a-39d1-4115-85a5-127537acc1e7.png) 34 | 35 | 36 | 37 | ## 二、Examples 38 | 39 | + Vanilla 40 | 41 | [![Edit crimson-sun-py8x7](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/crimson-sun-py8x7?fontsize=14&hidenavigation=1&theme=dark) 42 | 43 | ## 三、安装 44 | 45 | #### Install using npm 46 | ``` 47 | npm install mdebug --save 48 | 49 | ``` 50 | ## 四、使用 51 | 52 | ### 1. ES6 53 | ```javascript 54 | import mdebug from 'mdebug'; 55 | mdebug.init(); 56 | ``` 57 | 58 | ### 2.CDN 59 | ```javascript 60 | (function() { 61 | var scp = document.createElement('script'); 62 | // 加载最新的mdebug版本 63 | scp.src = 'https://unpkg.com/mdebug@latest/dist/index.js'; 64 | scp.async = true; 65 | scp.charset = 'utf-8'; 66 | // 加载成功并初始化 67 | scp.onload = function() { 68 | mdebug.init(); 69 | }; 70 | // 加载状态切换回调 71 | scp.onreadystate = function() {}; 72 | // 加载失败回调 73 | scp.onerror = function() {}; 74 | document.getElementsByTagName('head')[0].appendChild(scp); 75 | })(); 76 | ``` 77 | ## 五、API 78 | 79 | ### 1. init 80 | ```javascript 81 | mdebug.init({ 82 | containerId: '' // mdebug挂载容器id, 如果传空, 内部会自动生成一个不重复的id, 83 | plugins: [], // 传入mdebug插件 84 | hideToolbar: [], // 传入需要隐藏的tab id 85 | }); 86 | ``` 87 | ### 2. addPlugin 88 | ```javascript 89 | mdebug.addPlugin({ 90 | id: '', // tab id 91 | name: '', // tab对应的中文title, 92 | enName: '', // tab对应的英文title 93 | component: () => {}, // tab对应的react组件 94 | }); 95 | ``` 96 | 97 | ### 3. removePlugin 98 | ```javascript 99 | // 支持移除的panel对应的id 100 | /* 101 | System => system; 102 | Elements => elements; 103 | Console => console 104 | Application => application 105 | NetWork => network 106 | Performance => performance 107 | Settings => settings 108 | */ 109 | mdebug.removePlugin([]); 110 | ``` 111 | 112 | ### 4. exportLog 113 | ```javascript 114 | /* 115 | @returned { 116 | type: '' // 日志类型 117 | source: [], // 原始日志 118 | } 119 | @params type 120 | // type等于log, 返回所有的console日志 121 | // type等于net, 返回所有的net日志 122 | */ 123 | mdebug.exportLog(type); 124 | 125 | ``` 126 | 127 | ### 5. on 128 | ```javascript 129 | mdebug.on(eventName, callback); 130 | ``` 131 | ### 6. emit 132 | ```javascript 133 | mdebug.emit(eventName, data); 134 | ``` 135 | 136 | ## 六、事件列表 137 | | **事件名称** | **数据** | **触发时机** | 138 | | ---------- | :-----------: | :-----------: | 139 | | ready | object | mdebug加载完毕 140 | | addTab | object | 增加面板 141 | | removeTab | array | 移除面板 | 142 | | changeTab | object | 面板切换| 143 | 144 | 145 | ## 七、开发 146 | 147 | 1. npm i 148 | 2. npm start // 启动开发 149 | 3. npm run build //打包 150 | 4. npm run test // 单元测试 151 | 152 | ## 八、相关文章 153 | 1. [移动端前端开发调试](https://www.cnblogs.com/yzg1/p/5160594.html?utm_source=tuicool&utm_medium=referral) 154 | 2. [Chrome 开发者工具](https://developers.google.com/web/tools/chrome-devtools/) 155 | 156 | ## 九、相关项目 157 | 1. [eruda](https://github.com/liriliri/eruda) 158 | 2. [vConsole](https://github.com/Tencent/vConsole) 159 | 3. [react-json-tree](https://github.com/alexkuz/react-json-tree) 160 | 4. [基于React的移动端调试解决方案](https://github.com/abell123456/mdebug) 161 | 5. [a useful debugger for mobile](https://github.com/ghking1/mdebug) 162 | 6. [autoDevTools](https://github.com/chokcoco/autoDevTools) 163 | 7. [react-inspector](https://github.com/xyc/react-inspector) 164 | 8. [web-console](https://github.com/whinc/web-console) 165 | 9. [ChromeDevTools](https://github.com/ChromeDevTools/devtools-frontend) 166 | 167 | ## 十、License 168 | The MIT License (MIT). Please see [License File](./LICENSE) for more information. 169 | -------------------------------------------------------------------------------- /__tests__/__mocks__/index.js: -------------------------------------------------------------------------------- 1 | export const verifyBoolean = true; 2 | export const verifyArray = []; 3 | export const verifyObject = {}; 4 | export const verifyError = new Error('message'); 5 | export const verifyXmlhttp = new XMLHttpRequest(); -------------------------------------------------------------------------------- /__tests__/unit/utils/shared.test.js: -------------------------------------------------------------------------------- 1 | import { isObject, isError, isXMLHttpRequest } from '@/utils/shared'; 2 | import { 3 | verifyBoolean, 4 | verifyArray, 5 | verifyObject, 6 | verifyError, 7 | verifyXmlhttp 8 | } from '../../__mocks__'; 9 | 10 | describe('utils/shared module', () => { 11 | test('shared/isObject', () => { 12 | expect(isObject(verifyBoolean)).toBe(false); 13 | expect(isObject(verifyArray)).toBe(false); 14 | expect(isObject(verifyObject)).toBe(true); 15 | }); 16 | 17 | test('shared/isError', () => { 18 | expect(isError(verifyError)).toBe(true); 19 | expect(isError(verifyXmlhttp)).toBe(false); 20 | }); 21 | 22 | test('shared/isXMLHttpRequest', () => { 23 | expect(isXMLHttpRequest(verifyError)).toBe(false); 24 | expect(isXMLHttpRequest(verifyArray)).toBe(false); 25 | expect(isXMLHttpRequest(verifyXmlhttp)).toBe(true); 26 | }) 27 | }); -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebook/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 50 | // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in Webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | } 81 | ); 82 | // Stringify all values so we can feed into Webpack DefinePlugin 83 | const stringified = { 84 | 'process.env': Object.keys(raw).reduce((env, key) => { 85 | env[key] = JSON.stringify(raw[key]); 86 | return env; 87 | }, {}), 88 | }; 89 | 90 | return { raw, stringified }; 91 | } 92 | 93 | module.exports = getClientEnvironment; 94 | -------------------------------------------------------------------------------- /config/jest/babelTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const babelJest = require('babel-jest'); 4 | 5 | module.exports = babelJest.createTransformer({ 6 | presets: [require.resolve('babel-preset-react-app')], 7 | 8 | }); 9 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFileName = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFileName}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /config/jest/graphqlTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const loader = require('graphql-tag/loader'); 4 | 5 | module.exports = { 6 | process(src) { 7 | return loader.call({ cacheable() {} }, src); 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /config/jest/setUp.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihtml5/mdebug/742b4a0e9c73ebb82ed2ee6a33e9c35c9f173fd8/config/jest/setUp.js -------------------------------------------------------------------------------- /config/modules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | 8 | /** 9 | * Get the baseUrl of a compilerOptions object. 10 | * 11 | * @param {Object} options 12 | */ 13 | function getAdditionalModulePaths(options = {}) { 14 | const baseUrl = options.baseUrl; 15 | 16 | // We need to explicitly check for null and undefined (and not a falsy value) because 17 | // TypeScript treats an empty string as `.`. 18 | if (baseUrl == null) { 19 | // If there's no baseUrl set we respect NODE_PATH 20 | // Note that NODE_PATH is deprecated and will be removed 21 | // in the next major release of create-react-app. 22 | 23 | const nodePath = process.env.NODE_PATH || ''; 24 | return nodePath.split(path.delimiter).filter(Boolean); 25 | } 26 | 27 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 28 | 29 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 30 | // the default behavior. 31 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 32 | return null; 33 | } 34 | 35 | // Allow the user set the `baseUrl` to `appSrc`. 36 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 37 | return [paths.appSrc]; 38 | } 39 | 40 | // Otherwise, throw an error. 41 | throw new Error( 42 | chalk.red.bold( 43 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 44 | ' Create React App does not support other values at this time.' 45 | ) 46 | ); 47 | } 48 | 49 | function getModules() { 50 | // Check if TypeScript is setup 51 | const hasTsConfig = fs.existsSync(paths.appTsConfig); 52 | const hasJsConfig = fs.existsSync(paths.appJsConfig); 53 | 54 | if (hasTsConfig && hasJsConfig) { 55 | throw new Error( 56 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 57 | ); 58 | } 59 | 60 | let config; 61 | 62 | // If there's a tsconfig.json we assume it's a 63 | // TypeScript project and set up the config 64 | // based on tsconfig.json 65 | if (hasTsConfig) { 66 | config = require(paths.appTsConfig); 67 | // Otherwise we'll check if there is jsconfig.json 68 | // for non TS projects. 69 | } else if (hasJsConfig) { 70 | config = require(paths.appJsConfig); 71 | } 72 | 73 | config = config || {}; 74 | const options = config.compilerOptions || {}; 75 | 76 | const additionalModulePaths = getAdditionalModulePaths(options); 77 | 78 | return { 79 | additionalModulePaths: additionalModulePaths, 80 | hasTsConfig, 81 | }; 82 | } 83 | 84 | module.exports = getModules(); 85 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => envPublicUrl || require(appPackageJson).homepage; 26 | 27 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 28 | // "public path" at which the app is served. 29 | // Webpack needs to know it to put the right

基于React开发的移动web调试工具 更新日志

简单易用

功能全面

易扩展

高性能

使用cdn方式,一键接入类Chrome devtools, 内嵌React开发者工具,支持日志,网络,元素,代理,存储,性能等, 具有更好的网络捕获能力和丰富的日志展现形式暴露内部丰富的事件, 可以和react组件无缝进行集成支持大数据量展示, 不卡顿
NPM VersionPRsNode Version

一、快速体验

https://tnfe.github.io/mdebug

二、Installation

Install using npm

  2 | npm install mdebug --save
  3 | 

三、Useage

1.ES6

  4 |   import mdebug from 'mdebug';
  5 |   mdebug.init();
  6 | 

2.CDN

  7 | (function() {
  8 |     var scp = document.createElement('script');
  9 |     // 加载最新的mdebug版本
 10 |     scp.src = 'https://new.inews.gtimg.com/tnews/9d243fb8/a1f8/9d243fb8-a1f8-4bf7-9047-dfa01d8c9ca0.js';
 11 |     scp.async = true;
 12 |     scp.charset = 'utf-8';
 13 |     // 加载成功并初始化
 14 |     scp.onload = function() {
 15 |         mdebug.init();
 16 |     };
 17 |     // 加载状态切换回调
 18 |     scp.onreadystate = function() {};
 19 |     // 加载失败回调 
 20 |     scp.onerror = function() {};
 21 |     document.getElementsByTagName('head')[0].appendChild(scp);
 22 | })();
 23 | 

四、API

1.init

 24 | mdebug.init({
 25 |     containerId: '' // mdebug挂载容器id, 如果传空, 内部会自动生成一个不重复的id,
 26 |     plugins: [], // 传入mdebug插件
 27 |     hideToolbar: [], // 传入需要隐藏的tab id
 28 | });
 29 | 

2.addPlugin

 30 | mdebug.addPlugin({
 31 |     id: '', // tab id
 32 |     name: '', // tab对应的中文title,
 33 |     enName: '', // tab对应的英文title
 34 |     component: () => {}, // tab对应的react组件
 35 | });
 36 | 

3.removePlugin

 37 | // 支持移除的panel对应的id
 38 | /*
 39 | System => system;
 40 | Elements => elements;
 41 | Console => console
 42 | Application => application
 43 | NetWork => network
 44 | Performance => performance
 45 | Settings => settings
 46 | */
 47 | mdebug.removePlugin([]);
 48 | 

4.exportLog

 49 | /*
 50 | @returned {
 51 |   type: '' // 日志类型
 52 |   source: [], // 原始日志
 53 | }
 54 | @params type
 55 | // type等于log, 返回所有的console日志
 56 | // type等于net, 返回所有的net日志
 57 | */
 58 | mdebug.exportLog(type);
 59 | 

5.on

 60 | mdebug.on(eventName, callback);
 61 | 

6.emit

 62 | mdebug.emit(eventName, data);
 63 | 

五、事件列表

事件名称

数据

触发时机

readyobjectmdebug加载完毕
addTabobject增加面板
removeTabarray移除面板
changeTabobject面板切换

六、开发

  1. npm i
  2. npm start // 启动开发
  3. npm run build //打包

七、Articles

  1. 移动端前端开发调试
  2. Chrome 开发者工具

八、Projects

  1. eruda
  2. vConsole
  3. react-json-tree
  4. 基于React的移动端调试解决方案
  5. a useful debugger for mobile
  6. autoDevTools
  7. react-inspector
  8. web-console
  9. ChromeDevTools

九、License

The MIT License (MIT). Please see License File for more information.

105 | -------------------------------------------------------------------------------- /docs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | reporters: ['default'], 3 | collectCoverage: true, 4 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$'], 5 | testURL: 'http://localhost', 6 | transform: { 7 | '^.+\\.(js|jsx|mjs)$': 'babel-jest', 8 | '^.+\\.css$': '/config/jest/cssTransform.js', 9 | '^(?!.*\\.(js|jsx|mjs|css|json)$)': '/config/jest/fileTransform.js', 10 | }, 11 | moduleFileExtensions: ['web.js', 'mjs', 'js', 'json', 'web.jsx', 'jsx', 'node'], 12 | verbose: true, 13 | testPathIgnorePatterns: ['/node_modules/', 'jest.config.js', '/mock/', '/__mocks__'], 14 | setupFiles: [], 15 | testMatch: ['/__tests__/**/*.test.{js,jsx,mjs}'], 16 | testEnvironment: 'jsdom', 17 | moduleNameMapper: { 18 | '@/(.*)$': '/src/$1', 19 | '~/(.*)$': '/__mocks__/$1', 20 | }, 21 | coverageDirectory: '/coverage', 22 | collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], 23 | }; 24 | 25 | module.exports = config; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdebug", 3 | "version": "2.0.2", 4 | "main": "dist/index.js", 5 | "module": "dist/index.js", 6 | "jsnext:main": "dist/index.js", 7 | "dependencies": { 8 | "@babel/core": "7.4.3", 9 | "@babel/runtime": "7.0.0-beta.46", 10 | "@svgr/webpack": "4.1.0", 11 | "@typescript-eslint/eslint-plugin": "1.6.0", 12 | "@typescript-eslint/parser": "1.6.0", 13 | "babel-core": "7.0.0-bridge.0", 14 | "babel-eslint": "10.0.1", 15 | "babel-jest": "^24.8.0", 16 | "babel-loader": "8.0.5", 17 | "babel-plugin-named-asset-import": "^0.3.2", 18 | "babel-preset-react-app": "^9.0.0", 19 | "camelcase": "^5.2.0", 20 | "case-sensitive-paths-webpack-plugin": "2.2.0", 21 | "chalk": "2.4.1", 22 | "chokidar": "^3.5.1", 23 | "cookie": "^0.4.1", 24 | "css-loader": "2.1.1", 25 | "dotenv": "6.2.0", 26 | "dotenv-expand": "4.2.0", 27 | "eslint": "^5.16.0", 28 | "eslint-config-react-app": "^4.0.1", 29 | "eslint-loader": "2.1.2", 30 | "eslint-plugin-flowtype": "2.50.1", 31 | "eslint-plugin-import": "2.16.0", 32 | "eslint-plugin-jsx-a11y": "6.2.1", 33 | "eslint-plugin-react": "7.12.4", 34 | "eslint-plugin-react-hooks": "^1.5.0", 35 | "feature.js": "^1.1.3", 36 | "file-loader": "3.0.1", 37 | "fs-extra": "7.0.1", 38 | "graphql": "0.13.2", 39 | "graphql-tag": "2.9.2", 40 | "html-webpack-plugin": "4.0.0-beta.5", 41 | "identity-obj-proxy": "3.0.0", 42 | "is-class": "0.0.9", 43 | "is-wsl": "^1.1.0", 44 | "jest": "24.7.1", 45 | "jest-environment-jsdom-fourteen": "0.1.0", 46 | "jest-resolve": "24.7.1", 47 | "jest-watch-typeahead": "0.3.0", 48 | "loader-utils": "^1.1.0", 49 | "mini-css-extract-plugin": "0.5.0", 50 | "object-assign": "4.1.1", 51 | "optimize-css-assets-webpack-plugin": "5.0.1", 52 | "pnp-webpack-plugin": "1.2.1", 53 | "postcss-flexbugs-fixes": "4.1.0", 54 | "postcss-loader": "3.0.0", 55 | "postcss-normalize": "7.0.1", 56 | "postcss-preset-env": "6.6.0", 57 | "postcss-safe-parser": "4.0.1", 58 | "promise": "8.0.1", 59 | "qs": "^6.9.4", 60 | "raf": "3.4.0", 61 | "react": "^17.0.2", 62 | "react-dom": "^17.0.2", 63 | "react-devtools-inline": "4.0.5", 64 | "react-dev-utils": "^9.0.1", 65 | "react-draggable": "^4.4.3", 66 | "react-inspector": "^2.3.0", 67 | "react-redux": "^5.0.7", 68 | "redux": "^4.0.0", 69 | "resolve": "1.10.0", 70 | "sass-loader": "7.1.0", 71 | "scheduler": "0.0.0-375616788", 72 | "semver": "6.0.0", 73 | "style-loader": "0.23.1", 74 | "svgr": "1.9.2", 75 | "terser-webpack-plugin": "^4.2.3", 76 | "ts-pnp": "1.1.2", 77 | "url-loader": "1.1.2", 78 | "webpack": "^4.46.0", 79 | "webpack-dev-server": "3.2.1", 80 | "webpack-manifest-plugin": "2.0.4", 81 | "whatwg-fetch": "2.0.4" 82 | }, 83 | "devDependencies": { 84 | "prettier": "^1.18.2", 85 | "webpack-bundle-analyzer": "^4.4.2" 86 | }, 87 | "scripts": { 88 | "start": "node scripts/start.js", 89 | "build": "node scripts/build.js && node scripts/docs.js", 90 | "test": "jest", 91 | "deploy": "now --target production", 92 | "prettier": "prettier --single-quote --trailing-comma es5 --write '{public,src}/**/*.js'" 93 | }, 94 | "eslintConfig": { 95 | "extends": "react-app" 96 | }, 97 | "browserslist": { 98 | "development": [ 99 | "> 1%", 100 | "last 2 versions", 101 | "Firefox ESR", 102 | "Opera 12.1", 103 | "iOS 7" 104 | ], 105 | "production": [ 106 | "> 1%", 107 | "last 2 versions", 108 | "Firefox ESR", 109 | "Opera 12.1", 110 | "iOS 7" 111 | ] 112 | }, 113 | "babel": { 114 | "presets": [ 115 | "react-app" 116 | ] 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihtml5/mdebug/742b4a0e9c73ebb82ed2ee6a33e9c35c9f173fd8/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 13 | mdebug 14 | 15 | 47 | 48 | 49 | 50 |

51 | 53 |

54 | 55 |

基于React开发的移动web调试工具 更新日志

57 | 58 | 59 | 63 | 67 | 71 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
60 |

61 |

简单易用

62 |
64 |

65 |

功能全面

66 |
68 |

69 |

易扩展

70 |
72 |

73 |

高性能

74 |
使用cdn方式,一键接入类Chrome devtools, 内嵌React开发者工具,支持日志,网络,元素,代理,存储,性能等, 具有更好的网络捕获能力和丰富的日志展现形式暴露内部丰富的事件, 可以和react组件无缝进行集成支持大数据量展示, 不卡顿
83 | 84 |
85 | NPM Version 87 | PRs 89 | Node Version 91 |
92 | 93 |

一、快速体验

94 | 95 | https://tnfe.github.io/mdebug 96 | 97 |

二、Installation

98 | 99 |

Install using npm

100 |
101 | npm install mdebug --save
102 | 
103 |

三、Useage

104 | 105 |

1.ES6

106 |
107 |   import mdebug from 'mdebug';
108 |   mdebug.init();
109 | 
110 |

2.CDN

111 |
112 | (function() {
113 |     var scp = document.createElement('script');
114 |     // 加载最新的mdebug版本
115 |     scp.src = 'https://new.inews.gtimg.com/tnews/9d243fb8/a1f8/9d243fb8-a1f8-4bf7-9047-dfa01d8c9ca0.js';
116 |     scp.async = true;
117 |     scp.charset = 'utf-8';
118 |     // 加载成功并初始化
119 |     scp.onload = function() {
120 |         mdebug.init();
121 |     };
122 |     // 加载状态切换回调
123 |     scp.onreadystate = function() {};
124 |     // 加载失败回调 
125 |     scp.onerror = function() {};
126 |     document.getElementsByTagName('head')[0].appendChild(scp);
127 | })();
128 | 
129 | 130 |

四、API

131 |

1.init

132 |
133 | mdebug.init({
134 |     containerId: '' // mdebug挂载容器id, 如果传空, 内部会自动生成一个不重复的id,
135 |     plugins: [], // 传入mdebug插件
136 |     hideToolbar: [], // 传入需要隐藏的tab id
137 | });
138 | 
139 |

2.addPlugin

140 |
141 | mdebug.addPlugin({
142 |     id: '', // tab id
143 |     name: '', // tab对应的中文title,
144 |     enName: '', // tab对应的英文title
145 |     component: () => {}, // tab对应的react组件
146 | });
147 | 
148 | 149 |

3.removePlugin

150 |
151 | // 支持移除的panel对应的id
152 | /*
153 | System => system;
154 | Elements => elements;
155 | Console => console
156 | Application => application
157 | NetWork => network
158 | Performance => performance
159 | Settings => settings
160 | */
161 | mdebug.removePlugin([]);
162 | 
163 | 164 |

4.exportLog

165 |
166 | /*
167 | @returned {
168 |   type: '' // 日志类型
169 |   source: [], // 原始日志
170 | }
171 | @params type
172 | // type等于log, 返回所有的console日志
173 | // type等于net, 返回所有的net日志
174 | */
175 | mdebug.exportLog(type);
176 | 
177 | 178 |

5.on

179 |
180 | mdebug.on(eventName, callback);
181 | 
182 |

6.emit

183 |
184 | mdebug.emit(eventName, data);
185 | 
186 |

五、事件列表

187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 |

事件名称

数据

触发时机

readyobjectmdebug加载完毕
addTabobject增加面板
removeTabarray移除面板
changeTabobject面板切换
197 |

六、开发

198 |
    199 |
  1. npm i
  2. 200 |
  3. npm start // 启动开发
  4. 201 |
  5. npm run build //打包
  6. 202 |
203 |

七、Articles

204 |
    205 |
  1. 移动端前端开发调试
  2. 206 |
  3. Chrome 开发者工具
  4. 207 |
208 |

八、Projects

209 |
    210 |
  1. eruda
  2. 211 |
  3. vConsole
  4. 212 |
  5. react-json-tree
  6. 213 |
  7. 基于React的移动端调试解决方案
  8. 214 |
  9. a useful debugger for mobile
  10. 215 |
  11. autoDevTools
  12. 216 |
  13. react-inspector
  14. 217 |
  15. web-console
  16. 218 |
  17. ChromeDevTools
  18. 219 |
220 |

九、License

221 |

The MIT License (MIT). Please see License File for more information.

222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 279 | 289 | 290 | 291 | 292 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const path = require('path'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const fs = require('fs-extra'); 21 | const webpack = require('webpack'); 22 | const configFactory = require('../config/webpack.config'); 23 | const paths = require('../config/paths'); 24 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 25 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 26 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 27 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 28 | const printBuildError = require('react-dev-utils/printBuildError'); 29 | 30 | const measureFileSizesBeforeBuild = 31 | FileSizeReporter.measureFileSizesBeforeBuild; 32 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 33 | const useYarn = fs.existsSync(paths.yarnLockFile); 34 | 35 | // These sizes are pretty large. We'll warn for bundles exceeding them. 36 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 37 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 38 | 39 | const isInteractive = process.stdout.isTTY; 40 | 41 | // Warn and crash if required files are missing 42 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 43 | process.exit(1); 44 | } 45 | 46 | // Generate configuration 47 | const config = configFactory('production'); 48 | 49 | // We require that you explicitly set browsers and do not fall back to 50 | // browserslist defaults. 51 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 52 | checkBrowsers(paths.appPath, isInteractive) 53 | .then(() => { 54 | // First, read the current file sizes in build directory. 55 | // This lets us display how much they changed later. 56 | return measureFileSizesBeforeBuild(paths.appBuild); 57 | }) 58 | .then(previousFileSizes => { 59 | // Remove all content but keep the directory so that 60 | // if you're in it, you don't end up in Trash 61 | fs.emptyDirSync(paths.appBuild); 62 | // Merge with the public folder 63 | copyPublicFolder(); 64 | // Start the webpack build 65 | return build(previousFileSizes); 66 | }) 67 | .then( 68 | ({ stats, previousFileSizes, warnings }) => { 69 | if (warnings.length) { 70 | console.log(chalk.yellow('Compiled with warnings.\n')); 71 | console.log(warnings.join('\n\n')); 72 | console.log( 73 | '\nSearch for the ' + 74 | chalk.underline(chalk.yellow('keywords')) + 75 | ' to learn more about each warning.' 76 | ); 77 | console.log( 78 | 'To ignore, add ' + 79 | chalk.cyan('// eslint-disable-next-line') + 80 | ' to the line before.\n' 81 | ); 82 | } else { 83 | console.log(chalk.green('Compiled successfully.\n')); 84 | } 85 | 86 | console.log('File sizes after gzip:\n'); 87 | printFileSizesAfterBuild( 88 | stats, 89 | previousFileSizes, 90 | paths.appBuild, 91 | WARN_AFTER_BUNDLE_GZIP_SIZE, 92 | WARN_AFTER_CHUNK_GZIP_SIZE 93 | ); 94 | console.log(); 95 | 96 | const appPackage = require(paths.appPackageJson); 97 | const publicUrl = paths.publicUrl; 98 | const publicPath = config.output.publicPath; 99 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 100 | printHostingInstructions( 101 | appPackage, 102 | publicUrl, 103 | publicPath, 104 | buildFolder, 105 | useYarn 106 | ); 107 | }, 108 | err => { 109 | console.log(chalk.red('Failed to compile.\n')); 110 | printBuildError(err); 111 | process.exit(1); 112 | } 113 | ) 114 | .catch(err => { 115 | if (err && err.message) { 116 | console.log(err.message); 117 | } 118 | process.exit(1); 119 | }); 120 | 121 | // Create the production build and print the deployment instructions. 122 | function build(previousFileSizes) { 123 | // We used to support resolving modules according to `NODE_PATH`. 124 | // This now has been deprecated in favor of jsconfig/tsconfig.json 125 | // This lets you use absolute paths in imports inside large monorepos: 126 | if (process.env.NODE_PATH) { 127 | console.log( 128 | chalk.yellow( 129 | 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' 130 | ) 131 | ); 132 | console.log(); 133 | } 134 | 135 | console.log('Creating an optimized production build...'); 136 | 137 | const compiler = webpack(config); 138 | return new Promise((resolve, reject) => { 139 | compiler.run((err, stats) => { 140 | let messages; 141 | if (err) { 142 | if (!err.message) { 143 | return reject(err); 144 | } 145 | messages = formatWebpackMessages({ 146 | errors: [err.message], 147 | warnings: [], 148 | }); 149 | } else { 150 | messages = formatWebpackMessages( 151 | stats.toJson({ all: false, warnings: true, errors: true }) 152 | ); 153 | } 154 | if (messages.errors.length) { 155 | // Only keep the first error. Others are often indicative 156 | // of the same problem, but confuse the reader with noise. 157 | if (messages.errors.length > 1) { 158 | messages.errors.length = 1; 159 | } 160 | return reject(new Error(messages.errors.join('\n\n'))); 161 | } 162 | if ( 163 | process.env.CI && 164 | (typeof process.env.CI !== 'string' || 165 | process.env.CI.toLowerCase() !== 'false') && 166 | messages.warnings.length 167 | ) { 168 | console.log( 169 | chalk.yellow( 170 | '\nTreating warnings as errors because process.env.CI = true.\n' + 171 | 'Most CI servers set it automatically.\n' 172 | ) 173 | ); 174 | return reject(new Error(messages.warnings.join('\n\n'))); 175 | } 176 | 177 | return resolve({ 178 | stats, 179 | previousFileSizes, 180 | warnings: messages.warnings, 181 | }); 182 | }); 183 | }); 184 | } 185 | 186 | function copyPublicFolder() { 187 | fs.copySync(paths.appPublic, paths.appBuild, { 188 | dereference: true, 189 | filter: file => file !== paths.appHtml, 190 | }); 191 | } 192 | -------------------------------------------------------------------------------- /scripts/docs.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | /** 5 | * @param { delPath:String } (需要删除文件的地址) 6 | * @param { direct:Boolean } (是否需要处理地址) 7 | */ 8 | function deleteFile(origDelPath, direct) { 9 | const delPath = direct ? origDelPath : path.join(__dirname, origDelPath); 10 | try { 11 | /** 12 | * @des 判断文件或文件夹是否存在 13 | */ 14 | if (fs.existsSync(delPath)) { 15 | fs.unlinkSync(delPath); 16 | } else { 17 | console.log('inexistence path:', delPath); 18 | } 19 | } catch (error) { 20 | console.log('del error', error); 21 | } 22 | } 23 | 24 | /** 25 | * @des 参数解释同上 26 | */ 27 | function copyFolder(copiedPath, resultPath, direct) { 28 | if (!direct) { 29 | copiedPath = path.join(__dirname, copiedPath); 30 | resultPath = path.join(__dirname, resultPath); 31 | } 32 | 33 | function createDir(dirPath) { 34 | fs.mkdirSync(dirPath); 35 | } 36 | 37 | if (fs.existsSync(copiedPath)) { 38 | createDir(resultPath); 39 | /** 40 | * @des 方式一:利用子进程操作命令行方式 41 | */ 42 | // child_process.spawn('cp', ['-r', copiedPath, resultPath]) 43 | 44 | /** 45 | * @des 方式二: 46 | */ 47 | const files = fs.readdirSync(copiedPath, { withFileTypes: true }); 48 | for (let i = 0; i < files.length; i++) { 49 | const cf = files[i]; 50 | const ccp = path.join(copiedPath, cf.name); 51 | const crp = path.join(resultPath, cf.name); 52 | if (cf.isFile()) { 53 | /** 54 | * @des 创建文件,使用流的形式可以读写大文件 55 | */ 56 | const readStream = fs.createReadStream(ccp); 57 | const writeStream = fs.createWriteStream(crp); 58 | readStream.pipe(writeStream); 59 | } else { 60 | try { 61 | /** 62 | * @des 判断读(R_OK | W_OK)写权限 63 | */ 64 | fs.accessSync(path.join(crp, '..'), fs.constants.W_OK); 65 | copyFolder(ccp, crp, true); 66 | } catch (error) { 67 | console.log('folder write error:', error); 68 | } 69 | } 70 | } 71 | } else { 72 | console.log('do not exist path: ', copiedPath); 73 | } 74 | } 75 | 76 | function deleteFolder(origDelPath) { 77 | const delPath = path.join(__dirname, origDelPath); 78 | try { 79 | if (fs.existsSync(delPath)) { 80 | const delFn = function(address) { 81 | const files = fs.readdirSync(address); 82 | for (let i = 0; i < files.length; i++) { 83 | const dirPath = path.join(address, files[i]); 84 | if (fs.statSync(dirPath).isDirectory()) { 85 | delFn(dirPath); 86 | } else { 87 | deleteFile(dirPath, true); 88 | } 89 | } 90 | /** 91 | * @des 只能删空文件夹 92 | */ 93 | fs.rmdirSync(address); 94 | }; 95 | delFn(delPath); 96 | } else { 97 | console.log('do not exist: ', delPath); 98 | } 99 | } catch (error) { 100 | console.log('del folder error', error); 101 | } 102 | } 103 | 104 | deleteFolder('../docs'); 105 | copyFolder('../dist', '../docs'); 106 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const fs = require('fs'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const webpack = require('webpack'); 21 | const WebpackDevServer = require('webpack-dev-server'); 22 | const clearConsole = require('react-dev-utils/clearConsole'); 23 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 24 | const { 25 | choosePort, 26 | createCompiler, 27 | prepareProxy, 28 | prepareUrls, 29 | } = require('react-dev-utils/WebpackDevServerUtils'); 30 | const openBrowser = require('react-dev-utils/openBrowser'); 31 | const paths = require('../config/paths'); 32 | const configFactory = require('../config/webpack.config'); 33 | const createDevServerConfig = require('../config/webpackDevServer.config'); 34 | 35 | const useYarn = fs.existsSync(paths.yarnLockFile); 36 | const isInteractive = process.stdout.isTTY; 37 | 38 | // Warn and crash if required files are missing 39 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 40 | process.exit(1); 41 | } 42 | 43 | // Tools like Cloud9 rely on this. 44 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; 45 | const HOST = process.env.HOST || '0.0.0.0'; 46 | 47 | if (process.env.HOST) { 48 | console.log( 49 | chalk.cyan( 50 | `Attempting to bind to HOST environment variable: ${chalk.yellow( 51 | chalk.bold(process.env.HOST) 52 | )}` 53 | ) 54 | ); 55 | console.log( 56 | `If this was unintentional, check that you haven't mistakenly set it in your shell.` 57 | ); 58 | console.log( 59 | `Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}` 60 | ); 61 | console.log(); 62 | } 63 | 64 | // We require that you explicitly set browsers and do not fall back to 65 | // browserslist defaults. 66 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 67 | checkBrowsers(paths.appPath, isInteractive) 68 | .then(() => { 69 | // We attempt to use the default port but if it is busy, we offer the user to 70 | // run on a different port. `choosePort()` Promise resolves to the next free port. 71 | return choosePort(HOST, DEFAULT_PORT); 72 | }) 73 | .then(port => { 74 | if (port == null) { 75 | // We have not found a port. 76 | return; 77 | } 78 | const config = configFactory('development'); 79 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 80 | const appName = require(paths.appPackageJson).name; 81 | const useTypeScript = fs.existsSync(paths.appTsConfig); 82 | const urls = prepareUrls(protocol, HOST, port); 83 | const devSocket = { 84 | warnings: warnings => 85 | devServer.sockWrite(devServer.sockets, 'warnings', warnings), 86 | errors: errors => 87 | devServer.sockWrite(devServer.sockets, 'errors', errors), 88 | }; 89 | // Create a webpack compiler that is configured with custom messages. 90 | const compiler = createCompiler({ 91 | appName, 92 | config, 93 | devSocket, 94 | urls, 95 | useYarn, 96 | useTypeScript, 97 | webpack, 98 | }); 99 | // Load proxy config 100 | const proxySetting = require(paths.appPackageJson).proxy; 101 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic); 102 | // Serve webpack assets generated by the compiler over a web server. 103 | const serverConfig = createDevServerConfig( 104 | proxyConfig, 105 | urls.lanUrlForConfig 106 | ); 107 | const devServer = new WebpackDevServer(compiler, serverConfig); 108 | // Launch WebpackDevServer. 109 | devServer.listen(port, HOST, err => { 110 | if (err) { 111 | return console.log(err); 112 | } 113 | if (isInteractive) { 114 | clearConsole(); 115 | } 116 | 117 | // We used to support resolving modules according to `NODE_PATH`. 118 | // This now has been deprecated in favor of jsconfig/tsconfig.json 119 | // This lets you use absolute paths in imports inside large monorepos: 120 | if (process.env.NODE_PATH) { 121 | console.log( 122 | chalk.yellow( 123 | 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' 124 | ) 125 | ); 126 | console.log(); 127 | } 128 | 129 | console.log(chalk.cyan('Starting the development server...\n')); 130 | openBrowser(urls.localUrlForBrowser); 131 | }); 132 | 133 | ['SIGINT', 'SIGTERM'].forEach(function(sig) { 134 | process.on(sig, function() { 135 | devServer.close(); 136 | process.exit(); 137 | }); 138 | }); 139 | }) 140 | .catch(err => { 141 | if (err && err.message) { 142 | console.log(err.message); 143 | } 144 | process.exit(1); 145 | }); 146 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | // Do this as the first thing so that any code reading it knows the right env. 2 | process.env.BABEL_ENV = 'test'; 3 | process.env.NODE_ENV = 'test'; 4 | process.env.PUBLIC_URL = ''; 5 | 6 | // Makes the script crash on unhandled rejections instead of silently 7 | // ignoring them. In the future, promise rejections that are not handled will 8 | // terminate the Node.js process with a non-zero exit code. 9 | process.on('unhandledRejection', err => { 10 | throw err; 11 | }); 12 | 13 | // Ensure environment variables are read. 14 | require('../config/env'); 15 | 16 | const jest = require('jest'); 17 | let argv = process.argv.slice(2); 18 | 19 | // Watch unless on CI, in coverage mode, or explicitly running all tests 20 | if (!process.env.CI && argv.indexOf('--coverage') === -1 && argv.indexOf('--watchAll') === -1) { 21 | argv.push('--watch'); 22 | } 23 | 24 | jest.run(argv); 25 | -------------------------------------------------------------------------------- /src/App.module.css: -------------------------------------------------------------------------------- 1 | .mdebug *, 2 | .mdebug *::before, 3 | .mdebug *::after { 4 | box-sizing: border-box; 5 | -webkit-box-sizing: border-box; 6 | -moz-box-sizing: border-box; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | .mdebug { 12 | position: absolute; 13 | bottom: 0; 14 | left: 0; 15 | width: 100%; 16 | max-height: 100%; 17 | min-height: 300px; 18 | height: 40%; 19 | z-index: 10002; 20 | font-size: 13px; 21 | background-color: #fff; 22 | } 23 | 24 | .mdebugResize { 25 | height: 10px; 26 | width: 100%; 27 | cursor: ns-resize; 28 | position: absolute; 29 | top: -5px; 30 | left: 0; 31 | } 32 | 33 | .mdebugCon { 34 | position: fixed; 35 | top: 0; 36 | left: 0; 37 | bottom: 0; 38 | right: 0; 39 | width: 100%; 40 | height: 100%; 41 | z-index: 999999; 42 | } 43 | .mdebugMask { 44 | position: absolute; 45 | top: 0; 46 | left: 0; 47 | bottom: 0; 48 | right: 0; 49 | width: 100%; 50 | height: 100%; 51 | background-color: rgba(0, 0, 0, 0.2); 52 | cursor: pointer; 53 | } 54 | .mdebugMaskNone { 55 | display: none; 56 | } 57 | .mdebugActionCon { 58 | display: flex; 59 | position: absolute; 60 | right: 0; 61 | bottom: 0; 62 | width: 100%; 63 | width: 100%; 64 | list-style: none; 65 | border: 1px solid rgb(40, 131, 233); 66 | padding: 0; 67 | -webkit-overflow-scrolling: touch; 68 | overflow-x: scroll; 69 | overflow-y: hidden; 70 | z-index: 2000; 71 | } 72 | .mdebugAction { 73 | display: flex; 74 | width: 100%; 75 | background-color: #fff; 76 | } 77 | 78 | .mdebugAction li { 79 | display: flex; 80 | flex: 1; 81 | padding: 10px; 82 | font-size: 12px; 83 | width: 100%; 84 | height: 100%; 85 | justify-content: center; 86 | align-items: center; 87 | border-right: 1px solid rgb(40, 131, 233, 0.3); 88 | font-weight: 700; 89 | cursor: pointer; 90 | } 91 | .mdebugAction li:last-child { 92 | border-right: none; 93 | } 94 | .mdebugBtn { 95 | display: block; 96 | position: fixed; 97 | right: 0.76923077em; 98 | bottom: 0.76923077em; 99 | color: #fff; 100 | background-color: rgb(40, 131, 233); 101 | line-height: 1; 102 | font-size: 1.07692308em; 103 | padding: 0.61538462em 1.23076923em; 104 | z-index: 10000; 105 | border-radius: 0.30769231em; 106 | box-shadow: 0 0 0.61538462em rgba(0, 0, 0, 0.4); 107 | cursor: pointer; 108 | } 109 | -------------------------------------------------------------------------------- /src/components/header/header.module.css: -------------------------------------------------------------------------------- 1 | .mdebug-header { 2 | display: flex; 3 | width: 100%; 4 | list-style: none; 5 | border: 1px solid rgb(40, 131, 233); 6 | padding: 0; 7 | -webkit-overflow-scrolling: touch; 8 | overflow-x: scroll; 9 | overflow-y: hidden; 10 | } 11 | .mdebug-header::-webkit-scrollbar { 12 | display: none; 13 | -ms-overflow-style: none; 14 | } 15 | .mdebug-header li { 16 | display: flex; 17 | width: auto; 18 | padding: 12px; 19 | font-size: 12px; 20 | width: 100%; 21 | height: 100%; 22 | justify-content: center; 23 | align-items: center; 24 | border-right: 1px solid rgb(40, 131, 233, 0.3); 25 | font-weight: 700; 26 | cursor: pointer; 27 | } 28 | .mdebug-header li:last-child { 29 | border-right: none; 30 | } 31 | .mdebug-selectedTab { 32 | background-color: rgb(40, 131, 233); 33 | color: #fff; 34 | font-weight: 600; 35 | } 36 | #medebug-content ul { 37 | background-color: #fff!important; 38 | } -------------------------------------------------------------------------------- /src/components/header/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, Fragment } from 'react'; 2 | import styles from './header.module.css'; 3 | 4 | class Header extends PureComponent { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | currentIndex: 0, 9 | }; 10 | } 11 | render() { 12 | const { tabs, onSelectTab, curTab = {}, options, enableReactDevTools } = this.props; 13 | const { currentIndex } = curTab; 14 | const { hideToolbar = ['custom'] } = options || {}; 15 | const hideNewToolbar = !enableReactDevTools ? [...hideToolbar, 'react'] : hideToolbar; 16 | return ( 17 | 18 |
    19 | {tabs 20 | .filter(tab => !tab.alias || hideNewToolbar.indexOf(tab.alias) < 0) 21 | .map((source, index) => ( 22 |
  • { 25 | this.setState({ 26 | showDebug: true, 27 | currentIndex: index, 28 | }); 29 | onSelectTab({ 30 | currentIndex: index, 31 | name: source.name, 32 | enName: source.enName, 33 | id: source.id, 34 | }); 35 | }} 36 | className={ 37 | index === currentIndex 38 | ? styles['mdebug-selectedTab'] 39 | : styles['mdebug-noselected'] 40 | }> 41 | {source.enName} 42 |
  • 43 | ))} 44 |
45 |
46 | ); 47 | } 48 | } 49 | export default Header; 50 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import MdebugStrategy from './mdebug-strategy'; 2 | 3 | export { MdebugStrategy }; 4 | -------------------------------------------------------------------------------- /src/components/lockscroll/index.jsx: -------------------------------------------------------------------------------- 1 | import { PureComponent } from 'react'; 2 | import { getSysInfo } from '@/utils/ua'; 3 | 4 | const { os } = getSysInfo(navigator.userAgent); 5 | // 用于弹窗滚动效果 6 | class LockScroll extends PureComponent { 7 | static defaultProps = { 8 | scrollName: 'can-scroll', 9 | noScrollName: 'no-scroll', 10 | }; 11 | 12 | componentDidMount() { 13 | // 解决安卓微信webview的scroll禁止不彻底 14 | if (os.name === 'android') { 15 | document.querySelector('body').style.overflow = 'hidden'; 16 | document.querySelector('body').style.height = '100%'; 17 | document.querySelector('html').style.height = '100%'; 18 | document.querySelector('html').style.overflow = 'hidden'; 19 | } 20 | const { scrollName, noScrollName } = this.props; 21 | let noScrollElement = false; 22 | if (noScrollName === 'window') { 23 | noScrollElement = document || window; 24 | } else { 25 | noScrollElement = document.querySelector(`.${noScrollName}`); 26 | } 27 | const scrollElement = document.querySelector(`.${scrollName}`); 28 | if (noScrollElement) { 29 | noScrollElement.addEventListener('touchmove', (e) => { 30 | e.preventDefault(); 31 | }); 32 | } 33 | let startY; 34 | if (scrollElement) { 35 | scrollElement.addEventListener('touchstart', (e) => { 36 | startY = e.touches[0].pageY; 37 | }); 38 | // 处理移动过程中换方向无响应 39 | let topY = false; 40 | let bottomY = false; 41 | scrollElement.addEventListener('touchmove', (e) => { 42 | const moveEndY = e.changedTouches[0].pageY; 43 | const Y = moveEndY - startY; 44 | const { scrollTop } = scrollElement; 45 | if (Y > 0 && scrollTop <= 0) { 46 | // 到顶 47 | if (topY && Y <= topY) { 48 | e.stopPropagation(); 49 | } else { 50 | e.preventDefault(); 51 | } 52 | topY = Y; 53 | } else if (Y < 0 && scrollElement.scrollTop + scrollElement.clientHeight >= scrollElement.scrollHeight) { 54 | // 到底 55 | if (bottomY && Y >= bottomY) { 56 | e.stopPropagation(); 57 | } else { 58 | e.preventDefault(); 59 | } 60 | bottomY = Y; 61 | } else { 62 | e.stopPropagation(); 63 | } 64 | }); 65 | scrollElement.addEventListener('touchend', () => { 66 | bottomY = false; 67 | topY = false; 68 | }); 69 | } 70 | } 71 | componentWillUnmount() { 72 | if (os.name === 'android') { 73 | document.querySelector('body').style.overflow = 'auto'; 74 | document.querySelector('body').style.height = 'auto'; 75 | document.querySelector('html').style.overflow = 'auto'; 76 | document.querySelector('html').style.height = 'auto'; 77 | } 78 | const { scrollName, noScrollName } = this.props; 79 | let noScrollElement = false; 80 | if (noScrollName === 'window') { 81 | noScrollElement = window || document; 82 | } else { 83 | noScrollElement = document.querySelector(`.${noScrollName}`); 84 | } 85 | const scrollElement = document.querySelector(`.${scrollName}`); 86 | if (noScrollElement) { 87 | noScrollElement.removeEventListener('touchmove', (e) => { 88 | e.preventDefault(); 89 | }); 90 | } 91 | let startY; 92 | if (scrollElement) { 93 | scrollElement.removeEventListener('touchstart', (e) => { 94 | startY = e.touches[0].pageY; 95 | }); 96 | // 处理移动过程中换方向无响应 97 | let topY = false; 98 | let bottomY = false; 99 | scrollElement.removeEventListener('touchmove', (e) => { 100 | const moveEndY = e.changedTouches[0].pageY; 101 | const Y = moveEndY - startY; 102 | const { scrollTop } = scrollElement; 103 | if (Y > 0 && scrollTop <= 0) { 104 | // 到顶 105 | if (topY && Y <= topY) { 106 | e.stopPropagation(); 107 | } else { 108 | e.preventDefault(); 109 | } 110 | topY = Y; 111 | } else if (Y < 0 && scrollElement.scrollTop + scrollElement.clientHeight >= scrollElement.scrollHeight) { 112 | // 到底 113 | if (bottomY && Y >= bottomY) { 114 | e.stopPropagation(); 115 | } else { 116 | e.preventDefault(); 117 | } 118 | bottomY = Y; 119 | } else { 120 | e.stopPropagation(); 121 | } 122 | }); 123 | scrollElement.removeEventListener('touchend', () => { 124 | bottomY = false; 125 | topY = false; 126 | }); 127 | } 128 | } 129 | 130 | render() { 131 | return null; 132 | } 133 | } 134 | export default LockScroll; -------------------------------------------------------------------------------- /src/components/panel/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import Protector from '@/components/protector'; 3 | import styles from './pannel.module.css'; 4 | 5 | const Panel = props => { 6 | const { isActive, children, id } = props; 7 | if (isActive) { 8 | return ( 9 | 10 |
{children}
11 |
12 | ); 13 | } 14 | return null; 15 | }; 16 | 17 | export default memo(Panel); 18 | -------------------------------------------------------------------------------- /src/components/panel/pannel.module.css: -------------------------------------------------------------------------------- 1 | .mdebug-panel { 2 | position: relative; 3 | max-height: 85%; 4 | overflow: hidden; 5 | overflow-y: scroll; 6 | -webkit-overflow-scrolling: touch; 7 | word-break: break-all; 8 | } -------------------------------------------------------------------------------- /src/components/printer/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import ShowMore from '@/components/showmore'; 3 | import { isString, isUndefined } from '@/utils/shared'; 4 | import { toArrayObject } from '@/utils/object'; 5 | import { Inspector } from 'react-inspector'; 6 | import styles from './printer.module.css'; 7 | 8 | class MdebugPrinter extends PureComponent { 9 | formate(value) { 10 | try { 11 | if (toArrayObject(value)) { 12 | const arrayObjectValue = toArrayObject(value); 13 | return ; 14 | } 15 | if (isString(value)) { 16 | return {value}; 17 | } 18 | if (isUndefined(value)) { 19 | return 'undefined'; 20 | } 21 | if (typeof value === 'boolean') { 22 | return String(value); 23 | } 24 | if (typeof value === 'number') { 25 | return value; 26 | } 27 | if (typeof value === 'object' && `${value}` === 'null') { 28 | return 'null'; 29 | } 30 | return value.toString(); 31 | } catch (err) { 32 | return String(value); 33 | } 34 | } 35 | render() { 36 | const { data } = this.props; 37 | if (Array.isArray(data)) { 38 | const isMultiValue = data.length > 1; 39 | return data 40 | .map(value => this.formate(value)) 41 | .map((formatterValue, index) => ( 42 |
49 | {isMultiValue ? ( 50 |
51 |
{index + 1}.
52 |
{formatterValue}
53 |
54 | ) : ( 55 | formatterValue 56 | )} 57 |
58 | )); 59 | } 60 | return null; 61 | } 62 | } 63 | 64 | export default MdebugPrinter; 65 | -------------------------------------------------------------------------------- /src/components/printer/printer.module.css: -------------------------------------------------------------------------------- 1 | .mdebugConsoleDetail { 2 | padding: 10px 5px 50px; 3 | overflow-y: scroll; 4 | overflow-x: hidden; 5 | } 6 | .mdebugConsoleDetailLi { 7 | display: flex; 8 | padding: 5px; 9 | border-bottom: 1px solid #eee; 10 | line-height: 1.5; 11 | cursor: pointer; 12 | } 13 | .mdebugConsoleDetail > li:nth-of-type(1) { 14 | margin-top: 30px; 15 | } 16 | .mdebugConsoleLabel { 17 | display: inline-block; 18 | padding: 2px 5px; 19 | margin-right: 5px; 20 | font-size: 12px; 21 | font-weight: 700; 22 | color: #fff; 23 | height: 22px; 24 | } 25 | .mdebugConsoleValue { 26 | margin-top: 2px; 27 | display: flex; 28 | margin-right: 5px; 29 | } 30 | .mdebugConsoleValues { 31 | flex: 1; 32 | padding-left: 5px; 33 | border: 1px dashed rgb(40, 131, 233); 34 | } 35 | .mdebugConsoleMultiValue { 36 | margin-right: 3px; 37 | display: block; 38 | } 39 | .mdebugConsoleIndexValue { 40 | display: flex; 41 | } 42 | .mdebugConsoleIndex { 43 | margin-right: 3px; 44 | color: #888; 45 | } 46 | -------------------------------------------------------------------------------- /src/components/protector/index.jsx: -------------------------------------------------------------------------------- 1 | import { PureComponent } from 'react'; 2 | // https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html 3 | class MnuProtector extends PureComponent { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | ready: true, 8 | }; 9 | } 10 | componentDidCatch(err, info) { 11 | const { name } = this.props; 12 | this.setState( 13 | { 14 | ready: false, 15 | }, 16 | () => { 17 | // 2018.10.19 正常输出错误并设置级别为warn 18 | console.warn(`module ${name} err`, err, info); 19 | }, 20 | ); 21 | } 22 | render() { 23 | const { children } = this.props; 24 | const { ready } = this.state; 25 | if (!ready) { 26 | return null; 27 | } 28 | return children; 29 | } 30 | } 31 | 32 | export default MnuProtector; -------------------------------------------------------------------------------- /src/components/reactDevTools/app/App.module.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | justify-content: center; 5 | height: 100vh; 6 | background-color: var(--color-dimmer); 7 | font-family: var(--font-family-sans); 8 | font-size: var(--comfortable-font-size-sans-normal); 9 | } 10 | 11 | @media (max-width: 800px) { 12 | .App { 13 | flex-direction: column; 14 | } 15 | } 16 | 17 | .Left { 18 | flex: 1 0 250px; 19 | color: var(--color-text); 20 | overflow: auto; 21 | background-color: var(--color-background); 22 | 23 | border-radius: 0.25rem; 24 | 25 | line-height: 1.5; 26 | 27 | display: flex; 28 | flex-direction: column; 29 | } 30 | 31 | .LeftTop { 32 | background-color: var(--color-background-selected); 33 | color: var(--color-text-selected); 34 | padding: 0.5rem 1rem; 35 | flex: 0 0 auto; 36 | } 37 | 38 | .LeftTopHeader { 39 | font-weight: normal; 40 | font-size: var(--comfortable-font-size-sans-large); 41 | margin: 0; 42 | text-overflow: ellipsis; 43 | overflow-x: hidden; 44 | white-space: nowrap; 45 | } 46 | 47 | .LeftMiddle { 48 | display: flex; 49 | align-items: center; 50 | justify-content: space-between; 51 | padding: 0.25rem 1rem; 52 | background-color: var(--color-selected-tree-highlight-active); 53 | color: var(--color-dimmer); 54 | } 55 | 56 | .LeftMiddleLink { 57 | flex: 0 0 auto; 58 | color: var(--color-component-name); 59 | text-decoration: none; 60 | } 61 | .LeftMiddleLink:hover { 62 | text-decoration: underline; 63 | } 64 | 65 | .LeftBottom { 66 | padding: 0.5rem 1rem; 67 | flex: 1 1; 68 | overflow: auto; 69 | } 70 | 71 | .LeftBottom code { 72 | font-size: 14px; 73 | 74 | font-family: var(--font-family-monospace); 75 | font-size: var(--comfortable-font-size-monospace-normal); 76 | } 77 | 78 | .LeftBottom a code, 79 | .LeftBottom a { 80 | color: var(--color-component-name); 81 | } 82 | 83 | .LeftBottom p { 84 | margin: 0 0 1rem; 85 | } 86 | 87 | .LeftBottom details { 88 | margin: 0 0 1rem; 89 | } 90 | 91 | .LeftBottom ul { 92 | padding: 0; 93 | } 94 | 95 | .LeftBottom ul li { 96 | list-style: none; 97 | } 98 | 99 | .Spacer { 100 | flex: 0 0 0.5rem; 101 | } 102 | 103 | .Right { 104 | flex: 2 1 300px; 105 | border-left: none; 106 | display: flex; 107 | flex-direction: column; 108 | overflow: auto; 109 | } 110 | 111 | .ReactIcon { 112 | display: inline-block; 113 | margin-right: 0.5rem; 114 | } 115 | -------------------------------------------------------------------------------- /src/components/reactDevTools/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import BrowserWindow from '../browser/browserWindow'; 3 | 4 | import styles from './App.module.css'; 5 | 6 | export default function App({ children, defaultTabID, iframeSource, title }) { 7 | return ( 8 |
9 |
10 | 11 |
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/reactDevTools/browser/browserWindow.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react'; 2 | import DevTools from './devTools'; 3 | 4 | import styles from './browserWindow.module.css'; 5 | 6 | export default function FakeBrowserWindow({ 7 | defaultTabID = 'components', 8 | iframeSource = 'example-todo-list-app.js', 9 | title, 10 | }) { 11 | const iframeRef = useRef(null); 12 | const [tabID, setTabID] = useState(defaultTabID); 13 | 14 | return ( 15 |
16 |
17 |
18 | 24 |
25 |
setTabID('components')}> 28 | 32 | ⚛️ 33 | 34 | Components 35 |
36 |
setTabID('profiler')}> 39 | 40 | ⚛️ 41 | 42 | Profiler 43 |
44 |
45 |
46 |
52 |
53 |
54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/components/reactDevTools/browser/browserWindow.module.css: -------------------------------------------------------------------------------- 1 | .Wrapper { 2 | flex: 1; 3 | overflow: hidden; 4 | } 5 | 6 | .BrowserWindow { 7 | flex: 1 1; 8 | height: 100%; 9 | display: flex; 10 | flex-direction: column; 11 | overflow: hidden; 12 | background-color: #fff; 13 | border: 3px solid #f1f1f1; 14 | border-top-left-radius: 0.25rem; 15 | border-top-right-radius: 0.25rem; 16 | box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.25); 17 | } 18 | 19 | .BrowserRow { 20 | padding: 5px 10px; 21 | background: #f1f1f1; 22 | border-top-left-radius: 0.25rem; 23 | border-top-right-radius: 0.25rem; 24 | display: flex; 25 | flex-direction: row; 26 | align-items: center; 27 | height: 40px; 28 | flex: 0 0 auto; 29 | } 30 | 31 | .BrowserColumnLeft, 32 | .BrowserColumnMiddle, 33 | .BrowserColumnRight { 34 | display: flex; 35 | flex-direction: column; 36 | flex: 1; 37 | height: 100%; 38 | display: flex; 39 | align-items: center; 40 | flex-direction: row; 41 | overflow: auto; 42 | } 43 | 44 | .BrowserColumnLeft { 45 | flex: 0 0 60px; 46 | justify-content: space-between; 47 | padding-right: 15px; 48 | } 49 | 50 | .BrowserColumnMiddle { 51 | flex: 1; 52 | } 53 | 54 | .BrowserColumnRight { 55 | flex: 0 0 auto; 56 | display: flex; 57 | justify-content: flex-end; 58 | padding-left: 15px; 59 | } 60 | 61 | .BrowserMenuRight { 62 | display: inline-block; 63 | widows: 30px; 64 | } 65 | 66 | .BrowserBar { 67 | width: 17px; 68 | height: 3px; 69 | background-color: #aaa; 70 | margin: 3px 0; 71 | display: block; 72 | } 73 | 74 | .BrowserInput { 75 | width: 100%; 76 | height: 100%; 77 | border-radius: 3px; 78 | border: none; 79 | background-color: white; 80 | color: #666; 81 | padding: 5px; 82 | display: flex; 83 | align-items: center; 84 | } 85 | 86 | .BrowserContent { 87 | display: flex; 88 | flex-direction: column; 89 | flex: 1 0 100%; 90 | overflow: auto; 91 | } 92 | 93 | .TabBar { 94 | flex: 0 0 auto; 95 | background-color: #f7f7f7; 96 | border-top: 1px solid #eee; 97 | display: flex; 98 | align-items: center; 99 | justify-content: flex-end; 100 | font-size: var(--comfortable-font-size-sans-small); 101 | } 102 | 103 | .Tab, 104 | .TabActive { 105 | height: 2rem; 106 | display: flex; 107 | align-items: center; 108 | padding: 0 0.5rem; 109 | border-top: 2px solid transparent; 110 | border-bottom: 2px solid transparent; 111 | cursor: pointer; 112 | } 113 | .TabActive { 114 | border-bottom-color: #06f; 115 | } 116 | 117 | .Tab:hover, 118 | .TabActive:hover { 119 | background-color: rgba(0, 0, 0, 0.05); 120 | } 121 | 122 | .DevTools { 123 | width: 100%; 124 | flex: 1 0 200px; 125 | height: 100%; 126 | overflow: auto; 127 | } 128 | 129 | @media screen and (max-width: 600px) { 130 | .DevTools { 131 | min-height: 400px; 132 | } 133 | } 134 | 135 | .ReactIcon { 136 | display: inline-block; 137 | margin-right: 0.5rem; 138 | } 139 | 140 | .offscreenIframe { 141 | position: fixed; 142 | left: -10000px; 143 | bottom: -10000px; 144 | } -------------------------------------------------------------------------------- /src/components/reactDevTools/browser/devTools.jsx: -------------------------------------------------------------------------------- 1 | import React, { useLayoutEffect, useState } from 'react'; 2 | import { 3 | activate as activateBackend, 4 | initialize as initializeBackend, 5 | } from 'react-devtools-inline/backend'; 6 | import { initialize as initializeFrontend } from 'react-devtools-inline/frontend'; 7 | 8 | export default function DevTools({ iframeRef, tabID }) { 9 | const [DevTools, setDevTools] = useState(null); 10 | 11 | useLayoutEffect(() => { 12 | const iframe = iframeRef.current; 13 | 14 | const contentWindow = iframe.contentWindow; 15 | 16 | contentWindow.__REACT_DEVTOOLS_TARGET_WINDOW__ = window; 17 | 18 | initializeBackend(contentWindow); 19 | 20 | localStorage.removeItem('React::DevTools::componentFilters'); 21 | 22 | const DevTools = initializeFrontend(contentWindow); 23 | 24 | setDevTools(DevTools); 25 | 26 | activateBackend(contentWindow); 27 | }, [iframeRef]); 28 | 29 | return DevTools !== null && ; 30 | } 31 | -------------------------------------------------------------------------------- /src/components/reactDevTools/console/console.jsx: -------------------------------------------------------------------------------- 1 | import React, { useLayoutEffect, useRef, useState } from 'react'; 2 | 3 | import styles from './Console.module.css'; 4 | 5 | function argsToText(args) { 6 | if (args.length === 0) { 7 | return ''; 8 | } 9 | 10 | let text = args.shift(); 11 | 12 | if (typeof text === 'string') { 13 | while (text.includes('%s')) { 14 | text = text.replace('%s', args.shift()); 15 | } 16 | } 17 | 18 | while (args.length > 0) { 19 | text += ' ' + args.shift(); 20 | } 21 | 22 | return text; 23 | } 24 | 25 | export default function Console({ hidden, iframeRef }) { 26 | const inputRef = useRef(); 27 | const [entries, setEntries] = useState([]); 28 | 29 | useLayoutEffect(() => { 30 | const { contentWindow } = iframeRef.current; 31 | contentWindow.console.error = (...args) => 32 | setEntries(entries => [...entries, { type: 'Error', text: argsToText(args) }]); 33 | contentWindow.console.info = (...args) => 34 | setEntries(entries => [...entries, { type: 'Info', text: argsToText(args) }]); 35 | contentWindow.console.log = (...args) => 36 | setEntries(entries => [...entries, { type: 'Log', text: argsToText(args) }]); 37 | contentWindow.console.warn = (...args) => 38 | setEntries(entries => [...entries, { type: 'Warn', text: argsToText(args) }]); 39 | }, [iframeRef]); 40 | 41 | const handleKeyDown = event => { 42 | if (event.key === 'Enter') { 43 | event.preventDefault(); 44 | 45 | const text = event.target.innerText; 46 | 47 | event.target.innerText = ''; 48 | 49 | const { contentWindow } = iframeRef.current; 50 | 51 | try { 52 | contentWindow.eval(text); 53 | } catch (error) { 54 | setEntries(entries => [...entries, { type: 'Error', text: error.message }]); 55 | } 56 | } 57 | }; 58 | 59 | return ( 60 |