【腾讯云前端性能优化大赛】两种非常规性能优化手段

2021-12-30 17:22:07 浏览数 (2)

方案一

代码语言:javascript复制
const code = `
!(function () {
  "use strict"
  const assert = $$assert
  function createElement(tag) {
    const ele = document.createElement(tag.tagName)
    if (tag.attributes) {
      Object
        .keys(tag.attributes)
        .forEach(key => ele[key] = tag.attributes[key])
    }
    if (tag.innerHTML) {
      ele.innerHTML = tag.innerHTML
    }
    ele.async = false
    ele.defer = false
    return ele
  }
  function appendTag(tag) {
    document.body.appendChild(createElement(tag))
  }
  function inlineTag(tag, innerHTML) {
    let path
    if (tag.tagName.toUpperCase() === 'LINK') {
      tag.tagName = 'style'
      path = tag.attributes.href
      delete tag.attributes
    } else {
      path = tag.attributes.src
      delete tag.attributes.src
      tag.attributes.async = false
    }
    tag.innerHTML = `// From CacheStorage: ${path} \n${innerHTML}`
    return tag
  }
  const cacheName = 'assert'
  try {
    window.caches
      .open(cacheName)
      .then((cache) => {
        return assert.map((tag) => {
          if (tag.innerHTML) {
            return tag
          }
          if (!tag.attributes.src && !tag.attributes.href) {
            throw new Error(`tag.innerHTML 与 tag.attributes.src、tag.attributes.href 必传一个, 当前为 ${JSON.stringify(tag)}`)
          }
          const assertURL = tag.tagName.toUpperCase() === 'SCRIPT' ? tag.attributes.src : tag.attributes.href
          return cache.match(assertURL)
            .then((response) => {
              if (!response) {
                return cache
                  .add(assertURL)
                  .then(
                    () => cache
                      .match(assertURL)
                      .then(response => response.text())
                      .then(innerHTML => inlineTag(tag, innerHTML))
                  )
              } else {
                console.info(`From CacheStorage: ${assertURL}`)
                return response.text().then(innerHTML => inlineTag(tag, innerHTML))
              }
            })
        })
      })
      .then((scriptList) => {
        // Ensure the order of execution
        scriptList
          .reduce(
            (a, b) =>
              a
                .then(appendTag)
                .then(() => b)
          )
          .then(appendTag)
      })
  } catch (e) {
    console.error(e)
    setTimeout(() => {
      if (window.fundebug) {
        fundebug.notifyError(e)
      }
    }, 3000)
    const fragment = document.createDocumentFragment()
    assert.map(tag => {
      const ele = createElement(tag)
      fragment.appendChild(ele)
    })
    document.body.appendChild(fragment)
  }
})();
`

const compiler = source =>
  require('@babel/core').transformSync(
    source,
    {
      presets: [ '@babel/preset-env' ]
    }
  )
    .code

class CacheStaticPlugin {
  constructor(htmlWebpackPlugin, test) {
    this.htmlWebpackPlugin = htmlWebpackPlugin
    this.test = test
  }
  isTargetTag(tag) {
    let link
    const result = (tag.tagName === 'script' || tag.tagName === 'link')
      && !tag.innerHTML
      && tag.attributes
      && (link = (tag.attributes.src || tag.attributes.href))
      && (this.test instanceof RegExp ? this.test.test(link) : this.test(link))
    if (!result) {
    }
    return !!result
  }
  injectCacheStaticCode(scriptList) {
    return compiler(code.replace('$$assert', JSON.stringify(scriptList)))
  }
  apply(compiler) {
    compiler.hooks.compilation.tap('StaticCachePlugin', compilation => {
      const hooks = this.htmlWebpackPlugin.getHooks(compilation)
      hooks.alterAssetTagGroups.tap('StaticCachePlugin', assets => {
        assets.bodyTags = [
          ...assets.bodyTags.filter(tag => !this.isTargetTag(tag)),
          {
            tagName: 'script',
            innerHTML: this.injectCacheStaticCode(assets.bodyTags.filter(this.isTargetTag.bind(this))),
            closeTag: true
          }
        ]
      })
    })
  }
}

module.exports = CacheStaticPlugin

这是一个 Webpack 插件。使用时,通过传入特定的 正则表达式,筛选出需要缓存的静态 JS、CSS 文件,在 HTML 页面中注入一段代码。

当浏览器运行到这段代码时,带有特定标识符的 js、css 文件通过 cache.add() API 下载,并储存到 CacheStorage 中,接着把下载到的代码通过 script 标签注入到 HTML 中即可(注入时需考虑顺序问题)

优化思路

方案二

代码语言:javascript复制
// cache
const OFFLINE_SUFFIX = '?offline';

let CURRENT_CACHES = {
  offline: 'offline'
};

const cacheFiles = [
  '/?offline'
];

self.addEventListener('install', function(event) {
  event.waitUntil(self.skipWaiting());
  event.waitUntil(
    caches.open(CURRENT_CACHES.offline).then(cache => {
      return cache.addAll(cacheFiles);
    })
  );
});

const matchCache = async function(event, caches) {
  const res = await Promise.all([
    caches.match(event.request.url   OFFLINE_SUFFIX),
    caches.match(event.request.url)
  ]);
  if (res.some(e => e)) {
    return res.filter(e => {
      if (e) return e;
    })[0];
  } else {
    return new Response();
  }
};

const matchOfflineUrl = function(origin, event) {
  if (event.request.method !== 'GET') {
    return false;
  }
  return (
    cacheFiles.some(url => origin   url === event.request.url) ||
    cacheFiles.some(url => origin   url === event.request.url   OFFLINE_SUFFIX)
  );
};

self.addEventListener('fetch', function(event) {
  const origin = location.origin;
  if (matchOfflineUrl(origin, event)) {
    event.respondWith(
      fetch(event.request.url)
        .then(response => {
          if (!response.ok) {
            return caches.open(CURRENT_CACHES.offline).then(cache => {
              event.request.url === origin   '/' && cache.add(cacheFiles[0]);
              cache.put(event.request, response.clone());
              return response;
            });
          } else {
            return matchCache(event, caches).then(r => r);
          }
        })
        .catch(err => {
          console.error(err);
          return matchCache(event, caches).then(r => r);
        })
    );
  }
});

此方案为使用 Service Worker 技术中对 fetch 方法的监听,当 fetch 请求失败时,自动使用 CacheStorage 中的缓存进行返回

当用户再次进行联网时,更新缓存中储存的信息。

该方案与 Google Workbox 中某种策略方案类似

如果想了解其他前端性能优化方案,可以参考我另一篇文章:https://mp.weixin.qq.com/s/uly9sDgcUnuHdkfuEiUSXg

优化结果

0 人点赞