Authentication - 应用初始化
Authentication 应用和 Marketing 应用的结构相同,可以在其基础上修改,或使用相同步骤创建。
首先完成:创建 Auth 目录 - npm 初始化 - 安装依赖 - 修改启动脚本。
然后添加 webpack 配置文件:
// container\webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
devServer: {
port: 8080,
historyApiFallback: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-react', '@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime']
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
})
]
}
添加模板文件 public/index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Auth</title>
</head>
<body>
<div id="dev-auth"></div>
</body>
</html>
添加入口文件:
// auth\src\index.js
import('./bootstrap')
// auth\src\bootstrap.js
import React from 'react'
import ReactDOM from 'react-dom'
import { createMemoryHistory, createBrowserHistory } from 'history'
import App from './App'
function mount(el, { onNavigate, defaultHistory } = {}) {
const history = defaultHistory || createMemoryHistory()
if (onNavigate) history.listen(onNavigate)
ReactDOM.render(<App history={history} />, el)
return {
onParentNavigate({ pathname: nextPathname }) {
const pathname = history.location.pathname
if (pathname !== nextPathname) {
history.push(nextPathname)
}
}
}
}
if (process.env.NODE_ENV === 'development') {
const el = document.querySelector('#dev-auth')
if (el) {
mount(el, {
defaultHistory: createBrowserHistory()
})
}
}
export { mount }
添加 App.js 组件:
// auth\src\App.js
import React from 'react'
import { Router, Route, Switch } from 'react-router-dom'
import Signin from './components/Signin'
function App({ history }) {
return (
<Router history={history}>
<Switch>
<Route path="/auth/signin">
<Signin />
</Route>
</Switch>
</Router>
)
}
export default App
添加登录页面组件:
// auth\src\components\Signin.js
import React from 'react'
export default function SignIn() {
const styles = {
width: '400px',
margin: '20px auto'
}
return (
<div style={styles}>
<h2>登录</h2>
<form onSubmit={e => e.preventDefault()} noValidate>
<input type="email" name="email" placeholder="邮件地址" autoFocus />
<br />
<input type="password" name="password" placeholder="密码" />
<br />
<input type="checkbox" /> 记住我
<br />
<button type="submit">
登录
</button>
</form>
</div>
)
}
npm start
启动应用,访问 http://localhost:8082/auth/signin
,但是控制台报错:
原因是当前访问的 main.js
和 remoteEntry.js
是相对于 url 的路径,修改 webpack 配置,指定静态资源路径:
// auth\webpack.config.js
// 添加 output 配置项
output: {
publicPath: 'http://localhost:8082/'
},
重启应用,再次访问正常。
向 Container 和 Marketing 应用也添加 output
配置,避免后续出现类似问题。
Container - 加载 Auth 微应用
添加 Auth 远程模块配置:
// container\webpack.config.js
new ModuleFerationPlugin({
name: 'container',
remotes: {
marketing: 'marketing@http://localhost:8081/remoteEntry.js',
auth: 'auth@http://localhost:8082/remoteEntry.js'
},
shared: packgeJson.dependencies
})
添加 AuthApp 组件(复制 MarketingApp.js
):
// container\src\components\AuthApp.js
import React, { useEffect, useRef } from 'react'
import { mount } from 'auth/AuthApp'
import { useHistory } from 'react-router-dom'
export default function AuthApp() {
const ref = useRef()
const history = useHistory()
useEffect(() => {
const { onParentNavigate } = mount(ref.current, {
onNavigate({ pathname: nextPathname }) {
const pathname = history.location.pathname
// 路由不同时更新路由
if (pathname !== nextPathname) history.push(nextPathname)
}
})
// 容器应用路由变化通知微应用
if (onParentNavigate) history.listen(onParentNavigate)
}, [])
return <div ref={ref}></div>
}
添加路由,当访问 /auth/signin
时匹配 Auth 微应用:
// container\src\App.js
import React from 'react'
import { Router, Route, Switch } from 'react-router-dom'
import { createBrowserHistory } from 'history'
import MarketingApp from './components/MarketingApp'
import AuthApp from './components/AuthApp'
import Header from './components/Header'
const history = createBrowserHistory()
function App() {
return (
<Router history={history}>
<Header />
<Switch>
<Route path="/auth/signin">
<AuthApp />
</Route>
<Route path="/">
<MarketingApp />
</Route>
</Switch>
</Router>
)
}
export default App
重启 Container 应用,访问 http://localhost:8080
,不过现在需要点击两次 登录
链接才会正常显示登录表单。
解决点击两次登录链接才会显示登录页面的 BUG
当前点击 登录
链接时,容器应用的路由切换为 /auth/signin
,于是会加载 AuthApp。
这是首次加载 AuthApp 组件,当前在首次挂载(mount
)微应用时默认访问的都是 /
,因为在使用 createMemoryHistory
创建路由时没有传递初始参数。
当再次点击 登录
链接时,容器应用通知微应用路由发生了变化,微应用同步路由变化,最终才显示了登录页面。
同理,在地址栏输入 http://localhost:8080/pricing
或 http://localhost:8080/auth/signin
直接访问时,也不会显示期望的页面。
原因是微应用在初始创建路由对象时没有指定默认路由。
容器应用在调用微应用 mount
方法时可以将当前路由传递给微应用,作为其默认路由。
// container\src\components\AuthApp.js
import React, { useEffect, useRef } from 'react'
import { mount } from 'auth/AuthApp'
import { useHistory } from 'react-router-dom'
export default function AuthApp() {
const ref = useRef()
const history = useHistory()
useEffect(() => {
const { onParentNavigate } = mount(ref.current, {
onNavigate({ pathname: nextPathname }) {
const pathname = history.location.pathname
// 路由不同时更新路由
if (pathname !== nextPathname) history.push(nextPathname)
},
// 传递当前路由作为微应用的默认路由
initialPath: history.location.pathname
})
// 容器应用路由变化通知微应用
if (onParentNavigate) history.listen(onParentNavigate)
}, [])
return <div ref={ref}></div>
}
// auth\src\bootstrap.js
import React from 'react'
import ReactDOM from 'react-dom'
import { createMemoryHistory, createBrowserHistory } from 'history'
import App from './App'
function mount(el, { onNavigate, defaultHistory, initialPath } = {}) {
const history = defaultHistory || createMemoryHistory({
// 指定默认匹配地址
initialEntries: [initialPath]
})
if (onNavigate) history.listen(onNavigate)
ReactDOM.render(<App history={history} />, el)
return {
onParentNavigate({ pathname: nextPathname }) {
const pathname = history.location.pathname
if (pathname !== nextPathname) {
history.push(nextPathname)
}
}
}
}
if (process.env.NODE_ENV === 'development') {
const el = document.querySelector('#dev-auth')
if (el) {
mount(el, {
defaultHistory: createBrowserHistory()
})
}
}
export { mount }
现在点击一次 登录
链接即可显示登录页面。
相同方式,也给 Marketing 应用添加默认路由后,直接访问 http://localhost:8080/pricing
可以显示价格页面。
微应用懒加载
目前所有的加载微应用的组件(AuthApp
、MarketingApp
)都会在用户初始访问时被加载,页面需要等待微应用组件加载完成后才会显示,这样会导致加载时间过长,解决办法就是懒加载微应用。
React 提供了 lazy
方法和 Suspense
组件用于懒加载组件:
// container\src\App.js
import React, { lazy, Suspense } from 'react'
import { Router, Route, Switch } from 'react-router-dom'
import { createBrowserHistory } from 'history'
import Header from './components/Header'
// 懒加载微应用
const MarketingApp = lazy(() => import('./components/MarketingApp'))
const AuthApp = lazy(() => import('./components/AuthApp'))
const history = createBrowserHistory()
function App() {
return (
<Router history={history}>
<Header />
<Suspense fallback={<div>正在加载组件</div>}>
<Switch>
<Route path="/auth/signin">
<AuthApp />
</Route>
<Route path="/">
<MarketingApp />
</Route>
</Switch>
</Suspense>
</Router>
)
}
export default App