微前端解决方案初探 07 基于模块联邦的微前实现方案(Authentication 应用初始化、配置初始路由、微应用懒加载)

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.jsremoteEntry.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/pricinghttp://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 可以显示价格页面。

微应用懒加载

目前所有的加载微应用的组件(AuthAppMarketingApp)都会在用户初始访问时被加载,页面需要等待微应用组件加载完成后才会显示,这样会导致加载时间过长,解决办法就是懒加载微应用。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值