Skip to content

Commit

Permalink
feature(vue-next): hippy vue next ssr (#3526)
Browse files Browse the repository at this point in the history
* feat(hippy-vue-next): add ssr style parser

* feat(hippy-vue-next): add ssr runtime support

* feat(vue-next): add server renderer package

* feat(vue-next): server-renderer normal version commit

* feat(vue-next): optimize text render function

* feat(vue-next): add ssr render & compiler package

* feat(vue-next): init vue-next-ssr-demo

* feat(vue-next): ssr support scoped style & ssr bug fix

* feat(vue-next): fix ssr style issue

* feat(vue-next): fix ssr demo problem

* feat(vue-next): add webpack ssr build config

* feat(vue-next): webpack ssr demo commit

* feat(vue-next): fix build problem

* feat(vue-next): fix build problem

* feat(vue-next): fix ssr build problem

* docs(vue-next): add ssr description doc

* docs(vue-next): add how to use ssr doc& update demo readme

* docs(vue-next): add ssr how to use and description

* docs(vue-next): add ssr architecture image

* feat(vue-next): add ssr store usage & update docs

* feat(vue-next): add ssr unit-test and style parser unit-test

* feat(vue-next): add server-render unit test case

* feat(vue-next): add server-render vnode unit test case

* feat(vue-next): add server-render unit test case

* feat(vue-next): add compiler-ssr unit test case

* feat(vue-next): add ssr related unit test case & commit

* feat(vue-next): commit totally native component ssr support

* feat(vue-next): complete ssr render event map

* feat(vue-next): complete ssr render event map & add unit test case

* feat(vue-next): complete ssr render event map & add unit test case

* Revert "feat(vue-next): complete ssr render event map & add unit test case"

This reverts commit 72ac570.

* feat(vue-next): add unit test setup file

* feat(vue-next): resolve fragment render problem

* feat(vue-next): fix native component render problem

* feat(vue-next): modify native component render unit test case

* fix(vue-next): fix websocket demo field name issue

* fix(vue-next): fix ssr demo platform issue

* fix(vue-next): fix ssr text node render issue

* feat(vue-next): add ssr text node render unit test case

* feat(vue-next-demo): optimize ssr build

* feat(vue-next): compatible node props and style props

* feat(vue-next): compatible fontWeight && placeholder

* feat(vue-next): specify ssr package types & update demo pinia version

* feat(vue-next): compatible iframe ssr load issue

* feat(vue-next): compatible ssr style & store issue

* feat(vue-next): fix iframe ssr props issue

* feat(vue-next): fix textInput ssr props & style issue

* feat(vue-next): fix waterfall ssr issue

* feat(vue-next): fix pull-header-footer ssr issue

* feat(vue-next): fix nested scroll ssr issue

* feat(vue-next): fix loop animation ssr issue

* feat(vue-next): fix animation demo ssr issue

* feat(vue-next): fix div demo & css comment property issue

* feat(vue-next): fix all ssr demo issue

* feat(vue-next): commit ssr dev mode all logic

* feat(vue-next): dev & build tools complete

* feat(vue-next): optimize dev experience

* docs(vue-next): update ssr usage doc

* feat(vue-next): add ssr render & style unit test case

* feat(vue-next): add ssr style-parse & ssr node parse unit test case

* docs(vue-next): optimize ssr doc

* feat(vue-next): update ssr demo node engines min version

* feat(vue-next): optimize ssr render string transform performance

* feat(vue-next): add new dir for ssr demo

* feat(vue-next): fix ssr demo issue & optimize comment

* feat(vue-next): fix div demo issue & native background android issue

* docs(vue-next): update ssr docs

* docs(vue-next): update vue-next ssr docs
  • Loading branch information
gguoyu authored Jan 19, 2024
1 parent 5f90d4a commit 952f2f7
Show file tree
Hide file tree
Showing 200 changed files with 18,722 additions and 673 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ module.exports = {
'@typescript-eslint/consistent-type-assertions': 'off',
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/prefer-for-of': 'off',
'@typescript-eslint/no-require-imports': 'off',
},
parserOptions: {
project: ['./**/tsconfig.json'],
Expand Down Expand Up @@ -169,6 +170,8 @@ module.exports = {
['sfc', resolveVue('sfc')],
['he', path.resolve(__dirname, './packages/hippy-vue/src/util/entity-decoder')],
['@hippy-vue-next-style-parser', resolvePackage('hippy-vue-next-style-parser')],
['@hippy-vue-next', resolvePackage('hippy-vue-next')],
['@hippy-vue-next-server-renderer', resolvePackage('hippy-vue-next-server-renderer')],
],
},
},
Expand Down
Binary file added docs/assets/img/hippy-vue-next-ssr-arch-cn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
137 changes: 118 additions & 19 deletions docs/en-us/hippy-vue/vue3.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ If you want to use Vue-Router, you need to use additional initialization logic
<router-view />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { defineComponent, ref } from 'vue';

export default defineComponent({
export default defineComponent({
setup() {
const msg: string = 'This is the Root Page';
return {
msg,
};
},
const msg: string = 'This is the Root Page';
return {
msg,
};
},
});
</script>

Expand All @@ -77,15 +77,15 @@ export default defineComponent({
<div><span>{{ msg }}</span></div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { defineComponent, ref } from 'vue';

export default defineComponent({
export default defineComponent({
setup() {
const msg: string = 'This is the Index Page';
return {
msg,
};
},
const msg: string = 'This is the Index Page';
return {
msg,
};
},
});
</script>

Expand Down Expand Up @@ -147,15 +147,14 @@ const routes = [
path: '/',
component: Index,
},
];
];

const router = createRouter({
history: createMemoryHistory(),
routes,
});
```


# Custom Components & Modules

In @hippy/vue-next, the `registerElement` method is also available for registering custom components and mapping tags in the template to native components.
Expand Down Expand Up @@ -402,13 +401,113 @@ function isCustomTag(tag) {
},
```

# Server Side Render

@hippy/vue-next is now supported SSR, the specific code can be viewed in [Demo](https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-ssr-demo)'s SSR Part
, For the implementation and principle of Vue SSR, you can refer to the [official document](https://cn.vuejs.org/guide/scaling-up/ssr.html)

## How To Use SSR

Read `How To Use SSR` in [Demo](https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-ssr-demo)

## Principle

### SSR Architecture

<img src="en-us/assets/img/hippy-vue-next-ssr-arch-en.png" alt="hippy-vue-next SSR Architecture" width="80%"/>

### Description

The implementation of @hippy/vue-next SSR involves three operating environments: compile time, client runtime, and server runtime. On the basis of vue-next ssr, we developed @hippy/vue-next-server-renderer
Used for server-side runtime node rendering, developed @hippy/vue-next-compiler-ssr for compiling vue template files at compile time. And @hippy/vue-next-style-parser for server-side rendering
Style insertion for Native Node List. Let's illustrate what @hippy/vue-next SSR does through the compilation and runtime process of a template

We have a template like `<div id="test" class="test-class"></div>`

- Compiler

Through @hippy/vue-next-compiler-ssr, our template transform to render funtions like

```javascript
_push(`{"id":${ssrGetUniqueId()},"index":0,"name":"View","tagName":"div","props":{"class":"test-class","id": "test",},"children":[]},`)
```

- Server Side Runtime

Through @hippy/vue-next-server-renderer, render function obtained during compilation is executed to obtain the json object of the corresponding node.
Note that the ssrGetUniqueId method in the render function is provided in @hippy/vue-next-server-renderer, where the server-renderer will also process
the attribute values of the nodes, and finally get the json object of the Native Node

```javascript
{ "id":1,"index":0,"name":"View","tagName":"div","props":{"class":"test-class","id": "test",},"children":[] }
```

> For the handwritten non-sfc template rendering function, it cannot be processed in the compiler, and it is also executed in the server-renderer
- Client Side Runtime

Through @hippy/vue-next-style-parser, nodes returned by server are insert styles, and insert node props by @hippy/vue-next. Then insert native nodes to
native to complete rendering node on screen.
After the node is inserted to the screen, the asynchronous jsBundle on the client side is loaded asynchronously through the global.dynamicLoad provided
by the system to complete the Hydrate on the client side and execute the follow-up process.

## Different

There are some differences between the Demo initialization of the SSR version and the initialization of the asynchronous version. Here is a detailed description of the differences

- src/main-native.ts Change

1. Use createSSRApp to replace the previous createApp, createApp only supports CSR rendering, while createSSRApp supports both CSR and SSR
2. The ssrNodeList parameter is added during initialization as the Hydrate initialization node list. Here the initialized node list returned by our server is stored in global.hippySSRNodes, and pass it as a parameter to createSSRApp when calling it.
3. Call app.mount after router.isReady is completed, because if you don’t wait for the routing to complete, it will be different from the node rendered by the server, causing Hydrate to report an error

```javascript
- import { createApp } from '@hippy/vue-next';
+ import { createSSRApp } from '@hippy/vue-next';
- const app: HippyApp = createApp(App, {
+ const app: HippyApp = createSSRApp(App, {
// ssr rendered node list, use for hydration
+ ssrNodeList: global.hippySSRNodes,
});
+ router.isReady().then(() => {
+ // mount app
+ app.mount('#root');
+ });
```
- src/main-server.ts Add
main-server.ts is the business jsBundle running on the server side, so no code splitting is required. The whole can be built as a bundle. Its core function is to complete the first-screen rendering logic on the server side, process the obtained first-screen Hippy node, insert node attributes and store (if it exists), and return.
And return the maximum uniqueId of the currently generated node for subsequent use by the client.
>Note that the server-side code is executed synchronously. If a data request is made asynchronously, the request may have been returned before the data is obtained. For this problem, Vue SSR provides a dedicated API to handle this problem:
>[onServerPrefetch](https://cn.vuejs.org/api/composition-api-lifecycle.html#onserverprefetch).
>There is also an example of using onServerPrefetch in app.vue of [Demo](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-ssr-demo/src/app.vue)
- server.ts Add
server.ts is the entry file executed by the server. Its role is to provide a Web Server, receive the SSR CGI request from the client, and return the result to the client as response data, including the rendering node list, store, and global style list.
- src/main-client.ts Add
main-client.ts is the entry file executed by the client. Unlike the previous pure client rendering, the client entry file of SSR only includes the request to obtain the first screen node, insert the first screen node style, and insert the node into the terminal to complete the rendering. related logic.
- src/ssr-node-ops.ts Add
ssr-node-ops.ts encapsulates the operation logic of inserting, updating, and deleting SSR nodes that do not depend on @hippy/vue-next runtime.
- src/webpack-plugin.ts Add
webpack-plugin.ts encapsulates the initialization logic of Hippy App required for SSR rendering.
# Additional Differences
@hippy/vue-next is basically functionally aligned with @hippy/vue now, but the APIs are slightly different from @hippy/vue, and there are still some problems that have not been solved, here is the description:
- Vue.Native
In @hippy/vue, the capabilities provided by Native are provided by the Native attribute mounted on the global Vue. In Vue3.x, this implementation is no longer feasible. You can access Native as follows:
In @hippy/vue, the capabilities provided by Native are provided by the Native attribute mounted on the global Vue. In Vue3.x, this implementation is no longer feasible. You can access Native as follows:
```javascript
import { Native } from '@hippy/vue-next';
Expand Down Expand Up @@ -475,9 +574,9 @@ function isCustomTag(tag) {

- Type hints for native APIs and customized components

@hippy/vue-next provides type hints for native APIs.
@hippy/vue-next provides type hints for native APIs.
If there is a customized native api, it can also be extended in a similar way

```javascript
declare module '@hippy/vue-next' {
export interface NativeInterfaceMap {
Expand Down
114 changes: 107 additions & 7 deletions docs/hippy-vue/vue3.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ demo 参考:https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-ne
"@hippy/vue-router-next-history": "latest",
```


## 初始化

```javascript
Expand Down Expand Up @@ -402,6 +401,107 @@ function isCustomTag(tag) {

```

# 服务端渲染

@hippy/vue-next 现已支持服务端渲染,具体代码可以查看[示例项目](https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-ssr-demo)中的 SSR
部分,关于 Vue SSR 的实现及原理,可以参考[官方文档](https://cn.vuejs.org/guide/scaling-up/ssr.html)

## 如何使用SSR

请参考[示例项目](https://github.com/Tencent/Hippy/tree/master/examples/hippy-vue-next-ssr-demo)说明文档中的 How To Use SSR

## 实现原理

### SSR 架构图

<img src="assets/img/hippy-vue-next-ssr-arch-cn.png" alt="hippy-vue-next SSR 架构图" width="80%"/>

### 详细说明

@hippy/vue-next SSR 的实现涉及到了编译时,客户端运行时,以及服务端运行时三个运行环境。在 vue-next ssr的基础上,我们开发了 @hippy/vue-next-server-renderer
用于服务端运行时节点的渲染,开发了 @hippy/vue-next-compiler-ssr 用于编译时 vue 模版文件的编译。以及 @hippy/vue-next-style-parser 用于服务端渲染得到的
Native Node List 的样式插入。下面我们通过一个模版的编译和运行时过程来说明 @hippy/vue-next SSR 做了哪些事情

我们有形如`<div id="test" class="test-class"></div>`的一段模版

- 编译时

模版经过 @hippy/vue-next-compiler-ssr 的处理,得到了形如

```javascript
_push(`{"id":${ssrGetUniqueId()},"index":0,"name":"View","tagName":"div","props":{"class":"test-class","id": "test",},"children":[]},`)
```

的 render function

- 服务端运行时

在服务端运行时,编译时得到的 render function 执行后得到了对应节点的 json object。注意 render function 中的
ssrGetUniqueId 方法,是在 @hippy/vue-next-server-renderer 中提供的,在这里 server-renderer 还会对
节点的属性值等进行处理,最后得到 Native Node 的 json object

```javascript
{ "id":1,"index":0,"name":"View","tagName":"div","props":{"class":"test-class","id": "test",},"children":[] }
```

> 对于手写的非 sfc 模版的渲染函数,在 compiler 中无法处理,也是在 server-renderer 中执行的
- 客户端运行时

在客户端运行时,通过 @hippy/vue-next-style-parser,给服务端返回的节点插入样式,并直接调用 hippy native 提供的
native API,将返回的 Native Node 对象作为参数传入,并完成节点的渲染上屏。 完成节点上屏之后,再通过系统提供的
global.dynamicLoad 异步加载客户端异步版 jsBundle,完成客户端 Hydrate 并执行后续流程。

## 初始化差异

SSR 版本的 Demo 初始化与异步版的初始化有一些差异部分,这里对其中的差异部分做一个详细的说明

- src/main-native.ts 变更

1. 使用 createSSRApp 替换之前的 createApp,createApp 仅支持 CSR 渲染,而 createSSRApp 同时支持 CSR 和 SSR
2. 在初始化时候新增了 ssrNodeList 参数,作为 Hydrate 的初始化节点列表。这里我们服务端返回的初始化节点列表保存在了 global.hippySSRNodes 中,并将其作为参数在createSSRApp时传入
3. 将 app.mount 放到 router.isReady 完成后调用,因为如果不等待路由完成,会与服务端渲染的节点有所不同,导致 Hydrate 时报错

```javascript
- import { createApp } from '@hippy/vue-next';
+ import { createSSRApp } from '@hippy/vue-next';
- const app: HippyApp = createApp(App, {
+ const app: HippyApp = createSSRApp(App, {
// ssr rendered node list, use for hydration
+ ssrNodeList: global.hippySSRNodes,
});
+ router.isReady().then(() => {
+ // mount app
+ app.mount('#root');
+ });
```
- src/main-server.ts 新增
main-server.ts 是在服务端运行的业务 jsBundle,因此不需要做代码分割。整体构建为一个 bundle 即可。其核心功能就是在服务端完成首屏渲染逻辑,并将得到的首屏 Hippy 节点进行处理,插入节点属性和 store(如果存在)后返回,
以及返回当前已生成节点的最大 uniqueId 供客户端后续使用。
>注意,服务端代码是同步执行的,如果有数据请求走了异步方式,可能会出现还没有拿到数据,请求就已经返回了的情况。对于这个问题,Vue SSR 提供了专用 API 来处理这个问题:
>[onServerPrefetch](https://cn.vuejs.org/api/composition-api-lifecycle.html#onserverprefetch)。
>在 [Demo](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-ssr-demo/src/app.vue) 的 app.vue 中也有 onServerPrefetch 的使用示例
- server.ts 新增
server.ts 是服务端执行的入口文件,其作用是提供 Web Server,接收客户端的 SSR CGI 请求,并将结果作为响应数据返回给客户端,包括了渲染节点列表,store,以及全局的样式列表。
- src/main-client.ts 新增
main-client.ts 是客户端执行的入口文件,与之前纯客户端渲染不同,SSR的客户端入口文件仅包含了获取首屏节点请求、插入首屏节点样式、以及将节点插入终端完成渲染的相关逻辑。
- src/ssr-node-ops.ts 新增
ssr-node-ops.ts 封装了不依赖 @hippy/vue-next 运行时的 SSR 节点的插入,更新,删除等操作逻辑
- src/webpack-plugin.ts 新增
webpack-plugin.ts 封装了 SSR 渲染所需 Hippy App 的初始化逻辑
# 其他差异说明
目前 `@hippy/vue-next``@hippy/vue` 功能上基本对齐,不过在 API 方面与 @hippy/vue 有一些区别,以及还有一些问题还没有解决,这里做些说明:
Expand Down Expand Up @@ -475,8 +575,8 @@ function isCustomTag(tag) {
- Native 接口和自定义组件的类型提示
@hippy/vue-next 提供了 Native 模块接口的 Typescript 类型提示,如果有业务自定义的 Native 接口,也可以采用类似的方式进行扩展
@hippy/vue-next 提供了 Native 模块接口的 Typescript 类型提示,如果有业务自定义的 Native 接口,也可以采用类似的方式进行扩展
```javascript
declare module '@hippy/vue-next' {
export interface NativeInterfaceMap {
Expand All @@ -485,7 +585,7 @@ function isCustomTag(tag) {
}
```
@hippy/vue-next 也参考 `lib.dom.d.ts` 的事件声明提供了事件类型,具体可以参考 hippy-event.ts 文件。如果需要在内置的事件上进行扩展,可以采用类似方式
@hippy/vue-next 也参考 `lib.dom.d.ts` 的事件声明提供了事件类型,具体可以参考 hippy-event.ts 文件。如果需要在内置的事件上进行扩展,可以采用类似方式
```javascript
declare module '@hippy/vue-next' {
Expand All @@ -495,7 +595,7 @@ function isCustomTag(tag) {
}
```
在使用 `registerElement` 去注册组件的时候,利用了 typescript 的 `type narrowing`,在 switch case 中提供了准确的类型提示。如果在业务注册自定义组件的时候也需要类型提示,可以采用如下方式:
在使用 `registerElement` 去注册组件的时候,利用了 typescript 的 `type narrowing`,在 switch case 中提供了准确的类型提示。如果在业务注册自定义组件的时候也需要类型提示,可以采用如下方式:
```javascript
export interface HippyGlobalEventHandlersEventMap {
Expand All @@ -506,8 +606,8 @@ function isCustomTag(tag) {
}
```
更多信息可以参考 demo 里的 [extend.ts](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/src/extend.ts).
更多信息可以参考 demo 里的 [extend.ts](https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-next-demo/src/extend.ts).
- whitespace 处理
Vue2.x Vue-Loader `compilerOptions.whitespace` 默认值为 `preserve`, Vue3.x 默认值为 `condense`(可参考 [Vue3 whitespace说明](https://cn.vuejs.org/api/application.html#app-config-compileroptions-whitespace))。
Expand Down
Loading

0 comments on commit 952f2f7

Please sign in to comment.