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]:
- 所有图片等静态资源上传至
cdn
,css
中直接引用cdn
地址(推荐) - 借助
webpack
的 url-loader
将字体文件和图片打包成base64
(适用于字体文件和图片体积小的项目)(推荐) - 借助
webpack
的 file-loader
,在打包时给其注入完整路径(适用于字体文件和图片体积比较大的项目) - 将两种方案结合起来,小文件转
base64
,大文件注入路径前缀 vue-cli3
项目可以将css
打包到js
里面,不单独生成文件(不推荐,仅适用于 css 较少的项目)
对应方案的配置就不在此赘述了,可以看出,官方给出的方案,都是依靠子应用自己去实现的,那有没有一种方案,可以让子应用不做修改呢?
通过
nginx
代理转发,根据子应用的特殊前缀,基座服务器收到之后,转发到子项目上。- 这个解决方法虽然理论可行,但如果子应用比较多,情况就变得复杂起来了,所以不是很推荐。
通过自定义
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)
}
上次更新: 2023/01/31 19:48:05
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 , 转载请注明出处!