iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

npmとwebpackに触れてみる

■ はじめに

こんにちは。

株式会社iimonでフロントエンドを担当している白水です。

今回は、フロントエンドの技術の中で「npm」と「webpack」に軽く触れてみようと思います。

■Node.jsとは

npmを使うには、Node.jsをインストールする必要がありますが、そもそもNode.jsって何?と言われると言語化するのが難しいものだと思います。

以下の記事がわかりやすかったので、参考までにリンクを貼るだけにとどめたいと思います。

Node.jsとはなにか?なぜみんな使っているのか? - Qiita

■nodeとnpmが使えるか確認

❯ node -v
v20.7.0

❯ npm -v
10.2.1

❯ npm

npm -vでnpmのバージョンが表示されれば、npmが使えます。

npmで使えるコマンドは、npmと入力して実行すると、「All commands:」という部分で確認できます。

❯ mkdir sample

❯ cd sample

❯ touch {index.html,index.js}

今回使うディレクトリと、ファイルを作っておきます。

■ 従来の方法でJavaScript

<!-- index.html --> 

<!DOCTYPE html>  
<html lang="ja">  
<head>  
  <meta charset="UTF-8">  
  <title>JavaScript</title>  
  <script src="index.js"></script>
</head>  
<body>  
  <h1>index.html</h1>  
</body>  
</html>

まずは、書籍などでも書いてある昔ながらの方法でJSファイルを実行してみます。

<script src="index.js"></script>という行は、同じディレクトリにあるindex.jsという名前の別のJavaScriptファイルを参照しています。

// index.js  
console.log("JavaScript");

これで、準備ができたので、ブラウザで実行してみると、実行できます。

ここで、誰かが書いたmoment.jsのようなライブラリ(日付のフォーマットを人間が読みやすいようにするライブラリ)を追加してみます。

console.log(moment().startOf('day').fromNow());

以下のコードをindex.jsに書きます。

Moment.js | Home

これで実行してみると、エラーになります。

どうしてエラーになるかというと、moment.jsが含まれていることを前提として実行しているためです。

しかし、moment.jsは読み込んでいないのでエラーになります。

moment.jsのホームページには、上のような説明があります。

moment.min.jsファイルをダウンロードして、index.htmlファイルで読み込ませれば、実行できるので、moment.min.jsファイルをダウンロードしてプロジェクトに配置してみます。

<!DOCTYPE html>  
<html lang="ja">  
<head>  
  <meta charset="UTF-8">  
  <title>JavaScript Example</title>  
  <script src="./moment.js"></script>
  <script src="index.js"></script>
</head>  
<body>  
  <h1>Hello from HTML!</h1>  
</body>  
</html>

moment.min.jsは、index.jsの前に読み込まないとエラーになるので、moment.min.js または moment.jsを先に読み込みます。

これでブラウザをリロードすると、エラーなく実行できるようになります。

昔のやり方の良かった点は、

  • 簡単に理解できたこと

悪かった点は、

  • ライブラリが更新されるたに新しいバージョンファイルを探してダウンロードするのが面倒だったこと
  • 読み込む順番を考慮する必要があること

です。

もし、上記のように複数のライブラリを使いたくて、そのライブラリの読み込み順番を気にしながら<script>タグを書いていたら、気が狂いそうになると思います。

JavaScriptパッケージマネージャ(npm)を使ってみる

◆ npmとは?

npmと名のつくものは 2 つあります。

1つは、世界中の開発者が作った Node.js のパッケージが集められた場所です。

これを「npmレジストリ」と言います。

2つ目は、 Node.js のパッケージをプロジェクトにインストールして管理するためのCLI(コマンドラインインターフェイス)です。

2010年頃から、ライブラリのダウンロードとアップグレードのプロセスなどを自動化するために、JavaScriptパッケージマネージャが登場し出しました。

2013年にはBower(バウアー)が人気があったようです。

2015年頃にnpmが人気になり、2016年後半あたりから、yarn人気になりました。

◆npmを使ってライブラリをインストールしてみる

❯ npm init

手動でライブラリをダウンロードする代わりに、npmを使ってmoment.jsパッケージを自動的にインストールしてみます。

上記コマンドを実行すると、いくつかの質問されますが、すべてEnterでOKです。

そうすると、package.jsonというファイルが出来上がります。

これは、npmパッケージを管理するために使われるファイルです。

npm install moment // --saveは、デフォルトで入っているオプションなので使う必要なし

moment.jsをnpmを使ってインストールしてみます。

コマンドラインに上記のコマンドを入力すればOKです。

このコマンドは2つのことをしています。

  • moment.jsから、すべてのコードをnode_modulesというフォルダにダウンロードする。
  • moment.jsをpackage.jsonファイルを自動的に追記する。

node_modulesフォルダを共有するとファイルサイズが大きくなる可能性があるので、package.jsonファイルをGitHubなどで共有するだけで、他の開発者はnpm installコマンドで必要なパッケージを自動的にインストールできます。

そのおかげで、同じパッケージを同じバージョンで使うことができるようになります。

手動でmoment.jsをダウンロードする必要がなくなり、npmを使って自動的にダウンロードできるようになりました。

node_modulesフォルダの中を見ると、node_modules/moment/minディレクトリにmoment.min.jsファイルがあります。

なので、index.htmlファイルの中で、moment.min.jsを読み込むことができます。

<!DOCTYPE html>  
<html lang="ja">  
<head>  
  <meta charset="UTF-8">  
  <title>タイトル</title>  
   <script src="./node_modules/moment/min/moment.min.js"></script>
  <script src="index.js"></script>
</head>  
<body>  
  <h1>これは題名です</h1>  
</body>  
</html>

これでブラウザをリロードしてみても、エラーなく実行できます。

npmを使ってパッケージをインストールすることの良い点は、

  • npmを使ってコマンドラインからパッケージのダウンロードができるようになったこと

悪い点は、

  • node_modulesフォルダを探し回ってお目当てのパッケージの場所を見つけ、それを手作業でHTMLに追加していく必要があること

「node_modulesフォルダを探し回って」はかなり面倒なので、この探し回るを自動化してみます。

JavaScriptモジュールバンドルラー(webpack)を使う

ほとんどのプログラミング言語は、あるファイルから別のファイルにコードをインポートする方法があります。

JavaScriptブラウザーの中だけで動作するように設計されており、クライアントのコンピューターのファイルシステム(OS)にはアクセスできないようになっていました。以下記事を参照ください。

https://qiita.com/non_cal/items/a8fee0b7ad96e67713eb

なのでJavaScriptのコードを複数のファイルにまとめるには、グローバルに共有されている変数を各ファイルに読み込む必要がありました。

要するに、上記のmoment.min.jsの例では、moment.min.jsファイル全体がHTMLに読み込まれ、グローバル変数momentが定義され、moment.min.jsの後に読み込まれたどのファイルでもmoment.jsというライブラリが利用できるようになっています。

このJSの欠点を補うために、2009年あたりからCommonJSというサーバーサイドなどのブラウザ環境外で使えるようにするための、JavaScriptの仕様を定めることを目標としたプロジェクトが始まりました。

それが、CommonJSです。

CommonJSプロジェクトの仕様の多くはモジュールの仕様で、ほとんどのプログラミング言語にあるようにファイル間でコードのインポートとエクスポートができるようにするプロジェクトが始動しました。

CommonJSの仕様をサポートしたものが「node.js」です。

node.jsを使用すると、require('moment');というように外部ファイルを読み込むことができます。

// index.js  
const moment = require('moment');
console.log('今日は');
console.log(moment().startOf('day').fromNow());

しかし、この状態でブラウザで実行してもエラーになります。

理由は、ブラウザはECMAScriptの仕様は満たしていますが、CommonJSという仕様は満たしていません。

なので、require();という命令は理解できません。

requireはnode.jsで仕組みなので、node.jsでは実行できます。

node.jsはコンピュータのファイル・システムにアクセスできるのでnode.jsであればうまくいきますが、ブラウザはファイルシステムにアクセスできないので、外部ファイル(モジュール)を読み込むことはできません。

そこでモジュールバンドラーを使います。

モジュールバンドラーは、ファイルシステムにアクセスできるので(ブラウザで実行できる)(ファイルシステムにアクセスする必要がない)別の形のファイルを作成するツールです。

この場合、require文を見つけ、ブラウザで実行できる形式に置き換えて、1つのバンドルされたJavaScriptファイルを作り上げます。

2011年にあたりはBrowserify(ブラウザリファイ)で、複数のJavaScriptファイルやモジュールを1つのファイルにまとめて、Node.jsのrequire();をブラウザで利用可能な形式に変換することができたの注目されていました。

2015年頃にwebpackが広く使用されるモジュールバンドラーになりました。

require('moment')の例をブラウザで動作させるためのwebpackの使ってみます。

❯ npm install webpack webpack-cli --save-dev

まず、プロジェクトにwebpackをインストールします。

webpackとwebpack-cliコマンドラインからwebpackを使えるようにする)の2つのパッケージをインストールしてみます。

また、--save-dev 引数は、開発環境では必要ですが、本番では必要ないパッケージをインストールするときに使います。

{
  "name": "sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "moment": "^2.29.4"
  },
  "devDependencies": {
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4"
  }
}

インストールすると、devDependenciesにwebpackwebpack-cliがインストールされます。

❯ ./node_modules/.bin/webpack ./index.js --mode=development

このコマンドは、Node.jsプロジェクトでWebpackと呼ばれるモジュールバンドラを使用して、index.jsというエントリファイルを起点にしてビルドするためのコマンドです。

Webpackは、JavaScriptファイルとその依存関係(requireやimportしているコード)を解析し、1つまたは複数のファイルにまとめるツールです。

  • ./node_modules/.bin/webpack: プロジェクト内のローカルにインストールされたWebpackの実行ファイルへのパスです。

    ./node_modules/.binは、ローカルのプロジェクト内にインストールされたパッケージの実行可能ファイルが格納されているディレクトリです。

  • index.js: エントリポイントとなるJavaScriptファイルのパスです。

    Webpackはこのファイルを起点にして、他の依存関係を解決し、バンドルを生成します。

  • -mode=development: ビルドモードを指定するオプションです。この場合、開発モード (development) でWebpackを実行することを意味します。

    開発モードでは、バンドルされたファイルが圧縮されず、開発者がデバッグしやすい形式でファイルが生成されます。

    -**-mode=production** と指定することで、本番用の圧縮されたファイルができます。

デフォルトではdist/main.jsを作成することにします。

<!DOCTYPE html>  
<html lang="ja">  
<head>  
  <meta charset="UTF-8">  
  <title>JavaScript Example</title>  
  <script src="./dist/main.js"></script>
</head>  
<body>  
  <h1>Hello from HTML!</h1>  
</body>  
</html>

dist/main.js出力ができたので、これをブラウザで使用することにします。

これは、index.htmlファイルで、./dist/main.jsを読み込みます。

これでブラウザでも実行できるようになります。

しかし、index.jsを変更するたびにwebpackコマンドを実行する必要があります。

これを自動化するためにプロジェクトのルートディレクトリにあるwebpack.config.jsという設定ファイルを作ります。

// webpack.config.js  
module.exports = {  
    // 「./node_modules/.bin/webpack ./index.js --mode=development」の「--mode=development」の代わりになる
  mode: 'development',  
    // 起点となるファイル
  entry: './index.js',  
  // 出力先
  output: {  
    filename: 'main.js',  
    publicPath: 'dist'  
  }  
};
❯ ./node_modules/.bin/webpack

設定のおかげで、コマンドのindex.js--mode=developmentオプションを指定する必要はなくなります。

しかし、コードを変更するたびにこのコマンドを入力する必要があります。

これも自動化してみます。

■タスクランナー (npm スクリプト) を使ってみる

スクランナーは、ビルドする家庭を自動化するツールです。

例えば、

  • コードの最小化
  • 画像の圧縮
  • テストの実行
  • ファイルが変更されたらビルドする

などが、自動で実行したいタスクが色々あります。

昔は、GruntやGulpというツールが人気だったようです。

現在では、npmがタスクランナーツールとしての役割も担うようになりました。

{
    "name": "sample",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
    "watch": "webpack --watch" 
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "moment": "^2.29.4"
    },
    "devDependencies": {
        "webpack": "^5.89.0",
        "webpack-cli": "^5.1.4"
    }
}

npmスクリプトを書いてみます。

package.jsonファイルに以下を追加してみます。

  • "watch": "webpack --watch"
npm run watch

元々、./node_modules/.bin/webpack ./index.js --mode=developmentというコマンドを打つ必要がありましたが、

webpack.config.js mode: 'development'によって、コマンドの--mode=developmentが不要になり、entry: './index.js',によって、コマンドの./index.jsが不要になり、package.jsonスクリプトは、node.jsが各npmモジュールのパスの場所を知っているので、フルパス./node_modules/.bin/webpackをコマンドで指定しなくてもよくなりました。

--watchモードにすることで、ファイルに変更があると、勝手にビルドして、dist配下の吐き出してくれるようになります。