SourceMap解析CLI工具实现(1):https://developer.aliyun.com/article/1394851
简单做合并后的方法如下
const isHTTPSource = (sourcePath: string) => sourcePath.startsWith('http') async function getSourceMapFilePath(sourceJsPath: string) { if (!isHTTPSource(sourceJsPath)) { return getLocalSourceMapFilePath(sourceJsPath) } return getRemoteSourceMapFilePath(sourceJsPath) }
本小节示例代码
还原报错源码
有了前面的基础,咱们第一个整合功能就可以实现了 根据报错资源信息,还原源码和行列号,先给出方法的定义
interface SourceResult { /** * 源码 */ sourceCode: string /** * 源码文件路径 */ source: string /** * 行号 */ line: number /** * 列号 */ column: number } /** * 根据报错资源信息,获取对应源码信息 * @param url 报错资源 * @param line 行号 * @param column 列号 */ async function getErrorSourceResult( url: string, line: number, column: number ): Promise<SourceResult>
利用上面实现的getSourceMapFilePath
,配合source-map
的2个API即可实现originalPositionFor
,sourceContentFor
import fs from 'fs/promises' const sourceMapURL = await getSourceMapFilePath(url) // sourceMap 内容 const sourceMapCode = await (isHTTPSource(sourceMapURL) ? getRemoteSource(sourceMapURL).then((v) => v.body) : fs.readFile(sourceMapURL, 'utf-8')) const consumer = await createSourceMapConsumer(sourceMapCode) // 解析出原来的行列号,源文件路径等信息 const { name, ...rest } = consumer.originalPositionFor({ line, column }) // 获取源码 const sourceCode = consumer.sourceContentFor(rest.source!) const result = { ...rest, sourceCode }
方便终端里预览结果,可以编写一个printSourceResult
方法,友好的打印输出一些内容
getErrorSourceResult( 'https://script.sugarat.top/js/tests/index.9bb0da5c.js', 24, 17596 ).then(printResult)
方法实现如下(详细释义见注释)
/** * @param result * @param showMaxLine 控制显示的行数 */ export function printResult(result: SourceResult, showMaxLine = 5) { const { sourceCode, source, line, column } = result // 源码拆成数租 const lines = sourceCode.split('\n') // 打印错误路径 console.log(`error in ${source}:${line}:${column}`) console.log() // 计算要展示的行的起始位置,起始行号不能小于1 const startLine = Math.max(1, line - Math.floor(showMaxLine / 2)) // 结束位置不能大于总行数 const endLine = Math.min(lines.length, startLine + showMaxLine - 1) const showCode = lines // 截取需要展示的内容 .slice(startLine - 1, endLine) .map( (v, idx) => // 加上黄色行号 `${yellowStr(startLine + idx)} ${ // 针对错误的行进行下划线+红色展示 idx + startLine === line ? // 从错误的列号开始展示 v.slice(0, column - 1) + redStr(underlineStr(v.slice(column - 1))) : v }` ) .join('\n') console.log(showCode) }
打印彩色的场景有限,这里直接将需要的效果颜色对应的ANSI Escape code
从chalk
库中截取出来
const underlineStr = (v: any) => `\x1B[4m${v}\x1B[24m` const yellowStr = (v: any) => `\x1B[33m${v}\x1B[39m` const redStr = (v: any) => `\x1B[31m${v}\x1B[39m`
到此第一个功能的核心代码就封装好了
本小节示例代码
完整source生成
都知道通过sourceMap
可以获取完整的源码,所以一般的非开源应用,都是对sourceMap
文件做了环境隔离,防止源码泄露。
这部分就封装1个方法,实现将sourceMap中包含的所有源文件输出到本地指定目录
首先实现1个方法,将sourceMap中需要的信息解析出来
export async function getSourcesBySourceMapCode(sourceMapCode: string) { const consumer = await createSourceMapConsumer(sourceMapCode) const { sources } = consumer const result = sources.map((source) => { return { source, code: consumer.sourceContentFor(source) } }) return result }
配合文件操作(fs
模块),将内容输出到文件系统
import { existsSync, mkdirSync, writeFileSync } from 'fs' async function outPutSources( sources: SourceItem[], outPutDir = 'source-map-result/project' ) { for (const sourceItem of sources) { const { source, code } = sourceItem const filepath = path.resolve(process.cwd(), outPutDir, source) if (!existsSync(path.dirname(filepath))) { mkdirSync(path.dirname(filepath), { recursive: true }) } writeFileSync(filepath, code, 'utf-8') } }
示例代码与运行结果如下
getRemoteSource( 'https://script.sugarat.top/js/tests/index.9bb0da5c.js.map' ).then(async ({ body }) => { const sources = await getSourcesBySourceMapCode(body) console.log(sources.length, '个文件') outPutSources(sources) })
本小节示例代码
到此常用的2个能力的核心实现就完成了,下面将把其封装为一个CLI工具,方便接入使用
封装CLI
基于commander
进行实践
parse指令
首先是指令的定义
主要功能就是将指定的 error js
资源的通过sourcemap
还原出具体的报错源码
program // sourceUrl 格式 <url>[:line][:column] .command('parse <sourceUrl>') .description('parse error form url source') .alias('p') // 标明sourceUrl 是否为 sourceMap 资源 .option('-s, --source-map', 'set url source as sourceMap type') // 单独设置行号 .option('-l, --line <number>', 'set line number') // 单独设置列号 .option('-c, --column <number>', 'set column number') // 将结果输出到文件 .option('-o, --output [string]', 'set log output dir') // 设置展示的错误信息行数 .option('-n, --show-num <number>', 'set show error source lines', '5') .action(parseCommand)
后续的处理逻辑只需要把url
,line
,column
3个参数传给前面实现的getErrorSourceResult
方法即可
效果如下
本小节源码
sources指令
sources指令定义
program .command('sources <sourceUrl>') .description('generating source files by source-map') .alias('s') .option('-s, --source-map', 'set url source as sourceMap type') .option('-o, --output [string]', 'set log output dir') .action(sourcesCommand)
本小节源码
最后
这个CLI本身能力比较简单,依赖的核心库也只有source-map
。主要用于弥补缺失平台自动解析source-map能力的场景,协助定位js error
的报错源码
后续再出一篇在线sourcemap解析的工具,功能与CLI类似,不过是Web版的
CLI完整源码见GitHub
附录
其它同类 Web&CLI 工具
Web
- decodeSourceMap
CLI
- restore-source-tree
- source-map-tools
- source-map-cli
- source-map-to-source
- kaifu
- @hl-cli/restore-code