Webpack重要知識點

最近在系統整理知識點,現將Webpack的一些重要知識點羅列出來,方便快速查閱。

Tree Shaking

爲了使用tree shaking,須要知足如下條件:javascript

  • 使用ES2015語法(即importexport)
  • 在項目package.json文件中,添加sideEffects入口
  • 引入一個可以刪除未引用代碼(dead code)的壓縮工具(minifier)(例如:UglifyJSPlugin)

將文件標記爲無反作用(side-effect-free)

這種方式是經過package.jsonsideEffects屬性來實現的。css

{
  "sodeEffects": false
}

「反作用」的定義是,在導入時會執行特殊行爲的代碼,而不是僅僅暴露一個export或多個export。舉例說明,例如polyfill,它影響全局做用域,而且一般不提供export

注意,任何導入的文件都會受到tree shaking的影響。這意味着,若是在項目中使用相似css-loader並導入CSS文件,則須要將其添加到 side effect 列表中,以避免在生產模式中無心中將它刪除:java

{
  "sideEffects": ['*.css']
}

壓縮輸出

從 webpack 4 開始,也能夠經過 "mode" 配置選項輕鬆切換到壓縮輸出,只需設置爲 "production"。

也能夠在命令行接口中使用--optimize-minimize標記,來使用UglifyJSPluginnode

Code Splitting

code splitting的必要性webpack

  • 不進行code splitting,打包後單文件提交較大,加載時長較長,影響用戶體驗
  • 不進行code splitting,常常修改業務代碼,從新打包後,瀏覽器不能進行緩存,致使性能較差,影響用戶體驗

code splitting的配置

同步代碼方式

import _ from 'lodash';

webpack.common.js配置以下:git

....
optimization: {
  splitChunks: {
    chunks: 'all'
  }
}
....

配置後,會將公用類庫進行打包,生成一個vendors~main.js文件。github

異步代碼方式

function getComponent() {
  return import('lodash').then(({ default: _ }) => {
    var element = document.createElement('div');
    element.innerHTML = _.join(['Clear', 'love'], '');
    return element;
  })
}

getComponent().then(element => {
  document.body.appendChild(element);
})

SplitChunksPlugin 配置參數詳解

魔法註釋(magic comment)修改打包動態組件名稱

1. 使用@babel/plugin-syntax-dynamic-import支持動態引入插件

.babelrc中引用該插件web

....
plugins: ['@babel/plugin-syntax-dynamic-import']
....

2. 添加魔法註釋

function getComponent() {
  return import(/* webpackChunkName:"lodash" */'lodash').then(({ default: _ }) => {
    var element = document.createElement('div');
    element.innerHTML = _.join(['Clear', 'love'], '');
    return element;
  })
}

getComponent().then(element => {
  document.body.appendChild(element);
})

關鍵是註釋:webpackChunkName: "lodash".。打包後的文件名爲vendors~lodash.js
若想打包事後的文件名不帶vendors~前綴,能夠修改webpack.common.jsoptimization配置項:npm

....
optimization: {
  splitChunks: {
    chunks: "all",
    cacheGroups: {
      venders: false,
      default: false
    }
  }
}
....

splitChunks配置詳解

splitChunks: {
      chunks: 'async', // all async initial 是否對異步代碼進行的代碼分割
      minSize: 30000,  // 引入模塊大於30kb才進行代碼分割
      maxSize: 0, // 引入模塊大於Xkb時,嘗試對引入模塊二次拆分引入
      minChunks: 1, // 引入模塊至被使用X次後才進行代碼分割
      maxAsyncRequests: 5, // 
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10, // 優先級 
          filename: 'vendors.js' // 打包文件名稱
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true // 是否複用已打包代碼
        }
      }
    }
  }

Lazy Loading && Chunk

import 動態引入組件

function getComponent() {
  return import(/* webpackChunkName:"lodash" */'lodash').then(({ default: _ }) => {
    var element = document.createElement('div');
    element.innerHTML = _.join(['Clear', 'love'], '');
    return element;
  })
}

document.addEventListener('click', () => {
  /* 當點擊時才加載lodash */
  getComponent().then(element => {
    document.body.addChild(element);
  })
})

頁面初始化時,不會加載lodash。當點擊頁面時才加載。
import引入動態組件實現的Lazy Loading,其實跟Webpack沒什麼關係。importES6的語法標準。而Webpack藉助babel-profill能識別該語法。json

Chunk

每一個打包的js文件都是一個chunk

打包分析 && Preloading && Prefetching

藉助webpack analyse進行打包分析

package.jsonscripts項中進行配置:

....
scripts: {
  "dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js" 
}
....

打包後會生成stats.json,而後上傳該文件至webpack/analyse進行分析

其餘分析分工具

利用魔法註釋實現Preload/Prefetch

document.addEventListener('click', () => {
  import(/* webpackPrefetch: true */ 'lodash').then(() => {
    ....
  })
})

PreloadPrefetch的區別:

  • preloaded chunk與主模塊並行加載,而prefetched chunk是主模塊加載完後再加載
  • preloaded chunk具備中等優先級,能夠當即下載。而prefetched chunk是在瀏覽器空閒時下載。
  • 瀏覽器支持程度不一樣

具體能夠參考prefetching/preloading-modules

CSS文件的代碼分割

若沒有進行css的代碼分割,經過import方式引入的樣式文件,將會被看成普通的模塊打包到.js文件中。
若須要對css進行代碼分割,須要藉助mini-css-extract-plugin插件實現,具體以下:

// webpack.prod.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin")

....
module: [{
  test: /\.scss$/,
  use: [
    MiniCssExtractPlugin.loader,
    {
      loder: 'css-loader',
      options: { importLoaders: 2 }
    },
    'saas-loader',
    'postcss-loaerd'
  ]
},
{
  test: /\.css$/,
  use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
    'postcss-loader'
  ]
}],
plugins: [
  new MiniCssExtractPlugin({
    filename: '[name].css',  // 入口文件中直接引入css匹配該規則
    chunkFilename: '[name].chunk.css'  // 非入口文件中引入或嵌套引入匹配匹配該規則
  })
]
....

若須要對引入css進行合併、壓縮,能夠藉助optimize-css-assets-webpack-plugin。,具體配置以下:

// webpack.prod.js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin")
....
optimization: {
  minimizer: [new OptimizeCSSAssetsPlugin({})]
}
....

若以前有配置過tree shaking,則須要對如下文件進行修改:

  1. webpack.common.js
optimization: {
  usedExports: true
}
  1. package.json
"sideEffects": [
    "*.css"
  ]

Webpack與瀏覽器緩存

webpack實現瀏覽器緩存,主要是藉助配置output中的contenthash來實現的。

// webpack.prod.js
....
output: {
  filename: '[name].[contenthash].js',
  chunkFilename: '[name].[contenthash].js'
}
....

舊版webpack進行打包時,雖然文件沒有進行任何修改,但打包後生成的contenthash仍是會改變,這時須要再進行一些配置。

// webpack.common.js
....
optimization: {
  runtimeChunk: {
    name: 'runtime'
  }
}
....

構建性能

常規

保持版本最新

使用最新穩定版本的webpacknodenpm等,較新的版本更夠創建更高效的模塊樹以及提升解析速度。

loaders

loaders應用於最少數的必要模塊中,而不是:

// webpack.common.js
module: {
  rules: [
    {
      test: /\.jsx?$/,
      use: ['babel-loader']
    }
  ]
}

使用include字段僅將loader模塊應用在實際須要用其轉換的位置:

// webpack.common.js
module: {
  rules: [
    {
      test: /\.jsx?$/,
      include: path.resolve(__dirname, '../src'),
      use: ['babel-loader']
    }
  ]
}

Smaller = false

減小編譯的總體大小,以提升構建性能。儘可能保持chunks小巧。

  • 使用更小/更少的庫
  • 在多頁面應用程序中使用CommonsChunksPlugin
  • 在多頁面應用程序中以async模式使用CommonsChunksPlugin
  • 移除不須要的代碼
  • 只編譯你在開發的代碼

Worker Pool

thread-loader能夠將很是耗性能的loaders轉存到worker pool中。<br/>
不要使用太多的 workers ,由於 Node.js 的 runtime 和 loader 有必定的啓動開銷。最小化 workers 和主進程間的模塊傳輸。進程間通信(IPC)是很是消耗資源的。

持久化緩存

對於一些性能開銷較大的loader以前能夠添加cache-loader,啓用持久化緩存。

使用package.json中的postinstall清楚緩存目錄。

Dlls

使用DllPlugin將更新不頻繁的代碼進行單獨編譯。這將改善引用程序的編譯速度。即便它增長了構建過程的複雜度。

解析(resolve)

如下幾步能夠提升解析速度:

  • 儘可能減小resolve.modulesresolve.extensionsresolve.mainFilesresolve.desciriptionsFiles中類目的數量,由於它們會增長文件系統的調用次數。
  • 若是你不使用symlinks,能夠設置resolve.symlinks: false
  • 若是你使用自定義解析plugins,而且沒有指定context信息,能夠設置resolve.cacheWithContext: false

Development

在內存中編譯

如下幾個實用的工具經過在內存中進行代碼的編譯和資源的提供,但並不寫入磁盤來提升性能:

  • webpack-dev-server
  • webpack-hot-middleware
  • webpack-dev-middleware

Devtool

須要注意在不一樣的devtool的設置,會致使不一樣的性能差別。

  • eval具備最好的性能,但不能幫你轉義代碼
  • 若是你能接受稍微差一些的mapping質量,你可使用cheap-source-map選擇來提升性能
  • 使用eval-source-map配置進行增量編譯

在大多數狀況下,cheap-module-eval-source-map是最好的選擇

避免在生產環境在纔會用到的工具

某些實用工具,pluginsloaders都只能在構建生產環境時才使用。例如,在開發時使用UglifyJsPlugin來壓縮和修改代碼是沒有意義的。如下這些工具在開發中一般被排除在外:

  • UglifyJsPlugin
  • ExtractTextPlugin
  • [hash]/[chunkhash]
  • AggressiveSplittingPlugin
  • AggressiveMergingPlugin
  • ModuleConcatenationPlugin

最小化入口chunk

webpack只會在文件系統中生成已更新的chunk。應當在生成入口chunk時,儘可能減小入口chunk的體積,以提升性能。

Production

不要爲了很是小的性能增益,犧牲了你應用程序的質量!!請注意,在大多數狀況下優化代碼質量,比構建性能更重要。

多個編譯時

當進行多個編譯時,如下工具能夠幫助到你:

  • parallel-webpack: 它容許編譯工做在woker池中進行。
  • cache-loader: 緩存能夠在多個編譯之間共享。

工具相關問題

Babel

項目中的preset/plugins數量最小化

TypeScript

  • 在單獨的進程中使用fork-ts-checker-webpack-plugin進行類型檢查
  • 配置loaders時跳過類型檢查
  • 使用ts-loader時,設置happyPackMode: true以及 transpileOnly: true

Saas

node-sass中有個來自Node.js線程池的阻塞線程的bug。當使用thread-loader時,須要設置workParallelJobs: 2

其餘

修改CleanWebpackPlugin根路徑

webpack.config.js配置中,須要對plugins中的CleanWebpackPlugin的根路徑進行修改,能夠經過配置root參數。

....
plugins: [
  new CleanWebpackPlugin(['dist'], {
    root: path.resolve(__dirname, '../')
  })
]
....
相關文章
相關標籤/搜索