工程不经常改动,就不建仓库了,弄个压缩包完事。
代码压缩包:micro-qiankun.tar.gz
背景
文章不是为了讲怎么用 react & vite & qiankun 结合搭建。搭建也不复杂,直接看示例代码就可以了。主要是这其中遇到的一个问题:
qiankun.js:17 [import-html-entry]: error occurs while executing normal script <script type=”module”>
详情如图:
记录下这个问题的产生及解决。
介绍
qiankun.js 是一个微前端框架。对于微前端,之前也尝试过《用 webpack ModuleFederationPlugin 搭建微前端》。
两者差别的话:
- 如果用 vite,那 webpack5 MF 肯定用不了(废话~)。
- 对于 qiankun,是不论框架的,基座和各个子应用可以完全不同框架,因为用的沙箱机制,可以可以云云(详看官网介绍)。
- 而 webpack MF 其实也不是为了微前端出来,而是共享一部分功能出来,恰好能用作微前端。最终形式是暴露出一份 js 文件给主应用引用。那就最好基座和子应用都是同框架。
个人理解:
- 如果是成熟的几个不同项目,要揉在一起,但是彼此关联又不是很大,可以用 qiankun。
- 如果是本身就是同一个大项目,要拆成不同模块分工维护。有很多共享的数据,可以用 webpack ModuleFederationPlugin。
搭建
qiankun 的使用也挺简单的,根据官网指示就好了。用 vite 构建 react,可以参考《react 基础工程 Ⅱ(@reduxjs/toolkit & @craco/craco & vite)》,也不复杂。但是要混合使用,还差一点。
主要是没法直接用,因为 vite 调试的时候,就是用原生 esm 模块引入。网上有大佬先一步产出插件了:vite-plugin-qiankun。
根据大佬的指示,很快就搭建起来了,也成功运行起来,就是会报上述那个错误。如果是基座也是 vite 那还好,只会在控制台打印出来。如果基座是 webpack,就会变成错误遮罩一直挡着。
那么:
上述错误说的是在不声明 type="module"
的情况下使用了 esm 模块。我们知道 vite 的入口 js 就是一句:<script type="module" src="/src/index.js"></script>
。所以 vite-plugin-qiankun 插件就是把它变成动态 import()
引入。
这个没问题,重点是用 vite 构建 react 官方出了一个支持的插件:@vitejs/plugin-react。这个帮助调试时候快速更新,以及主动注入 react jsx runtime
。这也是可以解决一个缺少依赖的错误,这个错误也是老面孔了:Uncaught ReferenceError: React is not defined
。以前用 webpack + babel 也会出现。
如果打包工具不主动注入 react jsx runtime,那么就要自己在代码层面,在使用到 jsx 组件地方主动引入:import React from react;
。
这是其中一个功能,为了支持快速更新,也在页面注入了一段代码:
import RefreshRuntime from 'http://localhost:5173/@react-refresh'
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
也就是和报错相关联的那一段代码。这点在官网的 后端集成第二点 也有提到。
然后 qiankun 在引入子应用时,是使用类似 eval 的方法触发子应用 js。所以对于这段直接使用 import 的代码就报错了。
解决
既然知道了产生原因,就可以对症下药:
- 眼不见为净。不理它。上面说了,如果是基座是 vite 那就只会出现在控制台打印,不影响运行。而且这段代码是为了辅助调试使用的,打成生产包就没了。子应用都用 vite 难道基座不用?(狗头…)
- 不要用 @vitejs/plugin-react。有点因噎废食。且不说每份用到 jsx 的文件都要主动引入 react 很麻烦,万一后面更新了啥功能岂不是用不到?不如第一条不理这个错误。
- 自己写个 vite 插件处理一下:
// 插件写法
const htmlRemoveFreshPlugin = () => {
return {
name: 'html-transform',
transformIndexHtml(html) {
const $ = require('cheerio').load(html);
$('script').eq(0).remove();
// $('head').prepend(
// `<script>
// import((window.proxy ? (window.proxy.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ + '..') : '') + '/@react-refresh').then(({ default: RefreshRuntime }) => {
// RefreshRuntime.injectIntoGlobalHook(window)
// window.$RefreshReg$ = () => {}
// window.$RefreshSig$ = () => (type) => type
// window.__vite_plugin_react_preamble_installed__ = true
// })
// </script>`);
html = $.html();
return html;
},
};
};
// 引入部分
{
plugins: [
react(),
vitePluginQiankun(appName, { useDevMode: isDev }),
...(isDev ?
[htmlRemoveFreshPlugin()]
: []),
],
}
解释一下:
- vite 是基于 rollup 开发的,插件写法也和 rollup 插件一致。transformIndexHtml 是 vite 独有的钩子,用于处理返回的 html。
- cheerio 是 vite-plugin-qiankun 带的依赖。可以像 jQuery (jQuery 就不介绍了)一样操作 dom,也可以用来做爬虫相关使用。
- 做法就是把那段 esm 代码移除。如果还想要有注入的效果,可以把注释那段打开,变成动态 import() 引用。
我的建议做法是:
不理它。只是作用于调试,不阻塞的情况下没必要专门弄个插件去处理。主要是我把这段代码去掉后(包括基座对于这段的代码引入),发现还是可以热更新。是因为这段代码不是支持热更新的还是说别的地方起作用了?这个就不细究了。
另外:
开发子应用的时候。其实只看子应用的页面就可以了。如果硬要连同基座一起启动,在基座页面调试查看子应用,则使用 vite-plugin-qiankun 的时候一定要传入 { useDevMode: true }
。子应用引用入口 js 的时候,能带上完整的路径:import((window.proxy ? (window.proxy.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ + '..') : '') + '/src/index.js')
。不然子应用在基座页面调试,会变成引入基座的入口 js。