webpack之loader和plugin簡介
webpack是一個模塊打包器(module bundler),提供了一個核心,核心提供了很多開箱即用的功能,同時它可以用loader和plugin來擴展。webpack本身結構精巧,基於tapable的插件架構,擴展性強,眾多的loader或者plugin讓webpack稍顯複雜。
webpack常用配置包括:devtool、entry、 output、module、resolve、plugins、externals等,本文主要介紹下webpack常用的loader和plugin
webpack允許我們使用loader來處理文件,loader是一個導出為function的node模塊。可以將匹配到的文件進行一次轉換,同時loader可以鏈式傳遞。
loader的使用方式
一般loader的使用方式分為三種:
1:在配置文件webpack.config.js中配置
module.exports = {n module: {n rules: [n {n test: /.txt$/,n use: raw-loadern }n ]n }n}n
2:通過命令行參數方式
webpack --module-bind txt=raw-loadern
3:通過內聯使用
import txt from raw-loader!./file.txt;n
webpack常用的loader
樣式:style-loader、css-loader、less-loader、sass-loader等
文件:raw-loader、file-loader 、url-loader等
編譯:babel-loader、coffee-loader 、ts-loader等
校驗測試:mocha-loader、jshint-loader 、eslint-loader等
比如下面配置,可以匹配.scss的文件,分別經過sass-loader、css-loader、style-loader的處理。
sass-loader轉化sass為css文件,並且包一層module.exports成為一個js module。style-loader將創建一個style標籤將css文件嵌入到html中。css-loader則處理其中的@import和url()。
module.exports = {n module: {n rules: [n {n test: /.scss$/,n use:[n {loader:style-loader},n {loader:css-loader,options:{sourceMap:true,modules:true}},n {loader:sass-loader,options:{sourceMap:true}}n ],n exclude:/node_modules/n }n ]n }n}n
vue-loader、coffee-loader、babel-loader等可以將特定文件格式轉成js模塊、將其他語言轉化為js語言和編譯下一代js語言
file-loader、url-loader等可以處理資源,file-loader可以複製和放置資源位置,並可以指定文件名模板,用hash命名更好利用緩存。
url-loader可以將小於配置limit大小的文件轉換成內斂Data Url的方式,減少請求。
raw-loader可以將文件已字元串的形式返回
imports-loader、exports-loader等可以向模塊注入變數或者提供導出模塊功能,常見場景是:
1:jQuery插件注入$,imports-loader?$=jquery
2:禁用AMD,imports-loader?define=false
等同於:var $ = require("jquery") 和 var define = false;
expose-loader:暴露對象為全局變數
如何寫一個loader:官網介紹How to write a loader?
下面是一個簡單的raw-loader,它可以將文本類文件轉成字元串到js文件中。其中this.cacheable、this.value等是loader的api,分別是將結果標記為可緩存和把值傳遞給下一個loader。
module.exports = function(content) {ntthis.cacheable && this.cacheable();ntthis.value = content;ntreturn "module.exports = " + JSON.stringify(content);n}n
webpack的plugin比loader強大,通過鉤子可以涉及整個構建流程,可以做一些在構建範圍內的事情。
webpack常用的plugin
官網介紹Plugins
第三方插件webpack-contrib/awesome-webpack
首先webpack內置UglifyJsPlugin,壓縮和混淆代碼。
webpack內置CommonsChunkPlugin,提高打包效率,將第三方庫和業務代碼分開打包。
ProvidePlugin:自動載入模塊,代替require和import
new webpack.ProvidePlugin({n $: jquery,n jQuery: jqueryn })n
html-webpack-plugin可以根據模板自動生成html代碼,並自動引用css和js文件
extract-text-webpack-plugin 將js文件中引用的樣式單獨抽離成css文件
DefinePlugin 編譯時配置全局變數,這對開發模式和發布模式的構建允許不同的行為非常有用。
new webpack.DefinePlugin({n PRODUCTION: JSON.stringify(true),n VERSION: JSON.stringify("5fa3b9"),n BROWSER_SUPPORTS_HTML5: true,n TWO: "1+1",n "typeof window": JSON.stringify("object")n })n
HotModuleReplacementPlugin 熱更新
- 添加HotModuleReplacementPlugin
- entry中添加 "webpack-dev-server/client?http://localhost:8080/",
- entry中添加 "webpack/hot/dev-server"
(熱更新還可以直接用webpack_dev_server --hot --inline,原理也是在entry中添加了上述代碼)
webpack 內置的DllPlugin和DllReferencePlugin相互配合,前置第三方包的構建,只構建業務代碼,同時能解決Externals多次引用問題。DllReferencePlugin引用DllPlugin配置生成的manifest.json文件,manifest.json包含了依賴模塊和module id的映射關係
babili-webpack-plugin、transform-runtime 、transform-object-rest-spread
- babili-webpack-plugin:構建在babel之上,它的用處可以看BabiliWebpackPlugin
- transform-runtime :解決了babel在每個文件都插入了輔助代碼,代碼體積過大的問題。
- transform-object-rest-spread:Transform rest properties for object destructuring assignment and spread properties for object literals,為對象字面量添加解構賦值和spread屬性
optimize-css-assets-webpack-plugin 不同組件中重複的css可以快速去重
webpack-bundle-analyzer 一個webpack的bundle文件分析工具,將bundle文件以可交互縮放的treemap的形式展示。
compression-webpack-plugin 生產環境可採用gzip壓縮JS和CSS
happypack:通過多進程模型,來加速代碼構建
const os = require(os);n let HappyPack = require(happypack);n let happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length});n exports.plugins = [n new HappyPack({n id: jsx,n threadPool: happyThreadPool,n loaders: [ babel-loader ]n }),nn new HappyPack({n id: coffeescripts,n threadPool: happyThreadPool,n loaders: [ coffee-loader ]n })n ];nn exports.module.loaders = [n {n test: /.js$/,n loaders: [ happypack/loader?id=jsx ]n },n {n test: /.coffee$/,n loaders: [ happypack/loader?id=coffeescripts ]n },n ]n
寫一個webpack插件:
官網介紹:[how to write a plugin](How to write a plugin?)
主要的步驟如下:
- 編寫一個JavaScript命名函數。
- 在它的原型上定義一個apply方法。
- 指定掛載的webpack事件鉤子。
- 處理webpack內部實例的特定數據。
- 功能完成後調用webpack提供的回調。
編寫插件之前要理解compiler和compilation兩個對象,以及webpack生命周期的各個階段和鉤子,plugin比loader強大,通過plugin你可以訪問compliler和compilation過程,通過鉤子攔截webpack的執行。
比如我們可以在構建生成文件時,將所有生成的文件名生成到filelist.md的文件中
webpack會將compilation.assets的內容生成文件,所以可以在構建中利用它生成我們想要的文件。
function FileListPlugin(options) {}nFileListPlugin.prototype.apply = function(compiler) {n compiler.plugin(emit, function(compilation, callback) {n var filelist = In this build:nn;n for (var filename in compilation.assets) {n filelist += (- + filename +n);n }n compilation.assets[filelist.md] = {n source: function() {n return filelist;n },n size: function() {n return filelist.length;n }n };n callback();n });n};nnmodule.exports = FileListPlugin;n
比如我們可以在html-webpack-plugin生成文件後刷新頁面,完成熱更新效果。
var webpack = require(webpack)nvar webpackConfig = require(./webpack.config)nvar compiler = webpack(webpackConfig)nvar hotMiddleware = require(webpack-hot-middleware)(compiler, {n log: () => {}n})ncompiler.plugin(compilation, function (compilation) {n compilation.plugin(html-webpack-plugin-after-emit, function (data, cb) {n hotMiddleware.publish({ action: reload })n cb()n })n})n
比如我們可以在構建完成後,打開一個提示窗口。
class Notifier {n apply(compiler) {n compiler.plugin("done", (stats) => {n const pkg = require("./package.json");n const notifier = require("node-notifier");n const time = ((stats.endTime - stats.startTime) / 1000).toFixed(2);nn notifier.notify({n title: pkg.name,n message: `WebPack is done!n${stats.compilation.errors.length} errors in ${time}s`,n contentImage: "https://path/to/your/logo.png",n });n });n }n}nnmodule.exports = Notifier;n
webpack 插件分析
1. 首先介紹webpack源碼分析方法
- node --inspect-brk ./node_modules/webpack/bin/webpack.js --config ./webpack.config.js
- chrome輸入 chrome://inspect/
2. 主要的流程是:
3. webpack構建的主要鉤子:
主要包括編譯,分析模塊及依賴關係,構建模塊,封裝結果,生成文件等
4. compiler和compilation都繼承於Tapable
webpack的插件是基於Tapable的,Tapable允許你添加和應用插件到javascript模塊中,類似於 NodeJS的EventEmitter,可以被繼承和mixin到其他模塊中,詳情見官網Tapable
其中關鍵的方法是
- plugin(name:string, handler:function)
- apply(...pluginInstances: (AnyPlugin|function)[])
- applyPlugins*(name:string, ...)
- mixin(pt: Object)
tapable主要負責處理事件,採用的是發布訂閱模式,apply相當於trigger,plugin相當於addEventListener
Tapable.prototype.plugin = function plugin(name, fn) {ntif(Array.isArray(name)) {nttname.forEach(function(name) {ntttthis.plugin(name, fn);ntt}, this);nttreturn;nt}ntif(!this._plugins[name]) this._plugins[name] = [fn];ntelse this._plugins[name].push(fn);n};n
plugin方法將插件對應的方法加入一個數組中、註冊到事件(name)上,等待apply的時候串列調用/觸發
Compilation中做了很多事情,處理編譯過程。所對應的方法,如addEntry ,buildModule,processModuleDependencies,createChunkAssets,seal等
後記:
webpack的設計思想、插件機制值得我們深入學習和交流,還有一些特性,比如tree shaking,scope hoisting……
參考文章:
- 細說 webpack 之流程篇
- https://fengmiaosen.github.io/2017/03/21/webpack-core-code/
- Getting Started
- Webpack 源碼(一)-- Tapable 和 事件流
- webpack 源碼解析 | 李黃河BLOG
推薦閱讀:
※webpack打包之 緩存
※webpack增量打包
※create-react-boilerplate: 面向 React 技術棧的工程項目腳手架
※重溫 Webpack, Babel 和 React
※Webpack 3 的新功能:Scope Hoisting
TAG:webpack |