npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

monaco-editor-merge-conflict

v0.2.1

Published

VS Code-like inline merge conflict actions and preview for Monaco Editor

Readme

English | 简体中文

monaco-editor-merge-conflict

基于 monaco-editor >= 0.55.1 的 VS Code 风格 Git 冲突预览、处理与截图包。

示例截图

JavaScript 冲突

JavaScript 合并冲突预览

Vue 3 SFC 冲突

Vue 3 合并冲突预览

120 行 YAML 合并冲突长截图

能力

  • 普通 Git 两方冲突与 diff3 ||||||| base 冲突解析;
  • 当前/传入区块着色、接受当前、接受传入、接受双方、Diff 比较;
  • VS Code 风格文件路径头;
  • 每个冲突可传入 HEAD/Incoming 的 ref、作者、commit、提交时间和 message;提交详情仅显示在冲突标记行,不在文件头重复展示;
  • 提交信息直接显示在 HEAD(当前更改 · 用户 · message · YYYY-MM-DD HH:mm:ss · commit) 标记行;
  • 长 commit message 使用两侧独立弹性布局和省略号保护编辑器宽度,悬停可查看完整信息;
  • 从浏览器 File、Blob、File-like 对象或 { content, filePath } 载入;
  • 文件级 Source/Target add | modify | delete | rename 冲突建模与状态标识;
  • 每个冲突分别截图,或多个冲突合成一张长图;
  • 截图可配置冲突上方/下方代码行数,不受当前编辑器 viewport 高度限制;
  • .vue 文件识别及轻量 Vue SFC tokenizer;
  • 不依赖 DOM 的 JSON 分析与纯文本解决 API,适合 Skill、Agent、CLI 和服务端编排;
  • npm Browser ESM、ESM、CommonJS 和 IIFE/CDN 构建产物。

monaco-editor npm 包不包含 VS Code Workbench 的 Merge Conflict 扩展。本包只组合 Monaco 0.55.1 的公开 Decorations、View Zones、executeEdits 和 Diff Editor API,不引用 esm/vs/... 私有模块。

安装与开发

npm install monaco-editor monaco-editor-merge-conflict

本仓库本地运行:

npm install
npm run dev
npm run typecheck
npm test
npm run build

文件预览

import * as monaco from 'monaco-editor';
import {
  createMergeConflictFileEditor,
  loadConflictFile,
  zhCNMessages,
} from 'monaco-editor-merge-conflict/browser';
import 'monaco-editor-merge-conflict/style.css';

const source = await loadConflictFile(fileInput.files![0], {
  // 浏览器不会暴露用户机器上的完整路径,路径由业务显式传入。
  filePath: 'packages/web/src/App.vue',
});

const getConflictMetadata = (_conflict, index) => ({
  current: {
    ref: 'HEAD',
    author: 'Alice Chen',
    commit: '86f5a62',
    committedAt: '2026-08-26T18:42:13+08:00',
    message: 'fix(editor): preserve the current implementation',
  },
  incoming: {
    ref: index === 0 ? 'feature/login' : 'feature/layout',
    author: 'Bob Lin',
    commit: '516f350',
    committedAt: '2026-08-27T09:16:48+08:00',
    message: 'feat(editor): add the incoming implementation',
  },
});

const fileEditor = createMergeConflictFileEditor(monaco, container, {
  source,
  // 默认就是 1920 × 1080;这里可以按宿主布局覆盖。
  previewSize: { width: 1920, height: 1080 },
  editorOptions: { theme: 'vs-dark', minimap: { enabled: false } },
  controllerOptions: {
    messages: zhCNMessages,
    getConflictMetadata,
    // 默认最多显示 50 个 Unicode 字符;hover 中仍保留完整 message。
    commitMessageMaxLength: 50,
  },
});

fileEditor.dispose();

如果已经自行创建 Monaco Editor,也可以只挂载底层 controller。默认“比较更改”打开内置 Monaco Diff Editor;业务已有抽屉/路由时可以通过 onCompare 接管。

CLI

安装包后可以使用 monaco-merge-conflict,也可以直接通过 npx 调用:

# 输出适合 Agent/Skill 消费的 JSON
npx monaco-merge-conflict analyze src/App.vue

# 文件包含完整冲突块时退出码为 1,否则为 0
npx monaco-merge-conflict check src/App.vue

# 解决第 0 个冲突并输出到 stdout
npx monaco-merge-conflict resolve src/App.vue \
  --conflict 0 \
  --resolution incoming

# 写到新文件;使用 --write 可以原地修改
npx monaco-merge-conflict resolve src/App.vue \
  --conflict 0 \
  --resolution both \
  --output src/App.resolved.vue

三个命令都支持用 - 从 stdin 读取。resolve 默认不会覆盖输入文件,只有显式传入 --write 才会原地写入。

文件级冲突

文本区块冲突之外,可以从两个文件状态构造预览:

import {
  createConflictFileFromSides,
  getFileConflictKind,
} from 'monaco-editor-merge-conflict';

const source = createConflictFileFromSides({
  filePath: 'services/health/check.go',
  source: {
    change: 'add',
    ref: 'HEAD',
    content: 'package health\n\nfunc Check() bool { return true }',
  },
  target: {
    change: 'delete',
    ref: 'feature/remove-health-check',
  },
});

getFileConflictKind(source.fileConflict!); // add/delete

支持 add/add、add/delete、delete/modify、modify/delete、rename/rename 等组合。Delete 一侧内容为空,因此接受 Delete 侧会得到空文件。delete/delete 没有可预览内容,构造函数会拒绝该组合。文件级状态保留在结构化数据 API 中,不在文件头或截图中重复展示。

发布后的截图使用

截图依赖 Monaco、DOM 和 Canvas,因此需要在浏览器应用中调用。当前 Node.js CLI 提供 analyze、check 和 resolve,不能直接渲染 PNG。

下面是完整的浏览器 ESM 示例:从用户上传的冲突文件生成截图,并下载每个冲突区块对应的 PNG。

<input id="conflict-file" type="file" />
import * as monaco from 'monaco-editor';
import {
  captureMergeConflictScreenshots,
  loadConflictFile,
  zhCNMessages,
} from 'monaco-editor-merge-conflict/browser';
import 'monaco-editor-merge-conflict/style.css';

async function captureUploadedFile(file: File) {
  const source = await loadConflictFile(file, {
    // 浏览器不会暴露用户机器上的绝对路径,需要由业务主动传入。
    filePath: `packages/cli/src/${file.name}`,
  });

  return captureMergeConflictScreenshots(monaco, {
    source,
    // separate:每个冲突一张图;combined:所有冲突合成一张长图。
    mode: 'separate',
    contextBefore: 8,
    contextAfter: 12,
    // 不传 width 时,按照最长可见内容自适应宽度。
    minWidth: 480,
    maxWidth: 1920,
    pixelRatio: 2,
    controllerOptions: {
      messages: zhCNMessages,
      commitMessageMaxLength: 50,
      getConflictMetadata: (_conflict, index) => ({
        current: {
          ref: 'HEAD',
          author: 'Alice Chen',
          commit: '86f5a62',
          committedAt: '2026-08-26T18:42:13+08:00',
          message: 'fix(cli): preserve custom welcome messages',
        },
        incoming: {
          ref: index === 0 ? 'feature/version' : 'feature/profile',
          author: 'Bob Lin',
          commit: '516f350',
          committedAt: '2026-08-27T09:16:48+08:00',
          message: 'feat(cli): print version and usage details',
        },
      }),
    },
  });
}

const input = document.querySelector<HTMLInputElement>('#conflict-file');
const file = input?.files?.[0];
const screenshots = file ? await captureUploadedFile(file) : [];

screenshots.forEach((screenshot, index) => {
  const url = URL.createObjectURL(screenshot.blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = `conflict-${index + 1}.png`;
  link.click();
  URL.revokeObjectURL(url);
});

如果只需要在页面上展示截图,可以直接使用返回结果中的 dataUrl:

const firstScreenshot = screenshots[0];
if (firstScreenshot) {
  const image = new Image();
  image.src = firstScreenshot.dataUrl;
  document.body.append(image);
}

如果已经持有冲突文件的字符串内容,不需要先调用 loadConflictFile。使用 combined 可以把多个冲突区块生成一张长图:

const [longScreenshot] = await captureMergeConflictScreenshots(monaco, {
  source: {
    filePath: 'packages/cli/src/base.js',
    languageId: 'javascript',
    content: conflictContent,
  },
  mode: 'combined',
  contextBefore: 5,
  contextAfter: 5,
});

每个截图结果包含 blob、dataUrl、filePath、conflictIndexes、startLineNumber、endLineNumber、width 和 height。不传 width 时,截图函数会测量 Monaco 渲染行、操作条和文件路径,以最长可见内容决定宽度;传入 width 可固定宽度,也可用 minWidth/maxWidth 限制自适应范围。离屏编辑器会扩展到完整内容高度,所以大冲突不会被当前 viewport 裁切。最终 PNG 最大尺寸仍受浏览器 Canvas 实现上限约束;超大文件建议使用 mode: 'separate'。

批量任务复用一个 Editor

处理几十或上百个文件时,可以持有一个 renderer,依次等待每个文件截图完成。renderer 只创建一个 Monaco Editor,每次通过 setModel 切换新模型,并在继续前释放上一个 controller 和 model:

import * as monaco from 'monaco-editor';
import {
  createMergeConflictScreenshotRenderer,
} from 'monaco-editor-merge-conflict/browser';

const renderer = createMergeConflictScreenshotRenderer(monaco, {
  mode: 'separate',
  contextBefore: 3,
  contextAfter: 3,
  pixelRatio: 1,
  maxWidth: 1280,
});

try {
  for (const source of conflictFiles) {
    const screenshots = await renderer.capture({ source });
    await saveImmediately(source, screenshots);
  }
} finally {
  renderer.dispose();
}

不要对同一个 renderer 使用 Promise.all:一个 Editor 同一时刻只能展示一个 model,因此 API 会主动拒绝并发截图。建议每次拿到结果就立即写入文件并释放引用,不要在内存中长期保存全部 dataUrl。

无头浏览器示例

Playwright

examples/playwright-screenshot 是一个独立项目:从 npm 安装本包,通过 Playwright 启动无头 Chromium,载入存在冲突的 TypeScript 文件,并把 merge-conflict.png 导出到示例目录。

cd examples/playwright-screenshot
npm install
npx playwright install chromium
npm run screenshot

Puppeteer

examples/puppeteer-screenshot 从 npm 安装已发布包,通过 Puppeteer 启动无头 Chrome,并在示例目录导出自己的 merge-conflict.png。

cd examples/puppeteer-screenshot
npm install
npm run screenshot

100 文件批量案例

examples/playwright-batch-100 会生成 100 个冲突文件,只启动一个浏览器页面,并复用一个 Monaco Editor 导出全部 PNG。

cd examples/playwright-batch-100
npm install
npx playwright install chromium
npm run screenshot

程序化与自动化调用

纯函数不需要 Monaco、DOM 或 worker,输入输出可 JSON 序列化:

import {
  analyzeMergeConflictText,
  resolveMergeConflictText,
} from 'monaco-editor-merge-conflict';

const analysis = analyzeMergeConflictText({
  content,
  filePath: 'src/App.vue',
  getConflictMetadata,
});

// analysis.schemaVersion === 1
// analysis.conflicts[n].current / ancestor / incoming

const result = resolveMergeConflictText({
  content,
  conflict: 0, // 或 analysis.conflicts[0].id
  resolution: 'both',
});

console.log(result.content, result.remainingConflictCount);

推荐 Agent/Skill 流程:先调用 analyzeMergeConflictText,把结构化区块交给模型决策,再以冲突 index/id 调用 resolveMergeConflictText;不要让模型直接计算行偏移。

Vue

loadConflictFile 会把 .vue 映射为 vue。createMergeConflictFileEditor 和截图函数会自动调用 registerVueLanguage,提供 template、script、style 的基础着色。完整 Vue 诊断、跳转和补全不属于本包,应由宿主接入 Volar。

Demo 的 demo/examples 提供 Go、YAML、Vue 2 Options API、Vue 3 script setup、JavaScript 和 TypeScript 冲突文件,其中 JavaScript 示例包含两个冲突区块,另有一个 Current 60 行 + Incoming 60 行的 YAML 长冲突截图示例。下拉列表还覆盖 Add/Delete、Delete/Modify、Add/Add 和 Rename/Rename 文件级冲突。

源码模块

src/
├── core/        # 类型、解析器、格式化器、纯文本操作
├── editor/      # Controller、文件预览与 Diff 弹窗
├── files/       # File/Blob/文件路径与 language id
├── languages/   # Vue SFC language registration
├── screenshot/  # 多区块和长截图
├── styles/      # 编辑器、文件头、冲突装饰样式
└── index.ts     # npm 公共入口

发布入口

| 场景 | 入口 | | --- | --- | | Browser/打包器 ESM | dist/index.js 或包子路径 /browser | | Node/打包器 CommonJS | dist/index.cjs | | CDN/IIFE | dist/index.global.js,全局名 MonacoMergeConflict | | 类型 | dist/index.d.ts | | 样式 | monaco-editor-merge-conflict/style.css |

IIFE 仍由调用方把 Monaco namespace 作为第一个参数传入,不会重复打包 Monaco。

设计边界

  • 仅操作同时具备 <<<<<<<、=======、>>>>>>> 的完整块,避免误改残缺内容;
  • 冲突标记必须从行首开始且长度至少为 7;
  • “接受双方更改”为 current 后 incoming,不进行 AST 或语义合并;
  • 完整四窗格 Merge Editor(Base / Current / Incoming / Result)属于更高层产品形态,可复用本包解析和 resolve API 扩展。