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

工程配置

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

  1. {
  2. "mpa": true
  3. }

目录结构

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

  1. ├── build/
  2. ├── js/
  3. ├── dashboard.js
  4. └── about.js
  5. ├── css/
  6. ├── dashboard.css
  7. └── about.css
  8. ├── dashboard.html
  9. ├── about.html
  10. └── favicon.png # Favicon
  11. ├── public/
  12. + ├── index.html # 默认 html
  13. └── favicon.png # Favicon
  14. ├── src/ # 源码
  15. - ├── app.js
  16. - ├── routes.js
  17. - ├── store.js
  18. └── pages/ # 页面
  19. ├── Dashboard/ # Dashboard 页面,一个完整的 SPA 页面
  20. + ├── app.js # 页面配置入口
  21. └── routes.jsx # 路由配置入口
  22. ├── About/ # About 页面,比较简单,直接渲染一个组件
  23. + └── index.jsx # 页面组件入口
  24. └── Market / # Market 下没有 app 或者 index,不会作为一个 entry
  25. └── market.jsx
  26. ├── build.json
  27. ├── package.json
  28. └── tsconfig.json

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

不同的页面 entry 类型

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

SPA 类型的 entry

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

目录结构:

  1. ├── src/pages
  2. └── Dashboard/
  3. + ├── DashboardA/index.jsx
  4. + ├── DashboardB/index.jsx
  5. + ├── models/ // 如有状态管理相关诉求
  6. + ├── store.js // 如有状态管理相关诉求
  7. + ├── routes.js // 路由配置
  8. + └── app.js
  9. ├── build.json
  10. ├── package.json
  11. └── tsconfig.json

应用入口:

  1. // src/pages/Dashboard/app.js
  2. import { runApp } from 'ice';
  3. + import store from './store';
  4. + import routes from './routes';
  5. const appConfig = {
  6. app: {
  7. + // 如有状态管理诉求,需要手动包裹 provider
  8. + addProvider: ({ children }) => {
  9. + return <store.Provider>{children}</store.Provider>;
  10. + },
  11. },
  12. + router: {
  13. + // 需要手动引入 routes
  14. + routes
  15. + }
  16. };
  17. runApp(appConfig);

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

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

接下来配置路由信息:

  1. // src/pages/Dashboard/routes.js
  2. +import DashboardA from '@/pages/Dashboard/DashboardA';
  3. +import DashboardB from '@/pages/Dashboard/DashboardB';
  4. export default [
  5. {
  6. path: '/foo',
  7. component: DashboardA
  8. },
  9. {
  10. path: '/bar',
  11. component: DashboardB
  12. },
  13. ]

组件类型的 entry

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

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

如果有 export default 的语法,那么框架会自动包裹 runApp() 进行页面渲染,包裹后的伪代码如下:

  1. // .ice/entries/about/index.tsx
  2. import { runApp } from 'ice';
  3. import About from '@/pages/About';
  4. runApp({
  5. app: {
  6. renderComponnet() {
  7. return <About />;
  8. }
  9. }
  10. })

自定义渲染入口

如果不希望框架自动包裹 runApp(),那么不要通过 export default 导出组件,直接使用 ReactDOM.render() 渲染即可。

  1. // src/pages/About/index.tsx
  2. import React from 'react';
  3. function About() {
  4. return <>About 页面</>;
  5. }
  6. ReactDOM.render(<About />, document.getElementById('ice-container'));

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

高级用法

调试时指定单个 entry

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

  1. "scripts": {
  2. "start": "icejs start --mpa-entry dashboard",
  3. }

通过 ejs 语法定制不同页面的 html 内容

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

ejs 可使用的模板变量:

  • NODE_ENV:可选值为 development | production 用来区分 start / build 命令
  • pageName:当前渲染页面的页面名称,默认为 src/pages 目录下的一级目录名(默认小写)
  1. <!-- public/index.html -->
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta charset="utf-8" />
  6. <meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" />
  7. <meta name="viewport" content="width=device-width" />
  8. <title>title</title>
  9. </head>
  10. <body>
  11. <div>current env: <%= NODE_ENV %></div>
  12. <% if (pageName === 'Home') { %>
  13. <h2>Home page</h2>
  14. <% } %>
  15. <div id="ice-container"></div>
  16. </body>
  17. </html>

指定不同 entry 的 HTML 模板

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

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

  1. // build.json
  2. {
  3. "mpa": {
  4. "template": {
  5. "web.html": ["Dashboard", "Home"],
  6. "about.html": ["About"]
  7. }
  8. }
  9. }

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

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

  1. "mpa": {
  2. "openPage": "Home",
  3. }

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

指定调试时的路径

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

  1. "mpa": {
  2. "rewrites": {
  3. "dashboard": "site/dashboard"
  4. }
  5. }

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

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

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

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

  1. // build.json
  2. {
  3. "entry": {
  4. "index": "src/app",
  5. "login": "src/LoginEntry"
  6. }
  7. }

对应目录结构:

  1. ├── public/
  2. ├── src/
  3. ├── layouts/
  4. ├── pages/
  5. ├── routes.js
  6. ├── app.js
  7. + └── LoginEntry.jsx
  8. ├── build.json
  9. ├── package.json
  10. └── tsconfig.json

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