mip-project / mip

MIT License
11 stars 1 forks source link

MIP 2.0 的组件化方案 #4

Open zoumiaojiang opened 6 years ago

zoumiaojiang commented 6 years ago

mip 组件化方案

mip 组件化方案就是 vue 2.x 的方案,我们对 vue 做一些适应于 MIP 解决方案的改造处理,组件化方案主要包含以下几个部分:

其中,模版语法,生命周期、双向数据绑定原理等内容都和 vue 保持一致。

我们重点需要关注几点内容:

组件数据传入的机制

当在 html 页面中以自定义元素的方式引用组件的时候,需要考虑到直出数据的传入问题,在这里有两种方式:

props 传值的方式

适合传入「字符串」、「数字」,「布尔型」等简单类型的数据

<mip-img :src="'https://xxx.xxx.com/xxx.jpg'" :width="100" :height="100" />
<mip-img src="https://xxx.xxx.com/xxx.jpg" width="100" height="100" />

<script> 的方式

适合传入结构化的数据,比如「数组」、「对象」等:

<mip-a>
    <script type="application/json">
        {
            // 这里的 json 数据都是服务端直出的
            "name": "xxx"
        }
    </script>
    <div class="div1">balabala</div>
</mip-a>

如果不想使用 script 方式传入结构化的数据,还是想要借助于 props 机制,由于 custom element 是跑在 runtime 的时候,所以不能直接将 object 或者 array 丢到 props 里,只能换一种方法:props 可以接受 JSON string,MIP2.0 会自动解析 JSON string 为结构化的绑定数据:

<mip-a :data='{"name": "Jerry", "age": 21}'>
    <div class="div1">balabala</div>
</mip>

在模版中就可以通过 props 获取这个对象了:

<!-- mip-a.vue -->
<template>
    <div>
        <span>{{ data.name }}</span><span> {{ data.age }} </span>
    </div>
</template>
<script>
export default {
    // ...
   props: ['data'],
   // ...
};
</script>

兼容 MIP 1.0 的匿名 slot 机制

比如现在浏览器的 mip 页面中的 <mip-b> 标签的结构是如下所示:

<mip-b>
    <div class="div1"></div>
    <span class="span1"></span>
</mip-b>

对应的 <mip-b> 的组件 vue 模版如下:

<!-- mip-b.vue -->
<template>
    <div class="mip-b">
        <div class="content">这里是组件默认的内容</div>
        <slot></slot>
    </div>
</template>
<script>
    // ...
</script>

只要在组件中定义一个匿名的 标签,这个匿名的 slot 将接收 custom element 中的内容,当然也可以在 custom element 中指定具名的 slot,前提是模版中需要定义这个具名的 slot。

HTML 模版语法机制

在 html 的使用自定义标签中,不允许直接出现 vue 的模版语法,如果出现了,都当成正常 HTML 内容处理:

<mip-b>
    <script type="application/json">
        {
            "data" :  {
                "name": "xxx"
            }
        }
    </script>
    <div class="div1">{{ data.name }}</div>
</mip-b>

这段 html 只会渲染成 {{ data.name }},而不是 xxx

mip-template

如果需要在浏览器端的自定义标签中支持模版语法,需要借助 mip-template 标签,可以直接写 vue 模版语法,使用方式如下:

<mip-template>
    <script type="application/json">
        {
            "data" :  {
                "name": "xxx"
            }
        }
    </script>
    <div class="div1">{{ data.name }}</div>
</mip-template>

这种方式适用于只是纯展现的场景,不适合有交互的情况。并且必须使用 mip-template 标签。

兼容 1.0 的 <template> 标签

为了兼容 mip 1.0 的展现场景,由于 1.0 版本中的 <template type="mip-mustache"> 标签是基于 mustache 实现的,所以我们可以直接使用 mip 的 mip-mustache 组件<template type="mip-mustache"> 只会在其他组件中被使用,例如被 mip-list 组件使用:

<mip-list>
    <script type="application/json">
        {
            "items": [
                {
                    "name": "jerry",
                    "age": 21
                }, {
                    "name": "tom",
                    "age": 22
                }, {
                    "name": "green",
                    "age": 23
                }
            ]
        }
    </script>
    <template type="mip-mustache">
        <div>
            <li>name: {{name}}</li>
            <li>alias: {{age}}</li>
        </div>
    </template>
</mip-list>

默认 <template type="mip-mustache"> 标签为一个匿名的 slot,如有多个,需要标明具名的 slot

支持 HTML 事件处理

<div on="tap:mip-list:more">点击加载更多</div>

为了兼容 1.0,需要提供这样的一种语法糖,用来在 HTML 标签中可以绑定事件,并使用组件的事件处理函数或者调用 MIP 自身的通用事件处理函数。

兼容 1.0 的 <mip-data> 标签 和 m-bind 属性相关

这部分可以完全直接使用 mip 1.0 关于 mip-bind 的逻辑,相当于除了 2.0 组件化方案外,另外再提供一种双向绑定的机制。

<!-- 变更样式 -->
<style mip-custom>
    [data-clicked=true] {
        background: pink;
    }
</style>
<mip-data>
    <script type="application/json">
        {
            "clicked": false
        }
    </script>
</mip-data>
<span m-bind:data-clicked="clicked" on="tap:MIP.setData({clicked:!m.clicked})">来点我呀!</span>

了解 mip 1.0 这块的内容后,发现 1.0 的 <mip-data> 标签 和 mip-bind 属性相关只是适合做纯展现和简单交互的工作,复杂的工作还是要依赖 2.0 组件化机制。

新增 asyncData/syncData 生命周期支持

asyncData 生命周期中主要是处理异步请求数据,这部分的数据对应的内容不会在非浏览器环境下(通常指的是 spider)执行。 syncData 和 created 生命周期一致,处理需要直出(props 或者组件内的 <script> 中的)的数据以及对应内容,这个在非浏览器环境中也会执行。

需要对当前执行环境进行判断

支持 spider 环境的「SSR」

当在 spider 环境执行 MIP 2.0 runtime 的时候,为了 SEO 和性能需要,要确保能够渲染出一份内容直接存储到 MIP CDN,然后在搜索结果页中可以被展现。

原理是:在 spider 环境下,不会执行 asyncData 的生命周期,只会执行 syncData 的生命周期,这样开发者可以决定哪些内容是可以直接被搜索引擎索引并缓存的。

这里面需要注意的是,需要确保每个依赖的组件都会被渲染完成,类似 VUE 的 SSR 功能,也就是 MIP 2.0 组件在 spider 环境中渲染出来的内容类似如下:

<!-- 一堆 html -->
<mip-a data-a-88c22c74>
    <p data-a-23nm3484>MIP</p>
    <span data-a-3ynmjf633>一个吊炸天的东西</span>
</mip-a>
<!-- 一堆 html -->

组件反解混合机制

当在搜索结果页中展现了 MIP CDN 的内容之后,其实有两个问题:

这时候,需要有个步骤将 MIP CDN 中在搜索结果页展现的内容进行组件反解混合,在 MIP CDN 中的页面中是以下形式:

<!-- 一堆 html -->
<mip-a data-a-88c22c74>
    <div data-a-s09saj334>
        <p data-a-23nm3484>MIP</p>
        <span data-a-3ynmjf633>一个吊炸天的东西</span>
    </div>
</mip-a>
<!-- 一堆 html -->

而对应的 MIP 组件模块应该是如下:

<!-- mip-a.vue -->
<template>
    <div>
        <p @click="clickMe">{{ name }}</p>
        <span v-if="flag">{{ desc1 }}</span>
        <span v-else>{{ desc2 }}</span>
    </div>
</template>
<script>
export default {
    // ...
    syncData() {
        let data = {
            "name": "MIP",
            "flag": true,
            "desc1": "一个吊炸天的东西",
            "desc2": "一个垃圾东西"
        };
        this.data = data;
    },

    method: {
        clickMe() {
            // ...
        }
    }
}
</script>

组件反解混合解决方案:

参照 VUE SSR 方案进行 MIP CDN 内容到 MIP 组件 client 端渲染的反解混合绑定事件的操作(这里需要深入了解一下 VUE SSR 机制)。

提供组件安全沙盒环境

MIP 2.0 组件需要控在制组件中使用危险的全局变量的操作,比如 window, document 等,MIP 需要提供一个模块来专门提供给组件自动注入并使用(借助于 loader 来解决)。

<template></template>
<script>
/* -------------- loader 注入内容 ---------------- */
import {sandbox} from 'mip2';
let window = sandbox.window;
let setTimeout = sandbox.setTimeout;
// ...
/* ---------------------------------------------- */

setTimeout(() => {
    // ...
});

</script>

sandbox 具体的实现细节需要由 MIP runtime 给出 sandbox 模块。

封装基础组件

MIP 插件化

实现和 Vue 一样的插件化机制。

组件化方案的产出

产出为 umd 模块

zoumiaojiang commented 6 years ago

我这边总结了一下 mip 1.0 和 2.0 的方案,大部分情况都能够兼容的,但是在 HTML 方面有几点需要讨论下: mip 1.0 含有几个特殊的标签: <mip-data><template>

以及几个特殊的属性: m-bindm-texton

这里有几个问题: 1、由于现在有站长使用了这些特殊的标签和属性,我们是否需要兼容 mip 1.0 的这些? 2、如果我们要兼容,怎么设计组件化方案? 3、如果不兼容,怎么考虑站长升级的问题?

zoumiaojiang commented 6 years ago

我细化了下我们的方案,现在应该是可以完全兼容 1.0 的,大家可以 review 一下

tayqassqan commented 6 years ago

sandbox方案可参考 https://github.com/searchfe/sandbox