添加prefetch

const assert = require('assert')
 
class PrefetchWebpackPluginPlugin {
  constructor (options) {
    const defaultOptions = {
      /*
      * asyncChunks
      * initial
      * allChunks
      * allAssets
      * */
      include: 'asyncChunks',
      /*
      * default exclude sourcemap files
      * */
      fileBlacklist: [/\.map/]
    }
    this.options = Object.assign({}, defaultOptions, options)
  }
 
  extractChunks ({ compilation, optionsInclude }) {
    try {
      if (optionsInclude === undefined || optionsInclude === 'asyncChunks') {
        return compilation.chunks.filter(chunk => {
          if ('canBeInitial' in chunk) {
            return !chunk.canBeInitial()
          } else {
            return !chunk.isInitial()
          }
        })
      }
 
      if (optionsInclude === 'initial') {
        return compilation.chunks.filter(chunk => {
          if ('canBeInitial' in chunk) {
            return chunk.canBeInitial()
          } else {
            return chunk.isInitial()
          }
        })
      }
 
      if (optionsInclude === 'allChunks') {
        // Async chunks, vendor chunks, normal chunks.
        return compilation.chunks
      }
 
      if (optionsInclude === 'allAssets') {
        // Every asset, regardless of which chunk it's in.
        return [{ files: Object.keys(compilation.assets) }]
      }
 
      if (Array.isArray(optionsInclude)) {
        // Keep only user specified chunks.
        return compilation.chunks.filter((chunk) => chunk.name && optionsInclude.includes(chunk.name))
      }
    } catch (error) {
      return compilation.chunks
    }
 
    throw new Error(`The 'include' option isn't set to a recognized value: ${optionsInclude}`)
  }
 
  insertLinksIntoHead ({ html, links = [] }) {
    if (links.length === 0) {
      return html
    }
    const prefetchLinks = links.map(item => `'<link rel="prefetch" href="'+ASSETS_HOST+'${item}'+'">'`).join('+')
    const prefetchScript = `<script>document.write(${prefetchLinks});</script>`
 
    if (html.includes('</head>')) {
      return html.replace('</head>', `${prefetchScript}</head>`)
    }
 
    throw new Error(`The HTML provided did not contain a </head> or a <body>:\n\n${html}`)
  }
 
  addLinks (compilation, htmlPluginData) {
    const options = this.options
 
    const extractedChunks = this.extractChunks({
      compilation,
      optionsInclude: options.include
    })
 
    // Flatten the list of files.
    const allFiles = extractedChunks.reduce((accumulated, chunk) => {
      return accumulated.concat(chunk.files)
    }, [])
    const uniqueFiles = new Set(allFiles)
    const filteredFiles = [...uniqueFiles].filter(file => {
      return (
        !this.options.fileWhitelist ||
        this.options.fileWhitelist.some(regex => regex.test(file))
      )
    }).filter(file => {
      return (
        !this.options.fileBlacklist ||
        this.options.fileBlacklist.every(regex => !regex.test(file))
      )
    })
    // Sort to ensure the output is predictable.
    const sortedFilteredFiles = filteredFiles.sort()
 
    const links = []
    const publicPath = compilation.outputOptions.publicPath || ''
 
    for (const file of sortedFilteredFiles) {
      links.push(`${publicPath}${file}`)
    }
 
    htmlPluginData.html = this.insertLinksIntoHead({
      links,
      html: htmlPluginData.html
    })
    return htmlPluginData
  }
 
  apply (compiler) {
    compiler.hooks.compilation.tap(
      this.constructor.name,
      compilation => {
        // This is set in html-webpack-plugin pre-v4.
        let hook = compilation.hooks.htmlWebpackPluginAfterHtmlProcessing
 
        if (!hook) {
          const [HtmlWebpackPlugin] = compiler.options.plugins.filter(
            (plugin) => plugin.constructor.name === 'HtmlWebpackPlugin')
          assert(HtmlWebpackPlugin, 'Unable to find an instance of ' +
            'HtmlWebpackPlugin in the current compilation.')
          hook = HtmlWebpackPlugin.constructor.getHooks(compilation).beforeEmit
        }
 
        hook.tapAsync(
          this.constructor.name,
          (htmlPluginData, callback) => {
            try {
              callback(null, this.addLinks(compilation, htmlPluginData))
            } catch (error) {
              callback(error)
            }
          }
        )
      }
    )
  }
}
 
module.exports = PrefetchWebpackPluginPlugin

文件大小检测

const fs = require('fs-extra')
const path = require('path')
const logger = require('../utils/logger')
const { formatSize } = require('../utils')
 
/**
 * @class SizeChecker
 * 根据类型和阈值在编译完成后警告
 * 接受{limit,typeList}参数
 * @param limit:number 报警的文件大小阈值(上限),单位b
 * @param typeList:array 检测的文件类型组成的数组
 */
class FileSizePlugin {
  constructor (options) {
    const { appName, appRoot, limit = 100 * 1024, typeList = [], ignoreList = [] } = options
    this.appName = appName
    this.limit = limit
    this.typeList = typeList
    this.ignoreList = ignoreList.map(item => { return { filepath: path.resolve(appRoot, item) } })
    this.exceedList = []
  }
 
  /**
   * 两个数组的差集
   * @param {array} arr1
   * @param {array} arr2
   * @return {array}
   */
  getDifference (arr1, arr2) {
    return arr1.filter(v1 => arr2.findIndex(v2 => v1.filepath === v2.filepath) === -1)
  }
 
  /**
   * 两个数组的交集
   * @param {array} arr1
   * @param {array} arr2
   * @return {array}
   */
  getIntersection (arr1, arr2) {
    return arr1.filter(v1 => arr2.findIndex(v2 => v1.filepath === v2.filepath) !== -1)
  }
 
  apply (compiler) {
    compiler.hooks.emit.tap('FileSizePlugin', compilation => {
      // 检索每个(构建输出的)chunk
      compilation.chunks.forEach(chunk => {
        // 检索 chunk 中(内置输入的)的每个模块:
        chunk.getModules().forEach(module => {
          // 检索模块中包含的每个源文件路径
          module.buildInfo && module.buildInfo.fileDependencies && module.buildInfo.fileDependencies.forEach(filepath => {
            const reg = new RegExp(`.(${this.typeList.join('|')})$`)
            if (reg.test(filepath)) {
              const size = fs.statSync(filepath).size
              if (size > this.limit) {
                this.exceedList.push({ filepath, size })
              }
            }
          })
        })
      })
 
      if (this.exceedList.length > 0) {
        // 在ignore list列出,成功被忽略的
        const exceedAndIgnoreList = this.getIntersection(this.exceedList, this.ignoreList)
        if (exceedAndIgnoreList.length > 0) {
          console.log('\n')
          const maxLength = Math.max(...exceedAndIgnoreList.map(v => v.filepath.length))
          logger.done('以下文件已被忽略大小检查:')
          exceedAndIgnoreList.forEach(v => {
            logger.done(`- ${v.filepath.padEnd(maxLength + 4)}    ${formatSize(v.size)}`)
          })
        }
 
        // 项目没有用到的,但是在ignore list里列出
        const notUsedButInWhiteList = this.getDifference(this.ignoreList, this.exceedList)
        if (notUsedButInWhiteList.length > 0) {
          console.log('\n')
          logger.warn('以下文件未被使用或未超出限制:')
          notUsedButInWhiteList.forEach(v => {
            logger.warn(`- ${v.filepath}`)
          })
        }
 
        // 超出限定大小,并且没有在ignore list列出
        const notInIgnoreList = this.getDifference(this.exceedList, this.ignoreList)
        if (notInIgnoreList.length > 0) {
          const maxLength = Math.max(...notInIgnoreList.map(v => v.filepath.length))
          console.log('\n')
          logger.error(`以下文件超出限额 (${formatSize(this.limit)}):`)
          notInIgnoreList.forEach(v => {
            logger.error(`- ${v.filepath.padEnd(maxLength + 4)}    ${formatSize(v.size)}`)
          })
          logger.error(`应用 [${this.appName}] 构建失败,请仔细检查图片或视频文件大小.`)
          return process.exit(1)
        }
      } else if (this.ignoreList.length) {
        console.log('\n')
        logger.warn('以下文件未被使用或未超出限制:')
        this.ignoreList.forEach(v => {
          logger.warn(`- ${v.filepath}`)
        })
      }
    })
  }
}
 
module.exports = FileSizePlugin
 
    ...,
    // 校验图片大小
    new FileSizePlugin({
    appName,
    appRoot,
    limit: 200 * 1024,
    typeList: ['png', 'jpg', 'jpeg', 'gif', 'webp'],
    ignoreList: appConfig.imagesIgnoreSizeList || []
    }),
    // 校验视频大小
    new FileSizePlugin({
    appName,
    appRoot,
    limit: 500 * 1024,
    typeList: ['mp4', 'ogg', 'webm'],
    ignoreList: appConfig.videosIgnoreSizeList || []
    })