webpack务虚扫盲

打包工具的角色

所谓打包工具在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值。

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。

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属性来配置

rules: [
    {
        test: /\.js$/,
        loader: ‘babel-loader‘,
        options: {
            fakeoption: true
        }
    }
]  

另外通常babel-loader的配置项可以写在根目录下的.babelrc文件中

{
  "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结束标签之前。

var HtmlwebpackPlugin = require(‘html-webpack-plugin‘);

module.exports = {
    ...
    plugins: [
        new HtmlwebpackPlugin({       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(输出的文件名)来完成操作。

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插件来完成。

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中的代码。

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) {
    ...
/***/ }),
    ...
]);
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,获取返回值,然后进行缓存。

/******/ (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构建和产出三个阶段。

时间: 2024-11-13 10:02:20

webpack务虚扫盲的相关文章

Vue.js系列之项目搭建(vue2.0 + vue-cli + webpack )

1.安装node node.js环境(npm包管理器) cnpm npm的淘宝镜像 从node.js官网下载并安装node,安装过程很简单,一路"下一步"就可以了(傻瓜式安装).安装完成之后,打开命令行工具,输入 node -v,如果出现相应的版本号,则说明安装成功. npm包管理器,是集成在node中的,所以,直接输入 npm -v就会显示出npm的版本信息. 2.安装cnpm 在命令行中输入 npm install -g cnpm --registry=http://registr

angular踩坑之路:初探webpack

之前费了一番力气安装好了angular开发环境,后面的几天都是在angular中文官网上看文档,照着英雄教程一步一步操作,熟悉了angular的一些基本特性,这部分没有遇到什么大问题,还比较顺利.这两天在看官方文档中的Webpack简介,想跟着文档做一遍,了解一下如何用Webpack打包angular项目,结果遇到了一些问题,因为是初学angular和Webpack的小白,这些问题一时难以解决,花费了不少时间,想在这里记录一下. 首先跟着文档将相关的文件都添加到项目中,目录是这样子的: 根据文档

WebPack+WebStorm调试

怎么用webstorm来调试WebPack? 今天查了很多文档,最终在官网上耐心看完英文文档后,才解决.附上链接:https://blog.jetbrains.com/webstorm/2015/09/debugging-webpack-applications-in-webstorm/ 工具简介 Windows7 WebStorm2017 Webpack2 谷歌浏览器(默认浏览器) 操作 在Project视图里找到index.html,右击进入Debug模式 WebStrom会自动进入Debu

webpack教程

1 安装 webpack 2 初始化项目 3 webpack 配置 4 实时刷新 5 第三方库 6 模块化 7 打包.构建 安装 webpack# 我们通过 npm 在全局环境下安装 webpack: npm install webpack -g 安装完成后,我们可以使用 webpack 命令,执行 webpack --help 能够查看 webpack 提供的所有命令. 不过,建议在项目下安装一份局域的 webpack: npm install webpack --save-dev 如果觉得

webpack

30分钟手把手教你学webpack实战 阅读目录 一:什么是webpack? 他有什么优点? 二:如何安装和配置 三:理解webpack加载器 四:理解less-loader加载器的使用 五:理解babel-loader加载器的含义 六:了解下webpack的几个命令 七:webpack对多个模块依赖进行打包 八:如何独立打包成样式文件 九:如何打包成多个资源文件 十:关于对图片的打包 十一:React开发神器:react-hot-loader 回到顶部 什么是webpack? 他有什么优点?

Vue2.0与 [百度地图] 结合使用———vue+webpack+axios+百度地图实现组件之间的通信

Vue2.0与 [百度地图] 结合使用: 1.vue init webpack-simple vue-baidu-map 2.下载axios cnpm install axios; 3.在main.js中引入axios,并使用 import axios from 'axios' /* 把axios对象挂到Vue实例上面,其他组件在使用axios的时候直接 this.$http就可以了 */ Vue.prototype.$http = axios; 4.引入百度地图的js秘钥--->最好在inde

Webpack入门教程十六

84.HtmlWebpackPlugin的chunks的配置,webpack.config.js文件修改如下 var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry:{ 'Greeter':__dirname + "/app/Greeter.js", 'a':__dirname + "/app/a.js&

Webpack入门教程十七

88.在HtmlWebpackPlugin中使用excludeChunks项,修改webpack.config.js文件,修改内容如下 var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry:{ 'Greeter':__dirname + "/app/Greeter.js", 'a':__dirname + &qu

D1.1.利用npm(webpack)构建基本reactJS项目

前提: 已经安装nodejs和npm #全局安装webpack 自动构建化工具,职能管理项目:webpack-dev-server是开发工具,提供 Hot Module Replacement 功能# webpack 介绍:http://webpack.github.io/docs/what-is-webpack.html npm install -g webpack webpack-dev-server #在项目文件夹路径下,初始化npm项目 npm init #安装webpack和webpa