
uni-app html5+ 实现input 获取焦点聚焦但不弹出键盘,不需要原生插件,input框聚焦时不显示软键盘的方法
uni-app html5+ 实现input 获取焦点聚焦但不弹出键盘,不需要原生插件,input框聚焦时不显示软键盘的方法
有偿提供代码
附件是视频,可以看下,是否符合需求
QQ:543610866
uni-app html5+ 实现input 获取焦点聚焦但不弹出键盘,不需要原生插件,input框聚焦时不显示软键盘的方法
有偿提供代码
附件是视频,可以看下,是否符合需求
QQ:543610866

uniapp+uview后台管理系统|uni-app手机端后台uniUadmin
前段时间有给大家分享一个uni-app仿抖音小视频,今天分享一个uniapp移动端后台管理uni-uadmin实例项目。
uni-uadmin 一款基于uni-app+uviewUI+uniUI+mockjs+echarts等技术开发架构的手机移动端后台管理系统模板。包含了图表、自定义表格、表单、瀑布流及图文编辑器等业务模块,动态权限管理,错误页处理,可编译至H5+小程序+APP端。
技术栈
- 编辑器:HbuilderX3.3.5
- 使用技术:vue+uniapp+uViewUI+mockjs
- 弹窗组件:ua-popup(基于uni-app跨端弹框组件)
- 表格组件:ua-table(基于uni-app封装的多功能表格)
- 自定义组件:uaDock全新的dock风格tabbar组件
- uniapp图表组件:u-charts图表库
项目构建目录
共用模板
整个页面结构布局分为顶部自定义导航+内容区域+底部dock tab菜单三大部分。
<!-- 公共页面模板 -->
<template>
<view class="ua__pageview flexbox flex-col" :style="{'--SKIN': $store.state.skin, 'background': bgcolor, 'color': color}">
<slot name="header" />
<!-- //主容器 -->
<view class="ua__scrollview flex1">
<slot />
</view>
<!-- //底部 -->
<slot name="footer" />
<!-- //dock菜单 -->
<ua-dock v-if="dock && dock != 'false'" @click="handleDockClick" />
<!-- //函数式弹框 -->
<!-- //换肤弹框模板 -->
<ua-popup v-model="isVisibleSkin" position="right">
<Skin />
</ua-popup>
</view>
</template>
uniapp-uadmin 还支持动态权限控制。
uniapp自定义菜单tab组件ua-dock
<!-- //底部dock菜单 -->
<template>
<view class="ua__dockbar">
<scroll-view class="ua__dock-scroll ua__filter" :class="platform" scroll-x :style="{'background': bgcolor}">
<view class="ua__dock-wrap">
<!-- Tab菜单项 -->
<block v-for="(item, index) in menu" :key="index">
<view v-if="item.type == 'divider'" class="ua__dock-divider"></view>
<view v-else class="ua__dock-item" :class="currentTabIndex == index ? 'cur' : ''" @click="switchTab(index, item)">
<text v-if="item.icon" class="iconfont nvuefont" :class="item.icon">{{item.icon}}</text>
<image v-if="item.img" :src="item.img" class="iconimg" :style="{'font-size': item.iconSize}" />
<text v-if="item.badge" class="ua__badge ua__dock-badge">{{item.badge}}</text>
<text v-if="item.dot" class="ua__badge-dot ua__dock-badgeDot"></text>
</view>
</block>
</view>
</scroll-view>
</view>
</template>
props: {
// 当前索引
current: { type: [Number, String], default: 0 },
// 背景色
bgcolor: { type: String, default: null },
/**
* [ 菜单选项 ]
type 菜单类型 type: 'tab'支持uni.switchTab切换 type: 'divider'分割线
path 菜单页面地址
icon 菜单图标-iconfont图标
img 菜单图片
color 菜单图标颜色
title 标题
badge 圆点数字
dot 小红点
*/
menu: {
type: Array,
default: () => [
/* Tab菜单 */
{
type: 'tab',
path: '/pages/index/index',
icon: `\ue619`,
color: '#2979ff',
title: '首页',
},
{
type: 'tab',
path: '/pages/component/index',
icon: 'icon-component',
color: '#17c956',
title: '组件',
badge: 5,
},
{
type: 'tab',
path: '/pages/permission/index',
icon: 'icon-auth',
color: '#f44336',
title: '权限管理',
},
{
type: 'tab',
path: '/pages/setting/index',
icon: 'icon-wo',
color: '#8d1cff',
title: '设置',
dot: true,
},
{
path: '/pages/error/404',
img: require('@/static/mac/keychain.png'),
title: '错误页面',
},
{ type: 'divider' },
/* Nav菜单 */
{
img: require('@/static/logo.png'),
title: 'github',
},
{
img: 'https://www.uviewui.com/common/logo.png',
title: 'gitee',
},
{
img: require('@/static/mac/colorsync.png'),
title: '皮肤',
},
{
img: require('@/static/mac/info.png'),
title: '关于',
},
{ type: 'divider' },
{
img: require('@/static/mac/bin.png'),
title: '回收站',
badge: 12,
},
]
},
},
uniapp自定义多功能表格组件ua-table
ua-table 支持多行、多列,表头固定,自定义slot插槽内容,点击行列返回数据等功能。
<ua-table
:columns="columns"
headerBgColor="#eee"
:headerBold="true"
stripe
padding="5px 0"
:data="data.list"
height="450rpx"
>
</ua-table>
<script>
import Mock from 'mockjs'
export default {
data() {
return {
columns: [
{type: 'index', align: 'center', width: 100, fixed: true}, // 索引序号
{prop: 'title', label: '标题', align: 'left', width: '350'},
{prop: 'num', label: '搜索量', align: 'center', width: 120},
],
data: Mock.mock({
total: 100,
page: 1,
pagesize: 10,
'list|10': [
{
id: '@id()',
title: '@ctitle(10, 20)',
num: '@integer(1000,10000)'
}
]
}),
}
}
}
</script>
如果想实现一些自定义插槽内容,则可以通过如下方法实现。
<ua-table
:columns="columns"
headerBgColor="#eee"
:headerBold="true"
:stripe="true"
:data="data.list"
@row-click="handleRowClick"
@select="handleCheck"
height="750rpx"
style="border:1px solid #eee"
>
<template #default="{row, col, index}">
<block v-if="col.slot == 'image'">
<u-image :src="row.image" :lazy-load="true" height="100rpx" width="100rpx" @click="previewImage(row.image)" />
</block>
<block v-if="col.slot == 'switch'">
<u-switch v-model="row.switch" inactive-color="#fff" :size="36"></u-switch>
</block>
<block v-if="col.slot == 'tags'">
<u-tag :text="row.tags" bg-color="#607d8b" color="#fff" mode="dark" size="mini" />
</block>
<block v-if="col.slot == 'progress'">
<u-line-progress active-color="#1fb925" :percent="row.progress" :show-percent="false" :height="16"></u-line-progress>
</block>
<block v-if="col.slot == 'btns'">
<view class="ua__link success" @click.stop="handleFormEdit(row)">编辑</view>
<view class="ua__link error" @click.stop="handleDel(row, index)">删除</view>
</block>
</template>
</ua-table>
<script>
/**
* uniapp自定义表格
* @author XY Q:282310962
*/
import Mock from 'mockjs'
export default {
data() {
return {
columns: [
{type: 'selection', align: 'center', width: 80, fixed: true}, // 多选
{type: 'index', align: 'center', width: 80, fixed: true}, // 索引序号
{prop: 'author', label: '作者', align: 'center', width: 120},
{prop: 'title', label: '标题', align: 'left', width: 350},
{slot: 'image', label: '图片', align: 'center', width: 120},
{slot: 'switch', label: '推荐', align: 'center', width: 100},
{slot: 'tags', label: '标签', align: 'center', width: 100},
{slot: 'progress', label: '热度', align: 'center', width: 150},
{prop: 'date', label: '发布时间', align: 'left', width: 300}, // 时间
{slot: 'btns', label: '操作', align: 'center', width: 150, fixed: 'right'}, // 操作
],
data: Mock.mock({
total: 100,
page: 1,
pagesize: 10,
'list|30': [
{
id: '@id()',
author: '@cname()',
title: '@ctitle(10, 20)',
image: 'https://picsum.photos/400/400?random=' + '@guid()',
switch: '@boolean()',
'tags|1': ['admin', 'test', 'dev'],
progress: '@integer(30, 90)',
date: '@datetime()'
}
]
}),
}
}
}
</script>
OK,基于uni-app+uview-ui开发原生移动端中后台管理系统就分享到这里。
链接:https://juejin.cn/post/7058793828123148325
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
前段时间有给大家分享一个uni-app仿抖音小视频,今天分享一个uniapp移动端后台管理uni-uadmin实例项目。
uni-uadmin 一款基于uni-app+uviewUI+uniUI+mockjs+echarts等技术开发架构的手机移动端后台管理系统模板。包含了图表、自定义表格、表单、瀑布流及图文编辑器等业务模块,动态权限管理,错误页处理,可编译至H5+小程序+APP端。
技术栈
- 编辑器:HbuilderX3.3.5
- 使用技术:vue+uniapp+uViewUI+mockjs
- 弹窗组件:ua-popup(基于uni-app跨端弹框组件)
- 表格组件:ua-table(基于uni-app封装的多功能表格)
- 自定义组件:uaDock全新的dock风格tabbar组件
- uniapp图表组件:u-charts图表库
项目构建目录
共用模板
整个页面结构布局分为顶部自定义导航+内容区域+底部dock tab菜单三大部分。
<!-- 公共页面模板 -->
<template>
<view class="ua__pageview flexbox flex-col" :style="{'--SKIN': $store.state.skin, 'background': bgcolor, 'color': color}">
<slot name="header" />
<!-- //主容器 -->
<view class="ua__scrollview flex1">
<slot />
</view>
<!-- //底部 -->
<slot name="footer" />
<!-- //dock菜单 -->
<ua-dock v-if="dock && dock != 'false'" @click="handleDockClick" />
<!-- //函数式弹框 -->
<!-- //换肤弹框模板 -->
<ua-popup v-model="isVisibleSkin" position="right">
<Skin />
</ua-popup>
</view>
</template>
uniapp-uadmin 还支持动态权限控制。
uniapp自定义菜单tab组件ua-dock
<!-- //底部dock菜单 -->
<template>
<view class="ua__dockbar">
<scroll-view class="ua__dock-scroll ua__filter" :class="platform" scroll-x :style="{'background': bgcolor}">
<view class="ua__dock-wrap">
<!-- Tab菜单项 -->
<block v-for="(item, index) in menu" :key="index">
<view v-if="item.type == 'divider'" class="ua__dock-divider"></view>
<view v-else class="ua__dock-item" :class="currentTabIndex == index ? 'cur' : ''" @click="switchTab(index, item)">
<text v-if="item.icon" class="iconfont nvuefont" :class="item.icon">{{item.icon}}</text>
<image v-if="item.img" :src="item.img" class="iconimg" :style="{'font-size': item.iconSize}" />
<text v-if="item.badge" class="ua__badge ua__dock-badge">{{item.badge}}</text>
<text v-if="item.dot" class="ua__badge-dot ua__dock-badgeDot"></text>
</view>
</block>
</view>
</scroll-view>
</view>
</template>
props: {
// 当前索引
current: { type: [Number, String], default: 0 },
// 背景色
bgcolor: { type: String, default: null },
/**
* [ 菜单选项 ]
type 菜单类型 type: 'tab'支持uni.switchTab切换 type: 'divider'分割线
path 菜单页面地址
icon 菜单图标-iconfont图标
img 菜单图片
color 菜单图标颜色
title 标题
badge 圆点数字
dot 小红点
*/
menu: {
type: Array,
default: () => [
/* Tab菜单 */
{
type: 'tab',
path: '/pages/index/index',
icon: `\ue619`,
color: '#2979ff',
title: '首页',
},
{
type: 'tab',
path: '/pages/component/index',
icon: 'icon-component',
color: '#17c956',
title: '组件',
badge: 5,
},
{
type: 'tab',
path: '/pages/permission/index',
icon: 'icon-auth',
color: '#f44336',
title: '权限管理',
},
{
type: 'tab',
path: '/pages/setting/index',
icon: 'icon-wo',
color: '#8d1cff',
title: '设置',
dot: true,
},
{
path: '/pages/error/404',
img: require('@/static/mac/keychain.png'),
title: '错误页面',
},
{ type: 'divider' },
/* Nav菜单 */
{
img: require('@/static/logo.png'),
title: 'github',
},
{
img: 'https://www.uviewui.com/common/logo.png',
title: 'gitee',
},
{
img: require('@/static/mac/colorsync.png'),
title: '皮肤',
},
{
img: require('@/static/mac/info.png'),
title: '关于',
},
{ type: 'divider' },
{
img: require('@/static/mac/bin.png'),
title: '回收站',
badge: 12,
},
]
},
},
uniapp自定义多功能表格组件ua-table
ua-table 支持多行、多列,表头固定,自定义slot插槽内容,点击行列返回数据等功能。
<ua-table
:columns="columns"
headerBgColor="#eee"
:headerBold="true"
stripe
padding="5px 0"
:data="data.list"
height="450rpx"
>
</ua-table>
<script>
import Mock from 'mockjs'
export default {
data() {
return {
columns: [
{type: 'index', align: 'center', width: 100, fixed: true}, // 索引序号
{prop: 'title', label: '标题', align: 'left', width: '350'},
{prop: 'num', label: '搜索量', align: 'center', width: 120},
],
data: Mock.mock({
total: 100,
page: 1,
pagesize: 10,
'list|10': [
{
id: '@id()',
title: '@ctitle(10, 20)',
num: '@integer(1000,10000)'
}
]
}),
}
}
}
</script>
如果想实现一些自定义插槽内容,则可以通过如下方法实现。
<ua-table
:columns="columns"
headerBgColor="#eee"
:headerBold="true"
:stripe="true"
:data="data.list"
@row-click="handleRowClick"
@select="handleCheck"
height="750rpx"
style="border:1px solid #eee"
>
<template #default="{row, col, index}">
<block v-if="col.slot == 'image'">
<u-image :src="row.image" :lazy-load="true" height="100rpx" width="100rpx" @click="previewImage(row.image)" />
</block>
<block v-if="col.slot == 'switch'">
<u-switch v-model="row.switch" inactive-color="#fff" :size="36"></u-switch>
</block>
<block v-if="col.slot == 'tags'">
<u-tag :text="row.tags" bg-color="#607d8b" color="#fff" mode="dark" size="mini" />
</block>
<block v-if="col.slot == 'progress'">
<u-line-progress active-color="#1fb925" :percent="row.progress" :show-percent="false" :height="16"></u-line-progress>
</block>
<block v-if="col.slot == 'btns'">
<view class="ua__link success" @click.stop="handleFormEdit(row)">编辑</view>
<view class="ua__link error" @click.stop="handleDel(row, index)">删除</view>
</block>
</template>
</ua-table>
<script>
/**
* uniapp自定义表格
* @author XY Q:282310962
*/
import Mock from 'mockjs'
export default {
data() {
return {
columns: [
{type: 'selection', align: 'center', width: 80, fixed: true}, // 多选
{type: 'index', align: 'center', width: 80, fixed: true}, // 索引序号
{prop: 'author', label: '作者', align: 'center', width: 120},
{prop: 'title', label: '标题', align: 'left', width: 350},
{slot: 'image', label: '图片', align: 'center', width: 120},
{slot: 'switch', label: '推荐', align: 'center', width: 100},
{slot: 'tags', label: '标签', align: 'center', width: 100},
{slot: 'progress', label: '热度', align: 'center', width: 150},
{prop: 'date', label: '发布时间', align: 'left', width: 300}, // 时间
{slot: 'btns', label: '操作', align: 'center', width: 150, fixed: 'right'}, // 操作
],
data: Mock.mock({
total: 100,
page: 1,
pagesize: 10,
'list|30': [
{
id: '@id()',
author: '@cname()',
title: '@ctitle(10, 20)',
image: 'https://picsum.photos/400/400?random=' + '@guid()',
switch: '@boolean()',
'tags|1': ['admin', 'test', 'dev'],
progress: '@integer(30, 90)',
date: '@datetime()'
}
]
}),
}
}
}
</script>
OK,基于uni-app+uview-ui开发原生移动端中后台管理系统就分享到这里。
链接:https://juejin.cn/post/7058793828123148325
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

vue3和vite双向加持,uni-app性能再次提升
uni-app
对vue3 & Vite
的升级,是一个渐进式过程:
- 2020年9月:小程序平台支持 vue3 开发,小程序平台编译器依然使用webpack;
- 2021年5月:H5平台支持 vue3 开发,H5平台编译器升级为 Vite;
- 2021年8月:App平台支持 vue3 开发,App平台编译器升级为 Vite;
- 2021年11月:小程序平台编译器升级为 Vite;
至此,uni-app
在全平台支持了 Vite
编译及Vue 3.x
运行。
so,这场持续一年之久的大版本升级,究竟给uni-app
项目带来了哪些提升?
是时候总结(秀)一波了。
新版 uni-app 框架主要做了三大改进:
- 重写框架内核:基于
vue3 + ts
重写内置组件和API,实现更彻底、更高效的tree-shaking
; - 新增支持 Vite 构建工具,在H5平台实现秒开预览;
- 新增支持 Vue3.x,实现更灵活的开发方式,及更高的运行性能;
基于这三大改进,uni-app项目获得了多快好省四大收益:
- 更多的语法支持,支持组合式API,业务聚焦,开发效率更高;
- 更快的编译速度,H5平台十倍加速,小程序、App加速30%以上;
- 更好的运行性能,用户端响应更快,体验更好;
- 更小的代码体积,瘦身30%以上,更省体积、更省流量
更多的语法支持
新版uni-app
支持Vue 3.x框架,支持组合式API,可实现更聚焦的业务开发。
Vue 3.x的一些新增特性,uni-app
也已经完全支持,如:
- 支持
<script setup>
- 支持
<style scoped>
、<style module>
、State-Driven Dynamic CSS(v-bind)
- 支持
jsx
、tsx
(h5,app 平台支持,小程序不支持)
另外,在小程序平台,新版uni-app
也扩展了更多的语法,如:
- 更完善的模板语法支持(如
class
、style
支持函数、变量等,不再局限数组、对象类型) - 更完整的
props
支持(如传递函数) - 更完善的
slot
支持(如作用域插槽)
更快的编译速度
开发者日常工作中,最无聊的就是等待编译构建。
某乎上还有一个”程序员在等待编译的时候都做什么?“的讨论帖,可见编译时间对开发者而言,是一个多么尴尬无聊的碎片时间。
uni-app
本次升级vue3 & Vite
后,在编译时间上有多少改进?带给开发者多少福利?我们安排真实测试,以数据说话。
测试环境说明:
硬件:RedmiBook 14 二代
处理器:Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz
内存:16.0 GB
操作系统:Windows 11 专业版 64 位操作系统
关于编译速度,我们做了两个维度的对比:
- 纵向对比:挑选
uni-app
常用项目模板,在H5、小程序、App平台,分别测试vue 2.6
和vue 3.x
的编译时间 - 横向对比:使用业内优秀的其它跨端框架,创建默认项目模板,记录其编译时间,和
uni-app
的vue 3.x
版本进行对比
uni-app 历史版本纵向对比
我们选择uni-app默认模板
、uni-starter
、hello-uniapp
三个项目模板,分别测试vue 2.6
和vue 3.x
的编译时间。
uni-app项目编译时间的采集方式:
vue 2.6
版本编译时间 = webpack 的 stats.endTime - stats.startTimevue 3.x
版本编译时间 = 构建工具入口处记录 global.vite_start_time = performance.now(),构建工具编译完成时:performance.now() - global.vite_start_time
H5平台
对uni-app
的三个项目模板分别运行到H5平台,进行多次编译测试,并求其均值后,获得如下数据:
由此,我们可以观察到:
- 在
vue 2.6
环境下,随着项目复杂度的提升,H5首页预览所需编译时间会直线增加;这是因为在vue 2.6
版本下,虽然仅预览首页,但依然会使用webpack
编译整个项目资源;故项目越复杂,编译时间越长; - 在
vue 3.x
环境下,H5首页预览的编译时间跟项目复杂度也有关系,但增幅不大;这是因为在vue 3.x
版本下,使用Vite
进行构建,预览首页时仅编译首页及首页所依赖资源,不会编译其它页面资源。
通过图表对比,我们可以直观得出结论:vue 3.x
环境下的首页编译时间,平均不到vue 2.6
环境下的十分之一。
换言之,vue 3.x
版本下的首页编译速度,相比vue 2.6
版本,有十倍效率提升。
这个十倍效率提升,主要得益于新版采用Vite
作为构建工具,由此带来了两大好处:
- 使用原生 ESM 文件,无需打包,实现极速的服务启动;
- 预览(运行)使用
esbuild
作为打包工具,相比vue 2.6
环境下的webpack
,构建速度快 10-100 倍(这不是我们夸大,详见esbuild)
本着这个十倍效率提升,小伙伴们还不赶紧上手试试?
小程序平台
对uni-app
的三个模板项目运行到小程序平台,多次编译测试,并求其均值后,获得如下数据:
从上图对比数据来看,我们可以得出结论:小程序平台,vue 3.x
版本下的运行编译,相比vue 2.6
版本,编译性能至少提升30%;且项目越复杂,编译性能提升越明显,可以达到40% ~ 50%。
App平台
对uni-app
的三个项目模板继续运行到App平台,多次编译测试,并求其均值后,获得如下数据:
从上图对比数据来看,我们可以得出结论:App平台,vue 3.x
版本下的运行编译,相比vue 2.6
版本,编译性能提升将近50%。
虽没有H5平台的十倍效率提升那么刺激,但将近50%的速度提升,经常开发小程序/App的小伙伴,还不心动?
业内优秀框架横向对比
除了采用不同版本的uni-app
进行纵向对比外,我们还使用业内优秀的跨端框架Taro
,创建空的项目模板,进行横向对比测试。
具体测试方案:
- 安装
Taro
的最新cli,本文测试时使用的版本为"@Tarojs/Taro": "3.3.16" - 使用
Taro init
命令,分别选择react
、vue
、vue3
框架,创建三个默认项目模板,三个项目名称分别为taro3-react
、taro3-vue
、taro3-vue3
,如下图:
- 使用
npm run dev:h5
,运行到H5平台进行预览,记录每次预览编译时间,重复执行,求其均值
关于Taro
编译时间的计算方案:
- 开发一个
Taro
扩展插件,插件规范参考Taro官网 - 插件功能 - 在
ctx.onBuildStart
中记录开始编译时间 - 在
ctx.onBuildFinish
中记录编译结束时间 - 两者的时间差,即为编译过程消耗时间
然后使用uni-app
的cli
命令行,创建基于vue3.x
的空项目模板,项目命名为uni-app-vue3
。
我们使用各自框架的命令行,将如上创建的5个项目分别编译到H5平台和小程序平台,多次测试,并求其均值。
同框架版本在H5平台上的编译时间,结果如下:
从图中可以看出,uni-app
的vue3
版本,在H5平台上的首页编译预览性能是遥遥领先的。这个遥遥有多远呢?这么讲吧,你都编译20次了,友商第一次还没完呢。
继续编译到小程序平台,多次测试,求其均值,结果如下:
从图中可以看出,uni-app
的vue3
版本,在小程序平台上的编译性能也是遥遥领先的,这个遥遥也不近。
更好的运行速度
开发环节编译快了,那面向最终用户的软件,运行性能怎么样?
我们进入性能测试章节。
测试方案:
- 开发内容:开发一个仿微博小程序首页的复杂长列表,支持下拉刷新、上拉翻页、点赞。
- 界面如下:
- 测试机型:小米 Mi 10 pro、MIUI 12.5 (21.11.3 开发版) 、微信版本 8.0.16
- 准备工作:每次开始测试前,杀掉各App进程、清空内存,保证测试机环境基本一致;每次从本地读取静态数据,屏蔽网络差异。
- 评测点:长列表中的某个组件,比如点赞组件,点击时是否能及时的修改未赞和已赞状态?
测试计时方式:
- 选中某微博,点击“点赞”按钮,实现点赞状态状态切换(已赞高亮、未赞灰色),
- 点赞按钮 onclick函数开头开始计时,setData回调函数开头结束计时;
在小米手机上进行多次测试,求其平均值,结果如下:
记录条数 | 200 | 400 | 600 | 800 | 1000 |
---|---|---|---|---|---|
vue2 | 30ms | 43ms | 56ms | 72ms | 90ms |
vue3 | 8ms | 9ms | 9ms | 8ms | 9ms |
从表格中可以看出:
- 随着页面记录的增加,
vue 2.6
版本的uni-app
项目,点赞组件响应时间快速增加,响应越来越慢; - 基于
vue 3.x
的uni-app
项目,点赞组件的响应时间跟页面条数无关,一直保持极高的响应灵敏度,性能体验远高于vue 2.6
版本。
从这个常见的长列表组件响应实验来看,vue 3.x
的性能体验要远高于vue 2.6
版本。
更小的代码体积
项目发行后的代码体积,是一个很重要的考量指标:
- H5平台:更小的代码体积,可以帮助开发者节省服务端带宽及CDN流量,可实现更快的资源加载及页面渲染;
- 小程序平台:更小的代码体积,可加速小程序包的下载(甚至可能免了分包加载的繁琐),帮助用户更快进入小程序业务界面;
- App平台:更小的代码体积,可实现更快的App启动,帮助用户更快进入App首页
为了测试vue 3.x
新版升级后,代码体积的变化,我们同样做了两个维度的测试:
- 纵向对比:选择
uni-app
常用项目模板,在H5、小程序、App平台,分别测试vue 2.6
和vue 3.x
的编译包大小 - 横向对比:使用业内优秀的其它跨端框架,创建默认项目模板,记录其编译后的包体积大小,和
uni-app
版本进行对比
Tips:
- 开发阶段重在编译速度,对应
npm run dev
操作 - 发行阶段重在编译包大小,对应
npm run build
操作
uni-app 不同版本纵向对比
我们复用之前创建的uni-app默认模板
、uni-starter
、hello-uniapp
三个项目模板,分别测试vue 2.6
和vue 3.x
的编译包体积。
uni-app
项目编译包体积的采集方式:编译到对应平台后,记录编译后文件夹的大小。
H5平台
H5平台编译后代码体积记录如下:
从统计结果来看,uni-app
的vue3.x
版本,在H5平台上的编译包体积至少瘦身30%以上。
H5平台的瘦身优化,主要得益于uni-app
框架的底层全面重构,实现了更彻底的摇树优化。
小程序平台
小程序平台编译后代码体积记录如下:
从统计结果来看,uni-app
的vue3.x
版本,在小程序平台上也有大幅瘦身。
App平台
App平台编译后代码体积记录如下:
从统计结果来看,uni-app
的vue3.x
版本,在App平台上根据项目不同,会有不同幅度的瘦身。
从理论上来讲,项目中的页面模板越复杂,App平台的瘦身效果越明显。
业内优秀框架横向对比
关于编译后的代码体积,我们也和业内优秀的跨端框架Taro
进行了对比,复用前面章节创建的三个Taro
项目,分别编译到H5平台和小程序平台,计算其编译后的源码文件夹大小。
从图中可以看出,uni-app
的vue3版本,在H5平台上编译包体积是最小的,只有友商的十分之一左右。
我们继续测试,不同版本框架发行到微信小程序平台,记录其编译包大小:
从图中可以看出,uni-app
的vue3版本,在小程序平台上编译包体积也是最小的。
Tips:细心的开发者会发现,所有框架版本编译到小程序上的代码包体积都远小于其在H5平台上的包体积,这是因为小程序由平台厂商提供内置组件及接口实现,而H5平台则需跨端框架自己实现内置组件及接口,故H5平台的代码包普遍要大一些。
总结
综上,我们以数字说话,阐述了vue3版本uni-app
开发的诸多好处,再回顾一遍:
- 更多的语法
- 更快的编译
- 更好的运行
- 更少的代码
你还不赶紧升级新版uni-app
来试试吗?
对文本测试过程及结果有疑问的同学,欢迎到github上提交issue,欢迎指正。
uni-app
对vue3 & Vite
的升级,是一个渐进式过程:
- 2020年9月:小程序平台支持 vue3 开发,小程序平台编译器依然使用webpack;
- 2021年5月:H5平台支持 vue3 开发,H5平台编译器升级为 Vite;
- 2021年8月:App平台支持 vue3 开发,App平台编译器升级为 Vite;
- 2021年11月:小程序平台编译器升级为 Vite;
至此,uni-app
在全平台支持了 Vite
编译及Vue 3.x
运行。
so,这场持续一年之久的大版本升级,究竟给uni-app
项目带来了哪些提升?
是时候总结(秀)一波了。
新版 uni-app 框架主要做了三大改进:
- 重写框架内核:基于
vue3 + ts
重写内置组件和API,实现更彻底、更高效的tree-shaking
; - 新增支持 Vite 构建工具,在H5平台实现秒开预览;
- 新增支持 Vue3.x,实现更灵活的开发方式,及更高的运行性能;
基于这三大改进,uni-app项目获得了多快好省四大收益:
- 更多的语法支持,支持组合式API,业务聚焦,开发效率更高;
- 更快的编译速度,H5平台十倍加速,小程序、App加速30%以上;
- 更好的运行性能,用户端响应更快,体验更好;
- 更小的代码体积,瘦身30%以上,更省体积、更省流量
更多的语法支持
新版uni-app
支持Vue 3.x框架,支持组合式API,可实现更聚焦的业务开发。
Vue 3.x的一些新增特性,uni-app
也已经完全支持,如:
- 支持
<script setup>
- 支持
<style scoped>
、<style module>
、State-Driven Dynamic CSS(v-bind)
- 支持
jsx
、tsx
(h5,app 平台支持,小程序不支持)
另外,在小程序平台,新版uni-app
也扩展了更多的语法,如:
- 更完善的模板语法支持(如
class
、style
支持函数、变量等,不再局限数组、对象类型) - 更完整的
props
支持(如传递函数) - 更完善的
slot
支持(如作用域插槽)
更快的编译速度
开发者日常工作中,最无聊的就是等待编译构建。
某乎上还有一个”程序员在等待编译的时候都做什么?“的讨论帖,可见编译时间对开发者而言,是一个多么尴尬无聊的碎片时间。
uni-app
本次升级vue3 & Vite
后,在编译时间上有多少改进?带给开发者多少福利?我们安排真实测试,以数据说话。
测试环境说明:
硬件:RedmiBook 14 二代
处理器:Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz
内存:16.0 GB
操作系统:Windows 11 专业版 64 位操作系统
关于编译速度,我们做了两个维度的对比:
- 纵向对比:挑选
uni-app
常用项目模板,在H5、小程序、App平台,分别测试vue 2.6
和vue 3.x
的编译时间 - 横向对比:使用业内优秀的其它跨端框架,创建默认项目模板,记录其编译时间,和
uni-app
的vue 3.x
版本进行对比
uni-app 历史版本纵向对比
我们选择uni-app默认模板
、uni-starter
、hello-uniapp
三个项目模板,分别测试vue 2.6
和vue 3.x
的编译时间。
uni-app项目编译时间的采集方式:
vue 2.6
版本编译时间 = webpack 的 stats.endTime - stats.startTimevue 3.x
版本编译时间 = 构建工具入口处记录 global.vite_start_time = performance.now(),构建工具编译完成时:performance.now() - global.vite_start_time
H5平台
对uni-app
的三个项目模板分别运行到H5平台,进行多次编译测试,并求其均值后,获得如下数据:
由此,我们可以观察到:
- 在
vue 2.6
环境下,随着项目复杂度的提升,H5首页预览所需编译时间会直线增加;这是因为在vue 2.6
版本下,虽然仅预览首页,但依然会使用webpack
编译整个项目资源;故项目越复杂,编译时间越长; - 在
vue 3.x
环境下,H5首页预览的编译时间跟项目复杂度也有关系,但增幅不大;这是因为在vue 3.x
版本下,使用Vite
进行构建,预览首页时仅编译首页及首页所依赖资源,不会编译其它页面资源。
通过图表对比,我们可以直观得出结论:vue 3.x
环境下的首页编译时间,平均不到vue 2.6
环境下的十分之一。
换言之,vue 3.x
版本下的首页编译速度,相比vue 2.6
版本,有十倍效率提升。
这个十倍效率提升,主要得益于新版采用Vite
作为构建工具,由此带来了两大好处:
- 使用原生 ESM 文件,无需打包,实现极速的服务启动;
- 预览(运行)使用
esbuild
作为打包工具,相比vue 2.6
环境下的webpack
,构建速度快 10-100 倍(这不是我们夸大,详见esbuild)
本着这个十倍效率提升,小伙伴们还不赶紧上手试试?
小程序平台
对uni-app
的三个模板项目运行到小程序平台,多次编译测试,并求其均值后,获得如下数据:
从上图对比数据来看,我们可以得出结论:小程序平台,vue 3.x
版本下的运行编译,相比vue 2.6
版本,编译性能至少提升30%;且项目越复杂,编译性能提升越明显,可以达到40% ~ 50%。
App平台
对uni-app
的三个项目模板继续运行到App平台,多次编译测试,并求其均值后,获得如下数据:
从上图对比数据来看,我们可以得出结论:App平台,vue 3.x
版本下的运行编译,相比vue 2.6
版本,编译性能提升将近50%。
虽没有H5平台的十倍效率提升那么刺激,但将近50%的速度提升,经常开发小程序/App的小伙伴,还不心动?
业内优秀框架横向对比
除了采用不同版本的uni-app
进行纵向对比外,我们还使用业内优秀的跨端框架Taro
,创建空的项目模板,进行横向对比测试。
具体测试方案:
- 安装
Taro
的最新cli,本文测试时使用的版本为"@Tarojs/Taro": "3.3.16" - 使用
Taro init
命令,分别选择react
、vue
、vue3
框架,创建三个默认项目模板,三个项目名称分别为taro3-react
、taro3-vue
、taro3-vue3
,如下图:
- 使用
npm run dev:h5
,运行到H5平台进行预览,记录每次预览编译时间,重复执行,求其均值
关于Taro
编译时间的计算方案:
- 开发一个
Taro
扩展插件,插件规范参考Taro官网 - 插件功能 - 在
ctx.onBuildStart
中记录开始编译时间 - 在
ctx.onBuildFinish
中记录编译结束时间 - 两者的时间差,即为编译过程消耗时间
然后使用uni-app
的cli
命令行,创建基于vue3.x
的空项目模板,项目命名为uni-app-vue3
。
我们使用各自框架的命令行,将如上创建的5个项目分别编译到H5平台和小程序平台,多次测试,并求其均值。
同框架版本在H5平台上的编译时间,结果如下:
从图中可以看出,uni-app
的vue3
版本,在H5平台上的首页编译预览性能是遥遥领先的。这个遥遥有多远呢?这么讲吧,你都编译20次了,友商第一次还没完呢。
继续编译到小程序平台,多次测试,求其均值,结果如下:
从图中可以看出,uni-app
的vue3
版本,在小程序平台上的编译性能也是遥遥领先的,这个遥遥也不近。
更好的运行速度
开发环节编译快了,那面向最终用户的软件,运行性能怎么样?
我们进入性能测试章节。
测试方案:
- 开发内容:开发一个仿微博小程序首页的复杂长列表,支持下拉刷新、上拉翻页、点赞。
- 界面如下:
- 测试机型:小米 Mi 10 pro、MIUI 12.5 (21.11.3 开发版) 、微信版本 8.0.16
- 准备工作:每次开始测试前,杀掉各App进程、清空内存,保证测试机环境基本一致;每次从本地读取静态数据,屏蔽网络差异。
- 评测点:长列表中的某个组件,比如点赞组件,点击时是否能及时的修改未赞和已赞状态?
测试计时方式:
- 选中某微博,点击“点赞”按钮,实现点赞状态状态切换(已赞高亮、未赞灰色),
- 点赞按钮 onclick函数开头开始计时,setData回调函数开头结束计时;
在小米手机上进行多次测试,求其平均值,结果如下:
记录条数 | 200 | 400 | 600 | 800 | 1000 |
---|---|---|---|---|---|
vue2 | 30ms | 43ms | 56ms | 72ms | 90ms |
vue3 | 8ms | 9ms | 9ms | 8ms | 9ms |
从表格中可以看出:
- 随着页面记录的增加,
vue 2.6
版本的uni-app
项目,点赞组件响应时间快速增加,响应越来越慢; - 基于
vue 3.x
的uni-app
项目,点赞组件的响应时间跟页面条数无关,一直保持极高的响应灵敏度,性能体验远高于vue 2.6
版本。
从这个常见的长列表组件响应实验来看,vue 3.x
的性能体验要远高于vue 2.6
版本。
更小的代码体积
项目发行后的代码体积,是一个很重要的考量指标:
- H5平台:更小的代码体积,可以帮助开发者节省服务端带宽及CDN流量,可实现更快的资源加载及页面渲染;
- 小程序平台:更小的代码体积,可加速小程序包的下载(甚至可能免了分包加载的繁琐),帮助用户更快进入小程序业务界面;
- App平台:更小的代码体积,可实现更快的App启动,帮助用户更快进入App首页
为了测试vue 3.x
新版升级后,代码体积的变化,我们同样做了两个维度的测试:
- 纵向对比:选择
uni-app
常用项目模板,在H5、小程序、App平台,分别测试vue 2.6
和vue 3.x
的编译包大小 - 横向对比:使用业内优秀的其它跨端框架,创建默认项目模板,记录其编译后的包体积大小,和
uni-app
版本进行对比
Tips:
- 开发阶段重在编译速度,对应
npm run dev
操作 - 发行阶段重在编译包大小,对应
npm run build
操作
uni-app 不同版本纵向对比
我们复用之前创建的uni-app默认模板
、uni-starter
、hello-uniapp
三个项目模板,分别测试vue 2.6
和vue 3.x
的编译包体积。
uni-app
项目编译包体积的采集方式:编译到对应平台后,记录编译后文件夹的大小。
H5平台
H5平台编译后代码体积记录如下:
从统计结果来看,uni-app
的vue3.x
版本,在H5平台上的编译包体积至少瘦身30%以上。
H5平台的瘦身优化,主要得益于uni-app
框架的底层全面重构,实现了更彻底的摇树优化。
小程序平台
小程序平台编译后代码体积记录如下:
从统计结果来看,uni-app
的vue3.x
版本,在小程序平台上也有大幅瘦身。
App平台
App平台编译后代码体积记录如下:
从统计结果来看,uni-app
的vue3.x
版本,在App平台上根据项目不同,会有不同幅度的瘦身。
从理论上来讲,项目中的页面模板越复杂,App平台的瘦身效果越明显。
业内优秀框架横向对比
关于编译后的代码体积,我们也和业内优秀的跨端框架Taro
进行了对比,复用前面章节创建的三个Taro
项目,分别编译到H5平台和小程序平台,计算其编译后的源码文件夹大小。
从图中可以看出,uni-app
的vue3版本,在H5平台上编译包体积是最小的,只有友商的十分之一左右。
我们继续测试,不同版本框架发行到微信小程序平台,记录其编译包大小:
从图中可以看出,uni-app
的vue3版本,在小程序平台上编译包体积也是最小的。
Tips:细心的开发者会发现,所有框架版本编译到小程序上的代码包体积都远小于其在H5平台上的包体积,这是因为小程序由平台厂商提供内置组件及接口实现,而H5平台则需跨端框架自己实现内置组件及接口,故H5平台的代码包普遍要大一些。
总结
综上,我们以数字说话,阐述了vue3版本uni-app
开发的诸多好处,再回顾一遍:
- 更多的语法
- 更快的编译
- 更好的运行
- 更少的代码
你还不赶紧升级新版uni-app
来试试吗?
对文本测试过程及结果有疑问的同学,欢迎到github上提交issue,欢迎指正。
收起阅读 »
uni-app 清爽型商城app开源代码,支持小程序,h5,Android,ios
https://github.com/gooking/uni-app--mini-mall
[码云镜像:] https://gitee.com/javazj/uni-app--mini-mall
直接贴 GitHub 和 码云的下载地址吧,上面有演示二维码可以扫码体验,做的还是挺不错的
https://github.com/gooking/uni-app--mini-mall
[码云镜像:] https://gitee.com/javazj/uni-app--mini-mall
直接贴 GitHub 和 码云的下载地址吧,上面有演示二维码可以扫码体验,做的还是挺不错的
收起阅读 »
APP开发为什么选择uni-app,目前主流的APP开发方式总结和对比
Native App
使用原生语言开发的应用;
性能和体验都是最好,但开发和发布成本最高;
常用的开发技术:Swift,OC, Java
Web App
移动端的网站,常被称为H5应用,即运行在移动端浏览器的网站应用,一般泛指SPA模式开发的网站,与MPA对应,代表:微信公众号里的H5应用(微信公众号的H5又可以调用Native API,也可以认为是Hybrid App);
开发和发布成本最低,但性能最差;
常用的开发技术:VueJS、ReactJS等;
Hybrid App
混合模式移动应用,介于WebApp、Native App两者之间的App开发技术;
原理:JS写逻辑且可以通过JSBridge调用Native的API,用HTML+CSS编写界面,并由webview渲染界面;
渲染方式:webview渲染;
JSBridge统一封装了IOS和Android的API,因此Hybrid App具有跨平台效果;
JS逻辑的执行由webview内置的JS引擎决定,调用Native API是通过JSBridge来实现;
开发和发布成本介于Native App 和 WebApp之间。
热更新:支持
常用开发技术:PhoneGap、ApiCloud、MUI、Wex5、AppCan等;
React Native App
RN是Facebook开发并开源的一款UI框架,以解决Hybrid存在的缺陷与不足;
原理:JS写逻辑且运行在JS引擎中,底层自动把JS代码解析成对应平台(ios、安卓)的原生API,调用Native的API绘制原生UI,即原生渲染界面,这是与Hybird App最大的不同,因此性能好于Hybrid App。
渲染方式:原生渲染;
JS引擎为:ios为JSCore,andorid为v8,最新版rn开始在andorid上搞自己的js引擎Hermes
界面:由JSX语言写界面
布局:Flexbox;
基于的开发技术:ReactJS
热更新:支持;
思想:learn once, write anywhere; 注:不敢说write once,因为RN要针对ios和安卓各写一套代码;
Weex App
与React Native App类似,由阿里开发并开源一款UI框架;
原理:跟RN类似;
渲染方式:原生渲染
跟RN最大不同:Weex写一套代码即可运行在IOS和安卓中,RN要写两套代码,IOS一套,安卓一套;
JS引擎为:ios为JSCore,andorid为v8
界面:由Vue编写界面;
布局:Flexbox;
基于的开发技术:VueJS;
热更新:支持;
思想:write once, run anywhere;
注:
- 微信小程序类似于RN/Weex开发方式,也分为逻辑层和视图层;
- 微信小程序的页面属于混合渲染,什么是混合渲染?看后文总结;
Flutter APP
由Google开发并开源的一套UI框架,使用dart语言;逻辑和界面使用Flutter Engine;
Flutter使用Engine来绘制Widget(Flutter的显示单元),即Widget渲染界面,Dart代码都是通过AOT(Ahead Of Time)编译为平台的原生代码,所以Flutter可以直接与平台通信,不需要JS引擎的桥接。
Widget是不可变的,仅支持一帧,并且在每一帧上不会直接更新,要更新而必须使用Widget的状态。无状态和有状态widget的核心特性是相同的,每一帧他们都会重新构建,有一个State对象,它可以跨帧存储状态数据并恢复它。
渲染方式:Widget渲染界面
性能:Flutter APP是除了Native APP以外性能最好的;
热更新:不支持;
Uni-App
DCloud公司开发的一款基于vue.js的跨端的框架;
渲染方式:混合渲染、weex原生渲染、webview渲染。小程序和app-vue页面属于混合渲染,app-nvue页面全部是weex原生渲染。H5全部为webview渲染;
uni-app里的App端原生插件,这类插件使用IOS或者Android原生语言编写,封装成插件,供其他开发者使用js来调用;
原生插件分为原生组件component和原生模块module;
原生组件component只能在App-nvue页面中使用;
uni-app插件市场的大部分原生插件大部分属于原生模块module;
uni-app开发app性能足够好,用官方原话说是:点击跳转原文
> 当然,uni-app的app引擎并没有吊炸天。App平台,所有跨平台工具都还比不过原生,这是客观事实。只是,如果uni-app不能满足你的需求,你没有必要去用其他跨平台工具,直接上原生吧。
笔者认为使用uni-app开发最大的好处就是省成本和不错的生态:
- 成本,包括学习成本、开发成本,时间成本,招人成本等;
- 生态,包括开发者数量,社区活跃度,文档是否齐全等;
总结
目前主流的3大渲染引擎有:webview、React Native/weex、Flutter,复杂程度依次降低、渲染性能依次上升
混合渲染:主体为webview渲染,部分元素为原生渲染,比如导航栏、tabbar、video、map使用了原生控件
例如:微信小程序,uniapp发布的app-vue页面都属于混合渲染;
混合渲染虽然提升了性能,但也带来了其他问题,点击查看
Flutter的逻辑层和视图层统一,运行在同一套dart虚拟机下。
rn和weex使用原生渲染,性能高于webview,但是同为原生渲染,rn和weex怎么会慢于flutter呢?其实并不是原生渲染慢,而是js和原生通信慢
rn和weex分为js引擎和原生渲染层两个运行环境,当js引擎联网获取数据后,通知原生视图层更新界面时,有一个跨环境的通信折损。同样,但用户在屏幕上操作原生视图层时,要给js引擎发送通知,也会产生通信折损。
这个通信折损,普遍存在于所有逻辑和视图分离的框架中,各家小程序因为也使用这个架构,所以也存在这个问题。
因为flutter只有一个dart引擎,所有没有来回通信参数的性能问题,所以性能比rn和weex跟高;
这个通信的折损特别表现在跟手势的js响应操作绘制帧动画,或者说js连续操作界面元素方面。场景如:界面可拖动的浮动球、可拖动的滑块等。
为了解决通信的折损,RN搞了lottie的动画库,weex搞了BindingX,微信小程序搞了wxs,百度小程序搞了Filter,阿里小程序搞了SJS,uniapp若使用weex渲染时使用BindingX,使用app-vue时使用renderjs或wxs,renderjs和wxs是一种运行在视图层的js,不和逻辑层通信。
Native App
使用原生语言开发的应用;
性能和体验都是最好,但开发和发布成本最高;
常用的开发技术:Swift,OC, Java
Web App
移动端的网站,常被称为H5应用,即运行在移动端浏览器的网站应用,一般泛指SPA模式开发的网站,与MPA对应,代表:微信公众号里的H5应用(微信公众号的H5又可以调用Native API,也可以认为是Hybrid App);
开发和发布成本最低,但性能最差;
常用的开发技术:VueJS、ReactJS等;
Hybrid App
混合模式移动应用,介于WebApp、Native App两者之间的App开发技术;
原理:JS写逻辑且可以通过JSBridge调用Native的API,用HTML+CSS编写界面,并由webview渲染界面;
渲染方式:webview渲染;
JSBridge统一封装了IOS和Android的API,因此Hybrid App具有跨平台效果;
JS逻辑的执行由webview内置的JS引擎决定,调用Native API是通过JSBridge来实现;
开发和发布成本介于Native App 和 WebApp之间。
热更新:支持
常用开发技术:PhoneGap、ApiCloud、MUI、Wex5、AppCan等;
React Native App
RN是Facebook开发并开源的一款UI框架,以解决Hybrid存在的缺陷与不足;
原理:JS写逻辑且运行在JS引擎中,底层自动把JS代码解析成对应平台(ios、安卓)的原生API,调用Native的API绘制原生UI,即原生渲染界面,这是与Hybird App最大的不同,因此性能好于Hybrid App。
渲染方式:原生渲染;
JS引擎为:ios为JSCore,andorid为v8,最新版rn开始在andorid上搞自己的js引擎Hermes
界面:由JSX语言写界面
布局:Flexbox;
基于的开发技术:ReactJS
热更新:支持;
思想:learn once, write anywhere; 注:不敢说write once,因为RN要针对ios和安卓各写一套代码;
Weex App
与React Native App类似,由阿里开发并开源一款UI框架;
原理:跟RN类似;
渲染方式:原生渲染
跟RN最大不同:Weex写一套代码即可运行在IOS和安卓中,RN要写两套代码,IOS一套,安卓一套;
JS引擎为:ios为JSCore,andorid为v8
界面:由Vue编写界面;
布局:Flexbox;
基于的开发技术:VueJS;
热更新:支持;
思想:write once, run anywhere;
注:
- 微信小程序类似于RN/Weex开发方式,也分为逻辑层和视图层;
- 微信小程序的页面属于混合渲染,什么是混合渲染?看后文总结;
Flutter APP
由Google开发并开源的一套UI框架,使用dart语言;逻辑和界面使用Flutter Engine;
Flutter使用Engine来绘制Widget(Flutter的显示单元),即Widget渲染界面,Dart代码都是通过AOT(Ahead Of Time)编译为平台的原生代码,所以Flutter可以直接与平台通信,不需要JS引擎的桥接。
Widget是不可变的,仅支持一帧,并且在每一帧上不会直接更新,要更新而必须使用Widget的状态。无状态和有状态widget的核心特性是相同的,每一帧他们都会重新构建,有一个State对象,它可以跨帧存储状态数据并恢复它。
渲染方式:Widget渲染界面
性能:Flutter APP是除了Native APP以外性能最好的;
热更新:不支持;
Uni-App
DCloud公司开发的一款基于vue.js的跨端的框架;
渲染方式:混合渲染、weex原生渲染、webview渲染。小程序和app-vue页面属于混合渲染,app-nvue页面全部是weex原生渲染。H5全部为webview渲染;
uni-app里的App端原生插件,这类插件使用IOS或者Android原生语言编写,封装成插件,供其他开发者使用js来调用;
原生插件分为原生组件component和原生模块module;
原生组件component只能在App-nvue页面中使用;
uni-app插件市场的大部分原生插件大部分属于原生模块module;
uni-app开发app性能足够好,用官方原话说是:点击跳转原文
> 当然,uni-app的app引擎并没有吊炸天。App平台,所有跨平台工具都还比不过原生,这是客观事实。只是,如果uni-app不能满足你的需求,你没有必要去用其他跨平台工具,直接上原生吧。
笔者认为使用uni-app开发最大的好处就是省成本和不错的生态:
- 成本,包括学习成本、开发成本,时间成本,招人成本等;
- 生态,包括开发者数量,社区活跃度,文档是否齐全等;
总结
目前主流的3大渲染引擎有:webview、React Native/weex、Flutter,复杂程度依次降低、渲染性能依次上升
混合渲染:主体为webview渲染,部分元素为原生渲染,比如导航栏、tabbar、video、map使用了原生控件
例如:微信小程序,uniapp发布的app-vue页面都属于混合渲染;
混合渲染虽然提升了性能,但也带来了其他问题,点击查看
Flutter的逻辑层和视图层统一,运行在同一套dart虚拟机下。
rn和weex使用原生渲染,性能高于webview,但是同为原生渲染,rn和weex怎么会慢于flutter呢?其实并不是原生渲染慢,而是js和原生通信慢
rn和weex分为js引擎和原生渲染层两个运行环境,当js引擎联网获取数据后,通知原生视图层更新界面时,有一个跨环境的通信折损。同样,但用户在屏幕上操作原生视图层时,要给js引擎发送通知,也会产生通信折损。
这个通信折损,普遍存在于所有逻辑和视图分离的框架中,各家小程序因为也使用这个架构,所以也存在这个问题。
因为flutter只有一个dart引擎,所有没有来回通信参数的性能问题,所以性能比rn和weex跟高;
这个通信的折损特别表现在跟手势的js响应操作绘制帧动画,或者说js连续操作界面元素方面。场景如:界面可拖动的浮动球、可拖动的滑块等。
为了解决通信的折损,RN搞了lottie的动画库,weex搞了BindingX,微信小程序搞了wxs,百度小程序搞了Filter,阿里小程序搞了SJS,uniapp若使用weex渲染时使用BindingX,使用app-vue时使用renderjs或wxs,renderjs和wxs是一种运行在视图层的js,不和逻辑层通信。

如何在VUE中播放RTSP监控视频,且延迟小于300毫秒?
近期研究在VUE中播放RTSP实时视频,客户要求延迟低于300毫秒,并且要求支持多路同时播放,支持H.265格式视频,比较了下目前市场上常见的几种方案,以供大家参考!
一、海康威视官方WEB解决方案:
海康威视官方提供了两种WEB解决方案,即无插件方案和有插件方案。

1.无插件方案,实际采用的是服务器转码推流的方式,因为需要转码两次,导致延迟比较高,多路播放或者播放高清视频容易卡顿或者花屏,无法满足客户需求。
2.有插件版方案,虽然延迟比较低,但要求浏览器需要支持NPAPI插件,所以只能运行在IE和低版本Chrome等浏览器,IE明年就彻底不能用了,低版本的浏览器漏洞也比较多,商用还是有很大风险,只能忍痛放弃!
二、低版本浏览器VLC播放方案:
2015年之前Chrome等浏览器还未取消对NPAPI插件支持的时主流方案,继续使用低版本Chrome浏览器,通过VLC原生播放器直接播放,也不需要服务器支持,延迟非常低,终端也可以使用硬件的加速能力,多路播放也毫无压力。
缺点也非常明显:无法使用最新的浏览器和操作系统,不适合商用。如果能解决高版本的Chrome、Firefox、Edge等浏览器使用,此方案无疑是最佳选择!
三、猿大师VLC播放程序方案:
猿大师的VLC播放程序是基于猿大师中间件提供的内嵌网页播放的专利技术,底层调用VLC客户端的ActiveX控件可实现在Chrome等高版本浏览器中内嵌播放海康威视、大华等摄像头的RTSP视频流,可以以做到低延迟(300毫秒),支持多路同时播放,支持H.264和H.265格式,支持2K、4K等高清视频,兼容主流浏览器的老版本和最新版本,不用担心浏览器升级导致不能用的问题。
猿大师官网:http://www.yuanmaster.com
1.猿大师与大华官方网页延迟对比:https://www.bilibili.com/video/BV1ff4y1j7qg/
2.猿大师VLC播放程序与海康威视官方网页延迟对比:https://www.bilibili.com/video/BV1mr4y127oX/
3.猿大师VLC播放程序VUE测试页面效果演示:https://www.bilibili.com/video/BV1Y34y197Z3
近期研究在VUE中播放RTSP实时视频,客户要求延迟低于300毫秒,并且要求支持多路同时播放,支持H.265格式视频,比较了下目前市场上常见的几种方案,以供大家参考!
一、海康威视官方WEB解决方案:
海康威视官方提供了两种WEB解决方案,即无插件方案和有插件方案。
1.无插件方案,实际采用的是服务器转码推流的方式,因为需要转码两次,导致延迟比较高,多路播放或者播放高清视频容易卡顿或者花屏,无法满足客户需求。
2.有插件版方案,虽然延迟比较低,但要求浏览器需要支持NPAPI插件,所以只能运行在IE和低版本Chrome等浏览器,IE明年就彻底不能用了,低版本的浏览器漏洞也比较多,商用还是有很大风险,只能忍痛放弃!
二、低版本浏览器VLC播放方案:
2015年之前Chrome等浏览器还未取消对NPAPI插件支持的时主流方案,继续使用低版本Chrome浏览器,通过VLC原生播放器直接播放,也不需要服务器支持,延迟非常低,终端也可以使用硬件的加速能力,多路播放也毫无压力。
缺点也非常明显:无法使用最新的浏览器和操作系统,不适合商用。如果能解决高版本的Chrome、Firefox、Edge等浏览器使用,此方案无疑是最佳选择!
三、猿大师VLC播放程序方案:
猿大师的VLC播放程序是基于猿大师中间件提供的内嵌网页播放的专利技术,底层调用VLC客户端的ActiveX控件可实现在Chrome等高版本浏览器中内嵌播放海康威视、大华等摄像头的RTSP视频流,可以以做到低延迟(300毫秒),支持多路同时播放,支持H.264和H.265格式,支持2K、4K等高清视频,兼容主流浏览器的老版本和最新版本,不用担心浏览器升级导致不能用的问题。
猿大师官网:http://www.yuanmaster.com
1.猿大师与大华官方网页延迟对比:https://www.bilibili.com/video/BV1ff4y1j7qg/
2.猿大师VLC播放程序与海康威视官方网页延迟对比:https://www.bilibili.com/video/BV1mr4y127oX/
3.猿大师VLC播放程序VUE测试页面效果演示:https://www.bilibili.com/video/BV1Y34y197Z3

苹果内购项目怎样获取货币符号
let codes=[];
codes.push({country:'USD',code:'$ '})
codes.push({country:'AED',code:'AED'})
codes.push({country:'EGP',code:'EGP'})
codes.push({country:'EUR',code:'€ '})
codes.push({country:'AUD',code:'$ '})
codes.push({country:'PKR',code:'Rs '})
codes.push({country:'BRL',code:'R$ '})
codes.push({country:'BGN',code:'лв '})
codes.push({country:'PLN',code:'zł '})
codes.push({country:'DKK',code:'kr '})
codes.push({country:'RUB',code:'₽ '})
codes.push({country:'PHP',code:'₱ '})
codes.push({country:'COP',code:'$ '})
codes.push({country:'KZT',code:'₸ '})
codes.push({country:'KRW',code:'₩ '})
codes.push({country:'CAD',code:'$ '})
codes.push({country:'CZK',code:'Kč '})
codes.push({country:'QAR',code:'QAR'})
codes.push({country:'HRK',code:'kn '})
codes.push({country:'RON',code:'lei'})
codes.push({country:'MYR',code:'RM '})
codes.push({country:'PEN',code:'S/''})
codes.push({country:'MXN',code:'$ '})
codes.push({country:'ZAR',code:'R '})
codes.push({country:'NGN',code:'₦ '})
codes.push({country:'NOK',code:'kr '})
codes.push({country:'JPY',code:'¥ '})
codes.push({country:'SEK',code:'kr '})
codes.push({country:'CHF',code:'CHF'})
codes.push({country:'SAR',code:'SR '})
codes.push({country:'TWD',code:'NT '})
codes.push({country:'THB',code:'฿ '})
codes.push({country:'TZS',code:'TZS'})
codes.push({country:'TRY',code:'₺ '})
codes.push({country:'HKD',code:'HK$'})
codes.push({country:'SGD',code:'S$ '})
codes.push({country:'NZD',code:'$ '})
codes.push({country:'HUF',code:'Ft '})
codes.push({country:'ILS',code:'₪ })'
codes.push({country:'INR',code:'₹ '})
codes.push({country:'IDR',code:'Rp '})
codes.push({country:'GBP',code:'£ '})
codes.push({country:'VND',code:' đ '})
codes.push({country:'CLP',code:' $ '})
codes.push({country:'CNY',code:'¥ '})
大神们有没有更好的方式。原生的有,但是uniapp不知道怎样调用,获取各国的货币符号
let codes=[];
codes.push({country:'USD',code:'$ '})
codes.push({country:'AED',code:'AED'})
codes.push({country:'EGP',code:'EGP'})
codes.push({country:'EUR',code:'€ '})
codes.push({country:'AUD',code:'$ '})
codes.push({country:'PKR',code:'Rs '})
codes.push({country:'BRL',code:'R$ '})
codes.push({country:'BGN',code:'лв '})
codes.push({country:'PLN',code:'zł '})
codes.push({country:'DKK',code:'kr '})
codes.push({country:'RUB',code:'₽ '})
codes.push({country:'PHP',code:'₱ '})
codes.push({country:'COP',code:'$ '})
codes.push({country:'KZT',code:'₸ '})
codes.push({country:'KRW',code:'₩ '})
codes.push({country:'CAD',code:'$ '})
codes.push({country:'CZK',code:'Kč '})
codes.push({country:'QAR',code:'QAR'})
codes.push({country:'HRK',code:'kn '})
codes.push({country:'RON',code:'lei'})
codes.push({country:'MYR',code:'RM '})
codes.push({country:'PEN',code:'S/''})
codes.push({country:'MXN',code:'$ '})
codes.push({country:'ZAR',code:'R '})
codes.push({country:'NGN',code:'₦ '})
codes.push({country:'NOK',code:'kr '})
codes.push({country:'JPY',code:'¥ '})
codes.push({country:'SEK',code:'kr '})
codes.push({country:'CHF',code:'CHF'})
codes.push({country:'SAR',code:'SR '})
codes.push({country:'TWD',code:'NT '})
codes.push({country:'THB',code:'฿ '})
codes.push({country:'TZS',code:'TZS'})
codes.push({country:'TRY',code:'₺ '})
codes.push({country:'HKD',code:'HK$'})
codes.push({country:'SGD',code:'S$ '})
codes.push({country:'NZD',code:'$ '})
codes.push({country:'HUF',code:'Ft '})
codes.push({country:'ILS',code:'₪ })'
codes.push({country:'INR',code:'₹ '})
codes.push({country:'IDR',code:'Rp '})
codes.push({country:'GBP',code:'£ '})
codes.push({country:'VND',code:' đ '})
codes.push({country:'CLP',code:' $ '})
codes.push({country:'CNY',code:'¥ '})
大神们有没有更好的方式。原生的有,但是uniapp不知道怎样调用,获取各国的货币符号
收起阅读 »
uniapp使用微信小程序分包异步化能力临时方案
背景
参考问题:uniapp开发微信小程序如何使用分包异步化特性,目前(2022/02/07)uniapp中的pages.json配置不支持分包异步化的特性(按照微信官方文档配置,构建后并不会在app.json文件生成对应的配置,猜测是因为分包异步化中的pages为空,构建代码过滤了,有空查看构建源码确认一下)。
这里应该由uniapp官方支持一下这个功能,目前项目需要,先用下面的临时方案
解决方案
思路:在uniapp构建完成后,添加自己的构建脚本,做以下的事情
- 读取pages.json
- 判断pages.json中是否有配置分包异步化
- 把分包异步化相关配置写入app.json
- 寻找用到分包组件的地方(小程序的页面 or 组件json配置文件),注入组件占位(不注入的话小程序会报错导致分包内组件无法使用)
构建脚本源码
/* eslint-disable @typescript-eslint/no-require-imports */
const fs = require('fs');
const path = require('path');
console.log('开始处理异步化分包...');
// 读取pages.json
const pagesConfig = (() => {
const configPath = path.resolve(__dirname, '../../../pages.json'); // @NOTE 这里要根据脚本执行的路径改一下
const pages = fs.readFileSync(configPath, 'utf8');
// @NOTE 移除注释
let pagesJson = pages.replace(/\/\*.*\*\//g, '');
pagesJson = pagesJson.replace(/\/\/.*/g, '');
return JSON.parse(pagesJson);
})();
// 读取page.json中的异步分包(没有配置pages)
const asyncPackages = (pagesConfig.subPackages || []).filter(package => !package.pages || package.pages.length === 0);
// console.log(pagesConfig, asyncPackages);
// 写入app.json
const distPath = path.resolve(__dirname, '../../../../dist/build/mp-weixin'); // @NOTE 这里要根据脚本执行的路径改一下
const appJsonPath = path.resolve(distPath, 'app.json');
const appJson = JSON.parse(fs.readFileSync(appJsonPath, 'utf8'));
if (!appJson.subPackages) {
appJson.subPackages = [];
}
asyncPackages.forEach((package) => {
const hasInject = appJson.subPackages.find(pack => pack.root === package.root);
if (hasInject) {
return;
}
appJson.subPackages.push({
root: package.root,
pages: [],
});
});
fs.writeFileSync(appJsonPath, JSON.stringify(appJson));
// 寻找用到分包组件的地方,注入组件占位(不注入的话小程序会报错导致分包内组件无法使用)
const ignorePaths = [];
ignorePaths.push(appJsonPath); // 过滤app.json
asyncPackages.forEach((package) => {
ignorePaths.push(path.join(distPath, package.root)); // 过滤分包的内容
});
const injectPlaceholder = (filepath) => {
// 判断是否用到了分包的组件
const jsonConfig = require(filepath);
if (!jsonConfig.usingComponents) {
return;
}
const subPackageComponents = [];
// @TODO 可以考虑使用map来加快查找速度
Object.keys(jsonConfig.usingComponents).forEach((componentName) => {
const componentPath = jsonConfig.usingComponents[componentName];
const targetSubPackage = asyncPackages.find(package => componentPath.startsWith(`/${package.root}`));
if (targetSubPackage) {
// 防止重复添加
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
if (jsonConfig.componentPlaceholder && jsonConfig.componentPlaceholder[componentName]) {
return;
}
subPackageComponents.push(componentName);
}
});
if (subPackageComponents.length === 0) {
return;
}
console.log('开始处理: ', filepath);
if (!jsonConfig.componentPlaceholder) {
jsonConfig.componentPlaceholder = {};
}
subPackageComponents.forEach((name) => {
jsonConfig.componentPlaceholder[name] = 'view'; // 占位符全用view组件
});
fs.writeFileSync(filepath, JSON.stringify(jsonConfig));
console.log('处理完成: ', filepath);
};
findJSON(distPath, ignorePaths, injectPlaceholder);
console.log('异步化分包处理完成');
function findJSON(folder, ignorePaths, cb) {
fs.readdirSync(folder).forEach((filename) => {
const filepath = path.join(folder, filename);
const isIgnore = ignorePaths.some(ignorePath => filepath.startsWith(ignorePath));
if (isIgnore) {
return;
}
const stat = fs.statSync(filepath);
if (filename.endsWith('.json')) {
cb(filepath);
return;
}
if (stat.isDirectory()) {
findJSON(filepath, ignorePaths, cb);
}
});
}
最后
目前用这个方式解决了分包异步化中使用分包内的组件问题,至于分包内的js使用,大家可以验证一下,我暂时没有这个场景,所以没有验证。
上面的代码编写没有review,但测试过单个分包配置的场景,项目使用的时候请谨慎。
最后还是希望官方大佬支持一下分包异步化这个特性。
背景
参考问题:uniapp开发微信小程序如何使用分包异步化特性,目前(2022/02/07)uniapp中的pages.json配置不支持分包异步化的特性(按照微信官方文档配置,构建后并不会在app.json文件生成对应的配置,猜测是因为分包异步化中的pages为空,构建代码过滤了,有空查看构建源码确认一下)。
这里应该由uniapp官方支持一下这个功能,目前项目需要,先用下面的临时方案
解决方案
思路:在uniapp构建完成后,添加自己的构建脚本,做以下的事情
- 读取pages.json
- 判断pages.json中是否有配置分包异步化
- 把分包异步化相关配置写入app.json
- 寻找用到分包组件的地方(小程序的页面 or 组件json配置文件),注入组件占位(不注入的话小程序会报错导致分包内组件无法使用)
构建脚本源码
/* eslint-disable @typescript-eslint/no-require-imports */
const fs = require('fs');
const path = require('path');
console.log('开始处理异步化分包...');
// 读取pages.json
const pagesConfig = (() => {
const configPath = path.resolve(__dirname, '../../../pages.json'); // @NOTE 这里要根据脚本执行的路径改一下
const pages = fs.readFileSync(configPath, 'utf8');
// @NOTE 移除注释
let pagesJson = pages.replace(/\/\*.*\*\//g, '');
pagesJson = pagesJson.replace(/\/\/.*/g, '');
return JSON.parse(pagesJson);
})();
// 读取page.json中的异步分包(没有配置pages)
const asyncPackages = (pagesConfig.subPackages || []).filter(package => !package.pages || package.pages.length === 0);
// console.log(pagesConfig, asyncPackages);
// 写入app.json
const distPath = path.resolve(__dirname, '../../../../dist/build/mp-weixin'); // @NOTE 这里要根据脚本执行的路径改一下
const appJsonPath = path.resolve(distPath, 'app.json');
const appJson = JSON.parse(fs.readFileSync(appJsonPath, 'utf8'));
if (!appJson.subPackages) {
appJson.subPackages = [];
}
asyncPackages.forEach((package) => {
const hasInject = appJson.subPackages.find(pack => pack.root === package.root);
if (hasInject) {
return;
}
appJson.subPackages.push({
root: package.root,
pages: [],
});
});
fs.writeFileSync(appJsonPath, JSON.stringify(appJson));
// 寻找用到分包组件的地方,注入组件占位(不注入的话小程序会报错导致分包内组件无法使用)
const ignorePaths = [];
ignorePaths.push(appJsonPath); // 过滤app.json
asyncPackages.forEach((package) => {
ignorePaths.push(path.join(distPath, package.root)); // 过滤分包的内容
});
const injectPlaceholder = (filepath) => {
// 判断是否用到了分包的组件
const jsonConfig = require(filepath);
if (!jsonConfig.usingComponents) {
return;
}
const subPackageComponents = [];
// @TODO 可以考虑使用map来加快查找速度
Object.keys(jsonConfig.usingComponents).forEach((componentName) => {
const componentPath = jsonConfig.usingComponents[componentName];
const targetSubPackage = asyncPackages.find(package => componentPath.startsWith(`/${package.root}`));
if (targetSubPackage) {
// 防止重复添加
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
if (jsonConfig.componentPlaceholder && jsonConfig.componentPlaceholder[componentName]) {
return;
}
subPackageComponents.push(componentName);
}
});
if (subPackageComponents.length === 0) {
return;
}
console.log('开始处理: ', filepath);
if (!jsonConfig.componentPlaceholder) {
jsonConfig.componentPlaceholder = {};
}
subPackageComponents.forEach((name) => {
jsonConfig.componentPlaceholder[name] = 'view'; // 占位符全用view组件
});
fs.writeFileSync(filepath, JSON.stringify(jsonConfig));
console.log('处理完成: ', filepath);
};
findJSON(distPath, ignorePaths, injectPlaceholder);
console.log('异步化分包处理完成');
function findJSON(folder, ignorePaths, cb) {
fs.readdirSync(folder).forEach((filename) => {
const filepath = path.join(folder, filename);
const isIgnore = ignorePaths.some(ignorePath => filepath.startsWith(ignorePath));
if (isIgnore) {
return;
}
const stat = fs.statSync(filepath);
if (filename.endsWith('.json')) {
cb(filepath);
return;
}
if (stat.isDirectory()) {
findJSON(filepath, ignorePaths, cb);
}
});
}
最后
目前用这个方式解决了分包异步化中使用分包内的组件问题,至于分包内的js使用,大家可以验证一下,我暂时没有这个场景,所以没有验证。
上面的代码编写没有review,但测试过单个分包配置的场景,项目使用的时候请谨慎。
最后还是希望官方大佬支持一下分包异步化这个特性。
收起阅读 »
让小程序开发进入 `tailwind jit` 时代!
让小程序开发进入 tailwind jit
时代!
把
tailwindcss JIT
思想带入小程序开发吧!
笔者几个月前写了一个 tailwindcss-miniprogram-preset 预设,可是这个预设方案,可操作性非常的小,也不能兼容 tailwindcss v2/v3
的 Just in time
引擎,同时在写法上也有一定的变体。
于是笔者又设计了一个方案,并最终实现了 weapp-tailwindcss-webpack-plugin
。相比原先 preset
or postcss
方案,这个方案有很多的优势:
- 为
jit
设计,兼容v2/v3
2个版本的jit
引擎 - 开发者不需要额外记忆任何的写法变体,带来原汁原味的
tailwindcss
开发体验 - 兼容基于
webpack v4/v5
的小程序多端开发框架,简单易用
那么接下来就来展示一个基于 uni-app
的 demo 来快速体验这个方案吧。
最佳实践
前置准备: vscode
,vscode-tailwindcss
,nodejs lts
,微信开发者工具
1.通过vue-cli命令行创建工程
此部分内容见 uni-app快速上手
2.安装 npm 包
由于目前 uni-app
内置的 webpack
版本为 4
, postcss
版本为 7
我们需要安装 tailwindcss
的 postcss7-compat
版本:
yarn add -D weapp-tailwindcss-webpack-plugin postcss-rem-to-responsive-pixel tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
postcss-rem-to-responsive-pixel 是一个由笔者撰写的 postcss 插件,支持
rem
->rpx
,同时支持postcss7
和postcss8
,配置见此
3. 初始化 tailwind.config.js
和 postcss.config.js
只需要在初始化的文件内加入一些配置:
// tailwind.config.js 基础配置,无需任何preset
// https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/blob/main/demo/uni-app/tailwind.config.js
/** @type {import('@types/tailwindcss/tailwind-config').TailwindConfig} */
module.exports = {
mode: 'jit',
purge: {
content: ['./src/**/*.{vue,js,ts,jsx,tsx,wxml}']
},
corePlugins: {
preflight: false
}
}
// postcss.config.js 参考示例
// https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/blob/main/demo/uni-app/postcss.config.js
const path = require('path')
module.exports = {
parser: require('postcss-comment'),
plugins: [
require('postcss-import')({
resolve(id, basedir, importOptions) {
if (id.startsWith('~@/')) {
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(3))
} else if (id.startsWith('@/')) {
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(2))
} else if (id.startsWith('/') && !id.startsWith('//')) {
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(1))
}
return id
}
}),
require('autoprefixer')({
remove: process.env.UNI_PLATFORM !== 'h5'
}),
// #region 添加的部分开始
// tailwindcss for postcss7
require('tailwindcss')({ config: './tailwind.config.js' }),
// rem 转 rpx
require('postcss-rem-to-responsive-pixel/postcss7')({
rootValue: 32,
propList: ['*'],
transformUnit: 'rpx'
}),
// #endregion 添加的部分结束
require('@dcloudio/vue-cli-plugin-uni/packages/postcss')
]
}
4. 设置环境变量
添加 .env
设置 TAILWIND_MODE
# https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/blob/main/demo/uni-app/.env
# jit 模式 HMR
TAILWIND_MODE=watch
这是为了兼容 tailwindcss v2
的 HMR 方案,如果你是用的是 tailwindcss v3
就不需要了。
5. 在 src/App.vue
中引用:
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
//...
})
</script>
<style lang="scss">
/*每个页面公共css */
// scss 需要安装 yarn add -D sass sass-loader@^10
// 小程序需要 'base' 来注入变量,但不需要 html preflight
// @tailwind base;
// @tailwind utilities;
@import 'tailwindcss/base';
@import 'tailwindcss/utilities';
</style>
6. 在根目录下添加 vue.config.js
// vue.config.js
const { UniAppWeappTailwindcssWebpackPluginV4 } = require('weapp-tailwindcss-webpack-plugin')
/**
* @type {import('@vue/cli-service').ProjectOptions}
*/
const config = {
//....
configureWebpack: {
plugins: [new UniAppWeappTailwindcssWebpackPluginV4()]
}
//....
}
module.exports = config
现在,您就可以在 uni-app
中使用 jit
的大部分特性了!
jit example
vue / wxml
<view :class="[flag?'bg-red-900':'bg-[#fafa00]']">bg-[#fafa00]</view>
<view :class="{'bg-[#098765]':flag===true}">bg-[#098765]</view>
<view class="p-[20px] -mt-2 mb-[-20px] ">p-[20px] -mt-2 mb-[-20px] margin的jit 不能这么写 -m-[20px]</view>
<view class="space-y-[1.6rem]">
<view class="w-[300rpx] text-black text-opacity-[0.19]">w-[300rpx] text-black text-opacity-[0.19]</view>
<view class="min-w-[300rpx] max-h-[100px] text-[20px] leading-[0.9]">min-w-[300rpx] max-h-[100px] text-[20px] leading-[0.9]</view>
<view class="max-w-[300rpx] min-h-[100px] text-[#dddddd]">max-w-[300rpx] min-h-[100px] text-[#dddddd]</view>
<view class="flex items-center justify-center h-[100px] w-[100px] rounded-[40px] bg-[#123456] bg-opacity-[0.54] text-[#ffffff]">Hello</view>
<view class="border-[10px] border-[#098765] border-solid border-opacity-[0.44]">border-[10px] border-[#098765] border-solid border-opacity-[0.44]</view>
<view class="grid grid-cols-3 divide-x-[10px] divide-[#010101] divide-solid">
<view>1</view>
<view>2</view>
<view>3</view>
</view>
</view>
or @apply
<template><view class="hello">world</view></template>
<style lang="scss">
.hello {
@apply flex items-center justify-center h-[100px] w-[100px] rounded-[40px] bg-[#123456] bg-opacity-[0.54] text-[#ffffff] #{!important};
}
</style>
了解更多
上述只是一个简单的 hello world
,想要了解更多,可以到 Github,欢迎 star
/fork
。
Bugs & Issues
如果在使用过程中遇到 Bugs 或者提出问题,欢迎提交到此处,笔者会尽快复现并修改
让小程序开发进入 tailwind jit
时代!
把
tailwindcss JIT
思想带入小程序开发吧!
笔者几个月前写了一个 tailwindcss-miniprogram-preset 预设,可是这个预设方案,可操作性非常的小,也不能兼容 tailwindcss v2/v3
的 Just in time
引擎,同时在写法上也有一定的变体。
于是笔者又设计了一个方案,并最终实现了 weapp-tailwindcss-webpack-plugin
。相比原先 preset
or postcss
方案,这个方案有很多的优势:
- 为
jit
设计,兼容v2/v3
2个版本的jit
引擎 - 开发者不需要额外记忆任何的写法变体,带来原汁原味的
tailwindcss
开发体验 - 兼容基于
webpack v4/v5
的小程序多端开发框架,简单易用
那么接下来就来展示一个基于 uni-app
的 demo 来快速体验这个方案吧。
最佳实践
前置准备: vscode
,vscode-tailwindcss
,nodejs lts
,微信开发者工具
1.通过vue-cli命令行创建工程
此部分内容见 uni-app快速上手
2.安装 npm 包
由于目前 uni-app
内置的 webpack
版本为 4
, postcss
版本为 7
我们需要安装 tailwindcss
的 postcss7-compat
版本:
yarn add -D weapp-tailwindcss-webpack-plugin postcss-rem-to-responsive-pixel tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
postcss-rem-to-responsive-pixel 是一个由笔者撰写的 postcss 插件,支持
rem
->rpx
,同时支持postcss7
和postcss8
,配置见此
3. 初始化 tailwind.config.js
和 postcss.config.js
只需要在初始化的文件内加入一些配置:
// tailwind.config.js 基础配置,无需任何preset
// https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/blob/main/demo/uni-app/tailwind.config.js
/** @type {import('@types/tailwindcss/tailwind-config').TailwindConfig} */
module.exports = {
mode: 'jit',
purge: {
content: ['./src/**/*.{vue,js,ts,jsx,tsx,wxml}']
},
corePlugins: {
preflight: false
}
}
// postcss.config.js 参考示例
// https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/blob/main/demo/uni-app/postcss.config.js
const path = require('path')
module.exports = {
parser: require('postcss-comment'),
plugins: [
require('postcss-import')({
resolve(id, basedir, importOptions) {
if (id.startsWith('~@/')) {
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(3))
} else if (id.startsWith('@/')) {
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(2))
} else if (id.startsWith('/') && !id.startsWith('//')) {
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(1))
}
return id
}
}),
require('autoprefixer')({
remove: process.env.UNI_PLATFORM !== 'h5'
}),
// #region 添加的部分开始
// tailwindcss for postcss7
require('tailwindcss')({ config: './tailwind.config.js' }),
// rem 转 rpx
require('postcss-rem-to-responsive-pixel/postcss7')({
rootValue: 32,
propList: ['*'],
transformUnit: 'rpx'
}),
// #endregion 添加的部分结束
require('@dcloudio/vue-cli-plugin-uni/packages/postcss')
]
}
4. 设置环境变量
添加 .env
设置 TAILWIND_MODE
# https://github.com/sonofmagic/weapp-tailwindcss-webpack-plugin/blob/main/demo/uni-app/.env
# jit 模式 HMR
TAILWIND_MODE=watch
这是为了兼容 tailwindcss v2
的 HMR 方案,如果你是用的是 tailwindcss v3
就不需要了。
5. 在 src/App.vue
中引用:
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
//...
})
</script>
<style lang="scss">
/*每个页面公共css */
// scss 需要安装 yarn add -D sass sass-loader@^10
// 小程序需要 'base' 来注入变量,但不需要 html preflight
// @tailwind base;
// @tailwind utilities;
@import 'tailwindcss/base';
@import 'tailwindcss/utilities';
</style>
6. 在根目录下添加 vue.config.js
// vue.config.js
const { UniAppWeappTailwindcssWebpackPluginV4 } = require('weapp-tailwindcss-webpack-plugin')
/**
* @type {import('@vue/cli-service').ProjectOptions}
*/
const config = {
//....
configureWebpack: {
plugins: [new UniAppWeappTailwindcssWebpackPluginV4()]
}
//....
}
module.exports = config
现在,您就可以在 uni-app
中使用 jit
的大部分特性了!
jit example
vue / wxml
<view :class="[flag?'bg-red-900':'bg-[#fafa00]']">bg-[#fafa00]</view>
<view :class="{'bg-[#098765]':flag===true}">bg-[#098765]</view>
<view class="p-[20px] -mt-2 mb-[-20px] ">p-[20px] -mt-2 mb-[-20px] margin的jit 不能这么写 -m-[20px]</view>
<view class="space-y-[1.6rem]">
<view class="w-[300rpx] text-black text-opacity-[0.19]">w-[300rpx] text-black text-opacity-[0.19]</view>
<view class="min-w-[300rpx] max-h-[100px] text-[20px] leading-[0.9]">min-w-[300rpx] max-h-[100px] text-[20px] leading-[0.9]</view>
<view class="max-w-[300rpx] min-h-[100px] text-[#dddddd]">max-w-[300rpx] min-h-[100px] text-[#dddddd]</view>
<view class="flex items-center justify-center h-[100px] w-[100px] rounded-[40px] bg-[#123456] bg-opacity-[0.54] text-[#ffffff]">Hello</view>
<view class="border-[10px] border-[#098765] border-solid border-opacity-[0.44]">border-[10px] border-[#098765] border-solid border-opacity-[0.44]</view>
<view class="grid grid-cols-3 divide-x-[10px] divide-[#010101] divide-solid">
<view>1</view>
<view>2</view>
<view>3</view>
</view>
</view>
or @apply
<template><view class="hello">world</view></template>
<style lang="scss">
.hello {
@apply flex items-center justify-center h-[100px] w-[100px] rounded-[40px] bg-[#123456] bg-opacity-[0.54] text-[#ffffff] #{!important};
}
</style>
了解更多
上述只是一个简单的 hello world
,想要了解更多,可以到 Github,欢迎 star
/fork
。
Bugs & Issues
如果在使用过程中遇到 Bugs 或者提出问题,欢迎提交到此处,笔者会尽快复现并修改
收起阅读 »
IDE may already started at port xxxx, trying to connect
-
1关闭微信开发者工具,然后检查APPID换成自己的APPID
-
2先关闭再打开自己的端口号(微信开发者工具——>设置——>安全设置——>安全(服务端口号))
-
3在HBuilder X的项目>unpackage>dist>dev>project.config.json文件里面添加下面代码,保存运行即可。
"miniprogramRoot": "./unpackage/dist/dev/mp-weixin", -
我是这样子通过的 ,可以留言联系我
-
1关闭微信开发者工具,然后检查APPID换成自己的APPID
-
2先关闭再打开自己的端口号(微信开发者工具——>设置——>安全设置——>安全(服务端口号))
-
3在HBuilder X的项目>unpackage>dist>dev>project.config.json文件里面添加下面代码,保存运行即可。
"miniprogramRoot": "./unpackage/dist/dev/mp-weixin", -
我是这样子通过的 ,可以留言联系我