前端性能优化实战篇

发布时间:2023-06-23 08:30

某项目的前端性能优化实践

项目背景

项目生产环境前端静态资源总体积过大,图片资源未压缩,请求接口 30+ 个,整体平均加载耗时 20S,用户体验比较差劲。

优化目标

减少静态资源的体积,接口数量和加载的时长:

  1. 静态资源总体积 <= 5M
  2. 接口请求 <= 10
  3. 整体平均加载耗时 <= 2S

优化方案

1. 打包分离

一般来说,我们的代码都可以至少简单区分成业务代码第三方库。如果不做处理,每次构建时都需要把所有的代码重新构建一次,耗费大量的时间。然后大部分情况下,很多第三方库的代码并不会发生变更(除非是版本升级),这时就可以用到dll:把复用性较高的第三方模块打包到动态链接库中,在不升级这些库的情况下,动态库不需要重新打包,每次构建只重新打包业务代码

DLL(Dynamic Link Library)文件为动态链接库文件,在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。

使用DLLPlugin打包需要分离到动态库的模块,DllPluginwebpack内置的插件,不需要额外安装,直接配置webpack.dll.config.js文件:

module.exports = {=
  entry: {
    vue: ['vue', 'vue-router', 'vuex']
  },
  output: {
    filename: '[name].dll.js',
    path: resolve('dist/dll'),
    library: '[name]_dll_[hash]'
  },
  plugins: [
  // 接入 DllPlugin
    new webpack.DllPlugin({
      name: '[name]_dll_[hash]',
      path: path.join(__dirname, 'dist/dll', '[name].manifest.json')
    }),
  ]
}

在主构建配置文件使用动态库文件,在webpack.config中使用dll要用到DllReferencePlugin,这个插件通过引用 dll 的 manifest 文件来把依赖的名称映射到模块的 id 上:

 new webpack.DllReferencePlugin({
 	context: __dirname,
 	manifest: require('./dist/dll/vue.manifest.json')
 }),

在入口文件引入dll文件,生成的dll暴露出的是全局函数,因此还需要在入口文件里面引入对应的dll文件。


  

2. 公用组件 CDN 化

打包构建过程中通过 HtmlWebpckPlugin 动态插入公用组件 CDN 链接,减少打包体积,加快渲染速度。

html-webpack-plugin是 webpack 的一个插件,可以动态的创建和编辑 html 内容,在 html 中使用 esj语法可以读取到配置中的参数,简化了html文件的构建。

webpack 项目中,所引入的第三方资源会被统一打包进 vender 文件中,我们通过 webpack 的 externals 属性可以设置打包时排除该模块。

在 webpack.config.js 中配置忽略打包资源:

externals: {
	'vue': 'Vue',
	'vue-router': 'VueRouter',
	'vuex': 'Vuex',
	'axios': 'axios'
}

创建 CDN 资源配置文件 cdn.config.js:

export default {
	js: [
		'https://cdn.jsdelivr.net/npm/vue@2.6.11',
    'https://cdn.jsdelivr.net/npm/vue-router@3.2.0/dist/vue-router.min.js',
    'https://cdn.jsdelivr.net/npm/vuex@3.4.0/dist/vuex.min.js',
    'https://cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js'
	],
	css: []
}

根据构建环境 NODE_ENV 将 CDN 地址传递给 HtmlWebpackPlugin

if (process.env.NODE_ENV === 'production') {
	htmlWebpackConfig.cdns = CDNConfig;
}
new HtmlWebpackPlugin(htmlWebpackConfig)

重写 index.html 模版文件:



  
    
    <%= htmlWebpackPlugin.options.title %>

    
    <% for (var i in htmlWebpackPlugin.options.cdns && htmlWebpackPlugin.options.cdns.css) { %>
      
    <% } %>
    

    
    <% for (var i in htmlWebpackPlugin.options.cdns && htmlWebpackPlugin.options.cdns.js) { %>
      
    <% } %>
    
  
  
    
    

3. 启用预加载

 是一种 resource hint,用来指定页面加载后很快会被用到的资源,所以在页面加载的过程中,我们希望在浏览器开始主体渲染之前尽早 preload。是一种 resource hint,用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。

结合上一步优化,在 index.html中动态指定预加载文件:



  
    
    <%= htmlWebpackPlugin.options.title %>

    
    <% for (var i in htmlWebpackPlugin.options.cdns && htmlWebpackPlugin.options.cdns.css) { %>
      
      
    <% } %>
    

    
    <% for (var i in htmlWebpackPlugin.options.cdns && htmlWebpackPlugin.options.cdns.js) { %>
      
      
    <% } %>
    
  
  
    
    

4. DNS 预解析

在浏览器中输入一个域名,回车后,DNS(域名系统)会先将域名解析成对应的IP地址,然后根据IP地址去找到相应的网址。这样就完成了一个DNS查找,这个查找过程当然是要消耗时间的,大约消耗20毫秒,在这个查找过程中,我们的浏览器什么都不会做,保持一片空白。如果这样的查找很多,那么我们的网页性能将会受到很大影响,这就需要用到DNS缓存和预解析。

dns-prefetch 是尝试在请求资源之前解析域名。这可能是后面要加载的文件,也可能是用户尝试打开的链接目标。将需要预解析的 DNS 通过 link 标签进行设置:


5. 图片优化

图片延迟加载,在页面中,先不给图片设置路径,只有当图片出现在浏览器的可视区域时,才去加载真正的图片。首先可以将图片这样设置,在页面不可见时图片不会加载:


等页面可见时,使用 JS 加载图片:

const img = document.querySelector('img')
img.src = img.dataset.src

响应式图片,浏览器能够根据屏幕大小自动加载合适的图片。通过 picture 实现:


    
    
    

降低图片的质量,通过 image-webpack-loader 进行压缩:

{
  test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
  use:[
    {
    loader: 'url-loader',
    options: {
      limit: 10000, 
      name: utils.assetsPath('img/[name].[hash:7].[ext]')
      }
    },
    {
      loader: 'image-webpack-loader',
      options: {
        bypassOnDebug: true,
      }
    }
  ]
}

使用 webp 格式的图片,WebP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一。

在 Webpack 项目中可以使用 webp-loader 对本地图片进行转换:

loaders: [
  {
    test: /\.(jpe?g|png)$/i,
    loaders: [
      'file-loader',
      'webp-loader?{quality: 13}'
    ]
  }
]

使用字体图标 iconfont 代替图片图标,字体图标就是将图标制作成一个字体,使用时就跟字体一样,可以设置属性,例如 font-size、color 等等,非常方便。并且字体图标是矢量图,不会失真。还有一个优点是生成的文件特别小。

6. 设置缓存

避免重复传输相同的数据,节省网络带宽,加速资源获取。方法是添加Expire/Cache-Control头。通过使用Expires或者Cache-Control就可以使请求的内容具有缓存性,它避免了接下来的页面访问中不必要的HTTP请求。Expires头的内容是一个时间值,值就是资源在本地的过期时间。在当前时间还没有超过缓存资源的过期时间时,就直接使用这一缓存的资源,不会发送HTTP请求。Cache-Control的作用也是类似的,只不过它的值是一个表示距离缓存过期的一个秒数时间。

Nginx 配置如下:

server {
	add_header Cache-Control private;
	location ~ .*\.(js|css)$ {
    expires: 10d;
  }
}

7. 启用 Gzip 压缩

通过减小HTTP响应的大小也可以节省HTTP响应时间。Gzip可以压缩所有可能的文件类型,是减少文件体积、增加用户体验的简单方法。从HTTP/1.1开始,web客户端都默认支持HTTP请求中有Accept-Encoding文件头的压缩格式:Accept-Encoding: gzip。如果web服务器在请求的文件头中检测到上面的代码,就会以客户端列出的方式压缩响应内容。Web服务器把压缩方式通过响应文件头中的Content-Encoding来返回给浏览器。

server {
	add_header Cache-Control private;
	location ~ .*\.(js|css)$ {
    gzip on;
    gzip_http_version 1.1;
    gzip_comp_level 3;
    gzip_types text/plain application/json application/x-javascript application/css text/javascript;
  }
}

8. 接口合并

一个交互需要请求多个并行或串行接口实属正常,前端使用3g/4g等弱网络也着实是不可抗因素,所以最好的办法就是通过接口合并的方式来提高接口访问速度。

9. 骨架屏

为真实的组件做一个在尺寸、样式上非常接近真实组件的组件。提升用户感知体验,保证切换一致性。在真实组件开始渲染的时候,需要一定的时间和空间,时间指的是真实组件从创建到渲染的时间,包括请求接口、请求资源和渲染的时间,空间指的是页面布局中需要给真实组件留出刚好的位置,避免产生抖动。

10. 代码优化

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。vue-router 配置路由,可以实现按需加载。

{
  path: '/ceshi',
  name: 'ceshi',
  component: resolve => require(['../components/ceshi'], resolve)
}

使用 requestAnimationFrame 来实现视觉变化,大多数设备屏幕刷新率为 60 次/秒,也就是说每一帧的平均时间为 16.66 毫秒。在使用 JavaScript 实现动画效果的时候,最好的情况就是每次代码都是在帧的开头开始执行。而保证 JavaScript 在帧开始时运行的唯一方式是使用 requestAnimationFrame。 如果采取 setTimeout 或 setInterval 来实现动画的话,回调函数将在帧中的某个时点运行,可能刚好在末尾,而这可能经常会使我们丢失帧,导致卡顿。

/**  
 * If run as a requestAnimationFrame callback, this  
 * will be run at the start of the frame.  
 */  
function updateScreen(time) {  
  // Make visual updates here.  
}  
requestAnimationFrame(updateScreen); 

日常开发过程中,滚动事件做复杂计算频繁调用回调函数很可能会造成页面的卡顿,这时候我们更希望把多次计算合并成一次,只操作一个精确点,JS把这种方式称为debounce(防抖)和throttle(节流)。推荐使用 lodash 库:

// 避免窗口在变动时出现昂贵的计算开销。
$(window).on('resize', _.debounce(calculateLayout, 300));

// 避免在滚动时过分的更新定位
$(window).on('scroll', _.throttle(updatePosition, 100));

优化效果

优化项目 优化前 优化后 备注
静态文件总个数 150 10 减少 93.3%
静态文件总体积 12.4 MB 1.50 MB 减少 87.8%
模拟 Fast 3G 平均加载速度 8.8s 首次加载:3.8s,二次加载1.64s 减少 63.2%
图片总体积 140K 90K 减少 35.7%

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号