背景
用 Monaco Editor 实现差异对比工具,理由是他是微软开源的,功能强大,并且他是为 VS Code
提供动力的代码编辑器。
代码封装
注意点
worker
需要引入,否则会有个toUrl
的警告- 高度需要默认指定,所以最好设置
alwaysConsumeMouseWheel: true
,否则浏览器默认滚动行为会被禁止,比如说,你在这个编辑器滚动底部时,不会触发父容器滚动 - 页面卸载时,需要
释放编辑器实例
- watch监听到数据变化时,需要重新setModel,并且还需要
释放实例
<script setup lang="ts">
import * as monaco from 'monaco-editor'
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
// 设置 Monaco worker
self.MonacoEnvironment = {
getWorker(_: string, label: string) {
switch (label) {
case 'json':
return new jsonWorker()
case 'css':
case 'scss':
case 'less':
return new cssWorker()
case 'html':
case 'handlebars':
case 'razor':
return new htmlWorker()
case 'typescript':
case 'javascript':
return new tsWorker()
default:
return new editorWorker()
}
},
}
interface Props {
oldData: unknown
newData: unknown
language?: string
height?: string
theme?: string
}
const props = withDefaults(defineProps<Props>(), {
language: 'json',
height: '300px',
theme: 'vs', // vs vs-dark hc-black
})
interface EditorOptions extends monaco.editor.IDiffEditorConstructionOptions {
theme: string
}
const editorOptions: EditorOptions = {
theme: props.theme,
automaticLayout: true, // 自动布局
readOnly: true, // 只读模式
renderSideBySide: true, // false: 内联对比
scrollBeyondLastLine: false, // 不允许滚动到最后一行之后
hideUnchangedRegions: {
enabled: true, // 折叠
revealLineCount: 40,
minimumLineCount: 1,
contextLineCount: 1,
},
scrollbar: {
alwaysConsumeMouseWheel: false, // 允许鼠标滚轮滚动
},
}
const diffEditorContainer = ref<HTMLElement | null>(null)
let diffEditorInstance: monaco.editor.IStandaloneDiffEditor | null = null
const oldDataStr = computed(() => simpleStringify(props.oldData))
const newDataStr = computed(() => simpleStringify(props.newData))
function createModels(original: string, modified: string, language: string) {
const originalModel = monaco.editor.createModel(original, language)
const modifiedModel = monaco.editor.createModel(modified, language)
return { originalModel, modifiedModel }
}
function simpleStringify(value: unknown): string {
if (value === null || value === undefined) return ''
const valueType = typeof value
if (['number', 'boolean', 'string'].includes(valueType)) return String(value)
// JSON.stringify 会跳过函数和 symbol;若存在循环引用则会抛错。
// 这里不考虑循环引用情况,因为json配置不会有这种情况
try {
return JSON.stringify(value, null, 2)
} catch {
return typeof value === 'object' ? '[Object parse error]' : String(value)
}
}
watch([oldDataStr, newDataStr], ([newOriginal, newModified]) => {
if (!diffEditorInstance) return
const currentModel = diffEditorInstance.getModel()
if (
currentModel?.original.getValue() === newOriginal &&
currentModel.modified.getValue() === newModified
) {
return
}
currentModel?.original?.dispose()
currentModel?.modified?.dispose()
const { originalModel, modifiedModel } = createModels(newOriginal, newModified, props.language)
diffEditorInstance.setModel({ original: originalModel, modified: modifiedModel })
})
onMounted(() => {
if (!diffEditorContainer.value) return
diffEditorInstance = monaco.editor.createDiffEditor(diffEditorContainer.value, editorOptions)
const { originalModel, modifiedModel } = createModels(
oldDataStr.value,
newDataStr.value,
props.language
)
diffEditorInstance.setModel({ original: originalModel, modified: modifiedModel })
})
onBeforeUnmount(() => {
if (diffEditorInstance) {
const model = diffEditorInstance.getModel()
model?.original?.dispose()
model?.modified?.dispose()
diffEditorInstance.dispose()
diffEditorInstance = null
}
})
</script>
<template>
<div ref="diffEditorContainer" class="monaco-diff-editor" :style="{ height: height }" />
</template>
<style lang="scss" scoped>
.monaco-diff-editor {
width: 100%;
}
</style>
具体使用
<MonacoDiff old-data="111" new-Data="222" />