v-code-diff 1.13.1 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,14 +1,30 @@
1
1
  {
2
2
  "name": "v-code-diff",
3
3
  "type": "module",
4
- "version": "1.13.1",
5
- "description": "template component for vue-demi, can dev and build",
4
+ "version": "1.14.0",
5
+ "packageManager": "pnpm@11.2.2",
6
+ "description": "A code diff viewer for Vue 2.6, Vue 2.7, and Vue 3",
6
7
  "license": "MIT",
7
8
  "exports": {
8
9
  ".": {
9
10
  "types": "./types/index.d.ts",
10
11
  "import": "./dist/index.es.js",
11
- "require": "./dist/index.cjs.js"
12
+ "require": "./dist/index.cjs"
13
+ },
14
+ "./vue2": {
15
+ "types": "./types/index.d.ts",
16
+ "import": "./dist/v2/index.es.js",
17
+ "require": "./dist/v2/index.cjs"
18
+ },
19
+ "./vue2.7": {
20
+ "types": "./types/index.d.ts",
21
+ "import": "./dist/v2.7/index.es.js",
22
+ "require": "./dist/v2.7/index.cjs"
23
+ },
24
+ "./vue3": {
25
+ "types": "./types/index.d.ts",
26
+ "import": "./dist/v3/index.es.js",
27
+ "require": "./dist/v3/index.cjs"
12
28
  }
13
29
  },
14
30
  "main": "dist/index.cjs",
@@ -21,6 +37,7 @@
21
37
  "types"
22
38
  ],
23
39
  "scripts": {
40
+ "benchmark": "pnpm build:3 && vitest bench --run && node --expose-gc benchmarks/render.mjs",
24
41
  "build": "npm run clean && run-s build:**",
25
42
  "build:2": "vue-demi-switch 2 vue2 && pnpm --filter vue2-playground build",
26
43
  "build:2:umd": "vue-demi-switch 2 vue2 && format=umd pnpm --filter vue2-playground build",
@@ -38,7 +55,8 @@
38
55
  "lint:fix": "eslint . --ext .vue,.js,.jsx,.ts,.tsx,json --fix --ignore-path .gitignore",
39
56
  "postinstall": "node scripts/postinstall.cjs",
40
57
  "prepublishOnly": "npm run build",
41
- "release": "bumpp --commit --no-push --tag && npm publish"
58
+ "release": "bumpp --commit --no-push --tag && npm publish",
59
+ "test": "vitest run"
42
60
  },
43
61
  "peerDependencies": {
44
62
  "@vue/composition-api": "^1.4.9",
@@ -50,7 +68,7 @@
50
68
  }
51
69
  },
52
70
  "dependencies": {
53
- "diff": "^5.2.0",
71
+ "diff": "^5.2.2",
54
72
  "diff-match-patch": "^1.0.5",
55
73
  "highlight.js": "^11.10.0",
56
74
  "vue-demi": "^0.14.6"
@@ -67,22 +85,15 @@
67
85
  "npm-run-all": "^4.1.5",
68
86
  "rimraf": "^3.0.2",
69
87
  "sass": "^1.69.5",
88
+ "tsx": "^4.19.4",
70
89
  "typescript": "~4.7.4",
71
90
  "vite": "^5.4.2",
72
91
  "vite-plugin-css-injected-by-js": "^2.4.0",
92
+ "vitest": "^2.1.9",
73
93
  "vue": "^3.4.38",
74
94
  "vue-i18n": "^9.14.0",
75
95
  "vue-tsc": "^0.40.13",
76
96
  "vue2": "npm:vue@2",
77
97
  "vue3": "npm:vue@3"
78
- },
79
- "pnpm": {
80
- "packageExtensions": {
81
- "vue-template-compiler": {
82
- "peerDependencies": {
83
- "vue": "~2.6.14"
84
- }
85
- }
86
- }
87
98
  }
88
99
  }
@@ -0,0 +1,40 @@
1
+ import * as fs from 'node:fs'
2
+ import * as path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { createSplitDiff } from '../src/utils'
5
+
6
+ // 获取当前文件的 URL 和目录路径
7
+ const __filename = fileURLToPath(import.meta.url)
8
+ const __dirname = path.dirname(__filename)
9
+
10
+ // 构建文件的绝对路径
11
+ const oldTextPath = path.join(__dirname, '../demo/text/old-long-text.txt')
12
+ const newTextPath = path.join(__dirname, '../demo/text/new-long-text.txt')
13
+
14
+ // 同步读取文件内容
15
+ const oldLongText = fs.readFileSync(oldTextPath, 'utf-8')
16
+ const newLongText = fs.readFileSync(newTextPath, 'utf-8')
17
+
18
+ console.log('开始执行性能测试...') // 更新日志消息以匹配函数名
19
+
20
+ const label = 'createSplitDiff execution time' // 更新标签以匹配函数名
21
+
22
+ // 开始计时
23
+ console.time(label)
24
+
25
+ // 执行函数
26
+ try {
27
+ const _result = createSplitDiff(oldLongText, newLongText) // 将 result 重命名为 _result 以消除未使用变量的警告
28
+ console.log(_result.changes.length)
29
+ }
30
+ catch (error) {
31
+ console.error('执行 createSplitDiff 时出错:', error) // 更新错误消息
32
+ }
33
+
34
+ // 结束计时并打印时间
35
+ console.timeEnd(label)
36
+
37
+ console.log('性能测试完成.')
38
+
39
+ // 你可能还需要一个空的 export {} 来让 TypeScript 将其视为一个模块(如果需要)
40
+ // export {};
@@ -1,21 +1,18 @@
1
- const fs = require('fs')
2
- const { switchVersion, loadModule } = require('./utils.cjs')
1
+ const fs = require('node:fs')
2
+ const { switchVersion } = require('./utils.cjs')
3
3
 
4
- const Vue = loadModule('vue')
5
-
6
- if (fs.existsSync('.local'))
4
+ if (fs.existsSync('.local')) {
7
5
  console.log('Currently, it is the local development environment, not doing anything.')
6
+ }
7
+ else {
8
+ const { version } = require('vue')
8
9
 
9
- else if (!Vue || typeof Vue.version !== 'string')
10
- console.warn('[v-code-diff] Vue is not found. Please run "npm install vue" to install.')
11
-
12
- else if (Vue.version.startsWith('2.7.'))
13
- switchVersion('2.7')
14
-
15
- else if (Vue.version.startsWith('2.'))
16
- switchVersion('2')
17
-
18
- else if (Vue.version.startsWith('3.'))
19
- switchVersion('3')
20
-
21
- else console.warn(`[v-code-diff] Vue version v${Vue.version} is not suppported.`)
10
+ if (version.startsWith('2.7.'))
11
+ switchVersion('2.7')
12
+ else if (version.startsWith('2.'))
13
+ switchVersion('2')
14
+ else if (version.startsWith('3.'))
15
+ switchVersion('3')
16
+ else
17
+ throw new Error(`[v-code-diff] Vue version v${version} is not supported.`)
18
+ }
package/scripts/utils.cjs CHANGED
@@ -1,36 +1,21 @@
1
- const fs = require('fs')
2
- const path = require('path')
1
+ const fs = require('node:fs')
2
+ const path = require('node:path')
3
3
 
4
4
  const dir = path.resolve(__dirname, '..', 'dist')
5
5
 
6
- function loadModule(name) {
7
- try {
8
- return require(name)
9
- }
10
- catch (e) {
11
- return undefined
12
- }
13
- }
14
-
15
6
  function copy(name, version, vue) {
16
7
  vue = vue || 'vue'
17
8
  const src = path.join(dir, `v${version}`, name)
18
9
  const dest = path.join(dir, name)
19
10
  let content = fs.readFileSync(src, 'utf-8')
20
11
  content = content.replace(/'vue'/g, `'${vue}'`)
21
- // unlink for pnpm, #92
22
- try {
23
- fs.unlinkSync(dest)
24
- }
25
- catch (error) {
26
- }
12
+ fs.rmSync(dest, { force: true })
27
13
  fs.writeFileSync(dest, content, 'utf-8')
28
14
  }
29
15
 
30
16
  function switchVersion(version, vue) {
31
17
  copy('index.es.js', version, vue)
32
- copy('index.cjs.js', version, vue)
18
+ copy('index.cjs', version, vue)
33
19
  }
34
20
 
35
- module.exports.loadModule = loadModule
36
21
  module.exports.switchVersion = switchVersion
package/src/CodeDiff.vue CHANGED
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref, watch } from 'vue-demi'
2
+ import { computed, ref, shallowRef, watch } from 'vue-demi'
3
3
  import { createSplitDiff, createUnifiedDiff } from './utils'
4
4
  import UnifiedViewer from './unified/UnifiedViewer.vue'
5
5
  import SplitViewer from './split/SplitViewer.vue'
@@ -77,13 +77,14 @@ const raw = computed(() =>
77
77
  ? createUnifiedDiff(oldString.value, newString.value, props.language, props.diffStyle, props.forceInlineComparison, props.context, props.ignoreMatchingLines)
78
78
  : createSplitDiff(oldString.value, newString.value, props.language, props.diffStyle, props.forceInlineComparison, props.context, props.ignoreMatchingLines),
79
79
  )
80
- const diffChange = ref(raw.value)
80
+ const diffChange = shallowRef(raw.value)
81
81
  const isNotChanged = computed(() => diffChange.value.stat.additionsNum === 0 && diffChange.value.stat.deletionsNum === 0)
82
82
 
83
83
  const currentDiffIndex = ref(-1)
84
+ const root = ref<HTMLElement>()
84
85
 
85
86
  function goToNextDiff() {
86
- const diffs = document.querySelectorAll('.blob-code-addition')
87
+ const diffs = root.value!.querySelectorAll('[data-diff-change]')
87
88
  if (currentDiffIndex.value < diffs.length - 1) {
88
89
  currentDiffIndex.value++
89
90
  updateCurrentDiffHighlight(diffs)
@@ -91,7 +92,7 @@ function goToNextDiff() {
91
92
  }
92
93
 
93
94
  function goToPrevDiff() {
94
- const diffs = document.querySelectorAll('.blob-code-addition')
95
+ const diffs = root.value!.querySelectorAll('[data-diff-change]')
95
96
  if (currentDiffIndex.value > 0) {
96
97
  currentDiffIndex.value--
97
98
  updateCurrentDiffHighlight(diffs)
@@ -99,17 +100,18 @@ function goToPrevDiff() {
99
100
  }
100
101
 
101
102
  function updateCurrentDiffHighlight(diffs: NodeListOf<Element>) {
102
- diffs.forEach((diff: { classList: { remove: (arg0: string) => any } }) => diff.classList.remove('current-diff'))
103
+ root.value!.querySelectorAll('.current-diff').forEach(diff => diff.classList.remove('current-diff'))
103
104
 
104
105
  const currentDiff = diffs[currentDiffIndex.value]
105
106
 
106
107
  if (currentDiff) {
107
- currentDiff.classList.add('current-diff')
108
+ currentDiff.querySelectorAll('.blob-code').forEach(diff => diff.classList.add('current-diff'))
108
109
  currentDiff.scrollIntoView({ behavior: 'smooth', block: 'center' })
109
110
  }
110
111
  }
111
112
 
112
113
  watch(() => props, () => {
114
+ currentDiffIndex.value = -1
113
115
  diffChange.value = raw.value
114
116
  emits('diff', {
115
117
  stat: {
@@ -122,7 +124,7 @@ watch(() => props, () => {
122
124
  </script>
123
125
 
124
126
  <template>
125
- <div class="code-diff-view" :theme="theme" :style="{ maxHeight }">
127
+ <div ref="root" class="code-diff-view" :theme="theme" :style="{ maxHeight }">
126
128
  <div v-if="!hideHeader" class="file-header">
127
129
  <!-- line by line -->
128
130
  <div v-if="isUnifiedViewer" class="file-info">
@@ -167,8 +169,8 @@ watch(() => props, () => {
167
169
  </span>
168
170
  </div>
169
171
  </div>
170
- <UnifiedViewer v-if="isUnifiedViewer" :diff-change="diffChange" />
171
- <SplitViewer v-else :diff-change="diffChange" />
172
+ <UnifiedViewer v-if="isUnifiedViewer" :diff-change="diffChange" :language="language" />
173
+ <SplitViewer v-else :diff-change="diffChange" :language="language" />
172
174
  </div>
173
175
  </template>
174
176
 
@@ -16,9 +16,10 @@ function getCodeMarker(type: DiffType) {
16
16
  return ''
17
17
  }
18
18
 
19
- function onSplitLineMousedown(side: 'left' | 'right') {
20
- const leftElements = document.querySelectorAll('.file-diff-split .split-side-left')!
21
- const rightElements = document.querySelectorAll('.file-diff-split .split-side-right')!
19
+ function onSplitLineMousedown(event: MouseEvent, side: 'left' | 'right') {
20
+ const table = (event.currentTarget as HTMLElement).closest('.file-diff-split')!
21
+ const leftElements = table.querySelectorAll('.split-side-left')
22
+ const rightElements = table.querySelectorAll('.split-side-right')
22
23
 
23
24
  for (const el of rightElements)
24
25
  el.classList.toggle('no-select', side === 'left')
@@ -37,7 +38,10 @@ function onSplitLineMousedown(side: 'left' | 'right') {
37
38
 
38
39
  </td>
39
40
  </tr>
40
- <tr v-else-if="!splitLine.hide">
41
+ <tr
42
+ v-else-if="!splitLine.hide"
43
+ :data-diff-change="splitLine.left.type === DiffType.DELETE || splitLine.right.type === DiffType.ADD ? '' : undefined"
44
+ >
41
45
  <template v-for="(line, index) in [splitLine.left, splitLine.right]">
42
46
  <!-- eslint-disable -->
43
47
  <template v-if="line.type === DiffType.EMPTY">
@@ -66,7 +70,7 @@ function onSplitLineMousedown(side: 'left' | 'right') {
66
70
  'split-side-left': index === 0,
67
71
  'split-side-right': index === 1,
68
72
  }"
69
- @mousedown="onSplitLineMousedown(index === 0 ? 'left' : 'right')"
73
+ @mousedown="onSplitLineMousedown($event, index === 0 ? 'left' : 'right')"
70
74
  >
71
75
  <span
72
76
  class="blob-code-inner blob-code-marker" :data-code-marker="getCodeMarker(line.type)"
@@ -1,18 +1,43 @@
1
1
  <script setup lang="ts">
2
+ import { computed, ref, shallowRef, watch } from 'vue-demi'
2
3
  import type { SplitLineChange, SplitViewerChange } from '../types'
4
+ import { RENDER_BATCH_SIZE, highlightSplitLine } from '../utils'
3
5
  import SplitLine from './SplitLine.vue'
4
6
 
5
7
  const props = defineProps<{
6
8
  diffChange: SplitViewerChange
9
+ language: string
7
10
  }>()
8
11
 
12
+ const renderLimit = ref(RENDER_BATCH_SIZE)
13
+ const visibleChanges = shallowRef(props.diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined))
14
+ const renderedChanges = computed(() => visibleChanges.value.slice(0, renderLimit.value))
15
+ const remainingLines = computed(() => visibleChanges.value.length - renderedChanges.value.length)
16
+ const nextBatchSize = computed(() => Math.min(remainingLines.value, RENDER_BATCH_SIZE))
17
+
18
+ watch(() => props.diffChange, (diffChange) => {
19
+ renderLimit.value = RENDER_BATCH_SIZE
20
+ visibleChanges.value = diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined)
21
+ })
22
+
23
+ function highlightRenderedChanges() {
24
+ renderedChanges.value.forEach(line => highlightSplitLine(line, props.language))
25
+ }
26
+
9
27
  function expandHandler({ hideIndex }: SplitLineChange) {
10
28
  if (hideIndex === undefined)
11
29
  return
12
- props.diffChange.collector[hideIndex!].lines.forEach((line) => {
30
+ props.diffChange.collector[hideIndex].lines.forEach((line) => {
13
31
  line.hide = false
14
32
  line.fold = false
15
33
  })
34
+ visibleChanges.value = props.diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined)
35
+ highlightRenderedChanges()
36
+ }
37
+
38
+ function loadMore() {
39
+ renderLimit.value += RENDER_BATCH_SIZE
40
+ highlightRenderedChanges()
16
41
  }
17
42
  </script>
18
43
 
@@ -25,7 +50,14 @@ function expandHandler({ hideIndex }: SplitLineChange) {
25
50
  <col>
26
51
  </colgroup>
27
52
  <tbody>
28
- <SplitLine v-for="(item, index) in diffChange?.changes" :key="index" :split-line="item" @expand="expandHandler" />
53
+ <SplitLine v-for="(item, index) in renderedChanges" :key="index" :split-line="item" @expand="expandHandler" />
54
+ <tr v-if="remainingLines">
55
+ <td class="blob-code blob-code-hunk load-more" colspan="4">
56
+ <button class="load-more-button" type="button" @click="loadMore">
57
+ Show next {{ nextBatchSize }} lines ({{ remainingLines }} remaining)
58
+ </button>
59
+ </td>
60
+ </tr>
29
61
  </tbody>
30
62
  </table>
31
63
  </template>
package/src/style.scss CHANGED
@@ -168,6 +168,24 @@
168
168
  .blob-code-hunk {
169
169
  background-color: var(--color-accent-subtle);
170
170
  }
171
+
172
+ .load-more {
173
+ padding: 8px;
174
+ text-align: center;
175
+ }
176
+
177
+ .load-more-button {
178
+ padding: 4px 12px;
179
+ color: var(--color-accent-fg);
180
+ background: transparent;
181
+ border: 1px solid var(--color-border-default);
182
+ border-radius: 4px;
183
+ cursor: pointer;
184
+ }
185
+
186
+ .load-more-button:hover {
187
+ background-color: var(--color-accent-muted);
188
+ }
171
189
  }
172
190
 
173
191
  .file-diff-split {
package/src/types.ts CHANGED
@@ -23,6 +23,7 @@ export interface DiffLine {
23
23
 
24
24
  export interface SplitLineChange {
25
25
  fold?: boolean
26
+ highlighted?: boolean
26
27
  left: DiffLine
27
28
  right: DiffLine
28
29
  hide?: boolean
@@ -31,6 +32,7 @@ export interface SplitLineChange {
31
32
 
32
33
  export interface UnifiedLineChange {
33
34
  fold?: boolean
35
+ highlighted?: boolean
34
36
  type: DiffType
35
37
  code: string
36
38
  delNum?: number
@@ -25,7 +25,7 @@ function getCodeMarker(type: DiffType) {
25
25
 
26
26
  </td>
27
27
  </tr>
28
- <tr v-else-if="!line.hide">
28
+ <tr v-else-if="!line.hide" :data-diff-change="line.type !== DiffType.EQUAL ? '' : undefined">
29
29
  <td
30
30
  class="blob-num"
31
31
  :class="{
@@ -1,25 +1,57 @@
1
1
  <script setup lang="ts">
2
+ import { computed, ref, shallowRef, watch } from 'vue-demi'
2
3
  import type { UnifiedLineChange, UnifiedViewerChange } from '../types'
4
+ import { RENDER_BATCH_SIZE, highlightUnifiedLine } from '../utils'
3
5
  import UnifiedLine from './UnifiedLine.vue'
4
6
 
5
7
  const props = defineProps<{
6
8
  diffChange: UnifiedViewerChange
9
+ language: string
7
10
  }>()
8
11
 
12
+ const renderLimit = ref(RENDER_BATCH_SIZE)
13
+ const visibleChanges = shallowRef(props.diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined))
14
+ const renderedChanges = computed(() => visibleChanges.value.slice(0, renderLimit.value))
15
+ const remainingLines = computed(() => visibleChanges.value.length - renderedChanges.value.length)
16
+ const nextBatchSize = computed(() => Math.min(remainingLines.value, RENDER_BATCH_SIZE))
17
+
18
+ watch(() => props.diffChange, (diffChange) => {
19
+ renderLimit.value = RENDER_BATCH_SIZE
20
+ visibleChanges.value = diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined)
21
+ })
22
+
23
+ function highlightRenderedChanges() {
24
+ renderedChanges.value.forEach(line => highlightUnifiedLine(line, props.language))
25
+ }
26
+
9
27
  function expandHandler({ hideIndex }: UnifiedLineChange) {
10
28
  if (hideIndex === undefined)
11
29
  return
12
- props.diffChange.collector[hideIndex!].lines.forEach((line) => {
30
+ props.diffChange.collector[hideIndex].lines.forEach((line) => {
13
31
  line.hide = false
14
32
  line.fold = false
15
33
  })
34
+ visibleChanges.value = props.diffChange.changes.filter(line => !line.hide || line.hideIndex !== undefined)
35
+ highlightRenderedChanges()
36
+ }
37
+
38
+ function loadMore() {
39
+ renderLimit.value += RENDER_BATCH_SIZE
40
+ highlightRenderedChanges()
16
41
  }
17
42
  </script>
18
43
 
19
44
  <template>
20
45
  <table class="diff-table">
21
46
  <tbody>
22
- <UnifiedLine v-for="(item, index) in diffChange?.changes" :key="index" :line="item" @expand="expandHandler" />
47
+ <UnifiedLine v-for="(item, index) in renderedChanges" :key="index" :line="item" @expand="expandHandler" />
48
+ <tr v-if="remainingLines">
49
+ <td class="blob-code blob-code-hunk load-more" colspan="3">
50
+ <button class="load-more-button" type="button" @click="loadMore">
51
+ Show next {{ nextBatchSize }} lines ({{ remainingLines }} remaining)
52
+ </button>
53
+ </td>
54
+ </tr>
23
55
  </tbody>
24
56
  </table>
25
57
  </template>