0


前端性能优化实战篇

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

项目背景

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

优化目标

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

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

优化方案

1. 打包分离

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

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

使用

DLLPlugin

打包需要分离到动态库的模块,

DllPlugin

webpack

内置的插件,不需要额外安装,直接配置

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文件。

<body>
  <div id="app"></div>
  <script src="./dist/dll/vue.dll.js" ></script>
</body>

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/[email protected]',
    'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.min.js',
    'https://cdn.jsdelivr.net/npm/[email protected]/dist/vuex.min.js',
    'https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js'
    ],
    css: []
}

根据构建环境

NODE_ENV

将 CDN 地址传递给

HtmlWebpackPlugin

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

重写

index.html

模版文件:

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <title><%= htmlWebpackPlugin.options.title %></title>

    <!-- 使用 CDN 的 CSS 文件 start -->
    <% for (var i in htmlWebpackPlugin.options.cdns && htmlWebpackPlugin.options.cdns.css) { %>
      <link href="<%= htmlWebpackPlugin.options.cdns.css[i] %>" rel="stylesheet">
    <% } %>
    <!-- 使用 CDN 的 CSS 文件 end -->

    <!-- 使用 CDN 的 JS 文件 -->
    <% for (var i in htmlWebpackPlugin.options.cdns && htmlWebpackPlugin.options.cdns.js) { %>
      <script src="<%= htmlWebpackPlugin.options.cdns.js[i] %>"></script>
    <% } %>
    <!-- 使用 CDN 的 JS 文件 end -->
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

3. 启用预加载

<link rel="preload">

是一种 resource hint,用来指定页面加载后很快会被用到的资源,所以在页面加载的过程中,我们希望在浏览器开始主体渲染之前尽早 preload。

<link rel="prefetch">

是一种 resource hint,用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。

结合上一步优化,在

index.html

中动态指定预加载文件:

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <title><%= htmlWebpackPlugin.options.title %></title>

    <!-- 使用 CDN 的 CSS 文件 start -->
    <% for (var i in htmlWebpackPlugin.options.cdns && htmlWebpackPlugin.options.cdns.css) { %>
      <link href="<%= htmlWebpackPlugin.options.cdns.css[i] %>" rel="preload">
      <link href="<%= htmlWebpackPlugin.options.cdns.css[i] %>" rel="stylesheet">
    <% } %>
    <!-- 使用 CDN 的 CSS 文件 end -->

    <!-- 使用 CDN 的 JS 文件 -->
    <% for (var i in htmlWebpackPlugin.options.cdns && htmlWebpackPlugin.options.cdns.js) { %>
      <link href="<%= htmlWebpackPlugin.options.cdns.js[i] %>" rel="stylesheet">
      <script src="<%= htmlWebpackPlugin.options.cdns.js[i] %>"></script>
    <% } %>
    <!-- 使用 CDN 的 JS 文件 end -->
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

4. DNS 预解析

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

dns-prefetch

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

link

标签进行设置:

<link rel="dns-prefetch" href="//host-name-to-prefetch.com">

5. 图片优化

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

<img data-src="path-to-image">

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

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

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

picture

实现:

<picture>
    <source srcset="pic-1000.jpg" media="(min-width: 801px)">
    <source srcset="pic-500.jpg" media="(max-width: 800px)">
    <img src="pic-500.jpg" alt="">
</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));

优化效果

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


本文转载自: https://blog.csdn.net/weixin_42974827/article/details/126362801
版权归原作者 KinHKin 所有, 如有侵权,请联系我们删除。

“前端性能优化实战篇”的评论:

还没有评论