Skip to content

Commit 3f4489c

Browse files
authored
feat(hydration): improve diff UI (#131)
1 parent 440cb6a commit 3f4489c

File tree

4 files changed

+441
-198
lines changed

4 files changed

+441
-198
lines changed
-4.46 KB
Loading
Lines changed: 42 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script setup lang="ts">
22
import { codeToHtml } from 'shiki/bundle/web'
33
import type { ComponentInternalInstance, VNode } from 'vue'
4+
import { diffLines, type ChangeObject } from 'diff'
5+
import { transformerNotationDiff } from '@shikijs/transformers'
46
57
const props = defineProps<{
68
issue: { instance: ComponentInternalInstance, vnode: VNode, htmlPreHydration: string | undefined, htmlPostHydration: string | undefined }
@@ -14,63 +16,40 @@ const element = computed(() => props.issue.instance.vnode.el as HTMLElement | un
1416
1517
const { highlightElement, inspectElementInEditor, clearHighlight } = useElementHighlighter()
1618
17-
const compact = ref(true)
18-
const contextLines = 3
19+
const diffHtml = ref('')
1920
20-
function diffSlice(a: string, b: string) {
21-
const la = (a || '').split('\n')
22-
const lb = (b || '').split('\n')
23-
let start = 0
24-
while (start < la.length && start < lb.length && la[start] === lb[start]) start++
25-
let enda = la.length - 1
26-
let endb = lb.length - 1
27-
while (enda >= start && endb >= start && la[enda] === lb[endb]) {
28-
enda--
29-
endb--
30-
}
31-
// If identical, show a small head
32-
if (start >= la.length && start >= lb.length) {
33-
return {
34-
a: la.slice(0, Math.min(10, la.length)).join('\n'),
35-
b: lb.slice(0, Math.min(10, lb.length)).join('\n'),
36-
}
37-
}
38-
const from = Math.max(0, start - contextLines)
39-
const toA = Math.min(la.length, enda + 1 + contextLines)
40-
const toB = Math.min(lb.length, endb + 1 + contextLines)
41-
return {
42-
a: la.slice(from, toA).join('\n'),
43-
b: lb.slice(from, toB).join('\n'),
44-
}
21+
async function render(pre: string, post: string) {
22+
const diff = diffLines(pre, post, { stripTrailingCr: true, ignoreNewlineAtEof: true })
23+
diffHtml.value = await codeToHtml(generateDiffHtml(diff), {
24+
theme: 'github-dark', lang: 'html', transformers: [
25+
transformerNotationDiff(),
26+
],
27+
})
4528
}
4629
47-
const preHtml = ref('')
48-
const postHtml = ref('')
49-
50-
async function render(pre: string, post: string) {
51-
const preOut = await codeToHtml(pre, { theme: 'github-dark', lang: 'html' })
52-
const postOut = await codeToHtml(post, { theme: 'github-dark', lang: 'html' })
53-
preHtml.value = preOut
54-
postHtml.value = postOut
30+
function generateDiffHtml(change: ChangeObject<string>[]) {
31+
return change.map((part) => {
32+
if (part.added) {
33+
return `// [!code ++]\n${part.value}`
34+
}
35+
else if (part.removed) {
36+
return `// [!code --]\n${part.value} `
37+
}
38+
else {
39+
return part.value
40+
}
41+
}).join('')
5542
}
5643
5744
const fullPre = computed(() => props.issue.htmlPreHydration ?? '')
5845
const fullPost = computed(() => props.issue.htmlPostHydration ?? '')
5946
60-
watchEffect(async () => {
61-
const pre = fullPre.value
62-
const post = fullPost.value
63-
if (compact.value) {
64-
const { a, b } = diffSlice(pre, post)
65-
await render(a, b)
66-
}
67-
else {
68-
await render(pre, post)
69-
}
70-
})
47+
watch([fullPre, fullPost], ([newPre, newPost]) => {
48+
render(newPre, newPost)
49+
}, { immediate: true })
7150
7251
function copy(text: string) {
73-
navigator.clipboard?.writeText(text).catch(() => {})
52+
navigator.clipboard?.writeText(text).catch(() => { })
7453
}
7554
</script>
7655

@@ -110,17 +89,6 @@ function copy(text: string) {
11089
class="text-lg"
11190
/>
11291
</n-button>
113-
<n-button
114-
size="small"
115-
quaternary
116-
@click="compact = !compact"
117-
>
118-
<Icon
119-
name="material-symbols:compare-arrows"
120-
class="text-lg"
121-
/>
122-
<span class="ml-1">{{ compact ? 'Show full' : 'Compact' }}</span>
123-
</n-button>
12492
<n-button
12593
size="small"
12694
quaternary
@@ -146,25 +114,21 @@ function copy(text: string) {
146114
</div>
147115
</div>
148116

149-
<div class="grid mt-3 gap-2 grid-cols-2">
150-
<div>
151-
<div class="text-xs text-neutral-500 mb-1">
152-
Pre Hydration
153-
</div>
154-
<div
155-
class="w-full overflow-auto"
156-
v-html="preHtml"
157-
/>
158-
</div>
159-
<div>
160-
<div class="text-xs text-neutral-500 mb-1">
161-
Post Hydration
162-
</div>
163-
<div
164-
class="w-full overflow-auto"
165-
v-html="postHtml"
166-
/>
167-
</div>
168-
</div>
117+
<div
118+
class="w-full mt-3 overflow-auto rounded-lg"
119+
v-html="diffHtml"
120+
/>
169121
</n-card>
170122
</template>
123+
124+
<style lang="scss" scoped>
125+
:deep(.diff) {
126+
&.add {
127+
background-color: rgba(22, 163, 74, 0.15); // green-600 at 15% opacity
128+
}
129+
130+
&.remove {
131+
background-color: rgba(220, 38, 38, 0.15); // red-600 at 15% opacity
132+
}
133+
}
134+
</style>

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"dependencies": {
3838
"@nuxt/devtools-kit": "^3.0.0",
3939
"@nuxt/kit": "^4.1.2",
40-
"h3": "1.15.4",
40+
"h3": "^1.15.4",
4141
"magic-string": "^0.30.19",
4242
"nitropack": "^2.12.6",
4343
"shiki": "^3.13.0",
@@ -47,6 +47,9 @@
4747
"web-vitals": "^5.1.0"
4848
},
4949
"devDependencies": {
50+
"diff": "^8.0.2",
51+
"@shikijs/transformers": "^3.15.0",
52+
"sass-embedded": "^1.93.3",
5053
"@nuxt/devtools": "^3.0.0",
5154
"@nuxt/devtools-ui-kit": "^3.0.0",
5255
"@nuxt/eslint": "^1.9.0",

0 commit comments

Comments
 (0)