原文: How to Build a Code Editor with React that Compiles and Executes in 40+ Languages
オンラインのコード実行プラットフォームを利用すれば、あなたのお気に入りのプログラミング言語でコードを書き、そのコードを同じプラットフォーム上で実行することができます。
自分で作成したプログラム (たとえば、JavaScript で作成されたバイナリ検索プログラム) の出力を確認できると理想的です。
今日は、40 以上の異なるプログラミング言語でコードをコンパイルおよび実行できる CodeRush と呼ばれるオンラインコード実行プラットフォームを構築していきます。
構築するもの

以下の機能を備えた多彩なコードエディターを構築します。
- VS Code にも使われているコードエディター (Monaco Editor)。
- 40 を超えるプログラミング言語をサポートし、標準入出力を使用して Web アプリ上のコードをコンパイルできる。
- 利用可能なテーマのリストからエディターのテーマを変更できる。
- 実行されたコードに関する情報 (コードにかかった時間、使用されたメモリ、ステータスなど) を取得できる。
技術スタック
このプロジェクトでは、以下の技術スタックを使用します。
- React.js – フロントエンド用
- TailwindCSS – スタイル用
- Judge0 – コードのコンパイルおよび実行用
- RapidAPI – Judge0 コードの迅速なデプロイ
- Monaco Editor – プロジェクトを支えるコードエディター
プロジェクトの構造
プロジェクトの構造は非常にシンプルで理解しやすくなっています。
- Components: すべてのコンポーネント又は再利用可能なコードスニペットがここにあります。(例: CodeEditorWindow や Landing)
- hooks: ここにすべてのカスタムフックがあります。(キーボードイベントを使用してコードをコンパイルするためのキープレスフックを使用する予定です。)
- lib: すべてのライブラリ関数はここにあります。(ここでテーマを定義する関数を作成します。)
- constants: ドロップダウンの
languageOptions
やcustomStyles
などのすべての定数がここに入力されます。 - utils: コードの保守に役立つ一般的なユーティリティー関数がここにあります。
アプリケーションの流れ
コードを深く掘り下げる前に、まずアプリケーションの流れと、ゼロからコードを書いていく方法を把握しましょう。
- ユーザーは Web アプリケーションにアクセスし、好みのプログラミング言語 (デフォルトは JavaScript) を選択できます。
- ユーザーがコードの作成を完了すると、コードをコンパイルし、出力ウィンドウで出力、結果を確認できます。
- コード出力ウィンドウには、コードスニペットの成功または失敗が表示されます。すべての情報がコード出力ウィンドウで確認できます。
- ユーザーはコードスニペットにカスタム入力を追加でき、Judge0 (オンラインコンパイラー) はユーザーが指定したカスタム入力を考慮します。
- ユーザーは、実行されたコードに関する関連情報を確認できます。(例: コードのコンパイルと実行に 5 ms かかり、2024 kb のメモリが使用され、ランタイムステータスは成功)
フォルダー構造とアプリケーションの流れについて少し理解できたところで、コードを詳しく見て、すべてがどのように機能するかをみてみましょう。
コードエディターコンポーネントを構築する方法

コードエディターコンポーネントは主に、使用およびカスタマイズできる NPM パッケージ、Monaco Editor で構成されます。
// CodeEditorWindow.js
import React, { useState } from "react";
import Editor from "@monaco-editor/react";
const CodeEditorWindow = ({ onChange, language, code, theme }) => {
const [value, setValue] = useState(code || "");
const handleEditorChange = (value) => {
setValue(value);
onChange("code", value);
};
return (
<div className="overlay rounded-md overflow-hidden w-full h-full shadow-4xl">
<Editor
height="85vh"
width={`100%`}
language={language || "javascript"}
value={value}
theme={theme}
defaultValue="// some comment"
onChange={handleEditorChange}
/>
</div>
);
};
export default CodeEditorWindow;
Editor
コンポーネントは @monaco-editor/react
パッケージから取得されたもので、指定された高さ 85vh
でコードエディターを起動できます。
Editor
コンポーネントはいくつかの props を受け取ります:
language
: 構文ハイライトやインテリセンスのために必要な、言語を指定する必須項目。theme
: コードスニペットの色と背景 (チュートリアルの後半で構成します)。value
: コードエディターに入力される実際のコード値。onChange
: これは、コードエディターの値が変更されたときにトリガーされます。後で Judge0 API を呼び出してコンパイルできるように、変更された値を状態に保存する必要があります。
エディターは、その親コンポーネントである Landing.js
から、onChange
、language
、code
、theme
の props を受け取ります。コードエディター内の値が変更されるたびに、親コンポーネントの Landing
内にある onChange
ハンドラーが呼び出されます。
ランディングコンポーネントを構築する
ランディングコンポーネントは主に 3 つの部分で構成されています。
language
とtheme
のドロップダウンコンポーネントを備えたActions Bar
Code Editor Window
コンポーネントOutput and Custom Input
コンポーネント
// Landing.js
import React, { useEffect, useState } from "react";
import CodeEditorWindow from "./CodeEditorWindow";
import axios from "axios";
import { classnames } from "../utils/general";
import { languageOptions } from "../constants/languageOptions";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { defineTheme } from "../lib/defineTheme";
import useKeyPress from "../hooks/useKeyPress";
import Footer from "./Footer";
import OutputWindow from "./OutputWindow";
import CustomInput from "./CustomInput";
import OutputDetails from "./OutputDetails";
import ThemeDropdown from "./ThemeDropdown";
import LanguagesDropdown from "./LanguagesDropdown";
const javascriptDefault = `// some comment`;
const Landing = () => {
const [code, setCode] = useState(javascriptDefault);
const [customInput, setCustomInput] = useState("");
const [outputDetails, setOutputDetails] = useState(null);
const [processing, setProcessing] = useState(null);
const [theme, setTheme] = useState("cobalt");
const [language, setLanguage] = useState(languageOptions[0]);
const enterPress = useKeyPress("Enter");
const ctrlPress = useKeyPress("Control");
const onSelectChange = (sl) => {
console.log("selected Option...", sl);
setLanguage(sl);
};
useEffect(() => {
if (enterPress && ctrlPress) {
console.log("enterPress", enterPress);
console.log("ctrlPress", ctrlPress);
handleCompile();
}
}, [ctrlPress, enterPress]);
const onChange = (action, data) => {
switch (action) {
case "code": {
setCode(data);
break;
}
default: {
console.warn("case not handled!", action, data);
}
}
};
const handleCompile = () => {
// We will come to the implementation later in the code
};
const checkStatus = async (token) => {
// We will come to the implementation later in the code
};
function handleThemeChange(th) {
// We will come to the implementation later in the code
}
useEffect(() => {
defineTheme("oceanic-next").then((_) =>
setTheme({ value: "oceanic-next", label: "Oceanic Next" })
);
}, []);
const showSuccessToast = (msg) => {
toast.success(msg || `Compiled Successfully!`, {
position: "top-right",
autoClose: 1000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
};
const showErrorToast = (msg) => {
toast.error(msg || `Something went wrong! Please try again.`, {
position: "top-right",
autoClose: 1000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
};
return (
<>
<ToastContainer
position="top-right"
autoClose={2000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
<div className="h-4 w-full bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500"></div>
<div className="flex flex-row">
<div className="px-4 py-2">
<LanguagesDropdown onSelectChange={onSelectChange} />
</div>
<div className="px-4 py-2">
<ThemeDropdown handleThemeChange={handleThemeChange} theme={theme} />
</div>
</div>
<div className="flex flex-row space-x-4 items-start px-4 py-4">
<div className="flex flex-col w-full h-full justify-start items-end">
<CodeEditorWindow
code={code}
onChange={onChange}
language={language?.value}
theme={theme.value}
/>
</div>
<div className="right-container flex flex-shrink-0 w-[30%] flex-col">
<OutputWindow outputDetails={outputDetails} />
<div className="flex flex-col items-end">
<CustomInput
customInput={customInput}
setCustomInput={setCustomInput}
/>
<button
onClick={handleCompile}
disabled={!code}
className={classnames(
"mt-4 border-2 border-black z-10 rounded-md shadow-[5px_5px_0px_0px_rgba(0,0,0)] px-4 py-2 hover:shadow transition duration-200 bg-white flex-shrink-0",
!code ? "opacity-50" : ""
)}
>
{processing ? "Processing..." : "Compile and Execute"}
</button>
</div>
{outputDetails && <OutputDetails outputDetails={outputDetails} />}
</div>
</div>
<Footer />
</>
);
};
export default Landing;
ランディングページの基本構造をさらに詳しくみてみましょう。
CodeEditorWindow コンポーネント
上記に説明したように、CodeEditorWindow コンポーネントは (変更され続ける) コードと、コード内の変更を追跡する onChange
メソッドを考慮します。
// onChange method implementation
const onChange = (action, data) => {
switch (action) {
case "code": {
setCode(data);
break;
}
default: {
console.warn("case not handled!", action, data);
}
}
};
code
の状態を設定し、変更を追跡するだけでいいのです。
CodeEditorWindow
コンポーネントは、構文の強調表示とインテリセンスが必要な現在選択されている言語である language
の props も考慮します。
Monaco Editor によって受け入れられた language props を追跡し、コンパイルも処理する languageOptions
配列を作成しました。(judge0
API によって受け入れられる languageId
を追跡します)
// constants/languageOptions.js
export const languageOptions = [
{
id: 63,
name: "JavaScript (Node.js 12.14.0)",
label: "JavaScript (Node.js 12.14.0)",
value: "javascript",
},
{
id: 45,
name: "Assembly (NASM 2.14.02)",
label: "Assembly (NASM 2.14.02)",
value: "assembly",
},
...
...
...
...
...
...
{
id: 84,
name: "Visual Basic.Net (vbnc 0.0.0.5943)",
label: "Visual Basic.Net (vbnc 0.0.0.5943)",
value: "vbnet",
},
];
すべての languageOptions
オブジェクトには、id
、name
、label
、および value
キーが含まれています。languageOptions
配列全体を取得してドロップダウン内に配置し、オプションとして指定できます。
ドロップダウンの状態が変化するたびに、onSelectChange
メソッドは選択された id
を追跡し、状態を適切に変更します。
LanguageDropdown コンポーネント

// LanguageDropdown.js
import React from "react";
import Select from "react-select";
import { customStyles } from "../constants/customStyles";
import { languageOptions } from "../constants/languageOptions";
const LanguagesDropdown = ({ onSelectChange }) => {
return (
<Select
placeholder={`Filter By Category`}
options={languageOptions}
styles={customStyles}
defaultValue={languageOptions[0]}
onChange={(selectedOption) => onSelectChange(selectedOption)}
/>
);
};
export default LanguagesDropdown;
ドロップダウンの場合は、ドロップダウンとその変更ハンドラーを処理する react-select パッケージを使用します。
React select は、defaultValue
と options
を主要引数として受け取ります。options
は、関連するすべてのドロップダウン値を自動的に表示する配列 (ここでは languageOptions
を渡します) です。
defaultValue
という props は、コンポーネントに提供されるデフォルト値です。ここではデフォルト言語として JavaScript を選択します。(これは言語の配列内で最初の言語です。)
ユーザーが言語を変更するたびに、onSelectChange
コールバックを使用して言語を変更します。
const onSelectChange = (sl) => {
setLanguage(sl);
};
ThemeDropdown コンポーネント

ThemeDropdown
コンポーネントは、実は (UI および React-select パッケージを含む) LanguageDropdown
コンポーネントに非常に似ています。
// ThemeDropdown.js
import React from "react";
import Select from "react-select";
import monacoThemes from "monaco-themes/themes/themelist";
import { customStyles } from "../constants/customStyles";
const ThemeDropdown = ({ handleThemeChange, theme }) => {
return (
<Select
placeholder={`Select Theme`}
// options={languageOptions}
options={Object.entries(monacoThemes).map(([themeId, themeName]) => ({
label: themeName,
value: themeId,
key: themeId,
}))}
value={theme}
styles={customStyles}
onChange={handleThemeChange}
/>
);
};
export default ThemeDropdown;
ここでは、Monaco Editor 用にネット上で入手可能で、多種のきれいなデザインテーマを使用することができる、MonacoThemes
というパッケージを利用します。
自由に利用できるテーマのリストがこちらです。
// lib/defineTheme.js
import { loader } from "@monaco-editor/react";
const monacoThemes = {
active4d: "Active4D",
"all-hallows-eve": "All Hallows Eve",
amy: "Amy",
"birds-of-paradise": "Birds of Paradise",
blackboard: "Blackboard",
"brilliance-black": "Brilliance Black",
"brilliance-dull": "Brilliance Dull",
"chrome-devtools": "Chrome DevTools",
"clouds-midnight": "Clouds Midnight",
clouds: "Clouds",
cobalt: "Cobalt",
dawn: "Dawn",
dreamweaver: "Dreamweaver",
eiffel: "Eiffel",
"espresso-libre": "Espresso Libre",
github: "GitHub",
idle: "IDLE",
katzenmilch: "Katzenmilch",
"kuroir-theme": "Kuroir Theme",
lazy: "LAZY",
"magicwb--amiga-": "MagicWB (Amiga)",
"merbivore-soft": "Merbivore Soft",
merbivore: "Merbivore",
"monokai-bright": "Monokai Bright",
monokai: "Monokai",
"night-owl": "Night Owl",
"oceanic-next": "Oceanic Next",
"pastels-on-dark": "Pastels on Dark",
"slush-and-poppies": "Slush and Poppies",
"solarized-dark": "Solarized-dark",
"solarized-light": "Solarized-light",
spacecadet: "SpaceCadet",
sunburst: "Sunburst",
"textmate--mac-classic-": "Textmate (Mac Classic)",
"tomorrow-night-blue": "Tomorrow-Night-Blue",
"tomorrow-night-bright": "Tomorrow-Night-Bright",
"tomorrow-night-eighties": "Tomorrow-Night-Eighties",
"tomorrow-night": "Tomorrow-Night",
tomorrow: "Tomorrow",
twilight: "Twilight",
"upstream-sunburst": "Upstream Sunburst",
"vibrant-ink": "Vibrant Ink",
"xcode-default": "Xcode_default",
zenburnesque: "Zenburnesque",
iplastic: "iPlastic",
idlefingers: "idleFingers",
krtheme: "krTheme",
monoindustrial: "monoindustrial",
};
const defineTheme = (theme) => {
return new Promise((res) => {
Promise.all([
loader.init(),
import(`monaco-themes/themes/${monacoThemes[theme]}.json`),
]).then(([monaco, themeData]) => {
monaco.editor.defineTheme(theme, themeData);
res();
});
});
};
export { defineTheme };
monaco-themes
というパッケージは、コードエディターのデザインの方向性を定義するために使用できる多数のテーマを提供します。
defineTheme
関数は、ユーザーが選択するであろうさまざまなテーマを扱います。defineTheme
関数は、monaco.editor.defineTheme(theme,themeData)
アクションを使用して実際に Monaco Editor のテーマを設定する Promise を返します。このコード行は、Monaco Editor コードウィンドウ内のテーマを実際に変更する役割を担っています。
defineTheme
関数は、先ほど ThemeDropdown.js
コンポーネントで見た onChange
コールバックを利用して呼び出されます。
// Landing.js - handleThemeChange() function
function handleThemeChange(th) {
const theme = th;
console.log("theme...", theme);
if (["light", "vs-dark"].includes(theme.value)) {
setTheme(theme);
} else {
defineTheme(theme.value).then((_) => setTheme(theme));
}
}
handleThemeChange()
関数は、テーマが light
か dark
かをチェックします。これらのテーマはデフォルトで MonacoEditor
コンポーネントで利用できるため、defineTheme()
メソッドを呼び出す必要はありません。
そうでない場合は、defineTheme()
コンポーネントを呼び出して、選択したテーマの状態を設定します。

Judge0 を使用してコードをコンパイルする方法
アプリケーションの根幹部である、さまざまな言語でコードをコンパイルする方法を見ていきましょう。
コードをコンパイルするには、Judge0 を使用します。Judge0 は、対話可能でかつシンプルな、オープンソースのコード実行システムです。
いくつかの引数 (ソースコード、言語 ID) を使用して単純な API 呼び出しを実行し、応答として出力を取得することができます。
Judge0 をセットアップして、次のステップに進みましょう。
- Judge0 に移動し、ベーシックプランを選択します
- Judge0 は実際は RapidAPI でホスティングされています。ベーシックプランに登録してください。
- 登録すると、コード実行システムへの API 呼び出しを行うために必要な
RAPIDAPI_HOST
とRAPIDAPI_KEY
をコピーできます。
ダッシュボードは以下のようなものになります。

API 呼び出しには X-RapidAPI-Host
引数と X-RapidAPI-Key
引数が必要です。後ほど使用できるように、次のように .env
ファイルに保存します。
REACT_APP_RAPID_API_HOST = YOUR_HOST_URL
REACT_APP_RAPID_API_KEY = YOUR_SECRET_KEY
REACT_APP_RAPID_API_URL = YOUR_SUBMISSIONS_URL
React では、環境変数に REACT_APP
というプレフィックスを最初に含めることが重要です。
SUBMISSIONS_URL
は、使用する URL です。これは基本的に host
とそれに続く /submission
ルートで構成されます。
例えばこの場合では、https://judge0-ce.p.rapidapi.com/submissions
が submissions
URL になります。
変数を正しく設定したら、compilation
(コンパイル) ロジックを処理できるようになります。
コンパイルフローとロジック
コンパイルの流れは以下の通りです。
Compile and Execute
ボタンをクリックすると、handleCompile()
関数が呼び出されます。handleCompile()
関数は、languageId
、source_code
、stdin
(この場合は customInput) を本体引数として、submissions
URL 上でJudge0 RapidAPI
のバックエンドにリクエストします。- the
options
はhost
とsecret
をヘッダーとして受け取ります。 base64_encoded
とfields
は、渡すことができるオプションの引数です。submission
POST リクエストはリクエストをサーバーに登録し、プロセスを作成します。post
リクエストの応答は、後で実行のステータスを確認するために使用するtoken
です。(処理中、承認済み、時間制限超過、実行時間の例外など、さまざまなステータスがあります。)- 結果が返されると、結果が成功か失敗かを条件付きで確認し、結果を出力画面に表示します。
実際にコードを見てみて、handleCompile()
メソッドを理解しましょう。
const handleCompile = () => {
setProcessing(true);
const formData = {
language_id: language.id,
// encode source code in base64
source_code: btoa(code),
stdin: btoa(customInput),
};
const options = {
method: "POST",
url: process.env.REACT_APP_RAPID_API_URL,
params: { base64_encoded: "true", fields: "*" },
headers: {
"content-type": "application/json",
"Content-Type": "application/json",
"X-RapidAPI-Host": process.env.REACT_APP_RAPID_API_HOST,
"X-RapidAPI-Key": process.env.REACT_APP_RAPID_API_KEY,
},
data: formData,
};
axios
.request(options)
.then(function (response) {
console.log("res.data", response.data);
const token = response.data.token;
checkStatus(token);
})
.catch((err) => {
let error = err.response ? err.response.data : err;
setProcessing(false);
console.log(error);
});
};
上記の通り、handleCompile()
メソッドは languageId
、source_code
、stdin
を受け取ります。特に source_code
と stdin
の前に btoa
が使用されていることに注目してください。これは、API に渡すパラメータで base64_encoded: true
を使用しているのにあわせて、文字列を Base64 エンコードするためのものです。
応答が成功でトークンを入手したら、checkStatus()
メソッドを呼び出して /submissions/${token}
ルートをポーリングします。
const checkStatus = async (token) => {
const options = {
method: "GET",
url: process.env.REACT_APP_RAPID_API_URL + "/" + token,
params: { base64_encoded: "true", fields: "*" },
headers: {
"X-RapidAPI-Host": process.env.REACT_APP_RAPID_API_HOST,
"X-RapidAPI-Key": process.env.REACT_APP_RAPID_API_KEY,
},
};
try {
let response = await axios.request(options);
let statusId = response.data.status?.id;
// Processed - we have a result
if (statusId === 1 || statusId === 2) {
// still processing
setTimeout(() => {
checkStatus(token)
}, 2000)
return
} else {
setProcessing(false)
setOutputDetails(response.data)
showSuccessToast(`Compiled Successfully!`)
console.log('response.data', response.data)
return
}
} catch (err) {
console.log("err", err);
setProcessing(false);
showErrorToast();
}
};
以前に送信したコードの結果を取得するには、応答として受け取った token
を使用して submissions
API をポーリングする必要があります。
上記のように、エンドポイントに対して GET リクエストを行います。レスポンスがあると、statusId === 1 || statusId === 2
をチェックしていますが、これはどういう意味なのでしょうか。
私たちが API に提出したコードに関連する合計 14
のステータスがあります。それらは以下の通りです:
export const statuses = [
{
id: 1,
description: "In Queue",
},
{
id: 2,
description: "Processing",
},
{
id: 3,
description: "Accepted",
},
{
id: 4,
description: "Wrong Answer",
},
{
id: 5,
description: "Time Limit Exceeded",
},
{
id: 6,
description: "Compilation Error",
},
{
id: 7,
description: "Runtime Error (SIGSEGV)",
},
{
id: 8,
description: "Runtime Error (SIGXFSZ)",
},
{
id: 9,
description: "Runtime Error (SIGFPE)",
},
{
id: 10,
description: "Runtime Error (SIGABRT)",
},
{
id: 11,
description: "Runtime Error (NZEC)",
},
{
id: 12,
description: "Runtime Error (Other)",
},
{
id: 13,
description: "Internal Error",
},
{
id: 14,
description: "Exec Format Error",
},
];
したがって、statusId ===1
または statusId ===2
の場合、コードがまだ処理中であり、API を再度呼び出して結果が得られるかどうかを確認する必要があることを意味します。
このため、if
文の条件に checkStatus()
関数を再度呼び出す setTimeout()
があり、内部で API を再度呼び出してステータスを確認します。
ステータスが 2
または 3
以外の場合は、コードの実行が完了し、結果が得られたことを意味します。successfully compiled
(コンパイル成功) か、Time Limit Exceeded
(タイムリミット超過) か、あるいは Runtime Exception
(ランタイム例外の発生) のいずれかになります。statusId
は各シナリオを表し、これらを再現することができます。
例えば、while(true)
では time limit exceeded
のエラーが発生します。

または、構文に誤りがあると、コンパイルエラーが発生します。

どの場合においても、何らかの結果を得ることになります。そして、この結果を outputDetails
の状態に保存します。これは、画面の右側 (出力ウィンドウ) に表示するものがあることを保証するためです。
出力ウィンドウコンポーネント

import React from "react";
const OutputWindow = ({ outputDetails }) => {
const getOutput = () => {
let statusId = outputDetails?.status?.id;
if (statusId === 6) {
// compilation error
return (
<pre className="px-2 py-1 font-normal text-xs text-red-500">
{atob(outputDetails?.compile_output)}
</pre>
);
} else if (statusId === 3) {
return (
<pre className="px-2 py-1 font-normal text-xs text-green-500">
{atob(outputDetails.stdout) !== null
? `${atob(outputDetails.stdout)}`
: null}
</pre>
);
} else if (statusId === 5) {
return (
<pre className="px-2 py-1 font-normal text-xs text-red-500">
{`Time Limit Exceeded`}
</pre>
);
} else {
return (
<pre className="px-2 py-1 font-normal text-xs text-red-500">
{atob(outputDetails?.stderr)}
</pre>
);
}
};
return (
<>
<h1 className="font-bold text-xl bg-clip-text text-transparent bg-gradient-to-r from-slate-900 to-slate-700 mb-2">
Output
</h1>
<div className="w-full h-56 bg-[#1e293b] rounded-md text-white font-normal text-sm overflow-y-auto">
{outputDetails ? <>{getOutput()}</> : null}
</div>
</>
);
};
export default OutputWindow;
これは、成功または失敗のシナリオのみを表示する、シンプルなコンポーネントです。
getOutput()
メソッドは、テキストの色がどのように見えるか、および何を表示するかを決定します。
statusId
が6
の場合: コンパイルエラーが発生しています。この場合、API はcompile_output
を返し、それを使用してエラーを表示できます。statusId
が3
の場合: これはAccepted
という成功のシナリオです。この場合、API はstdout
(標準出力) を返します。これは、API に提供したコードから返されたデータを表示するために使用されます。statusId
が5
の場合: 時間制限超過エラーが発生しています。コード内に無限ループ条件があるか、またはコード実行の標準の5
秒時間を超えていることを表示するのみです。- その他すべてのステータスについては、標準的な
stderr
オブジェクトを取得し、エラーを表示するために使用します。 atob()
メソッドが使用されている点に注意してください。これは、出力を Base64 文字列として受け取るためです。デコードするためにatob()
メソッドを使用します。
以下は、JavaScript での Binary Search
プログラムの成功シナリオです。

出力詳細コンポーネント

OutputDetails
コンポーネントは、最初にコンパイルしたコードスニペットに関連する詳細を表示するためのシンプルなマッパーです。データはすでに outputDetails
状態変数に設定されています。
import React from "react";
const OutputDetails = ({ outputDetails }) => {
return (
<div className="metrics-container mt-4 flex flex-col space-y-3">
<p className="text-sm">
Status:{" "}
<span className="font-semibold px-2 py-1 rounded-md bg-gray-100">
{outputDetails?.status?.description}
</span>
</p>
<p className="text-sm">
Memory:{" "}
<span className="font-semibold px-2 py-1 rounded-md bg-gray-100">
{outputDetails?.memory}
</span>
</p>
<p className="text-sm">
Time:{" "}
<span className="font-semibold px-2 py-1 rounded-md bg-gray-100">
{outputDetails?.time}
</span>
</p>
</div>
);
};
export default OutputDetails;
time
、memory
、status.description
はすべて API 応答から受信された後、outputDetails
に保存され表示されます。
キーボードイベント
最後は、ctrl+enter
を使って、コードをコンパイルする機能です。これには、Web アプリケーションでさまざまなキーボードイベントのイベントリスナーの役割をする、カスタムフックを作成します (カスタムフックは非常に便利で、コードもきれいに保てます)。
// useKeyPress.js
import React, { useState } from "react";
const useKeyPress = function (targetKey) {
const [keyPressed, setKeyPressed] = useState(false);
function downHandler({ key }) {
if (key === targetKey) {
setKeyPressed(true);
}
}
const upHandler = ({ key }) => {
if (key === targetKey) {
setKeyPressed(false);
}
};
React.useEffect(() => {
document.addEventListener("keydown", downHandler);
document.addEventListener("keyup", upHandler);
return () => {
document.removeEventListener("keydown", downHandler);
document.removeEventListener("keyup", upHandler);
};
});
return keyPressed;
};
export default useKeyPress;
// Landing.js
...
...
...
const Landing = () => {
...
...
const enterPress = useKeyPress("Enter");
const ctrlPress = useKeyPress("Control");
...
...
}
ここでは、純粋な JavaScript の Event Listeners
を使用して、目的の target
キーのイベントを待ち、応答します。
この Hook
は、keydown
と keyup
のイベントを待ちます。Enter
と Control
のターゲットキーを使用してフックを初期化します。
targetKey === key
かどうかを確認し、それに応じて keyPressed
を設定しているため、keyPressed
で返されたブール値 (true または false) を使用することができます。
これで、useEffect
フックでこれらのイベントにリスナーを付け、両方が同時に押されたことを確認できます。
useEffect(() => {
if (enterPress && ctrlPress) {
console.log("enterPress", enterPress);
console.log("ctrlPress", ctrlPress);
handleCompile();
}
}, [ctrlPress, enterPress]);
これにより、ユーザーが control
キーと enter
キーを連続して押すか、同時に押すたびに、handleCompile()
メソッドが呼び出されます。
注意事項
楽しいプロジェクトではありますが、Judge0 の基本プランには、1 日 100 回までのリクエスト制限があります。
その対策法として、独自のサーバーやドロップレットを (Digital Ocean 上で) 起動し、オープンソースプロジェクトを独自にホストすることができます (これについての素晴らしいドキュメントも用意されています)。
まとめ
最終結果は以下になります。
- 40 以上の言語でコンパイルできるコードエディター
- コードエディターのデザインを変更するためのテーマ切り替え
- RapidAPI 上での API の相互作用とホスティング
- カスタムフックを使用した React でキーボードイベントの使用
- 楽しい時間! ;)
最後に、プロジェクトをさらに掘り下げたい方は、以下の機能を追加してみてはどうでしょうか。
- ログインおよび登録モジュール: コードを自分の個人ダッシュボードに保存できるようにします。
- インターネット上で他の人とコードをシェアする方法
- プロフィールページとカスタマイズ
- Socket プログラミングと操作変換を使用した、単一のコードスニペットでのペアプログラミング
- お気に入りのコードスニペットをブックマークする
- CodePen のような、保存されたコードスニペットのカスタムダッシュボード
私にとって、このアプリケーションをゼロからコーディングすることはとても楽しく、TailwindCSS は私のお気に入りであり、アプリケーションのスタイリングには欠かせないリソースです。
もしこの記事が役に立ったのなら、GitHub Repository に⭐️をつけていただけると嬉しいです。
質問があれば、Twitter や Website からお気軽にご連絡ください。喜んでサポートさせていただきます。