作者:余韻之
webpack目前是前端常用的工程化工具了。它可以幫助我們自動化構建打包各類的資源,極大的提高了我們打包代碼的效率。在webpack看來,所有的資源文件都是模塊(module),只是處理的方式不同。
一、初探webpack
1、安裝webpack
建議不要全局安裝webpack,因為不同的項目webpack的版本號是不一樣的。這樣多個項目來回切換是很不方便的。
npm install wepack webpack-cli -g
在項目內(nèi)安裝webpack
npm install wepack webpack-cli -D
注意:webpack-cli的作用是我們可以在命令行里直接使用webpack
檢查版本
npx webpack -v
查看 webpack 所有可以安裝的版本號
npm info webpack
2、最簡單的webpack.config.js的配置
const path = require('path');
module.exports = {
entry: {
main:'./src/index.js'
},
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
這段配置是告訴我們:
- 我們需要打包入口文件是./src/index.js,
- 最后輸出的打包文件是在當前目錄下dist/main.js
- 假如存在bundle/index.html,就可以通過script引入main.js文件了。當然這個也可以通過webpack自動化構建。
3、兩種打包方式
1) npx
npx webpack
npx 表示會在當前目錄里尋找依賴變量 webpack
2) script
在package.json里配置
"scripts": {
"bundle": "webpack"
},
于是可以運行
npm run bundle
運行 npx webpack / npm run bundle 會先檢查是否有配置文件webpack.config.js,如果沒有就走默認配置,如果有就走配置文件
4、打包的參數(shù)詳解

- Hash:表示這一次打包的唯一標識值
- Version:表示這一次打包的使用版本
- Time:表示當前打包整體耗時
- Asset:表示此次打包出現(xiàn)了bundle.js
- Size:表示該文件的大小
- Chunk Names 表示的是 entry里入口的key,默認為main,也可以任意改為 xxx,yyy
- Entrypoint main = bundle.js 表示入口文件 以及依次打包的文件[0][1][2]……
二、使用Loader打包資源
1、什么是loader?
webpack不能識別非js的模塊,需要對于不同的模塊提供不同的打包方案,于是要求助于loader。如:css-loader、sass-loader、file-loader、vue-loader、postcss-loader等等
2、打包圖片
file-loader實現(xiàn)原理思路: 當發(fā)現(xiàn)代碼引入圖片模塊,首先把圖片移動到dist目錄下并改了名字,得到了相對于dist的地址,作為返回值給到我們引入的變量之中
module.exports = {
entry: {
main: './src/index.js'
},
module: {
rules: [{
test: /\.(jpg|png)$/,
use: {
loader: 'file-loader',
options: {
name: '[name]_[hash].[ext]', // [name] [hash] [ext] 均為占位符
outputPath: 'images/', // 打包出的結果放在images/目錄下
limit: 10240
}
}
}]
},
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
3、打包CSS 或 SASS
- css-loader:會幫我們分析幾個css的關系(互相引入)合并成一個css
- style-loader:當css-loader合并成了一個css,style-loader會把內(nèi)容掛載到head部分
- sass-loader:解析sass成css
- postcss-loader:自動添加-webkit等前綴,兼容不同瀏覽器樣式
使用了postcss-loader,需要在根目錄創(chuàng)建postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
webpack.config.js中module配置
module: {
rules: [
{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240
}
}
},
{
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader'
}
},
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}]
},
Loader解析是有先后順序的:從下到上,從右到左
4、CSS modules
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options:{
importLoaders: 2,// 表示scss文件導入了scss文件依然走postcss-loader sass-loader
modules:true // 開啟css模塊化
}
},
'sass-loader',
'postcss-loader'
]
}
modules開啟為true后,就可以使用模塊化CSS互不干擾。否則引入的CSS或者SASS的代碼會造成全局污染
使用方式如下
import style from './index.scss’;
var img = new Image();
img.src = avatar;
img.classList.add(style.avatar);
5、打包字體
在webpack.config.js配置
{
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader'
}
},
三、使用Plugins打包便捷
1、什么是Plugins?
plugins是在某個時刻(剛打包、打包結束,打包中間)做一件事
2、html-webpack-plugin
在打包結束時,在dist自動生成html,并且把打包的main.js自動引入html的script的標簽
3、clean-webpack-plugin
在每次打包生成的dist文件前,先刪除里面的內(nèi)容
4、安裝及配置兩個plugin
npm install html-webpack-plugin clean-webpack-plugin -D
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin’);
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html' // 參考模板為 src/index.html
}), new CleanWebpackPlugin(['dist'])],
還有其他的plugins,比如熱更新 HotModuleReplacementPlugin ……
后面慢慢加上和介紹
四、Entry 和 Output 配置
前面的都是單入口,單出口的配置。
1、多入口,多出口
entry: {
main: './src/index.js’,
sub: ‘./src/index.js'
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
}), new CleanWebpackPlugin(['dist'])
],
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
最后的結果是html 里會引入兩個js文件
<script scr=“./main.js"></script>
<script scr=“./sub.js"></script>
2、把打包的JS上傳到CDN
entry: {
main: './src/index.js’,
sub: ‘./src/index.js'
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
}), new CleanWebpackPlugin(['dist'])
],
output: {
publicPath: 'http://cdn.com.cn', // 最后打包出來是http://cdn.com.cn、main.js
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
html引入的script 會自動加上publicPath路徑
<script scr=“http://cdn.com.cn/main.js"></script>
<script scr=“http://cdn.com.cn/sub.js"></script>
五、SourceMap
1、什么是SourceMap
先舉個例子:
打開瀏覽器發(fā)現(xiàn)代碼報錯了。。?!,F(xiàn)在知道dist目錄下main.js 文件 96行出錯。
用了sourceMap之后(它是一個映射關系),于是知道dist目錄下main.js文件96行實際上對應的是src目錄下index.js文件中的第1行
使用了sourceMap 打包速度是會變慢的。同時dist里多了一個xx.js.map文件,原理一個vlq集合
2、配置SourceMap
在webpack.config.js 里 devtool
devtool: 'cheap-module-eval-source-map',
常用的幾個source-map的前綴:
- Inline:inline-source-map 是把xx.js.map內(nèi)容直接打包到xx.js里,用data url形式的方式放在末尾,會告訴你第幾行第幾列除了問題,很耗費性能
- Cheap:添加cheap-inline-source-map 可以精確到每一行不精確到每一列出錯 ,可以降低打包時間,提高性能
- Module:如果要管第三方模塊代碼的映射可以加上module,可以加上cheap-module-inline-source-map
- Eval:eval是打包最快的方式 ,通過eval是執(zhí)行效率最快、性能最好的方式,但是如果代碼復雜的話,提示的內(nèi)容可能不夠全面,用eval的方式執(zhí)行JS代碼
3、最佳實踐
開發(fā)環(huán)境:提示全,打包速度快
mode: 'development',
devtool: 'cheap-module-eval-source-map',
生產(chǎn)環(huán)境:提示效果會更好
mode: 'production',
devtool: 'cheap-module-source-map',
六、webpackDevServer
1、如何解決每次手動打包,手動啟動瀏覽器刷新頁面更新代碼?
- webpack —watch
在package.json 里 script 添加
"watch": "webpack --watch"
優(yōu)點:監(jiān)聽到源代碼改變,會自動打包
缺點:需要手動刷新頁面
- webpack-dev-server
在package.json 里 script 添加
"start": "webpack-dev-server",
在devDependencies安裝
"webpack-dev-server": "^3.1.10"
優(yōu)點:監(jiān)聽到源代碼改變,會自動打包,自動啟動服務器,自動更新瀏覽器
注意使用了webpack-dev-server打包后就不會目錄里有dist了,而是放在電腦某個內(nèi)存里,可以提高效率。
2、如何啟動時自動打開瀏覽器
在webpack.config.js配置
devServer: {
contentBase: './dist',
open: true, // 默認啟動開啟瀏覽器
port: 8080, // 端口設置為8080
},
七、熱更新 HMR(HOT MODULE REPLACE)
如果只是純粹使用了webpack-dev-server那么當改變了代碼,瀏覽器會自動刷新初始化數(shù)據(jù)。有時候比較麻煩。
有沒有什么辦法可以做到:
當修改了css只改變css的樣式,js新增的數(shù)據(jù)不變。

此時把背景色改成紅色

或者當修改了某一個模塊的數(shù)據(jù),另一個模塊的數(shù)據(jù)不改變
配置如下
webpack.config.js
const webpack = require('webpack’);
devServer: {
contentBase: './dist',
open: true,
port: 8080,
hot: true, // 開啟熱更新的功能
hotOnly: true // 即使HMR不生效,也不更新瀏覽器
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist']),
new webpack.HotModuleReplacementPlugin() // 配置熱更新plugins
],
同時必須添加模塊更新的代碼。例如有兩個模塊,當改變了number模塊,就更新number,counter模塊不改變
index.js
import counter from './counter';
import number from './number';
counter();
number();
if(module.hot) {
module.hot.accept('./number', () => {
document.body.removeChild(document.getElementById('number'));
number();
})
}
css可以不寫 module.hot判斷 是因為css-loader已經(jīng)實現(xiàn)了。如果一些特別的模塊,就需要自己寫一個module.hot來判斷
八、Babel處理ES6語法
1、安裝依賴
npm install —save-dev babel-loader @babel/core @babel/preset-env
- babel-loader : 提供識別模塊的打包工具
- @babel/core : 識別js代碼轉化為AST抽象語法樹,編譯轉化成新的語法
- @babel/preset-env : 把ES6代碼轉化為ES5語法,提供了翻譯規(guī)則
2、打包方式
二選一
1)安裝@babel/profill
npm install -save-dev @babel/profill
缺點:這個會出現(xiàn)全局污染
配置規(guī)則
rules:[{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options:{
presets: [['@babel/preset-env', {
targets: {
chrome: "67",
},
useBuiltIns: 'usage' // 表示做polyfill 根據(jù)業(yè)務代碼來加對應的代碼,可以減少打包的體積
}]]
}
}],
注意:useBuiltIns: 'usage' 表示按需引入
2)安裝 @babel/plugin-transform-runtime
npm install -save-dev @babel/plugin-transform-runtime @babel/runtime @babel/runtime-corejs2
優(yōu)點:可是使用閉包的方式不影響其他環(huán)境變量
配置 創(chuàng)建.babelrc
{
"plugins": [["@babel/plugin-transform-runtime", {
"corejs": 2, // 使用了2 就需要安裝 @babel/runtime-corejs2
"helpers": true,
"regenerator": true,
"useESModules": false
}]]
}
webpack.config.js
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
九、打包React代碼
1、安裝依賴
@babel/preset-react : 可以解析JSX
npm install --save-dev @babel/preset-react
webpack.config.js
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
}
創(chuàng)建.babelrc
{
"presets": [
[
"@babel/preset-env", {
"targets": {
"chrome": "67"
},
"useBuiltIns": "usage"
}
],
"@babel/preset-react"
]
}
注意:先解析react的語法,然后再把ES6語法解析為ES5。presets是自下而上,自右邊而左來解析的