微信小程序原理解析之WXML和WXSS编译

2,043 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

微信小程序的页面有4个基本文件,分别是wxml、wxss、js、json,wxml相当于html文件,wxss相当于css,js文件里是逻辑代码,json是配置文件。本文主要分析的是跟视图层相关的wxml和wxss文件,探究其在小程序内的编译过程。

WXML文件编译

WXML编译流程

整个wxml是类似于模板+数据的形式组成,wxml的编译会先编译出结构生成函数,然后再结合data生成Virtual Dom,最后形成Dom Tree。整体流程如图所示:

编译流程.png

wcc编译WXML

wxml通过wcc工具进行编译,首先来找到wcc编辑工具,把控制台调为top选项,然后执行help()

控制台help.png 再执行index为8的方法openVendor(),打开工具文件夹。

wcc.png

wcc复制出来,以编译home页面wxml为例,执行如下方法。

./wcc -d index.wxml >> home.js

$gwx.png

$gwx方法

分析编译出来的home.js文件可以看到,整体上其实是一个函数$gwx,它用于后续virtual dom的编译。

/*v0.5vv_20200413_syb_scopedata*/window.__wcc_version__='v0.5vv_20200413_syb_scopedata';window.__wcc_version_info__={"customComponents":true,"fixZeroRpx":true,"propValueDeepCopy":false};
var $gwxc
var $gaic={}
$gwx=function(path,global){
...
}

生成virtual dom

结合开发者工具home页面对应的webview,可以看到$gwx函数执行后会生成generateFunc函数generateFunc.png 再执行generateFunc,就能得到virtual dom,这种最终转换成虚拟dom的形式,跟vue框架的编译流程有些类似,优点是不用频繁操作dom来改变页面,以一种低成本的方式达到页面动态渲染。
接着看下home页面的index.wxml文件结构。

<!-- /pages/home/index.wxml -->
<view class="home">
  <view class="home-msg">
    <text>{{msg}}</text>
  </view>
</view>

然后分别执行不带参数和不参数的generateFunc方法。

var decodeName = decodeURI("./pages/home/index.wxml")
var generateFunc = $gwx(decodeName)
// 不传入参数
generateFunc()
// 传入参数
generateFunc({msg: 'home page'})

得到如下的虚拟dom结构,可以看到在tag为wx-view的数组中会直接把参数填入children,用于后续渲染。

合并geneFun.png

WXSS文件编译

WXSS编译流程

wxss文件的编译同wxml类似,也是通过编译工具完成。先是用wcsc编译工具把wxss文件编译成js文件,再转换后把样式插入到style中,如下图所示:

wxss编译流程图.png

wcsc编译WXSS

同上一样,使用openVendor()打开WeappVendor文件夹,复制出wcsc工具,还是以home页面为例,粘贴到home目录下,执行如下命令进行编译。

./wcsc -js index.wxss >> wxsstojs.js

得到wxss对应的js文件,如下图所示,可以看到整个js文件其实是对一些设备信息进行定义后,执行setCsstoHead方法。 wxsstojs文件.png

setCssToHead方法

setCssToHead方法从函数名称上很好理解,就是把css设置到head中,使其起作用进行样式渲染,这样整个环节就完成了。

// setCssToHead方法
var Ca = {};
var css_id;
var info = info || {};
var _C = __COMMON_STYLESHEETS__
function makeup(file, opt) {
...
}
if ( !style )
{
  var head = document.head || document.getElementsByTagName('head')[0]; // 获取head节点
  style = document.createElement('style'); // 创建style标签
  style.type = 'text/css'; // 设置type
  style.setAttribute( "wxss:path", info.path ); // 设置属性
  head.appendChild(style); // 把style插入到head
  ...
}

从上面代码可以清楚的看到把style插入到head的过程,比较简单,就不一一解读了。
可能有个疑问,整个js是怎么执行的,看下home页面对应的webview,是通过eval方法执行了。

home页面webview.png 插入的style和原始wxss文件做个对比,发现基本上差不多,但rpx已转成了px,这点后面讲。

// pages/home/index.wxss
.home {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.home-msg {
  margin-top: 400rpx;
}

彩蛋环节

rpx转成px

rpx是微信小程序的单位,开发者在写样式无需做兼容转化就能直接用,我们来看下小程序内部转化的逻辑。
在wxss编译后的js文件中,有个transformRPX方法,进行rpx转px,实际上就是rpx值 / 基础宽度750 * 设备宽度

// wxsstojs.js
var eps = 1e-4;
var transformRPX = window.__transformRpx__ || function(number, newDeviceWidth) {
  if ( number === 0 ) return 0;
    number = number / BASE_DEVICE_WIDTH * ( newDeviceWidth || deviceWidth ); // rpx换算成px单位值
    number = Math.floor(number + eps); // 精度处理
  if (number === 0) { // 计算值过小处理
    if (deviceDPR === 1 || !isIOS) {
      return 1;
    } else {
      return 0.5;
    }
  }
  return number;
}

写在最后

本文写到这里结束了,欢迎支持下,点赞+关注+评论三连!