【微前端】10分钟学会乾坤大挪移

简介: 今天刚刚学习了一个微前端框架——乾坤,正着热乎劲,写一篇入门博客。这篇文章不会讨论太多的原理和实现,只是一个入门写 Hello World 的教程。文章的很多地方都参考官网,但是官网的教程太简洁了,个人觉得还是做不到无脑上手,希望这篇文章可以帮到正在入门微前端的伙伴。想直接看代码的,我写了个比官网更简单的例子,点击即可学会。

image.png


前言


今天刚刚学习了一个微前端框架——乾坤,正着热乎劲,写一篇入门博客。这篇文章不会讨论太多的原理和实现,只是一个入门写 Hello World 的教程。


文章的很多地方都参考官网,但是官网的教程太简洁了,个人觉得还是做不到无脑上手,希望这篇文章可以帮到正在入门微前端的伙伴。


想直接看代码的,我写了个比官网更简单的例子,点击即可学会。


什么是微前端


首先,来了解一下微前端是个啥。


当我们写了一个又一个的 SPA 应用。突然有一天,老板说要将这些应用合并,前端工程们就头大了——每个应用的代码都是一座摇摇欲坠的💩山,别说合并了,就算动都不敢动呀。


虽然很麻烦,但是前端工程师还是把这个问题解决了,而这个将多个 SPA 合并成一个 Web App 的解决方案就是微前端


为什么要微前端


“多个 SPA 合并成一个 Web App?”,可能有人会想到用 <iframe/> 也可以实现一个网页里内嵌多个网页呀。原因有:


  • 不感知 url 状态,比如前进、后退没法玩
  • UI 不同步、DOM 不同享。<iframe/> 本质上是页面的硬隔离,所以如果你有个遮罩层,可能只能在那一小片区域才展示遮罩层
  • 页面之间的通信很麻烦
  • 每次都要加载子应用,速度很慢


而微前端正好可以补足上面的缺点。


微前端的优势


除了解决了上面的问题,微前端还有如下的优点:


  • 子应用技术栈无关,即类似上页说的页面硬隔离,但是是以 sandbox 的方式实现的
  • 合并多个子应用,相对地,也可以将大应用拆解成多个应用,实现业务解耦
  • 子应用高度自治,发布、报错、测试流程仅限于子应用,不会受别的业务影响,同时也不影响别的业务


乾坤由来


最原始的微前端框架并不是乾坤,而是 single-spa。但是这个框架只提供最基本的功能,而且全是英文,文档写得也很繁琐,应该没人想去看。


阿里的乾坤则是基于 single-spa 开发的又一个微前端框架,提供了更多的功能,也解决一些坑,官网也很简洁。


不过,个人觉得有点太简洁了,写 Hello World 的时候还是遇到一些坑,只能看 Github 的 /examples 目录学习。


主应用 VS 子应用


首先,要知道现在项目并不是只有一个了,而是区分出 主应用子应用,关系如下:

image.png


两者区别:


  • 主应用
  • 概念:就是要统治各个子应用的应用,也即合并结果页面
  • 负责子应用的注册、路由分发。可以简单理解为 React.js 和 Vue.js 里的 App 组件,主要做一些初始化、路由注册、全局状态注册、销毁时的动作


  • 子应用
  • 概念:各个 SPA 应用,可以理解为 SPA 里的页面组件
  • 负责暴露一些函数,以此对接主应用,让主应用知道:哦,原来你是子应用,要和我对接。常见的对应函数有:bootstrap, mount, unmount


项目创建


乾坤官网最推荐的做法是将主应用和子应用分成两个项目,各自管理。当然,也可以一个项目里分成不同的目录来存放。

├── main    # 主应用
├── baidu   # 子应用
└── taobao # 子应用
复制代码


如果你觉得 官方的例子 太复杂,也可以看我自己建的 qiankun-bigass-app,子应用只有两个用 React.js 的项目。我把很多无关的代码都删了。


实现主应用


理清上面的关系后,我们直接干代码,先看主应用。

首先,我们弄一个 .html 文件出来,作为主页面的 HTML 模板:

<body>
  <div class="mainapp">
    <!-- 标题栏 -->
    <header class="mainapp-header">
      <h1>QianKun</h1>
    </header>
    <div class="mainapp-main">
      <!-- 侧边栏 -->
      <ul class="mainapp-sidemenu">
        <li onclick="push('/taobao')">淘宝</li>
        <li onclick="push('/baidu')">百度</li>
      </ul>
      <!-- 子应用  -->
      <main id="subapp-container"></main>
    </div>
  </div>
  <script>
    function push(subapp) { history.pushState(null, subapp, subapp) }
  </script>
</body>
复制代码


然后,使用 Webpack,指定为 template HTML,并配置 dev server,注意一定要配置 headers,不然会有跨域的问题,子应用同理:

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './index.js',
  devtool: 'source-map',
  devServer: {
    open: true,
    port: '7099',
    clientLogLevel: 'warning',
    disableHostCheck: true,
    compress: true,
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    historyApiFallback: true,
    overlay: { warnings: false, errors: true },
  },
  output: {
    publicPath: '/',
  },
  mode: 'development',
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: ['@babel/plugin-transform-react-jsx'],
          },
        },
      },
      {
        test: /\.(le|c)ss$/,
        use: ['style-loader', 'css-loader', 'less-loader'],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: process.env.MODE === 'multiple' ? './multiple.html' : './index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
      },
    }),
  ],
};
复制代码


入口文件 index.js 就比较重要了,需要完成主应用的很多事情:

import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start, initGlobalState } from 'qiankun';
import './index.less';
/**
 * 主应用 **可以使用任意技术栈**
 * 以下分别是 React 和 Vue 的示例,可切换尝试
 */
import render from './Render';
// import render from './render/VueRender';
/**
 * Step1 初始化应用(可选)
 */
render({ loading: true });
const loader = loading => render({ loading });
/**
 * Step2 注册子应用
 */
registerMicroApps(
  [
    {
      name: 'taobao',
      entry: '//localhost:7101',
      container: '#subapp-viewport',
      loader,
      activeRule: '/taobao',
    },
    {
      name: 'baidu',
      entry: '//localhost:7102',
      container: '#subapp-viewport',
      loader,
      activeRule: '/baidu',
    },
  ],
  {
    beforeLoad: [
      app => {
        console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
      },
    ],
    beforeMount: [
      app => {
        console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
      },
    ],
    afterUnmount: [
      app => {
        console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
      },
    ],
  },
);
const { onGlobalStateChange, setGlobalState } = initGlobalState({
  user: 'qiankun',
});
onGlobalStateChange((value, prev) => console.log('[onGlobalStateChange - master]:', value, prev), true);
setGlobalState({
  ignore: 'master',
  user: {
    name: 'master',
  },
});
/**
 * Step3 设置默认进入的子应用
 */
setDefaultMountApp('/taobao');
/**
 * Step4 启动应用
 */
start();
runAfterFirstMounted(() => {
  console.log('[MainApp] first app mounted');
});
复制代码


上面主要完成:初始化、注册子应用、设置配置全局状态、设置默认进入子应用、启动应用。


至于初始渲染函数,可以这么写:

import React from 'react';
import ReactDOM from 'react-dom';
/**
 * 渲染子应用
 */
function Render(props) {
  const { loading } = props;
  return (
    <>
      {loading && <h4 className="subapp-loading">Loading...</h4>}
      <div id="subapp-viewport" />
    </>
  );
}
export default function render({ loading }) {
  const container = document.getElementById('subapp-container');
  ReactDOM.render(<Render loading={loading} />, container);
}
复制代码


实现子应用


子应用其实和官网的差不多,这里以 React.js 子应用举例。首先用 create-react-app 来创建子应用:

create-react-app baidu
复制代码


src 目录下新增 public-path.js

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
复制代码


设置 history 模式路由的 base:

const RouteExample = () => {
  return (
    <Router basename={window.__POWERED_BY_QIANKUN__ ? '/baidu' : '/'}>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
      </nav>
      <Suspense fallback={null}>
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/about" component={About} />
        </Switch>
      </Suspense>
    </Router>
  );
};
export default function App() {
  return (
    <div className="app-main">
      <h1>淘宝Taobao</h1>
      <hr/>
      <RouteExample />
    </div>
  );
}
复制代码


__POWERED_BY_QIANKUN__ 用于判断现在是否作为子应用被访问,其它地方与普通 React.js App 没差别。


去掉一些无用的文件后,在入口配置子应用:

function render(props) {
  const { container } = props;
  ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}
if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}
// 监听全局状态
function storeTest(props) {
  props.onGlobalStateChange((value, prev) => console.log('淘宝', `[onGlobalStateChange - ${props.name}]:`, value, prev), true);
  props.setGlobalState({
    ignore: props.name,
    user: {
      name: props.name,
    },
  });
}
export async function bootstrap() {
  console.log('[淘宝] react app bootstraped');
}
export async function mount(props) {
  console.log('[淘宝] props from main framework', props);
  storeTest(props);
  render(props);
}
export async function unmount(props) {
  const { container } = props;
  ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
复制代码

注意上面的 bootstrap, mountunmount 一定要 export 出去,不然没人知道这个是子应用。


下一步,是修改 Webpack 的配置。但是 creat-react-app 造出来的 React App 不 eject 出来就改不了,这里官网推荐使用 @rescripts/cli 来修改:

yarn add -D @rescript/cli
复制代码


在根目录添加 .rescriptsrc.js,并加上:

const { name } = require('./package');
module.exports = {
  webpack: config => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    config.output.jsonpFunction = `webpackJsonp_${name}`;
    config.output.globalObject = 'window';
    return config;
  },
  devServer: _ => {
    const config = _;
    config.headers = {
      'Access-Control-Allow-Origin': '*',
    };
    config.historyApiFallback = true;
    config.hot = false;
    config.watchContentBase = false;
    config.liveReload = false;
    return config;
  },
};
复制代码


Webpack 配置同样很重要,一个是配置 historyApiFallback 处理单页的 404 问题,另一个是通过 Access-Control-Allow-Origin 解决主应用访问子应用的跨域问题。


在上面的主应用里看到我们是要访问不同的端口的,那端口要怎么配置呢?可以通过 .env 来配置:

SKIP_PREFLIGHT_CHECK=true
BROWSER=none
PORT=7101
WDS_SOCKET_PORT=7102
复制代码

更多框架的配置可见这里


API 粗讲


乾坤的 API 也不是很多,详见这里。简单讲一下用处:

API 用处 类比
registerMicroApps 主应用用来注册多个子应用的函数 类似于 Vue 和 React 的路由
start 启动主应用 类似于 React.js 的 render 函数和 Vue.js 的 new Vue()
loadMicroApp 手动加载子应用 也类似于 React.js 的 render 函数和 Vue.js 的 new Vue(),只不过更自由了
prefetchApps 预加载子应用 类似于 Webpack 的 prefetch 功能
addGlobalUncaughtErrorHandler 页面报错时可以用于上报和兜底 -
removeGlobalUncaughtErrorHandler 都懂的 -
initGlobalState 初始化全局状态 类似于 Redux 的 createStore 和 Vue 的 new Vue.Store()


FAQ


请看 常见问题,最佳实践和遇到的问题都能在这里解决。

(完)




相关文章
|
设计模式 资源调度 Kubernetes
微前端应用 乾坤 开发实践
乾坤作为一款微前端应用框架,可以有效的实现前端应用程序的模块化设计,代码复用和可维护性。下面我们来通过实际开发来看看乾坤的使用方式以及实践。
|
22天前
|
前端开发 JavaScript
乾坤qiankun(微前端)样式隔离解决方案--使用插件替换前缀
乾坤qiankun(微前端)样式隔离解决方案--使用插件替换前缀
203 8
|
20天前
|
前端开发 JavaScript 开发者
qiankun(乾坤)微前端框架简介
qiankun(乾坤)微前端框架简介
57 1
|
前端开发 数据可视化 JavaScript
为什么要用微前端?如何使用乾坤微前端?
为什么要用微前端?如何使用乾坤微前端?
为什么要用微前端?如何使用乾坤微前端?
|
21天前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
|
21天前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
34 0
|
22天前
|
人工智能 自然语言处理 运维
前端大模型应用笔记(一):两个指令反过来说大模型就理解不了啦?或许该让第三者插足啦 -通过引入中间LLM预处理用户输入以提高多任务处理能力
本文探讨了在多任务处理场景下,自然语言指令解析的困境及解决方案。通过增加一个LLM解析层,将复杂的指令拆解为多个明确的步骤,明确操作类型与对象识别,处理任务依赖关系,并将自然语言转化为具体的工具命令,从而提高指令解析的准确性和执行效率。
|
21天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
21天前
|
机器学习/深度学习 弹性计算 自然语言处理
前端大模型应用笔记(二):最新llama3.2小参数版本1B的古董机测试 - 支持128K上下文,表现优异,和移动端更配
llama3.1支持128K上下文,6万字+输入,适用于多种场景。模型能力超出预期,但处理中文时需加中英翻译。测试显示,其英文支持较好,中文则需改进。llama3.2 1B参数量小,适合移动端和资源受限环境,可在阿里云2vCPU和4G ECS上运行。
|
21天前
|
前端开发 算法 测试技术
前端大模型应用笔记(五):大模型基础能力大比拼-计数篇-通义千文 vs 文心一言 vs 智谱 vs 讯飞vsGPT
本文对比测试了通义千文、文心一言、智谱和讯飞等多个国产大模型在处理基础计数问题上的表现,特别是通过链式推理(COT)提示的效果。结果显示,GPTo1-mini、文心一言3.5和讯飞4.0Ultra在首轮测试中表现优秀,而其他模型在COT提示后也能显著提升正确率,唯有讯飞4.0-Lite表现不佳。测试强调了COT在提升模型逻辑推理能力中的重要性,并指出免费版本中智谱GLM较为可靠。
前端大模型应用笔记(五):大模型基础能力大比拼-计数篇-通义千文 vs 文心一言 vs 智谱 vs 讯飞vsGPT