publicPath
是什么一般状况下,覆盖 CRA 配置的解决方案有两种:css
npm run eject
react-app-rewired
或者 rescripts
等第三方工具这里使用第二种方式,缘由也比较简单,由于魔改 webpack
是须要很大的勇气的,且 npm run eject
不可逆(虽然能够经过其余方式恢复,但太麻烦了),而且对于须要覆盖的配置,咱们也是有针对性的,因此使用第三方工具会更好一些。html
我这里使用 rescripts
这个库,它与 create-react-rewired
大同小异。前端
首先,对于 single-spa
中要加载的微前端应用,咱们须要提供诸如 bootstrap
、 mount
以及 unmount
等若干生命周期钩子,但 CRA
中 webpack
的默认打包方式不会将这些方法暴露出来,因此声明配置以下:node
config.output.library = `${name}-[name]`; config.output.libraryTarget = 'umd'; config.output.jsonpFunction = `webpackJsonp_${name}`; config.output.globalObject = 'window';
让咱们来挨个分析下每行配置的做用及意义:react
library
和 libraryTarget
是共同生效的,默认状况下,libraryTarget
的值是 var
,即在 entry file
在执行后,会返回一个变量,咱们这里使用 umd
的缘由是由于,在主应用中,咱们仍然会使用 module system
,不管是 webpack
仍是 Systemjs
,所以 umd
是一个最佳选择,由于它适配全部的 module system
。jsonpFunction
是用来按需加载 chunk
的工具函数,因为微前端应用中,一个页面中,会同时存在多个 webpack
运行时环境,因此可能会存在命名冲突,致使加载 chunk
时出现意想不到的后果,手动设置一个惟一的命名能够解决这个冲突globalObject
自己属性的默认值便是 window
,但因为 libraryTarget
咱们设置成了 umd
,对于 nodejs 环境,全局对象时 global
而非 window
,这里显示地声明它是 window
证实微前端应用只是针对 browser 而言的HMR 功能通常是针对开发环境而言的,对于为何微前端应用在开发环境要关闭 HMR,我尚未深刻研究,但关闭它是官方代码库示例中提供的最佳实践。webpack
在 CRA 中,HMR 功能是分两部分存在的,一个是 webpack.config.devServer
提供的,另外一个是 CRA 本身实现的 webpackHotDevClient
,咱们需依次移除或者关闭它们。git
首先关闭 webpack.devServer
的 HMR 功能,很简单,添加以下配置:github
config.hot = false; config.watchContentBase = false; config.liveReload = false;
再来移除 webpackHotDevClient
,这个会稍微麻烦一些,由于它是直接声明在 webpack.config.entry
中的,因此使用下面的代码移除它:web
config.entry = config.entry.filter( (e) => !e.includes('webpackHotDevClient') );
同时还有 HotModuleReplacementPlugin
插件,它提供 css 的 HMR 功能,利用相同的代码移除它:npm
config.plugins = config.plugins.filter( (p) => !(p instanceof webpack.HotModuleReplacementPlugin) );
这样就彻底从 CRA 中移除了 HMR 的功能。
以 html-entry
为前提实现的微前端框架,构建前提既是微前端应用要支持跨域访问,对于部署阶段,咱们能够在 web server
或代理层完成该步骤,对于开发阶段,咱们则须要对 devServer
进行一些调整,由于它默认是不支持跨域访问的。
解决跨域问题除了配置反向代理以外,还可使用 CORS
来解决,在 devServer
中,显示使用后者更加快捷,添加以下代码便可:
config.headers = { 'Access-Control-Allow-Origin': '*', };
这样既实现了最简单的 CORS
配置,但知足开发环境中对于跨域访问的支持,足够了。
很简单,声明以下配置便可:
config.historyApiFallback = true;
推荐使用 history
做为微前端子应用的路由模式,由于在全局路由解析中,针对 hash
的匹配并不像 url
那样灵活,同时也存在一些微妙的 bug。
也十分简单,使用以下代码:
config.port = 7101;
这里的 7101
,是微前端子应用监听的接口,建议不论在开发阶段,仍是在部署阶段,都使用相同的接口以减小分辨接口的心智负担。
须要单独指定 publicPath
的缘由是由于,当前咱们的微前端架构依赖于 html entry
,每一个路径所对应的 entry
所加载的微前端应用,必然会有一些从 publicPath
加载资源的代码。
但在项目中,除非将全部子应用项目中的静态资源目录集合到一块儿,托管在主应用中,或者使用 CDN
,否则在项目启动时,会遇到不少 404
的错误,其根本缘由是由于,以前请求的静态资源,并非托管在主应用的服务器上,而是子应用的,所以如何在主应用或者代理层中映射这些静态资源的加载请求,是必需要解决的事情。
默认状况下,CRA 的 publicPath
是 /
,即相对于当前服务器的域名,子应用在主应用中加载时,所相对的是主应用服务器的域名,因此这里须要对每一个子应用声明不一样的 publicPath
。
在 CRA 中声明 publicPath
有两种,
package.json
中添加 homepage
字段PUBLIC_PATH
环境变量当前我使用的方式是第一种,由于第二种在当前的 CRA 版本中不生效(感受像是一个 bug),以下:
{ "name": "vcapp-login", "homepage": "/login", ... }
除了针对静态资源设置单独的 publicPath
以外,还须要在应用中,针对动态使用 publicPath
的地方作出修改,这个在 qiankun
中已经有响应的解决方案,以下:
if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
简单原理就是动态的注入了 __INJECTED_PUBLIC_PATH_BY_QIANKUN__
这个全局变量,它是经过 html entry
导入 entry
时,动态解析出来的。
众所周知,CRA 启动的 entry
文件是 src/index.jsx
,若是一个项目须要适配为微前端应用,势必须要在 index.jsx
中实现各类微前端模块的生命周期函数。
在微前端应用的架构中,很重要的一点既是解耦,若是咱们在开发子应用时,且没有用到任何和主应用或者其余子应用相关的模块或者状态时,仍然须要其余它们显示不是一种理想的开发模式。
咱们理想的模式应该是,咱们仍然能够按照传统 SPA 的启动方式,开发子应用,当须要与主应用集成,或者与其余子应用调试时,又能够以微前端模块的方式启动它。这实际上是在说,咱们当前的子应用要支持多 entry
启动模式。
在 CRA 中,虽然能够经过覆盖 webpack
的方式来解决这个问题,可是我认为有更简便的方法。考虑到不管是传统启动方式,仍是微前端模块的启动方式,这两种启动方式在同一时间,咱们只会使用一种,那咱们移花接木式的变动 index.jsx
的内容,在 CRA 加载 entry
以前欺骗它岂不是更好?这里咱们能够利用如下两点来实现相似的效果:
npm scripts
中的 hook 前缀来截止启动指令index.jsx
的内容因为涉及到的代码较多,这里就简单贴一个 npm scripts
的截图好了,以下:
能够发现,对于 start
、 build
,均支持两种模式的指令,从而适配不一样开发模式下的构建需求。
最后说一点,对于 index.jsx
内容的更改,最简单的方式即时经过软连接的方式来实现,提早提供两份被连接的目标文件,好比:
micro.tsx
和 standalone.tsx
均对应不一样的 entry
入口,使用 CRA 启动应用前,动态地建立软连接将它们和 index.tsx
文件连接起来便可(因为项目中使用了 ts,后缀为 .tsx
,js 项目同理)。
以后咱们就能够愉快地在主应用中引入咱们的子应用了,主要配置有两个,一是注册子应用,以下:
registerMicroApps( [ // 其余子应用 ..., { name: "vcapp-login", entry: "//localhost:7101/login", container: "#subapp-container", activeRule: "/trade-login/", }, ], )
二是增长对于子应用的 publicPath
的配置,这儿会分为两部分,一个是部署环境下的,一个是开发环境下的,这里分享开发环境下的。我主应用项目使用的打包器是 parcel
,所以能够直接对它内部的 web server
增长中间件来完成这部分工做,以下:
app.use( createProxyMiddleware("/login", { target: "http://localhost:7101", }) );
注意这里的 7101
,与上文中的 7101
对应,若是它们不一致,会形成子应用加载失败。
svg
格式的图片,没有写在 url-loader
的匹配规则中,若是子应用使用了 svg
图片,须要覆盖 url-loader
配置已适配 publicPath
变动形成的影响因为仓库代码在公司内网,不太方面直接拷贝出来,往后有时间会单另在 github
建立一个示例项目。
同时因为该微前端应用的架构基于 single-spa
和 qiankun
,对于 CRA 项目向微前端项目的迁移所作的一些工做并不具备通用性。
对于微前端这种架构,我更多地将它做为一种可以渐进式地重构项目的手段在使用,对于大型复杂项目,并无太多的经验,一是由于没机会作相似的复杂度极高的中台项目,二是由于不少巨石应用,大可能是旧项目,因此将它用做重构项目的一种手段也许更能发挥它的用处。
若有错误,还望指出。