0%

前言

JS代码发布到生产环境一般需要经过压缩混淆文件合并来减少体积和HTTP请求,而且我们现在使用TS开发,需要经过编译成JS代码。
这样得到的代码debug或者分析线上错误会比较困难。
这时候就需要SourceMap来协助解决了。

什么是Source map

Sourcemap是一个存储位置信息的文件。文件里霸气村的是转换后的代码位置和对应的转换前的位置。
这样在浏览器的调试工具里就可以直接看到原始代码,而不是转换后的代码了。

转换后的代码

转换前的代码

浏览器中如何启用sourcemap

在压缩代码的最后一行,加上这个引用,浏览器便会自动启动sourcemap。

1
//# sourceMappingURL=app.js.map

wenpack中如何配置生成sourcemap

在Vue-cli中可以默认在production环境下是生成sourcemap的,所有如果配置中有这句话,注释掉就可以了。

在其他模式下可以这样设置

1
2
3
4
5
6
7
module.exports = {
...
configureWebpack: config => {
config.devtool = "source-map";
},
...
};

Source Map 格式

SourceMap的格式如下, 整个文件是一个json文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"version": 3, // SourceMap的版本
"sources": [ //转换前的文件,有多条的话代表可能存在多个文件合并的情况
"app.js",
"header.js"
],
"names": [ //转换前的所有变量名和属性名
"data"
],
"mappings": "EAAcJ,EAAK,GACnBK,EAAiBL;EAAK,GAIHM,EAAI;", // 记录位置信息的字符串
"file": "js/app.c09f6ba8.js", // 转换后的文件名
"sourcesContent": [ // 转换前的文件内容列表,与sources列表依次对应
" \t// install a JSONP \n}\n"
],
"sourceRoot": "" // 转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空。
}

mappings属性

两个文件的位置是如何对应起来的。
关键就是mappings属性,它分为3层。

  • 每个分号对应转换后源码的一行。
  • 每个逗号对应转换后源码的一个位置。
  • 每一段(aACE)以VLQ编码标识,代表该位置转换前的源码位置

以这个为例

1
"mappings": "EAAcJ,EAAK,GACnBK,EAAiBL;EAAK,GAIHM,EAAI;"

标识转换后的源码有两行,第一行有3个位置,第二行有两个位置。

位置对应的原理

每个位置用5位标识

  • 第一位,表示这个位置在转换后代码第几列
  • 第二位,表示这个位置属于sources属性中的哪一个文件
  • 第三位,表示这个位置属于转换前的代码第几行
  • 第四位,表示这个位置属于转换前的代码第几列
  • 第五位,表示这个位置属于names属性的的哪一个变量

需要注意的是

  • 所有值都是以0作为基数
  • 第五位不是必须的,如果该位置没有对应names属性中的变量,第五位可以省略
  • 每一位都采用VLQ编码表示, 由于VLQ编码是变长的,所以每一位可以由多个字符构成

比如某个位置是AAAAA,name表示这个位置的5个位实际上都是0。意思是,该位置在转换后的代码的第0列,对应的souces属性中的第0个文件,属于转换前代码的第0行第0列,对应names属性中的第0个变量。

Base64 VLQ编码

VLQ是Variable-length quantity 的缩写,是一种通用的、使用任意位数的二进制来表示一个任意大的数字的一种编码方式。
它的特点就是可以非常精简地表示很大的数值。

对16进行VLQ编码

  • 将16改写成二进制形式10000
  • 在最右边补充符号位。因为16大于0,所以符号位为0,整个数变成100000。
  • 从右边的最低位开始,将整个数每隔5位,进行分段,即变成1和00000两段。如果最高位所在的段不足5位,则前面补0,因此两段变成00001和00000。
  • 将两段的顺序倒过来,即00000和00001。
  • 在每一段的最前面添加一个”连续位”,除了最后一段为0,其他都为1,即变成100000和000001。
  • 将每一段转成Base 64编码。
  • 查表 数值16的VLQ编码为gB。
步骤 结果
将16改写成二进制形式 10000
16大于0,末尾符号位补0 100000
五位一组做分组,不足的补0 00001 00000
将组倒序排序 00000 00001
最后一组开头补0,其余补1 100000 000001
转64进制 gB

如何利用SourceMap 还原异常部分的源码

比如报错信息如下

1
2
3
4
5
6
7
{
"message": "TypeError: undefined is not an object (evaluating 'e.effectiveType')",
"filename": "https://xxx.xxx.cn/js/app.e29e2bb2.js",
"lineno": 1,
"colno": 60783,
"error": {}
}

Firefox 开源一个 source-map 的包,可以利用这个来还原代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const sourceMap = require('source-map')
const fs = require('fs')
const { readFileSync } = fs
const { SourceMapConsumer } = sourceMap

// 读取map文件
const rawSourceMap = fs.readFileSync('./app.xxx.js.map', 'utf8')
// 通过sourceMap库转换为sourceMapConsumer对象
const smc = await new SourceMapConsumer(rawSourceMap)
// 传入要查找的行列数,查找到压缩前的源文件及行列数
const ret = smc.originalPositionFor({ line: 1, column: 60783 })
const co = smc.sourceContentFor(ret.source)
const coList = co
.split('\n')
.slice(ret.line - 3, ret.line + 3)
.join('\r\n')

这样就可以还原到原来的代码,然后可以展示出附近的行数

下载镜像

1、首先打开Docker, 选择注册表,下载对应版本的mysql镜像,默认选择latest为最新版本

2、下载成功后,在映像里即可看到该镜像

创建并启动容器

选中下载的镜像,点击启动

设置好容器名字之后,点击高级设置。

在创建容器之前,可以先设置一下端口和映射文件夹

文件夹设置如下:

如果启动失败,可能是文件夹权限不够,可以不设置文件夹映射
或者ssh到群晖里,设置权限

1
sudo chmod -R 777 /volume1/xxx

为了不和本机的端口冲突,端口设置如下:

在环境变量中设置root初始密码

点击应用,启动容器

设置远程访问

在容器启动后,需要进入容器对mysql进行一些配置,以便在局域网内访问

1、进入终端,选中mysql容器,点击左上角的详情

2、点击终端 -> 新增,可以看到下方出现用户和主机名

3、登录mysql设置远程访问

  • 登录mysql
    1
    mysql -uroot -p
  • 输入密码后,给root用户设置远程权限
    1
    2
    use mysql;
    GRANT ALL ON *.* TO 'root'@'%';
  • 修改认证方式, mysql8.0 以上版本可以修改认证方式 以兼容旧的软件
    1
    2
    ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
    select host, user, plugin from user;

此时用软件可以看到已经可以链接上mysql

4、创建新用户
为了安全考虑,可以创建新用户并设置远程访问权限,而不是给默认的root用户添加远程访问权限

  • 创建用户
    1
    CREATE USER 'user1'@'%' IDENTIFIED BY 'xxx';
  • 设置远程登录
    1
    ALTER USER 'user1'@'%' IDENTIFIED WITH mysql_native_password BY 'xxx';
  • 设置数据库权限
    1
    GRANT ALL PRIVILEGES ON *.* TO 'user1'@'%' WITH GRANT OPTION;
  • 刷新权限(GRANT ALL 需要)
    1
    flush privileges;
  • 更新root密码
    1
    ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'password';

至此,mysql就完成了安装和配置了