profile
viewpoint
Pines Pines-Cheng JD.com, Inc. shenzhen https://segmentfault.com/u/pines_cheng Think Like an Engineer

Pines-Cheng/blog 395

技术博客

Pines-Cheng/awesome-nodejs-cn 215

Nodejs 资源大全中文版,正在翻译中。。。

Pines-Cheng/awesome-vue-cn 196

vuejs的优秀资源。

Pines-Cheng/awesome-gulp-cn 123

Gulp资料大全:入门、插件、包等,已完结。

Pines-Cheng/comet4j 4

Automatically exported from code.google.com/p/comet4j

Pines-Cheng/awesome-wechat-weapp 3

微信小程序开发资源汇总 :100:

Pines-Cheng/awesome-vue 2

A curated list of awesome things related to Vue.js

Pines-Cheng/awesome-react 1

A collection of awesome things regarding React ecosystem.

Pines-Cheng/document-style-guide 1

中文技术文档的写作规范

Pines-Cheng/airbnbJavascriptNote 0

Learning airbnb-javascript-style && Note something!

push eventNervJS/taro

Pines-Cheng

commit sha 8883d2dd90c95dd703549d1facb3476ca2f3f7c3

feat(rn): 优化启动文案提示

view details

push time in 7 days

push eventNervJS/taro

Pines-Cheng

commit sha cf3dab66ea41c8066e135c5b5149d6c0c06cfa02

fix(rn-runner): copy config/*.json to dist

view details

push time in 7 days

push eventNervJS/taro

Pines

commit sha 54a9bc6d2b8e6138c54243ebda5382afc3190c50

docs(rn): update react-native.md

view details

push time in 8 days

issue openedPines-Cheng/blog

GraphQL 相关

GraphQL 作为 FaceBook 2015 年推出的 API 定义/查询语言,在历经了两年的发展之后,社区已相对发达和完善。目前社区相关的文章已经很多,有兴趣的同学可以去 Google,或者直接看 GraphQL 官方教程 以及 Apollo GraphQL Server 官方文档

使用 Axios 请求 GraphQL API

Axios 是用于 JavaScript 的 HTTP 客户端库。 如果你正在使用 React、 Vue 或 Angular 构建一个应用程序,使用类似 Apollo Client 的 GraphQL 客户端库是很有用的,但是如果你只需要使用一个 GraphQL 调用,比如来自 Node.js 脚本,你可以使用任何你想要的库。

graphql-tag 用来把 string 转化成 GraphQL 的 AST。既然,在客户端发请求时仍然使用 string,为什么需要客户端转成 AST,这有几个原因

  • 编译成 AST 可以在编译时检确保 query 的合法性 (比如查询了不存在的字段)
  • 可以按照特定条件对多个 query 进行合并,多个请求合并为同一个请求
  • 可以按照客户端缓存对某些字段进行过滤 (skip),避免冗余查询
  • ... 诸多好处

在服务器端也有诸多好处,如

  • 解析出来客户端请求的 field,与数据库一对比,按需请求数据库字段
  • 添加新的 directives
  • ...
// gql.ts
import gql from 'graphql-tag'

export const PLUGINS = gql`
  query($filter: PluginFilter, $pageInfo: PageInfo) {
    res: plugins(filter: $filter, pageInfo: $pageInfo) {
      count
      plugins {
        ${BASE_PLUGIN_FIELDS}
        npmUrl
        gitUrl
        filePath
        starCount
        downloadCount
        commentCount
      }
    }
  }
`
import { print } from 'graphql'

const res = await axios.post(graphqlURL, {
  query: print(PLUGINS),
  variables: {
    pageInfo: {
      limit: 200
    }
  }
})

graphql-tag 的好处我们上面已经提到了。这里 print 的作用是: Converts an AST into a string, using one set of reasonable formatting rules。

不过你也可以选择直接传入: query: print(" XXX ")

当前阻力

GraphQL 已经出来这么久,仍然处于不温不火的阶段,重要存在一下几个阻力(引用自 winter):

  • 现在主流的后端架构,集群间通讯还是http或者rpc的API,固定输入输出字段,根本不能满足GraphQL的需求,要上就都得改
  • GraphQL 的每一个实体背后可能对应着不同的数据库甚至不同类型的存储集群,后端集群间的海量数据自由 join,基本还是无解的,只能搭专门的集群处理,这个不清楚FB是否有什么黑科技,严重怀疑FB自己也没做到全业务查询
  • 新加一个 GraphQL 集群,意味着中心化的流量,现在真正做到中心化统一接入层的企业都很少,而且中心化流量要求巨大的中心化集群,技术上运维上又是一个难题。

Resolve 逐层解析会有一些问题,如果 Resolve 请求数据库,就要用到 DataLoader DataLoader 是 gql 服务器性能的关键一环,也是 gql 社区的主要推动完善方向,就像 react 里面的shouldComponentUpdate 一样制约着 gql 服务器的性能。

在客户端实现一个 API Layer

来自 @kuitos

GraphQL 作为一个标准化并自带类型系统的 API Layer,其工程价值我也不再过多广告了。只是在实践过程中,既然我们无法完全避免服务端与客户端的实体与接口定义重复(使用 apollo-codegen 可以避免一部分),而且对于大部分小团队而言,运维一个 productive nodejs system 实际上都是力有未逮。**那么我们是不是可以考虑在纯客户端构建一个类 GraphQL 的 API Layer 呢?**这样既可以有效的避免编码重复,也能大大的降低对团队的要求,可操作的空间也比增加一个 nodejs 中间层大得多。

其实很简单,你只需要在客户端把 Apollo Server 中要写的 resolvers 写一遍,然后配上一些性能提升手段(如缓存等),你的 API Layer 就完成了。

经过这一层的数据处理,我们就能确保我们的应用运行在前端自己定义的数据模型之下。这样之后后端接口不论是数据结构还是字段名的变更,我们只需要在这一层做简单调整即可,而不会影响到我们上层的业务及视图。相应的,我们的业务层逻辑不再会直接对接接口 url,而是将其隐藏在 API Layer 下,这样不仅能提升业务代码的可读性,也能做到眼不见为净。。。

事实上,对于大部分团队而言,客户端 API Layer 已经够用了,增加一层 GraphQL 并不是那么必要。而且如果没有很好的支持将客户端接口转换成 GraphQL Schema 和 resolver 的工具时,我们并不能很愉快的 coding,毕竟两端重复的工作还是有点多。

参考

created time in 8 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。++## 怎么定义高性能?++提起互联网应用性能这个词,很多人在脑海中的词法解析就是,“是否足够快?”,似乎加载速度成为衡量系统性能的唯一指标。但这其实是不够准确的,试想一下,如果一个小程序加载速度非常快,用户花费很短时间就能看到页面的主体内容,但此时搜索框却无法输入内容,功能无法被流畅使用,用户可能就不会关心页面渲染有多快了。所以,我们不应该单纯考虑速度指标而忽略用户的感知体验,而应该全方位衡量用户在使用过程中能感知到的与应用加载相关的每个节点。++谷歌为 Web 应用定义了以用户为中心的性能指标体系,每个指标都与用户体验节点息息相关:++| 体验 | 指标 |+|-----|------|+| 页面能否正常访问?| 首次绘制 (`FP`)/首次内容绘制 (`FCP`) |+| 页面内容是否有用?| 首次有效绘制 (`FMP`) |+| 页面功能是否可用?| 可交互时间 (`TTI`) |++其中,“是否有用?” 这个问题是非常主观的,对于不同场景的系统可能会有完全不一样的回答,所以 `FMP` 是一个比较模糊的概念指标,不存在规范化的数值衡量。++小程序作为一个新的内容载体,衡量指标跟 Web 应用是非常类似的。对于大多数小程序而言,上述指标对应的含义为:++- FP:白屏时间;+- FMP:首屏渲染完成时间;+- TTI:页面加载完成时间;++综上,我们已基本确定了高性能的概念指标,接下来就是如何利用数值指标来描绘性能表现。++### 小程序官方性能指标++小程序官方针对小程序性能表现制订了权威的数值指标,主要围绕 **渲染表现**、**`setData` 数据量**、**元素节点数** 和 **网络请求延时** 这几个维度来给予定义(下面只列出部分关键指标):++- 首屏时间不超过 5 秒;+- 渲染时间不超过 500ms;+- 每秒调用 `setData` 的次数不超过 20 次;+- `setData` 的数据在 `JSON.stringify` 后不超过 256kb;+- 页面 WXML 节点少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个;+- 所有网络请求都在 1 秒内返回结果;++> 详见 [小程序性能评分规则](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html)++我们应该把这一系列的官方指标作为小程序的性能及格线,不断地打磨和提升小程序的整体体验,降低用户流失率。另外,这些指标会直接作为小程序体验评分工具的性能评分规则(体验评分工具会根据这些规则的权重和求和公式计算出体验得分)。++我们团队内部在官方性能指标的基础上,进一步浓缩优化指标系数,旨在对产品体验更高要求:++- 首屏时间不超过 2.5 秒;+- `setData` 的数据量不超过 100kb;+- 所有网络请求都在 1 秒内返回结果;+- 组件滑动、长列表滚动无卡顿感;++### 体验评分工具++小程序提供了 [体验评分工具(`Audits` 面板)](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/audits.html) 来测量上述的指标数据,其集成在开发者工具中,在小程序运行时实时检查相关问题点,并为开发者给出优化建议。++![体验评分面板](https://img13.360buyimg.com/ling/jfs/t1/92760/7/14417/406134/5e650dedE27aa858d/76b18b5f88de7288.png)++> 以上截图均来自小程序官方文档++体验评分工具是目前检测小程序性能问题最直接有效的途径,我们团队已经把体验评分作为页面/组件是否能达到精品门槛的重要考量手段之一。++### 小程序后台性能分析++我们知道,体验评分工具是在本地运行小程序代码时进行分析,但性能数据往往需要在真实环境和大数据量下才更有说服力。恰巧,**小程序管理平台** 和 **小程序助手** 为开发者提供了大量的真实数据统计。其中,性能分析面板从 **启动性能**、**运行性能** 和 **网络性能** 这三个维度分析数据,开发者可以根据客户端系统、机型、网络环境和访问来源等条件做精细化分析,非常具有考量价值。++![小程序助手性能分析](https://img20.360buyimg.com/ling/jfs/t1/95979/40/14592/773778/5e6500f1Eb92950cc/8c8bc8c4493a3c61.jpg)++> 其中,启动总耗时 = 小程序环境初始化 + 代码包加载 + 代码执行 + 渲染耗时++### 第三方测速系统++很多时候,宏观的耗时统计对于性能瓶颈点分析往往是杯水车薪,作用甚少,我们需要更细致地针对某个页面某些关键节点作测速统计,排查出暴露性能问题的代码区块,才能更有效地针对性优化。京喜小程序使用的是内部自研的测速系统,支持对地区、运营商、网络、客户端系统等多条件筛选,同时也支持数据可视化、同比分析数据等能力。京喜首页主要围绕 **页面 `onLoad`**、**`onReady`**、**数据加载完成**、**首屏渲染完成**、**各业务组件首次渲染完成** 等几个关键节点统计测速上报,旨在全链路监控性能表现。++![内部测速系统](https://img10.360buyimg.com/ling/jfs/t1/88143/36/14203/512179/5e620b0cEcedc020b/afe68aaf36311303.jpg)++> 另外,微信为开发者提供了 [测速系统](https://developers.weixin.qq.com/miniprogram/dev/framework/performanceReport/),也支持针对客户端系统、网络类型、用户地区等维度统计数据,有兴趣的可以尝试。++## 了解小程序底层架构++为了更好地为小程序制订性能优化措施,我们有必要先了解小程序的底层架构,以及与 web 浏览器的差异性。++微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。++而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了**双线程模型**:++- **视图层**:也就是 webview 线程,负责启用不同的 webview 来渲染不同的小程序页面;+- **逻辑层**:一个单独的线程执行 JS 代码,可以控制视图层的逻辑;++![双线程模型图](https://img20.360buyimg.com/ling/jfs/t1/88668/37/14434/308258/5e650263Ebf67d978/06dee7fb8293b482.png)++> 上图来自小程序官方开发指南++然而,**任何线程间的数据传输都是有延时的**,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线层之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。++作为小程序开发者,我们常常会被下面几个问题所困扰:++- 小程序启动慢;+- 白屏时间长;+- 页面渲染慢;+- 运行内存不足;++接下来,我们会结合小程序的底层架构分析出这些问题的根本原因,并针对性地给出解决方案。++## 小程序启动太慢?++小程序启动阶段,也就是如下图所示的展示加载界面的阶段。++![小程序加载界面](https://img13.360buyimg.com/ling/jfs/t1/88406/6/14244/64417/5e6219bdE1f9d7f95/07bcc55a6c147058.png)++在这个阶段中(包括启动前后的时机),微信会默默完成下面几项工作:++**1. 准备运行环境:**++  在小程序启动前,微信会先启动双线程环境,并在线程中完成小程序基础库的初始化和预执行。++  > 小程序基础库包括 WebView 基础库和 AppService 基础库,前者注入到视图层中,后者注入到逻辑层中,分别为所在层级提供其运行所需的基础框架能力。++**2. 下载小程序代码包:**++  在小程序初次启动时,需要下载编译后的代码包到本地。如果启动了小程序分包,则只有主包的内容会被下载。另外,代码包会保留在缓存中,后续启动会优先读取缓存。++**3. 加载小程序代码包:**++  小程序代码包下载好之后,会被加载到适当的线程中执行,基础库会完成所有页面的注册。++  > 在此阶段,主包内的所有页面 JS 文件及其依赖文件都会被自动执行。+  +  > 在页面注册过程中,基础库会调用页面 JS 文件的 Page 构造器方法,来记录页面的基础信息(包括初始数据、方法等)。++**4. 初始化小程序首页:**++  在小程序代码包加载完之后,基础库会根据启动路径找到首页,根据首页的基础信息初始化一个页面实例,并把信息传递给视图层,视图层会结合 WXML 结构、WXSS 样式和初始数据来渲染界面。++综合考虑,为了节省小程序的“点点点”时间(小程序的启动动画是三个圆点循环跑马灯),除了给每位用户发一台高配 5G 手机并顺带提供千兆宽带网络之外,还可以尽量 **控制代码包大小**,缩小代码包的下载时间。++### 无用文件、函数、样式剔除++经过多次业务迭代,无可避免的会存在一些弃用的组件/页面,以及不被调用的函数、样式规则,这些冗余代码会白白占据宝贵的代码包空间。而且,目前小程序的打包会将工程下所有文件都打入代码包内,并没有做依赖分析。++因此,我们需要及时地剔除不再使用的模块,以保证代码包空间利用率保持在较高水平。通过一些工具化手段可以有效地辅助完成这一工作。++- **文件依赖分析**++在小程序中,所有页面的路径都需要在小程序代码根目录 `app.json` 中被声明,类似地,自定义组件也需要在页面配置文件 `page.json` 中被声明。另外,WXML、WXSS 和 JS 的模块化都需要特定的关键字来声明依赖引用关系。++WXML 中的 `import` 和 `include`:++```jsx+<!-- A.wxml -->+<template name='A'>+  <text>{{text}}</text>+</template>++<!-- B.wxml -->+<import src="A.wxml"/>+<template is="A" data="{{text: 'B'}}"/>+```++```jsx+<!-- A.wxml -->+<text> A </text>++<!-- B.wxml -->+<include src="A.wxml"/>+<text> B </text>+```++WXSS 中的 `@import`:++```css+@import './A.wxss'+```++JS 中的 `require`/`import`:++```js+const A = require('./A')+```++所以,可以说小程序里的所有依赖模块都是有迹可循的,我们只需要利用这些关键字信息递归查找,遍历出文件依赖树,然后把没用的模块剔除掉。++- **JS、CSS Tree-Shaking**++JS Tree-Shaking 的原理就是借助 `Babel` 把代码编译成抽象语法树(AST),通过 AST 获取到函数的调用关系,从而把未被调用的函数方法剔除掉。不过这需要依赖 ES module,而小程序最开始是遵循 commonjs 规范的,这意味着是时候来一波“痛并快乐着”的改造了。++而 CSS 的 Tree-Shaking 可以利用 PurifyCSS 插件来完成。关于这两项技术,有兴趣的可以“谷歌一下”,这里就不铺开细讲了。++题外,京东的小程序团队已经把这一系列工程化能力集成在一套 CLI 工具中,有兴趣的可以看看这篇分享:[小程序工程化探索](https://mp.weixin.qq.com/s/_NSJTQ-4-8gTnwTVK-tn0A)。++### 减少代码包中的静态资源文件++小程序代码包最终会经过 GZIP 压缩放在 CDN 上,但 GZIP 压缩对于图片资源来说效果非常低。如 `JPG`、`PNG` 等格式文件,本身已经被压缩过了,再使用 GZIP 压缩有可能体积更大,得不偿失。所以,除了部分用于容错的图片必须放在代码包(譬如网络异常提示)之外,建议开发者把图片、视频等静态资源都放在 CDN 上。++> 需要注意,`Base64` 格式本质上是长字符串,和 CDN 地址比起来也会更占空间。++### 逻辑后移,精简业务逻辑++这是一个 “痛并快乐着” 的优化措施。“痛” 是因为需要给后台同学提改造需求,分分钟被打;“快乐” 则是因为享受删代码的过程,而且万一出 Bug 也不用背锅了...(开个玩笑)++通过让后台承担更多的业务逻辑,可以节省小程序前端代码量,同时线上问题还支持紧急修复,不需要经历小程序的提审、发布上线等繁琐过程。++总结得出,**一般不涉及前端计算的展示类逻辑,都可以适当做后移**。譬如京喜首页中的幕帘弹窗(如下图)逻辑,这里共有 10+ 种弹窗类型,以前的做法是前端从接口拉取 10+ 个不同字段,根据优先级和 “是否已展示”(该状态存储在本地缓存) 来决定展示哪一种,最后代码大概是这样的:++```js+// 检查每种弹窗类型是否已展示+Promise.all([+  check(popup_1),+  check(popup_2),+  // ...+  check(popup_n)+]).then(result => {+  // 优先级排序+  const queue = [{+    show: result.popup_1+    data: data.popup_1+  }, {+    show: result.popup_2+    data: data.popup_2+  }, +  // ...+  {+    show: result.popup_n+    data: data.popup_n+  }]+})+```++逻辑后移之后,前端只需负责拿幕帘字段做展示就可以了,代码变成这样:++```js+this.setData({+  popup: data.popup+})+```++![首页幕帘弹窗](https://img13.360buyimg.com/ling/jfs/t1/104567/26/14214/46840/5e621c48E4e33b7a9/440ca946478f53ac.jpg)++### 复用模板插件++京喜首页作为电商系统的门户,需要应对各类频繁的营销活动、升级改版等,同时也要满足不同用户属性的界面个性化需求(俗称 “千人千面”)。如何既能减少为应对多样化场景而产生的代码量,又可以提升研发效率,成为燃眉之急。++类似于组件复用的理念,我们需要提供更丰富的可配置能力,实现更高的代码复用度。参考小时候很喜欢玩的 “乐高” 积木玩具,**我们把首页模块的模板元素作颗粒度更细的划分,根据样式和功能抽象出一块块“积木”原料(称为插件元素)**。当首页模块在处理接口数据时,会启动插件引擎逐个装载插件,最终输出个性化的模板样式,整个流程就好比堆积木。当后续产品/运营需要新增模板时,只要在插件库中挑选插件排列组合即可,不需要额外新增/修改组件内容,也更不会产生难以维护的 `if` / `else` 逻辑,so easy~++当然,要完成这样的插件化改造免不了几个先决条件:++- **用户体验设计的统一**。如果设计风格总是天差地别的,强行插件化只会成为累赘。+- **服务端接口的统一**。同上,如果得浪费大量的精力来兼容不同模块间的接口字段差异,将会非常蛋疼。++下面为大家提供部分例程来辅助理解。其中,`use` 方法会接受各类处理钩子最终拼接出一个 `Function`,在对应模块处理数据时会被调用。++```js+// bi.helper.js++/**+ * 插件引擎+ * @param {function} options.formatName 标题处理钩子+ * @param {function} options.validList 数据校验器钩子+ */ +const use = options => data => format(data)++/**+ * 预置插件库+ */ +nameHelpers = {+  text: data => data.text,+  icon: data => data.icon+}+listHelpers = {+  single: list => list.slice(0, 1),+  double: list => list.slice(0, 2)+}++/**+ * “堆积木”+ */+export default {+  1000: use({+    formatName: nameHelpers.text,+    validList: listHelpers.single+  }),++  1001: use({+    formatName: nameHelpers.icon,+    validList: listHelpers.double+  })+}+```++```jsx+<!-- bi.wxml -->+<!-- 各模板节点实现 -->+<template name="renderName">+  <view wx:if="{{type === 'text'}}"> text </view>+  <view wx:elif="{{type === 'icon'}}"> icon </view>+</template>++<view class="bi__name">+  <template is="renderName" data="{{...data.name}"/>+</view>+```++```js+// bi.js+Component({+  ready() {+    // 根据 tpl 值选择解析函数+    const formatData = helper[data.tpl]+    this.setData({+      data: formatData(data)+    })+  }+})+```++### 分包加载++小程序启动时只会下载主包/独立分包,启用分包可以有效减少下载时间。(独立)分包需要遵循一些原则,详细的可以看官方文档:++- [使用分包](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html)+- [独立分包](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/independent.html)++### 部分页面 h5 化++小程序提供了 [web-view](https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html) 组件,支持在小程序环境内访问网页。当实在无法在小程序代码包中腾出多余空间时,可以考虑降级方案 —— 把部分页面 h5 化。 ++> 小程序和 h5 的通信可以通过 JSSDK 或 postMessage 通道来实现。+  +## 白屏时间过长?++白屏阶段,是指小程序代码包下载完(也就是启动界面结束)之后,页面完成首屏渲染的这一阶段,也就是 FMP (首次有效绘制)。++FMP 没法用标准化的指标定义,但对于大部分小程序来说,页面首屏展示的内容都需要依赖服务端的接口数据,那么影响白屏加载时间的主要由这两个元素构成:++- **网络资源加载时间**;+- **渲染时间**;++### 启用本地缓存++小程序提供了读写本地缓存的接口,数据存储在设备硬盘上。由于本地 I/O 读写(毫秒级)会比网络请求(秒级)要快很多,所以在用户访问页面时,可以优先从缓存中取上一次接口调用成功的数据来渲染视图,待网络请求成功后再覆盖最新数据重新渲染。除此之外,缓存数据还可以作为兜底数据,避免出现接口请求失败时页面空窗,一石二鸟。++但并非所有场景都适合缓存策略,譬如对数据即时性要求非常高的场景(如抢购入口)来说,展示老数据可能会引发一些问题。++小程序默认会按照 **不同小程序**、**不同微信用户** 这两个维度对缓存空间进行隔离。诸如京喜小程序首页也采用了缓存策略,会进一步按照 **数据版本号**、**用户属性** 来对缓存进行再隔离,避免信息误展示。++### 数据预拉取++小程序官方为开发者提供了一个在小程序冷启动时提前拉取第三方接口的能力:[数据预拉取](https://developers.weixin.qq.com/miniprogram/dev/framework/ability/pre-fetch.html)。++> 关于冷启动和热启动的定义可以看 [这里](https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/operating-mechanism.html#%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%90%AF%E5%8A%A8)++数据预拉取的原理其实很简单,就是在小程序启动时,微信服务器代理小程序客户端发起一个 HTTP 请求到第三方服务器来获取数据,并且把响应数据存储在本地客户端供小程序前端调取。当小程序加载完成后,只需调用微信提供的 API `wx.getBackgroundFetchData` 从本地缓存获取数据即可。这种做法可以充分利用小程序启动和初始化阶段的等待时间,使更快地完成页面渲染。++京喜小程序首页已经在生产环境实践过这个能力,从每日千万级的数据分析得出,预拉取使冷启动时获取到接口数据的时间节点从 2.5s 加速到 1s(提速了 60%)。虽然提升效果非常明显,但这个能力依然存在一些不成熟的地方:++- **预拉取的数据会被强缓存**;++  由于预拉取的请求最终是由微信的服务器发起的,也许是出于服务器资源限制的考虑,预拉取的数据会缓存在微信本地一段时间,缓存失效后才会重新发起请求。经过真机实测,在微信购物入口冷启动京喜小程序的场景下,预拉取缓存存活了 30 分钟以上,这对于数据实时性要求比较高的系统来说是非常致命的。++- **请求体和响应体都无法被拦截**;++  由于请求第三方服务器是从微信的服务器发起的,而不是从小程序客户端发起的,所以本地代理无法拦截到这一次真实请求,这会导致开发者无法通过拦截请求的方式来区分获取线上环境和开发环境的数据,给开发调试带来麻烦。++  小程序内部接口的响应体类型都是 `application/octet-stream`,即数据格式未知,使本地代理无法正确解析。++- **微信服务器发起的请求没有提供区分线上版和开发版的参数,且没有提供用户 IP 等信息**;++如果这几个问题点都不会影响到你的场景,那么可以尝试开启预拉取能力,这对于小程序首屏渲染速度是质的提升。++### 跳转时预拉取++为了尽快获取到服务端数据,比较常见的做法是在页面 `onLoad` 钩子被触发时发起网络请求,但其实这并不是最快的方式。从发起页面跳转,到下一个页面 `onLoad` 的过程中,小程序需要完成一些环境初始化及页面实例化的工作,耗时大概为 300 ~ 400 毫秒。++实际上,我们可以在发起跳转前(如 `wx.navigateTo` 调用前),提前请求下一个页面的主接口并存储在全局 `Promise` 对象中,待下个页面加载完成后从 `Promise` 对象中读取数据即可。++这也是双线程模型所带来的优势之一,不同于多页面 web 应用在页面跳转/刷新时就销毁掉 window 对象。++### 分包预下载++如果开启了分包加载能力,在用户访问到分包内某个页面时,小程序才会开始下载对应的分包。当处于分包下载阶段时,页面会维持在 “白屏” 的启动态,这用户体验是比较糟糕的。++幸好,小程序提供了 [分包预下载](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/preload.html) 能力,开发者可以配置进入某个页面时预下载可能会用到的分包,避免在页面切换时僵持在 “白屏” 态。++### 非关键渲染数据延迟请求++这是关键渲染路径优化的其中一个思路,从缩短网络请求时延的角度加快首屏渲染完成时间。++> 关键渲染路径(Critical Rendering Path)是指在完成首屏渲染的过程中必须发生的事件。++以京喜小程序如此庞大的小程序项目为例,每个模块背后都可能有着海量的后台服务作支撑,而这些后台服务间的通信和数据交互都会存在一定的时延。我们根据京喜首页的页面结构,把所有模块划分成两类:**主体模块**(导航、商品轮播、商品豆腐块等)和 **非主体模块**(幕帘弹窗、右侧挂件等)。++在初始化首页时,小程序会发起一个聚合接口请求来获取主体模块的数据,而非主体模块的数据则从另一个接口获取,通过拆分的手段来降低主接口的调用时延,同时减少响应体的数据量,缩减网络传输时间。++![京喜首页浮层模块](https://img20.360buyimg.com/ling/jfs/t1/88895/31/14330/199996/5e6224c5Ecbb05ab3/739a7c19bd10ff3c.jpg)++### 分屏渲染++这也是关键渲染路径优化思路之一,通过延迟非关键元素的渲染时机,为关键渲染路径腾出资源。++类似上一条措施,继续以京喜小程序首页为例,我们在 **主体模块** 的基础上再度划分出 **首屏模块**(商品豆腐块以上部分) 和 **非首屏模块**(商品豆腐块及以下部分)。当小程序获取到主体模块的数据后,会优先渲染首屏模块,在所有首屏模块都渲染完成后才会渲染非首屏模块和非主体模块,以此确保首屏内容以最快速度呈现。++![京喜首页分屏渲染](https://img12.360buyimg.com/img/jfs/t1/105202/13/15534/1217141/5e71ecf7Eb0e73ec3/ae581ad42fca386c.gif)++> 为了更好地呈现效果,上面 gif 做了降速处理++### 接口聚合,请求合并++在小程序中,发起网络请求是通过 [wx.request](https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html) 这个 API。我们知道,在 web 浏览器中,针对同一域名的 HTTP 并发请求数是有限制的;在小程序中也有类似的限制,但区别在于不是针对域名限制,而是针对 API 调用:++- `wx.request` (HTTP 连接)的最大并发限制是 10 个;+- `wx.connectSocket` (WebSocket 连接)的最大并发限制是 5 个;++超出并发限制数目的 HTTP 请求将会被阻塞,需要在队列中等待前面的请求完成,从而一定程度上增加了请求时延。因此,**对于职责类似的网络请求,最好采用节流的方式,先在一定时间间隔内收集数据,再合并到一个请求体中发送给服务端。**++### 图片资源优化++图片资源一直是移动端系统中抢占大流量的部分,尤其是对于电商系统。优化图片资源的加载可以有效地加快页面响应时间,提升首屏渲染速度。++- **使用 WebP 格式**++[WebP](https://developers.google.com/speed/webp) 是 Google 推出的一种支持有损/无损压缩的图片文件格式,得益于更优的图像数据压缩算法,其与 JPG、PNG 等格式相比,在肉眼无差别的图片质量前提下具有更小的图片体积(据官方说明,WebP 无损压缩体积比 PNG 小 26%,有损压缩体积比 JPEG 小 25-34%)。++> 小程序的 [image 组件](https://developers.weixin.qq.com/miniprogram/dev/component/image.html) 支持 JPG、PNG、SVG、WEBP、GIF 等格式。++- **图片裁剪&降质**++鉴于移动端设备的分辨率是有上限的,很多图片的尺寸常常远大于页面元素尺寸,这非常浪费网络资源(一般图片尺寸 2 倍于页面元素真实尺寸比较合适)。得益于京东内部强大的图片处理服务,我们可以通过资源的命名规则和请求参数来获取服务端优化后的图片:++裁剪成 100x100 的图片:`https://{host}/s100x100_jfs/{file_path}`;++降质 70%:`https://{href}!q70`;++- **图片懒加载、雪碧图优化**++这两者都是比较老生常谈的图片优化技术,这里就不打算细讲了。++小程序的 [image 组件](https://developers.weixin.qq.com/miniprogram/dev/component/image.html) 自带 `lazy-load` 懒加载支持。雪碧图技术(CSS Sprite)可以参考 [w3schools](https://www.w3schools.com/css/css_image_sprites.asp) 的教程。++- **降级加载大图资源**++在不得不使用大图资源的场景下,我们可以适当使用 “体验换速度” 的措施来提升渲染性能。++小程序会把已加载的静态资源缓存在本地,当短时间内再次发起请求时会直接从缓存中取资源(与浏览器行为一致)。因此,对于大图资源,**我们可以先呈现高度压缩的模糊图片,同时利用一个隐藏的 `<image>` 节点来加载原图,待原图加载完成后再转移到真实节点上渲染**。整个流程,从视觉上会感知到图片从模糊到高清的过程,但与对首屏渲染的提升效果相比,这点体验落差是可以接受的。++下面为大家提供部分例程:++```jsx+<!-- banner.wxml -->+<image src="{{url}}" />++<!-- 图片加载器 -->+<image+  style="width:0;height:0;display:none"+  src="{{preloadUrl}}"+  bindload="onImgLoad"+  binderror="onErrorLoad"+/>+```++```js+// banner.js+Component({+  ready() {+    this.originUrl = 'https://path/to/picture'  // 图片源地址+    this.setData({+      url: compress(this.originUrl)             // 加载压缩降质的图片+      preloadUrl: this.originUrl                // 预加载原图+    })+  },+  methods: {+    onImgLoad() {+      this.setData({+        url: this.originUrl                       // 加载原图+      })+    }+  }+})+```++> 注意,具有 `display: none` 样式的 `<image>` 标签只会加载图片资源,但不渲染。++京喜首页的商品轮播模块也采用了这种降级加载方案,在首屏渲染时只会加载第一帧降质图片。以每帧原图 20~50kb 的大小计算,这一措施可以在初始化阶段节省掉几百 kb 的网络资源请求。++![Banner 大图降级加载](https://img12.360buyimg.com/img/jfs/t1/90878/34/14835/1949580/5e6a03bfE35eaa7e4/4f1ccc2d512aba2a.gif)+ +> 为了更好地呈现效果,上面 gif 做了降速处理++### 骨架屏++一方面,我们可以从降低网络请求时延、减少关键渲染的节点数这两个角度出发,缩短完成 FMP(首次有效绘制)的时间。另一方面,我们也需要从用户感知的角度优化加载体验。++“白屏” 的加载体验对于首次访问的用户来说是难以接受的,我们可以使用尺寸稳定的骨架屏,来辅助实现真实模块占位和瞬间加载。++骨架屏目前在业界被广泛应用,京喜首页选择使用灰色豆腐块作为骨架屏的主元素,大致勾勒出各模块主体内容的样式布局。由于微信小程序不支持 SSR(服务端渲染),使动态渲染骨架屏的方案难以实现,因此京喜首页的骨架屏是通过 WXSS 样式静态渲染的。++有趣的是,京喜首页的骨架屏方案经历了 **“统一管理”** 和 **“(组件)独立管理”** 两个阶段。出于避免对组件的侵入性考虑,最初的骨架屏是由一个完整的骨架屏组件统一管理的:++```jsx+<!-- index.wxml -->+<skeleton wx:if="{{isLoading}}"></skeleton>+<block wx:else>+  页面主体+</block>+```++但这种做法的维护成本比较高,每次页面主体模块更新迭代,都需要在骨架屏组件中的对应节点同步更新(譬如某个模块的尺寸被调整)。除此之外,感官上从骨架屏到真实模块的切换是跳跃式的,这是因为骨架屏组件和页面主体节点之间的关系是整体条件互斥的,只有当页面主体数据 Ready(或渲染完毕)时才会把骨架屏组件销毁,渲染(或展示)主体内容。++为了使用户感知体验更加丝滑,我们把骨架屏元素拆分放到各个业务组件中,骨架屏元素的显示/隐藏逻辑由业务组件内部独立管理,这就可以轻松实现 “谁跑得快,谁先出来” 的并行加载效果。除此之外,骨架屏元素与业务组件共用一套 WXML 节点,且相关样式由公共的 `sass` 模块集中管理,业务组件只需要在适当的节点挂上 `skeleton` 和 `skeleton__block` 样式块即可,极大地降低了维护成本。++```jsx+<!-- banner.wxml -->+<view class="{{isLoading ? 'banner--skeleton' : ''}}">+  <view class="banner_wrapper"></view>+</view>+```++```scss+// banner.scss+.banner--skeleton {+  @include skeleton;+  .banner_wrapper {+    @include skeleton__block;+  }+}+```++![京喜首页骨架屏](https://img12.360buyimg.com/img/jfs/t1/95969/18/15455/623708/5e732920Ec8d08505/3df0e7cd84f9cc00.gif)++> 上面的 gif 在压缩过程有些小问题,大家可以直接访问【京喜】小程序体验骨架屏效果。++## 如何提升渲染性能?++当调用 `wx.navigateTo` 打开一个新的小程序页面时,小程序框架会完成这几步工作:++**1. 准备新的 webview 线程环境,包括基础库的初始化;**++**2. 从逻辑层到视图层的初始数据通信;**++**3. 视图层根据逻辑层的数据,结合 WXML 片段构建出节点树(包括节点属性、事件绑定等信息),最终与 WXSS 结合完成页面渲染;**++由于微信会提前开始准备 webview 线程环境,所以小程序的渲染损耗主要在后两者 **数据通信** 和 **节点树创建/更新** 的流程中。相对应的,比较有效的渲染性能优化方向就是:++- **降低线程间通信频次;**+- **减少线程间通信的数据量;**+- **减少 WXML 节点数量;**++### 合并 `setData` 调用++尽可能地把多次 `setData` 调用合并成一次。++我们除了要从编码规范上践行这个原则,还可以通过一些技术手段降低 `setData` 的调用频次。譬如,把同一个时间片([事件循环](https://github.com/aooy/blog/issues/5))内的 `setData` 调用合并在一起,Taro 框架就使用了这个优化手段。++在 Taro 框架下,调用 `setState` 时提供的对象会被加入到一个数组中,当下一次事件循环执行的时候再把这些对象合并一起,通过 `setData` 传递给原生小程序。++```js+// 小程序里的时间片 API+const nextTick = wx.nextTick ? wx.nextTick : setTimeout;+```++### 只把与界面渲染相关的数据放在 `data` 中++不难得出,`setData` 传输的数据量越多,线程间通信的耗时越长,渲染速度就越慢。根据微信官方测得的数据,传输时间和数据量大体上呈正相关关系:++![数据传输时间与数据量关系图](https://img10.360buyimg.com/ling/jfs/t1/94047/19/14475/10479/5e650290E47288ba5/7a2c2c327de0d241.png)++> 上图来自小程序官方开发指南++所以,与视图层渲染无关的数据尽量不要放在 `data` 中,可以放在页面(组件)类的其他字段下。++### 应用层的数据 diff++每当调用 `setData` 更新数据时,会引起视图层的重新渲染,小程序会结合新的 `data` 数据和 WXML 片段构建出新的节点树,并与当前节点树进行比较得出最终需要更新的节点(属性)。++即使小程序在底层框架层面已经对节点树更新进行了 diff,但我们依旧可以优化这次 diff 的性能。譬如,在调用 `setData` 时,提前确保传递的所有新数据都是有变化的,也就是针对 data 提前做一次 diff。++Taro 框架内部做了这一层优化。在每次调用原生小程序的 `setData` 之前,Taro 会把最新的 state 和当前页面实例的 data 做一次 diff,筛选出有必要更新的数据再执行 `setData`。++> 附 Taro 框架的 [数据 diff 规则](https://nervjs.github.io/taro/docs/optimized-practice.html#%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%95%B0%E6%8D%AE-diff)++### 去掉不必要的事件绑定++当用户事件(如 `Click`、`Touch` 事件等)被触发时,视图层会把事件信息反馈给逻辑层,这也是一个线程间通信的过程。但,如果没有在逻辑层中绑定事件的回调函数,通信将不会被触发。++所以,尽量减少不必要的事件绑定,尤其是像 `onPageScroll` 这种会被频繁触发的用户事件,会使通信过程频繁发生。++### 去掉不必要的节点属性++组件节点支持附加自定义数据 `dataset`(见下面例子),当用户事件被触发时,视图层会把事件 `target` 和 `dataset` 数据传输给逻辑层。那么,当自定义数据量越大,事件通信的耗时就会越长,所以应该避免在自定义数据中设置太多数据。++```jsx+<!-- wxml -->+<view+  data-a='A'+  data-b='B'+  bindtap='bindViewTap'+>+  Click Me!+</view>+```++```js+// js+Page({+  bindViewTap(e) {+    console.log(e.currentTarget.dataset)+  }+})+```++### 适当的组件颗粒度++小程序的组件模型与 WebComponents 标准中的 ShadowDOM 非常类似,每个组件都有独立的节点树,拥有各自独立的逻辑空间(包括独立的数据、`setData` 调用、`createSelectorQuery` 执行域等)。

「WebComponents」 加链接

TTImmortal

comment created time in 13 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。++## 怎么定义高性能?++提起互联网应用性能这个词,很多人在脑海中的词法解析就是,“是否足够快?”,似乎加载速度成为衡量系统性能的唯一指标。但这其实是不够准确的,试想一下,如果一个小程序加载速度非常快,用户花费很短时间就能看到页面的主体内容,但此时搜索框却无法输入内容,功能无法被流畅使用,用户可能就不会关心页面渲染有多快了。所以,我们不应该单纯考虑速度指标而忽略用户的感知体验,而应该全方位衡量用户在使用过程中能感知到的与应用加载相关的每个节点。++谷歌为 Web 应用定义了以用户为中心的性能指标体系,每个指标都与用户体验节点息息相关:++| 体验 | 指标 |+|-----|------|+| 页面能否正常访问?| 首次绘制 (`FP`)/首次内容绘制 (`FCP`) |+| 页面内容是否有用?| 首次有效绘制 (`FMP`) |+| 页面功能是否可用?| 可交互时间 (`TTI`) |++其中,“是否有用?” 这个问题是非常主观的,对于不同场景的系统可能会有完全不一样的回答,所以 `FMP` 是一个比较模糊的概念指标,不存在规范化的数值衡量。++小程序作为一个新的内容载体,衡量指标跟 Web 应用是非常类似的。对于大多数小程序而言,上述指标对应的含义为:++- FP:白屏时间;+- FMP:首屏渲染完成时间;+- TTI:页面加载完成时间;++综上,我们已基本确定了高性能的概念指标,接下来就是如何利用数值指标来描绘性能表现。++### 小程序官方性能指标++小程序官方针对小程序性能表现制订了权威的数值指标,主要围绕 **渲染表现**、**`setData` 数据量**、**元素节点数** 和 **网络请求延时** 这几个维度来给予定义(下面只列出部分关键指标):++- 首屏时间不超过 5 秒;+- 渲染时间不超过 500ms;+- 每秒调用 `setData` 的次数不超过 20 次;+- `setData` 的数据在 `JSON.stringify` 后不超过 256kb;+- 页面 WXML 节点少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个;+- 所有网络请求都在 1 秒内返回结果;++> 详见 [小程序性能评分规则](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html)++我们应该把这一系列的官方指标作为小程序的性能及格线,不断地打磨和提升小程序的整体体验,降低用户流失率。另外,这些指标会直接作为小程序体验评分工具的性能评分规则(体验评分工具会根据这些规则的权重和求和公式计算出体验得分)。++我们团队内部在官方性能指标的基础上,进一步浓缩优化指标系数,旨在对产品体验更高要求:++- 首屏时间不超过 2.5 秒;+- `setData` 的数据量不超过 100kb;+- 所有网络请求都在 1 秒内返回结果;+- 组件滑动、长列表滚动无卡顿感;++### 体验评分工具++小程序提供了 [体验评分工具(`Audits` 面板)](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/audits.html) 来测量上述的指标数据,其集成在开发者工具中,在小程序运行时实时检查相关问题点,并为开发者给出优化建议。++![体验评分面板](https://img13.360buyimg.com/ling/jfs/t1/92760/7/14417/406134/5e650dedE27aa858d/76b18b5f88de7288.png)++> 以上截图均来自小程序官方文档++体验评分工具是目前检测小程序性能问题最直接有效的途径,我们团队已经把体验评分作为页面/组件是否能达到精品门槛的重要考量手段之一。++### 小程序后台性能分析++我们知道,体验评分工具是在本地运行小程序代码时进行分析,但性能数据往往需要在真实环境和大数据量下才更有说服力。恰巧,**小程序管理平台** 和 **小程序助手** 为开发者提供了大量的真实数据统计。其中,性能分析面板从 **启动性能**、**运行性能** 和 **网络性能** 这三个维度分析数据,开发者可以根据客户端系统、机型、网络环境和访问来源等条件做精细化分析,非常具有考量价值。++![小程序助手性能分析](https://img20.360buyimg.com/ling/jfs/t1/95979/40/14592/773778/5e6500f1Eb92950cc/8c8bc8c4493a3c61.jpg)++> 其中,启动总耗时 = 小程序环境初始化 + 代码包加载 + 代码执行 + 渲染耗时++### 第三方测速系统++很多时候,宏观的耗时统计对于性能瓶颈点分析往往是杯水车薪,作用甚少,我们需要更细致地针对某个页面某些关键节点作测速统计,排查出暴露性能问题的代码区块,才能更有效地针对性优化。京喜小程序使用的是内部自研的测速系统,支持对地区、运营商、网络、客户端系统等多条件筛选,同时也支持数据可视化、同比分析数据等能力。京喜首页主要围绕 **页面 `onLoad`**、**`onReady`**、**数据加载完成**、**首屏渲染完成**、**各业务组件首次渲染完成** 等几个关键节点统计测速上报,旨在全链路监控性能表现。++![内部测速系统](https://img10.360buyimg.com/ling/jfs/t1/88143/36/14203/512179/5e620b0cEcedc020b/afe68aaf36311303.jpg)++> 另外,微信为开发者提供了 [测速系统](https://developers.weixin.qq.com/miniprogram/dev/framework/performanceReport/),也支持针对客户端系统、网络类型、用户地区等维度统计数据,有兴趣的可以尝试。++## 了解小程序底层架构++为了更好地为小程序制订性能优化措施,我们有必要先了解小程序的底层架构,以及与 web 浏览器的差异性。++微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。++而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了**双线程模型**:++- **视图层**:也就是 webview 线程,负责启用不同的 webview 来渲染不同的小程序页面;+- **逻辑层**:一个单独的线程执行 JS 代码,可以控制视图层的逻辑;++![双线程模型图](https://img20.360buyimg.com/ling/jfs/t1/88668/37/14434/308258/5e650263Ebf67d978/06dee7fb8293b482.png)++> 上图来自小程序官方开发指南++然而,**任何线程间的数据传输都是有延时的**,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线层之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。++作为小程序开发者,我们常常会被下面几个问题所困扰:++- 小程序启动慢;+- 白屏时间长;+- 页面渲染慢;+- 运行内存不足;++接下来,我们会结合小程序的底层架构分析出这些问题的根本原因,并针对性地给出解决方案。++## 小程序启动太慢?++小程序启动阶段,也就是如下图所示的展示加载界面的阶段。++![小程序加载界面](https://img13.360buyimg.com/ling/jfs/t1/88406/6/14244/64417/5e6219bdE1f9d7f95/07bcc55a6c147058.png)++在这个阶段中(包括启动前后的时机),微信会默默完成下面几项工作:++**1. 准备运行环境:**++  在小程序启动前,微信会先启动双线程环境,并在线程中完成小程序基础库的初始化和预执行。++  > 小程序基础库包括 WebView 基础库和 AppService 基础库,前者注入到视图层中,后者注入到逻辑层中,分别为所在层级提供其运行所需的基础框架能力。++**2. 下载小程序代码包:**++  在小程序初次启动时,需要下载编译后的代码包到本地。如果启动了小程序分包,则只有主包的内容会被下载。另外,代码包会保留在缓存中,后续启动会优先读取缓存。++**3. 加载小程序代码包:**++  小程序代码包下载好之后,会被加载到适当的线程中执行,基础库会完成所有页面的注册。++  > 在此阶段,主包内的所有页面 JS 文件及其依赖文件都会被自动执行。+  +  > 在页面注册过程中,基础库会调用页面 JS 文件的 Page 构造器方法,来记录页面的基础信息(包括初始数据、方法等)。++**4. 初始化小程序首页:**++  在小程序代码包加载完之后,基础库会根据启动路径找到首页,根据首页的基础信息初始化一个页面实例,并把信息传递给视图层,视图层会结合 WXML 结构、WXSS 样式和初始数据来渲染界面。++综合考虑,为了节省小程序的“点点点”时间(小程序的启动动画是三个圆点循环跑马灯),除了给每位用户发一台高配 5G 手机并顺带提供千兆宽带网络之外,还可以尽量 **控制代码包大小**,缩小代码包的下载时间。++### 无用文件、函数、样式剔除++经过多次业务迭代,无可避免的会存在一些弃用的组件/页面,以及不被调用的函数、样式规则,这些冗余代码会白白占据宝贵的代码包空间。而且,目前小程序的打包会将工程下所有文件都打入代码包内,并没有做依赖分析。++因此,我们需要及时地剔除不再使用的模块,以保证代码包空间利用率保持在较高水平。通过一些工具化手段可以有效地辅助完成这一工作。++- **文件依赖分析**++在小程序中,所有页面的路径都需要在小程序代码根目录 `app.json` 中被声明,类似地,自定义组件也需要在页面配置文件 `page.json` 中被声明。另外,WXML、WXSS 和 JS 的模块化都需要特定的关键字来声明依赖引用关系。++WXML 中的 `import` 和 `include`:++```jsx+<!-- A.wxml -->+<template name='A'>+  <text>{{text}}</text>+</template>++<!-- B.wxml -->+<import src="A.wxml"/>+<template is="A" data="{{text: 'B'}}"/>+```++```jsx+<!-- A.wxml -->+<text> A </text>++<!-- B.wxml -->+<include src="A.wxml"/>+<text> B </text>+```++WXSS 中的 `@import`:++```css+@import './A.wxss'+```++JS 中的 `require`/`import`:++```js+const A = require('./A')+```++所以,可以说小程序里的所有依赖模块都是有迹可循的,我们只需要利用这些关键字信息递归查找,遍历出文件依赖树,然后把没用的模块剔除掉。++- **JS、CSS Tree-Shaking**++JS Tree-Shaking 的原理就是借助 `Babel` 把代码编译成抽象语法树(AST),通过 AST 获取到函数的调用关系,从而把未被调用的函数方法剔除掉。不过这需要依赖 ES module,而小程序最开始是遵循 commonjs 规范的,这意味着是时候来一波“痛并快乐着”的改造了。++而 CSS 的 Tree-Shaking 可以利用 PurifyCSS 插件来完成。关于这两项技术,有兴趣的可以“谷歌一下”,这里就不铺开细讲了。++题外,京东的小程序团队已经把这一系列工程化能力集成在一套 CLI 工具中,有兴趣的可以看看这篇分享:[小程序工程化探索](https://mp.weixin.qq.com/s/_NSJTQ-4-8gTnwTVK-tn0A)。++### 减少代码包中的静态资源文件++小程序代码包最终会经过 GZIP 压缩放在 CDN 上,但 GZIP 压缩对于图片资源来说效果非常低。如 `JPG`、`PNG` 等格式文件,本身已经被压缩过了,再使用 GZIP 压缩有可能体积更大,得不偿失。所以,除了部分用于容错的图片必须放在代码包(譬如网络异常提示)之外,建议开发者把图片、视频等静态资源都放在 CDN 上。++> 需要注意,`Base64` 格式本质上是长字符串,和 CDN 地址比起来也会更占空间。++### 逻辑后移,精简业务逻辑++这是一个 “痛并快乐着” 的优化措施。“痛” 是因为需要给后台同学提改造需求,分分钟被打;“快乐” 则是因为享受删代码的过程,而且万一出 Bug 也不用背锅了...(开个玩笑)++通过让后台承担更多的业务逻辑,可以节省小程序前端代码量,同时线上问题还支持紧急修复,不需要经历小程序的提审、发布上线等繁琐过程。++总结得出,**一般不涉及前端计算的展示类逻辑,都可以适当做后移**。譬如京喜首页中的幕帘弹窗(如下图)逻辑,这里共有 10+ 种弹窗类型,以前的做法是前端从接口拉取 10+ 个不同字段,根据优先级和 “是否已展示”(该状态存储在本地缓存) 来决定展示哪一种,最后代码大概是这样的:++```js+// 检查每种弹窗类型是否已展示+Promise.all([+  check(popup_1),+  check(popup_2),+  // ...+  check(popup_n)+]).then(result => {+  // 优先级排序+  const queue = [{+    show: result.popup_1+    data: data.popup_1+  }, {+    show: result.popup_2+    data: data.popup_2+  }, +  // ...+  {+    show: result.popup_n+    data: data.popup_n+  }]+})+```++逻辑后移之后,前端只需负责拿幕帘字段做展示就可以了,代码变成这样:++```js+this.setData({+  popup: data.popup+})+```++![首页幕帘弹窗](https://img13.360buyimg.com/ling/jfs/t1/104567/26/14214/46840/5e621c48E4e33b7a9/440ca946478f53ac.jpg)++### 复用模板插件++京喜首页作为电商系统的门户,需要应对各类频繁的营销活动、升级改版等,同时也要满足不同用户属性的界面个性化需求(俗称 “千人千面”)。如何既能减少为应对多样化场景而产生的代码量,又可以提升研发效率,成为燃眉之急。++类似于组件复用的理念,我们需要提供更丰富的可配置能力,实现更高的代码复用度。参考小时候很喜欢玩的 “乐高” 积木玩具,**我们把首页模块的模板元素作颗粒度更细的划分,根据样式和功能抽象出一块块“积木”原料(称为插件元素)**。当首页模块在处理接口数据时,会启动插件引擎逐个装载插件,最终输出个性化的模板样式,整个流程就好比堆积木。当后续产品/运营需要新增模板时,只要在插件库中挑选插件排列组合即可,不需要额外新增/修改组件内容,也更不会产生难以维护的 `if` / `else` 逻辑,so easy~++当然,要完成这样的插件化改造免不了几个先决条件:++- **用户体验设计的统一**。如果设计风格总是天差地别的,强行插件化只会成为累赘。+- **服务端接口的统一**。同上,如果得浪费大量的精力来兼容不同模块间的接口字段差异,将会非常蛋疼。++下面为大家提供部分例程来辅助理解。其中,`use` 方法会接受各类处理钩子最终拼接出一个 `Function`,在对应模块处理数据时会被调用。++```js+// bi.helper.js++/**+ * 插件引擎+ * @param {function} options.formatName 标题处理钩子+ * @param {function} options.validList 数据校验器钩子+ */ +const use = options => data => format(data)++/**+ * 预置插件库+ */ +nameHelpers = {+  text: data => data.text,+  icon: data => data.icon+}+listHelpers = {+  single: list => list.slice(0, 1),+  double: list => list.slice(0, 2)+}++/**+ * “堆积木”+ */+export default {+  1000: use({+    formatName: nameHelpers.text,+    validList: listHelpers.single+  }),++  1001: use({+    formatName: nameHelpers.icon,+    validList: listHelpers.double+  })+}+```++```jsx+<!-- bi.wxml -->+<!-- 各模板节点实现 -->+<template name="renderName">+  <view wx:if="{{type === 'text'}}"> text </view>+  <view wx:elif="{{type === 'icon'}}"> icon </view>+</template>++<view class="bi__name">+  <template is="renderName" data="{{...data.name}"/>+</view>+```++```js+// bi.js+Component({+  ready() {+    // 根据 tpl 值选择解析函数+    const formatData = helper[data.tpl]+    this.setData({+      data: formatData(data)+    })+  }+})+```++### 分包加载++小程序启动时只会下载主包/独立分包,启用分包可以有效减少下载时间。(独立)分包需要遵循一些原则,详细的可以看官方文档:++- [使用分包](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html)+- [独立分包](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/independent.html)++### 部分页面 h5 化++小程序提供了 [web-view](https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html) 组件,支持在小程序环境内访问网页。当实在无法在小程序代码包中腾出多余空间时,可以考虑降级方案 —— 把部分页面 h5 化。 ++> 小程序和 h5 的通信可以通过 JSSDK 或 postMessage 通道来实现。+  +## 白屏时间过长?++白屏阶段,是指小程序代码包下载完(也就是启动界面结束)之后,页面完成首屏渲染的这一阶段,也就是 FMP (首次有效绘制)。++FMP 没法用标准化的指标定义,但对于大部分小程序来说,页面首屏展示的内容都需要依赖服务端的接口数据,那么影响白屏加载时间的主要由这两个元素构成:++- **网络资源加载时间**;+- **渲染时间**;++### 启用本地缓存++小程序提供了读写本地缓存的接口,数据存储在设备硬盘上。由于本地 I/O 读写(毫秒级)会比网络请求(秒级)要快很多,所以在用户访问页面时,可以优先从缓存中取上一次接口调用成功的数据来渲染视图,待网络请求成功后再覆盖最新数据重新渲染。除此之外,缓存数据还可以作为兜底数据,避免出现接口请求失败时页面空窗,一石二鸟。

感觉「设备硬盘」表述为 「当前设备」 更准确一些。

TTImmortal

comment created time in 13 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。++## 怎么定义高性能?++提起互联网应用性能这个词,很多人在脑海中的词法解析就是,“是否足够快?”,似乎加载速度成为衡量系统性能的唯一指标。但这其实是不够准确的,试想一下,如果一个小程序加载速度非常快,用户花费很短时间就能看到页面的主体内容,但此时搜索框却无法输入内容,功能无法被流畅使用,用户可能就不会关心页面渲染有多快了。所以,我们不应该单纯考虑速度指标而忽略用户的感知体验,而应该全方位衡量用户在使用过程中能感知到的与应用加载相关的每个节点。++谷歌为 Web 应用定义了以用户为中心的性能指标体系,每个指标都与用户体验节点息息相关:++| 体验 | 指标 |+|-----|------|+| 页面能否正常访问?| 首次绘制 (`FP`)/首次内容绘制 (`FCP`) |+| 页面内容是否有用?| 首次有效绘制 (`FMP`) |+| 页面功能是否可用?| 可交互时间 (`TTI`) |++其中,“是否有用?” 这个问题是非常主观的,对于不同场景的系统可能会有完全不一样的回答,所以 `FMP` 是一个比较模糊的概念指标,不存在规范化的数值衡量。++小程序作为一个新的内容载体,衡量指标跟 Web 应用是非常类似的。对于大多数小程序而言,上述指标对应的含义为:++- FP:白屏时间;+- FMP:首屏渲染完成时间;+- TTI:页面加载完成时间;++综上,我们已基本确定了高性能的概念指标,接下来就是如何利用数值指标来描绘性能表现。++### 小程序官方性能指标++小程序官方针对小程序性能表现制订了权威的数值指标,主要围绕 **渲染表现**、**`setData` 数据量**、**元素节点数** 和 **网络请求延时** 这几个维度来给予定义(下面只列出部分关键指标):++- 首屏时间不超过 5 秒;+- 渲染时间不超过 500ms;+- 每秒调用 `setData` 的次数不超过 20 次;+- `setData` 的数据在 `JSON.stringify` 后不超过 256kb;+- 页面 WXML 节点少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个;+- 所有网络请求都在 1 秒内返回结果;++> 详见 [小程序性能评分规则](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html)++我们应该把这一系列的官方指标作为小程序的性能及格线,不断地打磨和提升小程序的整体体验,降低用户流失率。另外,这些指标会直接作为小程序体验评分工具的性能评分规则(体验评分工具会根据这些规则的权重和求和公式计算出体验得分)。++我们团队内部在官方性能指标的基础上,进一步浓缩优化指标系数,旨在对产品体验更高要求:++- 首屏时间不超过 2.5 秒;+- `setData` 的数据量不超过 100kb;+- 所有网络请求都在 1 秒内返回结果;+- 组件滑动、长列表滚动无卡顿感;++### 体验评分工具++小程序提供了 [体验评分工具(`Audits` 面板)](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/audits.html) 来测量上述的指标数据,其集成在开发者工具中,在小程序运行时实时检查相关问题点,并为开发者给出优化建议。++![体验评分面板](https://img13.360buyimg.com/ling/jfs/t1/92760/7/14417/406134/5e650dedE27aa858d/76b18b5f88de7288.png)++> 以上截图均来自小程序官方文档++体验评分工具是目前检测小程序性能问题最直接有效的途径,我们团队已经把体验评分作为页面/组件是否能达到精品门槛的重要考量手段之一。++### 小程序后台性能分析++我们知道,体验评分工具是在本地运行小程序代码时进行分析,但性能数据往往需要在真实环境和大数据量下才更有说服力。恰巧,**小程序管理平台** 和 **小程序助手** 为开发者提供了大量的真实数据统计。其中,性能分析面板从 **启动性能**、**运行性能** 和 **网络性能** 这三个维度分析数据,开发者可以根据客户端系统、机型、网络环境和访问来源等条件做精细化分析,非常具有考量价值。++![小程序助手性能分析](https://img20.360buyimg.com/ling/jfs/t1/95979/40/14592/773778/5e6500f1Eb92950cc/8c8bc8c4493a3c61.jpg)++> 其中,启动总耗时 = 小程序环境初始化 + 代码包加载 + 代码执行 + 渲染耗时++### 第三方测速系统++很多时候,宏观的耗时统计对于性能瓶颈点分析往往是杯水车薪,作用甚少,我们需要更细致地针对某个页面某些关键节点作测速统计,排查出暴露性能问题的代码区块,才能更有效地针对性优化。京喜小程序使用的是内部自研的测速系统,支持对地区、运营商、网络、客户端系统等多条件筛选,同时也支持数据可视化、同比分析数据等能力。京喜首页主要围绕 **页面 `onLoad`**、**`onReady`**、**数据加载完成**、**首屏渲染完成**、**各业务组件首次渲染完成** 等几个关键节点统计测速上报,旨在全链路监控性能表现。++![内部测速系统](https://img10.360buyimg.com/ling/jfs/t1/88143/36/14203/512179/5e620b0cEcedc020b/afe68aaf36311303.jpg)++> 另外,微信为开发者提供了 [测速系统](https://developers.weixin.qq.com/miniprogram/dev/framework/performanceReport/),也支持针对客户端系统、网络类型、用户地区等维度统计数据,有兴趣的可以尝试。++## 了解小程序底层架构++为了更好地为小程序制订性能优化措施,我们有必要先了解小程序的底层架构,以及与 web 浏览器的差异性。++微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。++而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了**双线程模型**:++- **视图层**:也就是 webview 线程,负责启用不同的 webview 来渲染不同的小程序页面;+- **逻辑层**:一个单独的线程执行 JS 代码,可以控制视图层的逻辑;++![双线程模型图](https://img20.360buyimg.com/ling/jfs/t1/88668/37/14434/308258/5e650263Ebf67d978/06dee7fb8293b482.png)++> 上图来自小程序官方开发指南++然而,**任何线程间的数据传输都是有延时的**,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线层之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。++作为小程序开发者,我们常常会被下面几个问题所困扰:++- 小程序启动慢;+- 白屏时间长;+- 页面渲染慢;+- 运行内存不足;++接下来,我们会结合小程序的底层架构分析出这些问题的根本原因,并针对性地给出解决方案。++## 小程序启动太慢?++小程序启动阶段,也就是如下图所示的展示加载界面的阶段。++![小程序加载界面](https://img13.360buyimg.com/ling/jfs/t1/88406/6/14244/64417/5e6219bdE1f9d7f95/07bcc55a6c147058.png)++在这个阶段中(包括启动前后的时机),微信会默默完成下面几项工作:++**1. 准备运行环境:**++  在小程序启动前,微信会先启动双线程环境,并在线程中完成小程序基础库的初始化和预执行。++  > 小程序基础库包括 WebView 基础库和 AppService 基础库,前者注入到视图层中,后者注入到逻辑层中,分别为所在层级提供其运行所需的基础框架能力。++**2. 下载小程序代码包:**++  在小程序初次启动时,需要下载编译后的代码包到本地。如果启动了小程序分包,则只有主包的内容会被下载。另外,代码包会保留在缓存中,后续启动会优先读取缓存。++**3. 加载小程序代码包:**++  小程序代码包下载好之后,会被加载到适当的线程中执行,基础库会完成所有页面的注册。++  > 在此阶段,主包内的所有页面 JS 文件及其依赖文件都会被自动执行。+  +  > 在页面注册过程中,基础库会调用页面 JS 文件的 Page 构造器方法,来记录页面的基础信息(包括初始数据、方法等)。++**4. 初始化小程序首页:**++  在小程序代码包加载完之后,基础库会根据启动路径找到首页,根据首页的基础信息初始化一个页面实例,并把信息传递给视图层,视图层会结合 WXML 结构、WXSS 样式和初始数据来渲染界面。++综合考虑,为了节省小程序的“点点点”时间(小程序的启动动画是三个圆点循环跑马灯),除了给每位用户发一台高配 5G 手机并顺带提供千兆宽带网络之外,还可以尽量 **控制代码包大小**,缩小代码包的下载时间。++### 无用文件、函数、样式剔除++经过多次业务迭代,无可避免的会存在一些弃用的组件/页面,以及不被调用的函数、样式规则,这些冗余代码会白白占据宝贵的代码包空间。而且,目前小程序的打包会将工程下所有文件都打入代码包内,并没有做依赖分析。++因此,我们需要及时地剔除不再使用的模块,以保证代码包空间利用率保持在较高水平。通过一些工具化手段可以有效地辅助完成这一工作。++- **文件依赖分析**++在小程序中,所有页面的路径都需要在小程序代码根目录 `app.json` 中被声明,类似地,自定义组件也需要在页面配置文件 `page.json` 中被声明。另外,WXML、WXSS 和 JS 的模块化都需要特定的关键字来声明依赖引用关系。++WXML 中的 `import` 和 `include`:++```jsx+<!-- A.wxml -->+<template name='A'>+  <text>{{text}}</text>+</template>++<!-- B.wxml -->+<import src="A.wxml"/>+<template is="A" data="{{text: 'B'}}"/>+```++```jsx+<!-- A.wxml -->+<text> A </text>++<!-- B.wxml -->+<include src="A.wxml"/>+<text> B </text>+```++WXSS 中的 `@import`:++```css+@import './A.wxss'+```++JS 中的 `require`/`import`:++```js+const A = require('./A')+```++所以,可以说小程序里的所有依赖模块都是有迹可循的,我们只需要利用这些关键字信息递归查找,遍历出文件依赖树,然后把没用的模块剔除掉。++- **JS、CSS Tree-Shaking**++JS Tree-Shaking 的原理就是借助 `Babel` 把代码编译成抽象语法树(AST),通过 AST 获取到函数的调用关系,从而把未被调用的函数方法剔除掉。不过这需要依赖 ES module,而小程序最开始是遵循 commonjs 规范的,这意味着是时候来一波“痛并快乐着”的改造了。++而 CSS 的 Tree-Shaking 可以利用 PurifyCSS 插件来完成。关于这两项技术,有兴趣的可以“谷歌一下”,这里就不铺开细讲了。++题外,京东的小程序团队已经把这一系列工程化能力集成在一套 CLI 工具中,有兴趣的可以看看这篇分享:[小程序工程化探索](https://mp.weixin.qq.com/s/_NSJTQ-4-8gTnwTVK-tn0A)。++### 减少代码包中的静态资源文件++小程序代码包最终会经过 GZIP 压缩放在 CDN 上,但 GZIP 压缩对于图片资源来说效果非常低。如 `JPG`、`PNG` 等格式文件,本身已经被压缩过了,再使用 GZIP 压缩有可能体积更大,得不偿失。所以,除了部分用于容错的图片必须放在代码包(譬如网络异常提示)之外,建议开发者把图片、视频等静态资源都放在 CDN 上。++> 需要注意,`Base64` 格式本质上是长字符串,和 CDN 地址比起来也会更占空间。++### 逻辑后移,精简业务逻辑++这是一个 “痛并快乐着” 的优化措施。“痛” 是因为需要给后台同学提改造需求,分分钟被打;“快乐” 则是因为享受删代码的过程,而且万一出 Bug 也不用背锅了...(开个玩笑)++通过让后台承担更多的业务逻辑,可以节省小程序前端代码量,同时线上问题还支持紧急修复,不需要经历小程序的提审、发布上线等繁琐过程。++总结得出,**一般不涉及前端计算的展示类逻辑,都可以适当做后移**。譬如京喜首页中的幕帘弹窗(如下图)逻辑,这里共有 10+ 种弹窗类型,以前的做法是前端从接口拉取 10+ 个不同字段,根据优先级和 “是否已展示”(该状态存储在本地缓存) 来决定展示哪一种,最后代码大概是这样的:++```js+// 检查每种弹窗类型是否已展示+Promise.all([+  check(popup_1),+  check(popup_2),+  // ...+  check(popup_n)+]).then(result => {+  // 优先级排序+  const queue = [{+    show: result.popup_1+    data: data.popup_1+  }, {+    show: result.popup_2+    data: data.popup_2+  }, +  // ...+  {+    show: result.popup_n+    data: data.popup_n+  }]+})+```++逻辑后移之后,前端只需负责拿幕帘字段做展示就可以了,代码变成这样:++```js+this.setData({+  popup: data.popup+})+```++![首页幕帘弹窗](https://img13.360buyimg.com/ling/jfs/t1/104567/26/14214/46840/5e621c48E4e33b7a9/440ca946478f53ac.jpg)++### 复用模板插件++京喜首页作为电商系统的门户,需要应对各类频繁的营销活动、升级改版等,同时也要满足不同用户属性的界面个性化需求(俗称 “千人千面”)。如何既能减少为应对多样化场景而产生的代码量,又可以提升研发效率,成为燃眉之急。++类似于组件复用的理念,我们需要提供更丰富的可配置能力,实现更高的代码复用度。参考小时候很喜欢玩的 “乐高” 积木玩具,**我们把首页模块的模板元素作颗粒度更细的划分,根据样式和功能抽象出一块块“积木”原料(称为插件元素)**。当首页模块在处理接口数据时,会启动插件引擎逐个装载插件,最终输出个性化的模板样式,整个流程就好比堆积木。当后续产品/运营需要新增模板时,只要在插件库中挑选插件排列组合即可,不需要额外新增/修改组件内容,也更不会产生难以维护的 `if` / `else` 逻辑,so easy~++当然,要完成这样的插件化改造免不了几个先决条件:++- **用户体验设计的统一**。如果设计风格总是天差地别的,强行插件化只会成为累赘。+- **服务端接口的统一**。同上,如果得浪费大量的精力来兼容不同模块间的接口字段差异,将会非常蛋疼。++下面为大家提供部分例程来辅助理解。其中,`use` 方法会接受各类处理钩子最终拼接出一个 `Function`,在对应模块处理数据时会被调用。++```js+// bi.helper.js++/**+ * 插件引擎+ * @param {function} options.formatName 标题处理钩子+ * @param {function} options.validList 数据校验器钩子+ */ +const use = options => data => format(data)++/**+ * 预置插件库+ */ +nameHelpers = {+  text: data => data.text,+  icon: data => data.icon+}+listHelpers = {+  single: list => list.slice(0, 1),+  double: list => list.slice(0, 2)+}++/**+ * “堆积木”+ */+export default {+  1000: use({+    formatName: nameHelpers.text,+    validList: listHelpers.single+  }),++  1001: use({+    formatName: nameHelpers.icon,+    validList: listHelpers.double+  })+}+```++```jsx+<!-- bi.wxml -->+<!-- 各模板节点实现 -->+<template name="renderName">+  <view wx:if="{{type === 'text'}}"> text </view>+  <view wx:elif="{{type === 'icon'}}"> icon </view>+</template>++<view class="bi__name">+  <template is="renderName" data="{{...data.name}"/>+</view>+```++```js+// bi.js+Component({+  ready() {+    // 根据 tpl 值选择解析函数+    const formatData = helper[data.tpl]+    this.setData({+      data: formatData(data)+    })+  }+})+```++### 分包加载++小程序启动时只会下载主包/独立分包,启用分包可以有效减少下载时间。(独立)分包需要遵循一些原则,详细的可以看官方文档:++- [使用分包](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html)+- [独立分包](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/independent.html)++### 部分页面 h5 化++小程序提供了 [web-view](https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html) 组件,支持在小程序环境内访问网页。当实在无法在小程序代码包中腾出多余空间时,可以考虑降级方案 —— 把部分页面 h5 化。 ++> 小程序和 h5 的通信可以通过 JSSDK 或 postMessage 通道来实现。+  +## 白屏时间过长?++白屏阶段,是指小程序代码包下载完(也就是启动界面结束)之后,页面完成首屏渲染的这一阶段,也就是 FMP (首次有效绘制)。++FMP 没法用标准化的指标定义,但对于大部分小程序来说,页面首屏展示的内容都需要依赖服务端的接口数据,那么影响白屏加载时间的主要由这两个元素构成:++- **网络资源加载时间**;+- **渲染时间**;++### 启用本地缓存++小程序提供了读写本地缓存的接口,数据存储在设备硬盘上。由于本地 I/O 读写(毫秒级)会比网络请求(秒级)要快很多,所以在用户访问页面时,可以优先从缓存中取上一次接口调用成功的数据来渲染视图,待网络请求成功后再覆盖最新数据重新渲染。除此之外,缓存数据还可以作为兜底数据,避免出现接口请求失败时页面空窗,一石二鸟。++但并非所有场景都适合缓存策略,譬如对数据即时性要求非常高的场景(如抢购入口)来说,展示老数据可能会引发一些问题。++小程序默认会按照 **不同小程序**、**不同微信用户** 这两个维度对缓存空间进行隔离。诸如京喜小程序首页也采用了缓存策略,会进一步按照 **数据版本号**、**用户属性** 来对缓存进行再隔离,避免信息误展示。++### 数据预拉取++小程序官方为开发者提供了一个在小程序冷启动时提前拉取第三方接口的能力:[数据预拉取](https://developers.weixin.qq.com/miniprogram/dev/framework/ability/pre-fetch.html)。++> 关于冷启动和热启动的定义可以看 [这里](https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/operating-mechanism.html#%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%90%AF%E5%8A%A8)++数据预拉取的原理其实很简单,就是在小程序启动时,微信服务器代理小程序客户端发起一个 HTTP 请求到第三方服务器来获取数据,并且把响应数据存储在本地客户端供小程序前端调取。当小程序加载完成后,只需调用微信提供的 API `wx.getBackgroundFetchData` 从本地缓存获取数据即可。这种做法可以充分利用小程序启动和初始化阶段的等待时间,使更快地完成页面渲染。++京喜小程序首页已经在生产环境实践过这个能力,从每日千万级的数据分析得出,预拉取使冷启动时获取到接口数据的时间节点从 2.5s 加速到 1s(提速了 60%)。虽然提升效果非常明显,但这个能力依然存在一些不成熟的地方:++- **预拉取的数据会被强缓存**;++  由于预拉取的请求最终是由微信的服务器发起的,也许是出于服务器资源限制的考虑,预拉取的数据会缓存在微信本地一段时间,缓存失效后才会重新发起请求。经过真机实测,在微信购物入口冷启动京喜小程序的场景下,预拉取缓存存活了 30 分钟以上,这对于数据实时性要求比较高的系统来说是非常致命的。++- **请求体和响应体都无法被拦截**;++  由于请求第三方服务器是从微信的服务器发起的,而不是从小程序客户端发起的,所以本地代理无法拦截到这一次真实请求,这会导致开发者无法通过拦截请求的方式来区分获取线上环境和开发环境的数据,给开发调试带来麻烦。++  小程序内部接口的响应体类型都是 `application/octet-stream`,即数据格式未知,使本地代理无法正确解析。++- **微信服务器发起的请求没有提供区分线上版和开发版的参数,且没有提供用户 IP 等信息**;++如果这几个问题点都不会影响到你的场景,那么可以尝试开启预拉取能力,这对于小程序首屏渲染速度是质的提升。++### 跳转时预拉取++为了尽快获取到服务端数据,比较常见的做法是在页面 `onLoad` 钩子被触发时发起网络请求,但其实这并不是最快的方式。从发起页面跳转,到下一个页面 `onLoad` 的过程中,小程序需要完成一些环境初始化及页面实例化的工作,耗时大概为 300 ~ 400 毫秒。++实际上,我们可以在发起跳转前(如 `wx.navigateTo` 调用前),提前请求下一个页面的主接口并存储在全局 `Promise` 对象中,待下个页面加载完成后从 `Promise` 对象中读取数据即可。++这也是双线程模型所带来的优势之一,不同于多页面 web 应用在页面跳转/刷新时就销毁掉 window 对象。++### 分包预下载++如果开启了分包加载能力,在用户访问到分包内某个页面时,小程序才会开始下载对应的分包。当处于分包下载阶段时,页面会维持在 “白屏” 的启动态,这用户体验是比较糟糕的。++幸好,小程序提供了 [分包预下载](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/preload.html) 能力,开发者可以配置进入某个页面时预下载可能会用到的分包,避免在页面切换时僵持在 “白屏” 态。++### 非关键渲染数据延迟请求++这是关键渲染路径优化的其中一个思路,从缩短网络请求时延的角度加快首屏渲染完成时间。++> 关键渲染路径(Critical Rendering Path)是指在完成首屏渲染的过程中必须发生的事件。++以京喜小程序如此庞大的小程序项目为例,每个模块背后都可能有着海量的后台服务作支撑,而这些后台服务间的通信和数据交互都会存在一定的时延。我们根据京喜首页的页面结构,把所有模块划分成两类:**主体模块**(导航、商品轮播、商品豆腐块等)和 **非主体模块**(幕帘弹窗、右侧挂件等)。++在初始化首页时,小程序会发起一个聚合接口请求来获取主体模块的数据,而非主体模块的数据则从另一个接口获取,通过拆分的手段来降低主接口的调用时延,同时减少响应体的数据量,缩减网络传输时间。++![京喜首页浮层模块](https://img20.360buyimg.com/ling/jfs/t1/88895/31/14330/199996/5e6224c5Ecbb05ab3/739a7c19bd10ff3c.jpg)++### 分屏渲染++这也是关键渲染路径优化思路之一,通过延迟非关键元素的渲染时机,为关键渲染路径腾出资源。++类似上一条措施,继续以京喜小程序首页为例,我们在 **主体模块** 的基础上再度划分出 **首屏模块**(商品豆腐块以上部分) 和 **非首屏模块**(商品豆腐块及以下部分)。当小程序获取到主体模块的数据后,会优先渲染首屏模块,在所有首屏模块都渲染完成后才会渲染非首屏模块和非主体模块,以此确保首屏内容以最快速度呈现。++![京喜首页分屏渲染](https://img12.360buyimg.com/img/jfs/t1/105202/13/15534/1217141/5e71ecf7Eb0e73ec3/ae581ad42fca386c.gif)++> 为了更好地呈现效果,上面 gif 做了降速处理++### 接口聚合,请求合并++在小程序中,发起网络请求是通过 [wx.request](https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html) 这个 API。我们知道,在 web 浏览器中,针对同一域名的 HTTP 并发请求数是有限制的;在小程序中也有类似的限制,但区别在于不是针对域名限制,而是针对 API 调用:++- `wx.request` (HTTP 连接)的最大并发限制是 10 个;+- `wx.connectSocket` (WebSocket 连接)的最大并发限制是 5 个;++超出并发限制数目的 HTTP 请求将会被阻塞,需要在队列中等待前面的请求完成,从而一定程度上增加了请求时延。因此,**对于职责类似的网络请求,最好采用节流的方式,先在一定时间间隔内收集数据,再合并到一个请求体中发送给服务端。**++### 图片资源优化++图片资源一直是移动端系统中抢占大流量的部分,尤其是对于电商系统。优化图片资源的加载可以有效地加快页面响应时间,提升首屏渲染速度。++- **使用 WebP 格式**++[WebP](https://developers.google.com/speed/webp) 是 Google 推出的一种支持有损/无损压缩的图片文件格式,得益于更优的图像数据压缩算法,其与 JPG、PNG 等格式相比,在肉眼无差别的图片质量前提下具有更小的图片体积(据官方说明,WebP 无损压缩体积比 PNG 小 26%,有损压缩体积比 JPEG 小 25-34%)。++> 小程序的 [image 组件](https://developers.weixin.qq.com/miniprogram/dev/component/image.html) 支持 JPG、PNG、SVG、WEBP、GIF 等格式。++- **图片裁剪&降质**++鉴于移动端设备的分辨率是有上限的,很多图片的尺寸常常远大于页面元素尺寸,这非常浪费网络资源(一般图片尺寸 2 倍于页面元素真实尺寸比较合适)。得益于京东内部强大的图片处理服务,我们可以通过资源的命名规则和请求参数来获取服务端优化后的图片:++裁剪成 100x100 的图片:`https://{host}/s100x100_jfs/{file_path}`;++降质 70%:`https://{href}!q70`;++- **图片懒加载、雪碧图优化**

雪碧图 (CSS Sprite)

TTImmortal

comment created time in 13 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。++## 怎么定义高性能?++提起互联网应用性能这个词,很多人在脑海中的词法解析就是,“是否足够快?”,似乎加载速度成为衡量系统性能的唯一指标。但这其实是不够准确的,试想一下,如果一个小程序加载速度非常快,用户花费很短时间就能看到页面的主体内容,但此时搜索框却无法输入内容,功能无法被流畅使用,用户可能就不会关心页面渲染有多快了。所以,我们不应该单纯考虑速度指标而忽略用户的感知体验,而应该全方位衡量用户在使用过程中能感知到的与应用加载相关的每个节点。++谷歌为 Web 应用定义了以用户为中心的性能指标体系,每个指标都与用户体验节点息息相关:++| 体验 | 指标 |+|-----|------|+| 页面能否正常访问?| 首次绘制 (`FP`)/首次内容绘制 (`FCP`) |+| 页面内容是否有用?| 首次有效绘制 (`FMP`) |+| 页面功能是否可用?| 可交互时间 (`TTI`) |++其中,“是否有用?” 这个问题是非常主观的,对于不同场景的系统可能会有完全不一样的回答,所以 `FMP` 是一个比较模糊的概念指标,不存在规范化的数值衡量。++小程序作为一个新的内容载体,衡量指标跟 Web 应用是非常类似的。对于大多数小程序而言,上述指标对应的含义为:++- FP:白屏时间;+- FMP:首屏渲染完成时间;+- TTI:页面加载完成时间;++综上,我们已基本确定了高性能的概念指标,接下来就是如何利用数值指标来描绘性能表现。++### 小程序官方性能指标++小程序官方针对小程序性能表现制订了权威的数值指标,主要围绕 **渲染表现**、**`setData` 数据量**、**元素节点数** 和 **网络请求延时** 这几个维度来给予定义(下面只列出部分关键指标):++- 首屏时间不超过 5 秒;+- 渲染时间不超过 500ms;+- 每秒调用 `setData` 的次数不超过 20 次;+- `setData` 的数据在 `JSON.stringify` 后不超过 256kb;+- 页面 WXML 节点少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个;+- 所有网络请求都在 1 秒内返回结果;++> 详见 [小程序性能评分规则](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html)++我们应该把这一系列的官方指标作为小程序的性能及格线,不断地打磨和提升小程序的整体体验,降低用户流失率。另外,这些指标会直接作为小程序体验评分工具的性能评分规则(体验评分工具会根据这些规则的权重和求和公式计算出体验得分)。++我们团队内部在官方性能指标的基础上,进一步浓缩优化指标系数,旨在对产品体验更高要求:++- 首屏时间不超过 2.5 秒;+- `setData` 的数据量不超过 100kb;+- 所有网络请求都在 1 秒内返回结果;+- 组件滑动、长列表滚动无卡顿感;++### 体验评分工具++小程序提供了 [体验评分工具(`Audits` 面板)](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/audits.html) 来测量上述的指标数据,其集成在开发者工具中,在小程序运行时实时检查相关问题点,并为开发者给出优化建议。++![体验评分面板](https://img13.360buyimg.com/ling/jfs/t1/92760/7/14417/406134/5e650dedE27aa858d/76b18b5f88de7288.png)++> 以上截图均来自小程序官方文档++体验评分工具是目前检测小程序性能问题最直接有效的途径,我们团队已经把体验评分作为页面/组件是否能达到精品门槛的重要考量手段之一。++### 小程序后台性能分析++我们知道,体验评分工具是在本地运行小程序代码时进行分析,但性能数据往往需要在真实环境和大数据量下才更有说服力。恰巧,**小程序管理平台** 和 **小程序助手** 为开发者提供了大量的真实数据统计。其中,性能分析面板从 **启动性能**、**运行性能** 和 **网络性能** 这三个维度分析数据,开发者可以根据客户端系统、机型、网络环境和访问来源等条件做精细化分析,非常具有考量价值。++![小程序助手性能分析](https://img20.360buyimg.com/ling/jfs/t1/95979/40/14592/773778/5e6500f1Eb92950cc/8c8bc8c4493a3c61.jpg)++> 其中,启动总耗时 = 小程序环境初始化 + 代码包加载 + 代码执行 + 渲染耗时++### 第三方测速系统++很多时候,宏观的耗时统计对于性能瓶颈点分析往往是杯水车薪,作用甚少,我们需要更细致地针对某个页面某些关键节点作测速统计,排查出暴露性能问题的代码区块,才能更有效地针对性优化。京喜小程序使用的是内部自研的测速系统,支持对地区、运营商、网络、客户端系统等多条件筛选,同时也支持数据可视化、同比分析数据等能力。京喜首页主要围绕 **页面 `onLoad`**、**`onReady`**、**数据加载完成**、**首屏渲染完成**、**各业务组件首次渲染完成** 等几个关键节点统计测速上报,旨在全链路监控性能表现。++![内部测速系统](https://img10.360buyimg.com/ling/jfs/t1/88143/36/14203/512179/5e620b0cEcedc020b/afe68aaf36311303.jpg)++> 另外,微信为开发者提供了 [测速系统](https://developers.weixin.qq.com/miniprogram/dev/framework/performanceReport/),也支持针对客户端系统、网络类型、用户地区等维度统计数据,有兴趣的可以尝试。++## 了解小程序底层架构++为了更好地为小程序制订性能优化措施,我们有必要先了解小程序的底层架构,以及与 web 浏览器的差异性。++微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。++而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了**双线程模型**:++- **视图层**:也就是 webview 线程,负责启用不同的 webview 来渲染不同的小程序页面;+- **逻辑层**:一个单独的线程执行 JS 代码,可以控制视图层的逻辑;++![双线程模型图](https://img20.360buyimg.com/ling/jfs/t1/88668/37/14434/308258/5e650263Ebf67d978/06dee7fb8293b482.png)++> 上图来自小程序官方开发指南++然而,**任何线程间的数据传输都是有延时的**,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线层之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。++作为小程序开发者,我们常常会被下面几个问题所困扰:++- 小程序启动慢;+- 白屏时间长;+- 页面渲染慢;+- 运行内存不足;++接下来,我们会结合小程序的底层架构分析出这些问题的根本原因,并针对性地给出解决方案。++## 小程序启动太慢?++小程序启动阶段,也就是如下图所示的展示加载界面的阶段。++![小程序加载界面](https://img13.360buyimg.com/ling/jfs/t1/88406/6/14244/64417/5e6219bdE1f9d7f95/07bcc55a6c147058.png)++在这个阶段中(包括启动前后的时机),微信会默默完成下面几项工作:++**1. 准备运行环境:**++  在小程序启动前,微信会先启动双线程环境,并在线程中完成小程序基础库的初始化和预执行。++  > 小程序基础库包括 WebView 基础库和 AppService 基础库,前者注入到视图层中,后者注入到逻辑层中,分别为所在层级提供其运行所需的基础框架能力。++**2. 下载小程序代码包:**++  在小程序初次启动时,需要下载编译后的代码包到本地。如果启动了小程序分包,则只有主包的内容会被下载。另外,代码包会保留在缓存中,后续启动会优先读取缓存。++**3. 加载小程序代码包:**++  小程序代码包下载好之后,会被加载到适当的线程中执行,基础库会完成所有页面的注册。++  > 在此阶段,主包内的所有页面 JS 文件及其依赖文件都会被自动执行。+  +  > 在页面注册过程中,基础库会调用页面 JS 文件的 Page 构造器方法,来记录页面的基础信息(包括初始数据、方法等)。++**4. 初始化小程序首页:**++  在小程序代码包加载完之后,基础库会根据启动路径找到首页,根据首页的基础信息初始化一个页面实例,并把信息传递给视图层,视图层会结合 WXML 结构、WXSS 样式和初始数据来渲染界面。++综合考虑,为了节省小程序的“点点点”时间(小程序的启动动画是三个圆点循环跑马灯),除了给每位用户发一台高配 5G 手机并顺带提供千兆宽带网络之外,还可以尽量 **控制代码包大小**,缩小代码包的下载时间。++### 无用文件、函数、样式剔除++经过多次业务迭代,无可避免的会存在一些弃用的组件/页面,以及不被调用的函数、样式规则,这些冗余代码会白白占据宝贵的代码包空间。而且,目前小程序的打包会将工程下所有文件都打入代码包内,并没有做依赖分析。++因此,我们需要及时地剔除不再使用的模块,以保证代码包空间利用率保持在较高水平。通过一些工具化手段可以有效地辅助完成这一工作。++- **文件依赖分析**++在小程序中,所有页面的路径都需要在小程序代码根目录 `app.json` 中被声明,类似地,自定义组件也需要在页面配置文件 `page.json` 中被声明。另外,WXML、WXSS 和 JS 的模块化都需要特定的关键字来声明依赖引用关系。++WXML 中的 `import` 和 `include`:++```jsx+<!-- A.wxml -->+<template name='A'>+  <text>{{text}}</text>+</template>++<!-- B.wxml -->+<import src="A.wxml"/>+<template is="A" data="{{text: 'B'}}"/>+```++```jsx+<!-- A.wxml -->+<text> A </text>++<!-- B.wxml -->+<include src="A.wxml"/>+<text> B </text>+```++WXSS 中的 `@import`:++```css+@import './A.wxss'+```++JS 中的 `require`/`import`:++```js+const A = require('./A')+```++所以,可以说小程序里的所有依赖模块都是有迹可循的,我们只需要利用这些关键字信息递归查找,遍历出文件依赖树,然后把没用的模块剔除掉。++- **JS、CSS Tree-Shaking**++JS Tree-Shaking 的原理就是借助 `Babel` 把代码编译成抽象语法树(AST),通过 AST 获取到函数的调用关系,从而把未被调用的函数方法剔除掉。不过这需要依赖 ES module,而小程序最开始是遵循 commonjs 规范的,这意味着是时候来一波“痛并快乐着”的改造了。++而 CSS 的 Tree-Shaking 可以利用 PurifyCSS 插件来完成。关于这两项技术,有兴趣的可以“谷歌一下”,这里就不铺开细讲了。++题外,京东的小程序团队已经把这一系列工程化能力集成在一套 CLI 工具中,有兴趣的可以看看这篇分享:[小程序工程化探索](https://mp.weixin.qq.com/s/_NSJTQ-4-8gTnwTVK-tn0A)。++### 减少代码包中的静态资源文件++小程序代码包最终会经过 GZIP 压缩放在 CDN 上,但 GZIP 压缩对于图片资源来说效果非常低。如 `JPG`、`PNG` 等格式文件,本身已经被压缩过了,再使用 GZIP 压缩有可能体积更大,得不偿失。所以,除了部分用于容错的图片必须放在代码包(譬如网络异常提示)之外,建议开发者把图片、视频等静态资源都放在 CDN 上。++> 需要注意,`Base64` 格式本质上是长字符串,和 CDN 地址比起来也会更占空间。++### 逻辑后移,精简业务逻辑++这是一个 “痛并快乐着” 的优化措施。“痛” 是因为需要给后台同学提改造需求,分分钟被打;“快乐” 则是因为享受删代码的过程,而且万一出 Bug 也不用背锅了...(开个玩笑)++通过让后台承担更多的业务逻辑,可以节省小程序前端代码量,同时线上问题还支持紧急修复,不需要经历小程序的提审、发布上线等繁琐过程。++总结得出,**一般不涉及前端计算的展示类逻辑,都可以适当做后移**。譬如京喜首页中的幕帘弹窗(如下图)逻辑,这里共有 10+ 种弹窗类型,以前的做法是前端从接口拉取 10+ 个不同字段,根据优先级和 “是否已展示”(该状态存储在本地缓存) 来决定展示哪一种,最后代码大概是这样的:++```js+// 检查每种弹窗类型是否已展示+Promise.all([+  check(popup_1),+  check(popup_2),+  // ...+  check(popup_n)+]).then(result => {+  // 优先级排序+  const queue = [{+    show: result.popup_1+    data: data.popup_1+  }, {+    show: result.popup_2+    data: data.popup_2+  }, +  // ...+  {+    show: result.popup_n+    data: data.popup_n+  }]+})+```++逻辑后移之后,前端只需负责拿幕帘字段做展示就可以了,代码变成这样:++```js+this.setData({+  popup: data.popup+})+```++![首页幕帘弹窗](https://img13.360buyimg.com/ling/jfs/t1/104567/26/14214/46840/5e621c48E4e33b7a9/440ca946478f53ac.jpg)++### 复用模板插件++京喜首页作为电商系统的门户,需要应对各类频繁的营销活动、升级改版等,同时也要满足不同用户属性的界面个性化需求(俗称 “千人千面”)。如何既能减少为应对多样化场景而产生的代码量,又可以提升研发效率,成为燃眉之急。++类似于组件复用的理念,我们需要提供更丰富的可配置能力,实现更高的代码复用度。参考小时候很喜欢玩的 “乐高” 积木玩具,**我们把首页模块的模板元素作颗粒度更细的划分,根据样式和功能抽象出一块块“积木”原料(称为插件元素)**。当首页模块在处理接口数据时,会启动插件引擎逐个装载插件,最终输出个性化的模板样式,整个流程就好比堆积木。当后续产品/运营需要新增模板时,只要在插件库中挑选插件排列组合即可,不需要额外新增/修改组件内容,也更不会产生难以维护的 `if` / `else` 逻辑,so easy~++当然,要完成这样的插件化改造免不了几个先决条件:++- **用户体验设计的统一**。如果设计风格总是天差地别的,强行插件化只会成为累赘。+- **服务端接口的统一**。同上,如果得浪费大量的精力来兼容不同模块间的接口字段差异,将会非常蛋疼。++下面为大家提供部分例程来辅助理解。其中,`use` 方法会接受各类处理钩子最终拼接出一个 `Function`,在对应模块处理数据时会被调用。++```js+// bi.helper.js++/**+ * 插件引擎+ * @param {function} options.formatName 标题处理钩子+ * @param {function} options.validList 数据校验器钩子+ */ +const use = options => data => format(data)++/**+ * 预置插件库+ */ +nameHelpers = {+  text: data => data.text,+  icon: data => data.icon+}+listHelpers = {+  single: list => list.slice(0, 1),+  double: list => list.slice(0, 2)+}++/**+ * “堆积木”+ */+export default {+  1000: use({+    formatName: nameHelpers.text,+    validList: listHelpers.single+  }),++  1001: use({+    formatName: nameHelpers.icon,+    validList: listHelpers.double+  })+}+```++```jsx+<!-- bi.wxml -->+<!-- 各模板节点实现 -->+<template name="renderName">+  <view wx:if="{{type === 'text'}}"> text </view>+  <view wx:elif="{{type === 'icon'}}"> icon </view>+</template>++<view class="bi__name">+  <template is="renderName" data="{{...data.name}"/>+</view>+```++```js+// bi.js+Component({+  ready() {+    // 根据 tpl 值选择解析函数+    const formatData = helper[data.tpl]+    this.setData({+      data: formatData(data)+    })+  }+})+```++### 分包加载++小程序启动时只会下载主包/独立分包,启用分包可以有效减少下载时间。(独立)分包需要遵循一些原则,详细的可以看官方文档:++- [使用分包](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html)+- [独立分包](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/independent.html)++### 部分页面 h5 化++小程序提供了 [web-view](https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html) 组件,支持在小程序环境内访问网页。当实在无法在小程序代码包中腾出多余空间时,可以考虑降级方案 —— 把部分页面 h5 化。 ++> 小程序和 h5 的通信可以通过 JSSDK 或 postMessage 通道来实现。+  +## 白屏时间过长?++白屏阶段,是指小程序代码包下载完(也就是启动界面结束)之后,页面完成首屏渲染的这一阶段,也就是 FMP (首次有效绘制)。++FMP 没法用标准化的指标定义,但对于大部分小程序来说,页面首屏展示的内容都需要依赖服务端的接口数据,那么影响白屏加载时间的主要由这两个元素构成:++- **网络资源加载时间**;+- **渲染时间**;++### 启用本地缓存++小程序提供了读写本地缓存的接口,数据存储在设备硬盘上。由于本地 I/O 读写(毫秒级)会比网络请求(秒级)要快很多,所以在用户访问页面时,可以优先从缓存中取上一次接口调用成功的数据来渲染视图,待网络请求成功后再覆盖最新数据重新渲染。除此之外,缓存数据还可以作为兜底数据,避免出现接口请求失败时页面空窗,一石二鸟。++但并非所有场景都适合缓存策略,譬如对数据即时性要求非常高的场景(如抢购入口)来说,展示老数据可能会引发一些问题。++小程序默认会按照 **不同小程序**、**不同微信用户** 这两个维度对缓存空间进行隔离。诸如京喜小程序首页也采用了缓存策略,会进一步按照 **数据版本号**、**用户属性** 来对缓存进行再隔离,避免信息误展示。++### 数据预拉取++小程序官方为开发者提供了一个在小程序冷启动时提前拉取第三方接口的能力:[数据预拉取](https://developers.weixin.qq.com/miniprogram/dev/framework/ability/pre-fetch.html)。++> 关于冷启动和热启动的定义可以看 [这里](https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/operating-mechanism.html#%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%90%AF%E5%8A%A8)++数据预拉取的原理其实很简单,就是在小程序启动时,微信服务器代理小程序客户端发起一个 HTTP 请求到第三方服务器来获取数据,并且把响应数据存储在本地客户端供小程序前端调取。当小程序加载完成后,只需调用微信提供的 API `wx.getBackgroundFetchData` 从本地缓存获取数据即可。这种做法可以充分利用小程序启动和初始化阶段的等待时间,使更快地完成页面渲染。++京喜小程序首页已经在生产环境实践过这个能力,从每日千万级的数据分析得出,预拉取使冷启动时获取到接口数据的时间节点从 2.5s 加速到 1s(提速了 60%)。虽然提升效果非常明显,但这个能力依然存在一些不成熟的地方:++- **预拉取的数据会被强缓存**;++  由于预拉取的请求最终是由微信的服务器发起的,也许是出于服务器资源限制的考虑,预拉取的数据会缓存在微信本地一段时间,缓存失效后才会重新发起请求。经过真机实测,在微信购物入口冷启动京喜小程序的场景下,预拉取缓存存活了 30 分钟以上,这对于数据实时性要求比较高的系统来说是非常致命的。++- **请求体和响应体都无法被拦截**;++  由于请求第三方服务器是从微信的服务器发起的,而不是从小程序客户端发起的,所以本地代理无法拦截到这一次真实请求,这会导致开发者无法通过拦截请求的方式来区分获取线上环境和开发环境的数据,给开发调试带来麻烦。++  小程序内部接口的响应体类型都是 `application/octet-stream`,即数据格式未知,使本地代理无法正确解析。++- **微信服务器发起的请求没有提供区分线上版和开发版的参数,且没有提供用户 IP 等信息**;++如果这几个问题点都不会影响到你的场景,那么可以尝试开启预拉取能力,这对于小程序首屏渲染速度是质的提升。++### 跳转时预拉取++为了尽快获取到服务端数据,比较常见的做法是在页面 `onLoad` 钩子被触发时发起网络请求,但其实这并不是最快的方式。从发起页面跳转,到下一个页面 `onLoad` 的过程中,小程序需要完成一些环境初始化及页面实例化的工作,耗时大概为 300 ~ 400 毫秒。++实际上,我们可以在发起跳转前(如 `wx.navigateTo` 调用前),提前请求下一个页面的主接口并存储在全局 `Promise` 对象中,待下个页面加载完成后从 `Promise` 对象中读取数据即可。++这也是双线程模型所带来的优势之一,不同于多页面 web 应用在页面跳转/刷新时就销毁掉 window 对象。++### 分包预下载++如果开启了分包加载能力,在用户访问到分包内某个页面时,小程序才会开始下载对应的分包。当处于分包下载阶段时,页面会维持在 “白屏” 的启动态,这用户体验是比较糟糕的。++幸好,小程序提供了 [分包预下载](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/preload.html) 能力,开发者可以配置进入某个页面时预下载可能会用到的分包,避免在页面切换时僵持在 “白屏” 态。++### 非关键渲染数据延迟请求++这是关键渲染路径优化的其中一个思路,从缩短网络请求时延的角度加快首屏渲染完成时间。++> 关键渲染路径(Critical Rendering Path)是指在完成首屏渲染的过程中必须发生的事件。

「Critical Rendering Path」 加链接

TTImmortal

comment created time in 13 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。++## 怎么定义高性能?++提起互联网应用性能这个词,很多人在脑海中的词法解析就是,“是否足够快?”,似乎加载速度成为衡量系统性能的唯一指标。但这其实是不够准确的,试想一下,如果一个小程序加载速度非常快,用户花费很短时间就能看到页面的主体内容,但此时搜索框却无法输入内容,功能无法被流畅使用,用户可能就不会关心页面渲染有多快了。所以,我们不应该单纯考虑速度指标而忽略用户的感知体验,而应该全方位衡量用户在使用过程中能感知到的与应用加载相关的每个节点。++谷歌为 Web 应用定义了以用户为中心的性能指标体系,每个指标都与用户体验节点息息相关:++| 体验 | 指标 |+|-----|------|+| 页面能否正常访问?| 首次绘制 (`FP`)/首次内容绘制 (`FCP`) |+| 页面内容是否有用?| 首次有效绘制 (`FMP`) |+| 页面功能是否可用?| 可交互时间 (`TTI`) |++其中,“是否有用?” 这个问题是非常主观的,对于不同场景的系统可能会有完全不一样的回答,所以 `FMP` 是一个比较模糊的概念指标,不存在规范化的数值衡量。++小程序作为一个新的内容载体,衡量指标跟 Web 应用是非常类似的。对于大多数小程序而言,上述指标对应的含义为:++- FP:白屏时间;+- FMP:首屏渲染完成时间;+- TTI:页面加载完成时间;++综上,我们已基本确定了高性能的概念指标,接下来就是如何利用数值指标来描绘性能表现。++### 小程序官方性能指标++小程序官方针对小程序性能表现制订了权威的数值指标,主要围绕 **渲染表现**、**`setData` 数据量**、**元素节点数** 和 **网络请求延时** 这几个维度来给予定义(下面只列出部分关键指标):++- 首屏时间不超过 5 秒;+- 渲染时间不超过 500ms;+- 每秒调用 `setData` 的次数不超过 20 次;+- `setData` 的数据在 `JSON.stringify` 后不超过 256kb;+- 页面 WXML 节点少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个;+- 所有网络请求都在 1 秒内返回结果;++> 详见 [小程序性能评分规则](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html)++我们应该把这一系列的官方指标作为小程序的性能及格线,不断地打磨和提升小程序的整体体验,降低用户流失率。另外,这些指标会直接作为小程序体验评分工具的性能评分规则(体验评分工具会根据这些规则的权重和求和公式计算出体验得分)。++我们团队内部在官方性能指标的基础上,进一步浓缩优化指标系数,旨在对产品体验更高要求:++- 首屏时间不超过 2.5 秒;+- `setData` 的数据量不超过 100kb;+- 所有网络请求都在 1 秒内返回结果;+- 组件滑动、长列表滚动无卡顿感;++### 体验评分工具++小程序提供了 [体验评分工具(`Audits` 面板)](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/audits.html) 来测量上述的指标数据,其集成在开发者工具中,在小程序运行时实时检查相关问题点,并为开发者给出优化建议。++![体验评分面板](https://img13.360buyimg.com/ling/jfs/t1/92760/7/14417/406134/5e650dedE27aa858d/76b18b5f88de7288.png)++> 以上截图均来自小程序官方文档++体验评分工具是目前检测小程序性能问题最直接有效的途径,我们团队已经把体验评分作为页面/组件是否能达到精品门槛的重要考量手段之一。++### 小程序后台性能分析++我们知道,体验评分工具是在本地运行小程序代码时进行分析,但性能数据往往需要在真实环境和大数据量下才更有说服力。恰巧,**小程序管理平台** 和 **小程序助手** 为开发者提供了大量的真实数据统计。其中,性能分析面板从 **启动性能**、**运行性能** 和 **网络性能** 这三个维度分析数据,开发者可以根据客户端系统、机型、网络环境和访问来源等条件做精细化分析,非常具有考量价值。++![小程序助手性能分析](https://img20.360buyimg.com/ling/jfs/t1/95979/40/14592/773778/5e6500f1Eb92950cc/8c8bc8c4493a3c61.jpg)++> 其中,启动总耗时 = 小程序环境初始化 + 代码包加载 + 代码执行 + 渲染耗时++### 第三方测速系统++很多时候,宏观的耗时统计对于性能瓶颈点分析往往是杯水车薪,作用甚少,我们需要更细致地针对某个页面某些关键节点作测速统计,排查出暴露性能问题的代码区块,才能更有效地针对性优化。京喜小程序使用的是内部自研的测速系统,支持对地区、运营商、网络、客户端系统等多条件筛选,同时也支持数据可视化、同比分析数据等能力。京喜首页主要围绕 **页面 `onLoad`**、**`onReady`**、**数据加载完成**、**首屏渲染完成**、**各业务组件首次渲染完成** 等几个关键节点统计测速上报,旨在全链路监控性能表现。++![内部测速系统](https://img10.360buyimg.com/ling/jfs/t1/88143/36/14203/512179/5e620b0cEcedc020b/afe68aaf36311303.jpg)++> 另外,微信为开发者提供了 [测速系统](https://developers.weixin.qq.com/miniprogram/dev/framework/performanceReport/),也支持针对客户端系统、网络类型、用户地区等维度统计数据,有兴趣的可以尝试。++## 了解小程序底层架构++为了更好地为小程序制订性能优化措施,我们有必要先了解小程序的底层架构,以及与 web 浏览器的差异性。++微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。++而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了**双线程模型**:++- **视图层**:也就是 webview 线程,负责启用不同的 webview 来渲染不同的小程序页面;+- **逻辑层**:一个单独的线程执行 JS 代码,可以控制视图层的逻辑;++![双线程模型图](https://img20.360buyimg.com/ling/jfs/t1/88668/37/14434/308258/5e650263Ebf67d978/06dee7fb8293b482.png)++> 上图来自小程序官方开发指南++然而,**任何线程间的数据传输都是有延时的**,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线层之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。++作为小程序开发者,我们常常会被下面几个问题所困扰:++- 小程序启动慢;+- 白屏时间长;+- 页面渲染慢;+- 运行内存不足;++接下来,我们会结合小程序的底层架构分析出这些问题的根本原因,并针对性地给出解决方案。++## 小程序启动太慢?++小程序启动阶段,也就是如下图所示的展示加载界面的阶段。++![小程序加载界面](https://img13.360buyimg.com/ling/jfs/t1/88406/6/14244/64417/5e6219bdE1f9d7f95/07bcc55a6c147058.png)++在这个阶段中(包括启动前后的时机),微信会默默完成下面几项工作:++**1. 准备运行环境:**++  在小程序启动前,微信会先启动双线程环境,并在线程中完成小程序基础库的初始化和预执行。++  > 小程序基础库包括 WebView 基础库和 AppService 基础库,前者注入到视图层中,后者注入到逻辑层中,分别为所在层级提供其运行所需的基础框架能力。++**2. 下载小程序代码包:**++  在小程序初次启动时,需要下载编译后的代码包到本地。如果启动了小程序分包,则只有主包的内容会被下载。另外,代码包会保留在缓存中,后续启动会优先读取缓存。++**3. 加载小程序代码包:**++  小程序代码包下载好之后,会被加载到适当的线程中执行,基础库会完成所有页面的注册。++  > 在此阶段,主包内的所有页面 JS 文件及其依赖文件都会被自动执行。+  +  > 在页面注册过程中,基础库会调用页面 JS 文件的 Page 构造器方法,来记录页面的基础信息(包括初始数据、方法等)。++**4. 初始化小程序首页:**++  在小程序代码包加载完之后,基础库会根据启动路径找到首页,根据首页的基础信息初始化一个页面实例,并把信息传递给视图层,视图层会结合 WXML 结构、WXSS 样式和初始数据来渲染界面。++综合考虑,为了节省小程序的“点点点”时间(小程序的启动动画是三个圆点循环跑马灯),除了给每位用户发一台高配 5G 手机并顺带提供千兆宽带网络之外,还可以尽量 **控制代码包大小**,缩小代码包的下载时间。++### 无用文件、函数、样式剔除++经过多次业务迭代,无可避免的会存在一些弃用的组件/页面,以及不被调用的函数、样式规则,这些冗余代码会白白占据宝贵的代码包空间。而且,目前小程序的打包会将工程下所有文件都打入代码包内,并没有做依赖分析。++因此,我们需要及时地剔除不再使用的模块,以保证代码包空间利用率保持在较高水平。通过一些工具化手段可以有效地辅助完成这一工作。++- **文件依赖分析**++在小程序中,所有页面的路径都需要在小程序代码根目录 `app.json` 中被声明,类似地,自定义组件也需要在页面配置文件 `page.json` 中被声明。另外,WXML、WXSS 和 JS 的模块化都需要特定的关键字来声明依赖引用关系。++WXML 中的 `import` 和 `include`:++```jsx+<!-- A.wxml -->+<template name='A'>+  <text>{{text}}</text>+</template>++<!-- B.wxml -->+<import src="A.wxml"/>+<template is="A" data="{{text: 'B'}}"/>+```++```jsx+<!-- A.wxml -->+<text> A </text>++<!-- B.wxml -->+<include src="A.wxml"/>+<text> B </text>+```++WXSS 中的 `@import`:++```css+@import './A.wxss'+```++JS 中的 `require`/`import`:++```js+const A = require('./A')+```++所以,可以说小程序里的所有依赖模块都是有迹可循的,我们只需要利用这些关键字信息递归查找,遍历出文件依赖树,然后把没用的模块剔除掉。++- **JS、CSS Tree-Shaking**++JS Tree-Shaking 的原理就是借助 `Babel` 把代码编译成抽象语法树(AST),通过 AST 获取到函数的调用关系,从而把未被调用的函数方法剔除掉。不过这需要依赖 ES module,而小程序最开始是遵循 commonjs 规范的,这意味着是时候来一波“痛并快乐着”的改造了。++而 CSS 的 Tree-Shaking 可以利用 PurifyCSS 插件来完成。关于这两项技术,有兴趣的可以“谷歌一下”,这里就不铺开细讲了。++题外,京东的小程序团队已经把这一系列工程化能力集成在一套 CLI 工具中,有兴趣的可以看看这篇分享:[小程序工程化探索](https://mp.weixin.qq.com/s/_NSJTQ-4-8gTnwTVK-tn0A)。++### 减少代码包中的静态资源文件++小程序代码包最终会经过 GZIP 压缩放在 CDN 上,但 GZIP 压缩对于图片资源来说效果非常低。如 `JPG`、`PNG` 等格式文件,本身已经被压缩过了,再使用 GZIP 压缩有可能体积更大,得不偿失。所以,除了部分用于容错的图片必须放在代码包(譬如网络异常提示)之外,建议开发者把图片、视频等静态资源都放在 CDN 上。++> 需要注意,`Base64` 格式本质上是长字符串,和 CDN 地址比起来也会更占空间。++### 逻辑后移,精简业务逻辑++这是一个 “痛并快乐着” 的优化措施。“痛” 是因为需要给后台同学提改造需求,分分钟被打;“快乐” 则是因为享受删代码的过程,而且万一出 Bug 也不用背锅了...(开个玩笑)++通过让后台承担更多的业务逻辑,可以节省小程序前端代码量,同时线上问题还支持紧急修复,不需要经历小程序的提审、发布上线等繁琐过程。++总结得出,**一般不涉及前端计算的展示类逻辑,都可以适当做后移**。譬如京喜首页中的幕帘弹窗(如下图)逻辑,这里共有 10+ 种弹窗类型,以前的做法是前端从接口拉取 10+ 个不同字段,根据优先级和 “是否已展示”(该状态存储在本地缓存) 来决定展示哪一种,最后代码大概是这样的:++```js+// 检查每种弹窗类型是否已展示+Promise.all([+  check(popup_1),+  check(popup_2),+  // ...+  check(popup_n)+]).then(result => {+  // 优先级排序+  const queue = [{+    show: result.popup_1+    data: data.popup_1+  }, {+    show: result.popup_2+    data: data.popup_2+  }, +  // ...+  {+    show: result.popup_n+    data: data.popup_n+  }]+})+```++逻辑后移之后,前端只需负责拿幕帘字段做展示就可以了,代码变成这样:++```js+this.setData({+  popup: data.popup+})+```++![首页幕帘弹窗](https://img13.360buyimg.com/ling/jfs/t1/104567/26/14214/46840/5e621c48E4e33b7a9/440ca946478f53ac.jpg)++### 复用模板插件++京喜首页作为电商系统的门户,需要应对各类频繁的营销活动、升级改版等,同时也要满足不同用户属性的界面个性化需求(俗称 “千人千面”)。如何既能减少为应对多样化场景而产生的代码量,又可以提升研发效率,成为燃眉之急。++类似于组件复用的理念,我们需要提供更丰富的可配置能力,实现更高的代码复用度。参考小时候很喜欢玩的 “乐高” 积木玩具,**我们把首页模块的模板元素作颗粒度更细的划分,根据样式和功能抽象出一块块“积木”原料(称为插件元素)**。当首页模块在处理接口数据时,会启动插件引擎逐个装载插件,最终输出个性化的模板样式,整个流程就好比堆积木。当后续产品/运营需要新增模板时,只要在插件库中挑选插件排列组合即可,不需要额外新增/修改组件内容,也更不会产生难以维护的 `if` / `else` 逻辑,so easy~++当然,要完成这样的插件化改造免不了几个先决条件:++- **用户体验设计的统一**。如果设计风格总是天差地别的,强行插件化只会成为累赘。+- **服务端接口的统一**。同上,如果得浪费大量的精力来兼容不同模块间的接口字段差异,将会非常蛋疼。++下面为大家提供部分例程来辅助理解。其中,`use` 方法会接受各类处理钩子最终拼接出一个 `Function`,在对应模块处理数据时会被调用。++```js+// bi.helper.js++/**+ * 插件引擎+ * @param {function} options.formatName 标题处理钩子+ * @param {function} options.validList 数据校验器钩子+ */ +const use = options => data => format(data)++/**+ * 预置插件库+ */ +nameHelpers = {+  text: data => data.text,+  icon: data => data.icon+}+listHelpers = {+  single: list => list.slice(0, 1),+  double: list => list.slice(0, 2)+}++/**+ * “堆积木”+ */+export default {+  1000: use({+    formatName: nameHelpers.text,+    validList: listHelpers.single+  }),++  1001: use({+    formatName: nameHelpers.icon,+    validList: listHelpers.double+  })+}+```++```jsx+<!-- bi.wxml -->+<!-- 各模板节点实现 -->+<template name="renderName">+  <view wx:if="{{type === 'text'}}"> text </view>+  <view wx:elif="{{type === 'icon'}}"> icon </view>+</template>++<view class="bi__name">+  <template is="renderName" data="{{...data.name}"/>+</view>+```++```js+// bi.js+Component({+  ready() {+    // 根据 tpl 值选择解析函数+    const formatData = helper[data.tpl]+    this.setData({+      data: formatData(data)+    })+  }+})+```++### 分包加载++小程序启动时只会下载主包/独立分包,启用分包可以有效减少下载时间。(独立)分包需要遵循一些原则,详细的可以看官方文档:++- [使用分包](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html)+- [独立分包](https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/independent.html)++### 部分页面 h5 化++小程序提供了 [web-view](https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html) 组件,支持在小程序环境内访问网页。当实在无法在小程序代码包中腾出多余空间时,可以考虑降级方案 —— 把部分页面 h5 化。 ++> 小程序和 h5 的通信可以通过 JSSDK 或 postMessage 通道来实现。

「postMessage」 外链

TTImmortal

comment created time in 13 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。++## 怎么定义高性能?++提起互联网应用性能这个词,很多人在脑海中的词法解析就是,“是否足够快?”,似乎加载速度成为衡量系统性能的唯一指标。但这其实是不够准确的,试想一下,如果一个小程序加载速度非常快,用户花费很短时间就能看到页面的主体内容,但此时搜索框却无法输入内容,功能无法被流畅使用,用户可能就不会关心页面渲染有多快了。所以,我们不应该单纯考虑速度指标而忽略用户的感知体验,而应该全方位衡量用户在使用过程中能感知到的与应用加载相关的每个节点。++谷歌为 Web 应用定义了以用户为中心的性能指标体系,每个指标都与用户体验节点息息相关:++| 体验 | 指标 |+|-----|------|+| 页面能否正常访问?| 首次绘制 (`FP`)/首次内容绘制 (`FCP`) |+| 页面内容是否有用?| 首次有效绘制 (`FMP`) |+| 页面功能是否可用?| 可交互时间 (`TTI`) |++其中,“是否有用?” 这个问题是非常主观的,对于不同场景的系统可能会有完全不一样的回答,所以 `FMP` 是一个比较模糊的概念指标,不存在规范化的数值衡量。++小程序作为一个新的内容载体,衡量指标跟 Web 应用是非常类似的。对于大多数小程序而言,上述指标对应的含义为:++- FP:白屏时间;+- FMP:首屏渲染完成时间;+- TTI:页面加载完成时间;++综上,我们已基本确定了高性能的概念指标,接下来就是如何利用数值指标来描绘性能表现。++### 小程序官方性能指标++小程序官方针对小程序性能表现制订了权威的数值指标,主要围绕 **渲染表现**、**`setData` 数据量**、**元素节点数** 和 **网络请求延时** 这几个维度来给予定义(下面只列出部分关键指标):++- 首屏时间不超过 5 秒;+- 渲染时间不超过 500ms;+- 每秒调用 `setData` 的次数不超过 20 次;+- `setData` 的数据在 `JSON.stringify` 后不超过 256kb;+- 页面 WXML 节点少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个;+- 所有网络请求都在 1 秒内返回结果;++> 详见 [小程序性能评分规则](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html)++我们应该把这一系列的官方指标作为小程序的性能及格线,不断地打磨和提升小程序的整体体验,降低用户流失率。另外,这些指标会直接作为小程序体验评分工具的性能评分规则(体验评分工具会根据这些规则的权重和求和公式计算出体验得分)。++我们团队内部在官方性能指标的基础上,进一步浓缩优化指标系数,旨在对产品体验更高要求:++- 首屏时间不超过 2.5 秒;+- `setData` 的数据量不超过 100kb;+- 所有网络请求都在 1 秒内返回结果;+- 组件滑动、长列表滚动无卡顿感;++### 体验评分工具++小程序提供了 [体验评分工具(`Audits` 面板)](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/audits.html) 来测量上述的指标数据,其集成在开发者工具中,在小程序运行时实时检查相关问题点,并为开发者给出优化建议。++![体验评分面板](https://img13.360buyimg.com/ling/jfs/t1/92760/7/14417/406134/5e650dedE27aa858d/76b18b5f88de7288.png)++> 以上截图均来自小程序官方文档++体验评分工具是目前检测小程序性能问题最直接有效的途径,我们团队已经把体验评分作为页面/组件是否能达到精品门槛的重要考量手段之一。++### 小程序后台性能分析++我们知道,体验评分工具是在本地运行小程序代码时进行分析,但性能数据往往需要在真实环境和大数据量下才更有说服力。恰巧,**小程序管理平台** 和 **小程序助手** 为开发者提供了大量的真实数据统计。其中,性能分析面板从 **启动性能**、**运行性能** 和 **网络性能** 这三个维度分析数据,开发者可以根据客户端系统、机型、网络环境和访问来源等条件做精细化分析,非常具有考量价值。++![小程序助手性能分析](https://img20.360buyimg.com/ling/jfs/t1/95979/40/14592/773778/5e6500f1Eb92950cc/8c8bc8c4493a3c61.jpg)++> 其中,启动总耗时 = 小程序环境初始化 + 代码包加载 + 代码执行 + 渲染耗时++### 第三方测速系统++很多时候,宏观的耗时统计对于性能瓶颈点分析往往是杯水车薪,作用甚少,我们需要更细致地针对某个页面某些关键节点作测速统计,排查出暴露性能问题的代码区块,才能更有效地针对性优化。京喜小程序使用的是内部自研的测速系统,支持对地区、运营商、网络、客户端系统等多条件筛选,同时也支持数据可视化、同比分析数据等能力。京喜首页主要围绕 **页面 `onLoad`**、**`onReady`**、**数据加载完成**、**首屏渲染完成**、**各业务组件首次渲染完成** 等几个关键节点统计测速上报,旨在全链路监控性能表现。++![内部测速系统](https://img10.360buyimg.com/ling/jfs/t1/88143/36/14203/512179/5e620b0cEcedc020b/afe68aaf36311303.jpg)++> 另外,微信为开发者提供了 [测速系统](https://developers.weixin.qq.com/miniprogram/dev/framework/performanceReport/),也支持针对客户端系统、网络类型、用户地区等维度统计数据,有兴趣的可以尝试。++## 了解小程序底层架构++为了更好地为小程序制订性能优化措施,我们有必要先了解小程序的底层架构,以及与 web 浏览器的差异性。++微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。++而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了**双线程模型**:++- **视图层**:也就是 webview 线程,负责启用不同的 webview 来渲染不同的小程序页面;+- **逻辑层**:一个单独的线程执行 JS 代码,可以控制视图层的逻辑;++![双线程模型图](https://img20.360buyimg.com/ling/jfs/t1/88668/37/14434/308258/5e650263Ebf67d978/06dee7fb8293b482.png)++> 上图来自小程序官方开发指南++然而,**任何线程间的数据传输都是有延时的**,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线层之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。++作为小程序开发者,我们常常会被下面几个问题所困扰:++- 小程序启动慢;+- 白屏时间长;+- 页面渲染慢;+- 运行内存不足;++接下来,我们会结合小程序的底层架构分析出这些问题的根本原因,并针对性地给出解决方案。++## 小程序启动太慢?++小程序启动阶段,也就是如下图所示的展示加载界面的阶段。++![小程序加载界面](https://img13.360buyimg.com/ling/jfs/t1/88406/6/14244/64417/5e6219bdE1f9d7f95/07bcc55a6c147058.png)++在这个阶段中(包括启动前后的时机),微信会默默完成下面几项工作:++**1. 准备运行环境:**++  在小程序启动前,微信会先启动双线程环境,并在线程中完成小程序基础库的初始化和预执行。++  > 小程序基础库包括 WebView 基础库和 AppService 基础库,前者注入到视图层中,后者注入到逻辑层中,分别为所在层级提供其运行所需的基础框架能力。++**2. 下载小程序代码包:**++  在小程序初次启动时,需要下载编译后的代码包到本地。如果启动了小程序分包,则只有主包的内容会被下载。另外,代码包会保留在缓存中,后续启动会优先读取缓存。++**3. 加载小程序代码包:**++  小程序代码包下载好之后,会被加载到适当的线程中执行,基础库会完成所有页面的注册。++  > 在此阶段,主包内的所有页面 JS 文件及其依赖文件都会被自动执行。+  +  > 在页面注册过程中,基础库会调用页面 JS 文件的 Page 构造器方法,来记录页面的基础信息(包括初始数据、方法等)。++**4. 初始化小程序首页:**++  在小程序代码包加载完之后,基础库会根据启动路径找到首页,根据首页的基础信息初始化一个页面实例,并把信息传递给视图层,视图层会结合 WXML 结构、WXSS 样式和初始数据来渲染界面。++综合考虑,为了节省小程序的“点点点”时间(小程序的启动动画是三个圆点循环跑马灯),除了给每位用户发一台高配 5G 手机并顺带提供千兆宽带网络之外,还可以尽量 **控制代码包大小**,缩小代码包的下载时间。++### 无用文件、函数、样式剔除++经过多次业务迭代,无可避免的会存在一些弃用的组件/页面,以及不被调用的函数、样式规则,这些冗余代码会白白占据宝贵的代码包空间。而且,目前小程序的打包会将工程下所有文件都打入代码包内,并没有做依赖分析。++因此,我们需要及时地剔除不再使用的模块,以保证代码包空间利用率保持在较高水平。通过一些工具化手段可以有效地辅助完成这一工作。++- **文件依赖分析**++在小程序中,所有页面的路径都需要在小程序代码根目录 `app.json` 中被声明,类似地,自定义组件也需要在页面配置文件 `page.json` 中被声明。另外,WXML、WXSS 和 JS 的模块化都需要特定的关键字来声明依赖引用关系。++WXML 中的 `import` 和 `include`:++```jsx+<!-- A.wxml -->+<template name='A'>+  <text>{{text}}</text>+</template>++<!-- B.wxml -->+<import src="A.wxml"/>+<template is="A" data="{{text: 'B'}}"/>+```++```jsx+<!-- A.wxml -->+<text> A </text>++<!-- B.wxml -->+<include src="A.wxml"/>+<text> B </text>+```++WXSS 中的 `@import`:++```css+@import './A.wxss'+```++JS 中的 `require`/`import`:++```js+const A = require('./A')+```++所以,可以说小程序里的所有依赖模块都是有迹可循的,我们只需要利用这些关键字信息递归查找,遍历出文件依赖树,然后把没用的模块剔除掉。++- **JS、CSS Tree-Shaking**++JS Tree-Shaking 的原理就是借助 `Babel` 把代码编译成抽象语法树(AST),通过 AST 获取到函数的调用关系,从而把未被调用的函数方法剔除掉。不过这需要依赖 ES module,而小程序最开始是遵循 commonjs 规范的,这意味着是时候来一波“痛并快乐着”的改造了。

「commonjs」 to 「CommonJS」

TTImmortal

comment created time in 13 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。++## 怎么定义高性能?++提起互联网应用性能这个词,很多人在脑海中的词法解析就是,“是否足够快?”,似乎加载速度成为衡量系统性能的唯一指标。但这其实是不够准确的,试想一下,如果一个小程序加载速度非常快,用户花费很短时间就能看到页面的主体内容,但此时搜索框却无法输入内容,功能无法被流畅使用,用户可能就不会关心页面渲染有多快了。所以,我们不应该单纯考虑速度指标而忽略用户的感知体验,而应该全方位衡量用户在使用过程中能感知到的与应用加载相关的每个节点。++谷歌为 Web 应用定义了以用户为中心的性能指标体系,每个指标都与用户体验节点息息相关:++| 体验 | 指标 |+|-----|------|+| 页面能否正常访问?| 首次绘制 (`FP`)/首次内容绘制 (`FCP`) |+| 页面内容是否有用?| 首次有效绘制 (`FMP`) |+| 页面功能是否可用?| 可交互时间 (`TTI`) |++其中,“是否有用?” 这个问题是非常主观的,对于不同场景的系统可能会有完全不一样的回答,所以 `FMP` 是一个比较模糊的概念指标,不存在规范化的数值衡量。++小程序作为一个新的内容载体,衡量指标跟 Web 应用是非常类似的。对于大多数小程序而言,上述指标对应的含义为:++- FP:白屏时间;+- FMP:首屏渲染完成时间;+- TTI:页面加载完成时间;++综上,我们已基本确定了高性能的概念指标,接下来就是如何利用数值指标来描绘性能表现。++### 小程序官方性能指标++小程序官方针对小程序性能表现制订了权威的数值指标,主要围绕 **渲染表现**、**`setData` 数据量**、**元素节点数** 和 **网络请求延时** 这几个维度来给予定义(下面只列出部分关键指标):++- 首屏时间不超过 5 秒;+- 渲染时间不超过 500ms;+- 每秒调用 `setData` 的次数不超过 20 次;+- `setData` 的数据在 `JSON.stringify` 后不超过 256kb;+- 页面 WXML 节点少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个;+- 所有网络请求都在 1 秒内返回结果;++> 详见 [小程序性能评分规则](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html)++我们应该把这一系列的官方指标作为小程序的性能及格线,不断地打磨和提升小程序的整体体验,降低用户流失率。另外,这些指标会直接作为小程序体验评分工具的性能评分规则(体验评分工具会根据这些规则的权重和求和公式计算出体验得分)。++我们团队内部在官方性能指标的基础上,进一步浓缩优化指标系数,旨在对产品体验更高要求:++- 首屏时间不超过 2.5 秒;+- `setData` 的数据量不超过 100kb;+- 所有网络请求都在 1 秒内返回结果;+- 组件滑动、长列表滚动无卡顿感;++### 体验评分工具++小程序提供了 [体验评分工具(`Audits` 面板)](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/audits.html) 来测量上述的指标数据,其集成在开发者工具中,在小程序运行时实时检查相关问题点,并为开发者给出优化建议。++![体验评分面板](https://img13.360buyimg.com/ling/jfs/t1/92760/7/14417/406134/5e650dedE27aa858d/76b18b5f88de7288.png)++> 以上截图均来自小程序官方文档++体验评分工具是目前检测小程序性能问题最直接有效的途径,我们团队已经把体验评分作为页面/组件是否能达到精品门槛的重要考量手段之一。++### 小程序后台性能分析++我们知道,体验评分工具是在本地运行小程序代码时进行分析,但性能数据往往需要在真实环境和大数据量下才更有说服力。恰巧,**小程序管理平台** 和 **小程序助手** 为开发者提供了大量的真实数据统计。其中,性能分析面板从 **启动性能**、**运行性能** 和 **网络性能** 这三个维度分析数据,开发者可以根据客户端系统、机型、网络环境和访问来源等条件做精细化分析,非常具有考量价值。++![小程序助手性能分析](https://img20.360buyimg.com/ling/jfs/t1/95979/40/14592/773778/5e6500f1Eb92950cc/8c8bc8c4493a3c61.jpg)++> 其中,启动总耗时 = 小程序环境初始化 + 代码包加载 + 代码执行 + 渲染耗时++### 第三方测速系统++很多时候,宏观的耗时统计对于性能瓶颈点分析往往是杯水车薪,作用甚少,我们需要更细致地针对某个页面某些关键节点作测速统计,排查出暴露性能问题的代码区块,才能更有效地针对性优化。京喜小程序使用的是内部自研的测速系统,支持对地区、运营商、网络、客户端系统等多条件筛选,同时也支持数据可视化、同比分析数据等能力。京喜首页主要围绕 **页面 `onLoad`**、**`onReady`**、**数据加载完成**、**首屏渲染完成**、**各业务组件首次渲染完成** 等几个关键节点统计测速上报,旨在全链路监控性能表现。++![内部测速系统](https://img10.360buyimg.com/ling/jfs/t1/88143/36/14203/512179/5e620b0cEcedc020b/afe68aaf36311303.jpg)++> 另外,微信为开发者提供了 [测速系统](https://developers.weixin.qq.com/miniprogram/dev/framework/performanceReport/),也支持针对客户端系统、网络类型、用户地区等维度统计数据,有兴趣的可以尝试。++## 了解小程序底层架构++为了更好地为小程序制订性能优化措施,我们有必要先了解小程序的底层架构,以及与 web 浏览器的差异性。++微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。++而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了**双线程模型**:++- **视图层**:也就是 webview 线程,负责启用不同的 webview 来渲染不同的小程序页面;+- **逻辑层**:一个单独的线程执行 JS 代码,可以控制视图层的逻辑;++![双线程模型图](https://img20.360buyimg.com/ling/jfs/t1/88668/37/14434/308258/5e650263Ebf67d978/06dee7fb8293b482.png)++> 上图来自小程序官方开发指南++然而,**任何线程间的数据传输都是有延时的**,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线层之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。++作为小程序开发者,我们常常会被下面几个问题所困扰:++- 小程序启动慢;+- 白屏时间长;+- 页面渲染慢;+- 运行内存不足;++接下来,我们会结合小程序的底层架构分析出这些问题的根本原因,并针对性地给出解决方案。++## 小程序启动太慢?++小程序启动阶段,也就是如下图所示的展示加载界面的阶段。++![小程序加载界面](https://img13.360buyimg.com/ling/jfs/t1/88406/6/14244/64417/5e6219bdE1f9d7f95/07bcc55a6c147058.png)++在这个阶段中(包括启动前后的时机),微信会默默完成下面几项工作:++**1. 准备运行环境:**++  在小程序启动前,微信会先启动双线程环境,并在线程中完成小程序基础库的初始化和预执行。++  > 小程序基础库包括 WebView 基础库和 AppService 基础库,前者注入到视图层中,后者注入到逻辑层中,分别为所在层级提供其运行所需的基础框架能力。++**2. 下载小程序代码包:**++  在小程序初次启动时,需要下载编译后的代码包到本地。如果启动了小程序分包,则只有主包的内容会被下载。另外,代码包会保留在缓存中,后续启动会优先读取缓存。++**3. 加载小程序代码包:**++  小程序代码包下载好之后,会被加载到适当的线程中执行,基础库会完成所有页面的注册。++  > 在此阶段,主包内的所有页面 JS 文件及其依赖文件都会被自动执行。+  +  > 在页面注册过程中,基础库会调用页面 JS 文件的 Page 构造器方法,来记录页面的基础信息(包括初始数据、方法等)。++**4. 初始化小程序首页:**++  在小程序代码包加载完之后,基础库会根据启动路径找到首页,根据首页的基础信息初始化一个页面实例,并把信息传递给视图层,视图层会结合 WXML 结构、WXSS 样式和初始数据来渲染界面。++综合考虑,为了节省小程序的“点点点”时间(小程序的启动动画是三个圆点循环跑马灯),除了给每位用户发一台高配 5G 手机并顺带提供千兆宽带网络之外,还可以尽量 **控制代码包大小**,缩小代码包的下载时间。++### 无用文件、函数、样式剔除++经过多次业务迭代,无可避免的会存在一些弃用的组件/页面,以及不被调用的函数、样式规则,这些冗余代码会白白占据宝贵的代码包空间。而且,目前小程序的打包会将工程下所有文件都打入代码包内,并没有做依赖分析。++因此,我们需要及时地剔除不再使用的模块,以保证代码包空间利用率保持在较高水平。通过一些工具化手段可以有效地辅助完成这一工作。++- **文件依赖分析**++在小程序中,所有页面的路径都需要在小程序代码根目录 `app.json` 中被声明,类似地,自定义组件也需要在页面配置文件 `page.json` 中被声明。另外,WXML、WXSS 和 JS 的模块化都需要特定的关键字来声明依赖引用关系。++WXML 中的 `import` 和 `include`:++```jsx+<!-- A.wxml -->+<template name='A'>+  <text>{{text}}</text>+</template>++<!-- B.wxml -->+<import src="A.wxml"/>+<template is="A" data="{{text: 'B'}}"/>+```++```jsx+<!-- A.wxml -->+<text> A </text>++<!-- B.wxml -->+<include src="A.wxml"/>+<text> B </text>+```++WXSS 中的 `@import`:++```css+@import './A.wxss'+```++JS 中的 `require`/`import`:++```js+const A = require('./A')+```++所以,可以说小程序里的所有依赖模块都是有迹可循的,我们只需要利用这些关键字信息递归查找,遍历出文件依赖树,然后把没用的模块剔除掉。++- **JS、CSS Tree-Shaking**++JS Tree-Shaking 的原理就是借助 `Babel` 把代码编译成抽象语法树(AST),通过 AST 获取到函数的调用关系,从而把未被调用的函数方法剔除掉。不过这需要依赖 ES module,而小程序最开始是遵循 commonjs 规范的,这意味着是时候来一波“痛并快乐着”的改造了。++而 CSS 的 Tree-Shaking 可以利用 PurifyCSS 插件来完成。关于这两项技术,有兴趣的可以“谷歌一下”,这里就不铺开细讲了。

「PurifyCSS」 加外链

TTImmortal

comment created time in 13 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。++## 怎么定义高性能?++提起互联网应用性能这个词,很多人在脑海中的词法解析就是,“是否足够快?”,似乎加载速度成为衡量系统性能的唯一指标。但这其实是不够准确的,试想一下,如果一个小程序加载速度非常快,用户花费很短时间就能看到页面的主体内容,但此时搜索框却无法输入内容,功能无法被流畅使用,用户可能就不会关心页面渲染有多快了。所以,我们不应该单纯考虑速度指标而忽略用户的感知体验,而应该全方位衡量用户在使用过程中能感知到的与应用加载相关的每个节点。++谷歌为 Web 应用定义了以用户为中心的性能指标体系,每个指标都与用户体验节点息息相关:++| 体验 | 指标 |+|-----|------|+| 页面能否正常访问?| 首次绘制 (`FP`)/首次内容绘制 (`FCP`) |+| 页面内容是否有用?| 首次有效绘制 (`FMP`) |+| 页面功能是否可用?| 可交互时间 (`TTI`) |++其中,“是否有用?” 这个问题是非常主观的,对于不同场景的系统可能会有完全不一样的回答,所以 `FMP` 是一个比较模糊的概念指标,不存在规范化的数值衡量。++小程序作为一个新的内容载体,衡量指标跟 Web 应用是非常类似的。对于大多数小程序而言,上述指标对应的含义为:++- FP:白屏时间;+- FMP:首屏渲染完成时间;+- TTI:页面加载完成时间;++综上,我们已基本确定了高性能的概念指标,接下来就是如何利用数值指标来描绘性能表现。++### 小程序官方性能指标++小程序官方针对小程序性能表现制订了权威的数值指标,主要围绕 **渲染表现**、**`setData` 数据量**、**元素节点数** 和 **网络请求延时** 这几个维度来给予定义(下面只列出部分关键指标):++- 首屏时间不超过 5 秒;+- 渲染时间不超过 500ms;+- 每秒调用 `setData` 的次数不超过 20 次;+- `setData` 的数据在 `JSON.stringify` 后不超过 256kb;+- 页面 WXML 节点少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个;+- 所有网络请求都在 1 秒内返回结果;++> 详见 [小程序性能评分规则](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html)++我们应该把这一系列的官方指标作为小程序的性能及格线,不断地打磨和提升小程序的整体体验,降低用户流失率。另外,这些指标会直接作为小程序体验评分工具的性能评分规则(体验评分工具会根据这些规则的权重和求和公式计算出体验得分)。++我们团队内部在官方性能指标的基础上,进一步浓缩优化指标系数,旨在对产品体验更高要求:++- 首屏时间不超过 2.5 秒;+- `setData` 的数据量不超过 100kb;+- 所有网络请求都在 1 秒内返回结果;+- 组件滑动、长列表滚动无卡顿感;++### 体验评分工具++小程序提供了 [体验评分工具(`Audits` 面板)](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/audits.html) 来测量上述的指标数据,其集成在开发者工具中,在小程序运行时实时检查相关问题点,并为开发者给出优化建议。++![体验评分面板](https://img13.360buyimg.com/ling/jfs/t1/92760/7/14417/406134/5e650dedE27aa858d/76b18b5f88de7288.png)++> 以上截图均来自小程序官方文档++体验评分工具是目前检测小程序性能问题最直接有效的途径,我们团队已经把体验评分作为页面/组件是否能达到精品门槛的重要考量手段之一。++### 小程序后台性能分析++我们知道,体验评分工具是在本地运行小程序代码时进行分析,但性能数据往往需要在真实环境和大数据量下才更有说服力。恰巧,**小程序管理平台** 和 **小程序助手** 为开发者提供了大量的真实数据统计。其中,性能分析面板从 **启动性能**、**运行性能** 和 **网络性能** 这三个维度分析数据,开发者可以根据客户端系统、机型、网络环境和访问来源等条件做精细化分析,非常具有考量价值。++![小程序助手性能分析](https://img20.360buyimg.com/ling/jfs/t1/95979/40/14592/773778/5e6500f1Eb92950cc/8c8bc8c4493a3c61.jpg)++> 其中,启动总耗时 = 小程序环境初始化 + 代码包加载 + 代码执行 + 渲染耗时++### 第三方测速系统++很多时候,宏观的耗时统计对于性能瓶颈点分析往往是杯水车薪,作用甚少,我们需要更细致地针对某个页面某些关键节点作测速统计,排查出暴露性能问题的代码区块,才能更有效地针对性优化。京喜小程序使用的是内部自研的测速系统,支持对地区、运营商、网络、客户端系统等多条件筛选,同时也支持数据可视化、同比分析数据等能力。京喜首页主要围绕 **页面 `onLoad`**、**`onReady`**、**数据加载完成**、**首屏渲染完成**、**各业务组件首次渲染完成** 等几个关键节点统计测速上报,旨在全链路监控性能表现。++![内部测速系统](https://img10.360buyimg.com/ling/jfs/t1/88143/36/14203/512179/5e620b0cEcedc020b/afe68aaf36311303.jpg)++> 另外,微信为开发者提供了 [测速系统](https://developers.weixin.qq.com/miniprogram/dev/framework/performanceReport/),也支持针对客户端系统、网络类型、用户地区等维度统计数据,有兴趣的可以尝试。++## 了解小程序底层架构++为了更好地为小程序制订性能优化措施,我们有必要先了解小程序的底层架构,以及与 web 浏览器的差异性。++微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。++而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了**双线程模型**:++- **视图层**:也就是 webview 线程,负责启用不同的 webview 来渲染不同的小程序页面;+- **逻辑层**:一个单独的线程执行 JS 代码,可以控制视图层的逻辑;++![双线程模型图](https://img20.360buyimg.com/ling/jfs/t1/88668/37/14434/308258/5e650263Ebf67d978/06dee7fb8293b482.png)++> 上图来自小程序官方开发指南++然而,**任何线程间的数据传输都是有延时的**,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线层之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。++作为小程序开发者,我们常常会被下面几个问题所困扰:++- 小程序启动慢;+- 白屏时间长;+- 页面渲染慢;+- 运行内存不足;++接下来,我们会结合小程序的底层架构分析出这些问题的根本原因,并针对性地给出解决方案。++## 小程序启动太慢?++小程序启动阶段,也就是如下图所示的展示加载界面的阶段。++![小程序加载界面](https://img13.360buyimg.com/ling/jfs/t1/88406/6/14244/64417/5e6219bdE1f9d7f95/07bcc55a6c147058.png)++在这个阶段中(包括启动前后的时机),微信会默默完成下面几项工作:++**1. 准备运行环境:**++  在小程序启动前,微信会先启动双线程环境,并在线程中完成小程序基础库的初始化和预执行。++  > 小程序基础库包括 WebView 基础库和 AppService 基础库,前者注入到视图层中,后者注入到逻辑层中,分别为所在层级提供其运行所需的基础框架能力。++**2. 下载小程序代码包:**++  在小程序初次启动时,需要下载编译后的代码包到本地。如果启动了小程序分包,则只有主包的内容会被下载。另外,代码包会保留在缓存中,后续启动会优先读取缓存。++**3. 加载小程序代码包:**++  小程序代码包下载好之后,会被加载到适当的线程中执行,基础库会完成所有页面的注册。++  > 在此阶段,主包内的所有页面 JS 文件及其依赖文件都会被自动执行。+  +  > 在页面注册过程中,基础库会调用页面 JS 文件的 Page 构造器方法,来记录页面的基础信息(包括初始数据、方法等)。++**4. 初始化小程序首页:**++  在小程序代码包加载完之后,基础库会根据启动路径找到首页,根据首页的基础信息初始化一个页面实例,并把信息传递给视图层,视图层会结合 WXML 结构、WXSS 样式和初始数据来渲染界面。++综合考虑,为了节省小程序的“点点点”时间(小程序的启动动画是三个圆点循环跑马灯),除了给每位用户发一台高配 5G 手机并顺带提供千兆宽带网络之外,还可以尽量 **控制代码包大小**,缩小代码包的下载时间。++### 无用文件、函数、样式剔除++经过多次业务迭代,无可避免的会存在一些弃用的组件/页面,以及不被调用的函数、样式规则,这些冗余代码会白白占据宝贵的代码包空间。而且,目前小程序的打包会将工程下所有文件都打入代码包内,并没有做依赖分析。++因此,我们需要及时地剔除不再使用的模块,以保证代码包空间利用率保持在较高水平。通过一些工具化手段可以有效地辅助完成这一工作。++- **文件依赖分析**++在小程序中,所有页面的路径都需要在小程序代码根目录 `app.json` 中被声明,类似地,自定义组件也需要在页面配置文件 `page.json` 中被声明。另外,WXML、WXSS 和 JS 的模块化都需要特定的关键字来声明依赖引用关系。++WXML 中的 `import` 和 `include`:++```jsx+<!-- A.wxml -->+<template name='A'>+  <text>{{text}}</text>+</template>++<!-- B.wxml -->+<import src="A.wxml"/>+<template is="A" data="{{text: 'B'}}"/>+```++```jsx+<!-- A.wxml -->+<text> A </text>++<!-- B.wxml -->+<include src="A.wxml"/>+<text> B </text>+```++WXSS 中的 `@import`:++```css+@import './A.wxss'+```++JS 中的 `require`/`import`:++```js+const A = require('./A')+```++所以,可以说小程序里的所有依赖模块都是有迹可循的,我们只需要利用这些关键字信息递归查找,遍历出文件依赖树,然后把没用的模块剔除掉。++- **JS、CSS Tree-Shaking**++JS Tree-Shaking 的原理就是借助 `Babel` 把代码编译成抽象语法树(AST),通过 AST 获取到函数的调用关系,从而把未被调用的函数方法剔除掉。不过这需要依赖 ES module,而小程序最开始是遵循 commonjs 规范的,这意味着是时候来一波“痛并快乐着”的改造了。

Tree-Shaking 可以加外链

TTImmortal

comment created time in 13 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。++## 怎么定义高性能?++提起互联网应用性能这个词,很多人在脑海中的词法解析就是,“是否足够快?”,似乎加载速度成为衡量系统性能的唯一指标。但这其实是不够准确的,试想一下,如果一个小程序加载速度非常快,用户花费很短时间就能看到页面的主体内容,但此时搜索框却无法输入内容,功能无法被流畅使用,用户可能就不会关心页面渲染有多快了。所以,我们不应该单纯考虑速度指标而忽略用户的感知体验,而应该全方位衡量用户在使用过程中能感知到的与应用加载相关的每个节点。++谷歌为 Web 应用定义了以用户为中心的性能指标体系,每个指标都与用户体验节点息息相关:++| 体验 | 指标 |+|-----|------|+| 页面能否正常访问?| 首次绘制 (`FP`)/首次内容绘制 (`FCP`) |+| 页面内容是否有用?| 首次有效绘制 (`FMP`) |+| 页面功能是否可用?| 可交互时间 (`TTI`) |++其中,“是否有用?” 这个问题是非常主观的,对于不同场景的系统可能会有完全不一样的回答,所以 `FMP` 是一个比较模糊的概念指标,不存在规范化的数值衡量。++小程序作为一个新的内容载体,衡量指标跟 Web 应用是非常类似的。对于大多数小程序而言,上述指标对应的含义为:++- FP:白屏时间;+- FMP:首屏渲染完成时间;+- TTI:页面加载完成时间;++综上,我们已基本确定了高性能的概念指标,接下来就是如何利用数值指标来描绘性能表现。++### 小程序官方性能指标++小程序官方针对小程序性能表现制订了权威的数值指标,主要围绕 **渲染表现**、**`setData` 数据量**、**元素节点数** 和 **网络请求延时** 这几个维度来给予定义(下面只列出部分关键指标):++- 首屏时间不超过 5 秒;+- 渲染时间不超过 500ms;+- 每秒调用 `setData` 的次数不超过 20 次;+- `setData` 的数据在 `JSON.stringify` 后不超过 256kb;+- 页面 WXML 节点少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个;+- 所有网络请求都在 1 秒内返回结果;++> 详见 [小程序性能评分规则](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html)++我们应该把这一系列的官方指标作为小程序的性能及格线,不断地打磨和提升小程序的整体体验,降低用户流失率。另外,这些指标会直接作为小程序体验评分工具的性能评分规则(体验评分工具会根据这些规则的权重和求和公式计算出体验得分)。++我们团队内部在官方性能指标的基础上,进一步浓缩优化指标系数,旨在对产品体验更高要求:++- 首屏时间不超过 2.5 秒;+- `setData` 的数据量不超过 100kb;+- 所有网络请求都在 1 秒内返回结果;+- 组件滑动、长列表滚动无卡顿感;++### 体验评分工具++小程序提供了 [体验评分工具(`Audits` 面板)](https://developers.weixin.qq.com/miniprogram/dev/framework/audits/audits.html) 来测量上述的指标数据,其集成在开发者工具中,在小程序运行时实时检查相关问题点,并为开发者给出优化建议。++![体验评分面板](https://img13.360buyimg.com/ling/jfs/t1/92760/7/14417/406134/5e650dedE27aa858d/76b18b5f88de7288.png)++> 以上截图均来自小程序官方文档++体验评分工具是目前检测小程序性能问题最直接有效的途径,我们团队已经把体验评分作为页面/组件是否能达到精品门槛的重要考量手段之一。++### 小程序后台性能分析++我们知道,体验评分工具是在本地运行小程序代码时进行分析,但性能数据往往需要在真实环境和大数据量下才更有说服力。恰巧,**小程序管理平台** 和 **小程序助手** 为开发者提供了大量的真实数据统计。其中,性能分析面板从 **启动性能**、**运行性能** 和 **网络性能** 这三个维度分析数据,开发者可以根据客户端系统、机型、网络环境和访问来源等条件做精细化分析,非常具有考量价值。++![小程序助手性能分析](https://img20.360buyimg.com/ling/jfs/t1/95979/40/14592/773778/5e6500f1Eb92950cc/8c8bc8c4493a3c61.jpg)++> 其中,启动总耗时 = 小程序环境初始化 + 代码包加载 + 代码执行 + 渲染耗时++### 第三方测速系统++很多时候,宏观的耗时统计对于性能瓶颈点分析往往是杯水车薪,作用甚少,我们需要更细致地针对某个页面某些关键节点作测速统计,排查出暴露性能问题的代码区块,才能更有效地针对性优化。京喜小程序使用的是内部自研的测速系统,支持对地区、运营商、网络、客户端系统等多条件筛选,同时也支持数据可视化、同比分析数据等能力。京喜首页主要围绕 **页面 `onLoad`**、**`onReady`**、**数据加载完成**、**首屏渲染完成**、**各业务组件首次渲染完成** 等几个关键节点统计测速上报,旨在全链路监控性能表现。++![内部测速系统](https://img10.360buyimg.com/ling/jfs/t1/88143/36/14203/512179/5e620b0cEcedc020b/afe68aaf36311303.jpg)++> 另外,微信为开发者提供了 [测速系统](https://developers.weixin.qq.com/miniprogram/dev/framework/performanceReport/),也支持针对客户端系统、网络类型、用户地区等维度统计数据,有兴趣的可以尝试。++## 了解小程序底层架构++为了更好地为小程序制订性能优化措施,我们有必要先了解小程序的底层架构,以及与 web 浏览器的差异性。++微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。++而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了**双线程模型**:++- **视图层**:也就是 webview 线程,负责启用不同的 webview 来渲染不同的小程序页面;+- **逻辑层**:一个单独的线程执行 JS 代码,可以控制视图层的逻辑;++![双线程模型图](https://img20.360buyimg.com/ling/jfs/t1/88668/37/14434/308258/5e650263Ebf67d978/06dee7fb8293b482.png)++> 上图来自小程序官方开发指南++然而,**任何线程间的数据传输都是有延时的**,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线层之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。

「双线层」 -> 「双线程」

TTImmortal

comment created time in 13 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。

建议加上京喜的录屏,展示性能优化成果,开篇先声夺人。

如果有优化前后的录屏对比,就更到位了。

TTImmortal

comment created time in 13 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。++## 怎么定义高性能?++提起互联网应用性能这个词,很多人在脑海中的词法解析就是,“是否足够快?”,似乎加载速度成为衡量系统性能的唯一指标。但这其实是不够准确的,试想一下,如果一个小程序加载速度非常快,用户花费很短时间就能看到页面的主体内容,但此时搜索框却无法输入内容,功能无法被流畅使用,用户可能就不会关心页面渲染有多快了。所以,我们不应该单纯考虑速度指标而忽略用户的感知体验,而应该全方位衡量用户在使用过程中能感知到的与应用加载相关的每个节点。++谷歌为 Web 应用定义了以用户为中心的性能指标体系,每个指标都与用户体验节点息息相关:++| 体验 | 指标 |+|-----|------|+| 页面能否正常访问?| 首次绘制 (`FP`)/首次内容绘制 (`FCP`) |

英文缩写建议补上英文全称,如:The Time to Interactive (TTI)

TTImmortal

comment created time in 13 days

Pull request review commento2team/o2team.github.io

create 2020-03-25-high-performance-miniprogram.md

+title: 如何打造高性能小程序门户+subtitle: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+cover: https://img30.360buyimg.com/ling/jfs/t1/107441/21/10193/503864/5e7af547E709032c8/3c53683050b6ecf7.jpg+categories: 性能优化+tags:+  - 小程序+  - 性能优化+author:+  nick: 吖伟+  github_name: JunreyCen+wechat:+    share_cover: https://img12.360buyimg.com/ling/jfs/t1/91542/5/16578/250500/5e7b0830E38c0951c/248cdd7b136103c7.png+    share_title: 如何打造高性能小程序门户+    share_desc: 在小程序开发时,我们经常会被各种性能问题所困扰,本文基于京喜小程序首页的性能优化实践经验,从小程序的底层原理出发探究性能优化的解决方案,助力打造高性能精品小程序。+date: 2020-03-25 18:00:00+---+>本文阅读时长约15分钟。京喜小程序开发团队核心成员倾力之作,都是干货,读完一定会收获满满,请大家耐心阅读~++## 背景++京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。++首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。++除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。

Taro 的部分可以加上链接。 Taro

TTImmortal

comment created time in 13 days

push eventNervJS/taro-native-shell

Pines

commit sha 28518fbb2e39c4127a03558511ce7c100f7fc432

Update README.md

view details

push time in 13 days

push eventNervJS/taro-native-shell

Pines

commit sha ef397c50c245e0195e48f7e52e929a06ee385aa0

Update README.md

view details

push time in 13 days

issue commentNervJS/taro-native-shell

启动失败

首先确定一下有没有 pod install

其次,如果 RN 依赖是 0.55.4,需要尝试 @zouyang2015 的方法。

0.59.9 不需要。

edenleung

comment created time in 13 days

issue commentNervJS/taro-native-shell

unable to open file (in target "taroDemoTests" in project "taroDemo")

image 你的 RN 版本是 0.60+?

错误提示很详细,RN 0.60+ 会自动 link 原生依赖,如果你手动 link 了,那就 unlink 一下。

BinZhiZhu

comment created time in 13 days

issue commentPines-Cheng/blog

Chrome DevTools 原理、拓展与整合

Message Passing

由于内容脚本(content scripts)运行在 web 页面的上下文中,而不是在扩展(extension)中,因此它们通常需要某种方式与扩展(extension)的其余部分进行通信。 例如,RSS 阅读器扩展可能使用内容脚本(content scripts)检测页面上是否存在 RSS feed,然后通知后台页面以显示该页面的页面操作图标。

扩展(extension)和它们的内容脚本(content scripts)之间的通信是通过消息传递(message passing)进行的。 任何一方都可以 监听(listen) 从另一端发送的消息,并在同一个通道(channel)上作出响应。 消息可以包含任何有效的 JSON 对象(null、 boolean、 number、 string、 array 或 object)。 有一个用于一次性请求( [one-time requests](https://developer.chrome.com/extensions/messaging#simple) )的简单 API ,还有一个更复杂的 API,它们允许你使用长期存在的连接( long-lived connections)通过共享上下文(shared context)交换(exchanging)多个消息。 如果您知道另一个扩展的 ID,也可以将消息发送到该扩展,这在跨插件扩展(cross-extension messages)消息部分中有介绍。

另外还有 Sending messages from web pagesNative messaging

Simple one-time requests

Long-lived connections

Cross-extension messaging

Sending messages from web pages

Native messaging

Security considerations

Examples

Pines-Cheng

comment created time in 15 days

push eventNervJS/taro

aoarashi1988

commit sha 514b67054eae26a0bce891cb41e3a6719b653c2d

Merge pull request #1 from NervJS/2.x 合并更新

view details

yefeng

commit sha 730aaa6f4daf3a643f745412302fcf1abcf1024b

feat: 增加RN中的Taro.addInterceptor

view details

yefeng

commit sha 6a6ad9fafeb89c9f5a773b41864252edab8626c8

fix: 修复bug

view details

yefeng

commit sha cf0a061c12a7ef26ee718d32ff2e4ff055c30104

chore: 去除多余的warning

view details

Pines

commit sha cc83cf6dc2a0250f492d6e7d8c91a6d090dcde98

Merge pull request #5657 from aoarashi1988/2.x fix(rn): 补充完成RN中的Taro.addInterceptor

view details

push time in 15 days

PR merged NervJS/taro

fix: 补充完成RN中的Taro.addInterceptor React Native

<!-- 请务必阅读贡献者指南: https://github.com/NervJS/taro/blob/master/CONTRIBUTING.md -->

<!-- PULL REQUEST TEMPLATE --> <!-- (Update "[ ]" to "[x]" to check a box) -->

这个 PR 做了什么? (简要描述所做更改)

这个 PR 是什么类型? (至少选择一个)

  • [x] 错误修复(Bugfix) issue id #4393
  • [ ] 新功能(Feature)
  • [ ] 代码重构(Refactor)
  • [ ] TypeScript 类型定义修改(Typings)
  • [ ] 文档修改(Docs)
  • [ ] 代码风格更新(Code style update)
  • [ ] 其他,请描述(Other, please describe):

这个 PR 满足以下需求:

  • [ ] 提交到 master 分支
  • [ ] Commit 信息遵循 Angular Style Commit Message Conventions
  • [ ] 所有测试用例已经通过
  • [ ] 代码遵循相关包中的 .eslintrc, .tslintrc, .stylelintrc 所规定的规范
  • [x] 在本地测试可用,不会影响到其它功能

这个 PR 涉及以下平台:

  • [ ] 微信小程序
  • [ ] 支付宝小程序
  • [ ] 百度小程序
  • [ ] 头条小程序
  • [ ] QQ 轻应用
  • [ ] 快应用平台(QuickApp)
  • [ ] Web 平台(H5)
  • [x] 移动端(React-Native)

其它需要 Reviewer 或社区知晓的内容:

+15 -3

2 comments

2 changed files

aoarashi1988

pr closed time in 15 days

push eventNervJS/taro

QinDachang

commit sha 1a9b7f0a12ccdb980948adbf32ac886756200b24

fix: 同步RN与微信的网络请求参数

view details

Pines

commit sha 921738149f48bef8b2891a46caa2412196d25fb8

Merge pull request #5752 from qindachang/2.x fix: 同步ReactNative与微信的网络请求参数

view details

push time in 15 days

PR merged NervJS/taro

fix: 同步ReactNative与微信的网络请求参数 React Native

<!-- 请务必阅读贡献者指南: https://github.com/NervJS/taro/blob/master/CONTRIBUTING.md -->

<!-- PULL REQUEST TEMPLATE --> <!-- (Update "[ ]" to "[x]" to check a box) -->

这个 PR 做了什么? (简要描述所做更改)

此PR用于修复同步ReactNative与微信的网络请求参数,例如有下面一段代码:

  return new Promise((resolve) => {
    const complete = (result) => {
      const networkErrorCode = 600
      formatResult = {
        ...handleData(result.data || {}, result.statusCode || networkErrorCode),
        header: result.header || {},
        status: result.statusCode || networkErrorCode
      }
      handleTips(tips, formatResult)
      if (formatResult.code !== 0) {
        errorReport.api({ code: formatResult.code, msg: formatResult.msg, url: newUrl, params: data })
      }
      resolve(formatResult)
    }
    // @ts-ignore
    Taro.request({ ...opt, complete })
  })

此种写法在微信、支付宝小程序可以正常运行,但是无法在ReactNative上正常运行,原因是ReactNative没有接收complete等参数。

这个 PR 是什么类型? (至少选择一个)

  • [x] 错误修复(Bugfix) issue id #
  • [ ] 新功能(Feature)
  • [ ] 代码重构(Refactor)
  • [ ] TypeScript 类型定义修改(Typings)
  • [ ] 文档修改(Docs)
  • [ ] 代码风格更新(Code style update)
  • [ ] 其他,请描述(Other, please describe):

这个 PR 满足以下需求:

  • [ ] 提交到 master 分支
  • [x] Commit 信息遵循 Angular Style Commit Message Conventions
  • [x] 所有测试用例已经通过
  • [x] 代码遵循相关包中的 .eslintrc, .tslintrc, .stylelintrc 所规定的规范
  • [x] 在本地测试可用,不会影响到其它功能

这个 PR 涉及以下平台:

  • [ ] 微信小程序
  • [ ] 支付宝小程序
  • [ ] 百度小程序
  • [ ] 头条小程序
  • [ ] QQ 轻应用
  • [ ] 快应用平台(QuickApp)
  • [ ] Web 平台(H5)
  • [x] 移动端(React-Native)

其它需要 Reviewer 或社区知晓的内容:

+56 -21

1 comment

2 changed files

qindachang

pr closed time in 15 days

pull request commentNervJS/taro

fix: 同步ReactNative与微信的网络请求参数

非常棒~

qindachang

comment created time in 15 days

issue commentelectron/electron

setDevToolsWebContents is not working for OOPIF webview

BrowserView instead work fine.

zcbenz

comment created time in 15 days

push eventNervJS/taro

Pines-Cheng

commit sha d24e53fa341a04254f36138f494da664b16c04e8

fix: ci error

view details

push time in 17 days

push eventNervJS/taro

Pines-Cheng

commit sha 629d906cfb43884625c8782fdfdffaf01420c21b

fix(rn-runner): const import error

view details

push time in 17 days

issue openedPines-Cheng/think

一些管理工具与原则

SWOT 分析法

分析问题,做决策。

image

  • Strengths:优势
  • Weaknesses:劣势
  • Opportunities:机会
  • Threats:威胁

意义:帮您清晰地把握全局,分析自己在资源方面的优势与劣势,把握环境提供的机会,防范可能存在的风险与威胁,对我们的成功有非常重要的意义。

PDCA 循环规则

做事的方法。

image

  • Plan:制定目标与计划;
  • Do:任务展开,组织实施;
  • Check:对过程中的关键点和最终结果进行检查,反馈;
  • Action:纠正偏差,对成果进行标准化,并确定新的目标,制定下一轮计划。

意义:每一项工作,都是一个 PDCA 循环,都需要计划、实施、检查结果,并进一步进行改进,同时进入下一个循环,只有在日积月累的渐进改善中,才可能会有质的飞跃,才可能取得完善每一项工作,完善自己的人生。

5W2H 法

写文章,演讲,读书等也适用。

image

  • What:工作的内容和达成的目标;
  • Why:做这项工作的原因;
  • Who:参加这项工作的具体人员,以及负责人;
  • When:在什么时间、什么时间段进行工作;
  • Where:工作发生的地点 ;
  • How:用什么方法进行;
  • How much:需要多少成本?

意义:做任何工作都应该从5W2H来思考,这有助于我们的思路的条理化,杜绝盲目性。我们的汇报也应该用5W2H,能节约写报告及看报告的时间。

SMART原则

制定目标或任务。

image

  • Specific 具体的;
  • Measurable 可测量的;
  • Attainable 可达到的;
  • Relevant 相关的;
  • Time based 时间的

意义:人们在制定工作目标或者任务目标时,考虑一下目标与计划是不是 SMART 化的。只有具备SMART 化的计划才是具有良好可实施性的,也才能指导保证计划得以实现。

特别注明: 有的又如此解释此原则:

——S代表具体(Specific),指绩效考核要切中特定的工作指标,不能笼统;

——M代表可度量(Measurable),指绩效指标是数量化或者行为化的,验证这些绩效指标的数据或者信息是可以获得的;

——A代表可实现(Attainable),指绩效指标在付出努力的情况下可以实现,避免设立过高或过低的目标;

——R代表现实性(realistic),指绩效指标是实实在在的,可以证明和观察;

——T代表有时限(time bound),注重完成绩效指标的特定期限。

时间管理-重要与紧急

image

  1. 重要且紧急
  • 紧急状况
  • 迫切的问题
  • 限期完成的工作
  • 你不做其他人也不能做
  1. 重要不紧急
  • 准备工作
  • 预防措施
  • 价值观的澄清
  • 计划
  • 人际关系的建立
  • 真正的再创造
  • 增进自己的能力
  1. 紧急不重要
  • 造成干扰的事、电话、信件、报告会议
  • 许多迫在眉捷的急事
  • 符合别人期望的事
  1. 不重要不紧急
  • 忙碌琐碎的事
  • 广告函件
  • 电话
  • 逃避性活动
  • 等待时间

优先顺序=重要性*紧迫性在进行时间安排时,应权衡各种事情的优先顺序,要学会”弹钢琴“。

对工作要有前瞻能力,防患于未然,如果总是在忙于救火,那将使我们的工作永远处理被动之中。

任务分解法WBS

image

WBS分解的原则

将主体目标逐步细化分解,最底层的任务活动可直接分派到个人去完成;每个任务原则上要求分解到不能再细分为止。

WBS分解的方法

  • 至上而下与至下而上的充分沟通;

  • 一对一个别交流;

  • 小组讨论。

  • WBS分解的标准*:

  • 分解后的活动结构清晰;

  • 逻辑上形成一个大的活动;

  • 集成了所有的关键因素包含临时的里程碑和监控点;

  • 所有活动全部定义清楚。

意义:学会分解任务,只有将任务分解得足够细,您才能心里有数,您才能有条不紊地工作,您才能统筹安排您的时间表。

二八原则

image

巴列特定律:“总结果的80%是由总消耗时间中的20%所形成的。” 按事情的“重要程度”编排事务优先次序的准则是建立在“重要的少数与琐碎的多数”的原理的基础上。

举例说明:

  • 80%的销售额是源自20%的顾客;
  • 80%的电话是来自20%的朋友;
  • 80%的总产量来自20%的产品;
  • 80%的财富集中在20%的人手中;

这启示我们在工作中要善于抓主要矛盾,善于从纷繁复杂的工作中理出头绪,把资源用在最重要、最紧迫的事情上。

created time in 18 days

push eventNervJS/taro

Pines-Cheng

commit sha 6f13132d0594030ce7368ad8401c553bcc868461

docs: add join us

view details

push time in 19 days

issue commentzhaomenghuan/zhaomenghuan.github.io

赵梦欢 | 匠心博客

你好,我们最近也在开发 小程序平台开发者工具,能否交流一下。WX: Pines_Cheng

zhaomenghuan

comment created time in 19 days

issue openedPines-Cheng/blog

Integrating with DevTools

Chrome DevTools 本身就具备很好的拓展性。如果 DevTools 缺少一个你需要的特性,你可以找一找现成的扩展(extension),或者干脆自己写一个,同时你也可以选择将 DevTools 功能集成到你的应用中。

使用 DevTools 构建自定义解决方案有两种基本方式:

  • DevTools Extension:一个插入到 DevTools 中的 Chrome extension,可以增加功能和扩展用户界面。
  • Debugging Protocol Client:使用 Chrome remote debugging protocol 插入 Chrome 的底层调试支持的第三方应用程序。

下面的小节将讨论这两种方法。

DevTools Chrome extensions

DevTools UI 是一个嵌入在 Chrome 中的 web 应用程序。 Devtools 扩展使用 Chrome extensions system 为 DevTools 添加功能。DevTools 扩展可以向 DevTools 添加新的面板(panels),向 Elements 和 Sources 面板侧边栏(panel sidebar)添加新的窗格(panes),检查 resources 和 network 事件,以及在被 inspected 的浏览器选项卡(tab)中执行 JavaScript 表达式。

如果你想开发一个 DevTools 扩展:

image

image

有关 DevTools 扩展的实例列表,请参考 Sample DevTools Extensions。 这些示例包括许多可供参考的 Extensions 源码。

Debugging protocol clients

第三方应用程序,如 IDE、编辑器、持续集成框架和测试框架都可以与 Chrome 调试器集成,以调试代码、实时预览代码和 CSS 更改,并控制浏览器。 客户端使用 Chrome debugging protocol 与 Chrome 实例进行交互,该实例既可以在同一个系统上运行,也可以远程运行。

注意:目前,Chrome debugging protocol 每个 Page 只支持一个客户端。 因此,您可以使用 DevTools inspect 页面,或者使用第三方客户端,但两者不能同时 inspect。

有两种方法可以与调试协议集成:

  • 运行在 Chrome 中的应用程序(比如基于 Web 的 IDE)可以使用调试器模块 chrome.debugger 创建 Chrome 扩展,此模块允许扩展与调试器直接交互,绕过 DevTools 用户界面。参见:Using the debugger extension API
  • 其他应用程序可以使用 wire protocol 和 debugger 直接交互,该协议需要通过 WebSocket 连接交换 JSON 消息。

有关一些集成示例,请参考:Sample Debugging Protocol Clients

参考

created time in 21 days

push eventNervJS/taro

xuanping

commit sha a5348b40a9b8a2c80227461541fd4f9e92facf6a

feat(rn): RN 端支持setKeepScreenOn & setScreenBrightness & getScreenBrightness

view details

Pines

commit sha 0931c2876bc82e6079fcb7891fa86a75178735ed

Merge pull request #5601 from lxp-git/2.x feat(rn): RN 端支持setKeepScreenOn & setScreenBrightness & getScreenBrig…

view details

push time in 21 days

PR merged NervJS/taro

feat(rn): RN 端支持setKeepScreenOn & setScreenBrightness & getScreenBrig…

…htness

<!-- 请务必阅读贡献者指南: https://github.com/NervJS/taro/blob/master/CONTRIBUTING.md -->

<!-- PULL REQUEST TEMPLATE --> <!-- (Update "[ ]" to "[x]" to check a box) -->

这个 PR 做了什么? (简要描述所做更改)

feat(rn): RN 端支持setKeepScreenOn & setScreenBrightness & getScreenBrightness

这个 PR 是什么类型? (至少选择一个)

  • [ ] 错误修复(Bugfix) issue id #
  • [x] 新功能(Feature)
  • [ ] 代码重构(Refactor)
  • [ ] TypeScript 类型定义修改(Typings)
  • [ ] 文档修改(Docs)
  • [ ] 代码风格更新(Code style update)
  • [ ] 其他,请描述(Other, please describe):

这个 PR 满足以下需求:

  • [] 提交到 master 分支
  • [] Commit 信息遵循 Angular Style Commit Message Conventions
  • [] 所有测试用例已经通过
  • [] 代码遵循相关包中的 .eslintrc, .tslintrc, .stylelintrc 所规定的规范
  • [x] 在本地测试可用,不会影响到其它功能

这个 PR 涉及以下平台:

  • [ ] 微信小程序
  • [ ] 支付宝小程序
  • [ ] 百度小程序
  • [ ] 头条小程序
  • [ ] QQ 轻应用
  • [ ] 快应用平台(QuickApp)
  • [ ] Web 平台(H5)
  • [x] 移动端(React-Native)

其它需要 Reviewer 或社区知晓的内容:

+28 -50

1 comment

3 changed files

lxp-git

pr closed time in 21 days

pull request commentNervJS/taro

fix: 补充完成RN中的Taro.addInterceptor

link.addInterceptor?

aoarashi1988

comment created time in 21 days

issue commentNervJS/taro

taro-native-shell 壳子,android studio 启动报错,具体报错信息看内容。

  • You might have mismatching versions of React and the renderer (such as React DOM)
  • You might be breaking the Rules of Hooks
  • You might have more than one copy of React in the same app\nSee https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem

提示很清楚了啊,三种原因之一。

dxhuii

comment created time in 21 days

push eventNervJS/taro

Pines-Cheng

commit sha 2b440b8ba7afcca3b45f725010be7374943c3654

feat(rn): rn 端基于 webpack 编译重构

view details

Pines-Cheng

commit sha 05d4c116bb1acc7ce870d5ff86aa0b744e823e68

docs: update rn webpack config

view details

Pines-Cheng

commit sha d88948f496c67dbc629033cbeb9f6334306a97e7

feat(rn): build rn_bundle

view details

Pines-Cheng

commit sha f0c8983f774c83724ecf73fcba0a1fed35a73eee

docs(rn): 添加 rn 离线包打包详情

view details

push time in 22 days

issue commentNervJS/taro

React Native 端开发体验问题汇总

进 Taro RN 开发群可以统一回复凹凸小助手:RN

image

Pines-Cheng

comment created time in 22 days

push eventNervJS/taro

Pines

commit sha 7bbd3afd886195b6a2f547f405eb3c2d64211d76

Update README.md

view details

push time in 22 days

issue closedNervJS/taro

react-native壳子程序报错

问题描述 react-native壳子程序报错

复现步骤

1、git clone '壳子地址'
2、npm install
3、修改gradle配置文件,修改gradle为本地地址
//D:\2359634711\taroCjdy\native\taro-native-shell\android\gradle\wrapper\gradle-wrapper.properties

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=file:///D:/gradle/gradle-5.1.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
4、react-native run-android

期望行为 可以运行壳子demo

报错信息

image

系统信息

Taro v1.2 及以上版本已添加 taro info 命令,方便大家查看系统及依赖信息,运行该命令后将结果贴下面即可。

image

补充信息 可能是gradle版本问题?或者java版本问题?根据提示加上stacktrace会有很多报错,API过时等等。

closed time in 22 days

2359634711

issue closedNervJS/taro

[RN]建议npm run build:rn -- --watch时忽略cache

问题描述 今日为一个rn项目接入taro-router-rn,出现一些很诡异的问题,删了node_modules重装也不行。遇到的问题有:

  1. https://github.com/facebook/metro/issues/242
  2. https://github.com/facebook/react-native/issues/22675

最后发现应该是rn的cache问题。我们以前习惯是使用node node_modules/react-native/local-cli/cli.js start --reset-cache来启动metro的。taro在dev:rn最好补充这个,不在意那么一点启动时间啦。。。好过找问题找个把小时

系统信息

👽 Taro v1.3.21


  Taro CLI 1.3.21 environment info:
    System:
      OS: macOS 10.14.6
      Shell: 5.3 - /bin/zsh
    Binaries:
      Node: 11.9.0 - /usr/local/bin/node
      Yarn: 1.13.0 - /usr/local/bin/yarn
      npm: 6.9.0 - /usr/local/bin/npm
    npmGlobalPackages:
      typescript: 2.4.2

closed time in 22 days

tourze

issue commentNervJS/taro

[RN]建议npm run build:rn -- --watch时忽略cache

RN 的 launchPackager 命令没有暴露出 clear cache 的接口,这一步还是得手动做: node ./node_modules/react-native/local-cli/cli.js start --reset-cache

tourze

comment created time in 22 days

issue closedNervJS/taro

[rn]希望rn可以根据app所定义的页面来编译

期望行为 1.希望可以通过配置app中的pages与subPackages来编译页面文件 2.希望可以只编译rn或不带平台标记的命名文件 PS:目前的项目目录都编译

  • 操作系统: [e.g. Windows 10]
  • Taro 版本 [e.g. v.0.0.64]
  • Node.js 版本 [e.g. v9.0.0]
  • 报错平台 [rn]

closed time in 22 days

YuanQuan

issue commentNervJS/taro

[rn]希望rn可以根据app所定义的页面来编译

已实现,将在 2.1 中发布。

YuanQuan

comment created time in 22 days

issue commentNervJS/taro

taro打包apk问题

更新了打包 APP 的文档:

生成 React Native 离线包(jsbundle)

在打包成 ipa 或 apk 应用包之前,我们先要得到 React Native 离线包:

  • 方式一:
taro build --type rn

然后会生成 rn_bundle 目录,目录下会生成的 React Native 离线包,也就是打包后的 js。

└── rn_bundle (或 rn_temp/bundle)    ├── assets    ├── index.bundle    └── index.bundle.meta

  • 方式二:

使用 React Native 的 bundle 命令将 rn_temp 目录下的 RN 代码及资源打包成 jsbundle,命令如下:

node ./node_modules/react-native/local-cli/cli.js bundle --entry-file ./rn_temp/index.js --bundle-output ./rn_bundle/index.bundle --assets-dest ./rn_bundle --dev false

其中参数可以自行调整,--bundle-output 可以制定任意目录,然后将 bundle 目录下的文件 copy 到 taro-native-shell目录即可。

当然,也可以通过指定 --bundle-output 直接输出到 taro-native-shell目录。

通过 React Native 离线包构建 APP

一般来说,主要就三步:

  1. copy 生成的 jsbundle 到 React Native 工程对应目录下,如:taro-native-shell 工程或自己的 React Native 工程
  2. 修改对应的入口文件的 Bundle 名称,如 iOS 的 AppDelegate.m 文件,Android 的 MainApplication 文件
  3. 通过 React Native 命令或者对应的 IDE (Xcode/Android Studio) 打包 APP。

搜索 React Native 离线包 ,可以得到很多操作指引和教程,这里不再赘述,以下两篇可以借鉴:

somnuslbl

comment created time in 22 days

issue closedNervJS/taro

taro打包apk问题

<!-- 如果是提交 bug,请搜索文档和 issue,确认以下事项:

  • 该问题没有在其他 issue 和文档讨论到,不属于重复内容
  • 除了「 补充信息」外,每一都必填 不满足以上两点要求的 bug 报告,issue 会被直接关掉 请多多理解,您现在的不便将会使 Taro 开发者更高效地定位你的问题,修复你的问题 像你一样的 Taro 的使用者也可以通过搜索找到你提供的 bug,对各方都有很大好处 -->

问题描述

<!-- 站在其它人的角度尽可能清晰地、简洁地把问题描述清楚 --> taro按照官方文档打包apk,安装到真机上后闪退。编译后的rn_temp文件是需要放到taro-native-shell目录里的。

复现步骤

<!-- 复现问题的步骤。代码只贴截图,不贴文字会被视为无效issue -->

  1. <!-- 打开'...' -->
  2. <!-- 点击'....' -->
  3. <!-- 滚动到'....' -->
  4. <!-- 看见的现象... -->
/**
 * 这段注释后可以贴代码
 * 提供完整可复现的代码和整理好代码格式,有助于我们快速定位问题,节省你我时间
 * 代码提供不全或代码格式混乱的 issues 【有可能会被忽略】
 * 
 * 查看如何插入代码:https://coding.net/help/doc/project/markdown.html#i-5
 */


期望行为

<!-- 请在下一行用简洁清晰的语言描述你期望的行为 --> 建议把react-native的编译、打包做得更详细些

报错信息

<!-- 请在下一行贴上你的完整报错截图或文字 -->

系统信息

<!-- 使用taro info命令即可查看系统及依赖信息。将该命令运行结果贴下面即可。 -->

补充信息

<!-- (可选)根据你的调查研究,出现这个问题的原因可能在哪里? -->

<!-- 感谢您的热心反馈!别忘了用preview按钮预览结果再提交 -->

如果您有功能上的建议,可以提到 FeatHub

使用上的问题,欢迎在「Taro 社区」一起交流

closed time in 22 days

somnuslbl

issue commentNervJS/taro

脚手架build路径有问题

打离线包 bundle ,直接使用

iOS:

node ./node_modules/react-native/local-cli/cli.js bundle --entry-file /rn_temp/index.js --bundle-output ./bundle --platform ios --assets-dest ./bundle --dev false

Android:

node ./node_modules/react-native/local-cli/cli.js bundle --entry-file /rn_temp/index.js --bundle-output ./bundle --platform android --assets-dest ./bundle --dev false

打出离线包,更快更方便。

hujingyuki

comment created time in 22 days

issue closedNervJS/taro

0.59 500 duplicate symbols for architecture arm64

0.59 ios pod install之后执行archive会报这个错误 删除Libraries下的所有引用,用pod管理,之后能正常打包

closed time in 22 days

ShaoGongBra

issue closedNervJS/taro

next(3.0) vue版本如何支持app问题。

您好,想了解下未来3.0版本,如果用vue编写的话对应生成app的解决方案是? react有react native对应比较好理解。但vue版本呢?

closed time in 22 days

yruson

issue commentNervJS/taro

next(3.0) vue版本如何支持app问题。

未来 3.0 版本原生应用这边只支持 React。

yruson

comment created time in 22 days

issue commentNervJS/taro

样式全局污染

直接开启 CSS Modules 就能重现吗?

moseszhou

comment created time in 24 days

issue commentNervJS/taro

taro rn 问题

具体是碰到了什么问题呢?

icantunderstand

comment created time in a month

startedumijs/umi-ui

started time in a month

pull request commentNervJS/taro

fix(taro-components-rn): 修复Input组件

赞一下~👍

aijle

comment created time in a month

push eventNervJS/taro

Peter

commit sha 047d86f8b84635b34c1b1ed63a0861cfe060ec46

fix(taro-components-rn): 修复Input组件

view details

Pines

commit sha a91f376e92f94a6849555e76158a5d64c1107d60

Merge pull request #5521 from aijle/rn_input fix(taro-components-rn): 修复Input组件props.value改变后,onBlur事件参数不正确 和 Input高度问题

view details

push time in a month

PR merged NervJS/taro

fix(taro-components-rn): 修复Input组件

<!-- 请务必阅读贡献者指南: https://github.com/NervJS/taro/blob/master/CONTRIBUTING.md -->

<!-- PULL REQUEST TEMPLATE --> <!-- (Update "[ ]" to "[x]" to check a box) -->

这个 PR 做了什么? (简要描述所做更改) Input获取焦点时同步tmpValue,防止props.value改变后,onBlur事件参数不正确 _multiline, _autoHeight同时满足时才更新Input高度

这个 PR 是什么类型? (至少选择一个)

  • [x] 错误修复(Bugfix) issue id #
  • [ ] 新功能(Feature)
  • [ ] 代码重构(Refactor)
  • [ ] TypeScript 类型定义修改(Typings)
  • [ ] 文档修改(Docs)
  • [ ] 代码风格更新(Code style update)
  • [ ] 其他,请描述(Other, please describe):

这个 PR 满足以下需求:

  • [x] 提交到 master 分支
  • [x] Commit 信息遵循 Angular Style Commit Message Conventions
  • [x] 所有测试用例已经通过
  • [x] 代码遵循相关包中的 .eslintrc, .tslintrc, .stylelintrc 所规定的规范
  • [x] 在本地测试可用,不会影响到其它功能

这个 PR 涉及以下平台:

  • [ ] 微信小程序
  • [ ] 支付宝小程序
  • [ ] 百度小程序
  • [ ] 头条小程序
  • [ ] QQ 轻应用
  • [ ] 快应用平台(QuickApp)
  • [ ] Web 平台(H5)
  • [x] 移动端(React-Native)

其它需要 Reviewer 或社区知晓的内容:

+7 -3

0 comment

1 changed file

aijle

pr closed time in a month

pull request commentNervJS/taro

feat(rn): RN 端支持setKeepScreenOn & setScreenBrightness & getScreenBrig…

非常感谢~

lxp-git

comment created time in a month

issue commentNervJS/taro

RN端 swiper中包含input时,输入值后swiper会自动回到第一个tab页

这么说也有道理,我来看看。

hujingyuki

comment created time in a month

issue commentNervJS/taro

RN端 swiper中包含input时,输入值后swiper会自动回到第一个tab页

Swiper 套 Input 。。。 这样交互不适合吧。。

hujingyuki

comment created time in a month

issue closedNervJS/taro

如何写reactnative 原生代码 获取硬件信息 判断是否运行xp框架? 调用现有的reactnative库

如何写reactnative 原生代码 获取硬件信息 判断是否运行xp框架? 调用现有的reactnative库

closed time in a month

vanxv

issue commentNervJS/taro

如何写reactnative 原生代码 获取硬件信息 判断是否运行xp框架? 调用现有的reactnative库

参考这里:跨平台开发

react native 库本质上也是通过 JS 代码调用的,只需要将这部分代码写成跨端代码,在其他端就不会被编译了。

vanxv

comment created time in a month

issue commentNervJS/taro

0.59 500 duplicate symbols for architecture arm64

感谢反馈,看起来像是 pod 依赖安装的问题。

ShaoGongBra

comment created time in a month

issue closedNervJS/taro

请问ios如何禁止页面整体滚动?

<View className='page-root' style={{backgroundColor: 'blue'}}>
        <Header title='标题' />
// 这个组件是会自动占据剩余空间的滚动组件
        <Scroll>
        <View style={{height: Taro.pxTransform(10000)}} />
        </Scroll>
        <View style={{height: Taro.pxTransform(50),backgroundColor: 'red'}} />
</View>

显示效果如下,我可以拉着整个页面向下滑动,怎么禁止他,页面上有这个配置,但是似乎没有作用

static config = {
    disableScroll: true
  }

Simulator Screen Shot - iPhone 11 Pro Max - 2020-03-02 at 11 26 04

closed time in a month

ShaoGongBra

issue commentNervJS/taro

请问ios如何禁止页面整体滚动?

如果页面需要禁止滚动或者自定义滚动,可以在 Page 的 config 里面添加:disableScroll:true,这样就不会默认在页面上套 ScrollView 了。

ShaoGongBra

comment created time in a month

issue openedPines-Cheng/blog

SQL to MongoDB Mapping Chart

image

翻译原文:MongoDB 官方文档:SQL to MongoDB Mapping Chart

前言

很多开发者首次接触数据库(通常是在高校课堂)的概念,或者说接触第一个数据库,通常是 SQL 数据库,而现在,NoSQL 数据库越来越流行,很多原 SQL 数据的使用者难免有转向 NoSQL 的需求。而作为 NoSQL 数据库的代表,MongoDB 在社区越来越流行,生产环境的使用也日益广泛。

对于 SQL 转战 NoSQL的开发人员来说,最难的一步其实是将原有的 SQL 的概念和知识直接复用过来,最大化的减小学习的成本。

其实,这一步 MongoDB 官方已经为大家考虑到了,那就是在:MongoDB CRUD Operations > MongoDB CRUD Operations > SQL to MongoDB Mapping Chart,这篇文档非常好的总结了 SQL 对应 MongoDB 的术语和概念,还有可执行文件、SQL 语句/MongoDB 语句等,

可以说对于 SQL 数据库开发人员,如果理解了他们之间的对应关系,那么就一只脚就迈进了 MongoDB 的大门。

Terminology and Concepts

下表介绍了各种 SQL 术语和概念以及相应的 MongoDB 术语和概念.

SQL术语/概念 MongoDB 术语/概念
database database
table collection
row documentBSON document
column field
index index
table joins (表联接) $lookup, embedded documents (嵌入式文档)
primary key 指定任何唯一的列或者列组合作为主键 primary key 在 MongoDB 中, 主键自动设置为 _id 字段
aggregation (如:group by) aggregation pipeline (聚合管道)参考:SQL to Aggregation Mapping Chart
SELECT INTO NEW_TABLE $out 参考: SQL to Aggregation Mapping Chart
MERGE INTO TABLE $merge (从MongoDB 4.2开始可用) 参考:SQL to Aggregation Mapping Chart
transactions transactions

TIP

在许多情况下, 非规范化数据模型(嵌入式文档和数组) denormalized data model (embedded documents and arrays) 将继续是您数据和用例的最佳选择,而不是多文档事务. 也就是说,对于许多场景,对数据进行适当的建模将最大限度地减少对 多文档事务(multi-document transactions)的需求。

Executables

下表显示了一些数据库可执行文件和相应的 MongoDB 可执行文件。 这张表并不是详尽无遗的。

MongoDB MySQL Oracle Informix DB2
Database Server mongod mysqld oracle IDS DB2 Server
Database Client mongo mysql sqlplus DB-Access DB2 Client

Examples

下表显示了各种 SQL 语句和相应的 MongoDB 语句。 表中的例子假定以下条件:

  • Sql 示例假设一个名为 people 的表。
  • MongoDB 的示例假定一个名为 people 的集合包含以下原型的文档:
{
  _id: ObjectId("509a8fb2f3f4948bd2f983a0"),
  user_id: "abc123",
  age: 55,
  status: 'A'
}

Create and Alter

CREATE TABLE

  • SQL 模式语句:
CREATE TABLE people (
    id MEDIUMINT NOT NULL
        AUTO_INCREMENT,
    user_id Varchar(30),
    age Number,
    status char(1),
    PRIMARY KEY (id)
)
  • MongoDB 模式语句:
db.people.insertOne( {
    user_id: "abc123",
    age: 55,
    status: "A"
 } )

在第一个 insertOne()insertMany() 操作上隐式创建。 如果没有指定 _id 字段,则自动添加主键 _id

但是,您也可以显式地创建一个集合:

db.createCollection("people")

ALTER TABLE / ADD

  • SQL模式语句:
ALTER TABLE people
ADD join_date DATETIME
  • MongoDB 模式语句:
db.people.updateMany(
    { },
    { $set: { join_date: new Date() } }
)

集合不描述或强制执行其文档的结构;也就是说,在集合级别上没有结构上的改变。

但是,在文档级别,updateMany() 操作可以使用 $set 操作符向现有文档添加字段。

ALTER TABLE / DROP COLUMN

  • SQL模式语句:
ALTER TABLE people
DROP COLUMN join_date
  • MongoDB 模式语句:
db.people.updateMany(
    { },
    { $unset: { "join_date": "" } }
)

集合不描述或强制执行其文档的结构;也就是说,在集合级别上没有结构上的改变。

但是,在文档级别,updateMany() 操作可以使用 $unset 操作符从文档中删除字段。

CREATE INDEX

  • SQL 模式语句:
CREATE INDEX idx_user_id_asc
ON people(user_id)
  • MongoDB 模式语句:
db.people.createIndex( { user_id: 1 } )

CREATE INDEX / Multi

  • SQL模式语句:
CREATE INDEX
       idx_user_id_asc_age_desc
ON people(user_id, age DESC)
  • MongoDB 模式语句:
db.people.createIndex( { user_id: 1, age: -1 } )

DROP TABLE

  • SQL模式语句:
DROP TABLE people
  • MongoDB 模式语句:
db.people.drop()

更多有关使用的方法和操作符的详细信息,请参阅:

另见:

Insert

下表显示了与向表中插入记录相关的各种 SQL 语句以及相应的 MongoDB 语句。

  • SQL INSERT 语句
INSERT INTO people(user_id,
                  age,
                  status)
VALUES ("bcd001",
        45,
        "A")
  • Mongodb insertOne() 语句
db.people.insertOne(
   { user_id: "bcd001", age: 45, status: "A" }
)

有关更多信息,请参见 db.collection.insertOne()

另见:

Select

下表显示了与从表中读取记录相关的各种 SQL 语句以及相应的 MongoDB 语句。

NOTE:

find() 方法总是包含返回文档中的 _id 字段,除非通过 projection 特别排除。 下面的一些 SQL 查询可能包含一个 _id 字段来反映这一点,即使该字段没有包含在相应的 find() 查询中。

SELECT ... WHERE

  • SQL 语句

SELECT user_id, status
FROM people
WHERE status = "A"
  • Mongodb 语句
db.people.find(
    { status: "A" },
    { user_id: 1, status: 1, _id: 0 }
)

SELECT ... AND

  • SQL 语句
SELECT *
FROM people
WHERE age > 25
AND   age <= 50
  • Mongodb 语句
db.people.find(
   { age: { $gt: 25, $lte: 50 } }
)

SELECT ... OR

  • SQL 语句
SELECT *
FROM people
WHERE status = "A"
OR age = 50
  • Mongodb 语句
db.people.find(
    { $or: [ { status: "A" } , { age: 50 } ] }
)

SELECT ... LIKE

  • SQL 语句
FROM people
WHERE user_id like "%bc%"
  • Mongodb 语句
db.people.find( { user_id: /bc/ } )

-or-

db.people.find( { user_id: { $regex: /bc/ } } )

SELECT ... OEDER BY

  • SQL 语句
SELECT *
FROM people
WHERE status = "A"
ORDER BY user_id ASC
  • Mongodb 语句
db.people.find( { status: "A" } ).sort( { user_id: 1 } )

SELECT ... COUNT

  • SQL 语句
SELECT COUNT(user_id)
FROM people
  • Mongodb 语句
db.people.count( { user_id: { $exists: true } } )

or

db.people.find( { user_id: { $exists: true } } ).count()

SELECT DISTINCT

  • SQL 语句
SELECT DISTINCT(status)
FROM people
  • Mongodb 语句

db.people.aggregate( [ { $group : { _id : "$status" } } ] )

或者,对于不同的不超过 [BSON 大小限制](https://docs.mongodb.com/manual/reference/limits/#limit-bson-document-size) 的值集

db.people.distinct( "status" )

SELECT ... LIMIT SKIP

  • SQL 语句
SELECT *
FROM people
LIMIT 5
SKIP 10
  • Mongodb 语句
db.people.find().limit(5).skip(10)

EXPLAIN SELECT

  • SQL 语句
EXPLAIN SELECT *
FROM people
WHERE status = "A"
  • Mongodb 语句
db.people.find( { status: "A" } ).explain()

有关所使用的方法的详细信息,请参阅:

运算符(operators):

另见:

Update Records

下面显示了与更新表中现有记录相关的各种 SQL 语句以及相应的 MongoDB 语句。

UPDATE ... SET

  • SQL 语句
UPDATE people
SET status = "C"
WHERE age > 25
  • Mongodb 语句
db.people.updateMany(
   { age: { $gt: 25 } },
   { $set: { status: "C" } }
)

UPDATE ... INC

  • SQL 语句
UPDATE people
SET age = age + 3
WHERE status = "A"
  • Mongodb 语句
db.people.updateMany(
   { status: "A" } ,
   { $inc: { age: 3 } }
)

有关示例中使用的方法和运算符的详细信息,请参阅:

另见:

Delete Records

下面显示了与从表中删除记录相关的各种 SQL 语句以及相应的 MongoDB 语句。

DELETE WHERE

  • SQL 语句
DELETE FROM people
WHERE status = "D"
  • Mongodb 语句
db.people.deleteMany( { status: "D" } )

DELETE

  • SQL 语句
DELETE FROM people
  • Mongodb 语句
db.people.deleteMany({})

有关更多信息,请参见 db.collection.deleteMany()

看到这里,想必大家应该已经将脑海中 SQL 相关的知识和 MongoDB 一一对应起来了,那么剩下的就需要大家多多的实践,深入挖掘。

但是无论何时,都要记住,MongoDB 官方文档 绝对是你能找到的最权威、最全面的资料。

created time in a month

issue commentNervJS/taro

RN组件Picker有bug

我来看看

hujingyuki

comment created time in a month

issue commentNervJS/taro

RichText组件不显示内容

没有哪个组件,麻烦详细一点。

ShaoGongBra

comment created time in a month

issue commentNervJS/taro

RN端Swiper组件无法触发onChange事件

能贴一下代码吗?

hujingyuki

comment created time in a month

issue openedPines-Cheng/blog

微前端相关

如果是 widget 级别,那么微前端跟业务组件的区别在哪里?微前端到底是因何而生?

微前端的核心价值在于 "技术栈无关",这才是它诞生的理由。

而对于 ToB 应用而言,3~5 年太常见了好吗!去看看阿里云最早的那些产品的控制台,去看看那些电信软件、银行软件,哪个不是 10 年+ 的寿命?企业软件的升级有多痛这个我就不多说了。所以大部分企业应用都会有一个核心的诉求,就是如何确保我的遗产代码能平滑的迁移,以及如何确保我在若干年后还能用上时下热门的技术栈?

对很多做 ToB 领域的中小企业而言,这样的系统可能是他们安身立命之本,不是能说扔就扔的,他们承担不了那么高的试错成本。

我们只需要在主系统构造一个足够轻量的基座,然后让各子应用按照共同的协议去实现即可。这个协议可以包括,主应用应该如何加载子应用,以及子应用如何被主应用感知、调度,应用之间如何通信等。

这个协议不应该包括,子应用要如何确保隔离性、安全性,也就是子应用除了实现一些较为简单的协议之外,跟开发一个正常的 spa 应用应该没有任何差别,包括不应该有 开发、构建、发布 等流程上的侵入。只要子应用实现了这几个协议,其他的东西怎么玩,我们都不需要关心或干预。

这样的话,其实整个系统就变成了一个真正的、基于运行时的插件平台了。

有一个非常合适的例子,我们通常是怎么看待可视化搭建平台的?我想大部分 pro code 玩家都是不太敢轻易尝试这个方式去开发自己的核心产品的,原因是什么呢?很简单,不可控。我的产品的上限由平台决定而不是我自己的 coding 能力决定,这就很要命了。尤其是一些核心模块,后面我想做一些个性性的改造可能都支持不了。但是如果有了微前端机制呢,只需要搭建平台去实现相关的协议,平台产出的页面就能很轻易的被集成到我们自己的应用里了。我们开发时可以选择需要强控制的页面自己写,边缘页面用可视化生成即可,完全没有任何心理负担。

为什么我认为"技术栈无关"才是微前端的初衷?

我们听到了很多不同团队的分享中,关于微前端带来的各种业务提升、产品提升的价值。比如产品的自由组合能力,比如以 widget 这种可视化方式直接输出产品的能力等等,将这些价值视作微前端诞生的理由。

但我对此一直保持的观点是,微前端首先解决的,是如何解构巨石应用,从而解决巨石应用随着技术更迭、产品升级、人员流动带来的工程上的问题。解构之后还需要再重组,重组的过程中我们就会碰到各种 隔离性、依赖去重、通信、应用编排 等问题。在解决了这些问题之后,才是产品的自由组合、widget 输出等能力。同时由于有了前者能力的铺垫和加持,这些产品上的价值提升也就变得很自然和容易。

总结的很精确。「空间分离带来的协作问题」是在一个规模可观的应用的场景下会明显出现的问题,而「时间延续带来的升级维护」几乎是所有年龄超过 3 年的 web 应用都会存在的问题。

玉伯提到:

今天看各 BU 的业务问题,微前端的前提,还是得有主体应用,然后才有微组件或微应用,解决的是可控体系下的前端协同开发问题(含空间分离带来的协作和时间延续带来的升级维护)

微前端方案正确的架构姿势

既然「技术栈无关」是微前端的核心价值,那么整个架构方案的实现上,都应该秉持这一原则,任何违背这一原则的做法都应该被摒弃。

「技术栈无关」是架构上的准绳,具体到实现时,对应的就是:应用之间不应该有任何直接或间接的技术栈、依赖、以及实现上的耦合

所以我认为正确的微前端方案的目标应该是:方案上跟使用 iframe 做微前端一样简单,同时又解决了 iframe 带来的各种体验上的问题。

理想状态下,以此为目标的微前端应用,是自动具备流通能力的,且这个流通能力不会因为主应用的实现升级而丧失(也就是说在 19 年能接入主应用的微前端应用,到了 2025 年也应该能正常接入正常运行,并同样保有在不同主应用间流通的能力)。

参考

created time in a month

issue commentkuitos/kuitos.github.io

微前端的核心价值

基于阿里云平台和体量来搞,想象空间很大。

kuitos

comment created time in a month

issue commentNervJS/taro

View组件嵌套View,里面的View的空白处无法获得点击事件

这个确实奇怪🤔

ShaoGongBra

comment created time in a month

issue commentNervJS/taro

脚手架build路径有问题

正在 fix

hujingyuki

comment created time in a month

push eventNervJS/taro

Pines-Cheng

commit sha cd224a4f8a831a2394aa6fb251e503f4e087482a

fix(cli): 旧项目升级未自动更新RN版本

view details

push time in a month

delete branch Pines-Cheng/fabric

delete branch : patch-1

delete time in 2 months

issue commentNervJS/taro

rn上Block组件会被编译成View组件,会有样式问题

RN 端对于 Block 组件也没有想到更好的处理方法。

ShaoGongBra

comment created time in 2 months

PR opened umijs/fabric

fix(eslint): add .d.ts in import/resolver

to fix error Unable to resolve path to module when import types from *.d.ts

+2 -2

0 comment

1 changed file

pr created time in 2 months

push eventPines-Cheng/fabric

Pines

commit sha 2686049165bbc7b856fd8747f2f15ae10d474914

fix(eslint): add .d.ts in import/resolver to fix error Unable to resolve path to module when import types from *.d.ts

view details

push time in 2 months

fork Pines-Cheng/fabric

💪一些保证代码质量的配置

fork in 2 months

IssuesEvent

push eventNervJS/taro

Pines-Cheng

commit sha cb5330234a65142baef03a9ed060d04608b8863a

fix(rn): -port 参数对于RN调试失效 close #5413

view details

push time in 2 months

issue closedNervJS/taro

[taro-2.0.1]--port 参数对于RN调试失效

<!-- 如果是提交 bug,请搜索文档和 issue,确认以下事项:

  • 该问题没有在其他 issue 和文档讨论到,不属于重复内容
  • 除了「 补充信息」外,每一都必填 不满足以上两点要求的 bug 报告,issue 会被直接关掉 请多多理解,您现在的不便将会使 Taro 开发者更高效地定位你的问题,修复你的问题 像你一样的 Taro 的使用者也可以通过搜索找到你提供的 bug,对各方都有很大好处 -->

问题描述

"build:rn": "npm run env:prod && ./node_modules/@tarojs/cli/bin/taro build --type rn --port 8082 --watch",

调用上面的命令无法修改debug模式的端口。 <!-- 站在其它人的角度尽可能清晰地、简洁地把问题描述清楚 -->

复现步骤

<!-- 复现问题的步骤。代码只贴截图,不贴文字会被视为无效issue -->

/**
 * 这段注释后可以贴代码
 * 提供完整可复现的代码和整理好代码格式,有助于我们快速定位问题,节省你我时间
 * 代码提供不全或代码格式混乱的 issues 【有可能会被忽略】
 * 
 * 查看如何插入代码:https://coding.net/help/doc/project/markdown.html#i-5
 */

npm run build:rn

期望行为

<!-- 请在下一行用简洁清晰的语言描述你期望的行为 --> 能够设置rn的host server的启动端口,我查过cli的代码,rn编译启动的时候并没有把port参数传入

  startServerInNewWindow({ appPath});

function startServerInNewWindow({ port = 8081, appPath }) {

.......
}

报错信息

<!-- 请在下一行贴上你的完整报错截图或文字 -->

系统信息

<!-- 使用taro info命令即可查看系统及依赖信息。将该命令运行结果贴下面即可。 --> Taro v2.0.1

Taro CLI 2.0.1 environment info: System: OS: macOS 10.15.3 Shell: 5.7.1 - /bin/zsh Binaries: Node: 12.14.1 - /usr/local/bin/node Yarn: 1.21.1 - /usr/local/bin/yarn npm: 6.13.7 - /usr/local/bin/npm npmPackages: @tarojs/cli: 2.0.1 => 2.0.2 @tarojs/components: 2.0.1 => 2.0.0-beta.13 @tarojs/components-qa: 2.0.1 => 2.0.0-beta.13 @tarojs/components-rn: 2.0.1 => 2.0.2 @tarojs/mini-runner: 2.0.1 => 2.0.0-beta.13 @tarojs/plugin-less: 2.0.1 => 2.0.2 @tarojs/router: 2.0.1 => 2.0.0-beta.13 @tarojs/taro: 2.0.1 => 2.0.0-beta.13 @tarojs/taro-alipay: 2.0.1 => 2.0.0-beta.13 @tarojs/taro-h5: 2.0.1 => 2.0.0-beta.13 @tarojs/taro-qq: 2.0.1 => 2.0.0-beta.13 @tarojs/taro-quickapp: 2.0.1 => 2.0.0-beta.13 @tarojs/taro-redux-rn: 2.0.1 => 2.0.2 @tarojs/taro-rn: 2.0.1 => 2.0.2 @tarojs/taro-router-rn: 2.0.1 => 2.0.2 @tarojs/taro-swan: 2.0.1 => 2.0.0-beta.13 @tarojs/taro-tt: 2.0.1 => 2.0.0-beta.13 @tarojs/taro-weapp: 2.0.1 => 2.0.0-beta.13 @tarojs/webpack-runner: 2.0.1 => 2.0.0-beta.13 eslint-config-taro: 2.0.1 => 2.0.0-beta.13 eslint-plugin-taro: 2.0.1 => 2.0.0-beta.13 nerv-devtools: ^1.5.6 => 1.5.6 nervjs: ^1.5.6 => 1.5.6 react: 16.3.1 => 16.3.1 react-native: 0.59.10 => 0.55.4 stylelint-config-taro-rn: 2.0.1 => 2.0.0-beta.13 stylelint-taro-rn: 2.0.1 => 2.0.0-beta.13

补充信息

<!-- (可选)根据你的调查研究,出现这个问题的原因可能在哪里? -->

<!-- 感谢您的热心反馈!别忘了用preview按钮预览结果再提交 -->

如果您有功能上的建议,可以提到 FeatHub

使用上的问题,欢迎在「Taro 社区」一起交流

closed time in 2 months

moseszhou

push eventNervJS/taro

Pines-Cheng

commit sha 4bcdc8a7cac46ac153cc603b774176397aa5688f

fix(rn): react-redux 升级导致的 ref 错误

view details

push time in 2 months

issue commentNervJS/taro

[taro-2.0.1]--port 参数对于RN调试失效

好的~

moseszhou

comment created time in 2 months

issue openedPines-Cheng/think

系统权限的设计与实现

角色权限设计的100种解法

在业界接受度较高的功能权限模型是RBAC(Role-Based Access Control)模型,其基本理念是将“角色”这个概念赋予用户,在系统中用户与权限之间通过角色进行关联,以这样的方法来实现灵活配置。

image

在大型平台的应用上,试想如果用户量上万,新增一个角色时,可能需要为大量用户都分配一遍新的角色,工程量仍然巨大,此时即可以引入用户组的概念:如果部分用户的使用场景是相对一致和基础的,我们可以把这些用户打包成一个组,基于这个组的对象进行角色和权限的赋予。

同理如果权限较多时也会存在一样的问题,处理方式是引入权限组的概念,将使用场景相对固定的一组功能或权限打包成组赋予角色。但是一般来讲一个系统中权限功能的体量是相对有限和可控的,所以实际应用中对权限组的使用较少。

image

节点包括:

  • 用户
  • 用户组
  • 角色
  • 角色组
  • 权限(页面、操作、数据)
  • 权限组(页面、操作、数据)

关系包括:

  • 是/否关系 *继承关系 *限制关系(互斥、范围限制、边界限制、字段限制……)
  • ……

created time in 2 months

issue closedNervJS/taro

Form表单中的Picker组件内容无法显示

<!-- 如果是提交 bug,请搜索文档和 issue,确认以下事项:

  • 该问题没有在其他 issue 和文档讨论到,不属于重复内容
  • 除了「 补充信息」外,每一都必填 不满足以上两点要求的 bug 报告,issue 会被直接关掉 请多多理解,您现在的不便将会使 Taro 开发者更高效地定位你的问题,修复你的问题 像你一样的 Taro 的使用者也可以通过搜索找到你提供的 bug,对各方都有很大好处 -->

问题描述

如题,单独使用Picker组件没有问题,但是放在Form组件中就无法显示 <!-- 站在其它人的角度尽可能清晰地、简洁地把问题描述清楚 -->

复现步骤

<!-- 复现问题的步骤。代码只贴截图,不贴文字会被视为无效issue -->

  1. <!-- 打开'...' -->
  2. <!-- 点击'....' -->
  3. <!-- 滚动到'....' -->
  4. <!-- 看见的现象... -->
render() {
  return (
    <View>
      <Form>
        <Picker
          mode="selector"
          range={this.state.selector}
          onChange={this.onChange}
        >
          <View>
            <Text>当前选择:{this.state.selectorChecked}</Text>
          </View>
        </Picker>
      </Form>
    </View>
  )
}
/**
 * 这段注释后可以贴代码
 * 提供完整可复现的代码和整理好代码格式,有助于我们快速定位问题,节省你我时间
 * 代码提供不全或代码格式混乱的 issues 【有可能会被忽略】
 * 
 * 查看如何插入代码:https://coding.net/help/doc/project/markdown.html#i-5
 */


期望行为

可以正常显示Picker内部的内容 <!-- 请在下一行用简洁清晰的语言描述你期望的行为 -->

报错信息

无 <!-- 请在下一行贴上你的完整报错截图或文字 -->

系统信息

👽 Taro v1.3.25


  Taro CLI 1.3.25 environment info:
    System:
      OS: macOS 10.15.2
      Shell: 5.7.1 - /bin/zsh
    Binaries:
      Node: 12.14.0 - /usr/local/bin/node
      Yarn: 1.21.1 - /usr/local/bin/yarn
      npm: 6.13.4 - /usr/local/bin/npm
    npmPackages:
      @tarojs/async-await: ^1.3.25 => 1.3.25 
      @tarojs/cli: 1.3.25 => 1.3.25 
      @tarojs/components: 1.3.25 => 1.3.25 
      @tarojs/components-rn: ^1.3.25 => 1.3.30 
      @tarojs/plugin-babel: 1.3.25 => 1.3.25 
      @tarojs/plugin-csso: 1.3.25 => 1.3.25 
      @tarojs/plugin-sass: 1.3.25 => 1.3.25 
      @tarojs/plugin-uglifyjs: 1.3.25 => 1.3.25 
      @tarojs/redux: ^1.3.25 => 1.3.25 
      @tarojs/redux-h5: ^1.3.25 => 1.3.25 
      @tarojs/router: 1.3.25 => 1.3.25 
      @tarojs/taro: 1.3.25 => 1.3.25 
      @tarojs/taro-alipay: 1.3.25 => 1.3.25 
      @tarojs/taro-h5: 1.3.25 => 1.3.25 
      @tarojs/taro-qq: 1.3.25 => 1.3.25 
      @tarojs/taro-quickapp: 1.3.25 => 1.3.25 
      @tarojs/taro-redux-rn: ^1.3.25 => 1.3.30 
      @tarojs/taro-rn: ^1.3.25 => 1.3.30 
      @tarojs/taro-router-rn: ^1.3.25 => 1.3.30 
      @tarojs/taro-swan: 1.3.25 => 1.3.25 
      @tarojs/taro-tt: 1.3.25 => 1.3.25 
      @tarojs/taro-weapp: 1.3.25 => 1.3.25 
      @tarojs/webpack-runner: ^1.3.27 => 1.3.27 
      eslint-config-taro: 1.3.25 => 1.3.25 
      eslint-plugin-taro: 1.3.25 => 1.3.25 
      nerv-devtools: ^1.5.0 => 1.5.5 
      nervjs: ^1.5.0 => 1.5.5 
      react: 16.3.1 => 16.3.1 
      react-native: 0.55.4 => 0.55.4 
      stylelint-config-taro-rn: 1.3.25 => 1.3.25 
      stylelint-taro-rn: 1.3.25 => 1.3.25 

<!-- 使用taro info命令即可查看系统及依赖信息。将该命令运行结果贴下面即可。 -->

补充信息

<!-- (可选)根据你的调查研究,出现这个问题的原因可能在哪里? -->

<!-- 感谢您的热心反馈!别忘了用preview按钮预览结果再提交 -->

如果您有功能上的建议,可以提到 FeatHub

使用上的问题,欢迎在「Taro 社区」一起交流

closed time in 2 months

aoarashi1988

issue commentNervJS/taro

Form表单中的Picker组件内容无法显示

name 是必须的哦,文档里面ye也是有说明的。

image

aoarashi1988

comment created time in 2 months

issue closedNervJS/taro

将Taro ScrollView组件的enableBackToTop属性设为true,在RN端、钉钉小程序都无效

问题描述 将Taro ScrollView组件的enableBackToTop属性设为true,在RN端、钉钉小程序都无效。

    <ScrollView
        className='u-news-list'
        enableBackToTop
        scrollY
        onScrollToLower={this.onScrollToLower.bind(this, showDatas)}
    >...</ScrollView>

期望行为 竖向时,iOS 点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部

系统信息

  • 操作系统: [MacOS 10.15]
  • Taro 版本 [v1.3.21]
  • Node.js 版本 [v11.13.0]
  • 报错平台 [钉钉小程序、RN端]

closed time in 2 months

zyfly

issue closedNervJS/taro

react native Picker 点击没有效果 文件添加了Text

import Taro, { Component } from '@tarojs/taro'
import { View, Text, Picker } from '@tarojs/components'

export default class PagePicker extends Component {
  state = {
    selector: ['美国', '中国', '巴西', '日本'],
    selectorChecked: '美国',
    timeSel: '12:01',
    dateSel: '2018-04-22'
  }

  onChange = e => {
    this.setState({
      selectorChecked: this.state.selector[e.detail.value]
    })
  }

  onTimeChange = e => {
    this.setState({
      timeSel: e.detail.value
    })
  }
  onDateChange = e => {
    this.setState({
      dateSel: e.detail.value
    })
  }

  render () {
    return (
      <View className='container'>
        <View className='page-body'>
          <View className='page-section'>
            <Text>地区选择器</Text>
            <View>
              <Picker mode='selector' range={this.state.selector} onChange={this.onChange}>
                <View className='picker'>
                  <Text>当前选择:{this.state.selectorChecked}</Text>
                </View>
              </Picker>
            </View>
          </View>
          <View className='page-section'>
            <Text>时间选择器</Text>
            <View>
              <Picker mode='time' onChange={this.onTimeChange}>
                <View className='picker'>
                  <Text>当前选择:{this.state.timeSel}</Text>
                </View>
              </Picker>
            </View>
          </View>
          <View className='page-section'>
            <Text>日期选择器</Text>
            <View>
              <Picker mode='date' onChange={this.onDateChange}>
                <View className='picker'>
                  <Text>当前选择:{this.state.dateSel}</Text>
                </View>
              </Picker>
            </View>
          </View>
        </View>
      </View>
    )
  }
}

closed time in 2 months

qq857254562

issue closedNervJS/taro

picker 确定 取消 文字不能修改

<!-- 如果是提交 bug,请搜索文档和 issue,确认以下事项:

  • 该问题没有在其他 issue 和文档讨论到,不属于重复内容
  • 除了「 补充信息」外,每一都必填 不满足以上两点要求的 bug 报告,issue 会被直接关掉 请多多理解,您现在的不便将会使 Taro 开发者更高效地定位你的问题,修复你的问题 像你一样的 Taro 的使用者也可以通过搜索找到你提供的 bug,对各方都有很大好处 -->

问题描述

<!-- 站在其它人的角度尽可能清晰地、简洁地把问题描述清楚 -->

复现步骤

<!-- 复现问题的步骤。代码只贴截图,不贴文字会被视为无效issue -->

  1. <!-- 打开'...' -->
  2. <!-- 点击'....' -->
  3. <!-- 滚动到'....' -->
  4. <!-- 看见的现象... -->
/**
 * 这段注释后可以贴代码
 * 提供完整可复现的代码和整理好代码格式,有助于我们快速定位问题,节省你我时间
 * 代码提供不全或代码格式混乱的 issues 【有可能会被忽略】
 * 
 * 查看如何插入代码:https://coding.net/help/doc/project/markdown.html#i-5
 */


期望行为

可以修改文字 <!-- 请在下一行用简洁清晰的语言描述你期望的行为 -->

报错信息

<!-- 请在下一行贴上你的完整报错截图或文字 -->

系统信息

<!-- 使用taro info命令即可查看系统及依赖信息。将该命令运行结果贴下面即可。 -->

补充信息

<!-- (可选)根据你的调查研究,出现这个问题的原因可能在哪里? -->

<!-- 感谢您的热心反馈!别忘了用preview按钮预览结果再提交 -->

如果您有功能上的建议,可以提到 FeatHub

使用上的问题,欢迎在「Taro 社区」一起交流

closed time in 2 months

qq857254562

issue commentNervJS/taro

picker 确定 取消 文字不能修改

不贴一下代码吗?

qq857254562

comment created time in 2 months

issue commentNervJS/taro

useDidShow 在 RN 端报错

正在补充中~

ickeep

comment created time in 2 months

issue commentNervJS/taro

将Taro ScrollView组件的enableBackToTop属性设为true,在RN端、钉钉小程序都无效

Swiper 组件没有看到 enableBackToTop 这个属性啊。

zyfly

comment created time in 2 months

issue closedNervJS/taro

[RN]css中的flex如果传值三个,会报错

问题描述 image

截图这里转译成:

    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: 100%;

就可以了应该

期望行为 参考 https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex 拆分成N项

系统信息

👽 Taro v1.3.0-beta.3


  Taro CLI 1.3.0-beta.3 environment info:
    System:
      OS: macOS 10.14.4
      Shell: 5.3 - /bin/zsh
    Binaries:
      Node: 11.9.0 - /usr/local/bin/node
      Yarn: 1.13.0 - /usr/local/bin/yarn
      npm: 6.9.0 - /usr/local/bin/npm
    npmGlobalPackages:
      typescript: 2.4.2

closed time in 2 months

tourze

issue closedNervJS/taro

rn 端 onLongPress 事件不生效

问题描述

如题,代码如下:

image

期望行为

弹出 “长按” 两字

报错信息

无任何反应

系统信息

Taro v1.3.18

Taro CLI 1.3.18 environment info: System: OS: Windows 10 Binaries: Node: 10.13.0 - C:\Program Files\nodejs\node.EXE Yarn: 1.17.3 - C:\Users\Administrator\AppData\Roaming\npm\yarn.CMD npm: 6.4.1 - C:\Program Files\nodejs\npm.CMD

closed time in 2 months

ibubbo

issue commentNervJS/taro

rn 端 onLongPress 事件不生效

image

使用 iOS 真机测试,正常。

ibubbo

comment created time in 2 months

issue closedNervJS/taro

关于网络请求和长度单位问题

您好,我们尝试将React-Native项目改造成Taro项目 想问下 1.原有的fetch/ajax一定要改成Taro.request吗? 2.长度单位能否使用数字如width: 1; 而不是使用Px单位呢? 3.我们希望转化成本低一些

求助,谢谢

closed time in 2 months

penghuwan

issue commentNervJS/taro

关于网络请求和长度单位问题

不写单位(px)而直接写数字的话,那么单位默认就是px 这样子写本身属于不规范的写法,而且 IDE 也会有警告或报错,还是不支持了。

penghuwan

comment created time in 2 months

more