Skip to main content

多页应用 MPA

icejs 默认的应用类型是 SPA 单页应用,但在某些业务场景下存在 MPA 多页应用的诉求,基于此 icejs 内置提供了 MPA 的方案。

工程配置#

在工程配置文件 build.json 中开启 MPA 应用配置:

{
"mpa": true
}

目录结构#

MPA 应用以页面为维度进行划分,符合 src/pages/*/[index|app].[ts|js] 的路径都会作为一个单独的 entry,整体结构:

├── build/
│ ├── js/
│ │ ├── dashboard.js
│ │ └── about.js
│ ├── css/
│ │ ├── dashboard.css
│ │ └── about.css
│ ├── dashboard.html
│ ├── about.html
│ └── favicon.png # Favicon
├── public/
+ │ ├── index.html # 默认 html
│ └── favicon.png # Favicon
├── src/ # 源码
- │ ├── app.js
- │ ├── routes.js
- │ ├── store.js
│ └── pages/ # 页面
│ ├── Dashboard/ # Dashboard 页面,一个完整的 SPA 页面
+ │ │ ├── app.js # 页面配置入口
│ │ └── routes.jsx # 路由配置入口
│ ├── About/ # About 页面,比较简单,直接渲染一个组件
+ │ │ └── index.jsx # 页面组件入口
│ └── Market / # Market 下没有 app 或者 index,不会作为一个 entry
│ └── market.jsx
├── build.json
├── package.json
└── tsconfig.json

如上结构,开启 mpa 之后将会包含 dashboard、about 两个 entry。

不同的 entry 应用类型#

pages 下的每个 entry 可以是一个单独的 SPA,也可以是简单的一个页面组件,具体可以结合业务情况使用。

SPA 类型的 entry#

SPA 类型的 entry 整体跟 icejs 的 SPA 应用基本接近,包含 app.js, routes.js 等文件。

目录结构:

├── src/pages
│ └── Dashboard/
+ │ ├── DashboardA/index.jsx
+ │ ├── DashboardB/index.jsx
+ │ ├── models/ // 如有状态管理相关诉求
+ │ ├── store.js // 如有状态管理相关诉求
+ │ ├── routes.js // 路由配置
+ │ └── app.js
├── build.json
├── package.json
└── tsconfig.json

应用入口:

// src/pages/Dashboard/app.js
import { runApp } from 'ice';
+ import store from './store';
+ import routes from './routes';
const appConfig = {
app: {
+ // 如有状态管理诉求,需要手动包裹 provider
+ addProvider: ({ children }) => {
+ return <store.Provider>{children}</store.Provider>;
+ },
},
+ router: {
+ // 需要手动引入 routes
+ routes
+ }
};
runApp(appConfig);

MPA 场景下不支持某个页面使用文件路由(约定路由),仅支持配置式路由

仅支持通过 src/routes 配置路由,不支持直接配置 router.routes: [{}]

接下来配置路由信息:

// src/pages/Dashboard/routes.js
+import DashboardA from '@/pages/Dashboard/DashboardA';
+import DashboardB from '@/pages/Dashboard/DashboardB';
export default [
{
path: '/foo',
component: DashboardA
},
{
path: '/bar',
component: DashboardB
},
]

组件类型的 entry#

如果只是渲染一个简单的组件/页面,直接在 index.tsx 中导出组件即可:

// src/pages/About/index.tsx
import React from 'react';
export default function About() {
return <>About 页面</>;
}

如果有 export default 那么框架会自动调用 ReactDOM.render 渲染组件,如果希望自行渲染组件的话则不需要通过 export default 导出组件。

指定不同 entry 的 HTML 模板#

默认情况下,entryName 为 dashboard 的入口会优先使用 public/dashboard.html 作为 HTML 模板,如果该文件不存在则会使用 public/index.html 兜底。

同时 icejs 也支持通过 template 字段更加灵活的指定 HTML 模板:

// build.json
{
"mpa": {
"template": {
"web.html": ["Dashboard", "Home"],
"about.html": ["About"]
}
}
}

注意:通过 entry 字段配置的多页应用不支持配置 mpa.template 字段。

高级用法#

调试时指定单个 entry#

默认 MPA 应用在开发阶段会启动所有页面,如果你只想调试某个页面,可以通过指定 --mpa-entry 来启动。

"scripts": {
"start": "icejs start --mpa-entry dashboard",
}

EJS 模版#

默认情况下 MPA 使用 public/index.html 作为 HTML 模版,如果存在一些简单的差异化渲染逻辑,可以通过 EJS 语法进行渲染。

可使用变量:

  • NODE_ENV:可选值为 development | production 用来区分 start / build 命令
  • pageName:当前渲染页面的页面名称,默认为 src/pages 目录下的一级目录名(默认小写)

html 中使用 EJS 语法:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" />
<meta name="viewport" content="width=device-width" />
<title>title</title>
</head>
<body>
<div>current env: <%= NODE_ENV %></div>
<% if (pageName === 'Home') { %>
<h2>Home page</h2>
<% } %>
<div id="ice-container"></div>
</body>
</html>

指定调试时浏览器默认打开的页面#

可以通过配置 openPage 指定多个页面时默认打开指定的页面。

"mpa": {
"openPage": "Home",
}

注意:通过 entry 字段配置的多页应用不支持配置 mpa.openPage 字段。

指定调试时的路径#

本地开发默认以 pages 下的目录名为调试路径,比如 src/pages/Dashboard 下的 MPA 页面,在调试时将默认在 http://localhost:3333/dashboard 进行调试。 如果希望调试路径和最终部署时的路径一致,可以通过配置 rewrites 参数:

"mpa": {
"rewrites": {
"dashboard": "site/dashboard"
}
}

上述的配置可以让 MPA 页面在 http://localhost:3333/site/dashboard 下进行调试,前端路由的 basename 可以同该路径保持一致,这样可以确保部署后无需额外针对 basename 进行的订正。

如果 MPA 页面中不耦合路由,则不需要关心 rewrite 逻辑

通过 entry 字段更加灵活的开启 MPA#

mpa: true 是 icejs 推荐的 MPA 最佳实践,但有一些场景可能会更加灵活,比如应用整体还是一个大的 SPA 应用,只是需要增加一个轻量的 entry,这时候可以直接在 build.json 中配置 entry 字段:

// build.json
{
"entry": {
"index": "src/app",
"login": "src/LoginEntry"
}
}

对应目录结构:

├── public/
├── src/
│ ├── layouts/
│ ├── pages/
│ ├── routes.js
│ ├── app.js
+ │ └── LoginEntry.jsx
├── build.json
├── package.json
└── tsconfig.json

LoginEntry.jsx 需要自行调用 ReactDOM.render() 渲染。