微信小程序 Source Map 的使用及一键解析

背景介绍

在微信小程序后台的错误日志里或项目自己收集的错误日志里,经常会出现一些如下格式的错误堆栈信息:

Cannot read property 'video' of undefined
TypeError: Cannot read property 'video' of undefined
at a.<anonymous> (https://usr/app-service.js:8534:329320)
at s (https://usr/app-service.js:8534:2420)
at Object.next (https://usr/app-service.js:8534:1715)
at https://usr/app-service.js:8534:1440
at s (https://usr/app-service.js:8534:1211)
at a.t.swiperChangeUpDown (https://usr/app-service.js:8534:328219)
at a.<anonymous> (https://usr/app-service.js:8534:327713)
at s (https://usr/app-service.js:8534:2420)
at Object.next (https://usr/app-service.js:8534:1715)
1
2
3
4
5
6
7
8
9
10
11

以错误堆栈信息里的第 3 行为例,其显示错误发生在app-service.js文件的8543329320列,但在我们的微信小程序的产出里并不存在类似的app-service.js文件。

实际上,在微信开发者工具或 CI 工具上传小程序项目时,会将项目里的app.js、各个页面的js文件等所有的 JS 文件编译并打包成一个 JS 文件(每个分包会单独打包成一个 JS 文件),这个文件就是app-service.js

尽管有打包后的 JS 文件的报错位置,但想要定位到微信小程序源码里哪里报错,却依然比较困难。

Source Map

微信小程序为了开发者能够根据打包后的 JS 错误位置定位到源码里的错误位置,提供了 Source Map 支持。在开发者工具里若是开启了“ES6 转 ES5”(或“代码压缩”、“增强编译”),在上传代码时会生成Source Mapopen in new window文件,方便定位错误位置。

小程序后台获取的 Source Map 压缩包解压后的结构如下:

├── __APP__
│   └── app-service.map.map
├── __FULL__
│   └── app-service.map.map
├── sub-package-A
│   └── app-service.map.map
├── sub-package-B
│   └── app-service.map.map
└── ...
    └── app-service.map.map
1
2
3
4
5
6
7
8
9
10

其中:

  • __APP__/app-service.map.map是小程序主包的 Source Map 文件
  • __FULL__/app-service.map.map是小程序主包 + 分包合并在一起的 Source Map 文件
  • 其他的是各个分包单独的 Source Map 文件,比如sub-package-A/app-service.map.mapsub-package-A分包的 Source Map 文件

获取到 Source Map 文件并按照官方的 Source Map 使用方法open in new window编写代码进行解析后,可以将app-service.js里的错误位置还原到上传之前的小程序代码。

然而,我第一次获得的小程序源码位置仍然不是我实际开发时的文件位置。对比解析得到的文件名称发现,我们是使用了小程序第三方开发框架uni-app并使用Vue.js来开发微信小程序的,而uni-app在生产环境打包时会将Vue.js开发的源码编译为经过压缩混淆的小程序代码,因此我们通过 Source Map 解析出的源码位置其实是uni-app编译产出的小程序代码的位置,而不是使用Vue.js编写的源码位置。

幸运的是,微信小程序针对这种“使用外部的编译脚本对源文件进行处理”的情况,也提供了解决方案,只需我们将第三方开发框架的源码在编译为小程序代码时生成的 Source Map 文件放置在小程序代码文件的相同目录下,开发者工具会读取、解析 Source Map 文件,并基于该 Source Map 文件在打包上传时生成最终的 Source Map 文件。

使用小程序第三方开发框架编译后产出的小程序代码目录:

├── pages
|   ├── index.js
|   ├── index.json
|   ├── index.wxml
|   ├── index.wxss
|   └── index.js.map
├── app.js
├── app.js.map
1
2
3
4
5
6
7
8

经过这样的方式产生的 Source Map 文件,就能将app-service.js里的错误位置映射到第三方开发框架的源码位置上。以下我将简单地描述如何配置项目来生成 Source Map 文件。

生成 Source Map

对于未使用小程序第三方框架而是采用原生的微信小程序语法开发的项目来说,只需要在project.config.json里配置开启“ES6 转 ES5”(或“代码压缩”、“增强编译”),这样在上传体验版时就会生成 Source Map 文件。

project.config.json里相关配置如下:

{
  "description": "小程序项目配置文件",
  "setting": {
    // 以下三个配置,至少开启一项,上传代码时才能生成 Source Map
    "es6": true, // 是否启用 ES6 转 ES5
    "minified": true, // 上传代码时是否自动压缩
    "enhance": true, // 是否打开增强编译
    // ...
  }
}
1
2
3
4
5
6
7
8
9
10

针对使用了小程序第三方框架的项目,除了以上的project.config.json配置,还要配置第三方框架在生产环境打包时生成初始的 Source Map 文件。

uni-app开发的项目来说,配置 Webpack 在生产环境打包时生成 Source Map 文件,并将该 Source Map 文件放置在与原先的 JS 文件同一目录下。

// vue.config.js
module.exports = {
    // ...
    configureWebpack: config => {
        if (process.env.NODE_ENV === 'production') {
            config.devtool = 'source-map';
            config.output.sourceMapFilename = '../' + process.env.UNI_PLATFORM + '/[name].js.map'
        }
    },
    // ...
}
1
2
3
4
5
6
7
8
9
10
11

获取 Source Map

目前有很多种方式获取 Source Map 文件:

  • 若是通过微信开发者工具上传小程序代码,在上传完成后可以下载 Source Map 压缩包
  • 若是通过CI 工具open in new window上传,可以在上传完成后调用ci.getDevSourceMap方法获取 Source Map 压缩包
  • 此外,针对线上版小程序,可在“微信小程序后台open in new window --> 开发管理 --> 错误日志 --> 错误内容”里下载

解析 Source Map

目前微信小程序官方主要提供了两种解析 Source Map 的方式。

方式一:自行编写代码进行解析。

import fs = require('fs')
import { SourceMapConsumer } from 'source-map'


async function originalPositionFor(line, column) {
  const sourceMapFilePath = 'Source Map 文件在硬盘里的位置'
  const sourceMapConsumer = await new SourceMapConsumer(JSON.parse(fs.readFileSync(sourceMapFilePath, 'utf8')))

  return  sourceMapConsumer.originalPositionFor({
      line,
      column,
   })
}

originalPositionFor(出错的行,出错的列)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

方式二:下载最新版的开发者工具,使用“菜单-设置-拓展设置-调试器插件”,添加“sourceMap 匹配调试”插件。

添加 Source Map 插件使用 Source Map 插件

一键解析

官方提供的两种解析 Source Map 的方式,每次只能解析单个 Source Map 文件,每次解析过程为:

  1. 获取错误堆栈每一行报错信息的文件名称及行列号
  2. 自行判断需要使用主包或是哪个分包的 Source Map 文件
  3. 手动上传选定的 Source Map 文件,输入行列号,进行解析

这个过程在常规的错误堆栈日志都多达十来行的的情况下(正如文章开头所展示的),解析起来将会极其低效。

为了解决这个问题,我开发了一键解析 Source Map 的服务,可以一次性将错误堆栈里的所有报错信息全都解析出来。我们只需将整个 Source Map 压缩包上传,输入完整的错误堆栈日志,即可一键解析。

服务地址:微信小程序 Source Map 一键解析服务open in new window

上传 Source Map 压缩包进行一键解析

常见问题说明

经常会有这样、那样的原因导致错误无法解析到源码上,比如:

  • 问题一: 解压下载的 Source Map 压缩包,打开其中任意一个 Source Map 文件,文件中的mappings字段里全是分号;

这个问题是最普遍的,其原因是在上传小程序项目时,没有开启“ES6 转 ES5”/“代码压缩”/“增强编译”,导致没有生成 Source Map,这也是下载的 Source Map 文件里的mappings字段里全是;的原因。解决方法是,“ES6 转 ES5”/“代码压缩”/“增强编译”这三个选项至少要开启一个,上传代码时才能生成 Source Map。

  • 问题二:WAServiceMainContext.jsWASubContext.js等文件里的错误无法解析出来。

WAServiceMainContext.jsWASubContext.js等文件都是微信小程序基础库里的文件,无法解析出来是正常的。Source Map 是我们小程序的业务代码在上传时生成的,自然只能解析出业务代码(app-service.js)产生的错误。当然,有时候基础库文件产生的错误,可能也是我们业务代码导致的,我们可以先解析 Source Map 定位到业务代码的错误位置,进而确定是什么原因导致的基础库文件报错。

  • 问题三:错误日志是未开启 Source Map 功能的线上版本代码产生的,Source Map 压缩包是开启 Source Map 后在开发者工具上传获得的。

这种情况是错误日志与 Source Map 压缩包不配套导致的。使用解析服务时,若错误日志是从线上获取到的,则一定要使用开启了 Source Map 的线上对应版本的 Source Map 压缩包,小程序版本要完全一致。比如,错误日志是小程序版本 v1.1.1 产生的,则需要使用开启了 Source Map 功能的小程序版本 v1.1.1 对应的 Source Map 压缩包。

总结

微信小程序 Source Map 的使用对我们追查小程序线上报错起到了极其重要的作用,而 Source Map 一键解析服务则极大地提升了解析效率。目前 Source Map 一键解析服务仍处于逐步完善状态,非常欢迎大家试用。如有更好的优化建议或者遇到使用问题,欢迎在服务页面留言!

参考文档