1. Webpack简介
Webpack是一个开源的JavaScript模块打包工具,其最核心的功能是解决模块之间的依赖,把各个模块按照特定的规则和顺序组织在一起,最终合并为一个JS文件(有时会有多个,这里讨论的只是最基本的情况)。这个过程就叫作模块打包。
-
手动引入script文件的缺点:
- 需要手动维护JavaScript的加载顺序
- 每一个script标签都需要向服务器发送一次请求,增加负担
- 每个script中,顶层作用域是全局作用域,会造成全局作用域污染
-
模块化的优点:
- 通过导入导出语句可以清晰地看到模块间的依赖关系
- 模块可以借助工具打包为合并后的资源文件,减少了网络开销
- 各个模块间的作用域隔离,避免了命名冲突
-
模块化打包工具:解决模块间的依赖
工作方式:1.将存在依赖关系的模块按照特定规则合并为单个JS文件,一次全部加载进页面
2.在页面初始时加载一个入口模块,其他模块异步加载
-
流行的打包工具:Webpack Parcel Rollup
2. 模块打包
-
CommonJs(Node.js中版本)
-
模块
CommonJS模块会形成一个属于模块自身的作用域,所有的变量及函数只有自己能访问,对外是不可见
-
导出
moudle.exports = { name: 'calculater', add: function(a,b) { return a+b; } }
-
导入(获取的是一份导出值的拷贝,不会改变该模块)
const calculator = require('./calculator.js'); const sum = calculator.add(2,3);
导入模块时有两种情况:
- require的模块第一次被加载,首先执行该模块,然后导出内容
- require的模块曾被加载过,直接导出上次执行后得到的结果
- 模块会有一个module对象用来存放其信息,这个对象中有一个属性loaded用于记录该模块是否被加载过。它的值默认为false,当模块第一次被加载和执行过后会置为true,后面再次加载时检查到module.loaded为true,则不会再次执行模块代码
- require函数可以接受表达式,可以动态地指定模块加载路径
-
特点
CommonJs对模块依赖地解决是”动态的——模块依赖关系地建立发生在代码运行阶段“,CommonJs模块被执行前,没有办法确定明确的依赖关系,模块的导入导出发生在代码的运行阶段
-
-
ES6 Moudle(只读值的动态映射,不能更改导入值)
ES6 Moudle会自动采用严格模式
-
导出
-
命名导出
const name = 'calculator'; const add = function(){}; export { name, add as getSUm };
-
默认导出
export default { name: 'calulator', add: function(){} }
-
-
导入
// 命名导入 import {name, add} from './calculator.js'; import * as calculator from './calculator.js'; // 默认导入 import myCalculator from './calculator.js'; // 混合导入 import React, { Component } from 'react';
-
特点
- ES6 Moudle对模块依赖是“静态的”——模块依赖关系的建立发生在代码编译阶段
- ES6 Moudle的导入导出语句是声明式的,它不支持导入的路径是一个表达式,并且导入导出语句必须位于模块的顶层作用域(不能放在if语句中)
- 死代码监测和排除
- 模块变量类型检查
- 编译器优化
-
-
AMD(Asyncronous Module Definition异步模块定义)
在AMD中使用define函数来定义模块,它可以接受3个参数。第1个参数是当前模块的id,相当于模块名;第2个参数是当前模块的依赖,比如上面我们定义的getSum模块需要引入calculator模块作为依赖;第3个参数用来描述模块的导出值,可以是函数或对象。如果是函数则导出的是函数的返回值;如果是对象则直接导出对象本身。
// 定义AMD模块 define('getSum', ['calculator'], function(math) { return function(a,b) { // xxx } }) // 异步加载模块 require(['getSum'], function(getSum) { getSum(2,3); })
-
UMD(Universal Moudule Definition通用模块标准)
它的目标是使一个模块能运行在各种环境下,不论是CommonJS、AMD,还是非模块化的环境(当时ES6 Module还未被提出)
UMD其实就是根据当前全局对象中的值判断目前处于哪种模块环境。当前环境是AMD,就以AMD的形式导出;当前环境是CommonJS,就以CommonJS的形式导出。
-
模块打包原理
// 立即执行匿名函数 (function(modules) { //模块缓存 var installedModules = {}; // 实现require function _webpack_require_(moduleId) { ... } // 执行入口模块的加载 return _webpack_require_(_webpack_require_.s = 0); })({ // moudles: 以key-value形式存储所有被打包的模块 0: function(module, exports, _webpack_require_) { // 打包入口 module.exports = _webpack_require_("3qiv"); }, "3qiv": function(module, exports, _webpcak_require_) { // index.js内容 }, jkzz: function(module,exports) { // calculator.js内容 } });
- installedModules对象。每个模块只在第一次被加载的时候执行,之后其导出值就被存储到这个对象里面,当再次被加载的时候直接从这里取值,而不会重新执行。
- webpack_require__函数。对模块加载的实现,在浏览器中可以通过调用_webpack_require(module_id)来完成模块导入
- modules对象。工程中所有产生了依赖关系的模块都会以key-value的形式放在这里。key可以理解为一个模块的id,由数字或者一个很短的hash字符串构成;value则是由一个匿名函数包裹的模块实体,匿名函数的参数则赋予了每个模块导出和导入的能力。
3. 资源输入输出
Webpack会从入口文件开始检索,并将具有依赖关系的模块生成一棵依赖树,最终得到一个chunk(代码块)。由这个chunk得到的打包产物我们一般称之为bundle
-
Webpack通过context和entry这两个配置项来共同决定入口文件的路径,context可以理解为资源入口的路径前缀,在配置时要求必须使用绝对路径的形式,entry的配置可以有多种形式:字符串、数组、对象、函数,传入一个数组的作用是将多个资源预先合并,在打包时Webpack会将数组中的最后一个元素作为实际的入口路径,如果想要定义多入口,则必须使用对象的形式。对象的属性名(key)是chunk name,属性值(value)是入口路径
-
单页应用
在Webpack默认配置中,当一个bundle大于250kB时(压缩前)会认为这个bundle已经过大了,在打包时会发生警告
提取vendor(供应商):在Webpack中vendor一般指的是工程所使用的库、框架等第三方模块集中打包而产生的bundle
通过这样的配置,app.js产生的bundle将只包含业务模块,其依赖的第三方模块将会被抽取出来生成一个新的bundle,由于vendor仅仅包含第三方模块,这部分不会经常变动,因此可以有效地利用客户端缓存,在用户后续请求页面时会加快整体的渲染速度
-
多页应用
每个页面都只加载各自必要的逻辑,而不是将所有页面打包到同一个bundle中。因此每个页面都需要有一个独立的bundle
-
配置资源出口
r仅仅包含第三方模块,这部分不会经常变动,因此可以有效地利用客户端缓存,在用户后续请求页面时会加快整体的渲染速度
-
多页应用
每个页面都只加载各自必要的逻辑,而不是将所有页面打包到同一个bundle中。因此每个页面都需要有一个独立的bundle
[外链图片转存中…(img-F93x3HVZ-1635051292682)]
-
配置资源出口
所有与出口相关的配置都集中在output对象里