codeタグ用HTMLエスケープミニアプリの開発(セキュリティと利便性を両立した自作ツール)

公開日: 更新日:

目次

1. HTMLエスケープの課題と従来の解決策

ブログ記事でHTMLやJavaScriptのコードを紹介する際、<、>、&などの文字をそのまま記述すると、ブラウザがHTMLタグとして解釈してしまい、表示が崩れる問題があります。

エスケープが必要な理由

  • 表示崩れの防止:HTMLタグの誤認識を回避
  • セキュリティ対策:XSS攻撃の防止
  • コードの正確な表示:意図した通りの表示を実現

これまでは、外部のWebサービスを利用してエスケープ処理を行っていました。代表的なものとして、以下のようなサイトがあります。

Free Online HTML Escape / Unescape Tool - FreeFormatter.com

外部のHTMLエスケープツール
外部のHTMLエスケープツール

2. 既存ツールの限界とリスク

外部のエスケープツールには、以下のような課題があります。

外部ツールのリスク

  • データの送信リスク:機密情報を含むコードの外部送信による情報漏洩の可能性
  • 可用性の問題:サービス停止時の業務影響
  • 広告・トラッキング:不要な情報収集への懸念
  • プライバシー:処理内容の透明性が確保されない

特に、会社のブログやプライベートなプロジェクトのコードを扱う際、「バックグラウンドでどのような処理が行われているか分からない」という不安は常に付きまといます。

3. 自作ミニアプリによる解決

これらの課題を根本的に解決するため、完全にローカルで動作する自作のミニアプリを開発しました。

Code Escape for <code> tag

自作のHTMLエスケープミニアプリ
自作のHTMLエスケープミニアプリ

3.1 アプリケーションの特徴

主要機能

  • 完全ローカル処理:JavaScriptによるクライアントサイド実行
  • カスタマイズ可能なエスケープパターン:必要な文字のみを選択的にエスケープ
  • ワンクリックコピー:結果を瞬時にクリップボードにコピー
  • 設定の永続化:LocalStorageによるパターン設定の保存

3.2 セキュリティ面での優位性

自作アプリケーションにより、以下のセキュリティ上の利点を得られます。

  • データの外部送信なし:すべての処理がブラウザ内で完結
  • 透明性の確保:ソースコードが完全に把握できる
  • 依存関係の最小化:外部サービスへの依存を排除

4. 実装詳細と技術選択

4.1 技術スタックの選定理由

今回のミニアプリでは、以下の要件を満たす技術を選択しました。

  • 高速な開発とデプロイ:プロトタイプから本格運用まで短期間で実現
  • 軽量で高速な動作:ユーザー体験を重視した性能
  • 保守性の高い構成:長期的なメンテナンスを考慮

採用技術スタック

  • Vite:高速な開発環境とシンプルな設定、モジュールの最適化
  • Bootstrap:素早いUI構築とレスポンシブ対応、一貫したデザインシステム
  • PostCSS:CSS最適化とファイルサイズ削減、プロダクション向け最適化

4.2 なぜVite + Bootstrap + PostCSSなのか

初めてViteを使用しましたが、以下の理由で選択しました。

  • 開発サーバーの高速起動:従来のWebpackと比較して圧倒的な速度
  • 設定の簡素化:最小限の設定でモダンな開発環境を構築
  • 本格的なビルド最適化:プロダクション向けの高度な最適化機能

4.3 設定ファイルの詳細

以下は、今回の開発で使用した主要な設定ファイルです。自分のメモも兼ねて、実際のコードを掲載します。

vite.config.js

const path = require('node:path');
import viteCompression from 'vite-plugin-compression';
import {defineConfig} from 'vite';

/** @type {import('vite').UserConfig} */
export default defineConfig({
  root: path.resolve(__dirname, 'src'),
  base: '/',
  build: {
    outDir: '../dist',
  },
  server: {
    port: 8080,
  },
  plugins: [viteCompression()],
});

postcss.config.js

const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const purgeCss = require('@fullhuman/postcss-purgecss');

/** @type {import('postcss-load-config').Config} */
const config = {
  plugins: [
    autoprefixer(),
    purgeCss({
      content: ['src/**/*.html'],
      variables: true,
    }),
    cssnano(),
  ],
};
module.exports = config;

4.4 コア機能の実装

アプリケーションの中核となるJavaScriptの実装です:

main.js

// Import our custom CSS
import '../scss/styles.scss';

const DEFAULT_PATTERN = `&<>"{}#$%'()*+-\/`;

const CodeElement = document.getElementById('code');
const PatternElement = document.getElementById('pattern');
const EscapedElement = document.getElementById('escaped');
const CopyButtonElement = document.getElementById('copy');

// CodeElement
CodeElement.placeholder = `For example...\n<tag></tag>\n{{ template }}`;
CodeElement.addEventListener('input', (e) => {
  updateEscapedElement();
});
CodeElement.addEventListener('input', (e) => {
  EscapedElement.value = escapeHtml(CodeElement.value);
  CopyButtonElement.disabled = !Boolean(CodeElement.value);
});

function updateEscapedElement() {
  EscapedElement.value = escapeHtml(CodeElement.value);
  CopyButtonElement.disabled = !Boolean(CodeElement.value);
}

// PatternElement
PatternElement.value = getPattern() ? getPattern() : DEFAULT_PATTERN;
PatternElement.placeholder = DEFAULT_PATTERN;
PatternElement.addEventListener('input', (e) => {
  setPattern(PatternElement.value);
  updateEscapedElement();
});

// CopyButtonElement
CopyButtonElement.addEventListener('click', (e) => {
  onClickCopy();
});

export function onClickCopy() {
  if (EscapedElement.value) {
    navigator.clipboard.writeText(EscapedElement.value);
  }
}

function escapeHtml(text) {
  const pattern = `[${PatternElement.value}]`;
  const regexPattern = new RegExp(pattern, 'g');
  return text.replace(regexPattern, (character) => {
    return '&#' + character.charCodeAt(0) + ';';
  });

}

function getPattern() {
  return window.localStorage.getItem('pattern');
}

function setPattern(value) {
  return window.localStorage.setItem('pattern', value);
}

5. 使用方法と活用例

5.1 基本的な使用方法

  1. コード入力:左側のテキストエリアにエスケープしたいコードを入力
  2. パターン調整:必要に応じてエスケープ対象文字を調整
  3. 結果コピー:右側に表示された結果をワンクリックでコピー
  4. ブログ投稿:コピーした内容をcodeタグ内に貼り付け

5.2 実践での活用

この記事のコード例も、実際に自作ミニアプリを使用してエスケープ処理を行っています。従来の外部ツールでは不安だった機密性の高いコードも、安心して処理できるようになりました。

6. まとめ

HTMLエスケープという日常的な課題に対し、セキュリティと利便性を両立した自作ツールを開発しました。

プロジェクトの成果

  • セキュリティの向上:データの外部送信リスクを完全に排除
  • 利便性の向上:カスタマイズ可能で高速な処理
  • 技術力の向上:Viteを使った新しい開発体験の習得
  • 長期運用の安心:外部サービスへの依存から解放

小さなツールでも、開発プロセスで学んだ知見は今後のプロジェクトに活かせる貴重な経験となりました。特に、Vite + Bootstrap + PostCSSの組み合わせは、高速で保守性の高い開発環境として、今後も積極的に活用していきたいと考えています。

よろしければ、Code Escape for <code> tagをお試しください。同様の課題を抱える方のお役に立てれば幸いです。

小巻旭洋のプロフィール写真

小巻 旭洋

2014年からフリーランスとして活動し、2016年に株式会社フルーデンスを設立する。FileMaker開発歴は約10年。多数の企業システム構築を手がけ、特にデータベース設計と、JavaScript連携、Web連携、パフォーマンス最適化を専門としています。2025年から、Web業務システム・アプリ開発をメインに開発をしています。