阅读 124

10多种方案从webpack角度考虑前端优化

优化Loader配置

Loader配置:(1)优化正则匹配(2)通过cacheDirectory选项开启缓存(3)通过include、exclude来减少被处理的文件。

{
  // 1、如果项目源码中只有js文件,就不要写成/\.jsx?$/,以提升正则表达式的性能
  test: /\.js$/,
  // 2、babel-loader支持缓存转换出的结果,通过cacheDirectory选项开启
  loader: 'babel-loader?cacheDirectory',
  // 3、只对项目根目录下的src 目录中的文件采用 babel-loader
  include: [resolve('src')]
},
复制代码

优化resolve.modules配置

resolve.modules 用于配置Webpack去哪些目录下寻找第三方模块。resolve.modules的默认值是[node modules],含义是先去当前目录的/node modules目录下去找我们想找的模块,如果没找到,就去上一级目录../node modules中找,再没有就去../ .. /node modules中找,以此类推,这和Node.js的模块寻找机制很相似。当安装的第三方模块都放在项目根目录的./node modules目录下时,就没有必要按照默认的方式去一层层地寻找,可以指明存放第三方模块的绝对路径,以减少寻找。

resolve: {
// 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
modules: [path.resolve(__dirname,'node_modules')]
},

复制代码

优化resolve.alias配置

alias: {
  '@': resolve('src'),
},
// 通过以上的配置,引用src底下的common.js文件,就可以直接这么写
import common from '@/common.js';
复制代码

优化resolve.extensions配置

  • 后缀尝试列表要尽可能小,不要将项目中不可能存在的情况写到后缀尝试列表中。
  • 频率出现最高的文件后缀要优先放在最前面,以做到尽快退出寻找过程。
  • 在源码中写导入语句时,要尽可能带上后缀,从而可以避免寻找过程。例如在确定的情况下将 require(’. /data ’)写成require(’. /data.json ’),可以结合enforceExtension 和 enforceModuleExtension开启使用来强制开发者遵守这条优化
// 自动解析确定的扩展
    config.resolve.extensions.values(['.js', '.vue', '.json', '.less']),
复制代码

优化resolve.noParse配置

一些库如jQuery、ChartJS庞大又没有采用模块化标准,让Webpack去解析这些文件既耗时又没有意义,可以忽略这些。

// 使用正则表达式 
noParse: /jquerylchartjs/ 
// 使用函数,从 Webpack3.0.0开始支持 
noParse: (content)=> { 
// 返回true或false 
return /jquery|chartjs/.test(content); 
}
复制代码

排除打包externals

  • 排除打包依赖,防止对某个依赖项进行打包
  • 一般,一些成熟的第三方库,是不需要打包的,比如jquery,可以直接引入CDN

图片的加载和优化

sass中引入图片url,需要file-loader处理文件的导入

image-webpack-loader可以帮助我们对图片进行压缩和优化,在运行 webpack,发现会生成的图片的大小会被压缩很多(很想知道其中的原理,压缩什么样的图片)

vue.config.js

chainWebpack: config => {
    config.plugins.delete('prefetch')
    config.plugin('provide').use(webpack.ProvidePlugin, [{
      $: 'jquery',
      jquery: 'jquery',
      jQuery: 'jquery',
      'window.jQuery': 'jquery'
    }])
    config.module.rule('images')
      .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
      .use('image-webpack-loader')
      .loader('image-webpack-loader')
      .options({ bypassOnDebug: true })
  }

复制代码

webpack.config.js

  const path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            'css-loader'
          ]
        },
        {
          test: /\.(png|svg|jpg|gif|jpeg|ico)$/,
          use: [
            'file-loader',
+           {
+             loader: 'image-webpack-loader',
+             options: {
              	      mozjpeg: {
                        progressive: true,
                        quality: 65
                      },
                      optipng: {
                          enabled: false,
                      },
                      pngquant: {
                        quality: '65-90',
                        speed: 4
                        },
                      gifsicle: {
                        interlaced: false,
                      },
                      webp: {
                        quality: 75
                      }
+             }
+           },
          ]
        }
      ]
    }
  };
复制代码
config.module.rule('images')
      .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
      // .use('file-loader')
      .use('image-webpack-loader')
      .loader('image-webpack-loader')
      .options({                
        bypassOnDebug: true,
        mozjpeg: {
          progressive: true,
          quality: 65
        },
          optipng: {
            enabled: false,
        },
          // pngquant: {
          // quality: '65-90',
          // speed: 4
          // },
          pngquant: {quality: [0.65, 0.90], speed: 4},
          gifsicle: {
            interlaced: false,
          },
          webp: {
            quality: 75
          }
      });
复制代码
  • mozjpeg — Compress JPEG images
  • optipng — Compress PNG images
  • pngquant — Compress PNG images
  • svgo — Compress SVG images
  • gifsicle — Compress GIF images

And optional optimizers:

  • webp — Compress JPG & PNG images into WEBP

扩展学习:几种图片格式之间的区别

插件选用

  • file-loader和url-loader

url-loader相对于file-loader:如果图片小于配置大小,会转成base64字符串,减少图片的请求次数

  • compression-webpack-plugin:压缩文件,生产环境可采用gzip压缩JS和CSS

gzip开启:打包的时候生成 gzip 文件,部署的时候,让nginx直接读取 gzip 文件

new CompressionPlugin({    //打包文件为giz格式
    filename: '[path].gz[query]', //目标资源名称。[file] 会被替换成原资源。[path] 会被替换成原资源路径,[query] 替换成原查询字符串
    algorithm: 'gzip',//算法
    test: new RegExp('\\.(js|css)$'),
    threshold: 10240,//只处理比这个值大的资源。按字节计算
    minRatio: 0.8//只有压缩率比这个值小的资源才会被处理
})
复制代码
  • 配置全局sass无需再组件中引入即可使用
module.exports = {
    css: {
        extract: true,
        sourceMap: false,
        loaderOptions: {
          // 定义全局scss无需引入即可使用
          sass: {
            prependData: `
              @import "@/assets/css/variable.scss";
              @import "@/assets/css/common.scss";
              @import "@/assets/css/mixin.scss"; `
          }
        }
     },
}
复制代码
  • splitChunks:公共代码抽离
module.exports = {
    configureWebpack: config => { 
        config.optimization = {
          splitChunks: {
            cacheGroups: {
              vendor: {
                chunks: 'all',
                test: /node_modules/,
                name: 'vendor',
                minChunks: 1,
                maxInitialRequests: 5,
                minSize: 0,
                priority: 100
              }
            }
          }
        }
    }
}
复制代码
  • TerserPlugin:代码压缩去除console.log
module.exports = {
    configureWebpack: config => {
        if (process.env.NODE_ENV === 'production') {
            config.plugins.push(
                new TerserPlugin({
                  terserOptions: {
                    ecma: undefined,
                    warnings: false,
                    parse: {},
                    compress: {
                      drop_console: true,
                      drop_debugger: false,
                      pure_funcs: ['console.log'] // 移除console
                    }
                  }
                })
              )
        }
    }
}
复制代码

可参考 loader和plugin专题

引入cdn静态文件

const cdn = {
  // 忽略打包的第三方库
  externals: {
    vue: 'Vue',
    vuex: 'Vuex',
    'vue-router': 'VueRouter',
    axios: 'axios'
  },

  // 通过cdn方式使用
  js: [
    'https://cdn.bootcss.com/vue/2.6.11/vue.runtime.min.js',
    'https://cdn.bootcss.com/vue-router/3.1.2/vue-router.min.js',
    'https://cdn.bootcss.com/vuex/3.1.2/vuex.min.js',
    'https://cdn.bootcss.com/axios/0.19.2/axios.min.js',
    'https://cdn.bootcss.com/moment.js/2.24.0/moment.min.js',
  ],
  css: []
}
chainWebpack: config => {
    // 配置cdn引入
    config.plugin('html').tap(args => {
      args[0].cdn = cdn
      return args
    })
};
configureWebpack: config => {
     // 忽略打包配置
    config.externals = cdn.externals
}
   <body>
    <div id="app"></div>
    <!--循环引入-->
    <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
      <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>" crossorigin="anonymous"></script>
    <% } %>
  </body>
复制代码

注意:chainWebpack: (config) => {} 中配置的东西必须加分号结尾,不然会报错。

选择合适的source map模式(这是个人建议,但是不绝对)

  • 开发环境:eval-cheap-module-source-map
  • 生产环境:none | nosources-source-map

如果想在生成环境也想看到错误定位,也可以在生成环境使用 cheap-module-source-map

这样选择的原因:

  • eval的rebuild速度快,因此我们可以在本地环境中增加eval属性。
  • 使用eval-source-map会使打包后的文件太大,因此在生产环境中不会使用

合适的source map可以提高编译速度

开启缓存机制

  1. babel缓存

cacheDirectory:true 第二次构建时,会读取之前的缓存

  1. 文件资源缓存

方案:将代码文件名称,设置为哈希名称,名称发生变化时,就加载最新内容

打包后输出的文件指纹,包括hash、chunkhash、contenthash

  • hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改,并且全部文件都共用相同的hash值

构建生成的文件hash值都是一样的,所以hash计算是跟整个项目的构建相关,同一次构建过程中生成的哈希都是一样的。

缺点:不会变化的文件,hash值都会变化,没办法实现缓存

  • chunkhash:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash

可以对公共文件和程序入口文件单独打包,只要公共文件不变化,hash值就不会受到影响。

如果采用chunkhash,所以项目主入口文件index.js及其对应的依赖文件index.css由于被打包在同一个模块,所以共用相同的chunkhash,但是公共库由于是不同的模块,所以有单独的chunkhash。这样子就保证了在线上构建的时候只要文件内容没有更改就不会重复构建

缺点:index.css被index.js引用,如果index.js更改了代码,css文件就算内容没有任何改变,由于是该模块发生了改变,导致css文件会重复构建。

这个时候想到contenthash,对css文件单独使用contenthash,只要css文件内容不变, 那么不会重复构建。

  • contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变
  var extractTextPlugin = require('extract-text-webpack-plugin'),
  	path = require('path')
  
  module.exports = {
  	...
  	...
  	output:{
  		path:path.join(__dirname, '/dist/js'),
  		filename: 'bundle.[name].[chunkhash].js',
  	},
  	plugins:[
  		new extractTextPlugin('../css/bundle.[name].[contenthash].css')
  	]
  }

复制代码

比如:

js输出文件用chunkhash

output: {
        filename: '[name][chunkhash:8].js',
        path:__dirname + '/dist'
    }
复制代码

css MiniCssExtractPlugin处理用Contenthash

plugins:[
        new MiniCssExtractPlugin({
            filename: `[name][contenthash:8].css`
        })
    ]
复制代码

接合以上推导,我认为可以考虑这样的用法:得出依赖的css文件用contenthash,公共引入文件用chunkhash,尽量不用hash

扩展:文件指纹一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。但是在实际使用的时候,我们用的是自己的服务器

动态导入(懒加载和预加载)

1.懒加载:默认不加载,事件触发后才加载。

webpackChunkName: '加载名称'

  1. 预加载:先等待其他资源加载,浏览器空闲时,再加载

webpackPrefetch: true

缺点:在移动端有兼容性问题

document.getElementId('btn').onclick = function(){
    //import 启动懒加载
    //webpackChunkName: 'desc'  指定懒加载的文件名称
    //webpackPrefetch: true  启动预加载
    import(/*webpackChunkName: 'desc', webpackPrefetch: true */'test').then(()=>{
        console.lo('此处才加载了test文件,才能调用它文件中的东西')
    })
}
复制代码

treeshaking配置开启

  • 生产模式:tree-shaking会自动开启
  • 开发模式:
  1. usedExports
  2. sideEffects
optimization: {
    //标记未被使用的代码,打包后的未使用的代码会带上注释/*unused harmony export xxxx*/
    usedExports:true,
    //删除unused harmony export xxxx标记的代码
    minimize:true,
    //terser-webpack-plugin压缩插件:webpack4需要单独安装,webpack5无需安装,但需要引入
    minimizer: [new TerserPlugin()]
}
复制代码

开启副作用:

optimization: {
    sideEffects:true
}
复制代码

以上知识点包含webpack4和webpack5配置,笔者是给出方案,具体配置细节可参考官网

文章分类
前端
文章标签