Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .github/assets/devtools-hydration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
120 changes: 42 additions & 78 deletions client/app/components/HydrationIssue.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script setup lang="ts">
import { codeToHtml } from 'shiki/bundle/web'
import type { ComponentInternalInstance, VNode } from 'vue'
import { diffLines, type ChangeObject } from 'diff'
import { transformerNotationDiff } from '@shikijs/transformers'

const props = defineProps<{
issue: { instance: ComponentInternalInstance, vnode: VNode, htmlPreHydration: string | undefined, htmlPostHydration: string | undefined }
Expand All @@ -14,63 +16,40 @@

const { highlightElement, inspectElementInEditor, clearHighlight } = useElementHighlighter()

const compact = ref(true)
const contextLines = 3
const diffHtml = ref('')

function diffSlice(a: string, b: string) {
const la = (a || '').split('\n')
const lb = (b || '').split('\n')
let start = 0
while (start < la.length && start < lb.length && la[start] === lb[start]) start++
let enda = la.length - 1
let endb = lb.length - 1
while (enda >= start && endb >= start && la[enda] === lb[endb]) {
enda--
endb--
}
// If identical, show a small head
if (start >= la.length && start >= lb.length) {
return {
a: la.slice(0, Math.min(10, la.length)).join('\n'),
b: lb.slice(0, Math.min(10, lb.length)).join('\n'),
}
}
const from = Math.max(0, start - contextLines)
const toA = Math.min(la.length, enda + 1 + contextLines)
const toB = Math.min(lb.length, endb + 1 + contextLines)
return {
a: la.slice(from, toA).join('\n'),
b: lb.slice(from, toB).join('\n'),
}
async function render(pre: string, post: string) {
const diff = diffLines(pre, post, { stripTrailingCr: true, ignoreNewlineAtEof: true })
diffHtml.value = await codeToHtml(generateDiffHtml(diff), {
theme: 'github-dark', lang: 'html', transformers: [
transformerNotationDiff(),
],
})
}

const preHtml = ref('')
const postHtml = ref('')

async function render(pre: string, post: string) {
const preOut = await codeToHtml(pre, { theme: 'github-dark', lang: 'html' })
const postOut = await codeToHtml(post, { theme: 'github-dark', lang: 'html' })
preHtml.value = preOut
postHtml.value = postOut
function generateDiffHtml(change: ChangeObject<string>[]) {
return change.map((part) => {
if (part.added) {
return `// [!code ++]\n${part.value}`
}
else if (part.removed) {
return `// [!code --]\n${part.value} `
}
else {
return part.value
}
}).join('')
}

const fullPre = computed(() => props.issue.htmlPreHydration ?? '')
const fullPost = computed(() => props.issue.htmlPostHydration ?? '')

watchEffect(async () => {
const pre = fullPre.value
const post = fullPost.value
if (compact.value) {
const { a, b } = diffSlice(pre, post)
await render(a, b)
}
else {
await render(pre, post)
}
})
watch([fullPre, fullPost], ([newPre, newPost]) => {
render(newPre, newPost)
}, { immediate: true })

function copy(text: string) {
navigator.clipboard?.writeText(text).catch(() => {})
navigator.clipboard?.writeText(text).catch(() => { })
}
</script>

Expand Down Expand Up @@ -110,17 +89,6 @@
class="text-lg"
/>
</n-button>
<n-button
size="small"
quaternary
@click="compact = !compact"
>
<Icon
name="material-symbols:compare-arrows"
class="text-lg"
/>
<span class="ml-1">{{ compact ? 'Show full' : 'Compact' }}</span>
</n-button>
<n-button
size="small"
quaternary
Expand All @@ -146,25 +114,21 @@
</div>
</div>

<div class="grid mt-3 gap-2 grid-cols-2">
<div>
<div class="text-xs text-neutral-500 mb-1">
Pre Hydration
</div>
<div
class="w-full overflow-auto"
v-html="preHtml"
/>
</div>
<div>
<div class="text-xs text-neutral-500 mb-1">
Post Hydration
</div>
<div
class="w-full overflow-auto"
v-html="postHtml"
/>
</div>
</div>
<div
class="w-full mt-3 overflow-auto rounded-lg"
v-html="diffHtml"

Check warning on line 119 in client/app/components/HydrationIssue.vue

View workflow job for this annotation

GitHub Actions / ci

'v-html' directive can lead to XSS attack
/>
</n-card>
</template>

<style lang="scss" scoped>
:deep(.diff) {
&.add {
background-color: rgba(22, 163, 74, 0.15); // green-600 at 15% opacity
}

&.remove {
background-color: rgba(220, 38, 38, 0.15); // red-600 at 15% opacity
}
}
</style>
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"dependencies": {
"@nuxt/devtools-kit": "^3.0.0",
"@nuxt/kit": "^4.1.2",
"h3": "1.15.4",
"h3": "^1.15.4",
"magic-string": "^0.30.19",
"nitropack": "^2.12.6",
"shiki": "^3.13.0",
Expand All @@ -47,6 +47,9 @@
"web-vitals": "^5.1.0"
},
"devDependencies": {
"diff": "^8.0.2",
"@shikijs/transformers": "^3.15.0",
"sass-embedded": "^1.93.3",
"@nuxt/devtools": "^3.0.0",
"@nuxt/devtools-ui-kit": "^3.0.0",
"@nuxt/eslint": "^1.9.0",
Expand Down
Loading
Loading