抹桥的博客
Language
Home
Archive
About
GitHub
Language
主题色
250
3702 文字
19 分
【翻訳】webpack2 ガイド(下)
2017-01-19

キャッシュ(Caching)#

webpackで処理された静的リソースを長期的にキャッシュ可能にするためには、以下の対応が必要です。

  1. [chunkhash] を使用して、コンテンツの変更に基づいたキャッシュ識別子を各ファイルに生成する
  2. HTMLファイルでファイルを読み込む際に、コンパイル状態を使用してファイル名を取得する
  3. リソースをロードする前に、チャンクマニフェストJSONファイルを生成し、HTMLページに書き込む
  4. 起動コードを含むエントリコードブロックのハッシュ値が、その依存関係が変更されていない限り、変更されないようにする

存在する問題#

コードに何か更新が必要な場合、それはサーバーにデプロイされ、クライアントによって再ダウンロードされる必要があります。ネットワークの状態が良くない場合、これは非常に非効率な作業となります。これがブラウザが静的リソースをキャッシュする理由です。

しかし、これには落とし穴があります。新しいバージョンをリリースする際にファイル名を更新しないと、ブラウザはファイルが変更されていないと認識し、クライアントが最新のリソースを取得できない可能性があります。

この問題を解決する簡単な方法は、ブラウザに新しいファイル名を伝えることです。webpackを使用しない場合、ビルドバージョンを使用して今回の更新を識別します。

application.js?build=1
application.css?build=1

webpackでもこれは簡単です。webpackのビルドごとに、ファイル名を構成するために使用できる一意のハッシュ値が生成されます。以下の設定ファイルは、ハッシュ値を持つ2つのファイル名を生成します。

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

module.exports = {
  entry: {
    vendor: "./src/vendor.js",
    main: "./src/index.js",
  },
  output: {
    path: path.join(__dirname, "build"),
    filename: "[name].[hash].js",
  },
};

webpackコマンドを実行すると、以下の出力が得られます。

Hash: 55e783391098c2496a8f
Version: webpack 1.10.1
Time: 58ms
Asset Size Chunks Chunk Names
main.55e783391098c2496a8f.js 1.43 kB 0 [emitted] main
vendor.55e783391098c2496a8f.js 1.43 kB 1 [emitted] vendor
[0] ./src/index.js 46 bytes {0} [built]
[0] ./src/vendor.js 40 bytes {1} [built]

しかし、問題は、再コンパイルするたびにすべてのファイル名が変更され、クライアントが毎回アプリケーション全体のコードを再ダウンロードすることになる点です。では、クライアントが変更されたファイルのみをダウンロードするようにするにはどうすればよいでしょうか?

各ファイルに固有のハッシュを生成する#

ファイルの内容が変更されていない場合、コンパイルごとにファイル名が変わらないようにするにはどうすればよいでしょうか?例えば、すべての依存ライブラリファイルを格納するためのコードバンドルなどです。

Webpackはファイルの内容に基づいてハッシュ値を生成することを許可しています。これが更新された設定です。

// webpack.config.js
module.exports = {
  /*...*/
  output: {
    /*...*/
    filename: "[name].[chunkhash].js",
  },
};

この設定も2つのファイルを生成しますが、各ファイルは独自のハッシュ値を持っています。

main.155567618f4367cd1cb8.js 1.43 kB 0 [emitted] main
vendor.c2330c22cd2decb5da5a.js 1.43 kB 1 [emitted] vendor

開発環境では [chunkhash] を使用しないでください。これにより、コンパイル時間が長くなります。開発環境と本番環境の設定ファイルを分け、開発環境では [name].js を、本番環境では [name].[chunkhash].js を使用してください。

webpackコンパイル統計情報(compilation stats)からファイル名を取得する#

開発環境では、HTMLにファイルを読み込むだけで十分です。

<script src="main.js"></srcipt>

しかし、本番環境では、毎回異なるファイル名が生成されます。

<script src="main.12312123t234.js"></srcipt>

HTMLで正しいファイルを読み込むためには、ビルドに関する情報を知る必要があります。これは、以下のプラグインを使用してwebpackのコンパイル統計情報(compilation stats)から抽出できます。

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

module.exports = {
  /*...*/
  plugins: [
    function () {
      this.plugin("done", function (stats) {
        require("fs").writeFileSync(
          path.join(__dirname, "…", "stats.json"),
          JSON.stringify(stats.toJson()),
        );
      });
    },
  ],
};

または、以下のプラグインを使用してJSONファイルを公開することもできます。

webpack-manifest-plugin を通じて出力される簡単なファイルは以下のようになります。

{
  "main.js": "main.155567618f4367cd1cb8.js",
  "vendor.js": "vendor.c2330c22cd2decb5da5a.js"
}

その後の処理は、あなたのサービスによって決定されます。Railsベースのプロジェクト向けの素晴らしい例 [walk through for Rails-based projects](walk through for Rails-based projects) があります。Node.jsのサーバーサイドレンダリングを使用する場合は、webpack-isomorphic-tools を使用できます。アプリケーションがサーバーサイドレンダリングに依存しない場合、index.html を直接生成できます。これには以下の2つのプラグインを使用できます。

決定論的ハッシュ (Deterministic hashes)#

生成ファイルのサイズを圧縮するために、webpackはモジュールを識別するために名前の代わりにIDを使用します。コンパイル中にIDが生成され、コードブロック名にマッピングされて、チャンクマニフェストと呼ばれるJavaScriptオブジェクトに格納されます。これはエントリファイルに配置され、バンドルされたファイルが正常に機能するようにします。

これには以前と同じ問題があります。他の場所が変更されていなくても、どこかのファイルを変更すると、更新されたエントリにマニフェストファイルが含まれる必要があります。これにより新しいハッシュ値が生成され、エントリファイル名が変更され、長期キャッシュの恩恵を受けられなくなります。

この問題を解決するために、https://github.com/diurnalist/chunk-manifest-webpack-plugin を使用して、マニフェストファイルを個別のJSONファイルに抽出する必要があります。これが更新された設定ファイルで、chunk-manifest.json をバンドルされたフォルダの下に生成します。

// webpack.config.js
var ChunkManifestPlugin = require("chunk-manifest-webpack-plugin");

module.exports = {
  // your config values here
  plugins: [
    new ChunkManifestPlugin({
      filename: "chunk-manifest.json",
      manifestVariable: "webpackManifest",
    }),
  ],
};

エントリファイルからマニフェストファイルを削除した後、このファイルをwebpackに手動で提供する必要があります。上記の例で manifestVariable オプションに気づいたかもしれません。これはwebpackがマニフェストJSONファイルを検索するためのグローバル変数であり、そのためコードバンドルの前にHTMLに読み込む必要があります。HTMLにJSONファイルの内容を書き込むのは簡単で、HTMLファイルの head 部分は以下のようになります。

<html>
  <head>
    <script>
      //<![CDATA[
      window.webpackManifest = {
        0: "main.3d038f325b02fdee5724.js",
        1: "1.c4116058de00860e5aa8.js",
      };
      //]]>
    </script>
  </head>
  <body></body>
</html>
 

最終的な webpack.config.js ファイルは以下の通りです。

var path = require("path");
var webpack = require("webpack");
var ManifestPlugin = require("webpack-manifest-plugin");
var ChunkManifestPlugin = require("chunk-manifest-webpack-plugin");
var WebpackMd5Hash = require("webpack-md5-hash");

module.exports = {
  entry: {
    vendor: "./src/vendor.js",
    main: "./src/index.js",
  },
  output: {
    path: path.join(__dirname, "build"),
    filename: "[name].[chunkhash].js",
    chunkFilename: "[name].[chunkhash].js",
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: "vendor",
      minChunks: Infinity,
    }),
    new WebpackMd5Hash(),
    new ManifestPlugin(),
    new ChunkManifestPlugin({
      filename: "chunk-manifest.json",
      manifestVariable: "webpackManifest",
    }),
  ],
};

webpack-html-plugin を使用している場合、inline-manifest-webpack-plugin を使用してこれを行うことができます。

この設定を使用すると、サードパーティのコードブロック(vendors chunk)は、そのコードまたは依存関係を変更しない限り、変更されません。

シミング(Shimming)#

webpackはモジュールバンドラーとして、ES2015モジュール、CommonJS、AMDを含むモジュールシステムをサポートしています。しかし、多くの場合、サードパーティライブラリを使用する際に、それらが $jquery のようなグローバル変数に依存しているのを見かけます。また、公開する必要がある新しいグローバル変数を作成することもあります。webpackがこれらの非モジュール(broken modules)ファイルを理解できるようにするいくつかの異なる方法を見てみましょう。

dist ファイル下のバンドルされていないCommonJS/AMDファイルを、バンドルされた dist バージョンよりも優先してください。 ほとんどのモジュールは package.jsonmain フィールドで dist バージョンを指定します。これはほとんどの開発者にとって非常に便利ですが、webpackにとっては src ディレクトリにエイリアスを設定する方が、依存関係をより良く最適化できるため望ましいです。しかし、多くの場合、dist バージョンを使用しても大きな問題はありません。

// webpack.config.js

module.exports = {
  ...
  resolve: {
  alias: {
    jquery: "jquery/src/jquery"
  }
}
};

provide-plugin#

[provide-plugin](https://webpack.js.org/plugins/provide-plugin) を使用することで、このモジュールを webpack を通じて参照されるすべてのモジュールで利用可能な変数として提供できます。この変数を使用したときにのみ、対応するモジュールが読み込まれます。多くの古いモジュールは、jQueryの $jQuery のような特定のグローバル変数を使用します。このシナリオでは、グローバルな $ 識別子に遭遇するたびに、webpackで var $=require('jquery') と事前に設定できます。

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery",
    }),
  ],
};

imports-loader#

[imports-loader](https://webpack.js.org/loaders/imports-loader/) は、必要なグローバル変数を従来のモジュールに挿入します。例えば、一部の従来のモジュールは thiswindow オブジェクトを指すことに依存しています。これは、モジュールがCommonJSコンテキストで実行されるときに thismodule.exports を指すため問題を引き起こします。この場合、imports-loader を介して this を上書きできます。

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: require.resolve("some-module"),
        use: "imports-loader?this=>window",
      },
    ],
  },
};

これはAMD、CommonJS、そして従来のモジュールなど、異なるモジュールタイプをサポートしています。しかし、通常、define 変数をチェックし、奇妙な(quirky)コードを使用してこれらのプロパティを公開します。この場合、define = false を設定してCommonJSパスを強制することが役立つ場合があります。

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: require.resolve("some-module"),
        use: "imports-loader?define=>false",
      },
    ],
  },
};

exports-loader#

ライブラリファイルがグローバル変数を作成し、その利用者がそれを使用することを期待しているとします。この場合、[exports-loader](https://webpack.js.org/loaders/exports-loader/) を使用して、CommonJSスタイルのグローバル変数を公開する必要があります。例えば、filefile として、helpers.parseparse として公開するには:

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: require.resolve("some-module"),
        use: "exports-loader?file,parse=helpers.parse",
        // adds below code the the file's source:
        //  exports["file"] = file;
        //  exports["parse"] = helpers.parse;
      },
    ],
  },
};

script-loader#

[script-loader](https://webpack.js.org/loaders/script-loader/) は、HTMLに script タグを追加したのと同じように、グローバルコンテキストでコードを解析します。この場合、理論的にはすべてのモジュールが正常に動作するはずです。

このファイルは文字列としてコードにバンドルされます。webpack によって圧縮されないため、圧縮されたバージョンを使用してください。また、この状況ではwebpackが提供する開発ツールは使用できません。

legacy.js ファイルに以下が含まれているとします。

GLOBAL_CONFIG = {};

script-loader の使用#

require("script-loader!legacy.js");

一般的に、このような結果が得られます。

eval("GLOBAL_CONFIG = {}");

noParse オプション#

AMD/CommonJSスタイルのモジュールがなく、dist に含める必要がある場合、このモジュールを [noParse](https://webpack.js.org/configuration/module/#module-noparse) としてマークできます。これにより、webpack はこのモジュールを読み込むだけで、いかなる処理も行わないため、ビルド時間を短縮できます。

ProvidePlugin のようなASTサポートを必要とするものは機能しません。

module.exports = {
  module: {
    noParse: /jquery|backbone/,
  },
};

ライブラリファイルの作成#

webpackはアプリケーションコードをバンドルするためのツールですが、ライブラリコードをバンドルするためにも使用できます。もしあなたがJavaScriptライブラリの作者で、バンドルプロセスを合理化する方法を探しているなら、この章の内容が非常に役立つでしょう。

ライブラリの作成(Author a Library)#

ここでは、数字の1から5を対応する単語に変換し、その逆も行う簡潔なラッパーライブラリがあります。これは次のようになります。

src/index.js

import _ from "lodash";

import numRef from "./ref.json";

export function numToWord(num) {
  return _.reduce(
    numRef,
    (accum, ref) => {
      return ref.num === num ? ref.word : accum;
    },
    "",
  );
}

export function wordToNum(word) {
  return _.reduce(
    numRef,
    (accum, ref) => {
      return ref.word === word && word.toLowerCase() ? ref.num : accum;
    },
    -1,
  );
}

ライブラリの使用方法は以下の通りです。

//ES2015modules

import*aswebpackNumbersfrom'webpack-numbers';

...
webpackNumbers.wordToNum('Two')//outputis2
...

//CommonJSmodules

varwebpackNumbers=require('webpack-numbers');

...
webpackNumbers.numToWord(3);//outputisThree
...

//Asascripttag

<html>
...
<scriptsrc="https://unpkg.com/webpack-numbers"></script>
<script>
...
/*webpackNumbersisavailableasaglobalvariable*/
webpackNumbers.wordToNum('Five')//outputis5
...
</script>
</html>

完全なライブラリ設定とコードは webpack-library-example にあります。

webpackの設定#

次に、このライブラリをバンドルします。 • lodashはバンドルせず、利用者が読み込むことを期待する • ライブラリ名を webpack-numbers とし、変数を webpackNumbers とする • ライブラリは import webpackNumbers from 'webpack-numbers' または require('webpack-numbers') で読み込めるようにする • script タグで読み込んだ場合、グローバル変数 webpackNumbers でアクセスできるようにする • Node.jsで使用できるようにする

webpackの追加#

基本的なwebpack設定を追加します。

webpack.config.js

module.exports = {
  entry: "./src/index.js",
  output: {
    path: "./dist",
    filename: "webpack-numbers.js",
  },
};

これは、このライブラリをバンドルするための基本的な設定を追加します。

Loadersの追加#

しかし、対応するローダーがなければコードを解析することはできません。ここでは、JSONファイルの解析を追加するために json-loader を追加します。

webpack.config.js

module.exports = {
  // ...
  module: {
    rules: [{
      test: /.json$/,
      use: 'json-loader'
    }]
  }
};
 

externals の追加#

さて、webpack コマンドを実行すると、かなり大きなコードバンドルが生成されることに気づくでしょう。コードを調べると、lodashがコードバンドルにバンドルされていることがわかります。あなたのライブラリにとって lodash を一緒にバンドルすることは全く必要ありません。

externals 設定を通じてこれを行うことができます。

webpack.config.js

module.exports = {
  ...
    externals: {
  "lodash": {
    commonjs: "lodash",
      commonjs2: "lodash",
      amd: "lodash",
      root: "_"
  }
}
...
};
 

これは、利用者の環境であなたのライブラリが lodash に依存することを期待することを意味します。

libraryTarget の追加#

このライブラリが広く使用されるためには、異なる環境で同じように動作する必要があります。例えば、CommonJS、AMD、Node.js、またはグローバル変数としてなどです。

この目的を達成するために、webpack設定に library プロパティを追加する必要があります。

webpack.config.js

module.exports = {
  ...
    output: {
...
  library: 'webpackNumbers'
}
...
};
 

これにより、ライブラリが読み込まれたときにグローバル変数としてアクセスできるようになります。他の状況で使用できるようにするには、設定に libraryTarget の値を追加し続けます。

webpack.config.js

module.exports = {
  ...
    output: {
...
  library: 'webpackNumbers',
    libraryTarget:'umd' // Possible value - amd, commonjs, commonjs2, commonjs-module, this, var
}
...
};
 

library が設定されていても libraryTarget が設定されていない場合、libraryTargetconfig reference で指定されているようにデフォルトで var になります。

最後の一歩#

本番環境用の設定ファイルを調整する

バンドルされたファイルを package.json の指定されたフィールドに追加します。

package.json


{
    ...
    "main": "dist/webpack-numbers.js",
    "module": "src/index.js", // To add as standard module as per https://github.com/dherman/defense-of-dot-js/blob/master/proposal.md#typical-usage
    ...
}

これで、npmモジュールとして公開し、unpkg.comを通じてユーザーに配布できるようになります。

この記事は 2017年1月19日 に公開され、2017年1月19日 に最終更新されました。3182 日が経過しており、内容が古くなっている可能性があります。

【翻訳】webpack2 ガイド(下)
https://blog.kisnows.com/ja-JP/2017/01/19/webpack2-guide-3/
作者
Kisnows
公開日
2017-01-19
ライセンス
CC BY-NC-ND 4.0