首屏优化,webpack插件用于给html中js自动添加异步加载属性

因为要使用cheerio库,需要安装

javascript 复制代码
npm安装
npm install cheerio --save-dev

或使用 yarn安装
yarn add cheerio --dev

创建async-script-webpack-plugin.js

javascript 复制代码
const cheerio = require('cheerio');

class AsyncScriptWebpackPlugin {
  constructor(options = {}) {
    this.options = {
      // 默认添加 async 属性
      async: true,
      defer: false,
      // 可选:指定需要添加异步属性的脚本文件名或正则表达式
      include: [],
      exclude: [],
      ...options
    };
  }

  apply(compiler) {
    // 监听 HTML 生成后的钩子
    compiler.hooks.compilation.tap('AsyncScriptWebpackPlugin', (compilation) => {
      // 检查是否存在 html-webpack-plugin
      const HtmlWebpackPlugin = compiler.options.plugins.find(
        (plugin) => plugin.constructor.name === 'HtmlWebpackPlugin'
      );

      if (!HtmlWebpackPlugin) {
        console.warn('[AsyncScriptWebpackPlugin] 未检测到 HtmlWebpackPlugin,插件将不会生效');
        return;
      }

      // 注册钩子到 html-webpack-plugin 处理 HTML 之后
      if (compilation.hooks.htmlWebpackPluginAfterHtmlProcessing) {
        // 兼容旧版本 html-webpack-plugin
        compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tapAsync(
          'AsyncScriptWebpackPlugin',
          (data, cb) => {
            data.html = this.processHtml(data.html);
            cb(null, data);
          }
        );
      } else if (compilation.hooks.htmlWebpackPluginAlterAssetTags) {
        // 兼容新版本 html-webpack-plugin
        compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(
          'AsyncScriptWebpackPlugin',
          (data, cb) => {
            data.body = this.processScripts(data.body);
            data.head = this.processScripts(data.head);
            cb(null, data);
          }
        );
      }
    });
  }

  // 处理 HTML 字符串中的 script 标签
  processHtml(html) {
    const $ = cheerio.load(html);
    $('script').each((i, script) => {
      this.modifyScriptTag($, script);
    });
    return $.html();
  }

  // 处理 html-webpack-plugin 提供的 script 标签数组
  processScripts(scripts) {
    return scripts.map((script) => {
      if (script.tagName === 'script' && script.attributes && script.attributes.src) {
        const src = script.attributes.src;
        // 检查是否匹配包含/排除规则
        if (this.shouldProcessScript(src)) {
          if (this.options.async) script.attributes.async = true;
          if (this.options.defer) script.attributes.defer = true;
        }
      }
      return script;
    });
  }

  // 判断是否应该处理某个脚本
  shouldProcessScript(src) {
    const { include, exclude } = this.options;
    
    // 如果有排除规则且匹配,则不处理
    if (exclude.length > 0 && this.matchesAny(src, exclude)) {
      return false;
    }
    
    // 如果有包含规则且不匹配,则不处理
    if (include.length > 0 && !this.matchesAny(src, include)) {
      return false;
    }
    
    // 默认处理
    return true;
  }

  // 检查字符串是否匹配任何规则(字符串或正则)
  matchesAny(str, rules) {
    return rules.some((rule) => {
      if (typeof rule === 'string') {
        return str.includes(rule);
      }
      if (rule instanceof RegExp) {
        return rule.test(str);
      }
      return false;
    });
  }

  // 修改 script 标签
  modifyScriptTag($, script) {
    const $script = $(script);
    const src = $script.attr('src');
    
    // 如果没有 src 属性,可能是内联脚本,跳过
    if (!src) return;
    
    // 检查是否应该处理这个脚本
    if (!this.shouldProcessScript(src)) return;
    
    // 添加异步加载属性
    if (this.options.async) $script.attr('async', 'async');
    if (this.options.defer) $script.attr('defer', 'defer');
  }
}

module.exports = AsyncScriptWebpackPlugin;    

使用方法

javascript 复制代码
const AsyncScriptWebpackPlugin = require('./async-script-webpack-plugin');

module.exports = {
  // 其他 Webpack 配置...
  plugins: [
    new AsyncScriptWebpackPlugin({
      // 可选配置:
      async: true,   // 添加 async 属性(默认)
      defer: false,  // 不添加 defer 属性
      include: [     // 只处理匹配的脚本
        /vendor\.js$/,
        'analytics.js'
      ],
      exclude: [     // 排除匹配的脚本
        'critical.js'
      ]
    }),
    // 其他插件...
  ]
};
相关推荐
晓天衡宇•评测社区25 分钟前
Seedance 2.5 登顶图生视频榜单,Wan 3.0 在游戏与教育场景进入前列
前端·javascript·网络
卡皮巴拉c9929 分钟前
基于pnpm搭建monorepo项目
前端·javascript
jianpeng的工程笔记1 小时前
Colab Kaggle 防断开脚本
javascript·深度学习·kaggle·colab
天道kabuto2 小时前
Webpack 迁 Vite 踩坑记:一个让人抓狂的 Sourcemap 报错怎么破?
webpack·vite
三十而立洋2 小时前
JavaScript 原型链:一张图讲透「对象继承」的底层真相
前端·javascript
平头哥技术团队2 小时前
Day 13 | 调 line-height 和 margin:三处间距让名片页脱离模板感
开发语言·前端·javascript·学习·html5
老三_Micheal3 小时前
一文搞懂 Axios 请求参数详解:headers、params 与 body 的区别和使用场景
javascript
小刘在重生~3 小时前
Web 前端基础|HTML+CSS+JavaScript+Bootstrap
前端·css·html
研☆香3 小时前
js中 onload 事件的用法
开发语言·javascript·ecmascript
志尊宝3 小时前
Vue3 零基础每日笔记(005):ref 响应式基础——为什么 script 里要 .value
前端·javascript·vue.js·笔记