webpack 之 loader 和 plugin 簡介

做者介紹:趙鵬,美團點評點餐團隊成員javascript

webpack是一個模塊打包器(module bundler),提供了一個核心,核心提供了不少開箱即用的功能,同時它能夠用loader和plugin來擴展。webpack自己結構精巧,基於tapable的插件架構,擴展性強,衆多的loader或者plugin讓webpack顯得很複雜。
webpack經常使用配置包括:devtool、entry、 output、module、resolve、plugins、externals等,本文主要介紹下webpack經常使用的loader和plugincss

webpack容許咱們使用loader來處理文件,loader是一個導出爲function的node模塊。能夠將匹配到的文件進行一次轉換,同時loader能夠鏈式傳遞。html

loader的使用方式

通常loader的使用方式分爲三種:
1:在配置文件webpack.config.js中配置vue

module.exports = {
  module: {
    rules: [
      {
        test: /\.txt$/,
        use: 'raw-loader'
      }
    ]
  }
}複製代碼

2:經過命令行參數方式java

webpack --module-bind 'txt=raw-loader'複製代碼

3:經過內聯使用node

import txt from 'raw-loader!./file.txt';複製代碼

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()。jquery

module.exports = {
  module: {
    rules: [
        {
          test: /\.scss$/,
          use:[
              {loader:'style-loader'},
              {loader:'css-loader',options:{sourceMap:true,modules:true}},
              {loader:'sass-loader',options:{sourceMap:true}}
          ],
          exclude:/node_modules/
      }
    ]
  }
}複製代碼
  • 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) {
      this.cacheable && this.cacheable();
      this.value = content;
      return "module.exports = " + JSON.stringify(content);
    }複製代碼

    webpack的plugin比loader強大,經過鉤子能夠涉及整個構建流程,能夠作一些在構建範圍內的事情。webpack

webpack經常使用的plugin

  • 官網介紹plugins
  • 第三方插件awesome-webpack
  • 首先webpack內置UglifyJsPlugin,壓縮和混淆代碼。
  • webpack內置CommonsChunkPlugin,提升打包效率,將第三方庫和業務代碼分開打包。
  • ProvidePlugin:自動加載模塊,代替require和import
    new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery'
      })複製代碼
  • html-webpack-plugin能夠根據模板自動生成html代碼,並自動引用css和js文件
  • extract-text-webpack-plugin 將js文件中引用的樣式單獨抽離成css文件
  • DefinePlugin 編譯時配置全局變量,這對開發模式和發佈模式的構建容許不一樣的行爲很是有用。
    new webpack.DefinePlugin({
      PRODUCTION: JSON.stringify(true),
      VERSION: JSON.stringify("5fa3b9"),
      BROWSER_SUPPORTS_HTML5: true,
      TWO: "1+1",
      "typeof window": JSON.stringify("object")
    })複製代碼
  • HotModuleReplacementPlugin 熱更新git

    • 添加HotModuleReplacementPlugin
    • entry中添加 "webpack-dev-server/client?http://localhost:8080/",
    • entry中添加 "webpack/hot/dev-server"
    • (熱更新還能夠直接用webpack_dev_server --hot --inline,原理也是在entry中添加了上述代碼)
  • webpack 內置的DllPluginDllReferencePlugin相互配合,前置第三方包的構建,只構建業務代碼,同時能解決Externals屢次引用問題。DllReferencePlugin引用DllPlugin配置生成的manifest.json文件,manifest.json包含了依賴模塊和module id的映射關係github

  • babili-webpack-plugin、transform-runtime 、transform-object-rest-spread

    • babili-webpack-plugin:構建在babel之上 why
    • 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');
        let HappyPack = require('happypack');
        let happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length});
        exports.plugins = [
          new HappyPack({
            id: 'jsx',
            threadPool: happyThreadPool,
            loaders: [ 'babel-loader' ]
          }),
    
          new HappyPack({
            id: 'coffeescripts',
            threadPool: happyThreadPool,
            loaders: [ 'coffee-loader' ]
          })
        ];
    
        exports.module.loaders = [
          {
            test: /\.js$/,
            loaders: [ 'happypack/loader?id=jsx' ]
          },
          {
            test: /\.coffee$/,
            loaders: [ 'happypack/loader?id=coffeescripts' ]
          },
        ]複製代碼

寫一個webpack插件:

官網介紹: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) {}
FileListPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', function(compilation, callback) {
    var filelist = 'In this build:\n\n';
    for (var filename in compilation.assets) {
      filelist += ('- '+ filename +'\n');
    }
    compilation.assets['filelist.md'] = {
      source: function() {
        return filelist;
      },
      size: function() {
        return filelist.length;
      }
    };
    callback();
  });
};

module.exports = FileListPlugin;複製代碼

好比咱們能夠在html-webpack-plugin生成文件後刷新頁面,完成熱更新效果。

var webpack = require('webpack')
var webpackConfig = require('./webpack.config')
var compiler = webpack(webpackConfig)
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
  log: () => {}
})
compiler.plugin('compilation', function (compilation) {
  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
    hotMiddleware.publish({ action: 'reload' })
    cb()
  })
})複製代碼

好比咱們能夠在構建完成後,打開一個提示窗口。

class Notifier {
  apply(compiler) {
    compiler.plugin("done", (stats) => {
      const pkg = require("./package.json");
      const notifier = require("node-notifier");
      const time = ((stats.endTime - stats.startTime) / 1000).toFixed(2);

      notifier.notify({
        title: pkg.name,
        message: `WebPack is done!\n${stats.compilation.errors.length} errors in ${time}s`,
        contentImage: "https://path/to/your/logo.png",
      });
    });
  }
}

module.exports = Notifier;複製代碼

webpack 插件分析

  1. 首先介紹webpack源碼分析方法
  • node --inspect-brk ./node_modules/webpack/bin/webpack.js --config ./webpack.config.js
  • chrome輸入 chrome://inspect/
  1. 主要的流程是:
  2. webpack構建的主要鉤子:


主要包括編譯,分析模塊及依賴關係,構建模塊,封裝結果,生成文件等

  1. 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) {
    if(Array.isArray(name)) {
        name.forEach(function(name) {
            this.plugin(name, fn);
        }, this);
        return;
    }
    if(!this._plugins[name]) this._plugins[name] = [fn];
    else this._plugins[name].push(fn);
};複製代碼

plugin方法將插件對應的方法加入一個數組中、註冊到事件(name)上,等待apply的時候串行調用/觸發

Compilation中作了不少事情,處理編譯過程。所對應的方法,如addEntry ,buildModule,processModuleDependencies,createChunkAssets,seal等

後記:

webpack的設計思想、插件機制值得咱們深刻學習和交流,還有一些特性,好比tree shaking,scope hoisting……
參考文章:

相關文章
相關標籤/搜索