Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions app/pagesList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ export const pagesList = [
{ id: 10, title: "非同期処理②: Async/Await" },
],
},
{
id: "typescript",
lang: "TypeScript",
description: "にゃー",
pages: [
{ id: 1, title: "TypeScriptへようこそ" },
{ id: 2, title: "基本的な型と型推論" },
{ id: 3, title: "オブジェクト、インターフェース、型エイリアス" },
{ id: 4, title: "関数の型定義" },
{ id: 5, title: "型を組み合わせる" },
{ id: 6, title: "ジェネリクス" },
{ id: 7, title: "クラスとアクセス修飾子" },
{ id: 8, title: "非同期処理とユーティリティ型" },
],
},
{
id: "cpp",
lang: "C++",
Expand Down
2 changes: 1 addition & 1 deletion app/terminal/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function EditorComponent(props: EditorProps) {
const { files, writeFile } = useEmbedContext();
const code = files[props.filename] || props.initContent;
useEffect(() => {
if (!files[props.filename]) {
if (!files[props.filename] && props.initContent) {
writeFile({ [props.filename]: props.initContent });
}
}, [files, props.filename, props.initContent, writeFile]);
Expand Down
19 changes: 15 additions & 4 deletions app/terminal/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ interface SampleConfig {
replInitContent?: string; // ReplOutput[] ではない。stringのパースはruntimeが行う
editor: Record<string, string> | false;
exec: string[] | false;
readonlyFiles?: string[];
}
const sampleConfig: Record<RuntimeLang, SampleConfig> = {
python: {
Expand All @@ -65,8 +66,7 @@ const sampleConfig: Record<RuntimeLang, SampleConfig> = {
},
ruby: {
repl: true,
replInitContent:
'irb(main):001:0> puts "Hello, World!"\nHello, World!',
replInitContent: 'irb(main):001:0> puts "Hello, World!"\nHello, World!',
editor: {
"main.rb": 'puts "Hello, World!"',
},
Expand All @@ -83,10 +83,12 @@ const sampleConfig: Record<RuntimeLang, SampleConfig> = {
typescript: {
repl: false,
editor: {
"main.ts":
// main.tsにすると出力ファイルがjavascriptのサンプルと被る
"main2.ts":
'function greet(name: string): void {\n console.log("Hello, " + name + "!");\n}\n\ngreet("World");',
},
exec: ["main.ts"],
exec: ["main2.ts"],
readonlyFiles: ["main2.js"],
},
cpp: {
repl: false,
Expand Down Expand Up @@ -131,6 +133,15 @@ function RuntimeSample({
{config.exec && (
<ExecFile filenames={config.exec} language={lang} content="" />
)}
{config.readonlyFiles?.map((filename) => (
<EditorComponent
key={filename}
language={getAceLang(lang)}
filename={filename}
initContent=""
readonly
/>
))}
</div>
);
}
Expand Down
21 changes: 17 additions & 4 deletions app/terminal/typescript/runtime.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import type { CompilerOptions } from "typescript";
import type { ScriptTarget, CompilerOptions } from "typescript";
import type { VirtualTypeScriptEnvironment } from "@typescript/vfs";
import {
createContext,
Expand All @@ -14,7 +14,11 @@ import { useEmbedContext } from "../embedContext";
import { ReplOutput } from "../repl";
import { RuntimeContext } from "../runtime";

export const compilerOptions: CompilerOptions = {};
export const compilerOptions: CompilerOptions = {
lib: ["ESNext", "WebWorker"],
target: 10 satisfies ScriptTarget.ES2023,
strict: true,
};

const TypeScriptContext = createContext<{
init: () => void;
Expand Down Expand Up @@ -78,7 +82,12 @@ export function TypeScriptProvider({ children }: { children: ReactNode }) {
}

export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {
const { init, tsEnv } = useContext(TypeScriptContext);
const { init: tsInit, tsEnv } = useContext(TypeScriptContext);
const { init: jsInit } = jsEval;
const init = useCallback(() => {
tsInit();
jsInit?.();
}, [tsInit, jsInit]);

const { writeFile } = useEmbedContext();
const runFiles = useCallback(
Expand Down Expand Up @@ -129,6 +138,10 @@ export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {
)
);

for (const filename of Object.keys(files)) {
tsEnv.deleteFile(filename);
}

console.log(emitOutput);
const jsOutputs = jsEval.runFiles(
[emitOutput.outputFiles[0].name],
Expand All @@ -149,5 +162,5 @@ export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {
}

function getCommandlineStr(filenames: string[]) {
return `tsc ${filenames.join(" ")}`;
return `npx tsc ${filenames.join(" ")} && node ${filenames[0].replace(/\.ts$/, ".js")}`;
}
136 changes: 136 additions & 0 deletions public/docs/typescript-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# 第1章: TypeScriptへようこそ

JavaScriptの経験がある皆さん、TypeScriptの世界へようこそ。
この章では、TypeScriptがどのような言語であるか、なぜ現代のWeb開発のスタンダードとなっているのかを理解し、実際に開発環境を整えて最初のコードを実行するところまでを学びます。

## TypeScriptとは?

TypeScriptは、Microsoftによって開発されているオープンソースのプログラミング言語です。一言で言えば、**「型(Type)を持ったJavaScript」**です。

重要な特徴は以下の通りです:

* **JavaScriptのスーパーセット(上位互換):** すべての有効なJavaScriptコードは、有効なTypeScriptコードでもあります。つまり、今日から既存のJS知識をそのまま活かせます。
* **静的型付け:** JavaScriptは実行時に変数の型が決まる「動的型付け言語」ですが、TypeScriptはコンパイル時(コードを書いている途中やビルド時)に型をチェックする「静的型付け言語」としての性質を持ちます。
* **コンパイル(トランスパイル):** ブラウザやNode.jsはTypeScriptを直接理解できません。TypeScriptコンパイラ(`tsc`)を使って、標準的なJavaScriptファイルに変換してから実行します。

## なぜTypeScriptか?

「わざわざ型を書くのは面倒だ」と感じるかもしれません。しかし、中〜大規模な開発においてTypeScriptは以下の強力なメリットを提供します。

1. **型安全性(バグの早期発見):**
`undefined` のプロパティを読み取ろうとしたり、数値を期待する関数に文字列を渡したりするミスを、コードを実行する前にエディタ上で警告してくれます。
2. **強力なエディタサポート:**
VS Codeなどのエディタでは、型情報に基づいた正確なコード補完(IntelliSense)が効きます。APIの仕様をドキュメントで調べなくても、ドット`.`を打つだけで利用可能なメソッドが表示されます。
3. **リファクタリングの容易さ:**
変数名や関数名を変更する際、型情報があるおかげで、影響範囲を自動的に特定し、安全に一括置換できます。

## 環境構築

それでは、実際にTypeScriptを動かす環境を作りましょう。

### プロジェクトの作成とTypeScriptのインストール

今回はローカル環境にTypeScriptをインストールする方法を採用します。適当なディレクトリを作成し、ターミナルで以下のコマンドを実行してください。

※あらかじめ [Node.js](https://nodejs.org/) がインストールされていることを前提とします。

```bash
# プロジェクトフォルダの作成と移動
mkdir ts-tutorial
cd ts-tutorial

# package.jsonの初期化
npm init -y

# TypeScriptのインストール(開発用依存関係として)
npm install --save-dev typescript
```

インストールが完了したら、バージョンを確認してみましょう。

```bash
npx tsc --version
# Output: Version 5.x.x (バージョンは時期によります)
```

## 最初のTypeScript

いよいよ最初のTypeScriptコードを書いてみましょう。

### コードの記述

エディタで `hello.ts` というファイルを作成し、以下のコードを記述します。
JavaScriptと似ていますが、変数宣言の後ろに `: string` という「型注釈(Type Annotation)」が付いている点に注目してください。

```ts:hello.ts
// 変数messageにstring型(文字列)を指定
const message: string = "Hello, TypeScript World!";

// 数値を渡そうとするとエディタ上でエラーになります(後ほど解説)
console.log(message);
```

### コンパイルと実行

このままではNode.jsで実行できないため、JavaScriptにコンパイルします。

```bash
npx tsc hello.ts
```

エラーが出なければ、同じフォルダに `hello.js` というファイルが生成されています。中身を確認すると、型注釈が取り除かれた普通のJavaScriptになっているはずです。

生成されたJSファイルをNode.jsで実行します。

```ts-exec:hello.ts
Hello, TypeScript World!
```

これがTypeScript開発の基本的なサイクル(記述 → コンパイル → 実行)です。

このウェブサイトでは上のようにコードを編集して実行ボタンを押すとコンパイルと実行を行うことができる環境を埋め込んでいます。

またコンパイル後のjsファイルの内容も以下のように確認できます。

```js-readonly:hello.js
"use strict";
// 変数messageにstring型(文字列)を指定
const message = "Hello, TypeScript World!";
// 数値を渡そうとするとエディタ上でエラーになります(後ほど解説)
console.log(message);
```

## tsconfig.json: コンパイラの設定

毎回 `npx tsc hello.ts` のようにファイル名を指定するのは手間ですし、プロジェクト全体の設定も管理しづらくなります。そこで、`tsconfig.json` という設定ファイルを使用します。

以下のコマンドで初期設定ファイルを生成します。

```bash
npx tsc --init
```

生成された `tsconfig.json` には多くの設定項目がありますが、基本として以下の設定が有効(コメントアウトされていない状態)になっているか確認してください。

```json
{
"compilerOptions": {
"target": "es2016", /* コンパイル後のJSのバージョン */
"module": "commonjs", /* モジュールシステム */
"strict": true, /* 厳格な型チェックを有効にする(重要) */
"esModuleInterop": true, /* CommonJSモジュールとの互換性 */
"forceConsistentCasingInFileNames": true, /* ファイル名の大文字小文字を区別 */
"skipLibCheck": true /* 定義ファイルのチェックをスキップ */
}
}
```

### 設定ファイルを使ったコンパイル

`tsconfig.json` があるディレクトリでは、ファイル名を指定せずに以下のコマンドだけで、ディレクトリ内のすべてのTypeScriptファイルが設定に基づいてコンパイルされます。

```bash
npx tsc
```

> **Note:** `strict: true` はTypeScriptの恩恵を最大限に受けるために非常に重要です。このチュートリアルでは常にこの設定が有効であることを前提に進めます。
Loading