webpack务虚扫盲

2019年11月19日 阅读数:136
这篇文章主要向大家介绍webpack务虚扫盲,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

http://www.cnblogs.com/libin-1/p/6938581.htmljavascript

 

打包工具的角色

所谓打包工具在web开发中主要解决的问题是:css

(1)文件依赖管理。毕竟如今都是模块化开发,打包工具首先就是要梳理文件之间的依赖关系。html

(2)资源加载管理。web本质就是html、js和css的文件组合,文件的加载顺序(前后时机)和文件的加载数量(合并、嵌入、拆分)也是打包工具重点要解决的问题。vue

(3)效率与优化管理。提升开发效率,即写最少的代码,作最好的效果展现;尽量的使用工具,减小机械coding和优化页面效果,这个是考验打包工具是否具有魅力的点。java

打包工具的结构

由上图能够推出,打包工具的结构应该是tool+plugins的结构。tool提供基础能力,即文件依赖管理和资源加载管理;在此基础上经过一系列的plugins来丰富打包工具的能力。plugins相似互联网+的概念,文件经plugins处理以后,具有了web渲染中的某种优点。node

 

 为何使用webpack?

决定打包工具能走多远的是plugins的丰富程度,而webpack目前偏偏是最丰富的,我这里对比了一下fis与webpack在npm包上数据,看完就知道为何要使用webpack了。webpack

 

webpack的工做原理

webpack处理文件的过程能够分为两个维度:文件间的关系和文件的内容。文件间的关系处理,主要是经过文件和模块标记方法来实现;文件内容的处理主要经过loaders和plugins来处理。web

 

1.文件内容处理

在webpack的世界里,js是一等公民,是处理的入口,其余资源都是在js中经过相似require的方式引入。webpack虽然支持命令行操做,可是通常将配置写在webpack.conf.js文件中,文件内容是一个配置对象,基本配置项是:entry、ouput、module、plugins属性。正则表达式

entry与output

这里引入了一个chunk的概念,chunk表示一个文件,默认状况下webpack的输入是一个入口文件,输出也是一个文件,这个文件就是一个chunk,chunkId就是产出时给每一个文件一个惟一标识id,chunkhash就是文件内容的md5值,name就是在entry中指定的key值。npm

1
2
3
4
5
6
7
8
9
module.exports = {
     entry: {
         collection:  './src/main.js'      // collection为chunk的名字,chunk的入口文件是main.js
     },
     output: {
         path:  './dist/js' ,
         filename:  '[name].[chunkhash].js'    // 输出到dist/js目录下,以collection+chunk内容的md5值做为输出的文件名
     }
};  

输出:

 

module

moudle对应loader(加载器 )的配置,主要对指定类型的文件进行操做,举个例子:js类型的文件和css文件须要不一样的loader来处理。最经常使用的加载器是eslint-loader和babel-loader。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
     entry: {
         collection:  './src/main.js'      // collection为chunk的名字,chunk的入口文件是main.js
     },
     output: {
         path:  './dist/js' ,
         filename:  '[name].[chunkhash].js'    // 输出到dist/js目录下,以collection+chunk内容的md5值做为输出的文件名
     }
     module: {
         rules: [   // rules为数组,保存每一个加载器的配置
      {
        test: /\.js$/,   // test属性必须配置,值为正则表达式,用于匹配文件
        loader:  'babel-loader?fakeoption=true!eslint-loader' ,   // loader属性必须配置,值为字符串,对于匹配的文件使用babel-loader和eslint-loader处理,处理顺序从右向左,先eslint-loader,后babel-loader,loader之间用!隔开,loader与options用?隔开
        exclude: /node_module/,   // 对于匹配的文件进行过滤,排除node_module目录下的文件
        include:  './src'   // 指定匹配文件的范围
      }
    ] 
     }   
};   

其中,loader的options也能够单独使用options属性来配置

1
2
3
4
5
6
7
8
9
rules: [
     {
         test: /\.js$/,
         loader:  'babel-loader' ,
         options: {
             fakeoption:  true
         }
     }
]  

 另外一般babel-loader的配置项能够写在根目录下的.babelrc文件中

1
2
3
4
5
{
   "presets" : [ "stage-2" ],
   "plugins" : [ "transform-runtime" ]
}
    

plugins

plugins用于扩展webpack的功能,相比着loader更加灵活,不用指定文件类型。经常使用的plugins有三个,html-webpack-plugin、commonChunkPlugin和ExtractTextPlugin。

 

(1)html-webpack-plugin:生成入口html文件,因为webpack输出的js文件须要插入到html文件,以构成web入口;该插件默认有一个html文件模板,可是通常状况下须要为其指定一个html文件做为模板,webpack打包输出的js文件会插入到body结束标签以前。
1
2
3
4
5
6
7
8
9
10
11
var  HtmlwebpackPlugin = require( 'html-webpack-plugin' );
 
module.exports = {
     ...
     plugins: [
         new  HtmlwebpackPlugin({<br>       filename:  'collection.html' ,    // 入口html文件名
             template:  './src/index.html'   // 入口html文件模板
         })
     ]
     ...   
};
最终输出的入口文件以下图,生成的入口文件是在模板文件的基础上插入了打包后的js引用标签。
 
(2)commonChunkPlugin:主要提取公共业务代码与第三方类库代码
在介绍entry的时候,提到了chunk的概念,chunk指的就是一个代码块,即一个js文件。默认的状况下webpack只产生entry中指定的代码块,chunk的个数和entry中的key值个数相等,即单入口的状况下,默认只产出一个chunk。可是咱们一般但愿将入口之间的通用代码和第三方类库的代码提取出来,单独做为一个js文件来引用,第三方的文件通常不多变更,能够利用缓存机制把相关内容缓存起来,通用代码则能够避免重复加载。
commonChunkPlugin的处理级别是chunk级别,经过指定 chunks(输入的文件)+ minChunks(提取过滤器:通常是被引用的次数)+ name(输出的文件名)来完成操做。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
     ...
     plugins: [
         // 把经过npm包引用的第三方类库从入口文件中提取出来
         new  webpack.optimize.CommonsChunkPlugin({
             name:  'vendor' ,
             minChunks:  function  (module, count) {
                 // 指定范围是js文件来自node_modules
                 return  (module.resource && /\.js$/.test(module.resource) &&module.resource.indexOf(path.join(__dirname,  '../node_modules' )) === 0);
             }
         }),
         // 把webpack的module管理相关基础代码从vendor中提取到manifest
         new  webpack.optimize.CommonsChunkPlugin({
           name:  'manifest' ,
           chunks: [ 'vendor' ]
         })
     ]
     ...   
};
 
(3)ExtractTextPlugin:提取css片断到单独css文件

js是一等公民,webpack默认不产出css文件,产出css文件须要依赖ExtractTextPlugin插件来完成。

1
2
3
4
5
6
7
8
9
10
module.exports = {
     ...
     plugins: [
         // 把css片断从入口js文件中提取到对应入口名的css文件中
         new  ExtractTextPlugin({
             filename:  './dist/static/css/[name].[contenthash].css'
         }),
     ]
     ...   
};

  

2.文件间的关系处理

理清这个过程得倒推,先看一下经webpack处理后的js文件,下面的例子中主要涉及3个产出文件,manifest是webpack的module管理代码,vendor是第三方类库文件,collection是入口文件,加载的顺序是manifest-》vendor-》collection。

查看三个文件的内容可知:

vendor和collection的内容都是一个函数,相似jsonp请求回来的返回值。下面分别是vendor和collection中的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
webpackJsonp([0],[   // chunkid为0
/* 0 */
/***/  ( function (module, exports, __webpack_require__) {
     ...
/***/  }),
/* 1 */
/***/  ( function (module, exports) {
     ...
/* 2 */
     ...
/* 9 */
/***/  ( function (module, exports, __webpack_require__) {
     ...
/***/  }),
/* 10 */ ,    // 此处moduleid=10的模块为空
/* 11 */
/***/  ( function (module, exports) {
     ...
/***/  }),
     ...
]);

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
webpackJsonp([1],[  // chunkid为1
/* 0 */ ,   // moduleid为0-9的模块均为空
/* 1 */ ,
/* 2 */ ,
/* 3 */ ,
/* 4 */ ,
/* 5 */ ,
/* 6 */ ,
/* 7 */ ,
/* 8 */ ,
/* 9 */ ,
/* 10 */
/***/  ( function (module, __webpack_exports__, __webpack_require__) {
     ...
};
/***/  }),
/* 11 */ ,
/* 12 */ ,
     ....
/* 59 */
/***/  ( function (module, __webpack_exports__, __webpack_require__) {
 
"use strict" ;
Object.defineProperty(__webpack_exports__,  "__esModule" , { value:  true  });
/* harmony import */  var  __WEBPACK_IMPORTED_MODULE_0_vue__ = __webpack_require__(14);
/* harmony import */  var  __WEBPACK_IMPORTED_MODULE_1_vue_validator__ = __webpack_require__(58);
/* harmony import */  var  __WEBPACK_IMPORTED_MODULE_1_vue_validator___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_vue_validator__);
/* harmony import */  var  __WEBPACK_IMPORTED_MODULE_2__router__ = __webpack_require__(54);
     ...
/***/  }),
     ...
], [59]);   // 此处多了一个参数,参数值为[59],即moduleid=59的模块名是入口模块

 

从以上两个文件能够发现出一个规律,

(1)每一个文件的chunkid放在第一个参数;

(2)模块都放在第二个参数,每一个模块都有对应的id,数组都是从moduleid=0开始,依次增长,若是该模块不在该文件中,则使用空值来代替;

(3)入口文件中的函数多了一个参数,参数里面传入了一个moduleid,视为入口模块。

接下来,咱们看一下manifest文件的内容,来看看webpackJsonp函数到底是怎么运行的。

总的来讲是利用闭包传入了几个自由变量:

modules:模块自己是一个函数,modules用于存储模块函数数组。

installedModules:用于缓存模块的返回值,即module.exports。

installedChunks:用于标记chunk文件是否已经被加载。

webpackJsonp:chunk文件加载后的callback函数,主要将文件中的模块存储到modules对象中,同时标记chunk文件的下载状况,对于入口chunk来讲,等全部的模块都放入modules以后,执行入口模块函数。

__webpack_require__:模块加载函数,加载的策略是:根据moduleid读取,优先读取缓存installedModules,读取失败则读取modules,获取返回值,而后进行缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/******/  ( function (modules) {  // webpackBootstrap
/******/     // install a JSONP callback for chunk loading
/******/     var  parentJsonpFunction = window[ "webpackJsonp" ];
                 // webpackJsonp函数挂在window下,接收三个参数,chunkids:文件的id,moreModules: 文件中的module,executeModules:入口module
/******/     window[ "webpackJsonp" ] =  function  webpackJsonpCallback(chunkIds, moreModules, executeModules) {
/******/         // 把moreModules中的module的放入modules中,缓存起来
/******/         // 利用传入的chunkid在installedChunks标记对应的文件已下载
/******/         var  moduleId, chunkId, i = 0, resolves = [], result;
/******/         for (;i < chunkIds.length; i++) {
/******/             chunkId = chunkIds[i];
/******/             if (installedChunks[chunkId]) {
/******/                 resolves.push(installedChunks[chunkId][0]);
/******/             }
/******/             installedChunks[chunkId] = 0;
/******/         }
/******/         for (moduleId  in  moreModules) {
/******/             if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/                 modules[moduleId] = moreModules[moduleId];
/******/             }
/******/         }
/******/         if (parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
/******/         while (resolves.length) {
/******/             resolves.shift()();
/******/         }
                         // 存在入口模块,则加载对应的模块并执行
/******/         if (executeModules) {
/******/             for (i=0; i < executeModules.length; i++) {
/******/                 result = __webpack_require__(__webpack_require__.s = executeModules[i]);
/******/             }
/******/         }
/******/         return  result;
/******/     };
/******/
/******/     // 模块缓存对象,存放module的exports
/******/     var  installedModules = {};
/******/
/******/     // chunk是否下载标记对象,key为chunkid,值为0表示已经下载
/******/     var  installedChunks = {
/******/         2: 0   // 表示chunkid=2的文件已下载,其实就是manifest文件自己
/******/     };
/******/
/******/     // 模块加载函数:先从缓存读取,没有则从modules中读取module函数,执行后返回exports,最后缓存起来
/******/     function  __webpack_require__(moduleId) {
/******/
/******/         // Check if module is in cache
/******/         if (installedModules[moduleId]) {
/******/             return  installedModules[moduleId].exports;
/******/         }
/******/         // Create a new module (and put it into the cache)
/******/         var  module = installedModules[moduleId] = {
/******/             i: moduleId,
/******/             l:  false ,
/******/             exports: {}
/******/         };
/******/
/******/         // Execute the module function
/******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/         // Flag the module as loaded
/******/         module.l =  true ;
/******/
/******/         // Return the exports of the module
/******/         return  module.exports;
/******/     }
/******/
/******/     // This file contains only the entry chunk.
/******/     // The chunk loading function for additional chunks
/******/     __webpack_require__.e =  function  requireEnsure(chunkId) {
                     ...
/******/     };
/******/
/******/     // expose the modules object (__webpack_modules__)
/******/     __webpack_require__.m = modules;
/******/
/******/     // expose the module cache
/******/     __webpack_require__.c = installedModules;
/******/
/******/     // identity function for calling harmony imports with the correct context
/******/     __webpack_require__.i =  function (value) {  return  value; };
/******/
/******/     // define getter function for harmony exports
/******/     __webpack_require__.d =  function (exports, name, getter) {
/******/         if (!__webpack_require__.o(exports, name)) {
/******/             Object.defineProperty(exports, name, {
/******/                 configurable:  false ,
/******/                 enumerable:  true ,
/******/                 get: getter
/******/             });
/******/         }
/******/     };
/******/
/******/     // getDefaultExport function for compatibility with non-harmony modules
/******/     __webpack_require__.n =  function (module) {
/******/         var  getter = module && module.__esModule ?
/******/             function  getDefault() {  return  module[ 'default' ]; } :
/******/             function  getModuleExports() {  return  module; };
/******/         __webpack_require__.d(getter,  'a' , getter);
/******/         return  getter;
/******/     };
/******/
/******/     // Object.prototype.hasOwnProperty.call
/******/     __webpack_require__.o =  function (object, property) {  return  Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/     // __webpack_public_path__
/******/     __webpack_require__.p =  "./" ;
/******/
/******/     // on error function for async loading
/******/     __webpack_require__.oe =  function (err) { console.error(err);  throw  err; };
/******/  })
/************************************************************************/
/******/  ([]);
//# sourceMappingURL=manifest.e7a81691a524d5b99b6b.js.map

  

整体而言,下图能够简易的描述出webpack打包过程,该过程主要分为三个阶段:module构建、trunk构建和产出三个阶段。

打包工具的角色

所谓打包工具在web开发中主要解决的问题是:

(1)文件依赖管理。毕竟如今都是模块化开发,打包工具首先就是要梳理文件之间的依赖关系。

(2)资源加载管理。web本质就是html、js和css的文件组合,文件的加载顺序(前后时机)和文件的加载数量(合并、嵌入、拆分)也是打包工具重点要解决的问题。

(3)效率与优化管理。提升开发效率,即写最少的代码,作最好的效果展现;尽量的使用工具,减小机械coding和优化页面效果,这个是考验打包工具是否具有魅力的点。

打包工具的结构

由上图能够推出,打包工具的结构应该是tool+plugins的结构。tool提供基础能力,即文件依赖管理和资源加载管理;在此基础上经过一系列的plugins来丰富打包工具的能力。plugins相似互联网+的概念,文件经plugins处理以后,具有了web渲染中的某种优点。

 

 为何使用webpack?

决定打包工具能走多远的是plugins的丰富程度,而webpack目前偏偏是最丰富的,我这里对比了一下fis与webpack在npm包上数据,看完就知道为何要使用webpack了。

 

webpack的工做原理

webpack处理文件的过程能够分为两个维度:文件间的关系和文件的内容。文件间的关系处理,主要是经过文件和模块标记方法来实现;文件内容的处理主要经过loaders和plugins来处理。

 

1.文件内容处理

在webpack的世界里,js是一等公民,是处理的入口,其余资源都是在js中经过相似require的方式引入。webpack虽然支持命令行操做,可是通常将配置写在webpack.conf.js文件中,文件内容是一个配置对象,基本配置项是:entry、ouput、module、plugins属性。

entry与output

这里引入了一个chunk的概念,chunk表示一个文件,默认状况下webpack的输入是一个入口文件,输出也是一个文件,这个文件就是一个chunk,chunkId就是产出时给每一个文件一个惟一标识id,chunkhash就是文件内容的md5值,name就是在entry中指定的key值。

1
2
3
4
5
6
7
8
9
module.exports = {
     entry: {
         collection:  './src/main.js'      // collection为chunk的名字,chunk的入口文件是main.js
     },
     output: {
         path:  './dist/js' ,
         filename:  '[name].[chunkhash].js'    // 输出到dist/js目录下,以collection+chunk内容的md5值做为输出的文件名
     }
};  

输出:

 

module

moudle对应loader(加载器 )的配置,主要对指定类型的文件进行操做,举个例子:js类型的文件和css文件须要不一样的loader来处理。最经常使用的加载器是eslint-loader和babel-loader。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
     entry: {
         collection:  './src/main.js'      // collection为chunk的名字,chunk的入口文件是main.js
     },
     output: {
         path:  './dist/js' ,
         filename:  '[name].[chunkhash].js'    // 输出到dist/js目录下,以collection+chunk内容的md5值做为输出的文件名
     }
     module: {
         rules: [   // rules为数组,保存每一个加载器的配置
      {