webpackでライブラリを非CommonJS環境向けにビルドする方法

webpackでライブラリを非CommonJS環境向けにビルドする方法

はじめに (対象読者・この記事でわかること)

この記事は、webpackを使用したライブラリ開発経験がある開発者、またはブラウザ向けのJavaScriptライブラリをビルドしたいと考えている開発者を対象にしています。特に、Node.js環境以外で利用されることを想定したライブラリ開発に挑戦する方に最適です。

この記事を読むことで、webpackの「libraryモード」を活用して非CommonJS環境向けにライブラリをビルドする方法、externals設定の重要性、UMD形式での出力方法を理解できます。また、ビルド時のよくあるエラーとその解決策も学ぶことができ、ライブラリ開発の実践的なスキルを向上させることができます。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 前提となる知識1: JavaScriptの基本的な知識とモジュールシステム(CommonJS、ES Modules)の理解 前提となる知識2: webpackの基本的な設定とビルド経験 前提となる知識3: package.jsonとnpm/yarnの基本的な操作知識

webpackライブラリビルドの背景と必要性

webpackは主にアプリケーションのビルドツールとして知られていますが、実はライブラリのビルドにも強力な機能を提供しています。特に、ライブラリを開発する際には、Node.js環境だけでなく、ブラウザ環境やCDN経由で利用されるケースも多いため、様々なモジュールシステムに対応した形で出力する必要があります。

CommonJSはNode.jsの標準的なモジュールシステムですが、ブラウザ環境では直接サポートされていません。そのため、ライブラリをブラウザで利用するためには、UMD(Universal Module Definition)やIIFE(Immediately Invoked Function Expression)などの形式で出力する必要があります。

webpackのlibraryモードを使用することで、これらの要件を満たすライブラリを簡単にビルドできます。libraryモードでは、エントリーポイントからエクスポートする内容を指定し、ビルド結果をグローバル変数として、または様々なモジュールシステムに対応した形で出力できます。

また、ライブラリをビルドする際には、依存関係の処理も重要な要素です。ライブラリ利用者側で依存パッケージを別途インストールさせる場合、webpackのexternals設定を使用して、ビルド時にはバンドルしないように指定する必要があります。これにより、ライブラリのサイズを最適化し、利用側の依存関係を整理できます。

非CommonJS環境向けライブラリビルドの具体的な手順

ステップ1:プロジェクトの初期設定

まず、ライブラリ開発のためのプロジェクトをセットアップします。以下のコマンドで新しいプロジェクトを作成します。

mkdir my-library
cd my-library
npm init -y
npm install webpack webpack-cli --save-dev

次に、基本的なファイル構造を作成します。

my-library/
├── src/
│   └── index.js
├── package.json
└── webpack.config.js

src/index.jsにはライブラリのメイン機能を実装します。例として、簡単なユーティリティ関数を実装してみましょう。

// src/index.js
export const greet = (name) => {
  return `Hello, ${name}!`;
};

export const add = (a, b) => {
  return a + b;
};

ステップ2:webpackのライブラリモード設定

webpack.config.jsを設定して、ライブラリモードでビルドできるようにします。

// webpack.config.js
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-library.js',
    library: {
      name: 'MyLibrary',
      type: 'umd', // UMD形式で出力
    },
    globalObject: 'this', // ブラウザとNode.js両方で動作するように
  },
  externals: {
    // 外部依存関係を指定
    lodash: {
      commonjs: 'lodash',
      commonjs2: 'lodash',
      amd: 'lodash',
      root: '_'
    }
  },
  devtool: 'source-map'
};

この設定では、以下の重要なポイントがあります:

  1. library.type: 'umd' - UMD形式でライブラリを出力します。これにより、CommonJS、AMD、グローバル変数のいずれの環境でも利用可能になります。
  2. globalObject: 'this' - ブラウzar環境とNode.js環境の両方で動作するように設定します。
  3. externals - 外部依存関係を指定します。この例ではlodashを外部依存として扱い、ビルド結果には含めません。

ステップ3:package.jsonの設定

package.jsonにビルドスクリプトを追加します。

"scripts": {
  "build": "webpack"
},
"main": "dist/my-library.js", // CommonJS環境向けのエントリーポイント
"module": "dist/my-library.esm.js", // ES Modules環境向けのエントリーポイント
"browser": "dist/my-library.js", // ブラウザ環境向けのエントリーポイント
"types": "dist/index.d.ts", // TypeScriptの型定義ファイル
"files": [
  "dist"
],
"exports": {
  ".": {
    "import": "./dist/my-library.esm.js",
    "require": "./dist/my-library.js",
    "browser": "./dist/my-library.js"
  }
}

ステップ4:複数形式でのビルド設定

より柔軟な対応のため、複数の形式でビルドを行う設定も有効です。webpackの「library」オプションを配列形式で指定します。

// webpack.config.js
const path = require('path');

module.exports = [
  {
    mode: 'production',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'my-library.js',
      library: {
        name: 'MyLibrary',
        type: 'umd',
      },
      globalObject: 'this',
    },
    externals: {
      lodash: {
        commonjs: 'lodash',
        commonjs2: 'lodash',
        amd: 'lodash',
        root: '_'
      }
    },
    devtool: 'source-map'
  },
  {
    mode: 'production',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'my-library.esm.js',
      library: {
        type: 'module',
      },
    },
    externals: {
      lodash: 'lodash'
    },
    devtool: 'source-map'
  }
];

この設定では、UMD形式とES Modules形式の両方でビルドを行います。ES Modules形式のビルドでは、library.type: 'module'を指定します。

ステップ5:ビルドと動作確認

設定が完了したら、ビルドを実行します。

npm run build

ビルドが成功すると、distディレクトリに以下のファイルが生成されます。

生成されたライブラリの動作を確認するために、HTMLファイルを作成してみましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>MyLibrary Test</title>
</head>
<body>
  <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
  <script src="dist/my-library.js"></script>
  <script>
    // グローバル変数として利用
    console.log(MyLibrary.greet('World'));
    console.log(MyLibrary.add(1, 2));

    // CommonJS環境での利用(Node.jsなど)
    const MyLibrary = require('./dist/my-library.js');
    console.log(MyLibrary.greet('Node.js'));

    // ES Modules環境での利用
    import { greet, add } from './dist/my-library.esm.js';
    console.log(greet('ES Modules'));
    console.log(add(3, 4));
  </script>
</body>
</html>

ハマった点やエラー解決

ライブラリをビルドする際には、いくつかの典型的な問題に遭遇することがあります。ここでは、よくあるエラーとその解決策を紹介します。

エラー1:グローバル変数の競合

複数のライブラリを利用する際に、グローバル変数名が競合することがあります。これを防ぐために、ライブラリ名をユニークにするか、名前空間を利用します。

解決策: webpack.config.jsでlibrary.nameにユニークな名前を指定します。

output: {
  // ...
  library: {
    name: '[YourLibraryName]', // ユニークな名前を指定
    type: 'umd',
  },
}

エラー2:外部依存関係の正しい指定方法

ライブラリが依存する外部パッケージを正しく指定しないと、ビルド時に問題が発生したり、利用側で意図しない挙動を示したりします。

解決策: externals設定では、各モジュールシステムでの参照方法を正確に指定します。

externals: {
  'some-dependency': {
    commonjs: 'some-dependency',
    commonjs2: 'some-dependency',
    amd: 'some-dependency',
    root: 'SomeDependency' // グローバル変数としての名前
  }
}

エラー3:TypeScriptとの統合問題

TypeScriptを使用している場合、型定義ファイルの生成や参照で問題が発生することがあります。

解決策: 以下の手順でTypeScriptと連携させます。

  1. TypeScriptと必要なパッケージをインストールします。
npm install typescript ts-loader --save-dev
npm install @types/node --save-dev
  1. tsconfig.jsonを作成します。
{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "declaration": true,
    "declarationDir": "./dist",
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
  1. webpack.config.jsにTypeScriptの設定を追加します。
module.exports = {
  // ...他の設定
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
};

エラー4:Tree Shakingの問題

ビルド結果に不要なコードが含まれてしまうことがあります。これにより、ライブラリのサイズが不必要に大きくなります。

解決策: 以下の設定を追加してTree Shakingを有効にします。

module.exports = {
  // ...他の設定
  optimization: {
    usedExports: true,
    minimize: true,
  },
  mode: 'production',
};

また、package.jsonでsideEffectsを指定します。

{
  "sideEffects": false
}

ステップ6:パッケージの公開準備

ライブラリをnpmに公開するための最終設定を行います。

  1. package.jsonに必要な情報を追加します。
{
  "name": "my-library",
  "version": "1.0.0",
  "description": "My awesome JavaScript library",
  "main": "dist/my-library.js",
  "module": "dist/my-library.esm.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist"
  ],
  "exports": {
    ".": {
      "import": "./dist/my-library.esm.js",
      "require": "./dist/my-library.js",
      "browser": "./dist/my-library.js"
    }
  },
  "keywords": [
    "library",
    "example",
    "webpack"
  ],
  "author": "Your Name",
  "license": "MIT"
}
  1. .npmignoreファイルを作成して、不要なファイルを除外します。
node_modules/
src/
webpack.config.js
*.ts
.git/
.npmrc
.npmignore
  1. ビルドと公開準備が完了したら、以下のコマンドでパッケージをビルドします。
npm run build
  1. パッケージを公開します。
npm publish

まとめ

本記事では、webpackを使用して非CommonJS環境向けにライブラリをビルドする方法について解説しました。

この記事を通して、様々な環境で利用可能なJavaScriptライブラリを効率的に開発できるようになることを目指しました。ライブラリ開発は、コードの再利用性や保守性を高める上で重要なスキルです。ぜひ本記事で学んだ内容を実践し、自分だけの高品質なライブラリを開発してみてください。

今後は、ライブラリのテスト戦略や、バージョン管理に関するベストプラクティスについても記事にする予定です。 ```