在上一篇文章從0到1手把手帶你捋一套webpack+vue項目模板中,主要講解了一個單頁項目從npm init
開始如何手動搭建前端單頁腳手架
以後,這篇文章就繼續跟你們一塊兒分享一下如何從webpack
上來作出構建的優化。css
800+
,隨之帶來的挑戰就是項目中打包出來的js體積愈來愈大,構建速度愈來愈緩慢,無疑,從webpack
構建配置上就須要做出一系列的優化了。如下配置的優化,均在真實項目中有過實戰,這裏就在上篇文章demo項目基礎上來一一講解。聲明:本文基於webpack
版本號以下html
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11"
複製代碼
本文的項目地址:github.com/Paulinho89/…前端
在做出webpack
配置優化以前,首先咱們須要藉助一些webpack插件
來分析咱們當前的構建日誌
,以及構建速度
、構建體積
等。vue
經過設置stats
來統計咱們的構建的信息node
咱們在package.json
中添加以下配置react
"scripts": {
"build:stats": "webpack --config build/webpack.config.prod.js --json > stats.json"
}
複製代碼
運行npm run build:stats
後,再執行npm run prod
後,在咱們項目的根目錄下會生成一個stats.json
文件,這個文件會記錄咱們項目構建的各類信息,同時也能夠stats
後看到控制檯打印出對應的構建信息。webpack
剛纔提到的stats
來分析構建日誌,可是stats
的分析仍是比較有限,若是咱們想知道咱們使用的哪一個lodaer
,或者是哪一個plugin
的具體耗時該怎麼辦呢?speed-measure-webpack-plugin
就是一個不錯的分析插件。git
安裝es6
npm i speed-measure-webpack-plugin -D
複製代碼
配置github
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasureWebpackPlugin();
module.exports = smp.wrap(WebpackMerge(WebpackConfig, {
mode: "production",
devtool: "hidden-source-map",
entry: {
app: resolve(__dirname, "../src/main")
}
}));
複製代碼
運行npm run prod
,能夠很清楚的知道咱們每個loader
以及plugin
運行的耗時以及咱們的總打包的耗時。
安裝
npm i webpack-bundle-analyze -D
複製代碼
配置
const WebpackBundleAnalyzer = require('webpack-bundle-analyzer');
const { ANALYZE } = process.env;
const { BundleAnalyzerPlugin } = WebpackBundleAnalyzer;
if (ANALYZE === 'true') {
PluginConfig.push(new BundleAnalyzerPlugin());
}
複製代碼
咱們在package.json
中添加以下配置
"analyz": "cross-env NODE_ENV=production ANALYZE=true npm_config_report=true npm run prod",
複製代碼
運行npm run analyz
,瀏覽器會自動打開http://127.0.0.1:8888/
,此時咱們就能夠很清晰的看到每個打包後的js文件體積Gzip
前跟Gzip
後的大小對比,以及一些基礎包體積大小的對比。
上述,咱們藉助了一些插件來幫助咱們分析項目中打包的體積
、耗時
等,那接下來咱們就要從構建的速度
上來進行進一步的分析並優化。
原理:每次 webapck 解析一個模塊,HappyPack 會將它及它的依賴分配給 worker 線程中。
安裝
npm i happypack -D
複製代碼
配置
const HappyPack = require('happypack');
plugins: [
new HappyPack({
// id 標識符,要和 rules 中指定的 id 對應起來
id: 'babel',
// 須要使用的 loader,用法和 rules 中 Loader 配置同樣
// 能夠直接是字符串,也能夠是對象形式
loaders: ['babel-loader']
})
],
複製代碼
運行npm run prod
後對比可見,構建的時間縮短了2秒鐘
。
使用 terser-webpack-plugin
插件
安裝
npm i terser-webpack-plugin@1.3.0 -D
複製代碼
配置
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
cache: true
})
]
}
}
複製代碼
運行npm run prod
後對比可見,構建的時間縮短了500ms
。
思路:將
vue
、vue-router
基礎包經過cdn
引入,不打入bundle
中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>從0到1手把手帶你捋一套webpack+vue項目模板</title>
</head>
<body>
<div id="app">
<router-view></router-view>
</div>
<!-- 正常的引入 cdn 資源便可 -->
<script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
</body>
</html>
複製代碼
配置
module.exports = {
module: {
...
},
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter'
}
}
複製代碼
若是在項目中繼續使用的話,咱們依然可使用import
的方式引入。
import Vue from 'vue'
import VueRouter from 'vue-router'
複製代碼
這樣配置的話 webpack
在 dev
運行或 build
打包時,就不會去本地組件包中查找這些在 externals
中註冊的組件了(天然也不會將他們打包到一個 app.js
中去),而是會去 window
域下直接調用 Vue
, VueRouter
等對象。
思路:將 vue、vue-router等 基礎包打包成一個文件。
方法:使用 webapck內置的插件DLLPlugin 進行分包,DllReferencePlugin 對 manifest.json 引用。
配置
build
目錄下新建webpack.config.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
library: [
'vue',
'vue-router'
]
},
output: {
filename: '[name]_[hash].dll.js',
path: path.join(__dirname, '../library'),
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash]',
path: path.join(__dirname, '../library/[name].json')
})
]
}
複製代碼
咱們在package.json
中添加以下配置
"dll": "webpack --config build/webpack.config.dll"
複製代碼
運行npm run dll
後,項目根目錄下會自動生成一個library
文件夾,其中library.json
文件就是咱們接下來要在webpack.config.prod.js
中進行的映射。
webpack.config.prod.js
配置
const Webpack = require('webpack');
module.exports = {
plugins: [
new Webpack.DllReferencePlugin({
manifest: require('../library/library.json')
})
],
}
複製代碼
再次執行npm run prod
後對比發現,分包後的app.js
體積比分包前app.js
體積小了30kb
,構建速度上也有微弱的減小,固然咱們這裏只是把vue
、vue-router
抽離了出來作個演示,那當咱們項目比較大的時候,能夠把更多的業務基礎包抽離出來,效果會更加明顯。
babel-loader
開啓緩存,在babel-loader
後邊加上參數cacheDirectory=true
配置
plugins: [
...BasePlugins,
new HappyPack({
// id 標識符,要和 rules 中指定的 id 對應起來
id: 'babel',
// 須要使用的 loader,用法和 rules 中 Loader 配置同樣
// 能夠直接是字符串,也能夠是對象形式
loaders: ['babel-loader?cacheDirectory=true']
}),
]
複製代碼
執行npm run prod
後,對比發現緩存開啓後比開啓前快了600ms
使用 cache-loader
或者 hard-source-webpack-plugin
安裝
npm i hard-source-webpack-plugin -D
複製代碼
配置
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
plugins: [
new HardSourceWebpackPlugin()
],
}
複製代碼
執行npm run prod
後,會發如今咱們的node_modules
目錄下會自動幫助咱們生成一個.cache
目錄,裏邊存放的就是每次構建緩存的文件,運行後對比發現緩存開啓後比開啓前快了1800ms
,時間大大縮短,固然咱們這裏也只是爲了演示,縮短的時間不是很明顯,一旦在項目體積大的時候,開啓緩存構建,速度會有巨大的提高。
目的:儘量的少構建模塊,好比 babel-loader 不解析 node_modules
配置
module.exports = {
module: {
{
test: /\.js$/,
use: [
{
loader: 'thread-loader',
options: {
workers: 3
}
},
"babel-loader",
],
exclude: /node_modules/
}
}
}
複製代碼
resolve.modules
配置(減小模塊搜索層級)resolve.extensions
配置alias
配置
module.exports = {
resolve: {
extensions: [".js", ".json", ".css", ".less", ".vue"],
alias: {
vue$: "vue/dist/vue.common.js",
"@": resolve(__dirname, "../src")
}
}
}
複製代碼
概念:1個模塊可能有多個方法,只要其中的某個方法使用到了,則整個文件都會被打到 bundle 裏面去,tree shaking 就是隻把用到的方法打入 bundle ,沒用到的方法會在 uglify 階段被擦除掉。
使用
webpack4
中咱們把mode
設置爲production
狀況下默認開啓tree-shaking
那js
的tree-shaking
這裏就再也不細描述了,有興趣的小夥伴們能夠本身動手試試,那關於css
的tree-shaking
咱們該如何進行配置呢?
在沒有進行開啓css
的tree-shaking
前,咱們先來測試一下,在index.vue
中寫一行沒有使用的css
,看一下會不會被打包進去。
執行npm run prod
後發現,確實被打包到js文件中了。
使用purgecss-webpack-plugin
,前提是須要配置mini-css-extract-plugin
配合使用開啓css
的tree-shaking
。
安裝
npm i mini-css-extract-plugin purgecss-webpack-plugin -D
複製代碼
配置
const Path = require("path");
const glob = require('glob');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PurgecssPlugin = require('purgecss-webpack-plugin');
const PATHS = {
src: path.join(__dirname, 'src')
};
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
}),
// 開啓css的tree-shaking
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
})
]
}
複製代碼
運行完npm run prod
後發現,在index.vue
中寫一行沒有使用到的unused-css
這個樣式被擦出掉了,沒有被打包進去。
一般一個項目咱們會引入不少各類格式的圖片,多張圖片被打包之後,若是不作壓縮的話,體積仍是至關大的,因此生產環境對圖片體積的壓縮就顯得格外重要了。
方式
tinypng
手動壓縮,比較零碎,也不夠自動化image-webpack-loader
來進行自動壓縮這裏咱們就採用image-webpack-loader
來實現對圖片的自動壓縮。
安裝
npm i image-webpack-loader -D
複製代碼
配置
module.exports = {
module: {
{
test: /\.(jpg|jpeg|png|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
outputPath: "img/",
name: "[name]-[hash:6].[ext]"
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: '65-90',
speed: 4
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
}
]
}
}
}
複製代碼
<template>
<div class="container">
{{ msg }}
<img :src="require('@/images/bg.jpg')">
</div>
</template>
複製代碼
運行npm run prod
後對比發現,壓縮後的圖片的體積大大縮小。
壓縮前:
壓縮後:
一般咱們在項目中會使用babel來將不少es6中的API進行轉換成es5,可是仍是有不少新特性無法進行徹底轉換,好比promise、async await、map、set等語法,那麼咱們就須要經過額外的polyfill(墊片)來實現語法編譯上的支持。
方案 | 優勢 | 缺點 |
---|---|---|
babel-polyfill | vue、react官方支持 | 包的體積比較大,很難單獨抽離async await、map、set等語法 |
babel-plugin-transform-runtime | 只對須要使用到async/await 時,纔會自動引入polyfill,減少庫與工具包的體積 | 不能polyfill原型上的一些方法 |
polyfill-service | 只返回用戶須要用到的polyfill,並且由社區來維護,好比polyfill.io | 部分瀏覽可能不能識別 |
這裏咱們仍是推薦使用第三種方式,由polyfill.io
官方爲咱們提供的服務。
咱們能夠先來使用polyfill.io
驗證一下,在不一樣的User Agent
,是否是會下發不一樣的polyfill
。
iphone5
iphone6/7/8
iphoneX
咱們對比能夠發現,不一樣的手機機型,咱們去訪問polyfill.io/v3/polyfill…的時候,資源的體積大小是不同的。
項目中使用
<script src='https://polyfill.io/v3/polyfill.min.js'></script>
複製代碼
webpack5
已經在2020年的10月10號完成了發佈,可是目前基於項目架構在生產環境下的穩定性、可維護性來說,咱們這裏依然採用的是webpack4來分析構建的優化策略。webpack5
在項目打包優化上會更具備優點,如持久化的緩存、對node
中polyfill
的移除、更優的tree-shaking
、以及使人興奮的Module Federation
,這些新特性仍是很值得你們去升級探索的。接下來,我會在其中的一篇文章中給你們分享webpack5
項目升級實戰,敬請期待。若是您以爲這篇文章對您有一點點幫助,歡迎看完後給我點贊,您的點贊是我前進的動力,謝謝~~