阅读视图

发现新文章,点击刷新页面。

入坑node.js全面指南(二)

1. Express.js 架构哲学

作为 Node.js 生态中应用最广泛的 Web 框架,Express.js 以"最小化设计原则"为核心,通过模块化架构实现灵活扩展。

核心设计理念

  • 简约而不简单:仅提供 Web 开发的基础功能,其他能力通过中间件扩展
  • 中间件驱动架构:采用洋葱模型实现请求处理流水线
  • 无强制约束:不限定项目结构,开发者拥有完全自由
  • 渐进式增强:可根据需求添加功能模块,避免过度设计

关键架构组件

  • 中间件系统:请求处理流水线,每个中间件可访问请求/响应对象
  • 路由分发器:基于 HTTP 方法和 URL 的高效路由匹配
  • 视图引擎接口:支持多种模板引擎(EJS、Pug等)
  • 扩展接口:通过 app.set()/app.get() 管理应用配置

安装

npm install express

基本应用结构

const express = require('express');
const app = express();
const port = 3000;

// 中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 路由
app.get('/', (req, res) => {
  res.send('Hello Express!');
});

// 启动服务器
app.listen(port, () => {
  console.log(`服务器运行在 http://localhost:${port}`);
});

2. 中间件

中间件类型

  1. 应用级中间件:绑定到 app 实例

    // 记录请求时间的应用级中间件
    app.use((req, res, next) => {
      req.requestTime = Date.now();
      console.log(`请求时间: ${req.requestTime}`);
      next(); // 必须调用next传递控制权
    });
    
  2. 路由级中间件:绑定到路由实例

    const router = express.Router();
    router.use((req, res, next) => {
      console.log('路由中间件执行');
      next();
    });
    
  3. 错误处理中间件:四个参数的特殊中间件

    app.use((err, req, res, next) => {
      console.error(err.stack);
      res.status(500).send('服务器错误!');
    });
    
  4. 内置中间件

    app.use(express.json()); // 解析JSON请求体
    app.use(express.static('public')); // 静态文件服务
    
  5. 第三方中间件

    const helmet = require('helmet');
    app.use(helmet()); // 安全头部设置
    

中间件执行顺序

Express 中间件按照声明顺序执行,理解这一点对于正确配置应用至关重要:

  1. 请求到达服务器
  2. 按顺序执行匹配的中间件
  3. 每个中间件可以修改 req/res 对象
  4. 中间件通过 next() 传递控制权
  5. 当某个中间件发送响应时,链终止

3. 路由系统深度解析

路由基础

// 基础路由
app.get('/products', (req, res) => {
  res.json([{ id: 1, name: '手机' }]);
});

// 路由参数
app.get('/products/:id', (req, res) => {
  const id = req.params.id;
  res.json({ id, name: `商品${id}` });
});

// 查询参数
app.get('/search', (req, res) => {
  const q = req.query.q;
  res.send(`搜索: ${q}`);
});

4. 请求与响应处理

请求对象 (req)

  • req.params:路由参数
  • req.query:查询字符串参数
  • req.body:请求体内容(需要中间件解析)
  • req.cookies:客户端 cookies
  • req.headers:HTTP 请求头
  • req.ip:客户端 IP 地址

响应对象 (res)

  • res.status(code):设置状态码
  • res.send(body):发送响应
  • res.json(obj):发送 JSON 响应
  • res.render(view, locals):渲染模板
  • res.redirect(path):重定向
  • res.set(field, value):设置响应头
// 综合使用示例
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  if (!username || !password) {
    return res.status(400).json({ error: '用户名和密码必填' });
  }
  
  // 认证逻辑...
  res.cookie('token', 'abc123', { httpOnly: true });
  res.redirect('/dashboard');
});

5. Express vs Koa vs Node 深度对比

5.1 Express 与 Node.js 的关系

关系解析

  • Node.js 是运行时环境,提供 HTTP 模块等基础能力
  • Express 是基于 Node.js HTTP 模块的封装框架
  • Express 简化了路由、中间件等常见 Web 开发任务

5.2 Express 与 Koa 的深度对比

  • 设计理念:Express.js 设计较为灵活,提供了丰富的功能和插件,适合快速开发各种类型的 Web 应用。而 Koa 是由 Express.js 的原班人马打造,它更注重简洁和优雅,采用了更现代化的异步编程方式(如 async/await),并且没有捆绑任何中间件,开发者可以根据需求自由选择和组合中间件。

  • 中间件机制:Express.js 的中间件采用传统的回调函数形式,中间件之间通过next()方法传递控制权。在处理复杂的异步操作时,可能会出现回调地狱的问题。Koa 的中间件基于 async/await 语法,通过await next()实现中间件的执行和传递,代码更加简洁、易读,避免了回调地狱。

  • 错误处理:在 Express.js 中,错误处理通常通过在中间件或路由处理函数中捕获错误,并传递给错误处理中间件。而 Koa 使用 try/catch 块或者在 async 函数中返回错误,通过onerror事件来统一处理错误,错误处理更加直观和方便。

Express vs Koa 核心差异

特性 Express Koa
中间件模型 线性链式调用 洋葱模型
异步处理 回调函数 原生Async/Await
错误处理 集中式错误中间件 Try/Catch捕获
路由系统 内置Router 需koa-router
请求/响应封装 原生对象扩展 统一Context对象
体积大小 约4.5MB 约0.6MB

6. Express 实战案例:RESTful API 实现

const express = require('express');
const app = express();
app.use(express.json());

// 内存数据库
let users = [
  { id: 1, name: '张三', email: 'zhang@example.com' },
  { id: 2, name: '李四', email: 'li@example.com' }
];

// 获取所有用户
app.get('/api/users', (req, res) => {
  res.json({
    status: 'success',
    count: users.length,
    data: users
  });
});

// 创建新用户
app.post('/api/users', (req, res) => {
  const { name, email } = req.body;
  const newUser = {
    id: users.length + 1,
    name,
    email
  };
  
  users.push(newUser);
  
  res.status(201).json({
    status: 'success',
    data: newUser
  });
});

});

// 全局404处理
app.use((req, res) => {
  res.status(404).json({
    status: 'error',
    message: '路由不存在'
  });
});

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    status: 'error',
    message: '服务器内部错误'
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`服务器运行在 http://localhost:${PORT}`);
});

7. 总结

Express.js的优势:

  1. 简洁核心+丰富扩展:保持核心轻量,通过中间件提供无限扩展
  2. 渐进式采用路径:从简单路由到复杂应用均可胜任
  3. 社区驱动生态:庞大的中间件生态系统解决各种场景需求
  4. 文档与稳定性:优秀的文档质量和稳定的API设计

最佳实践建议

  1. 使用 express.Router 实现路由模块化
  2. 中间件按功能拆分为独立模块
  3. 统一错误处理机制
  4. 使用 helmet 增强安全性
  5. 结合 morgan 记录访问日志
  6. 使用 express-validator 进行输入验证

初探微前端:技术架构思想与主流实现方案

概念

🌐 微前端(Micro Frontends)概念详解

✅ 什么是微前端?

微前端是一种将前端应用拆分成多个独立、自治、可单独开发和部署的“小前端应用”的架构理念,借鉴了微服务在后端的思想。

通俗地说:

把一个大前端项目拆分成多个小应用,每个小应用可以独立开发、测试、部署、运行,最终集成在一个主应用中。


🧩 举个例子:

假设你在做一个电商系统,包含:

  • 商品展示
  • 购物车
  • 用户中心
  • 后台管理

传统方式是一个大 SPA 项目,全放在一个仓库里。但微前端下:

  • 商品展示 → 一个独立的前端项目
  • 购物车 → 一个独立的项目
  • 用户中心 → 一个独立项目
  • 这些模块运行时被主应用“动态加载”和“组合展示”。

💡 微前端的核心理念:

概念 解释
技术栈无关 每个子应用可以使用不同的框架,比如 React + Vue 混用
独立开发 子应用独立代码仓库、独立部署、独立生命周期
运行时集成 子应用按需加载,不需要打包到主应用中
状态隔离 每个子应用有自己的作用域,防止全局变量、样式等冲突
主从架构 主应用负责容器、路由分发,子应用负责各自的业务展示

🛠 常用微前端实现方式:

方案 原理 优缺点
iframe 子应用运行在 iframe 中 简单但隔离过强,通信困难,体验差
JavaScript 方式(如 qiankun) 主应用动态加载子应用资源,通过 JS 沙箱和 DOM 控制集成 灵活、性能好、适合企业场景,推荐使用
模块联邦(Module Federation) Webpack 5 提供的功能,构建时共享模块 更适合技术统一的团队,构建时集成而非运行时

✅ 微前端的优点:

  • ✨ 提升开发效率(多个团队并行开发)
  • 🛠 模块解耦,独立部署
  • 🔄 方便技术栈迁移(逐步替换 Vue2 → Vue3)
  • 🧪 子应用独立测试,更容易定位问题

⚠️ 微前端的挑战:

  • 全局状态管理复杂
  • 路由管理需要协调
  • 样式/脚本冲突需要隔离
  • 性能要优化(多个子应用同时加载时)

📌 总结一句话:

微前端就像把“大房子”拆成多个“独立小房间”,每个房间可以由不同团队装修(甚至风格不同),但最终拼成一个完整的“家”。


为什么不用 iframe(摘选自 qiankun 技术团队)?

Why Not Iframe?

为什么不用 iframe,这几乎是所有微前端方案第一个会被 challenge 的问题。但是大部分微前端方案又不约而同放弃了 iframe 方案,自然是有原因的,并不是为了 "炫技" 或者刻意追求 "特立独行"。

如果不考虑体验问题,iframe 几乎是最完美的微前端解决方案了。

iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题。

  1. url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
  2. UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
  3. 全局上下文完全隔离,内存变量不共享,frame 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
  4. 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程

其中有的问题比较好解决(问题1),有的问题我们可以睁一只眼闭一只眼(问题4),但有的问题我们则很难解决(问题3)甚至无法解决(问题2),而这些无法解决的问题恰恰又会给产品带来非常严重的体验问题, 最终导致我们舍弃了 iframe 方案。

qiankun(乾坤)

阿里开源的微前端框架,基于 single-spa 开发,对 single-spa 进行了封装。

核心通过 import-html-entry 动态加载子应用的 HTML、CSS、JS 等资源,和 JS 沙箱的机制实现

主应用中注册子应用

使用 registerMicroApps Api来基于路由地址加载子应用

import {
  registerMicroApps,
  start
}
from 'qiankun';
registerMicroApps([{
  name: 'sub-app-1-name',
  entry: '//localhost:3000',
  container: '#yourContainer',
  activeRule: '/yourActiveRule',
}], {
  beforeLoad: (app) => customLog(`子应用beforeLoad:$ {
    app.name
  }`),
  beforeMount: (app) => customLog(`子应用beforeMount:$ {
    app.name
  }`),
  afterMount: (app) => customLog(`子应用afterMount:$ {
    app.name
  }`),
  beforeUnmount: (app) => customLog(`子应用beforeUnmount:$ {
    app.name
  }`),
  afterUnmount: (app) => customLog(`子应用afterUnmount:$ {
    app.name
  }`),
});
start();

子应用配置

  1. 子应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用。
import {
  createApp
}
from "vue";

let instance = null;

function render(props = {}) {
  instance = createApp(App);
  instance.mount("#sub-app");
}

/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async
function bootstrap() {
  console.log('app bootstraped');
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async
function mount(props) {
  render(props);
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async
function unmount(props) {
  instance.unmount();
  instance.$el.innerHTML = "";
  instance = null;
}

/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async
function update(props) {
  console.log('update props', props);
}
  1. 子应用的打包工具需要增加如下配置:
// webpack v5:
const packageName = require('./package.json').name;
module.exports = {
  output: {
    // 将打包后的代码暴露为一个库,命名为 ${packageName}-[name]
    library: `$ {
      packageName
    } - [name]`,
    // "umd"格式,使模块能在不同环境中使用(CommonJS/AMD/全局变量)
    libraryTarget: 'umd',
    // 设置加载异步chunk时的全局变量名,防止多个微应用间冲突
    chunkLoadingGlobal: `webpackJsonp_$ {
      packageName
    }`,
  },
};

// webpack v4:
const packageName = require('./package.json').name;

module.exports = {
  output: {
    library: `$ {
      packageName
    } - [name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_$ {
      packageName
    }`,
  },
};

UMD(Universal Module Definition,通用模块定义)是一种兼容多种环境的JavaScript模块格式:

  • 它的主要特点是"一次编写,到处运行",可同时支持:
    • CommonJS(Node.js环境)
    • AMD(如RequireJS)
    • 全局变量(浏览器环境)
  • UMD格式的代码会首先检测当前运行环境,然后采用对应的模块系统:
    • 如果发现存在CommonJS环境,就使用module.exports导出
    • 如果发现存在AMD环境,就使用define定义模块
    • 如果以上都不存在,则将模块挂载到全局对象(如window)上
  • 在微前端架构中,UMD格式非常重要,因为它使子应用能够被主应用灵活加载,无论主应用使用哪种模块系统,都能正确识别和使用子应用。
  1. 新增 public-path.js 文件,用于修改运行时的 publicPath

以 vue 项目为例:

  • 在 src 目录新增 public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
  • 入口文件 main.js 修改,为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围
function render(props = {}) {
  const {
    container
  } = props;

  instance = createApp(App);
  instance.mount(container ? container.querySelector("#app") : "#app");
}

同时主应用中应传递用于渲染子应用的容器元素或者id:

registerMicroApps([{
  name: 'sub-app-1-name',
  entry: '//localhost:3000',
  container: '#yourContainer',
  activeRule: '/yourActiveRule',
  props: {
    container: "#sub-app-1-box",
  },
}]);
  1. 子应用需要配置允许跨域访问

生产环境需要配置 nginx,开发时可以通过配置开发代理实现:

// webpack.config.js 
devServer: {
  headers: {
    "Access-Control-Allow-Origin": "*",
  },
}

访问子应用静态资源

静态资源访问问题

以图片为例:

  1. 资源命名冲突:

    • 例如:子应用1和子应用2都有一个logo.png

    • 如果不配置不同的输出目录,它们会输出到同一位置(如/images/logo.png)

    • 结果:加载到页面上的只有一个应用的logo,另一个会被覆盖或找不到

  2. 跨域资源请求失败:

  3. 样式引用路径错误:

    • 例如:CSS中背景图片使用相对路径 background-image: url('../assets/bg.png')

    • 当CSS被集成到主应用后,相对路径解析会基于主应用URL

    • 结果:背景图片无法显示,控制台出现404错误

  4. 子应用间资源污染:

    • 例如:两个子应用都有 user-icon.svg 但设计不同

    • 用户在主应用中切换子应用时,由于缓存机制,可能显示错误的图标

    • 结果:UI显示混乱,用户体验受损

解决方法

小尺寸图片转为 base64 字符串,大尺寸图片通过配置代理(nginx)的方式解决:

// 子应用 webpack.config.js
module.exports = {
  module: {
    rules: [{
      test: /\.(png|jpe?g|gif|svg|webp)$/i,
      type: "asset",
      parser: {
        dataUrlCondition: {
          maxSize: 1024 * 1024,
          // 1MB
        },
      },
      generator: {
        filename: "sub1-images/[name].[hash:8][ext]",
        publicPath: "/",
      },
    },
    ]
  }
}
// 主应用 vite.config.js
import {
  defineConfig
}
from "vite";

export
default defineConfig({
    server:
    {
      proxy:
      {
        "/sub1-images":
        {
          target:
          "http://localhost:3000",
        },
        "/sub2-images": {
          target: "http://localhost:3001",
        },
      },
    },
  });

数据通信

通过在主应用中调用 initGlobalState Api 来实现,它会返回三个方法:

  • onGlobalStateChange: (callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) => void, 在当前应用监听全局状态,有变更触发 callback,fireImmediately = true 立即触发 callback
  • setGlobalState: (state: Record) => boolean, 按一级属性设置全局状态,微应用中只能修改已存在的一级属性
  • offGlobalStateChange: () => boolean,移除当前应用的状态监听,微应用 umount 时会默认调用

并且将这三个方法默认通过 props 传递给子应用,子应用只需接收到后 provide 到全局就行

路由跳转

主应用中控制切换不同子应用

通过路由配置,注意,展示子应用的路由匹配规则必须带通配符以匹配所有可能的路径,否则子应用内部路由切换时,路由地址改变,会导致页面无法加载:

// router.js
{
  path: "/sub-app-1/:subpath(.*)*",
  name: "sub-app-1",
  meta: {
    title: "展示子应用1",
  },
  component: () = >import("@/views/subApp1/index.vue"),
},
{
  path: "/sub-app-2/:subpath(.*)*",
  name: "sub-app-2",
  meta: {
    title: "展示子应用2",
  },
  component: () = >import("@/views/subApp2/index.vue"),
}
registerMicroApps([{
  name: "sub-app-1",
  // app name registered
  entry: "//localhost:3000",
  container: "#sub-app-1-box",
  activeRule: "/sub-app-1",
  props: {
    container: "#sub-app-1-box",
  },
},
{
  name: "sub-app-2",
  // app name registered
  entry: "//localhost:3001",
  container: "#sub-app-2-box",
  activeRule: "/sub-app-2",
  props: {
    container: "#sub-app-2-box",
  },
},
], );

子应用控制主应用路由跳转,切换到其他子应用

在主应用中注册子应用时,将主应用的路由实例传递给子应用,子应用接收后,provide 到全局中

// 主应用 main.js
import routes from "./router";

const router = createRouter({
  history: createWebHistory("/"),
  routes,
});

registerMicroApps([{
  name: "sub-app-1",
  // app name registered
  entry: "//localhost:3000",
  container: "#sub-app-1-box",
  activeRule: "/sub-app-1",
  props: {
    container: "#sub-app-1-box",
    router
  },
},
]);
// 子应用 main.js
function render(props = {}) {
  const {
    container,
    router: parentRouter,
    setGlobalState
  } = props;
  router = createRouter({
    history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? "/sub-app-1": "/"),
    routes,
  });

  instance = createApp(App);

  instance.use(router);
  instance.provide("setGlobalState", setGlobalState);
  instance.provide("parentRouter", parentRouter);
  instance.mount(container ? container.querySelector("#app") : "#app");
}

样式隔离

调用 strat 时,如下配置,qiankun 会改写子应用所添加的样式为所有样式规则增加一个特殊的选择器规则来限定其影响范围

start({
  sandbox: {
    experimentalStyleIsolation: true,
  },
});

此时主应用通过 !important 可以影响到子应用样式

Micro App

京东开源的微前端框架,借鉴了 WebComponent 的思想,通过js沙箱、样式隔离、元素隔离、路由隔离模拟实现了 ShadowDom 的隔离特性,并结合CustomElement将微前端封装成一个类 WebComponent 组件,从而实现微前端的组件化渲染。

主应用中注册子应用

Micro App 以组件的形式注册子应用:

// main.js
import microApp from "@micro-zoe/micro-app"microApp.start({
  lifeCycles: {
    created(e, appName) {
      customLog(`子应用$ {
        appName
      }被创建`);
    },
    beforemount(e, appName) {
      customLog(`子应用$ {
        appName
      }即将渲染`);
    },
    mounted(e, appName) {
      customLog(`子应用$ {
        appName
      }已经渲染完成`);
    },
    unmount(e, appName) {
      customLog(`子应用$ {
        appName
      }已经卸载`);
    },
    error(e, appName) {
      customLog(`子应用$ {
        appName
      }加载出错`);
    },
  },
});
// 展示子应用的页面
<micro-app 
    name="sub-app-1" 
    url="http://localhost:3002/" 
    router-mode="native" 
    baseroute="/sub-app-1/" 
    iframe 
></micro-app>

子应用配置

// main.js
const app = createApp(App) app.mount('#app')

// 卸载应用
window.unmount = () = >{
  app.unmount()
}

完成以上配置,子应用就可以正常加载了,也可以在此基础上配置 UMD 模式,以在多次渲染时获得更好的性能和内存表现

// main.js
import {
  createApp
}
from 'vue'import {
  createRouter,
  createWebHistory
}
from 'vue-router'import routes from './router'import App from './App.vue'

let app = null let router = null let history = null
// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行
window.mount = () = >{
  history = createWebHistory() router = createRouter({
    history,
    routes,
  })

  app = createApp(App) app.use(router) app.mount('#app')
}

// 👇 将卸载操作放入 unmount 函数,就是上面步骤2中的卸载函数
window.unmount = () = >{
  app.unmount() history.destroy() app = null router = null history = null
}

// 如果不在微前端环境,则直接执行mount渲染
if (!window.__MICRO_APP_ENVIRONMENT__) {
  window.mount()
}

访问子应用静态资源

image.png 可以直接通过子应用地址访问,micro-app 默认会对子应用相对地址的资源路径进行补全,以确保所有资源正常加载。

数据通信

  1. 子应用可以直接获取主应用初始下发的数据,可以用来初始化数据
<!-- 主应用 -->
<html>
 <head></head>
 <body>
   &nbsp;
  <micro-app  ="" name="sub-app-1" url="http://localhost:3002/" router-mode="native" baseroute="/sub-app-1/" iframe="" :data="data"></micro-app>
 </body>
</html>
// 子应用 main.js 
const data = window.microApp.getData();
  1. 通过 setData、dispatch Api 和 监听来实现数据

主应用发送数据:

// 返回值会放入数组中传递给setData的回调函数
microApp.setData('sub-app-name', {
    city: 'HK'
},
(res: any[]) = >{
    console.log(res) // ['返回值1', '返回值2']
})

主应用接收数据:

监听自定义事件:

// 在事件对象的detail.字段中,子应用每次发送都会触发change
<template> 
    <micro-app name='my-app' url='xx' @datachange='handleDataChange' / >
</template>

<script>
export default {
  methods: {
    handleDataChange (e) {
      console.log('来自子应用的数据:', e.detail.data)
    }
  }
}
</script > 

绑定监听函数

/**
 * 绑定监听函数
 * appName: 应用名称
 * dataListener: 绑定函数
 */
microApp.addDataListener(appName: string, dataListener: (data: Object) = >any)

子应用发送数据

// 返回值会放入数组中传递给dispatch的回调函数
window.microApp.dispatch({
    city: 'HK'
},
(res: any[]) = >{
    console.log(res) // ['返回值1', '返回值2']
})

子应用接收数据

/**
 * 绑定监听函数
 * appName: 应用名称
 * dataListener: 绑定函数
 */
window.microApp.addDataListener(dataListener: (data: Object) = >any)

路由跳转

主应用跳转子应用指定页面

/**
 * @param {string} name 必填,子应用的name
 * @param {string} path 必填,子应用除域名外的全量地址(也可以带上域名)
 * @param {boolean} replace 可选,是否使用replace模式,不新增堆栈记录,默认为false
 */
router.push({
    name: '子应用名称',
    path: '页面地址',
    replace: 是否使用replace模式
})

子应用跳转主应用指定页面

  1. 首先要在主应用执行
import microApp from '@micro-zoe/micro-app'

// 注册主应用路由
microApp.router.setBaseAppRouter(主应用的路由对象)
  1. 子应用中执行:
// 获取主应用路由
const baseRouter = window.microApp.router.getBaseAppRouter()

// 控制主应用跳转
baseRouter.主应用路由的方法 (...)

样式隔离

MicroApp的样式隔离是默认开启的,开启后会以标签作为样式作用域,利用标签的name属性为每个样式添加前缀,将子应用的样式影响禁锢在当前标签区域。

image.png

wujie(无界)

腾讯开源的微前端框架,采用 iframe 实现 js 沙箱,webcomponent 实现页面样式隔离

主应用中注册子应用

子应用配置

无界有三种运行模式:单例模式保活模式重建模式

其中保活模式重建模式子应用无需做任何改造工作,单例模式需要做生命周期改造:

// main.js
const router = createRouter({
    history: createWebHistory(),
    routes,
});

if (window.__POWERED_BY_WUJIE__) {
    let instance;
    window.__WUJIE_MOUNT = () = >{
        instance = createApp(App);
        instance.use(router);
        instance.mount("#app");
    };
    window.__WUJIE_UNMOUNT = () = >{
        instance.unmount();
    };
} else {
    createApp(App).use(router).mount("#app");
}

访问子应用静态资源

image.png

通信

  1. 主应用可以通过props注入数据和方法,子应用可以通过$wujie来获取:
// 主应用
setupApp({
    name: "sub-app-1",
    url: "http://localhost:3004",
    el: document.getElementById("sub-app-1"),
    props: {
        name: "job",
        age: 20,
    },
});

// 子应用
const props = window.$wujie ? .props;
  1. eventBus 通信
// 主应用
import {
    bus
}
from "wujie";

// 主应用监听事件
bus.$on("事件名字",
function(arg1, arg2, ...) {});
// 主应用发送事件
bus.$emit("事件名字", arg1, arg2, ...);
// 主应用取消事件监听
bus.$off("事件名字",
function(arg1, arg2, ...) {});

// 子应用
// 子应用监听事件
window.$wujie ? .bus.$on("事件名字",
function(arg1, arg2, ...) {});
// 子应用发送事件
window.$wujie ? .bus.$emit("事件名字", arg1, arg2, ...);
// 子应用取消事件监听
window.$wujie ? .bus.$off("事件名字",
function(arg1, arg2, ...) {});

路由跳转

以 history 模式主应用路由为例:

在主应用中通过 props 向子应用传递路由跳转方法,子应用中可以读取调用:

// 主应用
const router = userRouter();
setupApp({
    name: "sub-app-1",
    url: "http://localhost:3004",
    exec: true,
    alive: false,
    el: document.getElementById("sub-app-1"),
    sync: true,
    props: {
        jump: (path) = >router.push(path),
    },
});

// 子应用
// 跳转到子应用 B
function handleJump() {
    window.$wujie ? .props.jump({
        path: "/pathB"
    });
}
// 跳转到子应用 B 的指定页面
function handleJump() {
    window.$wujie ? .props.jump({
        path: "/pathB",
        query: {
            B: "/test"
        }
    });
}

样式隔离

采用 webcomponent 来实现页面的样式隔离,无界会创建一个wujie自定义元素,然后将子应用的完整结构渲染在内部

image.png

❌