基本打包機制前端
本質上,webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(module bundler)。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關係圖(dependency graph),其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個 bundle。vue
打包過程能夠拆分爲四步:java
一、利用babel完成代碼轉換,並生成單個文件的依賴node
二、從入口開始遞歸分析,並生成依賴圖譜webpack
三、將各個引用模塊打包爲一個當即執行函數git
四、將最終的bundle文件寫入bundle.js中github
小解讀:web
1.1 利用@babel/parser解析代碼,識別modulenpm
1.2 利用@babel/traverse遍歷AST,獲取經過import引入的模塊並保存所依賴的模塊json
1.3 經過@babel/core和@babel/preset-env進行代碼的轉換,就是轉化ES6/7/8代碼等
1.4 輸出單個文件的依賴
return{ filename,//該文件名 dependencies,//該文件所依賴的模塊集合(鍵值對存儲) code//轉換後的代碼 }
2.1 從入口開始,廣度遍歷全部依賴,並輸出整個項目的依賴圖譜
graphArray.forEach(item => { graph[item.filename] = { dependencies: item.dependencies, code: item.code } }) return graph
3.1 生成代碼字符串
4.1 寫入文件
完整代碼見:https://github.com/LuckyWinty/blog/tree/master/code/bundleBuild
以上是打包的基本機制,而webpack的打包過程,會基於這些基本步驟進行擴展,主要有如下步驟:
1. 初始化參數 從配置文件和 Shell 語句中讀取與合併參數,得出最終的參數
2. 開始編譯 用上一步獲得的參數初始Compiler對象,加載全部配置的插件,通 過執行對象的run方法開始執行編譯
3. 肯定入口 根據配置中的 Entry 找出全部入口文件
4. 編譯模塊 從入口文件出發,調用全部配置的 Loader 對模塊進行編譯,再找出該模塊依賴的模塊,再遞歸本步驟直到全部入口依賴的文件都通過了本步驟的處理
5. 完成模塊編譯 在通過第4步使用 Loader 翻譯完全部模塊後, 獲得了每一個模塊被編譯後的最終內容及它們之間的依賴關係
6. 輸出資源:根據入口和模塊之間的依賴關係,組裝成一個個包含多個模塊的 Chunk,再將每一個 Chunk 轉換成一個單獨的文件加入輸出列表中,這是能夠修改輸出內容的最後機會
7. 輸出完成:在肯定好輸出內容後,根據配置肯定輸出的路徑和文件名,將文件的內容寫入文件系統中。
整個流程歸納爲3個階段,初始化、編譯、輸出。而在每一個階段中又會發生不少事件,Webpack會將這些事件廣播出來供Plugin使用。具體鉤子,能夠看官方文檔:https://webpack.js.org/api/compiler-hooks/#hooks
Webpack Loader
Loader 就像一個翻譯員,能將源文件通過轉化後輸出新的結果,而且一個文件還能夠鏈式地通過多個翻譯員翻譯。
概念:
開發Loader形式
1.基本形式
module.exports = function (source ) { return source; }
2.調用第三方模塊
const sass= require('node-sass'); module.exports = function (source) { return sass(source); }
因爲 Loader 運行在 Node.js 中,因此咱們能夠調用任意 Node.js 自帶的 API ,或者安裝第三方模塊進行調用
三、調用Webpack的Api
//獲取用戶爲 Loader 傳入的 options const loaderUtils =require ('loader-utils'); module.exports = (source) => { const options= loaderUtils.getOptions(this); return source; } //返回sourceMap module.exports = (source)=> { this.callback(null, source, sourceMaps); //當咱們使用 this.callback 返回內容時 ,該 Loader 必須返回 undefined, //以讓 Webpack 知道該 Loader 返回的結果在 this.callback 中,而不是 return中 return; } // 異步 module.exports = (source) => { const callback = this.async() someAsyncOperation(source, (err, result, sourceMaps, ast) => { // 經過 callback 返回異步執行後的結果 callback(err, result, sourceMaps, ast) }) } //緩存加速 module.exports = (source) => { //關閉該 Loader 的緩存功能 this.cacheable(false) return source }
source參數是compiler 傳遞給 Loader 的一個文件的原內容,這個函數須要返回處理後的內容,這裏爲了簡單起見,直接將原內容返回了,至關於該Loader 有作任何轉換.這裏結合了webpack的api和第三方模塊以後,能夠說loader能夠作的事情真的很是很是多了...
更多的webpack Api能夠看官方文檔:https://webpack.js.org/api/loaders
Webpack Plugin
專一處理 webpack 在編譯過程當中的某個特定的任務的功能模塊,能夠稱爲插件
概念:
開發基本形式
// 一、BasicPlugin.js 文件(獨立模塊) // 二、模塊對外暴露的 js 函數 class BasicPlugin{ //在構造函數中獲取用戶爲該插件傳入的配置 constructor(pluginOptions) { this.options = pluginOptions; } //三、原型定義一個 apply 函數,並注入了 compiler 對象 apply(compiler) { //四、掛載 webpack 事件鉤子(這裏掛載的是 emit 事件) compiler.plugin('emit', function (compilation, callback) { // ... 內部進行自定義的編譯操做 // 五、操做 compilation 對象的內部數據 console.log(compilation); // 六、執行 callback 回調 callback(); }); } } // 七、暴露 js 函數 module.exports = BasicPlugin;
Webpack 啓動後,在讀取配置的過程當中會先執行 new BasicPlugin(options )初始化一個 BasicPlugin 並得到其實例。在初始化 Compiler 對象後,再調用 basicPlugin.apply (compiler )爲插件實例傳入 compiler 對象。插件實例在獲取到 compiler 對象後,就能夠經過 compiler. plugin (事件名稱 ,回調函數)監聽到 Webpack 廣播的事件,而且能夠經過 compiler 對象去操做 Webpack。
Compiler對象
compiler 對象是 webpack 的編譯器對象,compiler 對象會在啓動 webpack 的時候被一次性地初始化,compiler 對象中包含了全部 webpack 可自定義操做的配置,例如 loader 的配置,plugin 的配置,entry 的配置等各類原始 webpack 配置等
webpack部分源碼:https://github.com/webpack/webpack/blob/10282ea20648b465caec6448849f24fc34e1ba3e/lib/webpack.js#L30
Compilation 對象
compilation 實例繼承於 compiler,compilation 對象表明了一次單一的版本 webpack 構建和生成編譯資源的過程。當運行 webpack 開發環境中間件時,每當檢測到一個文件變化,一次新的編譯將被建立,從而生成一組新的編譯資源以及新的 compilation 對象。一個 compilation 對象包含了 當前的模塊資源、編譯生成資源、變化的文件、以及 被跟蹤依賴的狀態信息。編譯對象也提供了不少關鍵點回調供插件作自定義處理時選擇使用。
Compiler 和 Compilation 的區別在於: Compiler 表明了整個 Webpack 從啓動到關閉的生命週期,而 Compilation 只表明一次新的編譯。
Tapable & Tapable 實例
webpack 的插件架構主要基於 Tapable 實現的,Tapable 是 webpack 項目組的一個內部庫,主要是抽象了一套插件機制。它相似於 NodeJS 的 EventEmitter 類,專一於自定義事件的觸發和操做。 除此以外, Tapable 容許你經過回調函數的參數訪問事件的生產者。
webpack本質上是一種事件流的機制,它的工做流程就是將各個插件串聯起來,而實現這一切的核心就是Tapable,webpack中最核心的負責編譯的Compiler和負責建立bundles的Compilation都是Tapable的實例,Tapable 可以讓咱們爲 javaScript 模塊添加並應用插件。 它能夠被其它模塊繼承或混合。
一些鉤子的含義:
同步鉤子,用tap方式註冊。異步鉤子,有三種註冊/發佈的模式,tap、tapAsync、tapPromise。
Tapable 簡化後的模型,就是咱們熟悉的發佈訂閱者模式
class SyncHook{ constructor(){ this.hooks = {} } tap(name,fn){ if(!this.hooks[name])this.hooks[name] = [] this.hooks[name].push(fn) } call(name){ this.hooks[name].forEach(hook=>hook(...arguments)) } }
Loader & Plugin 開發調試
npm link
1. 確保正在開發的本地 Loader 模塊的 package.json 已經配置好(最主要的main字段的入口文件指向要正確)
2. 在本地的 Npm 模塊根目錄下執行 npm link,將本地模塊註冊到全局
3. 在項目根目錄下執行 npm link loader-name ,將第 2 步註冊到全局的本地 Npm 模塊連接到項目的 node moduels 下,其中的 loader-name 是指在第 1 步的package.json 文件中配置的模塊名稱
Npm link 專門用於開發和調試本地的 Npm 模塊,能作到在不發佈模塊的狀況下, 將本地的一個正在開發的模塊的源碼連接到項目的 node_modules 目錄下,讓項目能夠直接使 用本地的 Npm 模塊。因爲是經過軟連接的方式實現的,編輯了本地的 Npm 模塊的代碼,因此在項目中也能使用到編輯後的代碼。
Resolveloader
ResolveLoader 用於配置 Webpack 如何尋找 Loader ,它在默認狀況下只會去 node_modules 目錄下尋找。爲了讓 Webpack 加載放在本地項目中的 Loader,須要修改 resolveLoader.modules。
構建工具選擇
針對不一樣的場景,選擇最合適的工具
經過對比,不難看出,Webpack和Rollup在不一樣場景下,都能發揮自身優點做用。webpack做爲打包工具,可是在定義模塊輸出的時候,webpack確不支持ESM,webpack插件系統龐大,確實有支持模塊級的Tree-Shacking的插件,如webpack-deep-scope-analysis-plugin。可是粒度更細化的,一個模塊裏面的某個方法,原本若是沒有被引用的話也能夠去掉的,就不行了....這個時候,就要上rollup了。rollup它支持程序流分析,能更加正確的判斷項目自己的代碼是否有反作用,其實就是rollup的tree-shaking更乾淨。因此咱們的結論是rollup 比較適合打包 js 的 sdk 或者封裝的框架等,例如,vue 源碼就是 rollup 打包的。而 webpack 比較適合打包一些應用,例如 SPA 或者同構項目等等。
結論:在開發應用時使用 Webpack,開發庫時使用 Rollup
資料推薦
補充學習資料:https://github.com/LuckyWinty/blog/issues/1
更多學習資料推薦:
Loader: http://www.javashuo.com/article/p-yceuurxv-ho.html
Tapable: http://www.javashuo.com/article/p-qbjcitwl-de.html
webpack:
更多:
想來深圳Shopee(外企,不加班,福利好,假期多)發展的。歡迎找我內推,前端、後臺、測試、產品等各類崗~^_^
其餘:若是方便的話,能夠關注一下個人github,並給我剛開始的博客項目點個start~ ^_^