標籤:val 目錄 context 標識 reset common prototype function 本質
打包工具的角色
所謂打包工具在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屬性必須配置,值為Regex,用於匹配檔案 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構建和產出三個階段。
webpack務虛掃盲