特定平台代码

    当开发者面对产品需求需要同时去支持多个不同平台的时候,我们期望的理想目标是一份代码可以同时跑在不同的平台上,对于开发者来说这是最美妙的事情,可以给开发者减少不少开发和维护成本。但现实是由于平台差异,主要包括组件、样式和 API 差异,导致很难达到这样一个理想目标。此外也有一些情况是属于产品需求带来的差异性,比如同一个产品可能 PM 要求在微信上加一个社交属性的功能,其它平台不需要。

    虽然尽量在抹平不同平台间的差异,但在某个阶段可能还很难或者做不到完全抹平,为了能让开发者撰写不同平台间的差异代码,okam 针对 配置脚本模板样式组件API 提供了相应的机制来撰写特定平台代码。

    !> 建议所有对齐目标以 微信小程序 作为参照,考虑到各家小程序平台都是在参考(对齐)微信小程序,因此不管是组件还是API,当你需要抹平差异性时候,建议以 微信小程序 作为参照。目前 Okam 提供的部分 API 对齐实现也是以 微信小程序 作为参照。

    目前支持的 App 类型(平台) appType 主要支持如下几种:

    • swan: 百度小程序
    • wx: 微信小程序
    • ant: 支付宝小程序
    • tt: 头条小程序
    • quick: 快应用

    下述文档中,如有提及 appType 默认情况下指代的都是这里罗列出来的 appType,对此不再赘述。

    配置

    目前 okam 支持了多个平台的小程序包括快应用开发,如果期望同一份代码在不同平台上运行,由于不同平台在应用配置上存在差异,尤其快应用跟其它小程序的配置上差异比较大。为了定义不同平台的配置,可以使用框架提供的私有环境属性 _${appType}Env 来定义特定平台的配置,甚至重写已有的一些配置定义。

    以下述 app config 为例子,_quickEnv 是针对 快应用 增加的特殊配置:

    • 如果构建的是 非快应用,该配置项会被忽略(删除)
    • 如果构建的是 快应用,会先将 _quickEnv 里定义的值跟外部的配置做 merge,注意对于 windows 配置项比较特殊,会做对象值 merge,而不是值直接覆盖。此外,对于第一层属性值为 null 以及 windows 配置属性里值为 null 属性都会被删除。

    经过处理后的 app config 将变成如下结构:

    1. {
    2. window: {
    3. navigationBarTextStyle: 'white',
    4. backgroundColor: '#211E2E',
    5. menu: true
    6. },
    7. package: 'com.okam.demo',
    8. name: 'okam-quick',
    9. versionCode: '1',
    10. icon: '/common/img/logo.png'
    11. }

    上述处理只是第一步处理,后续还会再基于快应用转换规则,再对上述配置再做二次转换,具体可以参考。

    为了开发针对不同平台的脚本逻辑,可以使用环境变量 process.env.APP_TYPE 进行判断,针对不同的 appType 做相应的逻辑处理:

    1. if (process.env.APP_TYPE === 'wx') {
    2. // 针对 微信小程序 处理逻辑
    3. }
    4. else if (process.env.APP_TYPE === 'swan') {
    5. // 针对 百度小程序 处理逻辑
    6. }
    7. // ...

    经过构建处理,会自动将 process.env.APP_TYPE 替换成对应的要构建的目标 appType 常量。

    模板

    为了在模板里定义不同平台的视图模板结构,可以使用 ${appType}-env 环境标签,该标签所起作用跟 template 类型,只是一个占位符,来包裹真实的模板代码。环境标签不可以互相嵌套,环境标签内部可以存在多个根节点。环境标签不支持任何属性定义包括指令使用。

    1. <template>
    2. <view class="home-page">
    3. <wx-env>
    4. <view class="wx-app-tip">
    5. 欢迎使用微信小程序
    6. </view>
    7. </wx-env>
    8. <swan-env>
    9. <swan-spec-component></swan-spec-component>
    10. </swan-env>
    11. </view>
    12. </template>

    根据构建的目标 appType 会保留目标 appType 的环境标签,移除非当前 appType 的环境标签(整个节点移除)。对于当前构建目标的 appType 的环境标签,会将其环境标签的包裹节点移除掉。

    假设当前的构建目标是微信小程序,则上述模板构建后的输出为:

    1. <template>
    2. <view class="home-page">
    3. <view class="wx-app-tip">
    4. 欢迎使用微信小程序
    5. </view>
    6. </view>
    7. </template>
    • 平台前缀的属性定义
    • 特定 appType 的媒介查询

    注意: 为了开启该功能,需要引入 postcss 插件 env,具体配置可以参考

    如果只是微调不同平台的样式差异,可以通过给样式属性添加平台前缀方式增加特定平台样式:,比如微信小程序下的字体想调大些,可以按如下方式来撰写:

    如果构建的目标 appType 是非微信小程序,则会移除非当前目标 appType 前缀的样式属性定义。如果构建目标是微信小程序,则会保留当前目标 appType 前缀属性定义,经过转换后,会移除该 appType 前缀,并移除其覆盖的样式属性声明。

    如果构建 appType 是非微信小程序,上述样式输出:

    1. margin: 15px 0;
    2. color: #000;
    3. font-size: 12px; /* 默认使用的样式 */
    4. }

    如果构建 appType 是微信小程序,上述样式输出:

    1. .page-wrap .title {
    2. margin: 15px 0;
    3. color: #000;
    4. font-size: 14px; /* 针对微信微调下样式 */
    5. }

    如果该样式规则只有 appType 前缀的样式声明,如果构建后,该样式规则的样式声明是空的,则整条规则会自动移除掉:

    1. .page-wrap .title {
    2. -wx-font-size: 14px; /* 针对微信微调下样式 */
    3. }

    如果构建 appType 是非微信小程序,上述样式规则会整条移除掉。

    媒介查询

    针对标准的 media 媒介查询语法的 media target 进行了扩展,增加了当前支持的 appType 的媒介查询。具体使用语法跟标准语法一致,除了要求 appType 媒介查询写在前面外。

    该扩展语法,一般用在使用了环境标签,该环境标签里定义的样式,只有其对应的 appType 的目标媒介才有的样式,可以使用该种语法。此外,对于不同平台样式差异比较大,比如快应用跟其它小程序,可以通过该语法进行特定平台的样式定义。

    1. @media not quick { /* 非快应用的样式定义 */
    2. .home-wrap {
    3. padding: 100px;
    4. height: 100vh;
    5. box-sizing: border-box;
    6. background: #ddd;
    7. }
    8. }
    9. @media quick { /* 快应用的样式定义 */
    10. .home-wrap {
    11. flex-direction: column;
    12. align-items: center;
    13. padding: 100px;
    14. flex: 1;
    15. font-size: 12px;
    16. background-color: #ddd;
    17. }
    18. }

    构建时候,如果构建目标是微信小程序,则会移除不满足当前构建目标 appType 的媒介查询的样式定义(整个样式规则移除掉),如果满足当前的媒介查询的 appType 则保留该样式规则,并移除媒介查询条件里appType 相关的媒介查询条件,如果只有 appType 的媒介查询条件,则将媒介查询包裹语法移除掉。

    假设上述构建的是快应用,则构建后上述样式输出如下,非快应用的媒介查询被整体移除,快应用的媒介查询条件被移除掉,保留相关的样式规则定义:

    组件

    如果针对不同平台,要加载使用的组件不同或者组件在不同平台有不同实现,目前提供了一个全局组件定义配置,目前主要用来对齐不同平台组件的实现,也可以用于自定义组件自动导入。

    比如,快应用平台下没有 button 原生组件,为了使原先使用的小程序 button 组件也一样能用,需要我们自己实现对应的快应用组件。假设我们实现了一个快应用的 button 组件,定义在 components/quick/Button.vue,下面代码为一个简单实现(未对齐原生小程序提供的功能),只是保证能用上 button 组件。

    1. <template>
    2. <div class="button">
    3. <slot></slot>
    4. </div>
    5. </template>
    6. <script>
    7. export default {
    8. props: {
    9. plain: {
    10. type: Boolean,
    11. default: false
    12. },
    13. disabled: {
    14. type: Boolean,
    15. default: false
    16. },
    17. loading: {
    18. type: Boolean,
    19. default: false
    20. },
    21. size: {
    22. type: String,
    23. default: 'default'
    24. },
    25. type: {
    26. default: 'default'
    27. }
    28. };
    29. </script>
    30. <style lang="stylus">
    31. .button
    32. display: flex
    33. justify-content: center
    34. align-items: center
    35. padding: 0 14px
    36. font-size: 18px
    37. text-align: center
    38. border-radius: 5px
    39. color: #000000
    40. background-color: #F8F8F8
    41. </style>
    • 构建配置定义全局组件

    在快应用的构建配置:quick.config.js 文件里加上 component.global 的全局组件配置。
    下述配置里,也定义了一个模板标签转换配置:button 转成 my-button 标签,主要是由于 button 属于特殊的组件名称,快应用不允许使用,因此需要将代码里的 button 标签转成 my-button 标签,然后在全局注册一个 my-button 组件,组件路径相对于项目源码,如果组件是一个 npm 模块,则直接引用模块 id 即可。

    1. {
    2. component: {
    3. template: {
    4. transformTags: {
    5. 'button': 'my-button'
    6. }
    7. },
    8. global: {
    9. 'my-button': './components/quick/Button'
    10. }
    11. }
    12. }
    1. <template>
    2. <view class="hello-wrap">
    3. <button plain class="hello-btn" @click="handleClick">Hello in {{from}}</button>
    4. </view>
    5. </template>

    component.global 配置方式,可以让我们轻松去对齐不同平台组件没有对齐或者缺失问题,而且代码不会因此存在各种特定平台代码逻辑。而且不用担心,特定平台代码组件会构建输出到其它平台上,构建会分析代码依赖关系,如果平台用不到的组件不存在构建产物里。

    如果由于平台实现不同的原因,需要针对不同平台写不同的自定义组件,或者某个平台下才需要引入某个自定义组件,同样也可以基于 component.global 配置来实现引用特定平台组件的逻辑。

    建议 对于特定平台的 组件 定义,按 appType 目录进行组织归类。

    由于不同平台提供的平台 API 存在一定差异,具体差异可能存在两方面,API 缺失(一个平台存在,另外一个平台缺失),或者同一个API,不同的平台间的实现存在一定差异性。okam 框架除了在框架层面会做一定的 API 差异抹平,当前阶段框架层面还做不到所有 API 差异的抹平。

    以支付宝的 showToast API 为例,我们可以通过官方文档,可以看下他们之间存在的差异性:

    • 支付宝 my.showToast API 参数定义

    • 微信 API 参数定义

      微信showToast

    目前所有的小程序平台的设计实现都是以微信作为参照,因此在抹平 API 差异上包括组件,okam 主要是以 微信小程序 作为标准和依据。

    下述代码为目前 okam 针对支付宝小程序默认提供的 showToast API 对齐的一个实现:

    1. {
    2. /**
    3. * Show toast api
    4. *
    5. * @param {Object} options the options to showToast
    6. */
    7. showToast(options) {
    8. let {title, icon, content, type} = options;
    9. if (title && !content) {
    10. options.content = title;
    11. }
    12. if (icon && !type) {
    13. options.type = icon;
    14. }
    15. my.showToast(options);
    16. }
    17. }

    如果当前 okam 没有提供某些 API 对齐实现,开发者可以基于构建配置选项 api 来定义自己的 API,此外,如果想针对特定平台增加特定的平台或者所有平台想增加自己业务相关的全局 API,也一样可以通过该选项来进行扩展。如果 okam 提供的 默认的 API 对齐实现有 bug 或者有问题,一样也可以通过该配置进行重写,当然我们还是希望开发者能积极给我们提 pr 或者 反馈 bug

    加上上述配置后,要求重启构建工具,重新构建,开发者可以在自己的组件、app 实例上下文通过 this.$api.hi 访问到我们扩展的 API。

    建议 对于特定平台的 API 定义,按 appType 目录进行组织归类。