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構建和產出三個階段。

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM