├── static └── .gitkeep ├── .eslintignore ├── config ├── prod.env.js ├── dev.env.js └── index.js ├── .editorconfig ├── src ├── App.vue ├── plugins │ ├── index.js │ ├── __performanceMonitor.js │ └── performanceMonitor.js ├── router │ └── index.js ├── views │ └── index.vue └── main.js ├── .gitignore ├── .postcssrc.js ├── index.html ├── .babelrc ├── LICENSE ├── .eslintrc.js ├── package.json └── README.md /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | import PerformanceMonitor from './performanceMonitor'; 2 | 3 | export default { 4 | install(Vue, options) { 5 | /* eslint-disable no-new */ 6 | new PerformanceMonitor(options); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | 4 | Vue.use(VueRouter); 5 | 6 | export default new VueRouter({ 7 | routes: [{ 8 | path: '/', 9 | name: 'Index', 10 | component: () => import( /* webpackChunkName:'index' */ '../views/index.vue') 11 | }] 12 | }) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 前端性能监控 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/views/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false, 7 | "targets": { 8 | "browsers": [ 9 | "> 1%", 10 | "last 2 versions", 11 | "not ie <= 8" 12 | ] 13 | } 14 | } 15 | ], 16 | "stage-2" 17 | ], 18 | "plugins": [ 19 | "transform-vue-jsx", 20 | "transform-runtime" 21 | ] 22 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import PerformanceMonitor from './plugins/performanceMonitor'; 7 | 8 | Vue.config.productionTip = false 9 | 10 | Vue.use(PerformanceMonitor, { 11 | reportUrl: "http://localhost:10300/performanceMonitor", 12 | appId: "performanceMonitor-1559318109525", 13 | appName: "performanceMonitor", 14 | env: 'dev' 15 | }); 16 | 17 | /* eslint-disable no-new */ 18 | new Vue({ 19 | el: '#app', 20 | components: { 21 | App 22 | }, 23 | template: '', 24 | router 25 | }) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 sky9102(https://github.com/sky9102) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: "babel-eslint" 7 | }, 8 | env: { 9 | browser: true 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | "plugin:vue/essential", 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | "standard" 17 | ], 18 | // required to lint *.vue files 19 | plugins: ["vue"], 20 | // add your custom rules here 21 | rules: { 22 | // allow async-await 23 | "generator-star-spacing": "off", 24 | // allow debugger during development 25 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", 26 | semi: 0, 27 | quotes: 0, 28 | "space-before-function-paren": 0, 29 | "no-multiple-empty-lines": 0, 30 | "comma-style": 0, 31 | "no-useless-return": 0, 32 | indent: 0, 33 | "no-unused-vars": 0, 34 | "o-undef": 0, 35 | "eol-last": 0, 36 | "comma-dangle": 0, 37 | eqeqeq: 0, 38 | "no-throw-literal": 0, 39 | "prefer-promise-reject-errors": 0, 40 | "no-trailing-spaces": 0, 41 | "no-new": 0, 42 | "operator-linebreak": 0, 43 | 'space-in-parens': 0, 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "performance-monitor", 3 | "version": "1.0.0", 4 | "description": "performance-monitor", 5 | "author": "sky9102", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "stylus": "^0.54.5", 15 | "stylus-loader": "^3.0.2", 16 | "vue": "^2.5.2", 17 | "vue-router": "^3.0.6" 18 | }, 19 | "devDependencies": { 20 | "autoprefixer": "^7.1.2", 21 | "babel-core": "^6.22.1", 22 | "babel-eslint": "^8.2.1", 23 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 24 | "babel-loader": "^7.1.1", 25 | "babel-plugin-syntax-jsx": "^6.18.0", 26 | "babel-plugin-transform-runtime": "^6.22.0", 27 | "babel-plugin-transform-vue-jsx": "^3.5.0", 28 | "babel-preset-env": "^1.3.2", 29 | "babel-preset-stage-2": "^6.22.0", 30 | "chalk": "^2.0.1", 31 | "copy-webpack-plugin": "^4.0.1", 32 | "css-loader": "^0.28.0", 33 | "eslint": "^4.15.0", 34 | "eslint-config-standard": "^10.2.1", 35 | "eslint-friendly-formatter": "^3.0.0", 36 | "eslint-loader": "^1.7.1", 37 | "eslint-plugin-import": "^2.7.0", 38 | "eslint-plugin-node": "^5.2.0", 39 | "eslint-plugin-promise": "^3.4.0", 40 | "eslint-plugin-standard": "^3.0.1", 41 | "eslint-plugin-vue": "^4.0.0", 42 | "extract-text-webpack-plugin": "^3.0.0", 43 | "file-loader": "^1.1.4", 44 | "friendly-errors-webpack-plugin": "^1.6.1", 45 | "html-webpack-plugin": "^2.30.1", 46 | "node-notifier": "^5.1.2", 47 | "optimize-css-assets-webpack-plugin": "^3.2.0", 48 | "ora": "^1.2.0", 49 | "portfinder": "^1.0.13", 50 | "postcss-import": "^11.0.0", 51 | "postcss-loader": "^2.0.8", 52 | "postcss-url": "^7.2.1", 53 | "rimraf": "^2.6.0", 54 | "semver": "^5.3.0", 55 | "shelljs": "^0.7.6", 56 | "uglifyjs-webpack-plugin": "^1.1.1", 57 | "url-loader": "^0.5.8", 58 | "vue-loader": "^13.3.0", 59 | "vue-style-loader": "^3.0.1", 60 | "vue-template-compiler": "^2.5.2", 61 | "webpack": "^3.6.0", 62 | "webpack-bundle-analyzer": "^2.9.0", 63 | "webpack-dev-server": "^2.9.1", 64 | "webpack-merge": "^4.1.0" 65 | }, 66 | "engines": { 67 | "node": ">= 6.0.0", 68 | "npm": ">= 3.0.0" 69 | }, 70 | "browserslist": [ 71 | "> 1%", 72 | "last 2 versions", 73 | "not ie <= 8" 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 10300, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | // Use Eslint Loader? 24 | // If true, your code will be linted during bundling and 25 | // linting errors and warnings will be shown in the console. 26 | useEslint: true, 27 | // If true, eslint errors and warnings will also be shown in the error overlay 28 | // in the browser. 29 | showEslintErrorsInOverlay: false, 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'cheap-module-eval-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | 43 | cssSourceMap: true 44 | }, 45 | 46 | build: { 47 | // Template for index.html 48 | index: path.resolve(__dirname, '../dist/index.html'), 49 | 50 | // Paths 51 | assetsRoot: path.resolve(__dirname, '../dist'), 52 | assetsSubDirectory: 'static', 53 | assetsPublicPath: '/', 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | productionSourceMap: true, 60 | // https://webpack.js.org/configuration/devtool/#production 61 | devtool: '#source-map', 62 | 63 | // Gzip off by default as many popular static hosts such as 64 | // Surge or Netlify already gzip all static assets for you. 65 | // Before setting to `true`, make sure to: 66 | // npm install --save-dev compression-webpack-plugin 67 | productionGzip: false, 68 | productionGzipExtensions: ['js', 'css'], 69 | 70 | // Run the build command with an extra argument to 71 | // View the bundle analyzer report after build finishes: 72 | // `npm run build --report` 73 | // Set to `true` or `false` to always turn it on or off 74 | bundleAnalyzerReport: process.env.npm_config_report 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前端性能监控 2 | 3 | > 简介:前端性能监控,实时上报用户浏览站点性能数据;监控的数据有 **准备新页面时间耗时、DNS查询耗时、TCP链接耗时、Request请求耗时、解析dom树耗时、页面白屏时间、domready时间、onload执行完成时间时间**;可以持续监控、评估、上报数据,及时发现瓶颈,从而优化。 4 | 5 | * 目的:解决前端性能监控、评估、预警页面性能的状况、发现瓶颈、从而指导优化工作。 6 | * 功能:记录**前端**性能信息。 7 | * 范围:任何前端应用 8 | * 使用:两行代码搞定,使用的复杂度几乎降低到了零 9 | 10 | ## 为什么要做监控页面性能? 11 | * 1、一个页面性能差的话会影响到很多方面。在公司层面,页面性能会影响公司收益,如用户打开页面等待的太久,用户可能会直接关掉页面,或者下次不再打开了,特别是在移动端用户对页面响应延迟容忍度很低。 12 | 13 | * 2、除此之外,页面的加载速度还将直接影响页面的SEO,网页加载速度太慢,用户会直接关掉,这直接增加页面的跳出率,当搜索引擎发现页面跳出率高,搜索引擎会认为该网站对用户的价值不高,从而降低排名。2018年7月谷歌公司新规定,页面访问时间比较长,谷歌公司将会降低该页面的搜索排名。 14 | 15 | * 3、虽然性能很重要,但在开发迭代中,开发会有所忽略,性能会随着版本迭代而有所衰减,所以我们需要一个性能监控系统,持续监控,评估,预警页面性能的状况,发现瓶颈,从而指导优化工作。 16 | 17 | * 4、页面性能的评估与监控有很多成熟优秀的工具 ,比如gtmetrix 网站,他可以同时查多个分析工具的的结果,会提供许多的建议。 18 | 19 | * 5、 但这种方式与真实情况偏离,无法反馈某个地区的整体速度,慢速用户多少,无法反映性能的波动情况,另外除了白屏之类的,我们还有一些功能性的测速,比如页面可点击时间,广告展示的时间等等,这些都是无法模拟监控的。 20 | 21 | * 6、为了持续监控不同网络环境下用户访问情况与页面各功能可用情况,我们选择在页面上植入JS来监控线上真实用户数据。具体做法就是用一段代码将用户的数据上报到我们的服务器,通过一个系统将数据汇总,处理,最后图形化数据,方便我们查看各个页面等性能。 22 | 23 | ## 特点 24 | * 可拔插 25 | * 代码侵入量小 26 | * 使用灵活方便 27 | 28 | ## 快速启动 29 | 30 | ``` bash 31 | npm install -g cnpm --registry=https://registry.npm.taobao.org 32 | cnpm install 33 | npm run dev 34 | ``` 35 | 此时,浏览器打开,输入网址http://localhost:10300, 进入 **性能监控测试页面**。 36 | 37 | ### 使用 38 | ``` 39 | import PerformanceMonitor from './plugins/performanceMonitor'; 40 | 41 | Vue.use(PerformanceMonitor, { 42 | reportUrl: "http://localhost:10300/performanceMonitor", 43 | appId: "performanceMonitor-1559318109525", 44 | appName: "performanceMonitor", 45 | env: "dev" 46 | }); 47 | ``` 48 | 49 | ## 配置参数 options 50 | 51 | 属性|说明|类型|默认值|是否可以为空 52 | --|:--:|--:|--:|--: 53 | reportUrl|性能上报地址|String|http://localhost:10300/performanceMonitor|N| 54 | env|环境:dev、test、uat、pro|String|dev|Y 55 | appId|项目ID|String||Y 56 | appName|项目名称|String||Y 57 | timeSpan|发送数据时的时间戳|Number|每次取当前的时间戳|Y| 58 | userAgent|userAgent|String|userAgent|Y| 59 | isSendBeacon|是否使用高级浏览器支持的 navigator.sendBeacon方法,这个方法可以用来发送一些小量数据,该方法是异步的,且在浏览器关闭请求也照样能发送请求,特别适合上报统计的场景。不支持时使用img的方式上报|Boolean|false|N| 60 | 61 | ## 上报数据 62 | 属性|说明 63 | --|:--: 64 | reportUrl|上报URL 65 | appId|appId 66 | env|环境:dev、test、uat、pro 67 | infoType|preformance 68 | timeSpan|当前时间戳 69 | userAgent|userAgent 70 | isSendBeacon|是否使用高级浏览器支持的 navigator.sendBeacon方法 71 | prepareNewPageTime|准备新页面时间耗时(毫秒) 72 | queryDNSTime|DNS查询耗时(毫秒) 73 | connectionTCPTime|TCP链接耗时(毫秒) 74 | requestTime|request请求耗时(毫秒) 75 | analysisDOMTime|解析dom树耗时(毫秒) 76 | whiteScreenTime|白屏时间(毫秒) 77 | domReadyTime|domready时间(毫秒) 78 | onloadSuccessTime|onload执行完成时间(毫秒) 79 | currenPagetUrl|当前页面地址 80 | 81 | 82 | ## 问题 83 | * 开发者有问题或者好的建议可以用[Issues](https://github.com/sky9102/performance-monitor/issues)反馈交流,请给出详细信息。 84 | * 如果有问题需要提问,请在提问前先完成以下过程: 85 | * 请仔细阅读本项目文档,查看能否解决; 86 | * 请提问前尽可能做一些DEBUG或者思考分析,然后提问时给出详细的错误相关信息以及个人对问题的理解。 87 | 88 | ## License 89 | [MIT](https://github.com/sky9102/performance-monitor/blob/master/LICENSE) Copyright (c) 2019 [sky9102](https://github.com/sky9102) 90 | -------------------------------------------------------------------------------- /src/plugins/__performanceMonitor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 格式化参数 3 | */ 4 | function formatParams(data = {}) { 5 | const arr = []; 6 | for (const name in data) { 7 | arr.push( 8 | encodeURIComponent(name) + "=" + encodeURIComponent(data[name]) 9 | ); 10 | } 11 | return arr.join("&"); 12 | } 13 | 14 | /** 15 | * 性能监控 16 | */ 17 | export default class PerformanceMonitor { 18 | constructor(ops) { 19 | this.options = { 20 | reportUrl: location.href, // 上报地址 21 | appId: "", // 项目ID 22 | appName: "", // 项目名称, 23 | env: "dev", // 环境:dev、test、uat、pro 24 | infoType: "preformance", // 信息类别 25 | timeSpan: Date.now(), // 发送数据时的时间戳 26 | userAgent: navigator.userAgent, 27 | isSendBeacon: false 28 | }; 29 | Object.assign(this.options, ops); 30 | // 任务列表,存放所有任务 31 | this.reqDataList = []; 32 | 33 | this.init(); 34 | } 35 | 36 | init() { 37 | this.listenOnLoad(); 38 | } 39 | 40 | listenOnLoad() { 41 | window.addEventListener("load", () => { 42 | setTimeout(() => { 43 | this.delaySetPerformanceData({ 44 | currenPagetUrl: location.href 45 | }); 46 | }); 47 | }); 48 | } 49 | 50 | delaySetPerformanceData(obj) { 51 | this.savePerformanceData(obj); 52 | } 53 | 54 | getPerformanceData() { 55 | const { 56 | timing 57 | } = window.performance; 58 | const { 59 | navigationStart = 0, // 准备加载页面的起始时间 60 | fetchStart = 0, // 开始检查缓存或开始获取资源的时间 61 | domainLookupStart = 0, // 开始进行dns查询的时间 62 | domainLookupEnd = 0, // dns查询结束的时间 63 | connectStart = 0, // 开始建立连接请求资源的时间 64 | connectEnd = 0, // 建立连接成功的时间. 65 | responseStart = 0, // 接收到第一个字节的时间 66 | responseEnd = 0, // 接收到最后一个字节的时间. 67 | domInteractive = 0, // 文档解析结束的时间 68 | domContentLoadedEventEnd = 0, // DOMContentLoaded事件结束的时间 69 | domComplete = 0, // current document readiness被设置 complete的时间 70 | loadEventEnd = 0 // onload事件结束的时间 71 | } = timing; 72 | 73 | // 准备新页面时间耗时 74 | const prepareNewPageTime = fetchStart - navigationStart; 75 | // DNS查询耗时 76 | const queryDNSTime = domainLookupEnd - domainLookupStart; 77 | // TCP链接耗时 78 | const connectionTCPTime = connectEnd - connectStart; 79 | // request请求耗时 80 | const requestTime = responseEnd - responseStart; 81 | // 解析dom树耗时 82 | const analysisDOMTime = domComplete - domInteractive; 83 | // 白屏时间 84 | const whiteScreenTime = responseStart - navigationStart; 85 | // domready时间 86 | const domReadyTime = domContentLoadedEventEnd - navigationStart; 87 | // onload执行完成时间时间 88 | const onloadSuccessTime = loadEventEnd - navigationStart; 89 | 90 | return { 91 | prepareNewPageTime, 92 | queryDNSTime, 93 | connectionTCPTime, 94 | requestTime, 95 | analysisDOMTime, 96 | whiteScreenTime, 97 | domReadyTime, 98 | onloadSuccessTime 99 | }; 100 | } 101 | 102 | async savePerformanceData(obj) { 103 | const performanceInfo = this.getPerformanceData(); 104 | await Object.assign(performanceInfo, obj, { 105 | timeSpan: Date.now() 106 | }); 107 | await this.reqDataList.push( 108 | Object.assign({}, this.options, performanceInfo) 109 | ); 110 | await this.asyncSendReport(); 111 | } 112 | 113 | asyncSendReport() { 114 | const { 115 | isSendBeacon = false, reportUrl = "" 116 | } = this.options; 117 | let repDataList = this.reqDataList; 118 | 119 | while (repDataList.length > 0) { 120 | const reqData = repDataList.shift(); 121 | (data => { 122 | setTimeout(() => { 123 | this.sendReport(data, reportUrl, isSendBeacon); 124 | }); 125 | })(reqData); 126 | } 127 | } 128 | 129 | /** 130 | * 高级浏览器还支持 navigator.sendBeacon方法。 131 | * 这个方法可以用来发送一些小量数据,该方法是异步的,且在浏览器关闭请求也照样能发,特别适合上报统计的场景。 132 | * 不支持时使用img的方式上报 133 | */ 134 | sendReport(performance, reportUrl, isSendBeacon = false) { 135 | if (isSendBeacon && navigator.sendBeacon) { 136 | this.sendBeacon(performance, reportUrl); 137 | return; 138 | } 139 | this.sendImage(performance, reportUrl); 140 | } 141 | 142 | sendBeacon(data, reportUrl) { 143 | navigator.sendBeacon(reportUrl, JSON.stringify(data)); 144 | } 145 | 146 | sendImage(data, reportUrl) { 147 | const image = new Image(); 148 | image.src = `${reportUrl}?${formatParams(data)}`; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/plugins/performanceMonitor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 格式化参数 3 | */ 4 | function formatParams(data = {}) { 5 | const arr = []; 6 | for (const name in data) { 7 | arr.push( 8 | encodeURIComponent(name) + "=" + encodeURIComponent(data[name]) 9 | ); 10 | } 11 | return arr.join("&"); 12 | } 13 | 14 | /** 15 | * 性能监控 16 | */ 17 | class PerformanceMonitor { 18 | constructor(ops) { 19 | this.options = { 20 | reportUrl: location.href, // 上报地址 21 | appId: "", // 项目ID 22 | appName: "", // 项目名称, 23 | env: "dev", // 环境:dev、test、uat、pro 24 | infoType: "preformance", // 信息类别 25 | timeSpan: Date.now(), // 发送数据时的时间戳 26 | userAgent: navigator.userAgent, 27 | isSendBeacon: false 28 | }; 29 | Object.assign(this.options, ops); 30 | // 任务列表,存放所有任务 31 | this.reqDataList = []; 32 | 33 | this.init(); 34 | } 35 | 36 | init() { 37 | this.listenOnLoad(); 38 | } 39 | 40 | listenOnLoad() { 41 | window.addEventListener("load", () => { 42 | setTimeout(() => { 43 | this.delaySetPerformanceData({ 44 | currenPagetUrl: location.href 45 | }); 46 | }); 47 | }); 48 | } 49 | 50 | delaySetPerformanceData(obj) { 51 | this.savePerformanceData(obj); 52 | } 53 | 54 | getPerformanceData() { 55 | const { timing, memory, navigation } = window.performance; 56 | const { 57 | navigationStart = 0, // 准备加载页面的起始时间 58 | // unloadEventStart = 0, // 如果前一个文档和当前文档同源,返回前一个文档开始unload的时间 59 | // unloadEventEnd = 0, // 如果前一个文档和当前文档同源,返回前一个文档开始unload结束的时间 60 | // redirectStart = 0, // 如果有重定向,这里是重定向开始的时间. 61 | // redirectEnd = 0, // 如果有重定向,这里是重定向结束的时间. 62 | fetchStart = 0, // 开始检查缓存或开始获取资源的时间 63 | domainLookupStart = 0, // 开始进行dns查询的时间 64 | domainLookupEnd = 0, // dns查询结束的时间 65 | connectStart = 0, // 开始建立连接请求资源的时间 66 | connectEnd = 0, // 建立连接成功的时间. 67 | // secureConnectionStart = 0, // 如果是https请求.返回ssl握手的时间 68 | // requestStart = 0, // 开始请求文档时间(包括从服务器,本地缓存请求) 69 | responseStart = 0, // 接收到第一个字节的时间 70 | responseEnd = 0, // 接收到最后一个字节的时间. 71 | // domLoading = 0, // ‘current document readiness’ 设置为 loading的时间 (这个时候还木有开始解析文档) 72 | domInteractive = 0, // 文档解析结束的时间 73 | // domContentLoadedEventStart = 0, // DOMContentLoaded事件开始的时间 74 | domContentLoadedEventEnd = 0, // DOMContentLoaded事件结束的时间 75 | domComplete = 0, // current document readiness被设置 complete的时间 76 | // loadEventStart = 0, // 触发onload事件的时间 77 | loadEventEnd = 0 // onload事件结束的时间 78 | } = timing; 79 | 80 | // const { 81 | // usedJSHeapSize = 0, // JS 对象(包括V8引擎内部对象)占用的内存,一定小于 totalJSHeapSize,否则可能出现内存泄漏 82 | // totalJSHeapSize = -1 // 可使用的内存 83 | // } = memory; 84 | 85 | // 准备新页面时间耗时 86 | const prepareNewPageTime = fetchStart - navigationStart; 87 | // DNS查询耗时 88 | const queryDNSTime = domainLookupEnd - domainLookupStart; 89 | // TCP链接耗时 90 | const connectionTCPTime = connectEnd - connectStart; 91 | // request请求耗时 92 | const requestTime = responseEnd - responseStart; 93 | // 解析dom树耗时 94 | const analysisDOMTime = domComplete - domInteractive; 95 | // 白屏时间 96 | const whiteScreenTime = responseStart - navigationStart; 97 | // domready时间 98 | const domReadyTime = domContentLoadedEventEnd - navigationStart; 99 | // onload执行完成时间 100 | const onloadSuccessTime = loadEventEnd - navigationStart; 101 | 102 | // 内存是否溢出 103 | // const memoryOverFlow = totalJSHeapSize > usedJSHeapSize ? 0 : 1; 104 | 105 | // 页面的加载方式 106 | // const pageLoadType = navigation.type; 107 | // const pageLoadTypeStr = this.pageLoadMethod(navigation.type); 108 | 109 | return { 110 | prepareNewPageTime, 111 | queryDNSTime, 112 | connectionTCPTime, 113 | requestTime, 114 | analysisDOMTime, 115 | whiteScreenTime, 116 | domReadyTime, 117 | onloadSuccessTime 118 | // memoryOverFlow, 119 | // pageLoadTypeStr 120 | // pageLoadType 121 | }; 122 | } 123 | 124 | async savePerformanceData(obj) { 125 | const performanceInfo = this.getPerformanceData(); 126 | await Object.assign(performanceInfo, obj, { 127 | timeSpan: Date.now() 128 | }); 129 | await this.reqDataList.push( 130 | Object.assign({}, this.options, performanceInfo) 131 | ); 132 | await this.asyncSendReport(); 133 | } 134 | 135 | /** 136 | * 页面的加载方式 137 | * @param {*} type navigation.type 138 | */ 139 | pageLoadMethod(type) { 140 | switch (type) { 141 | case 0: 142 | return "点击链接、地址栏输入、表单提交、脚本操作等方式加载"; 143 | case 1: 144 | return "通过“重新加载”按钮或者location.reload()方法加载"; 145 | case 2: 146 | return "网页通过“前进”或“后退”按钮加载"; 147 | default: 148 | return "任何其他来源的加载"; 149 | } 150 | } 151 | 152 | asyncSendReport() { 153 | const { isSendBeacon = false, reportUrl = "" } = this.options; 154 | let repDataList = this.reqDataList; 155 | 156 | while (repDataList.length > 0) { 157 | const reqData = repDataList.shift(); 158 | (data => { 159 | setTimeout(() => { 160 | this.sendReport(data, reportUrl, isSendBeacon); 161 | }); 162 | })(reqData); 163 | } 164 | } 165 | 166 | /** 167 | * 高级浏览器还支持 navigator.sendBeacon方法。 168 | * 这个方法可以用来发送一些小量数据,该方法是异步的,且在浏览器关闭请求也照样能发,特别适合上报统计的场景。 169 | * 不支持时使用img的方式上报 170 | */ 171 | sendReport(performance, reportUrl, isSendBeacon = false) { 172 | if (isSendBeacon && navigator.sendBeacon) { 173 | this.sendBeacon(performance, reportUrl); 174 | return; 175 | } 176 | this.sendImage(performance, reportUrl); 177 | } 178 | 179 | sendBeacon(data, reportUrl) { 180 | navigator.sendBeacon(reportUrl, JSON.stringify(data)); 181 | } 182 | 183 | sendImage(data, reportUrl) { 184 | const image = new Image(); 185 | image.src = `${reportUrl}?${formatParams(data)}`; 186 | } 187 | } 188 | 189 | export default { 190 | install(Vue, options) { 191 | /* eslint-disable no-new */ 192 | new PerformanceMonitor(options); 193 | } 194 | }; 195 | --------------------------------------------------------------------------------