Webpack打包優化

1、前言

       對於使用vue開發項目的FE來講,打包上線這個環節相信你們都不陌生。本文主要是介紹如何經過webpack(實踐版本:webpack4.16.5)的配置來提升打包構建速度以及減少包的體積。javascript

2、優化策略  

 css,js,html壓縮

使用optimize-css-assets-webpack-plugin 來壓縮css。在webpack4的配置項中,需在optimization下的minimizer對象中去實例化使用。css

// 壓縮css
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')

// 在optimization.minimizer中引用
new OptimizeCSSAssetsPlugin()複製代碼

使用MiniCssExtractPlugin來抽離內聯css到外聯文件html

// 抽離內聯css到外部css文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 在optimization.minimizer中引用
new MiniCssExtractPlugin({  
    filename: utils.assetsPath('css/[name].[contenthash:8].css'),  
    chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css')
})複製代碼

使用uglifyjs-webpack-plugin來壓縮js,重點屬性parallel,用來開啓多進程並行執行js壓縮,大大提升構建速度。vue

//壓縮js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

//在optimization.minimizer中引用
new UglifyJsPlugin({  
    uglifyOptions: {    
        warnings: false,    
        mangle: {      
            safari10: true    
        },    
        compress: {      
            drop_debugger: false,      
            drop_console: true, //console      
            pure_funcs: ['console.log'] // 移除console    
        },    
        output:{      
             // 去掉註釋內容      
             comments: false    
        }  
    },  
    sourceMap: false,  
    cache: true,  
    parallel: true
})複製代碼

使用html-webpack-plugin來壓縮html代碼,並實現自動化注入腳本以及樣式文件,對於文件名中包含哈希的Webpack捆綁包尤爲有用。java

// 壓縮html
const HtmlWebpackPlugin = require('html-webpack-plugin')

// 在plugins中引入
new webpack.HtmlWebpackPlugin({    filename:path.resolve(__dirname, '../dist/index.html'),
    template: 'index.html',    inject: true,
    favicon: resolve('favicon.ico'),
    title:'標題',
    minify: {  
        removeComments: true,  
        collapseWhitespace: true,  
        removeAttributeQuotes: true
    } })
    
 複製代碼

拆包

webpack4.x中使用splitChunks來進行拆包,抽離第三方依賴庫。默認狀況下,webpack將會基於如下條件自動分割代碼塊:node

  • 新的代碼塊被共享或者來自node_modules文件夾
  • 新的代碼塊大於30kb(在min+giz以前)
  • 按需加載代碼塊的請求數量應該<=5
  • 頁面初始化時加載代碼塊的請求數量應該<=3

默認配置以下:webpack

splitChunks: {
    chunks: "async",
    minSize: 30000, // 模塊的最小體積
    minChunks: 1, // 模塊的最小被引用次數
    maxAsyncRequests: 5, // 按需加載的最大並行請求數
    maxInitialRequests: 3, // 一個入口最大並行請求數
    automaticNameDelimiter: '~', // 文件名的鏈接符
    name: true,
    cacheGroups: { // 緩存組
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
        default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}複製代碼

能夠經過自定義配置項修改配置,如像這樣抽離第三方依賴庫(如單獨將elementUI打包)ios

splitChunks: {  
    chunks: 'all',  
    cacheGroups: {    
        lib: {      
            name: 'chunks-libs',      
            test: /[\\/]node_modules[\\/]/,      
            priority: 10,      
            chunks: 'initial' // 只打包初始時依賴的第三方    
        },    
       elementUI: {      
            name: 'chunk-elementUI', // 單獨將 elementUI 拆包      
            priority: 20, // 權重要大於 libs 和 app 否則會被打包進 libs 或者 app      
            test: /[\\/]node_modules[\\/]element-ui[\\/]/,      
            chunks: 'all'    
       }  
    }
}複製代碼

通常還需配合runtimeChunk使用(runtimeChunk的具體做用)。在抽取 Webpack 運行時代碼的時候,要指定 runtimeChunk 屬性:git

  • true:表示每一個入口都抽取一個公共的運行時代碼(適用於單入口)
  • 'single':表示多個入口抽取一個公共的運行時代碼,通常使用這種方式(適用於多入口)
// optimization.runtimeChunk 具體使用規則  詳見 
//https://webpack.js.org/configuration/optimization/#optimizationruntimechunk

runtimeChunk: true複製代碼

Happypack

因爲 JavaScript 是單線程模型,在webpack構建過程當中,咱們須要使用Loader對js,css,圖片,字體等文件作轉換操做,而且轉換的文件數據量也是很是大的,且這些轉換操做不能併發處理文件,而是須要一個個文件進行處理,HappyPack的基本原理是將這部分任務分解到多個子進程中去並行處理,子進程處理完成後把結果發送到主進程中,從而減小總的構建時間。(ps:對file-loader和url-loader支持很差,因此這兩個loader就不須要換成HappyPack)github

Happypack運行機制


Happy使用方式

// 多進程併發執行loader  默認爲單線程
const HappyPack = require('happypack')
const os = require('os')// 根據系統的內核數量 指定線程池個數 也能夠其餘數量
const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length})//插件中引入
plugins:[
    new HappyPack({ 
        // 基礎參數設置  
        id: 'babel', // 上面loader?後面指定的id  
        loaders: ['babel-loader?cacheDirectory'], // 實際匹配處理的loader  這裏加入了緩存控制 
        threadPool: happyThreadPool,    
        verbose: true
    })
]


// loader處理器中使用 經過id匹配
modules: {
     test: /\.js$/,      
     use:'happypack/loader?id=babel',  
     exclude: /node_modules/, // 排除不處理的目錄  
     include: [    
        resolve('src'), resolve('test'),   
        resolve('mock'),    
        resolve('node_modules/webpack-dev-server/client')  
    ]
}複製代碼

緩存與增量構建

緩存構建:webpack構建中,通常須要使用許多loader來預處理文件,以babel-loader爲例。能夠經過設置cacheDirectorycacheDirectory=true來達到緩存的目的。

{  
    test: /\.js$/,  
    loader: 'babel-loader?cacheDirectory',  
    exclude: /node_modules/, // 排除不處理的目錄  
    include: [    
        resolve('src'),    
        resolve('test'),    
        resolve('mock'),    
        resolve('node_modules/webpack-dev-server/client')  
    ]
}複製代碼

cacheDirectory對loader轉譯後的結果進行緩存,以後的webpack進行構建時,都會去嘗試讀取緩存來避免高耗能的babel從新轉譯過程。


增量構建:使用增量構建而不是全量構建有利於構建速度的提高。全量構建即每次從新構建都須要從新編譯一次(包括未修改部分),而增量構建對於未修改的部分不會再從新編譯,對於rebuild可以大大提升編譯速度。對於開發階段,可使用webpack-dev-server來達到增量編譯的目的,對於生產階段,能夠經過給生成的文件添加hash(或chunkhash 或contenthash )來實現增量構建。

output: {  
    path: config.build.assetsRoot,  
    filename: utils.assetsPath('js/[name].[chunkhash:8].js'),  
    chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].js')
}複製代碼

優化模塊查找路徑

經過配置resolve.modules來告訴webpack解析模塊時應該搜索的目錄。默認配置採用向上遞歸搜索的方式去尋找,設置特定搜索目錄有助於webpack更快搜索到目標。

resolve: {  
    extensions: ['.js', '.vue', '.json'],  
    alias: {    
        '@': resolve('src')  
    },  
    modules: [    
        resolve('src'),    
        resolve('node_modules')  
    ]}
}複製代碼

DllPlugin和DllReferencePlugin

dll全稱爲動態連接庫,先經過dllPlugin生成清單文件(這個文件包含了從 requireimport 的request到模塊 id 的映射),而後經過DllReferencePlugin引用該清單文件,將依賴的名稱映射到模塊的 id 上。這樣每次打包時.先去查找清單裏中是否已經存在這個依賴,若是已經存在,則不打包,若是還沒存在,則須要打包。與經過externals 的方式引入第三方庫相似,dll主要用於那些沒有能夠在<script>標籤中引入的資源的模塊(純npm包)。

使用方式

新建一個webpack.dll.conf.js文件,並執行webpack --config ./build/webpack.dll.conf.js會在static/js文件夾下生成dll.vendor.js以及在根目錄下生成mainfest.json

//webpack.dll.conf.js
const webpack = require('webpack');
const path = require('path');
const vendors = [  'vue',  'vue-router',  'vuex',  'axios'];
module.exports = {  
    output: {    
         path: path.resolve(__dirname, '../static/js'),    
         filename: 'dll.[name].js',    library: '[name]'  
    }, 
    entry: {    vendor: vendors,  }, 
    plugins: [    
         new webpack.DllPlugin({      
             path: 'manifest.json',      
             name: '[name]',      
             context: __dirname    
         })  
    ]
}複製代碼

在項目配置文件(如webpack.base.conf.js)中經過DllReferencePlugin引入

// build/webpack.base.conf.js
 const manifest = require('../manifest.json')    
    
 // 插件中引入
 plugins: [
   new webpack.DllReferencePlugin({
       mainfest
    })
 ]複製代碼

而後在index.html手動引入該文件

<script type="text/javascript" src="/static/js/dll.vendor.js"></script>複製代碼

externals配合cdn加載第三方庫

externals的做用是從打包的bundle文件中排除依賴。通俗點講,就是在項目中經過import引入的依賴在打包的時候不會打包到bundle包中去,而是經過script的方式去訪問這些依賴。與Dll不一樣的是,要去維護cdn

// build/webpack.base.conf.js
// externals 對象中的   key表明第三方依賴名(同package.json包中依賴名)
// value表明暴露給外部使用的別名  這裏element-ui的別名爲ELEMENT
module.exports ={
    externals: {  
        'vue':'Vue',  
        'vue-router':'VueRouter',  
        'axios':'axios',  
        'vuex':'Vuex',  
        'element-ui':'ELEMENT'
    }
}複製代碼

在index.html文件中經過cdn方式引入

// head中引入
<link href="https://cdn.bootcss.com/element-ui/2.4.6/theme-chalk/index.css" rel="stylesheet">
// body中引入
<script type="text/javascript" src="/static/js/dll.vendor.js"></script>
<script src="https://cdn.bootcss.com/vue/2.5.17/vue.min.js"></script>
<script src="https://cdn.bootcss.com/element-ui/2.4.6/index.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.6/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script>複製代碼

在相關文件中移除相關引入的依賴

// main.js
//經過cdn的方式註釋相關依賴

// import Vue from "vue";
// import ElementUI from "element-ui";
//  Vue.use(ElementUI);


// store/index.js
// 註釋掉vuex import引入方式

// import Vue from 'vue';
// import Vuex from 'vuex';
// Vue.use(Vuex);


// router/router.js
// 註釋掉vue-router
 
// import Vue from 'vue';
// import Router from 'vue-router';


//使用externals中vue-router暴露的全局對象名VueRouter進行實例化
const router = new VueRouter({   
     mode: "history",    
     scrollBehavior: () => ({y: 0}),       
    routes: [otherRouter, ...constantRouterMap]
});複製代碼


分析工具

查看 webpack 打包後全部組件與組件間的依賴關係,能夠經過打包分析工具來實現。

webpack-bundle-analyzer

 webpack-bundle-analyzer是一個webpack打包分析插件,它將打包的依賴關係以樹形圖的方式呈現,直觀方便。

plugins:[
    new BundleAnalyzerPlugin({  
        analyzerPort: 8080,  
        generateStatsFile: false
    })
]複製代碼

在插件配置項中引入並經過cnpm run build --report來生成可視化分析圖,效果圖以下:

官方分析工具

webpack提供的一個官方工具,可查看你的項目版本信息,有多少modules,多少chunks,中間有多少錯誤信息、有多少警告,各個模塊之間的依賴關係等。

使用方式

首先經過webpack --config ./build/webpack.prod.conf.js --json > stats.json 生成json文件(ps:若是使用了Happypack,需刪除該json文件前兩行,不然不是標準的json),而後經過官網分析工具地址將該文件上傳,如圖所示:


具體分析結果能夠經過點擊相應的模塊查看詳情。


總結

webpack優化並無一個通用方案,這裏我只是列出了我使用過的一些策略,具體業務需具體分析,對症下藥。但願本文能幫助到一些有須要的小夥伴~

相關參考資料:

Webpack

花褲衩-手摸手webpack4

Webpack Analyse

HappyPack

webpack打包分析與性能優化

相關文章
相關標籤/搜索