Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/support-hippy-vue-next-ssr #3526

Merged
merged 74 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
13132e3
feat(hippy-vue-next): add ssr style parser
gguoyu Mar 2, 2023
a54f04b
Merge branch 'master' into feature/hippy-vue-next-ssr
gguoyu Mar 3, 2023
a999a42
feat(hippy-vue-next): add ssr runtime support
gguoyu Mar 3, 2023
8f4c4e8
Merge branch 'master' into feature/hippy-vue-next-ssr
gguoyu Mar 4, 2023
f48a9e9
feat(vue-next): add server renderer package
gguoyu Mar 5, 2023
c3b07dd
feat(vue-next): server-renderer normal version commit
gguoyu Mar 6, 2023
627f6c8
feat(vue-next): optimize text render function
gguoyu Mar 6, 2023
e907f41
feat(vue-next): add ssr render & compiler package
gguoyu Mar 6, 2023
26e5166
feat(vue-next): init vue-next-ssr-demo
gguoyu Mar 6, 2023
9005612
feat(vue-next): ssr support scoped style & ssr bug fix
gguoyu Mar 10, 2023
a1972ec
feat(vue-next): fix ssr style issue
gguoyu Mar 10, 2023
522d644
feat(vue-next): fix ssr demo problem
gguoyu Mar 14, 2023
5bed3a7
feat(vue-next): add webpack ssr build config
gguoyu Mar 17, 2023
63abb9c
feat(vue-next): webpack ssr demo commit
gguoyu Mar 20, 2023
633cf23
feat(vue-next): fix build problem
gguoyu Mar 21, 2023
1c6e617
feat(vue-next): fix build problem
gguoyu Mar 23, 2023
2bfd063
feat(vue-next): fix ssr build problem
gguoyu Mar 24, 2023
44973a3
docs(vue-next): add ssr description doc
gguoyu Mar 24, 2023
a017318
docs(vue-next): add how to use ssr doc& update demo readme
gguoyu Mar 27, 2023
c2c3f1f
docs(vue-next): add ssr how to use and description
gguoyu Mar 27, 2023
defab54
docs(vue-next): add ssr architecture image
gguoyu Mar 27, 2023
321c870
feat(vue-next): add ssr store usage & update docs
gguoyu Mar 27, 2023
b4245ab
feat(vue-next): add ssr unit-test and style parser unit-test
gguoyu Mar 28, 2023
d4636e5
feat(vue-next): add server-render unit test case
gguoyu Mar 29, 2023
2cc2626
feat(vue-next): add server-render vnode unit test case
gguoyu Mar 29, 2023
c036633
feat(vue-next): add server-render unit test case
gguoyu Mar 30, 2023
1f81d60
feat(vue-next): add compiler-ssr unit test case
gguoyu Mar 30, 2023
5c602cd
feat(vue-next): add ssr related unit test case & commit
gguoyu Mar 31, 2023
e82ca6c
feat(vue-next): commit totally native component ssr support
gguoyu Apr 3, 2023
ad26661
feat(vue-next): complete ssr render event map
gguoyu Apr 3, 2023
d287ef6
feat(vue-next): complete ssr render event map & add unit test case
gguoyu Apr 3, 2023
72ac570
feat(vue-next): complete ssr render event map & add unit test case
gguoyu Apr 3, 2023
adb4255
Revert "feat(vue-next): complete ssr render event map & add unit test…
gguoyu Apr 3, 2023
6b1dce3
feat(vue-next): add unit test setup file
gguoyu Apr 3, 2023
32719aa
feat(vue-next): merge master and resolve conflict
gguoyu Apr 3, 2023
9c2132b
feat(vue-next): resolve fragment render problem
gguoyu Apr 3, 2023
586ff71
feat(vue-next): fix native component render problem
gguoyu Apr 4, 2023
eb3be28
feat(vue-next): modify native component render unit test case
gguoyu Apr 4, 2023
f82c072
fix(vue-next): fix websocket demo field name issue
gguoyu Apr 6, 2023
80f8fa8
Merge branch 'master' into feature/hippy-vue-next-ssr
gguoyu May 23, 2023
0729d89
fix(vue-next): fix ssr demo platform issue
gguoyu May 23, 2023
58f5eee
fix(vue-next): fix ssr text node render issue
gguoyu May 24, 2023
a1bedef
feat(vue-next): add ssr text node render unit test case
gguoyu May 24, 2023
be0962e
feat(vue-next-demo): optimize ssr build
gguoyu Aug 17, 2023
4859c86
feat(vue-next): compatible node props and style props
gguoyu Sep 14, 2023
18fe529
feat(vue-next): compatible fontWeight && placeholder
gguoyu Sep 14, 2023
0e980d2
feat(vue-next): specify ssr package types & update demo pinia version
gguoyu Sep 15, 2023
b31dac0
feat(vue-next): compatible iframe ssr load issue
gguoyu Sep 15, 2023
46cb81e
feat(vue-next): compatible ssr style & store issue
gguoyu Sep 15, 2023
4e027ea
feat(vue-next): fix iframe ssr props issue
gguoyu Sep 15, 2023
e4b6997
feat(vue-next): fix textInput ssr props & style issue
gguoyu Sep 16, 2023
aefb6e8
feat(vue-next): fix waterfall ssr issue
gguoyu Sep 16, 2023
d5c5207
feat(vue-next): fix pull-header-footer ssr issue
gguoyu Sep 16, 2023
99f11b6
feat(vue-next): fix nested scroll ssr issue
gguoyu Sep 16, 2023
b9d22d8
feat(vue-next): fix loop animation ssr issue
gguoyu Sep 16, 2023
751052a
feat(vue-next): fix animation demo ssr issue
gguoyu Sep 16, 2023
b4a6a23
feat(vue-next): fix div demo & css comment property issue
gguoyu Sep 16, 2023
2ca32da
feat(vue-next): fix all ssr demo issue
gguoyu Sep 18, 2023
ad14dc4
feat(vue-next): commit ssr dev mode all logic
gguoyu Sep 19, 2023
2b36c91
feat(vue-next): dev & build tools complete
gguoyu Sep 21, 2023
9afcbbf
feat(vue-next): optimize dev experience
gguoyu Sep 21, 2023
3b77203
docs(vue-next): update ssr usage doc
gguoyu Sep 21, 2023
f7aeeef
feat(vue-next): add ssr render & style unit test case
gguoyu Sep 21, 2023
43a1308
feat(vue-next): add ssr style-parse & ssr node parse unit test case
gguoyu Sep 22, 2023
af9c8b4
docs(vue-next): optimize ssr doc
gguoyu Sep 22, 2023
b1f1473
Merge branch 'master' into feature/hippy-vue-next-ssr
gguoyu Sep 22, 2023
655afbe
feat(vue-next): update ssr demo node engines min version
gguoyu Sep 22, 2023
bab2309
feat(vue-next): optimize ssr render string transform performance
gguoyu Sep 23, 2023
23a57d8
feat(vue-next): add new dir for ssr demo
gguoyu Sep 25, 2023
6c403df
feat(vue-next): fix ssr demo issue & optimize comment
gguoyu Sep 27, 2023
737e5e5
feat(vue-next): fix div demo issue & native background android issue
gguoyu Oct 7, 2023
69e8df2
docs(vue-next): update ssr docs
gguoyu Oct 7, 2023
ddbeafc
Merge branch 'master' into feature/hippy-vue-next-ssr
gguoyu Jan 19, 2024
e4b878c
docs(vue-next): update vue-next ssr docs
gguoyu Jan 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading