编写自定义Webpack插件注入cdn引用

1,221 阅读1分钟

项目(Vue2.7)最近要部署到外网,所以一些内网使用的资源文件需要根据发布内容进行更换或者删除,最初为了临时解决外网部署问题写了一版很low的方案,具体代码如下:

<!-- index.html -->
<!DOCTYPE html>
<html lang="">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <title>
    <%= htmlWebpackPlugin.options.title %>
  </title>
  <script src="<%= BASE_URL %>lib.min.js" type="text/javascript"></script>
  <link ignore rel="stylesheet" id="wicCss" rel="prefetch"
    href="<%= !JSON.parse(VUE_APP_OTAF_FLAG) ? '/wic-uportal/comps/wic-comps.css' : '' %>" />
  <script src="" type="text/javascript"></script>
  <script id="rdcLib"
    src="<%= !JSON.parse(VUE_APP_OTAF_FLAG) ? '/zte-rdcloud-rdc-libuportal/rdc-component/lib.min.js' : '' %>"
    type="text/javascript"></script>
  <script id="wicLib" src="<%= !JSON.parse(VUE_APP_OTAF_FLAG) ? '/wic-uportal/comps/wic-comps.umd.min.js' : '' %>"
    type="text/javascript"></script>
  <script ignore
    src="<%= JSON.parse(VUE_APP_OTAF_FLAG) ? '/iui/component/thirdparty/jquery/jquery.min.js' : '' %>"></script>
  <script ignore src="<%= JSON.parse(VUE_APP_OTAF_FLAG) ? '/iui/component/common/js/tools.min.js' : '' %>"></script>
</head>

<body>
  <div id="xxapp-wm"></div> <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>
</body>

</html>

 其中VUE_APP_OTAF_FLAG是区分内外网的一个标识字段,定义在环境变量中的。

这种写法会导致打包之后存在src为空的script和href为空的link标签。所以趁着业务开发间隙改良了一版,奖cdn的内容在vue.config.js中进行引用注入,具体如下:

<!-- index.html -->
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
    
    <% if(htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css){ %>
      <% for(var css of htmlWebpackPlugin.options.cdn.css){ %>
        <link rel="stylesheet" href="<%= css %>" />
      <% } %>
    <% } %>

    <% if(htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js){ %>
      <% for(var js of htmlWebpackPlugin.options.cdn.js){ %>
        <script src="<%= js %>"></script>
      <% } %>
    <% } %>

  </head>
  <body>
    <div id="xxapp-wm"></div>
    <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>
  </body>
</html>

//vue.config.js
const isOtaf = JSON.parse(process.env.VUE_APP_OTAF_FLAG)
const cdn = isOtaf ? {
  js: [`${process.env.BASE_URL}/lib.min.js`, "/iui/component/thirdparty/jquery/jquery.min.js", "/iui/component/common/js/tools.min.js"],
  css: [],
} : {
  js: [,"/zte-rdcloud-rdc-libuportal/rdc-component/lib.min.js", "/wic-uportal/comps/wic-comps.umd.min.js"],
  css: ["/wic-uportal/comps/wic-comps.css"],
};

chainWebpack: config => {
  config
    .plugin('html')
    .tap(args => {
      args[0].title = 'RAG Studio';
      args[0].cdn = cdn;
      return args
    })
},

其实写到这种程度已经解决了空地址引用的问题,但是为了尽量简洁index.html文件中的代码,将功能从vue.config.js中剥离出来,同时又可以给组内前端小伙伴一个封装Webpack插件的具象化示例,最终还是决定将cdn注入使用插件的方式完成,所以又进行了一次优化,封装了一个webpack插件,完成资源的引用注入。

插件代码:

// injectCdnPlugin.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

class InjectCdnPlugin {
  constructor (options) {
    this.options = options;
    this.pluginName = 'InjectCdnPlugin';
  }

  apply (compiler) {
    const pluginName = this.pluginName; // 使用pluginName
    compiler.hooks.compilation.tap(pluginName, compilation => {
      HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tap(
        pluginName,
        data => {
          // 确保scriptTag和linkTag都是数组
          if (!Array.isArray(data.assetTags.scripts)) {
            data.assetTags.scripts = [];
          }
          if (!Array.isArray(data.assetTags.styles)) {
            data.assetTags.styles = [];
          }
          // 添加额外的script标签
          const scriptTag = data.assetTags.scripts;
          // console.log(data.assetTags)
          this.options.js.forEach(scriptEntry => {
            scriptTag.unshift({
              tagName: 'script',
              voidTag: false,
              meta: { plugin: pluginName },
              attributes: {
                src: scriptEntry.src || scriptEntry
              }
            });
          });
          // 额外添加link标签来引入CSS
          const linkTag = data.assetTags.styles;
          this.options.css.forEach(cssEntry => {
            linkTag.unshift({
              tagName: 'link',
              voidTag: true, // link标签是void标签,没有结束标签
              meta: { plugin: pluginName },
              attributes: {
                rel: 'stylesheet',
                type: 'text/css',
                href: cssEntry.href || cssEntry // 假设你的css对象有href属性
              }
            });
          });
        }
      );
    });
  }
}

module.exports = InjectCdnPlugin;

创建插件实例:

// injectCdnPluginInstance.js
// 导入InjectCdnPlugin插件
const InjectCdnPlugin = require('./injectCdnPlugin');

// 获取publicPath和isOtaf标志
const publicPath = process.env.BASE_URL;
const isOtaf = JSON.parse(process.env.VUE_APP_OTAF_FLAG);

// 定义CDN资源的URL
const cdnJsUrls = {
  otaf: ['/iui/component/common/js/tools.min.js', '/iui/component/thirdparty/jquery/jquery.min.js', `${process.env.BASE_URL}/lib.min.js`],
  notOtaf: ['/wic-uportal/comps/wic-comps.umd.min.js', '/zte-rdcloud-rdc-libuportal/rdc-component/lib.min.js', '/iui/component/vue2lib/lib.min.js']
};

const cdnCssUrls = {
  otaf: [],
  notOtaf: ['/wic-uportal/comps/wic-comps.css']
};

// 根据isOtaf标志选择正确的CDN配置
const cdn = isOtaf ? {
  js: cdnJsUrls.otaf,
  css: cdnCssUrls.otaf
} : {
  js: cdnJsUrls.notOtaf,
  css: cdnCssUrls.notOtaf
};

// 创建InjectCdnPlugin实例
const injectCdnPluginInstance = new InjectCdnPlugin(cdn);

// 将插件实例作为模块的默认导出
module.exports = injectCdnPluginInstance;

vue.config.js配置:

//vue.config.js
const injectCdnPluginInstance = require('./src/lib/injectCdnPluginInstance.js');
plugins: [  injectCdnPluginInstance],

使用不同指令进行内外网打包生成的最终index.html代码如下:

内网:

<!doctype html>
<html lang="">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="icon" href="/ai/favicon.ico">
    <title>RAG Studio</title>
    <script src="/iui/component/vue2lib/lib.min.js" type="module"></script>
    <script src="/zte-rdcloud-rdc-libuportal/rdc-component/lib.min.js" type="module"></script>
    <script src="/wic-uportal/comps/wic-comps.umd.min.js" type="module"></script>
    <script defer="defer" type="module" src="/ai/js/chunk-vendors.0f41a209.js"></script>
    <script defer="defer" type="module" src="/ai/js/app.e6ae2e39.js"></script>
    <link rel="stylesheet" href="/wic-uportal/comps/wic-comps.css">
    <link href="/ai/css/app.efcc1ee0.css" rel="stylesheet">
    <script src="/iui/component/vue2lib/lib.min.js" nomodule></script>
    <script src="/zte-rdcloud-rdc-libuportal/rdc-component/lib.min.js" nomodule></script>
    <script src="/wic-uportal/comps/wic-comps.umd.min.js" nomodule></script>
    <script defer="defer" src="/ai/js/chunk-vendors-legacy.0f41a209.js" nomodule></script>
    <script defer="defer" src="/ai/js/app-legacy.0036262a.js" nomodule></script>
</head>
<body>
    <div id="xxapp-wm"></div><noscript><strong>We're sorry but RAG Studio doesn't work properly without JavaScript
            enabled. Please enable it to continue.</strong></noscript>
    <div id="app"></div>
</body>
</html>

外网:

<!doctype html>
<html lang="">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="icon" href="/aab-portal/favicon.ico">
    <title>RAG Studio</title>
    <script src="/aab-portal/lib.min.js" type="module"></script>
    <script src="/iui/component/thirdparty/jquery/jquery.min.js" type="module"></script>
    <script src="/iui/component/common/js/tools.min.js" type="module"></script>
    <script defer="defer" type="module" src="/aab-portal/js/chunk-vendors.0f41a209.js"></script>
    <script defer="defer" type="module" src="/aab-portal/js/app.1ce80f14.js"></script>
    <link href="/aab-portal/css/app.7a2197c6.css" rel="stylesheet">
    <script src="/aab-portal/lib.min.js" nomodule></script>
    <script src="/iui/component/thirdparty/jquery/jquery.min.js" nomodule></script>
    <script src="/iui/component/common/js/tools.min.js" nomodule></script>
    <script defer="defer" src="/aab-portal/js/chunk-vendors-legacy.0f41a209.js" nomodule></script>
    <script defer="defer" src="/aab-portal/js/app-legacy.6f7641cc.js" nomodule></script>
</head>
<body>
    <div id="xxapp-wm"></div><noscript><strong>We're sorry but RAG Studio doesn't work properly without JavaScript
            enabled. Please enable it to continue.</strong></noscript>
    <div id="app"></div>
</body>
</html>

内外网两个环境部署之后验证正常。