Repository: whinc/web-console Branch: master Commit: 207247e3144c Files: 97 Total size: 557.2 KB Directory structure: gitextract_hjfdfrq1/ ├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .npmrc ├── .postcssrc ├── CHANGELOG.md ├── cypress.json ├── docs/ │ ├── depoly.md │ ├── plugin.md │ └── snapshot.md ├── gulpfile.js ├── jest.config.js ├── package.json ├── public/ │ ├── index.html │ ├── index_cdn.html │ ├── style_import1.css │ ├── style_import2.css │ ├── style_link.css │ ├── test_application.js │ ├── test_console.js │ ├── test_network.js │ ├── test_plugin.js │ └── vue.js ├── readme.md ├── src/ │ ├── App.vue │ ├── components/ │ │ ├── VFootBar.vue │ │ ├── VHighlightView.vue │ │ ├── VIcon.vue │ │ ├── VJSONViewer/ │ │ │ ├── JSONTextBlock.vue │ │ │ ├── JSONTextInlineBlock.vue │ │ │ ├── VJSONViewer.vue │ │ │ └── index.js │ │ ├── VTabBar.vue │ │ ├── VTabBarItem.vue │ │ └── index.js │ ├── constants/ │ │ ├── PanelType.js │ │ └── index.js │ ├── main.js │ ├── panels/ │ │ ├── application/ │ │ │ ├── ApplicationPanel.vue │ │ │ ├── TabStorage.vue │ │ │ └── XStorage.js │ │ ├── console/ │ │ │ ├── ConsolePanel.vue │ │ │ ├── Message.vue │ │ │ ├── TextBlock.vue │ │ │ └── TextInlineBlock.vue │ │ ├── element/ │ │ │ ├── BoxModel.vue │ │ │ ├── ElementPanel.vue │ │ │ ├── NodeLink.vue │ │ │ ├── NodeView.vue │ │ │ ├── StyleColorValue.vue │ │ │ ├── StyleProperty.vue │ │ │ ├── StyleRule.vue │ │ │ ├── TabComputed.vue │ │ │ ├── TabStyles.vue │ │ │ └── Tag.vue │ │ ├── index.js │ │ ├── network/ │ │ │ ├── HttpStatus.js │ │ │ ├── NetworkPanel.vue │ │ │ ├── NetworkRequest.vue │ │ │ ├── RequestType.js │ │ │ ├── TabHeaders.vue │ │ │ ├── TabPreview.vue │ │ │ └── TabResponse.vue │ │ └── settings/ │ │ └── SettingsPanel.vue │ ├── plugins/ │ │ ├── Plugin.js │ │ ├── index.js │ │ ├── pluginEvents.js │ │ └── pluginManager.js │ ├── polyfill.js │ ├── styles/ │ │ ├── _global.scss │ │ ├── _mixins.scss │ │ ├── _triangles.scss │ │ └── _variables.scss │ └── utils/ │ ├── EventBus.js │ ├── Logger.js │ ├── TaskScheduler.js │ ├── consoleHooks.js │ ├── filters.js │ ├── index.js │ ├── miscs.js │ └── style.js ├── tests/ │ ├── api/ │ │ ├── data/ │ │ │ ├── response.css │ │ │ ├── response.html │ │ │ ├── response.js │ │ │ ├── response.json │ │ │ └── response.txt │ │ └── index.js │ ├── e2e/ │ │ ├── .eslintrc │ │ ├── plugins/ │ │ │ └── index.js │ │ ├── specs/ │ │ │ └── test.js │ │ └── support/ │ │ ├── commands.js │ │ └── index.js │ └── unit/ │ ├── .eslintrc │ └── HelloWorld.spec.js ├── tsconfig.json └── vue.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ "@vue/app" ], "plugins": [ [ "component", { "libraryName": "mint-ui", "style": true } ] ] } ================================================ FILE: .eslintrc ================================================ { "root": true, "extends": [ "plugin:vue/essential", "eslint:recommended" ], "env": { "browser": true, "node": true }, "globals": { }, "rules": { "no-console": "off", "no-case-declarations": "off" } } ================================================ FILE: .gitignore ================================================ .DS_Store node_modules /dist /demo /tests/e2e/videos/ /tests/e2e/screenshots/ # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln ================================================ FILE: .npmignore ================================================ * */ !dist/*.js !package*.json ================================================ FILE: .npmrc ================================================ git-tag-version=false ================================================ FILE: .postcssrc ================================================ { "plugins": { "autoprefixer": {} } } ================================================ FILE: CHANGELOG.md ================================================ ## [0.7.2](https://github.com/whinc/web-console/compare/v0.7.1...v0.7.2) (2019-04-23) ### Bug Fixes - **console:** 修复打印多个参数时,它们之间没有空白符分割的问题 ([2292733](https://github.com/whinc/web-console/commit/2292733)) ## [0.7.1](https://github.com/whinc/web-console/compare/v0.7.0...v0.7.1) (2019-04-23) ### Bug Fixes - 修复 activeTab 失效问题 ([c785e38](https://github.com/whinc/web-console/commit/c785e38)) # [0.7.0](https://github.com/whinc/web-console/compare/v0.6.2...v0.7.0) (2019-04-23) ### Bug Fixes - **plugin:** 修复 onWebConsoleShow 触发两次的问题;修复插件面板高度被改变的 bug ([bd88ddd](https://github.com/whinc/web-console/commit/bd88ddd)) - **plugin:** 修复插件生命周期未触发问题和插件重复问题 ([7c89fd6](https://github.com/whinc/web-console/commit/7c89fd6)) - 修复 lint 告警 ([82aedb5](https://github.com/whinc/web-console/commit/82aedb5)) ### Features - **plugin:** 初步支持插件的注册,展示,部分生命周期方法 ([513b530](https://github.com/whinc/web-console/commit/513b530)) - **plugin:** 增加插件获取当前设置的方法;增加插件首次加载设置的周期方法 ([0f5d129](https://github.com/whinc/web-console/commit/0f5d129)) - **plugin:** 增强插件支持 ([342a499](https://github.com/whinc/web-console/commit/342a499)) - **plugin:** 新增几种内置组件暴露给插件使用 ([a17c840](https://github.com/whinc/web-console/commit/a17c840)) - 更新在线 demo ([3275f42](https://github.com/whinc/web-console/commit/3275f42)) ## [0.6.2](https://github.com/whinc/web-console/compare/v0.6.1...v0.6.2) (2019-03-26) ### Bug Fixes - **network:** 修复部分情况下请求参数未展示的 bug ([edeff31](https://github.com/whinc/web-console/commit/edeff31)) ## [0.6.1](https://github.com/whinc/web-console/compare/v0.6.0...v0.6.1) (2019-03-24) ### Bug Fixes - **network:** 修复样式 ([f815474](https://github.com/whinc/web-console/commit/f815474)) # [0.6.0](https://github.com/whinc/web-console/compare/v0.5.0...v0.6.0) (2019-03-24) ### Bug Fixes - **network:** 修复 fetch 请求参数为 Request 时的异常 ([e4739b6](https://github.com/whinc/web-console/commit/e4739b6)) - **network:** 修复请求状态展示错误 ([74f53b6](https://github.com/whinc/web-console/commit/74f53b6)) ### Features - **network:** 初步支持 fetch 请求 ([5e64f03](https://github.com/whinc/web-console/commit/5e64f03)) - **settings:** 支持设置"显示/隐藏"网络请求类型 ([b70cad5](https://github.com/whinc/web-console/commit/b70cad5)) # [0.5.0](https://github.com/whinc/web-console/compare/v0.4.7...v0.5.0) (2019-03-16) ### Bug Fixes - 修复 lint 警告 ([e21046e](https://github.com/whinc/web-console/commit/e21046e)) ### Features - **console:** 增加搜索日志 ([8c90bac](https://github.com/whinc/web-console/commit/8c90bac)) ## [0.4.7](https://github.com/whinc/web-console/compare/v0.4.6...v0.4.7) (2019-03-07) ### Bug Fixes - 修复 vue-infinite-scroll 插件与宿主环境冲突,导致宿主引入该插件后无效的问题 ([8a89a15](https://github.com/whinc/web-console/commit/8a89a15)) ## [0.4.6](https://github.com/whinc/web-console/compare/v0.4.4...v0.4.6) (2019-03-04) ### Bug Fixes - 修复 PC 端滚动条覆盖面板右边缘的问题 ([bd03c0c](https://github.com/whinc/web-console/commit/bd03c0c)) - **application:** 输入框设置高度撑破父元素的问题 ([dbbfa9c](https://github.com/whinc/web-console/commit/dbbfa9c)) - **console:** 将输出内容对齐到每行的顶部 ([85a0c5f](https://github.com/whinc/web-console/commit/85a0c5f)) - **element:** 修复
标签不换行导致的 element 面板中盒模型样式坍塌问题 ([1a6cd8b](https://github.com/whinc/web-console/commit/1a6cd8b)) ### Reverts - px 单位转 vw 单位 ([93c52b8](https://github.com/whinc/web-console/commit/93c52b8)) ## [0.4.4](https://github.com/whinc/web-console/compare/v0.4.2...v0.4.4) (2019-03-01) ### Bug Fixes - **console:** 修复打印多参数时参数之间无空白符分隔的问题 ([7c7c525](https://github.com/whinc/web-console/commit/7c7c525)) ### Features - **settings:** 增加反馈入口 ([26984b9](https://github.com/whinc/web-console/commit/26984b9)) ## [0.4.2](https://github.com/whinc/web-console/compare/v0.4.1...v0.4.2) (2019-02-26) ### Features - **console:** 处理未捕获的异常(error 和 unhandledrejection 事件) ([04e7da5](https://github.com/whinc/web-console/commit/04e7da5)) ## [0.4.1](https://github.com/whinc/web-console/compare/v0.4.0-beta.2...v0.4.1) (2019-02-21) ### Bug Fixes - **console:** 修复 Symbole 作为对象 key 时,打印对象报错的问题 ([27ff599](https://github.com/whinc/web-console/commit/27ff599)) - **console:** 修复打印数组时末尾多出一个逗号 ([5b5e04d](https://github.com/whinc/web-console/commit/5b5e04d)) ### Features - **console:** log 格式输出支持 %c 占位符 ([0ba036d](https://github.com/whinc/web-console/commit/0ba036d)) # [0.4.0-beta.2](https://github.com/whinc/web-console/compare/v0.4.0-beta.1...v0.4.0-beta.2) (2019-02-18) ### Bug Fixes - **element:** 修复 Element.prototype.getAttributeNames 的兼容性问题 ([92b0355](https://github.com/whinc/web-console/commit/92b0355)) # [0.4.0-beta.1](https://github.com/whinc/web-console/compare/v0.3.0-beta.6...v0.4.0-beta.1) (2019-02-16) ### Bug Fixes - **application:** 修复选中行没有高亮颜色的 bug ([33a4633](https://github.com/whinc/web-console/commit/33a4633)) ### Features - **element:** 展示元素计算属性的继承值 ([072fbf2](https://github.com/whinc/web-console/commit/072fbf2)) - **element:** 计算属性面板的颜色值增加颜色小方块展示 ([65987d2](https://github.com/whinc/web-console/commit/65987d2)) # [0.3.0-beta.6](https://github.com/whinc/web-console/compare/v0.3.0-beta.5...v0.3.0-beta.6) (2019-02-01) ### Bug Fixes - 去除 ios 默认的 tap 高亮效果 ([873c02c](https://github.com/whinc/web-console/commit/873c02c)) - 无法复制文本的问题 ([a224c2e](https://github.com/whinc/web-console/commit/a224c2e)) - **components:** 修复 TabBar 组件子项超出宽度部分不可见的问题 ([56b7c40](https://github.com/whinc/web-console/commit/56b7c40)) - **console:** 优化消息换行展示 ([ed0567a](https://github.com/whinc/web-console/commit/ed0567a)) - **console:** 修复 Element.prototype.scrollTo 兼容性问题 ([70d3410](https://github.com/whinc/web-console/commit/70d3410)) - **console:** 修复数组包含非数字下标时的展示问题 ([0e9de4f](https://github.com/whinc/web-console/commit/0e9de4f)) - **element:** 修复审查分组选择器匹配元素时报错问题 ([fb43814](https://github.com/whinc/web-console/commit/fb43814)) - **element:** 修复样式被宿主样式覆盖的 bug ([3e218ba](https://github.com/whinc/web-console/commit/3e218ba)) - **element:** 禁止审查 doctype 元素 ([bc7cea6](https://github.com/whinc/web-console/commit/bc7cea6)) - **element:** 隐藏 dom paths 的滚动条 ([e13e27b](https://github.com/whinc/web-console/commit/e13e27b)) ### Features - **element:** dom path 紧凑展示时优先展示元素 id ([24e631f](https://github.com/whinc/web-console/commit/24e631f)) - **element:** 元素审查的 computed 面板增加盒模型 ([883ac26](https://github.com/whinc/web-console/commit/883ac26)) - **element:** 元素审查面板新增计算样式 ([ce87971](https://github.com/whinc/web-console/commit/ce87971)) - **element:** 审查元素的 styles 面板增加盒模型 ([fd83e9a](https://github.com/whinc/web-console/commit/fd83e9a)) - **element:** 支持属性值中颜色值预览 ([8acb299](https://github.com/whinc/web-console/commit/8acb299)) - **element:** 支持显示 doctype 元素 ([47f862d](https://github.com/whinc/web-console/commit/47f862d)) - **element:** 新增 DOM 路径 ([4d41b3c](https://github.com/whinc/web-console/commit/4d41b3c)) - **element:** 新增属性缩写形式折叠展开 ([02b952d](https://github.com/whinc/web-console/commit/02b952d)) - **element:** 美化元素审查面板样式 ([e5309b3](https://github.com/whinc/web-console/commit/e5309b3)) - **element:** 计算样式增加获取继承样式的方法 ([6934b5a](https://github.com/whinc/web-console/commit/6934b5a)) - **element:** 选中底部 dom paths 时 dom tree 自动滚动到可视区域 ([22d6b1e](https://github.com/whinc/web-console/commit/22d6b1e)) # [0.3.0-beta.5](https://github.com/whinc/web-console/compare/v0.3.0-beta.4...v0.3.0-beta.5) (2018-12-06) ### Features - **application:** 调整底部清除和刷新范围;删除存储数据增加二次确认弹窗 ([1953419](https://github.com/whinc/web-console/commit/1953419)) - **element:** 支持审查元素样式 ([26344f6](https://github.com/whinc/web-console/commit/26344f6)) - **settings:** 增加 CHANGELOG ([7e92468](https://github.com/whinc/web-console/commit/7e92468)) ### Performance Improvements - **console:** 提升批量打印日志时的显示性能 ([776891e](https://github.com/whinc/web-console/commit/776891e)) # [0.3.0-beta.4](https://github.com/whinc/web-console/compare/v0.3.0-beta.3...v0.3.0-beta.4) (2018-12-03) ### Bug Fixes - **network:** 修复底部隐藏按钮失效问题 ([a0d2269](https://github.com/whinc/web-console/commit/a0d2269)) ### Features - **application:** 支持折叠工具栏 ([ca20bfd](https://github.com/whinc/web-console/commit/ca20bfd)) - **application:** 新增刷新全部和清除全部快捷按钮 ([fd4e4b4](https://github.com/whinc/web-console/commit/fd4e4b4)) # [0.3.0-beta.3](https://github.com/whinc/web-console/compare/v0.3.0-beta.2...v0.3.0-beta.3) (2018-12-03) ### Bug Fixes - **element:** 修复同时选中开始和结束标签的问题 ([6a0cb15](https://github.com/whinc/web-console/commit/6a0cb15)) - **settings:** 移除 input 的 appearance 样式 ([fa9a0fe](https://github.com/whinc/web-console/commit/fa9a0fe)) - **settings:** 移除 About 页标题地下多出的横线 ([14e8cde](https://github.com/whinc/web-console/commit/14e8cde)) ### Features - **application:** 增加底部隐藏按钮 ([5eb10fe](https://github.com/whinc/web-console/commit/5eb10fe)) - **element:** 优化元素选中态样式;支持指定 DOM 树展开深度 ([76bb6d3](https://github.com/whinc/web-console/commit/76bb6d3)) - **element:** 增加元素审查面板 ([03e82f8](https://github.com/whinc/web-console/commit/03e82f8)) - **element:** 支持无内容标签的正确显示 ([cd31e3e](https://github.com/whinc/web-console/commit/cd31e3e)) - **element:** 节点显式过长时自动换行 ([aa89681](https://github.com/whinc/web-console/commit/aa89681)) # [0.3.0-beta.2](https://github.com/whinc/web-console/compare/v0.3.0-beta.1...v0.3.0-beta.2) (2018-11-29) ### Bug Fixes - **settings:** 移除 input 的 appearance 样式 ([c7a25b1](https://github.com/whinc/web-console/commit/c7a25b1)) ### Features - **element:** 元素增加折叠展开箭头 ([d000198](https://github.com/whinc/web-console/commit/d000198)) - **element:** 支持显式 html 结构 ([fbac371](https://github.com/whinc/web-console/commit/fbac371)) # [0.3.0-beta.1](https://github.com/whinc/web-console/compare/0.3.0-beta.1...v0.3.0-beta.1) (2018-11-24) ### Bug Fixes - **application:** 分页加载边界条件判断错误 ([0f18ca1](https://github.com/whinc/web-console/commit/0f18ca1)) - **application:** 删除后自动选中下一项 ([3007755](https://github.com/whinc/web-console/commit/3007755)) - **application:** 删除并重新写入 cookie 不更新 ([477c0fa](https://github.com/whinc/web-console/commit/477c0fa)) - **application:** 移除 IOS 输入框边框;弹窗可见时阻止 IOS 上缩放行为 ([31c50ec](https://github.com/whinc/web-console/commit/31c50ec)) ### Performance Improvements - **application:** 优化修改和删除操作的性能 ([c376187](https://github.com/whinc/web-console/commit/c376187)) - **application:** 大幅提升展示大量(1W+)storage 和 cookie 的性能 ([66b8c45](https://github.com/whinc/web-console/commit/66b8c45)) # [0.3.0-beta.0](https://github.com/whinc/web-console/compare/v0.2.0-rc.15...v0.3.0-beta.0) (2018-11-20) ### Bug Fixes - **application:** cookie 操作 ([01dc531](https://github.com/whinc/web-console/commit/01dc531)) - **application:** 异常 ([bf80d3b](https://github.com/whinc/web-console/commit/bf80d3b)) - **application:** 样式问题 ([c41b07c](https://github.com/whinc/web-console/commit/c41b07c)) - **network:** 解决 ios 上网络面板无法滚动问题 ([a3d39a6](https://github.com/whinc/web-console/commit/a3d39a6)) ### Features - **application:** localStorage 和 sessionStorage 支持编辑、新增 ([3cef20c](https://github.com/whinc/web-console/commit/3cef20c)) - **application:** 优化编辑状态样式 ([10e7727](https://github.com/whinc/web-console/commit/10e7727)) - **application:** 固定表头滚动表内容 ([9624ea3](https://github.com/whinc/web-console/commit/9624ea3)) - **application:** 增加 value 的过滤 ([84eddef](https://github.com/whinc/web-console/commit/84eddef)) - **application:** 增加对 cookie 的编辑和新增操作 ([273af90](https://github.com/whinc/web-console/commit/273af90)) - **application:** 新增 localStorage 和 sessionStorage ([292f863](https://github.com/whinc/web-console/commit/292f863)) - **application:** 美化 cookie 面板 ([4deefb9](https://github.com/whinc/web-console/commit/4deefb9)) - **application:** 表格增加奇偶行色差 ([e8996e8](https://github.com/whinc/web-console/commit/e8996e8)) - **console:** 增加最大消息数量限制 ([d69e15c](https://github.com/whinc/web-console/commit/d69e15c)) ### Performance Improvements - 用 v-if 替换部分 v-show 提高性能 ([1ec40f2](https://github.com/whinc/web-console/commit/1ec40f2)) - **application:** 优化保存性能 ([802c87f](https://github.com/whinc/web-console/commit/802c87f)) - **application:** 提高巨量(1w+)storage 条目的展示性能 ([258568d](https://github.com/whinc/web-console/commit/258568d)) - **console:** 优化短期内批量打印日志造成的 UI 假死现象 ([53be149](https://github.com/whinc/web-console/commit/53be149)) # [0.2.0-rc.15](https://github.com/whinc/web-console/compare/v0.2.0-rc.14...v0.2.0-rc.15) (2018-11-08) # [0.2.0-rc.14](https://github.com/whinc/web-console/compare/v0.2.0-rc.13...v0.2.0-rc.14) (2018-11-08) ### Bug Fixes - **console:** ios 上滚动失效 ([c282ad5](https://github.com/whinc/web-console/commit/c282ad5)) - **console:** 美化 safari 上输出的错误堆栈信息 ([4634cfa](https://github.com/whinc/web-console/commit/4634cfa)) - **settings:** ios 上图标展示过大 ([66f7beb](https://github.com/whinc/web-console/commit/66f7beb)) ### Features - **console:** 增加时间戳 ([ec6752c](https://github.com/whinc/web-console/commit/ec6752c)) - **settings:** 增加设置面板 ([89c4492](https://github.com/whinc/web-console/commit/89c4492)) - **settings:** 增加设置项持久化 ([1f542bc](https://github.com/whinc/web-console/commit/1f542bc)) # [0.2.0-rc.13](https://github.com/whinc/web-console/compare/v0.2.0-rc.12...v0.2.0-rc.13) (2018-11-06) ### Bug Fixes - **console:** 循环打印错误日志 ([a570170](https://github.com/whinc/web-console/commit/a570170)) ### Features - **console:** 缩略展示长字符串 ([a7af920](https://github.com/whinc/web-console/commit/a7af920)) # [0.2.0-rc.12](https://github.com/whinc/web-console/compare/v0.2.0-rc.11...v0.2.0-rc.12) (2018-11-05) ### Features - **application:** 实现 cookie 的展示、删除、过滤、刷新 ([0a26517](https://github.com/whinc/web-console/commit/0a26517)) # [0.2.0-rc.11](https://github.com/whinc/web-console/compare/v0.2.0-rc.10...v0.2.0-rc.11) (2018-10-31) ### Features - 增加设置(展示版本信息) ([6ab8caa](https://github.com/whinc/web-console/commit/6ab8caa)) # [0.2.0-rc.10](https://github.com/whinc/web-console/compare/v0.2.0-rc.9...v0.2.0-rc.10) (2018-10-30) ### Bug Fixes - **console:** 保留消息换行符 ([2d75a7c](https://github.com/whinc/web-console/commit/2d75a7c)) - **console:** 数组展示 ([8b8faa0](https://github.com/whinc/web-console/commit/8b8faa0)) ### Features - **application:** 增加 cookie 展示 ([4df4f8c](https://github.com/whinc/web-console/commit/4df4f8c)) - **application:** 新增 Application 面板 ([91daa76](https://github.com/whinc/web-console/commit/91daa76)) - **application:** 新增 cookie/localStorage/sessionStorage 的 UI ([aac507d](https://github.com/whinc/web-console/commit/aac507d)) - **console:** 打印 Error 对象堆栈信息 ([599206b](https://github.com/whinc/web-console/commit/599206b)) - **console:** 输出日志时可自动定位到最新位置 ([2509392](https://github.com/whinc/web-console/commit/2509392)) # [0.2.0-rc.9](https://github.com/whinc/web-console/compare/v0.2.0-rc.7...v0.2.0-rc.9) (2018-10-26) ### Performance Improvements - **console:** 提升面板切换性能 ([a068d6c](https://github.com/whinc/web-console/commit/a068d6c)) # [0.2.0-rc.7](https://github.com/whinc/web-console/compare/v0.2.0-rc.6...v0.2.0-rc.7) (2018-10-09) # [0.2.0-rc.6](https://github.com/whinc/web-console/compare/v0.2.0-rc.5...v0.2.0-rc.6) (2018-10-09) ### Features - **network:** 响应数据增加语法高亮 ([dcc3df7](https://github.com/whinc/web-console/commit/dcc3df7)) - **network:** 增加 JSON 数据预览 ([a099205](https://github.com/whinc/web-console/commit/a099205)) - **network:** 增加响应数据预览 ([0bb4e95](https://github.com/whinc/web-console/commit/0bb4e95)) - **network:** 支持预览 gif/jpg/svg/txt ([72fcd52](https://github.com/whinc/web-console/commit/72fcd52)) # [0.2.0-rc.5](https://github.com/whinc/web-console/compare/v0.2.0-rc.4...v0.2.0-rc.5) (2018-09-14) ### Bug Fixes - 修复触摸滚动穿透问题 ([f017338](https://github.com/whinc/web-console/commit/f017338)) # [0.2.0-rc.4](https://github.com/whinc/web-console/compare/v0.2.0-rc.3...v0.2.0-rc.4) (2018-09-11) ### Bug Fixes - **network:** response 数据超出边界时无法滚动查看 ([4111487](https://github.com/whinc/web-console/commit/4111487)) # [0.2.0-rc.3](https://github.com/whinc/web-console/compare/v0.2.0-rc.2...v0.2.0-rc.3) (2018-09-10) ### Bug Fixes - **console:** 未正确识别占位符 %c 导致的显示问题 ([c740d16](https://github.com/whinc/web-console/commit/c740d16)) - 面板可见时悬浮按钮未隐藏 ([41f8af5](https://github.com/whinc/web-console/commit/41f8af5)) # [0.2.0-rc.2](https://github.com/whinc/web-console/compare/v0.2.0-rc.1...v0.2.0-rc.2) (2018-09-08) # [0.2.0-rc.1](https://github.com/whinc/web-console/compare/v0.1.5...v0.2.0-rc.1) (2018-09-08) ### Bug Fixes - 对象展示问题 ([0eb0a14](https://github.com/whinc/web-console/commit/0eb0a14)) - 测试用例报错 ([f2ad431](https://github.com/whinc/web-console/commit/f2ad431)) - **console:** 修复字符串显式时没有引号包裹;修复字符串很长时没有自动换行的问题 ([42b27c2](https://github.com/whinc/web-console/commit/42b27c2)) - 移除同名文件 ([3c86612](https://github.com/whinc/web-console/commit/3c86612)) - **console:** get 访问器的计算结果没有展开 ([64fc61e](https://github.com/whinc/web-console/commit/64fc61e)) - **console:** vue 向原始日志数据中添加额外属性的问题 ([ef0424b](https://github.com/whinc/web-console/commit/ef0424b)) - **console:** 使日志每行内容垂直居中 ([5d95035](https://github.com/whinc/web-console/commit/5d95035)) - **console:** 多参数打印空格问题 ([9b90a00](https://github.com/whinc/web-console/commit/9b90a00)) - **console:** 对象的折叠展开态 ([0c59572](https://github.com/whinc/web-console/commit/0c59572)) - **console:** 属性名和值过长时导致属性名未对齐;Symb 和转符串拼接时报错 ([0e6c192](https://github.com/whinc/web-console/commit/0e6c192)) - **console:** 打印对象时丢失不可枚举和 symbol 属性 ([f3901ef](https://github.com/whinc/web-console/commit/f3901ef)) - **console:** 打印的对象属性键换行导致不易阅读的问题 ([53f018a](https://github.com/whinc/web-console/commit/53f018a)) - **console:** 样式错误显式为斜体 ([83f24ca](https://github.com/whinc/web-console/commit/83f24ca)) - **console:** 计算属性结果没有高亮 ([a769a41](https://github.com/whinc/web-console/commit/a769a41)) - **network:** 状态码 ([d39e2c2](https://github.com/whinc/web-console/commit/d39e2c2)) ### Features - **cosole:** 增加多参数高亮展示;增加 console 面板内部错误处理; ([0b13707](https://github.com/whinc/web-console/commit/0b13707)) - **network:** 优化网络请求 Headers 的展示 ([fe26231](https://github.com/whinc/web-console/commit/fe26231)) - **network:** 增加对 POST 中 JSON 数据格式的展示 ([2b25641](https://github.com/whinc/web-console/commit/2b25641)) - **network:** 增加接口测试代码;解决 onreadystatechange 事件注册时机问题 ([2822d35](https://github.com/whinc/web-console/commit/2822d35)) - **network:** 增加清除;优化行高;补充测试用例 ([ceff5e7](https://github.com/whinc/web-console/commit/ceff5e7)) - **network:** 增加请求头、查询参数和 POST 数据展示 ([4075a9f](https://github.com/whinc/web-console/commit/4075a9f)) - **network:** 请求头信息分类展示 ([6fdb205](https://github.com/whinc/web-console/commit/6fdb205)) - **network:** 错误状态码高亮展示 ([78e78ac](https://github.com/whinc/web-console/commit/78e78ac)) - 入口浮标支持滑动、图标样式 ([01bee17](https://github.com/whinc/web-console/commit/01bee17)) ## [0.1.5](https://github.com/whinc/web-console/compare/v0.1.4...v0.1.5) (2018-06-20) ## [0.1.4](https://github.com/whinc/web-console/compare/v0.1.3...v0.1.4) (2018-06-20) ## [0.1.3](https://github.com/whinc/web-console/compare/v0.1.2...v0.1.3) (2018-06-20) ## [0.1.2](https://github.com/whinc/web-console/compare/v0.1.1...v0.1.2) (2018-06-12) ## [0.1.1](https://github.com/whinc/web-console/compare/1dfc2f0...v0.1.1) (2018-06-12) ### Bug Fixes - **build:** 修复 npm run serve 问题 ([93162f9](https://github.com/whinc/web-console/commit/93162f9)) - **build:** 修复构建问题 ([a3eb6a2](https://github.com/whinc/web-console/commit/a3eb6a2)) - **demo:** 修正日志打印接口调用 ([83882a9](https://github.com/whinc/web-console/commit/83882a9)) - **network:** 修复手机上无法滚动 ([919c618](https://github.com/whinc/web-console/commit/919c618)) ### Features - **console:** 增加 clear 和 hide 两个操作 ([f0238a4](https://github.com/whinc/web-console/commit/f0238a4)) - **console:** 增加日志消息分类展示 ([f17a653](https://github.com/whinc/web-console/commit/f17a653)) - **ConsolePanel:** 美化日志输出样式 ([1dfc2f0](https://github.com/whinc/web-console/commit/1dfc2f0)) - **network:** 内容超出时变为 scroll ([d4efd7f](https://github.com/whinc/web-console/commit/d4efd7f)) - **network:** 初步实现对 XMLHttpRequest 的请求的拦截显示 ([bfc8f72](https://github.com/whinc/web-console/commit/bfc8f72)) - WebConsole 支持初始化参数;增加测试用例 ([9974f00](https://github.com/whinc/web-console/commit/9974f00)) - **network:** 增加样式 ([dbf58f8](https://github.com/whinc/web-console/commit/dbf58f8)) - **network:** 展示请求响应头和数据 ([3469c9f](https://github.com/whinc/web-console/commit/3469c9f)) - **network:** 显示请求的 response 内容 ([d3c759e](https://github.com/whinc/web-console/commit/d3c759e)) - 导出全局的 WebConsole 变量 ([a0282a4](https://github.com/whinc/web-console/commit/a0282a4)) - 面板的 Tab 栏替换成自定义组件 ([37f6923](https://github.com/whinc/web-console/commit/37f6923)) ================================================ FILE: cypress.json ================================================ { "pluginsFile": "tests/e2e/plugins/index.js" } ================================================ FILE: docs/depoly.md ================================================ ## 部署 发布到 npm 仓库: ```bash npm run semantic-release ``` 更新 github.io 在线示例: ```bash npm run depoly ``` 本地调试 ```js const script = document.createElement("script"); script.src = "http://localhost:8081/app.js"; script.onload = function() { new window.WebConsole(); }; document.body.appendChild(script); ``` ================================================ FILE: docs/plugin.md ================================================ # web-console 插件开发 通过插件可以增强 web-console 的能力,扩大使用场景。web-console 插件基于 Vue 组件开发,提供了丰富的 API、生命周期方法、内置组件、自定义偏好设置,同时提供了一个初始的插件开发项目模板方便开发、测试和发布。 ## 一个简单的插件示例 下面是一个简单的插件示例。 首先,创建一个文件 MyPlugin.js 用于定义插件。 ```js export default function(WebConsole, options) { return new WebConsole.Plugin({ id: "myPlugin", name: "我的插件", component: { render(h) { return h("h1", null, "这是我的第一个插件"); } } }); } ``` 然后通过`WebConsole.use()`方法注册插件,当 web-console 运行起来后,插件会自动加载到主面板。 ```js // 使用插件 import WebConsole from "@whinc/web-console"; import MyPlugin from "my-plugin"; WebConsole.use(MyPlugin); new WebConsole(); ``` 运行截图如下: ![](snapshot_plugin.png) ## 创建插件 web-console 插件是一个返回`WebConsole.Plugin`实例的函数,函数的第一个参数是`WebConsole`对象,第二个参数是传递给插件的参数。 ```js function MyPlugin(WebConsole, options) { return new WebConsole.Plugin({ /*...*/ }); } ``` `WebConsole.Plugin`类是所有插件的基类,返回的插件实例必须继承自它,它的构造函数接受一个配置参数,支持的字段及取值如下: | 字段 | 类型 | 必填 | 备注 | | --------- | ------------ | ----- | ------------------------------------ | | id | string | true | 不能与已安装的插件 id 相同 | | name | string | false | 插件名称,用于显示,缺省时与 id 相同 | | component | VueComponent | true | 插件主面板,是一个 Vue 组件 | | settings | Object | false | 增加到设置面板的设置项 | > 参数配置中的 component 注入了一些 web-console 组件,可以直接使用。(后续补充关于这些内置组件的文档) ## 注册插件 通过`WebConsole.use()`方法注册插件。 ```js WebConsole.use(MyPlugin, {/*...*/}) new WebConsole(}) ``` > 注意:插件必须在 WebConsole 实例化之前注册,否则不生效。 ## 插件生命周期 目前插件支持如下生命周期方法,这些插件方法在`component`所赋值的组件中可用。 | 生命周期方法 | 执行时机 | 备注 | | --------------------------------------------------- | ---------------------------- | ----------------------------- | | `onWebConsoleReady(hostProxy)` | 主面板首次渲染完成时 | 可访问 DOM 和配置,仅执行一次 | | `onWebConsoleShow(hostProxy)` | 主面板从不可见变为**可见**时 | | | `onWebConsoleHide(hostProxy)` | 主面板从可见变为**不可见**时 | | `onWebConsoleTabChanged(hostProxy, newTab, oldTab)` | 切换不同子面板时 | | `onWebConsoleSettingsLoaded(hostProxy, settings)` | 偏好设置首次加载时 | | `onWebConsoleSettingsChanged(hostProxy, settings)` | 偏好设置变化时 | 使用示例: ```js export default function(WebConsole, options) { return new WebConsole.Plugin({ id: "myPlugin", name: "我的插件", component: { render(h) { return h("h1", null, "这是我的第一个插件"); }, methods: { onWebConsoleShow() { console.log("Show"); }, onWebConsoleHide() { console.log("Hide"); } } } }); } ``` ## 插件偏好设置 web-console 有一个集中的偏好设置界面(点击主面板右上角的齿轮进入),插件可以在其中添加自己的偏好设置项,添加方式是在创建插件时指定`settings`属性,该属性接受一个数组,数组每个元素是一个设置描述对象。 支持的设置描述对象如下(还会支持更多): ```js // 复选框 { type: 'checkbox', desc: '是否开启', name: 'isOpen', value: false } // 下拉选择框 { type: "select", desc: "选择颜色", name: 'color', value: 'red', options: [ { text: "红色", value: 'red' }, { text: "绿色", value: 'green' }, { text: "蓝色", value: 'blue' }, ] } ``` 使用示例: ```js export default function(WebConsole, options) { return new WebConsole.Plugin({ id: "myPlugin", name: "我的插件", component: { render(h) { return h("h1", null, "这是我的第一个插件"); }, methods: { onWebConsoleSettingsLoaded(hostProxy, settings) { console.log("加载设置:", settings); }, onWebConsoleSettingsChanged(hostProxy, settings) { console.log("设置变化:", settings); } } }, settings: [ { type: "checkbox", desc: "是否选中", name: "isChecked", value: false } ] }); } ``` 运行截图: ![](snapshot_plugin_settings1.png) ![](snapshot_plugin_settings2.png) ================================================ FILE: docs/snapshot.md ================================================ # Element 面板 ![element](snapshot_element.png) ![element](snapshot_element2.png) ![element](snapshot_element3.png) # Console 面板 ![console](snapshot_console.png) # Network 面板 ![network](snapshot_network.png) # Application 面板 ![application](snapshot_application.png) ================================================ FILE: gulpfile.js ================================================ const pkgInfo = require("./package.json"); const child_process = require("child_process"); const process = require("process"); var gulp = require("gulp"); gulp.task("default", function(cb) { // 将你的默认的任务代码放在这 }); let version = "prerelease"; gulp.task("version", cb => { const oldVersion = pkgInfo.version; console.log(oldVersion); console.log(process.argv, ";", process.argv0); child_process.exec("npm version " + version, (err, stdout, stderr) => { if (err) cb(err); console.log(stdout); }); }); ================================================ FILE: jest.config.js ================================================ module.exports = { moduleFileExtensions: [ 'js', 'jsx', 'json', 'vue' ], transform: { '^.+\\.vue$': 'vue-jest', '^.+\\.jsx?$': 'babel-jest' }, moduleNameMapper: { '^@/(.*)$': '/src/$1' }, snapshotSerializers: [ 'jest-serializer-vue' ] } ================================================ FILE: package.json ================================================ { "name": "@whinc/web-console", "version": "0.7.2", "author": "whincwu@163.com", "license": "MIT", "keywords": [ "console", "vConsole", "web-console", "webConsole", "log" ], "repository": "https://github.com/whinc/web-console", "main": "dist/web-console.umd.min.js", "scripts": { "serve": "vue-cli-service serve", "build:lib": "vue-cli-service build --target lib --name web-console --dest dist src/main.js", "depoly:lib": "npm run lint && npm run build:lib && cross-env NPM_TOKEN=$NPM_TOKEN semantic-release && npm run changelog", "build:demo": "vue-cli-service build --target app --dest demo", "depoly:demo": "npm run build:demo && npx gh-pages -d demo", "lint": "vue-cli-service lint src/**/*.js", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", "report": "vue-cli-service build --report", "test:unit": "vue-cli-service test:unit", "test:api": "cd ./tests/api && nodemon index.js", "e2e": "vue-cli-service e2e" }, "dependencies": { "cookie_js": "^1.2.2", "highlight.js": "^9.14.2", "lodash.clonedeep": "^4.5.0", "mint-ui": "^2.2.13", "specificity": "^0.4.1", "url-search-params": "^1.1.0", "vue": "^2.5.22", "vue-infinite-scroll": "^2.0.2" }, "devDependencies": { "@commitlint/cli": "^7.5.0", "@commitlint/config-conventional": "^7.5.0", "@vue/cli-plugin-babel": "^3.4.1", "@vue/cli-plugin-e2e-cypress": "^3.4.1", "@vue/cli-plugin-eslint": "^3.4.1", "@vue/cli-plugin-unit-jest": "^3.5.1", "@vue/cli-service": "^3.4.1", "@vue/test-utils": "^1.0.0-beta.28", "babel-core": "^7.0.0-0", "babel-jest": "^22.4.4", "babel-plugin-component": "^1.1.1", "conventional-changelog-angular": "^5.0.3", "conventional-changelog-cli": "^2.0.12", "cross-env": "^5.2.0", "cz-conventional-changelog": "^2.1.0", "gh-pages": "^1.2.0", "gulp": "^4.0.0", "husky": "^1.3.1", "i": "^0.3.6", "node-sass": "^4.11.0", "nodemon": "^1.18.9", "prettier": "1.14.2", "pretty-quick": "^1.10.0", "sass-loader": "^6.0.6", "semantic-release": "^15.13.3", "semantic-release-cli": "^4.1.1", "vue-template-compiler": "^2.5.22" }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ], "husky": { "hooks": { "pre-commit": "pretty-quick --staged", "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }, "prettier": { "printWidth": 120 }, "commitlint": { "extends": [ "@commitlint/config-conventional" ] }, "release": { "ci": false, "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", "@semantic-release/npm" ] } } ================================================ FILE: public/index.html ================================================ web-console

WebConsole

Console Test

打印不同级别日志 打印多参数 打印格式化 打印对象 触发异常

Network Test

测试 XHR 请求 测试 fetch 请求 测试 HTTP 状态码 测试响应数据

query string & body

get post:raw post:raw:text post:raw:json post:form-data post:x-www-form-urlencoded

Application Test

测试 Cookie 测试 LocalStorage 测试 SessionStorage

Other

日志性能测试 测试插件
普通日志(log) 信息日志(info) 调试日志(debug) 警告日志(warn) 报错日志(error)

打印对象 打印错误

发起 XMLHttpRequest 请求 发起 fetch 请求

切换

================================================ FILE: public/index_cdn.html ================================================ 测试CDN下载 ================================================ FILE: public/style_import1.css ================================================ @import url("style_import2.css"); /* 测试元素审查 */ #element { border-color: green; } .xyz { border: 2px dashed blue; padding: 2px; } ================================================ FILE: public/style_import2.css ================================================ /* 测试元素审查 */ #element { border-color: yellow; } .xyz { border: 3px dotted yellow; padding: 3px; } ================================================ FILE: public/style_link.css ================================================ /* 测试元素审查 */ #element { background-color: white; } .xyz { border: 1px solid red; padding: 1px; } ================================================ FILE: public/test_application.js ================================================ window.$application = (function() { function testWriteCookie(n = 1) { for (var i = 0; i < n; ++i) { const obj = { timestamp: Date.now() }; document.cookie = i + "=" + JSON.stringify(obj); } } function testWriteLocalStorage(n = 1) { for (var i = 0; i < n; ++i) { const obj = { timestamp: Date.now() }; window.localStorage.setItem(i, JSON.stringify(obj)); } } function testWriteSessionStorage(n = 1) { for (var i = 0; i < n; ++i) { const obj = { timestamp: Date.now() }; window.sessionStorage.setItem(i, JSON.stringify(obj)); } } return { testWriteCookie, testWriteSessionStorage, testWriteLocalStorage }; })(); ================================================ FILE: public/test_console.js ================================================ window.$console = (function() { function testLogLevel() { console.log("log"); console.info("info"); console.debug("debug"); console.warn("warn"); console.error("error"); } function testLogParams() { console.log(100, true, undefined, null, { a: 100 }); } // 测试log格式化输出 function testLogFormat(value, format) { console.log("console.log(%" + format + ", %s)", JSON.stringify(value)); console.log(format, value); } function testIntervalLog(interval = 1000) { console.log(Date.now()); setTimeout(testIntervalLog.bind(null, arguments), interval); } function testException() { // 测试直接输出 Error 对象 console.error(new ReferenceError("reference error")); // 测试未捕获的全局异常 setTimeout(function() { throw new Error("This is a uncaught exception"); }); // 测试未处理的 rejected Promise Promise.reject("uncaught rejected promise"); } // 打印对象 function testObject() { let obj2 = { // 测试数值类型 a: 1, a5: { a: 1, b: 2, c: { a: 1, b: 2 } }, // 测试数组 a3: [1, 2], a4: [1, 2, [1, 2, [1, 2, 3], 3], 3], // 测试属性值很长时的展示 a1: "10101010101010101010101010101010101010101010101001010101", a2: 10101010101010101010110101010101010110101011010101011010101, // 测试字符串类型 b: "b", // 测试转义字符展示 b1: '"b1"', c: true, d: undefined, e: null, f: Symbol(), g: { a: 1, f1: () => { return 1; }, f2: function() { return 2; }, b: "b", c: { a: 1, b: "b" }, d: undefined, e: null }, [Symbol()]: 10, [Symbol("age")]: 28, f1: function() {}, f2: () => {}, f3: function say() {}, Z: "Z", M: "m", z: "z", // 测试排序规则 __a: "public __a", _a: "public _a", $a: "public $a" }; Object.defineProperties(obj2, { // value h: { enumerable: true, configurable: true, value: 10, writable: false }, // value i: { enumerable: false, configurable: false, value: 10, writable: true }, // getter and setter j: { get: () => { return Math.random() + ""; }, set: v => {} }, J: { get: () => { return "" + Math.random(); }, set: v => {} }, // only getter k: { get: function() { return { a: 1, b: "b" }; } }, K: { get: function() { return [ { a: 1, b: "b" } ]; } }, // only setter l: { set: function(v) {} }, [Symbol("a")]: { value: "1", enumerable: false }, // 测试属性排序规则 __b: { value: "private __b", enumerable: false }, _b: { value: "private _b", enumerable: false }, $b: { value: "private $b", enumerable: false } }); Object.defineProperties(obj2.g, { h: { enumerable: true, configurable: true, value: 10 }, i: { enumerable: false, configurable: false, value: 10 }, j: { get: () => { return Math.random(); }, set: v => {} }, k: { get: function() { return Math.random(); }, set: function() {} } }); Object.defineProperties(obj2.g.c, { c: { enumerable: false, configurable: false, value: 10 }, d: { get: function() { return Math.random(); }, set: function() {} } }); console.log("对象全属性:", obj2); console.log("三种函数:", { f1: function() {}, f2: () => {}, f3: function say() {} }); var arr = []; arr["10"] = "10"; arr["29"] = "29"; arr[" "] = " "; // 0x20 arr[" "] = " "; arr[" a"] = " a"; arr[" a"] = " a"; // 空白符处理 arr["!"] = "!"; // 0x21 arr["0"] = "0"; // 0x30 arr["1"] = "1"; // 0x31 arr["2"] = "2"; // 0x31 arr[":"] = ":"; // 0x3A arr["A"] = "A"; // 0x41 arr["B"] = "B"; // 0x42 arr["["] = "["; // 0x5B arr["a"] = "a"; // 0x61 arr["b"] = "b"; // 0x62 arr["{"] = "{"; // 0x7B console.log("数组 key 排序:", arr); const thumbObj = { extra: `{"num_total":300,"num_left":157,"discount":"{\"create_time\":1531970051000,\"discount_data\":\"[{\\\"amount_min\\\":\\\"100\\\",\\\"amount_cut\\\":\\\"10\\\"},{\\\"amount_min\\\":\\\"200\\\",\\\"amount_cut\\\":\\\"25\\\"},{\\\"amount_min\\\":\\\"300\\\",\\\"amount_cut\\\":\\\"50\\\"}]\",\"discount_id\":3,\"discount_name\":\"满减-yanger\",\"discount_prod\":\"[\\\"WM-180101001\\\",\\\"WM-180101002\\\",\\\"WM-180101004\\\",\\\"WM-180101006\\\",\\\"WM-180101008\\\",\\\"WM-180525102\\\",\\\"WM-180101017\\\"]\",\"discount_provider\":\"0\",\"discount_status\":\"1\",\"discount_type\":\"1\",\"update_time\":1540458898000}"}`, isChange: false }; const thumbObj2 = [ `{"num_total":300,"num_left":157,"discount":"{\"create_time\":1531970051000,\"discount_data\":\"[{\\\"amount_min\\\":\\\"100\\\",\\\"amount_cut\\\":\\\"10\\\"},{\\\"amount_min\\\":\\\"200\\\",\\\"amount_cut\\\":\\\"25\\\"},{\\\"amount_min\\\":\\\"300\\\",\\\"amount_cut\\\":\\\"50\\\"}]\",\"discount_id\":3,\"discount_name\":\"满减-yanger\",\"discount_prod\":\"[\\\"WM-180101001\\\",\\\"WM-180101002\\\",\\\"WM-180101004\\\",\\\"WM-180101006\\\",\\\"WM-180101008\\\",\\\"WM-180525102\\\",\\\"WM-180101017\\\"]\",\"discount_provider\":\"0\",\"discount_status\":\"1\",\"discount_type\":\"1\",\"update_time\":1540458898000}"}` ]; thumbObj2.push(thumbObj); console.log("测试对象缩略信息:", thumbObj, thumbObj2); } // 格式化输出 function testFormat() { var numInt = 1; var numFloat = 1.23; var str = "hello"; var obj = { a: 1, b: 2, c: { d: 3 } }; var arr = [1, 2, [3, obj]]; // 测试占位符的支持类型 testLogFormat(numInt, "%i"); testLogFormat(numFloat, "%i"); testLogFormat(numInt, "%d"); testLogFormat(numFloat, "%d"); testLogFormat(numInt, "%f"); testLogFormat(numFloat, "%f"); testLogFormat(obj, "%s"); testLogFormat(obj, "%o"); testLogFormat(arr, "%o"); testLogFormat(obj, "%O"); testLogFormat(arr, "%O"); // 测试占位符缺少对应的参数 console.log('console.log("-%s%i%d%f%o%O%c-")'); console.log("-%s%i%d%f%o%O%c-"); /* 测试占位符之间的空白符 */ // 期望输出 "a1b2c 3" console.log("a%sb%sc", 1, 2, 3); // 测试参数多余占位符 console.log('console.log("-%s-", 99, {a: "1"})'); console.log("-%s-", 99, { a: 1 }); // 测试占位符和参数的高亮 console.log("console.log('%s-%s-%s-%s-%s-%s-%s', 100, true, null, undefined, '100', {}, [])"); console.log("%s-%s-%s-%s-%s-%s-%s", 100, true, null, undefined, "100", {}, []); // 测试占位符 %c 的展示 console.log( `%c111 222 %c 333 %c %c %O %o %i %f %s %%`, "color: white; background-color: rgba(0, 116, 217, 0.69); padding: 2px 5px; border-radius: 2px", "color: #0074D9", "", "color: white; background-color: rgba(255, 65, 54, 0.69); padding: 2px 5px; margin-right: 5px; border-radius: 2px", { a: 444 }, { b: 555 }, 666, 777, "888" ); } return { testLogLevel: testLogLevel, testLogParams: testLogParams, testFormat: testFormat, testException: testException, testIntervalLog: testIntervalLog, testObject: testObject }; })(); ================================================ FILE: public/test_network.js ================================================ window.$network = (function() { var baseURL = "http://localhost:8090"; var nodeApi = "https://nodejs.org/dist/latest-v8.x/docs/api/index.json"; function testXMLHttpRequest() {} function testFetch() { if (typeof window.fetch !== "function") { console.warn("current browser version don't support fetch api"); return; } var url = baseURL + "/get_status/" + 200; // 不带选项 var options = { method: "GET", headers: { "Content-Type": "text/plain" } }; fetch(url); fetch(url, options); fetch(new Request(url)); fetch(new Request(url, options)); fetch(new Request(url, options), { headers: { "Content-Type": "text/html" } }); } // 测试 HTTP 状态码 function testHTTPStatus() { request({ url: nodeApi }); [100, 200, 300, 400, 500].forEach(function(status) { request({ url: baseURL + "/get_status/" + status }); }); } /** * 测试请求参数 */ function testRequestParams(type = "get") { var email = "xx@yy.com"; var password = "zz"; switch (type) { case "get": // GET request({ url: baseURL + "/get?email=" + email + "&password=" + password + "&a=&b" }); break; case "post:raw": request({ url: baseURL + "/post", method: "POST", data: "hello" }); break; case "post:raw:text": request({ url: baseURL + "/post", method: "POST", requestHeaders: { "Content-Type": "text/plain" }, data: "hello" }); break; case "post:raw:json": request({ url: baseURL + "/post", method: "POST", requestHeaders: { "Content-Type": "application/json;charset=UTF-8" }, data: '{"email": aa}' }); break; case "post:x-www-form-urlencoded": request({ url: baseURL + "/post", method: "POST", requestHeaders: { "Content-Type": "application/x-www-form-urlencoded" }, data: "email=" + encodeURIComponent(email) + "&password=" + encodeURIComponent(password) }); break; case "post:form-data": var formData = new FormData(); formData.append("email", email); formData.append("password", password); request({ url: baseURL + "/post", method: "POST", data: formData }); break; } } // 测试响应数据类型 // 参考 http://devdocs.io/http/basics_of_http/mime_types function testResponseData() { const mimeTypeList = [ "application/json", "application/javascript", "text/css", "text/plain", "text/html", "image/jpeg", "image/png", "image/gif", "image/svg+xml" ]; mimeTypeList.forEach(mimeType => { request({ url: baseURL + "/get_data/?mime_type=" + encodeURIComponent(mimeType) }); }); } return { testHTTPStatus: testHTTPStatus, testResponseData: testResponseData, testRequestParams: testRequestParams, testFetch: testFetch, testXMLHttpRequest: testXMLHttpRequest }; })(); function request(options) { options = options || {}; var url = options.url; var method = options.method || "GET"; var data = options.data || null; var requestHeaders = options.requestHeaders || {}; /* XMLHttpRequest */ var xhr = new window.XMLHttpRequest(); xhr.onreadystatechange = function() { // console.log("readyState:", this.readyState); if (this.readyState === XMLHttpRequest.DONE) { // console.log(this.getResponseHeader("content-type")); // console.log(this.response); } }; xhr.open(method, url); Object.keys(requestHeaders).forEach(key => { xhr.setRequestHeader(key, requestHeaders[key]); }); xhr.send(data); /* fetch */ if (typeof window.fetch === "function") { fetch(url, { method: method, body: data, headers: requestHeaders }); } else { console.warn('current brwoser don\'t support "fetch"'); } } ================================================ FILE: public/test_plugin.js ================================================ function plugin1(WebConsole) { return new WebConsole.Plugin({ id: "plugin1", name: "插件1", component: function() { var plugin = this; var tag = plugin.toString(); return { data: function() { return { text: "plugin1 panel", activeTab: "A" }; }, methods: { onWebConsoleReady: function(hostProxy) { this.hostProxy = hostProxy; console.log(tag, "onWebConsoleReady"); }, onWebConsoleShow: function(hostProxy) { console.log(tag, "onWebConsoleShow"); }, onWebConsoleHide: function(hostProxy) { console.log(tag, "onWebConsoleHide"); }, onWebConsoleTabChanged: function(hostProxy, newVal, oldVal) { console.log(tag, "onWebConsoleTabChanged", newVal); }, onWebConsoleSettingsLoaded: function(hostProxy, settings) { console.log(tag, "onWebConsoleSettingsLoaded", settings); }, onWebConsoleSettingsChanged: function(hostProxy, settings) { console.log(tag, "onWebConsoleSettingsChanged", settings); }, hidePanel: function() { this.hostProxy.hidePanel(); }, printSettings: function() { console.log("settings:", this.hostProxy.getSettings()); } }, render: function(h) { var vm = this; return h( "div", { style: { display: "flex", "flex-direction": "column" } }, [ h( "div", { style: { flex: "1" } }, [ h( "button", { on: { click: this.hidePanel } }, "hide panel" ), h( "button", { style: { "margin-left": "10px" }, on: { click: this.printSettings } }, "print settings" ), h( "div", { style: { "margin-top": "20px", display: "flex", "flex-direction": "row" } }, ["setting", "close", "refresh", "ban", "edit", "save", "add", "cancel", "expand", "collapse"].map( name => h("VIcon", { style: { "margin-left": "10px", width: "20px", height: "20px" }, props: { name: name } }) ) ) ] ), h("VJSONViewer", { props: { value: { a: 1, b: "2", c: { a: 1, b: "2" } } } }), h("VHighlightView", { props: { code: "var a = 1;\nvar b 2;\nconsole.log('a + b =', a + b)", language: "javascript" } }), h( "VTabBar", { props: { value: vm.activeTab, equalWidth: true }, on: { input: function(id) { vm.activeTab = id; } } }, [ h("VTabBarItem", { props: { id: "A" } }, "Tab1"), h("VTabBarItem", { props: { id: "B" } }, "Tab2"), h("VTabBarItem", { props: { id: "C" } }, "Tab3"), h("VTabBarItem", { props: { id: "D" } }, "Tab4") ] ), h("v-foot-bar", { props: { buttons: [ { text: "Hide", click: function() { vm.hostProxy.hidePanel(); } } ] } }) // v-foot-bar ] ); // div } }; }, settings: [ { type: "checkbox", name: "plugin0", value: false, desc: "test plugin0" } ] }); } function plugin2(WebConsole) { return new WebConsole.Plugin({ id: "plugin2", name: "插件2", component: { data: function() { return { text: "plugin2 panel" }; }, render: function(h) { return h("div", {}, this.text); } }, settings: function() { return [ { type: "checkbox", name: this.id, value: false, desc: "test " + this.id } ]; } }); } window.$plugins = [plugin1, plugin2]; ================================================ FILE: public/vue.js ================================================ /*! * Vue.js v2.5.17 * (c) 2014-2018 Evan You * Released under the MIT License. */ (function(global, factory) { typeof exports === "object" && typeof module !== "undefined" ? (module.exports = factory()) : typeof define === "function" && define.amd ? define(factory) : (global.Vue = factory()); })(this, function() { "use strict"; /* */ var emptyObject = Object.freeze({}); // these helpers produces better vm code in JS engines due to their // explicitness and function inlining function isUndef(v) { return v === undefined || v === null; } function isDef(v) { return v !== undefined && v !== null; } function isTrue(v) { return v === true; } function isFalse(v) { return v === false; } /** * Check if value is primitive */ function isPrimitive(value) { return ( typeof value === "string" || typeof value === "number" || // $flow-disable-line typeof value === "symbol" || typeof value === "boolean" ); } /** * Quick object check - this is primarily used to tell * Objects from primitive values when we know the value * is a JSON-compliant type. */ function isObject(obj) { return obj !== null && typeof obj === "object"; } /** * Get the raw type string of a value e.g. [object Object] */ var _toString = Object.prototype.toString; function toRawType(value) { return _toString.call(value).slice(8, -1); } /** * Strict object type check. Only returns true * for plain JavaScript objects. */ function isPlainObject(obj) { return _toString.call(obj) === "[object Object]"; } function isRegExp(v) { return _toString.call(v) === "[object RegExp]"; } /** * Check if val is a valid array index. */ function isValidArrayIndex(val) { var n = parseFloat(String(val)); return n >= 0 && Math.floor(n) === n && isFinite(val); } /** * Convert a value to a string that is actually rendered. */ function toString(val) { return val == null ? "" : typeof val === "object" ? JSON.stringify(val, null, 2) : String(val); } /** * Convert a input value to a number for persistence. * If the conversion fails, return original string. */ function toNumber(val) { var n = parseFloat(val); return isNaN(n) ? val : n; } /** * Make a map and return a function for checking if a key * is in that map. */ function makeMap(str, expectsLowerCase) { var map = Object.create(null); var list = str.split(","); for (var i = 0; i < list.length; i++) { map[list[i]] = true; } return expectsLowerCase ? function(val) { return map[val.toLowerCase()]; } : function(val) { return map[val]; }; } /** * Check if a tag is a built-in tag. */ var isBuiltInTag = makeMap("slot,component", true); /** * Check if a attribute is a reserved attribute. */ var isReservedAttribute = makeMap("key,ref,slot,slot-scope,is"); /** * Remove an item from an array */ function remove(arr, item) { if (arr.length) { var index = arr.indexOf(item); if (index > -1) { return arr.splice(index, 1); } } } /** * Check whether the object has the property. */ var hasOwnProperty = Object.prototype.hasOwnProperty; function hasOwn(obj, key) { return hasOwnProperty.call(obj, key); } /** * Create a cached version of a pure function. */ function cached(fn) { var cache = Object.create(null); return function cachedFn(str) { var hit = cache[str]; return hit || (cache[str] = fn(str)); }; } /** * Camelize a hyphen-delimited string. */ var camelizeRE = /-(\w)/g; var camelize = cached(function(str) { return str.replace(camelizeRE, function(_, c) { return c ? c.toUpperCase() : ""; }); }); /** * Capitalize a string. */ var capitalize = cached(function(str) { return str.charAt(0).toUpperCase() + str.slice(1); }); /** * Hyphenate a camelCase string. */ var hyphenateRE = /\B([A-Z])/g; var hyphenate = cached(function(str) { return str.replace(hyphenateRE, "-$1").toLowerCase(); }); /** * Simple bind polyfill for environments that do not support it... e.g. * PhantomJS 1.x. Technically we don't need this anymore since native bind is * now more performant in most browsers, but removing it would be breaking for * code that was able to run in PhantomJS 1.x, so this must be kept for * backwards compatibility. */ /* istanbul ignore next */ function polyfillBind(fn, ctx) { function boundFn(a) { var l = arguments.length; return l ? (l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a)) : fn.call(ctx); } boundFn._length = fn.length; return boundFn; } function nativeBind(fn, ctx) { return fn.bind(ctx); } var bind = Function.prototype.bind ? nativeBind : polyfillBind; /** * Convert an Array-like object to a real Array. */ function toArray(list, start) { start = start || 0; var i = list.length - start; var ret = new Array(i); while (i--) { ret[i] = list[i + start]; } return ret; } /** * Mix properties into target object. */ function extend(to, _from) { for (var key in _from) { to[key] = _from[key]; } return to; } /** * Merge an Array of Objects into a single Object. */ function toObject(arr) { var res = {}; for (var i = 0; i < arr.length; i++) { if (arr[i]) { extend(res, arr[i]); } } return res; } /** * Perform no operation. * Stubbing args to make Flow happy without leaving useless transpiled code * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/) */ function noop(a, b, c) {} /** * Always return false. */ var no = function(a, b, c) { return false; }; /** * Return same value */ var identity = function(_) { return _; }; /** * Generate a static keys string from compiler modules. */ function genStaticKeys(modules) { return modules .reduce(function(keys, m) { return keys.concat(m.staticKeys || []); }, []) .join(","); } /** * Check if two values are loosely equal - that is, * if they are plain objects, do they have the same shape? */ function looseEqual(a, b) { if (a === b) { return true; } var isObjectA = isObject(a); var isObjectB = isObject(b); if (isObjectA && isObjectB) { try { var isArrayA = Array.isArray(a); var isArrayB = Array.isArray(b); if (isArrayA && isArrayB) { return ( a.length === b.length && a.every(function(e, i) { return looseEqual(e, b[i]); }) ); } else if (!isArrayA && !isArrayB) { var keysA = Object.keys(a); var keysB = Object.keys(b); return ( keysA.length === keysB.length && keysA.every(function(key) { return looseEqual(a[key], b[key]); }) ); } else { /* istanbul ignore next */ return false; } } catch (e) { /* istanbul ignore next */ return false; } } else if (!isObjectA && !isObjectB) { return String(a) === String(b); } else { return false; } } function looseIndexOf(arr, val) { for (var i = 0; i < arr.length; i++) { if (looseEqual(arr[i], val)) { return i; } } return -1; } /** * Ensure a function is called only once. */ function once(fn) { var called = false; return function() { if (!called) { called = true; fn.apply(this, arguments); } }; } var SSR_ATTR = "data-server-rendered"; var ASSET_TYPES = ["component", "directive", "filter"]; var LIFECYCLE_HOOKS = [ "beforeCreate", "created", "beforeMount", "mounted", "beforeUpdate", "updated", "beforeDestroy", "destroyed", "activated", "deactivated", "errorCaptured" ]; /* */ var config = { /** * Option merge strategies (used in core/util/options) */ // $flow-disable-line optionMergeStrategies: Object.create(null), /** * Whether to suppress warnings. */ silent: false, /** * Show production mode tip message on boot? */ productionTip: "development" !== "production", /** * Whether to enable devtools */ devtools: "development" !== "production", /** * Whether to record perf */ performance: false, /** * Error handler for watcher errors */ errorHandler: null, /** * Warn handler for watcher warns */ warnHandler: null, /** * Ignore certain custom elements */ ignoredElements: [], /** * Custom user key aliases for v-on */ // $flow-disable-line keyCodes: Object.create(null), /** * Check if a tag is reserved so that it cannot be registered as a * component. This is platform-dependent and may be overwritten. */ isReservedTag: no, /** * Check if an attribute is reserved so that it cannot be used as a component * prop. This is platform-dependent and may be overwritten. */ isReservedAttr: no, /** * Check if a tag is an unknown element. * Platform-dependent. */ isUnknownElement: no, /** * Get the namespace of an element */ getTagNamespace: noop, /** * Parse the real tag name for the specific platform. */ parsePlatformTagName: identity, /** * Check if an attribute must be bound using property, e.g. value * Platform-dependent. */ mustUseProp: no, /** * Exposed for legacy reasons */ _lifecycleHooks: LIFECYCLE_HOOKS }; /* */ /** * Check if a string starts with $ or _ */ function isReserved(str) { var c = (str + "").charCodeAt(0); return c === 0x24 || c === 0x5f; } /** * Define a property. */ function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }); } /** * Parse simple path. */ var bailRE = /[^\w.$]/; function parsePath(path) { if (bailRE.test(path)) { return; } var segments = path.split("."); return function(obj) { for (var i = 0; i < segments.length; i++) { if (!obj) { return; } obj = obj[segments[i]]; } return obj; }; } /* */ // can we use __proto__? var hasProto = "__proto__" in {}; // Browser environment sniffing var inBrowser = typeof window !== "undefined"; var inWeex = typeof WXEnvironment !== "undefined" && !!WXEnvironment.platform; var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase(); var UA = inBrowser && window.navigator.userAgent.toLowerCase(); var isIE = UA && /msie|trident/.test(UA); var isIE9 = UA && UA.indexOf("msie 9.0") > 0; var isEdge = UA && UA.indexOf("edge/") > 0; var isAndroid = (UA && UA.indexOf("android") > 0) || weexPlatform === "android"; var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || weexPlatform === "ios"; var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; // Firefox has a "watch" function on Object.prototype... var nativeWatch = {}.watch; var supportsPassive = false; if (inBrowser) { try { var opts = {}; Object.defineProperty(opts, "passive", { get: function get() { /* istanbul ignore next */ supportsPassive = true; } }); // https://github.com/facebook/flow/issues/285 window.addEventListener("test-passive", null, opts); } catch (e) {} } // this needs to be lazy-evaled because vue may be required before // vue-server-renderer can set VUE_ENV var _isServer; var isServerRendering = function() { if (_isServer === undefined) { /* istanbul ignore if */ if (!inBrowser && !inWeex && typeof global !== "undefined") { // detect presence of vue-server-renderer and avoid // Webpack shimming the process _isServer = global["process"].env.VUE_ENV === "server"; } else { _isServer = false; } } return _isServer; }; // detect devtools var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; /* istanbul ignore next */ function isNative(Ctor) { return typeof Ctor === "function" && /native code/.test(Ctor.toString()); } var hasSymbol = typeof Symbol !== "undefined" && isNative(Symbol) && typeof Reflect !== "undefined" && isNative(Reflect.ownKeys); var _Set; // $flow-disable-line /* istanbul ignore if */ if (typeof Set !== "undefined" && isNative(Set)) { // use native Set when available. _Set = Set; } else { // a non-standard Set polyfill that only works with primitive keys. _Set = (function() { function Set() { this.set = Object.create(null); } Set.prototype.has = function has(key) { return this.set[key] === true; }; Set.prototype.add = function add(key) { this.set[key] = true; }; Set.prototype.clear = function clear() { this.set = Object.create(null); }; return Set; })(); } /* */ var warn = noop; var tip = noop; var generateComponentTrace = noop; // work around flow check var formatComponentName = noop; { var hasConsole = typeof console !== "undefined"; var classifyRE = /(?:^|[-_])(\w)/g; var classify = function(str) { return str .replace(classifyRE, function(c) { return c.toUpperCase(); }) .replace(/[-_]/g, ""); }; warn = function(msg, vm) { var trace = vm ? generateComponentTrace(vm) : ""; if (config.warnHandler) { config.warnHandler.call(null, msg, vm, trace); } else if (hasConsole && !config.silent) { console.error("[Vue warn]: " + msg + trace); } }; tip = function(msg, vm) { if (hasConsole && !config.silent) { console.warn("[Vue tip]: " + msg + (vm ? generateComponentTrace(vm) : "")); } }; formatComponentName = function(vm, includeFile) { if (vm.$root === vm) { return ""; } var options = typeof vm === "function" && vm.cid != null ? vm.options : vm._isVue ? vm.$options || vm.constructor.options : vm || {}; var name = options.name || options._componentTag; var file = options.__file; if (!name && file) { var match = file.match(/([^/\\]+)\.vue$/); name = match && match[1]; } return (name ? "<" + classify(name) + ">" : "") + (file && includeFile !== false ? " at " + file : ""); }; var repeat = function(str, n) { var res = ""; while (n) { if (n % 2 === 1) { res += str; } if (n > 1) { str += str; } n >>= 1; } return res; }; generateComponentTrace = function(vm) { if (vm._isVue && vm.$parent) { var tree = []; var currentRecursiveSequence = 0; while (vm) { if (tree.length > 0) { var last = tree[tree.length - 1]; if (last.constructor === vm.constructor) { currentRecursiveSequence++; vm = vm.$parent; continue; } else if (currentRecursiveSequence > 0) { tree[tree.length - 1] = [last, currentRecursiveSequence]; currentRecursiveSequence = 0; } } tree.push(vm); vm = vm.$parent; } return ( "\n\nfound in\n\n" + tree .map(function(vm, i) { return ( "" + (i === 0 ? "---> " : repeat(" ", 5 + i * 2)) + (Array.isArray(vm) ? formatComponentName(vm[0]) + "... (" + vm[1] + " recursive calls)" : formatComponentName(vm)) ); }) .join("\n") ); } else { return "\n\n(found in " + formatComponentName(vm) + ")"; } }; } /* */ var uid = 0; /** * A dep is an observable that can have multiple * directives subscribing to it. */ var Dep = function Dep() { this.id = uid++; this.subs = []; }; Dep.prototype.addSub = function addSub(sub) { this.subs.push(sub); }; Dep.prototype.removeSub = function removeSub(sub) { remove(this.subs, sub); }; Dep.prototype.depend = function depend() { if (Dep.target) { Dep.target.addDep(this); } }; Dep.prototype.notify = function notify() { // stabilize the subscriber list first var subs = this.subs.slice(); for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } }; // the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. Dep.target = null; var targetStack = []; function pushTarget(_target) { if (Dep.target) { targetStack.push(Dep.target); } Dep.target = _target; } function popTarget() { Dep.target = targetStack.pop(); } /* */ var VNode = function VNode(tag, data, children, text, elm, context, componentOptions, asyncFactory) { this.tag = tag; this.data = data; this.children = children; this.text = text; this.elm = elm; this.ns = undefined; this.context = context; this.fnContext = undefined; this.fnOptions = undefined; this.fnScopeId = undefined; this.key = data && data.key; this.componentOptions = componentOptions; this.componentInstance = undefined; this.parent = undefined; this.raw = false; this.isStatic = false; this.isRootInsert = true; this.isComment = false; this.isCloned = false; this.isOnce = false; this.asyncFactory = asyncFactory; this.asyncMeta = undefined; this.isAsyncPlaceholder = false; }; var prototypeAccessors = { child: { configurable: true } }; // DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ prototypeAccessors.child.get = function() { return this.componentInstance; }; Object.defineProperties(VNode.prototype, prototypeAccessors); var createEmptyVNode = function(text) { if (text === void 0) text = ""; var node = new VNode(); node.text = text; node.isComment = true; return node; }; function createTextVNode(val) { return new VNode(undefined, undefined, undefined, String(val)); } // optimized shallow clone // used for static nodes and slot nodes because they may be reused across // multiple renders, cloning them avoids errors when DOM manipulations rely // on their elm reference. function cloneVNode(vnode) { var cloned = new VNode( vnode.tag, vnode.data, vnode.children, vnode.text, vnode.elm, vnode.context, vnode.componentOptions, vnode.asyncFactory ); cloned.ns = vnode.ns; cloned.isStatic = vnode.isStatic; cloned.key = vnode.key; cloned.isComment = vnode.isComment; cloned.fnContext = vnode.fnContext; cloned.fnOptions = vnode.fnOptions; cloned.fnScopeId = vnode.fnScopeId; cloned.isCloned = true; return cloned; } /* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto); var methodsToPatch = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function(method) { // cache original method var original = arrayProto[method]; def(arrayMethods, method, function mutator() { var args = [], len = arguments.length; while (len--) args[len] = arguments[len]; var result = original.apply(this, args); var ob = this.__ob__; var inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); break; } if (inserted) { ob.observeArray(inserted); } // notify change ob.dep.notify(); return result; }); }); /* */ var arrayKeys = Object.getOwnPropertyNames(arrayMethods); /** * In some cases we may want to disable observation inside a component's * update computation. */ var shouldObserve = true; function toggleObserving(value) { shouldObserve = value; } /** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. */ var Observer = function Observer(value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; def(value, "__ob__", this); if (Array.isArray(value)) { var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); this.observeArray(value); } else { this.walk(value); } }; /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */ Observer.prototype.walk = function walk(obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } }; /** * Observe a list of Array items. */ Observer.prototype.observeArray = function observeArray(items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); } }; // helpers /** * Augment an target Object or Array by intercepting * the prototype chain using __proto__ */ function protoAugment(target, src, keys) { /* eslint-disable no-proto */ target.__proto__ = src; /* eslint-enable no-proto */ } /** * Augment an target Object or Array by defining * hidden properties. */ /* istanbul ignore next */ function copyAugment(target, src, keys) { for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; def(target, key, src[key]); } } /** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */ function observe(value, asRootData) { if (!isObject(value) || value instanceof VNode) { return; } var ob; if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) { ob = value.__ob__; } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value); } if (asRootData && ob) { ob.vmCount++; } return ob; } /** * Define a reactive property on an Object. */ function defineReactive(obj, key, val, customSetter, shallow) { var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return; } // cater for pre-defined getter/setters var getter = property && property.get; if (!getter && arguments.length === 2) { val = obj[key]; } var setter = property && property.set; var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); if (childOb) { childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); } } } return value; }, set: function reactiveSetter(newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return; } /* eslint-enable no-self-compare */ if ("development" !== "production" && customSetter) { customSetter(); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); } }); } /** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. */ function set(target, key, val) { if ("development" !== "production" && (isUndef(target) || isPrimitive(target))) { warn("Cannot set reactive property on undefined, null, or primitive value: " + target); } if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key); target.splice(key, 1, val); return val; } if (key in target && !(key in Object.prototype)) { target[key] = val; return val; } var ob = target.__ob__; if (target._isVue || (ob && ob.vmCount)) { "development" !== "production" && warn( "Avoid adding reactive properties to a Vue instance or its root $data " + "at runtime - declare it upfront in the data option." ); return val; } if (!ob) { target[key] = val; return val; } defineReactive(ob.value, key, val); ob.dep.notify(); return val; } /** * Delete a property and trigger change if necessary. */ function del(target, key) { if ("development" !== "production" && (isUndef(target) || isPrimitive(target))) { warn("Cannot delete reactive property on undefined, null, or primitive value: " + target); } if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1); return; } var ob = target.__ob__; if (target._isVue || (ob && ob.vmCount)) { "development" !== "production" && warn("Avoid deleting properties on a Vue instance or its root $data " + "- just set it to null."); return; } if (!hasOwn(target, key)) { return; } delete target[key]; if (!ob) { return; } ob.dep.notify(); } /** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. */ function dependArray(value) { for (var e = void 0, i = 0, l = value.length; i < l; i++) { e = value[i]; e && e.__ob__ && e.__ob__.dep.depend(); if (Array.isArray(e)) { dependArray(e); } } } /* */ /** * Option overwriting strategies are functions that handle * how to merge a parent option value and a child option * value into the final value. */ var strats = config.optionMergeStrategies; /** * Options with restrictions */ { strats.el = strats.propsData = function(parent, child, vm, key) { if (!vm) { warn('option "' + key + '" can only be used during instance ' + "creation with the `new` keyword."); } return defaultStrat(parent, child); }; } /** * Helper that recursively merges two data objects together. */ function mergeData(to, from) { if (!from) { return to; } var key, toVal, fromVal; var keys = Object.keys(from); for (var i = 0; i < keys.length; i++) { key = keys[i]; toVal = to[key]; fromVal = from[key]; if (!hasOwn(to, key)) { set(to, key, fromVal); } else if (isPlainObject(toVal) && isPlainObject(fromVal)) { mergeData(toVal, fromVal); } } return to; } /** * Data */ function mergeDataOrFn(parentVal, childVal, vm) { if (!vm) { // in a Vue.extend merge, both should be functions if (!childVal) { return parentVal; } if (!parentVal) { return childVal; } // when parentVal & childVal are both present, // we need to return a function that returns the // merged result of both functions... no need to // check if parentVal is a function here because // it has to be a function to pass previous merges. return function mergedDataFn() { return mergeData( typeof childVal === "function" ? childVal.call(this, this) : childVal, typeof parentVal === "function" ? parentVal.call(this, this) : parentVal ); }; } else { return function mergedInstanceDataFn() { // instance merge var instanceData = typeof childVal === "function" ? childVal.call(vm, vm) : childVal; var defaultData = typeof parentVal === "function" ? parentVal.call(vm, vm) : parentVal; if (instanceData) { return mergeData(instanceData, defaultData); } else { return defaultData; } }; } } strats.data = function(parentVal, childVal, vm) { if (!vm) { if (childVal && typeof childVal !== "function") { "development" !== "production" && warn( 'The "data" option should be a function ' + "that returns a per-instance value in component " + "definitions.", vm ); return parentVal; } return mergeDataOrFn(parentVal, childVal); } return mergeDataOrFn(parentVal, childVal, vm); }; /** * Hooks and props are merged as arrays. */ function mergeHook(parentVal, childVal) { return childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal; } LIFECYCLE_HOOKS.forEach(function(hook) { strats[hook] = mergeHook; }); /** * Assets * * When a vm is present (instance creation), we need to do * a three-way merge between constructor options, instance * options and parent options. */ function mergeAssets(parentVal, childVal, vm, key) { var res = Object.create(parentVal || null); if (childVal) { "development" !== "production" && assertObjectType(key, childVal, vm); return extend(res, childVal); } else { return res; } } ASSET_TYPES.forEach(function(type) { strats[type + "s"] = mergeAssets; }); /** * Watchers. * * Watchers hashes should not overwrite one * another, so we merge them as arrays. */ strats.watch = function(parentVal, childVal, vm, key) { // work around Firefox's Object.prototype.watch... if (parentVal === nativeWatch) { parentVal = undefined; } if (childVal === nativeWatch) { childVal = undefined; } /* istanbul ignore if */ if (!childVal) { return Object.create(parentVal || null); } { assertObjectType(key, childVal, vm); } if (!parentVal) { return childVal; } var ret = {}; extend(ret, parentVal); for (var key$1 in childVal) { var parent = ret[key$1]; var child = childVal[key$1]; if (parent && !Array.isArray(parent)) { parent = [parent]; } ret[key$1] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child]; } return ret; }; /** * Other object hashes. */ strats.props = strats.methods = strats.inject = strats.computed = function(parentVal, childVal, vm, key) { if (childVal && "development" !== "production") { assertObjectType(key, childVal, vm); } if (!parentVal) { return childVal; } var ret = Object.create(null); extend(ret, parentVal); if (childVal) { extend(ret, childVal); } return ret; }; strats.provide = mergeDataOrFn; /** * Default strategy. */ var defaultStrat = function(parentVal, childVal) { return childVal === undefined ? parentVal : childVal; }; /** * Validate component names */ function checkComponents(options) { for (var key in options.components) { validateComponentName(key); } } function validateComponentName(name) { if (!/^[a-zA-Z][\w-]*$/.test(name)) { warn( 'Invalid component name: "' + name + '". Component names ' + "can only contain alphanumeric characters and the hyphen, " + "and must start with a letter." ); } if (isBuiltInTag(name) || config.isReservedTag(name)) { warn("Do not use built-in or reserved HTML elements as component " + "id: " + name); } } /** * Ensure all props option syntax are normalized into the * Object-based format. */ function normalizeProps(options, vm) { var props = options.props; if (!props) { return; } var res = {}; var i, val, name; if (Array.isArray(props)) { i = props.length; while (i--) { val = props[i]; if (typeof val === "string") { name = camelize(val); res[name] = { type: null }; } else { warn("props must be strings when using array syntax."); } } } else if (isPlainObject(props)) { for (var key in props) { val = props[key]; name = camelize(key); res[name] = isPlainObject(val) ? val : { type: val }; } } else { warn( 'Invalid value for option "props": expected an Array or an Object, ' + "but got " + toRawType(props) + ".", vm ); } options.props = res; } /** * Normalize all injections into Object-based format */ function normalizeInject(options, vm) { var inject = options.inject; if (!inject) { return; } var normalized = (options.inject = {}); if (Array.isArray(inject)) { for (var i = 0; i < inject.length; i++) { normalized[inject[i]] = { from: inject[i] }; } } else if (isPlainObject(inject)) { for (var key in inject) { var val = inject[key]; normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val }; } } else { warn( 'Invalid value for option "inject": expected an Array or an Object, ' + "but got " + toRawType(inject) + ".", vm ); } } /** * Normalize raw function directives into object format. */ function normalizeDirectives(options) { var dirs = options.directives; if (dirs) { for (var key in dirs) { var def = dirs[key]; if (typeof def === "function") { dirs[key] = { bind: def, update: def }; } } } } function assertObjectType(name, value, vm) { if (!isPlainObject(value)) { warn('Invalid value for option "' + name + '": expected an Object, ' + "but got " + toRawType(value) + ".", vm); } } /** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. */ function mergeOptions(parent, child, vm) { { checkComponents(child); } if (typeof child === "function") { child = child.options; } normalizeProps(child, vm); normalizeInject(child, vm); normalizeDirectives(child); var extendsFrom = child.extends; if (extendsFrom) { parent = mergeOptions(parent, extendsFrom, vm); } if (child.mixins) { for (var i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm); } } var options = {}; var key; for (key in parent) { mergeField(key); } for (key in child) { if (!hasOwn(parent, key)) { mergeField(key); } } function mergeField(key) { var strat = strats[key] || defaultStrat; options[key] = strat(parent[key], child[key], vm, key); } return options; } /** * Resolve an asset. * This function is used because child instances need access * to assets defined in its ancestor chain. */ function resolveAsset(options, type, id, warnMissing) { /* istanbul ignore if */ if (typeof id !== "string") { return; } var assets = options[type]; // check local registration variations first if (hasOwn(assets, id)) { return assets[id]; } var camelizedId = camelize(id); if (hasOwn(assets, camelizedId)) { return assets[camelizedId]; } var PascalCaseId = capitalize(camelizedId); if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId]; } // fallback to prototype chain var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; if ("development" !== "production" && warnMissing && !res) { warn("Failed to resolve " + type.slice(0, -1) + ": " + id, options); } return res; } /* */ function validateProp(key, propOptions, propsData, vm) { var prop = propOptions[key]; var absent = !hasOwn(propsData, key); var value = propsData[key]; // boolean casting var booleanIndex = getTypeIndex(Boolean, prop.type); if (booleanIndex > -1) { if (absent && !hasOwn(prop, "default")) { value = false; } else if (value === "" || value === hyphenate(key)) { // only cast empty string / same name to boolean if // boolean has higher priority var stringIndex = getTypeIndex(String, prop.type); if (stringIndex < 0 || booleanIndex < stringIndex) { value = true; } } } // check default value if (value === undefined) { value = getPropDefaultValue(vm, prop, key); // since the default value is a fresh copy, // make sure to observe it. var prevShouldObserve = shouldObserve; toggleObserving(true); observe(value); toggleObserving(prevShouldObserve); } { assertProp(prop, key, value, vm, absent); } return value; } /** * Get the default value of a prop. */ function getPropDefaultValue(vm, prop, key) { // no default, return undefined if (!hasOwn(prop, "default")) { return undefined; } var def = prop.default; // warn against non-factory defaults for Object & Array if ("development" !== "production" && isObject(def)) { warn( 'Invalid default value for prop "' + key + '": ' + "Props with type Object/Array must use a factory function " + "to return the default value.", vm ); } // the raw prop value was also undefined from previous render, // return previous default value to avoid unnecessary watcher trigger if (vm && vm.$options.propsData && vm.$options.propsData[key] === undefined && vm._props[key] !== undefined) { return vm._props[key]; } // call factory function for non-Function types // a value is Function if its prototype is function even across different execution context return typeof def === "function" && getType(prop.type) !== "Function" ? def.call(vm) : def; } /** * Assert whether a prop is valid. */ function assertProp(prop, name, value, vm, absent) { if (prop.required && absent) { warn('Missing required prop: "' + name + '"', vm); return; } if (value == null && !prop.required) { return; } var type = prop.type; var valid = !type || type === true; var expectedTypes = []; if (type) { if (!Array.isArray(type)) { type = [type]; } for (var i = 0; i < type.length && !valid; i++) { var assertedType = assertType(value, type[i]); expectedTypes.push(assertedType.expectedType || ""); valid = assertedType.valid; } } if (!valid) { warn( 'Invalid prop: type check failed for prop "' + name + '".' + " Expected " + expectedTypes.map(capitalize).join(", ") + ", got " + toRawType(value) + ".", vm ); return; } var validator = prop.validator; if (validator) { if (!validator(value)) { warn('Invalid prop: custom validator check failed for prop "' + name + '".', vm); } } } var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/; function assertType(value, type) { var valid; var expectedType = getType(type); if (simpleCheckRE.test(expectedType)) { var t = typeof value; valid = t === expectedType.toLowerCase(); // for primitive wrapper objects if (!valid && t === "object") { valid = value instanceof type; } } else if (expectedType === "Object") { valid = isPlainObject(value); } else if (expectedType === "Array") { valid = Array.isArray(value); } else { valid = value instanceof type; } return { valid: valid, expectedType: expectedType }; } /** * Use function string name to check built-in types, * because a simple equality check will fail when running * across different vms / iframes. */ function getType(fn) { var match = fn && fn.toString().match(/^\s*function (\w+)/); return match ? match[1] : ""; } function isSameType(a, b) { return getType(a) === getType(b); } function getTypeIndex(type, expectedTypes) { if (!Array.isArray(expectedTypes)) { return isSameType(expectedTypes, type) ? 0 : -1; } for (var i = 0, len = expectedTypes.length; i < len; i++) { if (isSameType(expectedTypes[i], type)) { return i; } } return -1; } /* */ function handleError(err, vm, info) { if (vm) { var cur = vm; while ((cur = cur.$parent)) { var hooks = cur.$options.errorCaptured; if (hooks) { for (var i = 0; i < hooks.length; i++) { try { var capture = hooks[i].call(cur, err, vm, info) === false; if (capture) { return; } } catch (e) { globalHandleError(e, cur, "errorCaptured hook"); } } } } } globalHandleError(err, vm, info); } function globalHandleError(err, vm, info) { if (config.errorHandler) { try { return config.errorHandler.call(null, err, vm, info); } catch (e) { logError(e, null, "config.errorHandler"); } } logError(err, vm, info); } function logError(err, vm, info) { { warn("Error in " + info + ': "' + err.toString() + '"', vm); } /* istanbul ignore else */ if ((inBrowser || inWeex) && typeof console !== "undefined") { console.error(err); } else { throw err; } } /* */ /* globals MessageChannel */ var callbacks = []; var pending = false; function flushCallbacks() { pending = false; var copies = callbacks.slice(0); callbacks.length = 0; for (var i = 0; i < copies.length; i++) { copies[i](); } } // Here we have async deferring wrappers using both microtasks and (macro) tasks. // In < 2.4 we used microtasks everywhere, but there are some scenarios where // microtasks have too high a priority and fire in between supposedly // sequential events (e.g. #4521, #6690) or even between bubbling of the same // event (#6566). However, using (macro) tasks everywhere also has subtle problems // when state is changed right before repaint (e.g. #6813, out-in transitions). // Here we use microtask by default, but expose a way to force (macro) task when // needed (e.g. in event handlers attached by v-on). var microTimerFunc; var macroTimerFunc; var useMacroTask = false; // Determine (macro) task defer implementation. // Technically setImmediate should be the ideal choice, but it's only available // in IE. The only polyfill that consistently queues the callback after all DOM // events triggered in the same loop is by using MessageChannel. /* istanbul ignore if */ if (typeof setImmediate !== "undefined" && isNative(setImmediate)) { macroTimerFunc = function() { setImmediate(flushCallbacks); }; } else if ( typeof MessageChannel !== "undefined" && (isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === "[object MessageChannelConstructor]") ) { var channel = new MessageChannel(); var port = channel.port2; channel.port1.onmessage = flushCallbacks; macroTimerFunc = function() { port.postMessage(1); }; } else { /* istanbul ignore next */ macroTimerFunc = function() { setTimeout(flushCallbacks, 0); }; } // Determine microtask defer implementation. /* istanbul ignore next, $flow-disable-line */ if (typeof Promise !== "undefined" && isNative(Promise)) { var p = Promise.resolve(); microTimerFunc = function() { p.then(flushCallbacks); // in problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. if (isIOS) { setTimeout(noop); } }; } else { // fallback to macro microTimerFunc = macroTimerFunc; } /** * Wrap a function so that if any code inside triggers state change, * the changes are queued using a (macro) task instead of a microtask. */ function withMacroTask(fn) { return ( fn._withTask || (fn._withTask = function() { useMacroTask = true; var res = fn.apply(null, arguments); useMacroTask = false; return res; }) ); } function nextTick(cb, ctx) { var _resolve; callbacks.push(function() { if (cb) { try { cb.call(ctx); } catch (e) { handleError(e, ctx, "nextTick"); } } else if (_resolve) { _resolve(ctx); } }); if (!pending) { pending = true; if (useMacroTask) { macroTimerFunc(); } else { microTimerFunc(); } } // $flow-disable-line if (!cb && typeof Promise !== "undefined") { return new Promise(function(resolve) { _resolve = resolve; }); } } /* */ var mark; var measure; { var perf = inBrowser && window.performance; /* istanbul ignore if */ if (perf && perf.mark && perf.measure && perf.clearMarks && perf.clearMeasures) { mark = function(tag) { return perf.mark(tag); }; measure = function(name, startTag, endTag) { perf.measure(name, startTag, endTag); perf.clearMarks(startTag); perf.clearMarks(endTag); perf.clearMeasures(name); }; } } /* not type checking this file because flow doesn't play well with Proxy */ var initProxy; { var allowedGlobals = makeMap( "Infinity,undefined,NaN,isFinite,isNaN," + "parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent," + "Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl," + "require" // for Webpack/Browserify ); var warnNonPresent = function(target, key) { warn( 'Property or method "' + key + '" is not defined on the instance but ' + "referenced during render. Make sure that this property is reactive, " + "either in the data option, or for class-based components, by " + "initializing the property. " + "See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.", target ); }; var hasProxy = typeof Proxy !== "undefined" && isNative(Proxy); if (hasProxy) { var isBuiltInModifier = makeMap("stop,prevent,self,ctrl,shift,alt,meta,exact"); config.keyCodes = new Proxy(config.keyCodes, { set: function set(target, key, value) { if (isBuiltInModifier(key)) { warn("Avoid overwriting built-in modifier in config.keyCodes: ." + key); return false; } else { target[key] = value; return true; } } }); } var hasHandler = { has: function has(target, key) { var has = key in target; var isAllowed = allowedGlobals(key) || key.charAt(0) === "_"; if (!has && !isAllowed) { warnNonPresent(target, key); } return has || !isAllowed; } }; var getHandler = { get: function get(target, key) { if (typeof key === "string" && !(key in target)) { warnNonPresent(target, key); } return target[key]; } }; initProxy = function initProxy(vm) { if (hasProxy) { // determine which proxy handler to use var options = vm.$options; var handlers = options.render && options.render._withStripped ? getHandler : hasHandler; vm._renderProxy = new Proxy(vm, handlers); } else { vm._renderProxy = vm; } }; } /* */ var seenObjects = new _Set(); /** * Recursively traverse an object to evoke all converted * getters, so that every nested property inside the object * is collected as a "deep" dependency. */ function traverse(val) { _traverse(val, seenObjects); seenObjects.clear(); } function _traverse(val, seen) { var i, keys; var isA = Array.isArray(val); if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { return; } if (val.__ob__) { var depId = val.__ob__.dep.id; if (seen.has(depId)) { return; } seen.add(depId); } if (isA) { i = val.length; while (i--) { _traverse(val[i], seen); } } else { keys = Object.keys(val); i = keys.length; while (i--) { _traverse(val[keys[i]], seen); } } } /* */ var normalizeEvent = cached(function(name) { var passive = name.charAt(0) === "&"; name = passive ? name.slice(1) : name; var once$$1 = name.charAt(0) === "~"; // Prefixed last, checked first name = once$$1 ? name.slice(1) : name; var capture = name.charAt(0) === "!"; name = capture ? name.slice(1) : name; return { name: name, once: once$$1, capture: capture, passive: passive }; }); function createFnInvoker(fns) { function invoker() { var arguments$1 = arguments; var fns = invoker.fns; if (Array.isArray(fns)) { var cloned = fns.slice(); for (var i = 0; i < cloned.length; i++) { cloned[i].apply(null, arguments$1); } } else { // return handler return value for single handlers return fns.apply(null, arguments); } } invoker.fns = fns; return invoker; } function updateListeners(on, oldOn, add, remove$$1, vm) { var name, def, cur, old, event; for (name in on) { def = cur = on[name]; old = oldOn[name]; event = normalizeEvent(name); /* istanbul ignore if */ if (isUndef(cur)) { "development" !== "production" && warn('Invalid handler for event "' + event.name + '": got ' + String(cur), vm); } else if (isUndef(old)) { if (isUndef(cur.fns)) { cur = on[name] = createFnInvoker(cur); } add(event.name, cur, event.once, event.capture, event.passive, event.params); } else if (cur !== old) { old.fns = cur; on[name] = old; } } for (name in oldOn) { if (isUndef(on[name])) { event = normalizeEvent(name); remove$$1(event.name, oldOn[name], event.capture); } } } /* */ function mergeVNodeHook(def, hookKey, hook) { if (def instanceof VNode) { def = def.data.hook || (def.data.hook = {}); } var invoker; var oldHook = def[hookKey]; function wrappedHook() { hook.apply(this, arguments); // important: remove merged hook to ensure it's called only once // and prevent memory leak remove(invoker.fns, wrappedHook); } if (isUndef(oldHook)) { // no existing hook invoker = createFnInvoker([wrappedHook]); } else { /* istanbul ignore if */ if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { // already a merged invoker invoker = oldHook; invoker.fns.push(wrappedHook); } else { // existing plain hook invoker = createFnInvoker([oldHook, wrappedHook]); } } invoker.merged = true; def[hookKey] = invoker; } /* */ function extractPropsFromVNodeData(data, Ctor, tag) { // we are only extracting raw values here. // validation and default values are handled in the child // component itself. var propOptions = Ctor.options.props; if (isUndef(propOptions)) { return; } var res = {}; var attrs = data.attrs; var props = data.props; if (isDef(attrs) || isDef(props)) { for (var key in propOptions) { var altKey = hyphenate(key); { var keyInLowerCase = key.toLowerCase(); if (key !== keyInLowerCase && attrs && hasOwn(attrs, keyInLowerCase)) { tip( 'Prop "' + keyInLowerCase + '" is passed to component ' + formatComponentName(tag || Ctor) + ", but the declared prop name is" + ' "' + key + '". ' + "Note that HTML attributes are case-insensitive and camelCased " + "props need to use their kebab-case equivalents when using in-DOM " + 'templates. You should probably use "' + altKey + '" instead of "' + key + '".' ); } } checkProp(res, props, key, altKey, true) || checkProp(res, attrs, key, altKey, false); } } return res; } function checkProp(res, hash, key, altKey, preserve) { if (isDef(hash)) { if (hasOwn(hash, key)) { res[key] = hash[key]; if (!preserve) { delete hash[key]; } return true; } else if (hasOwn(hash, altKey)) { res[key] = hash[altKey]; if (!preserve) { delete hash[altKey]; } return true; } } return false; } /* */ // The template compiler attempts to minimize the need for normalization by // statically analyzing the template at compile time. // // For plain HTML markup, normalization can be completely skipped because the // generated render function is guaranteed to return Array. There are // two cases where extra normalization is needed: // 1. When the children contains components - because a functional component // may return an Array instead of a single root. In this case, just a simple // normalization is needed - if any child is an Array, we flatten the whole // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep // because functional components already normalize their own children. function simpleNormalizeChildren(children) { for (var i = 0; i < children.length; i++) { if (Array.isArray(children[i])) { return Array.prototype.concat.apply([], children); } } return children; } // 2. When the children contains constructs that always generated nested Arrays, // e.g.