webpack安裝
- 安裝本地webpack
- yarn add webpack webpack-cli -D
webpack可以零配置
- 打包工具 -> 輸出打包後的結果
- 打包 (支援模組化,多個模組(js/img/style)打包成一個模組)
webpack的執行流程
- 因為瀏覽器環境不支援node語法
- webpack封裝自己的 _webpack_require_方法提取模組中的module.exports內容
- 使用遞迴原理,遍歷提取每個依賴的模組
webpack基礎配置
- webpack.config.js
- 最簡單的配置
- mode -> 檔案打包的模式
mode : 'development', // production
複製程式碼
- entry -> 打包檔案入口
entry : './src/index.js'
複製程式碼
- output -> 打包檔案出口(必須為絕對路徑)
output : {
filename : 'bundle.[hash:8].js', //打包後的檔案加上8位雜湊,罷免打包檔案快取問題
path : path.resolve(__dirname, 'build')
}
複製程式碼
webpack-dev-server
- yarn add webpack-dev-server -D
- npx webpack-dev-server
- 啟動一個靜態伺服器(記憶體中,沒有實體檔案)
- devServer -> 伺服器配置
devServer : {
port : 3000,
progress : true, // 載入時顯示進度條
contentBase : './build', // 以build目錄為靜態服務入口
compress : true, // 所有服務都使用gzip壓縮
}
複製程式碼
webpack的外掛(都是類)
plugins : [...] // 外掛
複製程式碼
打包html
- yarn add html-webpack-plugin -D
const HtmlWebpackPlugin = require('html-webpack-plugin')
const config = {
plugins : [ // 陣列,放著所有webpack的外掛
new HtmlWebpackPlugin({
template : './src/index.html',
filename : 'index.html',
minify : { //html處理配置
removeAttributeQuotes : true, //去除雙引號
collapseWhitespace : true //縮寫成一行
},
hash : true //引入js是否使用hash
})
],
...
}
複製程式碼
抽離樣式
- yarn add mini-css-extract-plugin -D
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const config = {
plugins : [
new MiniCssExtractPlugin({
filename : 'css/main.[hash:10].css'
})
],
module : {
rules : [
{
test : /\.css$/,
use : [
MiniCssExtractPlugin.loader, // 替換掉style-loader,把css打包抽離到main.css中
'css-loader'
]
},
{
test : /\.styl$/,
use : [
MiniCssExtractPlugin.loader, // 替換掉style-loader,把css打包抽離到main.css中
'css-loader',
'stylus-loader'
]
}
]
}
...
}
複製程式碼
給css3加上字首
- yarn add postcss-loader autoprefixer -D
- 需要新增一個 postcss.config.js,使用postcss-loader會自動呼叫該檔案
postcss.config.js
->
module.exports = {
plugins : [
require('autoprefixer)
]
}
____________________________________________________________________________________________________________
const config = {
module : {
rules : [
{
test : /\.css$/,
use : [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
},
]
},
...
}
複製程式碼
壓縮樣式
- webpack預設使用uglifyjs給js壓縮,如果想給css壓縮,需要手動修改optimization.minimizer配置,並手動給js壓縮
- yarn add optimize-css-assets-webpack-plugin -D
- yarn add uglifyjs-webpack-plugin -D
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin")
const config = {
optimization : {
minimizer : [
new UglifyJsPlugin({
cache: true, //快取
parallel: true, //併發壓縮
sourceMap: true //生成map對映
}),
new OptimizeCSSAssetsPlugin({})
]
}
}
複製程式碼
webpack處理模組
module : {
rules : [ {test : //, use : [...]}, {}, {}] // 模組處理規則
}
複製程式碼
處理樣式模組,插到style裡
- css less sass stylus
- css -> yarn add css-loader style-loader -D
- less -> yarn add less less-loader -D
- sass -> yarn add node-sass sass-loader -D
- stylus -> yarn add stylus stylus-loader -D
const config = {
module : {
rules : [
{
test : /\.css$/,
use : [
{
loader : 'style-loader',
options : {
insertAt : 'top' //把解析的style插入頂部,避免覆蓋原有style
}
},
'css-loader', // 處理@import 解析路徑
]
},
{
test : /\.styl$/,
use : [
{
loader : 'style-loader',
options : {
insertAt : 'top' //把解析的style插入頂部,避免覆蓋原有style
}
},
'css-loader', // 處理@import 解析路徑
'stylus-lader' // stylus -> css
]
}
]
}
}
複製程式碼
處理js模組
- yarn add babel-loader @babel/core @babel/preset-env -D
- babel-loader
- @babel/core -> babel核心模組,包含很多transform方法去轉換程式碼
- @babel/preset-env -> 程式碼轉化的外掛
- @babel/plugin-transform-runtime
- @babel/runtime -> 把公用的方法提取到一個單獨的庫中
const config = {
module : {
rules : [
{
test : /\.js$/,
use : {
loader : 'babel-loader',
options : {
presets : [
'@babel/preset-env' //包含大部分es6 -> es5的外掛
],
plugins : [
... // 部分高階的js語法轉換外掛
'@babel/plugin-transform-runtime', // async, promise, generator
]
}
},
include : path.resolve(__dirname, 'src'),
exclude : /node_modules/
}
]
}
}
複製程式碼
js語法檢測
- yarn add eslint eslint-loader -D
- 去eslint官網下載一個適合的 .eslintrc.json檔案放在根目錄
const config = {
module : {
rules : [
{
test : /\.js$/,
use : {
loader : 'eslint-loader',
options : {
enforce : 'pre' // 把該loader變為前置loader,優先普通loader(一般的都是)執行
}
},
}
]
}
}
複製程式碼
全域性變數引入
-
以jquery為例
-
expose-loader -> 使用內聯loader expose-loader把jquery暴露到window上
import $ from 'expose-loader?$!jquery'
console.log($)
console.log(window.$)
//或者在webpack.config.js中配置
{
test : require.resolve('jquery'),
use : 'expose-loader?$'
}
複製程式碼
- webpack.ProvidePlugin(webpack的外掛) -> 每個模組中都注入$
const webpack = require('webpack')
plugins : [
new webpack.ProvidePlugin({
$ : 'jquery'
})
]
console.log($)
複製程式碼
- 使用cdn引入
const config = {
externals : {
"jquery": {
commonjs: "$", //如果我們的庫執行在Node.js環境中,import $ from 'jquery'等價於const $ = require('jquery')
commonjs2: "$", //同上
amd: "$", //如果我們的庫使用require.js等載入,等價於 define(["jquery"], factory);
root: "$" //如果我們的庫在瀏覽器中使用,需要提供一個全域性的變數‘$’,等價於 var $ = (window.$) or ($);
}
},
...
}
複製程式碼
處理圖片模組
- url-loader / file-loader(在output目錄裡生成一個檔案,使用import, 並把路徑返回)
// 在js中
import logo from 'logo.jpg'
let oImg = new Image()
oImg.src = logo
document.body.appendChild(oImg)
// 在webpack.config.js
module : {
rules : [
{
test : /\.(jpg|png|git)$/,
use : {
loader : 'url-loader',
options : {
fallback : 'file-loader', // 備用loader,當圖片大小超過limit值時使用
limit : 1024 * 200
}
}
}
]
}
複製程式碼
- html-withimg-loader
{
test : /\.html$/,
use : 'html-withimg-loader' // 把html上的img標籤使用url-loader處理圖片
}
複製程式碼
打包檔案分類(一般修改對應的filename屬性,特殊除外)
- clean-webpack-plugin (每次打包都清空輸出目錄)
- 圖片目錄
{
test : /\.(jpg|png|git)$/,
use : {
loader : 'url-loader',
options : {
limit : 1024 * 2,
fallback : 'file-loader',
outputPath : '/static/img/' // 新增outputPath屬性,規定輸出路徑
}
}
}
複製程式碼
- js目錄
output : {
filename : 'static/js/bundle.[hash:10].js', // 修改filename
path : path.resolve(__dirname, 'build')
},
複製程式碼
- 樣式目錄
new CssPlugin({
filename : 'static/css/main.[hash:10].css' // 修改filename
})
複製程式碼
打包多頁應用
- entry為一個物件
entry : {
home : './src/home.js',
shop : './src/shop.js'
}
複製程式碼
- output.filename使用[name]屬性,[name]為entry的屬性
output : {
filename : '[name].[hash:8].js',
path : path.resolve(__dirname, 'build')
}
複製程式碼
- 每個HtmlPlugin只能打包一個頁面,如果多頁面,需要使用多個HtmlPlugin
plugins : [
new HtmlPlugin({
template : './src/index.html',
filename : 'home.html',
chunks : ['home'] // 程式碼塊,引入指定的js模組,entry的屬性
}),
new HtmlPlugin({
template : './src/index.html',
filename : 'shop.html',
chunks : ['shop'] // 程式碼塊,引入指定的js模組,entry的屬性
})
]
複製程式碼
source-map配置
- 配置devtool屬性 (4種格式)
- eval系列會整合到打包後的檔案,使檔案變大, 最好不要用
- √
devtool : source-map
-> 產生單獨的對映檔案,報錯時標識出錯的行和列 - ×
devtool : eval-source-map
-> 整合到打包後的檔案中,報錯時標識出錯的行和列 - √
devtool : cheap-module-source-map
-> 產生單獨的檔案對映檔案,報錯時只標識出錯的行 - ×
devtool : cheap-module-eval-source-map
-> 整合到打包後的檔案中,報錯時只標識出錯的行
檔案實時打包
- 新增watch屬性和配置watchOptions
const config = {
watch : true, // 開啟實時打包檔案
watchOptions : {
poll : 1000, // 每秒監聽多少次
ignored : /node_modules/, // 打包忽略的目錄
aggregateTimeout : 600, // 600毫秒防抖
}
}
複製程式碼
webpack解決跨域
- webpack-dev-server預設使用http-proxy-middleware中介軟體實現代理
// 請求路徑: '/api/user' -> http://127.0.0.1:12306/api/user
devServer : {
proxy : {
// 處理所有/api開頭的請求
'/api' : {
target : 'http://127.0.0.1:820', // 把請求打到 http://127.0.0.1:820
pathRewrite : {
'/api' : '' // 把請求中的'/api'換成'': 127.0.0.1:12306/api/user -> 127.0.0.1:820/user
}
}
}
}
複製程式碼
- devServer的before鉤子函式, 所有請求都會通過這裡
// 請求路徑: '/user' -> http://127.0.0.1:12306/user
devServer : {
before (app) { // app為express生成的
app.get('/user', (req, res => {
res.json({name : 'xyd-before'})
}))
}
}
複製程式碼
- 在服務端執行webpack,因為都在同一伺服器,則不存在跨域
- 需要 webpack-dev-middleware中介軟體
// server.js
const express = require('express')
const webpack = require('webpack')
const webpackMiddle = require('webpack-dev-middleware')
const webpackConfig = require('./webpack.config.js')
const app = express()
const compiler = webpack(webpackConfig)
app.use( webpackMiddle(compiler) )
app.listen(820, () => console.log('server start, listening port 820'))
複製程式碼
resolve(模組解析配置)
- resolve.alias -> 引入模組的別名
resolve : {
alias : {
js : path.resolve(__dirname, 'src/js') // import 'js/a.js' = import '/src/js/a.js'
}
}
複製程式碼
- resolve.extensions -> 自動解析模組字尾,記得加 .
resolve : {
extensions : ['.js', '.css', '.styl', '.json'] // 帶有這些檔案字尾的可以省略
}
複製程式碼
環境變數
定義環境變數
- 在開發中判斷環境變數
- new webpack.DefinePlugin
// webpack.config.js
plugins : [
nwe webpack.DefinePlugin({
DEV : JSON.stringify('dev')
})
]
// index.js
let host = ''
if (DEV === 'dev') {
host = 'http:127.0.0.1:8080/'
} else {
host = 'http:www.xuyede.com/'
}
複製程式碼
判斷不同環境
- 使用三個配置檔案, 使用 webpack-merge合併
- webpack.base.js -> 公用基礎配置
- webpack.dev.js -> 開發環境配置
- webpack.prod.js -> 生產環境配置
//webpack.dev.js
const merge = require('webpack-merge')
const base = require('./webpack.base.js')
const devConfig = merge(base, {
mode : 'development',
...
})
module.exports = devConfig
複製程式碼
webpack的優化
externals -> 將依賴的庫指向全域性變數,從而不用打包 (注意需要手動引入CDN min.js檔案)
externals : {
'react' : 'window.React', //對於react這個模組,不打包,直接用cdn檔案
'jquery' : 'window.jQuery'
}
複製程式碼
module.noParse -> 匹配的項不解析依賴庫
module : {
noParse : /jquery|lodash/, // 引入jquery|lodash時,不會去解析jquery|lodash相關的依賴庫
}
複製程式碼
webpack.IgnorePlugin -> 在使用import|require引入模組時,忽略匹配到的模組
// moment模組預設將所有本地化的內容一起打包,使用IgnorePlugin
plugins : [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) // 在moment中, 如果引入 .locale, 就忽略掉
]
複製程式碼
動態連線庫 (dll)
建立一個依賴庫,用來伺服業務程式碼 webpack.DllPlugin(dll庫配置檔案) + webpack.DllReferencePlugin(業務程式碼配置檔案) 步驟:
- 打包dll
- 引用dll,打包業務程式碼
//打包dll包,配置dll.config.js
const path = require('path')
const webpack = require('webpack')
const vendors = [
'react',
'react-dom',
// ... 其他庫
]
const dllConfig = {
mode : 'development',
entry : {
'_dll' : vendors
},
output : {
filename : '[name].js',
path : path.resolve(__dirname, 'build'),
library : '[name]' //把 _dll.js的結果匯出到 var _dll變數中
},
plugins : [
new webpack.DllPlugin({
path : path.resolve(__dirname, 'build', 'manifest.json'),
name : '[name]', //dll暴露的物件名,必須與output.library一致
})
]
}
複製程式碼
// 引入_dll.js到html中
複製程式碼
// 打包業務程式碼,第三方庫會先去_dll.js中找,沒有再打包第三方庫
const webpack = require('webpack')
const config = {
plugins : [
new webpack.DllReferencePlugin({
manifest : path.resolve(__dirname, 'build', 'manifest.json')
})
]
}
複製程式碼
// 修改 clean-webpack-plugin,避免沒事打包都刪除全部
plugins : [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns : [
path.join(process.cwd(), 'build/static/*'), //只清理static
]
})
]
複製程式碼
多執行緒打包
- happypack
- 將原有的 webpack 對 loader 的執行過程,從單一程式的形式擴充套件多程式模式
- 不改變原來loader的配置格式
const HappyPack = require('happypack')
const os = require('os')
// 生成一個共享程式池,有os.cpus().length個子程式
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
const config = {
module : {
rules : [
{
test : /\.js$/,
| use : {
use : 'happypack/loader?id=happyBabel' | 使用前-> loader : 'babel-loader'
| }
include : path.resolve(__dirname, 'src'),
exclude : /node_modules/
}
]
},
plugins : [
new HappyPack({
id : 'happyBabel', // 對應上面的id
loaders : [
'babel-loader' //用法和loader一樣
],
threadPool: happyThreadPool, //如果有多個HappyPack例項,則啟用該屬性,共享happyThreadPool程式池
}),
...
]
}
複製程式碼
- happypack 注意點
- happypack對 file-loader和 url-loader不友好,避免在happypack中使用
- happypack使用mini-css-extract-plugin需要把 MiniCssPlugin放到主執行緒中處理
// happypack打包樣式(mini-css-extract-plugin)
const HappyPack = require('happypack')
const MiniCssPlugin = require('mini-css-extract-plugin')
const config = {
module : {
rules : [
{
test : /\.css$/,
use : [
MiniCssPlugin.loader, // MiniCssPlugin留在主執行緒中執行
'happypack/loader?id=happyCss'
]
}
]
},
plugins : [
new MinicssPlugin({
filename : 'static/css/main.css'
}),
new HappyPack({
id : 'happyCss',
loaders : [
'css-loader',
'post-loader'
]
})
]
}
複製程式碼
webpack自帶優化
- tree-shaking -> 只有import語法才生效,把業務中沒有用到的程式碼自動剔除
- scope hosting -> 作用域提升
抽離公共程式碼 (把公共模組提取成一個庫)
- optimization.shpitChunks.cacheGroups
// 公共模組抽離
optimization : {
splitChunks : { // 分割程式碼塊
cacheGroups : { // 快取組
common : {
chunks : 'initial', // 初始化時抽離
minSize : 0, // 最小抽離程式碼的大小
minChunks : 2, // 最小抽離程式碼的呼叫次數
name : 'common' // 抽離檔案的名字
}
}
}
}
複製程式碼
// 第三方庫抽離
optimization : {
splitChunks : {
cacheGroups : {
common : { ... },
vendor : {
priority : 1, // 快取組的優先順序,數字越大越先處理
test : /node_modules/, // 匹配
chunks : 'initial', // 初始化時抽離
minSize : 0, // 最小抽離程式碼的大小
minChunks : 2, // 最小抽離程式碼的呼叫次數
name : 'vendor' // 抽離檔案的名字
}
}
}
}
複製程式碼
懶載入
- import() -> 原理: 使用jsonp實現動態載入
- 直接使用 import('./xyd.js') 引入
- import('./xyd.js) 返回一個Promise物件
// 假設有一個資原始檔 source.json, 點選按鈕時才請求
let oBtn = document.createElement('button')
oBtn.innerHTML = 'request source'
oBtn.addEventListener('click', () => {
// 使用懶載入
import('./source.json')
.then(data => {
// ...
})
}, null)
document.body.appendChild(oBtn)
複製程式碼
熱更新
- 程式碼更改時不會重新整理整個頁面, 只更新更改的模組
- devServer.hot : true
- new webpack.NamedModulesPlugin() -> 開啟HMR時可以顯示更新模組的相對路徑
- new webpack.HotModuleReplacementPlugin() -> 啟用HMR, 介面暴露到 module.hot
import str from './source'
if (module.hot) {
module.hot.accept('./source', () => { // 當'./source'模組更新的時候,做對應的處理
console.log('source模組更新了')
let str = require('./source') // 模組更新,重新引入
})
}
複製程式碼