提交 8f393f7e 编写于 作者: fxy060608's avatar fxy060608

feat(x-ios): 提供 parseUTSJavaScriptRuntimeStacktrace

上级 2cd6d1dc
import picker from '@ohos.file.picker';
import fs from '@ohos.file.fs';
import promptAction from '@ohos.promptAction';
import { getCurrentInstance, onMounted, nextTick, onBeforeUnmount } from 'vue';
/**
* @vue/shared v3.4.21
......@@ -39,7 +40,26 @@ const capitalize = cacheStringFunction((str) => {
});
const LINEFEED = '\n';
const ON_READY = 'onReady';
const ON_UNLOAD = 'onUnload';
let lastLogTime = 0;
function formatLog(module, ...args) {
const now = Date.now();
const diff = lastLogTime ? now - lastLogTime : 0;
lastLogTime = now;
return `[${now}][${diff}ms][${module}]:${args
.map((arg) => JSON.stringify(arg))
.join(' ')}`;
}
const invokeArrayFns = (fns, arg) => {
let ret;
for (let i = 0; i < fns.length; i++) {
ret = fns[i](arg);
}
return ret;
};
function once(fn, ctx = null) {
let res;
return ((...args) => {
......@@ -51,6 +71,86 @@ function once(fn, ctx = null) {
});
}
class EventChannel {
id;
listener;
emitCache;
constructor(id, events) {
this.id = id;
this.listener = {};
this.emitCache = [];
if (events) {
Object.keys(events).forEach((name) => {
this.on(name, events[name]);
});
}
}
emit(eventName, ...args) {
const fns = this.listener[eventName];
if (!fns) {
return this.emitCache.push({
eventName,
args,
});
}
fns.forEach((opt) => {
opt.fn.apply(opt.fn, args);
});
this.listener[eventName] = fns.filter((opt) => opt.type !== 'once');
}
on(eventName, fn) {
this._addListener(eventName, 'on', fn);
this._clearCache(eventName);
}
once(eventName, fn) {
this._addListener(eventName, 'once', fn);
this._clearCache(eventName);
}
off(eventName, fn) {
const fns = this.listener[eventName];
if (!fns) {
return;
}
if (fn) {
for (let i = 0; i < fns.length;) {
if (fns[i].fn === fn) {
fns.splice(i, 1);
i--;
}
i++;
}
}
else {
delete this.listener[eventName];
}
}
_clearCache(eventName) {
for (let index = 0; index < this.emitCache.length; index++) {
const cache = this.emitCache[index];
const _name = eventName
? cache.eventName === eventName
? eventName
: null
: cache.eventName;
if (!_name)
continue;
const location = this.emit.apply(this, [_name, ...cache.args]);
if (typeof location === 'number') {
this.emitCache.pop();
continue;
}
this.emitCache.splice(index, 1);
index--;
}
}
_addListener(eventName, type, fn) {
(this.listener[eventName] || (this.listener[eventName] = [])).push({
fn,
type,
});
}
}
const CHOOSE_SIZE_TYPES = ['original', 'compressed'];
const CHOOSE_SOURCE_TYPES = ['album', 'camera'];
function elemsInArray(strArr, optionalVal) {
......@@ -878,6 +978,55 @@ const initI18nChooseImageMsgsOnce = /*#__PURE__*/ once(() => {
}
});
function getCurrentPage() {
const pages = getCurrentPages();
const len = pages.length;
if (len) {
return pages[len - 1];
}
}
function getCurrentPageVm() {
const page = getCurrentPage();
if (page) {
return page.$vm;
}
}
function invokeHook(vm, name, args) {
if (isString(vm)) {
args = name;
name = vm;
vm = getCurrentPageVm();
}
else if (typeof vm === 'number') {
const page = getCurrentPages().find((page) => page.$page.id === vm);
if (page) {
vm = page.$vm;
}
else {
vm = getCurrentPageVm();
}
}
if (!vm) {
return;
}
const hooks = vm.$[name];
return hooks && invokeArrayFns(hooks, args);
}
function initPageVm(pageVm, page) {
pageVm.route = page.route;
pageVm.$vm = pageVm;
pageVm.$page = page;
pageVm.$mpType = 'page';
pageVm.$fontFamilySet = new Set();
if (page.meta.isTabBar) {
pageVm.$.__isTabBar = true;
// TODO preload? 初始化时,状态肯定是激活
pageVm.$.__isActive = true;
}
}
const API_CHOOSE_IMAGE = 'chooseImage';
const ChooseImageOptions = {
formatArgs: {
......@@ -1031,6 +1180,90 @@ var uni$1 = {
getSystemInfoSync: getSystemInfoSync
};
globalThis.uni = uni$1;
const pages = [];
function addCurrentPage(page) {
const $page = page.$page;
if (!$page.meta.isNVue) {
return pages.push(page);
}
// 开发阶段热刷新需要移除旧的相同 id 的 page
const index = pages.findIndex((p) => p.$page.id === page.$page.id);
if (index > -1) {
pages.splice(index, 1, page);
}
else {
pages.push(page);
}
}
function setupPage(component) {
const oldSetup = component.setup;
component.inheritAttrs = false; // 禁止继承 __pageId 等属性,避免告警
component.setup = (_, ctx) => {
const { attrs: { __pageId, __pagePath, __pageQuery, __pageInstance }, } = ctx;
if (('production' !== 'production')) {
console.log(formatLog(__pagePath, 'setup'));
}
const instance = getCurrentInstance();
const pageVm = instance.proxy;
initPageVm(pageVm, __pageInstance);
addCurrentPage(initScope(__pageId, pageVm, __pageInstance));
{
onMounted(() => {
nextTick(() => {
// onShow被延迟,故onReady也同时延迟
invokeHook(pageVm, ON_READY);
});
// TODO preloadSubPackages
});
onBeforeUnmount(() => {
invokeHook(pageVm, ON_UNLOAD);
});
}
if (oldSetup) {
return oldSetup(__pageQuery, ctx);
}
};
return component;
}
function initScope(pageId, vm, pageInstance) {
{
const $getAppWebview = () => {
return plus.webview.getWebviewById(pageId + '');
};
vm.$getAppWebview = $getAppWebview;
vm.$.ctx.$scope = {
$getAppWebview,
};
}
vm.getOpenerEventChannel = () => {
if (!pageInstance.eventChannel) {
pageInstance.eventChannel = new EventChannel(pageId);
}
return pageInstance.eventChannel;
};
return vm;
}
function isVuePageAsyncComponent(component) {
return isFunction(component);
}
const pagesMap = new Map();
function definePage(pagePath, asyncComponent) {
pagesMap.set(pagePath, once(createFactory(asyncComponent)));
}
function createFactory(component) {
return () => {
if (isVuePageAsyncComponent(component)) {
return component().then((component) => setupPage(component));
}
return setupPage(component);
};
}
var index = {
uni: uni$1,
__definePage: definePage,
};
export { uni$1 as uni };
export { index as default };
......@@ -72,11 +72,16 @@ export function uniAppIOSPlugin(): UniVitePlugin {
const appServiceMap = bundle[APP_SERVICE_FILENAME_MAP]
if (appServiceMap && appServiceMap.type === 'asset') {
fs.outputFileSync(
path.resolve(
process.env.UNI_OUTPUT_DIR,
'../.sourcemap/app-ios',
APP_SERVICE_FILENAME_MAP
),
process.env.UNI_APP_X_CACHE_DIR
? path.resolve(
process.env.UNI_APP_X_CACHE_DIR,
APP_SERVICE_FILENAME_MAP
)
: path.resolve(
process.env.UNI_OUTPUT_DIR,
'../.sourcemap/app-ios',
APP_SERVICE_FILENAME_MAP
),
appServiceMap.source
)
delete bundle[APP_SERVICE_FILENAME_MAP]
......
......@@ -13457,6 +13457,17 @@ function createRightWindowTsx(rightWindow, layoutState, windowState) {
}, windowState), null, 16)])], 12, ["data-show"]), [[vue.vShow, layoutState.showRightWindow || layoutState.apiShowRightWindow]]);
}
}
function updateBackgroundColorContent(backgroundColorContent) {
{
return;
}
}
function useBackgroundColorContent(pageMeta) {
function update() {
updateBackgroundColorContent(pageMeta.backgroundColorContent || "");
}
vue.watchEffect(update);
}
function usePageHeadTransparentBackgroundColor(backgroundColor) {
const { r, g: g2, b } = hexToRgba(backgroundColor);
return `rgba(${r},${g2},${b},0)`;
......@@ -13915,10 +13926,10 @@ const index = defineSystemComponent({
const pageMeta = providePageMeta(getStateId());
const navigationBar = pageMeta.navigationBar;
const pageStyle = {};
if (pageMeta.backgroundColorContent) {
pageStyle.backgroundColor = pageMeta.backgroundColorContent;
}
useDocumentTitle(pageMeta);
{
useBackgroundColorContent(pageMeta);
}
return () => vue.createVNode(
"uni-page",
{
......
......@@ -26691,6 +26691,23 @@ const UniServiceJSBridge$1 = /* @__PURE__ */ extend(ServiceJSBridge, {
UniViewJSBridge.subscribeHandler(event, args, pageId);
}
});
function updateBackgroundColorContent(backgroundColorContent) {
if (backgroundColorContent) {
document.body.style.setProperty(
"--background-color-content",
backgroundColorContent
);
} else {
document.body.style.removeProperty("--background-color-content");
}
}
function useBackgroundColorContent(pageMeta) {
function update() {
updateBackgroundColorContent(pageMeta.backgroundColorContent || "");
}
watchEffect(update);
onActivated(update);
}
function usePageHeadTransparentBackgroundColor(backgroundColor) {
const { r, g: g2, b } = hexToRgba(backgroundColor);
return `rgba(${r},${g2},${b},0)`;
......@@ -27425,10 +27442,10 @@ const index = defineSystemComponent({
const pageMeta = providePageMeta(getStateId());
const navigationBar = pageMeta.navigationBar;
const pageStyle = {};
if (pageMeta.backgroundColorContent) {
pageStyle.backgroundColor = pageMeta.backgroundColorContent;
}
useDocumentTitle(pageMeta);
{
useBackgroundColorContent(pageMeta);
}
return () => createVNode(
"uni-page",
{
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`uts:stacktrace:runtime parseUTSJavaScriptRuntimeStacktrace 1`] = `
"error: ReferenceError:Can't find variable: a
at pages/index/index.uvue:3:8
1 | <script lang="uts">
2 | setTimeout(() => {
3 | uni.__log__('log','at pages/index/index.uvue:3',a)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 | }, 1000)
5 | export default {"
`;
exports[`uts:stacktrace:runtime parseUTSJavaScriptRuntimeStacktrace 2`] = `
"error: at <Index __pageId=1 __pagePath="pages/index/index" __pageQuery= ... >
Can't find variable: a
at pages/index/index.uvue:7:12
5 | export default {
6 | onLoad() {
7 | uni.__log__('log','at pages/index/index.uvue:7',a)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8 | }
9 | }"
`;
exports[`uts:stacktrace:runtime parseUTSKotlinRuntimeStacktrace 1`] = `
"error: java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
at pages/index/index.uvue:13:12
......
{"version":3,"file":"app-service.js","sources":["pages/index/index.uvue","App.uvue","main.uts"],"sourcesContent":["<script lang=\"uts\">\r\n setTimeout(() => {\r\n uni.__log__('log','at pages/index/index.uvue:3',a)\r\n }, 1000)\r\n export default {\r\n onLoad() {\r\n uni.__log__('log','at pages/index/index.uvue:7',a)\r\n }\r\n }\r\n</script>","<script lang=\"uts\">\r\n\tlet firstBackTime = 0\r\n\texport default {\r\n\t\tonLaunch: function () {\r\n\t\t\tuni.__log__('log','at App.uvue:5','App Launch')\r\n\t\t},\r\n\t\tonShow: function () {\r\n\t\t\tuni.__log__('log','at App.uvue:8','App Show')\r\n\t\t},\r\n\t\tonHide: function () {\r\n\t\t\tuni.__log__('log','at App.uvue:11','App Hide')\r\n\t\t},\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\tonExit: function () {\r\n\t\t\tuni.__log__('log','at App.uvue:32','App Exit')\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n\t.uni-row {\r\n\t\tflex-direction: row;\r\n\t}\r\n\r\n\t.uni-column {\r\n\t\tflex-direction: column;\r\n\t}\r\n</style>","import App from './App.uvue'\r\n\r\nimport { createSSRApp } from 'vue'\n\r\nexport function createApp() {\r\n\tconst app = createSSRApp(App)\r\n\treturn {\r\n\t\tapp\r\n\t}\r\n}"],"names":["defineComponent","createSSRApp"],"mappings":";;;IACI,UAAU,CAAC,MAAA;QACP,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,6BAA6B,EAAC,CAAC,CAAA,CAAA;IACrD,CAAC,EAAE,IAAI,CAAA,CAAA;AACP,wBAAeA,mBAAA,CAAA;QACX,MAAM,GAAA;YACF,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,6BAA6B,EAAC,CAAC,CAAA,CAAA;SACrD;KACJ,CAAA;;;;;;;;;;;;;;ACNH,sBAAeA,mBAAA,CAAA;IACd,IAAA,QAAQ,EAAE,YAAA;YACT,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,eAAe,EAAC,YAAY,CAAA,CAAA;SAC9C;IACD,IAAA,MAAM,EAAE,YAAA;YACP,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,eAAe,EAAC,UAAU,CAAA,CAAA;SAC5C;IACD,IAAA,MAAM,EAAE,YAAA;YACP,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAA,CAAA;SAC7C;IAmBD,IAAA,MAAM,EAAE,YAAA;YACP,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAA,CAAA;SAC7C;KACF,CAAA;;;;;;IC9BS,MAAM,UAAU,GAAG,OAAO,UAAU,KAAK,WAAW,GAAG,QAAQ,CAAC,aAAa,CAAC,EAAE,GAAG,UAAU,CAAA;IAC7F,UAAU,CAAC,MAAM,GAAG,IAAI,CAAA;aAKlB,SAAS,GAAA;IACxB,IAAA,MAAM,GAAG,GAAGC,gBAAY,CAAC,GAAG,CAAC,CAAA;QAC7B,OAAO;YACN,GAAG;SACH,CAAA;IACF,CAAC;IACS,SAAS,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC;;;;;;"}
\ No newline at end of file
import path from 'path'
import { parseUTSKotlinRuntimeStacktrace } from '../src/stacktrace/kotlin'
import { parseUTSJavaScriptRuntimeStacktrace } from '../src/stacktrace/js'
describe('uts:stacktrace:runtime', () => {
test('parseUTSKotlinRuntimeStacktrace', async () => {
const cacheDir = path.resolve(
__dirname,
'examples/uni-app-x/unpackage/cache/.kotlin'
'examples/uni-app-x/unpackage/cache/app-android'
)
expect(
parseUTSKotlinRuntimeStacktrace(
......@@ -76,4 +77,31 @@ at uni.UNIXXXXXXX.IndexKt.test(index.kt:40)`,
)
).toMatchSnapshot()
})
test('parseUTSJavaScriptRuntimeStacktrace', async () => {
const cacheDir = path.resolve(
__dirname,
'examples/uni-app-x/unpackage/cache/app-ios'
)
expect(
parseUTSJavaScriptRuntimeStacktrace(
`app-service.js(5:60) ReferenceError:Can't find variable: a @app-service.js:5:60`,
{
cacheDir,
}
)
).toMatchSnapshot()
expect(
parseUTSJavaScriptRuntimeStacktrace(
`at <Index __pageId=1 __pagePath="pages/index/index" __pageQuery= ... >
Can't find variable: a
onLoad@app-service.js:9:64
callWithErrorHandling@uni-app-x-framework.js:2279:23
callWithAsyncErrorHandling@uni-app-x-framework.js:2286:38
@uni-app-x-framework.js:4763:45`,
{
cacheDir,
}
)
).toMatchSnapshot()
})
})
import { originalPositionForSync } from '../sourceMap'
import {
COLORS,
type GenerateRuntimeCodeFrameOptions,
generateCodeFrame,
lineColumnToStartEnd,
resolveSourceMapDirByCacheDir,
resolveSourceMapFileBySourceFile,
splitRE,
} from './utils'
interface GenerateJavaScriptRuntimeCodeFrameOptions
extends GenerateRuntimeCodeFrameOptions {}
const JS_ERROR_RE = /\(\d+:\d+\)\s(.*)\s@([^\s]+\.js)\:(\d+)\:(\d+)/
const VUE_ERROR_RE = /@([^\s]+\.js)\:(\d+)\:(\d+)/
// app-service.js(4:56) ReferenceError:Can't find variable: a @app-service.js:4:56
export function parseUTSJavaScriptRuntimeStacktrace(
stacktrace: string,
options: GenerateJavaScriptRuntimeCodeFrameOptions
) {
const res: string[] = []
const lines = stacktrace.split(splitRE)
const sourceMapDir = resolveSourceMapDirByCacheDir(options.cacheDir)
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
let codes = parseUTSJavaScriptRuntimeStacktraceJsErrorLine(
line,
sourceMapDir
)
if (codes.length) {
const color = options.logType
? COLORS[options.logType as string] || ''
: ''
const [errorCode, ...other] = codes
let error = 'error: ' + errorCode
if (color) {
error = color + error + color
}
return [error, ...other].join('\n')
}
codes = parseUTSJavaScriptRuntimeStacktraceVueErrorLine(line, sourceMapDir)
if (codes.length && res.length) {
const color = options.logType
? COLORS[options.logType as string] || ''
: ''
let error = 'error: ' + res[0]
if (color) {
error = color + error + color
}
const [, ...other] = res
const otherCodes = other.map((item) => {
if (color) {
return color + item + color
}
return item
})
return [error, ...otherCodes, ...codes].join('\n')
}
res.push(line)
}
return ''
}
// at <Index __pageId=1 __pagePath="pages/index/index" __pageQuery= ... >
// Can't find variable: a
// onLoad@app-service.js:9:64
// callWithErrorHandling@uni-app-x-framework.js:2279:23
function parseUTSJavaScriptRuntimeStacktraceVueErrorLine(
lineStr: string,
sourceMapDir: string
) {
const lines: string[] = []
const matches = lineStr.match(VUE_ERROR_RE)
if (!matches) {
return lines
}
const [, filename, line] = matches
const sourceMapFile = resolveSourceMapFileBySourceFile(filename, sourceMapDir)
if (!sourceMapFile) {
return lines
}
const originalPosition = originalPositionForSync({
sourceMapFile,
line: parseInt(line),
column: 0,
withSourceContent: true,
})
if (originalPosition.source && originalPosition.sourceContent) {
lines.push(
`at ${originalPosition.source.split('?')[0]}:${originalPosition.line}:${
originalPosition.column
}`
)
if (originalPosition.line !== null && originalPosition.column !== null) {
const { start, end } = lineColumnToStartEnd(
originalPosition.sourceContent,
originalPosition.line,
originalPosition.column
)
lines.push(
generateCodeFrame(originalPosition.sourceContent, start, end).replace(
/\t/g,
' '
)
)
}
}
return lines
}
function parseUTSJavaScriptRuntimeStacktraceJsErrorLine(
lineStr: string,
sourceMapDir: string
) {
const lines: string[] = []
const matches = lineStr.match(JS_ERROR_RE)
if (!matches) {
return lines
}
const [, error, filename, line] = matches
const sourceMapFile = resolveSourceMapFileBySourceFile(filename, sourceMapDir)
if (!sourceMapFile) {
return lines
}
const originalPosition = originalPositionForSync({
sourceMapFile,
line: parseInt(line),
column: 0,
withSourceContent: true,
})
if (originalPosition.source && originalPosition.sourceContent) {
lines.push(error)
lines.push(
`at ${originalPosition.source.split('?')[0]}:${originalPosition.line}:${
originalPosition.column
}`
)
if (originalPosition.line !== null && originalPosition.column !== null) {
const { start, end } = lineColumnToStartEnd(
originalPosition.sourceContent,
originalPosition.line,
originalPosition.column
)
lines.push(
generateCodeFrame(originalPosition.sourceContent, start, end).replace(
/\t/g,
' '
)
)
}
}
return lines
}
......@@ -2,7 +2,15 @@ import path from 'path'
import fs from 'fs-extra'
import { relative } from '../utils'
import { originalPositionFor, originalPositionForSync } from '../sourceMap'
import { generateCodeFrame, lineColumnToStartEnd, splitRE } from './utils'
import {
COLORS,
type GenerateRuntimeCodeFrameOptions,
generateCodeFrame,
lineColumnToStartEnd,
resolveSourceMapDirByCacheDir,
resolveSourceMapFileBySourceFile,
splitRE,
} from './utils'
export interface MessageSourceLocation {
type: 'exception' | 'error' | 'warning' | 'info' | 'logging' | 'output'
......@@ -175,31 +183,14 @@ function parseFilenameByClassName(className: string) {
return kotlinManifest.manifest[className.split('$')[0]] || 'index.kt'
}
function resolveSourceMapFileByKtFile(file: string, sourceMapDir: string) {
const sourceMapFile = path.resolve(sourceMapDir, file + '.map')
if (fs.existsSync(sourceMapFile)) {
return sourceMapFile
}
}
const COLORS: Record<string, string> = {
warn: '\u200B',
error: '\u200C',
}
interface GenerateRuntimeCodeFrameOptions {
interface GenerateKotlinRuntimeCodeFrameOptions
extends GenerateRuntimeCodeFrameOptions {
appid: string
cacheDir: string
logType?: 'log' | 'info' | 'warn' | 'debug' | 'error'
}
function resolveSourceMapDirByCacheDir(cacheDir: string) {
return path.resolve(cacheDir, 'sourceMap')
}
export function parseUTSKotlinRuntimeStacktrace(
stacktrace: string,
options: GenerateRuntimeCodeFrameOptions
options: GenerateKotlinRuntimeCodeFrameOptions
) {
const appid = normalizeAppid(options.appid || DEFAULT_APPID)
if (!stacktrace.includes('uni.' + appid + '.')) {
......@@ -209,13 +200,10 @@ export function parseUTSKotlinRuntimeStacktrace(
const re = createRegExp(appid)
const res: string[] = []
const lines = stacktrace.split(splitRE)
const sourceMapDir = resolveSourceMapDirByCacheDir(options.cacheDir)
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
const codes = parseUTSKotlinRuntimeStacktraceLine(
line,
re,
resolveSourceMapDirByCacheDir(options.cacheDir)
)
const codes = parseUTSKotlinRuntimeStacktraceLine(line, re, sourceMapDir)
if (codes.length && res.length) {
const color = options.logType
? COLORS[options.logType as string] || ''
......@@ -245,7 +233,7 @@ function parseUTSKotlinRuntimeStacktraceLine(
}
const [, className, line] = matches
const sourceMapFile = resolveSourceMapFileByKtFile(
const sourceMapFile = resolveSourceMapFileBySourceFile(
parseFilenameByClassName(className),
sourceMapDir
)
......
import fs from 'fs'
import path from 'path'
export interface GenerateRuntimeCodeFrameOptions {
cacheDir: string
logType?: 'log' | 'info' | 'warn' | 'debug' | 'error'
}
export const COLORS: Record<string, string> = {
warn: '\u200B',
error: '\u200C',
}
export const splitRE = /\r?\n/
export function resolveSourceMapDirByCacheDir(cacheDir: string) {
return path.resolve(cacheDir, 'sourcemap')
}
export function resolveSourceMapFileBySourceFile(
file: string,
sourceMapDir: string
) {
const sourceMapFile = path.resolve(sourceMapDir, file + '.map')
if (fs.existsSync(sourceMapFile)) {
return sourceMapFile
}
}
const range: number = 2
function posToNumber(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册