webpack優化

1.production 模式打包自帶優化

  • tree shakingcss

    tree shaking是一個術語、一般用於打包時移除js中未引用的代碼(dead-code),它依賴於ES6模塊系統中的import 和 export 的***靜態結構特性***html

    開發時引入一個模塊時,若是隻引用其中一個功能,上線打包時只會把用到的功能打包進bundle中,其餘沒有用到的功能都不會打包進來,能夠實現最簡單的基本優化vue

    1. 建立一個 math.js, 拋出兩個方法
    export const add = (a, b) => a + b
    export const minus = (a, b) => a- b
    複製代碼
    1. 在 main.js 中使用
    // tree shaking 分析
    // 如果此時使用 require 引入,無論 math 中的方法是否使用,都會被打包
    const math = require('./utils/math')
    // 如果使用 import 引入, 只會打包使用了 math 的方法
    import { add } from './utils/math'
    console.log('index 頁面',math.add(1,2));
    console.log('index 頁面',add(1,2));
    複製代碼
    1. 根據不一樣的引入方式進行打包,觀察打包後的文件
  • scope hoistingnode

    Scope hositing 做用:是將模塊之間的關係進行結果推測,可讓webpack文件打包出來的代碼文件更小、運行的更快jquery

    scope hositing實現原理:分析出模塊之間的依賴關係,儘量的把打散的模塊合併到一個函數中,可是前提是不能形成代碼冗餘, 所以只有哪些被引用了一次的模塊可能被合併webpack

    因爲scope hositing 須要分析出模塊之間的依賴關係,所以源碼必須使用ES6模塊化語句,否則就不能生效,緣由和 tree shaking同樣web

    1. 在 main.js 中定義幾個變量並輸出vue-router

      const a = 1
      const b = 2
      const c = 3
      // webpack 在這裏會進行 預執行,將結果推斷後打包放在這裏
      console.log(a + b + c)
      console.log(a, b, c)
      複製代碼
    2. 打包以後代碼變成npm

      console.log(6),console.log(1,2,3)
      複製代碼

      由於三個變量只是在這個地方定義而且使用,並無在其餘位置使用,webpack會直接以具體的數值進行打包,節省了三個變量的定義json

  • 代碼壓縮

    全部代碼使用UglifyJsPlugin進行壓縮、混淆

2.CSS優化

2.1 將CSS提取到獨立文件中

Mini-css-extract-plugin 是用於將 CSS 提取爲獨立的文件的插件,對每一個包含css的js文件都會建立一個css文件,支持按需加載css和sourceMap

  • 只能用於webpack4中,優點

    • 異步加載
    • 不重複編譯,性能更好
    • 更容易使用
    • 只針對css
  • 使用

    • 安裝

      npm i -D mini-css-extract-plugin
      複製代碼
    • 引用

      const MiniCssExtractPlugin = require('mini-css-extract-plugin');
      複製代碼
    • 建立插件對象,配置抽離的css文件名,支持placeholder語法

      new MiniCssExtractPlugin({
      	filename:'[name].css' // [name] 就是 placeholder 語法
      })
      複製代碼
    • 將原來配置的全部 style-loader 替換爲 MiniCssExtractPlugin.loader

      {
        test:/\.css$/,
        use:[MiniCssExtractPlugin.loader, 'css-loader']
      },
      {
        test:/\.less$/,
        use:[MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
      },
      {
        test:/\.scss$/,
        use:[MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
      },
      複製代碼

2.2 自動添加CSS前綴

使用 postcss,須要使用 postcss-loader 和 autoprefixer

  1. 安裝

    npm i -D postcss-loader autoprefixer
    複製代碼
  2. 修改配置文件,將 postcss-loader 放置在 css-loader 右邊

    {
      test:/\.css$/,
      use:[MiniCssExtractPlugin.loader, 'css-loader',  'postcss-loader',]
    },
    {
      test:/\.less$/,
      use:[MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader']
    },
    {
      test:/\.scss$/,
      use:[MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader']
    },
    複製代碼
  3. 項目根目錄下添加 postcss 的配置文件: postcss.config.js

    module.exports = {
      plugins: [
        require('autoprefixer')({
          browsers: [
            // 加這個後能夠出現額外的兼容性前綴
            "> 0.01%"
          ]
        })
      ]
    }
    複製代碼

2.3 開啓CSS壓縮

須要使用 optimize-css-assets-webpack-plugin 插件來完成css壓縮

可是因爲配置css壓縮時會覆蓋掉webpack默認的優化設置,致使JS代碼沒法壓縮,因此還須要把JS代碼壓縮插件倒入進來 terser-webpack-plugin

  1. 安裝

    npm i -D terser-webpack-plugin optimize-css-assets-webpack-plugin
    複製代碼
  2. 引用

    const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
    const TerserPlugin = require('terser-webpack-plugin');
    複製代碼
  3. 配置

    optimization:{
      minimizer: [
        new TerserPlugin({}),
        new OptimizeCssAssetsPlugin({})
      ]
    }
    複製代碼

webpack4默認採用的JS壓縮插件是 uglifyjs-webpack-plugin,在 mini-css-extract-plugin上一個版本中還推薦使用該插件,可是新的版本卻建議使用 terser-webpack-plugin

3.JS優化

code splitting 是webpack打包時用到的重要的優化特性之1、此特性可以把代碼分離到不一樣的bundle中,而後能夠按需加載或者並行加載這些文件,代碼分離能夠用於獲取更小的bundle,以及控制資源加載優先級,若是可以合理的使用可以極大影響加載時間

  • 三種常見的代碼分離方法
    • 入口起點:使用entry配置,手動的分離代碼
    • 放置重複:使用 SplitChunksPlugin 去重和分離 chunk
    • 動態導入:經過模塊的內聯函數調用來分離代碼

3.1手動配置多入口

  • 手動配置多入口會存在一些問題
    • 若是入口chunks之間包含重複的模塊,哪些重複的模塊都會被引入到各個打包後的js文件中
    • 方法不夠靈活,而且不能將核心應用程序邏輯進行動態拆分代碼
  1. 在webpack配置文件中配置多個入口

    entry:{
    	main: './src/main.js',
    	other: './src/other.js'
    },
    output:{
    	path: path.join(__dirname, '..', './dist'),
    	filename: '[name].js',
    	publickPath: '/'
    }
    複製代碼
  2. 在main.js 和 other.js 中都共同引入一個模塊, 並使用其功能

    1. Main.js

      import $ from 'jquery'
      
      $(() => {
      	$('<div></div>').html('main').appendTo('body')
      })
      複製代碼
    2. other.js

      import $ from 'jquery'
      
      $(() => {
      	$('<div></div>').html('other').appendTo('body')
      })
      複製代碼
  3. 打包文件,能夠看到 main 和 other 打包的文件中都加載的了 jquery

3.2抽取公共代碼

webpack4 以上使用的插件爲 SplitChunksPlugin,webpack4 以前的使用的 CommonChunkPlugin已經被移除,最新版本的webpack中只須要在配置文件中的optimization節點下添加一個splitChunks屬性便可進行相關配置

  1. 修改配置文件

    optimization
      splitChunks:{
        chunks: "all"
      }
    }
    複製代碼
  2. 打包查看文件

    打包以後會將各自的入口文件進行打包,額外會再生產一份js文件,此文件中就是各個chunk中所引用的公共部分

  • splitChunksPlugin 配置參數

    SplitChunksPlugin 的配置只須要在 optimization 節點下的 splitChunks 進行修改便可,若是沒有任何修改,則會使用默認設置

    默認的 SplitChunksPlugin 配置適用於絕大多數用戶

    • webpack 會基於以下默認原則自動分割代碼

      • 公用代碼塊或者來自 node_modules 文件夾的組件模塊
      • 打包的代碼塊大小超過30kb,最小化壓縮以前的
      • 按需加載代碼塊時,同時發送的請求最大數量不該該超過5
      • 頁面初始化時,同時發送的請求最大數量不該該超過3
    • SplitChunksPlugin 默認配置

    module.exports = {
      optimization: {
        splitChunks: {
          chunks: 'async', // 只對異步加載的模塊進行拆分,import('jquery').then()就是典型的異步加載,可選項還有 all | initial
          minSize: 30000, // 模塊最少大於 30kb 纔會拆分
          maxSize: 0, // 爲0時模塊大小無上限,只要大於 30kb 都會拆分。如果非0,超過了maxSize的值,會進一步拆分
          minChunks: 1, // 模塊最少引用一次纔會拆分
          maxAsyncRequests: 5, // 異步加載時同時發送的請求數量最大不能超過5,超過5的部分不拆分
          maxInitialRequests: 3, // 頁面初始化時,同時發送的請求數量最大不能超過3,超過3的不跟不拆分
          automaticNameDelimiter: '~', // 默認的鏈接符
          name: true, // 拆分的chunk名,設置爲true表示根據模塊名和CacheGroup的key來自動生成,使用上面的鏈接符鏈接
          cacheGroups: { // 緩存組配置,上面配置讀取完成後進行拆分,若是須要把多個模塊拆分到一個文件,就須要緩存,因此命名爲緩存組
            vendors: { // 自定義緩存組名
              test: /[\\/]node_modules[\\/]/, // 檢查 node_modules 目錄,只要模塊在該目錄下就使用上面配置拆分到這個組
              priority: -10, // 權重爲-10,決定了那個組優先匹配,假如node_modules下面有個模塊要拆分,同時知足vendors和default組,此時就會分到 priority 值比較大的組,由於 -10 > -20 因此分到 vendors 組
              filename:'vendoes.js'
            },
            default: { // 默認緩存組名
              minChunks: 2, // 最少引用兩次纔會被拆分
              priority: -20, // 權重 -20
              reuseExistingChunk: true // 若是主入口中引入了兩個模塊,其中一個正好也引用了後一個,就會直接複用,無需引用兩次
            }
          }
        }
      }
    };
    複製代碼

3.3動態導入(懶加載)

webpack4默認是容許import語法動態導入的,可是須要babel的插件支持,最新版babel的插件包爲:@babel/plugin-syntax-dynamic-import,須要注意動態導入最大的好處就是實現了懶加載,用到那個模塊纔會加載那個模塊,能夠提升SPA應用程序的首屏加載速度,三大框架的路由懶加載原理同樣

  1. 安裝

    npm i -D @babel/plugin-syntax-dynamic-import
    複製代碼
  2. 修改 .babelrc ,添加 @babel/plugin-syntax-dynamic-import 插件

    {
    	"presets": ["@babel/env"],
    	"plugins": [
    		"@babel/plugin-proposal-class-properties",
    		"@babel/plugin-syntax-dynamic-import"
    	]
    }
    複製代碼
  3. 將jq模塊動態導入

    function getDivDom(){
    	// import('jquery') 返回的是一個 promise,如果低版本須要注意
    	return import('jquery').then(({default: $}) => {
    		return $('<div></div>').html('動態導入')
    	})
    }
    複製代碼
  4. 給某個按鈕添加點擊事件,點擊後調用getDivDom函數建立元素並添加到頁面

    window.onload = () => {
      document.getElementById('btn').addEventListener('click',() => {
        getDivDom().then(item => {
          item.appendTo('body')
        })
      })
    }
    複製代碼

4.noParse

在引入一些第三方模塊時,如jq等,咱們知道其內部確定不會依賴其餘模塊,由於咱們用到的只是一個單獨的js或者css文件,因此此時若是webpack再去解析他們的內部依賴關係,實際上是很是浪費時間的,就須要阻止webpack浪費精力去解析這些明知道沒有依賴的庫,能夠在webpack的配置文件的module節點下加上noParse,並配置正則來肯定不須要解析依賴關係的模塊

module:{
	noParse: /jquery|bootstrap/  // jquery|bootstrap 之間不能加空格變成 jquery | bootstrap, 會無效
}
複製代碼

5.IgnorePlugin

在引入一些第三方模塊時,例如momentJS、dayJS,其內部會作i18n處理,因此會包含不少語言包,而語言包打包時會比較佔用空間,若是項目只須要用到中文或者少數語言,能夠忽略掉全部的語言包,而後按需引入語言包,從而使得構建效率更高,打包生成的文件更小

  • 以moment爲例

    import moment from 'moment'
    moment.locale('zh-CN') // 設置爲中文
    
    console.log(moment().subtract(6, 'days').calendar())
    複製代碼
  1. 首先要找到moment依賴的語言包時什麼,經過查看moment的源碼來分析

    function loadLocale(name) {
        var oldLocale = null;
        // TODO: Find a better way to register and load all the locales in Node
        if (!locales[name] && (typeof module !== 'undefined') &&
                module && module.exports) {
            try {
                oldLocale = globalLocale._abbr;
                var aliasedRequire = require;
                aliasedRequire('./locale/' + name);
                getSetGlobalLocale(oldLocale);
            } catch (e) {}
        }
        return locales[name];
    }
    複製代碼

    經過 aliasedRequire('./locale/' + name) 能夠知道momentJS的多語言目錄是locale,全部的語言JS文件都在這個目錄中

  2. 使用IgnorePlugin插件忽略其依賴

    將momentJS的多語言目錄locale忽略

    new webpack.IgnorePlugin(/\.\/locale/, /moment/)
    複製代碼
  3. 須要使用某些依賴時自行手動引入

    忽略其依賴以後,moment.locale('zh-CN')就會失效,由於其所依賴的語言包全都被忽略了,須要手動將其引入

    import moment from 'moment'
    import  'moment/locale/zh-cn' // 須要手動引入方可生效
    moment.locale('zh-CN')
    
    console.log(moment().subtract(6, 'days').calendar())
    複製代碼

6.DLLPlugin

在引入一些第三方模塊時,例如Vue、React等,這些框架的文件通常都是不會修改的,而每次打包都須要去解析他們,也會影響打包速度,就算是作了拆分,也只是提升了上線後的用戶訪問速度,並不會提升構建速度,因此若是須要提升構建速度,應該使用動態連接庫的方式,相似windows的dll文件

藉助DLLPlugin插件實現將這些框架做爲一個個的動態連接庫,只構建一次,之後的每次構建都只會生成本身的業務代碼,能夠很好的提升構建效率

豬喲思想在於,講一些不作修改的依賴文件,提早打包,這樣咱們開發代碼發佈的時候就不須要再對這些代碼進行打包,從而節省了打包時間,主要使用兩個插件: DLLPlugin和DLLReferencePlugin

須要注意的是,如果使用的DLLPlugin,CleanWebpackPlugin插件會存在衝突,須要移除CleanWebpackPlugin插件

  • DLLPlugin

    使用一個單獨webpack配置建立一個dll文件,而且它還建立一個manifest.json,DLLReferencePlugin使用該json文件來作映射依賴性,這個文件會告訴webpack哪些文件已經提取打包好了

    • 配置參數
      • context(可選):manifest文件中請求的上下文,默認爲該webpack文件上下文
      • name:公開的dll函數的名稱,和output.library保持一致便可
      • path:manifest.json 生成的文件夾及名稱
  • DLLReferencePlugin

    該插件主要用於主webpack配置,它引用的dll須要預先構建的依賴該系

    • 配置參數
      • context: manifest文件中的請求上下文
      • manifest: DLLPlugin插件生成的manifest.json
      • content(可選): 請求的映射模塊id(默認爲manifest.content)
      • name(可選): dll暴露的名稱
      • scope(可選): 前綴用於訪問dll的文件
      • sourceType(可選): dll是如何暴露(libraryTarget)

將VUE項目中的庫抽取成DLL

  1. 準備一份將VUE打包成DLL的webpack配置文件。
  • 在build目錄下新建一個文件webpack.vue.js,專門用於打包vue的DLL的。
  • 配置入口:將多個要作成dll的庫全放進來
  • 配置出口:必定要設置library屬性,將打包好的結果暴露在全局
  • 配置plugin:設置打包後dll文件名和manifest文件所在地
// 此配置文件 是打包VUE全家桶的
const path = require('path')
const webpack = require('webpack')

module.exports = {
  mode: 'production',
  entry:{
    vue: [ 
      'vue/dist/vue',
      'vue-router'
    ]
  },
  output:{
    path: path.resolve(__dirname, '../dist'),
    filename: '[name]_dll.js',
    library: '[name]_dll' // 最終會在全局暴露出一個[name]_dll的對象
  },
  plugins:[
    new webpack.DllPlugin({
      name: '[name]_dll',
      path: path.resolve(__dirname, '../dist/manifest.json'),
    })
  ]
}
複製代碼

webpack.vue.js 只是用來打包生成 [name]_dd.js 文件和 manifest.json文件的,是不須要參與到業務代碼打包的,由於只會在每一次修改了須要生成dll文件的時間纔會執行一次,不然不須要參與到打包

  1. webpack.base.js中進行插件的配置

使用DllReferencePlugin指定manifest文件的位置便可

new webpack.DllReferencePlugin({
	manifest: path.resolve(__dirname, '../dist/manifest.json'),
})
複製代碼
  1. 因爲[name]_dll文件生成以後,並無動態的引入進去,因此須要一個插件能夠動態的將生成的dll文件引入

安裝add-asset-html-webpack-plugin

npm i -D add-asset-html-webpack-plugin
複製代碼

配置插件自動添加script標籤到HTML中,須要注意的是,必須在HtmlWebpackPlugin後面引入,由於HtmlWebpackPlugin是生產一個html文件,AddAssetHtmlWebpackPlugin是在已有的html中注入一個script,不然會被覆蓋

new AddAssetHtmlWebpackPlugin({
		filepath: path.resolve(__dirname, '../dist/vue_dll.js')
})
複製代碼
相關文章
相關標籤/搜索