微前端

微前端是一种架构模式,它将 Web 应用分解为更小的、独立开发和部署的应用,这些应用协同工作。

¥Microfrontends are an architectural pattern where a web application is decomposed into smaller, independently developed and deployed applications that work together.

Turborepo 通过集成的代理服务器,为在开发过程中本地运行垂直微前端(有时称为 "zones")提供了内置支持。代理协调多个应用并在它们之间路由流量。

¥Turborepo provides built-in support for running vertical microfrontends (sometimes called "zones") locally during development through an integrated proxy server. The proxy coordinates multiple applications and routes traffic between them.

假设你有一个包含多个前端应用的单体仓库:

¥Let's imagine you have a monorepo with multiple frontend applications:

package.json
turbo.json

在生产环境中,这些应用可以单独部署,然后使用反向代理或边缘路由组合在一起。但在开发过程中,你需要:

¥In production, these applications might be deployed separately and composed together using a reverse proxy or edge routing. But during development, you want to:

  • 使用单个命令同时运行所有应用

    ¥Run all applications simultaneously with a single command

  • 通过统一 URL(例如 http://localhost:3024)访问它们

    ¥Access them through a unified URL (e.g., http://localhost:3024)

  • 根据路径模式将请求路由到正确的应用

    ¥Route requests to the correct application based on path patterns

  • 支持热模块重载和 WebSocket 连接。

    ¥Support hot module reloading and WebSocket connections

  • 避免端口冲突和手动协调

    ¥Avoid port conflicts and manual coordination

入门

¥Getting started

Turborepo 提供了一个内置代理服务器,可在开发过程中自动路由微前端应用之间的流量。代理读取 microfrontends.json 配置文件,并在你运行 turbo dev 时启动。

¥Turborepo provides a built-in proxy server that automatically routes traffic between your microfrontend applications during development. The proxy reads a microfrontends.json configuration file and starts when you run turbo dev.

你可以尝试使用 npx create-turbo@latest -e with-microfrontends 创建的 monorepo 执行以下说明。

¥You can try the following instructions with the monorepo created by npx create-turbo@latest -e with-microfrontends.

创建 microfrontends.json

¥Create microfrontends.json

在父应用中创建 microfrontends.json 文件。当没有其他应用匹配时,所有请求都将回退到此应用。

¥Create a microfrontends.json file in your parent application. This application is the one that all requests will fall through to when not matched by another application.

./apps/web/microfrontends.json
{
  "$schema": "https://turbo.nodejs.cn/microfrontends/schema.json",
  "applications": {
    "web": {
      "development": {
        "local": {
          "port": 3000
        }
      }
    },
    "docs": {
      "development": {
        "local": {
          "port": 3001
        }
      },
      "routing": [
        {
          "paths": ["/docs", "/docs/:path*"]
        }
      ]
    }
  }
}

设置应用端口

¥Set application ports

接下来,使用 turbo get-mfe-port 命令在应用的开发脚本中设置端口。此命令在运行 turbo dev 时注入端口号:

¥Next, use the turbo get-mfe-port command to set the ports in your application's development scripts. This command injects the port number when running turbo dev:

./apps/web/package.json
{
  "scripts": {
    "dev": "next dev --port $(turbo get-mfe-port)"
  }
}

处理基本路径

¥Handle base paths

如果你使用的是框架,请根据框架的要求设置基本路径。

¥If you're using a framework, set the base path according to the needs of the framework.

./apps/my-app/next.config.ts
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  basePath: '/docs',
};
 
export default nextConfig;

有关框架特定指南,请参阅 访问框架的微前端部分

¥Visit the Microfrontends section of your framework for framework-specific guidance.

运行 turbo dev

¥Run turbo dev

运行 turbo dev 时,Turborepo 将执行以下操作:

¥When you run turbo dev, Turborepo will:

  1. 在配置的端口上启动代理服务器(默认端口:3024)。

    ¥Start the proxy server on the configured port (default: 3024)

  2. 在任务中注入 TURBO_MFE_PORT 环境变量以设置应用的端口

    ¥Inject the TURBO_MFE_PORT environment variable in tasks to set the applications' port

  3. 为所有已配置的应用运行开发任务

    ¥Run development tasks for all configured applications

  4. 根据路径模式将传入请求路由到相应的应用

    ¥Route incoming requests based on path patterns to the appropriate application

现在,你可以通过 http://localhost:3024 访问所有微前端应用。

¥Now you can access all of your microfrontends applications at http://localhost:3024.

配置

¥Configuration

applications 对象中的每个应用都可以具有以下属性:

¥Each application in the applications object can have the following properties:

如果未提供,则使用应用密钥来匹配 package.json 中的名称。

¥If not provided, the application key is used to match the name in `package.json.

development.local

应用在本地运行的端口。

¥The port where the application runs locally.

./apps/web/microfrontends.json
{
  "development": {
    "local": {
      "port": 3000
    }
  }
}

如果省略端口,Turborepo 将根据应用名称生成一个确定的端口(介于 3000 到 8000 之间)。

¥If you omit the port, Turborepo will generate a deterministic port based on the application name (between 3000-8000).

development.fallback

可选地,在应用未在本地运行时,提供要代理的目标。这最常用于将请求路由到生产环境,从而使像 turbo dev --filter=web 这样仅运行部分应用的命令获得无缝体验。

¥Optionally provide a target to proxy to when an application is not running locally. This is most frequently used to route to production to make commands like turbo dev --filter=web that only run a subset of applications a seamless experience.

./apps/web/microfrontends.json
{
  "development": {
    "fallback": "example.com"
  }
}

routing

应路由到此应用的路径组数组。如果没有提供路由,则应用将成为默认应用,捕获所有未匹配的路由。

¥An array of path groups that should route to this application. If no routing is provided, the application becomes the default application that catches all unmatched routes.

只能有一个应用没有 routing 配置。这是你的 "root" 应用,用于处理所有其他应用未匹配的路由。

¥Only one application can have no routing configuration. This is your "root" application that handles all routes not matched by other applications.

./apps/web/microfrontends.json
{
  "routing": [
    {
      "paths": ["/api/:version/users/:id"]
    },
    {
      "paths": ["/docs", "/docs/:path*"]
    }
  ]
}
  • 路径区分大小写:/Blog/blog 是不同的路由

    ¥Paths are case-sensitive: /Blog and /blog are different routes

  • 尾部斜杠已规范化:/home/home/ 匹配同一路由

    ¥Trailing slashes are normalized: /home and /home/ match the same route

精确匹配

¥Exact matches

./apps/web/microfrontends.json
{
  "paths": ["/pricing", "/about", "/contact"]
}

这些路径与原文完全一致。

¥These paths match exactly as written.

参数

¥Parameters

./apps/web/microfrontends.json
{
  "paths": ["/blog/:slug", "/users/:id/profile"]
}

: 开头的段匹配任何单个路径段。例如,/blog/:slug 匹配 /blog/hello,但不匹配 /blog/hello/world

¥Segments starting with : match any single path segment. For example, /blog/:slug matches /blog/hello but not /blog/hello/world.

通配符

¥Wildcards

./apps/web/microfrontends.json
{
  "paths": ["/docs/:path*", "/api/:version*"]
}

* 结尾的参数匹配零个或多个路径段。例如,/docs/:path* 匹配 /docs/docs/intro/docs/api/reference

¥Parameters ending with * match zero or more path segments. For example, /docs/:path* matches /docs, /docs/intro, and /docs/api/reference.

你还可以使用 + 修饰符来匹配一个或多个段(不包括空路径):

¥You can also use the + modifier to match one or more segments (excluding the empty path):

./apps/web/microfrontends.json
{
  "paths": ["/api/:path+"]
}

这匹配 /api/users/api/users/123,但不匹配 /api/api/

¥This matches /api/users and /api/users/123, but not /api or /api/.

复杂的路由模式

¥Complex routing patterns

你可以使用嵌套参数定义复杂的路由:

¥You can define sophisticated routing with nested parameters:

./apps/web/microfrontends.json
{
  "routing": [
    {
      "paths": [
        "/api/:version/users",
        "/api/:version/users/:id",
        "/api/:version/posts/:postId/comments/:commentId"
      ]
    }
  ]
}

分组标签

¥Group labels

将路由组织成逻辑组,以提高可维护性:

¥Organize routes into logical groups for better maintainability:

./apps/web/microfrontends.json
{
  "$schema": "https://turbo.nodejs.cn/microfrontends/schema.json",
  "applications": {
    "marketing": {
      "development": {
        "local": 3002
      },
      "routing": [
        {
          "group": "blog",
          "paths": ["/blog", "/blog/:slug*"]
        },
        {
          "group": "sales",
          "paths": ["/pricing", "/contact", "/demo"]
        }
      ]
    }
  }
}

group 字段用于组织管理,不影响路由行为。

¥The group field is for organizational purposes and doesn't affect routing behavior.

packageName

可选地,使用工作区中 package.json 中的包名称。

¥Optionally use the name of the package from package.json in your workspace.

./apps/main/microfrontends.json
{
  "applications": {
    "main-site": {
      "packageName": "web"
    }
  }
}

options.localProxyPort

可选地,使用 localProxyPort 选项更改代理端口

¥Optionally change the proxy port using the localProxyPort option

默认为 3024

¥Defaults to 3024.

./apps/web/microfrontends.json
{
  "options": {
    "localProxyPort": 8080
  }
}

与生产环境集成

¥Integrating with production

Turborepo 微前端代理仅供本地使用。你如何实现和集成生产微前端取决于你的生产基础架构。但是,我们可以集成你的本地环境和生产环境,从而创建跨环境的无缝体验。

¥The Turborepo microfrontends proxy is meant for local usage only. How you implement and integrate your production microfrontends depends on your production infrastructure. However, we can integrate your local and production environments to create a seamless experience across environments.

首先,我们构建了 Turborepo 的本地代理,以便与 Vercel 的微前端集成。我们期待与任何有意集成 Vercel 的基础架构提供商合作。

¥To start, we've built Turborepo's local proxy to integrate with Vercel's microfrontends. We look forward to working with any infrastructure providers that would also like to integrate.

Vercel 上的微前端

¥Microfrontends on Vercel

Vercel 原生支持以下微前端:

¥Vercel has native support for microfrontends that provide:

  • 增强性能的生产代理实现

    ¥Production proxy implementation with enhance performance

  • 跨环境的回退 URL 支持。

    ¥Fallback URL support across environments

  • Vercel 工具栏集成

    ¥Vercel toolbar integration

  • 功能标志支持

    ¥Feature flag support

  • 资源前缀处理

    ¥Asset prefix handling

Vercel 文档:了解更多信息

¥Learn more in Vercel's documentation.

迁移到 @vercel/microfrontends

¥Migrating to @vercel/microfrontends

如果在任何包中安装 @vercel/microfrontends 或将其添加到你的工作区,Turborepo 将自动使用它,而不是使用内置代理。这允许逐步迁移。

¥If you install @vercel/microfrontends in any package or add it to your workspace, Turborepo will automatically defer to it instead of using the built-in proxy. This allows for a gradual migration path.

你可以使用与 Turborepo 相同的 microfrontends.json 配置。Turborepo 的 microfrontends.json 架构是 Vercel 架构的子集,因此与 @vercel/microfrontends 兼容。

¥You can use the same microfrontends.json configuration as for Turborepo. Turborepo's microfrontends.json schema is a subset of Vercel's schema, so it is compatible with @vercel/microfrontends.

了解更多关于 @vercel/microfrontends访问 npm 上的软件包 的信息。

¥To learn more about @vercel/microfrontends, visit the package on npm.

故障排除

¥Troubleshooting

端口已被占用

¥Port already in use

默认情况下,微前端代理将尝试使用端口 3024。如果你已将该端口用于其他用途,则可以使用 options.localProxyPort 更改 Turborepo 的端口。

¥By default, the microfrontends proxy will try to use port 3024. If you already use that port for a different purpose, you can change Turborepo's port using the options.localProxyPort.

缺少 CSS、图片或其他资源,或路由不匹配

¥Missing CSS, images, or other assets, or routes not matching

确保微前端在其 routing 配置 中匹配的路径包含资源的路由。检查你的网络选项卡,查找与预期匹配或不匹配的路径。

¥Ensure that the paths that the microfrontends matches for in its routing configuration include the routes for the assets. Check your network tab to find paths that are or aren't matching as expected.

¥Links across applications causing errors

如果使用单页应用 (SPA) 链接(例如 Next.js <Link> 组件)进行跨应用链接,则可能会导致错误。即使端口和域名保持不变,应用也是不同的。这意味着根据定义,该路由不是 "单页应用"。

¥If you use a Single-Page Application (SPA) link (like the Next.js <Link> component) to link across applications, this can result in errors. Even though the port and domain remain the same, the applications are different. This means the routing is, by definition, not a "single-page application".

访问代理端口未重定向

¥Visiting a proxied port does not redirect

你仍然可以直接访问代理端口。例如,如果 localhost:3000 被代理到 localhost:3042,你仍然可以在浏览器中访问 localhost:3000

¥You are still be able to reach a proxied port directly. For example, if localhost:3000 is proxied to localhost:3042, you can still visit localhost:3000 in your browser.

如果你希望 localhost:3000 重定向到 localhost:3024,则必须在应用中手动设置。

¥If you would like for localhost:3000 to redirect to localhost:3024, you must set this up manually in your application.

应用无法启动

¥Applications not starting

验证以下内容:

¥Verify that:

  1. packageName 与你的实际包名匹配。

    ¥The packageName matches your actual package name

  2. 指定的 task 存在于包的 package.json 中。

    ¥The task specified exists in the package's package.json

  3. 每个应用的端口都可用。

    ¥Each application's port is available

  4. 所有依赖均已安装

    ¥All dependencies are installed

turbo get-mfe-port 无法正常工作

¥turbo get-mfe-port not working

如果在运行 turbo get-mfe-port 时遇到错误,请确保:

¥If you're getting an error when running turbo get-mfe-port, ensure that:

  1. 你正在从包目录(而非仓库根目录)运行命令。

    ¥You're running the command from a package directory (not the repository root)

  2. 包的 package.json 中包含 name 字段。

    ¥The package has a name field in its package.json

  3. 某个包中存在 microfrontends.json 文件

    ¥A microfrontends.json file exists in one of the packages

  4. 当前包列在 microfrontends.jsonapplications 部分。

    ¥The current package is listed in the applications section of microfrontends.json

  5. 如果使用 --skip-infer,则还必须指定指向仓库根目录的 --cwd

    ¥If using --skip-infer, you must also specify --cwd pointing to your repository root:

Terminal
turbo --skip-infer --cwd ../.. get-mfe-port

完整示例

¥Complete example

以下是一个完整的电子商务平台微前端配置示例:

¥Here's a full example of a microfrontends configuration for an e-commerce platform:

./apps/web/microfrontends.json
{
  "$schema": "https://turbo.nodejs.cn/microfrontends/schema.json",
  "options": {
    "localProxyPort": 3024
  },
  "applications": {
    "web": {
      "packageName": "web",
      "development": {
        "local": {
          "port": 3000
        }
      }
    },
    "docs": {
      "packageName": "documentation",
      "development": {
        "local": {
          "port": 3001
        }
      },
      "routing": [
        {
          "group": "documentation",
          "paths": [
            "/docs",
            "/docs/:path*",
            "/api-reference",
            "/api-reference/:path*"
          ]
        }
      ]
    },
    "blog": {
      "development": {
        "local": {
          "port": 3002
        }
      },
      "routing": [
        {
          "group": "content",
          "paths": [
            "/blog",
            "/blog/:slug",
            "/blog/category/:category",
            "/authors/:author"
          ]
        }
      ]
    },
    "shop": {
      "development": {
        "local": {
          "port": 3003
        }
      },
      "routing": [
        {
          "group": "commerce",
          "paths": [
            "/products",
            "/products/:id",
            "/cart",
            "/checkout",
            "/orders/:orderId"
          ]
        }
      ]
    }
  }
}

使用此配置:

¥With this configuration:

  • web 应用处理首页和任何未匹配的路由。

    ¥The web app handles the homepage and any unmatched routes

  • docs 应用处理所有文档。

    ¥The docs app handles all documentation

  • blog 应用处理博客文章和作者页面。

    ¥The blog app handles blog posts and author pages

  • shop 应用处理电子商务功能。

    ¥The shop app handles e-commerce functionality