前言
通常在面试中,除了考查技术根底、技术深度、技术广度,甚至是算法等外,通常还会考查你解决理论 场景问题 的能力。
面试官 A
:你这边的技术状况我基本上有大抵的理解了,那么接下来我问你一些场景题,给出大抵的思路就好了。
第一个问题就是:如果你要疾速排查我的项目在线上的问题,你会在怎么做?
面试者 B
:通常会依据错误信息来进行排查,因为错误信息中会给出对应的行列信息,能够通过这个来进行定位,或者是有对应的监控平台上能够查看对应的谬误日志等
面试官 A
:那你有没有思考过构建产物,也就是运行时代码是通过 代码分块、代码压缩、代码混同的,换句话说这个错误信息和你的源代码是不雷同的,间接定位仿佛不能实现疾速定位吧!
面试者 B
:这个我不太理解,您这边是怎么解决这个问题的呢?
面试官 A
:你没有过理解sourcemap
吗?bla… bla…
除了上述场景之外,其实大多我的项目比拟老,意味着其中既没有埋点也没有接入谬误监控,因而于是会失去如下的这样一个提示信息(以下谬误是模仿的成果):
当你想要间接点击链接定位时,就会看到如下的模式:
这怎么说呢?不能说截然不同,只能说息息相关()!外表上
针对老我的项目再想退出埋点和谬误监控可能就比拟艰难了,特地是当看到其中各种无奈让你了解的写法和逻辑,加之不同开发人员的迭代开发,始终在往上堆 x 山 (),就更加没法做什么进一步的优化了,只能说能跑就行, 不敢动,基本就不敢动! 放弃优雅,该打码就打码
那么还剩下的可能疾速帮忙咱们定位具体错误信息的形式是什么呢?没错就是本文的配角 sourcemap,这也是为什么会有本篇与 sourcemap 相干的文章。
心愿本篇文章能在你面试的路线上给予你一些帮忙!!!
文中若存在不正确之处,可在评论区斧正!!!
sourcemap 的应用和规定
sourcemap 是什么?
简略的说,就是一个以 .map
为后缀的文件,例如:
而 .map
文件外面的内容以 json
模式存储了 源代码 打包转换后的地位信息,核心内容如下:
- version:版本号,目前
sourcemap
规范的版本为 3 - file:指打包构建后的
文件名
,即bundle
文件名 - sources:指以后这个
bundle
文件所蕴含的所有源码文件
,因为存在分包等优化策略,一个bundle
文件可能会蕴含多个源码文件
的内容 - sourcesContent:指上述
sources
中每个源码文件
所对应的源码内容字符 - names:指在代码在经验
混同压缩
之前的变量名
,这个变量名蕴含导入模块名
、罕用办法名
- mappings:间接进行翻译就是
映射
的意思,即依据以上信息实现的源码代码地位和构建产物之间的一一映射关系 -
sourceRoot:指源码目录
sourcemap 有啥用?
下面曾经提到了 .map
文件中以 json
模式存储的数据内容,就是蕴含着源代码与构建产物之间的映射关系,那么它的作用天然就是实现:运行时代码
和 开发时代码
都能领有雷同精确的信息提醒。
常见的 开发时代码提醒,如下:
常见的 运行时代码提醒,如下:
可见 运行时代码提醒 的代码提示信息不够具体精确,而且以上只是简略的模仿了运行时谬误,而理论我的项目中大多数的运行时谬误是在不同的场景下才会呈现的,而此时因为处于生产环境中,在排查异样代码时就会体现出限度:
- 首先,运行时代码 和 开发时代码 不统一,导致错误信息也不同
- 其次,运行时代码 很难通过
debug
的形式进行调试
次要起因就是 开发时代码 到 运行时代码 的转变都须要经验以下几个解决阶段:
- 代码压缩,为了减小运行时代码的体积,会将源代码中的 换行符、无意义空格 等进行删除,使得代码紧凑在一起
- 代码混同,实际上是指将源代码转换成一种性能上等价,然而难于浏览和了解的模式,例如开发时代码中定义的 “ 见名知意 ” 的 函数名、模块名、变量名 等转换为相似 “a、b、c、…” 等无意义的名字,使得即便运行时代码被人获取,也难以猜想其作用
- 代码分块(chunk split),在古代前端构建工具(webpack、vite 等)中都反对将多个源代码文件合并成一个 bundle 文件,目标就是缩小 http 申请数量,以实现优化成果
因而,sourmap 就能够在这些解决阶段中构建出 运行时代码 和 开发时代码 代码的映射关系,使得运行时代码也可能像开发时代码一样提供给咱们具体而精确的信息,帮忙咱们在生产环境中也可能疾速定位到源代码中的地位。
如何疾速生成 sourcemap?
前端构建工具很多,这里只列举最罕用的两个:vite 和 webpack
vite 生成 sourcemap
vite
中只有通过设置 build.sourcemap
的选项配置即可,值类型包含:
-
boolean:true | false
-
其默认值为 false,当设置为 true 时,就会生成独自的
.map
文件,并且在对应的 bundle 文件中通过 正文 来指明对应的.map
文件,如下:
-
-
‘inline’
-
指定为该值 source map 将作为一个 data URI 附加在输入文件中,如下:
-
-
‘hidden’
'hidden'
的工作原理与'true'
类似,只是 bundle 文件中相应的正文将不被保留
webpack 生成 sourcemap
webpack
中只有通过设置 devtool
的选项配置即可,值类型包含以下类型的 组合:
- (none) 默认值
-
eval
- 会生成被
eval
函数包裹的模块内容,并在其中通过正文来注明是源文件地位,其中的sourceUrl
是用来来指定文件名 - 长处就是快,因为不必生成
.map
文件,并且 运行时代码 映射到 开发时代码 只须要提供对应的 源文件地址 -
毛病就是蕴含映射信息少,并且
eavl
函数因为安全性问题也是不倡议应用的
- 会生成被
-
source-map
- 会生成独自的
.map
文件蕴含version、file、sources、sourcesContent、names、mappings、sourceRoot
等信息,须要进行 mapping 和 编码 工作 - 长处就是领有独自的
.map
文件,使得 运行时代码 体积不会过大,并且可能提供具体的信息,蕴含文件名、行、列等信息 -
毛病就是慢,因为须要额定生成
.map
文件,并且随着模块内容的增多整体速度就越慢
- 会生成独自的
-
cheap
- 和
source-map
的形式不同,cheap
只会映射到源码的 行信息 ,即它 不会生成源码的列信息
,也不蕴含loader
的sourcemap
,因而相对来说会比source-map
的形式更快 - 长处就是速度更快,只映射到源码的 行信息 的起因是:通常在进行谬误定位时,大多数状况下只须要关注到 行 就能够晓得谬误起因,而很少会关注到 列,因而列信息其实不是必要性的
- 毛病就是映射信息会不够准确,因为一个文件可能会通过不同
loader
的解决,而它又不生成loader
相干的sourcemap
,天然会导致最终产物的信息不够准确
- 和
-
module
module
的形式生成的sourcemap
就会蕴含和loader
相干的sourcemap
信息- 须要
loader
相干的sourcemap
信息的起因在于:当一个文件被多个laoder
顺次进行转换解决后,其内容会产生不同的变动(例如:新内容的行列 和 源代码的行列 信息不统一
),就会使得咱们无奈去调试最初始的代码内容
-
inline
-
顾名思义,就是会将本来生成的
.map
文件的内容作为 DataURL(base64 模式
) 嵌入bundle
文件中,不独自生成.map
文件
-
-
hidden
-
会生成独自的
.map
文件,然而相比于source-map
的模式,其会在对应的bundle
文件中暗藏sourceMappingURL
的门路
-
-
nosources
- 在
source-map
生成的.map
文件中的sourceContent
存储的是源码内容,这样的益处是既能够依据文件门路来映射,也能够依据这部分内容来映射,换句话说source-map
提供了双重保险,但也减少了.map
文件体积 -
nosources
则是在可能保障文件门路能够精确建设映射的状况下,就能够把sourceContent
的内容给去除掉,使得.map
文件体积可能更小一些
- 在
以上和 webpack
相干的 devtool
的配置内容,eval
、source-map
都能够独自应用,也能够组合应用,但 module、inline、hidden、nosources、cheap
的形式肯定是蕴含 source-map
的内容的,如果你记不住或写错了,webpack
会给你相应的提示信息:
不同环境的 sourcemap 怎么选?
这里的环境指的就是 开发环境 和 生产环境 ,因为不同的组合形式在 构建 和 重构建 时的速度不同,另外还须要思考 .map
文件在线上可能带来的危险问题,因而必须要 衡量 应用 sourcemap
的组合形式,好在 webpack
文档中给咱们提供给了相应的组合形式,如下:
devtool | performance | production | quality | comment | |
---|---|---|---|---|---|
(none) | build: fastest rebuild: fastest | yes | bundle | Recommended choice for production builds with maximum performance. | |
eval |
build: fast rebuild: fastest | no | generated | Recommended choice for development builds with maximum performance. | |
eval-cheap-source-map |
build: ok rebuild: fast | no | transformed | Tradeoff choice for development builds. | |
eval-cheap-module-source-map |
build: slow rebuild: fast | no | original lines | Tradeoff choice for development builds. | |
eval-source-map |
build: slowest rebuild: ok | no | original | Recommended choice for development builds with high quality SourceMaps. | |
cheap-source-map |
build: ok rebuild: slow | no | transformed | ||
cheap-module-source-map |
build: slow rebuild: slow | no | original lines | ||
source-map |
build: slowest rebuild: slowest | yes | original | Recommended choice for production builds with high quality SourceMaps. | |
inline-cheap-source-map |
build: ok rebuild: slow | no | transformed | ||
inline-cheap-module-source-map |
build: slow rebuild: slow | no | original lines | ||
inline-source-map |
build: slowest rebuild: slowest | no | original | Possible choice when publishing a single file | |
eval-nosources-cheap-source-map |
build: ok rebuild: fast | no | transformed | source code not included | |
eval-nosources-cheap-module-source-map |
build: slow rebuild: fast | no | original lines | source code not included | |
eval-nosources-source-map |
build: slowest rebuild: ok | no | original | source code not included | |
inline-nosources-cheap-source-map |
build: ok rebuild: slow | no | transformed | source code not included | |
inline-nosources-cheap-module-source-map |
build: slow rebuild: slow | no | original lines | source code not included | |
inline-nosources-source-map |
build: slowest rebuild: slowest | no | original | source code not included | |
nosources-cheap-source-map |
build: ok rebuild: slow | no | transformed | source code not included | |
nosources-cheap-module-source-map |
build: slow rebuild: slow | no | original lines | source code not included | |
nosources-source-map |
build: slowest rebuild: slowest | yes | original | source code not included | |
hidden-nosources-cheap-source-map |
build: ok rebuild: slow | no | transformed | no reference, source code not included | |
hidden-nosources-cheap-module-source-map |
build: slow rebuild: slow | no | original lines | no reference, source code not included | |
hidden-nosources-source-map |
build: slowest rebuild: slowest | yes | original | no reference, source code not included | |
hidden-cheap-source-map |
build: ok rebuild: slow | no | transformed | no reference | |
hidden-cheap-module-source-map |
build: slow rebuild: slow | no | original lines | no reference | |
hidden-source-map |
build: slowest rebuild: slowest | yes | original | no reference. Possible choice when using SourceMap only for error reporting purposes. |
sourcemap 如何失效?
要使得 sourcemap
发挥作用,单单只是生成对应的映射规定还不够,还须要一个 解析工具 负责将 源代码
和 sourcemap
规定真正进行映射,通常这个解析工具是 浏览器 、 异样监控零碎(如:sentry) 和 手动映射。
浏览器
通常在古代浏览器中基本上会默认启用 sourcemap
映射性能,即只有对应的 bundle
文件中有 sourceMappingURL
或 sourceURL
等指向的正文内容即可,手动开启地位如下(大同小异):
Google 浏览器:
Firefox 浏览器:
Edge 浏览器:
异样监控零碎
这里以 sentry
为例 简略演示 一下,大抵包含:
- 接入 sentry
- 异样捕捉
- 增加 sourcemap
首先,在 Sentry
监控平台上注册 / 登录领有本人的账号,而后能够构建一个对应的我的项目(首次注册登录会有指引),我的项目创立好当前会生成一个 dsn
,在接入 sentry
时须要传入。
其次,依照疏导页的提醒在你的我的项目(以 vue3
为例)中装置依赖 npm install --save @sentry/vue @sentry/tracing
。
最初,在你我的项目入口文件(main.js
)中初始化接入 Sentry
即可:
import {createApp} from "vue";
import {createRouter} from "vue-router";
import * as Sentry from "@sentry/vue";
import {BrowserTracing} from "@sentry/tracing";
const app = createApp({// ...});
const router = createRouter({// ...});
Sentry.init({
app,
dsn: "https://x@x.ingest.sentry.io/x",
integrations: [
new BrowserTracing({routingInstrumentation: Sentry.vueRouterInstrumentation(router),
tracePropagationTargets: ["localhost", ...],
}),
],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
});
app.use(router);
app.mount("#app");
通过以上解决,默认状况下 Sentry
曾经能够主动获取到错误信息了,如:
显然,没有接入 sourcemap
的错误信息在 sentry
中也无奈进行疾速定位,因而下一步就是须要给 sentry
上传 sourcemap
相干的文件。
sentry 上传 sourcemap 流程(
sentry 文档
)
- 装置 webpack 插件:
npm install --save-dev @sentry/webpack-plugin
-
配置 webpack 插件
// vue.config.js const {defineConfig} = require('@vue/cli-service') const SentryWebpackPlugin = require('@sentry/webpack-plugin') module.exports = defineConfig({configureWebpack(config) {if (process.env.NODE_ENV === 'production') { config.devtool = 'hidden-source-map' config.plugins.push( new SentryWebpackPlugin({ include: './dist', ignoreFile: '.gitignore', ignore: ['node_modules', 'webpack.config.js'], configFile: './.sentryclirc', release: '0.0.1', urlPrefix: '~/js/', }), ) } }, })
-
在我的项目根目录下新建
.sentryclirc
文件[auth] token = 在 sentry 平台生成 [defaults] url = https://sentry.io/ // 如果是本人部署的就填部署地址,如果不是就不改 org = sentry 平台的 org project = sentry 平台的 project
- 如皋设置
release
字段,那么要保障main.js
中的Sentry。init({...})
和SentryWebpackPlugin
中的要放弃一致性,或者都不设置 - 构建产物
npm run build
- 进入
dist
目录通过http-server
启动本地服务模仿生产环境产生谬误 -
进入
sentry
中查看异样信息,上传sourcemap
文件后,错误信息如下:
手动映射
通常状况下为了安全性,不举荐应用 浏览器 映射的形式,尽管这种形式对你来说很简便,但也为居心叵测的人提供了便捷,因而,通常都会有接入对应的 监控平台 ,当然除此之外还能够通过 手动映射 的形式进行定位:
- 装置
source map
库:npm install source-map
-
新建
sourcemap.js
const {SourceMapConsumer} = require('source-map') const fs = require('fs') const rawSourceMap = fs.readFileSync('./dist/js/app.dde017e5.js.map', 'utf-8') // 填入错误信息 originalPositionFor('app.dde017e5.js:1:11871') function originalPositionFor(errInfo) {const [budleName, line, column] = errInfo.split(':') SourceMapConsumer.with(rawSourceMap, null, (consumer) => { const originalPosition = consumer.originalPositionFor({line: parseInt(line), column: parseInt(column), }) console.log('bundle name =', budleName) console.log('original position =', originalPosition) })
-
演示如下:
-
sourcemap 的映射原理
在 .map
文件中有 mappings
字段,其内容很难让人不留神,毕竟和其余内容相比看起来太不同凡响了:
实际上,mappings
以 Base64 VLQ 编码模式存储了映射到源代码 行、列 等相干信息。
为什么应用 Base64 VLQ 编码?
源代码通常都是很宏大的,单纯应用 数字 示意 行信息 和 列信息 会使得整个 .map
文件体积变大,而 Base64 VLQ 是一种 压缩数字 内容的编码方式,因而能够用来缩小文件体积。
因为 Base64 所能示意的数字存在 下限 ,如果须要示意超过下限的数字该怎么办,实际上只有 每个分号中的第一串英文 是用来示意代码的 第几行、第几列 的相对地位外,前面的都是绝对于之前的地位来做 加减法 的。
能够通过 BASE64 VLQ CODEC 这个网站理解具体的映射关系.
mappings 的组成
mappings
的内容次要由三局部组成:
-
英文串
- 每段英文串示意 运行时代码 和 开发时代码 地位关联的 base64VLQ 编码内容
-
每段英文串拥由 5 局部组成:
运行时代码
所在的列,通常源代码经压缩后只有1 行
,因而不须要存储行信息,只须要存储列信息- 对应
sources
字段下标,即对应哪个源文件 开发时代码
的第几行开发时代码
的第几列- 对应
names
字段下标,即对应哪个变量名
-
逗号
,
- 用于分隔一行代码中的内容或地位,例如
"var a = 1;console.log(a);"
相当于"var, a, =, 1, console, log"
- 用于分隔一行代码中的内容或地位,例如
-
分号
;
- 示意 运行时代码 的行信息,用来定位是编译后代码的第几行,如果启用代码压缩那么就不会有 分号,因为代码会被压缩在一行上
简略解析 mappings
上面以
console.log(1);
为例子简略介绍下对应关系,毕竟源码内容简单的不好剖析:// main.js console.log(1); // main.js.map { "version": 3, "file": "main.js", "mappings": "AAAAA,QAAQC,IAAI", "sources": ["webpack://vue3-wp5/./src/main.js"], "sourcesContent": ["console.log(1);\n"], "names": ["console", "log"], "sourceRoot": "" }
还是先关注
mappings
字段,其内容因为是编码后的内容,为了更直观的看到其代表的具体数字内容,咱们能够通过 BASE64 VLQ CODEC 网站来失去后果:
当初,咱们晓得了 "AAAAA,QAAQC,IAAI"
对应 [0,0,0,0,0], [8,0,0,8,1], [4,0,0,4]
,联合上述其内容就示意:
-
[0,0,0,0,0]
对应console
- 0:编译代码 第
0
列 - 0:对应
sources[0]
,即main.js
- 0:源代码 第
0
行 - 0:源代码 第
0
列 - 0:对应
names[0]
,即console
- 0:编译代码 第
-
[8,0,0,8,1]
对应log
- 8:编译代码 第
8
列,其实是 第8+0=8
列 - 0:对应
sources[0]
,即main.js
- 0:源代码 第
0
行 - 8:源代码 第
8
列,其实是 第8+0=8
列 - 1:对应
names[1]
,即log
- 8:编译代码 第
-
[4,0,0,4]
对应1
- 4:编译代码 第
4
列,其实是 第8+4=12
列 - 0:对应
sources[0]
,即main.js
- 0:源代码 第
0
行 - 4:源代码 第
4
列,其实是8+4=12
列 - 不是变量,因而没有和
names
相干信息
- 4:编译代码 第
是不是有些奇怪,明明 1
的地位比 log
的地位更靠后,为什么编码显示的列数却更小,别忘了上面这个规定:
实际上只有 每个分号中的第一串英文 是用来示意代码的 第几行、第几列 的相对地位外,前面的都是绝对于之前的地位来做 加减法 的
即理论显示的列号数应为:
最初
以上就是对 sourcemap
相干内容的介绍,心愿本文对你有所帮忙!!!
说个题外话,有人好奇我哪有那么内容要写,其实大多文章内容只是将本人须要解决或者共事遇到的问题进行总结和扩大而已,所以大多数文章的想法就来源于此,其次我认为写文章的准则就是:写进去的文章首先要保障本人有播种! 另外,更多的是看看各位掘友对同一个问题都会有什么更好的计划!
本文参加了 SegmentFault 思否面试闯关挑战赛,欢送正在浏览的你也退出。