---
title: "TypeScriptのexport/importの書き方｜export class・import typeとtsconfig既定で出るエラー"
url: "https://www.issoh.co.jp/tech/details/3664/"
published: 2024-09-26
updated: 2026-11-02
categories: ["TypeScript"]
publisher: "株式会社一創"
---

# TypeScriptのexport/importの書き方｜export class・import typeとtsconfig既定で出るエラー

TypeScriptの`export`と`import`は構文だけならJavaScriptと同じです。つまずくのは、`export class`が型と値の両方を公開すること、型だけを読み込むときに`import type`を求められること、そして`tsc --init`が作る設定がこの2点を厳しく検査することの3つです。この記事では、TypeScript 7.0.2とNode.js 26.5.0で実際にコンパイル・実行した結果（2026年11月2日）をもとに、書き方とエラーの直し方をまとめます。TypeScript自体の型注釈や始め方は[TypeScriptとは？型注釈と型推論の書き分け、JavaScriptとの違いと始め方](/tech/details/2815/)で扱っています。

## まとめ：export/importとクラスの要点

- トップレベルに`import`か`export`があるファイルはモジュールで、宣言はそのファイルの中に閉じます。`tsc --init`の設定（`moduleDetection: "force"`）では、どちらも無い実装ファイルもモジュールとして扱われます。ただし、宣言ファイルはこの設定の対象外です。
- `export class`は型と値を同時に公開します。受け取る側は`new`にも型注釈にも使えます。`interface`と`type`は型しか公開しません。
- `verbatimModuleSyntax`が有効だと、型だけのものを普通の`import`で読むとTS1484になります。`import type`か`import { type X }`に直します。逆に`import type`で読んだクラスは`new`できません（TS1361）。
- `npm init -y`（npm 11）は`package.json`に`"type": "commonjs"`を書きます。そこへ`tsc --init`の設定を置くと、`export class`などの値宣言にはTS1287、値を読み込むESM形式の`import`にはTS1295が出ます。型だけの宣言や`import type`は対象外です。`"type": "module"`に書き換えれば解消します。
- 迷ったら名前付きexportを使います。`export default`はファイルの主役が1つに決まっているときだけにします。

## export/importの基本の書き方

### export const・export function・export classの名前付きexport

宣言の前に`export`を付けると、その名前で外から読めるようになります。読む側は同じ名前を波かっこで受けます。Node.jsでそのまま動かす前提（`module: "nodenext"`）では、相対パスに拡張子が必要です。以下の例では、出力後の`.js`を書きます。`rewriteRelativeImportExtensions`を有効にする構成では、`.ts`を書いて出力時に変換することもできます。

```
// shapes.ts
export const UNIT = "cm";
export function format(n: number): string {
  return n.toFixed(2) + UNIT;
}
export interface Shape {
  area(): number;
}
export class Rect implements Shape {
  w: number;
  h: number;
  constructor(w: number, h: number) {
    this.w = w;
    this.h = h;
  }
  area() {
    return this.w * this.h;
  }
}

// main.ts
import { format, Rect, type Shape } from "./shapes.js";
const s: Shape = new Rect(2, 3);
console.log(format(s.area()));
```

宣言とは別の行でまとめて公開する書き方もあります。`export { UNIT, format };`のように並べると、ファイル末尾を見るだけで公開範囲がわかります。`export { format as formatCm };`のように`as`で別名にもできます。

よく検索される「export const」と`default`の組み合わせは書けません。`export default const LIMIT = 10;`は7.0.2でTS1109（Expression expected）になります。`default`の後ろに置けるのは式と、`function`・`class`・`interface`（TypeScript）などの宣言で、`const`宣言は置けません。定数を既定の公開にしたいなら`const LIMIT = 10; export default LIMIT;`と2行に分けます。

### export default classと名前付きexportの選び分け

`export default`はファイルに1つだけ置けて、読む側が好きな名前を付けます。

```
// circle.ts
export default class Circle {
  r: number;
  constructor(r: number) {
    this.r = r;
  }
  area() {
    return Math.PI * this.r ** 2;
  }
}

// main.ts
import Circle from "./circle.js";
import MyCircle from "./circle.js";
```

2行目のように別名で読めてしまうことが、`default`の弱点です。同じクラスがファイルごとに違う名前で呼ばれ、エディタの一括リネームも検索も効きにくくなります。名前付きexportも`as`で読み込み側の名前を変更できますが、公開名は明示されます。公開名を`Rectt`のように間違えるとTS2724（`Did you mean 'Rect'?`）でコンパイル時に止まります。

判断はこう決めます。部品を複数公開するファイル、ライブラリの公開口、チームで共有するコードは名前付きexportにします。`default`を使うのは、フレームワークがファイル単位で`default`を要求する場合（Next.jsの`page.tsx`など）と、ファイルの主役が1つに決まっていて他に何も公開しない場合だけです。両方を混ぜると`import Circle, { Rect } from ...`のように読む側の書き方が揃わなくなります。

### 再exportによる公開窓口の集約（export \* as・export type \*）

複数のファイルを1か所から公開する「まとめ口」（`index.ts`）には再exportを使います。次の4行は7.0.2でエラーなくコンパイルできました。

```
// index.ts
export * as geo from "./shapes.js";
export type * from "./shapes.js";
export { Rect as Rectangle } from "./shapes.js";
export { default as Circle } from "./circle.js";
```

`export * as geo`は名前空間オブジェクトとしてまとめて公開し、読む側は`geo.Rect`と書きます。`export type *`（TypeScript 5.0で追加）は型だけを再公開し、出力されたJavaScriptからは行ごと消えます。`default`を再公開するときは`{ default as Circle }`と名前を付けます。

まとめ口は便利ですが、バンドラーを通さずに動かすと、値を1つimportするだけでも、出力JavaScriptに残る再export先は読み込み対象になります。型だけの再export先は、この理由では読み込まれません。アプリ内部のコードまで`index.ts`経由にする必要はありません。外部に見せる境界にだけ置きます。

## export classで公開されるもの（型と値）

### 宣言ごとに作られる型と値の対応

モジュールとクラスは、どちらも「コードをまとめる単位」に見えますが、役割は重なりません。モジュールはファイル単位のスコープの境界で、クラスはその中に置く宣言の1つです。宣言によって、型を作るのか値を作るのかが決まっています。TypeScriptハンドブックの[Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html)にある表を整理すると次のとおりです。

| 宣言                 | 型    | 値    | newできるか           |
| ------------------ | ---- | ---- | ----------------- |
| `class`            | 作る   | 作る   | 抽象クラス・アクセス制限を除き可能 |
| `interface`        | 作る   | 作らない | できない              |
| `type`             | 作る   | 作らない | できない              |
| `enum`             | 作る   | 作る   | できない              |
| `namespace`（実装あり）  | 名前空間 | 作る   | できない              |
| `function`・`const` | 作らない | 作る   | 値の構築シグネチャ次第       |

`export class Rect`と書くと、型の`Rect`（インスタンスの形）と値の`Rect`（コンストラクター関数）が同じ名前で公開されます。型定義ファイルを出力すると、`export declare class Rect`として残ります。一方`export interface Shape`は出力JavaScriptに何も残りません。`interface`を値として参照するとTS2693、型しか持たない`namespace`を値として参照するとTS2708になります。

### クラス型の構造的互換性と#プライベートフィールドによる区別

TypeScriptの型は構造で比べるので、クラス型も例外ではありません。次の`b`は`new`していないただのオブジェクトですが、`Rect`と同じプロパティとメソッドを持つので代入できます。

```
import { Rect } from "./shapes.js";
const b: Rect = { w: 1, h: 1, area: () => 1 };

class Secret {
  #key = 1;
  get() {
    return this.#key;
  }
}
const fake: Secret = { get: () => 2 };
```

`b`はエラーになりません。`fake`は7.0.2でTS2741（`Property '#key' is missing`）になります。ECMAScriptの`#`付きプライベートフィールドを持つクラスは、そのクラスから作ったインスタンスしか受け付けなくなります。「このクラスの実体だけを受け取りたい」なら`#`フィールドを1つ持たせ、形だけ合えばよいなら`interface`で受けるのが筋です。

## import typeが必要になる条件（verbatimModuleSyntax）

### interfaceを普通にimportするとTS1484になる理由

[verbatimModuleSyntax](https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax)は、`type`の付いた`import`だけを出力から消し、付いていない`import`はそのまま残すオプションです。TypeScript 5.9以降の`tsc --init`は、この設定を`true`にした`tsconfig.json`を作ります（[TypeScript 5.9のリリースノート](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-9.html)）。7.0.2・6.0.3・5.9.3の`--init`はどれも`verbatimModuleSyntax: true`・`moduleDetection: "force"`・`module: "nodenext"`を書き出しました。

この設定で`import { Shape, Rect } from "./shapes.js"`と書くと、型しかない`Shape`にTS1484が出ます。TS1484を放置したまま出力したJavaScriptには`import { Shape, Rect }`がそのまま残り、Node.jsで実行すると`SyntaxError: The requested module './shapes.js' does not provide an export named 'Shape'`で止まりました。tscはエラーがあっても既定では出力を止めず、今回の検証では`outDir`に指定した`dist`へ書き出すので、終了コードを見ずに実行すると実行時に初めて気づきます。直し方は2通りです。

```
import type { Shape } from "./shapes.js";
import { Rect } from "./shapes.js";

// または、上の2行の代わりに次の1行を使います。
// import { type Shape, Rect } from "./shapes.js";
```

1つのファイルから型と値を両方読むなら、下の`{ type Shape, Rect }`の方が1行で済みます。`export`側も同じで、型だけを再公開するときは`export type { Shape }`と書きます。

### import typeで読み込んだクラスの値利用制限（TS1361）

クラスは値でもあるので、`import type { Rect }`で読むと型注釈には使えますが`new Rect(2, 3)`はTS1361（`'Rect' cannot be used as a value because it was imported using 'import type'`）になります。インスタンスを作るファイルでは`type`を付けずに読みます。引数や戻り値の型にだけ使うファイルなら`import type`で十分で、そのファイルの出力からは`import`行が消えます。

通常の`import`と`import type`は、読み込んだ名前を実行時に使うかどうかで選びます。`new`・関数呼び出し・`instanceof`に使うなら`import`、型の位置にしか書かないなら`import type`です。

## `tsc --init`の設定で起きるモジュールのエラー

### ファイルのモジュール判定（moduleDetectionとexport {}）

[TypeScriptハンドブックのModules](https://www.typescriptlang.org/docs/handbook/2/modules.html)は、トップレベルの`import`・`export`を含むファイルをモジュール、含まないファイルをスクリプトと定義しています。スクリプトの宣言はグローバルに置かれるので、別ファイルの同名変数とぶつかります。

`import`も`export`も無い`a.ts`と`b.ts`の両方に`const count = 1;`を書き、`moduleDetection`も`module`も指定しない最小の`tsconfig.json`でコンパイルすると、7.0.2でも6.0.3でもTS2451（`Cannot redeclare block-scoped variable 'count'`）が両方のファイルに出ました。`moduleDetection: "force"`を指定するとエラーは0件になります。既定の`auto`でも、`module: "nodenext"`のもとで`package.json`が`"type": "module"`なら、`import`や`export`の無いファイルもモジュールとして扱われます（[moduleDetectionの説明](https://www.typescriptlang.org/tsconfig/#moduleDetection)）。`tsc --init`の設定はこの`force`を含むので、新しく作ったプロジェクトではこの衝突は起きません。古い`tsconfig.json`のままの場合は、ファイル末尾に`export {};`を1行足してもモジュール扱いになります。何も公開しない`export`ですが、モジュールにする目的で使われる定番の書き方です。

### nodenextのESM相対importに必要な拡張子（TS2835）

`module: "nodenext"`のもとでESMとして扱われるファイルでは、`import { Rect } from "./shapes"`がTS2835（`Relative import paths need explicit file extensions ... Did you mean './shapes.js'?`）になります。Node.jsのESMは拡張子を補わないので、TypeScriptも出力後のパスを書かせます。

Vite・esbuild・webpackなどのバンドラーが解決するプロジェクトなら、`module: "preserve"`（`moduleResolution`は`bundler`になります）にすると拡張子なしで書けます。Node.jsで直接動かすのか、バンドラーを通すのかで設定を選び、混ぜないことが大事です。

### CommonJSとverbatimModuleSyntaxによるTS1287・TS1295

npm 11.17.0の`npm init -y`は、`package.json`に`"type": "commonjs"`を書き出します。そこに`tsc --init`で作った設定を置き、普通に`export class`と`import`を書くと、7.0.2で次の2つが出ました。

- `export class Counter`の行：TS1287（`A top-level 'export' modifier cannot be used on value declarations in a CommonJS module when 'verbatimModuleSyntax' is enabled.`）
- `import { Counter }`の行：TS1295（`ECMAScript imports and exports cannot be written in a CommonJS file under 'verbatimModuleSyntax'.`）

`package.json`に`type`が無い場合も、CommonJS扱いになるので同じエラーです。ESMで書くつもりなら`"type": "module"`に書き換えます。2つのエラーはそれで消えます。CommonJSのまま書くなら、後述の`export =`と`import ... = require()`を使います。ESMとCommonJSの読み込みの違いやNode.jsでの相互運用は[ESMとCommonJSの違い｜require(esm)で変わった相互運用と移行手順【Node.js 26実測】](/tech/details/12462/)で詳しく扱っています。

## Node.jsで.tsを直接実行するときのクラスとimportの制約

Node.jsは型注釈を取り除くだけで`.ts`を実行できます（型の除去は v23.6.0・v22.18.0で既定有効、v25.2.0・v24.12.0で安定版）。変換が要る構文は扱わず、[Node.jsのTypeScriptドキュメント](https://nodejs.org/api/typescript.html)は`enum`、実行時のコードを含む`namespace`、パラメータプロパティ、`import`エイリアスを未対応の代表に挙げています。Node.js 26.5.0で試すと、モジュールとクラスまわりで次の3点に当たりました。

- `constructor(public w: number, public h: number) {}`のパラメータプロパティを持つクラスを読むと、`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`（TypeScript parameter property is not supported in strip-only mode）で止まります。フィールドを宣言してコンストラクター内で代入する書き方に直すと動きました。
- `type`を付けずに`interface`を読むと、実行時に`does not provide an export named 'Shape'`になります。型の除去は読み込み先のファイルを見ずに注釈を消すだけなので、`type`の無い`import`は値の読み込みとして残ります。`import type`が必須です。
- `import`のパスは`.ts`まで書きます。型検査だけを行うなら`noEmit: true`と`allowImportingTsExtensions: true`を指定します（無いとTS5097）。tscで出力もするなら`rewriteRelativeImportExtensions`を付けます。`.ts`のまま書けて、出力側のパスは`.js`に書き換わります。

このうちパラメータプロパティなど、JavaScriptへの変換が必要な構文は`erasableSyntaxOnly`（TypeScript 5.8で追加）で検出でき、パラメータプロパティはTS1294（`This syntax is not allowed when 'erasableSyntaxOnly' is enabled.`）になりました。`type`の付け忘れはこのオプションでは見つからないので、`verbatimModuleSyntax`と併用します。Node.jsでそのまま動かすコードを書くなら、最初から両方を入れておきます。

## CommonJSのexport =と7.0で削除されたモジュール設定

### export =とimport = requireの書き方

CommonJSとして出力するファイルで、`module.exports`にクラスを丸ごと入れたい場合はTypeScript独自の`export =`を使います。読む側は`import ... = require()`で受けます。

```
// counter.ts（"type": "commonjs"）
class Counter {
  n = 0;
  inc() {
    return ++this.n;
  }
}
export = Counter;

// main.ts
import Counter = require("./counter.js");
console.log(new Counter().inc());
```

この組み合わせは`verbatimModuleSyntax`が有効でもエラーにならず、`module.exports = Counter;`が出力されて実行結果は`1`でした。6.0以前の記事では`export =`のモジュールを`import Counter from`で読むために`esModuleInterop`を有効にする方法がよく紹介されますが、7.0では`esModuleInterop: false`と`allowSyntheticDefaultImports: false`の指定自体が削除され、どちらも常に有効です。

### 7.0で削除されたmodule・moduleResolution・baseUrlの移行先

2026年7月8日に公開された[TypeScript 7.0の告知](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/)は、次の設定を削除済みとしています。7.0.2では該当する値を書くとTS5108などで止まります。

| 削除された設定                             | 移行先                       |
| ----------------------------------- | ------------------------- |
| `module: amd / umd / system / none` | `esnext`か`preserve`＋バンドラー |
| `moduleResolution: node / node10`   | `nodenext`か`bundler`      |
| `moduleResolution: classic`         | `nodenext`か`bundler`      |
| `baseUrl`                           | `paths`をプロジェクト基準の相対パスで書く  |
| `esModuleInterop: false`            | 指定を消す（常に有効）               |

告知の文面は「systemjs」ですが、`tsconfig.json`に書く実際の値は`system`です。自分の設定を探すときは`"module": "system"`で検索します。`baseUrl`を外して`paths`を書き直す手順は[tsconfigのbaseUrl非推奨（TS5101）の直し方とignoreDeprecations・TypeScript 7.0の削除エラー](/tech/details/11519/)、6.0を経由する移行全体は[TypeScript 6.0とは｜5から6への移行手順とts5to6・7.0との違い【6.0.3対応】](/tech/details/11066/)、7.0そのものの変更点は[TypeScript 7（TS7）とは？Go製tscで約10倍速く｜7.0.2への移行とtsgo・7.1 betaの現状](/tech/details/10175/)にまとめています。

## 循環importで起きるReferenceError（const・classとfunctionの差）

`a.ts`と`b.ts`が互いを`import`し、`a.ts`が読み込まれた直後に`b`を呼ぶ構成を作りました。エントリーを`b.js`にすると、先に`a.js`が評価され、その時点で`b.js`の本体はまだ実行されていません。

```
// a.ts
import { b } from "./b.js";
export const a = () => "a";
console.log(b());

// b.ts
import { a } from "./a.js";
export function b() {
  return "b";
}
```

この形は`b`と表示されて成功しました。`function`宣言はモジュールのリンク時に初期化されるからです。`b.ts`を`export const b = () => "b";`にすると`ReferenceError: Cannot access 'b' before initialization`、`export class B`にして`new B()`を呼ぶと`Cannot access 'B' before initialization`になりました。クラス宣言は`const`と同じく、宣言の行が実行されるまで使えません。

TypeScriptはこの循環を型検査では検出しません。循環をなくすのが根本の対処で、両方が使う定数や型を3つ目のファイルに出すと、多くの場合は解消します。

## クラスを作るべきでない場面とモジュール分割の判断

モジュールがファイル単位で名前を閉じてくれる以上、「関連する関数をまとめるためだけのクラス」は要りません。`static`メソッドしか持たないクラス（`class StringUtil { static trim() {} }`のような形）は、関数を名前付きexportに並べたモジュールに置き換えます。インスタンスを作らないので`new`の意味が無く、使わない関数も`StringUtil`ごと読み込むことになります。

クラスにする価値があるのは、状態（フィールド）とそれを変える操作をひとまとめにしたいとき、`instanceof`で種類を判定したいとき、`#`フィールドで外から触れない値を持ちたいときです。データの形を決めたいだけなら`interface`か`type`で足ります。値オブジェクトや集約をクラスで組む実装例は[DDDの実装手順：値オブジェクトから集約まで既存コードを段階移行するTypeScript実例](/tech/details/17460/)にあります。

モジュールの分け方は、一緒に変更されるものを同じファイルに置くのが基本です。ファイルを分けた結果、値の相互importが生じ、初期化順序への依存や変更の連鎖が増えたら、分割を見直します。型だけの相互参照は実行時の循環依存にはなりません。前の章の循環importの形になる前に、共有部分を切り出します。

## よくある質問

### export interfaceとexport typeの違いは何ですか？

どちらも型だけを公開し、出力されるJavaScriptには何も残りません。違いは宣言の性質です。`interface`は同じ名前で2回書くと中身が合わさります（宣言のマージ）が、片方だけ`export`するとTS2395になります。`type`は同名で再宣言できず、ユニオン型や条件型など`interface`で書けない型も表せます。オブジェクトの形を公開するならどちらでも構いません。

### importのパスに.js拡張子は必要ですか？

`module: "nodenext"`（または`node16`）でESMとして扱われるファイルでは必要で、書かないとTS2835です。通常のJavaScript出力では`.js`を書きます。`rewriteRelativeImportExtensions`を使う構成やNode.jsで直接実行する構成では、`.ts`を指定できます。`module: "preserve"`でバンドラーが解決する構成なら省略できます。

### namespaceはモジュールの代わりに使えますか？

新しいコードでは使いません。ファイル単位のモジュールで名前は閉じられるので、`namespace`を重ねる理由がありません。実行時のコードを含む`namespace`はNode.jsの型の除去でも動きません。古い`module Foo {}`という書き方は、6.0.3でも7.0.2でもTS1540（`A 'namespace' declaration should not be declared using the 'module' keyword.`）になります。`namespace`キーワードに置き換えておきます。

### モジュールとクラスは同じファイルに書いてよいですか？

書けますし、普通の書き方です。ファイルがモジュールで、その中で`export class`を宣言する形になります。1ファイル1クラスにする決まりはTypeScriptにはありません。一緒に変更される小さなクラスや補助関数は同じファイルにまとめ、大きく育ったら分けます。

### 型定義ファイル（.d.ts）もモジュールとして扱われますか？

モジュールにもグローバルな宣言ファイルにもなります。通常の`.d.ts`には`moduleDetection: "force"`が適用されず、トップレベルに`import`か`export`があればモジュール、無ければグローバルな宣言になります。グローバルな型を足すつもりで書いた`.d.ts`に`import`を1行足すと、その宣言はモジュールの中に閉じてしまい、他のファイルから見えなくなります。その場合は`declare global { }`で囲みます。

## 関連記事

- [ESMとCommonJSの違い｜require(esm)で変わった相互運用と移行手順【Node.js 26実測】](/tech/details/12462/)
- [TypeScriptとは？型注釈と型推論の書き分け、JavaScriptとの違いと始め方【7.0.2対応】](/tech/details/2815/)
- [TypeScript 7（TS7）とは？Go製tscで約10倍速く｜7.0.2への移行とtsgo・7.1 betaの現状](/tech/details/10175/)
- [tsconfigのbaseUrl非推奨（TS5101）の直し方とignoreDeprecations・TypeScript 7.0の削除エラー](/tech/details/11519/)
- [TypeScript 6.0とは｜5から6への移行手順とts5to6・7.0との違い【6.0.3対応】](/tech/details/11066/)

---

出典: [TypeScriptのexport/importの書き方｜export class・import typeとtsconfig既定で出るエラー](<https://www.issoh.co.jp/tech/details/3664/>)（株式会社一創）
