目录

qiankun微应用加载的资源404问题记录

# 问题

在项目组使用 qiankun 接入子项目的过程中,出现了这样一个问题:

子应用在单独部署时,写在 css 中图标、字体等资源加载是正常的,但是接入 qiankun,之后这些资源加载就 404 了。通过查看这些资源请求 URL,都指向了基座地址,也就是说,子应用在 css 中使用的是相对地址。但是为什么子应用使用相对地址就会出错呢?

# 分析

关于子应用 css 中的字体文件和图片加载 404 问题,官方是这样解释的[1]

原因是 qiankun 将外链样式改成了内联样式,但是字体文件和背景图片的加载路径是相对路径。
css 文件一旦打包完成,就无法通过动态修改 publicPath 来修正其中的字体文件和背景图片的路径。

源码:[2]

/**
 * convert external css link to inline style for performance optimization
 * @param template
 * @param styles
 * @param opts
 * @return embedHTML
 */
function getEmbedHTML(template, styles, opts = {}) {
  const { fetch = defaultFetch } = opts
  let embedHTML = template

  return getExternalStyleSheets(styles, fetch).then((styleSheets) => {
    embedHTML = styles.reduce((html, styleSrc, i) => {
      html = html.replace(genLinkReplaceSymbol(styleSrc), `<style>/* ${styleSrc} */${styleSheets[i]}</style>`)
      return html
    }, embedHTML)
    return embedHTML
  })
}

# 解决方案

对于以上问题,qiankun 官方给出了以下解决方案[1:1]

  1. 所有图片等静态资源上传至 cdncss 中直接引用 cdn 地址(推荐
  2. 借助 webpack 的 url-loader 将字体文件和图片打包成 base64(适用于字体文件和图片体积小的项目)(推荐
  3. 借助 webpack 的 file-loader ,在打包时给其注入完整路径(适用于字体文件和图片体积比较大的项目)
  4. 将两种方案结合起来,小文件转 base64 ,大文件注入路径前缀
  5. vue-cli3 项目可以将 css 打包到 js 里面,不单独生成文件(不推荐,仅适用于 css 较少的项目)

对应方案的配置就不在此赘述了,可以看出,官方给出的方案,都是依靠子应用自己去实现的,那有没有一种方案,可以让子应用不做修改呢?

  1. 通过 nginx 代理转发,根据子应用的特殊前缀,基座服务器收到之后,转发到子项目上。

    • 这个解决方法虽然理论可行,但如果子应用比较多,情况就变得复杂起来了,所以不是很推荐。
  2. 通过自定义 fetch 方法,修正 css 中的相对地址[3][4]

    • 通过源码可以看到,获取 css 的资源,是通过 fetch 方法是获取的,qiankun 提供了自定义 fetch 的功能,通过自定义 fetch ,获取 css 是通过正则,将相对地址替换成正确的绝对地址,可以修正请求资源 404 的问题。但是这种方式只能修正通过外链形式引入的样式,通过内联方式引入的样式处理不了。同时未经过大规模的试验,可能有些边缘情况没有处理到,仅作参考。

自定义 fetch 方法代码:

const fetch = (url, ...args) => {
  if (typeof url === 'string' && url.endsWith('.css')) {
    return window
      .fetch(url, ...args)
      .then((resp) => resp.text())
      .then((css) => {
        const hasQuote = /^\s*('|")/
        return [
          /(@import\s+)(')(.+?)(')/gi,
          /(@import\s+)(")(.+?)(")/gi,
          /(url\s*\()(\s*')([^']+?)(')/gi,
          /(url\s*\()(\s*")([^"]+?)(")/gi,
          /(url\s*\()(\s*)([^\s'")].*?)(\s*\))/gi
        ].reduce((css, reg) => {
          return css.replace(reg, (all, lead, quote1, path, quote2) => {
            let ret
            if (
              path.startsWith('data:') ||
              path.startsWith('http') ||
              path.startsWith('ftp') ||
              path.startsWith('//')
            ) {
              // base64、cdn的资源不处理
              ret = path
            } else {
              ret = new URL(path, url).toString()
            }
            if (hasQuote.test(ret) && hasQuote.test(quote1)) quote1 = quote2 = ''
            return lead + quote1 + ret + quote2
          })
        }, css)
      })
      .then((css) => Promise.resolve({ text: () => css }))
  }
  return window.fetch(url, ...args)
}

  1. 微应用打包之后 css 中的字体文件和图片加载 404 (opens new window) ↩︎ ↩︎

  2. 源码位置 github (opens new window) gitee (opens new window) ↩︎

  3. 希望提供可后处理 css 资源的参数,类似 getTemplate 参数可处理 html 模板 (opens new window) ↩︎

  4. 支持把微应用 css 内的 url 地址转换为完整的(cdn)链接 (opens new window) ↩︎

上次更新: 2023/01/31 19:48:05

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 , 转载请注明出处!