作成理由
Windowsで開発していた時は、WinMergeを利用していましたがMacで利用できなかったので作りました。
WEBシステムはあまり利用したくなかったのと、VSCodeのDiffが使いづらかったのも一因です。
また、ネイティブアプリにするためにElectronを利用してみました。
ビルドしたアプリを配布するのは面倒なので、コードとコマンド貼っておきます。
動作イメージ
環境セットアップとビルド
1:後述の「コード関連」に記載されている内容を作業ディレクトリに配置
2:パッケージのインストール
npm install
3:アプリのスタート(動作確認用)
npm start
4:アプリケーションのビルド
npm run dist
5:作業ディレクトリ内にdistが作成されて、dmgファイルが生成されます。
生成されるもの:electron-diff-tool-1.0.0-arm64.dmg
6:上記dmgファイルを実行すれば通常のアプリケーションとしてインストールできます。
コード関連
- ディレクトリ構造
.
├── index.html
├── main.js
├── package.json
├── renderer.js
└── styles.css
- index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>縁の差分確認ツール</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="app">
<h1>縁の差分確認ツール</h1>
<div class="input-container">
<textarea id="text1" placeholder="ここに元のテキストを入力してください"></textarea>
<textarea id="text2" placeholder="ここに変更後のテキストを入力してください"></textarea>
</div>
<div class="button-container">
<button id="compareBtn">比較</button>
<button id="refreshBtn">リフレッシュ</button>
</div>
<div id="result">
<div id="text1Result"></div>
<div id="text2Result"></div>
</div>
</div>
<script src="renderer.js"></script>
</body>
</html>
- main.js
const { app, BrowserWindow } = require("electron");
const path = require("path");
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
});
win.loadFile("index.html");
}
app.whenReady().then(createWindow);
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
- renderer.js
const diff = require("diff");
function compareTexts() {
const original = document.getElementById("text1").value;
const modified = document.getElementById("text2").value;
const differences = diff.diffWordsWithSpace(original, modified);
const originalResult = document.getElementById("text1Result");
const modifiedResult = document.getElementById("text2Result");
originalResult.innerHTML = "";
modifiedResult.innerHTML = "";
let originalLine = document.createElement("div");
let modifiedLine = document.createElement("div");
differences.forEach((part) => {
const spanOriginal = document.createElement("span");
const spanModified = document.createElement("span");
if (part.added) {
spanModified.textContent = part.value;
spanModified.className = "added";
spanOriginal.className = "empty";
} else if (part.removed) {
spanOriginal.textContent = part.value;
spanOriginal.className = "removed";
spanModified.className = "empty";
} else {
spanOriginal.textContent = part.value;
spanModified.textContent = part.value;
}
originalLine.appendChild(spanOriginal);
modifiedLine.appendChild(spanModified);
if (part.value.includes("\n")) {
originalResult.appendChild(originalLine);
modifiedResult.appendChild(modifiedLine);
originalLine = document.createElement("div");
modifiedLine = document.createElement("div");
}
});
if (originalLine.childNodes.length > 0) {
originalResult.appendChild(originalLine);
modifiedResult.appendChild(modifiedLine);
}
}
function refreshAll() {
document.getElementById("text1").value = "";
document.getElementById("text2").value = "";
document.getElementById("text1Result").innerHTML = "";
document.getElementById("text2Result").innerHTML = "";
}
document.getElementById("compareBtn").addEventListener("click", compareTexts);
document.getElementById("refreshBtn").addEventListener("click", refreshAll);
- styles.css
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--background-color: #f5f6fa;
--text-color: #2c3e50;
--border-color: #dcdde1;
}
body {
font-family: "Meiryo", "Hiragino Kaku Gothic ProN", "MS PGothic", sans-serif;
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
margin: 0;
}
#app {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 30px;
}
h1 {
text-align: center;
color: var(--primary-color);
margin-bottom: 30px;
font-size: 2.5em;
}
.input-container {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
textarea {
width: 100%;
height: 200px;
padding: 15px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 14px;
resize: vertical;
transition: border-color 0.3s ease;
}
textarea:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
}
.button-container {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 30px;
}
button {
padding: 12px 24px;
font-size: 16px;
cursor: pointer;
border: none;
border-radius: 4px;
transition: all 0.3s ease;
font-weight: bold;
}
#compareBtn {
background-color: var(--primary-color);
color: white;
}
#compareBtn:hover {
background-color: #2980b9;
}
#refreshBtn {
background-color: var(--secondary-color);
color: white;
}
#refreshBtn:hover {
background-color: #27ae60;
}
#result {
display: flex;
gap: 20px;
}
#text1Result,
#text2Result {
width: 100%;
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 15px;
white-space: pre-wrap;
font-family: "Meiryo", "Hiragino Kaku Gothic ProN", "MS PGothic", monospace;
line-height: 1.5;
max-height: 400px;
overflow-y: auto;
background-color: #f8f9fa;
}
.added {
background-color: #e6ffed;
color: #24292e;
padding: 2px 0;
}
.removed {
background-color: #ffeef0;
color: #24292e;
padding: 2px 0;
}
.empty {
display: none;
}
@media (max-width: 768px) {
.input-container,
#result {
flex-direction: column;
}
textarea,
#text1Result,
#text2Result {
width: calc(100% - 30px);
}
}
- package.json
{
"name": "electron-diff-tool",
"version": "1.0.0",
"description": "simple diff tool",
"main": "main.js",
"scripts": {
"start": "electron .",
"pack": "electron-builder --dir",
"dist": "electron-builder"
},
"build": {
"appId": "com.hoge.hageappname",
"mac": {
"category": "hoge.app.category.type"
},
"win": {
"target": [
"nsis",
"portable"
]
},
"linux": {
"target": [
"AppImage",
"deb"
]
}
},
"dependencies": {
"diff": "^5.1.0"
},
"devDependencies": {
"electron": "^20.0.0",
"electron-builder": "^24.13.3"
}
}
最後に
ライブラリがあったのでサクッと作れました。先人たちには頭が下がりますね。
以上、どなたかのお役に立てば幸いです。