第六期:前端九條啟發分享

lulu_up發表於2021-10-13

第六期:前端九條啟發分享

本期

     這個系列是原本叫'九條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
image.png

     (重點)我們把.map檔案都單獨拿出來放在一個map資料夾裡, 如果這個檔案在build資料夾內會自動幫我們做了soucemap的解析功能。

image.png

我們可以看到報出的error正式我們的'replace'錯誤, 點選下放的main壓縮檔案。

image.png

記住這兩個引數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命令執行這個檔案就可以得到如下結果:

image.png

二: 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() 方法會使使用者代理在有機會時非同步地向伺服器傳送資料,同時不會延遲頁面的解除安裝或影響下一導航的載入效能。這就解決了提交分析資料時的所有的問題:資料可靠,傳輸非同步並且不會影響下一頁面的載入。

image.png

三: 如何監測到頁面卡死無響應

     該如何監控到使用者的頁面崩潰了? 因為js屬於單執行緒執行, 如果頁面裡有一個死迴圈, 那麼我們後面的程式碼都無法被執行, 當然錯誤的監控程式碼也就無法被執行?。

     思路就是要跳出單執行緒, Worker 執行緒降下來了正義之光, Worker 執行緒是可以獨立於 JS 主執行緒執行的子執行緒,不受 JS 主執行緒影響, 而我們可以通過Worker 執行緒每3秒給JS 主執行緒傳送一個訊息, 如果超過6秒沒有收到主執行緒的回覆, 那就可以認定是主執行緒卡死了。

     下面是一個簡易版的監控:
image.png

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裡面這個日期元件用到了它, 並且還會列出這個包的大小。

image.png

六: vscode工作空間設定

就是圖裡的這兩個設定項:

image.png

     工作空間是指使用VS Code開啟的某個資料夾,在該資料夾下會建立一個名為.vscode的隱藏資料夾,裡面包含著僅適用於當前目錄的VS Code的設定,工作空間的設定會覆蓋使用者的設定, 我們來把字型調大。

image.png

變得好大:

image.png

image.png

我們改回 12

image.png

利用這個功能可以做格式化的統一以及某個外掛針對特定專案的針對性配置。

七: 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一下把資訊填完整:

image.png

包檔案index.js內容為

export default () => {
  console.log("我是本地的包");
};

package.json中新增配置, file:指向本地

image.png

要注意, 當前需要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幾種問題型別, 真的是非常開眼界, 這次就是這樣, 希望與你一起進步。

相關文章