您好,登錄后才能下訂單哦!
這篇文章主要介紹webpack中loader和plugin的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
1 基礎回顧
首先我們先回顧一下webpack常見配置,因為后面會用到,所以簡單介紹一下。
1.1 webpack常見配置
// 入口文件 entry: { app: './src/js/index.js', }, // 輸出文件 output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist'), publicPath: '/' //確保文件資源能夠在 http://localhost:3000 下正確訪問 }, // 開發者工具 source-map devtool: 'inline-source-map', // 創建開發者服務器 devServer: { contentBase: './dist', hot: true // 熱更新 }, plugins: [ // 刪除dist目錄 new CleanWebpackPlugin(['dist']), // 重新穿件html文件 new HtmlWebpackPlugin({ title: 'Output Management' }), // 以便更容易查看要修補(patch)的依賴 new webpack.NamedModulesPlugin(), // 熱更新模塊 new webpack.HotModuleReplacementPlugin() ], // 環境 mode: "development", // loader配置 module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.(png|svg|jpg|gif)$/, use: [ 'file-loader' ] } ] }
這里面我們重點關注 module和plugins屬性,因為今天的重點是編寫loader和plugin,需要配置這兩個屬性。
1.2 打包原理
識別入口文件
通過逐層識別模塊依賴。(Commonjs、amd或者es6的import,webpack都會對其進行分析。來獲取代碼的依賴)
webpack做的就是分析代碼。轉換代碼,編譯代碼,輸出代碼
最終形成打包后的代碼
這些都是webpack的一些基礎知識,對于理解webpack的工作機制很有幫助。
2 loader
OK今天第一個主角登場
2.1 什么是loader?
loader是文件加載器,能夠加載資源文件,并對這些文件進行一些處理,諸如編譯、壓縮等,最終一起打包到指定的文件中
處理一個文件可以使用多個loader,loader的執行順序是和本身的順序是相反的,即最后一個loader最先執行,第一個loader最后執行。
第一個執行的loader接收源文件內容作為參數,其他loader接收前一個執行的loader的返回值作為參數。最后執行的loader會返回此模塊的JavaScript源碼
2.2 手寫一個loader
需求:
處理.txt文件
對字符串做反轉操作
首字母大寫
例如:abcdefg轉換后為Gfedcba
OK,我們開始
1)首先創建兩個loader(這里以本地loader為例)
為什么要創建兩個laoder?理由后面會介紹
reverse-loader.js
module.exports = function (src) { if (src) { console.log('--- reverse-loader input:', src) src = src.split('').reverse().join('') console.log('--- reverse-loader output:', src) } return src; }
uppercase-loader.js
module.exports = function (src) { if (src) { console.log('--- uppercase-loader input:', src) src = src.charAt(0).toUpperCase() + src.slice(1) console.log('--- uppercase-loader output:', src) } // 這里為什么要這么寫?因為直接返回轉換后的字符串會報語法錯誤, // 這么寫import后轉換成可以使用的字符串 return `module.exports = '${src}'` }
看,loader結構是不是很簡單,接收一個參數,并且return一個內容就ok了。
然后創建一個txt文件
2)mytest.txt
abcdefg
3)現在開始配置webpack
module.exports = { entry: { index: './src/js/index.js' }, plugins: [...], optimization: {...}, output: {...}, module: { rules: [ ..., { test: /\.txt$/, use: [ './loader/uppercase-loader.js', './loader/reverse-loader.js' ] } ] } }
這樣就配置完成了
4)我們在入口文件中導入這個腳本
為什么這里需要導入呢,我們不是配置了webapck處理所有的.txt文件么?
因為webpack會做過濾,如果不引用該文件的話,webpack是不會對該文件進行打包處理的,那么你的loader也不會執行
import _ from 'lodash'; import txt from '../txt/mytest.txt' import '../css/style.css' function component() { var element = document.createElement('div'); var button = document.createElement('button'); var br = document.createElement('br'); button.innerHTML = 'Click me and look at the console!'; element.innerHTML = _.join('【' + txt + '】'); element.className = 'hello' element.appendChild(br); element.appendChild(button); // Note that because a network request is involved, some indication // of loading would need to be shown in a production-level site/app. button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => { var print = module.default; print(); }); return element; } document.body.appendChild(component());
package.json配置
{ ..., "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --config webpack.prod.js", "start": "webpack-dev-server --open --config webpack.dev.js", "server": "node server.js" }, ... }
然后執行命令
npm run build
這樣我們的loader就寫完了。
現在回答為什么要寫兩個loader?
看到執行的順序沒,我們的配置的是這樣的
use: [ './loader/uppercase-loader.js', './loader/reverse-loader.js' ]
正如前文所說, 處理一個文件可以使用多個loader,loader的執行順序是和本身的順序是相反的
我們也可以自己寫loader解析自定義模板,像vue-loader是非常復雜的,它內部會寫大量的對.vue文件的解析,然后會生成對應的html、js和css。
我們這里只是講述了一個最基礎的用法,如果有更多的需要,可以查看《loader官方文檔》
3 plugin
3.1 什么是plugin?
在 Webpack 運行的生命周期中會廣播出許多事件,Plugin 可以監聽這些事件,在合適的時機通過 Webpack 提供的 API 改變輸出結果。
plugin和loader的區別是什么?
對于loader,它就是一個轉換器,將A文件進行編譯形成B文件,這里操作的是文件,比如將A.scss或A.less轉變為B.css,單純的文件轉換過程
plugin是一個擴展器,它豐富了wepack本身,針對是loader結束后,webpack打包的整個過程,它并不直接操作文件,而是基于事件機制工作,會監聽webpack打包過程中的某些節點,執行廣泛的任務。
3.2 一個最簡的插件
/plugins/MyPlugin.js(本地插件)
class MyPlugin { // 構造方法 constructor (options) { console.log('MyPlugin constructor:', options) } // 應用函數 apply (compiler) { // 綁定鉤子事件 compiler.plugin('compilation', compilation => { console.log('MyPlugin') )) } } module.exports = MyPlugin
webpack配置
const MyPlugin = require('./plugins/MyPlugin') module.exports = { entry: { index: './src/js/index.js' }, plugins: [ ..., new MyPlugin({param: 'xxx'}) ], ... };
這就是一個最簡單的插件(雖然我們什么都沒干)
webpack 啟動后,在讀取配置的過程中會先執行 new MyPlugin(options) 初始化一個 MyPlugin 獲得其實例。
在初始化 compiler 對象后,再調用 myPlugin.apply(compiler) 給插件實例傳入 compiler 對象。
插件實例在獲取到 compiler 對象后,就可以通過 compiler.plugin(事件名稱, 回調函數) 監聽到 Webpack 廣播出來的事件。
并且可以通過 compiler 對象去操作 webpack。
看到這里可能會問compiler是啥,compilation又是啥?
Compiler 對象包含了 Webpack 環境所有的的配置信息,包含 options,loaders,plugins 這些信息,這個對象在 Webpack 啟動時候被實例化,它是全局唯一的,可以簡單地把它理解為 Webpack 實例;
Compilation 對象包含了當前的模塊資源、編譯生成資源、變化的文件等。當 Webpack 以開發模式運行時,每當檢測到一個文件變化,一次新的 Compilation 將被創建。Compilation 對象也提供了很多事件回調供插件做擴展。通過 Compilation 也能讀取到 Compiler 對象。
Compiler 和 Compilation 的區別在于:
Compiler 代表了整個 Webpack 從啟動到關閉的生命周期,而 Compilation 只是代表了一次新的編譯。
3.3 事件流
webpack 通過 Tapable 來組織這條復雜的生產線。
webpack 的事件流機制保證了插件的有序性,使得整個系統擴展性很好。
webpack 的事件流機制應用了觀察者模式,和 Node.js 中的 EventEmitter 非常相似。
綁定事件
compiler.plugin('event-name', params => { ... });
觸發事件
compiler.apply('event-name',params)
3.4 需要注意的點
只要能拿到 Compiler 或 Compilation 對象,就能廣播出新的事件,所以在新開發的插件中也能廣播出事件,給其它插件監聽使用。
傳給每個插件的 Compiler 和 Compilation 對象都是同一個引用。也就是說在一個插件中修改了 Compiler 或 Compilation 對象上的屬性,會影響到后面的插件。
有些事件是異步的,這些異步的事件會附帶兩個參數,第二個參數為回調函數,在插件處理完任務時需要調用回調函數通知 webpack,才會進入下一處理流程 。例如:
compiler.plugin('emit',function(compilation, callback) { ... // 處理完畢后執行 callback 以通知 Webpack // 如果不執行 callback,運行流程將會一直卡在這不往下執行 callback(); });
關于complier和compilation,webpack定義了大量的鉤子事件。開發者可以根據自己的需要在任何地方進行自定義處理。
3.5 手寫一個plugin
場景:
小程序mpvue項目,通過webpack編譯,生成子包(我們作為分包引入到主程序中),然后考入主包當中。生成子包后,里面的公共靜態資源wxss引用地址需要加入分包的前綴:/subPages/enjoy_given。
在未編寫插件前,生成的資源是這樣的,這個路徑如果作為分包引入主包,是沒法正常訪問資源的。
所以需求來了:
修改dist/static/css/pages目錄下,所有頁面的樣式文件(wxss文件)引入公共資源的路徑。
因為所有頁面的樣式都會引用通用樣式vender.wxss
那么就需要把@import "/static/css/vendor.wxss"; 改為:@import "/subPages/enjoy_given/static/css/vendor.wxss";復制代碼
OK 開始!
1)創建插件文件 CssPathTransfor.js
CssPathTransfor.js
class CssPathTransfor { apply (compiler) { compiler.plugin('emit', (compilation, callback) => { console.log('--CssPathTransfor emit') // 遍歷所有資源文件 for (var filePathName in compilation.assets) { // 查看對應的文件是否符合指定目錄下的文件 if (/static\/css\/pages/i.test(filePathName)) { // 引入路徑正則 const reg = /\/static\/css\/vendor\.wxss/i // 需要替換的最終字符串 const finalStr = '/subPages/enjoy_given/static/css/vendor.wxss' // 獲取文件內容 let content = compilation.assets[filePathName].source() || '' content = content.replace(reg, finalStr) // 重寫指定輸出模塊內容 compilation.assets[filePathName] = { source () { return content; }, size () { return content.length; } } } } callback() }) } } module.exports = CssPathTransfor
看著挺多,實際就是遍歷compilation.assets模塊。對符合要求的文件進行正則替換。
2)修改webpack配置
var baseWebpackConfig = require('./webpack.base.conf') var CssPathTransfor = require('../plugins/CssPathTransfor.js') var webpackConfig = merge(baseWebpackConfig, { module: {...}, devtool: config.build.productionSourceMap ? '#source-map' : false, output: {...}, plugins: [ ..., // 配置插件 new CssPathTransfor(), ] })
插件編寫完成后,執行編譯命令
以上是“webpack中loader和plugin的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。