移动端疑难杂症(持续更新)

写在前面

本来记录在印象笔记中,但放着老是不看,为加深印象以及搜索方便,立一文统一记录移动端的兼容性问题

前人总结

司徒正美-mobileHack

总结几个移动端 H5 软键盘的大坑

input 中输入不要有空格

onChange(replace(/\s+/g, '')

<input onChange={onChange(e.target.value.replace(/\s+/g, ""))} />

input 去除 ios 端输入法首字母大写状态

<input type="text" autocapitalize="off" autocorrect="off" />

css 一行省略

普通版本

overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;

有赞版本

display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;

CSS 多行省略

-webkit-line-clamp: 3; // 文本行数
display: -webkit-box;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
word-break: break-all;
overflow: hidden;

ios 使用 border-radius 时失效

原因:ios 端,该元素使用 transform 属性会导致 border-radius 失效

解决方法:在使用动画效果(使用 transform)元素的上一级的 css 上加上以下属性:

-webkit-transform: rotate(0deg);

开发H5营销页面遇到的问题

ios端不能自动播放音乐、视频(但之前看过一个网易云音乐的 H5 能自动播放音乐)

微信里打开的H5不支持点击下载,需要长按保存

ios 端高度超过一屏,input 输入完后不回弹

这个问题老生常谈,但目前我的 iPhone X 手机 ios 15系统未遇到这种情况,而 iPhone 7 手机遇到了这个问题。猜测是老的手机用的 webview 的 bug,解决方法是在监听 input 的 onblur 事件,在失去焦点的时候,拿到它滑动后的高度,使用 window.scrollTo 回到原来的 scrollHeight

如代码:

<input
  type="text"
  onBlur={() => {
    setTimeout(() => {
      if (util.system.ios) {
        const scrollHeight =
          document.documentElement.scrollTop || document.body.scrollTop || 0;
        window.scrollTo(0, Math.max(scrollHeight - 1, 0));
      }
    }, 100);
  }}
/>

看网上答疑:在 IOS12 ,微信版本 v6.7.4 及以上,输入框获取焦点,键盘弹起,页面(webview)整体往上滚动,当键盘收起后,不会到原位,导致键盘原来所在位置是空白的

安卓手机键盘弹出,希望表单跟着向上移动

ios 手机会跟着选中目标后滚动到中间位置,但是安卓系统不会,但你点击下方的表单时,表单不会向上滚动,键盘弹出后会遮住目标表单

安卓中的可视高度=我们看到的页面高度+软键盘的高度,而 IOS 的可视高度与软键盘无关

在 componentDidMount 或者 useEffect 中监听 resize,以 函数式组件为例

const App = () => {
  const originHeight =
    document.documentElement.clientHeight || document.body.clientHeight;
  useEffect(() => {
    window.addEventListener("resize", resizeWindow);
    return () => {
      window.removeEventListener("resize", resizeWindow);
    };
  }, []);
  const resizeWindow = () => {
    if (util.system.android) {
      const resizeHeight =
        document.documentElement.clientHeight || document.body.clientHeight;
      const activeElement = document.activeElement;

      if (resizeHeight < originHeight) {
        if (activeElement && activeElement.tagName === "INPUT") {
          setTimeout(() => {
            activeElement.scrollIntoView({ block: "center" });
          }, 100);
        }
      }
    }
  };
  return <div id="app"></div>;
};

思路如下:

在组件初始化时监听 resize 事件,如果在 android 系统的话,获取文档调整后的高度以及选择的组件,通过对比原先高度,如果文档调整后的高度小于原先的高度,意味着有键盘弹出,我们就使用scrollIntoView,让目标选中组件滚动到页面中间

更多H5软键盘兼容性问题可以看看这这个帖子:可能这些是你想要的H5软键盘兼容方案

如何实现页面刷新后不定位到之前的滚动位置

history.scrollRestoration

if (history.scrollRestoration) {
  history.scrollRestoration = "manual";
}

源自:https://www.zhangxinxu.com/wordpress/2022/05/history-scrollrestoration/

检测 passive 是否支持

var passiveSupported = false;

try {
  var options = Object.defineProperty({}, "passive", {
    get: function () {
      passiveSupported = true;
    },
  });

  window.addEventListener("test", null, options);
} catch (err) {}

https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener

Input 几个有趣的属性

<input
  autocomplete="false"
  autoCorrect="off"
  autocapitalize="off"
  autofocus="false"
/>

autoComplete="false":自动记录输入的值

浏览器不允许为此字段自动输入或选择一个值

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Attributes/autocomplete

autoCorrect="off":自动纠错

autoCapitalize="off":自动大小写

控制用户输入/编辑文本输入时文本输入是否自动大写,以及如何自动大写

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/autocapitalize

autoFocus=false:自动对焦

自动对焦

微信开发

ios右上角消失

document.addEventListener("WeixinJSBridgeReady", function onBridgeReady() {
  WeixinJSBridge.call("showOptionMenu");
});

https://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1_JS%E6%8E%A5%E5%8F%A3

微信开发

最新版的微信已经不支持通过 debugx5.qq.com 打开 vconsole 了。要调试H5可以通过chrome远程:

① 把手机和电脑用usb连起来 ② 在手机微信中访问 http://[喵喵] debugxweb.qq.com/?inspector=true ③ 在电脑浏览器中访问 chrome://inspect/#devices

隐藏微信网页右上角的按钮

document.addEventListener("WeixinJSBridgeReady", function onBridgeReady() {
  `
// 通过下面这个API隐藏右上角按钮`;
  WeixinJSBridge.call("hideOptionMenu");
});
document.addEventListener("WeixinJSBridgeReady", function onBridgeReady() {
  // 通过下面这个API显示右上角按钮`

  WeixinJSBridge.call("showOptionMenu");
});

https://www.jianshu.com/p/d7f5f5131783

input ios端高度问题

芝麻开门,显示全文!

张潇雨的人生信念笔记

1.无论如何定义[成功],能达到这个状态的人都是极少数

2.一个人是 TA 打交道最多的五个人的平均水平

3.一个人水平的下限由 TA 学习的最差的五个对象决定

4.只向最好的人学习,其他人都不知道自己在干什么,不要理会它们的[建议] 心得:听自己敬佩的人的建议,其他人不用听。父母也好,朋友也罢,不要因为这层关系加重判断,要听,就听成功的人的意见,并放大此人意见的权重

打个比方,一个男生和一个女生,男生小镇青年,家境普通,学历普通,身高长相也普通。女生城市户口,父母公务员,学历比男生高,长相尚可(从小就有人追)。这样一对比,大多数网友的第一反应是不门当户对,这就是网友的意见。多数人靠着提供者提供的信息来分析事物,如果提供信息者隐藏或者没意识到重要信息导致没说出来,那么人给出的意见就不同。 所以微博上的热搜,这类事不用太较真,真真假假,假假真真。公说公有理,婆说婆有理 遇到人生重大事情时该怎么做?这让我想起了《白鹿原》里主角白嘉轩,他每次遇到决定不了的事情时,就会去请教姐夫朱先生,朱先生是私塾老师,是小说中的灵魂人物。姐夫每每点几句,白嘉轩就知道怎么做了 当你遇到一个重要事情时,不妨问问你敬佩的人,客观描述事情,让他分析。不要感情用事,不要代入,不要带有色眼镜

5.在没达到信息手机两的门槛之前,不轻易做判断和决策。多对自己和他人说[我不知道]

6.不是生活中的每一个问题都要解决,和问题共处是人生常态。把精力用在重要的事情上

7.寻找 [非对称回报] 的机会,即那种 [失败了损失很低,单一旦成功回报巨大] 的机会

8.更好的机会是 [失败了有一点线性回报,但成功理由巨大的指数回报],当然这中机会会非常稀少;

9.人境遇的改变往往是非线性的。积累和等待的过程很难熬,这是很多人无法改变的原因之一

10.耐心是非常值得拥有的品质

11.一个人的境遇绝大部分都受运气支配,但运气是可以被影响的

12.人很难靠出卖自己的单位时间获得财富

13.财富来自于对杠杆的使用,常见的杠杆有:互联网、人力、资本、声望、时间等等

14.财富的积累来源自不可代替性。学者让自己很难被替代,就像公司要建立自己的护城河。 心得:成为解决问题的人

15.很多时候最好的竞争优势就是 [别人觉得麻烦而你不觉得]

16.凡是都反过来想:想要投资成功,先弄清什么让人投资失败;想要生活幸福,先看什么事会导致人生不幸福。

17.在混沌、开放、随机的系统内,努力 [减少错误] 要比 [追求正确] 有效得多

18.人是无法用一套思维方式正确认知世界的。广泛地吸收知识,建立多远思维模型是成功的前提

19.当两件事看起来有些矛盾的时候,几乎总有一个更高层面的东西把它们统一起来。不断寻找,你就可以发现游戏的元规则是什么

20.将大部分的时间用在掌握游戏的元规则上

21.详细大数定律。一件事哪怕只有 10% 的把握,连试 20 次之后成功了也有九城

22.大多数的所谓心情问题、状态问题、创造力问题,都是身体健康和精力的问题 心得:大多数人不会主动解决,但身体会给你反馈 我检验精力是否饱满的两个技巧, 一在地铁上坐着看视频,精力指数小于 10,无论看什么都会发困,以至于闭上眼睛就能睡着 二找个安静的地方(图书馆或者半夜)看书,精力指数小于 50 就会打哈气 一般早上的精力比较好,所以早上的效率会比下午,晚上高

23.吃得健康、持续锻炼、保证睡眠,几乎可以待人走出任何困境

24.日常中积累小的信息和正反馈,这样在大的选择面前就会更加从容淡定

25.意志力这种东西几乎不存在。与其努力提高自己的意志力,不如给自己创造更好的环境于试无需调用意志力 心得:鄙人就是,一个好的环境对自己很重要 所以才想和女友搬到一起,这样能省下时间去学习

26.多和 [真、善、美]的东西在一起。它们的珍贵程度叶恒实这个顺序

27.在解决生存问题之后,人的幸福感主要由身边关系 的质量决定

28.爱不是一种情侣之间特有的东西,而是一种生命状态,也是你选择与世界沟通的方式

29.[对自己诚实]试最被低估的品质。一个人如果嫉妒坦诚,TA 就是无坚不催的

30.学会不带评判地自我觉察,这是一切改变的起点

31.人们懂得很多道理但仍然无法改变,是因为 [大脑知道] 和 [身体知道] 完全是两件事 心得:韩寒的“听过很多道理,依然过不好这一生”也是大脑知道。如果通过一些小事让自己觉知很重要

芝麻开门,显示全文!

this 关键字

先说结论:谁调用它,this就指向谁

前言:在讲作用域的时候,我们讲到了this,因为JavaScript中的作用域是词法作用域,在哪里定义,就在那里形成作用域。而与词法作用域相对应的还有一个作用域叫动态作用域,调用时去寻找它所处的位置。那个时候我就说道 this机制 和动态作用域很像。

关于this

为什么使用 this

我们解释一下为什么要使用this,用一个例子

function identify() {
  return this.name.toUpperCase();
}

function speak() {
  var greeting = "Hello, I'm" + identify.call(this);
  console.log(greeting);
}

var me = {
  name: "johan",
};

var you = {
  name: "elaine",
};

identify.call(me); // JOHAN
identify.call(you); // ELAINE

speak.call(me); // Hello, I'm JOHAN
speak.call(you); // Hello, I'm ELAINE

这段代码可以在不同的上下文对象(me 和 you)中重复使用函数identity() 和 speak(),不用针对每个对象编写不同版本的函数

如果不适用 this,那就需要给identify() 和 speak() 显式传入一个上下文对象

function identify(context) {
  return context.name.toUpperCase();
}

function speak(context) {
  var greeting = "Hello, I'm" + identify(context);
  console.log(greeting);
}

identify(you); // ELAINE
speak(me); // Hello, I'm JOHAN

看到这里你也许明白了,this 是一种更为优雅的”传递”对象引用的方式。这个例子还过于简单,当你遇到7.8个甚至10几个函数(或叫方法)之间的调用时,显式传值无疑会变得混乱。除此之外,在原型中,函数自动引入合适的上下文对象是极为重要的,这个我们放在原型章中在讲。

this到底是什么

this到底是一种什么样的机制。

  1. this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。
  2. this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式
  3. 当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

调用位置

正如上面所讲,this是在运行时绑定的,它的上下文取决于函数调用时的各个条件。在JavaScript中函数的调用有以下几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用apply或者call调用。下面我们按照调用方式不同,分别讨论 this 的含义

作为对象方法调用

在 JavaScript中,函数也是对象,因此函数可以作为一个对象的属性,此时该函数被称为该对象的方法,在调用这种调用方式时,this 被自然绑定到该对象

var people = {
  name: "elaine",
  age: 26,
  sayName: function () {
    console.log(this.name);
  },
};
people.sayName(); // elaine

作为函数调用

函数也可以直接被调用,此时 this 绑定到全局对象。在浏览器中,window 就是该全局对象。比如下面的例子:函数被调用时,this 被绑定到全局对象,接下来执行赋值语句,相当于隐式的声明了一个全局变量,这显然不是调用者希望的

function sayAge(age) {
  this.age = age;
}
sayAge(5);
// age 已经成为一个值为 5 的全局变量

对于内部函数,即声明在另外一个函数体内的函数,这种绑定到全局对象的方式会产生另外一个问题。我们仍然以前提到的 people 对象为例,这次我们希望在 sayName 方法内定义一个函数,函数打印年龄。结果可能出乎大家意料,不仅年龄没有打印出,反而多了一个全局变量 age

var people = {
  name: "elaine",
  age: 26,
  sayName: function (age) {
    var sayAge = function (age) {
      this.age = age;
    };
    sayAge(age);
  },
};
people.sayName(5);
people.age; // 26
age; // 5

这属于 JavaScript 的设计缺陷,正确的设计方式是内部函数的 this 应该绑定到其外层函数对应的对象上,为了规避这一设计缺陷,聪明的 JavaScript 程序员想出了变量代替的方式,约定俗成,该变量一般被称为 that

var people = {
  name: "elaine",
  age: 26,
  sayName: function (age) {
    var that = this;
    var sayAge = function (age) {
      that.age = age;
    };
    sayAge(age);
  },
};
people.sayName(5);
people.age; // 5
age; // 没有定义

当然,当我们使用ES6中的箭头函数时,我们会发现箭头函数也能做到相同的效果

var people = {
  name: "elaine",
  age: 26,
  sayName: (age) => {
    var sayAge = function (age) {
      this.age = age;
    };
    sayAge(age);
  },
};
people.sayName(5);
people.age; // 26
age; // 5

可答案却让我匪夷所思,箭头函数难道不应该把this指向它的上一层吗?这个我们在后面会解释

作为构造函数调用

JavaScript 支持面向对象式编程,与主流的面向对象式编程语言不同, JavaScript并没有类(Class)的概念,而是使用基于原型(prototype-base)的继承方式(ES6中的Class其实是原型继承的语法糖)。相应的,JavaScript中的构造函数也很特殊,如果不适用new调用,则和普通函数一样。作为又一项约定俗成的准则,构造函数以大写字母开头,提醒调用者使用正确的方式调用。如果调用正确,this绑定到新创建的对象上。

function People(name, age) {
  this.name = name;
  this.age = age;
}

使用 apply 或 call 调用

让我们再一次重申,在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。很多 JavaScript 中的技巧以及类库都用到了该方法。让我们看一个具体的例子:

function People(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function(name, age) {
        this.name = name;
        this.age = age;
    }
}
var elaine = new People('elaine', 26);
var johan = {name: 'johan', age: 26};
elaine.sayName('elaine1', 261);
elaine.sayName.apply(johan, ['johan1', 261])
console.log(elaine.name) // elaine1;
console.log(elaine.age) // 261
console.log(johan) { name: "johan1", age: 261 }

在上面的例子中,我们使用构造函数生成了一个对象elaine,该对象同时具有 sayName 方法;使用对象字面量创建了另一个对象 johan,我们看到使用apply 可以将 elaine 上的方法应用到 johan 上,这时候 this 也被绑定到对象 johan 上,另一个 call 也具备相同的功能,不同的是最后的参数不是作为一个数组统一传入,而是分开传入的

elaine.sayName.call(johan, "johan1", 261);

回过头来看,apply 和 call 的语义就是 elaine 的方法 sayName 作用于 johan ,sayName 需要传入的参数,我从第二个参数开始传值;或者说 johan 调用 elaine 的 sayName 方法,从第二个参数开始传值

call和apply具有掰弯this指向的能力

箭头函数

与箭头函数相关的语法和特征我们会在ES6篇中着重描述,这里,我们只讲箭头函数与 this 的关系。在“作为函数调用”小节中我们使用箭头函数,试图让它绑定,但是却感觉错了

网上对箭头函数与this 关系的解释是:箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中的this的值和外层的this是一样的。这个解释很困扰我,就好比你看高中政治课本,一谈到马克思主义思想浪潮时虽然文字都看的懂,但是连在一起却那么的神奇,让人疑惑不止。

其实箭头函数很简单,和我们之前说作用域时谈到的动态作用域和静态作用域(词法作用域)有关系。this本身的机制和动态作用域很像,而箭头函数的出现,某种程度上规避了JavaScript 的设计缺陷(正确的设计方式应该是内部函数的 this 应该绑定到其外层函数对应的对象上)

"use strict"; // 严格模式下
var people = {
  name: "eliane",
  age: 26,
  sayName: () => console.log(this.name, this),
  sayName2: function () {
    console.log(this.name, this);
  },
};
people.sayName(); // undefined Window {...}
people.sayName2(); // elaine, peole {...}

使用箭头函数后,就不用管调用者是谁,它只关心在哪里调用

var foo = {
  bar: {
    a: () => console.log(this),
  },
};
foo.bar.a(); // window

函数的执行环境

我们之前一直在讲一件事,this是如何被调用的,也说了this是什么,那么我们来看看,一个函数被执行时会发生什么?

一个函数被执行时,会创建一个执行环境(或叫活动记录,或叫执行上下文,英文名 ExecutionContext),函数所有的行为都发生在此执行环境中,构建该执行环境时,JavaScript 首先会创建 arguments 变量,其中包含调用函数时传入的参数。接下来创建作用域链。然后初始化变量,首先初始化函数的形参表,值为 arguments 变量中对应的值,如果 arguments 变量中没有对应值,则该形参初始化为 undefined。如果该函数中含有内部函数,则初始化这些内部函数。如果没有,继续初始化该函数内定义的局部变量,需要注意的是此时这些变量初始化为 undefined,其赋值操作在执行环境( ExecutionContext )创建成功后,函数执行时才会执行,这点对于我们理解JavaScript中的变量作用域非常重要。

最后是 this 变量赋值,如前所述,会根据函数调用方式的不同,赋给 this 全局对象,当前对象等。至此函数的执行环境( ExecutionContext )创建成功,函数开始逐行执行,所需变量均从之前构建好的执行环境( ExecutionContext )中读取

this有什么作用

全局执行上下文中:this 指向了 window 对象,方便我们来调用全局 window 对象

函数执行上下文中:this 指向了调用该函数的对象,减少的参数的传递,原来如何需要在函数内部操作被调用对象,当然还需要将对象作为参数传递进去,而又了 this,就不需要了,直接拿 this 就可以操作该调用对象的属性

结语

结语就留给后面的自己吧

构造函数就是个模式,this未来会指向new出来的对象。创建 Person 的实例时,this.name 将引用新创建的对象,并将一个名为 name 的属性放入新对象中。

this 其实很好理解,它就是一个代词,表示”这个“。

生活中遇到一些事物规律,我们归纳总结,得出结论,用一个名词代替这个规律,例如马太效应,墨菲定律,我们约定俗成,这个词就是表示这些意。这样一抽象,彼此信息消耗就减少了。this 其实很好理解,this 就是”这个“。

var foo = {
  value: 1,
};
function bar() {
  console.log(this.value);
}
bar();

调用函数bar,函数中的 this 就默认代指 window。window上没有value,那结果就是 undefined。

var foo = {
  value: 1,
};
function bar() {
  console.log(this.value);
}
bar.call(foo);

call 能硬核掰弯this指向,将this指向第一个参数,所以这段代码中,this 代指 foo , foo 上有value,所以打印结果是 1

针对 js 中的 this 指向问题,知乎上有人曾经回答过:

https://www.zhihu.com/question/412637481/answer/1539325572

  • this 的灵活指向,属于 JS 自己发明的语言
  • this 指向存在的问题是公认的
  • this 的这种设计既不利于代码可读性,也不利于性能优化,完全可对其世家强制性
  • this 设计问题的更远,是产品营销需求与设计师个人偏好之间的冲突

this 是万恶之源,大家都是(词法)静态作用域,就他是动态的.

参考资料

芝麻开门,显示全文!

umi项目优化

此场景在项目中遇见,一看文档,二看 umi 开发社区中的大佬的优化,三结合自己以前的 webpack 配置经验总结而出。

为防止文档链接失效,笔者准备有些必要时候能抄就抄,保存文章整体性。

注意:此项目主要是笔者的经验之谈以及代码大小优化部分。

介绍完毕,准备开干:

查看包结构

首先,要查看代码尺寸,最直观的方式是 build 它,查看生成文件大小,不过 umi 中自带了查看包大小的 webpack 插件 analyze,所以我们需要在 package.json 中配置 script 脚本:

{
  "script": {
    "analyze": "cross-env  ANALYZE=1 umi dev",
    "build:analyze": "cross-env  ANALYZE=1 umi build"
  }
}

执行 umi devumi build 时,增加环境变量ANALYZE=1 可查看产物的依赖占比

配置 externals

这个在 webpack 中常用到,就是比如基本库,可以通过 externals 的配置引入相关的 umd 文件,减少编译消耗

比如 react 和 react-dom:

export default {
  // 配置 external
  externals: {
    react: "window.React",
    "react-dom": "window.ReactDOM",
  },
  // 引入被 external 库的 scripts
  // 区分 development 和 production,使用不同的产物
  scripts:
    process.env.NODE_ENV === "development"
      ? [
          "https://gw.alipayobjects.com/os/lib/react/16.8.6/umd/react.development.js",
          "https://gw.alipayobjects.com/os/lib/react-dom/16.8.6/umd/react-dom.development.js",
        ]
      : [
          "https://gw.alipayobjects.com/os/lib/react/16.8.6/umd/react.production.min.js",
          "https://gw.alipayobjects.com/os/lib/react-dom/16.8.6/umd/react-dom.production.min.js",
        ],
};

减少补丁尺寸

因为本项目为移动端项目,所以这块的配置如下

targets: {
    chrome: 49,
    firefox: 45,
    safari: 10,
    edge: 13,
    ios: 10,
},

调整 splitChunks 策略,减少整体尺寸

做一块的前提是将dynamicImport(按需加载),在没开启按需加载前,umi 只会生成一个 js 和一个 css,即 umi.jsumi.css 。优点是省心,部署方便,缺点是对用户来说初次打开网站会比较慢。这显然是要优化的,因为前端优化中按需加载是肯定要做的,具体如何做可以查看 UmiJS 的官网,这里不做描述。

但如果开了 dynamicImport,产物特别大,每个出口文件都包含了相同的依赖,比如 自己的 UI 库,可尝试通过 splitChunks 配置调整公共依赖的提取策略

比如:

export default {
  chunks: ["vendors", "umi"],
  chainWebpack: function (config, { webpack }) {
    config.merge({
      optimization: {
        splitChunks: {
          chunks: "all",
          minSize: 30000,
          minChunks: 3,
          automaticNameDelimiter: ".",
          cacheGroups: {
            vendor: {
              name: "vendors",
              test({ resource }) {
                return /[\\/]node_modules[\\/]/.test(resource);
              },
              priority: 10,
            },
          },
        },
      },
    });
  },
};

图片资源压缩

UI 提供图片素材后,可通过 TinyPNG(https://tinypng.com/) 或者 pngquant 等网站对图片经验进一步压缩

选用可替代的依赖库

大的依赖包能换就换,例如 monent 库比较大,我们可以替换成 dayjs,或者如果只用到其中的几个方法(例如 lodash 中的防抖节流)完成可以自己写

开启 gzip 压缩

下载 webpack 插件

npm i compression-webpack-plugin @types/compression-webpack-plugin --save

进入 umi 的配置文件中,引入并配置 gzip 压缩

import CompressionPlugin from 'compression-webpack-plugin';
import { defineConfig } from 'umi';
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;

export default defineConfig({
    ...
    chainWebpack(memo: any, args: any) {
        memo.plugin('CompressionPlugin').use(
            new CompressionPlugin({
                filename: '[path].gz[query]',
                algorithm: 'gzip',
                test: productionGzipExtensions,
                threshold: 10240,
                minRatio: 0.8,
                deleteOriginalAssets: false,
            }),
        );
    },
})

以上就是 umi 项目的优化方案。

经过压缩后,30 个页面的代码从没优化前的 3M 代码,压缩到 2.5M(含 gzip),虽然压缩大小没怎么变化,但是用户体验有了质的飞跃。

配合 nginx 的 gzip 压缩以及缓存策略,更能加快页面访问

参考资料

芝麻开门,显示全文!