webpack@5の主な変更点まとめ

2020 / 10 / 09

Edit
🚨 This article hasn't been updated in over a year
💁‍♀️ This post was copied from Hatena Blog

予定では、明日の 10 日に webpack のメジャーバージョンである v5 がリリースされますが、まだエコシステムが安定していない可能性があるため、注意してアップグレードを行ってください。

webpack 5 release plan · Issue #11406 · webpack/webpack TL;DR: release planned for 2020-10-10 After nearly 1 year of beta testing and about 2 years of devel...

change log: https://github.com/webpack/changelog-v5

移行ガイド: https://webpack.js.org/migrate/5

追加機能

Persistent Caching

このバージョンからは今までメモリ上でしか行ってなかったファイルシステムによるキャッシュが導入されます。以下のように設定することにより、大幅な速度改善が見込めます。

module.exports = {
  cache: {
    type: "filesystem",
    buildDependencies: {
      config: [__filename],
    },
  },
};

なし ↓

asset main.js 36.3 KiB [emitted] [minimized] (name: main)
orphan modules 584 KiB [orphan] 554 modules
cacheable modules 117 KiB
  ./src/index.js + 103 modules 117 KiB [built] [code generated]
  ./src/foo.js 21 bytes [built] [code generated]
webpack 5.0.0-rc.2 compiled successfully in 1836 ms

あり ↓

asset main.js 36.3 KiB [compared for emit] [minimized] (name: main)
cached modules 700 KiB [cached] 556 modules
webpack 5.0.0-rc.2 compiled successfully in 429 ms

詳しくは以下の記事を参照にしてください。

webpack@5で入るPersistent Cachingについて - 技術探し webpack@5で新しく導入されるPersistent Cachingの機能を紹介します。

Module Federation

リポジトリ間(バンドル間)を跨ぐときにライブラリなどの重複しているコードを以下のように効率よく扱いバンドルサイズを下げる仕組みです。この機能はお互いの webpack と連携を取り合う必要があるため互いに webpack@5 である必要があります。

詳しくは以下の記事を参考にしてください。

webpack@5で入るModule Federationについて - 技術探し webpackの次のバージョンで入るmodule federationという仕組みを説明します。

assetModules type の追加

今まで画像などを読み込むときに、file-loader や url-loader, raw-loader などを使っていましたがそれがネイティブサポートされました。

module.exports = {
  output: {
    assetModuleFilename: "images/[hash][ext]",
  },
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        type: "asset/resource",
      },
    ],
  },
};

詳しくは以下の記事を参考にしてください。

webpackの次のバージョンで入るassetModulesの紹介 - 技術探し 新しく入る機能であるassetModulesの紹介をします。

チャンク名が ID へ変更

今まで以下のようにwebpackChunkNameと書かなければ読めないファイル名となっていましたが、人が読める形となります。それに伴い、開発中でのwebpackChunkNameの指定をする必要がなくなることが期待されます。

(async () => {
  await import(/* webpackChunkName: "foo" */ "./foo");
})();

名前をつけたときの出力

asset main.js 2.79 KiB [emitted] [minimized] (name: main)
asset foo.js 114 bytes [emitted] [minimized] (name: foo)
runtime modules 7.23 KiB 10 modules
cacheable modules 217 bytes
  ./src/index.js 190 bytes [built] [code generated]
  ./src/foo.js 27 bytes [built] [code generated]
webpack 5.0.0-rc.4 compiled successfully in 274 ms

v5 のデフォルトでは以下のようにdeterministicという設定の新しいアルゴリズムが追加され、モジュール/チャンクの名前に 3~4 桁の数値 ID が付与されるようになります。これにより、ハッシュ化されたモジュール ID による gzip でのパフォーマンス低下は修正されました。

asset main.js 2.79 KiB [emitted] [minimized] (name: main)
asset 717.js 114 bytes [emitted] [minimized]
runtime modules 7.23 KiB 10 modules
cacheable modules 186 bytes
  ./src/index.js 159 bytes [built] [code generated]
  ./src/foo.js 27 bytes [built] [code generated]
webpack 5.0.0-rc.4 compiled successfully in 283 ms

ファイル名を自動的に付与したい場合

module.exports = {
  optimization: {
    chunkIds: "named",
  },
};
asset main.js 2.8 KiB [emitted] [minimized] (name: main)
asset src_foo_js.js 123 bytes [emitted] [minimized]
runtime modules 7.23 KiB 10 modules
cacheable modules 186 bytes
  ./src/index.js 159 bytes [built] [code generated]
  ./src/foo.js 27 bytes [built] [code generated]
webpack 5.0.0-rc.4 compiled successfully in 256 ms

optimization.chunkIdsnamed を追加すればファイル名が確定しますが本番環境では表示されていいものなのかを検討してください。また、optimization.splitChunks.nameはなくなったのでこちらに移行してください。

import.meta のサポート

// ./src/index.js
console.log(import.meta.url);
console.log(import.meta.webpack);
// ./dist/main.js
// 生成されたファイルは固定値として入り、import.meta.url, webpackは存在しなくなる
console.log("file:///Users/hiroppy/webpack/src/index.js");
console.log(5);

また、HMR 時に今までは以下のように書いていましたが、これからはimport.meta.webpackHotを使うことが可能です。これを使うことにより、Node.js の module への依存を減らし、ESM に沿うような書き方に変わります。

// <= 4
if (module.hot) {
  module.hot.accept();
}

// >= 5
if (import.meta.webpackHot) {
  import.meta.webpackHot.accept();
}

// or
import.meta.webpackHot?.accept();

data, file, http(s)のプロトコルのサポート

import x from "data:text/javascript,export default 42";
console.log(x); // 42

import y from "file:///Users/hiroppy/webpack/src/index.js";

また、フラグメント(#)もサポートされました。

const eIndexOf = require("es5-ext/array/\0#/e-index-of#fragment");

http(s)プロトコルは、まだ完全にサポートされていないため以下の設定が必要です。

const webpack = require("webpack");

module.exports = {
  plugins: [
    new webpack.experiments.schemes.HttpUriPlugin(),
    new webpack.experiments.schemes.HttpsUriPlugin(),
  ],
};

// index.js
import codeOfConduct from "https://raw.githubusercontent.com/webpack/webpack/master/CODE_OF_CONDUCT.md";
console.log(codeOfConduct);

Native Worker のサポート

new Worker(new URL('...', import.meta.url))が WebWorker を作るようにサポートされました。これは SharedWorker も同様です。

const fooWorker = new SharedWorker(
  new URL("./foo-worker.js", import.meta.url),
  {
    name: "foo",
  },
);

publicPath の自動化

新しくデフォルト値としてautoが追加され、document.currentScript, document.getElementsByTagName('script'), self.location の中から自動的に決定されます。注意点として、IE ではdocument.currentScriptがサポートされていないため、deferred か async のスクリプトには使用することができません。

module.exports = {
  output: {
    publicPath: "auto",
  },
};

Tree Shaking の最適化

ネストされたモジュールの場合、今までは使われていないbは削除できませんでしたが v5 からは追跡可能となりできるようになりました。

// inner.js
export const a = 1;
export const b = 2;

// module.js
import * as inner from "./inner";
export { inner };

// user.js
import * as module from "./module";
console.log(module.inner.a);

v4 では、モジュールの関係性しか見ていませんでしたが、v5 から入ったoptimization.innerGraphにより、内部モジュールへの最適化も行えるようになりました。

import { something } from "./something";

function usingSomething() {
  return something;
}

export function test() {
  return usingSomething();
}

以下のケースが対象です。

  • 関数宣言
  • クラス宣言
  • 変数宣言 及び export default

Optimization.sideEffectsでは、ソースコードから副作用のないモジュールの単純なケースを検出できるようになりました。クラスおよび関数宣言、簡単な init 式を使用した変数宣言、ifwhileforswitchexportimport、簡単なフラグを使用した関数呼び出し 等です。

また、CJS もサポートされました。

  • module.exports = require('...')
  • module.exports.a.b.c = require('...').a.b.c
  • Object.defineProperty(module.exports, 'xxx', ...)
  • require('abc').xxx

このサポートは、ESM、CJS 間でも動くので、今後どちらのモジュールシステムを使っているかを気にせずに最適化行えるようになります。

これは別の記事で詳細に説明するので予定です。

output.filename, output. chunkFilename の関数化

output.filenameは今まで文字列しか受け取りませんでしたが、関数にすることが可能となったため更に柔軟な設定を表現することが可能となります。

module.exports = {
  output: {
    filename: ({ chunk }) => {
      if (chunk.name === "main") return "main.bundle.[contenthash].js";
      return "foo.bundle.[contenthash].js";
    },
  },
};

externalsType の追加

externalsTypepromise, import, scriptが追加され、より柔軟に対応できるようになりました。

  • promise: varと同様だが、非同期モジュールとなる
  • import: import()を使い、非同期のネイティブ ESM モジュールを読み込む
  • script: <script>を使い、事前に定義されたグローバル変数を公開するスクリプトを読み込む
module.exports = {
  externalsType: "promise",
};

target の詳細化と browserslist のサポート

targetに対して、詳細な設定ができるようになりました。 配列を受け取るようになり、target: ['web', 'es2015'] 等の書き方が行えるようになりました。 また、browserslist がされたため、webの場合はtargetの設定は不要となります。

GitHub - browserslist/browserslist: 🦔 Share target browsers between different front-end tools, like Autoprefixer, Stylelint and babel-preset-env 🦔 Share target browsers between different front-end tools, like Autoprefixer, Stylelint and babel-p...

デフォルト値はtarget: 'browserslist'となり、フォールバック先は変わらずにwebとなります。

TypeScript 型定義ファイルの提供

@types/webpackは不要になりました。

import { WebpackOptionsNormalized } from "webpack";

const config: WebpackOptionsNormalized = {
  entry: "index.js",
  output: {
    filename: "bundle.js",
  },
};

splitChunks でのサイズ設定値の変更

今までは、JS のみのチャンクサイズでしたが、さらに詳細に指定できるようになりました。

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        test: {
          name: "test",
          minSize: {
            javascript: 100,
            webassembly: 100,
            style: 100,
          },
        },
      },
    },
  },
};

また、本番環境でのminSizeのデフォルト値は20kとなりました。

実験的段階

top-level-await のサポート

シンタックスは ESM の仕様に沿いますが、まだ stage-3 なので実験的フェーズです。

GitHub - tc39/proposal-top-level-await: top-level `await` proposal for ECMAScript (stage 4) top-level `await` proposal for ECMAScript (stage 4) - GitHub - tc39/proposal-top-level-await: top-le...

// webpack.config.js
module.exports = {
  experiments: {
    topLevelAwait: true,
  },
};
const x = await import("file:///Users/hiroppy/Desktop/webpack-5/src/foo.js");

console.log(x);

script タグでのモジュールサポート

バンドル時に使われる IIFE が取り除かれ、<script type="module">経由で呼び出される形に出力されます。この場合、仕様に沿い厳格モードと遅延ロードが有効化されます。

module.exports = {
  experiments: {
    outputModule: true,
  },
};

破壊的変更

最低要求バージョンが Node.js@10 へ

webpack 及び webpack のコアにおけるエコシステムが要求する Node.js のバージョンの最低値は 10 となります。

Node.js の polyfill の自動挿入が廃止

メンバー間でも賛否両論がありましたが、理由としては以下のような目的があります。

  • webpack は web へ向かっている
  • polyfill 自体が完全互換なものではない
  • メンテナンスコストの高さ

自分が経験した例としては、processutilに依存している Node.js のコードをクライアントサイドで使う場合があり、v5 に上げたら動かなくなる場合があります。

実際に webpack4 まで使っていた polyfill は以下のリポジトリで管理されているので、これを参考にして各自で追加する必要があります。

GitHub - webpack/node-libs-browser: [DEPRECATED] The node core libs for in browser usage. [DEPRECATED] The node core libs for in browser usage. - GitHub - webpack/node-libs-browser: [DEPRECA...

これに伴い、node.*の中のネイティブモジュールがすべて廃止となります。 また、global, __filename, __dirnameはデフォルトでfalseの値となります。

module.exports = {
  node: {
    // Buffer: false, これは廃止
    global: false,
    __filename: false,
    __dirname: false,
  },
};

JSON での named export の禁止

ESM の仕様上、これは許可されていないためこれが行われているコードの場合警告が出るようになるため、以下のように変更する必要があります。

// 😵
import { version } from "./package.json";

// 🙂
import package from "./package.json";
const { version } = package;

loader と use の違いを厳格化

rules.loaderrules.useで目的に合ってない使い方の設定の場合、エラーを吐くようになりました。 useoptionsがない場合のみ使用可能(引数は受け入れ可)となり、optionsがある場合はloaderを使わなければなりません。

デフォルトランタイムが一部 ES2015 へ変更

webpack の生成するコードのデフォルトが一部 es5 から es2015 となります。 これはあくまでもバンドルサイズを減らすことが目的なため、varからconstにはなったりせず、function() => {} となります。 もし IE をサポートしている場合は以下を追加する必要があります。

module.exports = {
  target: ["web", "es5"],
};

また、これは追加機能として用意された browserslist を用いて回避することも可能です。

# browserslist
last 1 version