HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

HBuilderX: 内置浏览器新功能

内置浏览器 HBuilderX

内置浏览器新功能

HBuilderX 2.6.10+版本,内置浏览器 增加 “查找代码中对应的DOM节点” 和 “高亮浏览器内对应元素” 功能

  • 在内置浏览器,右键菜单,点击查找代码中对应的DOM节点,即可跳转到编辑器相应位置
  • 在编辑器内,右键菜单,点击高亮浏览器内对应元素, 即可在内置浏览器显示相应元素

示例

继续阅读 »

内置浏览器新功能

HBuilderX 2.6.10+版本,内置浏览器 增加 “查找代码中对应的DOM节点” 和 “高亮浏览器内对应元素” 功能

  • 在内置浏览器,右键菜单,点击查找代码中对应的DOM节点,即可跳转到编辑器相应位置
  • 在编辑器内,右键菜单,点击高亮浏览器内对应元素, 即可在内置浏览器显示相应元素

示例

收起阅读 »

uni-app黑魔法:小程序自定义组件运行到H5平台

小程序组件

引言

移动互联网的初期,囿于设备硬件性能限制,流量以原生App为主,iOS、Android是当时两大平台。

随着硬件及OS的更新换代,H5可承载的体验逐步完善,为提高开发效率、节约资源(复用代码)以及热更新等目的,Hybrid模式成为主流;以及轻应用、服务号等平台的助推,H5网页流量暴涨,成为第三大平台。

2017年1月9日,微信发布小程序,历经3年发展,在今年主题为”未完成 Always Beta“的微信公开课 PRO上,微信团队披露,2019年小程序日活跃用户超过 3 亿,全年累计成交额达8000亿,同比增长超160%。

看到小程序如此惊人的增长力,我们有理由相信,有中国特色的小程序互联网时代已经到来,微信小程序也已成为继iOS、Android、H5之后的第四大流量平台。

平台分裂,为不同平台编写相同的业务代码,是件无趣的事情。

有追求的程序员,一直在探索代码复用的方案,Hybrid App即是代表。

而在如今的小程序时代,对于同样基于WEB技术的H5和小程序,如何实现代码复用,是很多前端工程师探索的方向。业内也已有不少成熟方案,从场景上来说,大致分为三类:

  1. 基于跨端框架,从头开发,一套代码,发行多个平台,比如DCloud出品的uni-app、京东凹凸实验室的taro
  2. 复用H5代码,转换H5代码在小程序环境中执行;适用于有H5平台沉淀,未开发小程序或小程序完善度较低的开发者;
    • 美团的mpvue框架是早期探索解决这个问题的代表,但因小程序不支持dom操作,故mpvue适用于vue的无dom操作的H5代码转换;
  • 最近微信官方推出的kbone,也是为了解决“把 Web 端的代码挪到小程序环境内执行”;不过,kbone 相比 mpvue 前进了一步(当然也有了新的性能缺陷),因为:

    kbone实现了一个适配器,在适配层里模拟出了浏览器环境,让 Web 端的代码可以不做什么改动便可运行在小程序里。

    1. 复用小程序代码,转换小程序代码在web环境中运行;适用于有小程序代码沉淀,未开发H5或H5平台完善度较低的开发者;这个方向业内成熟的方案还比较少。

uni-app近期支持了小程序自定义组件运行到H5平台,是对如上第三种场景的一种探索。

需求场景

鉴于小程序的低成本获客特征,很多厂商选择先开发小程序,验证业务模式后,再扩展至H5、App等其它平台。

开发者虽可借助转换器将小程序代码转换为uni-app项目(或其它跨端框架项目),快速实现多平台发行;但不少开发者是不敢轻易决策将跨端版本替换之前线上的小程序版本的,毕竟线上版本已稳定运行了一段时间。

常选的方案是:让原生小程序版本和uni-app跨端版本并行一段时间,微信平台继续使用原生版本,其它平台使用uni-app跨端版本;经过一段时间验证uni-app版本稳定后,再使用uni-app版替换掉原生小程序版本。

在这段并行的时间内,开发者需要同时维护微信原生、uni-app两个版本,新增业务需编写两份逻辑相同的代码,重复劳动,成本叠加,如何改善?

借助uni-app 支持将微信小程序组件运行到H5平台的特性,我们给出一种思路:

  • 开发者在原生小程序项目中,将新增业务以自定义组件的方式开发,优先上线小程序;
  • 拷贝小程序组件的wxml/wxss/js/json文件到uni-app 项目下,通过uni-app的编译器及运行时,保证小程序自定义组件在H5平台的正确运行。

这个方案的好处是:

  • 优先小程序开发,毕竟小程序早已上线,有存量用户

  • 复用小程序组件,新增业务仅需开发一套代码即可,降低开发成本

不止自己开发的小程序组件,业内开源的三方小程序组件,均可复制到uni-app项目项目中,运行到H5平台。

另外,部分公司的产品经理,会要求不同平台有不同的交互,但核心业务逻辑是相同的,开发者常会通过维护不同项目的方式来满足产品经理需求。此时,采取如上方案,同样可满足多个项目复用相同业务逻辑的诉求。

实际上,uni-app之前已支持将小程序自定义组件运行到App平台,对于有小程序组件沉淀或优先小程序的开发者来说,这是个好消息,一套业务组件,快速运行到iOS、Android、H5、微信小程序这四大流量平台(实际上也可运行到QQ小程序平台)。

uni-app 引用小程序组件演示

uni-app项目中使用自定义组件的方法很简单,分为三步:

1、拷贝小程序自定义组件到uni-app项目根目录下的wxcomponents文件夹下

2、在 pages.json 对应页面的 style -> usingComponents引入组件,如:

{  
    "pages": [  
        {  
            "path": "index/index",  
            "style": {  
                "usingComponents": {  
                     "custom": "/wxcomponents/custom/index"  
                }  
            }  
        }  
    ]  
}

3、在页面中使用自定义组件,如:

<!-- 页面模板 (index.vue) -->  
<view>  
    <!-- 在页面中对自定义组件进行引用 -->  
    <custom name="uni-app"></custom>  
</view>

方案实现思路

简单介绍下uni-app的多端发行原理。

uni-app基于Vue.js runtime,页面文件遵循Vue.js 单文件组件 (SFC) 规范,天然对H5的支持比较好,发行到H5平台时,先通过vue-loader解析.vue文件,导出Vue.js 组件选项对象,然后在运行时补充规范实现:

  • 组件:框架提供内置组件(view/swiper/picker等)的实现,保证平台UI及交互的一致性
  • 接口:在H5平台封装框架接口,比如路由跳转,showToast等界面交互
  • 生命周期:Vue.js的理念是一切皆为组件,没有应用和页面的概念;框架需创造出应用及页面的概念,模拟onLaunch、onShow等钩子

uni-app发行到小程序平台时,逻辑又有不同,主要工作有2块:

  • 编译器:将.vue文件拆分成wxml/wxss/js/json4个原生页面文件
  • 运行时:Vue.js和小程序都是逻辑视图层框架,都有数据绑定功能;运行时会实现Vue.js到小程序的数据同步,及小程序到Vue.js的事件代理

小程序自定义组件类似小程序原生的页面开发,一个自定义组件同样由wxml/wxss/js/json 4个文件组成,另有单独的组件规范(如Component 构造器、Behaviors特性等)。

所以,小程序自定义组件运行到H5平台,可借助uni-app已有平台功能快速实现:

  • 编译阶段:将wxml/wxss/js/json4个文件合并为.vue文件(类似 uni-app 发行到小程序的逆过程),然后调用uni-app发行H5平台的编译过程,通过vue-loader解析.vue文件,导出 Vue.js 组件选项对象
  • 运行阶段:实现 Component 构造器、Behaviors特性,模拟自定义组件特有的生命周期

编译:转换文件(mp2vue)

小程序自定义组件发行到H5平台,在编译环节主要有2项工作:

  1. 将自定义组件的wxml/wxss/js/json 4个文件组成,编译转换成.vue文件,即小程序转vue,可简写为mp2vue

  2. 通过vue-loader解析.vue文件,导出 Vue.js 组件选项对象

其中,步骤2是Vue.js项目的标准编译过程,略过不提;我们重点阐述步骤1。

mp2vue将4个独立wxml/wxss/js/json 的文件合并成一个.vue文件,并组装成templatescriptstyle 这种三段式的结构,流程包括:

  1. wxml文件生成template节点,同时完成指令、事件等模板语法转换
  2. js/json文件生成script节点,同时完成组件注册过
  3. wxss文件生成style节点,自动转换部分css兼容语法
  4. 合并为.vue文件

具体实现上,uni-app编译前先扫描wxcomponents目录,若存在则认为有小程序自定义组件,启动文件转换工作(uni-migration插件来完成):

//加载转换器  
const migrate = require('@dcloudio/uni-migration')   
//扫描wxcomponents目录  
const wxcomponents = path.resolve(process.env.UNI_INPUT_DIR, 'wxcomponents')   
if (fs.existsSync(wxcomponents)) {   
  migrate(wxcomponents, false, {  
    silent: true   
  }) // 转换 mp-weixin 小程序组件  
}

接着开始对wxml/wxss/js/json文件逐个解析,并合并为一个.vue文件:

module.exports = function transformFile(input, options) {  
    //首先转换json文件,判断是否为组件  
  const [jsCode, isComponent] = transformJsonFile(filepath + '.json', deps)  
  options.isComponent = isComponent  
    //转换 wxml 文件  
  const [templateCode, wxsCode = '', wxsFiles = []] = transformTemplateFile(filepath + templateExtname, options)  
    //转换wxss文件  
  const styleCode = transformStyleFile(filepath + styleExtname, options, deps) || ''  
  //转换js文件  
  const scriptCode = transformScriptFile(filepath + '.js', jsCode, options, deps)  
    // 生成合并后的.vue文件源码  
  return [  
    `${commentsCode}<template>  
${templateCode}  
</template>  
${wxsCode}  
<script>  
${scriptCode}  
</script>  
<style platform="mp-weixin">  
${styleCode}  
</style>`,  
    deps,  
    wxsFiles  
  ]  
}

进一步细节说明,wxml文件转为template节点时,需完成各项指令、事件等模板语法的转换,例如:

小程序自定义组件 Vue组件 描述
wx:if v-if 条件渲染
wx:for v-for 列表渲染
bindtap @click 元素点击事件

将一个最简自定义组件,按照如上流程转换,结果示意如下:

运行时:模拟小程序组件环境

uni-app的编译器并不转换小程序组件的 JS 代码,依然保留Component构造器的写法,甚至其中的API依然是wx.开头的方式,这些都依赖uni-app在H5平台的运行时来解决,主要有如下几部分内容:

  • Component构造器:解析小程序组件的各种选项配置,转换为Vue组件定义,包括变通实现其中的差异部分,如小程序组件特有的”组件所在页面的生命周期“
  • Behaviors特性:转换为Vue的混入(mixin)
  • 数据响应:在H5平台实现setData接口及this.data.xx = yy的数据通讯机制
  • API前缀:可在运行时通过代理机制,自动将wx.xx替换为uni.xx,这个比较简单,不详述

Component构造器

uni-app在H5平台定义了一个Component函数,执行到小程序的Component构造器函数后,开始循环解析其属性,并转换成Vue组件属性,流程示意代码如下:

export function Component (options) {  
  const componentOptions = parseComponent(options)  
  componentOptions.mixins.unshift(polyfill)  
  componentOptions.mpOptions.path = global['__wxRoute']  
  initRelationsHandler(componentOptions)  
  global['__wxComponents'][global['__wxRoute']] = componentOptions  
}  

export function parseComponent (mpComponentOptions) {  
  const {  
    data,  
    options,  
    methods,  
    behaviors,  
    lifetimes,  
    observers,  
    relations,  
    properties,  
    pageLifetimes,  
    externalClasses  
  } = mpComponentOptions  

  const vueComponentOptions = {  
    mixins: [],  
    props: {},  
    watch: {},  
    mpOptions: {  
      mpObservers: []  
    }  
  }  

    // 开始逐个解析所有属性  
  parseData(data, vueComponentOptions)  
  parseOptions(options, vueComponentOptions)  
  parseMethods(methods, vueComponentOptions)  
  parseBehaviors(behaviors, vueComponentOptions)  
  parseLifetimes(lifetimes, vueComponentOptions)  
  parseObservers(observers, vueComponentOptions)  
  parseRelations(relations, vueComponentOptions)  
  parseProperties(properties, vueComponentOptions)  
  parsePageLifetimes(pageLifetimes, vueComponentOptions)  
  parseExternalClasses(externalClasses, vueComponentOptions)  
  parseLifecycle(mpComponentOptions, vueComponentOptions)  
  parseDefinitionFilter(mpComponentOptions, vueComponentOptions)  
    // 返回 Vue 组件  
  return vueComponentOptions  
}

在这个过程中,需处理小程序自定义组件和 Vue组件的属性对应关系及细节差异,如小程序组件的lifetimes

小程序自定义组件 Vue/uni-app 描述
created onServiceCreated 小程序的created触发时,可以访问子组件信息,而Vuecreated访问不到,故需uni-app框架映射到其它时机(onServiceCreated)执行
attached onServiceAttached 同上
ready mounted Vue 生命周期
moved - Vue中不存在该钩子,暂不支持转换
detached destroyed Vue 生命周期
小程序的pageLifetimes(组件所在页面的生命周期)在Vue中是没有的,需要映射为uni-app封装的页面生命周期: 小程序自定义组件 uni-app 描述
ready onPageShow 页面被展示时执行
hide onPageHide 页面被隐藏时执行
resize onPageResize 页面尺寸变化时执行

Behaviors特性的实现过程,类似Component构造器,不再赘述。

数据响应

Vue和小程序都有一套数据绑定系统,但机制不同,比如在Vue体系下,数据赋值是这样的:

this.a = 1

但在小程序中,数据赋值方式则是这样的:

this.setData({  
    a:1  
}) //响应式  
this.data.a = 2 //非响应式

另外,小程序和Vue在数据的propertiesobserver等方面都存在不少差异,经过我们评估,若将小程序的数据响应用法直接映射到Vue体系下,复杂度较高且有性能压力,故uni-app在H5平台按照微信的语法规范,单独实现了一套数据响应系统。

// 小程序的setData在H5平台的实现  
function setData (data, callback) {  
  if (!isPlainObject(data)) {  
    return  
  }  
  Object.keys(data).forEach(key => {  
    if (setDataByExprPath(key, data[key], this.data)) {  
      !hasOwn(this, key) && proxy(this, SOURCE_KEY, key);  
    }  
  });  
  this.$forceUpdate();//数据变化,强制视图更新(响应式)  
  isFn(callback) && this.$nextTick(callback);  
}

setData挂载到 vm 对象上,可通过this.setData这种小程序的方式调用;同时将数据绑定到data属性上,支持this.data.xx的访问方式。

export function initState (vm) {  
  const instanceData = JSON.parse(JSON.stringify(vm.$options.mpOptions.data || {}))  
  vm[SOURCE_KEY] = instanceData  

  //vm对象上挂载 setData 方法,实现小程序方法  
    vm.setData = setData   

  const propertyDefinition = {  
    get () {  
      return vm[SOURCE_KEY]  
    },  
    set (value) {  
      vm[SOURCE_KEY] = value  
    }  
  }  
    //小程序用法,可通过this.data.xx访问  
  Object.defineProperties(vm, {  
    data: propertyDefinition,  
    properties: propertyDefinition  
  })  

  Object.keys(instanceData).forEach(key => {  
    proxy(vm, SOURCE_KEY, key)  
  })  
}

虽然数据响应是uni-app自己实现的,但渲染依然使用了Vue框架的render函数,此时需小程序规范中的this.data.xx和Vue规范中的this.xx保持一致,通过代理的方式实现:

// mp/polyfill/state/proxy.js  
const sharedPropertyDefinition = {  
  enumerable: true,  
  configurable: true  
};  

function proxy (target, sourceKey, key) {  
  sharedPropertyDefinition.get = function proxyGetter () {  
    return this[sourceKey][key]  
  };  
  sharedPropertyDefinition.set = function proxySetter (val) {  
    this[sourceKey][key] = val;  
  };  
  Object.defineProperty(target, key, sharedPropertyDefinition);  
}

这里仅列出了主要的几步,中间涉及细节很多;部分无法通过Vue扩展机制实现的功能,只好修改Vue.js的内核源码,比如updateProperties支持、小程序wxsexternalClasses等功能在H5平台的支持,都需要定制部分 Vue.js runtime 源码。

结语

本文分享了uni-app将微信小程序自定义组件发行到H5平台的实现思路,希望对大家有所启发。

但这种方案,归根到底是为了解决多套项目并存时的业务重复开发的问题。

如果你是从头开发,我们建议直接选择业内成熟的跨端框架,既可以保持一套代码,更省力的维护,还可以借助框架的成熟生态(如跨端UI库插件市场),基于成熟轮子,快速完成业务的上线开发;

uni-app框架代码,包括小程序组件发行到H5平台的代码,全部开源在github,如果大家对本文逻辑有疑问,欢迎提交issue交流。

继续阅读 »

引言

移动互联网的初期,囿于设备硬件性能限制,流量以原生App为主,iOS、Android是当时两大平台。

随着硬件及OS的更新换代,H5可承载的体验逐步完善,为提高开发效率、节约资源(复用代码)以及热更新等目的,Hybrid模式成为主流;以及轻应用、服务号等平台的助推,H5网页流量暴涨,成为第三大平台。

2017年1月9日,微信发布小程序,历经3年发展,在今年主题为”未完成 Always Beta“的微信公开课 PRO上,微信团队披露,2019年小程序日活跃用户超过 3 亿,全年累计成交额达8000亿,同比增长超160%。

看到小程序如此惊人的增长力,我们有理由相信,有中国特色的小程序互联网时代已经到来,微信小程序也已成为继iOS、Android、H5之后的第四大流量平台。

平台分裂,为不同平台编写相同的业务代码,是件无趣的事情。

有追求的程序员,一直在探索代码复用的方案,Hybrid App即是代表。

而在如今的小程序时代,对于同样基于WEB技术的H5和小程序,如何实现代码复用,是很多前端工程师探索的方向。业内也已有不少成熟方案,从场景上来说,大致分为三类:

  1. 基于跨端框架,从头开发,一套代码,发行多个平台,比如DCloud出品的uni-app、京东凹凸实验室的taro
  2. 复用H5代码,转换H5代码在小程序环境中执行;适用于有H5平台沉淀,未开发小程序或小程序完善度较低的开发者;
    • 美团的mpvue框架是早期探索解决这个问题的代表,但因小程序不支持dom操作,故mpvue适用于vue的无dom操作的H5代码转换;
  • 最近微信官方推出的kbone,也是为了解决“把 Web 端的代码挪到小程序环境内执行”;不过,kbone 相比 mpvue 前进了一步(当然也有了新的性能缺陷),因为:

    kbone实现了一个适配器,在适配层里模拟出了浏览器环境,让 Web 端的代码可以不做什么改动便可运行在小程序里。

    1. 复用小程序代码,转换小程序代码在web环境中运行;适用于有小程序代码沉淀,未开发H5或H5平台完善度较低的开发者;这个方向业内成熟的方案还比较少。

uni-app近期支持了小程序自定义组件运行到H5平台,是对如上第三种场景的一种探索。

需求场景

鉴于小程序的低成本获客特征,很多厂商选择先开发小程序,验证业务模式后,再扩展至H5、App等其它平台。

开发者虽可借助转换器将小程序代码转换为uni-app项目(或其它跨端框架项目),快速实现多平台发行;但不少开发者是不敢轻易决策将跨端版本替换之前线上的小程序版本的,毕竟线上版本已稳定运行了一段时间。

常选的方案是:让原生小程序版本和uni-app跨端版本并行一段时间,微信平台继续使用原生版本,其它平台使用uni-app跨端版本;经过一段时间验证uni-app版本稳定后,再使用uni-app版替换掉原生小程序版本。

在这段并行的时间内,开发者需要同时维护微信原生、uni-app两个版本,新增业务需编写两份逻辑相同的代码,重复劳动,成本叠加,如何改善?

借助uni-app 支持将微信小程序组件运行到H5平台的特性,我们给出一种思路:

  • 开发者在原生小程序项目中,将新增业务以自定义组件的方式开发,优先上线小程序;
  • 拷贝小程序组件的wxml/wxss/js/json文件到uni-app 项目下,通过uni-app的编译器及运行时,保证小程序自定义组件在H5平台的正确运行。

这个方案的好处是:

  • 优先小程序开发,毕竟小程序早已上线,有存量用户

  • 复用小程序组件,新增业务仅需开发一套代码即可,降低开发成本

不止自己开发的小程序组件,业内开源的三方小程序组件,均可复制到uni-app项目项目中,运行到H5平台。

另外,部分公司的产品经理,会要求不同平台有不同的交互,但核心业务逻辑是相同的,开发者常会通过维护不同项目的方式来满足产品经理需求。此时,采取如上方案,同样可满足多个项目复用相同业务逻辑的诉求。

实际上,uni-app之前已支持将小程序自定义组件运行到App平台,对于有小程序组件沉淀或优先小程序的开发者来说,这是个好消息,一套业务组件,快速运行到iOS、Android、H5、微信小程序这四大流量平台(实际上也可运行到QQ小程序平台)。

uni-app 引用小程序组件演示

uni-app项目中使用自定义组件的方法很简单,分为三步:

1、拷贝小程序自定义组件到uni-app项目根目录下的wxcomponents文件夹下

2、在 pages.json 对应页面的 style -> usingComponents引入组件,如:

{  
    "pages": [  
        {  
            "path": "index/index",  
            "style": {  
                "usingComponents": {  
                     "custom": "/wxcomponents/custom/index"  
                }  
            }  
        }  
    ]  
}

3、在页面中使用自定义组件,如:

<!-- 页面模板 (index.vue) -->  
<view>  
    <!-- 在页面中对自定义组件进行引用 -->  
    <custom name="uni-app"></custom>  
</view>

方案实现思路

简单介绍下uni-app的多端发行原理。

uni-app基于Vue.js runtime,页面文件遵循Vue.js 单文件组件 (SFC) 规范,天然对H5的支持比较好,发行到H5平台时,先通过vue-loader解析.vue文件,导出Vue.js 组件选项对象,然后在运行时补充规范实现:

  • 组件:框架提供内置组件(view/swiper/picker等)的实现,保证平台UI及交互的一致性
  • 接口:在H5平台封装框架接口,比如路由跳转,showToast等界面交互
  • 生命周期:Vue.js的理念是一切皆为组件,没有应用和页面的概念;框架需创造出应用及页面的概念,模拟onLaunch、onShow等钩子

uni-app发行到小程序平台时,逻辑又有不同,主要工作有2块:

  • 编译器:将.vue文件拆分成wxml/wxss/js/json4个原生页面文件
  • 运行时:Vue.js和小程序都是逻辑视图层框架,都有数据绑定功能;运行时会实现Vue.js到小程序的数据同步,及小程序到Vue.js的事件代理

小程序自定义组件类似小程序原生的页面开发,一个自定义组件同样由wxml/wxss/js/json 4个文件组成,另有单独的组件规范(如Component 构造器、Behaviors特性等)。

所以,小程序自定义组件运行到H5平台,可借助uni-app已有平台功能快速实现:

  • 编译阶段:将wxml/wxss/js/json4个文件合并为.vue文件(类似 uni-app 发行到小程序的逆过程),然后调用uni-app发行H5平台的编译过程,通过vue-loader解析.vue文件,导出 Vue.js 组件选项对象
  • 运行阶段:实现 Component 构造器、Behaviors特性,模拟自定义组件特有的生命周期

编译:转换文件(mp2vue)

小程序自定义组件发行到H5平台,在编译环节主要有2项工作:

  1. 将自定义组件的wxml/wxss/js/json 4个文件组成,编译转换成.vue文件,即小程序转vue,可简写为mp2vue

  2. 通过vue-loader解析.vue文件,导出 Vue.js 组件选项对象

其中,步骤2是Vue.js项目的标准编译过程,略过不提;我们重点阐述步骤1。

mp2vue将4个独立wxml/wxss/js/json 的文件合并成一个.vue文件,并组装成templatescriptstyle 这种三段式的结构,流程包括:

  1. wxml文件生成template节点,同时完成指令、事件等模板语法转换
  2. js/json文件生成script节点,同时完成组件注册过
  3. wxss文件生成style节点,自动转换部分css兼容语法
  4. 合并为.vue文件

具体实现上,uni-app编译前先扫描wxcomponents目录,若存在则认为有小程序自定义组件,启动文件转换工作(uni-migration插件来完成):

//加载转换器  
const migrate = require('@dcloudio/uni-migration')   
//扫描wxcomponents目录  
const wxcomponents = path.resolve(process.env.UNI_INPUT_DIR, 'wxcomponents')   
if (fs.existsSync(wxcomponents)) {   
  migrate(wxcomponents, false, {  
    silent: true   
  }) // 转换 mp-weixin 小程序组件  
}

接着开始对wxml/wxss/js/json文件逐个解析,并合并为一个.vue文件:

module.exports = function transformFile(input, options) {  
    //首先转换json文件,判断是否为组件  
  const [jsCode, isComponent] = transformJsonFile(filepath + '.json', deps)  
  options.isComponent = isComponent  
    //转换 wxml 文件  
  const [templateCode, wxsCode = '', wxsFiles = []] = transformTemplateFile(filepath + templateExtname, options)  
    //转换wxss文件  
  const styleCode = transformStyleFile(filepath + styleExtname, options, deps) || ''  
  //转换js文件  
  const scriptCode = transformScriptFile(filepath + '.js', jsCode, options, deps)  
    // 生成合并后的.vue文件源码  
  return [  
    `${commentsCode}<template>  
${templateCode}  
</template>  
${wxsCode}  
<script>  
${scriptCode}  
</script>  
<style platform="mp-weixin">  
${styleCode}  
</style>`,  
    deps,  
    wxsFiles  
  ]  
}

进一步细节说明,wxml文件转为template节点时,需完成各项指令、事件等模板语法的转换,例如:

小程序自定义组件 Vue组件 描述
wx:if v-if 条件渲染
wx:for v-for 列表渲染
bindtap @click 元素点击事件

将一个最简自定义组件,按照如上流程转换,结果示意如下:

运行时:模拟小程序组件环境

uni-app的编译器并不转换小程序组件的 JS 代码,依然保留Component构造器的写法,甚至其中的API依然是wx.开头的方式,这些都依赖uni-app在H5平台的运行时来解决,主要有如下几部分内容:

  • Component构造器:解析小程序组件的各种选项配置,转换为Vue组件定义,包括变通实现其中的差异部分,如小程序组件特有的”组件所在页面的生命周期“
  • Behaviors特性:转换为Vue的混入(mixin)
  • 数据响应:在H5平台实现setData接口及this.data.xx = yy的数据通讯机制
  • API前缀:可在运行时通过代理机制,自动将wx.xx替换为uni.xx,这个比较简单,不详述

Component构造器

uni-app在H5平台定义了一个Component函数,执行到小程序的Component构造器函数后,开始循环解析其属性,并转换成Vue组件属性,流程示意代码如下:

export function Component (options) {  
  const componentOptions = parseComponent(options)  
  componentOptions.mixins.unshift(polyfill)  
  componentOptions.mpOptions.path = global['__wxRoute']  
  initRelationsHandler(componentOptions)  
  global['__wxComponents'][global['__wxRoute']] = componentOptions  
}  

export function parseComponent (mpComponentOptions) {  
  const {  
    data,  
    options,  
    methods,  
    behaviors,  
    lifetimes,  
    observers,  
    relations,  
    properties,  
    pageLifetimes,  
    externalClasses  
  } = mpComponentOptions  

  const vueComponentOptions = {  
    mixins: [],  
    props: {},  
    watch: {},  
    mpOptions: {  
      mpObservers: []  
    }  
  }  

    // 开始逐个解析所有属性  
  parseData(data, vueComponentOptions)  
  parseOptions(options, vueComponentOptions)  
  parseMethods(methods, vueComponentOptions)  
  parseBehaviors(behaviors, vueComponentOptions)  
  parseLifetimes(lifetimes, vueComponentOptions)  
  parseObservers(observers, vueComponentOptions)  
  parseRelations(relations, vueComponentOptions)  
  parseProperties(properties, vueComponentOptions)  
  parsePageLifetimes(pageLifetimes, vueComponentOptions)  
  parseExternalClasses(externalClasses, vueComponentOptions)  
  parseLifecycle(mpComponentOptions, vueComponentOptions)  
  parseDefinitionFilter(mpComponentOptions, vueComponentOptions)  
    // 返回 Vue 组件  
  return vueComponentOptions  
}

在这个过程中,需处理小程序自定义组件和 Vue组件的属性对应关系及细节差异,如小程序组件的lifetimes

小程序自定义组件 Vue/uni-app 描述
created onServiceCreated 小程序的created触发时,可以访问子组件信息,而Vuecreated访问不到,故需uni-app框架映射到其它时机(onServiceCreated)执行
attached onServiceAttached 同上
ready mounted Vue 生命周期
moved - Vue中不存在该钩子,暂不支持转换
detached destroyed Vue 生命周期
小程序的pageLifetimes(组件所在页面的生命周期)在Vue中是没有的,需要映射为uni-app封装的页面生命周期: 小程序自定义组件 uni-app 描述
ready onPageShow 页面被展示时执行
hide onPageHide 页面被隐藏时执行
resize onPageResize 页面尺寸变化时执行

Behaviors特性的实现过程,类似Component构造器,不再赘述。

数据响应

Vue和小程序都有一套数据绑定系统,但机制不同,比如在Vue体系下,数据赋值是这样的:

this.a = 1

但在小程序中,数据赋值方式则是这样的:

this.setData({  
    a:1  
}) //响应式  
this.data.a = 2 //非响应式

另外,小程序和Vue在数据的propertiesobserver等方面都存在不少差异,经过我们评估,若将小程序的数据响应用法直接映射到Vue体系下,复杂度较高且有性能压力,故uni-app在H5平台按照微信的语法规范,单独实现了一套数据响应系统。

// 小程序的setData在H5平台的实现  
function setData (data, callback) {  
  if (!isPlainObject(data)) {  
    return  
  }  
  Object.keys(data).forEach(key => {  
    if (setDataByExprPath(key, data[key], this.data)) {  
      !hasOwn(this, key) && proxy(this, SOURCE_KEY, key);  
    }  
  });  
  this.$forceUpdate();//数据变化,强制视图更新(响应式)  
  isFn(callback) && this.$nextTick(callback);  
}

setData挂载到 vm 对象上,可通过this.setData这种小程序的方式调用;同时将数据绑定到data属性上,支持this.data.xx的访问方式。

export function initState (vm) {  
  const instanceData = JSON.parse(JSON.stringify(vm.$options.mpOptions.data || {}))  
  vm[SOURCE_KEY] = instanceData  

  //vm对象上挂载 setData 方法,实现小程序方法  
    vm.setData = setData   

  const propertyDefinition = {  
    get () {  
      return vm[SOURCE_KEY]  
    },  
    set (value) {  
      vm[SOURCE_KEY] = value  
    }  
  }  
    //小程序用法,可通过this.data.xx访问  
  Object.defineProperties(vm, {  
    data: propertyDefinition,  
    properties: propertyDefinition  
  })  

  Object.keys(instanceData).forEach(key => {  
    proxy(vm, SOURCE_KEY, key)  
  })  
}

虽然数据响应是uni-app自己实现的,但渲染依然使用了Vue框架的render函数,此时需小程序规范中的this.data.xx和Vue规范中的this.xx保持一致,通过代理的方式实现:

// mp/polyfill/state/proxy.js  
const sharedPropertyDefinition = {  
  enumerable: true,  
  configurable: true  
};  

function proxy (target, sourceKey, key) {  
  sharedPropertyDefinition.get = function proxyGetter () {  
    return this[sourceKey][key]  
  };  
  sharedPropertyDefinition.set = function proxySetter (val) {  
    this[sourceKey][key] = val;  
  };  
  Object.defineProperty(target, key, sharedPropertyDefinition);  
}

这里仅列出了主要的几步,中间涉及细节很多;部分无法通过Vue扩展机制实现的功能,只好修改Vue.js的内核源码,比如updateProperties支持、小程序wxsexternalClasses等功能在H5平台的支持,都需要定制部分 Vue.js runtime 源码。

结语

本文分享了uni-app将微信小程序自定义组件发行到H5平台的实现思路,希望对大家有所启发。

但这种方案,归根到底是为了解决多套项目并存时的业务重复开发的问题。

如果你是从头开发,我们建议直接选择业内成熟的跨端框架,既可以保持一套代码,更省力的维护,还可以借助框架的成熟生态(如跨端UI库插件市场),基于成熟轮子,快速完成业务的上线开发;

uni-app框架代码,包括小程序组件发行到H5平台的代码,全部开源在github,如果大家对本文逻辑有疑问,欢迎提交issue交流。

收起阅读 »

关于v3下一些子组件样式出现问题

v3 css

其实样式出现问题基本上是有地方给了样式,为什么v3下有一些样式会乱掉呢,那是因为和其它库里面的一下样式class类名重复了,这时候修改你的class名称就可以了,为什么v2不会,v3会这样子,具体问官方吧

其实样式出现问题基本上是有地方给了样式,为什么v3下有一些样式会乱掉呢,那是因为和其它库里面的一下样式class类名重复了,这时候修改你的class名称就可以了,为什么v2不会,v3会这样子,具体问官方吧

自己编辑的 淡蓝色 主题

自定义 主题
{  
"editor.caretWidth" : 1,  
"editor.colorScheme" : "Default",  
"editor.wordWrap" : false,  
"explorer.autoReveal" : true,  
"explorer.iconTheme" : "vs-seti",  
"workbench.colorCustomizations": {  
"[Default]": {  
"sideBar.background":"#bde2fa", //文件项目管理器颜色  
"sideBar.border":"#2369cb",    //文件资源管理器边框颜色  
"editor.background":"#d7ebfd", //编辑区域背景颜色  

"toolBar.background":"#e4f0fd",//工具栏背景颜色  
"toolBar.hoverBackground":"#93befd",//工具栏上图标被选中时的背景颜色  
"toolBar.border":"#1b2154",//工具栏边框颜色  

"tab.activeBackground":"#53d3fa",//标签卡  选中时的背景颜色  
"tab.activeForeground":"#341703",//选中时的前景颜色  
"tab.inactiveBackground":"#a4d9fa",//未选中时的背景颜色  
"tab.inactiveForeground":"#938D8B",//未选中时的前景颜色  
"tab.hoverBackground":"#fab86d",//鼠标滑过时的背景颜色  
"tab.unfocusedHoverBackground":"#4671fd",//未选中分栏里鼠标滑过未选中标签的背景颜色  
"tab.unfocusedActiveForeground":"#242227",//未选中分栏里选中标签的前景颜色  
"tab.unfocusedInActiveForeground":"#FDF0ED",//未选中分栏里未选中标签的前景颜色  
"editorGroupHeader.tabsBackground":"#9dedfd",//tabs背景颜色  

"editorSuggestWidget.selectedBackground":"#53d3fa",//代码助手 弹窗选中条目时背景颜色  
"editorSuggestWidget.background":"#a5e3f0",//助手弹窗背景颜色  
"editorSuggestWidget.border":"#cee0fa",//助手弹窗边框颜色  
"editor.wordWrap": true,// 是否自动换行。  

"input.background":"#75dbfa",//文本框背景颜色  
"inputValidation.infoBackground":"#e8fafd",//下拉框背景颜色  
"inputList.hoverBackground":"#a3e7fa",//鼠标滑过item背景颜色  
"inputList.border":"#75dbfa",//下拉框边框颜色  

"list.foreground":"#5f936c",//前景颜色  
"list.highlightForeground":"#139556",//高亮时前景颜色  
"list.activeSelectionBackground":"#faab49",//选中条目背景颜色  
"list.activeSelectionForeground":"#18b46b",//选中条目前景颜色  
"list.hoverBackground":"#fab86d",//鼠标滑过背景颜色  

"outlineBackground":"#FDF0ED",//文档结构背景颜色  
"scrollbarSlider.background":"#4b7cd6",//滚动条背景颜色  
"scrollbarSlider.hoverBackground":"#2656bd",//鼠标滑过滚动条背景颜色  

"extensionButton.prominentBackground":"#d3f2fd",//预览按钮  背景颜色  
"extensionButton.prominentForeground":"#460e1c",//前景颜色  
"extensionButton.border":"#549fb4",// 边框颜色  
"extensionButton.prominentHoverBackground":"#facb89",//鼠标滑过时的背景颜色  
"extensionButton.checkColor":"#6285e4",//选中时的前景颜色  

"settings.textInputBorder":"#d4f5fa",//文本框边框颜色  
"inputOption.activeBorder":"#2f8de4",//文本框有焦点时边框颜色  
"settings.dropdownBorder":"#fadad1",// combobox下拉列表边框颜色  
"settings.dropdownListBorder":"#fadad1",//combobox item边框颜色  
"imageview.background":"#FDF0ED",//浅色方格颜色  
"imageview.foreground":"#fadad1",//深色方格颜色  
"statusBar.background":"#ecfbfd",//状态栏背景颜色  
"statusBar.foreground":"#938D8B",//状态栏前景颜色  
"minimap.handle.background":"#fadad1"//迷你地图滑块背景  
}  
},  
    "editor.minimap.enabled":false  

}
继续阅读 »
{  
"editor.caretWidth" : 1,  
"editor.colorScheme" : "Default",  
"editor.wordWrap" : false,  
"explorer.autoReveal" : true,  
"explorer.iconTheme" : "vs-seti",  
"workbench.colorCustomizations": {  
"[Default]": {  
"sideBar.background":"#bde2fa", //文件项目管理器颜色  
"sideBar.border":"#2369cb",    //文件资源管理器边框颜色  
"editor.background":"#d7ebfd", //编辑区域背景颜色  

"toolBar.background":"#e4f0fd",//工具栏背景颜色  
"toolBar.hoverBackground":"#93befd",//工具栏上图标被选中时的背景颜色  
"toolBar.border":"#1b2154",//工具栏边框颜色  

"tab.activeBackground":"#53d3fa",//标签卡  选中时的背景颜色  
"tab.activeForeground":"#341703",//选中时的前景颜色  
"tab.inactiveBackground":"#a4d9fa",//未选中时的背景颜色  
"tab.inactiveForeground":"#938D8B",//未选中时的前景颜色  
"tab.hoverBackground":"#fab86d",//鼠标滑过时的背景颜色  
"tab.unfocusedHoverBackground":"#4671fd",//未选中分栏里鼠标滑过未选中标签的背景颜色  
"tab.unfocusedActiveForeground":"#242227",//未选中分栏里选中标签的前景颜色  
"tab.unfocusedInActiveForeground":"#FDF0ED",//未选中分栏里未选中标签的前景颜色  
"editorGroupHeader.tabsBackground":"#9dedfd",//tabs背景颜色  

"editorSuggestWidget.selectedBackground":"#53d3fa",//代码助手 弹窗选中条目时背景颜色  
"editorSuggestWidget.background":"#a5e3f0",//助手弹窗背景颜色  
"editorSuggestWidget.border":"#cee0fa",//助手弹窗边框颜色  
"editor.wordWrap": true,// 是否自动换行。  

"input.background":"#75dbfa",//文本框背景颜色  
"inputValidation.infoBackground":"#e8fafd",//下拉框背景颜色  
"inputList.hoverBackground":"#a3e7fa",//鼠标滑过item背景颜色  
"inputList.border":"#75dbfa",//下拉框边框颜色  

"list.foreground":"#5f936c",//前景颜色  
"list.highlightForeground":"#139556",//高亮时前景颜色  
"list.activeSelectionBackground":"#faab49",//选中条目背景颜色  
"list.activeSelectionForeground":"#18b46b",//选中条目前景颜色  
"list.hoverBackground":"#fab86d",//鼠标滑过背景颜色  

"outlineBackground":"#FDF0ED",//文档结构背景颜色  
"scrollbarSlider.background":"#4b7cd6",//滚动条背景颜色  
"scrollbarSlider.hoverBackground":"#2656bd",//鼠标滑过滚动条背景颜色  

"extensionButton.prominentBackground":"#d3f2fd",//预览按钮  背景颜色  
"extensionButton.prominentForeground":"#460e1c",//前景颜色  
"extensionButton.border":"#549fb4",// 边框颜色  
"extensionButton.prominentHoverBackground":"#facb89",//鼠标滑过时的背景颜色  
"extensionButton.checkColor":"#6285e4",//选中时的前景颜色  

"settings.textInputBorder":"#d4f5fa",//文本框边框颜色  
"inputOption.activeBorder":"#2f8de4",//文本框有焦点时边框颜色  
"settings.dropdownBorder":"#fadad1",// combobox下拉列表边框颜色  
"settings.dropdownListBorder":"#fadad1",//combobox item边框颜色  
"imageview.background":"#FDF0ED",//浅色方格颜色  
"imageview.foreground":"#fadad1",//深色方格颜色  
"statusBar.background":"#ecfbfd",//状态栏背景颜色  
"statusBar.foreground":"#938D8B",//状态栏前景颜色  
"minimap.handle.background":"#fadad1"//迷你地图滑块背景  
}  
},  
    "editor.minimap.enabled":false  

}
收起阅读 »

即时通信、IM、java、socketio、在线客服

主要功能:
1、用户模块:登陆、注册
2、消息模块:单聊、群聊、客服(支持文字、图片、语音-针对app)
3、所有消息都是加密传输、支持消息离线(当用户不在线时)

功能展示:

注:

  1. 即时通信后端采用spring boot 开发框架,按模块化开发的方式开发
  2. 即时通信可以结合自己的用户体系,也就是,后端实现用户的相关接口,就可以接入自己的用户体系

体验地址
h5: http://114.67.115.15/h5/index.html(兼容ios,android的h5,pc端为chrome)
android 下载地址 : http://114.67.115.15/uploads/im/publish/chat_beta.apk
ios 下载地址 :ios没有证书,暂不提供
联系人QQ:2624342267

继续阅读 »

主要功能:
1、用户模块:登陆、注册
2、消息模块:单聊、群聊、客服(支持文字、图片、语音-针对app)
3、所有消息都是加密传输、支持消息离线(当用户不在线时)

功能展示:

注:

  1. 即时通信后端采用spring boot 开发框架,按模块化开发的方式开发
  2. 即时通信可以结合自己的用户体系,也就是,后端实现用户的相关接口,就可以接入自己的用户体系

体验地址
h5: http://114.67.115.15/h5/index.html(兼容ios,android的h5,pc端为chrome)
android 下载地址 : http://114.67.115.15/uploads/im/publish/chat_beta.apk
ios 下载地址 :ios没有证书,暂不提供
联系人QQ:2624342267

收起阅读 »

linux wine上使用HBuilderX

wine linux

前言:
因linux上没有itunes,无法真机运行App;linux上无法运行微信等小程序开发工具。这些问题暂时无法解决。所以DCloud暂时不会投入很多精力开发纯正的linux版,目前提供linux上的2个方案

  1. linux的cli方案,可以调用HBuilderX的cli,适于在linux服务器上调用cli进行运行、发行等工程自动化方案。具体另见:https://ask.dcloud.net.cn/article/41469
  2. wine环境的解决方案,如下:

1. Linux在wine上使用HBuilderX的注意事项

wine上运行HBuilderX, 优缺点如下:

  • HBuilderX支持打开、编辑各种文本文件
  • 支持web项目开发(web普通项目、vue项目等)
  • 支持uniapp h5开发
  • 不支持真机运行5+app、uniapp等项目,由于受第三方工具的影响(具体可参考5.1章节)
  • 各种小程序工具,包含微信小程序、支付宝小程序等,既不支持linux,也不支持在wine下使用。

2. HBuilderX在wine上运行效果

先来看下,wine + HBuilderX的运行效果

3. wine环境安装

3.1 Linux环境说明

  • HBuilderXLinux wine上的运行,必须安装Linux桌面操作系统
  • 以下所有的操作,都是基于ubuntu操作系统
  • 如果您使用其它Linux发行版,如Redhat Fedora SUSE等,安装wine,请参考wine官方文档
  • 如果您在其它Linux桌面操作系统使用wine + HBuilderX,遇到问题,可以反馈给我们。

3.2 wine文档

在安装wine过程中,可能遇到其它问题,请参考官方文档:

https://wiki.winehq.org

https://wiki.winehq.org/Ubuntu

3.3 wine安装

特别注意,下面操作使用到的apt相关的命令,只适用于ubuntu、deb等系统;并不是所有的linux发行版都有apt命令。 其它发行版安装wine,请参考wine官方文档

如果您使用的是 64 位系统,请开启 32 bit 架构支持(如果您之前没有开启的话):

$ sudo dpkg --add-architecture i386 

下载添加仓库密钥:

$ wget -nc https://dl.winehq.org/wine-builds/winehq.key  
$ sudo apt-key add winehq.key

添加仓库:

$ sudo apt-add-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ bionic main'

更新安装包:

$ sudo apt update

然后安装 以下任一一个安装包

稳定分支(选择): 

    sudo apt install --install-recommends winehq-stable  
    如果失败  
    sudo apt-get update  
    sudo apt install --install-recommends winehq-stable

开发分支:

$ sudo apt install --install-recommends winehq-devel

Staging 分支:

$ sudo apt install --install-recommends winehq-staging

(可能需要安装 wine-mono、wine-gecko,点击确定即可)

安装完成之后:

$ wine --version

3.4 wine配置(1)

配置wine:

$ winecfg

此命令会提示安装 wine-mono、wine-gecko,点击确定即可。
但这个安装过程可能会很慢,要看网速。

3.5 wine配置(2)

安装wine-gecko,请点击确定

3.6 wine配置(3)

wine配置,应用程序默认设置,如下图,请点击确定

4. HBuilderX:安装启动

4.1 HBuilderX下载、解压

  1. 打开 HBuilderX官网 下载Windows HBuilderX安装包
  2. HBuilderX安装包是一个压缩包,需要解压

4.2 HBuilderX 程序启动

wine上启动windows HBuilderX程序有以下几种方法:

  • 在终端上,进入HBuilderX解压后的目录, 输入 wine HBuilderX.exe
  • 直接双击exe程序
  • 单击右键exe程序,选择打开方式也可以选择Wine打开。

4.3. HBuilderX启动后可能遇到的问题

使用wine启动HBuidlerX.exe后,可能会遇到无法显示顶部菜单的问题

解决方法:使用alt+t s 快捷键打开系统设置,选择使用原生windows窗口样式

5. 其它

5.1 问题:为什么5+app、uniapp项目,无法运行到真机和小程序?

原因:

  • ios真机运行依赖于itunes
  • 微信小程序开发者工具、以及其它小程序厂商本身也没有提供linux版本;并且各大windows小程序开发者工具在wine下运行也会出现各种错误
继续阅读 »

前言:
因linux上没有itunes,无法真机运行App;linux上无法运行微信等小程序开发工具。这些问题暂时无法解决。所以DCloud暂时不会投入很多精力开发纯正的linux版,目前提供linux上的2个方案

  1. linux的cli方案,可以调用HBuilderX的cli,适于在linux服务器上调用cli进行运行、发行等工程自动化方案。具体另见:https://ask.dcloud.net.cn/article/41469
  2. wine环境的解决方案,如下:

1. Linux在wine上使用HBuilderX的注意事项

wine上运行HBuilderX, 优缺点如下:

  • HBuilderX支持打开、编辑各种文本文件
  • 支持web项目开发(web普通项目、vue项目等)
  • 支持uniapp h5开发
  • 不支持真机运行5+app、uniapp等项目,由于受第三方工具的影响(具体可参考5.1章节)
  • 各种小程序工具,包含微信小程序、支付宝小程序等,既不支持linux,也不支持在wine下使用。

2. HBuilderX在wine上运行效果

先来看下,wine + HBuilderX的运行效果

3. wine环境安装

3.1 Linux环境说明

  • HBuilderXLinux wine上的运行,必须安装Linux桌面操作系统
  • 以下所有的操作,都是基于ubuntu操作系统
  • 如果您使用其它Linux发行版,如Redhat Fedora SUSE等,安装wine,请参考wine官方文档
  • 如果您在其它Linux桌面操作系统使用wine + HBuilderX,遇到问题,可以反馈给我们。

3.2 wine文档

在安装wine过程中,可能遇到其它问题,请参考官方文档:

https://wiki.winehq.org

https://wiki.winehq.org/Ubuntu

3.3 wine安装

特别注意,下面操作使用到的apt相关的命令,只适用于ubuntu、deb等系统;并不是所有的linux发行版都有apt命令。 其它发行版安装wine,请参考wine官方文档

如果您使用的是 64 位系统,请开启 32 bit 架构支持(如果您之前没有开启的话):

$ sudo dpkg --add-architecture i386 

下载添加仓库密钥:

$ wget -nc https://dl.winehq.org/wine-builds/winehq.key  
$ sudo apt-key add winehq.key

添加仓库:

$ sudo apt-add-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ bionic main'

更新安装包:

$ sudo apt update

然后安装 以下任一一个安装包

稳定分支(选择): 

    sudo apt install --install-recommends winehq-stable  
    如果失败  
    sudo apt-get update  
    sudo apt install --install-recommends winehq-stable

开发分支:

$ sudo apt install --install-recommends winehq-devel

Staging 分支:

$ sudo apt install --install-recommends winehq-staging

(可能需要安装 wine-mono、wine-gecko,点击确定即可)

安装完成之后:

$ wine --version

3.4 wine配置(1)

配置wine:

$ winecfg

此命令会提示安装 wine-mono、wine-gecko,点击确定即可。
但这个安装过程可能会很慢,要看网速。

3.5 wine配置(2)

安装wine-gecko,请点击确定

3.6 wine配置(3)

wine配置,应用程序默认设置,如下图,请点击确定

4. HBuilderX:安装启动

4.1 HBuilderX下载、解压

  1. 打开 HBuilderX官网 下载Windows HBuilderX安装包
  2. HBuilderX安装包是一个压缩包,需要解压

4.2 HBuilderX 程序启动

wine上启动windows HBuilderX程序有以下几种方法:

  • 在终端上,进入HBuilderX解压后的目录, 输入 wine HBuilderX.exe
  • 直接双击exe程序
  • 单击右键exe程序,选择打开方式也可以选择Wine打开。

4.3. HBuilderX启动后可能遇到的问题

使用wine启动HBuidlerX.exe后,可能会遇到无法显示顶部菜单的问题

解决方法:使用alt+t s 快捷键打开系统设置,选择使用原生windows窗口样式

5. 其它

5.1 问题:为什么5+app、uniapp项目,无法运行到真机和小程序?

原因:

  • ios真机运行依赖于itunes
  • 微信小程序开发者工具、以及其它小程序厂商本身也没有提供linux版本;并且各大windows小程序开发者工具在wine下运行也会出现各种错误
收起阅读 »

HBuilderX: ftp插件/sftp插件使用教程 ( 适用HBuilderX 2.7.11版本及以下)

sftp FTP

本文档仅适用于2.6.7 至 2.7.11之间的HBuilderX版本。2.7.12起的FTP插件文档另见https://ask.dcloud.net.cn/article/37430

1. ftp插件简介

1.1 插件安装

插件名称:Ftp Sync, 点击菜单【工具】【插件安装】,安装Ftp Sync插件

1.2 插件介绍

此插件是vscode中流行的ftp插件,因HBuilderX兼容vscode的部分插件生态,也可以在HBuilderX中使用。

  • 使用此插件,可将本地工作区文件,与FTP服务器或linux服务器文件同步。
  • 支持ftp/sftp协议
  • 支持远程文件上传下载删除
  • 支持自动保存上传

2. ftp使用介绍

使用步骤:

  1. 如下图,在项目管理器,新建一个空目录或空项目,然后选中
  2. 右键菜单,点击【Ftp-sync: 创建连接配置】
  3. 系统自动创建.ftp/ftp-sync.json配置文件
  4. 编辑ftp-sync.json, 填写host(ip)username(用户名)password(密码)port(端口)protocol(协议)
  5. 填写完配置文件,右键菜单,点击【Ftp-sync: 浏览远程文件】
  6. 选择指定文件,进行上传下载删除操作s

3. ftp配置文件参数说明

{  
    "remotePath": "/",                  // 远程服务器文件路径,默认为根目录,可以指定目录,比如/srv  
    "host": "host",                     // 主机IP,即目标电脑IP地址  
    "username": "username",             // ftp用户名,或目标电脑用户名  
    "password": "password",             // ftp密码,或目标电脑密码  
    "port": 21,                         // 端口,ftp默认为21; ssh/sftp默认为22。  
    "secure": false,  
    "protocol": "ftp",                  // 协议:支持ftp、sftp  
    "uploadOnSave": false,              // 是否修改后立即保存上传  
    "passive": false,  
    "debug": false,  
    "privateKeyPath": null,  
    "passphrase": null,  
    "agent": null,  
    "allow": [],  
    "ignore": [  
        "\\.ftp",  
        "\\.git",  
        "\\.DS_Store"  
    ],  
    "generatedFiles": {  
        "extensionsToInclude": [],  
        "path": ""  
    }  
}

注意:配置文件是放在项目下的普通json文件,存放着ftp账号。若你的项目使用三方node库,请注意其可靠性,避免ftp账户失窃

3.1 ftp协议

如果远程服务器,搭建了ftp服务,usernamepassword需要填写ftp的用户密码

3.2 sftp协议

sftp不需要任何配置,开启SSH默认就开启了SFTP

也就是说,如果您的远程服务器为linux系统,即使没有搭建ftp服务,也可以通过sftp连接,使用文件同步的服务。

  • SSH/SFTP,默认端口为:22
  • 使用sftp服务,usernamepassword,需要填写目标电脑的用户、密码

4. 使用sftp,连接同步Mac/linux服务器文件

sftp不需要任何配置,开启SSH默认就开启了SFTP

只要支持ssh协议的电脑,都可以使用本插件同步修改文件

另外,也可以通过外部命令方式搭配三方软件使用ftp,详见:https://ask.dcloud.net.cn/article/35459

5. ftp操作配置快捷键

菜单【工具】【自定义快捷键】

备注: 2.6.12+版本开始支持

[  
  // 项目管理器 右键菜单 FTP: 上传选中文件  
  {  
    "key": "ctrl+shift+u",  
    "command": "extension.ftpsyncuploadselected"  
  },  
  // 项目管理器 右键菜单  FTP: 下载选中文件  
  {  
    "key": "ctrl+shift+d",  
    "command": "extension.ftpsyncdownloadselected"  
  },  
  // 项目管理器 右键菜单  FTP: 浏览远程文件  
  {  
    "key": "ctrl+alt+b",  
    "command": "extension.ftpsynclistselected"  
  },  
  // 编辑器 右键菜单 FTP: 上传当前文件   
  {  
    "key": "ctrl+shift+c",  
    "command": "extension.ftpsyncsingle"  
  },  
  // 项目管理器 右键菜单  FTP: 创建连接配置  
  {  
    "key": "ctrl+shift+i",  
    "command": "extension.ftpsyncinit"  
  }  
]  
继续阅读 »

本文档仅适用于2.6.7 至 2.7.11之间的HBuilderX版本。2.7.12起的FTP插件文档另见https://ask.dcloud.net.cn/article/37430

1. ftp插件简介

1.1 插件安装

插件名称:Ftp Sync, 点击菜单【工具】【插件安装】,安装Ftp Sync插件

1.2 插件介绍

此插件是vscode中流行的ftp插件,因HBuilderX兼容vscode的部分插件生态,也可以在HBuilderX中使用。

  • 使用此插件,可将本地工作区文件,与FTP服务器或linux服务器文件同步。
  • 支持ftp/sftp协议
  • 支持远程文件上传下载删除
  • 支持自动保存上传

2. ftp使用介绍

使用步骤:

  1. 如下图,在项目管理器,新建一个空目录或空项目,然后选中
  2. 右键菜单,点击【Ftp-sync: 创建连接配置】
  3. 系统自动创建.ftp/ftp-sync.json配置文件
  4. 编辑ftp-sync.json, 填写host(ip)username(用户名)password(密码)port(端口)protocol(协议)
  5. 填写完配置文件,右键菜单,点击【Ftp-sync: 浏览远程文件】
  6. 选择指定文件,进行上传下载删除操作s

3. ftp配置文件参数说明

{  
    "remotePath": "/",                  // 远程服务器文件路径,默认为根目录,可以指定目录,比如/srv  
    "host": "host",                     // 主机IP,即目标电脑IP地址  
    "username": "username",             // ftp用户名,或目标电脑用户名  
    "password": "password",             // ftp密码,或目标电脑密码  
    "port": 21,                         // 端口,ftp默认为21; ssh/sftp默认为22。  
    "secure": false,  
    "protocol": "ftp",                  // 协议:支持ftp、sftp  
    "uploadOnSave": false,              // 是否修改后立即保存上传  
    "passive": false,  
    "debug": false,  
    "privateKeyPath": null,  
    "passphrase": null,  
    "agent": null,  
    "allow": [],  
    "ignore": [  
        "\\.ftp",  
        "\\.git",  
        "\\.DS_Store"  
    ],  
    "generatedFiles": {  
        "extensionsToInclude": [],  
        "path": ""  
    }  
}

注意:配置文件是放在项目下的普通json文件,存放着ftp账号。若你的项目使用三方node库,请注意其可靠性,避免ftp账户失窃

3.1 ftp协议

如果远程服务器,搭建了ftp服务,usernamepassword需要填写ftp的用户密码

3.2 sftp协议

sftp不需要任何配置,开启SSH默认就开启了SFTP

也就是说,如果您的远程服务器为linux系统,即使没有搭建ftp服务,也可以通过sftp连接,使用文件同步的服务。

  • SSH/SFTP,默认端口为:22
  • 使用sftp服务,usernamepassword,需要填写目标电脑的用户、密码

4. 使用sftp,连接同步Mac/linux服务器文件

sftp不需要任何配置,开启SSH默认就开启了SFTP

只要支持ssh协议的电脑,都可以使用本插件同步修改文件

另外,也可以通过外部命令方式搭配三方软件使用ftp,详见:https://ask.dcloud.net.cn/article/35459

5. ftp操作配置快捷键

菜单【工具】【自定义快捷键】

备注: 2.6.12+版本开始支持

[  
  // 项目管理器 右键菜单 FTP: 上传选中文件  
  {  
    "key": "ctrl+shift+u",  
    "command": "extension.ftpsyncuploadselected"  
  },  
  // 项目管理器 右键菜单  FTP: 下载选中文件  
  {  
    "key": "ctrl+shift+d",  
    "command": "extension.ftpsyncdownloadselected"  
  },  
  // 项目管理器 右键菜单  FTP: 浏览远程文件  
  {  
    "key": "ctrl+alt+b",  
    "command": "extension.ftpsynclistselected"  
  },  
  // 编辑器 右键菜单 FTP: 上传当前文件   
  {  
    "key": "ctrl+shift+c",  
    "command": "extension.ftpsyncsingle"  
  },  
  // 项目管理器 右键菜单  FTP: 创建连接配置  
  {  
    "key": "ctrl+shift+i",  
    "command": "extension.ftpsyncinit"  
  }  
]  
收起阅读 »

使用uniapp有感而发,并提出建议

我认为uniapp应该舍弃小程序兼容,全部精力做vue-native。我相信用不了多长时间uniapp绝对可以与react-native,flutter形成三足鼎立的态势,形成强大的vue-native生态。

我认为uniapp应该舍弃小程序兼容,全部精力做vue-native。我相信用不了多长时间uniapp绝对可以与react-native,flutter形成三足鼎立的态势,形成强大的vue-native生态。

easemob-websdk 不能使用

uni_app 环信 IM

通过 1。npm init -y

2.npm install easemob-websdk --save

引用时:import websdk from 'easemob-websdk'

报错如下 但是再vue-cli 项目里面不报错

通过 1。npm init -y

2.npm install easemob-websdk --save

引用时:import websdk from 'easemob-websdk'

报错如下 但是再vue-cli 项目里面不报错

求购买一款小说app 参考全民小说那种,可以换源

服务器不存小说数据,相当于一个小说搜索引擎。带广告位。参考 全民小说那种 带价来 q1258968020

服务器不存小说数据,相当于一个小说搜索引擎。带广告位。参考 全民小说那种 带价来 q1258968020

ios分享反复跳转及transport交付提示90046错误com.apple.developer.associated-domains

uniapp

这个东西花费了我一天时间
如果UniversalLinks没有配置对的话,微信会直接提示你错误,
可以分享微信,而且会反复跳转,基本上是manifest.json文件配置错误了,而且这个时候在Safari上是无法用UniversalLinks打开App的
一般情况都是com.apple.developer.associated-domains配置错误,我遇到的问题都是这个配置错误。
错误配置有很多,我贴个参考

"ios" : {  
                "privacyDescription" : {  
                    "NSPhotoLibraryUsageDescription" : "获取相册信息用于用户选择头像",  
                    "NSCameraUsageDescription" : "调用摄像头用于网页端登录"  
                },  
                "capabilities" : {  
                    "entitlements" : {  
                        "com.apple.developer.associated-domains" : [ "https://xxxx.xxxx.cn/" ]  
                    }  
                }  
            },

正确配置

"ios" : {  
                "privacyDescription" : {  
                    "NSPhotoLibraryUsageDescription" : "获取相册信息用于用户选择头像",  
                    "NSCameraUsageDescription" : "调用摄像头用于网页端登录"  
                },  
                "capabilities" : {  
                    "entitlements" : {  
                        "com.apple.developer.associated-domains" : [ "applinks:xxxx.xxxx.cn" ]  
                    }  
                }  
            },  

其中很容易多写“/”,“https://”等,这个改正之后,我这两个问题就解决了,并且用Safari可以打开app。
当然如果不用分享功能,直接关掉Associated Domains,并把manifest.json里面的capabilities去掉就行了

同时这个分享的问题在官方的示例APP:Hello uni-app里面也是存在的,官方的示例也是多次反复跳转,我一直也为uni-app开发就是这种情况,但是我用了MobLink的示例APP之后才发现只有第一次会反复跳转,之后再分享就不会出现反复跳转的问题了。

继续阅读 »

这个东西花费了我一天时间
如果UniversalLinks没有配置对的话,微信会直接提示你错误,
可以分享微信,而且会反复跳转,基本上是manifest.json文件配置错误了,而且这个时候在Safari上是无法用UniversalLinks打开App的
一般情况都是com.apple.developer.associated-domains配置错误,我遇到的问题都是这个配置错误。
错误配置有很多,我贴个参考

"ios" : {  
                "privacyDescription" : {  
                    "NSPhotoLibraryUsageDescription" : "获取相册信息用于用户选择头像",  
                    "NSCameraUsageDescription" : "调用摄像头用于网页端登录"  
                },  
                "capabilities" : {  
                    "entitlements" : {  
                        "com.apple.developer.associated-domains" : [ "https://xxxx.xxxx.cn/" ]  
                    }  
                }  
            },

正确配置

"ios" : {  
                "privacyDescription" : {  
                    "NSPhotoLibraryUsageDescription" : "获取相册信息用于用户选择头像",  
                    "NSCameraUsageDescription" : "调用摄像头用于网页端登录"  
                },  
                "capabilities" : {  
                    "entitlements" : {  
                        "com.apple.developer.associated-domains" : [ "applinks:xxxx.xxxx.cn" ]  
                    }  
                }  
            },  

其中很容易多写“/”,“https://”等,这个改正之后,我这两个问题就解决了,并且用Safari可以打开app。
当然如果不用分享功能,直接关掉Associated Domains,并把manifest.json里面的capabilities去掉就行了

同时这个分享的问题在官方的示例APP:Hello uni-app里面也是存在的,官方的示例也是多次反复跳转,我一直也为uni-app开发就是这种情况,但是我用了MobLink的示例APP之后才发现只有第一次会反复跳转,之后再分享就不会出现反复跳转的问题了。

收起阅读 »

【uniapp差量更新、热加载、热更新、热修复】uniapp增量更新解决方案~

热更新 差量更新

前言

各位D友大家好,调皮的杨大宝又回来了,虽然很久没在这里出现了,但是我一直在关注uniapp的成长。真的是越来越强大了。自从uniapp出生时分享过几次经验之后由于项目完结所以uniapp的使用暂时告一段落。偶尔回来一看之前的文章确实帮助了不少的D友获得很多肯定,顿感心头一暖。所以这次我决定又来搞事情啦~O(∩_∩)O哈哈~
差量更新h5+是支持的,之所以uniapp没有支持不是因为底层技术问题而是uniapp自身结构问题。我们知道uniapp是经过打包编译后运行的,h5+没有经过编译,所以文件、目录结构是稳定的,所以可以直接替换文件。uniapp只能编译后进行差量更新,即便你编译后也要必须保证编译配置(pages_path,和小程序一样需要在pages里注册页面路由)的完整。这个就比较费劲了。个人猜测官方没出uniapp差量更新方案估计是这个原因。既然找到原因那么就从这里出发开始搞吧。

第一步(变换开发模式)

把原有的单文件路由模式改成组件模式这样我们就不用考虑编译配置了。
目录结构


入口文件代码

<template>  
    <component :is="pageId" :options="options"></component>  
</template>  

<script>  
    //这里只是一个入口文件,不建议写业务代码.真正的页面代码以组件的形式引入便于管理和升级  

    //开始批量加载页面  
    const modulesFiles = require.context('@/pages/home/pages', true, /\.nvue$/);  
    const modules = modulesFiles.keys().reduce((modules, modulePath) => {  
      const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')  
      const value = modulesFiles(modulePath)  
      modules[moduleName] = value.default  
      return modules  
    }, {})  
    export default {  
        components:modules,  
        data() {  
            return {  
                options:{},  
                name:'home',  
                pageId: 'index',  
                version:'1.0.0',  
            }  
        },  
        onLoad(options) {  
            console.log(options)  
            this.options = options;  
            this.pageId = options.pageId?options.pageId:'index';  
            //开始检查版本更新  
            uni.$emit('checkUpdateModule',{name:this.name,version:this.version})  
        },  
        methods: {  

        }  
    }  
</script>  

checkUpdateModule方法我写在了app.vue里面,代码如下

<script>  
    export default {  
        onLaunch: function() {  
            console.log('App Launch')  
            // #ifdef APP-PLUS    
            let CheckedModuleArr = [];  
            uni.$on('checkUpdateModule',function(data){  
                if(!CheckedModuleArr.includes(data.name)){  
                    CheckedModuleArr.push(data.name)  
                    //plus.runtime.getProperty(plus.runtime.appid, function(widgetInfo) {    
                        uni.request({    
                            url: 'http://192.168.1.144/',    
                            data: {    
                                version: data.version,    
                                name: data.name    
                            },    
                            success: (result) => {  
                                var data = result.data;    
                                if (data.update && data.wgtUrl) {    
                                    console.log(data);  
                                    uni.downloadFile({    
                                        url: data.wgtUrl,  
                                        success: (downloadResult) => {    
                                            if (downloadResult.statusCode === 200) {    
                                                plus.runtime.install(downloadResult.tempFilePath, {    
                                                    force: true   
                                                }, function() {    
                                                    console.log('install success...');    
                                                    //plus.runtime.restart();    
//有意思的是这种方式热加载热更新居然不用重启局生效,具体可自己把握  
                                                }, function(e) {    
                                                    console.error(e);   
                                                });    
                                            }else{  
                                                console.error('download fail...');  
                                            }    
                                        }    
                                    });    
                                }    
                            }    
                        });    
                    //});    
                }  
            })  
            // #endif  
        },  
        onShow: function() {  
            console.log('App Show')  
        },  
        onHide: function() {  
            console.log('App Hide')  
        }  
    }  
</script>

第二步(制作wgtu包)

首先我们要先编译运行一下然后根据下图目录找到编译后的文件
以更新test模块为例

制作步骤参考 https://ask.dcloud.net.cn/article/199

注意

这样的话就可以进行差量更新了。
这里要注意的是 pages里面只需要注册每个模块的入口文件就可以了跳转也只需要跳转入口文件目录然后根据options参数进行动态加载组件方法参考:

//pageId 为组件名称(即文件名)。这里注意文件名不要和原有组件冲突  
uni.navigateTo({  
                    url:'/pages/home/index?pageId=page2'  
                })

ps

至此就结束了,个人测试没有问题,但是这种方法可能会带来性能上的损耗,自己看着办吧。

值得一提的是这种方式似乎更新完无需重启应用,真正达到无感升级。(具体各位自己再测吧)

其他功能插件:
1、uniapp图片裁剪插件
2、swiper + scroll-view 实现下拉刷新
3、单击back隐藏到后台
4、【插件、图表】7种图表漂亮丰富
5、小程序转uni-app、小程序移植uniapp经验分享~ 这个官方现在有更好的解决方案了,仅供参考

继续阅读 »

前言

各位D友大家好,调皮的杨大宝又回来了,虽然很久没在这里出现了,但是我一直在关注uniapp的成长。真的是越来越强大了。自从uniapp出生时分享过几次经验之后由于项目完结所以uniapp的使用暂时告一段落。偶尔回来一看之前的文章确实帮助了不少的D友获得很多肯定,顿感心头一暖。所以这次我决定又来搞事情啦~O(∩_∩)O哈哈~
差量更新h5+是支持的,之所以uniapp没有支持不是因为底层技术问题而是uniapp自身结构问题。我们知道uniapp是经过打包编译后运行的,h5+没有经过编译,所以文件、目录结构是稳定的,所以可以直接替换文件。uniapp只能编译后进行差量更新,即便你编译后也要必须保证编译配置(pages_path,和小程序一样需要在pages里注册页面路由)的完整。这个就比较费劲了。个人猜测官方没出uniapp差量更新方案估计是这个原因。既然找到原因那么就从这里出发开始搞吧。

第一步(变换开发模式)

把原有的单文件路由模式改成组件模式这样我们就不用考虑编译配置了。
目录结构


入口文件代码

<template>  
    <component :is="pageId" :options="options"></component>  
</template>  

<script>  
    //这里只是一个入口文件,不建议写业务代码.真正的页面代码以组件的形式引入便于管理和升级  

    //开始批量加载页面  
    const modulesFiles = require.context('@/pages/home/pages', true, /\.nvue$/);  
    const modules = modulesFiles.keys().reduce((modules, modulePath) => {  
      const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')  
      const value = modulesFiles(modulePath)  
      modules[moduleName] = value.default  
      return modules  
    }, {})  
    export default {  
        components:modules,  
        data() {  
            return {  
                options:{},  
                name:'home',  
                pageId: 'index',  
                version:'1.0.0',  
            }  
        },  
        onLoad(options) {  
            console.log(options)  
            this.options = options;  
            this.pageId = options.pageId?options.pageId:'index';  
            //开始检查版本更新  
            uni.$emit('checkUpdateModule',{name:this.name,version:this.version})  
        },  
        methods: {  

        }  
    }  
</script>  

checkUpdateModule方法我写在了app.vue里面,代码如下

<script>  
    export default {  
        onLaunch: function() {  
            console.log('App Launch')  
            // #ifdef APP-PLUS    
            let CheckedModuleArr = [];  
            uni.$on('checkUpdateModule',function(data){  
                if(!CheckedModuleArr.includes(data.name)){  
                    CheckedModuleArr.push(data.name)  
                    //plus.runtime.getProperty(plus.runtime.appid, function(widgetInfo) {    
                        uni.request({    
                            url: 'http://192.168.1.144/',    
                            data: {    
                                version: data.version,    
                                name: data.name    
                            },    
                            success: (result) => {  
                                var data = result.data;    
                                if (data.update && data.wgtUrl) {    
                                    console.log(data);  
                                    uni.downloadFile({    
                                        url: data.wgtUrl,  
                                        success: (downloadResult) => {    
                                            if (downloadResult.statusCode === 200) {    
                                                plus.runtime.install(downloadResult.tempFilePath, {    
                                                    force: true   
                                                }, function() {    
                                                    console.log('install success...');    
                                                    //plus.runtime.restart();    
//有意思的是这种方式热加载热更新居然不用重启局生效,具体可自己把握  
                                                }, function(e) {    
                                                    console.error(e);   
                                                });    
                                            }else{  
                                                console.error('download fail...');  
                                            }    
                                        }    
                                    });    
                                }    
                            }    
                        });    
                    //});    
                }  
            })  
            // #endif  
        },  
        onShow: function() {  
            console.log('App Show')  
        },  
        onHide: function() {  
            console.log('App Hide')  
        }  
    }  
</script>

第二步(制作wgtu包)

首先我们要先编译运行一下然后根据下图目录找到编译后的文件
以更新test模块为例

制作步骤参考 https://ask.dcloud.net.cn/article/199

注意

这样的话就可以进行差量更新了。
这里要注意的是 pages里面只需要注册每个模块的入口文件就可以了跳转也只需要跳转入口文件目录然后根据options参数进行动态加载组件方法参考:

//pageId 为组件名称(即文件名)。这里注意文件名不要和原有组件冲突  
uni.navigateTo({  
                    url:'/pages/home/index?pageId=page2'  
                })

ps

至此就结束了,个人测试没有问题,但是这种方法可能会带来性能上的损耗,自己看着办吧。

值得一提的是这种方式似乎更新完无需重启应用,真正达到无感升级。(具体各位自己再测吧)

其他功能插件:
1、uniapp图片裁剪插件
2、swiper + scroll-view 实现下拉刷新
3、单击back隐藏到后台
4、【插件、图表】7种图表漂亮丰富
5、小程序转uni-app、小程序移植uniapp经验分享~ 这个官方现在有更好的解决方案了,仅供参考

收起阅读 »