mirror of
https://github.com/liriliri/eruda.git
synced 2026-04-05 10:28:34 +08:00
perf(console): large object expansion
This commit is contained in:
@@ -2,6 +2,7 @@ import stringify from './stringify'
|
||||
import origGetAbstract from '../lib/getAbstract'
|
||||
import beautify from 'js-beautify'
|
||||
import JsonViewer from '../lib/JsonViewer'
|
||||
import ObjViewer from '../lib/ObjViewer'
|
||||
import {
|
||||
isObj,
|
||||
isStr,
|
||||
@@ -185,7 +186,8 @@ export default class Log extends Emitter {
|
||||
}
|
||||
}
|
||||
click(logger) {
|
||||
const { type, src, args } = this
|
||||
const { type, src } = this
|
||||
let { args } = this
|
||||
const $el = this._$el
|
||||
|
||||
switch (type) {
|
||||
@@ -198,23 +200,29 @@ export default class Log extends Emitter {
|
||||
case 'dir':
|
||||
case 'group':
|
||||
case 'groupCollapsed':
|
||||
if (src) {
|
||||
if (src || args) {
|
||||
const $json = $el.find('.eruda-json')
|
||||
if ($json.hasClass('eruda-hidden')) {
|
||||
if ($json.data('init') !== 'true') {
|
||||
const jsonViewer = new JsonViewer(src, $json)
|
||||
jsonViewer.on('change', () => this.updateSize(false))
|
||||
if (src) {
|
||||
const jsonViewer = new JsonViewer(src, $json)
|
||||
jsonViewer.on('change', () => this.updateSize(false))
|
||||
} else {
|
||||
if (type === 'table' || args.length === 1) {
|
||||
if (isObj(args[0])) args = args[0]
|
||||
}
|
||||
const objViewer = new ObjViewer(args, $json, {
|
||||
showUnenumerable: Log.showUnenumerable,
|
||||
showGetterVal: Log.showGetterVal
|
||||
})
|
||||
objViewer.on('change', () => this.updateSize(false))
|
||||
}
|
||||
$json.data('init', 'true')
|
||||
}
|
||||
$json.rmClass('eruda-hidden')
|
||||
} else {
|
||||
$json.addClass('eruda-hidden')
|
||||
}
|
||||
} else if (args) {
|
||||
this.extractObj(() => {
|
||||
this.click(logger)
|
||||
delete this.args
|
||||
})
|
||||
} else if (type === 'group' || type === 'groupCollapsed') {
|
||||
logger.toggleGroup(this)
|
||||
}
|
||||
|
||||
@@ -121,7 +121,8 @@
|
||||
bottom: 0;
|
||||
color: var(--foreground);
|
||||
width: 100%;
|
||||
background: var(--accent);
|
||||
border-top: 1px solid var(--border);
|
||||
background: var(--darker-background);
|
||||
display: block;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
|
||||
@@ -53,7 +53,7 @@ export default class JsonViewer extends Emitter {
|
||||
this._appendTpl()
|
||||
this._bindEvent()
|
||||
}
|
||||
jsonToHtml(data, firstLevel) {
|
||||
_jsonToHtml(data, firstLevel) {
|
||||
let ret = ''
|
||||
|
||||
each(['enumerable', 'unenumerable', 'symbol'], type => {
|
||||
@@ -63,23 +63,22 @@ export default class JsonViewer extends Emitter {
|
||||
typeKeys.sort(sortObjName)
|
||||
for (let i = 0, len = typeKeys.length; i < len; i++) {
|
||||
const key = typeKeys[i]
|
||||
ret += this.createEl(key, data[type][key], type, firstLevel)
|
||||
ret += this._createEl(key, data[type][key], type, firstLevel)
|
||||
}
|
||||
})
|
||||
|
||||
if (data.proto) {
|
||||
if (ret === '') {
|
||||
ret = this.jsonToHtml(data.proto, firstLevel)
|
||||
ret = this._jsonToHtml(data.proto)
|
||||
} else {
|
||||
ret += this.createEl('__proto__', data.proto, 'proto', firstLevel)
|
||||
ret += this._createEl('__proto__', data.proto, 'proto')
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
createEl(key, val, keyType, firstLevel = false) {
|
||||
_createEl(key, val, keyType, firstLevel = false) {
|
||||
let type = typeof val
|
||||
let id
|
||||
|
||||
if (val === null) {
|
||||
return `<li>${wrapKey(key)}<span class="eruda-null">null</span></li>`
|
||||
@@ -103,7 +102,7 @@ export default class JsonViewer extends Emitter {
|
||||
} else if (val === '(...)') {
|
||||
return `<li>${wrapKey(key)}<span class="eruda-special">${val}</span></li>`
|
||||
} else if (isObj(val)) {
|
||||
id = val.id
|
||||
const id = val.id
|
||||
const referenceId = val.reference
|
||||
const objAbstract = getObjAbstract(val) || upperFirst(type)
|
||||
|
||||
@@ -121,7 +120,7 @@ export default class JsonViewer extends Emitter {
|
||||
firstLevel ? '' : 'style="display:none"'
|
||||
}>`
|
||||
|
||||
if (firstLevel) obj += this.jsonToHtml(this._map[id])
|
||||
if (firstLevel) obj += this._jsonToHtml(this._map[id])
|
||||
|
||||
return obj + '</ul><span class="eruda-close"></span></li>'
|
||||
}
|
||||
@@ -149,7 +148,7 @@ export default class JsonViewer extends Emitter {
|
||||
_appendTpl() {
|
||||
const data = this._map[this._data.id]
|
||||
|
||||
this._$el.html(this.jsonToHtml(data, true))
|
||||
this._$el.html(this._jsonToHtml(data, true))
|
||||
}
|
||||
_bindEvent() {
|
||||
const map = this._map
|
||||
@@ -165,7 +164,7 @@ export default class JsonViewer extends Emitter {
|
||||
|
||||
if ($this.data('first-level')) return
|
||||
if (circularId) {
|
||||
$this.find('ul').html(self.jsonToHtml(map[circularId], false))
|
||||
$this.find('ul').html(self._jsonToHtml(map[circularId], false))
|
||||
$this.rmAttr('data-object-id')
|
||||
}
|
||||
|
||||
@@ -268,14 +267,14 @@ function objToArr(data, id, type) {
|
||||
return ret
|
||||
}
|
||||
|
||||
const encode = str => {
|
||||
export const encode = str => {
|
||||
return escape(toStr(str))
|
||||
.replace(/\n/g, '↵')
|
||||
.replace(/\f|\r|\t/g, '')
|
||||
}
|
||||
|
||||
// $, upperCase, lowerCase, _
|
||||
function sortObjName(a, b) {
|
||||
export function sortObjName(a, b) {
|
||||
const numA = toNum(a)
|
||||
const numB = toNum(b)
|
||||
if (!isNaN(numA) && !isNaN(numB)) {
|
||||
@@ -320,7 +319,7 @@ function transCode(code) {
|
||||
return code
|
||||
}
|
||||
|
||||
function getObjAbstract(data) {
|
||||
export function getObjAbstract(data) {
|
||||
const { type, value } = data
|
||||
if (!type) return
|
||||
|
||||
@@ -344,7 +343,7 @@ function extractFnHead(str) {
|
||||
return str
|
||||
}
|
||||
|
||||
function getFnAbstract(str) {
|
||||
export function getFnAbstract(str) {
|
||||
if (str.length > 500) str = str.slice(0, 500) + '...'
|
||||
|
||||
return 'ƒ ' + trim(extractFnHead(str).replace('function', ''))
|
||||
|
||||
267
src/lib/ObjViewer.js
Normal file
267
src/lib/ObjViewer.js
Normal file
@@ -0,0 +1,267 @@
|
||||
import {
|
||||
Emitter,
|
||||
getProto,
|
||||
isNum,
|
||||
isBool,
|
||||
lowerCase,
|
||||
isObj,
|
||||
upperFirst,
|
||||
keys,
|
||||
each,
|
||||
toSrc,
|
||||
isPromise,
|
||||
type,
|
||||
$,
|
||||
difference,
|
||||
allKeys,
|
||||
filter
|
||||
} from './util'
|
||||
import { encode, getFnAbstract, sortObjName } from './JsonViewer'
|
||||
import evalCss from './evalCss'
|
||||
|
||||
let hasEvalCss = false
|
||||
|
||||
export default class ObjViewer extends Emitter {
|
||||
constructor(
|
||||
data,
|
||||
$el,
|
||||
{ showUnenumerable = false, showGetterVal = false } = {}
|
||||
) {
|
||||
super()
|
||||
|
||||
if (!hasEvalCss) {
|
||||
evalCss(require('./json.scss'))
|
||||
hasEvalCss = true
|
||||
}
|
||||
|
||||
this._data = [data]
|
||||
this._$el = $el
|
||||
this._visitor = new Visitor()
|
||||
this._map = {}
|
||||
this._showUnenumerable = showUnenumerable
|
||||
this._showGetterVal = showGetterVal
|
||||
|
||||
this._appendTpl()
|
||||
this._bindEvent()
|
||||
}
|
||||
_objToHtml(data, firstLevel) {
|
||||
let ret = ''
|
||||
|
||||
const types = ['enumerable']
|
||||
const enumerableKeys = keys(data)
|
||||
let unenumerableKeys = []
|
||||
let symbolKeys = []
|
||||
|
||||
if (this._showUnenumerable && !firstLevel) {
|
||||
types.push('unenumerable')
|
||||
types.push('symbol')
|
||||
unenumerableKeys = difference(
|
||||
allKeys(data, {
|
||||
prototype: false,
|
||||
unenumerable: true
|
||||
}),
|
||||
enumerableKeys
|
||||
)
|
||||
symbolKeys = filter(
|
||||
allKeys(data, {
|
||||
prototype: false,
|
||||
symbol: true
|
||||
}),
|
||||
key => {
|
||||
return typeof key === 'symbol'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
each(['enumerable', 'unenumerable', 'symbol'], type => {
|
||||
let typeKeys = []
|
||||
if (type === 'symbol') {
|
||||
typeKeys = symbolKeys
|
||||
} else if (type === 'unenumerable') {
|
||||
typeKeys = unenumerableKeys
|
||||
} else {
|
||||
typeKeys = enumerableKeys
|
||||
}
|
||||
typeKeys.sort(sortObjName)
|
||||
for (let i = 0, len = typeKeys.length; i < len; i++) {
|
||||
const key = typeKeys[i]
|
||||
let val = ''
|
||||
try {
|
||||
val = data[key]
|
||||
if (isPromise(val)) {
|
||||
val.catch(() => {})
|
||||
}
|
||||
} catch (e) {
|
||||
val = e.message
|
||||
}
|
||||
ret += this._createEl(key, val, type, firstLevel)
|
||||
}
|
||||
})
|
||||
|
||||
const proto = getProto(data)
|
||||
if (!firstLevel && proto) {
|
||||
if (ret === '') {
|
||||
ret = this._objToHtml(proto)
|
||||
} else {
|
||||
ret += this._createEl('__proto__', proto, 'proto')
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
_createEl(key, val, keyType, firstLevel = false) {
|
||||
const visitor = this._visitor
|
||||
let t = typeof val
|
||||
const valType = type(val, false)
|
||||
|
||||
if (val === null) {
|
||||
return `<li>${wrapKey(key)}<span class="eruda-null">null</span></li>`
|
||||
} else if (isNum(val) || isBool(val)) {
|
||||
return `<li>${wrapKey(key)}<span class="eruda-${t}">${encode(
|
||||
val
|
||||
)}</span></li>`
|
||||
}
|
||||
|
||||
if (valType === 'RegExp') t = 'regexp'
|
||||
if (valType === 'Number') t = 'number'
|
||||
|
||||
if (valType === 'Number' || valType === 'RegExp') {
|
||||
return `<li>${wrapKey(key)}<span class="eruda-${t}">${encode(
|
||||
val.value
|
||||
)}</span></li>`
|
||||
} else if (valType === 'Undefined' || valType === 'Symbol') {
|
||||
return `<li>${wrapKey(key)}<span class="eruda-special">${lowerCase(
|
||||
valType
|
||||
)}</span></li>`
|
||||
} else if (val === '(...)') {
|
||||
return `<li>${wrapKey(key)}<span class="eruda-special">${val}</span></li>`
|
||||
} else if (isObj(val)) {
|
||||
const visitedObj = visitor.get(val)
|
||||
let id
|
||||
if (visitedObj) {
|
||||
id = visitedObj.id
|
||||
} else {
|
||||
id = visitor.set(val)
|
||||
this._map[id] = val
|
||||
}
|
||||
const objAbstract = getObjAbstract(val, valType) || upperFirst(t)
|
||||
|
||||
let obj = `<li ${
|
||||
firstLevel ? 'data-first-level="true"' : ''
|
||||
} ${'data-object-id="' + id + '"'}>
|
||||
<span class="${
|
||||
firstLevel ? '' : 'eruda-expanded eruda-collapsed'
|
||||
}"></span>
|
||||
${wrapKey(key)}
|
||||
<span class="eruda-open">${
|
||||
firstLevel ? '' : objAbstract
|
||||
}</span>
|
||||
<ul class="eruda-${t}" ${
|
||||
firstLevel ? '' : 'style="display:none"'
|
||||
}>`
|
||||
|
||||
if (firstLevel) obj += this._objToHtml(val)
|
||||
|
||||
return obj + '</ul><span class="eruda-close"></span></li>'
|
||||
}
|
||||
|
||||
function wrapKey(key) {
|
||||
if (firstLevel) return ''
|
||||
if (isObj(val) && val.jsonSplitArr) return ''
|
||||
|
||||
let keyClass = 'eruda-key'
|
||||
if (
|
||||
keyType === 'unenumerable' ||
|
||||
keyType === 'proto' ||
|
||||
keyType === 'symbol'
|
||||
) {
|
||||
keyClass = 'eruda-key-lighter'
|
||||
}
|
||||
|
||||
return `<span class="${keyClass}">${encode(key)}</span>: `
|
||||
}
|
||||
|
||||
return `<li>${wrapKey(key)}<span class="eruda-${typeof val}">"${encode(
|
||||
val
|
||||
)}"</span></li>`
|
||||
}
|
||||
_appendTpl() {
|
||||
this._$el.html(this._objToHtml(this._data, true))
|
||||
}
|
||||
_bindEvent() {
|
||||
const map = this._map
|
||||
|
||||
const self = this
|
||||
|
||||
this._$el.on('click', 'li', function(e) {
|
||||
const $this = $(this)
|
||||
const circularId = $this.data('object-id')
|
||||
const $firstSpan = $(this)
|
||||
.find('span')
|
||||
.eq(0)
|
||||
|
||||
if ($this.data('first-level')) return
|
||||
if (circularId) {
|
||||
$this.find('ul').html(self._objToHtml(map[circularId], false))
|
||||
$this.rmAttr('data-object-id')
|
||||
}
|
||||
|
||||
e.stopImmediatePropagation()
|
||||
|
||||
if (!$firstSpan.hasClass('eruda-expanded')) return
|
||||
|
||||
const $ul = $this.find('ul').eq(0)
|
||||
if ($firstSpan.hasClass('eruda-collapsed')) {
|
||||
$firstSpan.rmClass('eruda-collapsed')
|
||||
$ul.show()
|
||||
} else {
|
||||
$firstSpan.addClass('eruda-collapsed')
|
||||
$ul.hide()
|
||||
}
|
||||
|
||||
self.emit('change')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getObjAbstract(data, type) {
|
||||
if (!type) return
|
||||
|
||||
if (type === 'Function') {
|
||||
return getFnAbstract(toSrc(data))
|
||||
}
|
||||
if (type === 'Array') {
|
||||
return `Array(${data.length})`
|
||||
}
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
class Visitor {
|
||||
constructor() {
|
||||
this.id = 0
|
||||
this.visited = []
|
||||
}
|
||||
set(val) {
|
||||
const { visited, id } = this
|
||||
const obj = {
|
||||
id,
|
||||
val
|
||||
}
|
||||
visited.push(obj)
|
||||
|
||||
this.id++
|
||||
|
||||
return id
|
||||
}
|
||||
get(val) {
|
||||
const { visited } = this
|
||||
|
||||
for (let i = 0, len = visited.length; i < len; i++) {
|
||||
const obj = visited[i]
|
||||
if (val === obj.val) return obj
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
each,
|
||||
getObjType,
|
||||
endWith,
|
||||
isEmpty,
|
||||
evalCss
|
||||
isEmpty
|
||||
} from './util'
|
||||
import evalCss from './evalCss'
|
||||
|
||||
let hasEvalCss = false
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export default {
|
||||
contrast: '#f2f7fd',
|
||||
varColor: '#c80000',
|
||||
stringColor: '#1a1aa6',
|
||||
keywordColor: '#0d22aa',
|
||||
keywordColor: '#881280',
|
||||
numberColor: '#1c00cf',
|
||||
operatorColor: '#808080',
|
||||
linkColor: '#1155cc',
|
||||
|
||||
Reference in New Issue
Block a user