Marimo

Marimo 查看完整档案

杭州编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

Marimo 收藏了文章 · 2019-11-13

精读《你不知道的javascript(中卷)》

前言

《你不知道的 javascript》是一个前端学习必读的系列,让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaScript每一个零部件的用途。本书《你不知道的javascript》中卷介绍了该系列的两个主题:“类型和语法”以及“异步与性能”。这两块也是值得我们反复去学习琢磨的两块只是内容,今天我们用思维导图的方式来精读一遍。(思维导图图片可能有点小,记得点开看,你会有所收获)

第一部分 作用域和闭包

类型


JavaScript 有 七 种 内 置 类 型: null 、 undefined 、 boolean 、 number 、 string 、 object 和 symbol ,可以使用 typeof 运算符来查看。

变量没有类型,但它们持有的值有类型。类型定义了值的行为特征。

很 多 开 发 人 员 将 undefined 和 undeclared 混 为 一 谈, 但 在 JavaScript 中 它 们 是 两 码 事。 undefined 是值的一种。 undeclared 则表示变量还没有被声明过。

遗憾的是, JavaScript 却将它们混为一谈, 在我们试图访问 "undeclared" 变量时这样报 错:ReferenceError: a is not defined, 并 且 typeof 对 undefined 和 undeclared 变 量 都 返 回 "undefined" 。

然而,通过 typeof 的安全防范机制(阻止报错)来检查 undeclared 变量,有时是个不错的办法。


JavaScript 中的数组是通过数字索引的一组任意类型的值。字符串和数组类似,但是它们的 行为特征不同, 在将字符作为数组来处理时需要特别小心。 JavaScript 中的数字包括“整 数”和“浮点型”。

基本类型中定义了几个特殊的值。

null 类型只有一个值 null , undefined 类型也只有一个值 undefined 。 所有变量在赋值之 前默认值都是 undefined 。 void 运算符返回 undefined 。

数 字 类 型 有 几 个 特 殊 值, 包 括 NaN ( 意 指“not a number” , 更 确 切 地 说 是“invalid number” )、 +Infinity 、 -Infinity 和 -0 。

简单标量基本类型值(字符串和数字等)通过值复制来赋值 / 传递, 而复合值(对象等) 通过引用复制来赋值 / 传递。 JavaScript 中的引用和其他语言中的引用 / 指针不同,它们不 能指向别的变量 / 引用,只能指向值。

原生函数

JavaScript

为基本数据类型值提供了封装对象,称为原生函数(如 String 、 Number 、 Boolean 等)。它们为基本数据类型值提供了该子类型所特有的方法和属性(如: String#trim() 和 Array#concat(..) )。

对于简单标量基本类型值,比如 "abc" ,如果要访问它的 length 属性或 String.prototype 方法, JavaScript 引擎会自动对该值进行封装(即用相应类型的封装对象来包装它)来实现对这些属性和方法的访问。

强制类型转换

本章介绍了 JavaScript 的数据类型之间的转换,即强制类型转换:包括显式和隐式。

强制类型转换常常为人诟病, 但实际上很多时候它们是非常有用的。 作为有使命感的 JavaScript 开发人员,我们有必要深入了解强制类型转换,这样就能取其精华,去其糟粕。

显式强制类型转换明确告诉我们哪里发生了类型转换, 有助于提高代码可读性和可维 护性。

隐式强制类型转换则没有那么明显,是其他操作的副作用。感觉上好像是显式强制类型转 换的反面,实际上隐式强制类型转换也有助于提高代码的可读性。

在处理强制类型转换的时候要十分小心,尤其是隐式强制类型转换。在编码的时候,要知 其然,还要知其所以然,并努力让代码清晰易读。

语法


JavaScript 语法规则中的许多细节需要我们多花点时间和精力来了解。 从长远来看, 这有 助于更深入地掌握这门语言。

语句和表达式在英语中都能找到类比——语句就像英语中的句子,而表达式就像短语。表 达式可以是简单独立的,否则可能会产生副作用。

JavaScript 语法规则之上是语义规则(也称作上下文)。例如, { } 在不同情况下的意思不 尽相同,可以是语句块、对象常量、解构赋值(ES6)或者命名函数参数(ES6)。

JavaScript 详细定义了运算符的优先级(运算符执行的先后顺序)和关联(多个运算符的 组合方式)。只要熟练掌握了这些规则,就能对如何合理地运用它们作出自己的判断。

ASI(自动分号插入)是 JavaScript 引擎的代码解析纠错机制, 它会在需要的地方自动插 入分号来纠正解析错误。问题在于这是否意味着大多数的分号都不是必要的(可以省略), 或者由于分号缺失导致的错误是否都可以交给 JavaScript 引擎来处理。

JavaScript 中有很多错误类型, 分为两大类:早期错误(编译时错误, 无法被捕获)和运 行时错误(可以通过 try..catch 来捕获)。所有语法错误都是早期错误,程序有语法错误 则无法运行。

函数参数和命名参数之间的关系非常微妙。尤其是 arguments 数组,它的抽象泄漏给我们 挖了不少坑。因此,尽量不要使用 arguments ,如果非用不可,也切勿同时使用 arguments 和其对应的命名参数。

finally 中代码的处理顺序需要特别注意。它们有时能派上很大用场,但也容易引起困惑, 特别是在和带标签的代码块混用时。总之,使用 finally 旨在让代码更加简洁易读,切忌 弄巧成拙。

switch 相对于 if..else if.. 来说更为简洁。需要注意的一点是,如果对其理解得不够透 彻,稍不注意就很容易出错。

第二部分 this 和对象原型

异步:现在与将来


实际上, JavaScript 程序总是至少分为两个块:第一块 现在 运行;下一块 将来 运行, 以响 应某个事件。尽管程序是一块一块执行的,但是所有这些块共享对程序作用域和状态的访 问,所以对状态的修改都是在之前累积的修改之上进行的。

一旦有事件需要运行, 事件循环就会运行, 直到队列清空。 事件循环的每一轮称为一个 tick。 用户交互、IO 和定时器会向事件队列中加入事件。

任意时刻,一次只能从队列中处理一个事件。执行事件的时候,可能直接或间接地引发一 个或多个后续事件。

并发是指两个或多个事件链随时间发展交替执行,以至于从更高的层次来看,就像是同时 在运行(尽管在任意时刻只处理一个事件)。

通常需要对这些并发执行的“进程”(有别于操作系统中的进程概念)进行某种形式的交 互协调,比如需要确保执行顺序或者需要防止竞态出现。这些“进程”也可以通过把自身 分割为更小的块,以便其他“进程”插入进来。

回调

回调函数是 JavaScript 异步的基本单元。但是随着 JavaScript 越来越成熟,对于异步编程领域的发展,回调已经不够用了。

第一,大脑对于事情的计划方式是线性的、阻塞的、单线程的语义,但是回调表达异步流 程的方式是非线性的、非顺序的,这使得正确推导这样的代码难度很大。难于理解的代码 是坏代码,会导致坏 bug。

我们需要一种更同步、更顺序、更阻塞的的方式来表达异步,就像我们的大脑一样。

第二,也是更重要的一点,回调会受到控制反转的影响,因为回调暗中把控制权交给第三 方(通常是不受你控制的第三方工具!)来调用你代码中的 continuation。 这种控制转移导 致一系列麻烦的信任问题,比如回调被调用的次数是否会超出预期。

可以发明一些特定逻辑来解决这些信任问题,但是其难度高于应有的水平,可能会产生更 笨重、更难维护的代码,并且缺少足够的保护,其中的损害要直到你受到 bug 的影响才会 被发现。

我们需要一个通用的方案来解决这些信任问题。不管我们创建多少回调,这一方案都应可 以复用,且没有重复代码的开销。

我们需要比回调更好的机制。到目前为止,回调提供了很好的服务,但是未来的 JavaScript 需要更高级、功能更强大的异步模式。本书接下来的几章会深入探讨这些新型技术。

Promise

Promise 非常好,请使用。它们解决了我们因只用回调的代码而备受困扰的控制反转问题。

它们并没有摈弃回调,只是把回调的安排转交给了一个位于我们和其他工具之间的可信任 的中介机制。

Promise 链也开始提供(尽管并不完美)以顺序的方式表达异步流的一个更好的方法,这 有助于我们的大脑更好地计划和维护异步 JavaScript 代码。我们将在第 4 章看到针对这个 问题的一种更好的解决方案!

生成器


生成器是 ES6 的一个新的函数类型, 它并不像普通函数那样总是运行到结束。 取而代之 的是, 生成器可以在运行当中(完全保持其状态)暂停, 并且将来再从暂停的地方恢复 运行。

这种交替的暂停和恢复是合作性的而不是抢占式的,这意味着生成器具有独一无二的能力 来暂停自身,这是通过关键字 yield 实现的。不过,只有控制生成器的迭代器具有恢复生 成器的能力(通过 next(..) )。

yield / next(..) 这一对不只是一种控制机制,实际上也是一种双向消息传递机制。 yield .. 表 达式本质上是暂停下来等待某个值,接下来的 next(..) 调用会向被暂停的 yield 表达式传回 一个值(或者是隐式的 undefined )。

在异步控制流程方面,生成器的关键优点是:生成器内部的代码是以自然的同步 / 顺序方 式表达任务的一系列步骤。其技巧在于,我们把可能的异步隐藏在了关键字 yield 的后面, 把异步移动到控制生成器的迭代器的代码部分。

换句话说,生成器为异步代码保持了顺序、同步、阻塞的代码模式,这使得大脑可以更自 然地追踪代码,解决了基于回调的异步的两个关键缺陷之一。

程序性能

本部分的前四章都是基于这样一个前提:异步编码模式使我们能够编写更高效的代码,通 常能够带来非常大的改进。但是,异步特性只能让你走这么远,因为它本质上还是绑定在 一个单事件循环线程上。

因此,在这一章里,我们介绍了几种能够进一步提高性能的程序级别的机制。

Web Worker 让你可以在独立的线程运行一个 JavaScript 文件(即程序),使用异步事件在 线程之间传递消息。 它们非常适用于把长时间的或资源密集型的任务卸载到不同的线程 中,以提高主 UI 线程的响应性。

SIMD 打算把 CPU 级的并行数学运算映射到 JavaScript API, 以获得高性能的数据并行运算,比如在大数据集上的数字处理。

最后, asm.js 描述了 JavaScript 的一个很小的子集, 它避免了 JavaScript 难以优化的部分 (比如垃圾收集和强制类型转换),并且让 JavaScript 引擎识别并通过激进的优化运行这样 的代码。可以手工编写 asm.js, 但是会极端费力且容易出错,类似于手写汇编语言(这也 是其名字的由来)。实际上, asm.js 也是高度优化的程序语言交叉编译的一个很好的目标, 比如 Emscripten 把 C/C++ 转换成 JavaScript(https://github.com/kripken/em...

JavaScript 还有一些更加激进的思路已经进入非常早期的讨论, 尽管本章并没有明确包含 这些内容,比如近似的直接多线程功能(而不是藏在数据结构 API 后面)。不管这些最终 会不会实现,还是我们将只能看到更多的并行特性偷偷加入 JavaScript, 但确实可以预见, 未来 JavaScript 在程序级别将获得更加优化的性能。

性能测试与调优

对一段代码进行有效的性能测试,特别是与同样代码的另外一个选择对比来看看哪种方案 更快,需要认真注意细节。

与其打造你自己的统计有效的性能测试逻辑,不如直接使用 Benchmark.js 库,它已经为你 实现了这些。但是,编写测试要小心,因为我们很容易就会构造一个看似有效实际却有缺 陷的测试,即使是微小的差异也可能扭曲结果,使其完全不可靠。

从尽可能多的环境中得到尽可能多的测试结果以消除硬件/ 设备的偏差, 这一点很重要。 jsPerf.com 是很好的网站,用于众包性能测试运行。

遗憾的是,很多常用的性能测试执迷于无关紧要的微观性能细节,比如 x++ 对比 ++x 。编 写好的测试意味着理解如何关注大局, 比如关键路径上的优化以及避免落入类似不同的 JavaScript 实现细节这样的陷阱中。

尾调用优化是 ES6 要求的一种优化方法。它使 JavaScript 中原本不可能的一些递归模式变 得实际。 TCO 允许一个函数在结尾处调用另外一个函数来执行,不需要任何额外资源。这意味着,对递归算法来说,引擎不再需要限制栈深度。

扩展

思维导图能比较清晰的还原整本书的知识结构体系,如果你还没用看过这本书,可以按照这个思维导图的思路快速预习一遍,提高学习效率。学习新事物总容易遗忘,我比较喜欢在看书的时候用思维导图做些记录,便于自己后期复习,如果你已经看过了这本书,也建议你收藏复习。如果你有神马建议或则想法,欢迎留言或加我微信交流:646321933,备注技术交流

精读《你不知道的javascript》上卷

精读《深入浅出Node.js》

思维导图下载地址

你不知道的 javascript(中卷)PDF 下载地址

查看原文

Marimo 赞了文章 · 2019-08-30

高程3总结#第6章面向对象的程序设计

面向对象的程序设计

理解对象

属性类型

  • 创建自定义对象的最简单方式就是创建一个Object的实例,然后再为它添加属性和方法

    var person=new Object();
    person.name="Nicholas";
    person.age=20;
    person.job="Software Engineer";
    person.sayName=function(){
      alert(this.name)
    }
  • 对象字面量称为创建这种对象的首选方式

    var person={
      name:"Nicholas",
      age:20,
      job:"Software Engineer",
      sayName:function(){
        alert(this.name);
      }
    };
  • 两种属性

    • 数据属性,可以读取和写入值,有四个描述其行为的特性

      • [[Configurable]]表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,特性默认值为true
      • [[Enumerable]]表示能否通过for-in循环返回属性,特性默认值为true
      • [[Writable]]表示能否修改属性的值,特性默认值为true
      • [[Value]]包含这个属性的数据值,读取属性值的时候,从这个位置读,写入属性值的时候,把新值保存在这个位置,特性默认值是undefined
      • 要修改属性默认的特性,使用Object.definePropery()方法,这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。

        var person={};
        Object.definePropery(person,"name",{
          writable:false,
          value:"Nicholas"
        });
        alert(person.name);//"Nicholas"
        person.name="Greg";
        alert(person.name);//"Nicholas"
    • 访问器属性,包含一对getter和setter函数。在读取访问器属性的时候,会调用getter函数,这个函数负责返回有效的值。在写入访问器属性的时候,会调用setter函数并传入新值,这个函数负责决定如何处理数据。

      • [[Configurable]]表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性,特性默认值为true
      • [[Enumerable]]表示能否通过for-in循环返回属性,特性默认值为true
      • [[Get]]在读取属性时调用的函数,默认值为undefined
      • [[Set]]在写入属性时调用的函数,默认值为undefined
      • 访问器属性不能直接定义,必须使用Object.defineProperty()来定义

定义多个属性

  • Object.defineProperties()方法可以通过描述符一次定义多个属性,这个方法接收两个对象参数,一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应

    var book = {};
    Object.defineProperties(book, {
      _year: {
        value: 2004
      },
      edition: {
        value: 1
      },
      year: {
        get: function(){
          return this._year;
        },
        set: function(newValue){
          if (newValue > 2004) {
            this._year = newValue;
            this.edition += newValue - 2004;
          }
        }
      }
    });

读取属性的特性

  • Object.getOwnPropertyDescription()方法,可以取得给定属性的描述符,这个方法接收两个参数,属性所在的对象和要读取其描述符的属性名称

    var book = {};
    Object.defineProperties(book, {
      _year: {
        value: 2004
      },
      edition: {
        value: 1
      },
      year: {
        get: function(){
          return this._year;
        },
        set: function(newValue){
          if (newValue > 2004) {
            this._year = newValue;
            this.edition += newValue - 2004;
          }
        }
      }
    });
    var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
    alert(descriptor.value); //2004
    alert(descriptor.configurable); //false
    alert(typeof descriptor.get); //"undefined"
    var descriptor = Object.getOwnPropertyDescriptor(book, "year");
    alert(descriptor.value); //undefined
    alert(descriptor.enumerable); //false
    alert(typeof descriptor.get); //"function"

创建对象

工厂模式

function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
  alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

构造函数模式

function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
  alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
  • 只要通过new操作符来调用,那它就可以作为构造函数

    // 当作构造函数使用
    var person = new Person("Nicholas", 29, "Software Engineer");
    person.sayName(); //"Nicholas"
    // 作为普通函数调用
    Person("Greg", 27, "Doctor"); // 添加到 window
    window.sayName(); //"Greg"
    // 在另一个对象的作用域中调用
    var o = new Object();
    Person.call(o, "Kristen", 25, "Nurse");
    o.sayName(); //"Kristen"

原型模式

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
  • 无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象,默认情况下,所有的原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。

图片描述
图片描述
图片描述

组合使用构造函数模式和原型模式

function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
  alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

动态原型模式

function Person(name, age, job){
//属性
this.name = name;
this.age = age;
this.job = job;
// 方法
if (typeof this.sayName != "function"){
  Person.prototype.sayName = function(){
    alert(this.name);
  };
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

寄生构造函数模式

function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
  alert(this.name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"

稳妥构造函数模式

function Person(name, age, job){
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
  alert(name);
};
//返回对象
return o;
}

继承

原型链

图片描述
图片描述

  • 可以通过两种方式来确定原型和实例之间的关系,第一种方式是使用instanceof操作符,只要这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true

    alert(instance instanceof Object); //true
    alert(instance instanceof SuperType); //true
    alert(instance instanceof SubType); //true
  • 第二种方式是使用isPrototypeOf()方法,只要是原型链中出现过的原型,都可以说是原型链所派生的实例的原型,因此isPrototypeOf()方法也会返回true

    alert(Object.prototype.isPrototypeOf(instance)); //true
    alert(SuperType.prototype.isPrototypeOf(instance)); //true
    alert(SubType.prototype.isPrototypeOf(instance)); //true

借用构造函数

function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
// 继承了 SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
  • 对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数

    function SuperType(name){
      this.name = name;
    }
    function SubType(){
      //继承了 SuperType,同时还传递了参数
      SuperType.call(this, "Nicholas");
      //实例属性
      this.age = 29;
    }
    var instance = new SubType();
    alert(instance.name); //"Nicholas";
    alert(instance.age); //29

组合继承

  • 指的是将原型链和借用构造函数的技术组合到一块

    function SuperType(name){
      this.name = name;
      this.colors = ["red", "blue", "green"];
    }
    SuperType.prototype.sayName = function(){
      alert(this.name);
    };
    function SubType(name, age){
      //继承属性
      SuperType.call(this, name);
      this.age = age;
    }
    //继承方法
    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function(){
      alert(this.age);
    };
    var instance1 = new SubType("Nicholas", 29);
    instance1.colors.push("black");
    alert(instance1.colors); //"red,blue,green,black"
    instance1.sayName(); //"Nicholas";
    instance1.sayAge(); //29
    var instance2 = new SubType("Greg", 27);
    alert(instance2.colors); //"red,blue,green"
    instance2.sayName(); //"Greg";
    instance2.sayAge(); //27

原型式继承

var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"

寄生式继承

  • 思路和构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,函数在内部以某种方式来增强对象

    function createAnother(original){
      var clone = object(original); //通过调用函数创建一个新对象
      clone.sayHi = function(){ //以某种方式来增强这个对象
        alert("hi");
      };
      return clone; //返回这个对象
    }
    var person = {
      name: "Nicholas",
      friends: ["Shelby", "Court", "Van"]
    };
    var anotherPerson = createAnother(person);
    anotherPerson.sayHi(); //"hi"

寄生组合式继承

  • 无论什么情况下都会调用两次超类型构造函数,一次是在创建子类型原型的时候,另一次实在子类型构造函数内部

    function SuperType(name){
      this.name = name;
      this.colors = ["red", "blue", "green"];
    }
    SuperType.prototype.sayName = function(){
      alert(this.name);
    };
    function SubType(name, age){
      SuperType.call(this, name); // 第二次调用 SuperType()
      this.age = age;
    }
    SubType.prototype = new SuperType(); // 第一次调用 SuperType()
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function(){
      alert(this.age);
    };

图片描述

查看原文

赞 3 收藏 0 评论 4

Marimo 赞了文章 · 2019-08-29

论一个前端工程师如何快速学习,成长。准备自己的35岁 【-原创精读】

clipboard.png

前端工程师这个岗位,真的是反人性的

我们来思考一个问题:

一个6年左右经验的前端工程师:

前面两年在用jQuery

期间一直在用React-native(一步一步踩坑过来的那种)

最近两年还在写微信小程序

下面一个2年经验的前端工程师:

并不会跨平台技术,他的两年工作都是React框架还有制作微信小程序,年轻能加班

这时候去面试一家公司,面试官会如何选择?

当然,有人会说6年经验的疯狂学习,技术牛逼。这种情况不太多,我们看普通大众情况

可是在大多数面试官眼里,年轻人加入可以为团队注入新鲜血液

能加班,学东西快。因为一个重复的工作做了2年和6年,区别是不大的。

这或许就是前端所说的学会一个新框架就弯道超车吧

前端工程师的技术更新迭代极快

我们的技术真的疯狂在更新迭代,要命的是,在这个时代,我们是996的代言者。人口如此之多,你不上,立马有人补上,因为有人比你更缺钱。

前端的更新迭代,很容易出现断崖式更新迭代,还有时代的交替必然产物,比如传统的直接操作DOM的库,变成了现在的 数据驱动框架。

仅仅是因为一个diff算法和虚拟DOM的结合

那谁又能保证不出现比目前更有优势的算法和开发模式?

在这个快速开发注重应用层的时代,除非在有很多资源的大公司给予充分时间和研发支持,否则就得用别人的轮子。

最致命的一点,目前所有的库,大都是与框架配合。 那么离开了这个框架,它所配对的库,你也无法使用,换而言之,一直在写业务的我们,一直在用别人的轮子(大部分开发者)。长久以往,底层的了解越来越少,年龄越来越大,接受东西越来越慢。这时候35岁来了

平时写的都是Ajax请求加后台的CRUD

我曾经面试过一位6年经验的“全栈工程师”

2年PHP纯后端
2年Node.js中间层
2年React.js前端


一开始我看见简历就有一种不好的预感,因为我们需要一个高级的工程师一起攻克难关

后面一共问了三个问题:

redis是单线程的吗?为什么它快,有什么缺点

Node.js的多线程和多进程有什么区别?

手动实现promise里面,连续resolve两次,最后then(arg)里面参数的值是多少

结果一个都没有答上来

我就知道 该迅速结束了

面试不同的人,要给不同的标准去评判,如果是一个基本岗位,人品更重要

技术攻坚岗位,技术更重要


上班996,很累,没有太多学习时间

我觉得这是一个伪命题,人真的是可以做到007的。甚至更多

当然 并不能一直是007,而且要加上适当的锻炼。

工作太忙,一直被拖死,下班了睡大觉,睡醒急急忙忙上班加班。

上班8个小时,写8个小时代码,下班技术宅

这样更不可取,30岁后真的尽量不应该再靠投递简历找工作了(尽量,也不绝对)

人际关系很重要,什么样的朋友都需要,但是不能让他们的无效社交消耗你的时间

多结交没有恶习的朋友,上进的朋友

靠谱的朋友加上勤奋的你往往能给你带来更好的未来

技术一辈子,还是做管理一辈子?

这也是一个伪命题,世界上没有稳定的职位(除了大天朝的编制)

技术和管理都不能做一辈子,只有顺应时代发展和自身的不断提升才能跟上脚步不被淘汰。

10年前是jQuery,10年后是React


说说如何防止这些情况的出现吧:

在这个年代,有人说大前端什么都要学,其实是扯淡

专精一个方向,例如数据可视化,动画,IM

或者专精一个领域,例如App制作 ,后台管理系统开发

但是在专精一个领域的基础是,你所有的基础知识非常扎实。小到js底层语言,大到框架和库,跨平台开发的原理。

曾经面试一个5年经验的同学,直接告诉我原理没有用,库都实现了。

这么说,也没错。

但是这种人是到不了一个更高的领域,例如JS主线程和GUI渲染线程互斥,这个如果不知道,如何做深层次的性能优化?

在学会使用一个技术后,了解他的底层使用原理能帮你使用得更好,甚至有时间可以去看一些核心源码。说到源码,别跟傻子一样都去看。 只看核心的重点部分就好

数据结构,算法,设计模式,要不要了解?

要!但是前期是你的其他基础已经扎实,然后再来慢慢学习这些东西,当然,算法不要学习那些过于刁钻古怪的题目。 平常的 够用来

新技术来了,要不要学?

例如React-nativeflutter 都要学吗?

如果不是公司用到,那么不要花大精力去学习,曾经面试过一位同学, React-native从0.11版本用到现在,可是他也就会个React-native。 还很普通

一精多专是最好的状态

不要盲目崇拜新技术

在工作之余 把基础打扎实,慢慢去学习算法这些内容。

前端学精一个方向+会大多数常见的算法就很厉害了

但是新技术出来,一定要去了解他的原理。

例如React-nativeflutter有什么不同?底层原理是什么? 小程序的开发原理是什么?

等等。

有了这些知识储备,以后使用起来就很容易上手,出现问题也很容易解决。


最后 应该准备Plan B计划

靠技术吃饭可以,但是不能有靠技术吃一辈子饭的思想(可能仅适用互联网)

大家发现,财务自由的人,收入来源都不是唯一的

这边亏损,那边能赚回来

风险分散

应该适当准备自己的副业,35岁以后可以不在一线开发。

及早做准备


这篇文章虽然写得很短,但是我希望能帮助到大家,这也是我最近悟出的道理。后面我准备出一个系列叫: 如何成功的上班摸鱼 , 欢迎大家订阅我 觉得写得不错可以给个赞

另外我们segmentFault前端交流群还有名额

加我的个人微信:CALASFxiaotan 拉你入群哦~

小姐姐们在等你哦~

查看原文

赞 39 收藏 19 评论 14

Marimo 关注了专栏 · 2019-08-15

前端漫游指南

在前端茫茫宇宙中的一份有温度的指南。定期更新优质原创及翻译文章

关注 14

Marimo 收藏了文章 · 2019-08-15

【Cute-Webpack】Webpack4 入门手册(共 18 章)

webpack_bg

介绍

1. 背景

最近和部门老大,一起在研究团队【EFT - 前端新手村】的建设,目的在于:帮助新人快速了解和融入公司团队帮助零基础新人学习和入门前端开发并且达到公司业务开发水平

本文也是属于【EFT - 前端新手村】的一部分,用来帮助新人快速入门 Webpack4,内容偏基础,当然也可以作为复习材料~~这里分享给各位小伙伴啦!

2. 文章概要

我将从最基础的【项目初始化】开始介绍,到【处理 CSS / JS / 图片】,到【热更新,打包优化】等等,一一介绍和实践。

文章共分为 18 章,关于最基础的四个核心概念,可以到我整理的另一篇文章 《Webpack4 的四个核心概念》 中学习。

3. 教程目录

教程目录

一、 项目初始化

1. 初始化 demo

新建并进入文件夹 leo:

mkdir leo
cd leo

然后本地安装 webpackwebpack-cli在 Webpack 4.0以后需要单独安装):

npm install webpack webpack-cli --save-dev

初始化项目结构:

+ ├─package.json
+ ├─dist          // 存放最终打包的文件
+ │  └─index.html
+ ├─src           // 存放入口文件等开发文件
+ │  └─index.js
+ ├─webpack.config.js  // webpack的配置文件

安装 lodash

npm install lodash --save-dev

--save 可以简写为-S, --save-dev可以简写为-D.

开发 index.js

import _ from 'lodash';

function createElement(){
    let div = document.createElement('div');
    div.innerHTML = _.join(['my', 'name', 'is', 'leo'], '');
    return div;
}
document.body.appendChild(createElement());

开发 webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index.js',
    mode: 'development',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    }
}

2. 打包测试

开始第一次打包任务:

npx webpack

// 输出:

Hash: 030b37b6b9a0b4344437
Version: webpack 4.39.1Time: 308ms
Built at: 2019-08-07 08:10:21
  Asset     Size  Chunks             Chunk Names
main.js  552 KiB    main  [emitted]  main
Entrypoint main = main.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {main} [built][./src/index.js] 225 bytes {main} [built]
    + 1 hidden module

打包成功后,生成的文件会保存在 dist 目录中。

现在在 dist/index.html 中引入打包后的 main.js,打开浏览器测试:

<script data-original="./main.js"></script>

二、 webpack 处理 CSS 模块

这一部分,我们开始学着使用 webpack 去处理 css 相关的模块。

1. 修改代码

在项目 src 目录中,新建 style 文件夹,并新建 index.css 文件:

  ├─package.json
  ├─dist          // 存放最终打包的文件
  │  └─index.html
  ├─src           // 存放入口文件等开发文件
  │  ├─index.js
+ │  └─style
+ │     └─index.css
  ├─webpack.config.js  // webpack的配置文件

接着在 index.js 的新建元素方法中,添加 classbox,这样新建的元素就带有 boxclass 属性:

// src/index.js

import _ from 'lodash';
import './style/index.css';// 引入样式文件

function createElement(){
  let div = document.createElement('div');
  div.innerHTML = _.join(['my', 'name', 'is', 'leo'], '');
+ div.className = 'box';
  return div;
}
document.body.appendChild(createElement());

然后在 index.css 文件为 box

// src/style/index.css

.box{
    color: red;
}

注意:

这里使用 import './style/index.css'; 引入我们的样式文件,是没办法解析使用,这时我们需要在 webpack 中使用到第三方 loader 插件,这里我们使用:

  • css-loader : 用于处理 css 文件,使得能在 js 文件中引入使用;
  • style-loader : 用于将 css 文件注入到 index.html 中的 <style> 标签上;

2. 安装配置插件

安装插件:

npm install --save-dev style-loader css-loader

再到 webpack.config.js 中添加 css 解析的 loader 配置:

// webpack.config.js

module: {
  rules: [
    {
      test: /\.css$/,
      use: ["style-loader", "css-loader"]
    }
  ]
}

参数介绍:

test需要匹配的模块后缀名
use:对应处理的 loader 插件名称(处理顺序是从右往左)。

3. 打包测试

npx webpack

// 输出:

Hash: 28b3965aa1b6a0047536
Version: webpack 4.39.1
Time: 482msBuilt at: 2019-08-09 07:45:25  Asset     Size  Chunks             Chunk Names
main.js  565 KiB    main  [emitted]  main
Entrypoint main = main.js
[./node_modules/_css-loader@3.2.0@css-loader/dist/cjs.js!./src/style/index.css] 190 bytes {main} [built]
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built][./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {main} [built][./src/index.js] 303 bytes {main} [built]
[./src/style/index.css] 447 bytes {main} [built]
    + 3 hidden modules

这时候可以看见 index.html 中,文本已经变成红色,并且 css 代码已经添加到 <style> 标签上。

webpack01

三、 webpack 模块介绍和处理 sass

在这一节中,我们会介绍 webpack 中的模块,并且介绍如何去处理 sass 文件。

1. webpack 模块介绍

这里介绍的模块(module)是指 webpack.config.js 文件中的 module 配置,它决定了如何处理项目中的不同类型模块。

比如上一节介绍的,使用 style-loader css-loader 两个插件去处理 css 文件。

webpack 模块支持如下语句:

  • ES2015 import 语句;
  • CommonJS require() 语句;
  • AMD definerequire 语句;
  • css/sass/less 文件中 @import 语句;
  • 样式 (url(...)) 或者 HTML 文件 (<img data-original=...>) 中的图片链接 (image url)

这里建议使用 ES2015 的引入方法,毕竟这是标准。

更多参数介绍,可访问中文官网的介绍:
《webpack 配置选项》

2. 常用模块

2.1 module.noParse

值的类型:RegExp | [RegExp] | function

防止 webpack 解析那些符合匹配条件的文件,忽略的文件夹中不应该含有 importrequiredefine的调用,或任何其他导入机制,忽略的 library 可以提高构建效率

// webpack.config.js

module: {
  noParse: function(content){
    return /jquery|lodash/.test(content);
  }
}

2.2 module.rules

创建模块时,匹配请求的规则数组。按照规则为对应模块使用对应的 loader,或修改解析器(parser)。

// webpack.config.js

module: {
  rules: [
    { test: /\.css$/, use: ['style-loader', 'css-loader']}
  ]
}
  • module.rules 参数有:

use:为模块使用指定 loader,并且可以传入一个字符串数组,加载顺序从右往左

  • module.rules 匹配条件有:

{test : Condition}匹配特定条件,非必传,支持一个正则表达式正则表达式数组
{include : Condition}匹配特定条件,非必传,支持一个字符串字符串数组
{exclude : Condition}排除特定条件,非必传,支持一个字符串字符串数组
{and : [Condition]}:必须匹配数组中的所有条件;
{or : [Condition]}:匹配数组中任一条件;
{not : [Condition]}:必须排除这个条件;

更多参数介绍,可访问中文官网的介绍:
《Rule》
// webpack.config.js

module: {
  rules: [
    { 
      test: /\.css$/, 
      use: ['style-loader', 'css-loader'],
      include: [
        path.resolve(__dirname, "app/style.css"),
        path.resolve(__dirname, "vendor/style.css")
      ]
    }
  ]
}

3. 加载 Sass 文件

需要使用到 sass-loader 的插件,这里先安装:

npm install sass-loader node-sass --save-dev

src/style 目录下添加 leo.scss 文件,并添加内容:

// leo.scss

$bg-color: #ee3;
.box{
    background-color: $bg-color;
}

然后在 src/index.js 中引入 leo.scss 文件:

// src/index.js
import './style/leo.scss';

npx webpack 重新打包,并打开 dist/index.html 可以看到背景颜色已经添加上去:

webpack03

4. 添加快捷打包命令

npx webpack 这个命令我们需要经常使用,对于这种命令,我们可以把它写成命令,方便每次使用。

我们在 package.jsonscripts 中添加一个命令为 build,以后打包只要执行 npm run build 即可:

"scripts": {
  "build": "npx webpack -c webpack.config.js"
},

这里的 -c webpack.config.js 中,-c 后面跟着的是 webpack 配置文件的文件名,默认可以不写。

四、 webpack 开启 SourceMap 和添加 CSS3 前缀

添加 SourceMap 是为了方便打包之后,我们在项目中调试样式,定位到样式在源文件的位置。

1. 开启 SourceMap

css-loadersass-loader 都可以通过设置 options 选项启用 sourceMap

// webpack.config.js

rules: [
  {
    test: /\.(sc|c|sa)ss$/,
    use: [
      "style-loader", 
      {
        loader:"css-loader",
        options:{ sourceMap: true }
      },
      {
        loader:"sass-loader",
        options:{ sourceMap: true }
      },
    ]
  }
]

再重新打包,看下 index.html 的样式,样式已经定位到源文件上了:

webpack04

这样我们在开发过程中,调试样式就方便很多了。

2. 为样式添加 CSS3 前缀

这里我们用到 PostCSS 这个 loader,它是一个 CSS 预处理工具,可以为 CSS3 的属性添加前缀,样式格式校验(stylelint),提前使用 CSS 新特性,实现 CSS 模块化,防止 CSS 样式冲突。

首先安装 PostCSS

npm install postcss-loader autoprefixer --save-dev

另外还有:

  • postcss-cssnext 可以让我们使用 CSS4 的样式,并能配合 autoprefixer 进行浏览器部分兼容的补全,还支持嵌套语法。
  • precss 类似 scss 语法,如果我们只需要使用嵌套,就可以用它替换 scss
  • postcss-import 让我们可以在@import CSS文件的时 webpack 能监听并编译。
更多参数介绍,可访问中文官网的介绍:
《postcss-loader》

开始添加 postcss-loader 并设置 autoprefixer

// webpack.config.js

rules: [
  {
    test: /\.(sc|c|sa)ss$/,
    use: [
      "style-loader", 
      {
        loader:"css-loader",
        options:{ sourceMap: true }
      },
      {
        loader:"postcss-loader",
        options: {
          ident: "postcss",
          sourceMap: true,
          plugins: loader => [
            require('autoprefixer')(),
            // 这里可以使用更多配置,如上面提到的 postcss-cssnext 等
            // require('postcss-cssnext')()
          ]
        }
      },
      {
        loader:"sass-loader",
        options:{ sourceMap: true }
      },
    ]
  }
]

还需要在 package.json 中添加判断浏览器版本:

// package.json

{
  //...
  "browserslist": [
    "> 1%", // 全球浏览器使用率大于1%,最新两个版本并且是IE8以上的浏览器,加前缀 
    "last 2 versions",
    "not ie <= 8"
  ]
}

为了做测试,我们修改 src/style/leo.scss.box 的样式:

// src/style/leo.scss

.box{
    background-color: $bg-color;
    display: flex;
}

然后重新打包,可以看见 CSS3 属性的前缀已经添加上去了:

webpack05

五、 webpack 将 CSS 抽取成单独文件

在之前学习中,CSS 样式代码都是写到 index.html<style> 标签中,这样样式代码多了以后,很不方便。

于是我们需要将这些样式打包成单独的 CSS 文件。

webpack4 开始使用 mini-css-extract-plugin 插件,而在 1-3 版本使用 extract-text-webpack-plugin

注意:抽取样式以后,就不能使用 style-loader 注入到 html 中。

安装插件:

npm install mini-css-extract-plugin --save-dev

引入插件:

// webpack.config.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

然后修改 rules,将 style-loader,替换成 MiniCssExtractPlugin.loader ,然后添加 plugins 配置项:

// webpack.config.js

module: {
  rules: [
    {
      test: /\.(sc|c|sa)ss$/,
      use: [
        MiniCssExtractPlugin.loader, 
        {
          loader:"css-loader",
          options:{ sourceMap: true }
        },
        {
          loader:"postcss-loader",
          options: {
            ident: "postcss",
            sourceMap: true,
            plugins: loader => [require('autoprefixer')()]
          }
        },
        {
          loader:"sass-loader",
          options:{ sourceMap: true }
        },
      ]
    }
  ]
},
plugins: [
  new MiniCssExtractPlugin({
    filename: '[name].css', // 最终输出的文件名
    chunkFilename: '[id].css'
  })
]

然后重新打包,这时候可以看到我们 dist 目录下就多了个 main.css 文件:

webpack06

因为现在已经将 CSS 都抽取成单独文件,所以在 dist/index.html 中,我们需要手动引入 main.css 了:

// index.html

<link rel="stylesheet" href="main.css">

六、 webpack 压缩 CSS 和 JS

为了缩小打包后包的体积,我们经常做优化的时候,将 CSS 和 JS 文件进行压缩,这里需要使用到不同的插件。

1. 压缩 CSS

使用 optimize-css-assets-webpack-plugin 压缩 CSS 的插件。

安装插件:

npm install optimize-css-assets-webpack-plugin --save-dev

使用插件:

// webpack.config.js

// ... 省略
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
  // ... 省略
  plugins: [
    // ... 省略
    new OptimizeCssAssetsPlugin({})
  ],
}

重新打包,可以看到 main.css 已经被压缩成一行代码,即压缩成功~

2. 压缩 JS

使用 uglifyjs-webpack-plugin 压缩 JS 的插件。

安装插件:

npm install uglifyjs-webpack-plugin --save-dev

引入插件:

// webpack.config.js

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

使用插件:

// webpack.config.js
// ... 省略
module.exports = {
  // ... 省略
  plugins: [
    // ... 省略
    new OptimizeCssAssetsPlugin({}),
    new UglifyJsPlugin({
      cache: true, parallel: true, sourceMap: true
    })
  ],
}

其中 UglifyJsPlugin 的参数:

cache:当 JS 没有发生变化则不压缩;
parallel:是否启用并行压缩;
sourceMap:是否启用 sourceMap;

然后重新打包,查看 main.js,已经被压缩了:

webpack07

七、webpack 为文件名添加 hash 值

由于我们打包出来的 cssjs 文件是静态文件,就存在缓存问题,因此我们可以给文件名添加 hash 值,防止缓存。

1. 添加 hash 值

直接在 webpack.config.js 中,为需要添加 hash 值的文件名添加 [hash] 就可以:

// webpack.config.js

module.exports = {
  // ... 省略其他
  output: {
    filename: 'main.[hash].js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css',
      chunkFilename: '[id].[hash].css'
    }),
  ],
}

配置完成后,重新打包,就可以看到文件名中包含了 hash 值了:

webpack08

2. 动态引用打包后的文件

由于我们前面给打包的文件名添加了 hash 值,会导致 index.html 引用文件错误,所以我们需要让它能动态引入打包后的文件。

这里我们使用 HtmlWebpackPlugin 插件,它可以把打包后的 CSS 或者 JS 文件直接引用注入到 HTML 模版中,就不用每次手动修改。

安装插件:

npm install html-webpack-plugin --save-dev

引入插件:

// webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');

使用插件:

// webpack.config.js

plugins: [
  new HtmlWebpackPlugin({
    title: "leo study!",   // 生成的文件标题
    filename: "main.html", // 最终生成的文件名
    minify: { // 压缩选项
      collapseWhitespace: true, // 移除空格
      removeComments: true, // 移除注释
      removeAttributeQuotes: true, // 移除双引号
    }
  })
],

关于 html-webpack-plugin 更多介绍可以《查看文档》https://github.com/jantimon/h...

接着我们打包以后,可以看见 dist 目录下,多了 main.html 的文件,格式化以后,可以看出,已经动态引入打包后的 CSS 文件和 JS 文件了:

webpack09

八、 webpack 清理目录插件

在之前,我们每次打包都会生成新的文件,并且在添加 hash 值以后,文件名不会出现重复的情况,导致旧文件的冗余。

为了解决这个问题,我们需要在每次打包之前,将 /dist 目录清空,再进行打包。

这里我们使用 clean-webpack-plugin 插件来实现。

安装插件:

npm install clean-webpack-plugin --save-dev

引入插件:

// webpack.config.js

const CleanWebpackPlugin = require('clean-webpack-plugin');

使用插件:

// webpack.config.js

plugins: [
  new CleanWebpackPlugin()
],

参数 cleanOnceBeforeBuildPatterns 是表示需要清除的文件夹。

这样我们每次打包之前,都会先将 /dist 目录清空一次,再执行打包。

更多参数介绍,可访问中文官网的介绍:
《clean-webpack-plugin》

九、 webpack 图片处理和优化

1. 图片处理

在项目中引入图片:

// src/style/leo.scss

.box{
    background-color: $bg-color;
    display: flex;
    background: url('./../assets/logo.jpg')
}

这时候我们如果直接打包,会报错。

我们需要使用 file-loader 插件来处理文件导入的问题。

安装插件:

npm install file-loader --save-dev

使用插件:

// webpack.config.js

module: {
  {
    test: /\.(png|svg|jpg|jpeg|gif)$/,
    use: ["file-loader"]
  }]
},

重新打包以后,发现 dist 目录下多了一个如 373e5e0e214390f8aa9e7abb4c7c635c.jpg 名称的文件,这就是我们打包后的图片。

webpack10

2. 图片优化

更进一步,我们可以对图片进行压缩和优化,这里我们用到 image-webpack-loader 插件来处理。

安装插件:

npm install image-webpack-loader --save-dev

使用插件:

// webpack.config.js

module: {
  {
    test: /\.(png|svg|jpg|jpeg|gif)$/,
    include: [path.resolve(__dirname, 'src/')],
    use: ["file-loader",{
        loader: "image-webpack-loader",
        options: {
          mozjpeg: { progressive: true, quality: 65 },
          optipng: { enabled: false },
          pngquant: { quality: '65-90', speed: 4 },
          gifsicle: { interlaced: false },
          webp: { quality: 75 }
        }
      },
    ]
  }]
},
更多参数介绍,可访问中文官网的介绍:
《image-webpack-loader》

再重新打包,我们可以看到图片打包前后,压缩了很大:

webpack11

十、 webpack 图片 base64 和字体处理

1. 图片 base64 处理

url-loader 功能类似于 file-loader,可以将 url 地址对应的文件,打包成 base64 的 DataURL,提高访问效率。

安装插件:

npm install url-loader --save-dev

使用插件:

注意:这里需要将前面配置的 image-webpack-loader 先删除掉,在使用 url-loader
// webpack.config.js

module: {
  {
    test: /\.(png|svg|jpg|jpeg|gif)$/,
    include: [path.resolve(__dirname, 'src/')],
    use: [
      {
        loader: 'url-loader', // 根据图片大小,把图片转换成 base64
          options: { limit: 10000 }, 
      },
      {
        loader: "image-webpack-loader",
        options: {
          mozjpeg: { progressive: true, quality: 65 },
          optipng: { enabled: false },
          pngquant: { quality: '65-90', speed: 4 },
          gifsicle: { interlaced: false },
          webp: { quality: 75 }
        }
      },
    ]
  }]
},
更多参数介绍,可访问中文官网的介绍:
《url-loader》

2. 字体处理

字体处理的方式和图片处理方式是一样的,只是我们在配置 rules 时的 test 值不相同:

// webpack.config.js

module: {
  {
    test: /\.(woff|woff2|eot|ttf|otf)$/,
    include: [path.resolve(__dirname, 'src/')],
    use: [ 'file-loader' ]
  }
},

十一、 webpack 配置合并和提取公共配置

在开发环境(development)和生产环境(production)配置文件有很多不同,但也有部分相同,为了不每次更换环境的时候都修改配置,我们就需要将配置文件做合并,和提取公共配置。

我们使用 webpack-merge 工具,将两份配置文件合并。

安装插件:

npm install webpack-merge --save-dev

然后调整目录结构,为了方便,我们将原来 webpack.config.js 文件修改名称为 webpack.commen.js,并复制两份相同的文件出来,分别修改文件名为 webpack.prod.jswebpack.dev.js

  ├─package.json
  ├─dist
  ├─src
- ├─webpack.config.js
+ ├─webpack.common.js  // webpack 公共配置文件
+ ├─webpack.prod.js    // webpack 生产环境配置文件
+ ├─webpack.dev.js     // webpack 开发环境配置文件

由于我们文件调整了,所以在 package.json 中,打包命令也需要调整,并且配置 mode 模式。

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
- "build": "npx webpack -c webpack.config.js",
+ "build": "npx webpack -c webpack.dev.js --mode development",
+ "dist": "npx webpack -c webpack.prod.js --mode production"
},

1. 调整 webpack.common.js

我们先调整 webpack.common.js 文件,将通用的配置保留,不是通用的配置删除,结果如下:

// webpack.common.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  module: {
    noParse: function (content) {return /jquery|lodash/.test(content);},
    rules: [
    {
      test: /\.(png|svg|jpg|jpeg|gif)$/,
      include: [path.resolve(__dirname, 'src/')],
      use: [{
        loader: 'url-loader', // 根据图片大小,把图片转换成 base64
        options: { limit: 10000 },
      },{
        loader: "image-webpack-loader",
        options: {
          mozjpeg: { progressive: true, quality: 65 },
          optipng: { enabled: false },
          pngquant: { quality: '65-90', speed: 4 },
          gifsicle: { interlaced: false },
          webp: { quality: 75 }
        }
      }]
    },{
      test: /\.(woff|woff2|eot|ttf|otf)$/,
      include: [path.resolve(__dirname, 'src/')],
      use: [ 'file-loader' ]
    }]
  },
  plugins: [
      new HtmlWebpackPlugin({
          title: "leo study!",
          filename: "main.html",
          template: path.resolve(__dirname, 'src/index.html'), 
          minify: {
              collapseWhitespace: true,
              removeComments: true,
              removeAttributeQuotes: true,
          }
      }),
      new CleanWebpackPlugin()
  ],
}

2. 安装 babel-loader

安装 babel-loader 是为了将 ES6 及以上版本的 JS 代码转换成 ES5。

npm install babel-loader @babel/core @babel/preset-env --save-dev

使用插件:

// webpack.common.js

rules: [
  // ... 省略其他
  {
    test: /\.js$/,
    use: [{
      loader: 'babel-loader',
      options: {
        presets: ['@babel/preset-env']
      }
    }],
    exclude: /(node_modules|bower_components)/,
  }
]

关于 babel-loader 更多介绍可以《查看文档》https://webpack.js.org/loader...

3. 调整 webpack.dev.js

这里我们就需要用到 merge-webpack 插件进行配置合并了:

// webpack.dev.js

const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

let devConfig = {
  mode: 'development',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [{
      test: /\.(sc|c|sa)ss$/,
      use: [
        'style-loader', {
          loader: "css-loader",
          options: { sourceMap: true }
        }, {
          loader: "postcss-loader",
          options: {
              ident: "postcss", sourceMap: true,
              plugins: loader => [ require('autoprefixer')() ]
          }
        }, {
          loader: "sass-loader",
          options: { sourceMap: true }
        }
      ]
    }]
  }
}
module.exports = merge(common, devConfig)

4. 调整 webpack.prod.js

同样对于生产环境的配置,我们也需要用 merge-webpack 插件进行配置合并:

// webpack.prod.js

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

let prodConfig = {
  mode: 'production',
  output: {
    filename: 'main.[hash].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [{
      test: /\.(sc|c|sa)ss$/,
      use: [
        MiniCssExtractPlugin.loader, {
          loader: "css-loader",
          options: { sourceMap: true }
        },  {
          loader: "postcss-loader",
          options: {
            ident: "postcss", sourceMap: true,
            plugins: loader => [ require('autoprefixer')() ]
          }
        }, {
          loader: "sass-loader",
          options: { sourceMap: true }
        }
      ]
    }]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css',
      chunkFilename: '[id].[hash].css'
    }),
    new OptimizeCssAssetsPlugin({}),
    new UglifyJsPlugin({
      cache: true, parallel: true, sourceMap: true
    }),
  ],
}
module.exports = merge(common, prodConfig)

十二、 webpack 监控自动编译和启用 js 的 sourceMap

1. 开启 js 的 sourceMap

当 webpack 打包源代码后,就很难追踪到错误和警告在源代码的位置。

如将三个源文件打包一个 bundle 中,其中一个文件的代码报错,那么堆栈追中就会指向 bundle

为了能方便定位错误,我们使用 inline-source-map 选项,注意不要在生产环境中使用。

// webpack.dev.js

let devConfig = {
  // ... 省略其他
+  devtool: 'inline-source-map'
}

2. 测试 sourceMap

为了测试是否成功,我们将 src/index.js 代码中,在第 12 行上,添加一句日志打印。

// src/index.js

// ... 省略其他
+ console.log(111)

对比下开启 sourceMap 前后的区别:

webpack12

3. 开启监控自动编译

如果每次我们修改完代码,都要手动编译,那是多累的一件事。

为此我们使用 --watch 命令,让我们每次保存完,都会自动编译。

为此,我们需要在 package.json 中的打包命令添加 --watch 命令:

// package.json

- "build": "npx webpack --config webpack.dev.js",
+ "build": "npx webpack --config webpack.dev.js --watch",

这里仅对开发环境开启,生产环境不需要使用。

十三、 webpack 热更新

上一节介绍监控自动编译,当我们保存文件后,会自动编译文件,但是我们还是需要手动去刷新页面,才能看到编译后的结果。

于是为了自动编译之后,再自动重新加载,我们就可以使用 webpack-dev-server 来启动一个简单 web 服务器,实时重新加载。

1. 开启热更新

插件安装:

npm install webpack-dev-server --save-dev

使用插件:

// webpack.dev.js

const webpack = require('webpack');
const webpack = require('webpack');

let devConfig = {
  // ... 省略其他
  devServer: {
    contentBase: path.join(__dirname, 'dist'), 
    compress: true,
    hot: true,
    overlay: true, 
    open:true,
    publicPath: '/',
    host: 'localhost',
    port: '1200'
 }
 plugins: [
    new webpack.NamedModulesPlugin(), // 更容易查看(patch)的以来
    new webpack.HotModuleReplacementPlugin() // 替换插件
 ]
}

启动热更新:

npx webpack-dev-server --config webpack.dev.js

常用配置:

contentBase: path.join(__dirname, 'dist'), //本地服务器所加载的页面所在的目录
clinetLogLevel: 'warning', // 可能值有 none, error, warning 或者 info (默认值)
hot:true,//启动热更新替换特性,需要配合 webpack.HotModuleReplacementPlugin 插件
host:'0.0.0.0', // 启动服务器的 host
port:7000,      // 端口号
compress:true,  // 为所有服务启用gzip压缩
overlay: true,  // 在浏览器中显示全屏覆盖
stats: "errors-only" ,// 只显示包中的错误
open:true, // 启用“打开”后,dev服务器将打开浏览器。
proxy: {   // 设置代理
    "/api": {
        target: "http://localhost:3000",
        pathRewrite: {"^/api" : ""}
    }
}

这时候我们访问 http://localhost:1200/main.html 就可以看到页面,并且修改文件,页面也会同时刷新。

2. 优化命令

我们可以将 npx webpack-dev-server --config webpack.dev.js 写到 package.json 中作为一个命令:

// package.json

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build": "npx webpack --config webpack.dev.js --watch",
  "dist": "npx webpack --config webpack.prod.js",
+ "watch": "npx webpack-dev-server --config webpack.dev.js"
},

十四、 webpack 设置代理服务器和 babel 转换及优化

1. 设置代理服务器

接着上一节,接下来给 webpack 设置代理服务器:

// webpack.dev.js

let devConfig = {
  // ... 省略其他
  devServer: {
    // ... 省略其他
    proxy: { 
      "/api": { // 以 '/api' 开头的请求,会跳转到下面的 target 配置
        target: "http://192.168.30.33:8080",
        pathRewrite: {
          "^api": "/mock/api"
        }
    }
 }
}

最后当我们请求 /api/getuser 接口,就会转发到 http://192.168.30.33:8080/mock/api

2. babel 转换及优化

babel-loader 插件的安装,已经提前介绍,在【十一、 webpack 配置合并和提取公共配置】中。

这里讲一下 babel-loader 的优化。

babel-loader 可以配置 cacheDirectory 来提高打包效率:

  • cacheDirectory:默认值 false,开启后构建时会缓存文件夹,后续从缓存中读取,将提高打包效率。

十五、 webpack 开启 Eslint

安装插件:

npm install eslint eslint-loader --save-dev

另外还需要安装 eslint 解释器、校验规则等:

npm install babel-loader standard --save-dev

2. 添加 .eslintrc.js

在项目根目录创建 .eslintrc.js,指定 eslint 规则。

这份配置内容有点多,可以去 我的 gist 复制https://gist.github.com/pinga...

3. 添加 .eslintignore

在项目根目录创建 .eslintignore,指定 eslint 忽略一些文件不校验,比如内容可以是:

/dist/
/node_modules/

十六、 webpack 解析模块拓展名和别名

在 webpack 配置中,我们使用 resolve 来配置模块解析方式。

这是非常重要的,比如 import _ from 'lodash' ,其实是加载解析了 lodash.js 文件。

该配置就是用来设置加载和解析的方式。

在解析过程中,我们可以进行配置:

1. resolve.alias

当我们引入一些文件时,需要写很长的路径,这样使得代码更加复杂。

为此我们可以使用 resolve.alias,创建 importrequire 的别名,使模块引入更加简单。

使用配置:

// webpack.common.js

module.exports = {
  entry: './src/index.js',
+ resolve: {
+   alias: {
+     '@' : path.resolve(__dirname, 'src/')
+   }
+ }
  // 省略其他
}

alias 参数的含义:

使用 @ 来替代 path.resolve(__dirname, 'src/') 这个路径,接下来我们测试看看。

我们在 src/ 目录下新增 leo.js

// leo.js

export let name = 'pingan';

再到 src/index.js 中引入:

// index.js

import { name } from '@/leo.js';

这样就能正常引入。

当然,我们也可以根据实际情况,为不同路径设置不同别名

// webpack.common.js


alias: {
  '@' : path.resolve(__dirname, 'src/')
+ 'assets' : path.resolve(__dirname, 'src/assets/')
}
更多参数介绍,可访问中文官网的介绍:
《resolve》

2. resolve.extensions

resolve.extensions 用来自动解析确定的扩展,让我们在引入模块的时候,可以不用设置拓展名,默认值为:

extensions: [".js", ".json"]

使用配置:

// webpack.common.js

import { name } from '@/leo';

十七、 webpack 配置外部拓展

当我们使用 CDN 引入 jquery 时,我们并不想把它也打包到项目中,我们就可以配置 externals 外部拓展的选项,来将这些不需要打包的模块从输出的 bundle 中排除:

<script data-original="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>

配置 externals

// webpack.common.js

module.exports = {
  // ... 省略其他
+ externals: {
+   jquery: 'jQuery'
+ },
}

通过上面配置,我们就不会把不需要打包的模块打包进来。并且下面代码正常运行:

import $ from 'jquery';

$('.leo').show();
更多参数介绍,可访问中文官网的介绍:
《externals》

十八、 webpack 打包分析报表及优化总结

1. 生成报表

这里我们使用 webpack-bundle-analyzer 插件,来对打包后的文件进行数据分析,从来找到项目优化的方向。

webpack-bundle-analyzer 使用交互式可缩放树形图可视化 webpack 输出文件的大小。

安装插件:

npm install webpack-bundle-analyzer --save-dev

这个我们只有在开发环境中使用。

使用插件:

// webpack.dev.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

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

配置完成以后,我们执行 npm run build 打包,打包完成后,会自动打开一个数据报表分析的页面,地址是 http://127.0.0.1:8888/

webpack14

webpack-bundle-analyzer 将帮助我们:

  • 看清楚我们包内都包含什么模块;
  • 准确看出每个模块的组成;
  • 最后优化它!

我们经常将报表中区域最大的模块进行优化!

2. 通过报表优化项目

webpack14

我们可以看出,打包后的项目中 lodash.js 占了非常大的内存,我们就针对 lodash.js 进行优化。

我们将 lodash.js 改为 CDN 引入:

// index.html

<script data-original="https://cdn.bootcss.com/lodash.js/4.17.15/lodash.js"></script>

然后去设置上一节讲到的 externals

// webpack.common.js

externals: {
  jquery: 'jQuery',
+ lodash: '_'
},

再打包以后,可以看到 lodash.js 已经不在包里面了:

webpack15

并且打包后的文件,也能正常运行:

webpack16

更多参数介绍,可访问中文官网的介绍:
《webpack-bundle-analyzer》

参考资料

总结

本文是根据 《2019最新Webpack4.0教程4.x 成仙之路》 学习总结下来的学习之路,适合入门,涉及范围较多,内容比较长,需要能静下心来学习。

内容如果有误,欢迎留言指点,我会及时修改。

本文代码最终托管在我的 github 上,点击查看

希望自己的文章会对各位有所帮助,也欢迎各位大佬指点。

Author王平安
E-mailpingan8787@qq.com
博 客www.pingan8787.com
微 信pingan8787
每日文章推荐https://github.com/pingan8787...
ES小册js.pingan8787.com

微信公众号

bg

查看原文

Marimo 赞了文章 · 2019-08-15

【Cute-Webpack】Webpack4 入门手册(共 18 章)

webpack_bg

介绍

1. 背景

最近和部门老大,一起在研究团队【EFT - 前端新手村】的建设,目的在于:帮助新人快速了解和融入公司团队帮助零基础新人学习和入门前端开发并且达到公司业务开发水平

本文也是属于【EFT - 前端新手村】的一部分,用来帮助新人快速入门 Webpack4,内容偏基础,当然也可以作为复习材料~~这里分享给各位小伙伴啦!

2. 文章概要

我将从最基础的【项目初始化】开始介绍,到【处理 CSS / JS / 图片】,到【热更新,打包优化】等等,一一介绍和实践。

文章共分为 18 章,关于最基础的四个核心概念,可以到我整理的另一篇文章 《Webpack4 的四个核心概念》 中学习。

3. 教程目录

教程目录

一、 项目初始化

1. 初始化 demo

新建并进入文件夹 leo:

mkdir leo
cd leo

然后本地安装 webpackwebpack-cli在 Webpack 4.0以后需要单独安装):

npm install webpack webpack-cli --save-dev

初始化项目结构:

+ ├─package.json
+ ├─dist          // 存放最终打包的文件
+ │  └─index.html
+ ├─src           // 存放入口文件等开发文件
+ │  └─index.js
+ ├─webpack.config.js  // webpack的配置文件

安装 lodash

npm install lodash --save-dev

--save 可以简写为-S, --save-dev可以简写为-D.

开发 index.js

import _ from 'lodash';

function createElement(){
    let div = document.createElement('div');
    div.innerHTML = _.join(['my', 'name', 'is', 'leo'], '');
    return div;
}
document.body.appendChild(createElement());

开发 webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index.js',
    mode: 'development',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    }
}

2. 打包测试

开始第一次打包任务:

npx webpack

// 输出:

Hash: 030b37b6b9a0b4344437
Version: webpack 4.39.1Time: 308ms
Built at: 2019-08-07 08:10:21
  Asset     Size  Chunks             Chunk Names
main.js  552 KiB    main  [emitted]  main
Entrypoint main = main.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {main} [built][./src/index.js] 225 bytes {main} [built]
    + 1 hidden module

打包成功后,生成的文件会保存在 dist 目录中。

现在在 dist/index.html 中引入打包后的 main.js,打开浏览器测试:

<script data-original="./main.js"></script>

二、 webpack 处理 CSS 模块

这一部分,我们开始学着使用 webpack 去处理 css 相关的模块。

1. 修改代码

在项目 src 目录中,新建 style 文件夹,并新建 index.css 文件:

  ├─package.json
  ├─dist          // 存放最终打包的文件
  │  └─index.html
  ├─src           // 存放入口文件等开发文件
  │  ├─index.js
+ │  └─style
+ │     └─index.css
  ├─webpack.config.js  // webpack的配置文件

接着在 index.js 的新建元素方法中,添加 classbox,这样新建的元素就带有 boxclass 属性:

// src/index.js

import _ from 'lodash';
import './style/index.css';// 引入样式文件

function createElement(){
  let div = document.createElement('div');
  div.innerHTML = _.join(['my', 'name', 'is', 'leo'], '');
+ div.className = 'box';
  return div;
}
document.body.appendChild(createElement());

然后在 index.css 文件为 box

// src/style/index.css

.box{
    color: red;
}

注意:

这里使用 import './style/index.css'; 引入我们的样式文件,是没办法解析使用,这时我们需要在 webpack 中使用到第三方 loader 插件,这里我们使用:

  • css-loader : 用于处理 css 文件,使得能在 js 文件中引入使用;
  • style-loader : 用于将 css 文件注入到 index.html 中的 <style> 标签上;

2. 安装配置插件

安装插件:

npm install --save-dev style-loader css-loader

再到 webpack.config.js 中添加 css 解析的 loader 配置:

// webpack.config.js

module: {
  rules: [
    {
      test: /\.css$/,
      use: ["style-loader", "css-loader"]
    }
  ]
}

参数介绍:

test需要匹配的模块后缀名
use:对应处理的 loader 插件名称(处理顺序是从右往左)。

3. 打包测试

npx webpack

// 输出:

Hash: 28b3965aa1b6a0047536
Version: webpack 4.39.1
Time: 482msBuilt at: 2019-08-09 07:45:25  Asset     Size  Chunks             Chunk Names
main.js  565 KiB    main  [emitted]  main
Entrypoint main = main.js
[./node_modules/_css-loader@3.2.0@css-loader/dist/cjs.js!./src/style/index.css] 190 bytes {main} [built]
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built][./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {main} [built][./src/index.js] 303 bytes {main} [built]
[./src/style/index.css] 447 bytes {main} [built]
    + 3 hidden modules

这时候可以看见 index.html 中,文本已经变成红色,并且 css 代码已经添加到 <style> 标签上。

webpack01

三、 webpack 模块介绍和处理 sass

在这一节中,我们会介绍 webpack 中的模块,并且介绍如何去处理 sass 文件。

1. webpack 模块介绍

这里介绍的模块(module)是指 webpack.config.js 文件中的 module 配置,它决定了如何处理项目中的不同类型模块。

比如上一节介绍的,使用 style-loader css-loader 两个插件去处理 css 文件。

webpack 模块支持如下语句:

  • ES2015 import 语句;
  • CommonJS require() 语句;
  • AMD definerequire 语句;
  • css/sass/less 文件中 @import 语句;
  • 样式 (url(...)) 或者 HTML 文件 (<img data-original=...>) 中的图片链接 (image url)

这里建议使用 ES2015 的引入方法,毕竟这是标准。

更多参数介绍,可访问中文官网的介绍:
《webpack 配置选项》

2. 常用模块

2.1 module.noParse

值的类型:RegExp | [RegExp] | function

防止 webpack 解析那些符合匹配条件的文件,忽略的文件夹中不应该含有 importrequiredefine的调用,或任何其他导入机制,忽略的 library 可以提高构建效率

// webpack.config.js

module: {
  noParse: function(content){
    return /jquery|lodash/.test(content);
  }
}

2.2 module.rules

创建模块时,匹配请求的规则数组。按照规则为对应模块使用对应的 loader,或修改解析器(parser)。

// webpack.config.js

module: {
  rules: [
    { test: /\.css$/, use: ['style-loader', 'css-loader']}
  ]
}
  • module.rules 参数有:

use:为模块使用指定 loader,并且可以传入一个字符串数组,加载顺序从右往左

  • module.rules 匹配条件有:

{test : Condition}匹配特定条件,非必传,支持一个正则表达式正则表达式数组
{include : Condition}匹配特定条件,非必传,支持一个字符串字符串数组
{exclude : Condition}排除特定条件,非必传,支持一个字符串字符串数组
{and : [Condition]}:必须匹配数组中的所有条件;
{or : [Condition]}:匹配数组中任一条件;
{not : [Condition]}:必须排除这个条件;

更多参数介绍,可访问中文官网的介绍:
《Rule》
// webpack.config.js

module: {
  rules: [
    { 
      test: /\.css$/, 
      use: ['style-loader', 'css-loader'],
      include: [
        path.resolve(__dirname, "app/style.css"),
        path.resolve(__dirname, "vendor/style.css")
      ]
    }
  ]
}

3. 加载 Sass 文件

需要使用到 sass-loader 的插件,这里先安装:

npm install sass-loader node-sass --save-dev

src/style 目录下添加 leo.scss 文件,并添加内容:

// leo.scss

$bg-color: #ee3;
.box{
    background-color: $bg-color;
}

然后在 src/index.js 中引入 leo.scss 文件:

// src/index.js
import './style/leo.scss';

npx webpack 重新打包,并打开 dist/index.html 可以看到背景颜色已经添加上去:

webpack03

4. 添加快捷打包命令

npx webpack 这个命令我们需要经常使用,对于这种命令,我们可以把它写成命令,方便每次使用。

我们在 package.jsonscripts 中添加一个命令为 build,以后打包只要执行 npm run build 即可:

"scripts": {
  "build": "npx webpack -c webpack.config.js"
},

这里的 -c webpack.config.js 中,-c 后面跟着的是 webpack 配置文件的文件名,默认可以不写。

四、 webpack 开启 SourceMap 和添加 CSS3 前缀

添加 SourceMap 是为了方便打包之后,我们在项目中调试样式,定位到样式在源文件的位置。

1. 开启 SourceMap

css-loadersass-loader 都可以通过设置 options 选项启用 sourceMap

// webpack.config.js

rules: [
  {
    test: /\.(sc|c|sa)ss$/,
    use: [
      "style-loader", 
      {
        loader:"css-loader",
        options:{ sourceMap: true }
      },
      {
        loader:"sass-loader",
        options:{ sourceMap: true }
      },
    ]
  }
]

再重新打包,看下 index.html 的样式,样式已经定位到源文件上了:

webpack04

这样我们在开发过程中,调试样式就方便很多了。

2. 为样式添加 CSS3 前缀

这里我们用到 PostCSS 这个 loader,它是一个 CSS 预处理工具,可以为 CSS3 的属性添加前缀,样式格式校验(stylelint),提前使用 CSS 新特性,实现 CSS 模块化,防止 CSS 样式冲突。

首先安装 PostCSS

npm install postcss-loader autoprefixer --save-dev

另外还有:

  • postcss-cssnext 可以让我们使用 CSS4 的样式,并能配合 autoprefixer 进行浏览器部分兼容的补全,还支持嵌套语法。
  • precss 类似 scss 语法,如果我们只需要使用嵌套,就可以用它替换 scss
  • postcss-import 让我们可以在@import CSS文件的时 webpack 能监听并编译。
更多参数介绍,可访问中文官网的介绍:
《postcss-loader》

开始添加 postcss-loader 并设置 autoprefixer

// webpack.config.js

rules: [
  {
    test: /\.(sc|c|sa)ss$/,
    use: [
      "style-loader", 
      {
        loader:"css-loader",
        options:{ sourceMap: true }
      },
      {
        loader:"postcss-loader",
        options: {
          ident: "postcss",
          sourceMap: true,
          plugins: loader => [
            require('autoprefixer')(),
            // 这里可以使用更多配置,如上面提到的 postcss-cssnext 等
            // require('postcss-cssnext')()
          ]
        }
      },
      {
        loader:"sass-loader",
        options:{ sourceMap: true }
      },
    ]
  }
]

还需要在 package.json 中添加判断浏览器版本:

// package.json

{
  //...
  "browserslist": [
    "> 1%", // 全球浏览器使用率大于1%,最新两个版本并且是IE8以上的浏览器,加前缀 
    "last 2 versions",
    "not ie <= 8"
  ]
}

为了做测试,我们修改 src/style/leo.scss.box 的样式:

// src/style/leo.scss

.box{
    background-color: $bg-color;
    display: flex;
}

然后重新打包,可以看见 CSS3 属性的前缀已经添加上去了:

webpack05

五、 webpack 将 CSS 抽取成单独文件

在之前学习中,CSS 样式代码都是写到 index.html<style> 标签中,这样样式代码多了以后,很不方便。

于是我们需要将这些样式打包成单独的 CSS 文件。

webpack4 开始使用 mini-css-extract-plugin 插件,而在 1-3 版本使用 extract-text-webpack-plugin

注意:抽取样式以后,就不能使用 style-loader 注入到 html 中。

安装插件:

npm install mini-css-extract-plugin --save-dev

引入插件:

// webpack.config.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

然后修改 rules,将 style-loader,替换成 MiniCssExtractPlugin.loader ,然后添加 plugins 配置项:

// webpack.config.js

module: {
  rules: [
    {
      test: /\.(sc|c|sa)ss$/,
      use: [
        MiniCssExtractPlugin.loader, 
        {
          loader:"css-loader",
          options:{ sourceMap: true }
        },
        {
          loader:"postcss-loader",
          options: {
            ident: "postcss",
            sourceMap: true,
            plugins: loader => [require('autoprefixer')()]
          }
        },
        {
          loader:"sass-loader",
          options:{ sourceMap: true }
        },
      ]
    }
  ]
},
plugins: [
  new MiniCssExtractPlugin({
    filename: '[name].css', // 最终输出的文件名
    chunkFilename: '[id].css'
  })
]

然后重新打包,这时候可以看到我们 dist 目录下就多了个 main.css 文件:

webpack06

因为现在已经将 CSS 都抽取成单独文件,所以在 dist/index.html 中,我们需要手动引入 main.css 了:

// index.html

<link rel="stylesheet" href="main.css">

六、 webpack 压缩 CSS 和 JS

为了缩小打包后包的体积,我们经常做优化的时候,将 CSS 和 JS 文件进行压缩,这里需要使用到不同的插件。

1. 压缩 CSS

使用 optimize-css-assets-webpack-plugin 压缩 CSS 的插件。

安装插件:

npm install optimize-css-assets-webpack-plugin --save-dev

使用插件:

// webpack.config.js

// ... 省略
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
  // ... 省略
  plugins: [
    // ... 省略
    new OptimizeCssAssetsPlugin({})
  ],
}

重新打包,可以看到 main.css 已经被压缩成一行代码,即压缩成功~

2. 压缩 JS

使用 uglifyjs-webpack-plugin 压缩 JS 的插件。

安装插件:

npm install uglifyjs-webpack-plugin --save-dev

引入插件:

// webpack.config.js

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

使用插件:

// webpack.config.js
// ... 省略
module.exports = {
  // ... 省略
  plugins: [
    // ... 省略
    new OptimizeCssAssetsPlugin({}),
    new UglifyJsPlugin({
      cache: true, parallel: true, sourceMap: true
    })
  ],
}

其中 UglifyJsPlugin 的参数:

cache:当 JS 没有发生变化则不压缩;
parallel:是否启用并行压缩;
sourceMap:是否启用 sourceMap;

然后重新打包,查看 main.js,已经被压缩了:

webpack07

七、webpack 为文件名添加 hash 值

由于我们打包出来的 cssjs 文件是静态文件,就存在缓存问题,因此我们可以给文件名添加 hash 值,防止缓存。

1. 添加 hash 值

直接在 webpack.config.js 中,为需要添加 hash 值的文件名添加 [hash] 就可以:

// webpack.config.js

module.exports = {
  // ... 省略其他
  output: {
    filename: 'main.[hash].js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css',
      chunkFilename: '[id].[hash].css'
    }),
  ],
}

配置完成后,重新打包,就可以看到文件名中包含了 hash 值了:

webpack08

2. 动态引用打包后的文件

由于我们前面给打包的文件名添加了 hash 值,会导致 index.html 引用文件错误,所以我们需要让它能动态引入打包后的文件。

这里我们使用 HtmlWebpackPlugin 插件,它可以把打包后的 CSS 或者 JS 文件直接引用注入到 HTML 模版中,就不用每次手动修改。

安装插件:

npm install html-webpack-plugin --save-dev

引入插件:

// webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');

使用插件:

// webpack.config.js

plugins: [
  new HtmlWebpackPlugin({
    title: "leo study!",   // 生成的文件标题
    filename: "main.html", // 最终生成的文件名
    minify: { // 压缩选项
      collapseWhitespace: true, // 移除空格
      removeComments: true, // 移除注释
      removeAttributeQuotes: true, // 移除双引号
    }
  })
],

关于 html-webpack-plugin 更多介绍可以《查看文档》https://github.com/jantimon/h...

接着我们打包以后,可以看见 dist 目录下,多了 main.html 的文件,格式化以后,可以看出,已经动态引入打包后的 CSS 文件和 JS 文件了:

webpack09

八、 webpack 清理目录插件

在之前,我们每次打包都会生成新的文件,并且在添加 hash 值以后,文件名不会出现重复的情况,导致旧文件的冗余。

为了解决这个问题,我们需要在每次打包之前,将 /dist 目录清空,再进行打包。

这里我们使用 clean-webpack-plugin 插件来实现。

安装插件:

npm install clean-webpack-plugin --save-dev

引入插件:

// webpack.config.js

const CleanWebpackPlugin = require('clean-webpack-plugin');

使用插件:

// webpack.config.js

plugins: [
  new CleanWebpackPlugin()
],

参数 cleanOnceBeforeBuildPatterns 是表示需要清除的文件夹。

这样我们每次打包之前,都会先将 /dist 目录清空一次,再执行打包。

更多参数介绍,可访问中文官网的介绍:
《clean-webpack-plugin》

九、 webpack 图片处理和优化

1. 图片处理

在项目中引入图片:

// src/style/leo.scss

.box{
    background-color: $bg-color;
    display: flex;
    background: url('./../assets/logo.jpg')
}

这时候我们如果直接打包,会报错。

我们需要使用 file-loader 插件来处理文件导入的问题。

安装插件:

npm install file-loader --save-dev

使用插件:

// webpack.config.js

module: {
  {
    test: /\.(png|svg|jpg|jpeg|gif)$/,
    use: ["file-loader"]
  }]
},

重新打包以后,发现 dist 目录下多了一个如 373e5e0e214390f8aa9e7abb4c7c635c.jpg 名称的文件,这就是我们打包后的图片。

webpack10

2. 图片优化

更进一步,我们可以对图片进行压缩和优化,这里我们用到 image-webpack-loader 插件来处理。

安装插件:

npm install image-webpack-loader --save-dev

使用插件:

// webpack.config.js

module: {
  {
    test: /\.(png|svg|jpg|jpeg|gif)$/,
    include: [path.resolve(__dirname, 'src/')],
    use: ["file-loader",{
        loader: "image-webpack-loader",
        options: {
          mozjpeg: { progressive: true, quality: 65 },
          optipng: { enabled: false },
          pngquant: { quality: '65-90', speed: 4 },
          gifsicle: { interlaced: false },
          webp: { quality: 75 }
        }
      },
    ]
  }]
},
更多参数介绍,可访问中文官网的介绍:
《image-webpack-loader》

再重新打包,我们可以看到图片打包前后,压缩了很大:

webpack11

十、 webpack 图片 base64 和字体处理

1. 图片 base64 处理

url-loader 功能类似于 file-loader,可以将 url 地址对应的文件,打包成 base64 的 DataURL,提高访问效率。

安装插件:

npm install url-loader --save-dev

使用插件:

注意:这里需要将前面配置的 image-webpack-loader 先删除掉,在使用 url-loader
// webpack.config.js

module: {
  {
    test: /\.(png|svg|jpg|jpeg|gif)$/,
    include: [path.resolve(__dirname, 'src/')],
    use: [
      {
        loader: 'url-loader', // 根据图片大小,把图片转换成 base64
          options: { limit: 10000 }, 
      },
      {
        loader: "image-webpack-loader",
        options: {
          mozjpeg: { progressive: true, quality: 65 },
          optipng: { enabled: false },
          pngquant: { quality: '65-90', speed: 4 },
          gifsicle: { interlaced: false },
          webp: { quality: 75 }
        }
      },
    ]
  }]
},
更多参数介绍,可访问中文官网的介绍:
《url-loader》

2. 字体处理

字体处理的方式和图片处理方式是一样的,只是我们在配置 rules 时的 test 值不相同:

// webpack.config.js

module: {
  {
    test: /\.(woff|woff2|eot|ttf|otf)$/,
    include: [path.resolve(__dirname, 'src/')],
    use: [ 'file-loader' ]
  }
},

十一、 webpack 配置合并和提取公共配置

在开发环境(development)和生产环境(production)配置文件有很多不同,但也有部分相同,为了不每次更换环境的时候都修改配置,我们就需要将配置文件做合并,和提取公共配置。

我们使用 webpack-merge 工具,将两份配置文件合并。

安装插件:

npm install webpack-merge --save-dev

然后调整目录结构,为了方便,我们将原来 webpack.config.js 文件修改名称为 webpack.commen.js,并复制两份相同的文件出来,分别修改文件名为 webpack.prod.jswebpack.dev.js

  ├─package.json
  ├─dist
  ├─src
- ├─webpack.config.js
+ ├─webpack.common.js  // webpack 公共配置文件
+ ├─webpack.prod.js    // webpack 生产环境配置文件
+ ├─webpack.dev.js     // webpack 开发环境配置文件

由于我们文件调整了,所以在 package.json 中,打包命令也需要调整,并且配置 mode 模式。

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
- "build": "npx webpack -c webpack.config.js",
+ "build": "npx webpack -c webpack.dev.js --mode development",
+ "dist": "npx webpack -c webpack.prod.js --mode production"
},

1. 调整 webpack.common.js

我们先调整 webpack.common.js 文件,将通用的配置保留,不是通用的配置删除,结果如下:

// webpack.common.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  module: {
    noParse: function (content) {return /jquery|lodash/.test(content);},
    rules: [
    {
      test: /\.(png|svg|jpg|jpeg|gif)$/,
      include: [path.resolve(__dirname, 'src/')],
      use: [{
        loader: 'url-loader', // 根据图片大小,把图片转换成 base64
        options: { limit: 10000 },
      },{
        loader: "image-webpack-loader",
        options: {
          mozjpeg: { progressive: true, quality: 65 },
          optipng: { enabled: false },
          pngquant: { quality: '65-90', speed: 4 },
          gifsicle: { interlaced: false },
          webp: { quality: 75 }
        }
      }]
    },{
      test: /\.(woff|woff2|eot|ttf|otf)$/,
      include: [path.resolve(__dirname, 'src/')],
      use: [ 'file-loader' ]
    }]
  },
  plugins: [
      new HtmlWebpackPlugin({
          title: "leo study!",
          filename: "main.html",
          template: path.resolve(__dirname, 'src/index.html'), 
          minify: {
              collapseWhitespace: true,
              removeComments: true,
              removeAttributeQuotes: true,
          }
      }),
      new CleanWebpackPlugin()
  ],
}

2. 安装 babel-loader

安装 babel-loader 是为了将 ES6 及以上版本的 JS 代码转换成 ES5。

npm install babel-loader @babel/core @babel/preset-env --save-dev

使用插件:

// webpack.common.js

rules: [
  // ... 省略其他
  {
    test: /\.js$/,
    use: [{
      loader: 'babel-loader',
      options: {
        presets: ['@babel/preset-env']
      }
    }],
    exclude: /(node_modules|bower_components)/,
  }
]

关于 babel-loader 更多介绍可以《查看文档》https://webpack.js.org/loader...

3. 调整 webpack.dev.js

这里我们就需要用到 merge-webpack 插件进行配置合并了:

// webpack.dev.js

const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

let devConfig = {
  mode: 'development',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [{
      test: /\.(sc|c|sa)ss$/,
      use: [
        'style-loader', {
          loader: "css-loader",
          options: { sourceMap: true }
        }, {
          loader: "postcss-loader",
          options: {
              ident: "postcss", sourceMap: true,
              plugins: loader => [ require('autoprefixer')() ]
          }
        }, {
          loader: "sass-loader",
          options: { sourceMap: true }
        }
      ]
    }]
  }
}
module.exports = merge(common, devConfig)

4. 调整 webpack.prod.js

同样对于生产环境的配置,我们也需要用 merge-webpack 插件进行配置合并:

// webpack.prod.js

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

let prodConfig = {
  mode: 'production',
  output: {
    filename: 'main.[hash].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [{
      test: /\.(sc|c|sa)ss$/,
      use: [
        MiniCssExtractPlugin.loader, {
          loader: "css-loader",
          options: { sourceMap: true }
        },  {
          loader: "postcss-loader",
          options: {
            ident: "postcss", sourceMap: true,
            plugins: loader => [ require('autoprefixer')() ]
          }
        }, {
          loader: "sass-loader",
          options: { sourceMap: true }
        }
      ]
    }]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css',
      chunkFilename: '[id].[hash].css'
    }),
    new OptimizeCssAssetsPlugin({}),
    new UglifyJsPlugin({
      cache: true, parallel: true, sourceMap: true
    }),
  ],
}
module.exports = merge(common, prodConfig)

十二、 webpack 监控自动编译和启用 js 的 sourceMap

1. 开启 js 的 sourceMap

当 webpack 打包源代码后,就很难追踪到错误和警告在源代码的位置。

如将三个源文件打包一个 bundle 中,其中一个文件的代码报错,那么堆栈追中就会指向 bundle

为了能方便定位错误,我们使用 inline-source-map 选项,注意不要在生产环境中使用。

// webpack.dev.js

let devConfig = {
  // ... 省略其他
+  devtool: 'inline-source-map'
}

2. 测试 sourceMap

为了测试是否成功,我们将 src/index.js 代码中,在第 12 行上,添加一句日志打印。

// src/index.js

// ... 省略其他
+ console.log(111)

对比下开启 sourceMap 前后的区别:

webpack12

3. 开启监控自动编译

如果每次我们修改完代码,都要手动编译,那是多累的一件事。

为此我们使用 --watch 命令,让我们每次保存完,都会自动编译。

为此,我们需要在 package.json 中的打包命令添加 --watch 命令:

// package.json

- "build": "npx webpack --config webpack.dev.js",
+ "build": "npx webpack --config webpack.dev.js --watch",

这里仅对开发环境开启,生产环境不需要使用。

十三、 webpack 热更新

上一节介绍监控自动编译,当我们保存文件后,会自动编译文件,但是我们还是需要手动去刷新页面,才能看到编译后的结果。

于是为了自动编译之后,再自动重新加载,我们就可以使用 webpack-dev-server 来启动一个简单 web 服务器,实时重新加载。

1. 开启热更新

插件安装:

npm install webpack-dev-server --save-dev

使用插件:

// webpack.dev.js

const webpack = require('webpack');
const webpack = require('webpack');

let devConfig = {
  // ... 省略其他
  devServer: {
    contentBase: path.join(__dirname, 'dist'), 
    compress: true,
    hot: true,
    overlay: true, 
    open:true,
    publicPath: '/',
    host: 'localhost',
    port: '1200'
 }
 plugins: [
    new webpack.NamedModulesPlugin(), // 更容易查看(patch)的以来
    new webpack.HotModuleReplacementPlugin() // 替换插件
 ]
}

启动热更新:

npx webpack-dev-server --config webpack.dev.js

常用配置:

contentBase: path.join(__dirname, 'dist'), //本地服务器所加载的页面所在的目录
clinetLogLevel: 'warning', // 可能值有 none, error, warning 或者 info (默认值)
hot:true,//启动热更新替换特性,需要配合 webpack.HotModuleReplacementPlugin 插件
host:'0.0.0.0', // 启动服务器的 host
port:7000,      // 端口号
compress:true,  // 为所有服务启用gzip压缩
overlay: true,  // 在浏览器中显示全屏覆盖
stats: "errors-only" ,// 只显示包中的错误
open:true, // 启用“打开”后,dev服务器将打开浏览器。
proxy: {   // 设置代理
    "/api": {
        target: "http://localhost:3000",
        pathRewrite: {"^/api" : ""}
    }
}

这时候我们访问 http://localhost:1200/main.html 就可以看到页面,并且修改文件,页面也会同时刷新。

2. 优化命令

我们可以将 npx webpack-dev-server --config webpack.dev.js 写到 package.json 中作为一个命令:

// package.json

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build": "npx webpack --config webpack.dev.js --watch",
  "dist": "npx webpack --config webpack.prod.js",
+ "watch": "npx webpack-dev-server --config webpack.dev.js"
},

十四、 webpack 设置代理服务器和 babel 转换及优化

1. 设置代理服务器

接着上一节,接下来给 webpack 设置代理服务器:

// webpack.dev.js

let devConfig = {
  // ... 省略其他
  devServer: {
    // ... 省略其他
    proxy: { 
      "/api": { // 以 '/api' 开头的请求,会跳转到下面的 target 配置
        target: "http://192.168.30.33:8080",
        pathRewrite: {
          "^api": "/mock/api"
        }
    }
 }
}

最后当我们请求 /api/getuser 接口,就会转发到 http://192.168.30.33:8080/mock/api

2. babel 转换及优化

babel-loader 插件的安装,已经提前介绍,在【十一、 webpack 配置合并和提取公共配置】中。

这里讲一下 babel-loader 的优化。

babel-loader 可以配置 cacheDirectory 来提高打包效率:

  • cacheDirectory:默认值 false,开启后构建时会缓存文件夹,后续从缓存中读取,将提高打包效率。

十五、 webpack 开启 Eslint

安装插件:

npm install eslint eslint-loader --save-dev

另外还需要安装 eslint 解释器、校验规则等:

npm install babel-loader standard --save-dev

2. 添加 .eslintrc.js

在项目根目录创建 .eslintrc.js,指定 eslint 规则。

这份配置内容有点多,可以去 我的 gist 复制https://gist.github.com/pinga...

3. 添加 .eslintignore

在项目根目录创建 .eslintignore,指定 eslint 忽略一些文件不校验,比如内容可以是:

/dist/
/node_modules/

十六、 webpack 解析模块拓展名和别名

在 webpack 配置中,我们使用 resolve 来配置模块解析方式。

这是非常重要的,比如 import _ from 'lodash' ,其实是加载解析了 lodash.js 文件。

该配置就是用来设置加载和解析的方式。

在解析过程中,我们可以进行配置:

1. resolve.alias

当我们引入一些文件时,需要写很长的路径,这样使得代码更加复杂。

为此我们可以使用 resolve.alias,创建 importrequire 的别名,使模块引入更加简单。

使用配置:

// webpack.common.js

module.exports = {
  entry: './src/index.js',
+ resolve: {
+   alias: {
+     '@' : path.resolve(__dirname, 'src/')
+   }
+ }
  // 省略其他
}

alias 参数的含义:

使用 @ 来替代 path.resolve(__dirname, 'src/') 这个路径,接下来我们测试看看。

我们在 src/ 目录下新增 leo.js

// leo.js

export let name = 'pingan';

再到 src/index.js 中引入:

// index.js

import { name } from '@/leo.js';

这样就能正常引入。

当然,我们也可以根据实际情况,为不同路径设置不同别名

// webpack.common.js


alias: {
  '@' : path.resolve(__dirname, 'src/')
+ 'assets' : path.resolve(__dirname, 'src/assets/')
}
更多参数介绍,可访问中文官网的介绍:
《resolve》

2. resolve.extensions

resolve.extensions 用来自动解析确定的扩展,让我们在引入模块的时候,可以不用设置拓展名,默认值为:

extensions: [".js", ".json"]

使用配置:

// webpack.common.js

import { name } from '@/leo';

十七、 webpack 配置外部拓展

当我们使用 CDN 引入 jquery 时,我们并不想把它也打包到项目中,我们就可以配置 externals 外部拓展的选项,来将这些不需要打包的模块从输出的 bundle 中排除:

<script data-original="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>

配置 externals

// webpack.common.js

module.exports = {
  // ... 省略其他
+ externals: {
+   jquery: 'jQuery'
+ },
}

通过上面配置,我们就不会把不需要打包的模块打包进来。并且下面代码正常运行:

import $ from 'jquery';

$('.leo').show();
更多参数介绍,可访问中文官网的介绍:
《externals》

十八、 webpack 打包分析报表及优化总结

1. 生成报表

这里我们使用 webpack-bundle-analyzer 插件,来对打包后的文件进行数据分析,从来找到项目优化的方向。

webpack-bundle-analyzer 使用交互式可缩放树形图可视化 webpack 输出文件的大小。

安装插件:

npm install webpack-bundle-analyzer --save-dev

这个我们只有在开发环境中使用。

使用插件:

// webpack.dev.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

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

配置完成以后,我们执行 npm run build 打包,打包完成后,会自动打开一个数据报表分析的页面,地址是 http://127.0.0.1:8888/

webpack14

webpack-bundle-analyzer 将帮助我们:

  • 看清楚我们包内都包含什么模块;
  • 准确看出每个模块的组成;
  • 最后优化它!

我们经常将报表中区域最大的模块进行优化!

2. 通过报表优化项目

webpack14

我们可以看出,打包后的项目中 lodash.js 占了非常大的内存,我们就针对 lodash.js 进行优化。

我们将 lodash.js 改为 CDN 引入:

// index.html

<script data-original="https://cdn.bootcss.com/lodash.js/4.17.15/lodash.js"></script>

然后去设置上一节讲到的 externals

// webpack.common.js

externals: {
  jquery: 'jQuery',
+ lodash: '_'
},

再打包以后,可以看到 lodash.js 已经不在包里面了:

webpack15

并且打包后的文件,也能正常运行:

webpack16

更多参数介绍,可访问中文官网的介绍:
《webpack-bundle-analyzer》

参考资料

总结

本文是根据 《2019最新Webpack4.0教程4.x 成仙之路》 学习总结下来的学习之路,适合入门,涉及范围较多,内容比较长,需要能静下心来学习。

内容如果有误,欢迎留言指点,我会及时修改。

本文代码最终托管在我的 github 上,点击查看

希望自己的文章会对各位有所帮助,也欢迎各位大佬指点。

Author王平安
E-mailpingan8787@qq.com
博 客www.pingan8787.com
微 信pingan8787
每日文章推荐https://github.com/pingan8787...
ES小册js.pingan8787.com

微信公众号

bg

查看原文

赞 62 收藏 55 评论 10

Marimo 赞了文章 · 2019-08-14

如何在React中优雅的处理doubleClick

背景

上午楼主遇到一个需要处理双击事件的需求,在这里介绍下如何在触发doubleCLick时间的时候, 不触发click事件的解决办法, 顺便分享给大家。

问题阐述

首先, 我们的DOM 是天然支持dbClick 事件的, 线上demo:

https://codepen.io/scaukk/pen...

可以清晰的看到, 双击之后, 触发处理双击事件的逻辑, 但是同时也触发了两次click事件:

clipboard.png

这个副作用不是我们预期的, 需要处理一下。

解决办法

解决办法也很简单: 延迟 click事件的处理, 直到判断这个click 不在 doubleClick 中。

原理

这个延迟的click事件会放在一个 Promise 队列中, 并处于pending状态。

当doubleClick事件触发之后, 就取消所有的Pending Promises, 这些事件也就不会执行。

可取消的Promise

要处理这些处于 penging 状态的Promise, 我们需要用到可取消的Promise, 这个话题我在另一片文章中讨论过, 有兴趣的可以看一下。

下面是一个可以cancel的Promise的简单实现:

export const cancellablePromise = promise => {
  let isCanceled = false;

  const wrappedPromise = new Promise((resolve, reject) => {
    promise.then(
      value => (isCanceled ? reject({ isCanceled, value }) : resolve(value)),
      error => reject({ isCanceled, error }),
    );
  });

  return {
    promise: wrappedPromise,
    cancel: () => (isCanceled = true),
  };
};

要解决开头提到的这个问题, 我们就需要用到这个大杀器。

先看下最终的结果,双击一下:

clipboard.png

主要代码:

const EnhancedClickableBox = stopTriggerClicksOnDoubleClick(ClickableBox)

const DoubleClickExample = () => (
  <EnhancedClickableBox
    onClick={() => console.log("on click")}
    onDoubleClick={() => console.log("on double click")}
  />
);

const App = () => {
  return (
    <DoubleClickExample />
  )
}

ReactDOM.render(<App />, document.getElementById("app"));

线上Demo:

https://codepen.io/scaukk/pen...

Hooks 版本

const ClickableBox = ({ onClick, onDoubleClick }) => {
  const [handleClick, handleDoubleClick] = useClickPreventionOnDoubleClick(onClick, onDoubleClick);

  return (
    <button onClick={handleClick} onDoubleClick={handleDoubleClick}>
      Click or double click
    </button>
  );
};


const DoubleClickExample = () => (
  <ClickableBox
    onClick={() => console.log("on click")} 
    onDoubleClick={() => console.log("on double click")}/>
);



const App = () => {
  return (
   <DoubleClickExample />
  )
}

ReactDOM.render(<App />, document.getElementById("app"));

https://codepen.io/scaukk/pen...

是不是很简单, 学到了吧 XD

结语

处理双击事件的时候, 最好还是处理掉不必要的click调用, 免得产生bug.

如果文章对你有帮助, 点个赞支持一下呗。

文中若有错误,欢迎指出, 欢迎留言交流。

最后

clipboard.png

![图片上传中...]

查看原文

赞 10 收藏 4 评论 1

Marimo 关注了问题 · 2019-08-14

nginx反向代理出现301问题

问题描述

在用nginx进行反向代理的时候出现301:
日志代码:"GET /api/search_tags?type=tv&tag=%E7%83%AD%E9%97%A8&source=index HTTP/1.1" 301
nginx设置:
location /api {

        proxy_pass   http://movie.douban.com/j;
    }

浏览器就会出现直接访问的情况:
Request URL: https://movie.douban.com/j/se...
Referrer Policy: no-referrer-when-downgrade
但是用Postman可以正常返回数据:图片描述

想问下怎么解决

关注 3 回答 2

Marimo 提出了问题 · 2019-08-14

nginx反向代理出现301问题

问题描述

在用nginx进行反向代理的时候出现301:
日志代码:"GET /api/search_tags?type=tv&tag=%E7%83%AD%E9%97%A8&source=index HTTP/1.1" 301
nginx设置:
location /api {

        proxy_pass   http://movie.douban.com/j;
    }

浏览器就会出现直接访问的情况:
Request URL: https://movie.douban.com/j/se...
Referrer Policy: no-referrer-when-downgrade
但是用Postman可以正常返回数据:图片描述

想问下怎么解决

关注 3 回答 2

Marimo 提出了问题 · 2019-08-07

解决create-react-app在centOS环境下启动失败

在mac和win下面都正常,在centos用npm start和npm run build都启动失败
npm start错误如下:
/home/douban-movie-web/node_modules/react-dev-utils/WebpackDevServerUtils.js:166
compiler.hooks.done.tap('done', async stats => {

                              ^^^^^

SyntaxError: missing ) after argument list

at createScript (vm.js:56:10)
at Object.runInThisContext (vm.js:97:10)
at Module._compile (module.js:549:28)
at Object.Module._extensions..js (module.js:586:10)
at Module.load (module.js:494:32)
at tryModuleLoad (module.js:453:12)
at Function.Module._load (module.js:445:3)
at Module.require (module.js:504:17)
at require (internal/module.js:20:19)
at Object.<anonymous> (/home/douban-movie-web/scripts/start.js:29:5)

npm run build 错误如下:
/home/douban-movie/node_modules/@hapi/hoek/lib/deep-equal.js:17

options = { prototype: true, ...options };
                             ^^^

SyntaxError: Unexpected token ...

at createScript (vm.js:56:10)
at Object.runInThisContext (vm.js:97:10)
at Module._compile (module.js:549:28)
at Object.Module._extensions..js (module.js:586:10)
at Module.load (module.js:494:32)
at tryModuleLoad (module.js:453:12)
at Function.Module._load (module.js:445:3)
at Module.require (module.js:504:17)
at require (internal/module.js:20:19)
at Object.<anonymous> (/home/douban-movie/node_modules/@hapi/hoek/lib/index.js:9:19)
at Module._compile (module.js:577:32)
at Object.Module._extensions..js (module.js:586:10)
at Module.load (module.js:494:32)
at tryModuleLoad (module.js:453:12)
at Function.Module._load (module.js:445:3)
at Module.require (module.js:504:17)

关注 2 回答 1

认证与成就

  • 获得 0 次点赞
  • 获得 6 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 6 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-01-30
个人主页被 124 人浏览