見出し画像

Webpack 5ガイド「アセットモジュール」

随分お久しぶりです。

Webpack5ガイドの「Asset Modules 」の私家版和訳です。

以下、本文です。

アセットモジュール

アセットモジュールは、追加のローダーを設定せずに、アセットファイル(フォント、アイコンなど)を使用できるようにします。

webpack 5以前では、次のように使用するのが一般的でした:

  • raw-loaderは、ファイルを、文字列としてインポートする。

  • url-loaderは、ファイルを、データURIとしてバンドルにインライン化する。

  • file-loaderは、ファイルを、出力ディレクトリに出力する。

アセットモジュール型は、4つの新しいモジュール型を追加することで、これらのすべてのローダーを置き換えます:

  • asset/resourceは、個別のファイルを生成し、URLをエクスポートする。これ迄はfile-loaderを使うことで実現した。

  • asset/inlineは、アセットのデータURIをエクスポートする。これ迄はurl-loaderを使うことで実現した。

  • asset/sourceは、アセットのソースコードをエクスポートする。これ迄はraw-loaderを使うことで実現した。

  • assetは、データURIをエクスポートするか、別のファイルを出力するかを自動的に選択する。これ迄はアセットサイズ制限付きでurl-loaderを使うことで実現した。

webpack 5でアセットモジュールと共に、古いアセットローダー(例:file-loader/url-loader/raw-loader)を使用する場合、アセットが重複してしまうので、アセットモジュールがアセットを再び処理してしまわない様にストップさせたいことでしょう。これは、アセットのモジュール型を'javascript/auto'に設定することで可能です。

webpack.config.js

module.exports = {
  module: {
   rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
            }
          },
        ],
+       type: 'javascript/auto'
      },
   ]
  },
}

新しいURL呼び出しから来たアセットをアセットローダーから除外するには、ローダーの設定に、dependency: { not: ['url'] }を追加します。

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/i,
+       dependency: { not: ['url'] },
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
            },
          },
        ],
      },
    ],
  }
}

Resourceアセット

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
+ module: {
+   rules: [
+     {
+       test: /\.png/,
+       type: 'asset/resource'
+     }
+   ]
+ },
};

src/index.js

import mainImage from './images/main.png';

img.src = mainImage; // '/dist/151cfcfa1bd74779aadb.png'

全.pngファイルは、出力ディレクトリに出力され、それらのパスはバンドルに注入され、更にoutputPathとpublicPathをカスタマイズすることもできます。

出力ファイル名をカスタマイズする

デフォルトでは、asset/resourceモジュールは、ファイル名[hash][ext][query]で出力ディレクトリに出力されます。

webpack構成で、output.assetModuleFilenameを設定することで、このテンプレートを修正できます:

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
+   assetModuleFilename: 'images/[hash][ext][query]'
  },
  module: {
    rules: [
      {
        test: /\.png/,
        type: 'asset/resource'
      }
    ]
  },
};

出力ファイル名をカスタマイズするもう一つのケースは、指定ディレクトリに或る種類のアセットを出力する場合です:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
+   assetModuleFilename: 'images/[hash][ext][query]'
  },
  module: {
    rules: [
      {
        test: /\.png/,
        type: 'asset/resource'
-     }
+     },
+     {
+       test: /\.html/,
+       type: 'asset/resource',
+       generator: {
+         filename: 'static/[hash][ext][query]'
+       }
+     }
    ]
  },
};

この構成では、全htmlファイルは出力ディレクトリ内のstaticディレクトリに出力されます。

output.assetModuleFilenameと同様に、Rule.generator.filenameは、asset及びasset/resourceモジュール型でのみ動作します。

アセットのインライン化

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
-   assetModuleFilename: 'images/[hash][ext][query]'
  },
  module: {
    rules: [
      {
-       test: /\.png/,
-       type: 'asset/resource'
+       test: /\.svg/,
+       type: 'asset/inline'
-     },
+     }
-     {
-       test: /\.html/,
-       type: 'asset/resource',
-       generator: {
-         filename: 'static/[hash][ext][query]'
-       }
-     }
    ]
  }
};

src/index.js

- import mainImage from './images/main.png';
+ import metroMap from './images/metro.svg';

- img.src = mainImage; // '/dist/151cfcfa1bd74779aadb.png'
+ block.style.background = `url(${metroMap})`; // url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)

全.svgファイルは、データURIとしてバンドルに注入されます。

データURIジェネレーターをカスタマイズする

デフォルトでは、webpackが出力するデータURIは、Base64アルゴリズムでエンコードされたファイルコンテンツを表します。

カスタムエンコーディングアルゴリズムを使用したい場合は、ファイルの内容をエンコードするカスタム関数を指定できます:

webpack.config.js

const path = require('path');
+ const svgToMiniDataURI = require('mini-svg-data-uri');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.svg/,
        type: 'asset/inline',
+       generator: {
+         dataUrl: content => {
+           content = content.toString();
+           return svgToMiniDataURI(content);
+         }
+       }
      }
    ]
  },
};

これで、全.svgファイルは、mini-svg-data-uriパッケージでエンコードされることになります。

Sourceアセット

webpack.config.js

const path = require('path');
- const svgToMiniDataURI = require('mini-svg-data-uri');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
-       test: /\.svg/,
-       type: 'asset/inline',
-       generator: {
-         dataUrl: content => {
-           content = content.toString();
-           return svgToMiniDataURI(content);
-         }
-       }
+       test: /\.txt/,
+       type: 'asset/source',
      }
    ]
  },
};

src/example.txt

Hello world

src/index.js

- import metroMap from './images/metro.svg';
+ import exampleText from './example.txt';

- block.style.background = `url(${metroMap}); // url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)
+ block.textContent = exampleText; // 'Hello world'

全.txtファイルは、そのままバンドルに注入されます。

URLアセット

new URL('./path/to/asset', import.meta.url)を使用することで、webpackはアセットモジュールも生成します。

src/index.js

const logo = new URL('./logo.svg', import.meta.url);

構成内のtarget次第で、webpackは、上記のコードを異なる結果にコンパイルすることでしょう:

// target: web
new URL(
  __webpack_public_path__ + 'logo.svg',
  document.baseURI || self.location.href
);

// target: webworker
new URL(__webpack_public_path__ + 'logo.svg', self.location);

// target: node, node-webkit, nwjs, electron-main, electron-renderer, electron-preload, async-node
new URL(
  __webpack_public_path__ + 'logo.svg',
  require('url').pathToFileUrl(__filename)
);

webpack 5.38.0では、new URL()でも、データURLはサポートされています:

src/index.js

const url = new URL('data:,', import.meta.url);
console.log(url.href === 'data:,');
console.log(url.protocol === 'data:');
console.log(url.pathname === ',');

一般的なアセット型

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
+       test: /\.txt/,
+       type: 'asset',
      }
    ]
  },
};

今やデフォルトの条件に従うことで、 webpackはresourceとinlineの間を自動的に選択できる様になりました:8kb未満のファイルはinlineモジュール型として、それ以外はresourceモジュールタイプ型として、扱われます。

webpack構成のモジュールルール・レベルの、Rule.parser.dataUrlCondition.maxSizeオプションを設定することで、この条件を変更できます:

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.txt/,
        type: 'asset',
+       parser: {
+         dataUrlCondition: {
+           maxSize: 4 * 1024 // 4kb
+         }
+       }
      }
    ]
  },
};

また、モジュールをインライン化するか否かを決定する関数を指定することもできます。

インライン・ローダー構文の置換

アセットモジュール及びWebpack 5以前、先述のレガシーローダーでインライン構文 を使用できました。

現在では、全てのインラインローダー構文を除去し、インライン構文の機能を模倣するresourceQuery条件を使用することが推奨されています。

例えば、raw-loaderをasset/source型で置換するケース:

- import myModule from 'raw-loader!my-module';
+ import myModule from 'my-module?raw';

webpack構成で:

module: {
    rules: [
    // ...
+     {
+       resourceQuery: /raw/,
+       type: 'asset/source',
+     }
    ]
  },

rawアセットが他のローダーで処理されることを除外したい場合、ネガティブ条件を使用するか:

module: {
    rules: [
    // ...
+     {
+       test: /\.m?js$/,
+       resourceQuery: { not: [/raw/] },
+       use: [ ... ]
+     },
      {
        resourceQuery: /raw/,
        type: 'asset/source',
      }
    ]
  },

或いは、ルールのoneOfリストを使用します。ここでは最初に合致したルールだけが適用されます:

module: {
    rules: [
    // ...
+     { oneOf: [
        {
          resourceQuery: /raw/,
          type: 'asset/source',
        },
+       {
+         test: /\.m?js$/,
+         use: [ ... ]
+       },
+     ] }
    ]
  },

アセット出力の無効化

サーバーサイド・レンダリングの様なユースケースでは、アセット出力を無効にしたいこともあるでしょう。Rule.generator下のemitオプションで可能です:

module.exports = {
  // …
  module: {
    rules: [
      {
        test: /\.png$/i,
        type: 'asset/resource',
        generator: {
          emit: false,
        },
      },
    ],
  },
};

Further Reading

この記事が気に入ったらサポートをしてみませんか?