utquidem
Version:
The meta-framework suite designed from scratch for frontend-focused modern web development.
426 lines (294 loc) • 14.5 kB
Markdown
---
sidebar_position: 2
---
# 开发移动页面
本章将介绍如何使用 Modern.js,进行移动页面的开发。本章对应的代码仓库地址在[这里查看](https://github.com/modern-js-dev/modern-js-examples/tree/main/quick-start/mobile-pages)。
通过本章你可以了解到:
- 如何创建一个移动端网页项目。
- 如何为项目创建新入口。
- 如何使用 Modern.js 配置选项。
- 如何使用组件样式。
- 如何开发和使用 BFF API。
- 如何配置项目所需的静态资源、自定义 HTML。
- 如何使用测试功能。
## 环境准备
import EnvPrepare from '@site/docs/components/env-prepare.md';
<EnvPrepare/>
## 创建项目
使用 `@modern-js/create` 创建新项目,运行命令如下:
```bash
npx @modern-js/create mobile-pages
```
:::info 注
`mobile-pages` 为创建的项目名。
:::
按照如下选择,生成项目:
```bash
? 请选择你想创建的工程类型: 应用
? 请选择开发语言: TS
? 请选择包管理工具: pnpm
? 是否需要支持以下类型应用: 不需要
? 是否需要调整默认配置: 否
```
## 开发调试
进入项目根目录, 之后执行 `pnpm run dev` 即可启动开发服务器:
```bash
# 进入项目根目录
cd mobile-pages
# 启动开发服务器
pnpm run dev
```
浏览器中访问 `http://localhost:8080`,可以看到应用已经正常启动。
修改 `src/pages/index.tsx` 会触发重新编译和热更新,浏览器中页面会自动展示对应变化。
### Unbundled 开发模式
import DevUnbundle from '@site/docs/components/dev-unbundle.md'
<DevUnbundle/>
:::info 注
Unbundled 模式暂不支持在 Windows 平台使用,支持即将上线。
:::
### IDE 支持
import DevIDE from '@site/docs/components/dev-ide.md'
<DevIDE/>
## 创建入口
在 Modern.js 中,一个[入口](/docs/guides/tutorials/c07-app-entry/7.1-intro),经过构建后会生成一个对应的 HTML 文件。默认生成的项目只包含一个入口,而一个移动项目通常包含多个独立页面,现在我们创建一个新入口。
在项目根目录下,执行 `pnpm run new`,进行如下选择:
```bash
? 请选择你想要的操作: 创建工程元素
? 创建工程元素: 新建「应用入口」
? 填写入口名称: activity
? 是否修改默认的应用入口配置:否
```
创建完成,项目的 `src/` 目录结构如下:
```md
.
├── src/
│ ├── activity/
│ │ └── App.tsx
│ ├── mobile-pages/
│ │ └── App.tsx
│ ├── .eslintrc.json
```
其中, `activity/` 目录对应新建的入口,项目默认的入口(主入口)代码被移动到 `mobile-pages/` 目录下。
:::info 注
使用生成器将应用从单入口转换成多入口时,原本主入口的代码将会被移动到与当前应用 `package.json` 同名的目录下。
:::
重新启动应用,控制台会输出不同入口对应的访问地址。默认情况下,主入口对应的访问地址为 **{域名根路径}**,其他入口对应的访问地址为 **{域名根路径}/{入口名称}**,如下所示:
```bash
App running at:
> Local:
activity http://localhost:8080/activity
mobile-pages http://localhost:8080/
```
:::info 补充信息
更多信息,请参考【[添加应用入口](/docs/guides/tutorials/c07-app-entry/7.2-add-entry-in-cli)】。
:::
## 关闭客户端路由和状态管理
默认生成的项目是开启客户端路由和状态管理功能的,而一个移动项目一般都是由多个相对简单的独立页面组成,不涉及客户端路由和复杂的状态管理,此时我们可以关闭客户端路由和状态管理功能,以减少项目打包后的体积大小,提高页面性能。
我们打开项目根路径下的 `modern.config.js`,目前应该是这样的:
```js title="modern.config.js"
export default defineConfig({
runtime: {
state: true,
router: true,
},
});
```
然后我们将 `state` 和 `router` 这两个配置项设置为 `false`,就关闭了客户端路由和状态管理功能。
:::info 补充信息
更多用法,请参考【[`state`](/docs/apis/config/runtime/state)】、【[`router`](/docs/apis/config/runtime/router)】。
:::
## 组件样式
### Utility Class
Utility Class 是对通用功能(如文字居中、水平对齐等)涉及的 CSS 规则的封装。开发者可以直接使用这些 CSS Class 进行样式设置。
Modern.js 集成了主流的 Utility Class 解决方案 —— [Tailwind CSS](https://tailwindcss.com/),我们利用 [Tailwind CSS](https://tailwindcss.com/)
设置组件的通用样式。
首先,需要开启 Tailwind CSS 支持,在项目根目录下执行 `pnpm run new`,进行如下选择:
```bash
? 请选择你想要的操作: 启用可选功能
? 启用可选功能: 启用 Tailwind CSS 支持
```
然后在入口的根组件,这里我们以主入口为例,在 `src/mobile-pages/App.tsx` 添加如下代码:
```js title="src/mobile-pages/App.tsx"
import 'tailwindcss/base.css';
import 'tailwindcss/components.css';
import 'tailwindcss/utilities.css';
```
这样我们就可以在主入口下的组件中使用 Tailwind CSS 提供的 Utility Class 了。我们使用 `text-center` 这个 Class,为 `App.tsx` 中的文字设置居中效果:
```js title="src/mobile-pages/App.tsx"
const App: React.FC = () => (
<div className="text-center">Hello, Modern.js!</div>
);
```
浏览器访问 `http://localhost:8080`,会发现 `Hello, Modern.js!` 已经居中显示了。
:::info 补充信息
关于 Tailwind CSS 使用的更多介绍,请参考【[Tailwind CSS](/docs/guides/usages/css/tailwindcss)】。
:::
### CSS in JS
组件有时候需要设置自己的特有样式,这时候使用 Utility Class 就不是很方便了。我们可以用 CSS in JS 的写法直接在组件的 JS 代码中编写特有样式。Modern.js 通过集成 [styled-components](https://styled-components.com/) 提供 CSS in JS 的支持。
接下来,我们使用 CSS in JS 实现 `activity` 入口组件样式。
首先,我们在 `src/activity/App.tsx` 里修改顶部的代码,引入 `styled` 模块:
```js
import styled from '@modern-js/runtime/styled';
```
`styled` 模块的使用方式同 [styled-components](https://styled-components.com/) 一致,我们可以定义如下用于给页面标题添加样式的组件:
```js
const TitleWrapper = styled.div`
font-size: 2rem;
text-align: center;
margin: 0.5rem 0;
color: goldenrod;
`;
```
此时,`src/activity/App.tsx` 的完整代码如下:
```js title="src/activity/App.tsx"
import styled from '@modern-js/runtime/styled';
const TitleWrapper = styled.div`
font-size: 2rem;
text-align: center;
margin: 0.5rem 0;
color: goldenrod;
`;
const App: React.FC = () => {
return (
<div>
<TitleWrapper>Promotion Campaign</TitleWrapper>
</div>
);
};
export default App;
```
:::info 注
上面的示例也可以通过 Tailwind CSS 提供的 Utility Class 实现,这里仅用于举例说明 CSS in JS 的使用方式。通常,Utility Class + CSS in JS 的方案可以满足绝大多数项目的样式编写需求。
:::
:::info 补充信息
关于组件样式的更多用法,请参考【[CSS 开发方案](/docs/guides/usages/css/css-in-js)】。
:::
## 客户端兼容性
Modern.js 提供自动 Polyfill、Browserslist 配置、差异化分发等特性,能够最大化的兼顾客户端兼容性和资源加载性能。关于这部分内容的更多介绍,请参考【[客户端兼容性](/docs/guides/usages/basic-configuration/compatibility)】。
## 一体化 BFF
现在 `activity` 入口页面还没有数据,我们可以通过服务端 API 动态获取数据(商品列表数据)。服务端 API 地址为:<https://lf3-static.bytednsdoc.com/obj/eden-cn/beeh7uvzhq/products.json>。
但这个 API 并不是专门为当前项目提供的,部署也是在一个独立的域名下。
通常情况下,项目需要创建一个和当前项目部署在同一域名下的专属 API,并在这个 API 内部去调用原始数据获取,并进行裁剪聚合等。在前端,这样的需求通常使用 BFF 层来实现。
Modern.js 提供了开箱即用的 BFF 能力,支持和前端代码共同开发、调试、部署。
:::info 注
如果已经具备了为前端项目专门开发的、部署在同域下的 API,则不需要再创建 BFF 层,前端代码直接调用 API 即可。
:::
我们执行 `pnpm run new` 来开启 BFF 功能:
import LaunchBFFChoices from '@site/docs/components/launch-bff-choices.md';
<LaunchBFFChoices />
执行完成后,项目中新增了 `api/` 目录,添加 `api/products.ts` 文件,实现对数据获取 API 的调用(需要先安装 axios 依赖):
```js
import axios from 'axios';
export default async (): Promise<
{ id: string, name: string, price: number }[],
> => {
const res = await axios.get(
'https://lf3-static.bytednsdoc.com/obj/eden-cn/beeh7uvzhq/products.json',
);
return res.data;
};
```
重新执行 `dev` 命令,我们已经可以访问 `http://localhost:8080/api/products`,并成功获取推荐的商品数据。
接下来修改 `src/activity/App.tsx`,调用 BFF API 来获取数据。
通常情况下,组件代码中通过 `axios`,执行请求地址来获取数据。但是 Modern.js 提供了一种更加简洁的方式,可以像使用函数一样来调用 API,关键代码如下:
```js title="src/activity/App.tsx"
import React, {useEffect, useState} from 'react';
import products from '@api/products';
const App: React.FC = () => {
const [data, setData] = useState([]);
useEffect(() => {
const load = async () => {
const _data = await products();
setData(_data);
};
load();
}, []);
// ...
```
我们从 `@api/products` 路径下导入 `products`,然后直接把 `products` 作为函数调用,即等价于通过 `axios` 调用 API 获取数据的效果,这就是一体化 BFF。
渲染商品列表数据的关键代码如下:
```js title="src/activity/App.tsx"
// ...
const App: React.FC = () => {
const [data, setData] = useState([]);
// ...
return (
<div>
<TitleWrapper>Promotion Campaign</TitleWrapper>
<div>
{data.map(item => {
const { id, name, price } = item;
return (
<ItemWrapper key={id}>
<span>{name}</span>
<span>${price}</span>
</ItemWrapper>
);
})}
</div>
</div>
);
};
export default App;
```
:::info 补充信息
利用一体化 BFF,我们可以实现以往需要专门的服务端项目支持才能实现的需求,如集成微信 JS SDK 时的权限验证逻辑。更多信息请参考【[一体化 BFF](/docs/guides/features/server-side/bff/function)】。
:::
## 配置 Favicon 等通用静态资源
在 `config/` 目录下,添加 `favicon.ico` 图标文件,构建生成的 HTML 页面将包含 favicon 信息。
在 `config/public` 目录下,可以放置任意格式的静态资源化文件,文件会被 Serve 在应用同域名下。
在微信网页开发时,[绑定 JS 接口安全域名](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#2)需要上传一个微信提供的 txt 文件到自己的 Web 服务器,并保证文件可以被正常访问。
我们可以把这个文件放到 `config/public` 目录,然后访问 **{域名}/{文件名}**就可以获取文件内容。例如,我们在该目录下新建 `MP_verify_abcdef.txt` 文件,重新启动应用,访问 `http://localhost:8080/MP_verify_abcdef.txt` 即可获取文件内容。
:::info 补充信息
关于静态资源的更多使用方法,请参考配置 【[`config/public`](/docs/apis/hooks/mwa/config/public)】。
:::
## 自定义 HTML
Modern.js 支持修改默认使用的 HTML 模板文件。
例如,当我们需要集成 Google Analytics 等工具,统计分析用户的行为信息。我们可以通过自定义项目使用的 HTML 模板,在模板中添加相关工具的 JS SDK 引用代码,实现集成。
以 Google Analytics 为例,我们新建 `config/html/top.html` 文件,内容如下:
```js
<!-- Global Site Tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
</script>
```
在构建生成的 HTML 页面 `<head>` 标签内,将包含上面的代码,如下图所示:

:::info 补充信息
更多信息,请参考【[自定义 HTML 模板](/docs/guides/usages/basic-configuration/html)】。
:::
## 测试
Modern.js 内置 [Jest](https://jestjs.io/) 、[Testing Library](https://testing-library.com/) 等测试库/框架,提供单元测试、组件/页面集成测试、业务模型 Model 测试等功能。
使用测试功能,需要先开启该功能。在项目根目录下,执行 `pnpm run new`,进行如下选择:
```bash
? 请选择你想要的操作: 启用可选功能
? 启用可选功能: 启用「单元测试 / 集成测试」功能
```
安装依赖后,可以看到 package.json 中新增了一项依赖:
```bash
+ @modern-js/plugin-testing
```
这一插件提供了 Testing API 用于页面集成测试,我们以主入口为例,演示页面的集成测试。
新建 `src/mobile-pages/__tests__/` 目录,用于放置测试用例,并编写测试用例 `App.test.tsx`:
```js title="App.test.tsx" {1,6-7}
import { renderApp } from '@modern-js/runtime/testing';
import App from '../App';
describe('main entry', () => {
it('should have contents', () => {
const { getByText } = renderApp(<App />);
expect(getByText('Hello, Modern.js !')).toBeInTheDocument();
});
});
```
默认情况下,`src/` 目录下文件名匹配规则 `*.test.(t|j)sx?` 的文件都会被识别为测试用例。
执行 `pnpm run test`,会运行项目下的所有测试用例。
:::info 补充信息
更多用法,请参考【[Testing API](/docs/apis/runtime/testing/render)】。
:::
## 部署
import Deploy from '@site/docs/components/deploy.md';
<Deploy/>