從0搭建自己的webpack開發環境(五)
往期回顧:
前四篇文章我們已經掌握了webpack
各種常見的配置,這一片文章我們來看看如何實現webpack
中的優化。
我們先來編寫最基本的webpack配置
,然後依次實現其中的各種優化。
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const path = require("path"); module.exports = mode => { return { mode: mode, entry: "./src/main.js", output: { filename: "bundle.js", path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /\.(png|jpg|gif)$/, use: "file-loader" }, { test: /\.js$/, use: "babel-loader" // .babelrc已經配置支援react }, { test: /\.css$/, use: [ mode !== "development" ? MiniCssExtractPlugin.loader : "style-loader", "css-loader" ] } ] }, plugins: [ new PurgecssPlugin({ paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }) // 不匹配目錄,只匹配檔案 }), mode !== "development" && new MiniCssExtractPlugin({ filename: "css/[name].css" }), new HtmlWebpackPlugin({ template: "./src/template.html", filename: "index.html" }) ].filter(Boolean) }; };
.babelrc
配置檔案
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
1.刪除無用的Css樣式
先來看看編寫的程式碼
import './style.css' import React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render(<div>hello</div>,document.getElementById('root'));
body{
background: red
}
.class1{
background: red
}
這裡的
.class1
顯然是無用的,我們可以搜尋src
目錄下的檔案,刪除無用的樣式
const glob = require('glob'); const PurgecssPlugin = require('purgecss-webpack-plugin'); // 這裡需要配合mini-css-extract-plugin外掛 mode !== "development" && new PurgecssPlugin({ paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }) // 不匹配目錄,只匹配檔案 }),
2.圖片壓縮外掛
將打包後的圖片
進行優化
npm install image-webpack-loader --save-dev
在file-loader
之前使用壓縮圖片外掛
loader: "image-webpack-loader",
options: {
mozjpeg: {
progressive: true,
quality: 65
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.90, 0.95],
speed: 4
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
可以發現圖片大小是否有了明顯的變化
3.CDN載入檔案
我們希望通過cdn的方式
引入資源
const AddAssetHtmlCdnPlugin = require('add-asset-html-cdn-webpack-plugin')
new AddAssetHtmlCdnPlugin(true,{
'jquery':'https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js'
})
但是在程式碼中還希望引入jquery
來獲得提示
import $ from 'jquery'
console.log('$',$)
但是打包時依然會將jquery
進行打包
externals:{
'jquery':'$'
}
在配置檔案中標註jquery
是外部的,這樣打包時就不會將jquery進行打包了
4.Tree-shaking && Scope-Hoisting
4.1 Tree-shaking
顧名思義就是將沒用的內容搖晃掉,來看下程式碼:
main.js
import { minus } from "./calc";
console.log(minus(1,1));
calc.js
import {test} from './test';
export const sum = (a, b) => {
return a + b + 'sum';
};
export const minus = (a, b) => {
return a - b + 'minus';
};
test.js
export const test = ()=>{
console.log('hello')
}
console.log(test());
觀察上述程式碼會發現我們主要使用
minus
方法,test.js
程式碼其實是有副作用的!
預設mode:production
時,會自動tree-shaking
,但是打包後'hello'
依然會被打印出來,這時候我們需要配置規避副作用!
在package.json
中配置
"sideEffects":false,
如果這樣設定,預設就不會匯入css
檔案啦,因為我們引入css也是通過import './style.css'
的;
這裡重點就來了,tree-shaking
主要針對es6模組,我們可以使用require
語法匯入css,但是這樣用起來又有點格格不入,所以我們配置css
檔案不起副作用。
"sideEffects":[
"**/*.css"
]
在開發環境下預設tree-shaking
不會生效,可以配置標識提示。
optimization:{
usedExports:true
}
4.2 Scope Hoisting
作用域提升,可以減少程式碼體積,節約記憶體
let a = 1;
let b = 2;
let c = 3;
let d = a+b+c;
export default d;
// 引入d
import d from './d';
console.log(d);
最終打包後的結果會變成
console.log(6)
- 程式碼量明顯減少
- 減少多個函式後記憶體佔用也將減少
5.DllPlugin && DllReferencePlugin
每次構建時第三方模組
都需要重新構建,這個效能消耗比較大,我們可以先把第三方庫打包成動態連結庫,以後構建時只需要查詢構建好的庫就好了,這樣可以大大節約構建時間。
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<h1>hello</h1>,document.getElementById('root'))
5.1 DllPlugin
這裡我們可以先將
react
、react-dom
單獨進行打包
單獨打包建立webpack.dll.js
const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
entry:['react','react-dom'],
mode:'production',
output:{
filename:'react.dll.js',
path:path.resolve(__dirname,'dll'),
library:'react'
},
plugins:[
new DllPlugin({
name:'react',
path:path.resolve(__dirname,'dll/manifest.json')
})
]
}
執行"webpack --config webpack.dll.js
命令,可以看到dll目錄下建立了兩個檔案分別是manifest.json
和react.dll.js
關係是這個醬紫的,到時候我們會通過manifest.json
來找到react.dll.js
檔案中的模組進行載入。
5.2 DllReferencePlugin
在我們的專案中可以引用剛才打包好的動態連結庫
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
// 構建時會引用動態連結庫的內容
new DllReferencePlugin({
manifest:path.resolve(__dirname,'dll/manifest.json')
}),
// 需要手動引入react.dll.js
new AddAssetHtmlWebpackPlugin(
{ filepath: path.resolve(__dirname,'dll/react.dll.js') }
)
使用DllPlugin
可以大幅度提高構建速度
6.動態載入
實現點選後動態載入
檔案
let btn = document.createElement('button');
btn.innerHTML = '點選載入視訊';
btn.addEventListener('click',()=>{
import('./video').then(res=>{
console.log(res.default);
});
});
document.body.appendChild(btn);
給動態引入的檔案增加名字
output:{
chunkFilename:'[name].min.js'
}
import(/* webpackChunkName: "video" */ './video').then(res=>{
console.log(res.default);
})
這樣打包後的結果最終的檔案就是
video.min.js
7.打包檔案分析工具
安裝webpack-bundle-analyzer
外掛
npm install --save-dev webpack-bundle-analyzer
安裝後使用外掛
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
mode !== "development" && new BundleAnalyzerPlugin()
預設就會展現當前應用的分析圖表
8.SplitChunks
我們再來看下SplitChunks
這個配置,他可以在編譯時抽離第三方模組、公共模組。
先將專案配置成多入口檔案
entry:{
a:'./src/a.js',
b:'./src/b.js'
}
我們讓a,b兩個模組同時引用jquery
,這裡別忘了去掉之前的externals
配置
配置SplitChunks
外掛
預設配置在此,我們一個個描述下含義
splitChunks: {
chunks: 'async', // 分割非同步模組
minSize: 30000, // 分割的檔案最小大小
maxSize: 0,
minChunks: 1, // 引用次數
maxAsyncRequests: 5, // 最大非同步請求數
maxInitialRequests: 3, // 最大初始化請求數
automaticNameDelimiter: '~', // 抽離的命名分隔符
automaticNameMaxLength: 30, // 名字最大長度
name: true,
cacheGroups: { // 快取組
vendors: { // 先抽離第三方
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20, // 優先順序
reuseExistingChunk: true
}
}
}
我們將
async
改為initial
我們在為每個檔案動態匯入lodash
庫,並且改成async
import('lodash')
為每個入口引入
c.js
,並且改造配置檔案
splitChunks: {
chunks: 'all',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minSize:1, // 不是第三方模組,被引入兩次也會被抽離
minChunks: 2,
priority: -20,
}
}
}
這樣再反過來看
chunks
的引數是不是就瞭然於胸了呢?
9.熱更新(不是林更新)
模組熱替換(HMR - Hot Module Replacement)是webpack
提供的最有用的功能之一。它允許在執行時替換、新增、刪除各種模組,而無需進行完全重新整理重新載入整個頁面。
- 保留在完全重新載入頁面時丟失的應用程式的狀態;
- 只更新改變的內容,以節省開發時間;
- 調整樣式更加快速,幾乎等同於就在瀏覽器偵錯程式中更改樣式。
啟用熱更新,預設樣式可以支援熱更新,如果不支援熱更新則可以採用強制重新整理。
devServer:{
hot:true
}
new webpack.NamedModulesPlugin(),
讓js
支援熱更新
import sum from './sum';
console.log(sum(1,2));
if(module.hot){ // 如果支援熱更新
module.hot.accept(); // 當入口檔案變化後重新執行當前入口檔案
}
10.IgnorePlugin
配置忽略 import
和require
語法
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
11.費時分析
可以計算每一步執行的執行速度
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
module.exports =smw.wrap({
...
});
12.noParse
module.noParse
,對類似jq這類的依賴庫,內部不會引用其他庫,那麼我們在打包的時候就沒有必要再去解析,這樣能夠增加打包速率。
noParse:/jquery/
13.resolve
resolve: {
extensions: [".js",".jsx",".json",".css"],
alias:{},
modules:['node_modules']
},
14.include/exclude
在使用loader
時,可以指定哪些檔案不通過loader
,或者指定哪些檔案必須通過loader
。
{
test: /\.js$/,
use: "babel-loader",
// include:path.resolve(__dirname,'src'),
exclude:/node_modules/
},
15.happypack
多執行緒打包
,我們可以將不同的邏輯交給不同的執行緒來處理。
npm install --save-dev happypack
使用外掛
const HappyPack = require('happypack');
rules:[
{
test: /\.js$/,
use: 'happypack/loader?id=jsx'
},
{
test: /\.less$/,
use: 'happypack/loader?id=styles'
},
]
new HappyPack({
id: 'jsx',
threads: 4,
loaders: [ 'babel-loader' ]
}),
new HappyPack({
id: 'styles',
threads: 2,
loaders: [ 'style-loader', 'css-loader', 'less-loader' ]
})
16.總結
至此,我們五篇連載《從0搭建自己的webpack開發環境》先要告一段落了,希望大家可以通過這些文章對webpack能有全新的認識,對工作能更有幫助。
有什麼想要了解的前端知識,歡迎關注留言! 謝