webpack是一個模塊打包機,它會從指定入口文件開始,遞歸的尋找JavaScript模塊以及其餘一些瀏覽器沒法直接運行的擴展語言(Sass, TypeScript)等,將其打包成合適的格式以供瀏覽器使用。
它的做用有代碼轉換(利用各類loader將瀏覽器沒法識別的語言轉換成合適的格式),文件優化(好比說打包時壓縮體積),代碼分割,模塊合併,自動刷新(熱更新),代碼校驗,自動發佈。 引用了網上的一張圖來大體看一下webpack的運行機制: css
首先是安裝webpack,在本地項目文件夾下npm init初始化以後,下載webpack以及webpack-cli:html
npm init -y
npm i webpack webpack-cli -D
複製代碼
此時在文件夾下創建一個src文件夾,用於放置項目代碼。webpack此時能夠進行0配置打包,在命令行輸入npx webpack
能夠打包出一個dist文件夾,下面有一個main.js就是打包後的文件。這個打包後的文件內容,就是使用遞歸的方式解析src中的js模塊,遞歸的方法名爲__webpack_require__,它支持咱們在瀏覽器中使用CommonJs規範。
默認打包的配置很弱,它只能識別js模塊,在沒有配置的狀況下,webpack就至關於一個js模塊打包機。固然咱們不可能直接就0配置打包一個項目,下面我總結一下webpack中經常使用的一些基本配置。前端
webpack中默認的配置文件名爲webpack.config.js,在根目錄下創建一個名爲webpack.config.js的文件,就能夠在這個文件中寫配置項。它的內容遵循CommonJs規範,webpack提供給咱們修改這個文件名的一些方法:
(1)打包時輸入命令npx webpack --config webpack.config.my.js
。
(2)爲了避免用每次都在命令行輸出一串這麼長的命令,在package.json中配置scripts,"build" : "webpack --config webpack.config.my.js"
。
這兩種配置方法均可以修改默認配置文件名。先在webpack.config.my.js寫一段基本的配置:node
//webpack是用node寫的
let path = require('path');
module.exports = {
mode: 'development', //模式 生產環境production 開發模式development
entry: './src/index.js', //入口
output: { //出口
filename: 'bundle.[hash:8].js', //打包後的文件名,[hash]每次打包生成新的文件
//__dirname以當前目錄解析成絕對路徑
path: path.resolve(__dirname, 'dist'), ///path字段只接受絕對路徑,所以須要一個node模塊來輔助配置 path.resolve把相對路徑解析成絕對路徑
publicPath: 'http://www.help.com'//公共路徑,打包出的資源文件會帶着這個公共路徑。
}
}
複製代碼
loader幫助咱們告訴瀏覽器遇到不能識別的模塊應該怎麼處理,前面咱們說過webpack默認配置只是別js模塊,那麼解析圖片、css、less這些模塊就須要引入loader。react
圖片引入有三種方式:jquery
const logo = require('./01.jpg') //把圖片引入,返回的結構是一個新的圖片地址
const image = new Image();
console.log(logo); //用到file-loader 默認會在內部生成一張圖片,到build目錄下 把生成的圖片的名字返回回來
image.src = logo;
document.body.appendChild(image)
複製代碼
配置以下:webpack
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
name:[name].[ext],
outputPath: '/img/' //打包時另外生成一個img文件夾
}
}
},
複製代碼
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 200 * 1024,//小於200k使用url-loader,大於200k使用file-loader
outputPath: '/img/'
}
}
},
複製代碼
body {
background: red;
background: url('./01.jpg')
}
複製代碼
<img src="./01.jpg" alt="">
複製代碼
配置以下:ios
{
test: /\.html$/,
use: 'html-withimg-loader'
}
複製代碼
打包CSS文件,咱們須要用到兩個loader,一個是style-loader,它負責處理css文件中的import、url()語法。style-loader之內聯<style>
的形式將樣式都寫到模版html的<head>
頭部中。打包LESS文件一樣的套路,less-loader現將less轉換成css。
配置以下:nginx
module: {
rules: [
//loader的用法。字符串只用一個loader 多個loader須要用數組 loader的順序 默認從右向左 從下往上執行
{
test: /\.css$/,
use: [{
loader: 'style-loader',
options: {
insertAt: 'top'//插在最上面,讓本身寫在模版html<style>標籤中的樣式優先級較高
}
}, 'css-loader']
},
{
test: /\.less$/,
use: [{
loader: 'style-loader',
options: {
insertAt: 'top'
}
}, 'css-loader', 'less-loader']
}
]
}
複製代碼
以下圖所示,head標籤下面的三個樣式是分別在.css和.less文件中定義的樣式,而<head>
標籤上面的一個樣式是在模版html中本身設定的。 es6
用一個autoprefixer包和一個postcss-loader自動添加瀏覽器前綴,且這個插件是會更新的,之前transform在須要加上webkit前綴,但Chrome支持後postcss-loader就不會再給這個屬性加上前綴了。
npm i postcss-loader autoprefixer -D
//在根目錄建立postcss.config.js文件
module.exports = {
plugins: [require('autoprefixer')]
}
//在rules css配置中新加入postcss-loader
module: {
rules: [
//loader的用法。字符串只用一個loader 多個loader須要用數組 loader的順序 默認從右向左 從下往上執行
{
test: /\.css$/,
use: [{
loader: 'style-loader',
options: {
insertAt: 'top'//插在最上面,讓本身寫在模版html<style>標籤中的樣式優先級較高
}
}, 'css-loader']
}
]
}
複製代碼
Plugin能夠在webpack運行到某個階段的時候,幫助咱們作某些事情,相似於生命週期的概念。在某個時間點,須要某個機制完成一些事情。
在咱們打包文件後,該插件會生成一個html模版,而且把打包後的其餘文件在該模版中引用。生成的html模版的內容是咱們能夠本身定義的。
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html', //模版文件的位置
filename: 'index.html', //打包出來html文件的名稱
minify: {
removeAttributeQuotes: true, // 去除雙引號
collapseWhitespace: true, //變成一行
},
hash: true //添加一個hash戳
})
],
複製代碼
咱們打包時,把全部樣式抽離出來生成一個CSS文件,在模版html文件中以link形式引入。
npm i mini-css-extract-plugin -D
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
plugins: [
new MiniCssExtractPlugin({
filename: 'main.css'
})
]
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, //把style-loader換成MiniCssExtractPlugin.loader
'css-loader',
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
'postcss-loader'
]
}
]
}
複製代碼
進行到這一步會發現,生產模式下打包出來的main.css也沒有被壓縮,是由於用了MiniCssExtractPlugin這個插件不會壓縮css,須要本身壓縮。使用OptimizeCSSAssetsPlugin插件配置優化項,可是使用這個插件以後,css確實壓縮了,但js又不會壓縮了,所以還要用到UglifyJsPlugin再來壓縮js。
npm i optimize-css-assets-webpack-plugin -D
npm i uglifyjs-webpack-plugin -D
const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); //壓縮js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); //壓縮css
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true, //緩存
parallel: true, //併發壓縮
sourceMap: true // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({})
]
}
複製代碼
在沒有使用該插件以前,每次打包上一版本的文件會遺留在dist文件夾下,須要咱們手動刪除。這個插件,在每次打包以前,先把以前的dist文件夾刪除,打包生成新的dist目錄。
npm install --save-dev clean-webpack-plugin
const {CleanWebpackPulgin} = require('clean-webpack-plugin')
plugins: [
new CleanWebpackPlugin()
]
複製代碼
源代碼與打包後的代碼的映射關係,幫助咱們定位錯誤在源代碼中的位置。在devtool字段中配置,推薦的配置以下:
devtool:"cheap-module-eval-source-map" //開發環境
devtool:"cheap-module-source-map" //線上生產環境
複製代碼
一個提高開發效率的利器,每次改完代碼都要從新打包一次,刷新瀏覽器很是的麻煩。用webpack-dev-server搭建一個服務器,使得咱們不用真實的打包,而是在內存中打包,放置到devSever服務器上,以便咱們在開發時調試測試整個項目。
下載webpack-dev-server:
npm i webpack-dev-server -D
複製代碼
以後,先在package.json中配置scripts,"dev" : "webpack-dev-server --config webpack.config.my.js"
。而後配置一下devServer中的一些配置項:
devServer: { //開發服務器的配置
port: 8889, //端口號
progress: true, //進度條
contentBase: './dist', //指定了服務器資源的根目錄,可是在開發過程不會真實打包
compress: true, //啓用 gzip 壓縮
open: true //自動打開瀏覽器
},
複製代碼
聯調期間,先後端分離,直接獲取數據會跨域,上線後咱們使⽤用nginx轉發,開發期間,webpack-dev-server就能夠搞定這件事。
咱們先啓動服務器,mock一個接口:
const koa = require('koa')
const app = new koa()
const Router = require('koa-router')
const router = new Router()
router.get('/api/info', async (ctx, next) => {
ctx.body = {
username: 'zhunny',
message: 'hello mock'
}
ctx.status = 200
})
app.use(router.routes())
app.listen(3000)
複製代碼
此時在咱們前端項目中請求該接口的數據,會存在跨域問題,咱們在dev-server中配置服務器代理:
axios.get('http://localhost:3000/api/info').then(res=>{
console.log(res)
})
複製代碼
devServer: { //開發服務器的配置
port: 8889, //端口號
progress: true, //進度條
contentBase: './dist', //指定了服務器資源的根目錄,可是在開發過程不會真實打包
compress: true, //啓用 gzip 壓縮
open: true, //自動打開瀏覽器
proxy: {
"/api": {
target: "http://localhost:3000"
}
}
},
複製代碼
以後修改請求的接口:
axios.get('/api/info').then(res=>{
console.log(res)
})
複製代碼
webpack自己能夠處理ES6語法,可是有些瀏覽器對es六、es7或者是es8的語法還不能識別。出於兼容性的考慮,咱們會使用Babel來將ES6轉換成ES5語法。
npm i babel-loader @babel/core @babel/preset-env -D
{
test: /\.js$/,
exclude: /node_modules/, //排除該文件夾下的內容
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
}
複製代碼
固然只配置上述字段是不夠的,到此步爲止,一些es六、七、8新增的方法和類依然不能被識別。咱們還須要下載@babel/polyfill
,它將es六、七、8中的語法特性打包放到瀏覽器中,至關於一個補丁包。
它的基本使用方法是在入口js文件中引用:import '@babel/polyfill'
。可是這種方法是引入整個補丁包,使得webpack打包後的體積變大。咱們能夠對這點進行優化。移除在js文件中引用的@babel/polyfill
,配置文件中添加useBuiltIns字段,對使用到的es六、七、8語法特性按需加載。
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',//按需加載
corejs: 2 //指定core的版本
}
]
],
複製代碼
當咱們開發組件庫、工具庫這些場景時,在js文件中引用@babel/polyfill
就不合適了。由於@babel/polyfill
以全局變量的方式注入,會形成全局污染。上面用的按需加載usage的方法也不會形成全局污染,可是這個字段還在試驗階段。咱們可使用閉包方式@babel/plugin-transform-runtime
來代替。可是這種方式就不會對原型鏈上的某些方法進行轉義,所以開發正常的業務場景就比較適合用polyfill,無所謂全局變量的影響,咱們不須要擔憂某些原型鏈上的方法沒有被轉義。
npm i @babel/plugin-transform-runtime -D
npm i @babel/runtime -S
{
test: /\.js$/,
exclude: /node_modules/, //排除該文件夾下的內容
use: {
loader: 'babel-loader',
options: {
plugins: [
[
'@babel/plugin-transform-runtime',
{
absoluteRuntime:false,
corejs:2,
helpers:true,
regenerator:true,
useESMoudules:false
}
]
]
}
}
}
複製代碼
Babel配置可能內容較多,咱們能夠把options內容放到.babelrc中。
//.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage", //按需加載 實驗性的功能
"corejs": 2
}
],
"@babel/preset-react"
]
}
複製代碼
一些es7的提案如class,則還須要用到@babel/plugin-proposal-class-properties,裝飾器則須要用到@babel/plugin-proposal-decorators:
npm i @babel/plugin-proposal-class-properties -D npm i @babel/plugin-proposal-decorators -D {
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
],
plugins: [
['@babel/plugin-proposal-decorators', { "legacy": true }],
['@babel/plugin-proposal-class-properties', { "loose": true }],
['@babel/plugin-transform-runtime'] //generator
]
}
}
}
複製代碼
js語法的校驗用到了eslint以及eslint-loader,他的官網爲https://eslint.org
,eslint在使用時須要配置一個.eslint.json的規則文件放在根目錄,具體配置項見官網。
npm i eslint eslint-loader -D
{
test: /\.js$/,
use: {
loader: 'eslint-loader',
options: {
enforce: 'pre' //在普通loader以前執行
}
},
}
複製代碼
引入全局變量有三種方式,假如要在全局引入jquery庫:
import $ from 'jquery'
require(expose-loader)
console.log(window.$)
複製代碼
new webpack.providePlugin(
{$:'jquery'}
)
複製代碼