標籤:

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 熱更新

  1. 添加HotModuleReplacementPlugin
  2. entry中添加 "webpack-dev-server/client?localhost:8080/",
  3. entry中添加 "webpack/hot/dev-server"

(熱更新還可以直接用webpack_dev_server --hot --inline,原理也是在entry中添加了上述代碼)

webpack 內置的DllPluginDllReferencePlugin相互配合,前置第三方包的構建,只構建業務代碼,同時能解決Externals多次引用問題。DllReferencePlugin引用DllPlugin配置生成的manifest.json文件,manifest.json包含了依賴模塊和module id的映射關係

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

  1. babili-webpack-plugin:構建在babel之上,它的用處可以看BabiliWebpackPlugin
  2. transform-runtime :解決了babel在每個文件都插入了輔助代碼,代碼體積過大的問題。
  3. 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?)

主要的步驟如下:

  1. 編寫一個JavaScript命名函數。
  2. 在它的原型上定義一個apply方法。
  3. 指定掛載的webpack事件鉤子。
  4. 處理webpack內部實例的特定數據。
  5. 功能完成後調用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 之流程篇
  • fengmiaosen.github.io/2
  • Getting Started
  • Webpack 源碼(一)-- Tapable 和 事件流
  • webpack 源碼解析 | 李黃河BLOG

推薦閱讀:

webpack打包之 緩存
webpack增量打包
create-react-boilerplate: 面向 React 技術棧的工程項目腳手架
重溫 Webpack, Babel 和 React
Webpack 3 的新功能:Scope Hoisting

TAG:webpack |