Umi MPA 模式完整指南:无路由多页面构建实践
【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi
Umi 内置的 MPA(多页面应用)模式将src/pages目录下的*/index.[jt]sx?文件直接作为 webpack 入口进行打包,不生成路由、历史记录与umi.js,适用于 H5 开发、小程序/插件开发等场景。本文基于 mpa.en-US.md 与仓库源码(mpa.ts、extractExports.ts),完整讲解 MPA 模式的配置项、约定式入口、页面级配置、模板定制与按需启动,并给出可直接运行的示例。
MPA 模式是什么
Umi 支持传统的 MPA 模式,在此模式下,src/pages目录中的*/index.[jt]sx?文件会被当作 webpack 的 entry 进行打包。与默认的 SPA 模式不同,MPA 模式:
- 不使用路由:没有路由表、
history,也没有运行时框架代码umi.js; - 页面即入口:每个页面目录就是一个独立的打包入口,构建产物为多个 HTML 文件;
- 更贴合传统多页场景:满足 H5 开发、浏览器插件(kitchen 插件)开发等"一页一入口"的需求。
从源码看,MPA 功能由 packages/preset-umi/src/features/mpa/mpa.ts 实现,通过api.EnableBy.config在用户配置了mpa后启用,启动时会在终端打印黄色警告[MPA] MPA Mode Enabled。
与 Umi 3 MPA 的区别
需要注意,Umi 4 的 MPA 与 Umi 3 的 MPA 是两种不同的实现:
- Umi 3 的 MPA:模拟路由渲染机制,本质上仍是路由驱动;
- Umi 4 的 MPA:是真正的 MPA,每个页面独立编译、独立输出 HTML,不依赖路由系统。
两者各有优劣。Umi 4 的 MPA 因为跳过了路由、SSR 等大量插件能力(例如在 configPlugins.ts 中,启用 MPA 后react-router、react-router-dom不再被注入 alias),所以只适合作为纯构建工具使用,不适合承载依赖路由能力的复杂应用。
快速启用 MPA
mpa是 Umi 内置功能,无需额外安装插件,通过配置即可开启:
// .umirc.ts export default { mpa: { template: string, getConfigFromEntryFile: boolean, layout: string, entry: object, }, }配置项说明:
| 配置项 | 类型 | 说明 |
|---|---|---|
template | string | 全局 HTML 模板路径,例如template/index.html,从项目根目录开始查找,使用对应路径的index.html作为产物 HTML 模板 |
getConfigFromEntryFile | boolean | 是否从每个页面的入口文件(src/*/index.tsx)中读取独立配置,开启后可免去config.json |
layout | string | 全局默认布局组件路径 |
entry | object | 针对每个入口文件的配置,例如{ foo: { title: '...' } }可配置src/foo/index.tsx页面的title属性 |
这些配置项在源码中的 schema 校验位于 mpa.ts,使用 zod 定义为template(string)、layout(string)、getConfigFromEntryFile(boolean)、entry(object),整体deepPartial,即所有字段均可选。
约定式入口文件
MPA 的默认入口文件是src/pages目录下的*/index.[jt]sx?文件。目录结构示例:
+ src/pages - foo/index.tsx - bar/index.tsx - hoo.tsx上面的结构中,hoo.tsx因为不是目录/index.tsx形式,不会被当作入口。扫描后生成的entry结构为:
{ foo: 'src/pages/foo/index.tsx', bar: 'src/pages/bar/index.tsx' }构建后,每个入口会生成对应的 HTML 文件,产物为foo.html和bar.html。
源码中入口收集逻辑位于 mpa.ts:遍历src/pages目录下的每个子目录,通过getIndexFile按index.tsx→index.ts→index.jsx→index.js的优先级查找入口文件;随后在modifyEntry钩子中删除默认的umi入口,并为每个页面注入mpa/${dir}/index.tsx临时入口;最后在chainWebpack中为每个入口注册一个独立的html-webpack-plugin,生成{entry.name}.html。
页面级配置
config.json
约定方式:在与入口文件同层目录放置config.json声明页面配置:
+ src/pages + foo - index.tsx - config.jsonfoo/config.json可为页面配置独立的layout和title:
{ "layout": "@/layouts/bar.ts", "title": "foooooo" }目前默认支持的页面级配置项包括:
- template:页面模板路径,写法参考 html-webpack-plugin,使用 lodash template 语法引用变量;
- layout:页面布局,建议引用 src 目录下的文件并以
@/开头; - title:页面标题,默认为入口文件所在目录名;
- mountElementId:页面渲染时挂载节点的 id,默认为
root。
源码中的getConfig(mpa.ts)会读取入口同层的config.json并调用checkConfig校验,校验规则(mpa.ts)包括:
layout必须是 string,且以@/或/开头;template、title必须是 string;head、scripts必须是数组。
getConfigFromEntryFile
Umi 还实验性地支持另一种配置读取方式:开启mpa: { getConfigFromEntryFile: true }后,可以不用config.json,直接在入口文件中通过export const config导出页面配置:
// src/pages/foo/index.tsx export const config = { layout: '@/layouts/bar.ts', title: 'foooooo', }其底层实现是 extractExports.ts:通过 esbuild 将入口文件打包为 CJS 格式,注入ret = x.config || {}的虚拟入口代码,再以eval提取config导出对象。这也解释了为什么该功能被称为"实验性"——它依赖 esbuild 对入口文件的独立打包,遇到复杂的依赖解析场景可能受限。
注意:getConfigFromEntryFile与config.json两种方式是互斥的,collectEntry中会根据开关二选一读取配置。
entry
也可以在.umirc.ts中直接配置每个页面:
export default { mpa: { entry: { foo: { title: 'foo title' } } } }源码中全局配置、页面配置文件与config.json的合并顺序为:{ ...globalConfig, ...config },即.umirc.ts的entry配置会被config.json/export const config覆盖;而title的最终取值优先级为globalConfig?.title || config.title || dir(mpa.ts),即显式配置优先,缺省时回退到目录名。
按需启动
支持通过环境变量MPA_FILTER指定要启动的页面,以提升构建速度:
# file .env # 只会启动 bar、foo 这两个页面 MPA_FILTER=bar,foo对应源码为filterEntry(mpa.ts):读取process.env.MPA_FILTER,以逗号分隔后与目录名匹配,未命中的目录直接跳过,不参与入口收集与编译。
渲染
MPA 默认渲染方式为 React,入口文件只需导出 React 组件即可被渲染,无需自己编写ReactDOM.render逻辑:
export default function Page() { return <div>Hello</div> }生成的临时渲染文件(mpa/${dir}/index.tsx)内容由 mpa.ts 动态拼接:引入App、按需引入Layout,并根据 React 版本选择渲染 API——React 18 及以上使用ReactDOM.createRoot(...).render(...)并从react-dom/client引入,React 17 及以下使用ReactDOM.render(...)并从react-dom引入。
默认启用 React 18。如果需要 React 17 的渲染方式,请在项目中安装 React 17 依赖,框架会自动适配 React 版本:
$ pnpm i react@17 react-dom@17自定义 HTML 模板
MPA 默认模板如下:
<!DOCTYPE html> <html> <head> <title><%= title %></title> </head> <body> <div id="<%= mountElementId %>"></div> </body> </html>通过template配置可以自定义全局 HTML 模板,也可以在页面级配置中为不同页面指定不同模板。请确保模板中至少包含<%= title %>和<%= mountElementId %>两个变量。
模板选择逻辑(mpa.ts)优先级为:页面级entry.template→ 全局api.config.mpa.template→ 内置默认模板(mpa/template.html)。模板路径会以api.cwd(项目根目录)为基准resolve。模板中的变量通过templateParameters: entry注入,因此页面配置中的title、mountElementId以及自定义字段(如description)都可以在模板中通过<%= %>引用。
仓库自带的示例 examples/mpa 展示了完整用法:templates/default.html中额外引用了<%= description %>变量;pages/foo/config.json配置了title;pages/foo/index.tsx同时导出了config(用于演示getConfigFromEntryFile)并使用useState验证 MPA 下 hooks 与 mfsu 的兼容性。示例的启动与构建命令为npm run dev与npm run build(见 examples/mpa/package.json)。
总结
Umi 4 的 MPA 模式是一个"真实的多页面"构建方案:每个页面独立入口、独立 HTML 产物,无路由、无 history、无umi.js,天然适合 H5、插件等传统多页场景。使用时需注意它跳过了大量依赖路由的插件能力,仅适合作为纯构建工具;同时它提供了config.json、export const config、全局entry三种页面配置方式,以及MPA_FILTER按需启动能力,配合可定制的 HTML 模板,足以覆盖从简单多页到复杂定制的大部分需求。
【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考