Skip to content

Commit 132a98f

Browse files
committed
feat: cache path parsing function
1 parent b798716 commit 132a98f

File tree

6 files changed

+375
-9
lines changed

6 files changed

+375
-9
lines changed

packages/devtools/src/app/pages/session/[session].vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ModuleListItem, SessionContext } from '../../types/data'
33
import { useRoute } from '#app/composables/router'
44
import { computed, onMounted, reactive, shallowRef } from 'vue'
55
import { backend } from '../../state/backend'
6+
import { getFileTypeFromName } from '../../utils/icon'
67
78
const params = useRoute().params as {
89
session: string
@@ -14,9 +15,13 @@ const session = reactive({
1415
}) as SessionContext
1516
1617
onMounted(async () => {
17-
session.modulesList = await backend.value!.functions['vite:rolldown:get-module-list']!({
18+
const modules = await backend.value!.functions['vite:rolldown:get-module-list']!({
1819
session: params.session,
1920
})
21+
session.modulesList = modules.map(mod => ({
22+
id: mod.id,
23+
fileType: getFileTypeFromName(mod.id).name,
24+
}))
2025
})
2126
</script>
2227

packages/devtools/src/app/types/data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export interface ModuleListItem {
22
id: string
3+
fileType: string
34
}
45

56
export interface SessionContext {
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { FixedTupleMap, MaybeWeakMap, TupleMap } from './cache'
3+
4+
describe('maybeWeakMap', () => {
5+
it('should work', () => {
6+
const map = new MaybeWeakMap<any, any>()
7+
map.set('a', 'b')
8+
expect(map.get('a')).toBe('b')
9+
expect(map.get(1)).toBe(undefined)
10+
})
11+
12+
it('should work with weak keys', () => {
13+
const map = new MaybeWeakMap<any, any>()
14+
const symbol = Symbol('a')
15+
map.set(symbol, 'b')
16+
expect(map.get(symbol)).toBe('b')
17+
})
18+
19+
it('should work with object keys', () => {
20+
const map = new MaybeWeakMap<any, any>()
21+
const obj = {}
22+
map.set(obj, 'b')
23+
expect(map.get(obj)).toBe('b')
24+
})
25+
})
26+
27+
describe('tupleMap', () => {
28+
it('should work', () => {
29+
const map = new TupleMap<any, any>()
30+
map.set(['a', 'b'], 'c')
31+
expect(map.get(['a', 'b'])).toBe('c')
32+
expect(map.get(['a'])).toBe(undefined)
33+
expect(map.get(['a', 'c'])).toBe(undefined)
34+
expect(map.get(['a', 'b', 'c'])).toBe(undefined)
35+
})
36+
37+
it('should work with weak keys', () => {
38+
const map = new TupleMap<any, any>()
39+
const symbol = Symbol('a')
40+
map.set([symbol, 'b'], 'c')
41+
expect(map.get([symbol, 'b'])).toBe('c')
42+
expect(map.get([symbol])).toBe(undefined)
43+
expect(map.get([symbol, 'c'])).toBe(undefined)
44+
expect(map.get([symbol, 'b', 'c'])).toBe(undefined)
45+
map.set([symbol, 'b'], 'override')
46+
expect(map.get([symbol, 'b'])).toBe('override')
47+
map.delete([symbol, 'b'])
48+
expect(map.get([symbol, 'b'])).toBe(undefined)
49+
})
50+
})
51+
52+
describe('fixedTupleMap', () => {
53+
it('error on wrong key length', () => {
54+
const map = new FixedTupleMap(5)
55+
expect(() => map.set([1, 2, 3], 'a'))
56+
.toThrowErrorMatchingInlineSnapshot(`[Error: Expect tuple of length 5, got 3]`)
57+
})
58+
59+
it('iterator', () => {
60+
const map = new FixedTupleMap(5)
61+
const symbol = Symbol('a')
62+
map.set([symbol, 'b', 'c', 'd', 'e'], 'f')
63+
map.set([symbol, 'g', 'h', 'i', 'j'], 'k')
64+
map.set([symbol, 'l', 'm', 'n', 'o'], 'p')
65+
map.set([symbol, 'q', 'r', 's', 't'], 'u')
66+
map.set([symbol, 'v', 'w', 'x', 'y'], 'z')
67+
map.set([symbol, 'a', 'b', 'c', 'd'], 'e')
68+
map.set([symbol, 'f', 'g', 'h', 'i'], 'j')
69+
map.set([symbol, 'k', 'l', 'm', 'n'], 'o')
70+
map.set([symbol, 'p', 'q', 'r', 's'], 't')
71+
map.set([symbol, 'u', 'v', 'w', 'x'], 'y')
72+
map.set([symbol, 'z', 'a', 'b', 'c'], 'd')
73+
74+
const keys = Array.from(map._traverseMap())
75+
expect(keys)
76+
.toMatchInlineSnapshot(`
77+
[
78+
{
79+
"keys": [
80+
Symbol(a),
81+
"b",
82+
"c",
83+
"d",
84+
"e",
85+
],
86+
"map": "f",
87+
},
88+
{
89+
"keys": [
90+
Symbol(a),
91+
"g",
92+
"h",
93+
"i",
94+
"j",
95+
],
96+
"map": "k",
97+
},
98+
{
99+
"keys": [
100+
Symbol(a),
101+
"l",
102+
"m",
103+
"n",
104+
"o",
105+
],
106+
"map": "p",
107+
},
108+
{
109+
"keys": [
110+
Symbol(a),
111+
"q",
112+
"r",
113+
"s",
114+
"t",
115+
],
116+
"map": "u",
117+
},
118+
{
119+
"keys": [
120+
Symbol(a),
121+
"v",
122+
"w",
123+
"x",
124+
"y",
125+
],
126+
"map": "z",
127+
},
128+
{
129+
"keys": [
130+
Symbol(a),
131+
"a",
132+
"b",
133+
"c",
134+
"d",
135+
],
136+
"map": "e",
137+
},
138+
{
139+
"keys": [
140+
Symbol(a),
141+
"f",
142+
"g",
143+
"h",
144+
"i",
145+
],
146+
"map": "j",
147+
},
148+
{
149+
"keys": [
150+
Symbol(a),
151+
"k",
152+
"l",
153+
"m",
154+
"n",
155+
],
156+
"map": "o",
157+
},
158+
{
159+
"keys": [
160+
Symbol(a),
161+
"p",
162+
"q",
163+
"r",
164+
"s",
165+
],
166+
"map": "t",
167+
},
168+
{
169+
"keys": [
170+
Symbol(a),
171+
"u",
172+
"v",
173+
"w",
174+
"x",
175+
],
176+
"map": "y",
177+
},
178+
{
179+
"keys": [
180+
Symbol(a),
181+
"z",
182+
"a",
183+
"b",
184+
"c",
185+
],
186+
"map": "d",
187+
},
188+
]
189+
`)
190+
})
191+
})
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/**
2+
* An extended `WeakMap` that can also hold regular keys.
3+
* (in those cases, it behaves like a regular `Map` and does not free memory)
4+
*
5+
* An additional `clear` method is added to clear the regular map.
6+
*/
7+
export class MaybeWeakMap<K, V> implements WeakMap<any, V> {
8+
private _map: Map<K, V>
9+
private _weakMap: WeakMap<any, V>
10+
11+
constructor() {
12+
this._map = new Map()
13+
this._weakMap = new WeakMap()
14+
}
15+
16+
[Symbol.toStringTag]: string = 'MaybeWeakMap'
17+
18+
delete(key: K): boolean {
19+
if (this._weakMap.has(key))
20+
return this._weakMap.delete(key)
21+
return this._map.delete(key)
22+
}
23+
24+
has(key: K): boolean {
25+
if (this._weakMap.has(key))
26+
return true
27+
return this._map.has(key)
28+
}
29+
30+
set(key: K, value: V): this {
31+
if (this._weakMap.has(key))
32+
this._weakMap.set(key, value)
33+
else
34+
this._map.set(key, value)
35+
return this
36+
}
37+
38+
get(key: K): V | undefined {
39+
if (this._weakMap.has(key))
40+
return this._weakMap.get(key)
41+
return this._map.get(key)
42+
}
43+
44+
clear(): void {
45+
this._map.clear()
46+
}
47+
}
48+
49+
/**
50+
* A `Map` that accepts an array of keys, treating it as a tuple.
51+
*/
52+
export class FixedTupleMap<K extends readonly any[], V> {
53+
private _mapTree: Map<any, any>
54+
55+
constructor(
56+
public readonly length: K['length'],
57+
) {
58+
this._mapTree = new Map()
59+
}
60+
61+
private _getLastMap(key: K): Map<any, any> {
62+
let map = this._mapTree
63+
for (const k of key.slice(0, -1)) {
64+
if (!map.has(k)) {
65+
map.set(k, new Map())
66+
}
67+
map = map.get(k) as Map<any, any>
68+
}
69+
return map
70+
}
71+
72+
set(key: K, value: V): this {
73+
if (key.length !== this.length) {
74+
throw new Error(`Expect tuple of length ${this.length}, got ${key.length}`)
75+
}
76+
const lastMap = this._getLastMap(key)
77+
lastMap.set(key[key.length - 1], value)
78+
return this
79+
}
80+
81+
get(key: K): V | undefined {
82+
const lastMap = this._getLastMap(key)
83+
return lastMap.get(key[key.length - 1])
84+
}
85+
86+
clear(): void {
87+
this._mapTree.clear()
88+
}
89+
90+
delete(key: K): boolean {
91+
const lastMap = this._getLastMap(key)
92+
return lastMap.delete(key[key.length - 1])
93+
}
94+
95+
get size(): number {
96+
return this._mapTree.size
97+
}
98+
99+
* _traverseMap() {
100+
function* traverse(map: Map<any, any>, keys: any[], depthLeft: number): Generator<{ keys: any[], map: Map<any, V> }> {
101+
if (depthLeft === 0) {
102+
yield { keys, map }
103+
}
104+
else {
105+
for (const [key, value] of map.entries()) {
106+
yield* traverse(value, [...keys, key], depthLeft - 1)
107+
}
108+
}
109+
}
110+
111+
yield* traverse(this._mapTree, [], this.length)
112+
}
113+
}
114+
115+
export class TupleMap<K extends readonly any[], V> {
116+
private _lengthMap: Map<number, FixedTupleMap<K, V>>
117+
118+
constructor() {
119+
this._lengthMap = new Map()
120+
}
121+
122+
get(key: K): V | undefined {
123+
const length = key.length
124+
const map = this._lengthMap.get(length)
125+
return map?.get(key)
126+
}
127+
128+
set(key: K, value: V): this {
129+
const length = key.length
130+
let map = this._lengthMap.get(length)
131+
if (!map) {
132+
map = new FixedTupleMap(length)
133+
this._lengthMap.set(length, map)
134+
}
135+
map.set(key, value)
136+
return this
137+
}
138+
139+
delete(key: K): boolean {
140+
const length = key.length
141+
const map = this._lengthMap.get(length)
142+
return map?.delete(key) ?? false
143+
}
144+
145+
clear(): void {
146+
this._lengthMap.clear()
147+
}
148+
}
149+
150+
export function makeCachedFunction<T extends (...args: any[]) => any>(fn: T): T & { cache: TupleMap<Parameters<T>, ReturnType<T>> } {
151+
const cache = new TupleMap<Parameters<T>, ReturnType<T>>()
152+
const wrapper = function (this: ThisType<T>, ...args: Parameters<T>) {
153+
let result = cache.get(args)
154+
if (result)
155+
return result
156+
result = fn(...args)
157+
cache.set(args, result!)
158+
return result
159+
}
160+
wrapper.cache = cache
161+
return wrapper as T & { cache: TupleMap<Parameters<T>, ReturnType<T>> }
162+
}

0 commit comments

Comments
 (0)