本文將分紅如下 7 個小節:javascript
部分小節提供了很是詳細的實戰教程,讓你們動手實踐。css
另外我還寫了一個前端工程化 demo 放在 github 上。這個 demo 包含了 js、css、git 驗證,其中 js、css 驗證須要安裝 VSCode,具體教程在下文中會有說起。html
對於前端來講,技術選型挺簡單的。就是作選擇題,三大框架中選一個。我的認爲能夠依據如下兩個特色來選:前端
第二點對於小公司來講,特別重要。原本小公司就很差招人,要是還選一個市場佔有率不高的框架(例如 Angular),簡歷你都看不到幾個...vue
UI 組件庫更簡單,github 上哪一個 star 多就用哪一個。star 多,說明用的人就多,不少坑別人都替你踩過了,省事。java
先來看看統一代碼規範的好處:node
當團隊的成員都嚴格按照代碼規範來寫代碼時,能夠保證每一個人的代碼看起來都像是一我的寫的,看別人的代碼就像是在看本身的代碼。更重要的是咱們可以認識到規範的重要性,並堅持規範的開發習慣。python
建議找一份好的代碼規範,在此基礎上結合團隊的需求做個性化修改。webpack
下面列舉一些 star 較多的 js 代碼規範:ios
css 代碼規範也有很多,例如:
使用 eslint 能夠檢查代碼符不符合團隊制訂的規範,下面來看一下如何配置 eslint 來檢查代碼。
// eslint-config-airbnb-base 使用 airbnb 代碼規範 npm i -D babel-eslint eslint eslint-config-airbnb-base eslint-plugin-import
package.json
的 scripts
加上這行代碼 "lint": "eslint --ext .js test/ src/ bin/"
。而後執行 npm run lint
便可開始驗證代碼。不過這樣檢查代碼效率過低,每次都得手動檢查。而且報錯了還得手動修改代碼。
爲了改善以上缺點,咱們可使用 VSCode。使用它並加上適當的配置能夠在每次保存代碼的時候,自動驗證代碼並進行格式化,省去了動手的麻煩。
css 檢查代碼規範則使用 stylelint
插件。
因爲篇幅有限,具體如何配置請看個人另外一篇文章ESlint + stylelint + VSCode自動格式化代碼(2020)。
git 規範包括兩點:分支管理規範、git commit 規範。
通常項目分主分支(master)和其餘分支。
當有團隊成員要開發新功能或改 BUG 時,就從 master 分支開一個新的分支。例如項目要從客戶端渲染改爲服務端渲染,就開一個分支叫 ssr,開發完了再合併回 master 分支。
若是改一個 BUG,也能夠從 master 分支開一個新分支,並用 BUG 號命名(不過咱們小團隊嫌麻煩,沒這樣作,除非有特別大的 BUG)。
<type>(<scope>): <subject> <BLANK LINE> <body> <BLANK LINE> <footer>
大體分爲三個部分(使用空行分割):
示例
若是修復的這個BUG隻影響當前修改的文件,可不加範圍。若是影響的範圍比較大,要加上範圍描述。
例如此次 BUG 修復影響到全局,能夠加個 global。若是影響的是某個目錄或某個功能,能夠加上該目錄的路徑,或者對應的功能名稱。
// 示例1 fix(global):修復checkbox不能複選的問題 // 示例2 下面圓括號裏的 common 爲通用管理的名稱 fix(common): 修復字體太小的BUG,將通用管理下全部頁面的默認字體大小修改成 14px // 示例3 fix: value.length -> values.length
feat: 添加網站主頁靜態頁面 這是一個示例,假設對點檢任務靜態頁面進行了一些描述。 這裏是備註,能夠是放BUG連接或者一些重要性的東西。
chore 的中文翻譯爲平常事務、例行工做,顧名思義,即不在其餘 commit 類型中的修改,均可以用 chore 表示。
chore: 將表格中的查看詳情改成詳情
其餘類型的 commit 和上面三個示例差很少,就不說了。
驗證 git commit 規範,主要經過 git 的 pre-commit
鉤子函數來進行。固然,你還須要下載一個輔助工具來幫助你進行驗證。
下載輔助工具
npm i -D husky
在 package.json
加上下面的代碼
"husky": { "hooks": { "pre-commit": "npm run lint", "commit-msg": "node script/verify-commit.js", "pre-push": "npm test" } }
而後在你項目根目錄下新建一個文件夾 script
,並在下面新建一個文件 verify-commit.js
,輸入如下代碼:
const msgPath = process.env.HUSKY_GIT_PARAMS const msg = require('fs') .readFileSync(msgPath, 'utf-8') .trim() const commitRE = /^(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|release|workflow)(\(.+\))?: .{1,50}/ if (!commitRE.test(msg)) { console.log() console.error(` 不合法的 commit 消息格式。 請查看 git commit 提交規範:https://github.com/woai3c/Front-end-articles/blob/master/git%20commit%20style.md `) process.exit(1) }
如今來解釋下各個鉤子的含義:
"pre-commit": "npm run lint"
,在 git commit
前執行 npm run lint
檢查代碼格式。"commit-msg": "node script/verify-commit.js"
,在 git commit
時執行腳本 verify-commit.js
驗證 commit 消息。若是不符合腳本中定義的格式,將會報錯。"pre-push": "npm test"
,在你執行 git push
將代碼推送到遠程倉庫前,執行 npm test
進行測試。若是測試失敗,將不會執行此次推送。主要是項目文件的組織方式和命名方式。
用咱們的 Vue 項目舉個例子。
├─public ├─src ├─test
一個項目包含 public(公共資源,不會被 webpack 處理)、src(源碼)、test(測試代碼),其中 src 目錄,又能夠細分。
├─api (接口) ├─assets (靜態資源) ├─components (公共組件) ├─styles (公共樣式) ├─router (路由) ├─store (vuex 全局數據) ├─utils (工具函數) └─views (頁面)
文件名稱若是過長則用 - 隔開。
UI 規範須要前端、UI、產品溝通,互相商量,最後制定下來,建議使用統一的 UI 組件庫。
制定 UI 規範的好處:
測試是前端工程化建設必不可少的一部分,它的做用就是找出 bug,越早發現 bug,所須要付出的成本就越低。而且,它更重要的做用是在未來,而不是當下。
設想一下半年後,你的項目要加一個新功能。在加完新功能後,你不肯定有沒有影響到原有的功能,須要測試一下。因爲時間過去過久,你對項目的代碼已經不瞭解了。在這種狀況下,若是沒有寫測試,你就得手動一遍一遍的去試。而若是寫了測試,你只須要跑一遍測試代碼就 OK 了,省時省力。
寫測試還可讓你修改代碼時沒有心理負擔,不用一直想着改這裏有沒有問題?會不會引發 BUG?而寫了測試就沒有這種擔憂了。
在前端用得最多的就是單元測試(主要是端到端測試我用得不多,不熟),這裏着重講解一下。
單元測試就是對一個函數、一個組件、一個類作的測試,它針對的粒度比較小。
它應該怎麼寫呢?
例如一個取絕對值的函數 abs()
,輸入 1,2
,結果應該與輸入相同;輸入 -1,-2
,結果應該與輸入相反。若是輸入非數字,例如 "abc"
,應該拋出一個類型錯誤。
假設有這樣一個類:
class Math { abs() { } sqrt() { } pow() { } ... }
單元測試,必須把這個類的全部方法都測一遍。
組件測試比較難,由於不少組件都涉及了 DOM 操做。
例如一個上傳圖片組件,它有一個將圖片轉成 base64 碼的方法,那要怎麼測試呢?通常測試都是跑在 node 環境下的,而 node 環境沒有 DOM 對象。
咱們先來回顧一下上傳圖片的過程:
<input type="file" />
,選擇圖片上傳。input
的 change
事件,獲取 file
對象。FileReader
將圖片轉換成 base64 碼。這個過程和下面的代碼是同樣的:
document.querySelector('input').onchange = function fileChangeHandler(e) { const file = e.target.files[0] const reader = new FileReader() reader.onload = (res) => { const fileResult = res.target.result console.log(fileResult) // 輸出 base64 碼 } reader.readAsDataURL(file) }
上面的代碼只是模擬,真實狀況下應該是這樣使用
document.querySelector('input').onchange = function fileChangeHandler(e) { const file = e.target.files[0] tobase64(file) } function tobase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = (res) => { const fileResult = res.target.result resolve(fileResult) // 輸出 base64 碼 } reader.readAsDataURL(file) }) }
能夠看到,上面代碼出現了 window 的事件對象 event
、FileReader
。也就是說,只要咱們可以提供這兩個對象,就能夠在任何環境下運行它。因此咱們能夠在測試環境下加上這兩個對象:
// 重寫 File window.File = function () {} // 重寫 FileReader window.FileReader = function () { this.readAsDataURL = function () { this.onload && this.onload({ target: { result: fileData, }, }) } }
而後測試能夠這樣寫:
// 提早寫好文件內容 const fileData = 'data:image/test' // 提供一個假的 file 對象給 tobase64() 函數 function test() { const file = new File() const event = { target: { files: [file] } } file.type = 'image/png' file.name = 'test.png' file.size = 1024 it('file content', (done) => { tobase64(file).then(base64 => { expect(base64).toEqual(fileData) // 'data:image/test' done() }) }) } // 執行測試 test()
經過這種 hack 的方式,咱們就實現了對涉及 DOM 操做的組件的測試。個人 vue-upload-imgs 庫就是經過這種方式寫的單元測試,有興趣能夠了解一下。
TDD 就是根據需求提早把測試代碼寫好,而後根據測試代碼實現功能。
TDD 的初衷是好的,但若是你的需求常常變(你懂的),那就不是一件好事了。頗有可能你每天都在改測試代碼,業務代碼反而沒怎麼動。
因此到如今爲止,三年多的程序員生涯,我還沒嘗試過 TDD 開發。
雖然環境如此艱難,但有條件的狀況下仍是應該試一下 TDD 的。例如在你本身負責一個項目又不忙的時候,能夠採用此方法編寫測試用例。
我經常使用的測試框架是 jest,好處是有中文文檔,API 清晰明瞭,一看就知道是幹什麼用的。
在沒有學會自動部署前,我是這樣部署項目的:
npm run test
。git push
。npm run build
。一次兩次還行,若是每天都這樣,就會把不少時間浪費在重複的操做上。因此咱們要學會自動部署,完全解放雙手。
自動部署(又叫持續部署 Continuous Deployment,英文縮寫 CD)通常有兩種觸發方式:
webhook
事件。輪詢,就是構建軟件每隔一段時間自動執行打包、部署操做。
這種方式不太好,頗有可能軟件剛部署完我就改代碼了。爲了看到新的頁面效果,不得不等到下一次構建開始。
另外還有一個反作用,假如我一天都沒更改代碼,構建軟件仍是會不停的執行打包、部署操做,白白的浪費資源。
因此如今的構建軟件基本採用監聽 webhook
事件的方式來進行部署。
webhook
事件webhook 鉤子函數,就是在你的構建軟件上進行設置,監聽某一個事件(通常是監聽 push
事件),當事件觸發時,自動執行定義好的腳本。
例如 Github Actions
,就有這個功能。
對於新人來講,僅看我這一段講解是不可能學會自動部署的。爲此我特意寫了一篇自動化部署教程,不須要你提早學習自動化部署的知識,只要照着指引作,就能實現前端項目自動化部署。
前端項目自動化部署——超詳細教程(Jenkins、Github Actions),教程已經奉上,各位大佬看完後要是以爲有用,不要忘了點贊,感激涕零。
監控,又分性能監控和錯誤監控,它的做用是預警和追蹤定位問題。
性能監控通常利用 window.performance
來進行數據採集。
Performance 接口能夠獲取到當前頁面中與性能相關的信息,它是 High Resolution Time API 的一部分,同時也融合了 Performance Timeline API、Navigation Timing API、 User Timing API 和 Resource Timing API。
這個 API 的屬性 timing
,包含了頁面加載各個階段的起始及結束時間。
爲了方便你們理解 timing
各個屬性的意義,我在知乎找到一位網友對於 timing
寫的簡介(忘了姓名,後來找不到了,見諒),在此轉載一下。
timing: { // 同一個瀏覽器上一個頁面卸載(unload)結束時的時間戳。若是沒有上一個頁面,這個值會和fetchStart相同。 navigationStart: 1543806782096, // 上一個頁面unload事件拋出時的時間戳。若是沒有上一個頁面,這個值會返回0。 unloadEventStart: 1543806782523, // 和 unloadEventStart 相對應,unload事件處理完成時的時間戳。若是沒有上一個頁面,這個值會返回0。 unloadEventEnd: 1543806782523, // 第一個HTTP重定向開始時的時間戳。若是沒有重定向,或者重定向中的一個不一樣源,這個值會返回0。 redirectStart: 0, // 最後一個HTTP重定向完成時(也就是說是HTTP響應的最後一個比特直接被收到的時間)的時間戳。 // 若是沒有重定向,或者重定向中的一個不一樣源,這個值會返回0. redirectEnd: 0, // 瀏覽器準備好使用HTTP請求來獲取(fetch)文檔的時間戳。這個時間點會在檢查任何應用緩存以前。 fetchStart: 1543806782096, // DNS 域名查詢開始的UNIX時間戳。 //若是使用了持續鏈接(persistent connection),或者這個信息存儲到了緩存或者本地資源上,這個值將和fetchStart一致。 domainLookupStart: 1543806782096, // DNS 域名查詢完成的時間. //若是使用了本地緩存(即無 DNS 查詢)或持久鏈接,則與 fetchStart 值相等 domainLookupEnd: 1543806782096, // HTTP(TCP) 域名查詢結束的時間戳。 //若是使用了持續鏈接(persistent connection),或者這個信息存儲到了緩存或者本地資源上,這個值將和 fetchStart一致。 connectStart: 1543806782099, // HTTP(TCP) 返回瀏覽器與服務器之間的鏈接創建時的時間戳。 // 若是創建的是持久鏈接,則返回值等同於fetchStart屬性的值。鏈接創建指的是全部握手和認證過程所有結束。 connectEnd: 1543806782227, // HTTPS 返回瀏覽器與服務器開始安全連接的握手時的時間戳。若是當前網頁不要求安全鏈接,則返回0。 secureConnectionStart: 1543806782162, // 返回瀏覽器向服務器發出HTTP請求時(或開始讀取本地緩存時)的時間戳。 requestStart: 1543806782241, // 返回瀏覽器從服務器收到(或從本地緩存讀取)第一個字節時的時間戳。 //若是傳輸層在開始請求以後失敗而且鏈接被重開,該屬性將會被數製成新的請求的相對應的發起時間。 responseStart: 1543806782516, // 返回瀏覽器從服務器收到(或從本地緩存讀取,或從本地資源讀取)最後一個字節時 //(若是在此以前HTTP鏈接已經關閉,則返回關閉時)的時間戳。 responseEnd: 1543806782537, // 當前網頁DOM結構開始解析時(即Document.readyState屬性變爲「loading」、相應的 readystatechange事件觸發時)的時間戳。 domLoading: 1543806782573, // 當前網頁DOM結構結束解析、開始加載內嵌資源時(即Document.readyState屬性變爲「interactive」、相應的readystatechange事件觸發時)的時間戳。 domInteractive: 1543806783203, // 當解析器發送DOMContentLoaded 事件,即全部須要被執行的腳本已經被解析時的時間戳。 domContentLoadedEventStart: 1543806783203, // 當全部須要當即執行的腳本已經被執行(不論執行順序)時的時間戳。 domContentLoadedEventEnd: 1543806783216, // 當前文檔解析完成,即Document.readyState 變爲 'complete'且相對應的readystatechange 被觸發時的時間戳 domComplete: 1543806783796, // load事件被髮送時的時間戳。若是這個事件還未被髮送,它的值將會是0。 loadEventStart: 1543806783796, // 當load事件結束,即加載事件完成時的時間戳。若是這個事件還未被髮送,或者還沒有完成,它的值將會是0. loadEventEnd: 1543806783802 }
經過以上數據,咱們能夠獲得幾個有用的時間
// 重定向耗時 redirect: timing.redirectEnd - timing.redirectStart, // DOM 渲染耗時 dom: timing.domComplete - timing.domLoading, // 頁面加載耗時 load: timing.loadEventEnd - timing.navigationStart, // 頁面卸載耗時 unload: timing.unloadEventEnd - timing.unloadEventStart, // 請求耗時 request: timing.responseEnd - timing.requestStart, // 獲取性能信息時當前時間 time: new Date().getTime(),
還有一個比較重要的時間就是白屏時間,它指從輸入網址,到頁面開始顯示內容的時間。
將如下腳本放在 </head>
前面就能獲取白屏時間。
<script> whiteScreen = new Date() - performance.timing.navigationStart </script>
經過這幾個時間,就能夠得知頁面首屏加載性能如何了。
另外,經過 window.performance.getEntriesByType('resource')
這個方法,咱們還能夠獲取相關資源(js、css、img...)的加載時間,它會返回頁面當前所加載的全部資源。
它通常包括如下幾個類型
咱們只需用到如下幾個信息
// 資源的名稱 name: item.name, // 資源加載耗時 duration: item.duration.toFixed(2), // 資源大小 size: item.transferSize, // 資源所用協議 protocol: item.nextHopProtocol,
如今,寫幾行代碼來收集這些數據。
// 收集性能信息 const getPerformance = () => { if (!window.performance) return const timing = window.performance.timing const performance = { // 重定向耗時 redirect: timing.redirectEnd - timing.redirectStart, // 白屏時間 whiteScreen: whiteScreen, // DOM 渲染耗時 dom: timing.domComplete - timing.domLoading, // 頁面加載耗時 load: timing.loadEventEnd - timing.navigationStart, // 頁面卸載耗時 unload: timing.unloadEventEnd - timing.unloadEventStart, // 請求耗時 request: timing.responseEnd - timing.requestStart, // 獲取性能信息時當前時間 time: new Date().getTime(), } return performance } // 獲取資源信息 const getResources = () => { if (!window.performance) return const data = window.performance.getEntriesByType('resource') const resource = { xmlhttprequest: [], css: [], other: [], script: [], img: [], link: [], fetch: [], // 獲取資源信息時當前時間 time: new Date().getTime(), } data.forEach(item => { const arry = resource[item.initiatorType] arry && arry.push({ // 資源的名稱 name: item.name, // 資源加載耗時 duration: item.duration.toFixed(2), // 資源大小 size: item.transferSize, // 資源所用協議 protocol: item.nextHopProtocol, }) }) return resource }
經過對性能及資源信息的解讀,咱們能夠判斷出頁面加載慢有如下幾個緣由:
除了用戶網速過慢,咱們沒辦法以外,其餘兩個緣由都是有辦法解決的,性能優化將在下一節《性能優化》中會講到。
如今能捕捉的錯誤有三種。
addEventListener('error', callback, true)
在捕獲階段捕捉資源加載失敗錯誤。window.onerror
捕捉 js 錯誤。addEventListener('unhandledrejection', callback)
捕捉 promise 錯誤,可是沒有發生錯誤的行數,列數等信息,只能手動拋出相關錯誤信息。咱們能夠建一個錯誤數組變量 errors
在錯誤發生時,將錯誤的相關信息添加到數組,而後在某個階段統一上報,具體如何操做請看代碼
// 捕獲資源加載失敗錯誤 js css img... addEventListener('error', e => { const target = e.target if (target != window) { monitor.errors.push({ type: target.localName, url: target.src || target.href, msg: (target.src || target.href) + ' is load error', // 錯誤發生的時間 time: new Date().getTime(), }) } }, true) // 監聽 js 錯誤 window.onerror = function(msg, url, row, col, error) { monitor.errors.push({ type: 'javascript', row: row, col: col, msg: error && error.stack? error.stack : msg, url: url, // 錯誤發生的時間 time: new Date().getTime(), }) } // 監聽 promise 錯誤 缺點是獲取不到行數數據 addEventListener('unhandledrejection', e => { monitor.errors.push({ type: 'promise', msg: (e.reason && e.reason.msg) || e.reason || '', // 錯誤發生的時間 time: new Date().getTime(), }) })
經過錯誤收集,能夠了解到網站錯誤發生的類型及數量,從而能夠作相應的調整,以減小錯誤發生。
完整代碼和 DEMO 請看我另外一篇文章前端性能和錯誤監控的末尾,你們能夠複製代碼(HTML文件)在本地測試一下。
性能數據能夠在頁面加載完以後上報,儘可能不要對頁面性能形成影響。
window.onload = () => { // 在瀏覽器空閒時間獲取性能及資源信息 // https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback if (window.requestIdleCallback) { window.requestIdleCallback(() => { monitor.performance = getPerformance() monitor.resources = getResources() }) } else { setTimeout(() => { monitor.performance = getPerformance() monitor.resources = getResources() }, 0) } }
固然,你也能夠設一個定時器,循環上報。不過每次上報最好作一下對比去重再上報,避免一樣的數據重複上報。
我在DEMO裏提供的代碼,是用一個 errors
數組收集全部的錯誤,再在某一階段統一上報(延時上報)。
其實,也能夠改爲在錯誤發生時上報(即時上報)。這樣能夠避免在收集完錯誤延時上報還沒觸發,用戶卻已經關掉網頁致使錯誤數據丟失的問題。
// 監聽 js 錯誤 window.onerror = function(msg, url, row, col, error) { const data = { type: 'javascript', row: row, col: col, msg: error && error.stack? error.stack : msg, url: url, // 錯誤發生的時間 time: new Date().getTime(), } // 即時上報 axios.post({ url: 'xxx', data, }) }
window.performance
API 是有缺點的,在 SPA 切換路由時,window.performance.timing
的數據不會更新。
因此咱們須要另想辦法來統計切換路由到加載完成的時間。
拿 Vue 舉例,一個可行的辦法就是切換路由時,在路由的全局前置守衛 beforeEach
裏獲取開始時間,在組件的 mounted
鉤子裏執行 vm.$nextTick
函數來獲取組件的渲染完畢時間。
router.beforeEach((to, from, next) => { store.commit('setPageLoadedStartTime', new Date()) })
mounted() { this.$nextTick(() => { this.$store.commit('setPageLoadedTime', new Date() - this.$store.state.pageLoadedStartTime) }) }
除了性能和錯誤監控,其實咱們還能夠作得更多。
使用 window.navigator
能夠收集到用戶的設備信息,操做系統,瀏覽器信息...
是指經過互聯網訪問、瀏覽這個網頁的天然人。訪問您網站的一臺電腦客戶端爲一個訪客。00:00-24:00內相同的客戶端只被計算一次。一天內同個訪客屢次訪問僅計算一個UV。
在用戶訪問網站時,能夠生成一個隨機字符串+時間日期,保存在本地。在網頁發生請求時(若是超過當天24小時,則從新生成),把這些參數傳到後端,後端利用這些信息生成 UV 統計報告。
即頁面瀏覽量或點擊量,用戶每1次對網站中的每一個網頁訪問均被記錄1個PV。用戶對同一頁面的屢次訪問,訪問量累計,用以衡量網站用戶訪問的網頁數量。
傳統網站
用戶在進入 A 頁面時,經過後臺請求把用戶進入頁面的時間捎上。過了 10 分鐘,用戶進入 B 頁面,這時後臺能夠經過接口捎帶的參數能夠判斷出用戶在 A 頁面停留了 10 分鐘。
SPA
能夠利用 router 來獲取用戶停留時間,拿 Vue 舉例,經過 router.beforeEach
destroyed
這兩個鉤子函數來獲取用戶停留該路由組件的時間。
經過 document.documentElement.scrollTop
屬性以及屏幕高度,能夠判斷用戶是否瀏覽完網站內容。
經過 document.referrer
屬性,能夠知道用戶是從哪一個網站跳轉而來。
經過分析用戶數據,咱們能夠了解到用戶的瀏覽習慣、愛好等等信息,想一想真是恐怖,毫無隱私可言。
前面說的都是監控原理,但要實現仍是得本身動手寫代碼。爲了不麻煩,咱們能夠用現有的工具 sentry 去作這件事。
sentry 是一個用 python 寫的性能和錯誤監控工具,你可使用 sentry 提供的服務(免費功能少),也能夠本身部署服務。如今來看一下如何使用 sentry 提供的服務實現監控。
打開 https://sentry.io/signup/
網站,進行註冊。
選擇項目,我選的 Vue。
選完項目,下面會有具體的 sentry 依賴安裝指南。
根據提示,在你的 Vue 項目執行這段代碼 npm install --save @sentry/browser @sentry/integrations @sentry/tracing
,安裝 sentry 所需的依賴。
再將下面的代碼拷到你的 main.js
,放在 new Vue()
以前。
import * as Sentry from "@sentry/browser"; import { Vue as VueIntegration } from "@sentry/integrations"; import { Integrations } from "@sentry/tracing"; Sentry.init({ dsn: "xxxxx", // 這裏是你的 dsn 地址,註冊完就有 integrations: [ new VueIntegration({ Vue, tracing: true, }), new Integrations.BrowserTracing(), ], // We recommend adjusting this value in production, or using tracesSampler // for finer control tracesSampleRate: 1.0, });
而後點擊第一步中的 skip this onboarding
,進入控制檯頁面。
若是忘了本身的 DSN,請點擊左邊的菜單欄選擇 Settings
-> Projects
-> 點擊本身的項目 -> Client Keys(DSN)
。
在你的 Vue 項目執行一個打印語句 console.log(b)
。
這時點開 sentry 主頁的 issues 一項,能夠發現有一個報錯信息 b is not defined
:
這個報錯信息包含了錯誤的具體信息,還有你的 IP、瀏覽器信息等等。
但奇怪的是,咱們的瀏覽器控制檯並無輸出報錯信息。
這是由於被 sentry 屏蔽了,因此咱們須要加上一個選項 logErrors: true
。
而後再查看頁面,發現控制檯也有報錯信息了:
通常打包後的代碼都是通過壓縮的,若是沒有 sourcemap,即便有報錯信息,你也很難根據提示找到對應的源碼在哪。
下面來看一下如何上傳 sourcemap。
首先建立 auth token。
這個生成的 token 一會要用到。
安裝 sentry-cli
和 @sentry/webpack-plugin
:
npm install sentry-cli-binary -g npm install --save-dev @sentry/webpack-plugin
安裝完上面兩個插件後,在項目根目錄建立一個 .sentryclirc
文件(不要忘了在 .gitignore
把這個文件添加上,以避免暴露 token),內容以下:
[auth] token=xxx [defaults] url=https://sentry.io/ org=woai3c project=woai3c
把 xxx 替換成剛纔生成的 token。
org
是你的組織名稱。
project
是你的項目名稱,根據下面的提示能夠找到。
在項目下新建 vue.config.js
文件,把下面的內容填進去:
const SentryWebpackPlugin = require('@sentry/webpack-plugin') const config = { configureWebpack: { plugins: [ new SentryWebpackPlugin({ include: './dist', // 打包後的目錄 ignore: ['node_modules', 'vue.config.js', 'babel.config.js'], }), ], }, } // 只在生產環境下上傳 sourcemap module.exports = process.env.NODE_ENV == 'production'? config : {}
填完之後,執行 npm run build
,就能夠看到 sourcemap
的上傳結果了。
咱們再來看一下沒上傳 sourcemap 和上傳以後的報錯信息對比。
未上傳 sourcemap
已上傳 sourcemap
能夠看到,上傳 sourcemap 後的報錯信息更加準確。
選完刷新便可。
打開 performance 選項,就能看到你每一個項目的運行狀況。具體的參數解釋請看文檔 Performance Monitoring。
性能優化主要分爲兩類:
例如壓縮文件、使用 CDN 就屬於加載時優化;減小 DOM 操做,使用事件委託屬於運行時優化。
在解決問題以前,必須先找出問題,不然無從下手。因此在作性能優化以前,最好先調查一下網站的加載性能和運行性能。
一個網站加載性能如何主要看白屏時間和首屏時間。
將如下腳本放在 </head>
前面就能獲取白屏時間。
<script> new Date() - performance.timing.navigationStart </script>
首屏時間比較複雜,得考慮有圖片和沒有圖片的狀況。
若是沒有圖片,則在 window.onload
事件裏執行 new Date() - performance.timing.navigationStart
便可獲取首屏時間。
若是有圖片,則要在最後一個在首屏渲染的圖片的 onload
事件裏執行 new Date() - performance.timing.navigationStart
獲取首屏時間,實施起來比較複雜,在這裏限於篇幅就不說了。
配合 chrome 的開發者工具,咱們能夠查看網站在運行時的性能。
打開網站,按 F12 選擇 performance,點擊左上角的灰色圓點,變成紅色就表明開始記錄了。這時能夠模仿用戶使用網站,在使用完畢後,點擊 stop,而後你就能看到網站運行期間的性能報告。若是有紅色的塊,表明有掉幀的狀況;若是是綠色,則表明 FPS 很好。
另外,在 performance 標籤下,按 ESC 會彈出來一個小框。點擊小框左邊的三個點,把 rendering 勾出來。
這兩個選項,第一個是高亮重繪區域,另外一個是顯示幀渲染信息。把這兩個選項勾上,而後瀏覽網頁,能夠實時的看到你網頁渲染變化。
能夠部署一個前端監控系統來監控網站性能,上一節中講到的 sentry 就屬於這一類。
若是你安裝了 Chrome 52+ 版本,請按 F12 打開開發者工具。
它不只會對你網站的性能打分,還會對 SEO 打分。
網上關於性能優化的文章和書籍多不勝數,但有不少優化規則已通過時了。因此我寫了一篇性能優化文章前端性能優化 24 條建議(2020),分析總結出了 24 條性能優化建議,強烈推薦。
《重構2》一書中對重構進行了定義:
所謂重構(refactoring)是這樣一個過程:在不改變代碼外在行爲的前提下,對代碼作出修改,以改進程序的內部結構。重構是一種經千錘百煉造成的有條不紊的程序整理方法,能夠最大限度地減少整理過程當中引入錯誤的機率。本質上說,重構就是在代碼寫好以後改進它的設計。
重構和性能優化有相同點,也有不一樣點。
相同的地方是它們都在不改變程序功能的狀況下修改代碼;不一樣的地方是重構爲了讓代碼變得更加易讀、理解,性能優化則是爲了讓程序運行得更快。
重構能夠一邊寫代碼一邊重構,也能夠在程序寫完後,拿出一段時間專門去作重構。沒有說哪一個方式更好,視我的狀況而定。
若是你專門拿一段時間來作重構,建議你在重構一段代碼後,當即進行測試。這樣能夠避免修改代碼太多,在出錯時找不到錯誤點。
在《重構2》這本書中,介紹了多達上百個重構手法。但我以爲有兩個是比較經常使用的:
假設有一個查詢數據的接口 /getUserData?age=17&city=beijing
。如今須要作的是把用戶數據:{ age: 17, city: 'beijing' }
轉成 URL 參數的形式:
let result = '' const keys = Object.keys(data) // { age: 17, city: 'beijing' } keys.forEach(key => { result += '&' + key + '=' + data[key] }) result.substr(1) // age=17&city=beijing
若是隻有這一個接口須要轉換,不封裝成函數是沒問題的。但若是有多個接口都有這種需求,那就得把它封裝成函數了:
function JSON2Params(data) { let result = '' const keys = Object.keys(data) keys.forEach(key => { result += '&' + key + '=' + data[key] }) return result.substr(1) }
假設如今有一個註冊功能,用僞代碼表示:
function register(data) { // 1. 驗證用戶數據是否合法 /** * 驗證帳號 * 驗證密碼 * 驗證短信驗證碼 * 驗證身份證 * 驗證郵箱 */ // 2. 若是用戶上傳了頭像,則將用戶頭像轉成 base64 碼保存 /** * 新建 FileReader 對象 * 將圖片轉換成 base64 碼 */ // 3. 調用註冊接口 // ... }
這個函數包含了三個功能,驗證、轉換、註冊。其中驗證和轉換功能是能夠提取出來單獨封裝成函數的:
function register(data) { // 1. 驗證用戶數據是否合法 // verify() // 2. 若是用戶上傳了頭像,則將用戶頭像轉成 base64 碼保存 // tobase64() // 3. 調用註冊接口 // ... }
若是你對重構有興趣,強烈推薦你閱讀《重構2》這本書。
參考資料:
寫這篇文章主要是爲了對我這一年多工做經驗做總結,由於我基本上都在研究前端工程化以及如何提高團隊的開發效率。但願這篇文章能幫助一些對前端工程化沒有經驗的新手,經過這篇文章入門前端工程化。
若是這篇文章對你有幫助,請點一下贊,感激涕零。
本人具備三年+前端工做經驗,32歲,高中學歷,現尋求天津、北京地區的前端工做機會。
下面是我掌握的一些技能:
若是您以爲個人條件還能夠,能夠私信我或在評論區留言,謝謝。