剛完成一個基于React+Webpack的項目,漸漸地熟悉了Webpack的一些配置,開發過程中用到了不少Webpack插件,現在把覺得有用的Webpack插件總結一下當鞏固學習啦,主要涉及熱模塊替換[Hot Module Replacement]、ProvidePlugin、CommonsChunkPlugin、UglifyJsPlugin 、ExtractTextWebpackPlugin、DefinePlugin、DllPlugin等。
1、熱模塊替換Hot Module Replacement
熱模塊替換(HMR)是webpack提供的最有用的特性之一,熱模塊替換可以讓模塊在沒有頁面刷新的情況下實時更新代碼改動結果;
安裝webpack-dev-server
webpack-dev-server 是一個小型的Node.js Express服務器,它通過使用webpack-dev-middleware來為webpack打包的資源文件提供服務。可以認為webpack-dev-server就是一個擁有實時重載能力的靜態資源服務器(建議只在開發環境使用)
通過npm安裝:
npm install webpack-dev-server --save-dev```
運行
webpack-dev-server --open
##熱更新授權
一個簡單webpackHMR實例,這里感覺官方代碼比較簡單明了,直接復制過來啦
```javascript
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './index.js',
plugins: [
new webpack.HotModuleReplacementPlugin() // Enable HMR
],
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/'
},
devServer: {
hot: true, // Tell the dev-server we're using HMR
contentBase: resolve(__dirname, 'dist'),
publicPath: '/'
}
};
上述代碼配置中需要注意兩個地方:
(1)plugins:添加webpack.HotModuleReplacementPlugin模塊熱更新插件
(2)devServer配置hot:true 開啟模塊熱更新配置,更多配置詳見devServer
同時還需注意的幾個devServer配置屬性
inline: true|false
inline屬性用于切換webpack-der-server編譯并刷新瀏覽器的兩種不同模式:
(1)第一種也是默認的inline模式,這種模式是將腳本插入打包資源文件中復制熱更新,并在瀏覽器控制臺中輸出過程信息,訪問格式訪問格式是http://<host>:<port>/<path>;
(2)iframe 模式:使用iframe加載頁面,訪問格式http://<host>:<port>/webpack-dev-server/<path>
可以通過配置
inline: false//啟用iframe
在你的代碼中插入熱替換代碼
index.js 在入口文件結尾處插入
if (module.hot) {
module.hot.accept();
}
只有被 "accept"的代碼模塊才會被熱更新,所以你需要在父節點或者父節點的父節點... module.hot.accept 調用模塊。如以上代碼所示,在入口文件加入 module.hot.accept之后在入口文件引用的任何子模塊更新都會觸發熱更新。模塊更新完成,瀏覽器會輸出類型以下信息
也可以像官方提供的實例一樣accept特定的模塊,如下實例,當'./library'有任何更新是都會觸發熱模塊更新
import Lib from './library';
if (module.hot) {
module.hot.accept('./library', function() {
console.log('Accepting the updated library module!');
Library.log();
})
}
更多Hot Module Replacement資料參考
Hot Module Replacement
hot module replacement with webpack
webpack 熱加載你站住,我對你好奇很久了
2、ProvidePlugin
ProvidePlugin 可以在任何地方自動加載模塊而不需要import 或 require 方法:
例如:通過如下定義,在任何代碼中使用identifier變量,'module1'模塊都會被加載,identifier所代表的正式‘module1’模塊export的內容
new webpack.ProvidePlugin({
identifier: 'module1',
// ...
})
用途
(1)全局變量
如果你的項目里需要使用jQuery,如果每次使用的時候都需要通過import 或 require 去引入jQuery的話未免也太麻煩。這時候ProvidePlugin就可以幫上大忙了
//Webpack plugins定義
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
// 代碼模塊中調用
$('#item'); // <= 生效
jQuery('#item'); // <= just works
// $ is automatically set to the exports of module "jquery"
上述代碼可以看出通過ProvidePlugin定義的‘$’被調用時是直接生效的,webpack會自動把"jquery"給注入進模塊,而‘$’在模塊中則代表了‘jquery’ export的內容。這樣就不需要先let $=require('jquery')再調用啦。
(2)ProvidePlugin還可以根據不同環境使用不同配置
在實際的項目開發中我們通常會根據不同環境采用不同的webpack配置文件,如下代碼使用了三個不同文件代表了不同環境的配置,development.js、test.js、production.js分別代表了開發、測試、線上環境它們都輸出了一個包含name屬性的對象:
//development.js開發
module.exports = {
name:'development'
};
//test.js測試
module.exports = {
name:'test'
};
//production.js線上
module.exports = {
name:'production'
};
//webpack.dev.config.js 開發環境
new webpack.ProvidePlugin({
ENV: 'development'
})
//webpack.test.config.js 測試環境
new webpack.ProvidePlugin({
ENV: "test"
})
//webpack.pub.config.js 線上環境
new webpack.ProvidePlugin({
ENV: "production"
})
假如在代碼模塊中這么調用
if (ENV.name == 'development') {
console.log('做一些開發環境的事情')
} else if (ENV.name == 'test') {
console.log('做一些測試環境的事情')
} else if (ENV.name == 'production') {
console.log('有些事情必須留到線上環境做')
}
如上ProvidePlugin中定義的ENV在不同環境中就代表了不同模塊,這樣就可以區別的做一些開發、測試、生產環境不同的事情啦。
更多ProvidePlugin資料參考
ProvidePlugin
webpack 巧解環境配置問題
3、CommonsChunkPlugin
CommonsChunkPlugin是一個可以用來提取公共模塊的插件,配置:
{
name: string, // or
names: string[],
// 這是 common chunk 的名稱。已經存在的 chunk 可以通過傳入一個已存在的 chunk 名稱而被選擇。
// 如果一個字符串數組被傳入,這相當于插件針對每個 chunk 名被多次調用
// 如果該選項被忽略,同時 `options.async` 或者 `options.children` 被設置,所有的 chunk 都會被使用,否則 `options.filename` 會用于作為 chunk 名。
filename: string,
// common chunk 的文件名模板。可以包含與 `output.filename` 相同的占位符。
// 如果被忽略,原本的文件名不會被修改(通常是 `output.filename` 或者 `output.chunkFilename`)
minChunks: number|Infinity|function(module, count) -> boolean,
// 在傳入 公共chunk(commons chunk) 之前所需要包含的最少數量的 chunks 。
// 數量必須大于等于2,或者少于等于 chunks的數量
// 傳入 `Infinity` 會馬上生成 公共chunk,但里面沒有模塊。
// 你可以傳入一個 `function` ,以添加定制的邏輯(默認是 chunk 的數量)
chunks: string[],
// 通過 chunk name 去選擇 chunks 的來源。chunk 必須是 公共chunk 的子模塊。
// 如果被忽略,所有的,所有的 入口chunk (entry chunk) 都會被選擇。
children: boolean,
// 如果設置為 `true`,所有 公共chunk 的子模塊都會被選擇
async: boolean|string,
// 如果設置為 `true`,一個異步的 公共chunk 會作為 `options.name` 的子模塊,和 `options.chunks` 的兄弟模塊被創建。
// 它會與 `options.chunks` 并行被加載。可以通過提供想要的字符串,而不是 `true` 來對輸出的文件進行更換名稱。
minSize: number,
// 在 公共chunk 被創建立之前,所有 公共模塊 (common module) 的最少大小。
}
webpack用插件CommonsChunkPlugin進行打包的時候,將符合引用次數(minChunks)的模塊打包到name參數的數組的第一個塊里(chunk),然后數組后面的塊依次打包(查找entry里的key,沒有找到相關的key就生成一個空的塊),最后一個塊包含webpack生成的在瀏覽器上使用各個塊的加載代碼,所以頁面上使用的時候最后一個塊必須最先加載。
打包公共文件
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
// ( 公共chunk(commnons chunk) 的名稱)
filename: "commons.js",
// ( 公共chunk 的文件名)
// minChunks: 3,
// (模塊必須被3個 入口chunk 共享)
// chunks: ["pageA", "pageB"],
// (只使用這些 入口chunk)
})
配置例子:
文件目錄結構
//main.js
import A from './a'
//main1.js
import A from './a'
import B from './b'
//a.js
var A = 1;
exports.A = A;
//b.js
var B = 1;
exports.B = B;
//lib/jquery.js
var Jquery = 'fake jquery';
exports.$ = Jquery;
//lib/vue.js
var Vue = 'Fake Vue';
exports.Vue = Vue;
//package.json
{
"name": "CommonsChunkPluginExample",
"version": "1.0.0",
"main": "main.js",
"license": "MIT",
"scripts": {
"start": "webpack --config webpack.dev.config.js "
},
"dependencies": {
"webpack": "^2.6.1"
}
}
實例代碼,以上main.js、main1.js為入口文件;a.js、b.js為代碼模塊文件;lib/jquery.js、lib/vue.js模擬代碼庫文件
打包main.js、main1.js的的公共模塊
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: {
main: './main.js',
main1: './main1.js',
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
chunkFilename: '[name].js',
},
resolve: {
extensions: [' ', '*', '.js', '.jsx'],
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'commons'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'load'
})
],
}
按照預期
main.js、main1.js都引用了a.js,所以a.js被打包進commons.js中;b.js只被main1.js引用,將會被打包進main1.js中;打包生成的/dist/load.js包含了Webpack的加載代碼:
///dist/commons.js
webpackJsonp([2],[
/* 0 */
/***/ (function(module, exports) {
/**
* Created by thea on 2017/6/9.
*/
var A = 1;
exports.A = A;
/***/ })
]);
//dist/main1.js
webpackJsonp([0],[
/* 0 */,
/* 1 */
/***/ (function(module, exports) {
var B = 1;
exports.B = B;
/***/ }),
/* 2 */,
/* 3 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__a__ = __webpack_require__(0);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__a___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__a__);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__b__ = __webpack_require__(1);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__b___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1__b__);
/***/ })
],[3]);
提取第三方庫
在通常的項目開發中我們通常會引入一些第三方庫,在打包的時候我們通常也會希望將代碼拆分成公共代碼和應用代碼。將webpack.dev.config.js文件配置變化如下:
//webpack.dev.config.js
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: {
main: './main.js',
main1: './main1.js',
lib:['./lib/jquery.js','./lib/vue.js']//第三方庫
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
chunkFilename: '[name].js',
},
resolve: {
extensions: [' ', '*', '.js', '.jsx'],
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names:['commons','lib']//'lib'提取入口entry key 'lib'代表的文件單獨打包
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'load'
})
],
}
預期/lib/jquery.js、/lib/vue.js共同打包成為/dist/lib.js
///dist/lib.js
webpackJsonp([0],[
/* 0 */,
/* 1 */,
/* 2 */
/***/ (function(module, exports) {
var Jquery = 'fake jquery';
exports.$ = Jquery;
/***/ }),
/* 3 */
/***/ (function(module, exports) {
var Vue = 'Fake Vue';
exports.Vue = Vue;
/***/ }),
/* 4 */,
/* 5 */,
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__(2);
module.exports = __webpack_require__(3);
/***/ })
],[6]);
CommonsChunkPlugin更多資料參考
CommonsChunkPlugin
[webpack CommonsChunkPlugin詳細教程]
(https://segmentfault.com/a/1190000006808865)
4、ExtractTextWebpackPlugin 分離 CSS
Webpack打包默認會把css和js打包在一塊,然而我們通常習慣將css代碼中放在<head>標簽中,而將js引用放在頁面底部;將css代碼放在頁面頭部可以避免 FOUC 問題(表現為頁面內容已經加載出來,但是沒有樣式,過了一會兒樣式文件加載出來之后頁面回復正常);同時如果css和js分離也有利于這加強樣式的可緩存性;這是我們需要ExtractTextWebpackPlugin來分離js與css使得樣式文件單獨打包。
webpack處理css
在代碼中想JavaScript模塊一樣引入css文件
import styles from './style.css'
需要借助css-loader
和 style-loader
:
npm install --save-dev css-loader style-loader
在 webpack.config.js 中配置如下:
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}]
}
}
這樣,CSS 會跟你的 JavaScript 打包在一起,并且在初始加載后,通過一個 <style> 標簽注入樣式,然后作用于頁面。
這里有一個缺點就是,你無法使用瀏覽器的能力,去異步且并行去加載 CSS。取而代之的是,你的頁面需要等待整個 JavaScript 文件加載完,才能進行樣式渲染。
使用 ExtractTextWebpackPlugin
安裝
npm install --save-dev extract-text-webpack-plugin
為了使用這個插件,它需要通過三步被配置到 webpack.config.js 文件中。
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 1,
modules: true,
localIdentName: "[local]---[hash:base64:5]",
camelCase: true
}
}]
})
}]
},
plugins: [
new ExtractTextPlugin( ({
filename: '[name].css',//使用模塊名命名
allChunks: true
})
]
}
通過加入ExtractTextWebpackPlugin,每個模塊的css都會生成一個新文件,此時你可以作為一個單獨標簽添加到html文件中。
更多ExtractTextWebpackPlugin資料參考
webpack-contrib/extract-text-webpack-plugin
webpack ExtractTextWebpackPlugin
5、UglifyJsPlugin代碼壓縮輸出
代碼壓縮插件UglifyJsPlugin通過UglifyJS2來完成代碼壓縮,配置參考UglifyJS2,
調用例子:
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
6、設置全局變量插件[DefinePlugin
](https://webpack.js.org/plugins/define-plugin)
// webpack.config.js
const webpack = require('webpack');
module.exports = {
/*...*/
plugins:[
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
};
通過DefinePlugin定義的變量可在全局代碼中使用,例如Webpack配置文件定義了process.env.NODE_ENV='production',如果代碼中存在如下調用:
if (process.env.NODE_ENV !== 'production') console.log('...')
即等同于
if (false) console.log('...')
原理:DefinePlugin做的工作是在源代碼基礎上執行的全局查找替換工作,將DefinePlugin插件中定義的變量替換為插件中定義的變量值。
參考
DllPlugin& DllReferencePlugin
在前端項目構建中,為了提高打包效率,往往會將業務代碼合第三方庫打包。
之前有將將到過CommonsChunkPlugin可提供第三方庫單獨打包方法。但是由于每次運行Webpack,即使沒有代碼更新,也會重新打包,Webpack打包慢也是一直被詬病的問題,竟然減少打包內容才是王道;
其實Webpack還提供了一個提供了另外一個配置項Externals,提供了「從輸出的 bundle 中排除依賴」的方法:
如
index.html
...
<script src="https://code.jquery.com/jquery-3.1.0.js"
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
crossorigin="anonymous"></script>
...
webpack.config.js
externals: {
jquery: 'jQuery'
}
這樣就剝離了那些不需要改動的依賴模塊,換句話,下面展示的代碼還可以正常運行:
import $ from 'jquery';
$('.my-element').animate(...);
不過externals雖然解決了外部引用問題,但如果遇到像react模塊本身引入react入口文件,但是 Webpack 不知道這個入口文件等效于 react 模塊本身時,會出現重復打包現象,詳見徹底解決 Webpack 打包性能問題。
Webpack提供了DllPlugin、DllReferencePlugin兩個插件,既能解決第三方庫代碼無變化仍然要打包增加打包時間問題,也能解決通過外鏈引用但可能第三方庫還是被打包進來的問題。
DllPlugin插件用來把需要獨立打包的第三方庫分離出來單獨打包最后會輸出兩個文件,一個是打包好的第三方庫bundle還有一個是用來給 DllReferencePlugin
映射依賴的manifest.json
使用方法:
(1)新建一個專門用來打包第三方庫的Webpack配置文件
//webpack.dll.config.js
const webpack = require('webpack');
module.exports = {
entry: {
react: ['./res/polyfill.js', 'react', 'react-dom', 'redux', 'react-redux', 'react-router']
},
output: {
filename: '[name].bundle.js',
path: path.join(__dirname, 'res'),
library: '[name]_lib'
},
plugins: [
new webpack.DllPlugin({
path: '.res/[name]-manifest.json',//manifest.json輸出路徑,DllReferencePlugin需要用到
name: '[name]_library',//打包庫文件名
context:__dirname//可選,引用manifest.json的上下文,默認為Webpack配置文件上下文
})
]
};
運行webpack.dll.config.js
webpack --config webpack.dll.config.js
生成打包好的庫bundle,和manifest.json文件 'bundle.manifest.json',大致內容如下:
{
"name": "react_lib",
"content": {
"./node_modules/core-js/modules/_export.js": {
"id": 0,
"meta": {}
},
"./node_modules/core-js/modules/_global.js": {
"id": 1,
"meta": {}
},
"./node_modules/preact-compat/dist/preact-compat.es.js": {
"id": 2,
"meta": {
"harmonyModule": true
},
//其他引用
}
2、配置webpack.config.js中的插件DllReferencePlugin
plugins: [
new webpack.DllReferencePlugin({
context: '.',//需要與webpack.dll.config.js中DllPlugin上下文一致
manifest: require('./res/react-manifest.json')//DllPlugin輸出的manifest.json文件
})]
通過兩步完美分離第三方庫~~~~