rax-app icon indicating copy to clipboard operation
rax-app copied to clipboard

[RFC] Rax App 自定义 HTML

Open SoloJiang opened this issue 3 years ago • 5 comments

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 字符串填入挂载节点
  • 该方案依然让自定义挂载节点变得更加复杂,例如目前 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。

如何引导开发者使用

  1. 提供对应的 codemod 工具帮助开发者快速从现有的 document 模式迁移到新的模式
  2. 增量用户模板改为新的模式

SoloJiang avatar Apr 23 '21 09:04 SoloJiang

从框架本身渐进式演进来看,需要引入额外一套构建方式,会增加维护成本

这个点是 Document 引入的吧。

html-webpack-plugin 相比 Document 最核心的区别感觉在条件渲染这块,html 如果要做到 Document 那种能力需要借助模版语言开发体验和维护性都比较差。

ClarkXia avatar Apr 23 '21 09:04 ClarkXia

从框架本身渐进式演进来看,需要引入额外一套构建方式,会增加维护成本

这个点是 Document 引入的吧。

html-webpack-plugin 相比 Document 最核心的区别感觉在条件渲染这块,html 如果要做到 Document 那种能力需要借助模版语言开发体验和维护性都比较差。

是的,核心还是目前 Rax App 需要兼容构建 Document,再引入这个就会存在三种模式

SoloJiang avatar Apr 23 '21 09:04 SoloJiang

https://github.com/raxjs/rax-app/issues/657

SoloJiang avatar Apr 25 '21 08:04 SoloJiang

https://rax.js.org/docs/guide/safe-area

安全区域适配需要自定义html才能加入meta 吗?

waylon-gmail avatar Jun 14 '21 17:06 waylon-gmail

https://rax.js.org/docs/guide/safe-area

安全区域适配需要自定义html才能加入meta 吗?

这个 meta 默认已经加了

SoloJiang avatar Jun 15 '21 07:06 SoloJiang