Webapck 性能優化

上一章咱們介紹了Webpack經常使用配置和大體優化思路,這一章節咱們來看一下具體怎麼優化(未完待續,大佬們能夠在評論中提點意見)css

git地址:github.com/jxs7754/dem…html

1. 構建速度優化

  • 高版本的node和Webpack
  • 開啓多進程,加快解析、壓縮速度
  • 分包,分離基礎包
  • 利用緩存來提高二次構建速度
  • 減小文件搜索範圍

速度分析:使用speed-measure-webpack-plugin

const SpeedMeasureWebpackPlugin = reqire('speed-measure-webpack-plugin');
const smp = new SeedMeasureWebpackPlugin();
const webpackCofig = smp.wrap({
  plugins:[
    // MyPlugin(),
  ]
})
複製代碼

能夠分析整個打包的總耗時,能夠查看每一個loader和plugins的耗時狀況;vue

1.1 使用高版本的Node和Webapck

  • V8引擎的升級優化
  • webpack4 默認使用更快md4 hash算法
  • webpacks AST 能夠直接從 loader 傳遞給 AST,減小解析時間
  • 使用字符串方法替代正則表達式

1.2 開啓多進程

thread-loader

{
  module:{
    rules: [
      {
        test: '/.js$/',
        use: [
          {
            loader: 'thread-loader',
            options:{
              workers: 3,    
            }
          },
          'babel-loader'
        ]
      }
    ]
  }    
}

複製代碼

HappyPack(做者已經再也不維護)

const HappyPack = require('happypack');

exports.module = {
  rules: [
    {
      test: /.js$/,
      // 1) replace your original list of loaders with "happypack/loader":
      // loaders: [ 'babel-loader?presets[]=es2015' ],
      use: 'happypack/loader',
      include: [ /* ... */ ],
      exclude: [ /* ... */ ]
    }
  ]
};

exports.plugins = [
  // 2) create the plugin:
  new HappyPack({
    // 3) re-add the loaders you replaced above in #1:
    loaders: [ 'babel-loader?presets[]=es2015' ]
  })
];
複製代碼

多線程壓縮

// terser-webpack-plugin
module.exports = {
  optimization: {
    minimizer: {
      new TerserPlugin({
        parallel: 4,  
      })    
    }  
  }    
}
// 下面這倆個插件能夠配置多線程
//  parallel-uglify-plugin 
//  uglifyjs-webpack-plugin 
複製代碼

1.3 分包

設置Externals,使用 html-webpack-externals-plugin將基礎包(vue vue-router)經過CDN,不打入包中。node

new HtmlWebpackExternalsPlugin({
  externals: [
    {
      module: 'react',
      entry: 'https://xxx/react.min.js',
      global: 'React',
    },
    {
      module: 'react-dom',
      entry: 'https://xxx/react-dom.min.js',
      global: 'ReactDOM',
    },
  ],
}),
複製代碼

沒有CDN的狀況 能夠預編譯 DllPlugin進行分包,DllReferencePlugin對manifest.json 引用react

// 分包
module.exports = {
  mode: 'production',
  entry: {
    vue: ['vue/dist/vue.esm.js', 'vue-router', 'vuex'],
    axios: ['axios', 'qs'],
    // ui: ['element-ui'],
  },
  output: {
    filename: '[name]_[chunkhash:8].dll.js',
    path: path.join(__dirname, 'build'),
    library: '[name]',
  },
  plugins: [
    new CleanWebpackPlugin(),
    new webpack.DllPlugin({
      name: '[name]_[hash]',
      path: path.join(__dirname, 'build/[name].json'),
    }),
  ],
};
// 引用
module.exports = {
  plugins: [
     ...['vue', 'axios'].map((item) => new webpack.DllReferencePlugin({
      context: path.join(__dirname, './build'),
      manifest: require(`./build/${item}.json`),
    })),
  ]    
}
複製代碼

1.4 緩存

緩存是爲了二次構建時候,加快構建webpack

babel-loader 開啓緩存

{
    loader: 'babel-loader',
    options:{
      cacheDirectory: true    
    }
}    
複製代碼

terser-webpack-plugin 開啓緩存

{
  optimization: {
    minimizer: {
      new TerserPlugin({
        // 多線程
        parallel: 4,
        // 緩存
        cache: true,
      })    
    }  
  } 
}
複製代碼

hard-source-webpack-plugin 或者 cache-loader

1.5 減小文件搜素範圍

優化loader配置

因爲 Loader 對文件的轉換操做很耗時,因此須要讓儘量少的文件被 Loader 處理。能夠經過 test/include/exclude 三個配置項來命中 Loader 要應用規則的文件。ios

使用合理的alias

在實戰項目中常常會依賴一些龐大的第三方模塊,以 Vue 庫爲例,發佈出去的 Vue 庫中包含多套代碼, vue.runtime.esm.js 中只包含運行時的代碼。若是不用template選項能夠直接用這個減小打包體積。git

module.exports = {
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.runtime.esm.js',    
    }
  }    
}
複製代碼

優化resolve.modules配置

resolve.modules 的默認值是['node_modules'],含義是先去當前目錄的node_modules目錄下去找咱們想找的模塊,若是沒找到就去上一級目錄 ../node_modules 中找,再沒有就去 ../../node_modules中找,以此類推。當安裝的第三方模塊都放在項目根目錄的 node_modules 目錄下時,就沒有必要按照默認的方式去一層層地尋找,能夠指明存放第三方模塊的絕對路徑,以減小尋找。github

module.exports = {
  resolve: {
    modules: [path.resolve( __dirname,'node modules')]  
  }    
}
複製代碼

優化resolve.mainFields配置

在安裝的第三方模塊中都會有一個package.json文件,用於描述這個模塊的屬性,其中能夠存在多個字段描述入口文件,緣由是某些模塊能夠同時用於多個環境中,針對不一樣的運行環境須要使用不一樣的代碼。 segmentfault.com/a/119000001…web

優化resolve.extensions配置

在導入語句沒帶文件後綴時,Webpack會自動帶上後綴去嘗試詢問文件是否存在。若是這個列表越長,或者正確的後綴越日後,就會形成嘗試的次數越多,因此resolve.extensions的配置也會影響到構建的性能在配置resolve.extensions時須要遵照如下幾點,以作到儘量地優化構建性能。

  • 後綴列表儘量小
  • 頻率出現高的文件後綴優先放前面
  • 源碼中寫導入語句時,儘量帶上後綴
{
    extensions: ['.js'],
  },
複製代碼

2. 構建體積優化

  • 提取公共代碼、分割代碼、按需加載、懶加載
  • tree-shaking
  • scope-hoisting
  • 刪除無用的css
  • 動態polyfill
  • 代碼壓縮,開啓Gzip壓縮

體積分析 webpack-bundle-analyzer

能夠分析依賴的第三方模塊的大小、業務裏面組件的代碼大小

const BoundAnalysisPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  plugins: [
    new BoundAnalysisPlugin(),
  ]
}
複製代碼

2.1 提取公共代碼、分割代碼、按需加載、懶加載

// 組件按需加載
import {Button} from 'element-ui';

// 模塊按需加載
import {cloneDeep} from 'lodash-es';

// Vue 路由懶加載
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
複製代碼
optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 30000,
      minRemainingSize: 0,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 6,
      maxInitialRequests: 4,
      automaticNameDelimiter: '~',
      automaticNameMaxLength: 30,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
複製代碼

2.2 tree-shaking

1個模塊可能有多個方法,只要其中的某個方法使用到了,則整個文件都會被打到 bundle 裏面去,tree shaking 就是隻把用到的方法打入 bundle ,沒用到的方法會在 uglify 階段被擦除掉。 注意事項:

  • mode: production 默認開啓 babel設置 modules:false,
  • 必須使用ES6的語法

2.3 刪除無用的css

使用 purgecss-webpack-plugin 配合 mini-css-extract-plugin 使用

const config = {
  module:{
      rules: [
        {
          test: '/.scss$/',
          use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader',
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [
                // 自動擴展css
                require('autoprefixer')(),
              ],
            },
          },
        }
      ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: getAssetPath(
        `css/[name]_[contenthash:8]'}.css`, ), }), new PurgecssPlugin({ paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }), }), ] } 複製代碼

2.4 動態polyfill

方案 優勢 缺點
babel-polyfill 大而全 體積太大
@babel/plugin-transform-runtime 只polyfill用到的方法和類,體積較小 不能polyfill原型上的方法
polyfill-service 只返回客戶須要的polyfill 國內奇葩瀏覽器

2.5 Scope-Hoisting

ModuleConcatenationPlugin 如今webpack4在mode 不等於none都支持

2.6 圖片壓縮,代碼壓縮,還能夠開啓Gzip壓縮

使用 image-webpack-loader進行圖片壓縮

3. 加載優化

3.1 預加載

使用 @vue/preload-webpack-plugin 實現代碼預加載

const config = {
  plugins: [
    new PreloadPlugin({
      rel: 'preload',
      include: 'initial',
      fileBlacklist: [/\.map$/, /hot-update\.js$/],
    }),
    new PreloadPlugin({
      rel: 'prefetch',
      include: 'asyncChunks',
    }),
  ]
}
複製代碼

3.2 使用文件指紋,瀏覽器緩存

  • Hash:和整個項⽬的構建相關,只要項⽬文件有修改,整個項⽬構建的 hash 值就會更改
  • Chunkhash:和 webpack 打包的 chunk 有關,不一樣的 entry 會生成不一樣的 chunkhash 值
  • Contenthash:根據文件內容來定義 hash ,文件內容不不變,則 contenthash 不不變
// js
{
  output: {
    filename: '[name]_[chunkhash:8].js'  
  }    
}
// css
// MiniCssExtractPlugin
{
  plugins:[
    new MiniCssExtractPlugin({
      filename: '[name]_[contenthash:8].css'    
    })
  ]
}

// 圖片 
// file-loader 使用hash(這裏的hash是根據內容生成的,默認是md5)
{
  module:{
    rules:[
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [{
          loader: 'file-loader’, options: { name: 'img/[name][hash:8].[ext] ' } }] } ] } } 複製代碼

。。。未完待續

相關文章
相關標籤/搜索