不爱吃西红柿的鱼

不爱吃西红柿的鱼 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 anchengjian.com 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

不爱吃西红柿的鱼 收藏了文章 · 2019-06-26

git代码统计

命令行

查看git上的个人代码量:

git log --author="username" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -

结果示例:(记得修改 username)

added lines: 120745, removed lines: 71738, total lines: 49007

统计每个人增删行数

git log --format='%aN' | sort -u | while read name; do echo -en "$name\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -; done

结果示例

Max-laptop    added lines: 1192, removed lines: 748, total lines: 444
chengshuai    added lines: 120745, removed lines: 71738, total lines: 49007
cisen    added lines: 3248, removed lines: 1719, total lines: 1529
max-h    added lines: 1002, removed lines: 473, total lines: 529
max-l    added lines: 2440, removed lines: 617, total lines: 1823
mw    added lines: 148721, removed lines: 6709, total lines: 142012
spider    added lines: 2799, removed lines: 1053, total lines: 1746
thy    added lines: 34616, removed lines: 13368, total lines: 21248
wmao    added lines: 12, removed lines: 8, total lines: 4
xrl    added lines: 10292, removed lines: 6024, total lines: 4268
yunfei.huang    added lines: 427, removed lines: 10, total lines: 417
³Ÿö    added lines: 5, removed lines: 3, total lines: 2

查看仓库提交者排名前 5

git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r | head -n 5

贡献值统计

git log --pretty='%aN' | sort -u | wc -l

提交数统计

git log --oneline | wc -l

添加或修改的代码行数:

git log --stat|perl -ne 'END { print $c } $c += $1 if /(\d+) insertions/'

使用gitstats

GitStats项目,用Python开发的一个工具,通过封装Git命令来实现统计出来代码情况并且生成可浏览的网页。官方文档可以参考这里。

使用方法

git clone git://github.com/hoxu/gitstats.git
cd gitstats
./gitstats 你的项目的位置 生成统计的文件夹位置

可能会提示没有安装gnuplot画图程序,那么需要安装再执行:

//mac osx
brew install gnuplot
//centos linux
yum install gnuplot

生成的统计文件为HTML:
2014-8-16-git.jpg

使用cloc

npm install -g cloc

image

参考文章

git代码行统计命令集
统计本地Git仓库中不同贡献者的代码行数的一些方法
使用Git工具统计代码

查看原文

不爱吃西红柿的鱼 评论了文章 · 2019-03-20

通过 InnoSetup 美化安装界面

在 github 仓库里的 通过 InnoSetup 美化安装界面 提供持续更新

InnoSetup 的美化相应的帖子也比较多,但是代码不是很全。。。所以我专门出了这篇文章来记录下这个美化过程。
废话不多说,先上个成果:
安装界面

前端er们可以直接下载 vue-nw-seed 这个分支,一键 build就出效果了。

一、InnoSetup 增强版

这个部分很重要,是实现自定义界面的绝对前置步骤。
完成这个任务也很简单,可以自己下载安装就搞定。
当然,找资源的过程比较麻烦,所以直接提供了一个,并用 node-innosetup-compiler 包裹了一下,使之可以直接在 Node.js 下应用,最终的增强版在 deps/innosetup 这里可以看到。
注意
InnoSetup 本身是开源免费的,希望大家在用的过程中注意一下作者的 LICENSE

二、iss 配置文件

这个部分无需赘述,对于新手来说比较复杂的一个事儿,给个文档 What is Inno Setup? 先。

1、Setup Script Sections

可以直接双击 deps/innosetup/bin/Compil32.exe 打开一个可视化的配置窗口,按照引导可以直接生成一个通用流程中简单的安装配置,会生成一个类似 setup-simple.iss 这种的 iss 配置文件。
当然,一个扁平漂亮的界面,肯定不是这种简单的配置能满足的。。。
所以,看我提供的一个 setup.iss ,其包含完整的流程控制和界面的控制。
这里的代码太长了,加上注释 455 行,就不贴过来了,戳链接进去看吧。

2、Pascal Script

在那 455 行代码中主要就是 [code] 块下面的 Pascal Script ,通过它来控制安装流程和界面的美化。

  • 控制安装流程的原理是 InnoSetup 通过 Pascal Scripting: Event Functions 这种事件机制,把流程节点的控制交给 Pascal Script ,使其可以控制上一步下一步等等的操作。

  • 界面的美化,主要是调用两个美化插件动态库:botva2.dllInnoCallback.dll。用其来控制贴图的位置和样式,和给按钮绑定相应的事件等等的。

详细的控制方式参见 setup.iss 文件中的注释,此处不再详细解释啦。。。解释起来太多了。

3、setup resources

这个部分就是用来存放贴图资源和美化插件动态库的地方。
我默认放在示例项目的 /build/setup_resources 这个位置。
如果你去看过上文 setup.iss 这个文件的话,就会发现里面的资源文件路径的配置被搞成类似这个样子

#define LicenseFilePath "_resourcesPath_\license.txt"
#define SetupIconFilePath "_resourcesPath_\logo.ico"
#define ResourcesPath "_resourcesPath_\*"

这是因为各个项目要求的打包配置和路径可能不一致,特意做了一个处理,详见build-win-setup.js

// rewrite name, version to iss
fs.readFile(issPath, null, function(err, text) {
  if (err) return reject(err)

  let str = iconv.decode(text, 'gbk')
    .replace(/_name_/g, name)
    .replace(/_appName_/g, appName)
    .replace(/_version_/g, version)
    .replace(/_outputPath_/g, outputPath)
    .replace(/_outputFileName_/g, getOutputName(outputFileName, { name, version, platform }))
    .replace(/_filesPath_/g, files)
    .replace(/_resourcesPath_/g, resourcesPath)
    .replace(/_appPublisher_/g, appPublisher)
    .replace(/_appURL_/g, appURL)
    .replace(/_appId_/g, appId)


  fs.writeFile(tmpIssPath, iconv.encode(str, 'gbk'), null, function(err) {
    if (err) return reject(err)

    // inno setup start
    innosetupCompiler(tmpIssPath, { gui: false, verbose: true }, function(err) {
      fs.unlinkSync(tmpIssPath)
      if (err) return reject(err)
      resolve(opt)
    })
  })
})

如果仅仅想单纯的用 InnoSetup 打包安装美观的界面,可以自直接换一下相应的配路径置。

三、Q & A

需要单独说一下几个踩到的坑。。。
1、iss 文件需要什么特定的编码格式吗?
中文的话,需要 ansi 编码,不然用其他编码,打包出来在界面上的中文会乱码!
这也是我直接用文字贴图来代替 label 以确保界面中的文字显示万无一失的原因。

2、这个 InnoSetup 增强版 和和普通的有啥区别?
说实在的,我也没太搞明白,InnoSetup 本身就是开源和免费的,可以自己修改并编译。现在我用的这个版本应该是国内某前辈搜集的一些脚本等东西集合出来单独打包出来的一个增强版。

3、为啥不单独搞个 InnoSetup 美化的项目?
一方面不确定 InnoSetup 增强版 这个版权协议,另一方面不确定大家的需求咋样,暂时这样,让大家可以通过 源码 + 详细的注释 习得这部分的技能。如果确实这部分需求很强大,请私信我,或者发 issue 讨论下这个项目该咋整吧。

四、参考资料

查看原文

不爱吃西红柿的鱼 赞了文章 · 2018-06-21

喂,快给我打一个小程序预览码

需求

开发小程序的朋友们随时都会听到一句话:“喂,快给我打一个xxx环境的预览码”,无论你正在干什么,都得赶紧地回一句:“稍等,这就给你打码……”

然后苦逼的你build了一个xxx环境的包,打开了微信开发者工具,点了一下预览,等了一下,预览码出来了,你复制丢给你的爸爸们。

终于有一天,你正在专心致志做一些不可描述的事情时,“喂,快给我打一个xxx环境的预览码”,这时你内心怒吼了一句:“老子不给你打码!你自己打去!”

于是就有了这个需求,要搞个东西让爸爸们自主打码,嗯,应该就是只有一个按钮,点一下就可以出现预览二维码的东西,意淫了一下应该是这样的:

打码过程

没错!就这样干!

规划一下

干大事就要从胡思乱想开始,现在来想想要搞成这个功能,需要做点什么准备工作吧。

找微信开发者工具的接口
最重要的事情莫过于看看微信开发者工具有没有给我们提供这样的接口让我们去操作,经过一番查阅文档我们会发现,果然有!

https://developers.weixin.qq....
会发现,文档给我们提供了两种方式的接口,命令行调用以及HTTP调用。有了接口,一切都好办了,无非就是调一下接口,拿到二维码,贴到页面上去而已嘛,很简单。

梳理开发流程
我们就把这个简单的事情,用流程图说明一下:
https://www.processon.com/vie...
流程图

所需技术

工欲善其事,必先利其器,我们要搞这个东西,还是先要把用到的技术整理一下。

  1. 微信开发者工具
  2. 一个小程序项目(这里以一个mpvue项目为例子)
  3. 前端vue + vux,这里前端没什么需要做的东西,这样的搭配纯属是因为本来就正在做移动端的东西,直接拿来用而已。
  4. 后端koa2,当然后端用什么都可以,这里选择koa2,纯属是因为我也不会用别的……
  5. 前后端HTTP请求统一用axios
  6. 涉及到node操作命令行需要用到shelljs

好像没别的东西了,用到了再说吧。

撸起袖子从后端开始

为了省事,直接把前后端的东西放在一起。项目目录:

项目目录

可以看到server这个目录下放的都是后端的东西。

server/index.js
先看看入口文件index.js,从这里我们可以知道后端要做两件事情,第一要能访问到前端build出来的静态资源,第二要能与前端通过HTTP接口进行交互。见代码:

const path = require('path')
const Koa = require('koa')
const koaStatic = require('koa-static')
const bodyParser = require('koa-bodyparser')
const router = require('./router')
const app = new Koa()
const port = 9871
app.use(bodyParser())
// 处理静态资源 这里是前端build好之后的目录
app.use(koaStatic(
  path.resolve(__dirname, '../dist')
))
// 路由处理接口
app.use(router.routes()).use(router.allowedMethods())
// 监听端口
app.listen(9871)
console.log(`[demo] start-quick is starting at port ${port}`)

静态资源方面的话使用koa-static即可,重点是怎样给前端提供接口,这就要看路由了。

server/router/index.js

const Router = require('koa-router')
// 业务逻辑
const wx = require('../controller/wx')
const router = new Router({
  // 接口前缀 比如open接口 请求路径就是/api/open
  prefix: '/api'
})
router.get('/open', wx.open)
  .get('/login', wx.login)
  .get('/preview', wx.preview)
  .get('/build', wx.build)
module.exports = router

这里可以清晰看到,后端提供了四个接口,但具体每个接口的业务逻辑则封装在controller里的wx.js,如果以后还有别的业务逻辑,就在controller加相应的模块即可。

server/controller/wx.js

// 微信开发者工具接口调用逻辑
const {open, login, preview, build} = require('../utli/wxToolApi')
// 处理成功失败返回格式的工具
const {successBody, errorBody} = require('../utli')
class WxController {
  /**
   * 根据环境对mpvue项目进行打包
   * @returns {Promise<void>}
   */
  static async build (ctx) {
    // 前端传过来的get参数
    const query = ctx.request.query
    if (!query || !query.env) {
      ctx.body = errorBody(null, '构建项目失败')
      return
    }
    const [err, data] = await build(query.env)
    ctx.body = err ? errorBody(err, '构建项目失败') : successBody(data, '构建项目成功')
  }
  /**
   * 打开微信开发者工具
   * @returns {Promise<void>}
   */
  static async open (ctx) {
    const [err, data] = await open()
    ctx.body = err ? errorBody(err, '打开微信开发者工具失败') : successBody(data, '打开微信开发者工具成功')
  }
  /**
   * 登录微信开发者工具
   * @returns {Promise<void>}
   */
  static async login (ctx) {
    const [err, data] = await login()
    ctx.body = err ? errorBody(err, '登录二维码返回失败') : successBody(data, '登录二维码返回成功')
  }
  /**
   * 查看预览码
   * @returns {Promise<void>}
   */
  static async preview (ctx) {
    const [err, data] = await preview()
    ctx.body = err ? errorBody(err, '预览二维码返回失败') : successBody(data, '预览二维码返回成功')
  }
}
module.exports = WxController

为了代码更加清晰,这里将具体操作微信开发者工具的接口逻辑抽到util/wxToolApi.js里去了,仅仅处理怎样以统一格式返回给前端。
util/wxToolApi.js

const {promiseWrap, successBody, errorBody} = require('../utli')
const {INSTALL_PATH, PROJECT_PATH, PORT_PATH, PORT_FILE_NAME, HOST} = require('../const')
const {readFile} = require('../utli/nodeApi')
const shell = require('shelljs')
const axios = require('axios')
module.exports = {
  /**
   * 根据环境对mpvue项目进行打包
   * @param env [doc, pre, prd]
   * @returns {*}
   */
  build (env) {
    return promiseWrap(new Promise((resolve, reject) => {
      // 进入项目目录
      shell.cd(PROJECT_PATH)
      // 执行打包命令
      shell.exec(`npm run build:${env}`, function (code, stdout, stderr) {
        resolve(stdout)
      })
    }))
  },
  /**
   * 打开微信开发者工具
   * @returns {*}
   */
  open () {
    return promiseWrap(new Promise((resolve, reject) => {
      // 进入项目目录
      shell.cd(INSTALL_PATH)
      // 执行微信开发者工具接口“命令行启动工具”
      shell.exec(`cli -o ${PROJECT_PATH}`, function (code, stdout, stderr) {
        if (stderr) return reject(stderr)
        resolve(stdout)
      })
    }))
  },
  /**
   * 获取微信开发者工具端口号
   * @returns {Promise<*>}
   */
  async getPort () {
    shell.cd(PORT_PATH)
    // http 服务在工具启动后自动开启,HTTP 服务端口号在用户目录下记录,可通过检查用户目录、检查用户目录下是否有端口文件及尝试连接来判断工具是否安装/启动。
    const [err, data] = await readFile(PORT_FILE_NAME)
    return err ? errorBody(err, '读取端口号文件失败') : successBody(data, '读取端口号文件成功')
  },
  /**
   * 微信开发者工具进行登录
   * @returns {*}
   */
  login () {
    return promiseWrap(new Promise(async (resolve, reject) => {
      // 获取端口号
      const portData = await module.exports.getPort()
      if (portData.code !== 0) {
        reject(portData)
        return
      }
      const port = portData.data
      axios.get(`http://${HOST}:${port}/login?format=base64`)
        .then(res => {
          resolve(res.data)
        })
        .catch(e => {
          reject(e)
        })
    }))
  },
  /**
   * 微信开发者工具获取预览码
   * @returns {*}
   */
  preview () {
    return promiseWrap(new Promise(async (resolve, reject) => {
      const portData = await module.exports.getPort()
      if (portData.code !== 0) {
        reject(portData)
        return
      }
      const port = portData.data
      axios.get(`http://${HOST}:${port}/preview?format=base64&projectpath=${encodeURIComponent(PROJECT_PATH)}`)
        .then(res => {
          resolve(res.data)
        })
        .catch(e => {
          reject(e)
        })
    }))
  }
}

这里有一点需要注意,为什么只有open接口需要用命令行调用方式?那是因为HTTP调用方式必须加端口,比如open接口

# 打开工具
http://127.0.0.1:端口号/open
# 打开/刷新项目
http://127.0.0.1:端口号/open?projectpath=项目全路径

如果你根本都没有打开微信开发者工具,在以下地方就会找不到端口:

端口号文件位置:

macOS : ~/Library/Application Support/微信web开发者工具/Default/.ide

Windows : ~/AppData/Local/微信web开发者工具/User Data/Default/.ide

所以作为一个全自动化打码工具,怎么可能还要自己去手动打开微信开发者工具呢!

前端

后端的东西基本就那么多,终于到前端了,前端十分简单,就不多说了:

<template>
  <div>
    <group title="请选择环境">
      <radio :options="envOption" v-model="env"></radio>
    </group>
    <x-button class="btn" type="default" @click.native="handlePreviewProject">点击预览</x-button>
    <div v-if="loginImg" class="code">
      <divider>请先登录</divider>
      <img class="code-img" :data-original="loginImg" alt="">
    </div>
    <div v-if="preImg" class="code" id="preImg">
      <divider>预览二维码</divider>
      <img class="code-img" :data-original="`${base64Prefix}${preImg}`" alt="">
    </div>
  </div>
</template>

<script>
import {openProject, login, previewProject, buildProject} from 'SERVICES/index'
import {showLoading, hideLoading} from 'UTILS'
import { Divider, XButton, Radio, Group } from 'vux'
export default {
  data () {
    return {
      // data表示取得数据的协定名称,image/png 是数据类型名称,base64 是数据的编码方法,逗号后面就是这个image/png文件base64编码后的数据。
      base64Prefix: 'data:image/png;base64,',
      // 登录二维码
      loginImg: '',
      // 预览二维码
      preImg: '',
      // 环境 默认为doc
      env: 'doc',
      // 所有的环境选项
      envOption: ['doc', 'pre', 'prd']
    }
  },
  components: {
    Divider,
    XButton,
    Radio,
    Group
  },
  methods: {
    handleError (msg) {
      alert(msg)
    },
    async login () {
      const {data: {code, data, msg}} = await login()
      if (code !== 0) {
        this.handleError(msg)
        return code
      }
      this.loginImg = data
      return code
    },
    async previewProject () {
      const {data: {code, data, msg}} = await previewProject()
      if (code !== 0) {
        this.handleError(msg)
        return code
      }
      this.preImg = data
      return code
    },
    async handlePreviewProject () {
      showLoading()
      // 重置二维码
      this.resetImg()
      // 打开微信开发者工具
      const {data: {code}} = await openProject()
      if (code !== 0) {
        // 登录微信开发者工具
        await this.login()
        hideLoading()
        return
      }
      // 根据环境打包
      await buildProject(this.env)
      // 预览
      await this.previewProject()
      hideLoading()
    },
    resetImg () {
      this.loginImg = ''
      this.preImg = ''
    }
  }
}
</script>

<style lang='less'>
  .btn {
    width: 90%!important;
    margin: 30px auto 30px auto;
  }
  .code {
    display: flex;
    align-items: center;
    flex-direction: column;
    .code-img {
      width: 300px;
      height: 300px;
    }
  }
</style>

这里有一个坑就是,login返回的base64是带了data:image/jpeg;base64,前缀的,所以可以直接放到img的src里,但是获取预览码的preview返回的却没有这个前缀!所以需要自己加上去,就是那个base64Prefix:'data:image/png;base64,'

最后

其实到这里已经基本实现了整个打码功能,但如果真的要可以用还有很多事情没做。

  1. 部署到测试机器上。虽然可以直接用自己的机子作为部署这个工具的机器,但这实在是有点……如果要部署到测试机器上,有一个问题就是,微信开发者工具依赖图形界面,而服务器一般是命令行,虽然有 https://github.com/cytle/wech... 这样的项目移植微信开发者工具到linux,但这种部署方式似乎还是怪怪的。
  2. 假设完成了上述部署,进行小程序项目打包的环节需要修改一下,变成根据选择的环境,到相应的代码仓库(比如gitlab)拉取该环境的最新代码,然后进行安装依赖才能执行打包命令。
  3. 既然都做到这一步了,也不差把上传小程序也加上去,微信开发者工具接口也有提供,这样一来整个测试打码到上线的步骤都有了。

End~

查看原文

赞 38 收藏 33 评论 10

不爱吃西红柿的鱼 赞了文章 · 2018-05-24

打破砂锅问到底:详解Webpack中的sourcemap

关于webpack中sourcemap的文章很多,但感觉大部分是翻译官方文档的说明, 缺乏直观的用例,写这篇博客的目的是帮自己厘清这个概念, 也顺便将我自己收集的这方面的干货放在这。本文将尝试先讲清楚webpack中的sourcemap配置项的概念。

Webpack中sourcemap的配置

sourcemap是为了解决开发代码与实际运行代码不一致时帮助我们debug到原始开发代码的技术。尤其是如今前端开发中大部分的代码都经过编译,打包等工程化转换。比如开发环境下用scss写样式, 想在浏览器中在线编辑css那样编辑scss就不是那么容易了。从我自己看过的资料中, sourcemap的概念最早出现在12年, jquery1.9是较早支持sourcemap的库。这篇博客比较有代表性:Introduction to JavaScript Source Maps,阮一峰的文章JavaScript Source Map 详解也大量参考该博客。关于sourcemap的原理及作用,基本在这两篇文章中讲清楚了。回到webpack中的sourcemap,就我这几天的琢磨, 这方面资料相对比较零散,但凡搜索Webpack中sourcemap的配置, 总是能得到千篇一律的如下信息:
Sourcemap type Quality Notes

eval: 生成代码 每个模块都被eval执行,并且存在@sourceURL

cheap-eval-source-map: 转换代码(行内) 每个模块被eval执行,并且sourcemap作为eval的一个dataurl

cheap-module-eval-source-map: 原始代码(只有行内) 同样道理,但是更高的质量和更低的性能

eval-source-map: 原始代码 同样道理,但是最高的质量和最低的性能

cheap-source-map: 转换代码(行内) 生成的sourcemap没有列映射,从loaders生成的sourcemap没有被使用

cheap-module-source-map: 原始代码(只有行内) 与上面一样除了每行特点的从loader中进行映射

source-map: 原始代码 最好的sourcemap质量有完整的结果,但是会很慢

webpack中devtool的配置的官方文档在这 :webpack-devtool

疑问

反正我看完这些说明是云里雾里, 就我自己而言, 有3个疑问:

  1. eval和sourcemap有什么关系,eval模式是sourcemap吗?
  2. 包含cheap关键字的配置中只有行内是什么意思?
  3. 这几种不同的配置有什么区别?

解答

看似配置项很多, 其实只是五个关键字evalsource-mapcheapmoduleinline的任意组合。这五个关键字每一项都代表一个特性, 这四种特性可以任意组合。它们分别代表以下五种特性(单独看特性说明有点不明所以,别急,往下看):

  • eval: 使用eval包裹模块代码
  • source-map: 产生.map文件
  • cheap: 不包含列信息(关于列信息的解释下面会有详细介绍)也不包含loader的sourcemap
  • module: 包含loader的sourcemap(比如jsx to js ,babel的sourcemap)
  • inline: 将.map作为DataURI嵌入,不单独生成.map文件(这个配置项比较少见)

了解了以上各种不同特性, 再来逐一解答以上问题。

eval和sourcemap有什么关系,eval模式是sourcemap吗?

evalsource-map都是webpack中devtool的配置选项, eval模式是使用eval将webpack中每个模块包裹,然后在模块末尾添加模块来源//# souceURL, 依靠souceURL找到原始代码的位置。包含eval关键字的配置项并不单独产生.map文件(eval模式有点特殊, 它和其他模式不一样的地方是它依靠sourceURL来定位原始代码, 而其他所有选项都使用.map文件的方式来定位)。包含source-map关键字的配置项都会产生一个.map文件,该文件保存有原始代码与运行代码的映射关系, 浏览器可以通过它找到原始代码的位置。(注:包含inline关键字的配置项也会产生.map文件,但是这个map文件是经过base64编码作为DataURI嵌入),举个栗子:eval-source-mapevalsource-map的组合,可知使用eavl语句包括模块,也产生了.map文件。webpack将.map文件作为DataURI替换eval模式中末尾的//# souceURL。按照我自己的理解, eval.map文件都是sourcemap实现的不同方式,虽然大部分sourcemap的实现是通过产生.map文件, 但并不表示只能通过.map文件实现。下面是eval模式后产生的模块代码:
图片描述

包含cheap关键字的配置中只有行内是什么意思?

这里的列信息指的是代码的不包含原始代码的列信息。 官方文档对于包含cheap的解释是这样的:

> cheap-source-map - A SourceMap without **column-mappings**. SourceMaps
> from loaders are not used.

  

这句话翻译过来就是“在cheap-source-map模式下sourcemap不包含列信息,也不包含loaders的sourcemap”这里的“column-mappings”就是代码列数的意思,是否包含loaders的sourcemap有什么区别将在之后提到。debug的时候大部分人都只在意代码的行数, 很少关注列数, 列数就是该行代码从第一个字符开始到定位字符的位置(包括空白字符)包含cheap关键字的模式不包含列信息,体现在webpack中就是:如果包含cheap关键字,则产生的.map文件不包含列信息。也就是说当你在浏览器中点击该代码的位置时, 光标只定位到行数,不定位到具体字符位置。而不包含cheap关键字时, 点击控制台log将会定位到字符位置。

包含列信息后点击原始代码的定位,注意光标位置:
图片描述

不包含列信息的光标位置:
图片描述

这篇博客:Go to a line number at a specific column直观地展示了列数的概念。如果深入到webpack中的细节中体会该配置项,可以看这篇博客:SurviveJS:Source Maps ,该文章对比了webpack中所有配置项中.map文件的代码,这里截取eval-source-mapcheap-source-map的模式产生的.map文件代码中的mappings字段对比:

devtool: 'eval-source-map'

"mappings": "AAAAA,QAAQC,GAAR,CAAY,aAAZ",

devtool: 'cheap-source-map'


```
"mappings": "AAAA",
```

注:这里使用了VLQ编码,(关于VLQ编码还可参考这里:前端构建:Source Maps详解) 在VLQ编码中,逗号,表示字符列分割,分号;表示行分割。包含cheap关键字的配置项不包含列信息,也就没有逗号。关于VLQ编码, 本文最初的阮一峰的文章中有所解释。而不包含loader的sourcemap指的是不包含loader的sourcemap,不包含它时候如果你使用了诸如babel等代码编译工具时, 定位到的原始代码将是经过编译后的代码位置,而非原始代码。

比如当我用babel编译JS的时候,如果包含不包含loaders的sourcemap,此时debug到的将是编译后的代码, 而非原始代码,如图(这是使用cheap-source-map模式未包含loaders的sourcemap情况下的截图, debug的位置与之前的对比截图是同一个地方):
图片描述

这几种不同的配置有什么区别?

通过以上两个问题的解释, webpack中的sourcemap各个配置项异同应该有了一定认识,乍看之下各个配置项很难记忆, 但其实从每个关键字所代表的特性入手, 就能体会到他们的异同。他们在webpack中的主要区别一个体现在重构的性能上, 总的来说eval性能最好,source-map性能最低,但就我自身的实践来看大多用的是最完整的source-map,该模式对于不管是js还是css,scss等都能很好的覆盖, 相反其他模式都不完整, 在开发环境下重构性能似乎比不上功能的完善。
另外需要补充的是module关键字, 当加上module关键字webpack将会添加loader的sourcemap。

这篇博客:Webpack devtool source map 对于各个sourcemap配置项都作了对比和梳理, 有趣的是,作者在该文中也指出对于很多官方文档的不解,比如对于所谓的without column-mappings作者就不知道在讲什么:

A SourceMap without column-mappings. SourceMaps from loaders are not
used.No idea what that means
查看原文

赞 107 收藏 84 评论 5

不爱吃西红柿的鱼 回答了问题 · 2018-04-11

.bundle格式的文件可以反编译看到源码吗?

看起来 .bundle.js 后缀没区别,你随意打开一下看看

clipboard.png

关注 2 回答 1

不爱吃西红柿的鱼 关注了标签 · 2018-04-11

react-native

LEARN ONCE, WRITE ANYWHERE: BUILD MOBILE APPS WITH REACT

Build Native Mobile Apps using JavaScript and React.

关注 1058

不爱吃西红柿的鱼 关注了用户 · 2018-04-08

乌骑凤 @wuqifeng

关注 1

不爱吃西红柿的鱼 赞了文章 · 2018-04-08

Mpvue 小程序转 Web 实践总结

介绍

Mpvue 是一个使用 Vue.js 开发小程序的前端框架。框架基于 Vue.js 核心,修改了 Vue.js 的 runtime 和 compiler 实现,使其可以运行在小程序环境中,从而为小程序开发引入了整套 Vue.js 开发体验,同样也使得一套代码同时复用在小程序和 Web 中成为可能。本文将以 IT之家Lite 小程序的 Web 转换经过为线索,大致介绍一下转换的基本步骤及需要注意的一些事项。

目录结构

省略了部分与转换无关的文件
├─build
├─config
├─src
│ ├─components
│ ├─pages
│ ├─store
│ ├─styles
│ ├─utils
│ │  ├─api.js
│ │  ├─index.js
│ │  ├─request.js
│ │  └─wx.js
│ ├─App.vue
│ └─main.js
├─package-lock.json
└─package.json

转换步骤

0.前期准备

  • 建议使用 git 进行分支管理
  • 尽量避免使用不必要的小程序特有标签,如 text,image 等 #9137744
  • 避免直接使用 wx 对象,使用 import wx from 'wx' 的方式引入,便于 web 中改写 #c3ef6e7
// src/utils/wx.js
export default wx
  • 使用 flyio 作为请求库,配置 alias 将 flyio 指向 flyio/dist/npm/wx
  • 基于原分支新建 web-version 分支

1.修改打包配置

  • 可以在原有配置基础上修改,主要涉及 entry、target 及 loader 相关的配置项,这里我直接通过 vue-cli 生成了一个新的项目,复制 build、config 文件夹及 eslint、babel 等的配置文件替换原有配置,使用新项目的 package.json 并做相应修改,新建项目时各选项尽量与原项目保持一致 #ece3a76
  • 修改 main.js,指定挂载元素,顺利的话,这步之后执行 npm run dev 便已经可以跑起来了,有报错的话解决相应错误即可

2.配置路由

  • 添加 vue-router,并进行相应配置,建议使用 history 模式 #ddf94bc
  • 修改路由参数获取相关的代码 #b949197
  • 使用 router-link 替换 a 标签,避免页面重载 #eb09297

3.调整请求接口

  • 配置 alias 将 flyio 指向 flyio/dist/npm/fly
  • 小程序中不会有跨域的问题,但 web 中需配合后端进行请求转发或通过其他方式来解决这一问题 #f963975

4.转换小程序组件及 API

  • 底部导航栏,自己布局实现 #8d6d98b
.nav(v-if="$route.meta.nav")
  a.nav-item(href="/pages/news/list")
    img.nav-icon(v-if="$route.name === 'NewsList'", data-original="/static/assets/news-active.png")
    img.nav-icon(v-else, data-original="/static/assets/news.png")
    .nav-title(:class="{ active: $route.name === 'NewsList' }") 资讯
  a.nav-item(href="/pages/quanzi/list")
    img.nav-icon(v-if="$route.name === 'QuanziList'", data-original="/static/assets/quanzi-active.png")
    img.nav-icon(v-else, data-original="/static/assets/quanzi.png")
    .nav-title(:class="{ active: $route.name === 'QuanziList' }") 圈子
// src/utils/wx.js
import Vue from 'vue'

export default {
  showNavigationBarLoading () {
    Vue.prototype.$loading('加载中')
  },
  hideNavigationBarLoading () {
    Vue.prototype.$loading.close()
  },
  showToast ({ title }) {
    Vue.prototype.$toast.center(title)
  }
}

5.Web 优化

维护

在初步完成 Web 版的转换之后,便可以再次切换回主分支,后续的 feature 及 bugfix 均在主分支进行,待主分支发版后切换到 web 分支进行一次合并操作,可能会产生少量冲突,但也都会比较容易解决,最后处理下新引入的小程序特性即可,整体而言可维护性还是比较好的

总结

整个转换过程还是比较顺利的,主体部分约 1 个多小时完成,相对于小程序 web 的环境更为开放,所以大部分小程序的 api 可以通过各种方式模拟,由于是在两个不同的分支进行,也可以放心地使用各种浏览器端地特性,下面是几点开发及转换中的建议:

  • 下拉刷新及上拉加载尽量以 method 的形式实现,便于 web 中复用
onPullDownRefresh () {
  this.refresh()
},
onReachBottom () {
  this.loadmore()
},
methods: {
  ...mapActions([
    'getSlides',
    'getNews'
  ]),
  async refresh () {
    await Promise.all([
      this.getNews({ r: 2, init: true }),
      this.getSlides()
    ])
    wx.stopPullDownRefresh()
  },
  loadmore () {
    const { news } = this
    const lastnews = news[news.length - 1]
    this.getNews({ r: Date.parse(new Date(lastnews.postdate)) })
  },
}
  • 样式及脚本尽量不要直接修改原有代码,可通过 mixin、新建 style 标签等方式实现,避免造成冲突

展望

  • 双端统一的 UI 库,目前来看只能使用一些简单的 css 类库
  • 更好的路由支持,理想状态下,可以通过 vue-router 的配置文件自动生成各页面的 main.js 文件,并配置 entry,开发 .vue 文件时,可以直接使用 this.$routethis.$routerrouter-link 完成相关操作,避免每次手动修改

  1. Git 仓库
  1. Demo
查看原文

赞 25 收藏 22 评论 9

不爱吃西红柿的鱼 赞了文章 · 2018-04-08

美团小程序框架mpvue蹲坑指南

美团小程序框架mpvue(花名:没朋友)蹲坑指南

第一次接触小程序大概是17年初,当时小程序刚刚内侧,当时就被各种限制折腾的死去活来的,单向绑定,
没有promise,请求数限制,包大小限制,各种反人类,...反正我是感受到了满满的恶意.
最近接到一个工程类的小程序项目,做技术选型的时候,又把以前的东西捡起来看了看,重新熟悉了一下,
感觉小程序还是有在努力的,支持大部分es6语法了,还出了一个类Vue的mvvm框架wepy,还支持redux状态管理,
就大致建了个demo,跑了起来,赶紧虽然没有vue那么酸爽,但是还是挺ok的,至少比原生的小程序语法亲民很多.

然后就开始用wepy搭项目,写静态页面(由于公司的开发模式是先写静态页面,
等待后端的同学接口出来了再绑定数据),虽然wepy用起来比原生的顺手,
但是还是有很多坑的,这里就不列举了.....

就在我们静态页面快写完的时候,某天晚上论坛的时候看到一条消息, 美团出了个小程序框架mpVue
(不知道为什么,每次看到这个名字,我只想到3个字,没朋友,哈哈),
大致看了一下官方的介绍,主要有一下亮点:

  1. 跟vue一样的开发体验,包括vuex
  2. H5代码转换编译成小程序目标代码的能力

也就是说,不但可以用我们熟悉的vue语法开发,还有可能直接把你的h5页面编译成小程序.
该项目到目前位置,开源不到20天,已经收到将近7000个star,可见天下苦秦已久。

建了个demo,跑了一下,感觉简直就是开发界的良心之作啊.顺便把之前写的wepy的静态页面代码复制过来看了一下,
发现只要改动一点点,就可以顺利从wepy切换到mpvue上来(整个项目的切换时间在半天左右).
说做就做,当天就切到mpvue.一直到现在项目接近尾声了,整个开发过程,真是令人愉悦.

Bug....我今天好像不是来给mpvue做广告的,我是来找茬的..

下面就盘点一下我最近用mpvue开发,遇到的一些需要需要注意的细节.(或者说跟vue不同的地方)

一, 这个个人感觉是最大的坑,除了缺少文件会报错外,其他的代码语法错误等,
控制台大部分时间都是安静的(偶尔也会报一个xxx is undefined)
比较经常碰到的是这样 this.xxx =5 有些情况下会报错,而有些情况下则没有任何反应,
具体什么情况下会,什么情况下不会,我现在还没摸出规律来..

有一次我把

this.dataObject.map(() => { ...这里省略... })

结果map前面的 . 不小心给露掉了,实际代码变成

this.dataObjectmap(() => { ...这里省略... })

结果找了半天没找到问题的原因

二, 这个也是比较难受的地方,就是模板的数据绑定里面,没办法在模板语法里面调用methods方法
(或者说没办法调用computed以外的函数),有人也许会说,那可以用computed属性,那如果我想给函数传参怎么办?
看下面代码:

<template>
  <view v-for="item in costList" >
    {{formatCost(item)}}
  </view>
</template>

<script>
export default {
  data(){
    return{
      costList:[]
    }
  },
  methods: {
    formatCost(item){
    return item.toFixed(2)
    },
    getData(){
    let arr = [3.255,4.1,5,15]
    this.costList = arr
    }
  }
</script>

这个时候 {{formatCost(item)}}里面的内容,会渲染成空字符串,理由就是因为不支持函数,而且这中情况,
也无法使用computed属性,除非你想为每个数组元素写一个computed

这种情况,我的解决方案是在在获取到数据的时候,就先把数据改了.如上面的例子,我们可以在 getData方法里面这样写

let arr = [3.255,4.1,5,15]
// 遍历数组里面的元素,然后格式化一下,添加到 costList里去
arr.map(item => {
    this.costList.push = this.formatCost(item)
})


三, 所有页面里面的created生命周期函数 都会在小程序加载的时候,
一次性执行,而不是每进入一个页面执行一次,如,我有3个页面

pageA

...省略一些代码...
creatted(){
    console.log('pageA 的 created函数执行')
}

pageB

...省略一些代码...
creatted(){
    console.log('pageB 的 created函数执行')
}

pageC

...省略一些代码...
creatted(){
    console.log('pageC 的 created函数执行')
}

然后,启动小程序,不进入这3个页面,假设我现在有一个index页面,我们打开这个页面,会有一下输出

pageA 的 created函数执行
pageB 的 created函数执行
pageC 的 created函数执行

这个其实很好解决,用mounted或者onLoad或者onReady代替,说到这几个函数,那就顺便提一下,
这里的created和mounted是vue(mpvue)的生命周期,而onLoad、onReady是小程序的生命周期,mpvue官方给的说明是:

除了 Vue 本身的生命周期外,mpvue 还兼容了小程序生命周期,这部分生命周期钩子的来源于微信小程序的
Page, 除特殊情况外,不建议使用小程序的生命周期钩子。

但是官方给的生命周期图示里面,也表明了,小程序的onLoad、onReady比created、mounted执行的早,
也就是说如果我们在和onLoad onReady里面去请求数据的话,会相对的减少白屏时间(这里说的白屏是指数据未渲染的界面),
而且官方没说明为什么不建议使用小程序的生命周期,我们也尝试了,用小程序的生命周期,没发现生命问题,
所以我们还是比较倾向优先使用小程序的生命周期,毕竟用户体验才是王道。

四、挂载在Vue.prototype上的属性,在模板语法里面是undefined,必须经过computed计算过一下才能用。
在用vue的时候,我喜欢把图片的服务器路径存到vue的原型里面:

import config from './config'
Vue.prototype.$serverPath = config.serverPath

然后 我们在页面里面这样用

<img :data-original="$serverPath + 'logo.png'" />

这样 就可以避免在每个页面导入config文件,后期如果我们发布正式版的时候,只要在这边修改一下config配置文件就可以了
然额,这样写在mpvue里面,实际渲染出来的会是

<image data-original="undefinedlogo.png" ></image>

要想在每个页面里面使用,只能乖乖在每个页面里面导入,或者在computed里面返回this.$serverPath

五、用 v-for循环的时候,如果要给当前项指定一个索引,在vue下,为了省事,我通常喜欢这样做

v-for="item,index in list"

因为多打一对括号真的是很烦人。但是在mpvue下面却不行,你必须老老实实这样写,否则会报错。

v-for="(item,index) in list"

六、单独为每个页面的设置页面头部信息,有提供这个功能,不过文档不是很详细,几经尝试,才试出来。

我们的入口文件main.js(延续vue的叫法,暂且这么称呼吧,其实我觉得应该叫配置文件)里面可以这样配置,
官方文档大概也是这么说的

这部分内容来源于 app 和 page 的 entry 文件,通常习惯是 main.js,你需要在你的入口文件中
export default { config: {} },这才能被我们的 loader 识别为这是一个配置,需要写成 json 文件。
import Vue from 'vue';
import App from './app';

const vueApp = new Vue(App);
vueApp.$mount();

// 这个是我们约定的额外的配置
export default {
    // 这个字段下的数据会被填充到 app.json / page.json
    config: {
        pages: ['static/calendar/calendar', '^pages/list/list'], // Will be filled in webpack
        window: {  // 顶部栏的统一配置
            backgroundTextStyle: 'light',
            navigationBarBackgroundColor: '#455A73',
            navigationBarTitleText: '美团汽车票',
            navigationBarTextStyle: '#fff'
        }
    }
};
同时,这个时候,我们会根据 entry 的页面数据,自动填充到 app.json 中的 pages 字段。
pages 字段也是可以自定义的,约定带有 ^ 符号开头的页面,会放到数组的最前面。

我们看到,可以在config.window下面配置全局的顶部栏样式,但是如果我们想为每个页面指定一个样式呢?事实上,
以上方法只适合配置app.json里面的内容,如果你想要为你的每个页面都添加一种样式,那么应该这样做:
在页面所属的入口文件(main.js)里面添加以下内容,比如我想为 userCenter/index页面设置一个标题,
应该在userCenter/main.js里面加入

export default {
  config: {
    navigationBarTitleText: '个人中心',
  }
}

注意 这里跟上面那个全局配置不同的是,配置内容navigationBarTitleText是config的属性,
而全局配置里,则是config.window的属性

七、组件的命名问题,有一次,我写了一个局部的组件,为什么叫局部的组件呢,因为我只在某个页面里面使用,
所以为了简单化,我给这个组件取了个名字叫list.vue,然后在父组件引用:

<template>
<!-- 省略其他代码 -->
    <list />
</template>
<script>
  import list from './components/list'
  export default {
    components: {list},
    // 省略其他代码
  }
</script>

组件能正常显示,样式也没问题,一切看上去都是那么的正常,然而组件里面的逻辑就是不会执行。
加上本文第一点提到的,不会报错,让我一顿好找啊...
经过排查发现,跟组件的引入名称有关,应该是跟微信的关键字同名了。

<template>
<!-- 省略其他代码 -->
    <listA />
</template>
<script>
  import listA from './components/list'
  export default {
    components: {listA},
    // 省略其他代码
  }
</script>

这样就能正常运行,出了list,我目前踩到的还有tabbar,搞得我现在命名的时候,看到一些疑似关键的字眼,心理都有点阴影。。
这个应该是微信的问题吧,总之遇到了,就一块写出来。

八、组件第一次加载的时候不能执行onShow里面的内容,只有在隐藏又显示后,才会显示,而页面却每次进入都会显示
例如我们在一个组件里有一下代码

onLoad () {
  console.log('onLoad')
},
onShow () {
  console.log('onShow')
},
mounted () {
  console.log('mounted')
},

页面加载的时候,我们期望打印出来的是

onLoad
onShow
mounted

然后实际上,只打印出

onLoad
mounted

这个问题,我已给官方提Issue,不过目前还没得到回应

说到这里,我们顺便看看小程序的页面跳转方式,小程序在一个页面跳转(调用wx.navigateTo)到另一个页面的时候,
并不会销毁原来的页面,而是转到后台去,并且执行原页面里面的onHide里的代码,
这也是为什么小程序的页面路径最多只能十层,因为你访问过的页面,正常都会保存在内存里,相当于vue里的keep-alive,
如果允许跳转非常多页面的话,很容易导致内存使用过高。

当然,我们也可以使用wx.navigateBack wx.redirectTo wx.reLaunch 来销毁页面,这3个方法,会调用页面的onUnload函数

九、canvas放在scroll-view不会随着页面滚动,看起来好像是fixed固定在某个位置的,但是在普通的view里面却可以正常滚动。
这个问题其实是微信的问题,官方文档里面是有说明这点,不过我遇见问题的时候,没想到会是微信官方出的问题,各种百度谷歌,
都没找到这跟这个问题有关的,甚至我很怀疑是我自己代码的问题,于是新建了一个项目,然后直接考官方的示例代码,也是一样的效果。
后面就准备放弃,想其他解决方案了,没想到今天在官方文档 -scroll-view组件的介绍的最底部的 小字 里看到了

tip: 请勿在 scroll-view 中使用 textarea、map、canvas、video 组件

进一步查看了canvas组件的文档,发现也有类似的提示

tip: 请勿在 scroll-view、swiper、picker-view、movable-view 中使用 canvas 组件。

之所以把这一点也算进来,一是为这个问题坑了我好几天,我都在想其他方案了,二是这几天各种百度谷歌,
是有搜到几个类似的问题,但是都没人回答,我就在这边记录一下,希望后面踩到这个坑的童鞋能搜到。

十、同一个子组件,在2个不同的地方引用,会导致2个地方的样式都加载不了,而如果只在一个地方引用却没问题,
为什么把这个问题放到最后? 因为这只是前几个版本的脚手架有这个问题,后面的应该就没有这个问题了。
这个问题我也给官方提过Issue,官方给的回答是用新版本的脚手架重新生成项目,但是项目都快做完了,
这个时候重新生成,然后拷贝代码,感觉心太累了,所以抱着不折腾不罢休的态度,终于找到原因,是因为早期版本的脚手架,
缺少了 webpack-mpvue-asset-plugin 这个插件,新版的cli里面会自动添加这个插件。具体看Issue #180

还有一些官方明确指出的问题,这里就不一一列举了,有兴趣的童鞋可以直接查看mpvue官方文档

另外,最近正在做一个mpvue的基础教程,有兴趣的童鞋请前往我的
githubmpvue-tutorials,
您的一个Star,就是我最大的动力了。

查看原文

赞 32 收藏 29 评论 3

不爱吃西红柿的鱼 赞了文章 · 2018-04-08

mpvue 与微信小程序的火花

介绍

项目介绍

WeScale 定位为音乐训练小程序,初期规划了基础音阶的三个训练,以及他们的镜像模式。

  • 数字简谱
  • 字母简谱
  • 数字简谱对字母简谱

后期看情况更新追加其他训练。

产品展示

扫描下方小程序码或在微信小程序中搜索 WeScale,即可使用。

人员介绍

  • Myou Aki:明神,北漂前端,总有奇奇怪怪的想法想要实现,适合做产品的前端
  • Dr.Chan:老陈,后端、前端通吃,长得帅说话又好听的茂名吃货
  • Jackliu:大坚,产品、伪前端,不想做前端的产品不是好司机

缘起

明神每晚都要练着他的电吉他,敲着他的木鱼,突然一道光在脑海中闪过,机智的他迅速捕获到,当晚凌晨三点做完了这次小程序的原型。

之前和老陈搞了个 A股股票助手 --- stock-helper ,这次有明神带路,我们都想积累点小程序开发的经验,于是我和老陈就上车了。(滴~~学生卡)

恰逢美团刚刚开源了 mpvue,短短几周就迅速获得几千个 star,在 mpvue 开源前,最流行的应该是 wepy 。据说用 mpvue,能够像德芙一样顺滑地使用 vue 写微信小程序,于是我们开始了踩坑之路。

项目统计

预计一周完成,毕竟是大家都有正经事要做,硬是拖到了两周才完成。四个分支,总计提交 51次,越到 deadline 提交越多,目前已发布 v1.0.0 版本,已审核上线。

踩到的坑

  • 微信小程序不能使用本地资源

这个坑很常见,微信小程序不支持本地引用图片、音频、视频,所以需要外链。对于图片还可以使用 Base64 编码,直接在 html 或 css 中引用。根据图片根据图片体积或者可维护性考虑,酌情使用外链或者Base64编码。

  • 新增页面需要 npm run dev

这个是 mpvue 的问题。常见问题可以发现。解决的方法就是手动 npm run dev 一下。

  • 生命周期问题

mpvue 是兼容微信小程序的生命周期与 vue 的生命周期,也就是 vue 实例会接管小程序 Page 实例的生命钩子,因此需要使用到小程序的生命周期钩子时,可将相应的钩子方法定义在 vue 实例中,如定义当前Page的分享标题内容图片:

new Vue({
  data () {
    return {
      score: ''
    }
  },
  onShareAppMessage (res) {
    return {
      title: '我获得 ' + this.score + ' 分,快来一起掌握基础音阶知识吧!',
      path: '/pages/index/index',
      imageUrl: 'https://wechat.dddog.com.cn/static/wescale.jpg'
    }
  }
})

这个不知道如何描述,大致是非当前页面的 create() 会在当前页面执行,解决方法,用小程序的 onload()/ vue 的 mounted(),遇到问题看图就好:

lifecycle

  • Class 与 Style 绑定

不支持 vue 官方文档:Class 与 Style 绑定中的 classObject 和 styleObject
语法。
暂不支持在组件上使用 Class 与 Style 绑定

不支持就不用咯~

  • 没有 BOM/DOM 操作

mpvue 使得开发者可以使用标准 html、css 去编写小程序,当我们查看 mpvue 项目中的 dist 文件夹时可以发现,编写的 html、css 被解析成了小程序的 wxml、wxss ,固然小程序的运行环境也就是非标准的 WebView 了。因此我们web开发进行经常使用的 browser、navigator 实例自然是无法使用了,取而代之的是使用小程序浏览器提供的API —— wx实例去操作native元素。至于 DOM 操作,即使在vue中也是不建议使用的,还是用数据驱动去转化吧。也就是说所有关于 BOM / DOM 的操作都不行。用 vue 第三方 UI库时要注意, Dom 和 Bom 相关的 API 操作都无法实现。
解决方案: 这块主要是动画不能用,那就用 css3 咯~

  • 组件名不要和微信的组件名重名

试着写一个 swicth 的组件,发现渲染结果不对,查了原因才发现,微信小程序也有个 switch 的组件。
解决方案: 改名字啊。命名规范!

  • 微信小程序多声道

按正常的套路去使用小程序的 api —— wx.createInnerAudioContext() 是无法创建多声道的。本次技术的难点也在于如何创建微信小程序的多声道。查了一圈的资料,关于这点的资料甚少。查到一篇博客,通过创建多个 innerAudioContext 实例化对象,轮流调用的方式。对于原作者说小程序只能同时存在5个音频实例这一定,不敢苟同。毕竟我直接创建了 30个都没问题,哈哈

const audioContextNum = 30
let globalAudioContext = Array.from({ length: audioContextNum },
  (v, k) => wx.createInnerAudioContext())

如何寻找当前可用的声道,也是个难点,大致的思想是,把正在播放的实例封锁,待实例的 onEnded() 回调执行时取消封锁,使用时需要遍历所有实例,寻找当前可用的实例,看实例代码(与实际代码有删改):

// 自动寻找一个当前可用的 audioContext 实例
export function playedMusic (url) {
  let contextList = store.getters.globalAudioContext

  while (contextList !== store.getters.audioContextStatus.map(item => item === false).length) {
    let audioContextStatus = store.getters.audioContextStatus
    let index = store.getters.currentAudioIndex
    // 如果当前可用,封锁
    if (audioContextStatus[index]) {
      store.commit('setAudioContextStatus', {index, status: false})
      break
    } else {
      // 否则 ++index
      store.commit('setCurrentAudioIndex', ++index)
    }
  }

  const resultPromise = new Promise((resolve, reject) => {
    contextList[index].onPlay(() => {})
    contextList[index].onError((res) => {
      reject(res)
    })
    contextList[index].onEnded((res) => {
      reset(resolve)
    })
  })

  return resultPromise
}
  • 微信小程序的缓存

实际开发过程中发现。如果不预先对音频进行缓存,实际播放时会有一定的延迟,视网络情况。解决方案是先预加载,然后存在小程序的缓存中,官网介绍缓存有 10 M,足够用了。
首先是下载文件 wx.downloadFile(),得到 tempFilePath,再把临时文件保存为本地文件 wx.saveFile(),得到 savedFilePath,再将本地文件的的路径保存在缓存中 wx.setStorage()。这么多异步操作,当然用 Promise 再封装一下啦。

多文件的下载、保存、缓存, 回调、递归的思想:

  // 加载资源, 加载完隐藏loading
  _load(0, () => {
    // 更改Audio.js的config对象属性。
    config.musicUrl = JSON.parse(musicUrlTemp)

    const temp = JSON.parse(musicUrlTemp)
    temp.tempVerison = tempVerison
    wx.setStorage({key: 'musicUrl', data: temp})
    wx.hideLoading()
  })
  function _load (index, callback) {
    if (!musicUrlArr[index]) {
      callback()
    } else {
      downloadFile(musicUrlArr[index]).then((tempFilePath) => {
        saveFile(tempFilePath).then((savedFilePath) => {
          musicUrlTemp = musicUrlTemp.replace(
            musicUrlArr[index],
            savedFilePath
          )
          index++
          _load(index, callback)
        })
      })
    }
  }

缓存是否存在及缓存版本的判断:

  // 判断是否已有缓存且缓存版本正确
  if (temp && temp.tempVerison === tempVerison) {
    return false
  }
  • 全局变量

遇到很多需要全局变量,特别是状态的,最好统一管理。vue 的 vuex 是专为 Vue.js 应用程序开发的状态管理模式。使用过程遇到的坑是无法使用它的辅助函数 mapState、 mapGetters、 mapActions、 mapMutations 等。看下 mpvue 的 issue 感觉是 mpvue 的问题。
解决方案: 用最原始的 store.commit()、 store.getter

  • 数据分析及合法域名

调用微信小程序的网络请求 wx.request()、 wx.downloadFile() 之类 都需要 https 协议。 调微信的数据分析还要隔两个小时获取 access_token, 这些就是要服务器端的配置了。

条件: 域名及域名证书、服务器

获取 token 及 服务器写接口返回静态文件及微信的数据分析接口 可以参考这个, node.js 写的,写的很随意,随便看看。

  • ES6 的模块动态引用

参考博客:

  1. ES6 模块中的值属于【动态只读引用】。只说明一下复杂数据类型。
  2. 对于只读来说,即不允许修改引入变量的值, import 的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到 import 命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
  3. 对于动态来说,原始值发生变化, import 加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
// b.js
export let counter = {
  count: 1
}
setTimeout(() => {
  console.log('b.js-1', counter.count)
}, 1000)

// a.js
import { counter } from './b.js'
counter = {}
console.log('a.js-1', counter)

// Syntax Error: "counter" is read-only

虽然不能将 counter 重新赋值一个新的对象,但是可以给对象添加属性和方法。此时不会报错。这种行为类型与关键字 const 的用法。

// a.js
import { counter } from './b.js'
counter.count++
console.log(counter)

// 2

致谢

致谢所有参与产品、开发、测试,贡献出创意想法与建议的小伙伴。

我们有个小团队,自嘲为“咸鱼科技”,谁说咸鱼不能有梦想,哈哈。我们还需要 UI、运营等,如果你有想法、有创意、有技能可以加入我们的小团队!2333~

查看原文

赞 6 收藏 9 评论 0

认证与成就

  • 获得 532 次点赞
  • 获得 24 枚徽章 获得 1 枚金徽章, 获得 8 枚银徽章, 获得 15 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • 我的博客

    我的博客:在提交前用 Node.js 遍历文章目录生成 list.json,前端 Vue.js 根据 list.json 信息 再用 Fetch 加载 MarkDown 文件后前端渲染页面。

  • Chinese Checkers 中国跳棋

    Chinese Checkers by React.js & Socket.io. 一个由 React 为主要轮子写的中国跳棋,加上 Socket.io 通信,使两端都能连接玩耍,用了 React 全家桶。

注册于 2015-10-01
个人主页被 7.9k 人浏览