【webpack】插件 plugins

插件(plugins)

插件是 webpack 的支柱功能。插件目的在于解決 loader 無(wú)法實(shí)現(xiàn)的其他事。

webpack 插件是一個(gè)具有 apply 屬性的 JavaScript 對(duì)象。apply 屬性會(huì)被 webpack compiler 調(diào)用,并且 compiler 對(duì)象可在整個(gè)編譯生命周期訪問(wèn)。如ConsoleLogOnBuildWebpackPlugin.js

class ConsoleLogOnBuildWebpackPlugin {
    apply(compiler) {
    //compiler hook 的 tap 方法的第一個(gè)參數(shù),
    //應(yīng)該是駝峰式命名的插件名稱(chēng)
        compiler.hooks.run.tap(pluginName, compilation => {
            console.log("webpack 構(gòu)建過(guò)程開(kāi)始!");
        });
    }
}

一些插件簡(jiǎn)介

Name Description
AggressiveSplittingPlugin 將原來(lái)的 chunk 分成更小的 chunk
BabelMinifyWebpackPlugin 使用 babel-minify進(jìn)行壓縮
BannerPlugin 在每個(gè)生成的 chunk 頂部添加 banner
CommonsChunkPlugin 提取 chunks 之間共享的通用模塊
CompressionWebpackPlugin 預(yù)先準(zhǔn)備的資源壓縮版本,使用 Content-Encoding 提供訪問(wèn)服務(wù)
ContextReplacementPlugin 重寫(xiě) require 表達(dá)式的推斷上下文
CopyWebpackPlugin 將單個(gè)文件或整個(gè)目錄復(fù)制到構(gòu)建目錄
DefinePlugin 允許在編譯時(shí)(compile time)配置的全局常量
DllPlugin 為了極大減少構(gòu)建時(shí)間,進(jìn)行分離打包
EnvironmentPlugin DefinePlugin 中 process.env 鍵的簡(jiǎn)寫(xiě)方式。
ExtractTextWebpackPlugin 從 bundle 中提取文本(CSS)到單獨(dú)的文件
HotModuleReplacementPlugin 啟用模塊熱替換(Enable Hot Module Replacement - HMR)
HtmlWebpackPlugin 簡(jiǎn)單創(chuàng)建 HTML 文件,用于服務(wù)器訪問(wèn)
I18nWebpackPlugin 為 bundle 增加國(guó)際化支持
IgnorePlugin 從 bundle 中排除某些模塊
LimitChunkCountPlugin 設(shè)置 chunk 的最小/最大限制,以微調(diào)和控制 chunk
LoaderOptionsPlugin 用于從 webpack 1 遷移到 webpack 2
MinChunkSizePlugin 確保 chunk 大小超過(guò)指定限制
NoEmitOnErrorsPlugin 在輸出階段時(shí),遇到編譯錯(cuò)誤跳過(guò)
NormalModuleReplacementPlugin 替換與正則表達(dá)式匹配的資源
NpmInstallWebpackPlugin 在開(kāi)發(fā)時(shí)自動(dòng)安裝缺少的依賴(lài)
ProvidePlugin 不必通過(guò) import/require 使用模塊
SourceMapDevToolPlugin 對(duì) source map進(jìn)行更細(xì)粒度的控制
EvalSourceMapDevToolPlugin 對(duì) eval source map 進(jìn)行更細(xì)粒度的控制
UglifyjsWebpackPlugin 可以控制項(xiàng)目中 UglifyJS 的版本
ZopfliWebpackPlugin 通過(guò) node-zopfli 將資源預(yù)先壓縮的版本

更多第三方插件,請(qǐng)查看 awesome-webpack 列表。


用法案例:CommonsChunkPlugin

The CommonsChunkPlugin 已經(jīng)從 webpack v4 legato 中移除。想要了解在最新版本中如何處理 chunk,請(qǐng)查看 SplitChunksPlugin(該插件配置過(guò)程更為便捷高效)。

image

element-ui初始化工程為例看下vendor.js

import Vue from 'vue'
import ElementUI from 'element-ui'

由于插件可以攜帶參數(shù)/選項(xiàng),你必須在 webpack 配置中,向 plugins 屬性傳入 new 實(shí)例。根據(jù)你的 webpack 用法,這里有多種方式使用插件。

//package.json中引入依賴(lài)
"devDependencies": {
    ...
    "html-webpack-plugin": "^2.24.1",
    "webpack": "^2.4.1",
    ...
 }
//webpack.config.js中引入
//通過(guò) npm 直接安裝插件
const HtmlWebpackPlugin 
    = require('html-webpack-plugin');
//訪問(wèn)webpack內(nèi)置的插件
//CommonsChunkPlugin就是內(nèi)置插件之一
const webpack = require('webpack'); 

功能:該插件用于提取 chunks 之間共享的通用模塊。使用 CommonsChunkPlugin 從「應(yīng)用程序 bundle」中提取 vendor 引用(vendor reference) 到 vendor bundle,并把引用 vendor 的部分替換為 __webpack_require__() 調(diào)用

image

Node API(O(∩_∩)O~,這個(gè)是啥)

TODO」即便使用 Node API,用戶(hù)也應(yīng)該在配置中傳入 plugins 屬性。compiler.apply 并不是推薦的使用方式。

//some-node-script.js
//訪問(wèn) webpack 運(yùn)行時(shí)(runtime)
const webpack = require('webpack'); 
const configuration = require('./webpack.config.js');

let compiler = webpack(configuration);
compiler.apply(new webpack.ProgressPlugin());

compiler.run(function(err, stats) {
// ...
});

你知道嗎:以上看到的示例和 webpack 自身運(yùn)行時(shí)(runtime) 極其類(lèi)似。wepback 源碼中隱藏有大量使用示例,你可以用在自己的配置和腳本中。


官網(wǎng)插件

HtmlWebpackPlugin

根據(jù)配置的入口和出口,自動(dòng)生成index.html,且會(huì)自動(dòng)引入相關(guān)的依賴(lài)。

  • 如果你想要了解更多 HtmlWebpackPlugin 插件提供的全部功能和選項(xiàng),那么你就應(yīng)該多多熟悉 HtmlWebpackPlugin 倉(cāng)庫(kù)。
  • 你還可以看一下 html-webpack-template,除了默認(rèn)模板之外,還提供了一些額外的功能。

CleanWebpackPlugin

  • 你可能已經(jīng)注意到,由于過(guò)去的指南和代碼示例遺留下來(lái),導(dǎo)致我們的 /dist 文件夾相當(dāng)雜亂。
  • webpack 會(huì)生成文件,然后將這些文件放置在 /dist 文件夾中,但是 webpack 無(wú)法追蹤到哪些文件是實(shí)際在項(xiàng)目中用到的。
  • 通常,在每次構(gòu)建前清理 /dist 文件夾,是比較推薦的做法,因此只會(huì)生成用到的文件。讓我們完成這個(gè)需求。
  • clean-webpack-plugin 是一個(gè)比較普及的管理插件,讓我們安裝和配置下。
npm install clean-webpack-plugin --save-dev
  • webpack.config.js中配置:
  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
+ const CleanWebpackPlugin = require('clean-webpack-plugin');

  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
    plugins: [
+     new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Output Management'
      })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };
  • 現(xiàn)在執(zhí)行 npm run build,再檢查 /dist 文件夾。如果一切順利,你現(xiàn)在應(yīng)該不會(huì)再看到舊的文件,只有構(gòu)建后生成的文件!

WebpackManifestPlugin

該插件能夠在項(xiàng)目根目錄生成一份manifest.json的文件,通過(guò)該文件的映射關(guān)系可以讓我們知道webpack是如何追蹤所有模塊并映射到輸出bundle中的。

解讀webpack-manifest-plugin

//安裝插件:
npm install --save-dev webpack-manifest-plugin

//在 webpack.config.js中引入,大致如下:
var ManifestPlugin = require('webpack-manifest-plugin');
module.exports = {
   // ...
   plugins: [
     new ManifestPlugin() 
     // ManifestPlugin方法可以接受一些選項(xiàng)參數(shù)options,如
     // new ManifestPlugin(options)
   ]
};

//3.可選參數(shù)如下:
options.fileName 表示要生成文件的名稱(chēng),默認(rèn)為manifest.json
options.publicPath 表示生成映射文件的路徑,默認(rèn)為output.publicPath

還有其他的參數(shù)見(jiàn)官網(wǎng)
https://github.com/danethurber/webpack-manifest-plugin

HotModuleReplacementPlugin

開(kāi)發(fā)環(huán)境啟用 HMR:?jiǎn)⒂么斯δ軐?shí)際上相當(dāng)簡(jiǎn)單。而我們要做的,就是更新 webpack-dev-server 的配置,和使用 webpack 內(nèi)置的 HMR 插件

image

IgnorePlugin

importrequire 調(diào)用時(shí),忽略以下正則表達(dá)式匹配的模塊:

// requestRegExp 匹配(test)資源請(qǐng)求路徑的正則表達(dá)式。
// contextRegExp (可選)匹配(test)資源上下文(目錄)的正則表達(dá)式。
new webpack.IgnorePlugin(
    requestRegExp, [contextRegExp]
)

舉例:moment

比如我們要使用moment這個(gè)第三方依賴(lài)庫(kù),該庫(kù)主要是對(duì)時(shí)間進(jìn)行格式化,并且支持多個(gè)國(guó)家語(yǔ)言。

import moment from 'moment'

//設(shè)置語(yǔ)言
moment.locale('zh-cn');
let r = moment().endOf('day').fromNow();
console.log(r);
  • 這樣打印出來(lái)的就是中文的時(shí)間,因?yàn)槲以谑褂眠@個(gè)API的時(shí)候,前面設(shè)置了語(yǔ)言類(lèi)型moment.locale為中文zh-cn
  • 但是,雖然我設(shè)置了語(yǔ)言為中文,但是在打包的時(shí)候,是會(huì)將所有語(yǔ)言都打包進(jìn)去的。這樣就導(dǎo)致包很大,打包速度又慢
  • 所以,最好能夠少打包一些沒(méi)用的依賴(lài)目錄進(jìn)去
  • moment的包含./locale/該字段路徑的文件目錄就是各國(guó)語(yǔ)言的目錄,比如./locale/zh-cn就是中文語(yǔ)言
  • 使用IgnorePlugin
let webpack = require('webpack');
plugins:[
    //moment這個(gè)庫(kù)中,如果引用了./locale/目錄的內(nèi)容,
    //就忽略掉,不會(huì)打包進(jìn)去
    new webpack.IgnorePlugin(/\.\/locale/,/moment/),
]
  • 我們雖然按照上面的方法忽略了包含./locale/該字段路徑的文件目錄,但是也使得我們使用的時(shí)候不能顯示中文語(yǔ)言了,所以這個(gè)時(shí)候可以手動(dòng)引入中文語(yǔ)言的目錄
import moment from 'moment'
//手動(dòng)引入所需要的語(yǔ)言包
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
let r = moment().endOf('day').fromNow();
console.log(r);
  • 這樣就OK了。既能夠顯示中文,又把不必要的語(yǔ)言包都忽略打包了

DllPlugin

DLLPluginDLLReferencePlugin 用某種方法實(shí)現(xiàn)了拆分 bundles,同時(shí)還大大提升了構(gòu)建的速度dll是一種最簡(jiǎn)單粗暴并且極其有效的優(yōu)化方式)。

  1. Webpack 打包的時(shí)候,對(duì)于一些不經(jīng)常更新的第三方庫(kù),比如 reactlodash,我們希望能和自己的代碼分離開(kāi),Webpack 社區(qū)有兩種方案
    CommonsChunkPluginDLLPlugin
  2. 對(duì)于 CommonsChunkPlugin
    • webpack 每次打包實(shí)際還是需要去處理這些第三方庫(kù),只是打包完之后,能把第三方庫(kù)和我們自己的代碼分開(kāi)。
    • 且每次工程重新打包,verdors也會(huì)重新打包,打包的chunkhash就會(huì)改變,導(dǎo)致每次發(fā)布,vendors的緩存都會(huì)失效。這樣增加了用戶(hù)的流量消耗和首屏加載時(shí)間。
  3. DLLPlugin 則是能把第三方代碼完全分離開(kāi),即每次只打包項(xiàng)目自身的代碼。引入的第三方包沒(méi)有改變就不需要重新打包。
  4. DLLPlugin具體用法:
/**
要使用 DLLPlugin,需要額外新建一個(gè)配置文件。
所以對(duì)于用這種方式打包的項(xiàng)目,
一般會(huì)有下面兩個(gè)配置文件:
webpack.config.js
webpack.dll.config.js

先來(lái)看下 webpack.dll.config.js
*/
const webpack = require('webpack')
const library = '[name]_lib'
const path = require('path')
module.exports = {
    entry: {
        vendors: ['react', 'lodash']
        //上面的vendors,看到有人有的libs
    },
    output: {
        filename: '[name].dll.js',
        path: 'dist/',
        library
    },
    plugins: [
        new webpack.DllPlugin({
        //執(zhí)行的上下文環(huán)境,對(duì)之后DllReferencePlugin有用
            context: __dirname,
            path: path.join(
                __dirname, 'dist/[name]-manifest.json'
            ),
            // This must match the output.library option above
            name: library
        }),
    ],
}


//再改下 webpack.config.js 文件
const webpack = require('webpack')
module.exports = {
    entry: {
      app: './src/index'
    },
    output: {
      filename: 'app.bundle.js',
      path: 'dist/',
    },
    plugins: [
        new webpack.DllReferencePlugin({
            context: __dirname,
            //下面的vendors有人用libs
            manifest: require('./dist/vendors-manifest.json')
        })
    ]
}

/**
manifest: require('./dist/vendors-manifest.json') 
這里的路徑要和 webpack.dll.config.js 里面的對(duì)應(yīng)。
然后運(yùn)行:
*/
$ webpack --config webpack.dll.config.js
$ webpack --config webpack.config.js

//然后你的 html 文件像下面這樣引用
<script src="/dist/vendors.dll.js"></script>
<script src="/dist/app.bundle.js"></script>

/**
上面的用法也存在一些不方便的地方,
比如在 webpack.config.js 中要明確
指出對(duì)應(yīng)的 manifest.json 文件。
還有當(dāng) DLL 需要更新的時(shí)候,
比如 react 升級(jí)了,或者加入新的第三方庫(kù),
都需要手動(dòng)像下面這樣編譯一次。
*/
$ webpack --config webpack.dll.config.js


/**
---非官方插件(不需關(guān)注,沒(méi)有緩存作用了吧?)----
因?yàn)樯厦孢@些問(wèn)題,
所以基于官方的 DllReferencePlugin,
有一個(gè) Dll Link Plugin。鏈接如下:
www.npmjs.com/package/dll-link-webpack-plugin
使用這個(gè)插件,
只需要對(duì) webpack.config.js 作下小小的改動(dòng)
*/
const webpack = require('webpack')
const DllLinkPlugin = require('dll-link-webpack-plugin')

module.exports = {
    // ...
    plugins: [
        new DllLinkPlugin({
            config: require('webpack.dll.config.js')
        })
    ]
}
/**
直接替換掉 DllReferencePlugin,
然后傳入對(duì)應(yīng)的 DLL 配置文件就可以了。
每次打包的時(shí)候,只需要運(yùn)行
*/
$ webpack --config webpack.config.js
/**
上面的命令便會(huì)自動(dòng)生成對(duì)應(yīng)的 vendors 文件,
需要更新的時(shí)候,也會(huì)自動(dòng)更新。
*/

SplitChunksPlugin

  1. 在默認(rèn)情況下,SplitChunksPlugin 僅僅影響按需加載的代碼塊,因?yàn)楦某跏級(jí)K會(huì)影響HTML文件應(yīng)包含的腳本標(biāo)記以運(yùn)行項(xiàng)目。

  2. webpack將根據(jù)以下條件自動(dòng)拆分代碼塊:

    • 會(huì)被共享的代碼塊或者 node_mudules 文件夾中的代碼塊
    • 體積大于30KB的代碼塊(在gz壓縮前)
    • 按需加載代碼塊時(shí)的并行請(qǐng)求數(shù)量不超過(guò)5個(gè)
    • 加載初始頁(yè)面時(shí)的并行請(qǐng)求數(shù)量不超過(guò)3個(gè)
    //舉例:
    
    // 文件一:impvue.js
    import 'vue'
    ...
    
    // 文件二:index.js
    // 動(dòng)態(tài)加載 impvue.js
    import('./impvue')
    ...
    
    /**
    打包之后的結(jié)果會(huì)創(chuàng)建一個(gè)包含 vue 的獨(dú)立代碼塊,
    當(dāng)包含 impvue.js 的原始代碼塊被調(diào)用時(shí),
    這個(gè)獨(dú)立代碼塊會(huì)并行請(qǐng)求進(jìn)來(lái)。
    原因:
        1) vue 來(lái)自 node_modules 文件夾
        2) vue 體積超過(guò)30KB
        3) 導(dǎo)入調(diào)用時(shí)的并行請(qǐng)求數(shù)為2
        4) 不影響頁(yè)面初始加載
    */
    
    
  3. 我們這樣做的原因是因?yàn)椋?code>vue代碼并不像你的業(yè)務(wù)代碼那樣經(jīng)常變動(dòng),把它單獨(dú)提取出來(lái)就可以和你的業(yè)務(wù)代碼分開(kāi)緩存,極大的提高效率。

  4. SplitChunksPlugin的默認(rèn)配置

splitChunks: {
    /**
    chunks屬性用來(lái)選擇分割哪些代碼塊,
    可選值有:
        'all'(所有代碼塊)
        'async'(按需加載的代碼塊)
        'initial'(初始化代碼塊)
    */
    chunks: "async",
    minSize: 30000, // 模塊的最小體積
    minChunks: 1, // 模塊的最小被引用次數(shù)
    maxAsyncRequests: 5, // 按需加載的最大并行請(qǐng)求數(shù)
    maxInitialRequests: 3, // 一個(gè)入口最大并行請(qǐng)求數(shù)
    automaticNameDelimiter: '~', // 文件名的連接符
    name: true,
    /**
    緩存組是個(gè)有趣的功能:
    在默認(rèn)設(shè)置中,
    會(huì)將`node_mudules`中的模塊打包進(jìn)`vendors`,
    引用超過(guò)兩次的模塊分配到`default` `bundle` 中。
    更可以通過(guò) `priority` 來(lái)設(shè)置優(yōu)先級(jí)。
    */
    cacheGroups: { // 緩存組
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
        default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}
  1. 在項(xiàng)目中添加SplitChunksPlugin
/**
為了方便演示,我們先安裝兩個(gè)類(lèi)庫(kù): lodash 和 axios,
npm i lodash axios -S
修改 main.js,引入 lodash 和axios 并調(diào)用相應(yīng)方法:
*/
import Modal from './components/modal/modal'
import './assets/style/common.less'
import _ from 'lodash'
import axios from 'axios'
const App = function () {
  let div = document.createElement('div')
  div.setAttribute('id', 'app')
  document.body.appendChild(div)
  let dom = document.getElementById('app')
  let modal = new Modal()
  dom.innerHTML = modal.template({
    title: '標(biāo)題',
    content: '內(nèi)容',
    footer: '底部'
  })
}
const app = new App()
console.log(_.camelCase('Foo Bar'))
axios.get('aaa')

/**
-----------------------------
使用SplitChunksPlugin不需要安裝任何依賴(lài),
只需在 webpack.config.js 中的 config對(duì)象
添加 optimization 屬性:
*/
optimization: {
    splitChunks: {
      chunks: 'initial',
      automaticNameDelimiter: '.',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: 1
        }
      }
    },
    //一般都配合runtimeChunkPlugin使用
    runtimeChunk: {
      name: entrypoint => `manifest.${entrypoint.name}`
    }
  }
/**
配置 runtimeChunk 會(huì)給每個(gè)入口添加一個(gè)只包含runtime的額外的代碼塊,
name 的值也可以是字符串,不過(guò)這樣就會(huì)給每個(gè)入口添加相同的 runtime,
配置為函數(shù)時(shí),返回當(dāng)前的entry對(duì)象,即可分入口設(shè)置不同的runtime。
*/

/**
我們?cè)侔惭b一個(gè) webpack-bundle-analyzer,
這個(gè)插件會(huì)清晰的展示出打包后的各個(gè)bundle所依賴(lài)的模塊:
引入:
*/
const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer').BundleAnalyzerPlugin
//使用,在plugins數(shù)組中添加即可:
new BundleAnalyzerPlugin()
  1. 可以看看Webpack4SplitChunksPlugin這里提及的問(wèn)題

BannerPlugin

為每個(gè) chunk 文件頭部添加 banner(注釋/版權(quán))。

new webpack.BannerPlugin(options)
//options選項(xiàng):
{
  banner: string, // 其值為字符串,將作為注釋存在
  raw: boolean, // 如果值為 true,將直出,不會(huì)被作為注釋
  entryOnly: boolean, // 如果值為 true,將只在入口 chunks 文件中添加
  test: string | RegExp | Array,
  include: string | RegExp | Array,
  exclude: string | RegExp | Array,
}

CopyWebpackPlugin:拷貝靜態(tài)文件

將單個(gè)文件或整個(gè)目錄直接從源目錄拷貝到構(gòu)建目錄.

//config:配置了一些參數(shù)
new CopyWebpackPlugin([
  {
    from: path.resolve(__dirname, '../static'),
    to: config.build.assetsSubDirectory,
    ignore: ['.*']
  },
  //上面的static目錄先不管,
  //我們先看這個(gè),文件中是一些客戶(hù)可自行修改的配置
  //獨(dú)立出來(lái),方便修改
  {
    from: path.resolve(config.directory.root, 'sysconfig.js'),
    to: 'sysconfig.js'
  },
])

打包好的工程下:


image

image

ExtractTextWebpackPlugin:分離CSS文件

MiniCssExtractPlugin這個(gè)呢?

const ExtractTextPlugin 
    = require("extract-text-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: "css-loader"
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin("styles.css"),
    /**
    我看我們的工程中是這樣配置的
    具體的后續(xù)再好好看下:
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      allChunks: true,
    }),
    */
  ]
}

它會(huì)將所有的入口 chunk(entry chunks)中引用的 *.css,移動(dòng)到獨(dú)立分離的 CSS 文件。因此,你的樣式將不再內(nèi)嵌到 JS bundle 中,而是會(huì)放到一個(gè)單獨(dú)的 CSS 文件(即 styles.css)當(dāng)中。 如果你的樣式文件大小較大,這會(huì)做更快提前加載,因?yàn)?CSS bundle 會(huì)跟 JS bundle 并行加載。

再來(lái)看個(gè)CSS優(yōu)化(壓縮)相關(guān)的另一個(gè)插件optimize-css-assets-webpack-plugin:

/**這個(gè)插件可以接受下列配置(均為可選):
assetNameRegExp: 正則,用于匹配需要優(yōu)化的資源名。默認(rèn)值是 /\.css$/g

cssProcessor: 用于壓縮和優(yōu)化CSS 的處理器,默認(rèn)是 cssnano.這是一個(gè)函數(shù),
應(yīng)該按照 cssnano.process 接口(接受一個(gè)CSS和options參數(shù),返回一個(gè)Promise)

canPrint: {bool} 表示插件能夠在console中打印信息,默認(rèn)值是true
*/

// webpack4+
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// webpack4 
module.exports = {
  optimization: {
    minimizer: [
      new UglifyJsPlugin({
        cache: true,
        parallel: true,
        sourcMap: true
      }),
      new OptimizeCssAssetsPlugin({
        assetNameRegExp: /\.optimize\.css$/g,
        cssProcessor: require('cssnano'),
        cssProcessorOptions: { safe: true, discardComments: { removeAll: true } },
        canPrint: true
      }),
    ],
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader'
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css"
    }),
  ]
};

//webpack3中一般配合 ExtractTextPlugin一起使用。

UglifyjsWebpackPlugin

此插件使用uglify-js進(jìn)行js文件的壓縮。

  • 下面說(shuō)明是來(lái)自:關(guān)閉開(kāi)發(fā)環(huán)境的代碼壓縮UglifyJsPlugin
  • 開(kāi)發(fā)環(huán)境是不需要去壓縮代碼,主要是因?yàn)樘男阅芰耍啃薷囊粋€(gè)地方就要花幾秒去等待頁(yè)面渲染,非常浪費(fèi)開(kāi)發(fā)時(shí)間,解決辦法就是配置不同的環(huán)境變量
  • 去在開(kāi)發(fā)環(huán)境的時(shí)候不要這個(gè)UglifyJsPlugin插件,為此,webpack4又增加了新的Mode,且默認(rèn)值是production;而且更新后的webpack默認(rèn)是有UglifyJsPlugin這個(gè)配置的
  • 也就是說(shuō)在不設(shè)置任何環(huán)境變量的情況下,始終會(huì)有壓縮代碼的行為出現(xiàn),導(dǎo)致編譯極其耗時(shí),那我的解決辦法就是在package.json文件啟動(dòng)時(shí)設(shè)置環(huán)境變量:
    image

Tree Shaking:一個(gè)術(shù)語(yǔ),通常用于描述移除 JavaScript 上下文中的未引用代碼(dead-code)。新的webpack 4 正式版本,擴(kuò)展了這個(gè)檢測(cè)能力,通過(guò) package.jsonsideEffects 屬性作為標(biāo)記,向 compiler 提供提示,表明項(xiàng)目中的哪些文件是 pure(純的 ES2015 模塊),由此可以安全地刪除文件中未使用的部分。

  • 這種方式是通過(guò) package.jsonsideEffects 屬性來(lái)實(shí)現(xiàn)的。
{
  "name": "your-project",
  "sideEffects": false
}
  • 如果你的代碼確實(shí)有一些副作用,那么可以改為提供一個(gè)數(shù)組:
{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js"
  ]
}
  • webpack 4 開(kāi)始,也可以通過(guò) mode 配置選項(xiàng)輕松切換到壓縮輸出,只需設(shè)置為 production
  • 配置webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
 },
 mode: "production"
};

DefinePlugin

實(shí)用webpack插件之DefinePlugin

DefinePlugin 允許創(chuàng)建一個(gè)在編譯時(shí)可以配置的全局常量。這可能會(huì)對(duì)開(kāi)發(fā)模式和發(fā)布模式的構(gòu)建允許不同的行為非常有用。如果在開(kāi)發(fā)構(gòu)建中,而不在發(fā)布構(gòu)建中執(zhí)行日志記錄,則可以使用全局常量來(lái)決定是否記錄日志。這就是 DefinePlugin 的用處,設(shè)置它,就可以忘記開(kāi)發(fā)和發(fā)布構(gòu)建的規(guī)則。

new webpack.DefinePlugin({
/**
在vue-cli創(chuàng)建的項(xiàng)目中,
凡是src下的文件,都可以訪問(wèn)到下面這些變量,如VERSION,
例如main.js,App.vue等等
*/
  PRODUCTION: JSON.stringify(true),
  VERSION: JSON.stringify("5fa3b9"),
  BROWSER_SUPPORTS_HTML5: true,
  TWO: "1+1",
  //根據(jù)process.env.NODE_ENV區(qū)分環(huán)境,引入配置文件
  process.env: {NODE_ENV: "development"},
})

EnvironmentPlugin: 是一個(gè)通過(guò) DefinePlugin 來(lái)設(shè)置 process.env 環(huán)境變量的快捷方式。

new webpack.EnvironmentPlugin(['NODE_ENV', 'DEBUG'])
//上面的寫(xiě)法和下面這樣使用 DefinePlugin 的效果相同:
new webpack.DefinePlugin({
 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
 'process.env.DEBUG': JSON.stringify(process.env.DEBUG)
})

HashedModuleIdsPlugin

該插件會(huì)根據(jù)模塊的相對(duì)路徑生成一個(gè)四位數(shù)的hash作為模塊id, 建議用于生產(chǎn)環(huán)境。

//其他具體說(shuō)明再看看
new webpack.HashedModuleIdsPlugin({
  hashFunction: 'sha256',
  hashDigest: 'hex',
  hashDigestLength: 20
})

ZipPlugin(好像官網(wǎng)沒(méi)列)

發(fā)布的時(shí)候可以使用 壓縮插件將資源(圖片,配置文件等)壓縮成一個(gè) .zip 文件

//參數(shù)中配置了需要將工程打包
if (config.build.productionToZip) {
  const ZipPlugin = require('zip-webpack-plugin')
  webpackConfig.plugins.push(
    new ZipPlugin({
      path: config.build.assetsZipRoot,
      filename: 'build.zip',
      extension: 'zip',
    })
  )
}

ModuleConcatenationPlugin

  • webpack2處理后的每個(gè)模塊均被一個(gè)函數(shù)包裹,如下:
/* 50 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
    window.lib = {}
    ...   
/* harmony default export */ __webpack_exports__["a"] = (window.lib);
 
/***/ }),
  • 這樣會(huì)帶來(lái)一個(gè)問(wèn)題:降低瀏覽器中JS執(zhí)行效率,這主要是閉包函數(shù)降低了JS引擎解析速度。
  • 于是webpack團(tuán)隊(duì)參考Closure CompilerRollup JS,將一些有聯(lián)系的模塊,放到一個(gè)閉包函數(shù)里面去,通過(guò)減少閉包函數(shù)數(shù)量從而加快JS的執(zhí)行速度。
  • webpack3通過(guò)設(shè)置ModuleConcatenationPlugin使用這個(gè)新特性:
module.exports = { 
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
}
  • 記住,此插件僅適用于由 webpack 直接處理的 ES6 模塊。在使用轉(zhuǎn)譯器(transpiler)時(shí),你需要禁用對(duì)模塊的處理(例如 Babel 中的 modules 選項(xiàng))。

NpmInstallWebpackPlugin

在開(kāi)發(fā)時(shí)自動(dòng)安裝缺少的依賴(lài):package.json中需要配置了才會(huì)自動(dòng)安裝哦

//安裝
npm install --save-dev npm-install-webpack-plugin

//用法
//不要忘記引入哦
const NpmInstallPlugin = require('npm-install-webpack-plugin')
//插件配置:
plugins: [
  new NpmInstallPlugin()
],

//相當(dāng)于:
plugins: [
  new NpmInstallPlugin({
    // 使用 --save 或者 --save-dev
    dev: false,
    // 安裝缺少的 peerDependencies
    peerDependencies: true,
    // 減少控制臺(tái)日志記錄的數(shù)量
    quiet: false,
    // npm command used inside company, yarn is not supported yet
    npm: 'tnpm'
  });
],

//可以提供一個(gè) Function 來(lái)動(dòng)態(tài)設(shè)置 dev:
plugins: [
  new NpmInstallPlugin({
    dev: function(module, path) {
      return [
        "babel-preset-react-hmre",
        "webpack-dev-middleware",
        "webpack-hot-middleware",
      ].indexOf(module) !== -1;
    },
  }),
],

ProvidePlugin

自動(dòng)加載模塊,而不必到處 importrequire

//要自動(dòng)加載 jquery,我們可以將兩個(gè)變量都指向?qū)?yīng)的 node 模塊:
new webpack.ProvidePlugin({
  $: 'jquery',
  jQuery: 'jquery'
})
//然后在我們?nèi)我庠创a中:
$('#item'); // <= 起作用
jQuery('#item'); // <= 起作用

//使用:Lodash Map
new webpack.ProvidePlugin({
  _map: ['lodash', 'map']
})

//使用:Vue.js
new webpack.ProvidePlugin({
  Vue: ['vue/dist/vue.esm.js', 'default']
})

LimitChunkCountPlugin

當(dāng)你在編寫(xiě)代碼時(shí),可能已經(jīng)添加了許多代碼分離點(diǎn)(code split point)來(lái)實(shí)現(xiàn)按需加載(load stuff on demand)。在編譯完之后,你可能會(huì)注意到有一些很小的 chunk - 這產(chǎn)生了大量 HTTP 請(qǐng)求開(kāi)銷(xiāo)。幸運(yùn)的是,此插件可以通過(guò)合并的方式,后處理你的 chunk,以減少請(qǐng)求數(shù)。

//改善`chunk`傳輸?shù)牟寮?
//LimitChunkCountPlugin
new webpack.optimize.LimitChunkCountPlugin({
  maxChunks: 5, // 必須大于或等于 1
  minChunkSize: 1000
})

/**MinChunkSizePlugin:
通過(guò)合并小于 minChunkSize 大小的 chunk,
將 chunk 體積保持在指定大小限制以上。
*/
new webpack.optimize.MinChunkSizePlugin({
  // Minimum number of characters
  minChunkSize: 10000 
})

/**
AggressiveSplittingPlugin可以將 bundle拆分成更小的chunk,
直到各個(gè) chunk 的大小達(dá)到 option 設(shè)置的 maxSize。
它通過(guò)目錄結(jié)構(gòu)將模塊組織在一起。
*/
new webpack.optimize.AggressiveSplittingPlugin({
    minSize: 30000, // 字節(jié),分割點(diǎn)。默認(rèn):30720
    maxSize: 50000, // 字節(jié),每個(gè)文件最大字節(jié)。默認(rèn):51200
    chunkOverhead: 0, // 默認(rèn):0
    entryChunkMultiplicator: 1, // 默認(rèn):1
})

SourceMapDevToolPlugin

開(kāi)發(fā)模式下的插件工具了解下

生成source map的兩種配置

  1. 配置devtool
//最簡(jiǎn)單的生成source map的方式是,如下配置webpack.config.js,
const path = require('path');

module.exports = {
    devtool: 'source-map',
    entry: {
        index: path.resolve(__dirname, 'src/index.js'),
    },
    output: {
        devtoolModuleFilenameTemplate: '[resource-path]',
        path: path.resolve(__dirname, 'dist/'),
        filename: 'index.js',
    },
    module: {
        rules: [
            { 
                test: /\.js$/, 
                use: { 
                    loader: 'babel-loader', 
                    query: { presets: ['@babel/preset-env'] } 
                } 
                
            },
        ]
    },
};
/**
其中,devtool默認(rèn)值為false,
配置為source-map表示以獨(dú)立的文件形式生成source map。
因此,dist/ 文件夾下,會(huì)產(chǎn)生兩個(gè)文件,
index.js
index.js.map

index.js文件末尾,webpack會(huì)自動(dòng)添加一行注釋?zhuān)?//# sourceMappingURL=index.js.map

瀏覽器解析到這里,會(huì)自動(dòng)根據(jù)index.js的相對(duì)路徑,請(qǐng)求map文件并加載它。
*/
  1. SourceMapDevToolPlugin
/**
除了直接配置devtool之外,
還可以使用webpack官方插件SourceMapDevToolPlugin,
進(jìn)行更細(xì)粒度的source map配置。
*/
const path = require('path');
const webpack = require('webpack');

module.exports = {
    // devtool: 'source-map',
    entry: {
        index: path.resolve(__dirname, 'src/index.js'),
    },
    output: {
        // devtoolModuleFilenameTemplate: '[resource-path]',
        path: path.resolve(__dirname, 'dist/'),
        filename: 'index.js',
    },
    module: {
        rules: [
            { 
                test: /\.js$/, 
                use: { 
                    loader: 'babel-loader', 
                    query: { presets: ['@babel/preset-env'] } 
                } 
            },
        ]
    },
    plugins: [
        new webpack.SourceMapDevToolPlugin({
            filename: '[file].map',
            moduleFilenameTemplate: '[resource-path]',
            append: '\n//# sourceMappingURL=http://127.0.0.1:8080/dist/[url]'
        }),
    ],
};
/**
以上配置中,我們注釋掉了
    devtool和devtoolModuleFilenameTemplate,
將它們配置到了SourceMapDevToolPlugin中。
這樣配置,dist/目錄最后也會(huì)生成兩個(gè)文件,
index.js
index.js.map

由于我們使用了該插件的append功能,
修改了sourceMappingURL地址,
因此,index.js末尾source map文件的地址就變成了,
//# sourceMappingURL=http://127.0.0.1:8080/dist/index.js.map
*/
  1. 注:這里值得一提的是
同時(shí)配置devtool和SourceMapDevToolPlugin是不行的,
index.js文件末尾會(huì)被添加兩行sourceMappingURL,
//# sourceMappingURL=http://127.0.0.1:8080/dist/index.js.map
//# sourceMappingURL=index.js.map

而且map文件的內(nèi)容也不正確,是一個(gè)空的map文件,
  1. EvalSourceMapDevToolPlugin:對(duì) eval source map 進(jìn)行更細(xì)粒度的控制
  2. webpack——devtool里的7SourceMap模式
    我們先來(lái)看看文檔對(duì)這 7 種模式的解釋?zhuān)?/li>
模式 解釋
eval 每個(gè)module會(huì)封裝到 eval 里包裹起來(lái)執(zhí)行,并且會(huì)在末尾追加注釋 //@ sourceURL.
source-map 生成一個(gè)SourceMap文件.
hidden-source-map 和 source-map 一樣,但不會(huì)在 bundle 末尾追加注釋.
inline-source-map 生成一個(gè) DataUrl 形式的 SourceMap 文件.
eval-source-map 每個(gè)module會(huì)通過(guò)eval()來(lái)執(zhí)行,并且生成一個(gè)DataUrl形式的SourceMap
cheap-source-map 生成一個(gè)沒(méi)有列信息(column-mappings)的SourceMaps文件,不包含loader的 sourcemap(譬如 babel 的 sourcemap)
cheap-module-source-map 生成一個(gè)沒(méi)有列信息(column-mappings)的SourceMaps文件,同時(shí) loader 的 sourcemap 也被簡(jiǎn)化為只包含對(duì)應(yīng)行的。
- 注1:webpack 不僅支持這 7 種,
而且它們還是可以任意組合上面的eval、inline、hidden關(guān)鍵字,
就如文檔所說(shuō),你可以設(shè)置 souremap 選項(xiàng)為:
    cheap-module-inline-source-map。

- 注2:如果你的modules里面已經(jīng)包含了SourceMaps,
你需要用source-map-loader 來(lái)和合并生成一個(gè)新的 SourceMaps。

還有很多其他的,需要時(shí)自行搜索學(xué)習(xí)即可

......


Webpack 4進(jìn)階

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,505評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,017評(píng)論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,786評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,219評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,438評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,796評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,995評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,230評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,662評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,918評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,697評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容

  • 剛完成一個(gè)基于React+Webpack的項(xiàng)目,漸漸地熟悉了Webpack的一些配置,開(kāi)發(fā)過(guò)程中用到了不少Webp...
    清曉凝露閱讀 6,924評(píng)論 0 8
  • 作者:小 boy (滬江前端開(kāi)發(fā)工程師)本文原創(chuàng),轉(zhuǎn)載請(qǐng)注明作者及出處。原文地址:https://www.smas...
    iKcamp閱讀 2,767評(píng)論 0 18
  • webpack 有著豐富的插件接口(rich plugin interface)。webpack 自身的多數(shù)功能都...
    milletmi閱讀 2,212評(píng)論 0 0
  • 寫(xiě)在開(kāi)頭 先說(shuō)說(shuō)為什么要寫(xiě)這篇文章, 最初的原因是組里的小朋友們看了webpack文檔后, 表情都是這樣的: (摘...
    Lefter閱讀 5,310評(píng)論 4 31
  • Android采用分層結(jié)構(gòu)如下 應(yīng)用程序?qū)樱蝗渴褂肑ava語(yǔ)言編寫(xiě); 應(yīng)用程序框架簡(jiǎn)單的說(shuō)就是應(yīng)用程序需要調(diào)用的...
    大飛機(jī)閱讀 419評(píng)論 0 5