基於vue2.0的weex實踐(前端視角)

2dunn發表於2019-03-03

19年目標:消滅英語!我新開了一個公眾號記錄一個程式設計師學英語的歷程

有提升英語訴求的小夥伴可以關注公眾號:csenglish 程式設計師學英語,每天花10分鐘交作業,跟我一起學英語吧


前提:這段時間將公司的幾個用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" }
`,
    { 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 weex-vue-render@0.10.0檢視一下。

解決辦法一則將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相關的內容。

相關文章