基于vue2.0的weex实践(前端视角)

前提:这段时间将公司的几个用we写的weex页面用vue2.0进行了重构,客户端的weexsdk也更新到了0.10.0版本。这篇文章将会在前端视角写到一些新老版本之间有差异的地方。we老版本的实践文章

weex-vue-render v 0.10.0

ios weexSDK 0.10.0

android weexSDK 0.10.0

编译vue文件

在we时代,对于业务代码text.we文件我们借助weex-loader以及webpack可以轻易的进行编译,得到最后需要的js。但是在vue时代,我们需要两个loader,weex-loadervue-loader,对于公司接入来说我们不太会使用官方提供的脚手架工具,一般都是自己实现。这里容易产生混淆的是还有一个loader叫做weex-vue-loader,这个loader一般情况下对于开发者而言不需要手动调用。

在build阶段或者在dev阶段,我们会使用weex-loadervue-loader两个loader。

// native端使用的js

weexWebpackConfig.module.loaders.push({
  test: /\.vue(\?[^?]+)?$/,
  loader: require.resolve('weex-loader')
})
weexWebpackConfig.output.filename = '[name].weex.min.js'
// h5端使用的js

vueWebpackConfig.module.loaders.push({
  test: /\.vue(\?[^?]+)?$/,
  loader: require.resolve('vue-loader')
})
vueWebpackConfig.output.filename = '[name].min.js'

对于native端我们使用weex-loader进行编译vue文件,在weex-loader内部如果检测到该文件是vue则调用weex-vue-loader。对于H5端我们则使用vue官方提供的vue-loader进行vue的编译,同时我们对编译完成的文件名做区别,方便识别。

对于大家自己实现脚手架很容易造成问题的一点是我们需要加入一段注释,让weexsdk识别这是vue。

var bannerPlugin = new webpackG.BannerPlugin(
    '// { "framework": "Vue" }\n',
    { raw: true }
  )
webpackConfig.plugins.push(bannerPlugin);

基于vue2.0的weex实践(前端视角)

来源:官方文档

源码依赖管理

我们的业务是多页形式的,所以接入weex也非常方便。在每个页面引入该页面的由vue编译完后js文件。vueweex-vue-render作为公共资源我们统一引入,因为我们需要结合native方定制一些错误收集等module,所以我们选择将vue、weex-vue-render以及定制化的module、component共同作为源码管理,统一打包成一个公共的文件在H5端引用。

we时代我们也是这么做的,详见。vue时代在大体方向不变,在入口文件处做了微小的调整。

import Vue from 'vue'
window.Vue = Vue

/**
 * 注册component
 * 
 */

// import TestComponent from './components/test/test.vue'
// Vue.component('test-component', TestComponent)


/**
 * 注册module
 * 
 */
require('weex-vue-render')

// shopBase
import shopBase from './modules/shop-base'
window.weex.registerModule('shopBase', shopBase)

// shopModal
import shopModal from './modules/shop-modal'
window.weex.registerModule('shopModal', shopModal)

我们可以看到相比以前component和module都由weex进行注册,而现在vue时代,我们的component将由Vue进行注册,需要注意的是这里我将Vue挂载到了window对象下,因为在各自页面的业务代码中,我们的入口js中需要实例化Vue,需要用到Vue对象。

以及在注册module的时候我们的weex也是取自window对象下,require('weex-vue-render')这个步骤中会将weex挂载在window之下。

具体的component编写现在已经完成变成了vue文件的编写,有vue经验的同学1秒上手。

具体的module编写相比较以前也简化了很多,export一个包含module方法的对象,再调用weex.registerModule进行注册即可。值得注意的是在新官方文档上对html5的拓展部分没有讲到如何书写回调,回调的规则跟旧版还是一样的,下面的代码片段就是例子

const shopBase = {
  isOnline: function (callbackId) {
    const sender = this.sender;
    let hostname = window.location.hostname;
    let result = false;
    if (hostname.indexOf('showjoy.com') !== -1) {
      result = true;
    }
    sender.performCallback(callbackId, {
      data: result
    })
  }

}
export default shopBase


// another js

window.weex.registerModule('shopBase', shopBase)

viewport的转变

这部分内容原理解释的有些绕,对viewport适配不了解的同学可以先看些我之前的文章viewport-and-flexibleJs

开发过we页面的同学应该了解,在之前的weex-hmtl5版本的weex中应该是没有对多屏幕视口适配做处理的,框架把视口适配的工作交给了手淘的flexible.js进行基于rem的视口适配。我们在H5端写页面的时候需要引入一段flexible.js脚本。

基于vue2.0版本的weex舍弃了flexible.jsrem适配方案,改成了750px固定视口的方案。我们会看到不管在什么尺寸的手机屏幕上,layout viewport的值都为750px,所以我编写css代码的时候是无异的,都是以750为满屏宽进行代码的编写,区别仅在于现在要写度量单位px,而we时代是不需要的。

我在重构we页面的时候经历了weex-vue-render两个版本的迭代,一个是v-0.2.0还有一个就是v-0.10.0

这两个版本对viewport的处理也做了修改,我们在编写业务代码的时候也受到了一些由版本迭代带来的影响。

weex-vue-render v-0.2.0版本以及weex-html5老weex版本时我们在html文件中是可以不需要写meta[name=viewport]的,框架会自动帮我们计算得到页面需要的viewport信息。

weex-html5配合使用flexible适配多屏幕,这无需多言了。

weex-vue-render v-0.2.0的适配方式如下代码所示:

// 本文作者注释
// 此处默认值是750,由build时注入
const DEFAULT_VIEWPORT_WIDTH = process.env.VIEWPORT_WIDTH

function parseViewportWidth (config) {
  let width = DEFAULT_VIEWPORT_WIDTH
  if (config && config.width) {
    width = Number(config.width) || config.width
  }
  return width
}

export function setViewport (config = {}) {
  const doc = window.document

  if (doc) {
    const viewportWidth = parseViewportWidth(config)

    // set root font-size
    doc.documentElement.style.fontSize = viewportWidth / 10 + 'px'
  
  // 本文作者注释 
  // 重点语句 得到当前手机屏幕的宽度
    const screenWidth = window.screen.width 
    const scale = screenWidth / viewportWidth
    const contents = [
      `width=${viewportWidth}`,
      `initial-scale=${scale}`,
      `maximum-scale=${scale}`,
      `minimum-scale=${scale}`,
      `user-scalable=no`
    ]

    let meta = doc.querySelector('meta[name="viewport"]')
    if (!meta) {
      meta = doc.createElement('meta')
      meta.setAttribute('name', 'viewport')
      document.querySelector('head').appendChild(meta)
    }

    meta.setAttribute('content', contents.join(','))
  }
}
const screenWidth = window.screen.width

这条语句是这个适配脚本的重点之一,本意是为了拿到当前手机屏幕的ideal viewport理想视口,多数情况下window.screen.width是可以拿到屏幕的ideal viewport的,但是少部分安卓原生浏览器取到的值是有问题的。在正常情况iphone6下,这段脚本得到的值为:

`width=750`,
`initial-scale=0.5`,
`maximum-scale=0.5`,
`minimum-scale=0.5`,
`user-scalable=no`

但是在部分安卓手机下的浏览器会得到错误screenWidth从而得到错误的initial-scale,那么屏幕显示会出现问题。所以weex-vue-render v-0.10.0版本修改了这个脚本的实现,但是代价是我们必须要在html文件中添加一段meta[name=viewport]标签。

标签的形式是:<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">

需要添加这个标签没有在更新日志中体现,只在官网的example中修改了demo的实现,被这个坑绊到了。。。

现在我们看一下在weex-vue-render v-0.10.0中是如何获取到设备的ideal viewport的,这也就解释了为何要在html中添加这个标签了。

基于vue2.0的weex实践(前端视角)

我们直接看源码修改的部分,这里将window.screen.width修改成了document.documentElement.getBoundingClientRect().width

直白话讲就是取得当前屏幕的html的宽度,而html的宽度则是由<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">标签的width=device-width决定的,结合起来讲就是这个标签将html的宽度设置成了device-width宽,而这个宽就是设备的ideal viewport的宽。而且这个值是永远正确的。这种方式计算得到的viewport在各种设备上都会以750px宽度呈现。

另外如果我们是以源码依赖的方式而不是以script方式管理weex-vue-render的,0.10.0版本会有一个bug导致我们得到的viewport的值是undefined。

基于vue2.0的weex实践(前端视角)

原因在于weex-vue-rendernpm包中package.json的main字段指向了"main": "src/render/vue/index.js",源码,而源码中的viewport相关代码中含有let viewportWidth = process.env.VIEWPORT_WIDTH环境变量,并没有文档说明我们在源码依赖weex-vue-render进行编译的时候需要给环境变量process.env.VIEWPORT_WIDTH 设置750的值。所以编译出来的viewportWidth变量的值肯定是undefined。

大家可以npm i [email protected]查看一下。

解决办法一则将main入口指向weex-vue-render编译好的文件。方法二可以在我们自己管理源码的时候添加这个环境变量。

0.10.0sdk支持相对地址并自动补全

0.4.0版本的sdk中是不支持相对地址自动补全的,而我们前端同学在写业务逻辑时都是以相对地址的方式编写跳转路由或者api接口。

比如一个api请求

stream.fetch({
    method: 'GET',
    url: "/api/shop,
    type: 'json'
  }, function (response) {})

h5端浏览器会自动拼接上当前域名的host,而native端拿到的只是这个相对地址请求。我司native方的做法是在网络请求之前手动拼接上需要的host域名,完成网络请求。

但是native端的weexsdk从0.9.4版本开始支持不全相对地址。

Support relative URL, which will resolve real URL by bundle's URL.

但是比较坑的是与我们的业务场景不符合。因为sdk自动补全的host是根据js bundle的url进行的。

比如我们的js bundle地址是cdn1.xxx.com/js/weex.js,但是业务里需要请求的接口的地址是shop.m.xxx.com/api/getContent

问题就是我们在.vue页面里写接口都是相对地址/api/getContent,这样的话weexSDK会根据bundle的host进行转换相对地址,变成了cdn1.xxx.com/api/getContent,导致接口请求404。

所以这个时候需要native那边去写各自的handler和adapter,重写各自的网络请求,再在weexSDK中进行注册,这意味着需要在sdk进行不全之前native就先把url进行补全了,不把机会留给sdk进行处理。

这个问题的issue

小结

这还只是初步迁移到vue时代的尝试,但是基础已经打好,更多的业务已经可以接入进来了。未来还有许多在H5中考虑的内容需要重新在weex的视角下再进行考虑。

本文来自尚妆前端团队南洋,有什么需要讨论的欢迎找我,尤其是viewport相关的内容。

相关推荐