什麼是WebPack,爲什麼要使用它?
爲什要使用WebPack
現今的很多網頁其實可以看做是功能豐富的應用,它們擁有着複雜的JavaScript代碼和一大堆依賴包。爲了簡化開發的複雜度,前端社區涌現出了很多好的實踐方法
- 模塊化,讓我們可以把複雜的程序細化爲小的文件;
- 類似於TypeScript這種在JavaScript基礎上拓展的開發語言:使我們能夠實現目前版本的JavaScript不能直接使用的特性,並且之後還能轉換爲JavaScript文件使瀏覽器可以識別;
- Scss,less等CSS預處理器
- ...
這些改進確實大大的提高了我們的開發效率,但是利用它們開發的文件往往需要進行額外的處理才能讓瀏覽器識別,而手動處理又是非常繁瑣的,這就爲WebPack類的工具的出現提供了需求。
什麼是Webpack
WebPack可以看做是模塊打包機:它做的事情是,分析你的項目結構,找到JavaScript模塊以及其它的一些瀏覽器不能直接運行的拓展語言(Scss,TypeScript等),並將其轉換和打包爲合適的格式供瀏覽器使用。
WebPack和Grunt以及Gulp相比有什麼特性
其實Webpack和另外兩個並沒有太多的可比性,Gulp/Grunt是一種能夠優化前端的開發流程的工具,而WebPack是一種模塊化的解決方案,不過Webpack的優點使得Webpack在很多場景下可以替代Gulp/Grunt類的工具。
Grunt和Gulp的工作方式是:在一個配置文件中,指明對某些文件進行類似編譯,組合,壓縮等任務的具體步驟,工具之後可以自動替你完成這些任務。
Webpack的工作方式是:把你的項目當做一個整體,通過一個給定的主文件(如:index.js),Webpack將從這個文件開始找到你的項目的所有依賴文件,使用loaders處理它們,最後打包爲一個(或多個)瀏覽器可識別的JavaScript文件。
我們使用PostCSS來爲CSS代碼自動添加適應不同瀏覽器的CSS前綴。
首先安裝 postcss-loader
和 autoprefixer
(自動添加前綴的插件)
npm install --save-dev postcss-loader autoprefixer
接下來,在webpack配置文件中添加postcss-loader
,在根目錄新建postcss.config.js
,並添加如下代碼之後,重新使用npm start
打包時,你寫的css會自動根據Can i use裏的數據添加不同前綴了。
//webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true
}
}, {
loader: "postcss-loader"
}
]
}
]
}
}
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
插件(Plugins)
插件(Plugins)是用來拓展Webpack功能的,它們會在整個構建過程中生效,執行相關的任務。
Loaders和Plugins常常被弄混,但是他們其實是完全不同的東西,可以這麼來說,loaders是在打包構建過程中用來處理源文件的(JSX,Scss,Less..),一次處理一個,插件並不直接操作單個文件,它直接對整個構建過程其作用。
Webpack有很多內置插件,同時也有很多第三方插件,可以讓我們完成更加豐富的功能。
使用插件的方法
要使用某個插件,我們需要通過npm
安裝它,然後要做的就是在webpack配置中的plugins關鍵字部分添加該插件的一個實例(plugins是一個數組)繼續上面的例子,我們添加了一個給打包後代碼添加版權聲明的插件。
const webpack = require('webpack');
module.exports = {
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true
}
}, {
loader: "postcss-loader"
}
]
}
]
},
plugins: [
new webpack.BannerPlugin('版權所有,翻版必究')
],
};
通過這個插件,打包後的JS文件頭部便會添加 /*版權所有,翻板必究*/ 的版權聲明
這就是webpack插件的基礎用法了,下面給大家推薦幾個常用的插件
HtmlWebpackPlugin
這個插件的作用是依據一個簡單的index.html
模板,生成一個自動引用你打包後的JS文件的新index.html
。這在每次生成的js文件名稱不同時非常有用(比如添加了hash
值)。
安裝
npm install --save-dev html-webpack-plugin
這個插件自動完成了我們之前手動做的一些事情,在正式使用之前需要對一直以來的項目結構做一些更改:
- 移除public文件夾,利用此插件,
index.html
文件會自動生成,此外CSS已經通過前面的操作打包到JS中了。 - 在app目錄下,創建一個
index.tmpl.html
文件模板,這個模板包含title
等必須元素,在編譯過程中,插件會依據此模板生成最終的html頁面,會自動添加所依賴的 css, js,favicon等文件,index.tmpl.html
中的模板源代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Webpack Sample Project</title>
</head>
<body>
<div id='root'>
</div>
</body>
</html>
3.更新webpack
的配置文件,方法同上,新建一個build
文件夾用來存放最終的輸出文件
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/build",
filename: "bundle.js"
},
devtool: 'eval-source-map',
devServer: {
contentBase: "./public",//本地服務器所加載的頁面所在的目錄
historyApiFallback: true,//不跳轉
inline: true//實時刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true
}
}, {
loader: "postcss-loader"
}
]
}
]
},
plugins: [
new webpack.BannerPlugin('版權所有,翻版必究'),
new HtmlWebpackPlugin({
template: __dirname + "/app/index.tmpl.html"//new 一個這個插件的實例,並傳入相關的參數
})
],
};
再次執行npm start
你會發現,build文件夾下面生成了bundle.js
和index.html
。
Hot Module Replacement 熱加載
Hot Module Replacement
(HMR)也是webpack裏很有用的一個插件,它允許你在修改組件代碼後,自動刷新實時預覽修改後的效果。
在webpack中實現HMR也很簡單,只需要做兩項配置
- 在webpack配置文件中添加HMR插件;
- 在Webpack Dev Server中添加“hot”參數;
不過配置完這些後,JS模塊其實還是不能自動熱加載的,還需要在你的JS模塊中執行一個Webpack提供的API才能實現熱加載,雖然這個API不難使用,但是如果是React模塊,使用我們已經熟悉的Babel可以更方便的實現功能熱加載。
整理下我們的思路,具體實現方法如下
Babel
和webpack
是獨立的工具- 二者可以一起工作
- 二者都可以通過插件拓展功能
- HMR是一個webpack插件,它讓你能瀏覽器中實時觀察模塊修改後的效果,但是如果你想讓它工作,需要對模塊進行額外的配額;
- Babel有一個叫做
react-transform-hrm
的插件,可以在不對React模塊進行額外的配置的前提下讓HMR正常工作;
還是繼續上例來實際看看如何配置
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/build",
filename: "bundle.js"
},
devtool: 'eval-source-map',
devServer: {
contentBase: "./public",//本地服務器所加載的頁面所在的目錄
historyApiFallback: true,//不跳轉
inline: true,
hot: true
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true
}
}, {
loader: "postcss-loader"
}
]
}
]
},
plugins: [
new webpack.BannerPlugin('版權所有,翻版必究'),
new HtmlWebpackPlugin({
template: __dirname + "/app/index.tmpl.html"//new 一個這個插件的實例,並傳入相關的參數
}),
new webpack.HotModuleReplacementPlugin()//熱加載插件
],
};
產品階段的構建
目前爲止,我們已經使用webpack構建了一個完整的開發環境。但是在產品階段,可能還需要對打包的文件進行額外的處理,比如說優化,壓縮,緩存以及分離CSS和JS。
對於複雜的項目來說,需要複雜的配置,這時候分解配置文件爲多個小的文件可以使得事情井井有條,以上面的例子來說,我們創建一個webpack.production.config.js
的文件,在裏面加上基本的配置,它和原始的webpack.config.js很像,如下
// webpack.production.config.js
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件
output: {
path: __dirname + "/build",
filename: "bundle.js"
},
devtool: 'null', //注意修改了這裏,這能大大壓縮我們的打包代碼
devServer: {
contentBase: "./public", //本地服務器所加載的頁面所在的目錄
historyApiFallback: true, //不跳轉
inline: true,
hot: true
},
module: {
rules: [{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
}, {
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [{
loader: "css-loader",
options: {
modules: true
}
}, {
loader: "postcss-loader"
}],
})
}]
},
plugins: [
new webpack.BannerPlugin('版權所有,翻版必究'),
new HtmlWebpackPlugin({
template: __dirname + "/app/index.tmpl.html" //new 一個這個插件的實例,並傳入相關的參數
}),
new webpack.HotModuleReplacementPlugin() //熱加載插件
],
};
//package.json
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack",
"server": "webpack-dev-server --open",
"build": "NODE_ENV=production webpack --config ./webpack.production.config.js --progress"
},
"author": "",
"license": "ISC",
"devDependencies": {
...
},
"dependencies": {
"react": "^15.6.1",
"react-dom": "^15.6.1"
}
}
注意:如果是window電腦,build
需要配置爲"build": "set NODE_ENV=production && webpack --config ./webpack.production.config.js --progress"
.謝謝評論區簡友提醒。
優化插件
webpack提供了一些在發佈階段非常有用的優化插件,它們大多來自於webpack社區,可以通過npm安裝,通過以下插件可以完成產品發佈階段所需的功能
OccurenceOrderPlugin
:爲組件分配ID,通過這個插件webpack可以分析和優先考慮使用最多的模塊,併爲它們分配最小的IDUglifyJsPlugin
:壓縮JS代碼;ExtractTextPlugin
:分離CSS和JS文件
我們繼續用例子來看看如何添加它們,OccurenceOrder 和 UglifyJS plugins 都是內置插件,你需要做的只是安裝其它非內置插件
npm install --save-dev extract-text-webpack-plugin
在配置文件的plugins後引用它們
// webpack.production.config.js
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/build",
filename: "bundle.js"
},
devtool: 'none',
devServer: {
contentBase: "./public",//本地服務器所加載的頁面所在的目錄
historyApiFallback: true,//不跳轉
inline: true,
hot: true
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true
}
}, {
loader: "postcss-loader"
}
]
}
]
},
plugins: [
new webpack.BannerPlugin('版權所有,翻版必究'),
new HtmlWebpackPlugin({
template: __dirname + "/app/index.tmpl.html"
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin("style.css")
],
};
此時執行npm run build
可以看見 bundle.js的代碼是被壓縮後的
緩存
緩存無處不在,使用緩存的最好方法是保證你的文件名和文件內容是匹配的(內容改變,名稱相應改變)
webpack可以把一個哈希值添加到打包的文件名中,使用方法如下,添加特殊的字符串混合體([name], [id] and [hash])到輸出文件名前
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
..
output: {
path: __dirname + "/build",
filename: "bundle-[hash].js"
},
...
};
現在用戶會有合理的緩存了。
去除build
文件中的殘餘文件
添加了hash
之後,會導致改變文件內容後重新打包時,文件名不同而內容越來越多,因此這裏介紹另外一個很好用的插件clean-webpack-plugin
。
安裝:cnpm install clean-webpack-plugin --save-dev
使用:
引入clean-webpack-plugin
插件後在配置文件的plugins
中做相應配置即可:
const CleanWebpackPlugin = require("clean-webpack-plugin");
plugins: [
...// 這裏是之前配置的其它各種插件
new CleanWebpackPlugin('build/*.*', {
root: __dirname,
verbose: true,
dry: false
})
]