您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“webpack文件打包機制的示例分析”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“webpack文件打包機制的示例分析”這篇文章吧。
最近在重拾 webpack 一些知識點,希望對前端模塊化有更多的理解,以前對 webpack 打包機制有所好奇,沒有理解深入,淺嘗則止,最近通過對 webpack 打包后的文件進行查閱,對其如何打包 JS 文件有了更深的理解,希望通過這篇文章,能夠幫助讀者你理解:
webpack 單文件如何進行打包?
webpack 多文件如何進行代碼切割?
webpack1 和 webpack2 在文件打包上有什么區別?
webpack2 如何做到 tree shaking?
webpack3 如何做到 scope hoisting?
本文所有示例代碼全部放在我的 Github 上,看興趣的可以看看:
git clone https://github.com/happylindz/blog.git cd blog/code/webpackBundleAnalysis npm install
首先現在 webpack 作為當前主流的前端模塊化工具,在 webpack 剛開始流行的時候,我們經常通過 webpack 將所有處理文件全部打包成一個 bundle 文件, 先通過一個簡單的例子來看:
// src/single/index.js var index2 = require('./index2'); var util = require('./util'); console.log(index2); console.log(util); // src/single/index2.js var util = require('./util'); console.log(util); module.exports = "index 2"; // src/single/util.js module.exports = "Hello World"; // 通過 config/webpack.config.single.js 打包 const webpack = require('webpack'); const path = require('path') module.exports = { entry: { index: [path.resolve(__dirname, '../src/single/index.js')], }, output: { path: path.resolve(__dirname, '../dist'), filename: '[name].[chunkhash:8].js' }, }
通過 npm run build:single 可看到打包效果,打包內容大致如下(經過精簡):
// dist/index.xxxx.js (function(modules) { // 已經加載過的模塊 var installedModules = {}; // 模塊加載函數 function __webpack_require__(moduleId) { if(installedModules[moduleId]) { return installedModules[moduleId].exports; } var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); module.l = true; return module.exports; } return __webpack_require__(__webpack_require__.s = 3); })([ /* 0 */ (function(module, exports, __webpack_require__) { var util = __webpack_require__(1); console.log(util); module.exports = "index 2"; }), /* 1 */ (function(module, exports) { module.exports = "Hello World"; }), /* 2 */ (function(module, exports, __webpack_require__) { var index2 = __webpack_require__(0); index2 = __webpack_require__(0); var util = __webpack_require__(1); console.log(index2); console.log(util); }), /* 3 */ (function(module, exports, __webpack_require__) { module.exports = __webpack_require__(2); })]);
將相對無關的代碼剔除掉后,剩下主要的代碼:
首先 webpack 將所有模塊(可以簡單理解成文件)包裹于一個函數中,并傳入默認參數,這里有三個文件再加上一個入口模塊一共四個模塊,將它們放入一個數組中,取名為 modules,并通過數組的下標來作為 moduleId。
將 modules 傳入一個自執行函數中,自執行函數中包含一個 installedModules 已經加載過的模塊和一個模塊加載函數,最后加載入口模塊并返回。
__webpack_require__ 模塊加載,先判斷 installedModules 是否已加載,加載過了就直接返回 exports 數據,沒有加載過該模塊就通過 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__) 執行模塊并且將 module.exports 給返回。
很簡單是不是,有些點需要注意的是:
每個模塊 webpack 只會加載一次,所以重復加載的模塊只會執行一次,加載過的模塊會放到 installedModules,下次需要需要該模塊的值就直接從里面拿了。
模塊的 id 直接通過數組下標去一一對應的,這樣能保證簡單且唯一,通過其它方式比如文件名或文件路徑的方式就比較麻煩,因為文件名可能出現重名,不唯一,文件路徑則會增大文件體積,并且將路徑暴露給前端,不夠安全。
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__) 保證了模塊加載時 this 的指向 module.exports 并且傳入默認參數,很簡單,不過多解釋。
webpack 單文件打包的方式應付一些簡單場景就足夠了,但是我們在開發一些復雜的應用,如果沒有對代碼進行切割,將第三方庫(jQuery)或框架(React)和業務代碼全部打包在一起,就會導致用戶訪問頁面速度很慢,不能有效利用緩存,你的老板可能就要找你談話了。
那么 webpack 多文件入口如何進行代碼切割,讓我先寫一個簡單的例子:
// src/multiple/pageA.js const utilA = require('./js/utilA'); const utilB = require('./js/utilB'); console.log(utilA); console.log(utilB); // src/multiple/pageB.js const utilB = require('./js/utilB'); console.log(utilB); // 異步加載文件,類似于 import() const utilC = () => require.ensure(['./js/utilC'], function(require) { console.log(require('./js/utilC')) }); utilC(); // src/multiple/js/utilA.js 可類比于公共庫,如 jQuery module.exports = "util A"; // src/multiple/js/utilB.js module.exports = 'util B'; // src/multiple/js/utilC.js module.exports = "util C";
這里我們定義了兩個入口 pageA 和 pageB 和三個庫 util,我們希望代碼切割做到:
因為兩入口都是用到了 utilB,我們希望把它抽離成單獨文件,并且當用戶訪問 pageA 和 pageB 的時候都能去加載 utilB 這個公共模塊,而不是存在于各自的入口文件中。
pageB 中 utilC 不是頁面一開始加載時候就需要的內容,假如 utilC 很大,我們不希望頁面加載時就直接加載 utilC,而是當用戶達到某種條件(如:點擊按鈕)才去異步加載 utilC,這時候我們需要將 utilC 抽離成單獨文件,當用戶需要的時候再去加載該文件。
那么 webpack 需要怎么配置呢?
// 通過 config/webpack.config.multiple.js 打包 const webpack = require('webpack'); const path = require('path') module.exports = { entry: { pageA: [path.resolve(__dirname, '../src/multiple/pageA.js')], pageB: path.resolve(__dirname, '../src/multiple/pageB.js'), }, output: { path: path.resolve(__dirname, '../dist'), filename: '[name].[chunkhash:8].js', }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: 2, }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'] }) ] }
單單配置多 entry 是不夠的,這樣只會生成兩個 bundle 文件,將 pageA 和 pageB 所需要的內容全部放入,跟單入口文件并沒有區別,要做到代碼切割,我們需要借助 webpack 內置的插件 CommonsChunkPlugin。
首先 webpack 執行存在一部分運行時代碼,即一部分初始化的工作,就像之前單文件中的 __webpack_require__ ,這部分代碼需要加載于所有文件之前,相當于初始化工作,少了這部分初始化代碼,后面加載過來的代碼就無法識別并工作了。
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: 2, })
這段代碼的含義是,在這些入口文件中,找到那些引用兩次的模塊(如:utilB),幫我抽離成一個叫 vendor 文件,此時那部分初始化工作的代碼會被抽離到 vendor 文件中。
new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'], // minChunks: Infinity // 可寫可不寫 })
這段代碼的含義是在 vendor 文件中幫我把初始化代碼抽離到 mainifest 文件中,此時 vendor 文件中就只剩下 utilB 這個模塊了。你可能會好奇為什么要這么做?
因為這樣可以給 vendor 生成穩定的 hash 值,每次修改業務代碼(pageA),這段初始化時代碼就會發生變化,那么如果將這段初始化代碼放在 vendor 文件中的話,每次都會生成新的 vendor.xxxx.js,這樣不利于持久化緩存,如果不理解也沒關系,下次我會另外寫一篇文章來講述這部分內容。
另外 webpack 默認會抽離異步加載的代碼,這個不需要你做額外的配置,pageB 中異步加載的 utilC 文件會直接抽離為 chunk.xxxx.js 文件。
所以這時候我們頁面加載文件的順序就會變成:
mainifest.xxxx.js // 初始化代碼 vendor.xxxx.js // pageA 和 pageB 共同用到的模塊,抽離 pageX.xxxx.js // 業務代碼 當 pageB 需要 utilC 時候則異步加載 utilC
執行 npm run build:multiple 即可查看打包內容,首先來看下 manifest 如何做初始化工作(精簡版)?
// dist/mainifest.xxxx.js (function(modules) { window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) { var moduleId, chunkId, i = 0, callbacks = []; for(;i < chunkIds.length; i++) { chunkId = chunkIds[i]; if(installedChunks[chunkId]) callbacks.push.apply(callbacks, installedChunks[chunkId]); installedChunks[chunkId] = 0; } for(moduleId in moreModules) { if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } while(callbacks.length) callbacks.shift().call(null, __webpack_require__); if(moreModules[0]) { installedModules[0] = 0; return __webpack_require__(0); } }; var installedModules = {}; var installedChunks = { 4:0 }; function __webpack_require__(moduleId) { // 和單文件一致 } __webpack_require__.e = function requireEnsure(chunkId, callback) { if(installedChunks[chunkId] === 0) return callback.call(null, __webpack_require__); if(installedChunks[chunkId] !== undefined) { installedChunks[chunkId].push(callback); } else { installedChunks[chunkId] = [callback]; var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.type = 'text/javascript'; script.charset = 'utf-8'; script.async = true; script.src = __webpack_require__.p + "" + chunkId + "." + ({"0":"pageA","1":"pageB","3":"vendor"}[chunkId]||chunkId) + "." + {"0":"e72ce7d4","1":"69f6bbe3","2":"9adbbaa0","3":"53fa02a7"}[chunkId] + ".js"; head.appendChild(script); } }; })([]);
與單文件內容一致,定義了一個自執行函數,因為它不包含任何模塊,所以傳入一個空數組。除了定義了 __webpack_require__ ,還另外定義了兩個函數用來進行加載模塊。
首先講解代碼前需要理解兩個概念,分別是 module 和 chunk
chunk 代表生成后 js 文件,一個 chunkId 對應一個打包好的 js 文件(一共五個),從這段代碼可以看出,manifest 的 chunkId 為 4,并且從代碼中還可以看到:0-3 分別對應 pageA, pageB, 異步 utilC, vendor 公共模塊文件,這也就是我們為什么不能將這段代碼放在 vendor 的原因,因為文件的 hash 值會變。內容變了,vendor 生成的 hash 值也就變了。
module 對應著模塊,可以簡單理解為打包前每個 js 文件對應一個模塊,也就是之前 __webpack_require__ 加載的模塊,同樣的使用數組下標作為 moduleId 且是唯一不重復的。
那么為什么要區分 chunk 和 module 呢?
首先使用 installedChunks 來保存每個 chunkId 是否被加載過,如果被加載過,則說明該 chunk 中所包含的模塊已經被放到了 modules 中,注意是 modules 而不是 installedModules。我們先來簡單看一下 vendor chunk 打包出來的內容。
// vendor.xxxx.js webpackJsonp([3,4],{ 3: (function(module, exports) { module.exports = 'util B'; }) });
在執行完 manifest 后就會先執行 vendor 文件,結合上面 webpackJsonp 的定義,我們可以知道 [3, 4] 代表 chunkId,當加載到 vendor 文件后,installedChunks[3] 和 installedChunks[4] 將會被置為 0,這表明 chunk3,chunk4 已經被加載過了。
webpackJsonpCallback 一共有兩個參數,chuckIds 一般包含該 chunk 文件依賴的 chunkId 以及自身 chunkId,moreModules 代表該 chunk 文件帶來新的模塊。
var moduleId, chunkId, i = 0, callbacks = []; for(;i < chunkIds.length; i++) { chunkId = chunkIds[i]; if(installedChunks[chunkId]) callbacks.push.apply(callbacks, installedChunks[chunkId]); installedChunks[chunkId] = 0; } for(moduleId in moreModules) { if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } while(callbacks.length) callbacks.shift().call(null, __webpack_require__); if(moreModules[0]) { installedModules[0] = 0; return __webpack_require__(0); }
簡單說說 webpackJsonpCallback 做了哪些事,首先判斷 chunkIds 在 installedChunks 里有沒有回調函數函數未執行完,有的話則放到 callbacks 里,并且等下統一執行,并將 chunkIds 在 installedChunks 中全部置為 0, 然后將 moreModules 合并到 modules。
這里面只有 modules[0] 是不固定的,其它 modules 下標都是唯一的,在打包的時候 webpack 已經為它們統一編號,而 0 則為入口文件即 pageA,pageB 各有一個 module[0]。
然后將 callbacks 執行并清空,保證了該模塊加載開始前所以前置依賴內容已經加載完畢,最后判斷 moreModules[0], 有值說明該文件為入口文件,則開始執行入口模塊 0。
上面解釋了一大堆,但是像 pageA 這種同步加載 manifest, vendor 以及 pageA 文件來說,每次加載的時候 callbacks 都是為空的,因為它們在 installedChunks 中的值要嘛為 undefined(未加載), 要嘛為 0(已被加載)。installedChunks[chunkId] 的值永遠為 false,所以在這種情況下 callbacks 里根本不會出現函數,如果僅僅是考慮這樣的場景,上面的 webpackJsonpCallback 完全可以寫成下面這樣:
var moduleId, chunkId, i = 0, callbacks = []; for(;i < chunkIds.length; i++) { chunkId = chunkIds[i]; installedChunks[chunkId] = 0; } for(moduleId in moreModules) { if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } if(moreModules[0]) { installedModules[0] = 0; return __webpack_require__(0); }
但是考慮到異步加載 js 文件的時候(比如 pageB 異步加載 utilC 文件),就沒那么簡單,我們先來看下 webpack 是如何加載異步腳本的:
// 異步加載函數掛載在 __webpack_require__.e 上 __webpack_require__.e = function requireEnsure(chunkId, callback) { if(installedChunks[chunkId] === 0) return callback.call(null, __webpack_require__); if(installedChunks[chunkId] !== undefined) { installedChunks[chunkId].push(callback); } else { installedChunks[chunkId] = [callback]; var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.type = 'text/javascript'; script.charset = 'utf-8'; script.async = true; script.src = __webpack_require__.p + "" + chunkId + "." + ({"0":"pageA","1":"pageB","3":"vendor"}[chunkId]||chunkId) + "." + {"0":"e72ce7d4","1":"69f6bbe3","2":"9adbbaa0","3":"53fa02a7"}[chunkId] + ".js"; head.appendChild(script); } };
大致分為三種情況,(已經加載過,正在加載中以及從未加載過)
已經加載過該 chunk 文件,那就不用再重新加載該 chunk 了,直接執行回調函數即可,可以理解為假如頁面有兩種操作需要加載加載異步腳本,但是兩個腳本都依賴于公共模塊,那么第二次加載的時候發現之前第一次操作已經加載過了該 chunk,則不用再去獲取異步腳本了,因為該公共模塊已經被執行過了。
從未加載過,則動態地去插入 script 腳本去請求 js 文件,這也就為什么取名 webpackJsonpCallback ,因為跟 jsonp 的思想很類似,所以這種異步加載腳本在做腳本錯誤監控時經常出現 Script error,具體原因可以查看我之前寫的文章:前端代碼異常監控實戰
正在加載中代表該 chunk 文件已經在加載中了,比如說點擊按鈕觸發異步腳本,用戶點太快了,連點兩次就可能出現這種情況,此時將回調函數放入 installedChunks。
我們通過 utilC 生成的 chunk 來進行講解:
webpackJsonp([2,4],{ 4: (function(module, exports) { module.exports = "util C"; }) });
pageB 需要異步加載這個 chunk:
webpackJsonp([1,4],[ /* 0 */ (function(module, exports, __webpack_require__) { const utilB = __webpack_require__(3); console.log(utilB); const utilC = () => __webpack_require__.e/* nsure */(2, function(require) { console.log(__webpack_require__(4)) }); utilC(); }) ]);
當 pageB 進行某種操作需要加載 utilC 時就會執行 __webpack_require__.e(2, callback) 2,代表需要加載的模塊 chunkId(utilC),異步加載 utilC 并將 callback 添加到 installedChunks[2] 中,然后當 utilC 的 chunk 文件加載完畢后,chunkIds 包含 2,發現 installedChunks[2] 是個數組,里面還有之前還未執行的 callback 函數。
既然這樣,那我就將我自己帶來的模塊先放到 modules 中,然后再統一執行之前未執行完的 callbacks 函數,這里指的是存放于 installedChunks[2] 中的回調函數 (可能存在多個),這也就是說明這里的先后順序:
// 先將 moreModules 合并到 modules, 再去執行 callbacks, 不然之前未執行的 callback 依賴于新來的模塊,你不放進 module 我豈不是得不到想要的模塊 for(moduleId in moreModules) { if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } while(callbacks.length) callbacks.shift().call(null, __webpack_require__);
經過我對打包文件的觀察,從 webpack1 到 webpack2 在打包文件上有下面這些主要的改變:
首先,moduleId[0] 不再為入口執行函數做保留,所以說不用傻傻看到 moduleId[0] 就認為是打包文件的入口模塊,取而代之的是 window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {} 傳入了第三個參數 executeModules,是個數組,如果參數存在則說明它是入口模塊,然后就去執行該模塊。
if(executeModules) { for(i=0; i < executeModules.length; i++) { result = __webpack_require__(__webpack_require__.s = executeModules[i]); } }
其次,webpack2 中會默認加載 OccurrenceOrderPlugin 這個插件,即你不用 plugins 中添加這個配置它也會默認執行,那它有什么用途呢?主要是在 webpack1 中 moduleId 的不確定性導致的,在 webpack1 中 moduleId 取決于引入文件的順序,這就會導致這個 moduleId 可能會時常發生變化, 而 OccurrenceOrderPlugin 插件會按引入次數最多的模塊進行排序,引入次數的模塊的 moduleId 越小,比如說上面引用的 utilB 模塊引用次數為 2(最多),所以它的 moduleId 為 0。
webpackJsonp([3],[ /* 0 */ (function(module, exports) { module.exports = 'util B'; }) ]);
最后說下在異步加載模塊時, webpack2 是基于 Promise 的,所以說如果你要兼容低版本瀏覽器,需要引入 Promise-polyfill ,另外為引入請求添加了錯誤處理。
__webpack_require__.e = function requireEnsure(chunkId) { var promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); installedChunkData[2] = promise; // start chunk loading var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.type = 'text/javascript'; script.charset = 'utf-8'; script.async = true; script.timeout = 120000; script.src = __webpack_require__.p + "" + chunkId + "." + {"0":"ae9c5f5f","1":"0ac69acb","2":"20651a9c","3":"0cdc6c84"}[chunkId] + ".js"; var timeout = setTimeout(onScriptComplete, 120000); script.onerror = script.onload = onScriptComplete; function onScriptComplete() { // 防止內存泄漏 script.onerror = script.onload = null; clearTimeout(timeout); var chunk = installedChunks[chunkId]; if(chunk !== 0) { if(chunk) { chunk[1](new Error('Loading chunk ' + chunkId + ' failed.')); } installedChunks[chunkId] = undefined; } }; head.appendChild(script); return promise; };
可以看出,原本基于回調函數的方式已經變成基于 Promise 做異步處理,另外添加了 onScriptComplete 用于做腳本加載失敗處理。
在 webpack1 的時候,如果由于網絡原因當你加載腳本失敗后,即使網絡恢復了,你再次進行某種操作需要同個 chunk 時候都會無效,主要原因是失敗之后沒把 installedChunks[chunkId] = undefined; 導致之后不會再對該 chunk 文件發起異步請求。
而在 webpack2 中,當腳本請求超時了(2min)或者加載失敗,會將 installedChunks[chunkId] 清空,當下次重新請求該 chunk 文件會重新加載,提高了頁面的容錯性。
這些是我在打包文件中看到主要的區別,難免有所遺漏,如果你有更多的見解,歡迎在評論區留言。
什么是 tree shaking,即 webpack 在打包的過程中會將沒用的代碼進行清除(dead code)。一般 dead code 具有一下的特征:
代碼不會被執行,不可到達
代碼執行的結果不會被用到
代碼只會影響死變量(只寫不讀)
是不是很神奇,那么需要怎么做才能使 tree shaking 生效呢?
首先,模塊引入要基于 ES6 模塊機制,不再使用 commonjs 規范,因為 es6 模塊的依賴關系是確定的,和運行時的狀態無關,可以進行可靠的靜態分析,然后清除沒用的代碼。而 commonjs 的依賴關系是要到運行時候才能確定下來的。
其次,需要開啟 UglifyJsPlugin 這個插件對代碼進行壓縮。
我們先寫一個例子來說明:
// src/es6/pageA.js import { utilA, funcA, // 引入 funcA 但未使用, 故 funcA 會被清除 } from './js/utilA'; import utilB from './js/utilB'; // 引入 utilB(函數) 未使用,會被清除 import classC from './js/utilC'; // 引入 classC(類) 未使用,不會被清除 console.log(utilA); // src/es6/js/utilA.js export const utilA = 'util A'; export function funcA() { console.log('func A'); } // src/es6/js/utilB.js export default function() { console.log('func B'); } if(false) { // 被清除 console.log('never use'); } while(true) {} console.log('never use'); // src/es6/js/utilC.js const classC = function() {} // 類方法不會被清除 classC.prototype.saySomething = function() { console.log('class C'); } export default classC;
打包的配置也很簡單:
const webpack = require('webpack'); const path = require('path') module.exports = { entry: { pageA: path.resolve(__dirname, '../src/es6/pageA.js'), }, output: { path: path.resolve(__dirname, '../dist'), filename: '[name].[chunkhash:8].js' }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', minChunks: Infinity, }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) ] }
通過 npm run build:es6 對壓縮的文件進行分析:
// dist/pageA.xxxx.js webpackJsonp([0],[ function(o, t, e) { 'use strict'; Object.defineProperty(t, '__esModule', { value: !0 }); var n = e(1); e(2), e(3); console.log(n.a); },function(o, t, e) { 'use strict'; t.a = 'util A'; },function(o, t, e) { 'use strict'; for (;;); console.log('never use'); }, function(o, t, e) { 'use strict'; const n = function() {}; n.prototype.saySomething = function() { console.log('class C'); }; } ],[0]);
引入但是沒用的變量,函數都會清除,未執行的代碼也會被清除。但是類方法是不會被清除的。因為 webpack 不會區分不了是定義在 classC 的 prototype 還是其它 Array 的 prototype 的,比如 classC 寫成下面這樣:
const classC = function() {} var a = 'class' + 'C'; var b; if(a === 'Array') { b = a; }else { b = 'classC'; } b.prototype.saySomething = function() { console.log('class C'); } export default classC;
webpack 無法保證 prototype 掛載的對象是 classC,這種代碼,靜態分析是分析不了的,就算能靜態分析代碼,想要正確完全的分析也比較困難。所以 webpack 干脆不處理類方法,不對類方法進行 tree shaking。
更多的 tree shaking 的副作用可以查閱: Tree shaking class methods
scope hoisting,顧名思義就是將模塊的作用域提升,在 webpack 中不能將所有所有的模塊直接放在同一個作用域下,有以下幾個原因:
按需加載的模塊
使用 commonjs 規范的模塊
被多 entry 共享的模塊
在 webpack3 中,這些情況生成的模塊不會進行作用域提升,下面我就舉個例子來說明:
// src/hoist/utilA.js export const utilA = 'util A'; export function funcA() { console.log('func A'); } // src/hoist/utilB.js export const utilB = 'util B'; export function funcB() { console.log('func B'); } // src/hoist/utilC.js export const utilC = 'util C'; // src/hoist/pageA.js import { utilA, funcA } from './utilA'; console.log(utilA); funcA(); // src/hoist/pageB.js import { utilA } from './utilA'; import { utilB, funcB } from './utilB'; funcB(); import('./utilC').then(function(utilC) { console.log(utilC); })
這個例子比較典型,utilA 被 pageA 和 pageB 所共享,utilB 被 pageB 單獨加載,utilC 被 pageB 異步加載。
想要 webpack3 生效,則需要在 plugins 中添加 ModuleConcatenationPlugin。
webpack 配置如下:
const webpack = require('webpack'); const path = require('path') module.exports = { entry: { pageA: path.resolve(__dirname, '../src/hoist/pageA.js'), pageB: path.resolve(__dirname, '../src/hoist/pageB.js'), }, output: { path: path.resolve(__dirname, '../dist'), filename: '[name].[chunkhash:8].js' }, plugins: [ new webpack.optimize.ModuleConcatenationPlugin(), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: 2, }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', minChunks: Infinity, }) ] }
運行 npm run build:hoist 進行編譯,簡單看下生成的 pageB 代碼:
webpackJsonp([2],{ 2: (function(module, __webpack_exports__, __webpack_require__) { "use strict"; var utilA = __webpack_require__(0); // CONCATENATED MODULE: ./src/hoist/utilB.js const utilB = 'util B'; function funcB() { console.log('func B'); } // CONCATENATED MODULE: ./src/hoist/pageB.js funcB(); __webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 3)).then(function(utilC) { console.log(utilC); }) }) },[2]);
通過代碼分析,可以得出下面的結論:
因為我們配置了共享模塊抽離,所以 utilA 被抽出為單獨模塊,故這部分內容不會進行作用域提升。
utilB 無牽無掛,被 pageB 單獨加載,所以這部分不會生成新的模塊,而是直接作用域提升到 pageB 中。
utilC 被異步加載,需要抽離成單獨模塊,很明顯沒辦法作用域提升。
以上是“webpack文件打包機制的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。