8

第六期:前端九条启发分享

本期

     这个系列是原本叫'九条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几种问题类型, 真的是非常开眼界, 这次就是这样, 希望与你一起进步。


lulu_up
5.7k 声望6.9k 粉丝

自信自律, 终身学习, 创业者