第六期:前端九條啟發分享
本期
這個系列是原本叫'九條bug', 但是一直感覺名字不是很貼切, 想了很久決定從這次起改成九條啟發(雖然名字還是很爛), 這次要分享的內容大部分是專案結構相關的, 讓我們一起看看吧。
一: soucemap反解析打包後的檔案
我們正式環境佈置的都是壓縮後的檔案, 如果正式環境報錯那麼我們要如何準確知道錯誤發生在程式碼的什麼位置? 也許某些情況下你需要對程式碼進行 soucemap反解析
, 這就需要mozila 官方 sourcemap 反解庫
, 讓我們新建一個檔案嘗試一下:
npx create-react-app my_inspire
我們故意做一個error出來, 比如在App.js
裡新增如下的程式碼:
function cc(str) {
str.replace("-", ",");
}
cc();
進入到 node_module/react-scripts/config/webpack.config.js
做如下替換:
// const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
const shouldUseSourceMap = true;
執行yarn build
(重點)我們把.map
檔案都單獨拿出來放在一個map資料夾裡, 如果這個檔案在build
資料夾內會自動幫我們做了soucemap的解析
功能。
我們可以看到報出的error
正式我們的'replace'錯誤, 點選下放的main
壓縮檔案。
記住這兩個引數line = 1;
和 column = 241;
。
現在我們要新建一個專案:
mikdir my_map
cd ./my_map
npm init -y
yarn add source-map
mikdir src
cd src
touch index.js
在index.js
裡面新增解析程式碼:
const sourceMap = require("source-map");
const fs = require("fs");
let data = fs
.readFileSync(process.cwd() + "/map/static/js/main.761f3095.chunk.js.map")
.toString();
const consumer = new sourceMap.SourceMapConsumer(data);
consumer.then((code) => {
const line = 1;
const column = 241;
const res = code.originalPositionFor({ line, column });
console.log(
code
.sourceContentFor(res.source)
.split("\n")
.slice(Math.max(res.line - 5, 0), res.line + 5)
.join("\n")
);
});
一起看看這個程式碼.slice(Math.max(res.line - 5, 0), res.line + 5)
, 這裡是同時列印出錯程式碼的上下5行程式碼。
用node命令執行這個檔案就可以得到如下結果:
二: sendBeacon 傳送資料
比如我們要上報使用者的關閉頁面操作, 直接使用axios
上報就會出現請求沒有發出去或被瀏覽器cancel掉, 比如下面這種方式會使解除安裝變慢:
window.addEventListener('unload', logData, false);
function logData() {
var client = new XMLHttpRequest();
client.open("POST", "/log", false);
client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
client.send(analyticsData);
}
看了當前使用的埋點和監控庫的程式碼, 發現他們會使用sendBeacon api
進行資料的上報, 它可以保證資料有效送達,且不會阻塞頁面的解除安裝或載入, 值得注意的是這個方法最好只傳遞少量資料不應設計的複雜, 用法如下:
window.addEventListener('unload', logData, false);
function logData() {
navigator.sendBeacon("你的上報地址", "上報的資料");
}
使用 sendBeacon() 方法會使使用者代理在有機會時非同步地向伺服器傳送資料,同時不會延遲頁面的解除安裝或影響下一導航的載入效能。這就解決了提交分析資料時的所有的問題:資料可靠,傳輸非同步並且不會影響下一頁面的載入。
三: 如何監測到頁面卡死無響應
該如何監控到使用者的頁面崩潰了? 因為js
屬於單執行緒執行, 如果頁面裡有一個死迴圈
, 那麼我們後面的程式碼都無法被執行, 當然錯誤的監控程式碼也就無法被執行?。
思路就是要跳出單執行緒
, Worker 執行緒
降下來了正義之光, Worker 執行緒是可以獨立於 JS 主執行緒執行的子執行緒,不受 JS 主執行緒
影響, 而我們可以通過Worker 執行緒
每3秒給JS 主執行緒
傳送一個訊息, 如果超過6秒沒有收到主執行緒的回覆, 那就可以認定是主執行緒卡死了。
下面是一個簡易版的監控:
index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var worker = new Worker('./worker.js');
worker.onmessage = function (event) {
console.log(`收到了子執行緒的問候: ${event.data}`)
worker.postMessage('我很好!');
}
</script>
</body>
</html>
worker.js
:
let come = 0;
const timer = setInterval(() => {
waiting = true;
come -= 1;
self.postMessage("主執行緒你還好麼?");
if (come < -2) {
navigator.sendBeacon("你的上報地址", "上報頁面崩潰");
}
}, 3000);
self.addEventListener(
"message",
function (e) {
waiting = false;
console.log(`收到了主執行緒的答覆: ${e.data}`);
come += 1;
},
false
);
四: 深層依賴無法更新
比如我們的主專案
依賴a包
&b包
, a包
也依賴b包
, 當a包
升級了需要依賴b包
最新的版本時, 直接執行yarn
命令可能會導致我們我們主專案
裡面的b包
更新了, 但是a包
的package.json
檔案雖然寫了"b":"*"
但是可能也無法更新到最新版的b
, 刪掉node_modules
資料夾也無用, 因為這個bug是yarn.lock
檔案導致的, 需要我們yarn remove a
然後重新yarn add a
就可以解決問題。
五: yarn why
yarn why
命令主要是用於顯示需要包的原因, 比如我的專案裡yarn add antd
, 但是我沒有安裝dayjs
庫, 這時我執行命令:
yarn why dayjs
"antd#rc-picker" depends on it
antd
裡面這個日期元件用到了它, 並且還會列出這個包的大小。
六: vscode工作空間設定
就是圖裡的這兩個設定項:
工作空間是指使用VS Code開啟的某個資料夾,在該資料夾下會建立一個名為.vscode的隱藏資料夾,裡面包含著僅適用於當前目錄的VS Code的設定,工作空間的設定會覆蓋使用者的設定, 我們來把字型調大。
變得好大:
我們改回 12
利用這個功能可以做格式化的統一
以及某個外掛針對特定專案的針對性配置。
七: react非同步引入元件 @loadable/component
假如我們引入檔案的地址是動態的, 他的引入路徑是/home/home1
或者是/home/home2
, 如下的寫法會報錯:
const Home = () => import(`./home/home1`);
function App() {
return (
<div className="App">
<p>app 頁面</p>
<Home></Home>
</div>
);
}
export default App;
藉助外掛實現動態修改引入路徑:
yarn add import @loadable/component
import "./App.css";
import loadable from "@loadable/component";
const n = 1;
const Home = loadable(() => import(`./home/home${n}`));
function App() {
return (
<div className="App">
<p>app 頁面</p>
<Home></Home>
</div>
);
}
export default App;
這樣至少不用引入兩個元件。
八: package.json
引入本地包作為依賴
一提到引入本地包第一時間想到的就是npm link
& npm unlink
這對兄弟命令, 但是當你要引入的本地包
變多的時候就會很麻煩, 比如釋出程式碼的時候還要npm unlink
, 所以現在好多專案都採用lerna
來做專案的包管理, 以後有機會詳細聊聊lerna
。
在這裡我想介紹另一種方式, 如下圖:
我們有個包名為@lulu/bao
, 新建bao
資料夾, 放在專案裡並且需要npm init
一下把資訊填完整:
包檔案index.js
內容為
export default () => {
console.log("我是本地的包");
};
在package.json
中新增配置, file:
指向本地
要注意, 當前需要npm install
一次才可以生效, 就可以如下的方式使用了。
import bao from "@lulu/bao";
bao();
九: ts在替換ui庫時的重要性
ts
某些時候真的太重要了, 最近我參與了一個老工程整體替換ui
元件庫的專案, 本以為都是樣式上的不同, 結果踩坑滿滿。
比如說舊版的日期元件
的onchange
事件第二個引數返回的是dayjs
物件格式的, 但是新版元件返回的是string
, 導致之後的所有處理方法全不對了, 這種錯誤ts就可以很明確的報出來。
屬性的更替也是同理, 比如新版的某個元件身上不接收style
屬性了, 那麼之前傳入style
屬性的地方都飄紅, 我就需要想別的辦法把一堆style
以其他方式作用在元件上。
還有屬性值的範圍, button
元件的size之前有4個屬性可選, 但是新版的只有兩個, 真是很讓人頭禿。
end
第九條說了替換ui元件的問題, 下一篇我會專門聊一聊我為一個大專案替換ui元件遇到的20幾種問題型別, 真的是非常開眼界, 這次就是這樣, 希望與你一起進步。