一个魔方计时器小程序以及...热更新

2019-04-10 15:16:45 +08:00
 qiuyk

前段时间折腾魔方的时候,发现微信小程序里居然没有一个好用的魔方计时器,作为一个魔方爱好者(伪)和码农(真),那是不能忍的,于是就只能自己动手了。

熟悉魔方计时器操作的魔友应该很容易就知道 QTimer 怎么用。需要注意的是,QTimer 不鼓励大家随意修改自己的成绩,因此只有在完成的时候,左下角计时 Tab 会出现小红点,此时双击才会出现+2、DNF 和删除的操作。除此以外,QTimer 不允许大家修改和删除已经录入统计的成绩。在完成 5、12、50 和 100 次复原时,右下角统计 Tab 会相应的提醒。


你以为这样就完了?当然没有,我还试了一下微信小程序热更新的可行性。魔方计时器有个很重要的功能就是生成打乱公式,目前这个模块更新频繁,所以我决定基于 Sval 试试热更新这个模块。

首先,我将打乱生成的模块进行打包,exports 暴露出需要用到的函数,打包发布到公用的 CDN 上,Unpkg: QTimer

于是,在 App 初始化的时候,检查热更新模块版本,然后判断是否需要更新,如果需要则再将热更新模块拉回来,存在 LocalStorage 里。

// utils/patch.js
const version = wx.getStorageSync('__generate_scramble__version')
const code = wx.getStorageSync('__generate_scramble')

if (code) {
  // 如果已经缓存,先跑一遍代码
  worker.postMessage({ type: 'run', data: code })
}

wx.request({
  url: 'https://unpkg.com/qtimer?meta',
  success(res) {
    const integrity = res.data.integrity
    if (integrity && integrity !== version) {
      wx.request({
        url: 'https://unpkg.com/qtimer',
        success(res) {
          wx.setStorageSync('__generate_scramble__version', integrity)
          wx.setStorageSync('__generate_scramble', res.data)
          // 如果有更新,再跑一遍代码,覆盖之前的 exports
          worker.postMessage({ type: 'run', data: res.data })
        }
      })
    }
  }
})

因为打乱生成是 CPU 密集操作,我把他放在了 worker 里做。那么我们将拉取回来的代码,直接 postMessage 给 worker 去跑。同时,worker 把代码执行的 exports 给暴露出来共主线程调用。

// workers/hotload.js
const interpreter = require('interpreter/index.js')

worker.onMessage(req => {
  if (req.type === 'run') {
    interpreter.run(req.data) // 跑代码
  } else {
    const { generateScramble } = interpreter.exports
    const scramble = generateScramble(req.type) // 调用热更新模块的方法生成打乱
    worker.postMessage({ type: req.type, data: scramble })
  }
})

因为小程序本身禁用了evalnew FunctionsetTimeout等等,所以 interpreter 就是通过 Sval 构造的解释器。

// workers/interpreter/index.js
const Sval = require('sval')
const init = require('init.js')

const interpreter = new Sval()

// 先初始化一下需要用到的方法
// 免得第一次打开 LocalStorage 里面没有缓存
// 同时代码还没有拉回来,等拉取到代码会将初始化覆盖掉
interpreter.run(init)

module.exports = interpreter

最后,愉快地在业务代码里面找 worker 要结果就好了。

// app.js
const worker = wx.createWorker('workers/hotload.js')
App({
  // Worker 调用封装
  getWorkerResult(req, done) {
    worker.postMessage(req)
    worker.onMessage(done)
  }
})

// pages/timer/index.js
const app = getApp()
Page({
  getScramble() {
    // 调用 Worker 生成打乱
    app.getWorkerResult({ type: '3x3' }, res => {
      this.setData({ scramble: res.data })
    })
  }
})

不过说实话,性能不怎么样,在 xs 上,7 阶打乱生成算法用原生的跑需要 2ms,而用 Sval 跑需要 600ms。对于 QTimer 来说,打乱的生成不影响体验,但是在打乱生成模块稳定以后,还是需要替换成原生实现,打包进小程序里。

目前我只是将解释器跑在逻辑层,理论上应该也能跑在视图层(wxs),但是对视图(wxml)的影响还十分有限,所以热更新只能对业务逻辑进行更新。当然,你甚至可以十分疯狂地将整个 Page 或者整个 App 的逻辑做热更新,只是是否有必要而已。

const Sval = require('sval')
const interpreter = new Sval()

interpreter.import({ Page, wx, app: getApp() })
interprete.run(`
  Page({
    data: {},
    onShow() {},
    customMethod() {}
  })
`)


Github: QTimer

Github: Sval

3045 次点击
所在节点    JavaScript
3 条回复
pnongrata
2019-04-10 15:30:26 +08:00
很棒诶。

不允许删除的考虑是什么
qiuyk
2019-04-10 15:40:53 +08:00
@pnongrata 就是希望大家正视自己的成绩,尽量模拟比赛的环境。不要因为某次成绩较差影响了平均成绩,在事后把某次成绩删掉。

(其实,只是我比较懒,不想加这个功能 23333 )
qiuyk
2019-04-11 19:44:28 +08:00
还有一个问题就是,目前 worker 不能发送请求,所以只能主线程请求热更新代码,然后 postMessage 给 worker,如果热更新代码太多,会影响到初始化性能,所以还要控制好热更新模块的大小

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/553734

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX