美文网首页
webpack插件在优化LCP指标上的运用

webpack插件在优化LCP指标上的运用

作者: 0月 | 来源:发表于2023-04-27 10:41 被阅读0次

    前言

    最近采用webpack5配置了一个项目脚手架。当项目开发完成后,采用lighthouse进行性能测试时发现有一张图片影响LCP评分,所以需要解决这个问题。

    LCP

    最大内容绘制 (LCP) 是核心 Web 指标中的一项指标,用于测量可视区域中最大内容元素变为可见的时间点。该项指标可用于确定页面主要内容在屏幕上完成渲染的时间点。

    本项目经过实测发现以下问题:


    /assets/top-8face.png

    所以这个图片影响LCP需要优化,怎么做?这里给出了preload的建议,只要在html head标签内加上一句即可

      <link href="assets/top-8face.png" rel="preload" as="image">
    

    至此完成了该项优化。

    但是,事情可能没有那么简单,万一图片换了,top-[hash]变了呢?或者随着项目迭代,删除了这张图片,然后这个preload link很可能继续存在html里面,就会显得多余。万一影响LCP的不是图片,可以是p标签包裹的一大段文案或者是某个video、svg内嵌的image呢?万一项目是动态配置的多入口MPA呢?

    所以此时虽然可以手动写上一句就解决当前的问题,但是为了可维护性,从工程化角度来看,更应该是用工程化的手段来解决此类问题。

    先看看有没有现成的preload插件,找到一个vue官方维护的 @vue/preload-webpack-plugin, 但是这个插件默认把所有输出的资源都生成preload link,在输出资源比较多且没有开启http2时候,这其实是一种反向优化的手段。经过尝试配置和阅读它的源码,发现并不能满足我的需求,我只要一个preload link就行了,所以自己写一个吧。

    webpack插件实现新增preload link插入到html文件

    梳理一下本插件要做的事情:
    我们希望拿到名叫top-[hash].png的图片,有则组装一个preload link插入到html里面,没有则啥也不做

    在我的另外一篇文章【webpack进阶系列】plugin的原理探究 介绍过了插件原理与应用,参考官网示例现在我们可以直接在processAssets钩子里面配合stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE拿到所有资源assets,找出LCP图片和html文件,组装preload link插入到html文件,然后生成新的html。
    代码如下:

    preload-lcp-img-webpack-plugin.js

    /**
     * 根据lighthouse的建议,有一张图片:top-[hash].png影响LCP, 所以需要预加载这张影响LCP的图片,此插件仅适用此场景
     */
    const defaultOptions = {
      htmlFile: 'index.html'
    }
    class PreloadLCPImgWebpackPlugin {
      constructor(options = {}) {
        this.options = { ...defaultOptions, ...options }
      }
    
      apply(compiler) {
        const pluginName = 'PreloadLCPImgWebpackPlugin'
        const { webpack } = compiler
        const { Compilation } = webpack
        const { RawSource } = webpack.sources
    
        compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
          // 绑定到资源处理流水线(assets processing pipeline)
          compilation.hooks.processAssets.tap(
            {
              name: pluginName,
               // 用某个靠后的资源处理阶段, 确保所有资源已被插件添加到 compilation
              stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
            },
            (assets) => {
              const lcpImg = Object.keys(assets)
                .filter(filename => /.png$/i.test(filename)) // 图片
                .find((filename) => {
                  const arr = filename.split('/')
                  const name = arr[arr.length - 1]
                  return /^top/.test(name) // top-[hash].png就是影响LCP的图片
                })
    
              if (!lcpImg) return
    
              const webpackPublicPath = compilation.outputOptions.publicPath
              // webpack 5 set publicPath default value 'auto'
              const publicPath = webpackPublicPath.trim() !== '' && webpackPublicPath !== 'auto' ? webpackPublicPath : ''
    
              const html = assets[this.options.htmlFile]._value
              const preloadLink = `<link href="${publicPath}${lcpImg}" rel="preload" as="image">`
              const newHtml = html.replace('</head>', `${preloadLink}</head>`)
    
              delete assets[this.options.htmlFile] // 删除,下面再重新生成
              compilation.emitAsset(
                this.options.htmlFile,
                new RawSource(newHtml)
              )
            }
          )
        })
      }
    }
    
    module.exports = PreloadLCPImgWebpackPlugin
    

    在webpack.config.js中production环境使用插件

    const PreloadLCPImgWebpackPlugin = require('./webpack-plugins/preload-lcp-img-webpack-plugin')
    // ...省略其他代码
    if(isProduction) {
      config.plugins.push(
        new PreloadLCPImgWebpackPlugin(), // 预加载LCP图片
      )
    }
    

    效果


    优化后的LCP.png 没有LCP建议了.png

    总结

    webpack插件可以通过注册webpack的compiler、compilation实例的hook在特定的时机去【增加、删除、改变】内部的资源信息,最终可能影响构建输出。要写插件不难,难的是webapck里面几百个hook该用哪些hook,什么时机去做,能做什么事情,这个过程只能多参考其他插件的写法或者阅读源码。

    相关文章

      网友评论

          本文标题:webpack插件在优化LCP指标上的运用

          本文链接:https://www.haomeiwen.com/subject/ujdmjdtx.html