一次svg-sprite-loader和qiankun不显示精灵图bug修复记录

910 阅读2分钟

一、前言

主项目为微前端架构,为实现样式隔离已开启严格沙盒模式。要嵌入的子项目为最新vue-cli构建,内部使用的是html-webpack-plugin@^5.1.0,精灵图loader是svg-sprite-loader@^6.0.11子项目原来使用的是运行时动态注入单独运行没有问题图标正常显示,但是嵌入主项目图标就不显示。

二、排错历程

  1. 主项目下的其他子项目也有图标引入,我直接抄过来得了。参考其他子项目的处理方式是将运行时注入改为构建时直接注入

所做改动如下:

// vue.config.js 所做修改
const SpriteLoaderPlugin = require("svg-sprite-loader/plugin")

chainWebpack: (config) => {
    config.plugin("SpriteLoaderPlugin").use(SpriteLoaderPlugin, [
      {
        plainSprite: true
      }
    ])
    config.module.rule("svg").exclude.add(resolve("src/icons")).end()
    config.module
      .rule("icons")
      .test(/\.svg$/)
      .include.add(resolve("src/icons"))
      .end()
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({
        symbolId: "icon-[name]",
        extract: true // 开启提取模式
      })
      .end()
}
<!-- /public/index.html -->
<div style="height: 0">
	<% if (htmlWebpackPlugin.files.sprites) { %> <% for (var spriteFileName in
    htmlWebpackPlugin.files.sprites) { %> <%=
    htmlWebpackPlugin.files.sprites[spriteFileName] %> <% } %> <% } %>
</div>
  1. 抄的是滴水不漏,以为会解决问题,其实问题不但没解决,连子项目单独运行都没有图标了😂,于是开启面向百度编程(此处略去无数个搜索,关键词被搜烂了也没有)
  2. 最终在和同事静下来分析后开始怀疑是包不兼容导致的,我想不兼容那就看官网看github,最终在github的issue中找到了问题产生的原因,以及解决方式。

三、问题产生原因

  1. 主项目开启了严格沙盒模式,精灵图插件运行时注入默认注入到body中,因为沙盒是严格隔离的,所以无法突破隔离找到对应的symbol并use。主项目不可以关闭严格沙盒【毕竟已经运行好久了,关闭后产生的影响会很大】,那就只能改子项目
  2. 子项目使用的精灵图插件svg-sprite-loader@^6.0.11 和最新的html-webpack-plugin@^5.1.0不兼容,在html-webpack-plugin4以后便改掉了其暴露出的钩子名称,而精灵图插件依然使用的是3的钩子,所以导致开启提取模式后htmlWebpackPlugin.files.sprites注入失败,最终导致构建时精灵图注入失败

四、解决方案

  • 改相关的源码,使精灵图插件兼容最新的html-webpack-plugin插件【构建时解决】
    1. 改源码部分

      diff --git svg-sprite-loader0/lib/plugin.js svg-sprite-loader1/lib/plugin.js
      index 3d275b9..452a4d1 100644
      --- svg-sprite-loader0/lib/plugin.js
      +++ svg-sprite-loader1/lib/plugin.js
      @@ -70,8 +70,7 @@ class SVGSpritePlugin {
               .thisCompilation
               .tap(NAMESPACE, (compilation) => {
                 try {
      -            // eslint-disable-next-line global-require
      -            const NormalModule = require('webpack/lib/NormalModule');
      +            const NormalModule = compiler.webpack.NormalModule;
                   NormalModule.getCompilationHooks(compilation).loader
                     .tap(NAMESPACE, loaderContext => loaderContext[NAMESPACE] = this);
                 } catch (e) {
      @@ -100,9 +99,10 @@ class SVGSpritePlugin {
             compiler.hooks
               .compilation
               .tap(NAMESPACE, (compilation) => {
      -          if (compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration) {
      -            compilation.hooks
      -              .htmlWebpackPluginBeforeHtmlGeneration
      +          const hooks = HtmlWebpackPlugin.getHooks(compilation)
      +          if (hooks.beforeAssetTagGeneration) {
      +            hooks
      +              .beforeAssetTagGeneration
                     .tapAsync(NAMESPACE, (htmlPluginData, callback) => {
                       htmlPluginData.assets.sprites = this.beforeHtmlGeneration(compilation);
      
      @@ -110,9 +110,9 @@ class SVGSpritePlugin {
                     });
                 }
      
      -          if (compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing) {
      +          if (hooks.beforeHtmlProcessing) {
                   compilation.hooks
      -              .htmlWebpackPluginBeforeHtmlProcessing
      +              .beforeHtmlProcessing
                     .tapAsync(NAMESPACE, (htmlPluginData, callback) => {
                       htmlPluginData.html = this.beforeHtmlProcessing(htmlPluginData);
      
    2. 改完源码为防止其他同事再次下载有问题,需对此包打补丁,打补丁主要使用patch-package这个包,具体操作可以看官方文档,写的很全,多说一点就是注意操作顺序,先下包然后再改源码在打补丁

  • 不修改源码,挂载节点在运行时指定【运行时解决】
    1. 不采用提取模式,就默认的那种,但需要指定自定义的挂载脚本

      // vue.config.js 所做修改
      chainWebpack: (config) => {
          config.module.rule("svg").exclude.add(resolve("src/icons")).end()
          config.module
            .rule("icons")
            .test(/\.svg$/)
            .include.add(resolve("src/icons"))
            .end()
            .use("svg-sprite-loader")
            .loader("svg-sprite-loader")
            .options({
              symbolId: "icon-[name]",
      spriteModule: "./sprite" // 相对的是构建精灵图的文件夹
            })
            .end()
      }
      
    2. 增加全局变量供挂载脚本使用

      // 在 qiankun 暴露的 mount 函数中调用 render 函数,单独调用则无 container 。
      function render(props = {}) {
        const { container } = props;
        window._container = container; // 实际使用时用 store 等代替更好。
        router = createRouter({
          history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? "/app2/" : "/"),
          routes,
        });
      
        instance = createApp(App).mount(
          container ? container.querySelector("#app") : "#app"
        );
      }
      
    3. 增加挂载脚本

      // icons/svg/sprite.js
      import BrowserSprite from "svg-baker-runtime/src/browser-sprite";
      import domready from "domready";
      
      const sprite = new BrowserSprite();
      domready(() => {
        const mountContainer =
          window._container instanceof ShadowRoot
            ? window._container.querySelector("#app")
            : "body";
        sprite.mount(mountContainer);
      });
      
      export default sprite; // don't forget to export!
      

五、思考总结

  1. 对于插件排错时要关注其兼容性问题,免得做无用功
  2. 之前发现源码有问题的时候往往都是直接把整个文件拷下来,然后改完引用本地的。现在学会这个改源码的方式回想之前的做法有点蠢

参考