实战前端项目优化

1,410 阅读20分钟

总结整理前端工作中涉及到的优化方案

一、懒加载

1.懒加载的概念

懒加载也称为延迟加载、按需加载,指的是在一个网页中延迟加载大量的图片数据,是一种比较好的网页性能优化方式。当我们一个页面中用到了大量的图片,如果在首次加载的时候一次性全部加载,在网络情况不佳时,造成图片加载缓慢或者加载失败,是否造成不良好的用户体验。

使用图片懒加载就可以解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,随着滚动屏幕到可视区域,图片才会加载。这样使得网页的加载速度快了,服务器的负载也减轻了。

2.懒加载的特点
  • 减少无效资源加载: 使用懒加载明显减少了服务器的压力和流量,同时也减小了浏览器的负担。
  • 提升用户体验: 如果同时加载较多图片,可能需要等待的时间较长,这样影响了用户体验,而懒加载就可以大大提升用户体验。
  • 防止加载过多图片而影响其他资源文件的加载: 会影响网站的正常使用。
3.懒加载的实现原理

图片的加载是由src属性引发的,当对src属性赋值时,浏览器就会发送一个http请求图片资源。根据这个原理,我们可以使用HTML5的data-xxx属性来存储图片的路径,在需要加载图片的时候,将data-xxx中图片的路径赋值给src,这样就实现了图片的按需加载,即懒加载。

注意:data-xxx中的xxx可以自定义,这里我们用data-src来定义

懒加载的实现重点在于确定用户需要加载哪些图片,在浏览器中,可视区域内的资源就是用户需要的资源。所以当图片出现在可视区域时,获取图片的真实地址并赋值给图片即可。

下面使用两种方式来实现懒加载:

1. 使用原生JavaScript实现懒加载:

[ 知识点: ]

(1) window.innerHeight是浏览器可视区的高度。

(2) document.body.scrollTop || document.documentElement.scrollTop 是浏览器滚动过的距离

(3) imgs.offsetTop 是元素顶部距离文档顶部的高度(包括滚动条的距离)

[ 图示: ]

懒加载.png [ code: ]

<div class="container">
    <img src="loading.gif" data-src="pic.png">
    <img src="loading.gif" data-src="pic.png">
    <img src="loading.gif" data-src="pic.png">
    <img src="loading.gif" data-src="pic.png">
    <img src="loading.gif" data-src="pic.png">
    <img src="loading.gif" data-src="pic.png">
</div>
<script setup>
const imgs = document.querySelectorAll('img');
function lozyLoad(){ 
    const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
    const winHeight= window.innerHeight;
    for(let i=0;i < imgs.length;i++){
        if(imgs[i].offsetTop < scrollTop + winHeight ){
            imgs[i].src = imgs[i].getAttribute('data-src');
        }
    }
} 
window.onscroll = lozyLoad;
</script>

2. 使用IntersectionObserver属性实现:

[ 触发条件: ]

  1. 每当目标元素与设备视图或者其他元素发生交集的时候执行。设备视窗或者其他元素我们称它为根元素或根(root)。
  2. Observer第一次监听目标元素的时候。 [ 使用条件: ]
  • 图片懒加载——当图片滚动到可见时才进行加载。
  • 内容无限滚动——也就是用户滚动到接近内容底部时直接加载更多,而不需用户操作翻页,给用户一种网页可以无限滚动的错觉。
  • 检测广告的曝光情况——为了计算广告收益,需要知道广告元素的曝光情况。
  • 在用户看见某个区域时执行任务或播放动画。

[ code: ]

<div class="container">
  <img style="height:1080px;" data-src="./png1.png" />
  <img style="height:1080px;" data-src="./png2.png" />
  <img style="height:1080px;" data-src="./png3.png" />
  <img style="height:1080px;" data-src="./png4.png" />
  <img style="height:1080px;" data-src="./png5.png" />
  <img style="height:1080px;" data-src="./png6.png" />
</div>
<script>
  const imgs = document.querySelectorAll("img");
  const observe = new IntersectionObserver((observes) => {
    observes.forEach((ele) => {
      //如果isIntersecting为true,则说明元素进入可视化区域
      if (ele.isIntersecting) {
        //do something...
        ele.target.src = ele.target.dataset.src;
        observe.unobserve(ele.target); //取消监听
      }
    });
  });
  //创建observe后需要给定一个目标元素进行观察:
  imgs.forEach((el) => {
    observe.observe(el);
  });
</script>
4.懒加载与预加载的区别

两种方式都是提高页面性能的方式,两者主要区别是:一个为提前加载,一个为迟缓加载甚至不加载。懒加载对服务器有一定的缓解压力作用,预加载则会增加服务器压力。

  • 懒加载: 当用户需要访问时,再去加载,这样可以提高网站的首屏加载速度,提升用户体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的网站。
  • 预加载: 将所需的资源提前请求加载到本地,这样在后面需要用到时可以直接从缓存中获取。通过预加载能够减少用户的等待时间,提高用户体验。它适用于需要加载比较大的视频或图片场景。

二、回流与重绘

1.回流与重绘的概念及触发条件

(1)回流

当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流
下面这些操作都会导致回流

  • 页面的首次渲染
  • 浏览器的窗口大小发生变化
  • 元素的内容发生变化
  • 元素的尺寸或者位置发生变化
  • 元素的字体大小发生变化
  • 激活CSS伪类
  • 查询某些属性或者调用某些方法
  • 添加或者删除可见的DOM元素 在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的DOM元素重新排列,它的影响范围有两种:
  • 全局范围:从根节点开始,对整个渲染树进行重新布局
  • 局部范围:对渲染树的某部分或者一个渲染对象节点进行重新布局
(2)重绘

当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘。 下面这些操作会导致重绘

  • color、background相关属性:background-color、background-image等
  • outline相关属性:outline-color、outline-width、text-decoration
  • border-radius、visivility、box-shadow 注意:当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。

2.如何避免回流与重绘?

减少回流与重绘的措施

  • 操作DOM时,尽量在低层级的DOM节点进行操作
  • 不要使用table布局,一个小的改动可能会使整个table进行重新布局
  • 不要使用CSS的表达式
  • 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式
  • 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
  • 避免频繁操作DOM,可以创建一个文档片段documentFragment,在它上面应用所有DOM操作,最后一次性添加到文档中
  • 将元素先设置display:none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘
  • 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制 浏览器针对页面的回流与重绘,进行了自身的优化————渲染队列
浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。

上面,将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,本应该多次触发回流,变成了只触发一次回流。

3.如何优化动画?

对于如何优化动画,我们知道,一般情况下,动画需要频繁的操作DOM,就会导致页面性能问题,我们可以将动画的position属性设置为absolute或者fixed,将动画脱离文档流,这样他的回流就不会影响到页面。

4.documentFragment是什么?用它跟直接操作DOM的区别是什么?

MDN中对documentFragment的解释:

DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的Document使用,就像标准的document一样,存储由节点(nodes)组成的文档结构。与document相比,最大的区别是DocumentFragment不是真实DOM树的一部分,它的变化不会触发DOM树的重新渲染,且不会导致性能等问题。

当我们把一个DocumentFragment节点插入文档树时,插入的不是DocumentFragment自身,而是它的所有子孙节点。在频繁的DOM操作时就,我们可以将DOM元素插入DocumentFragment,之后一次性的将所有的子孙节点插入文档中。和直接操作DOM相比,将DocumentFragment节点插入DOM树时,不会触发页面的重绘,这样就大大提高了页面的性能。

三、节流与防抖

1.对节流防抖的理解

  • 防抖:在持续触发函数时,间隔一定时间未触发才会触发一次。
  • 节流:在持续触发函数时,间隔一定时间触发一次。

防抖函数的应用场景:

  • 按钮提交场景:防止多次提交,只执行最后一次。
  • 输入查询场景:防止连续输入请求,只执行连续输入的最后一次。

节流函数使用场景:

  • 拖拽场景:固定时间只执行一次,防止超高频次触发位置变动。
  • 滚动场景:监控浏览器resize。
  • 动画场景:避免短时间内多次触发动画引起性能问题。

2.实现节流函数和防抖函数

防抖:

function debounce(fn, wait) {
  var timer = null;

  return function() {
    var context = this,
      args = [...arguments];

    // 如果此时存在定时器的话,则取消之前的定时器重新记时
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }

    // 设置定时器,使事件间隔指定事件后执行
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, wait);
  };
}

节流:

// 时间戳版
function throttle(fn, delay) {
  var preTime = Date.now();

  return function() {
    var context = this,
      args = [...arguments],
      nowTime = Date.now();

    // 如果两次时间间隔超过了指定时间,则执行函数。
    if (nowTime - preTime >= delay) {
      preTime = Date.now();
      return fn.apply(context, args);
    }
  };
}

// 定时器版
function throttle (fun, wait){
  let timeout = null
  return function(){
    let context = this
    let args = [...arguments]
    if(!timeout){
      timeout = setTimeout(() => {
        fun.apply(context, args)
        timeout = null 
      }, wait)
    }
  }
}

四、图片优化

1.平时项目中常用的图片优化技巧

  1. 不用图片(hh,不用图片就不用优化啦)。很多时候用到的一些修饰类图片,能用CSS解决就不用图片!!!
  2. 对于移动端来说,屏幕宽度小,完全没有必要请求原图浪费宽带。图片都用CDN(CDN是将源站内容分发至最接近用户的节点,使用户可就近取得所需内容,提高用户访问的响应速度和成功率。解决因分布、带宽、服务器性能带来的访问延迟问题,适用于站点加速、点播、直播等场景。)加载,可以计算出适配屏幕的宽度,然后去请求相应裁好的图片。
  3. 小图使用base64格式。
  4. 多个图标文件可以合并为雪碧图。
  5. 选择正确的图片格式。
  • 对于能够显示WebP格式的浏览器尽量使用WebP格式。因为WebP格式具有极好的图像数据压缩算法,能带来更小的图片体积以及肉眼无差识别的质量,缺点就是兼容性。
  • 小图使用PNG,其实对于大部分图标这种图片,完全可以使用svg代替。
  • 照片使用JPEG。

2.常见图片格式及使用场景

索引色:为节约存储,把图像中使用的颜色与一个颜色表对应起来,索引色常使用16色、32色、64色、128色或256色等,但最多不得超过256色。
直接色:每个像素值分成R,G,B分量,每个分量作为单独的索引值对它做变换。也就是通过相应的彩色变换表找出基色强度,用变换后得到的R,G,B强度值产生的彩色称为直接色。它的特点是对每个基色进行变换。

  1. BMP:是无损的、既支持索引色也支持直接色的点阵图。这种图片格式几乎没有对数据进行压缩,所以BMP格式相对比较大。
  2. GIF:是无损的、采用索引色的点阵图。采用LZW压缩算法进行编码。文件小,是GIF格式的优点,同时,GIF格式还具有支持动画以及透明的优点。但是GIF格式仅支持8bit的索引色,所以GIF格式适用于对色彩要求不高同时需要文件体积较小的场景。
  3. JPEG:是有损的、采用直接色的点阵图。JPEG的图片的优点是采用了直接色,得益于更丰富的色彩,JPEG非常适合用来存储图片,与GIF相比,JPEG不适合用来存储企业Logo、线框类的图。因为有损压缩会导致图片模糊,而直接色的选用,又会导致图片文件比GIF更大。
  4. PNG-8:是无损的、使用索引色的点阵图。PNG是一种比较新的图片格式,PNG-8是非常好的GIF格式替代者,在可能的情况下,应该尽可能的使用PNG-8而不是GIF,因为在相同的图片效果下,PNG-8具有更小的文件体积。除此之外,PNG-8还支持透明度的调节,而GIF并不支持。除非需要动画的支持,否则没有理由使用GIF而不是PNG-8。
  5. PNG-24:是无损的、使用直接色的点阵图。PNG-24的优点在于它压缩了图片的数据,使得同样效果的图片,PNG-24格式的文件大小要比BMP小得多。当然,PNG-24的图片还是要比JPEG、GIF、PNG-8。
  6. SVG:是无损的矢量图。SVG是矢量图意味着SVG图片由直线和曲线以及绘制它们的方法组成。当放大SVG图片时,看到的还是线和曲线,而不会出现像素点。这意味着SVG图片在放大时,不会失真,所以它适合用来绘制Logo、Icon等。
  7. WebP:是谷歌开发的一种新图片格式,WebP是同时支持有损和无损压缩的、使用直接色的点阵图。从名字就可以看出来它是为Web而生的,何出此言呢?就是说相同质量的图片,WebP格式具有更小的文件体积。现在网站上充满大量的图片,如果能够降低每一个图片的文件大小,那么将大大减少浏览器和服务器之间的数据传输量,进而降低访问延迟,提升访问体验。目前只有Chrome和Opera浏览器支持WebP格式,兼容性不太好。
  • 在无损压缩的情况下,相同质量的WebP图片,文件比PNG小26%。
  • 在有损压缩的情况下,具有相同图片精度的WebP图片,文件要比JPEG小25% - 34%。
  • WebP图片格式支持图片透明度,一个无损压缩的WebP图片,如果要支持透明度只需要22%的额外文件大小。

五、Webpack优化

1.如何提高Webpack的打包速度

(1) 优化Loader

对于Loader来说,影响打包效率首当其中必属Babel了。因为Babel会将代码转为字符串生成AST,然后对AST进行转变最后生成新的代码,项目越大,转换代码越多,效率越低。当然,这是可以优化的。
首先我们优化Loader的文件搜索范围

module.exports = {
  module: {
    rules: [
      {
        // js 文件才使用 babel
        test: /\.js$/,
        loader: 'babel-loader',
        // 只在 src 文件夹下查找
        include: [resolve('src')],
        // 不会去查找的路径
        exclude: /node_modules/
      }
    ]
  }
}

对于Babel来说,希望只作用在JS代码上的,然后node_modules中使用的代码都是编译过的,所以完全没有必要再去处理一遍。
当然这样做还不够,还可以将Babel编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间。

loader: 'babel-loader?cacheDirectory=true'

(2) HappyPack

受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。

HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了

module: {
  loaders: [
    {
      test: /\.js$/,
      include: [resolve('src')],
      exclude: /node_modules/,
      // id 后面的内容对应下面
      loader: 'happypack/loader?id=happybabel'
    }
  ]
},
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader?cacheDirectory'],
    // 开启 4 个线程
    threads: 4
  })
]

(3)DllPlugin

DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。DllPlugin的使用方法如下:

// 单独配置在一个文件中
const path = require('path')
const webpack = require('webpack')
module.exports = {
  entry: {
    // 想统一打包的类库
    vendor: ['vue']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    library: '[name]-[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      // name 必须和 output.library 一致
      name: '[name]-[hash]',
      // 该属性需要与 DllReferencePlugin 中一致
      context: __dirname,
      path: path.join(__dirname, 'dist', '[name]-manifest.json')
    })
  ]
}

然后需要执行这个配置文件生成依赖文件,接下来需要使用 DllReferencePlugin 将依赖文件引入项目中

// webpack.conf.js
module.exports = {
  // ...省略其他配置
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      // manifest 就是之前打包出来的 json 文件
      manifest: require('./dist/vendor-manifest.json'),
    })
  ]
}

(4)代码压缩

在 Webpack3 中,一般使用 UglifyJS 来压缩代码,但是这个是单线程运行的,为了加快效率,可以使用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS,从而提高效率。

在 Webpack4 中,不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。

(5)其他

可以通过一些小的优化点来加快打包速度

  • resolve.extensions:用来表明文件后缀列表,默认查找顺序是 ['.js', '.json'],如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面
  • resolve.alias:可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径
  • module.noParse:如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助

2.如何用webpack来优化前端性能

⽤webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。

  • 压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css
  • 利⽤CDN加速: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径
  • Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现
  • Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
  • 提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

六、vue3项目打包优化配置

在vue3的项目中,原本配置webpack目录结构不见了,取而代之的则是vue.config.js(在根目录自建!)。

1.打包后不需要生成sourcemap文件

module.exports={
    productionSourceMap:false
}

2.图片压缩

vue正常打包之后一些图片文件很大,使打包体积很大,通过image-webpack-loader插件将大的图片进行压缩从而减小打包体积

(1) 先安装依赖:npm i image-webpack-loader --save-dev (2) 在vue.config.js中写入

module.exports={
    productionSourceMap:false,
    chainWebpack:config=>{
       //=========压缩图片 start========
       config.module
       .rule('images')
       .use('image-webpack-loader')
       .loader('image-webpack-loader')
       .options({bypassOnDebug:true})
       .end()
       //=========压缩图片 end========
    }
}

3.cdn配置(可选)

(1) 在vue.config.js最上边写入

// 是否为生产环境
const isProduction = process.env.NODE_ENV !== 'development'

// 本地环境是否需要使用cdn
const devNeedCdn = false

// cdn链接
const cdn = {
    // cdn:模块名称和模块作用域命名(对应window里面挂载的变量名称)
    externals: {
        vue: 'Vue',
        vuex: 'Vuex',
        'vue-router': 'VueRouter'
    },
    // cdn的css链接
    css: [],
    // cdn的js链接
    js: [
        'https://unpkg.com/vue@next',
        'https://cdn.staticfile.org/vuex/3.1.0/vuex.min.js',
        'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js'
    ]
}

(2) 在vue.config.js module.exports chainWebpack 中写入:

// ============注入cdn start============
config.plugin('html').tap(args => {
    // 生产环境或本地需要cdn时,才注入cdn
    if (isProduction || devNeedCdn) args[0].cdn = cdn
    return args
})
// ============注入cdn start============

(3) 在vue.config.js module.exports configureWebpack中写入:

configureWebpack: config => {
    // 用cdn方式引入,则构建时要忽略相关资源
    if (isProduction || devNeedCdn) config.externals = cdn.externals
}

(4) 当前配置的vue.config.js

// 是否为生产环境
const isProduction = process.env.NODE_ENV !== 'development'

// 本地环境是否需要使用cdn
const devNeedCdn = false

// cdn链接
const cdn = {
    // cdn:模块名称和模块作用域命名(对应window里面挂载的变量名称)
    externals: {
        vue: 'Vue',
        vuex: 'Vuex',
        'vue-router': 'VueRouter'
    },
    // cdn的css链接
    css: [],
    // cdn的js链接
    js: [
        'https://unpkg.com/vue@next',
        'https://cdn.staticfile.org/vuex/3.1.0/vuex.min.js',
        'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js'
    ]
}

module.exports = {
    productionSourceMap: false,
    chainWebpack: config => {
        // ============压缩图片 start============
        config.module
            .rule('images')
            .use('image-webpack-loader')
            .loader('image-webpack-loader')
            .options({ bypassOnDebug: true })
            .end()
        // ============压缩图片 end============

        // ============注入cdn start============
        config.plugin('html').tap(args => {
            // 生产环境或本地需要cdn时,才注入cdn
            if (isProduction || devNeedCdn) args[0].cdn = cdn
            return args
        })
        // ============注入cdn start============
    },
    configureWebpack: config => {
        // 用cdn方式引入,则构建时要忽略相关资源
        if (isProduction || devNeedCdn) config.externals = cdn.externals
    }
}

(5) 在public index.html 写入

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width,initial-scale=1.0" />
        <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
        <!-- 使用CDN的CSS文件 -->
        <% for (var i in htmlWebpackPlugin.options.cdn &&
        htmlWebpackPlugin.options.cdn.css) { %>
        <link
            href="<%= htmlWebpackPlugin.options.cdn.css[i] %>"
            rel="stylesheet"
        />
        <% } %>
        <!-- 使用CDN的CSS文件 -->
        <title>cdn引入</title>
    </head>
    <body>
        <noscript>
            <strong
                >We're sorry but cli3_base doesn't work properly without
                JavaScript enabled. Please enable it to continue.</strong
            >
        </noscript>
        <div id="app"></div>

        <!-- 使用CDN的JS文件 -->
        <% for (var i in htmlWebpackPlugin.options.cdn &&
        htmlWebpackPlugin.options.cdn.js) { %>
        <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
        <% } %>
        <!-- 使用CDN的JS文件 -->

        <!-- built files will be auto injected -->
    </body>
</html>

(6) 重启项目npm run serve
(7) 在src/router.js修改

Vue.use(Router)

改为

if (!window.VueRouter) Vue.use(Router)

(8) 重新启动npm run serve即可,现在的配置是开发环境,在浏览器的Network JS里面是看不到的。若想查看,请将vue.config.js里面的

// 本地环境是否需要使用cdn
const devNeedCdn = false

改为

// 本地环境是否需要使用cdn
const devNeedCdn = true

七、代码压缩

(1) 安装依赖:npm i -D uglifyjs-webpack-plugin (2) 在vue.config.js 头部引入文件

//代码压缩
const UglifyJsPlugin=require('uglifyjs-webpack-plugin');

(3) 在vue.config.js module.exports configureWebpack 里面新增

// 生产环境相关配置
if (isProduction) {
    // 代码压缩
    config.plugins.push(
        new UglifyJsPlugin({
            uglifyOptions: {
                //生产环境自动删除console
                compress: {
                    // warnings: false, // 若打包错误,则注释这行
                    drop_debugger: true,
                    drop_console: true,
                    pure_funcs: ['console.log']
                }
            },
            sourceMap: false,
            parallel: true
        })
    )
}

八、开启Gzip

(1) 安装依赖:npm i --save-dev compression-webpack-plugin@5.0.1 (2) 在vue.config.js顶部引入依赖

//gzip压缩
const CompressionWebpackPlugin=require('compression-webpack-plugin')

(3) 在vue.config.js module.exports configureWebpack里面新增,直接放在代码压缩下边即可

// 生产环境相关配置
if (isProduction) {
    // 代码压缩
    // ..................
    // gzip压缩
    const productionGzipExtensions = ['html', 'js', 'css']
    config.plugins.push(
        new CompressionWebpackPlugin({
            filename: '[path].gz[query]',
            algorithm: 'gzip',
            test: new RegExp(
                '\.(' + productionGzipExtensions.join('|') + ')$'
            ),
            threshold: 10240, // 只有大小大于该值的资源会被处理 10240
            minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
            deleteOriginalAssets: false // 删除原文件
        })
    )
}

九、公共代码抽离

(1) 在vue.config.js module.exports configureWebpack里面新增,直接放在gzip压缩下边即可

// 公共代码抽离
config.optimization = {
    splitChunks: {
        cacheGroups: {
            vendor: {
                chunks: 'all',
                test: /node_modules/,
                name: 'vendor',
                minChunks: 1,
                maxInitialRequests: 5,
                minSize: 0,
                priority: 100
            },
            common: {
                chunks: 'all',
                test: /[\/]src[\/]js[\/]/,
                name: 'common',
                minChunks: 2,
                maxInitialRequests: 5,
                minSize: 0,
                priority: 60
            },
            styles: {
                name: 'styles',
                test: /.(sa|sc|c)ss$/,
                chunks: 'all',
                enforce: true
            },
            runtimeChunk: {
                name: 'manifest'
            }
        }
    }
}

最后,奉上完成的vue.config.js

// 代码压缩
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

// gzip压缩
const CompressionWebpackPlugin = require('compression-webpack-plugin')

// 是否为生产环境
const isProduction = process.env.NODE_ENV !== 'development'

// 本地环境是否需要使用cdn
const devNeedCdn = true

// cdn链接
const cdn = {
    // cdn:模块名称和模块作用域命名(对应window里面挂载的变量名称)
    externals: {
        vue: 'Vue',
        vuex: 'Vuex',
        'vue-router': 'VueRouter'
    },
    // cdn的css链接
    css: [],
    // cdn的js链接
    js: [
        'https://unpkg.com/vue@next',
        'https://cdn.staticfile.org/vuex/3.1.0/vuex.min.js',
        'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js'
    ]
}

module.exports = {
    productionSourceMap: false,
    chainWebpack: config => {
        // ============压缩图片 start============
        config.module
            .rule('images')
            .use('image-webpack-loader')
            .loader('image-webpack-loader')
            .options({ bypassOnDebug: true })
            .end()
        // ============压缩图片 end============

        // ============注入cdn start============
        config.plugin('html').tap(args => {
            // 生产环境或本地需要cdn时,才注入cdn
            if (isProduction || devNeedCdn) args[0].cdn = cdn
            return args
        })
        // ============注入cdn start============
    },
    configureWebpack: config => {
        // 用cdn方式引入,则构建时要忽略相关资源
        if (isProduction || devNeedCdn) config.externals = cdn.externals

        // 生产环境相关配置
        if (isProduction) {
            // 代码压缩
            config.plugins.push(
                new UglifyJsPlugin({
                    uglifyOptions: {
                        //生产环境自动删除console
                        compress: {
                            warnings: false, // 若打包错误,则注释这行
                            drop_debugger: true,
                            drop_console: true,
                            pure_funcs: ['console.log']
                        }
                    },
                    sourceMap: false,
                    parallel: true
                })
            )

            // gzip压缩
            const productionGzipExtensions = ['html', 'js', 'css']
            config.plugins.push(
                new CompressionWebpackPlugin({
                    filename: '[path].gz[query]',
                    algorithm: 'gzip',
                    test: new RegExp(
                        '\.(' + productionGzipExtensions.join('|') + ')$'
                    ),
                    threshold: 10240, // 只有大小大于该值的资源会被处理 10240
                    minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
                    deleteOriginalAssets: false // 删除原文件
                })
            )

            // 公共代码抽离
            config.optimization = {
                splitChunks: {
                    cacheGroups: {
                        vendor: {
                            chunks: 'all',
                            test: /node_modules/,
                            name: 'vendor',
                            minChunks: 1,
                            maxInitialRequests: 5,
                            minSize: 0,
                            priority: 100
                        },
                        common: {
                            chunks: 'all',
                            test: /[\/]src[\/]js[\/]/,
                            name: 'common',
                            minChunks: 2,
                            maxInitialRequests: 5,
                            minSize: 0,
                            priority: 60
                        },
                        styles: {
                            name: 'styles',
                            test: /.(sa|sc|c)ss$/,
                            chunks: 'all',
                            enforce: true
                        },
                        runtimeChunk: {
                            name: 'manifest'
                        }
                    }
                }
            }
        }
    }
}

使用gzip压缩需要配置nginx

server{
    listen 8080
    server_name localhost
    
    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 9;
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
    gzip_vary on;
    gzip_disable "MSIE [1-6]\.";
    
    location /appShare {
        client_max_body_size 10m;
        root /home/test/webIndex/appShare;
        try_filtes $uri $uri/ /yourProduct/index.html;
        index index.htm index.html;
    }
}