Skip to main content

· 6 min read
加菲猫

📒 腾讯技术工程相关文章

⭐️ 收藏 | 腾讯技术 2020 年精华文章合集

🐛 生产环境如何 debug

  • 定位错误是前端还是后端接口返回的
    • 前端代码全局搜索关键字(vscode 或者 chorme devtools 中搜索)
    • 翻阅 network 面板中的请求,使用 ⌘ + F 打开 network search 面板进行搜索
  • 如何调试混淆压缩后的 JS
    • 使用 source 面板中的 pretty-print 选项
    • 这样还是存在问题,例如很多变量名、方法名都被混淆压缩了,然后 babel 会将 ES2015+ 语法进行语法转换,代码可读性降低
  • 如何在生产环境中使用 sourceMap 调试
    • 打开混淆压缩的代码,右键选择 Add source map
    • 这里需要添加一个 source map URL,可以将本地项目添加到 source 面板中的 Filesystem 中,或者启用静态资源服务
    • 添加之后就可以直接搜索项目中的源文件了
  • 如何在 chrome 中修改代码并调试
    • chrome devtools 提供了 local overrides 能力,指定修改后的文件的本地保存目录,当修改完代码保存的时候,就会将修改后的文件保存到你指定的目录目录下,当再次加载页面的时候,对应的文件不再读取网络上的文件,而是读取存储在本地修改过的文件
    • 打开 sources 下的 overrides 面板,点击 select folder overrides 选择修改后的文件件存储地址,我们就可以打开文件修改,修改完成后保存,重新刷新页面后,修改后的代码就被执行到了

前端工程师生产环境 debugger 技巧

📒 如何在 React 中优雅使用 ECharts

初始化 ECharts 的时候不要使用 id,否则无法渲染多个组件实例:

import * as React from "react";
import * as echarts from "echarts";

const LineChart = (props) => {
const chartRef = React.useRef();

React.useEffect(() => {
const chart = echarts.init(chartRef.current);
const option = {
// ...
}
chart.setOptions(option);
}, [props])

return <div ref={chartRef} className="chart"></div>
}

export default React.memo(LineChart);

如何让 ECharts 实现自适应,可以在窗口尺寸变化的时候,调用 chart 实例上的 resize 方法:

React.useEffect(() => {
const chart = echarts.init(chartRef.current);
const option = {
// ...
}
chart.setOptions(option);

const handleResize = () => {
chart.resize();
}

// 绑定 resize 事件监听器
window.addEventListener("resize", handleResize);

return () => {
// 组件更新或者卸载时移除监听
window.removeEventListener("resize", handleResize);
}
}, [props])

推荐使用 addEventListener 绑定事件,可以多次绑定,但是要注意及时 remove,不然会导致内存泄漏

📒 看火焰图分析调用栈的时候,看到一个 asyncGeneratorStep 的函数,一直没搞清楚这个在哪里用到了

image

事后才想到这是 babel 语法转换引入的 helper 函数

image

📒 使用 webpack-chain 对 vue-cli 默认配置进行修改

https://github.com/Yatoo2018/webpack-chain/tree/zh-cmn-Hans

📒 如何对 webpack 打包产物进行分析

经常需要分析打包产物的体积,看哪个包体积过大,做针对性优化。可以使用 Webpack Bundle Analyzer:

$ yarn add webpack-bundle-analyzer -D

webpack.config.js 中添加如下配置:

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}

然后执行打包构建命令:

$ yarn build --report

https://github.com/webpack-contrib/webpack-bundle-analyzer

📒 create-react-app 发布 5.0 版本

本次的 5.0 版本优化了快速刷新(Fast Refresh),支持了 Tailwind,并更新了不少内部依赖库,如 Webpack 5、Jest 27 和 EsLint 8 等。

https://github.com/facebook/create-react-app/releases/tag/v5.0.0

📒 处理你应用中的内存泄漏

作者 Stoyan 提到:“任何大小合理的应用中,都会存在一定程度的内存泄漏”。因此知道如何处理泄漏是一件很有用的事。在本文中,作者举了一个 React 中的例子,不过它的基本理念却可以运用在任何地方。

https://calendar.perfplanet.com/2021/plugging-memory-leaks-in-your-app/

📒 kalidokit:人体动作表情解读同步

效果还是和牛逼的,真人测试。

https://github.com/yeemachine/kalidokit

📒 xterm:把命令行搬到浏览器

https://xtermjs.org/

📒 microdiff:轻量快速的对比库

https://github.com/AsyncBanana/microdiff

· 4 min read
加菲猫

⭐️ Webpack 分包最佳实践

SplitChunksPlugin 进行分包的三要素:

  1. minChunks: 一个模块是否最少被 minChunks 个 chunk 所引用
  2. maxInitialRequests/maxAsyncRequests: 最多只能有 maxInitialRequests/maxAsyncRequests 个 chunk 需要同时加载 (如一个 Chunk 依赖 VendorChunk 才可正常工作,此时同时加载 chunk 数为 2)
  3. minSize/maxSize: chunk 的体积必须介于 (minSize, maxSize) 之间

哪些应该单独分包:

  1. Webpack 运行时
  2. React Framework 运行时,包括 React/React-DOM 及它们所有的依赖
  3. 大型库,体积特别大的库
  4. 公共库,至少被 4 个 Chunk 所引用的公共模块
tip

Webpack 配置最佳实践,除了 Vue-cli 和 CRA 源码,还可以参考 next.js 源码:

https://github.com/vercel/next.js/blob/canary/packages/next/build/webpack-config.ts

Webpack 性能系列四:分包优化

📒 【内部分享】看向未来 - 近期 TC39 提案汇总

📒 如何移除代码中的 console

  1. 使用 uglifyjs-webpack-pluginterser-webpack-plugin 中的 drop_console 配置;

  2. 使用 Babel 插件 babel-plugin-transform-remove-console

  3. 简单粗暴删除,直接重写 console.log 方法;

    console.log = function() {};
  4. 手写 webpack loader 删除;

    // clearConsole.js
    const reg = /(console.log\()(.*)(\))/g;
    module.exports = function(source) {
    source = source.replace(reg, "")
    return source;
    }

    基于正则匹配还是有一些问题,例如 const { log } = console 或者 const log = console.log.bind(console) 这种就匹配不到

📒 WebRTC 录屏技术

WebRTC 是一套基于音视轨的实时数据流传播的技术方案。通过浏览器原生 API navigator.mediaDevices.getDisplayMedia 方法实现提示用户选择和授权捕获展示的窗口,进而获取 stream (录制的屏幕音视流)。我们可以对 stream 进行转化处理,转成相对应的媒体数据,并将其数据存储。

var promise = navigator.mediaDevices.getDisplayMedia(constraints);

https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia

📒 Go 1.18 Beta 1 已经正支持泛型

📒 使用Golang、Gin和React、esbuild开发的Blog

📒 使用 patch-package 修复 npm 库的紧急问题

如果使用的 npm 库有 bug,可以使用 patch-package 创建一个补丁包。

工程化知识卡片 022: 质量保障篇之如何优雅某个 npm 库的紧急问题

📒 5 种有趣的 useEffect 无限循环类型

📒 用 CSS 来代替 JS 的实现

📒 React18 新特性:transition

📒 「2021」我给Vue.js生态贡献代码的这一年

⭐️ ⭐️ fiber:受到 Express 启发的 Web 应用框架,使用 Go 开发,与 Express 的 API 非常接近

package main

import "github.com/gofiber/fiber/v2"

func main() {
app := fiber.New()

app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World 👋!")
})

app.Listen(":3000")
}

https://github.com/gofiber/fiber

· 6 min read
加菲猫

📒 优雅获取 package.json 文件

这里需要注意一个问题,在 ES module 规范下无法直接引入 JSON 文件,只能通过 require 获取:

// ES module 模块
import React from "react";
import { debounce } from "lodash-es";
// JSON 模块只能通过 CJS 方式加载
const package = require("package.json");

import 命令目前只能用于加载 ES 模块,现在有一个提案,允许加载 JSON 模块。import 命令能够直接加载 JSON 模块以后,就可以像下面这样写:

import configData from './config.json' assert { type: "json" };
console.log(configData.appName);

import 命令导入 JSON 模块时,命令结尾的 assert {type: "json"} 不可缺少。这叫做导入断言,用来告诉 JavaScript 引擎,现在加载的是 JSON 模块

除此之外,还可以使用第三方库 read-pkg 获取,原理是通过 fs 模块读取 package.json 文件,然后反序列化为 JS 对象:

import process from 'node:process';
import fs, {promises as fsPromises} from 'node:fs';
import path from 'node:path';
import parseJson from 'parse-json';
import normalizePackageData from 'normalize-package-data';

export async function readPackage({cwd = process.cwd(), normalize = true} = {}) {
const filePath = path.resolve(cwd, 'package.json');
const json = parseJson(await fsPromises.readFile(filePath, 'utf8'));

if (normalize) {
normalizePackageData(json);
}

return json;
}

📒 如何覆盖某些元素的浏览器默认样式

很多元素,例如 <button><input type="text" /><input type="checkbox" /> 具有浏览器默认样式,有时候需要自己指定样式,如何覆盖浏览器默认样式,只需要下面一行代码:

input {
-webkit-appearance: none;
}

📒 如何让 div 按比例缩放

有时希望 div 自适应页面宽度的时候,可以按比例缩放,这种情况下可以使用 aspect-ratio 属性:

div {
aspect-ratio: auto 1 / 1;
}

推荐在 ratio 前面加 auto ,对于 input 等具有固有宽高比的替换元素将使用默认宽高比,否则就使用指定的宽高比。

注意 aspect-ratio 兼容 Chrome > 87 ,所有的 IE 浏览器都不兼容

aspect-ratio - MDN

📒 如何维护一个大型的 Next.js 应用

📒 Goober:CSS-in-JS 方案

这是一个 1 KB 大小的 CSS-in-JS 方案,可以替代 23 KB 的 styled-components 和 emotion 组合。而且如果你可以减少 goober 库 gzip 后的体积,他们还会奖励你美元。

https://github.com/cristianbote/goober

📒 使用支持 Tree Shaking 的包

如果可以的话,应尽量使用支持 Tree Shaking 的 npm 包,例如:

  • 使用 lodash-es 替代 lodash ,或者使用 babel-plugin-lodash 实现类似效果

📒 win10 安装 nvm-windows

下载地址:

https://github.com/coreybutler/nvm-windows/releases

用法跟 mac 上的 nvm 类似:

# 安装 nodejs v16.13.1
$ nvm install 16.13.1

# 查看已安装的 nodejs 版本
$ nvm list

# 使用指定版本的 nodejs
$ nvm use 16.13.1

# 卸载某个版本的 nodejs
$ nvm uninstall 16.13.1

个人猜测是通过修改环境变量实现 node 版本切换

在使用的时候遇到两个问题:

  1. 安装老版本 nodejs 的时候,node 安装成功,但是 npm 安装失败;
  2. 使用 nvm use 切换 node 版本的时候报错;

第一个问题,给 nvm 配置淘宝镜像即可解决。找到 nvm 安装目录下的 settings.txt 文件,添加配置:

node_mirror: https://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/

第二个问题,首先检查 nvm 安装路径没有中文、没有空格,然后如果问题还在,那就是权限问题,使用管理员权限打开 cmd 即可正常切换。

📒 获取 worker 线程最大并发数

通过下面的只读属性获取用户计算机的逻辑内核数:

logicalProcessors = window.navigator.hardwareConcurrency

下面的代码中,为每一个逻辑内核创建一个 worker 线程,充分利用 CPU 资源:

let workerList = [];

for (let i = 0; i < window.navigator.hardwareConcurrency; i++) {
let newWorker = {
worker: new Worker('cpuworker.js'),
inUse: false
};
workerList.push(newWorker);
}

Navigator.hardwareConcurrency - MDN

📒 TypeScript类型中的逆变协变

📒 如何优雅引入 node 内置模块

在引用 node 内置模块的时候可以加 node: 前缀,比如:

import util from 'node:util';
import { promisify } from 'node:util';
import { spawn, exec } from 'node:child_process';

通过增加前缀,可以将 node 内置模块与其他第三方模块区分开:

import process from 'node:process';
import fs, {promises as fsPromises} from 'node:fs';
import path from 'node:path';
import parseJson from 'parse-json';
import normalizePackageData from 'normalize-package-data';

看到这边有同学会问,为啥 node 中可以使用 ES module,实际上现在 node 已经支持了 ES module ,只需要在 package.json 中设置如下字段即可:

{
name: "xxx",
version: "1.0.0"
type: "module", // 默认 commonjs
}

从 vue-cli 源码中,我发现27行读取 json 文件有趣的 npm 包

📒 gradient-string: 在终端中输出漂亮的渐变色

image

https://github.com/bokub/gradient-string

📒 手写系列-实现一个铂金段位的 React

· 2 min read
加菲猫

📒 如何切换后端环境

在前端项目中,后端接口请求通常都通过 devServer 代理,这样解决了跨域问题。但是开发的时候经常需要切换后端环境,每次切换都要重启 devServer,这就导致每次都要重新构建,比较麻烦。

一种解决思路是,前端项目中后端接口地址使用 域名,在 系统 host 文件 中配置域名与 IP 地址的映射,这样每次切换后端环境,只需要修改系统 host 文件,然后在浏览器中刷新页面就可以实现切换。

但是每次手动修改 host 文件有点麻烦,可以使用 SwitchHost 工具,一键切换,非常方便。

📒 Vue 3 技术栈

  • Vue3.2:核心库
  • Vite2.6:官方推出的基于 ESM 的构建工具
  • vue-router-next:Vue3 官方路由
  • pinia:官方推出的状态管理库
  • TypeScript:静态类型检查
  • Volar:Vue3 的 vscode 插件

📒 Chrome 调试小技巧

在断点位置按 F9,可以一步一步往下执行,调试源码的时候查看调用栈特别有用。当然很多时候调用栈比较复杂,这时候通过 Performance 面板的火焰图看比较直观,火焰图的宽度代表执行耗时,火焰图的高度代表调用栈的深度。

📒 CommonJS 中的模块导出

CommonJS 规范中只有一种模块导出:module.exports ,而 exports 仅仅只是 module.exports 的引用而已

📒 推荐两个网站

  • caniuse:查询 api 兼容性
  • codeif:变量命名

· 4 min read
加菲猫

📒 垂直居中小技巧

相对父元素绝对定位,先向下偏移父元素高度的 50% ,在向上移动自身高度的 50%

.icon-common {
position: absolute;
top: 50%;
transform: translateY(-50%);
}

📒 Git 几点小知识技巧

📒 工程化知识卡片 014: 发包篇之 package.json 中 main、export、module 的区别何在

📒 第三方库如何解决潜在的间接依赖不可控问题

lockfile 对于第三方库仍然必不可少,像 reactnext.jswebpack 等均有 yarn.lock 。第三方库的 devDependencies 必须锁定,这样 Contributor 可根据 lockfile 很容易将项目跑起来。第三方库的 dependencies 有可能存在不可控问题,可参考 next.js 的解决方案:

  1. 将依赖版本在 package.json 中锁死

    {
    "dependencies": {
    "@babel/runtime": "7.15.4",
    "@hapi/accept": "5.0.2",
    "@napi-rs/triples": "1.0.3"
    }
    }
  2. 将部分依赖直接编译后直接引入,而非通过依赖的方式;

📒 语义化版本

semverSemantic Versioning 语义化版本的缩写,文档可见 semver.org/,它由 [major, minor, patch] 三部分组成,其中

  • major: 当你发了一个含有 Breaking Change 的 API
  • minor: 当你新增了一个向后兼容的功能时
  • patch: 当你修复了一个向后兼容的 Bug 时

对于 ~1.2.3 而言,它的版本号范围是 >=1.2.3 <1.3.0 对于 ^1.2.3 而言,它的版本号范围是 >=1.2.3 <2.0.0

当我们 npm i 时,默认的版本号是 ^,可最大限度地在向后兼容与新特性之间做取舍,但是有些库有可能不遵循该规则,我们在项目时应当使用 yarn.lock/package-lock.json 锁定版本号。

📒 Vue 3.2 响应式优化相关

Object.defineProperty 处理深度嵌套对象需要进行递归,而 Proxy 同样也只能监听当前层级的对象,如果深度嵌套也需要递归的,所以才有了 reactiveshallowReactive

之前看了黄轶老师写的 Vue3.2 响应式优化,Proxy 实际上比 Object.defineProperty 要慢,Vue 2.x 是直接在初始化阶段就进行深度递归,而 Vue3 的响应式优化就体现在只有访问对象属性的时候,再递归响应式,也就是延迟处理子对象,所以在初始化阶段性能较好。

📒 手把手实现一个 babel 插件

这篇做的 demo 是一个简单的 babel-plugin-import 深入Babel,这一篇就够了

这篇做的 demo 类似 Java 中的 lambok 保姆级教学!这次一定学会babel插件开发!

📒 从 16 个方向逐步搭建基于 vue3 的前端架构

📒 VuePress + Travis CI + Github Pages 全自动上线文档

· 5 min read
加菲猫

📒 构建提效

  • 打包工具:webpack -> rollup
  • 生成 AST :babel -> acorn
  • 语法转换:babel -> swc
  • 代码压缩:terser -> esbuild

📒 代码规范相关

  • ESLint 用于代码规范检查,在开发阶段提前规避问题,提升代码健壮性
  • Prettier 用于代码风格校验,统一团队编码风格

实际上编写文档、单元测试、类型声明、tsconfig.json 、ESLint 、Prettier 、lint-staged 、husky 这些对实现功能上来说没有任何作用,但是可以保障代码交付质量

📒 生成自增 ID :

看到一个通过 generator 函数生成自增 ID 的方法:

function *customIdGenerator() {
let i = 0;
while (true) {
yield i++;
}
}

看了下其实还可以通过闭包实现:

function customIdGenerator() {
let i = 0;
return {
next() {
return i++;
}
}
}

const IDGenerator = customIdGenerator();
IDGenerator.next(); // 0
IDGenerator.next(); // 1
IDGenerator.next(); // 2
IDGenerator.next(); // 3
IDGenerator.next(); // 4
IDGenerator.next(); // 5
tip

注意:

++i 是先加再作为表达式的值去赋值,相当于 (i = i + 1)

i++ 是先把原来的值作为表达式的值赋值再加

📒 专业名词

缓存命中 规则命中 增量构建

📒 Promise 四种常用工具方法

  • Promise.all() :接收一个 Promise 数组,如果所有 Promisefulfilled 则返回结果数组,只要有一个 Promise 变为 rejected ,则返回最先 rejected 的 Promise ,通常用于并发请求
  • Promise.race() :接收一个 Promise 数组,race 意思就是比谁快,返回状态最先改变的 Promise ,不管成功还是失败,通常用于请求超时处理
  • Promise.any() :接收一个 Promise 数组,返回最先 fulfilledPromise ,如果没有 Promise 状态转为 fulfilled ,则抛出一个 AggregateError
  • Promise.allSettled() :接收一个 Promise 数组,在所有 Promise 状态改变(fulfilled 或者 rejected)之后返回结果数组。Promise.allSettled 适用于异步任务互相不依赖,Promise.all 适用于异步任务互相之间需要依赖其他任务的结果;

📒 什么时候不能使用箭头函数

  • 需要函数提升时(箭头函数只能写成表达式形式);
  • 需要使用函数的 thisargumentsprototype 时;
  • 需要使用命名函数时(箭头函数是匿名的);
  • 需要作为构造函数时(箭头函数不能作为构造函数);
  • 需要在对象方法中访问当前对象时;
let obj = {
a: 1,
fn1: () => {
console.log(this)
},
fn2: function() {
console.log(this)
}
}
obj.fn1(); // Window
obj.fn2(); // {a: 1, fn1: ƒ, fn2: ƒ} ,这是 this 的隐式绑定

const f1 = obj.fn1;
const f2 = obj.fn2;
f1(); // Window
f2(); // Window ,隐式绑定取决于谁来调用,谁调用就指向谁

使用箭头函数之所以会指向 Window 是因为箭头函数等价于下面的代码:

var that = this; // 直接使用闭包缓存 this
let obj = {
a: 1,
fn1: function() {
console.log(that);
}
}

在对象方法(例如 Vue Options API)的异步回调中经常会遇到 this 丢失的情况,一般会使用闭包进行缓存:

// 使用 _this 变量进行缓存
const _this = this;
api.get("/api/xxx", function(res) {
_this.tableData = res;
})

// 除了使用闭包缓存 this ,还可以使用箭头函数
api.get("/api/xxx", (res) => {
this.tableData = res;
})

总结一下:箭头函数没有自己的 this ,没有 argument 对象,没有 prototype ,不能作为构造函数(用 new 调用会报错)。箭头函数会自动捕获上级词法作用域的 this ,并且箭头函数的 this 在声明的时候就已经确定了,不能通过 call 或者 apply 修改