本文在github作了收錄 github.com/Michael-lzg…css
demo源碼地址 github.com/Michael-lzg…vue
在 webpack 中,專一於處理 webpack 在編譯過程當中的某個特定的任務的功能模塊,能夠稱爲插件。它和 loader
有如下區別:node
loader
是一個轉換器,將 A 文件進行編譯成 B 文件,好比:將 A.less
轉換爲 A.css
,單純的文件轉換過程。webpack 自身只支持 js 和 json 這兩種格式的文件,對於其餘文件須要經過 loader
將其轉換爲 commonJS 規範的文件後,webpack 才能解析到。plugin
是一個擴展器,它豐富了 webpack 自己,針對是 loader
結束後,webpack 打包的整個過程,它並不直接操做文件,而是基於事件機制工做,會監聽 webpack 打包過程當中的某些節點,執行普遍的任務。webpack 插件有如下特徵webpack
(prototype)
上定義了一個注入 compiler
對象的 apply
方法。apply
函數中須要有經過 compiler
對象掛載的 webpack 事件鉤子,鉤子的回調中能拿到當前編譯的 compilation
對象,若是是異步編譯插件的話能夠拿到回調 callback。complition
對象的內部數據。class HelloPlugin { // 在構造函數中獲取用戶給該插件傳入的配置 constructor(options) {} // Webpack 會調用 HelloPlugin 實例的 apply 方法給插件實例傳入 compiler 對象 apply(compiler) { // 在emit階段插入鉤子函數,用於特定時機處理額外的邏輯; compiler.hooks.emit.tap('HelloPlugin', (compilation) => { // 在功能流程完成後能夠調用 webpack 提供的回調函數; }) // 若是事件是異步的,會帶兩個參數,第二個參數爲回調函數, compiler.plugin('emit', function (compilation, callback) { // 處理完畢後執行 callback 以通知 Webpack // 若是不執行 callback,運行流程將會一直卡在這不往下執行 callback() }) } } module.exports = HelloPlugin
new HelloPlugin(options)
初始化一個 HelloPlugin
得到其實例。compiler
對象後調用 HelloPlugin.apply(compiler)
給插件實例傳入 compiler 對象。compiler
對象後,就能夠經過 compiler.plugin
(事件名稱, 回調函數) 監聽到 Webpack 廣播出來的事件。 而且能夠經過 compiler
對象去操做 Webpack。webpack 本質上是一種事件流的機制,它的工做流程就是將各個插件串聯起來,而實現這一切的核心就是 Tapable
。git
Webpack 的 Tapable
事件流機制保證了插件的有序性,將各個插件串聯起來, Webpack 在運行過程當中會廣播事件,插件只須要監聽它所關心的事件,就能加入到這條 webapck 機制中,去改變 webapck 的運做,使得整個系統擴展性良好。github
Tapable
也是一個小型的 library,是 Webpack 的一個核心工具。相似於 node 中的 events 庫,核心原理就是一個訂閱發佈模式
。做用是提供相似的插件接口。方法以下:web
// 廣播事件 compiler.apply('event-name', params) compilation.apply('event-name', params) // 監聽事件 compiler.plugin('event-name', function (params) {}) compilation.plugin('event-name', function (params) {})
咱們來看下 Tapablevue-cli
function Tapable() { this._plugins = {} } //發佈name消息 Tapable.prototype.applyPlugins = function applyPlugins(name) { if (!this._plugins[name]) return var args = Array.prototype.slice.call(arguments, 1) var plugins = this._plugins[name] for (var i = 0; i < plugins.length; i++) { plugins[i].apply(this, args) } } // fn訂閱name消息 Tapable.prototype.plugin = function plugin(name, fn) { if (!this._plugins[name]) { this._plugins[name] = [fn] } else { this._plugins[name].push(fn) } } //給定一個插件數組,對其中的每個插件調用插件自身的apply方法註冊插件 Tapable.prototype.apply = function apply() { for (var i = 0; i < arguments.length; i++) { arguments[i].apply(this) } }
Tapable
爲 webpack 提供了統一的插件接口(鉤子)類型定義,它是 webpack 的核心功能庫。webpack 中目前有十種 hooks,在 Tapable 源碼中能夠看到,他們是:npm
exports.SyncHook = require('./SyncHook') exports.SyncBailHook = require('./SyncBailHook') exports.SyncWaterfallHook = require('./SyncWaterfallHook') exports.SyncLoopHook = require('./SyncLoopHook') exports.AsyncParallelHook = require('./AsyncParallelHook') exports.AsyncParallelBailHook = require('./AsyncParallelBailHook') exports.AsyncSeriesHook = require('./AsyncSeriesHook') exports.AsyncSeriesBailHook = require('./AsyncSeriesBailHook') exports.AsyncSeriesLoopHook = require('./AsyncSeriesLoopHook') exports.AsyncSeriesWaterfallHook = require('./AsyncSeriesWaterfallHook')
Tapable
還統一暴露了三個方法給插件,用於注入不一樣類型的自定義構建行爲:json
webpack 裏的幾個很是重要的對象,Compiler
, Compilation
和 JavascriptParser
都繼承了 Tapable
類,它們身上掛着豐富的鉤子。
一個 webpack 插件由如下組成:
下面實現一個最簡單的插件
class WebpackPlugin1 { constructor(options) { this.options = options } apply(compiler) { compiler.hooks.done.tap('MYWebpackPlugin', () => { console.log(this.options) }) } } module.exports = WebpackPlugin1
而後在 webpack 的配置中註冊使用就行,只須要在 webpack.config.js
裏引入並實例化就能夠了:
const WebpackPlugin1 = require('./src/plugin/plugin1') module.exports = { entry: { index: path.join(__dirname, '/src/main.js'), }, output: { path: path.join(__dirname, '/dist'), filename: 'index.js', }, plugins: [new WebpackPlugin1({ msg: 'hello world' })], }
此時咱們執行一下 npm run build
就能看到效果了
Compiler
對象包含了當前運行 Webpack 的配置,包括 entry
、output
、loaders
等配置,這個對象在啓動 Webpack 時被實例化,並且是全局惟一的。Plugin
能夠經過該對象獲取到 Webpack 的配置信息進行處理。
compiler 上暴露的一些經常使用的鉤子:
下面來舉個例子
class WebpackPlugin2 { constructor(options) { this.options = options } apply(compiler) { compiler.hooks.run.tap('run', () => { console.log('開始編譯...') }) compiler.hooks.compile.tap('compile', () => { console.log('compile') }) compiler.hooks.done.tap('compilation', () => { console.log('compilation') }) } } module.exports = WebpackPlugin2
此時咱們執行一下 npm run build
就能看到效果了
有一些編譯插件中的步驟是異步的,這樣就須要額外傳入一個 callback 回調函數,而且在插件運行結束時執行這個回調函數
class WebpackPlugin2 { constructor(options) { this.options = options } apply(compiler) { compiler.hooks.beforeCompile.tapAsync('compilation', (compilation, cb) => { setTimeout(() => { console.log('編譯中...') cb() }, 1000) }) } } module.exports = WebpackPlugin2
Compilation
對象表明了一次資源版本構建。當運行 webpack 開發環境中間件時,每當檢測到一個文件變化,就會建立一個新的 compilation
,從而生成一組新的編譯資源。一個 Compilation
對象表現了當前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態信息,簡單來說就是把本次打包編譯的內容存到內存裏。Compilation
對象也提供了插件須要自定義功能的回調,以供插件作自定義處理時選擇使用拓展。
簡單來講,Compilation
的職責就是構建模塊和 Chunk,並利用插件優化構建過程。
Compiler
表明了整個 Webpack 從啓動到關閉的生命週期,而 Compilation
只是表明了一次新的編譯,只要文件有改動,compilation
就會被從新建立。
Compilation
上暴露的一些經常使用的鉤子:
Compiler
和 Compilation
的區別
Compiler
表明了整個 Webpack 從啓動到關閉的生命週期Compilation
只是表明了一次新的編譯,只要文件有改動,compilation
就會被從新建立。在每次 webpack 打包以後,自動產生一個一個 markdown 文件清單,記錄打包以後的文件夾 dist 裏全部的文件的一些信息。
思路:
compiler.hooks.emit.tapAsync()
來觸發生成資源到 output 目錄以前的鉤子compilation.assets
獲取文件數量class FileListPlugin { constructor(options) { // 獲取插件配置項 this.filename = options && options.filename ? options.filename : 'FILELIST.md' } apply(compiler) { // 註冊 compiler 上的 emit 鉤子 compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => { // 經過 compilation.assets 獲取文件數量 let len = Object.keys(compilation.assets).length // 添加統計信息 let content = `# ${len} file${len > 1 ? 's' : ''} emitted by webpacknn` // 經過 compilation.assets 獲取文件名列表 for (let filename in compilation.assets) { content += `- ${filename}n` } // 往 compilation.assets 中添加清單文件 compilation.assets[this.filename] = { // 寫入新文件的內容 source: function () { return content }, // 新文件大小(給 webapck 輸出展現用) size: function () { return content.length }, } // 執行回調,讓 webpack 繼續執行 cb() }) } } module.exports = FileListPlugin
開發一個插件可以去除打包後代碼的註釋,這樣咱們的 bundle.js
將更容易閱讀
思路:
compiler.hooks.emit.tap()
來觸發生成文件後的鉤子compilation.assets
拿到生產後的文件,而後去遍歷各個文件.source()
獲取構建產物的文本,而後用正則去 replace 調註釋的代碼class RemoveCommentPlugin { constructor(options) { this.options = options } apply(compiler) { // 去除註釋正則 const reg = /("([^"]*(.)?)*")|('([^']*(.)?)*')|(/{2,}.*?(r|n))|(/*(n|.)*?*/)|(/******/)/g compiler.hooks.emit.tap('RemoveComment', (compilation) => { // 遍歷構建產物,.assets中包含構建產物的文件名 Object.keys(compilation.assets).forEach((item) => { // .source()是獲取構建產物的文本 let content = compilation.assets[item].source() content = content.replace(reg, function (word) { // 去除註釋後的文本 return /^/{2,}/.test(word) || /^/*!/.test(word) || /^/*{3,}//.test(word) ? '' : word }) // 更新構建產物對象 compilation.assets[item] = { source: () => content, size: () => content.length, } }) }) } } module.exports = RemoveCommentPlugin
webpack的異步加載原理及分包策略
總結18個webpack插件,總會有你想要的!
搭建一個 vue-cli4+webpack 移動端框架(開箱即用)
從零構建到優化一個相似vue-cli的腳手架
封裝一個toast和dialog組件併發布到npm
從零開始構建一個webpack項目
總結幾個webpack打包優化的方法
總結vue知識體系之高級應用篇
總結vue知識體系之實用技巧
總結vue知識體系之基礎入門篇
總結移動端H5開發經常使用技巧(乾貨滿滿哦!)