跳转到主文档

开发移动页面

本章将介绍如何使用 Modern.js,进行移动页面的开发。本章对应的代码仓库地址在这里查看

通过本章你可以了解到:

  • 如何创建一个移动端网页项目。
  • 如何为项目创建新入口。
  • 如何使用 Modern.js 配置选项。
  • 如何使用组件样式。
  • 如何开发和使用 BFF API。
  • 如何配置项目所需的静态资源、自定义 HTML。
  • 如何使用测试功能。
在线预览

在 StackBlitz 中预览:

环境准备

开始之前,请参考【环境准备】部分,确保本地环境正常。

创建项目

使用 @modern-js/create 创建新项目,运行命令如下:

npx @modern-js/create@modern-1 mobile-pages

mobile-pages 为创建的项目名。

按照如下选择,生成项目:

? 请选择你想创建的工程类型: 应用
? 请选择开发语言: TS
? 请选择包管理工具: pnpm
? 是否需要支持以下类型应用: 不需要
? 是否需要调整默认配置: 否

开发调试

进入项目根目录, 之后执行 pnpm run dev 即可启动开发服务器:

# 进入项目根目录
cd mobile-pages

# 启动开发服务器
pnpm run dev

浏览器中访问 http://localhost:8080,可以看到应用已经正常启动。

修改 src/pages/index.tsx 会触发重新编译和热更新,浏览器中页面会自动展示对应变化。

IDE 支持

Modern.js 对 VS Code 等主流 IDE 提供了开箱即用的支持,具备 Lint 问题自动检测、自动修复,代码提交前的准入检查等功能特性,可以让代码开发更加高效和智能。详细介绍请参考【确认编程环境】。

创建入口

在 Modern.js 中,一个入口,经过构建后会生成一个对应的 HTML 文件。默认生成的项目只包含一个入口,而一个移动项目通常包含多个独立页面,现在我们创建一个新入口。

在项目根目录下,执行 pnpm run new,进行如下选择:

? 请选择你想要的操作: 创建工程元素
? 创建工程元素: 新建「应用入口」
? 填写入口名称: activity
? 是否修改默认的应用入口配置:否

创建完成,项目的 src/ 目录结构如下:

.
├── src/
│   ├── activity/
│   │   └── App.tsx
│   ├── mobile-pages/
│   │   └── App.tsx
│   ├── .eslintrc.json

其中, activity/ 目录对应新建的入口,项目默认的入口(主入口)代码被移动到 mobile-pages/ 目录下。

使用生成器将应用从单入口转换成多入口时,原本主入口的代码将会被移动到与当前应用 package.json 同名的目录下。

重新启动应用,控制台会输出不同入口对应的访问地址。默认情况下,主入口对应的访问地址为 {域名根路径},其他入口对应的访问地址为 {域名根路径}/{入口名称},如下所示:

App running at:

> Local:
activity http://localhost:8080/activity
mobile-pages http://localhost:8080/
补充信息

更多信息,请参考【添加应用入口】。

关闭客户端路由和状态管理

默认生成的项目是开启客户端路由和状态管理功能的,而一个移动项目一般都是由多个相对简单的独立页面组成,不涉及客户端路由和复杂的状态管理,此时我们可以关闭客户端路由和状态管理功能,以减少项目打包后的体积大小,提高页面性能。

我们打开项目根路径下的 modern.config.js,目前应该是这样的:

modern.config.js
export default defineConfig({
runtime: {
state: true,
router: true,
},
});

然后我们将 staterouter 这两个配置项设置为 false,就关闭了客户端路由和状态管理功能。

补充信息

更多用法,请参考【state】、【router】。

组件样式

Utility Class

Utility Class 是对通用功能(如文字居中、水平对齐等)涉及的 CSS 规则的封装。开发者可以直接使用这些 CSS Class 进行样式设置。

Modern.js 集成了主流的 Utility Class 解决方案 —— Tailwind CSS,我们利用 Tailwind CSS 设置组件的通用样式。

首先,需要开启 Tailwind CSS 支持,在项目根目录下执行 pnpm run new,进行如下选择:

? 请选择你想要的操作: 启用可选功能
? 启用可选功能: 启用 Tailwind CSS 支持

然后在入口的根组件,这里我们以主入口为例,在 src/mobile-pages/App.tsx 添加如下代码:

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 中的文字设置居中效果:

src/mobile-pages/App.tsx
const App: React.FC = () => (
<div className="text-center">Hello, Modern.js!</div>
);

浏览器访问 http://localhost:8080,会发现 Hello, Modern.js! 已经居中显示了。

补充信息

关于 Tailwind CSS 使用的更多介绍,请参考【Tailwind CSS】。

CSS in JS

组件有时候需要设置自己的特有样式,这时候使用 Utility Class 就不是很方便了。我们可以用 CSS in JS 的写法直接在组件的 JS 代码中编写特有样式。Modern.js 通过集成 styled-components 提供 CSS in JS 的支持。

接下来,我们使用 CSS in JS 实现 activity 入口组件样式。

首先,我们在 src/activity/App.tsx 里修改顶部的代码,引入 styled 模块:

import styled from '@modern-js/runtime/styled';

styled 模块的使用方式同 styled-components 一致,我们可以定义如下用于给页面标题添加样式的组件:

const TitleWrapper = styled.div`
font-size: 2rem;
text-align: center;
margin: 0.5rem 0;
color: goldenrod;
`;

此时,src/activity/App.tsx 的完整代码如下:

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;

上面的示例也可以通过 Tailwind CSS 提供的 Utility Class 实现,这里仅用于举例说明 CSS in JS 的使用方式。通常,Utility Class + CSS in JS 的方案可以满足绝大多数项目的样式编写需求。

补充信息

关于组件样式的更多用法,请参考【CSS 开发方案】。

客户端兼容性

Modern.js 提供自动 Polyfill、Browserslist 配置、差异化分发等特性,能够最大化的兼顾客户端兼容性和资源加载性能。关于这部分内容的更多介绍,请参考【客户端兼容性】。

一体化 BFF

现在 activity 入口页面还没有数据,我们可以通过服务端 API 动态获取数据(商品列表数据)。服务端 API 地址为:https://lf3-static.bytednsdoc.com/obj/eden-cn/beeh7uvzhq/products.json

但这个 API 并不是专门为当前项目提供的,部署也是在一个独立的域名下。

通常情况下,项目需要创建一个和当前项目部署在同一域名下的专属 API,并在这个 API 内部去调用原始数据获取,并进行裁剪聚合等。在前端,这样的需求通常使用 BFF 层来实现。

Modern.js 提供了开箱即用的 BFF 能力,支持和前端代码共同开发、调试、部署。

如果已经具备了为前端项目专门开发的、部署在同域下的 API,则不需要再创建 BFF 层,前端代码直接调用 API 即可。

我们执行 pnpm run new 来开启 BFF 功能:

? 请选择你想要的操作: 启用可选功能
? 启用可选功能: 启用「BFF」功能
? 请选择 BFF 类型:函数写法
?请选择运行时框架:Express

执行完成后,项目中新增了 api/ 目录,添加 api/products.ts 文件,实现对数据获取 API 的调用(需要先安装 axios 依赖):

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,关键代码如下:

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。

渲染商品列表数据的关键代码如下:

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;
补充信息

利用一体化 BFF,我们可以实现以往需要专门的服务端项目支持才能实现的需求,如集成微信 JS SDK 时的权限验证逻辑。更多信息请参考【一体化 BFF】。

配置 Favicon 等通用静态资源

config/ 目录下,添加 favicon.ico 图标文件,构建生成的 HTML 页面将包含 favicon 信息。

config/public 目录下,可以放置任意格式的静态资源化文件,文件会被 Serve 在应用同域名下。

在微信网页开发时,绑定 JS 接口安全域名需要上传一个微信提供的 txt 文件到自己的 Web 服务器,并保证文件可以被正常访问。

我们可以把这个文件放到 config/public 目录,然后访问 {域名}/{文件名}就可以获取文件内容。例如,我们在该目录下新建 MP_verify_abcdef.txt 文件,重新启动应用,访问 http://localhost:8080/MP_verify_abcdef.txt 即可获取文件内容。

补充信息

关于静态资源的更多使用方法,请参考配置 【config/public】。

自定义 HTML

Modern.js 支持修改默认使用的 HTML 模板文件。

例如,当我们需要集成 Google Analytics 等工具,统计分析用户的行为信息。我们可以通过自定义项目使用的 HTML 模板,在模板中添加相关工具的 JS SDK 引用代码,实现集成。

以 Google Analytics 为例,我们新建 config/html/top.html 文件,内容如下:

<!-- 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> 标签内,将包含上面的代码,如下图所示:

custom-html

补充信息

更多信息,请参考【自定义 HTML 模板】。

测试

Modern.js 内置 JestTesting Library 等测试库/框架,提供单元测试、组件/页面集成测试、业务模型 Model 测试等功能。

使用测试功能,需要先开启该功能。在项目根目录下,执行 pnpm run new,进行如下选择:

? 请选择你想要的操作: 启用可选功能
? 启用可选功能: 启用「单元测试 / 集成测试」功能

安装依赖后,可以看到 package.json 中新增了一项依赖:

+ @modern-js/plugin-testing

这一插件提供了 Testing API 用于页面集成测试,我们以主入口为例,演示页面的集成测试。

新建 src/mobile-pages/__tests__/ 目录,用于放置测试用例,并编写测试用例 App.test.tsx

App.test.tsx
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,会运行项目下的所有测试用例。

补充信息

更多用法,请参考【Testing API】。