404 Not Found

404 Not Found


nginx

「`path` および `url` モジュール:クロスプラットフォームでのパスおよび URL の処理」

1. ストーリー:区切り文字が原因で起きたデプロイの惨事

ボブが開発したCLIツールは、macOS上では問題なく動作しました。パス連結には/が使用され、設定ファイルの読み込みもログの書き込みも問題なく機能しました。しかし、Linuxサーバーにデプロイすると、プログラムは直ちにエラーENOENT: no such file or directoryを報告しました。調査の結果、ボブがコード内でパス区切り文字として / をハードコーディングしていたのに対し、Windows での特定のパス処理ロジックでは \ が使用されていたため、パスの解析で混乱が生じていたことが判明した。この出来事を通じて、ボブは path モジュールの目的、すなわち「パスを手動で連結してはならない」ということを深く理解することになった。

(1) 以下のことを学びます


2. path モジュールの主要なメソッド

path モジュールは、Node.js に組み込まれているモジュールであり、ファイルパスの連結、解析、およびフォーマットを行うツールを提供し、オペレーティングシステム間のパス区切り文字の違いを自動的に処理します。

(1) path.join — パスの連結

path.join() 複数のパスセグメントを、現在のシステムの区切り文字を使用して自動的に1つの標準化されたパスに結合します。

▶ 例:path.join を使った基本的なパスの連結

JAVASCRIPT
const path = require('path');

const fullPath = path.join('/app', 'src', 'utils', 'helper.js');
console.log(fullPath);
// macOS/Linux: /app/src/utils/helper.js
// Windows:     \app\src\utils\helper.js

▶ 例:path.join による自動正規化

JAVASCRIPT
const path = require('path');

console.log(path.join('/app', '../config', 'settings.json'));
// /config/settings.json

console.log(path.join('src', '.', 'index.js'));
// src/index.js

(2) path.resolve — 絶対パスに変換する

path.resolve() 絶対パスが得られるまで、パスを右から左へと連結する。絶対パスが得られない場合は、現在の作業ディレクトリを基点とする。

▶ 例:path.resolve の基本的な使い方

JAVASCRIPT
const path = require('path');

console.log(path.resolve('src', 'index.js'));
// /current/working/dir/src/index.js

console.log(path.resolve('/app', 'src', 'index.js'));
// /app/src/index.js

console.log(path.resolve('/app', '/tmp', 'file.txt'));
// /tmp/file.txt(Use the absolute path on the far right as the reference.)

(3) path.parse および path.format — パスの解析と再構築

path.parse() はパスを、root、dir、base、ext、name の 5 つの部分に分割し、path.format() はオブジェクトをパス文字列として再構成します。

▶ 例:path.parse を使用してパスを解析する

JAVASCRIPT
const path = require('path');

const parsed = path.parse('/app/src/utils/helper.js');
console.log(parsed);
TEXT
{
  root: '/',
  dir: '/app/src/utils',
  base: 'helper.js',
  ext: '.js',
  name: 'helper'
}
100%
graph LR
    A["/app/src/utils/helper.js"] --> B["root: /"]
    A --> C["dir: /app/src/utils"]
    A --> D["base: helper.js"]
    D --> E["name: helper"]
    D --> F["ext: .js"]
    style A fill:#4CAF50,color:#fff
    style B fill:#FF9800,color:#fff
    style C fill:#2196F3,color:#fff
    style D fill:#9C27B0,color:#fff
    style E fill:#E91E63,color:#fff
    style F fill:#FF5722,color:#fff

▶ 例:path.format—パスの書き換え

JAVASCRIPT
const path = require('path');

const filePath = path.format({
  dir: '/app/src/utils',
  base: 'helper.js'
});
console.log(filePath);
// /app/src/utils/helper.js

(4) path.extname / path.basename / path.dirname

これら3つの方法は、それぞれファイル拡張子、ファイル名、およびパスのディレクトリ部分を抽出します。

▶ 例:パスの各部分を抽出する

JAVASCRIPT
const path = require('path');

const filePath = '/app/src/utils/helper.js';

console.log(path.extname(filePath));   // .js
console.log(path.basename(filePath));  // helper.js
console.log(path.basename(filePath, '.js')); // helper
console.log(path.dirname(filePath));   // /app/src/utils

3. クロスプラットフォームのパス定数

パス区切り文字や環境変数の区切り文字はオペレーティングシステムによって異なります。path モジュールでは、こうした違いに対応するための定数が用意されています。

(1) path.sep — パス区切り文字

プラットフォーム パス区切り
macOS / Linux /
Windows \

(2) path.delimiter — 環境変数の区切り文字

プラットフォーム パス区切り文字
macOS / Linux :
Windows ;

▶ 例:path.sep および path.delimiter の使用

JAVASCRIPT
const path = require('path');

console.log('Delimiter:', JSON.stringify(path.sep));
// macOS/Linux: "/"
// Windows:     "\\"

const envPaths = process.env.PATH.split(path.delimiter);
console.log('PATH Number of entries:', envPaths.length);

4. url モジュールと URL コンストラクタ

(1) url.parse(非推奨)と new URL() の比較

url.parse() は API の旧バージョンであり、非推奨とマークされています。WHATWG 標準に準拠した new URL() コンストラクタの使用をお勧めします。

機能 url.parse() new URL()
標準 旧版 Node.js WHATWG 標準
ステータス 非推奨 推奨
エラー処理 何も通知せずに null を返す TypeError をスローする
searchParams None 組み込みの URLSearchParams
パフォーマンス 遅い 速い

▶ 例:url.parse の旧来の使い方(推奨されません)

JAVASCRIPT
const url = require('url');

const parsed = url.parse('https://example.com/api/users?name=Bob&page=1');
console.log(parsed.hostname);
console.log(parsed.query);
TEXT
example.com
name=Bob&page=1

▶ 例:new URL() の推奨使用方法

JAVASCRIPT
const myUrl = new URL('https://example.com/api/users?name=Bob&page=1');

console.log(myUrl.hostname);
console.log(myUrl.pathname);
console.log(myUrl.searchParams.get('name'));
console.log(myUrl.searchParams.get('page'));
TEXT
example.com
/api/users
Bob
1

(2) url.searchParams —— クエリパラメータの操作

URL オブジェクトの searchParams プロパティは URLSearchParams のインスタンスであり、パラメータの追加、削除、更新、および照会を行うための便利なメソッドを提供しています。

▶ 例:searchParams に対する CRUD 操作

JAVASCRIPT
const myUrl = new URL('https://example.com/search');
myUrl.searchParams.set('q', 'nodejs');
myUrl.searchParams.set('lang', 'zh');
myUrl.searchParams.append('tag', 'backend');
myUrl.searchParams.append('tag', 'tutorial');
myUrl.searchParams.delete('lang');

console.log(myUrl.toString());
// https://example.com/search?q=nodejs&tag=backend&tag=tutorial

console.log(myUrl.searchParams.getAll('tag'));
// [ 'backend', 'tutorial' ]

(3) url.fileURLToPath — ファイルの URL をローカルパスに変換します

ESMモジュールでは、import.meta.urlfile://形式のURLを返しますが、これをurl.fileURLToPath()を使用してファイルシステムパスに変換する必要があります。

▶ 例:fileURLToPath による変換

JAVASCRIPT
const { fileURLToPath } = require('url');

const fileUrl = 'file:///app/src/index.js';
const filePath = fileURLToPath(fileUrl);
console.log(filePath);
// macOS/Linux: /app/src/index.js
// Windows:     \app\src\index.js

5. __dirname / __filename 対 import.meta.url

これが、現在のファイルパスを取得する点において、CJSとESMのモジュールシステム間の主な違いです。

機能 __dirname / __filename import.meta.url
モジュールシステム CJS (require) ESM (import)
戻り値の型 絶対パス文字列 file:// URL 文字列
利用可能性 グローバル変数、直接使用 fileURLToPath が必要
ディレクトリパス __dirname(直接取得) dirname(fileURLToPath()) が必要
ファイルパス __filename(直接アクセス) fileURLToPath() による変換が必要

▶ 例:CJSでの __dirname の使用

JAVASCRIPT
const path = require('path');

console.log('__dirname:', __dirname);
console.log('__filename:', __filename);

const configPath = path.join(__dirname, 'config', 'settings.json');
console.log(configPath);

▶ 例:ESMでのimport.meta.urlの使用

JAVASCRIPT
import { fileURLToPath } from 'url';
import path from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

console.log('__dirname:', __dirname);
console.log('__filename:', __filename);

6. 一般的なパス処理手法のクイックリファレンス表

メソッド パラメータ 戻り値 目的
path.join() ...paths string パスセグメントを連結し、自動的に正規化する
path.resolve() ...paths string 絶対パスに変換
path.parse() pathString object パスを構成要素ごとに分解する
path.format() pathObject string パスオブジェクトを文字列として再構築する
path.extname() pathString string ファイル拡張子を取得
path.basename() pathString[, ext] string ファイル名を取得(拡張子は省略可)
path.dirname() pathString string 目次へ
path.isAbsolute() pathString boolean 絶対パスかどうかを確認する
path.normalize() pathString string 正規化パス(... の処理)
path.relative() from, to string 「from」から「to」までの相対パスを取得する

7. クロスプラットフォームにおけるパス処理のベストプラクティス

(1) 基本原則

ルール 説明 誤った例 正しい例
path.join の使用 区切り文字の自動処理 'src' + '/' + 'index.js' path.join('src', 'index.js')
path.sep を使用 区切り文字の定数 str.split('/') str.split(path.sep)
path.resolve を使用 絶対パスを取得 process.cwd() + '/' + file path.resolve(file)
fileURLToPath を使用 ファイル URL を変換 import.meta.url.slice(7) fileURLToPath(import.meta.url)
ESM では __dirname の使用を避ける 利用不可 __dirname を直接使用する 代わりに import.meta.url を使用する

(2) よくあるエラーのパターン

JAVASCRIPT
const path = require('path');

// ❌ Hard-coded delimiters
const bad1 = '/app/data/' + 'config.json';

// ✅ Usage path.join
const good1 = path.join('/app', 'data', 'config.json');

// ❌ Manually Merge Working Directories
const bad2 = process.cwd() + '/output/result.log';

// ✅ Usage path.resolve
const good2 = path.resolve('output', 'result.log');

// ❌ String Replacement Delimiter
const bad3 = somePath.replace(/\\/g, '/');

// ✅ Usage path.normalize
const good3 = path.normalize(somePath);

8. 包括的な例:クロスプラットフォームのファイルパスツール

以下の例は、ボブが改良したCLIツールの主要なロジック、すなわち設定パスの読み込み、データディレクトリの構築、URLの解析、およびパラメータの抽出をシミュレートしたものです。

JAVASCRIPT
const path = require('path');
const { fileURLToPath } = require('url');

class PathTool {
  constructor(baseDir) {
    this.baseDir = baseDir || process.cwd();
  }

  resolveConfigPath(configRelativePath) {
    return path.resolve(this.baseDir, configRelativePath);
  }

  buildDataPath(...segments) {
    return path.join(this.baseDir, 'data', ...segments);
  }

  parseUrl(urlString) {
    const myUrl = new URL(urlString);
    return {
      protocol: myUrl.protocol,
      hostname: myUrl.hostname,
      pathname: myUrl.pathname,
      params: Object.fromEntries(myUrl.searchParams.entries())
    };
  }

  extractFileInfo(filePath) {
    const parsed = path.parse(filePath);
    return {
      directory: parsed.dir,
      fileName: parsed.name,
      extension: parsed.ext,
      fullPath: filePath
    };
  }

  toFilePath(urlOrPath) {
    if (urlOrPath.startsWith('file://')) {
      return fileURLToPath(urlOrPath);
    }
    return path.resolve(urlOrPath);
  }
}

const tool = new PathTool('/app/project');

// 1. Parsing the Configuration Path
const configPath = tool.resolveConfigPath('config/app.json');
console.log('Configuration Path:', configPath);
// /app/project/config/app.json

// 2. Data Merge Catalog
const dataPath = tool.buildDataPath('users', 'profiles.json');
console.log('Data Path:', dataPath);
// /app/project/data/users/profiles.json

// 3. Analysis URL and extract the parameters
const parsed = tool.parseUrl('https://api.example.com/v1/users?role=admin&active=true');
console.log('URL Analysis:', parsed);
// { protocol: 'https:', hostname: 'api.example.com',
//   pathname: '/v1/users', params: { role: 'admin', active: 'true' } }

// 4. Extract File Information
const info = tool.extractFileInfo('/app/project/data/users/profiles.json');
console.log('File Information:', info);
// { directory: '/app/project/data/users',
//   fileName: 'profiles', extension: '.json', fullPath: '...' }

// 5. file URL File Path
const localPath = tool.toFilePath('file:///app/project/config/app.json');
console.log('Local Path:', localPath);
// /app/project/config/app.json

❓ よくある質問

Q path.joinpath.resolve の違いは何ですか?
A path.join は単にパスセグメントを連結して正規化するだけであり、結果が絶対パスになることは保証されません。path.resolveは、絶対パスが生成されるまでパスを右から左へと展開します。絶対パスが見つからない場合は、process.cwd()を基底として使用します。
Q __dirname は ESM で使用できますか?
A いいえ。ESM モジュールには __dirname および __filename というグローバル変数は存在しません。同等の値を取得するには path.dirname(fileURLToPath(import.meta.url)) を使用する必要があります。
Q url.parse は非推奨になりましたか?
A はい、url.parse は非推奨としてマークされています。WHATWG 標準の new URL() コンストラクタの使用をお勧めします。このコンストラクタは、より優れたエラー処理機能と searchParams に対する組み込みサポートを提供しています。
Q WindowsとmacOS/Linuxの間でパスの互換性を確保するにはどうすればよいですか?
A パスの連結には常にpath.joinまたはpath.resolveを使用し、区切り文字の指定にはpath.sepを使用し、環境変数の処理にはpath.delimiterを使用してください。また、区切り文字の文字列をハードコーディングすることは避けてください。
Q import.meta.url は何を返しますか?
A 現在のモジュールに対応する file:// プロトコル URL 文字列(例:file:///app/src/index.js)を返します。この文字列は、fileURLToPath() を使用してファイルシステムパスに変換する必要があります。
Q path.isAbsolute は、プラットフォームが異なっても一貫した動作をしますか?
A いいえ、しません。POSIX システムでは、/usr/local は絶対パスですが、Windows では、C:\Users は絶対パスであるのに対し、/usr/local は絶対パスではありません。path.isAbsolute は、現在のプラットフォームに基づいて結果を決定します。

📖 まとめ


📝 練習問題

  1. このレッスンにあるすべてのコード例を完成させ、それぞれが正しく動作することを確認してください。
  2. 包括的な例を修正し、独自の拡張機能を追加する
  3. 公式ドキュメントを確認し、このレッスンで取り上げられていないAPIを1~2つ見つけ、それらのテストコードを作成してください。
  4. 振り返り:このレッスンで学んだことを、実際のプロジェクトにどのように活かしますか?
  5. このレッスンで学んだことと、これまでのレッスンの内容を組み合わせて、小さなプロジェクトを作成してみてください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%