weex-vue-render程式碼淺析
最近在做一些Weex web端的東西,不可避免的和weex-vue-render打起了交道,為了能更好的理解web端的構建,所以花了一點時間走讀了一遍weex-vue-render的原始碼,這裡做一個記錄,以防遺忘。
weex-vue-render是什麼
這是它的ofollow,noindex">github地址 。
我們知道Weex在構建的時候會打包出一個js給客戶端進行載入,而如果我們需要在web端也使用同一套程式碼進行渲染的話,還需要一個web端用的js,並且通過weex-vue-render去渲染。所以一句話概括,weex-vue-render就是用來渲染web端Weex的工具。
原始碼分析
首先我們來看工程下的入口檔案:packages/src/index.js
import weex from '../../../src' import components from '../../../src/components' import modules from '../../../src/modules' import directives from '../../../src/directives' const preInit = weex.init weex.init = function () { preInit.apply(weex, arguments) const plugins = components.concat(modules) plugins.forEach(function (plugin) { weex.install(plugin) }) for (const k in directives) { weex.install(directives[k]) } } if (global.Vue) { weex.init(global.Vue) } export default weex
首先組裝了一個數組,裡面是components和modules,並且呼叫了weex的install方法去註冊,之後呼叫了init方法初始化,最終將其export出去。
從這裡我們就知道,為什麼在載入了這個js之後,我們就可以獲取一個weex物件,這個weex物件就是這裡export出去的。
下面我們來看看weex.install都做了什麼吧~
install (module) { module.init(this) }
而components和modules這個陣列都有些什麼呢?
export default [ geolocation, storage, stream, clipboard, eventModule, modal, websocket, animation, dom, globalEvent, navigatorModule, webview, meta ]
export default [ // a, // div, // image, input, _switch, scrollable, slider, // text, textarea, video, web ]
可以看到,這些其實就和我們客戶端所註冊的是一樣的。我們挑一個來看看:
function getTextarea (weex) { const { extractComponentStyle } = weex const { inputCommon } = weex.mixins const { extend, mapFormEvents } = weex.utils return { name: 'weex-textarea', mixins: [inputCommon], props: { value: String, placeholder: String, disabled: { type: [String, Boolean], default: false }, autofocus: { type: [String, Boolean], default: false }, rows: { type: [String, Number], default: 2 }, returnKeyType: String }, render (createElement) { /* istanbul ignore next */ // if (process.env.NODE_ENV === 'development') { //validateStyles('textarea', this.$vnode.data && this.$vnode.data.staticStyle) // } const events = extend(mapFormEvents(this)) return createElement('html:textarea', { attrs: { 'weex-type': 'textarea', value: this.value, disabled: (this.disabled !== 'false' && this.disabled !== false), autofocus: (this.autofocus !== 'false' && this.autofocus !== false), placeholder: this.placeholder, rows: this.rows, 'return-key-type': this.returnKeyType }, domProps: { value: this.value }, on: this.createKeyboardEvent(events), staticClass: 'weex-textarea weex-el', staticStyle: extractComponentStyle(this) }) } } } export default { init (weex) { weex.registerComponent('textarea', getTextarea(weex)) } }
這是一個component,它的init方法,其實就是呼叫了weex的registerComponent將其註冊。
registerComponent (name, component) { if (!this.__vue__) { return console.log('[Vue Render] Vue is not found. Please import Vue.js before register a component.') } this._components[name] = 0 if (component._css) { const css = component._css.replace(/\b[+-]?[\d.]+rem;?\b/g, function (m) { return parseFloat(m) * 75 * weex.config.env.scale + 'px' }) utils.appendCss(css, `weex-cmp-${name}`) delete component._css } this.__vue__.component(name, component) }
而registerComponent方法,也就是呼叫了vue的component方法了~
說完了install,我們再來看一下init方法:
function init (Vue/*, options = {}*/) { if (_inited) { return } _inited = true setVue(Vue) Vue.prototype.$getConfig = () => { console.warn('[Vue Render] "this.$getConfig" is deprecated, please use "weex.config" instead.') return weex.config } const htmlRegex = /^html:/i Vue.config.isReservedTag = tag => htmlRegex.test(tag) Vue.config.parsePlatformTagName = tag => tag.replace(htmlRegex, '') function isWeexTag (tag) { return typeof weex._components[tag] !== 'undefined' } const oldGetTagNamespace = Vue.config.getTagNamespace Vue.config.getTagNamespace = function (tag) { if (isWeexTag(tag)) { return } return oldGetTagNamespace(tag) } Vue.mixin(base) Vue.mixin(event) Vue.mixin(style) Vue.mixin(sticky) }
其中會呼叫setVue方法:
export function setVue (vue) { if (!vue) { throw new Error('[Vue Render] Vue not found. Please make sure vue 2.x runtime is imported.') } if (global.weex.__vue__) { return } global.weex.__vue__ = vue weex.install(renderFunctionPlugin) console.log(`[Vue Render] install Vue ${vue.version}.`) } export default weex
可以看到,這裡將vue進行了賦值。而這裡的weex,其實是src/weex/instance.js。
大家去看這個js,可以看到裡面有很多方法,例如registerComponent,registerModule等,也就是上面提到的。
而在這個js中,會去import一個wx-env:
import './wx-env'
import '../lib/envd' import { init as initViewport } from './viewport' import { extend } from '../utils' /** * get WXEnvironment info. * @param{object} viewportInfo: info about viewport. * @param{object} envInfo: info parsed from lib.env. */ export function initEnv (viewportInfo, envInfo) { const browserName = envInfo.browser ? envInfo.browser.name : navigator.appName const browserVersion = envInfo.browser ? envInfo.browser.version.val : null let osName = envInfo.os.name if (osName.match(/(iPhone|iPad|iPod)/i)) { osName = 'iOS' } else if (osName.match(/Android/i)) { osName = 'android' } const osVersion = envInfo.os.version.val const env = { platform: 'Web', weexVersion: 'process.env.WEEX_VERSION', userAgent: navigator.userAgent, appName: browserName, appVersion: browserVersion, osName, osVersion, deviceModel: envInfo.os.name || null } /** * viewportInfo: scale, deviceWidth, deviceHeight. dpr */ return extend(viewportInfo, env) } // const viewportInfo = initViewport() // 750 by default currently // const scale = viewportInfo.scale // const units = { //REM: 12 * scale, //VW: viewportInfo.deviceWidth / 100, //VH: viewportInfo.deviceHeight / 100, //VMIN: Math.min(viewportInfo.deviceWidth, viewportInfo.deviceHeight) / 100, //VMAX: Math.max(viewportInfo.deviceWidth, viewportInfo.deviceHeight) / 100, //CM: 96 / 2.54 * scale, //MM: 96 / 25.4 * scale, //Q: 96 / 25.4 / 4 * scale, //IN: 96 * scale, //PT: 96 / 72 * scale, //PC: 96 / 6 * scale, //PX: scale // } // Object.freeze(units) // Object.freeze(env) // window.CSS_UNIT = units window.WXEnvironment = initEnv(initViewport(), window.lib.env)
這個類會呼叫initEnv方法,而initEnv的入參需要呼叫initViewport方法。
export function init (viewportWidth = width) { if (!isInited) { isInited = true const doc = window.document if (!doc) { console.error('[vue-render] window.document is undfined.') return } if (!doc.documentElement) { console.error('[vue-render] document.documentElement is undfined.') return } dpr = info.dpr = window.devicePixelRatio screenWidth = doc.documentElement.clientWidth screenHeight = doc.documentElement.clientHeight const resetDeviceHeight = function () { screenHeight = doc.documentElement.clientHeight const env = window.weex && window.weex.config.env info.deviceHeight = env.deviceHeight = screenHeight * dpr } // set root font for rem. setRootFont(screenWidth, viewportWidth) setMetaViewport(viewportWidth) window.addEventListener('resize', resetDeviceHeight) /** * why not to use window.screen.width to get screenWidth ? Because in some * old webkit browser on android system it get the device pixel width, which * is the screenWidth multiply by the device pixel ratio. * e.g. ip6 -> get 375 for virtual screen width. */ const scale = screenWidth / viewportWidth /** * 1. if set initial/maximum/mimimum-scale some how the page will have a bounce * effect when user drag the page towards horizontal axis. * 2. Due to compatibility reasons, not to use viewport meta anymore. * 3. viewport meta should always be: *<meta name="viewport" *content="width=device-width, *initial-scale=1, *maximum-scale=1, *user-scalable=no" /> */ extend(info, { scale, rootValue: viewportWidth / 10, deviceWidth: screenWidth * dpr, deviceHeight: screenHeight * dpr }) } return info }
這個方法做的事情其實就是初始化viewport。
setRootFont(screenWidth, viewportWidth)
這一段是設定html的font-size。
setMetaViewport(viewportWidth)
這一段是設定weex的weex-viewport標籤。
通過這些設定,Weex可以做到在web端不同機型的適配。
const DEFAULT_VIEWPORT_WIDTH = 750 function setRootFont (width, viewportWidth, force) { const doc = window.document const rem = width * 750 / viewportWidth / 10 if (!doc.documentElement) { return } const rootFontSize = doc.documentElement.style.fontSize if (!rootFontSize || force) { doc.documentElement.style.fontSize = rem + 'px' } info.rem = rem info.rootValue = viewportWidth / 10 }
rem這個變數的計算我們代入具體的數字就可以知道,其實就是screenWdith/10(因為viewportWidth預設是750)。
這樣呢,我們html的font-size就是screenWidth/10了。
這個我們需要結合文章開頭講到的webpack配置來一起看:
require('postcss-plugin-px2rem')({ // base on 750px standard. rootValue: 75, // to leave 1px alone. minPixelValue: 1.01 })
在配置中,我們使用了postcss-plugin-px2rem外掛,並且設定rootValue是75。這樣的話,我們在寫程式碼的時候所有的px值都會被除以75並轉換成rem,結合剛才的font-size,我們就可以做到螢幕的適配了。
總結
通過上面的原始碼分析,我們可以得出weex-vue-render做了以下幾件事:
- export出了一個weex物件(scr/weex/instance)。該物件具有registerComponent,registerModule等方法。
- 通過呼叫weex.install這個方法註冊了所有基礎的module和component。
- 通過設定html的font-size,並且配合postcss-plugin-px2rem外掛做到了適配不同的螢幕。