Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
genary
uni-app
提交
5831bdcb
U
uni-app
项目概览
genary
/
uni-app
与 Fork 源项目一致
Fork自
DCloud / uni-app
通知
1
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
1
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
U
uni-app
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
1
Issue
1
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
5831bdcb
编写于
6月 20, 2022
作者:
D
DCloud_LXH
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
fix(stacktracey): handle weixin sourcemap
上级
bad64b31
变更
8
隐藏空白更改
内联
并排
Showing
8 changed file
with
1070 addition
and
136 deletion
+1070
-136
packages/uni-stacktracey/__tests__/uni-stacktracey.spec.ts
packages/uni-stacktracey/__tests__/uni-stacktracey.spec.ts
+10
-13
packages/uni-stacktracey/dist/uni-stacktracey.cjs.js
packages/uni-stacktracey/dist/uni-stacktracey.cjs.js
+301
-39
packages/uni-stacktracey/dist/uni-stacktracey.es.js
packages/uni-stacktracey/dist/uni-stacktracey.es.js
+285
-22
packages/uni-stacktracey/src/index.ts
packages/uni-stacktracey/src/index.ts
+152
-49
packages/uni-stacktracey/src/stacktracey.ts
packages/uni-stacktracey/src/stacktracey.ts
+310
-0
packages/uni-stacktracey/test/__UNI_APPID__/h5/1.0.0/static/js/chunk-vendors.75525bd5.js.map
..._APPID__/h5/1.0.0/static/js/chunk-vendors.75525bd5.js.map
+0
-0
packages/uni-stacktracey/test/__UNI_APPID__/h5/1.0.0/static/js/pages-index-index.3ab0d0e5.js.map
...ID__/h5/1.0.0/static/js/pages-index-index.3ab0d0e5.js.map
+0
-0
packages/uni-stacktracey/test/index.js
packages/uni-stacktracey/test/index.js
+12
-13
未找到文件。
packages/uni-stacktracey/__tests__/uni-stacktracey.spec.ts
浏览文件 @
5831bdcb
...
...
@@ -34,26 +34,23 @@ describe('uni-stacktracey', () => {
test
(
'
uniStracktraceyPreset local
'
,
()
=>
{
stacktracey
(
uniErrorMsg
,
{
preset
:
uniStracktraceyPreset
({
base
:
path
.
resolve
(
__dirname
,
'
../test/__UNI__APPID__/1.0.0/.sourcemap/h5/
'
),
base
:
path
.
resolve
(
__dirname
,
'
../test/__UNI_APPID__/h5/1.0.0/
'
),
sourceRoot
:
''
,
}),
}).
then
((
res
:
string
)
=>
{
expect
(
res
).
toEqual
(
`Error: Sentry Error
at src/pages/index/index.vue:44
at src/pages/index/index.vue?be58:12
at node_modules/@
dcloudio/vue-cli-plugin-uni/packages/h5-vue/dist/vue.runtime.esm.js:1864
at node_modules/@dcloudio/vue-cli-plugin-uni/packages/h5-vue/dist/vue.runtime.esm.js:
2189
at node_modules/@dcloudio/vue-cli-plugin-uni/packages/h5-vue/dist/vue.runtime.esm.js:
1864
at node_modules/@dcloudio/vue-cli-plugin-uni/packages/h5-vue/dist/vue.runtime.esm.js:
2185
at node_modules/@dcloudio/vue-cli-plugin-uni/packages/h5-vue/dist/vue.runtime.esm.js:
7076
at node_modules/@
sentry/browser/esm/helpers.js:74
`
)
at src/pages/index/index.vue:44
at src/pages/index/index.vue?be58:12
:17
at node_modules/@
sentry/browser/esm/helpers.js:74:22
at node_modules/@dcloudio/vue-cli-plugin-uni/packages/h5-vue/dist/vue.runtime.esm.js:
1864:25
at node_modules/@dcloudio/vue-cli-plugin-uni/packages/h5-vue/dist/vue.runtime.esm.js:
2189:13
at node_modules/@dcloudio/vue-cli-plugin-uni/packages/h5-vue/dist/vue.runtime.esm.js:
1864:25
at node_modules/@dcloudio/vue-cli-plugin-uni/packages/h5-vue/dist/vue.runtime.esm.js:
2185:8
at node_modules/@
dcloudio/vue-cli-plugin-uni/packages/h5-vue/dist/vue.runtime.esm.js:7076:24
`
)
})
})
test
(
'
u
ni
StracktraceyPreset local
'
,
()
=>
{
test
(
'
u
ts
StracktraceyPreset local
'
,
()
=>
{
stacktracey
(
utsErrorMsg
,
{
preset
:
utsStracktraceyPreset
({
base
:
path
.
resolve
(
...
...
packages/uni-stacktracey/dist/uni-stacktracey.cjs.js
浏览文件 @
5831bdcb
'
use strict
'
;
Object
.
defineProperty
(
exports
,
'
__esModule
'
,
{
value
:
true
});
var
fs
=
require
(
'
fs
'
);
var
StackTracey
=
require
(
'
stacktracey
'
);
var
sourceMap
=
require
(
'
source-map
'
);
function
_interopDefaultLegacy
(
e
)
{
return
e
&&
typeof
e
===
'
object
'
&&
'
default
'
in
e
?
e
:
{
'
default
'
:
e
};
}
var
fs__default
=
/*#__PURE__*/
_interopDefaultLegacy
(
fs
);
var
StackTracey__default
=
/*#__PURE__*/
_interopDefaultLegacy
(
StackTracey
);
'
use strict
'
;
Object
.
defineProperty
(
exports
,
'
__esModule
'
,
{
value
:
true
});
var
fs
=
require
(
'
fs
'
);
var
sourceMap
=
require
(
'
source-map
'
);
function
_interopDefaultLegacy
(
e
)
{
return
e
&&
typeof
e
===
'
object
'
&&
'
default
'
in
e
?
e
:
{
'
default
'
:
e
};
}
var
fs__default
=
/*#__PURE__*/
_interopDefaultLegacy
(
fs
);
/* ------------------------------------------------------------------------ */
const
O
=
Object
,
isBrowser
=
typeof
window
!==
'
undefined
'
&&
window
.
window
===
window
&&
window
.
navigator
,
nodeRequire
=
isBrowser
?
null
:
module
.
require
,
// to prevent bundlers from expanding the require call
lastOf
=
(
x
)
=>
x
[
x
.
length
-
1
],
nixSlashes$1
=
(
x
)
=>
x
.
replace
(
/
\\
/g
,
'
/
'
),
pathRoot
=
isBrowser
?
window
.
location
.
href
:
nixSlashes$1
(
process
.
cwd
())
+
'
/
'
;
/* ------------------------------------------------------------------------ */
class
StackTracey
{
constructor
(
input
,
offset
)
{
this
.
itemsHeader
=
[];
this
.
isMP
=
false
;
const
originalInput
=
input
,
isParseableSyntaxError
=
input
&&
input
instanceof
SyntaxError
&&
!
isBrowser
;
/* new StackTracey () */
if
(
!
input
)
{
input
=
new
Error
();
offset
=
offset
===
undefined
?
1
:
offset
;
}
/* new StackTracey (Error) */
if
(
input
instanceof
Error
)
{
input
=
input
.
stack
||
''
;
}
/* new StackTracey (string) */
if
(
typeof
input
===
'
string
'
)
{
this
.
isMP
=
input
.
indexOf
(
'
MiniProgramError
'
)
!==
-
1
;
input
=
this
.
rawParse
(
input
)
.
slice
(
offset
)
.
map
((
x
)
=>
this
.
extractEntryMetadata
(
x
));
}
/* new StackTracey (array) */
if
(
Array
.
isArray
(
input
))
{
if
(
isParseableSyntaxError
)
{
const
rawLines
=
nodeRequire
(
'
util
'
)
.
inspect
(
originalInput
)
.
split
(
'
\n
'
),
fileLine
=
rawLines
[
0
].
split
(
'
:
'
),
line
=
fileLine
.
pop
(),
file
=
fileLine
.
join
(
'
:
'
);
if
(
file
)
{
input
.
unshift
({
file
:
nixSlashes$1
(
file
),
line
:
line
,
column
:
(
rawLines
[
2
]
||
''
).
indexOf
(
'
^
'
)
+
1
,
sourceLine
:
rawLines
[
1
],
callee
:
'
(syntax error)
'
,
syntaxError
:
true
,
});
}
}
this
.
items
=
input
;
}
else
{
this
.
items
=
[];
}
}
extractEntryMetadata
(
e
)
{
const
decomposedPath
=
this
.
decomposePath
(
e
.
file
||
''
);
const
fileRelative
=
decomposedPath
[
0
];
const
externalDomain
=
decomposedPath
[
1
];
return
O
.
assign
(
e
,
{
calleeShort
:
e
.
calleeShort
||
lastOf
((
e
.
callee
||
''
).
split
(
'
.
'
)),
fileRelative
:
fileRelative
,
fileShort
:
this
.
shortenPath
(
fileRelative
),
fileName
:
lastOf
((
e
.
file
||
''
).
split
(
'
/
'
)),
thirdParty
:
this
.
isThirdParty
(
fileRelative
,
externalDomain
)
&&
!
e
.
index
,
externalDomain
:
externalDomain
,
});
}
shortenPath
(
relativePath
)
{
return
relativePath
.
replace
(
/^node_modules
\/
/
,
''
)
.
replace
(
/^webpack
\/
bootstrap
\/
/
,
''
)
.
replace
(
/^__parcel_source_root
\/
/
,
''
);
}
decomposePath
(
fullPath
)
{
let
result
=
fullPath
;
if
(
isBrowser
)
result
=
result
.
replace
(
pathRoot
,
''
);
const
externalDomainMatch
=
result
.
match
(
/^
(
http|https
)\:\/\/?([^\/]
+
)\/(
.*
)
/
);
const
externalDomain
=
externalDomainMatch
?
externalDomainMatch
[
2
]
:
undefined
;
result
=
externalDomainMatch
?
externalDomainMatch
[
3
]
:
result
;
// if (!isBrowser) result = nodeRequire!('path').relative(pathRoot, result)
return
[
nixSlashes$1
(
result
).
replace
(
/^.*
\:\/\/?\/?
/
,
''
),
externalDomain
,
];
}
isThirdParty
(
relativePath
,
externalDomain
)
{
if
(
this
.
isMP
)
{
if
(
typeof
externalDomain
===
'
undefined
'
)
return
false
;
return
externalDomain
!==
'
usr
'
;
}
return
(
externalDomain
||
relativePath
[
0
]
===
'
~
'
||
// webpack-specific heuristic
relativePath
[
0
]
===
'
/
'
||
// external source
relativePath
.
indexOf
(
'
@dcloudio
'
)
!==
-
1
||
relativePath
.
indexOf
(
'
webpack/bootstrap
'
)
===
0
);
}
rawParse
(
str
)
{
const
lines
=
(
str
||
''
).
split
(
'
\n
'
);
const
entries
=
lines
.
map
((
line
,
index
)
=>
{
line
=
line
.
trim
();
let
callee
,
fileLineColumn
=
[],
native
,
planA
,
planB
;
if
((
planA
=
line
.
match
(
/at
(
.+
)
\(
eval at .+
\((
.+
)\)
, .+
\)
/
))
||
// eval calls
(
planA
=
line
.
match
(
/at
(
.+
)
\((
.+
)\)
/
))
||
(
line
.
slice
(
0
,
3
)
!==
'
at
'
&&
(
planA
=
line
.
match
(
/
(
.*
)
@
(
.*
)
/
))))
{
this
.
itemsHeader
.
push
(
'
%StacktraceyItem%
'
);
callee
=
planA
[
1
];
native
=
planA
[
2
]
===
'
native
'
;
fileLineColumn
=
(
planA
[
2
].
match
(
/
(
.*
)
:
(\d
+
)
:
(\d
+
)
/
)
||
planA
[
2
].
match
(
/
(
.*
)
:
(\d
+
)
/
)
||
planA
[
2
].
match
(
/
\[(
.*
)\]
/
)
||
[]).
slice
(
1
);
}
else
if
((
planB
=
line
.
match
(
/^
(
at
\s
*
)
*
(
.*
)\s
+
(
.+
)
:
(\d
+
)
:
(\d
+
)
/
)))
{
this
.
itemsHeader
.
push
(
'
%StacktraceyItem%
'
);
callee
=
planB
[
2
].
trim
();
fileLineColumn
=
planB
.
slice
(
3
);
}
else
{
this
.
itemsHeader
.
push
(
line
);
return
undefined
;
}
/* Detect things like Array.reduce
TODO: detect more built-in types */
if
(
callee
&&
!
fileLineColumn
[
0
])
{
const
type
=
callee
.
split
(
'
.
'
)[
0
];
if
(
type
===
'
Array
'
)
{
native
=
true
;
}
}
return
{
beforeParse
:
line
,
callee
:
callee
||
''
,
index
:
isBrowser
&&
fileLineColumn
[
0
]
===
window
.
location
.
href
,
native
:
native
||
false
,
file
:
nixSlashes$1
(
fileLineColumn
[
0
]
||
''
),
line
:
parseInt
(
fileLineColumn
[
1
]
||
''
,
10
)
||
undefined
,
column
:
parseInt
(
fileLineColumn
[
2
]
||
''
,
10
)
||
undefined
,
};
});
return
entries
.
filter
((
x
)
=>
x
!==
undefined
);
}
maxColumnWidths
()
{
return
{
callee
:
30
,
file
:
60
,
sourceLine
:
80
,
};
}
asTable
(
opts
)
{
const
maxColumnWidths
=
(
opts
&&
opts
.
maxColumnWidths
)
||
this
.
maxColumnWidths
();
const
trimmed
=
this
.
filter
((
e
)
=>
!
e
.
thirdParty
)
.
map
((
e
)
=>
parseItem
(
e
,
maxColumnWidths
,
this
.
isMP
));
const
trimmedThirdParty
=
this
.
filter
((
e
)
=>
e
.
thirdParty
)
.
map
((
e
)
=>
parseItem
(
e
,
maxColumnWidths
,
this
.
isMP
));
return
{
items
:
trimmed
.
items
,
thirdPartyItems
:
trimmedThirdParty
.
items
,
};
}
}
const
trimEnd
=
(
s
,
n
)
=>
s
&&
(
s
.
length
>
n
?
s
.
slice
(
0
,
n
-
1
)
+
'
…
'
:
s
);
const
trimStart
=
(
s
,
n
)
=>
s
&&
(
s
.
length
>
n
?
'
…
'
+
s
.
slice
(
-
(
n
-
1
))
:
s
);
function
parseItem
(
e
,
maxColumnWidths
,
isMP
)
{
const
filePath
=
(
isMP
?
e
.
file
&&
e
.
file
:
e
.
fileShort
&&
e
.
fileShort
)
+
`
${
e
.
line
?
'
:
'
+
e
.
line
:
''
}
`
+
`
${
e
.
column
?
'
:
'
+
e
.
column
:
''
}
`
;
return
[
'
at
'
+
trimEnd
(
isMP
?
e
.
callee
:
e
.
calleeShort
,
maxColumnWidths
.
callee
),
trimStart
(
filePath
||
''
,
maxColumnWidths
.
file
),
trimEnd
((
e
.
sourceLine
||
''
).
trim
()
||
''
,
maxColumnWidths
.
sourceLine
),
];
}
[
'
map
'
,
'
filter
'
,
'
slice
'
,
'
concat
'
].
forEach
((
method
)
=>
{
StackTracey
.
prototype
[
method
]
=
function
(
/*...args */
)
{
// no support for ...args in Node v4 :(
return
new
StackTracey
(
this
.
items
[
method
].
apply
(
this
.
items
,
arguments
));
};
});
/* ------------------------------------------------------------------------ */
var
StackTracey$1
=
StackTracey
;
const
nixSlashes
=
(
x
)
=>
x
.
replace
(
/
\\
/g
,
'
/
'
);
const
sourcemapCatch
=
{};
function
stacktracey
(
stacktrace
,
opts
)
{
const
parseStack
=
[];
const
stack
=
opts
.
preset
.
parseStacktrace
(
stacktrace
);
stack
.
items
.
forEach
((
item
,
index
)
=>
{
const
fn
=
()
=>
{
const
fn
=
(
item
,
index
)
=>
{
const
{
line
=
0
,
column
=
0
,
file
,
fileName
,
fileRelative
}
=
item
;
try
{
if
(
item
.
thirdParty
)
{
return
Promise
.
resolve
();
}
function
_getSourceMapContent
(
file
,
fileName
,
fileRelative
)
{
return
opts
.
preset
.
getSourceMapContent
(
file
,
fileName
,
fileRelative
)
.
then
((
content
)
=>
{
if
(
content
)
{
return
getConsumer
(
content
).
then
((
consumer
)
=>
{
const
sourceMapContent
=
parseSourceMapContent
(
consumer
,
{
return
parseSourceMapContent
(
consumer
,
{
line
,
column
,
});
if
(
sourceMapContent
)
{
const
{
source
,
sourcePath
,
sourceLine
,
sourceColumn
,
fileName
=
''
,
}
=
sourceMapContent
;
stack
.
items
[
index
]
=
Object
.
assign
({},
item
,
{
file
:
source
,
line
:
sourceLine
,
column
:
sourceColumn
,
fileShort
:
sourcePath
,
fileRelative
:
sourcePath
,
fileName
,
});
}
});
}
});
}
try
{
return
_getSourceMapContent
(
file
,
fileName
,
fileRelative
).
then
((
sourceMapContent
)
=>
{
if
(
sourceMapContent
)
{
const
{
source
,
sourcePath
,
sourceLine
,
sourceColumn
,
fileName
=
''
,
}
=
sourceMapContent
;
stack
.
items
[
index
]
=
Object
.
assign
({},
item
,
{
file
:
source
,
line
:
sourceLine
,
column
:
sourceColumn
,
fileShort
:
sourcePath
,
fileRelative
:
source
,
fileName
,
thirdParty
:
isThirdParty
(
sourcePath
),
});
/**
* 以 .js 结尾
* 包含 app-service.js 则需要再解析 两次
* 不包含 app-service.js 则无需再解析 一次
*/
const
curItem
=
stack
.
items
[
index
];
if
(
stack
.
isMP
&&
curItem
.
beforeParse
.
indexOf
(
'
app-service
'
)
!==
-
1
)
{
return
fn
(
curItem
,
index
);
}
}
});
}
catch
(
error
)
{
return
Promise
.
resolve
();
}
};
parseStack
.
push
(
fn
());
parseStack
.
push
(
fn
(
item
,
index
));
});
return
new
Promise
((
resolve
,
reject
)
=>
{
Promise
.
all
(
parseStack
)
...
...
@@ -69,6 +268,9 @@ function stacktracey(stacktrace, opts) {
});
});
}
function
isThirdParty
(
relativePath
)
{
return
relativePath
.
indexOf
(
'
@dcloudio
'
)
!==
-
1
;
}
function
getConsumer
(
content
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
sourceMap
.
SourceMapConsumer
.
with
)
{
...
...
@@ -126,9 +328,24 @@ function parseSourceMapContent(consumer, obj) {
};
}
}
function
joinItem
(
item
)
{
const
a
=
item
[
0
];
const
b
=
item
[
1
]
?
`
${
item
[
1
]}
`
:
''
;
const
c
=
item
[
2
]
?
`
${
item
[
2
]}
`
:
''
;
return
`
${
a
}${
b
}${
c
}
`
;
}
function
uniStracktraceyPreset
(
opts
)
{
const
{
base
,
sourceRoot
}
=
opts
;
const
{
base
,
sourceRoot
,
splitThirdParty
}
=
opts
;
let
stack
;
return
{
/**
*
* 微信特殊处理
* 微信解析步骤:
* 1. //usr/app-service.js -> 'weixin/__APP__/app-service.map.map'
* 2. //usr/pages/API/app-service.js -> 'weixin/pages/API/app-service.map.map'
* 3. uni-list-item/uni-list-item.js -> ${base}/uni-list-item/uni-list-item.js.map
*/
parseSourceMapUrl
(
file
,
fileName
,
fileRelative
)
{
// 组合 sourceMapUrl
if
(
fileRelative
.
indexOf
(
'
(
'
)
!==
-
1
)
...
...
@@ -138,18 +355,62 @@ function uniStracktraceyPreset(opts) {
if
(
sourceRoot
)
{
return
`
${
fileRelative
.
replace
(
sourceRoot
,
base
+
'
/
'
)}
.map`
;
}
return
`
${
base
}
/
${
fileRelative
}
.map`
;
let
baseAfter
=
''
;
if
(
stack
.
isMP
)
{
if
(
fileRelative
.
indexOf
(
'
app-service.js
'
)
!==
-
1
)
{
baseAfter
=
(
base
.
match
(
/
\w
$/
)
?
'
/
'
:
''
)
+
'
weixin
'
;
if
(
fileRelative
===
fileName
)
{
baseAfter
+=
'
/__APP__
'
;
}
fileRelative
=
fileRelative
.
replace
(
'
.js
'
,
'
.map
'
);
}
if
(
baseAfter
&&
!!
fileRelative
.
match
(
/^
\w
/
))
baseAfter
+=
'
/
'
;
}
return
`
${
base
}${
baseAfter
}${
fileRelative
}
.map`
;
},
getSourceMapContent
(
file
,
fileName
,
fileRelative
)
{
return
Promise
.
resolve
(
getSourceMapContent
(
this
.
parseSourceMapUrl
(
file
,
fileName
,
fileRelative
)));
if
(
stack
.
isMP
&&
fileRelative
.
indexOf
(
'
.js
'
)
===
-
1
)
{
return
Promise
.
resolve
(
''
);
}
const
sourcemapUrl
=
this
.
parseSourceMapUrl
(
file
,
fileName
,
fileRelative
);
return
Promise
.
resolve
(
getSourceMapContent
(
sourcemapUrl
));
},
parseStacktrace
(
stacktrace
)
{
return
new
StackTracey__default
[
"
default
"
](
stacktrace
);
stack
=
new
StackTracey$1
(
stacktrace
);
return
stack
;
},
asTableStacktrace
({
maxColumnWidths
,
stacktrace
,
stack
})
{
const
errorName
=
stacktrace
.
split
(
'
\n
'
)[
0
];
return
((
errorName
.
indexOf
(
'
at
'
)
===
-
1
?
`
${
errorName
}
\n`
:
''
)
+
(
stack
.
asTable
?
stack
.
asTable
({
maxColumnWidths
})
:
''
));
const
lines
=
stack
.
asTable
?
stack
.
asTable
(
maxColumnWidths
?
{
maxColumnWidths
}
:
undefined
)
:
{
items
:
[],
thirdPartyItems
:
[]
};
if
(
lines
.
items
.
length
||
lines
.
thirdPartyItems
.
length
)
{
const
{
items
:
stackLines
,
thirdPartyItems
:
stackThirdPartyLines
}
=
lines
;
const
userError
=
stack
.
itemsHeader
.
map
((
item
)
=>
{
if
(
item
===
'
%StacktraceyItem%
'
)
{
const
_stack
=
stackLines
.
shift
();
return
_stack
?
joinItem
(
_stack
)
:
''
;
}
return
item
;
})
.
filter
(
Boolean
)
.
join
(
'
\n
'
);
const
thirdParty
=
stackThirdPartyLines
.
length
?
stackThirdPartyLines
.
map
(
joinItem
).
join
(
'
\n
'
)
:
''
;
if
(
splitThirdParty
)
{
return
{
userError
,
thirdParty
,
};
}
return
userError
+
'
\n
'
+
thirdParty
;
}
else
{
return
errorName
;
}
},
};
}
...
...
@@ -205,6 +466,7 @@ function utsStracktraceyPreset(opts) {
.
filter
((
x
)
=>
x
!==
undefined
);
return
{
items
:
entries
,
itemsHeader
:
[],
};
},
asTableStacktrace
({
maxColumnWidths
,
stacktrace
,
stack
})
{
...
...
@@ -220,8 +482,8 @@ function utsStracktraceyPreset(opts) {
.
join
(
'
\n
'
);
},
};
}
exports
.
stacktracey
=
stacktracey
;
exports
.
uniStracktraceyPreset
=
uniStracktraceyPreset
;
exports
.
utsStracktraceyPreset
=
utsStracktraceyPreset
;
}
exports
.
stacktracey
=
stacktracey
;
exports
.
uniStracktraceyPreset
=
uniStracktraceyPreset
;
exports
.
utsStracktraceyPreset
=
utsStracktraceyPreset
;
packages/uni-stacktracey/dist/uni-stacktracey.es.js
浏览文件 @
5831bdcb
import
fs
from
'
fs
'
;
import
StackTracey
from
'
stacktracey
'
;
import
{
SourceMapConsumer
}
from
'
source-map
'
;
/* ------------------------------------------------------------------------ */
const
O
=
Object
,
isBrowser
=
typeof
window
!==
'
undefined
'
&&
window
.
window
===
window
&&
window
.
navigator
,
nodeRequire
=
isBrowser
?
null
:
module
.
require
,
// to prevent bundlers from expanding the require call
lastOf
=
(
x
)
=>
x
[
x
.
length
-
1
],
nixSlashes$1
=
(
x
)
=>
x
.
replace
(
/
\\
/g
,
'
/
'
),
pathRoot
=
isBrowser
?
window
.
location
.
href
:
nixSlashes$1
(
process
.
cwd
())
+
'
/
'
;
/* ------------------------------------------------------------------------ */
class
StackTracey
{
constructor
(
input
,
offset
)
{
this
.
itemsHeader
=
[];
this
.
isMP
=
false
;
const
originalInput
=
input
,
isParseableSyntaxError
=
input
&&
input
instanceof
SyntaxError
&&
!
isBrowser
;
/* new StackTracey () */
if
(
!
input
)
{
input
=
new
Error
();
offset
=
offset
===
undefined
?
1
:
offset
;
}
/* new StackTracey (Error) */
if
(
input
instanceof
Error
)
{
input
=
input
.
stack
||
''
;
}
/* new StackTracey (string) */
if
(
typeof
input
===
'
string
'
)
{
this
.
isMP
=
input
.
indexOf
(
'
MiniProgramError
'
)
!==
-
1
;
input
=
this
.
rawParse
(
input
)
.
slice
(
offset
)
.
map
((
x
)
=>
this
.
extractEntryMetadata
(
x
));
}
/* new StackTracey (array) */
if
(
Array
.
isArray
(
input
))
{
if
(
isParseableSyntaxError
)
{
const
rawLines
=
nodeRequire
(
'
util
'
)
.
inspect
(
originalInput
)
.
split
(
'
\n
'
),
fileLine
=
rawLines
[
0
].
split
(
'
:
'
),
line
=
fileLine
.
pop
(),
file
=
fileLine
.
join
(
'
:
'
);
if
(
file
)
{
input
.
unshift
({
file
:
nixSlashes$1
(
file
),
line
:
line
,
column
:
(
rawLines
[
2
]
||
''
).
indexOf
(
'
^
'
)
+
1
,
sourceLine
:
rawLines
[
1
],
callee
:
'
(syntax error)
'
,
syntaxError
:
true
,
});
}
}
this
.
items
=
input
;
}
else
{
this
.
items
=
[];
}
}
extractEntryMetadata
(
e
)
{
const
decomposedPath
=
this
.
decomposePath
(
e
.
file
||
''
);
const
fileRelative
=
decomposedPath
[
0
];
const
externalDomain
=
decomposedPath
[
1
];
return
O
.
assign
(
e
,
{
calleeShort
:
e
.
calleeShort
||
lastOf
((
e
.
callee
||
''
).
split
(
'
.
'
)),
fileRelative
:
fileRelative
,
fileShort
:
this
.
shortenPath
(
fileRelative
),
fileName
:
lastOf
((
e
.
file
||
''
).
split
(
'
/
'
)),
thirdParty
:
this
.
isThirdParty
(
fileRelative
,
externalDomain
)
&&
!
e
.
index
,
externalDomain
:
externalDomain
,
});
}
shortenPath
(
relativePath
)
{
return
relativePath
.
replace
(
/^node_modules
\/
/
,
''
)
.
replace
(
/^webpack
\/
bootstrap
\/
/
,
''
)
.
replace
(
/^__parcel_source_root
\/
/
,
''
);
}
decomposePath
(
fullPath
)
{
let
result
=
fullPath
;
if
(
isBrowser
)
result
=
result
.
replace
(
pathRoot
,
''
);
const
externalDomainMatch
=
result
.
match
(
/^
(
http|https
)\:\/\/?([^\/]
+
)\/(
.*
)
/
);
const
externalDomain
=
externalDomainMatch
?
externalDomainMatch
[
2
]
:
undefined
;
result
=
externalDomainMatch
?
externalDomainMatch
[
3
]
:
result
;
// if (!isBrowser) result = nodeRequire!('path').relative(pathRoot, result)
return
[
nixSlashes$1
(
result
).
replace
(
/^.*
\:\/\/?\/?
/
,
''
),
externalDomain
,
];
}
isThirdParty
(
relativePath
,
externalDomain
)
{
if
(
this
.
isMP
)
{
if
(
typeof
externalDomain
===
'
undefined
'
)
return
false
;
return
externalDomain
!==
'
usr
'
;
}
return
(
externalDomain
||
relativePath
[
0
]
===
'
~
'
||
// webpack-specific heuristic
relativePath
[
0
]
===
'
/
'
||
// external source
relativePath
.
indexOf
(
'
@dcloudio
'
)
!==
-
1
||
relativePath
.
indexOf
(
'
webpack/bootstrap
'
)
===
0
);
}
rawParse
(
str
)
{
const
lines
=
(
str
||
''
).
split
(
'
\n
'
);
const
entries
=
lines
.
map
((
line
,
index
)
=>
{
line
=
line
.
trim
();
let
callee
,
fileLineColumn
=
[],
native
,
planA
,
planB
;
if
((
planA
=
line
.
match
(
/at
(
.+
)
\(
eval at .+
\((
.+
)\)
, .+
\)
/
))
||
// eval calls
(
planA
=
line
.
match
(
/at
(
.+
)
\((
.+
)\)
/
))
||
(
line
.
slice
(
0
,
3
)
!==
'
at
'
&&
(
planA
=
line
.
match
(
/
(
.*
)
@
(
.*
)
/
))))
{
this
.
itemsHeader
.
push
(
'
%StacktraceyItem%
'
);
callee
=
planA
[
1
];
native
=
planA
[
2
]
===
'
native
'
;
fileLineColumn
=
(
planA
[
2
].
match
(
/
(
.*
)
:
(\d
+
)
:
(\d
+
)
/
)
||
planA
[
2
].
match
(
/
(
.*
)
:
(\d
+
)
/
)
||
planA
[
2
].
match
(
/
\[(
.*
)\]
/
)
||
[]).
slice
(
1
);
}
else
if
((
planB
=
line
.
match
(
/^
(
at
\s
*
)
*
(
.*
)\s
+
(
.+
)
:
(\d
+
)
:
(\d
+
)
/
)))
{
this
.
itemsHeader
.
push
(
'
%StacktraceyItem%
'
);
callee
=
planB
[
2
].
trim
();
fileLineColumn
=
planB
.
slice
(
3
);
}
else
{
this
.
itemsHeader
.
push
(
line
);
return
undefined
;
}
/* Detect things like Array.reduce
TODO: detect more built-in types */
if
(
callee
&&
!
fileLineColumn
[
0
])
{
const
type
=
callee
.
split
(
'
.
'
)[
0
];
if
(
type
===
'
Array
'
)
{
native
=
true
;
}
}
return
{
beforeParse
:
line
,
callee
:
callee
||
''
,
index
:
isBrowser
&&
fileLineColumn
[
0
]
===
window
.
location
.
href
,
native
:
native
||
false
,
file
:
nixSlashes$1
(
fileLineColumn
[
0
]
||
''
),
line
:
parseInt
(
fileLineColumn
[
1
]
||
''
,
10
)
||
undefined
,
column
:
parseInt
(
fileLineColumn
[
2
]
||
''
,
10
)
||
undefined
,
};
});
return
entries
.
filter
((
x
)
=>
x
!==
undefined
);
}
maxColumnWidths
()
{
return
{
callee
:
30
,
file
:
60
,
sourceLine
:
80
,
};
}
asTable
(
opts
)
{
const
maxColumnWidths
=
(
opts
&&
opts
.
maxColumnWidths
)
||
this
.
maxColumnWidths
();
const
trimmed
=
this
.
filter
((
e
)
=>
!
e
.
thirdParty
)
.
map
((
e
)
=>
parseItem
(
e
,
maxColumnWidths
,
this
.
isMP
));
const
trimmedThirdParty
=
this
.
filter
((
e
)
=>
e
.
thirdParty
)
.
map
((
e
)
=>
parseItem
(
e
,
maxColumnWidths
,
this
.
isMP
));
return
{
items
:
trimmed
.
items
,
thirdPartyItems
:
trimmedThirdParty
.
items
,
};
}
}
const
trimEnd
=
(
s
,
n
)
=>
s
&&
(
s
.
length
>
n
?
s
.
slice
(
0
,
n
-
1
)
+
'
…
'
:
s
);
const
trimStart
=
(
s
,
n
)
=>
s
&&
(
s
.
length
>
n
?
'
…
'
+
s
.
slice
(
-
(
n
-
1
))
:
s
);
function
parseItem
(
e
,
maxColumnWidths
,
isMP
)
{
const
filePath
=
(
isMP
?
e
.
file
&&
e
.
file
:
e
.
fileShort
&&
e
.
fileShort
)
+
`
${
e
.
line
?
'
:
'
+
e
.
line
:
''
}
`
+
`
${
e
.
column
?
'
:
'
+
e
.
column
:
''
}
`
;
return
[
'
at
'
+
trimEnd
(
isMP
?
e
.
callee
:
e
.
calleeShort
,
maxColumnWidths
.
callee
),
trimStart
(
filePath
||
''
,
maxColumnWidths
.
file
),
trimEnd
((
e
.
sourceLine
||
''
).
trim
()
||
''
,
maxColumnWidths
.
sourceLine
),
];
}
[
'
map
'
,
'
filter
'
,
'
slice
'
,
'
concat
'
].
forEach
((
method
)
=>
{
StackTracey
.
prototype
[
method
]
=
function
(
/*...args */
)
{
// no support for ...args in Node v4 :(
return
new
StackTracey
(
this
.
items
[
method
].
apply
(
this
.
items
,
arguments
));
};
});
/* ------------------------------------------------------------------------ */
var
StackTracey$1
=
StackTracey
;
// @ts-ignore
{
// @ts-ignore
...
...
@@ -18,38 +200,56 @@ function stacktracey(stacktrace, opts) {
const
parseStack
=
[];
const
stack
=
opts
.
preset
.
parseStacktrace
(
stacktrace
);
stack
.
items
.
forEach
((
item
,
index
)
=>
{
const
fn
=
()
=>
{
const
fn
=
(
item
,
index
)
=>
{
const
{
line
=
0
,
column
=
0
,
file
,
fileName
,
fileRelative
}
=
item
;
try
{
if
(
item
.
thirdParty
)
{
return
Promise
.
resolve
();
}
function
_getSourceMapContent
(
file
,
fileName
,
fileRelative
)
{
return
opts
.
preset
.
getSourceMapContent
(
file
,
fileName
,
fileRelative
)
.
then
((
content
)
=>
{
if
(
content
)
{
return
getConsumer
(
content
).
then
((
consumer
)
=>
{
const
sourceMapContent
=
parseSourceMapContent
(
consumer
,
{
return
parseSourceMapContent
(
consumer
,
{
line
,
column
,
});
if
(
sourceMapContent
)
{
const
{
source
,
sourcePath
,
sourceLine
,
sourceColumn
,
fileName
=
''
,
}
=
sourceMapContent
;
stack
.
items
[
index
]
=
Object
.
assign
({},
item
,
{
file
:
source
,
line
:
sourceLine
,
column
:
sourceColumn
,
fileShort
:
sourcePath
,
fileRelative
:
sourcePath
,
fileName
,
});
}
});
}
});
}
try
{
return
_getSourceMapContent
(
file
,
fileName
,
fileRelative
).
then
((
sourceMapContent
)
=>
{
if
(
sourceMapContent
)
{
const
{
source
,
sourcePath
,
sourceLine
,
sourceColumn
,
fileName
=
''
,
}
=
sourceMapContent
;
stack
.
items
[
index
]
=
Object
.
assign
({},
item
,
{
file
:
source
,
line
:
sourceLine
,
column
:
sourceColumn
,
fileShort
:
sourcePath
,
fileRelative
:
source
,
fileName
,
thirdParty
:
isThirdParty
(
sourcePath
),
});
/**
* 以 .js 结尾
* 包含 app-service.js 则需要再解析 两次
* 不包含 app-service.js 则无需再解析 一次
*/
const
curItem
=
stack
.
items
[
index
];
if
(
stack
.
isMP
&&
curItem
.
beforeParse
.
indexOf
(
'
app-service
'
)
!==
-
1
)
{
return
fn
(
curItem
,
index
);
}
}
});
}
catch
(
error
)
{
return
Promise
.
resolve
();
}
};
parseStack
.
push
(
fn
());
parseStack
.
push
(
fn
(
item
,
index
));
});
return
new
Promise
((
resolve
,
reject
)
=>
{
Promise
.
all
(
parseStack
)
...
...
@@ -70,6 +270,9 @@ function stacktracey(stacktrace, opts) {
});
});
}
function
isThirdParty
(
relativePath
)
{
return
relativePath
.
indexOf
(
'
@dcloudio
'
)
!==
-
1
;
}
function
getConsumer
(
content
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
SourceMapConsumer
.
with
)
{
...
...
@@ -127,9 +330,24 @@ function parseSourceMapContent(consumer, obj) {
};
}
}
function
joinItem
(
item
)
{
const
a
=
item
[
0
];
const
b
=
item
[
1
]
?
`
${
item
[
1
]}
`
:
''
;
const
c
=
item
[
2
]
?
`
${
item
[
2
]}
`
:
''
;
return
`
${
a
}${
b
}${
c
}
`
;
}
function
uniStracktraceyPreset
(
opts
)
{
const
{
base
,
sourceRoot
}
=
opts
;
const
{
base
,
sourceRoot
,
splitThirdParty
}
=
opts
;
let
stack
;
return
{
/**
*
* 微信特殊处理
* 微信解析步骤:
* 1. //usr/app-service.js -> 'weixin/__APP__/app-service.map.map'
* 2. //usr/pages/API/app-service.js -> 'weixin/pages/API/app-service.map.map'
* 3. uni-list-item/uni-list-item.js -> ${base}/uni-list-item/uni-list-item.js.map
*/
parseSourceMapUrl
(
file
,
fileName
,
fileRelative
)
{
// 组合 sourceMapUrl
if
(
fileRelative
.
indexOf
(
'
(
'
)
!==
-
1
)
...
...
@@ -139,18 +357,62 @@ function uniStracktraceyPreset(opts) {
if
(
sourceRoot
)
{
return
`
${
fileRelative
.
replace
(
sourceRoot
,
base
+
'
/
'
)}
.map`
;
}
return
`
${
base
}
/
${
fileRelative
}
.map`
;
let
baseAfter
=
''
;
if
(
stack
.
isMP
)
{
if
(
fileRelative
.
indexOf
(
'
app-service.js
'
)
!==
-
1
)
{
baseAfter
=
(
base
.
match
(
/
\w
$/
)
?
'
/
'
:
''
)
+
'
weixin
'
;
if
(
fileRelative
===
fileName
)
{
baseAfter
+=
'
__APP__
'
;
}
fileRelative
=
fileRelative
.
replace
(
'
.js
'
,
'
.map
'
);
}
if
(
baseAfter
&&
!!
fileRelative
.
match
(
/^
\w
/
))
baseAfter
+=
'
/
'
;
}
return
`
${
base
}${
baseAfter
}${
fileRelative
}
.map`
;
},
getSourceMapContent
(
file
,
fileName
,
fileRelative
)
{
return
Promise
.
resolve
(
getSourceMapContent
(
this
.
parseSourceMapUrl
(
file
,
fileName
,
fileRelative
)));
if
(
stack
.
isMP
&&
fileRelative
.
indexOf
(
'
.js
'
)
===
-
1
)
{
return
Promise
.
resolve
(
''
);
}
const
sourcemapUrl
=
this
.
parseSourceMapUrl
(
file
,
fileName
,
fileRelative
);
return
Promise
.
resolve
(
getSourceMapContent
(
sourcemapUrl
));
},
parseStacktrace
(
stacktrace
)
{
return
new
StackTracey
(
stacktrace
);
stack
=
new
StackTracey$1
(
stacktrace
);
return
stack
;
},
asTableStacktrace
({
maxColumnWidths
,
stacktrace
,
stack
})
{
const
errorName
=
stacktrace
.
split
(
'
\n
'
)[
0
];
return
((
errorName
.
indexOf
(
'
at
'
)
===
-
1
?
`
${
errorName
}
\n`
:
''
)
+
(
stack
.
asTable
?
stack
.
asTable
({
maxColumnWidths
})
:
''
));
const
lines
=
stack
.
asTable
?
stack
.
asTable
(
maxColumnWidths
?
{
maxColumnWidths
}
:
undefined
)
:
{
items
:
[],
thirdPartyItems
:
[]
};
if
(
lines
.
items
.
length
||
lines
.
thirdPartyItems
.
length
)
{
const
{
items
:
stackLines
,
thirdPartyItems
:
stackThirdPartyLines
}
=
lines
;
const
userError
=
stack
.
itemsHeader
.
map
((
item
)
=>
{
if
(
item
===
'
%StacktraceyItem%
'
)
{
const
_stack
=
stackLines
.
shift
();
return
_stack
?
joinItem
(
_stack
)
:
''
;
}
return
item
;
})
.
filter
(
Boolean
)
.
join
(
'
\n
'
);
const
thirdParty
=
stackThirdPartyLines
.
length
?
stackThirdPartyLines
.
map
(
joinItem
).
join
(
'
\n
'
)
:
''
;
if
(
splitThirdParty
)
{
return
{
userError
,
thirdParty
,
};
}
return
userError
+
'
\n
'
+
thirdParty
;
}
else
{
return
errorName
;
}
},
};
}
...
...
@@ -206,6 +468,7 @@ function utsStracktraceyPreset(opts) {
.
filter
((
x
)
=>
x
!==
undefined
);
return
{
items
:
entries
,
itemsHeader
:
[],
};
},
asTableStacktrace
({
maxColumnWidths
,
stacktrace
,
stack
})
{
...
...
packages/uni-stacktracey/src/index.ts
浏览文件 @
5831bdcb
import
fs
from
'
fs
'
import
StackTracey
from
'
stacktracey
'
import
StackTracey
from
'
./
stacktracey
'
import
{
SourceMapConsumer
,
BasicSourceMapConsumer
,
...
...
@@ -27,8 +27,17 @@ type StacktraceyItems = StackTracey.Entry & {
}
type
Stacktracey
=
{
items
:
StacktraceyItems
[]
itemsHeader
:
StackTracey
[
'
itemsHeader
'
]
asTable
?:
StackTracey
[
'
asTable
'
]
}
type
asTableResult
=
|
string
|
{
userError
:
string
thirdParty
:
string
}
interface
StacktraceyPreset
{
/**
* 解析错误栈信息
...
...
@@ -43,7 +52,7 @@ interface StacktraceyPreset {
stack
:
Stacktracey
maxColumnWidths
?:
StackTracey
.
MaxColumnWidths
stacktrace
:
string
}):
string
}):
asTableResult
/**
* 编译后的文件名地址
* @param file
...
...
@@ -69,51 +78,82 @@ interface StacktraceyOptions {
export
function
stacktracey
(
stacktrace
:
string
,
opts
:
StacktraceyOptions
):
Promise
<
string
>
{
):
Promise
<
string
|
asTableResult
>
{
const
parseStack
:
Array
<
Promise
<
any
>>
=
[]
const
stack
=
opts
.
preset
.
parseStacktrace
(
stacktrace
)
stack
.
items
.
forEach
((
item
,
index
)
=>
{
const
fn
=
()
=>
{
const
fn
=
(
item
:
StacktraceyItems
,
index
:
number
):
Promise
<
undefined
|
void
>
=>
{
const
{
line
=
0
,
column
=
0
,
file
,
fileName
,
fileRelative
}
=
item
try
{
if
(
item
.
thirdParty
)
{
return
Promise
.
resolve
()
}
function
_getSourceMapContent
(
file
:
string
,
fileName
:
string
,
fileRelative
:
string
)
{
return
opts
.
preset
.
getSourceMapContent
(
file
,
fileName
,
fileRelative
)
.
then
((
content
)
=>
{
if
(
content
)
{
return
getConsumer
(
content
).
then
((
consumer
)
=>
{
const
sourceMapContent
=
parseSourceMapContent
(
consumer
,
{
return
parseSourceMapContent
(
consumer
,
{
line
,
column
,
})
if
(
sourceMapContent
)
{
const
{
source
,
sourcePath
,
sourceLine
,
sourceColumn
,
fileName
=
''
,
}
=
sourceMapContent
stack
.
items
[
index
]
=
Object
.
assign
({},
item
,
{
file
:
source
,
line
:
sourceLine
,
column
:
sourceColumn
,
fileShort
:
sourcePath
,
fileRelative
:
sourcePath
,
fileName
,
})
}
})
}
})
}
try
{
return
_getSourceMapContent
(
file
,
fileName
,
fileRelative
).
then
(
(
sourceMapContent
)
=>
{
if
(
sourceMapContent
)
{
const
{
source
,
sourcePath
,
sourceLine
,
sourceColumn
,
fileName
=
''
,
}
=
sourceMapContent
stack
.
items
[
index
]
=
Object
.
assign
({},
item
,
{
file
:
source
,
line
:
sourceLine
,
column
:
sourceColumn
,
fileShort
:
sourcePath
,
fileRelative
:
source
,
fileName
,
thirdParty
:
isThirdParty
(
sourcePath
),
})
/**
* 以 .js 结尾
* 包含 app-service.js 则需要再解析 两次
* 不包含 app-service.js 则无需再解析 一次
*/
const
curItem
=
stack
.
items
[
index
]
if
(
(
stack
as
StackTracey
).
isMP
&&
curItem
.
beforeParse
.
indexOf
(
'
app-service
'
)
!==
-
1
)
{
return
fn
(
curItem
,
index
)
}
}
}
)
}
catch
(
error
)
{
return
Promise
.
resolve
()
}
}
parseStack
.
push
(
fn
())
parseStack
.
push
(
fn
(
item
,
index
))
})
return
new
Promise
((
resolve
,
reject
)
=>
{
...
...
@@ -136,6 +176,10 @@ export function stacktracey(
})
}
function
isThirdParty
(
relativePath
:
string
)
{
return
relativePath
.
indexOf
(
'
@dcloudio
'
)
!==
-
1
}
function
getConsumer
(
content
:
string
):
Promise
<
BasicSourceMapConsumer
|
IndexedSourceMapConsumer
>
{
...
...
@@ -182,19 +226,17 @@ function getSourceMapContent(sourcemapUrl: string) {
}
}
type
SourceMapContent
=
|
undefined
|
{
source
:
string
sourcePath
:
string
sourceLine
:
number
sourceColumn
:
number
fileName
:
string
|
undefined
}
type
SourceMapContent
=
{
source
:
string
sourcePath
:
string
sourceLine
:
number
sourceColumn
:
number
fileName
:
string
|
undefined
}
function
parseSourceMapContent
(
consumer
:
BasicSourceMapConsumer
|
IndexedSourceMapConsumer
,
obj
:
Position
):
SourceMapContent
{
):
SourceMapContent
|
undefined
{
// source -> 'uni-app:///node_modules/@sentry/browser/esm/helpers.js'
const
{
source
,
...
...
@@ -219,13 +261,32 @@ function parseSourceMapContent(
interface
UniStracktraceyPresetOptions
{
base
:
string
sourceRoot
:
string
splitThirdParty
?:
boolean
}
function
joinItem
(
item
:
string
[])
{
const
a
=
item
[
0
]
const
b
=
item
[
1
]
?
`
${
item
[
1
]}
`
:
''
const
c
=
item
[
2
]
?
`
${
item
[
2
]}
`
:
''
return
`
${
a
}${
b
}${
c
}
`
}
export
function
uniStracktraceyPreset
(
opts
:
UniStracktraceyPresetOptions
):
StacktraceyPreset
{
const
{
base
,
sourceRoot
}
=
opts
const
{
base
,
sourceRoot
,
splitThirdParty
}
=
opts
let
stack
:
StackTracey
return
{
/**
*
* 微信特殊处理
* 微信解析步骤:
* 1. //usr/app-service.js -> 'weixin/__APP__/app-service.map.map'
* 2. //usr/pages/API/app-service.js -> 'weixin/pages/API/app-service.map.map'
* 3. uni-list-item/uni-list-item.js -> ${base}/uni-list-item/uni-list-item.js.map
*/
parseSourceMapUrl
(
file
,
fileName
,
fileRelative
)
{
// 组合 sourceMapUrl
if
(
fileRelative
.
indexOf
(
'
(
'
)
!==
-
1
)
...
...
@@ -234,24 +295,65 @@ export function uniStracktraceyPreset(
if
(
sourceRoot
)
{
return
`
${
fileRelative
.
replace
(
sourceRoot
,
base
+
'
/
'
)}
.map`
}
return
`
${
base
}
/
${
fileRelative
}
.map`
let
baseAfter
=
''
if
(
stack
.
isMP
)
{
if
(
fileRelative
.
indexOf
(
'
app-service.js
'
)
!==
-
1
)
{
baseAfter
=
(
base
.
match
(
/
\w
$/
)
?
'
/
'
:
''
)
+
'
weixin
'
if
(
fileRelative
===
fileName
)
{
baseAfter
+=
'
/__APP__
'
}
fileRelative
=
fileRelative
.
replace
(
'
.js
'
,
'
.map
'
)
}
if
(
baseAfter
&&
!!
fileRelative
.
match
(
/^
\w
/
))
baseAfter
+=
'
/
'
}
return
`
${
base
}${
baseAfter
}${
fileRelative
}
.map`
},
getSourceMapContent
(
file
,
fileName
,
fileRelative
)
{
return
Promise
.
resolve
(
getSourceMapContent
(
this
.
parseSourceMapUrl
(
file
,
fileName
,
fileRelative
)
)
)
if
(
stack
.
isMP
&&
fileRelative
.
indexOf
(
'
.js
'
)
===
-
1
)
{
return
Promise
.
resolve
(
''
)
}
const
sourcemapUrl
=
this
.
parseSourceMapUrl
(
file
,
fileName
,
fileRelative
)
return
Promise
.
resolve
(
getSourceMapContent
(
sourcemapUrl
)
)
},
parseStacktrace
(
stacktrace
)
{
return
new
StackTracey
(
stacktrace
)
stack
=
new
StackTracey
(
stacktrace
)
return
stack
},
asTableStacktrace
({
maxColumnWidths
,
stacktrace
,
stack
})
{
const
errorName
=
stacktrace
.
split
(
'
\n
'
)[
0
]
return
(
(
errorName
.
indexOf
(
'
at
'
)
===
-
1
?
`
${
errorName
}
\n`
:
''
)
+
(
stack
.
asTable
?
stack
.
asTable
({
maxColumnWidths
})
:
''
)
)
const
lines
=
stack
.
asTable
?
stack
.
asTable
(
maxColumnWidths
?
{
maxColumnWidths
}
:
undefined
)
:
{
items
:
[],
thirdPartyItems
:
[]
}
if
(
lines
.
items
.
length
||
lines
.
thirdPartyItems
.
length
)
{
const
{
items
:
stackLines
,
thirdPartyItems
:
stackThirdPartyLines
}
=
lines
const
userError
=
stack
.
itemsHeader
.
map
((
item
)
=>
{
if
(
item
===
'
%StacktraceyItem%
'
)
{
const
_stack
=
stackLines
.
shift
()
return
_stack
?
joinItem
(
_stack
)
:
''
}
return
item
})
.
filter
(
Boolean
)
.
join
(
'
\n
'
)
const
thirdParty
=
stackThirdPartyLines
.
length
?
stackThirdPartyLines
.
map
(
joinItem
).
join
(
'
\n
'
)
:
''
if
(
splitThirdParty
)
{
return
{
userError
,
thirdParty
,
}
}
return
userError
+
'
\n
'
+
thirdParty
}
else
{
return
errorName
}
},
}
}
...
...
@@ -312,7 +414,7 @@ export function utsStracktraceyPreset(
return
undefined
}
const
fileName
=
planA
[
1
]
const
fileName
:
string
=
planA
[
1
]
?
(
planB
=
planA
[
1
].
match
(
/
(
.*
)
*
\/(
.+
)
/
)
||
[])[
2
]
||
''
:
''
...
...
@@ -336,6 +438,7 @@ export function utsStracktraceyPreset(
return
{
items
:
entries
as
StackTracey
.
Entry
[],
itemsHeader
:
[],
}
},
asTableStacktrace
({
maxColumnWidths
,
stacktrace
,
stack
})
{
...
...
packages/uni-stacktracey/src/stacktracey.ts
0 → 100644
浏览文件 @
5831bdcb
'
use strict
'
interface
EntryMetadata
{
beforeParse
:
string
callee
:
string
index
:
boolean
native
:
boolean
file
:
string
line
:
number
|
undefined
column
:
number
|
undefined
calleeShort
?:
string
}
/* ------------------------------------------------------------------------ */
const
O
=
Object
,
isBrowser
=
/* eslint-disable */
typeof
window
!==
'
undefined
'
&&
/* eslint-disable */
window
.
window
===
window
&&
/* eslint-disable */
window
.
navigator
,
nodeRequire
=
isBrowser
?
null
:
module
.
require
,
// to prevent bundlers from expanding the require call
lastOf
=
(
x
:
Array
<
any
>
)
=>
x
[
x
.
length
-
1
],
nixSlashes
=
(
x
:
string
)
=>
x
.
replace
(
/
\\
/g
,
'
/
'
),
pathRoot
=
isBrowser
?
window
.
location
.
href
:
nixSlashes
(
process
.
cwd
())
+
'
/
'
/* ------------------------------------------------------------------------ */
class
StackTracey
{
items
:
StackTracey
.
Entry
[]
itemsHeader
:
string
[]
=
[]
isMP
:
boolean
=
false
constructor
(
input
:
string
|
Error
|
any
,
offset
?:
number
)
{
const
originalInput
=
input
,
isParseableSyntaxError
=
input
&&
input
instanceof
SyntaxError
&&
!
isBrowser
/* new StackTracey () */
if
(
!
input
)
{
input
=
new
Error
()
offset
=
offset
===
undefined
?
1
:
offset
}
/* new StackTracey (Error) */
if
(
input
instanceof
Error
)
{
input
=
input
.
stack
||
''
}
/* new StackTracey (string) */
if
(
typeof
input
===
'
string
'
)
{
this
.
isMP
=
input
.
indexOf
(
'
MiniProgramError
'
)
!==
-
1
input
=
(
this
.
rawParse
(
input
)
as
EntryMetadata
[])
.
slice
(
offset
)
.
map
((
x
:
EntryMetadata
)
=>
this
.
extractEntryMetadata
(
x
))
}
/* new StackTracey (array) */
if
(
Array
.
isArray
(
input
))
{
if
(
isParseableSyntaxError
)
{
const
rawLines
=
nodeRequire
!
(
'
util
'
)
.
inspect
(
originalInput
)
.
split
(
'
\n
'
),
fileLine
=
rawLines
[
0
].
split
(
'
:
'
),
line
=
fileLine
.
pop
(),
file
=
fileLine
.
join
(
'
:
'
)
if
(
file
)
{
input
.
unshift
({
file
:
nixSlashes
(
file
),
line
:
line
,
column
:
(
rawLines
[
2
]
||
''
).
indexOf
(
'
^
'
)
+
1
,
sourceLine
:
rawLines
[
1
],
callee
:
'
(syntax error)
'
,
syntaxError
:
true
,
})
}
}
this
.
items
=
input
}
else
{
this
.
items
=
[]
}
}
extractEntryMetadata
(
e
:
EntryMetadata
)
{
const
decomposedPath
=
this
.
decomposePath
(
e
.
file
||
''
)
const
fileRelative
=
decomposedPath
[
0
]
const
externalDomain
=
decomposedPath
[
1
]
return
O
.
assign
(
e
,
{
calleeShort
:
e
.
calleeShort
||
lastOf
((
e
.
callee
||
''
).
split
(
'
.
'
)),
fileRelative
:
fileRelative
,
fileShort
:
this
.
shortenPath
(
fileRelative
),
fileName
:
lastOf
((
e
.
file
||
''
).
split
(
'
/
'
)),
thirdParty
:
this
.
isThirdParty
(
fileRelative
,
externalDomain
)
&&
!
e
.
index
,
externalDomain
:
externalDomain
,
})
}
shortenPath
(
relativePath
:
string
)
{
return
relativePath
.
replace
(
/^node_modules
\/
/
,
''
)
.
replace
(
/^webpack
\/
bootstrap
\/
/
,
''
)
.
replace
(
/^__parcel_source_root
\/
/
,
''
)
}
decomposePath
(
fullPath
:
string
):
string
[]
{
let
result
=
fullPath
if
(
isBrowser
)
result
=
result
.
replace
(
pathRoot
,
''
)
const
externalDomainMatch
=
result
.
match
(
/^
(
http|https
)\:\/\/?([^\/]
+
)\/(
.*
)
/
)
const
externalDomain
=
externalDomainMatch
?
externalDomainMatch
[
2
]
:
undefined
result
=
externalDomainMatch
?
externalDomainMatch
[
3
]
:
result
// if (!isBrowser) result = nodeRequire!('path').relative(pathRoot, result)
return
[
nixSlashes
(
result
).
replace
(
/^.*
\:\/\/?\/?
/
,
''
),
// cut webpack:/// and webpack:/ things
externalDomain
!
,
]
}
isThirdParty
(
relativePath
:
string
,
externalDomain
:
string
)
{
if
(
this
.
isMP
)
{
if
(
typeof
externalDomain
===
'
undefined
'
)
return
false
return
externalDomain
!==
'
usr
'
}
return
(
externalDomain
||
relativePath
[
0
]
===
'
~
'
||
// webpack-specific heuristic
relativePath
[
0
]
===
'
/
'
||
// external source
relativePath
.
indexOf
(
'
@dcloudio
'
)
!==
-
1
||
relativePath
.
indexOf
(
'
webpack/bootstrap
'
)
===
0
)
}
rawParse
(
str
:
string
)
{
const
lines
=
(
str
||
''
).
split
(
'
\n
'
)
const
entries
=
lines
.
map
((
line
,
index
)
=>
{
line
=
line
.
trim
()
let
callee
,
fileLineColumn
=
[],
native
,
planA
,
planB
if
(
(
planA
=
line
.
match
(
/at
(
.+
)
\(
eval at .+
\((
.+
)\)
, .+
\)
/
))
||
// eval calls
(
planA
=
line
.
match
(
/at
(
.+
)
\((
.+
)\)
/
))
||
(
line
.
slice
(
0
,
3
)
!==
'
at
'
&&
(
planA
=
line
.
match
(
/
(
.*
)
@
(
.*
)
/
)))
)
{
this
.
itemsHeader
.
push
(
'
%StacktraceyItem%
'
)
callee
=
planA
[
1
]
native
=
planA
[
2
]
===
'
native
'
fileLineColumn
=
(
planA
[
2
].
match
(
/
(
.*
)
:
(\d
+
)
:
(\d
+
)
/
)
||
planA
[
2
].
match
(
/
(
.*
)
:
(\d
+
)
/
)
||
planA
[
2
].
match
(
/
\[(
.*
)\]
/
)
||
[]
).
slice
(
1
)
}
else
if
((
planB
=
line
.
match
(
/^
(
at
\s
*
)
*
(
.*
)\s
+
(
.+
)
:
(\d
+
)
:
(\d
+
)
/
)))
{
this
.
itemsHeader
.
push
(
'
%StacktraceyItem%
'
)
callee
=
planB
[
2
].
trim
()
fileLineColumn
=
planB
.
slice
(
3
)
}
else
{
this
.
itemsHeader
.
push
(
line
)
return
undefined
}
/* Detect things like Array.reduce
TODO: detect more built-in types */
if
(
callee
&&
!
fileLineColumn
[
0
])
{
const
type
=
callee
.
split
(
'
.
'
)[
0
]
if
(
type
===
'
Array
'
)
{
native
=
true
}
}
return
{
beforeParse
:
line
,
callee
:
callee
||
''
,
/* eslint-disable */
index
:
isBrowser
&&
fileLineColumn
[
0
]
===
window
.
location
.
href
,
native
:
native
||
false
,
file
:
nixSlashes
(
fileLineColumn
[
0
]
||
''
),
line
:
parseInt
(
fileLineColumn
[
1
]
||
''
,
10
)
||
undefined
,
column
:
parseInt
(
fileLineColumn
[
2
]
||
''
,
10
)
||
undefined
,
}
})
return
entries
.
filter
((
x
)
=>
x
!==
undefined
)
}
maxColumnWidths
()
{
return
{
callee
:
30
,
file
:
60
,
sourceLine
:
80
,
}
}
asTable
(
opts
?:
{
maxColumnWidths
:
StackTracey
.
MaxColumnWidths
})
{
const
maxColumnWidths
=
(
opts
&&
opts
.
maxColumnWidths
)
||
this
.
maxColumnWidths
()
const
trimmed
=
(
this
as
any
)
.
filter
((
e
:
StackTracey
.
Entry
)
=>
!
e
.
thirdParty
)
.
map
((
e
:
StackTracey
.
Entry
)
=>
parseItem
(
e
,
maxColumnWidths
,
this
.
isMP
))
const
trimmedThirdParty
=
(
this
as
any
)
.
filter
((
e
:
StackTracey
.
Entry
)
=>
e
.
thirdParty
)
.
map
((
e
:
StackTracey
.
Entry
)
=>
parseItem
(
e
,
maxColumnWidths
,
this
.
isMP
))
return
{
items
:
trimmed
.
items
as
Array
<
string
[]
>
,
thirdPartyItems
:
trimmedThirdParty
.
items
as
Array
<
string
[]
>
,
}
}
}
const
trimEnd
=
(
s
:
string
,
n
:
number
)
=>
s
&&
(
s
.
length
>
n
?
s
.
slice
(
0
,
n
-
1
)
+
'
…
'
:
s
)
const
trimStart
=
(
s
:
string
,
n
:
number
)
=>
s
&&
(
s
.
length
>
n
?
'
…
'
+
s
.
slice
(
-
(
n
-
1
))
:
s
)
function
parseItem
(
e
:
StackTracey
.
Entry
,
maxColumnWidths
:
StackTracey
.
MaxColumnWidths
,
isMP
:
boolean
)
{
const
filePath
=
(
isMP
?
e
.
file
&&
e
.
file
:
e
.
fileShort
&&
e
.
fileShort
)
+
`
${
e
.
line
?
'
:
'
+
e
.
line
:
''
}
`
+
`
${
e
.
column
?
'
:
'
+
e
.
column
:
''
}
`
return
[
'
at
'
+
trimEnd
(
isMP
?
e
.
callee
:
e
.
calleeShort
,
maxColumnWidths
.
callee
),
trimStart
(
filePath
||
''
,
maxColumnWidths
.
file
),
trimEnd
((
e
.
sourceLine
||
''
).
trim
()
||
''
,
maxColumnWidths
.
sourceLine
),
]
}
/* Array methods
------------------------------------------------------------------------ */
;[
'
map
'
,
'
filter
'
,
'
slice
'
,
'
concat
'
].
forEach
((
method
)
=>
{
;(
StackTracey
.
prototype
as
any
)[
method
]
=
function
(
/*...args */
)
{
// no support for ...args in Node v4 :(
return
new
StackTracey
(
this
.
items
[
method
].
apply
(
this
.
items
,
arguments
))
}
})
/* ------------------------------------------------------------------------ */
export
default
StackTracey
declare
namespace
StackTracey
{
interface
SourceFile
{
path
:
string
text
:
string
lines
:
string
[]
error
?:
Error
}
interface
Location
{
file
:
string
line
?:
number
column
?:
number
}
interface
Entry
extends
Location
{
beforeParse
:
string
callee
:
string
index
:
boolean
native
:
boolean
calleeShort
:
string
fileRelative
:
string
fileShort
:
string
fileName
:
string
thirdParty
:
boolean
hide
?:
boolean
sourceLine
?:
string
sourceFile
?:
SourceFile
error
?:
Error
line
?:
number
column
?:
number
}
interface
MaxColumnWidths
{
callee
:
number
file
:
number
sourceLine
:
number
}
}
packages/uni-stacktracey/test/__UNI_
_APPID__/1.0.0/.sourcemap/h5
/static/js/chunk-vendors.75525bd5.js.map
→
packages/uni-stacktracey/test/__UNI_
APPID__/h5/1.0.0
/static/js/chunk-vendors.75525bd5.js.map
浏览文件 @
5831bdcb
文件已移动
packages/uni-stacktracey/test/__UNI_
_APPID__/1.0.0/.sourcemap/h5
/static/js/pages-index-index.3ab0d0e5.js.map
→
packages/uni-stacktracey/test/__UNI_
APPID__/h5/1.0.0
/static/js/pages-index-index.3ab0d0e5.js.map
浏览文件 @
5831bdcb
文件已移动
packages/uni-stacktracey/test/index.js
浏览文件 @
5831bdcb
...
...
@@ -20,6 +20,17 @@ Run with --stacktrace option to get the stack trace. Run with --info or --debug
BUILD FAILED in 2s
`
stacktracey
(
utsErrorMsg
,
{
preset
:
utsStracktraceyPreset
({
base
:
path
.
resolve
(
__dirname
,
'
./nativeplugins-sourceMap/DCloud-UTSPlugin/
'
),
sourceRoot
:
'
DCloud-UTSPlugin/android/src/
'
,
}),
}).
then
((
res
)
=>
{
console
.
log
(
'
res :>>
'
,
res
)
})
const
uniErrorMsg
=
`Error: Sentry Error
at a.throwError(/static/js/pages-index-index.3ab0d0e5.js:1:567)
...
...
@@ -30,24 +41,12 @@ at ee(/static/js/chunk-vendors.75525bd5.js:34:11927)
at HTMLElement.n(/static/js/chunk-vendors.75525bd5.js:34:13824)
at HTMLElement.o._wrapper(/static/js/chunk-vendors.75525bd5.js:34:53966)
at HTMLElement.i(/static/js/chunk-vendors.75525bd5.js:7:609894)`
stacktracey
(
uniErrorMsg
,
{
preset
:
uniStracktraceyPreset
({
base
:
path
.
resolve
(
__dirname
,
'
./__UNI__APPID__/1.0.0/.sourcemap/h5/
'
),
sourceRoot
:
''
,
// splitThirdParty: true
}),
}).
then
((
res
)
=>
{
console
.
log
(
'
res :>>
'
,
res
)
})
/* stacktracey(utsErrorMsg, {
preset: utsStracktraceyPreset({
base: path.resolve(
__dirname,
'./nativeplugins-sourceMap/DCloud-UTSPlugin/'
),
sourceRoot: 'DCloud-UTSPlugin/android/src/',
}),
}).then((res) => {
console.log('res :>> ', res)
})
*/
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录