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] ' } }] } ] } } 复制代码

。。。未完待续

相关文章
相关标签/搜索