Skip to content

Commit 1687131

Browse files
committed
prismとxtermもdynamic importに変更
1 parent 42b5b50 commit 1687131

File tree

3 files changed

+131
-108
lines changed

3 files changed

+131
-108
lines changed

app/terminal/highlight.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
import Prism from "prismjs";
21
import chalk from "chalk";
32
import { RuntimeLang } from "./runtime";
4-
// 言語定義をインポート
5-
import "prismjs/components/prism-python";
6-
import "prismjs/components/prism-ruby";
7-
import "prismjs/components/prism-javascript";
3+
4+
export async function importPrism() {
5+
if (typeof window !== "undefined") {
6+
const Prism = await import("prismjs");
7+
// 言語定義をインポート
8+
await import("prismjs/components/prism-python");
9+
await import("prismjs/components/prism-ruby");
10+
await import("prismjs/components/prism-javascript");
11+
return Prism;
12+
} else {
13+
return null!;
14+
}
15+
}
816

917
type PrismLang = "python" | "ruby" | "javascript";
1018

@@ -74,6 +82,7 @@ const prismToAnsi: Record<string, (text: string) => string> = {
7482
* @returns {string} ANSIで色付けされた文字列
7583
*/
7684
export function highlightCodeToAnsi(
85+
Prism: typeof import("prismjs"),
7786
code: string,
7887
language: RuntimeLang
7988
): string {
@@ -129,3 +138,4 @@ export function highlightCodeToAnsi(
129138
""
130139
);
131140
}
141+

app/terminal/repl.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useCallback, useEffect, useRef, useState } from "react";
4-
import { highlightCodeToAnsi } from "./highlight";
4+
import { highlightCodeToAnsi, importPrism } from "./highlight";
55
import chalk from "chalk";
66
import {
77
clearTerminal,
@@ -11,7 +11,7 @@ import {
1111
systemMessageColor,
1212
useTerminal,
1313
} from "./terminal";
14-
import { Terminal } from "@xterm/xterm";
14+
import type { Terminal } from "@xterm/xterm";
1515
import { useEmbedContext } from "./embedContext";
1616
import { emptyMutex, langConstants, RuntimeLang, useRuntime } from "./runtime";
1717

@@ -69,6 +69,13 @@ export function ReplTerminal({
6969
}: ReplComponentProps) {
7070
const { addReplOutput } = useEmbedContext();
7171

72+
const [Prism, setPrism] = useState<typeof import("prismjs") | null>(null);
73+
useEffect(() => {
74+
if(Prism === null){
75+
importPrism().then((prism) => setPrism(prism));
76+
}
77+
}, [Prism]);
78+
7279
const {
7380
ready: runtimeReady,
7481
mutex: runtimeMutex = emptyMutex,
@@ -122,7 +129,7 @@ export function ReplTerminal({
122129
// inputBufferを更新し、画面に描画する
123130
const updateBuffer = useCallback(
124131
(newBuffer: () => string[]) => {
125-
if (terminalInstanceRef.current) {
132+
if (terminalInstanceRef.current && Prism) {
126133
hideCursor(terminalInstanceRef.current);
127134
// バッファの行数分カーソルを戻す
128135
if (inputBuffer.current.length >= 2) {
@@ -141,7 +148,7 @@ export function ReplTerminal({
141148
);
142149
if (language) {
143150
terminalInstanceRef.current.write(
144-
highlightCodeToAnsi(inputBuffer.current[i], language)
151+
highlightCodeToAnsi(Prism, inputBuffer.current[i], language)
145152
);
146153
} else {
147154
terminalInstanceRef.current.write(inputBuffer.current[i]);
@@ -153,7 +160,7 @@ export function ReplTerminal({
153160
showCursor(terminalInstanceRef.current);
154161
}
155162
},
156-
[prompt, promptMore, language, terminalInstanceRef]
163+
[Prism, prompt, promptMore, language, terminalInstanceRef]
157164
);
158165

159166
// ランタイムからのoutputを描画し、inputBufferをリセット

app/terminal/terminal.tsx

Lines changed: 104 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"use client";
22

33
import { useEffect, useRef, useState } from "react";
4-
import { Terminal } from "@xterm/xterm";
5-
import { FitAddon } from "@xterm/addon-fit";
4+
import type { Terminal } from "@xterm/xterm";
5+
import type { FitAddon } from "@xterm/addon-fit";
66
import "@xterm/xterm/css/xterm.css";
77
import chalk from "chalk";
88
import { useChangeTheme } from "../[docs_id]/themeToggle";
@@ -70,104 +70,110 @@ export function useTerminal(props: TerminalProps) {
7070

7171
// ターミナルの初期化処理
7272
useEffect(() => {
73-
const abortController = new AbortController();
74-
// globals.cssでフォントを指定し読み込んでいるが、
75-
// それが読み込まれる前にterminalを初期化してしまうとバグる。
76-
document.fonts.load("0.875rem Inconsolata Variable").then(() => {
77-
if (!abortController.signal.aborted) {
78-
const fromCSS = (varName: string) =>
79-
window.getComputedStyle(document.body).getPropertyValue(varName);
80-
// "--color-" + color_name のように文字列を分割するとTailwindCSSが認識せずCSSの値として出力されない場合があるので注意
81-
const term = new Terminal({
82-
cursorBlink: true,
83-
convertEol: true,
84-
cursorStyle: "bar",
85-
cursorInactiveStyle: "none",
86-
fontSize: 14,
87-
lineHeight: 1.4,
88-
letterSpacing: 0,
89-
fontFamily: "'Inconsolata Variable','Noto Sans JP Variable'",
90-
theme: {
91-
// DaisyUIの変数を使用してテーマを設定している
92-
// TODO: ダークテーマ/ライトテーマを切り替えたときに再設定する?
93-
background: fromCSS("--color-base-300"),
94-
foreground: fromCSS("--color-base-content"),
95-
cursor: fromCSS("--color-base-content"),
96-
selectionBackground: fromCSS("--color-primary"),
97-
selectionForeground: fromCSS("--color-primary-content"),
98-
black: fromCSS("--color-black"),
99-
brightBlack: fromCSS("--color-neutral-500"),
100-
red: fromCSS("--color-red-600"),
101-
brightRed: fromCSS("--color-red-400"),
102-
green: fromCSS("--color-green-600"),
103-
brightGreen: fromCSS("--color-green-400"),
104-
yellow: fromCSS("--color-yellow-700"),
105-
brightYellow: fromCSS("--color-yellow-400"),
106-
blue: fromCSS("--color-indigo-600"),
107-
brightBlue: fromCSS("--color-indigo-400"),
108-
magenta: fromCSS("--color-fuchsia-600"),
109-
brightMagenta: fromCSS("--color-fuchsia-400"),
110-
cyan: fromCSS("--color-cyan-600"),
111-
brightCyan: fromCSS("--color-cyan-400"),
112-
white: fromCSS("--color-base-100"),
113-
brightWhite: fromCSS("--color-white"),
114-
},
115-
});
116-
terminalInstanceRef.current = term;
117-
118-
fitAddonRef.current = new FitAddon();
119-
term.loadAddon(fitAddonRef.current);
120-
// fitAddon.fit();
121-
122-
term.open(terminalRef.current);
123-
124-
// https://github.com/xtermjs/xterm.js/issues/2478
125-
// my.code();ではCtrl+Cでのkeyboardinterruptは要らないので、コピーペーストに置き換えてしまう
126-
term.attachCustomKeyEventHandler((arg) => {
127-
if (
128-
arg.ctrlKey &&
129-
(arg.key === "c" || arg.key === "x") &&
130-
arg.type === "keydown"
131-
) {
132-
const selection = term.getSelection();
133-
if (selection) {
134-
navigator.clipboard.writeText(selection);
73+
if (typeof window !== "undefined") {
74+
const abortController = new AbortController();
75+
// globals.cssでフォントを指定し読み込んでいるが、
76+
// それが読み込まれる前にterminalを初期化してしまうとバグる。
77+
Promise.all([
78+
import("@xterm/xterm"),
79+
import("@xterm/addon-fit"),
80+
document.fonts.load("0.875rem Inconsolata Variable"),
81+
]).then(([{ Terminal }, { FitAddon }]) => {
82+
if (!abortController.signal.aborted) {
83+
const fromCSS = (varName: string) =>
84+
window.getComputedStyle(document.body).getPropertyValue(varName);
85+
// "--color-" + color_name のように文字列を分割するとTailwindCSSが認識せずCSSの値として出力されない場合があるので注意
86+
const term = new Terminal({
87+
cursorBlink: true,
88+
convertEol: true,
89+
cursorStyle: "bar",
90+
cursorInactiveStyle: "none",
91+
fontSize: 14,
92+
lineHeight: 1.4,
93+
letterSpacing: 0,
94+
fontFamily: "'Inconsolata Variable','Noto Sans JP Variable'",
95+
theme: {
96+
// DaisyUIの変数を使用してテーマを設定している
97+
// TODO: ダークテーマ/ライトテーマを切り替えたときに再設定する?
98+
background: fromCSS("--color-base-300"),
99+
foreground: fromCSS("--color-base-content"),
100+
cursor: fromCSS("--color-base-content"),
101+
selectionBackground: fromCSS("--color-primary"),
102+
selectionForeground: fromCSS("--color-primary-content"),
103+
black: fromCSS("--color-black"),
104+
brightBlack: fromCSS("--color-neutral-500"),
105+
red: fromCSS("--color-red-600"),
106+
brightRed: fromCSS("--color-red-400"),
107+
green: fromCSS("--color-green-600"),
108+
brightGreen: fromCSS("--color-green-400"),
109+
yellow: fromCSS("--color-yellow-700"),
110+
brightYellow: fromCSS("--color-yellow-400"),
111+
blue: fromCSS("--color-indigo-600"),
112+
brightBlue: fromCSS("--color-indigo-400"),
113+
magenta: fromCSS("--color-fuchsia-600"),
114+
brightMagenta: fromCSS("--color-fuchsia-400"),
115+
cyan: fromCSS("--color-cyan-600"),
116+
brightCyan: fromCSS("--color-cyan-400"),
117+
white: fromCSS("--color-base-100"),
118+
brightWhite: fromCSS("--color-white"),
119+
},
120+
});
121+
terminalInstanceRef.current = term;
122+
123+
fitAddonRef.current = new FitAddon();
124+
term.loadAddon(fitAddonRef.current);
125+
// fitAddon.fit();
126+
127+
term.open(terminalRef.current);
128+
129+
// https://github.com/xtermjs/xterm.js/issues/2478
130+
// my.code();ではCtrl+Cでのkeyboardinterruptは要らないので、コピーペーストに置き換えてしまう
131+
term.attachCustomKeyEventHandler((arg) => {
132+
if (
133+
arg.ctrlKey &&
134+
(arg.key === "c" || arg.key === "x") &&
135+
arg.type === "keydown"
136+
) {
137+
const selection = term.getSelection();
138+
if (selection) {
139+
navigator.clipboard.writeText(selection);
140+
return false;
141+
}
142+
}
143+
if (arg.ctrlKey && arg.key === "v" && arg.type === "keydown") {
135144
return false;
136145
}
137-
}
138-
if (arg.ctrlKey && arg.key === "v" && arg.type === "keydown") {
139-
return false;
140-
}
141-
return true;
142-
});
143-
144-
setTermReady(true);
145-
onReadyRef.current?.();
146-
}
147-
});
148-
149-
const observer = new ResizeObserver(() => {
150-
// fitAddon.fit();
151-
const dims = fitAddonRef.current?.proposeDimensions();
152-
if (dims && !isNaN(dims.cols)) {
153-
const rows = Math.max(5, getRowsRef.current?.(dims.cols) ?? 0);
154-
terminalInstanceRef.current?.resize(dims.cols, rows);
155-
}
156-
});
157-
observer.observe(terminalRef.current);
158-
159-
return () => {
160-
abortController.abort("terminal component dismount");
161-
observer.disconnect();
162-
if (fitAddonRef.current) {
163-
fitAddonRef.current.dispose();
164-
fitAddonRef.current = null;
165-
}
166-
if (terminalInstanceRef.current) {
167-
terminalInstanceRef.current.dispose();
168-
terminalInstanceRef.current = null;
169-
}
170-
};
146+
return true;
147+
});
148+
149+
setTermReady(true);
150+
onReadyRef.current?.();
151+
}
152+
});
153+
154+
const observer = new ResizeObserver(() => {
155+
// fitAddon.fit();
156+
const dims = fitAddonRef.current?.proposeDimensions();
157+
if (dims && !isNaN(dims.cols)) {
158+
const rows = Math.max(5, getRowsRef.current?.(dims.cols) ?? 0);
159+
terminalInstanceRef.current?.resize(dims.cols, rows);
160+
}
161+
});
162+
observer.observe(terminalRef.current);
163+
164+
return () => {
165+
abortController.abort("terminal component dismount");
166+
observer.disconnect();
167+
if (fitAddonRef.current) {
168+
fitAddonRef.current.dispose();
169+
fitAddonRef.current = null;
170+
}
171+
if (terminalInstanceRef.current) {
172+
terminalInstanceRef.current.dispose();
173+
terminalInstanceRef.current = null;
174+
}
175+
};
176+
}
171177
}, []);
172178

173179
// テーマが変わったときにterminalのテーマを更新する

0 commit comments

Comments
 (0)