rax-app
rax-app copied to clipboard
[RFC] Rax App 自定义 HTML
Rax App 自定义 HTML
- 提案时间: 2021 年 4 月 23 日
- 相关实现的 issue:
- 相关实现的 pr:
概述
面向未来,Rax App 推荐开发者尽量少的自定义 HTML,但是依然存在一些特殊的场景业务需要自定义 HTML 内容。
现有的方案中,Rax App 提供了 src/document/index.jsx
,用以下方式在构建时执行 renderToString
获取 HTML 文档:
import { createElement } from 'rax';
import { Root, Style, Script } from 'rax-document';
function Document(props) {
return (
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover"/>
<title>{props.title}</title>
<Style />
</head>
<body>
{/* root container */}
<Root />
<Script />
</body>
</html>
);
}
export default Document;
但随着业务发展,该方案渐渐无法满足诉求,基于此我们希望可以通过更强大的方式来构建 HTML 文档。
背景
现有的 src/document/index.jsx
方案优势是:
- 在无需引入额外模板引擎的前提下,可以用 JSX 的方式编写 HTML 结构,写法灵活(条件渲染等)
- 针对 SSR 场景,可以在该文件中使用
Document.getInitialProps
统一获取页面数据(这个实用性后续再讨论)
劣势是:
- CSR & SSR 均需要构建
src/document/index.jsx
- 在不引入 cheerio 的前提下,使用
rax-document
无法很好的对产出的 HTML 结构进行修改 - TDK 只能在
src/document/index.jsx
里做,但实际上有很多业务场景 TDK 是和页面逻辑关联的 - 页面渲染的节点是固定的,即
id="root"
- 存在历史包袱,即多页应用差异性渲染依然需要使用
path
作为唯一值
为什么不用 html-webpack-plugin
-
核心最大的问题还是无法解决 SSR 场景下条件渲染的问题,参考现有 icejs 的相关实现,如果业务需要在服务端渲染的时候做条件渲染,只能在服务端调用
render(ctx, { htmlTemplate })
。这种形式引入了两个问题:- 业务本身的 HTML 逻辑需要分开维护,即
public/index.html
一份,htmlTemplate
一份 - 必须使用 cheerio 或者约束业务将页面渲染的节点使用特殊的占位符标识。否则 SSR 时,无法将页面渲染出的 HTML 字符串填入挂载节点
- 业务本身的 HTML 逻辑需要分开维护,即
-
该方案依然让自定义挂载节点变得更加复杂,例如目前 icejs 只能用
id="root"
作为挂载节点 -
从 Rax App 框架本身渐进式演进来看,需要引入额外一套构建方式,会增加维护成本
解法
我们新的方案即使无法解决所有的问题,但是依然会着重考虑以下几个点:
- 几乎所有常用属性和节点都可以由框架自定义,不用依赖 cheerio
- 页面组件 TDK 的能力,使用方法参考 react-helmet
- 通过 page name 进行差异性渲染
- 页面挂载节点可自定义
基于以上所有的分析,新方案大方向上沿用 src/document/index.jsx
,将 rax-document
的能力内置到 rax-app
,并由 rax-app
对外提供 HTML 核心组件,同时面向三方的 build scripts 插件提供操作 HTML 结构的能力。
基础示例
Document
// ./src/document/index.tsx
import { createElement } from 'rax';
import { Head, Body, Root, BundleStyle, BundleScript } from 'rax-app';
function Document(props) {
return (
<html>
<Head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover"/>
<title>{props.title}</title>
<BundleStyle />
</Head>
<Body>
{/* root container */}
<Root />
<BundleScript />
</Body>
</html>
);
}
export default Document;
TDK
// ./src/pages/Home/index.tsx
import { createElement } from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import { Title, Meta } from 'rax-app';
import styles from './index.module.css';
function Home(props) {
return (
<View className={styles.homeContainer}>
<Title>{props.data?.title}</Title>
<Meta description="Home Page" />
<Text className={styles.homeTitle}>Welcome to Your Rax App</Text>
<Text className={styles.homeInfo}>More information about Rax</Text>
<Text className={styles.homeInfo}>Visit https://rax.js.org</Text>
</View>
);
}
Home.getInitialProps = async function () {
return {
data: {
title: 'Hello World',
},
};
};
export default Home;
Rax App 提供的 HTML 组件
组件名 | 作用 | 是否能在非 Document 模式使用 |
---|---|---|
Head | head 标签 | ✘ |
Body | body 标签 | ✘ |
Title | title 标签,构建后插入到 head 标签内 | ✔️ |
Meta | meta 标签,构建后插入到 head 标签内 | ✔️ |
Link | link 标签,构建后插入到开发者业务样式的 link 标签前 | ✔️ |
Style | style 标签,构建后插入到开发者业务样式的 link 标签前 | ✔️ |
Script | script 标签,构建后插入到 Root 节点和开发者业务 js bundle 之间 | ✔️ |
BundleScript | 开发者的业务 js bundle | ✘ |
BundleStyle | 开发者的业务样式的 link 标签 | ✘ |
基于以上能力,95% 以上的业务都不再需要 document。
如何引导开发者使用
- 提供对应的 codemod 工具帮助开发者快速从现有的 document 模式迁移到新的模式
- 增量用户模板改为新的模式
从框架本身渐进式演进来看,需要引入额外一套构建方式,会增加维护成本
这个点是 Document 引入的吧。
html-webpack-plugin 相比 Document 最核心的区别感觉在条件渲染这块,html 如果要做到 Document 那种能力需要借助模版语言开发体验和维护性都比较差。
从框架本身渐进式演进来看,需要引入额外一套构建方式,会增加维护成本
这个点是 Document 引入的吧。
html-webpack-plugin 相比 Document 最核心的区别感觉在条件渲染这块,html 如果要做到 Document 那种能力需要借助模版语言开发体验和维护性都比较差。
是的,核心还是目前 Rax App 需要兼容构建 Document,再引入这个就会存在三种模式
https://github.com/raxjs/rax-app/issues/657
https://rax.js.org/docs/guide/safe-area
安全区域适配需要自定义html才能加入meta 吗?
https://rax.js.org/docs/guide/safe-area
安全区域适配需要自定义html才能加入meta 吗?
这个 meta 默认已经加了