Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: calculate label of graph node #1431

Merged
merged 2 commits into from Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
103 changes: 87 additions & 16 deletions packages/ui/client/composables/module-graph.ts
Expand Up @@ -9,23 +9,94 @@ export type ModuleGraph = Graph<ModuleType, ModuleNode, ModuleLink>
export type ModuleGraphController = GraphController<ModuleType, ModuleNode, ModuleLink>
export type ModuleGraphConfig = GraphConfig<ModuleType, ModuleNode, ModuleLink>

function defineExternalModuleNode(module: string): ModuleNode {
let label = module
if (label.includes('/node_modules/'))
label = label.split(/\/node_modules\//g).pop()!.split(/\//g).shift()!
else
label = label.split(/\//g).pop()!
export interface ModuleLabelItem {
id: string
raw: string
splits: string[]
candidate: string
finished: boolean
}

return defineNode<ModuleType, ModuleNode>({
color: 'var(--color-node-external)',
label: {
color: 'var(--color-node-external)',
fontSize: '0.875rem',
text: label,
},
isFocused: false,
export function calcExternalLabels(labels: ModuleLabelItem[]): Map<string, string> {
const result: Map<string, string> = new Map()
const splitMap: Map<string, number[]> = new Map()
const firsts: number[] = []
while (true) {
let finishedCount = 0
labels.forEach((label, i) => {
const { splits, finished } = label
// record the candidate as final label text when label is marked finished
if (finished) {
finishedCount++
const { raw, candidate } = label
result.set(raw, candidate)
return
}
if (splits.length === 0) {
label.finished = true
return
}
const head = splits[0]
if (splitMap.has(head)) {
label.candidate += label.candidate === '' ? head : `/${head}`
splitMap.get(head)?.push(i)
splits.shift()
// eslint-disable-next-line @typescript-eslint/brace-style
} else {
splitMap.set(head, [i])
// record the index of the label where the head first appears
firsts.push(i)
}
})
// update candidate of label which index appears in first array
firsts.forEach((i) => {
const label = labels[i]
const head = label.splits.shift()
label.candidate += label.candidate === '' ? head : `/${head}`
})
splitMap.forEach((value) => {
if (value.length === 1) {
const index = value[0]
labels[index].finished = true
}
})
splitMap.clear()
firsts.length = 0
if (finishedCount === labels.length)
break
}
return result
}

export function createModuleLabelItem(module: string): ModuleLabelItem {
let raw = module
if (raw.includes('/node_modules/'))
raw = module.split(/\/node_modules\//g).pop()!
const splits = raw.split(/\//g)
return {
raw,
splits,
candidate: '',
finished: false,
id: module,
type: 'external',
}
}

function defineExternalModuleNodes(modules: string[]): ModuleNode[] {
const labels: ModuleLabelItem[] = modules.map(module => createModuleLabelItem(module))
const map = calcExternalLabels(labels)
return labels.map(({ raw, id }) => {
return defineNode<ModuleType, ModuleNode>({
color: 'var(--color-node-external)',
label: {
color: 'var(--color-node-external)',
fontSize: '0.875rem',
text: map.get(raw) ?? '',
},
isFocused: false,
id,
type: 'external',
})
})
}

Expand All @@ -47,7 +118,7 @@ export function getModuleGraph(data: ModuleGraphData, rootPath: string | undefin
if (!data)
return defineGraph({})

const externalizedNodes = data.externalized.map(module => defineExternalModuleNode(module)) ?? []
const externalizedNodes = defineExternalModuleNodes(data.externalized)
const inlinedNodes = data.inlined.map(module => defineInlineModuleNode(module, module === rootPath)) ?? []
const nodes = [...externalizedNodes, ...inlinedNodes]
const nodeMap = Object.fromEntries(nodes.map(node => [node.id, node]))
Expand Down
50 changes: 50 additions & 0 deletions test/core/test/module-label.test.ts
@@ -0,0 +1,50 @@
import { expect, test } from 'vitest'
import { calcExternalLabels, createModuleLabelItem } from '../../../packages/ui/client/composables/module-graph'

const getExternalLabelsObj = (modules: string[]) => {
const labels = modules.map(module => createModuleLabelItem(module))
return Object.fromEntries(calcExternalLabels(labels))
}

test('calculate label of external module', () => {
const modules1: string[] = []
expect(getExternalLabelsObj(modules1)).toEqual({})
const modules2 = ['']
expect(getExternalLabelsObj(modules2)).toEqual({ '': '' })
const modules3 = [
'org/testA',
'org/testB',
]
expect(getExternalLabelsObj(modules3)).toEqual({
'org/testA': 'org/testA',
'org/testB': 'org/testB',
})
const modules4 = [...modules3, 'org/testC']
expect(getExternalLabelsObj(modules4)).toEqual({
'org/testA': 'org/testA',
'org/testB': 'org/testB',
'org/testC': 'org/testC',
})
const modules5 = [
'orgA',
'orgB',
]
expect(getExternalLabelsObj(modules5)).toEqual({
orgA: 'orgA',
orgB: 'orgB',
})
const modules6 = ['orgA', 'orgB', 'orgA/dist']
expect(getExternalLabelsObj(modules6)).toEqual({
'orgA': 'orgA',
'orgB': 'orgB',
'orgA/dist': 'orgA/dist',
})
const modules7 = [
'@testing-library/jest-dom/dist/index.js',
'@testing-library/react/dist/index.js',
]
expect(getExternalLabelsObj(modules7)).toEqual({
'@testing-library/jest-dom/dist/index.js': '@testing-library/jest-dom',
'@testing-library/react/dist/index.js': '@testing-library/react',
})
})