小程序编译&发布,小程序的编译原理技术

知梧 1549 2022-09-16

本文关于小程序编译&发布,小程序的编译原理技术。

小程序编译&发布,小程序的编译原理技术

1、编译

在微信开发者工具中直接编辑就可以,立马就能在左侧看到效果,还可以真机调试,扫描二维码,或者登录微信号,即可在手机上打开微信查看

2、发布

微信开发者工具编译后点击上传,输入版本号和项目备注即可

官方还提供了命名上传和http上传,具体可查看官方文档。

上传成功之后,登录小程序管理后台 - 开发管理 - 开发版本 就可以找到刚提交上传的版本了。

3、提交审核

首先确保小程序基本信息已经配置好,如何点击提交版本,输入项目信息。提交成功后可能会看不到审核版本,这是后台的bug,重新登录后台就可以了,接下来就等待审核通过

原理分析

双线程模型

无论是微信小程序还是支付宝小程序还是百度智能小程序等等,他们的总体架构都是基于双线程的。

其中用于处理业务逻辑的 JS 代码运行在单独的线程里,渲染层(template、css)则运行在另外一个单独的线程里。

以微信小程序为例:

双线程模型不同于单线程模型,逻辑层与渲染层的数据交互需要通过 JSBridge,二者是通过发布订阅,基于当前比较比较著名的 MVVM,来实现数据的双向绑定的,从而实现数据通信。

这样我们在微信小程序中通过在逻辑层中 setData 来改变 Model 层的数据就能够实现视图数据的异步更新。

以下是微信小程序的生命周期:

整体架构

注:以下所有内容均围绕微信开发者工具展开。

打开微信开发者工具的源代码,他是基于 NW.js 运行的,所以下图中的 package.nw 就是我们要重点钻研的对象:

这里面有很多代码,都是经过混淆与压缩的,将代码在 VSCode 中打开,并安装 Prettier - Code formatter 插件可以实现对源码的格式化。但此时代码中已经不具有语义化的变量了,只能通过 API 大致推断代码是干什么的。

源码中有一个 vendor 文件夹是值得注意的,通过它可以快速新建一个示例项目,同时里面有一个十分重要的 2.17.0.wxvpkg 包,它是微信小程序的基础库,包含了下文所提及的 WebService 与 WebView 等逻辑层与渲染层的处理。

 WAWebview

小程序视图层基础库,提供视图层的基础能力:

var __wxLibrary = {

  fileName: 'WAWebview.js',

  envType: 'WebView',

  contextType: 'others',

  execStart: Date.now()

};

var __WAWebviewStartTime__ = Date.now();

var __libVersionInfo__ = {

  "updateTime": "2020.4.4 10:25:02",

  "version": "2.10.4"

};

 

/**

 * core-js 模块

 */

!function(n, o, Ye) {

  ...

  }, function(e, t, i) {

    var n = i(3),

      o = "__core-js_shared__",

      r = n[o] || (n[o] = {});

    e.exports = function(e) {

      return r[e] || (r[e] = {})

    }

  ...

}(1, 1);

 

var __wxConfig;

var __wxTest__ = false;

 

var wxRunOnDebug = function(e) {

  e()

};

 

/**

 * 基础模块

 */

var Foundation = function(i) {

  ...

}]).default;

 

var nativeTrans = function(e) {

  ...

}(this);

 

/**

 * 消息通信模块

 */

var WeixinJSBridge = function(e) {

  ...

}(this);

 

/**

 * 监听 nativeTrans 相关事件

 */

!function() {

  ...

}();

 

/**

 * 解析配置

 */

!function(r) {

  ...

  __wxConfig = _(__wxConfig), __wxConfig = v(__wxConfig), Foundation.onConfigReady(function() {

    m()

  }), n ? __wxConfig.__readyHandler = A : d ? Foundation.onBridgeReady(function() {

    WeixinJSBridge.on("onWxConfigReady", A)

  }) : Foundation.onLibraryReady(A)

}(this);

 

/**

 * 异常捕获(error、onunhandledrejection)

 */

!function(e) {

  function t(e) {

    Foundation.emit("unhandledRejection", e) || console.error("Uncaught (in promise)", e.reason)

  }

  "object" == typeof e && "function" == typeof e.addEventListener ? (e.addEventListener("unhandledrejection", function(e) {

    t({

      reason: e.reason,

      promise: e.promise

    }), e.preventDefault()

  }), e.addEventListener("error", function(e) {

    var t;

    t = e.error, Foundation.emit("error", t) || console.error("Uncaught", t), e.preventDefault()

  })) : void0 === e.onunhandledrejection && Object.defineProperty(e, "onunhandledrejection", {

    value: function(e) {

      t({

        reason: (e = e || {}).reason,

        promise: e.promise

      })

    }

  })

}(this);

 

/**

 * 原生缓冲区

 */

var NativeBuffer = function(e) {

  ...

}(this);

var WeixinNativeBuffer = NativeBuffer;

var NativeBuffer = null;

 

/**

 * 日志模块:wxConsole、wxPerfConsole、wxNativeConsole、__webviewConsole__

 */

var wxConsole = ["log", "info", "warn", "error", "debug", "time", "timeEnd", "group", "groupEnd"].reduce(function(e, t) {

  return e[t] = function() {}, e

}, {});

 

var wxPerfConsole = ["log", "info", "warn", "error", "time", "timeEnd", "trace", "profile", "profileSync"].reduce(function(e, t) {

  return e[t] = function() {}, e

}, {});

 

var wxNativeConsole = function(i) {

  ...

}([function(e, t, i) {

  ...

}]).default;

 

var __webviewConsole__ = function(i) {

  ...

}([function(e, t, i) {

  ...

}]);

 

/**

 * 上报模块

 */

var Reporter = function(i) {

  ...

}([function(e, L, O) {

  ...

}]).default;

 

var Perf = function(i) {

  ...

}([function(e, t, i) {

  ...

}]).default;

 

/**

 * 视图层 API

 */

var __webViewSDK__ = function(i) {

  ...

}([function(e, L, O) {

  ...

}]).default;

var wx = __webViewSDK__.wx;

 

/**

 * 组件系统

 */

var exparser = function(i) {

  ...

}([function(e, t, i) {

  ...

}]);

 

/**

 * 框架粘合层

 * 

 * 使用 exparser.registerBehavior 和 exparser.registerElement 方法注册内置组件

 * 转发 window、wx 对象上到事件转发到 exparser

 */

!function(i) {

  ...

}([function(e, t) {

  ...

}, function(e, t) {}, , function(e, t) {}]);

 

/**

 * Virtual DOM 

 */

var __virtualDOMDataThread__ = false;

var __virtualDOM__ = function(i) {

  ...

}([function(e, t, i) {

  ...

}]);

 

/**

 * __webviewEngine__

 */

var __webviewEngine__ = function(i) {

  ...

}([function(e, t, i) {

  ...

}]);

 

/**

 * 注入默认样式到页面

 */

!function() {

  ...

  function e() {

     var e = i('...');

    __wxConfig.isReady ? void0 !== __wxConfig.theme && i(t, e.nextElementSibling) : __wxConfig.onReady(function() {

      void0 !== __wxConfig.theme && i(t, e.nextElementSibling)

    })

  }

  window.document && "complete" === window.document.readyState ? e() : window.onload = e

}();

 

var __WAWebviewEndTime__ = Date.now();

typeof __wxLibrary.onEnd === 'function' && __wxLibrary.onEnd();

__wxLibrary = undefined;


WAWebview 主要由以下几个部分组件:


Foundation:基础模块


WeixinJSBridge:消息通信模块


exparser:组件系统模块


__virtualDOM__:Virtual DOM 模块


__webViewSDK__:WebView SDK 模块


Reporter:日志上报模块 (异常和性能统计数据)


2.2.2 WAService

小程序逻辑层基础库,提供逻辑层基础能力:


var __wxLibrary = {

  fileName: 'WAService.js',

  envType: 'Service',

  contextType: 'App:Uncertain',

  execStart: Date.now()

};

var __WAServiceStartTime__ = Date.now();

 

(function(global) {

  var __exportGlobal__ = {};

  var __libVersionInfo__ = {

    "updateTime": "2020.4.4 10:25:02",

    "version": "2.10.4"

  };

  var __Function__ = global.Function;

  var Function = __Function__;

 

  /**

   * core-js 模块

   */

  !function(r, o, Ke) {

  }(1, 1);

 

  var __wxTest__ = false;

  var wxRunOnDebug = function(e) {

    e()

  };

 

  var __wxConfig;

  /**

   * 基础模块

   */

  var Foundation = function(n) {

    ...

  }([function(e, t, n) {

    ...

  }]).default;

 

  var nativeTrans = function(e) {

    ...

  }(this);

 

  /**

   * 消息通信模块

   */

  var WeixinJSBridge = function(e) {

    ...

  }(this);

 

  /**

   * 监听 nativeTrans 相关事件

   */

  !function() {

    ...

  }();

 

  /**

   * 解析配置

   */

  !function(i) {

    ...

  }(this);

 

  /**

   * 异常捕获(error、onunhandledrejection)

   */

  !function(e) {

    ...

  }(this);

 

  /**

   * 原生缓冲区

   */

  var NativeBuffer = function(e) {

    ...

  }(this);

  WeixinNativeBuffer = NativeBuffer;

  NativeBuffer = null;

 

  var wxConsole = ["log", "info", "warn", "error", "debug", "time", "timeEnd", "group", "groupEnd"].reduce(function(e, t) {

      return e[t] = function() {}, e

    }, {});

 

  var wxPerfConsole = ["log", "info", "warn", "error", "time", "timeEnd", "trace", "profile", "profileSync"].reduce(function(e, t) {

    return e[t] = function() {}, e

  }, {});

 

  var wxNativeConsole = function(n) {

    ...

  }([function(e, t, n) {

    ...

  }]).default;

 

  /**

   * Worker 模块

   */

  var WeixinWorker = function(e) {

    ...

  }(this);

 

  /**

   * JSContext

   */

  var JSContext = function(n) {

    ...

  }([

    ...

  }]).default;

 

  var __appServiceConsole__ = function(n) {

    ...

  }([function(e, N, R) {

    ...

  }]).default;

 

  var Protect = function(n) {

    ...

  }([function(e, t, n) {

    ...

  }]);

 

  var Reporter = function(n) {

    ...

  }([function(e, N, R) {

    ...

  }]).default;

 

  var __subContextEngine__ = function(n) {

    ...

  }([function(e, t, n) {

    ...

  }]);

 

  var __waServiceInit__ = function() {

    ...

  }

 

  function __doWAServiceInit__() {

    var e;

    "undefined" != typeof wx && wx.version && (e = wx.version), __waServiceInit__(), e && "undefined" != typeof __exportGlobal__ && __exportGlobal__.wx && (__exportGlobal__.wx.version = e)

  }

  __subContextEngine__.isIsolateContext();

  __subContextEngine__.isIsolateContext() || __doWAServiceInit__();

  __subContextEngine__.initAppRelatedContexts(__exportGlobal__);

})(this);

 

var __WAServiceEndTime__ = Date.now();

typeof __wxLibrary.onEnd === 'function' && __wxLibrary.onEnd();

__wxLibrary = undefined;


WAService 基本组成:


Foundation:基础模块


WeixinJSBridge:消息通信模块


WeixinNativeBuffer:原生 Buffer


WeixinWorker:Worker 线程


JSContext:JS Engine Context


Protect:JS 保护的对象


__subContextEngine__:提供 App、Page、Component、Behavior、getApp、getCurrentPages 等方法


2.2.3 虚拟 DOM

微信小程序在 WAService 里面实现了小程序的 __virtualDOM__,通过 __virtualDOM__ 模块,可以实现 JS 对象到 DOM 对象的映射。


但是这个虚拟 DOM 通过 diff 和 patch 后并不是转换成原生的 DOM 元素,而是微信小程序里面自定义的 DOM 元素,这些 DOM 元素的操作通过 Exparser 模块来统一管理:


在 WAWebview 中包含了所有的 wx 自定义标签:


同时,__virtualDOM__ 模块提供了很多的基础 API,比如:


getAll:获取所有 Node


getNodeById:根据 Id 获取 Node


getNodeId:获取 NodeId


addNode:添加节点


removeNode:删除节点


getExparser:获取 Exparser 对象(基于 WebComponent 的 shadow DOM 模型,可以在 JS 环境中运行,所有与节点树相关的操作都依赖于他)


...


(更多的 API 定义可以在 WAService.js 里面去查询)


WeiXinJSBridge

WeixinJSBridge 提供了视图层 JS 与 Native、视图层与逻辑层之间消息通信的机制,提供了如下几个方法:

里面最重要的便是 on 和 invoke,通过 on 来注册事件,通过 invoke 来触发相应的事件。

微信开发者工具

微信开发者工具中的小程序是跑在 NW.js 中的,这里是他的官方 API 文档:https://nwjs.readthedocs.io/en/latest/

他是基于 Chromium 和 Node.js 的,因此我们编译后的虚拟 DOM 转换成真实 DOM 后,通过他来运行。

 一些反编译技巧

我们可以通过开发者工具,在 Devtools 里输入 help 可以得到很多指令:

其中比较有用的是 openVendor。这个函数可以打开当前项目的源码,其实也就是包含了 wcc 和 wcsc 编译工具的一个文件夹:

有了这些文件之后,对我们之后的分析会很有帮助。

我们可以将这些文件拷贝到一个单独的目录,在 VSCode 中打开该项目,并安装以下插件:

这个插件可以将微信开发者工具中的所有以 .wxvpkg 结尾的文件进行解压缩。

同时通过他来将 quickstart 中的 miniProgramJs.wxvpkg 进行解压,得到我们在开发者工具中的源码文件。

 编译原理

 wcc 编译 wxml

微信小程序提供了 wcc 工具来编译 wxml 代码。通过上面得到的代码,我们可以实现对 wxml 的编译,以开发者工具创建的 Demo 项目中的首页为例:

<view class="container">

  <view class="userinfo">

    <block wx:if="{{canIUseOpenData}}">

      <view class="userinfo-avatar" bindtap="bindViewTap">

        <open-data type="userAvatarUrl"></open-data>

      </view>

      <open-data type="userNickName"></open-data>

    </block>

    <block wx:elif="{{!hasUserInfo}}">

      <button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>

      <button wx:elif="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>

      <view wx:else> 请使用1.4.4及以上版本基础库 </view>

    </block>

    <block wx:else>

      <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>

      <text class="userinfo-nickname">{{userInfo.nickName}}</text>

    </block>

  </view>

  <view class="usermotto">

    <text class="user-motto">{{motto}}</text>

  </view>

</view>


经过以下指令编译:

./wcc ./quickstart/miniProgramJs.unpack/pages/index/index.wxml > index.js

会得到 JS 描述文件:

它会声明一个 $gwx 函数,通过它可以得到 Virtual DOM。接着我们在这个文件里添加几行代码去调用它,并通过 Node.js 或者 NW.js 执行这个文件:

var data = $gwx('./quickstart/miniProgramJs.unpack/pages/index/index.wxml')();

console.log(JSON.stringify(data, null, 2));

可以得到我们想要的最终的 Virtual DOM 结构:


{

  "tag": "wx-page",

  "children": [

    {

      "tag": "wx-view",

      "attr": {

        "class": "container"

      },

      "children": [

        {

          "tag": "wx-view",

          "attr": {

            "class": "userinfo"

          },

          "children": [

            {

              "tag": "wx-view",

              "attr": {},

              "children": [

                " 请使用1.4.4及以上版本基础库 "

              ],

              "raw": {},

              "generics": {}

            }

          ],

          "raw": {},

          "generics": {}

        },

        {

          "tag": "wx-view",

          "attr": {

            "class": "usermotto"

          },

          "children": [

            {

              "tag": "wx-text",

              "attr": {

                "class": "user-motto"

              },

              "children": [

                ""

              ],

              "raw": {},

              "generics": {}

            }

          ],

          "raw": {},

          "generics": {}

        }

      ],

      "raw": {},

      "generics": {}

    }

  ]

}


然后通过 window.exparser.registerElemtent 方法将这些 tag 转换成真实 DOM:

比如说,以上的 wx-text 就会被转换成类似于以下 DOM:

<span>

   <span style="display: none"></span>

   <span>{{这里是具体的文字内容}}</span>

</span>

 wcsc 编译 wxss

同样的,以 Demo 项目里的 index.wxss 为例,运行一下指令:

./wcsc ./quickstart/miniProgramJs.unpack/pages/index/index.wxss -js -o ./css.js

可以将该文件从 wxss 格式的内容,转换成 JS 的内容:

这里面会生成 setCssToHead 方法,用于将相应的 css 转换后(如 rpx 转 px 等等),通过 style 标签插入到文档的 head 里面。

通信原理

小程序逻辑层和渲染层的通信会由 Native (微信客户端)做中转,逻辑层发送网络请求也经由 Native 转发。

视图层组件:

内置组件中有部分组件是利用到客户端原生提供的能力,既然需要客户端原生提供的能力,那就会涉及到视图层与客户端的交互通信。这层通信机制在 iOS 和安卓系统的实现方式并不一样,iOS 是利用了 WKWebView 的提供 messageHandlers 特性,而在安卓则是往 WebView 的 window 对象注入一个原生方法,最终会封装成 WeiXinJSBridge 这样一个兼容层,主要提供了调用(invoke)和监听(on)这两种方法。

逻辑层接口:

逻辑层与客户端原生通信机制与渲染层类似,不同在于,iOS 平台可以往 JavaScriptCore 框架注入一个全局的原生方法,而安卓方面则是跟渲染层一致的。

无论是视图层还是逻辑层,开发者都是间接地调用到与客户端原生通信的底层接口。一般微信小程序会对逻辑层接口做层封装后,才暴露给开发者,封装的细节可能是统一入参、做些参数校验、兼容各平台或版本问题等等。

启动机制

小程序有冷启动与热启动两种方式:

假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台态的小程序切换到前台,这个过程就是热启动。

冷启动指的是用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动。

小程序没有重启的概念:

当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是 5 分钟)会被微信主动销毁。

当短时间内(5s)连续收到两次以上收到系统内存告警,会进行小程序的销毁。

启动流程:

总结

小程序拥有接近原生 App 的体验。

小程序并不是真正的 “无需-”,只是小程序的体积很小,在当今高速的网络环境下能够快速-,用户感知不到,更确切的来说是 “无感-”。

基于移动端布局的局限性,可以高效且简单的开发,迭代快速。

小程序是双线程模型,逻辑层和渲染层分别运行在不同的线程中,通过 JSBridge 进行通信。

在小程序里有实现专门的 JSBridge 来实现 JS 和 Native 的双向调用。

wxml 文件通过 wcc 编译:wxml => JS => VirtualDOM。

wxss 文件通过 wcsc 编译:wxss => JS => style。

小程序其实也是一种 hybrid 技术,但是他围绕宿主应用,实现了更为强大的生态,提供更为便捷的服务。

上述就是小编为大家整理的小程序编译&发布,小程序的编译原理技术相关内容。

国内(北京、上海、广州、深圳、成都、重庆、杭州、西安、武汉、苏州、郑州、南京、天津、长沙、东莞、宁波、佛山、合肥、青岛)Finclip软件分析、比较及推荐。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:来自国外网站的一堆有用或有趣的小工具,也许对你有所帮助(一)
下一篇:22个免费数据恢复软件(二)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~