web-dev-qa-db-ja.com

Webpack 4-ベンダーチャンクの作成

Webpack 3構成では、以下のコードを使用して個別のvendor.jsチャンクを作成します。

entry: {
    client: ['./client.js'],
    vendor: ['babel-polyfill', 'react', 'react-dom', 'redux'],
},

output: {
  filename: '[name].[chunkhash].bundle.js',
  path: '../dist',
  chunkFilename: '[name].[chunkhash].bundle.js',
  publicPath: '/',
},

plugins: [
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime',
    }),
],

すべての変更により、Webpack 4でそれを行う方法がわかりません。CommonChunksPluginが削除されたことを知っているので、それを実現する別の方法があります。 このチュートリアル も読みましたが、ランタイムチャンクを抽出し、outputプロパティを適切に定義することについてはまだ確信がありません。

EDIT:残念ながら、ここで最も人気のある回答で問題が発生していました。 私の答え をご覧ください。

43

ここにいくつかの例があります: https://github.com/webpack/webpack/tree/master/examples

あなたの例に基づいて、私はこれがに翻訳すると信じています:

// mode: "development || "production",
entry: {
  client: './client.js',
},
output: {
  path: path.join(__dirname, '../dist'),
  filename: '[name].chunkhash.bundle.js',
  chunkFilename: '[name].chunkhash.bundle.js',
  publicPath: '/',
},
optimization: {
  splitChunks: {
    cacheGroups: {
      vendor: {
        chunks: 'initial',
        name: 'vendor',
        test: 'vendor',
        enforce: true
      },
    }
  },
  runtimeChunk: true
}
26
glued

エントリプロパティからベンダーを削除し、最適化プロパティを次のように設定できます。

entry: {
 client: './client.js'
},

output: {
 path: path.join(__dirname, '../dist'),
 filename: '[name].chunkhash.bundle.js',
 chunkFilename: '[name].chunkhash.bundle.js',
 publicPath: '/',
},

optimization: {
  splitChunks: {
   cacheGroups: {
    vendor: {
     test: /node_modules/,
     chunks: 'initial',
     name: 'vendor',
     enforce: true
    },
   }
  } 
 }

このソースを確認してください webpackの例

22
jhamPac

vendorsruntimeを分離するには、 optimizationオプション。

可能なWebpack 4構成:

// mode: 'development' | 'production' | 'none'

entry: {
    client: ['./client.js'],
    vendor: ['babel-polyfill', 'react', 'react-dom', 'redux'],
},

output: {
    filename: '[name].[chunkhash].bundle.js',
    path: '../dist',
    chunkFilename: '[name].[chunkhash].bundle.js',
    publicPath: '/',
},

optimization: {
    runtimeChunk: 'single',
    splitChunks: {
        cacheGroups: {
            vendor: {
                test: /[\\/]node_modules[\\/]/,
                name: 'vendors',
                enforce: true,
                chunks: 'all'
            }
        }
    }
}

W4に関連する詳細情報は、この Webpack-Demo にあります。

また、optimization.splitChunks.chunksプロパティを"all"に変更することでも同じことができます。続きを読む こちら

注:optimization.splitChunksで設定できます。例ではチャンクについて説明していますが、デフォルトでは非同期チャンクに対してのみ機能しますが、optimization.splitChunks.chunks: "all"を使用すると初期チャンクにも同じことが当てはまります。

16
Carloluis

ベンダーのjsバンドルサイズを減らすため。ノードモジュールパッケージを異なるバンドルファイルに分割できます。私はこれを参照しました blog webpackによって生成された巨大なベンダーファイルを分割するため。最初に使用したリンクの要点:

optimization: {
   runtimeChunk: 'single',
   splitChunks: {
    chunks: 'all',
    maxInitialRequests: Infinity,
    minSize: 0,
    cacheGroups: {
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        name(module) {
        // get the name. E.g. node_modules/packageName/not/this/part.js
        // or node_modules/packageName
        const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];

      // npm package names are URL-safe, but some servers don't like @ symbols
      return `npm.${packageName.replace('@', '')}`;
      },
    },
  },
 },
}

複数のパッケージをグループ化し、異なるバンドルにまとめたい場合は、次の要点を参照してください。

optimization: {
runtimeChunk: 'single',
  splitChunks: {
    chunks: 'all',
    maxInitialRequests: Infinity,
    minSize: 0,
    cacheGroups: {
      reactVendor: {
        test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
        name: "reactvendor"
      },
      utilityVendor: {
        test: /[\\/]node_modules[\\/](lodash|moment|moment-timezone)[\\/]/,
        name: "utilityVendor"
      },
      bootstrapVendor: {
        test: /[\\/]node_modules[\\/](react-bootstrap)[\\/]/,
        name: "bootstrapVendor"
      },
      vendor: {
         test: /[\\/]node_modules[\\/](!react-bootstrap)(!lodash)(!moment)(!moment-timezone)[\\/]/,
      name: "vendor"
    },
    },
  },
}
12
swapnil2993

しばらくして、この構成がわかった:

entry: {
  vendor: ['@babel/polyfill', 'react', 'react-dom', 'redux'],
  client: './client.js',
},
optimization: {
  splitChunks: {
    cacheGroups: {
      vendor: {
        chunks: 'initial',
        name: 'vendor',
        test: 'vendor',
        enforce: true
      },
    }
  },
  runtimeChunk: true
}

ブラウザの非互換性エラーを引き起こしていた@babel/polyfillの読み込みに何とか失敗していました...それで最近、 更新されたwebpackドキュメント を調べ、 a way を見つけました@babel/polyfillを適切にロードしていた明示的なベンダーチャンク:

const moduleList = ["@babel/polyfill", "react", "react-dom"];
...

  entry: {
    client: ["@babel/polyfill", "../src/client.js"]
  }
  optimization: {
    runtimeChunk: "single",
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: new RegExp(
            `[\\/]node_modules[\\/](${moduleList.join("|")})[\\/]`
          ),
          chunks: "initial",
          name: "vendors",
          enforce: true
        }
      }
    }
  }

含まれているコードのalloneエントリとthensplitChunks.cacheGroups.vendor.testでどのモジュールをvendorチャンクに分割するかを指定します。

それでも、これが100%正しいかどうか、またはこれが文字通りこれまでで最も紛らわしいものの1つであるため、改善できるかどうかはわかりません。ただし、これはドキュメントに最も近いようで、 webpack-bundle-analyzer で検査すると正しいチャンクが生成されるようです(変更されたチャンクのみが更新され、残りはビルド全体で同じままです) )polyfillの問題を修正します。

9

あなたがこれを行うと思います:

optimization: {
    splitChunks: {
        chunks: 'all',
    },
    runtimeChunk: true,
}

vendors~およびruntime~チャンクが作成されます。 ソクラは言ったsplitChunksのデフォルトはこれです:

splitChunks: {
    chunks: "async",
    minSize: 30000,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    name: true,
    cacheGroups: {
        default: {
            minChunks: 2,
            priority: -20
            reuseExistingChunk: true,
        },
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        }
    }
}

既にvendorsおよびdefaultバンドルが含まれています。テストでは、defaultバンドルが表示されていません。

これらのファイルを含めるために予想されるワークフローが何であるかはわかりませんが、このヘルパー関数をPHPで作成しました。

public static function webpack_asset($chunkName, $extensions=null, $media=false) {
    static $stats;
    if($stats === null) {
        $stats = WxJson::loadFile(WX::$path.'/webpack.stats.json');
    }
    $paths = WXU::array_get($stats,['assetsByChunkName',$chunkName],false);
    if($paths === false) {
        throw new \Exception("webpack asset not found: $chunkName");
    }
    foreach($stats['assetsByChunkName'] as $cn => $files) {
        if(self::EndsWith($cn, '~' . $chunkName)) {
            // prepend additional supporting chunks
            $paths = array_merge($files, $paths);
        }
    }
    $html = [];
    foreach((array)$paths as $p) {
        $ext = WXU::GetFileExt($p);
        if($extensions) {
            if(is_array($extensions)) {
                if(!in_array($ext,$extensions)) {
                    continue;
                }
            } elseif(is_string($extensions)) {
                if($ext !== $extensions) {
                    continue;
                }
            } else {
                throw new \Exception("Unexpected type for \$extensions: ".WXU::get_type($extensions));
            }
        }
        switch($ext) {
            case 'js':
                $html[] = WXU::html_tag('script',['src'=>$stats['publicPath'].$p,'charset'=>'utf-8'],'');
                break;
            case 'css':
                $html[] = WXU::html_tag('link',['href'=>$stats['publicPath'].$p,'rel'=>'stylesheet','type'=>'text/css','media'=>$media],null); // "charset=utf-8" doesn't work in IE8
                break;
        }
    }
    return implode(PHP_EOL, $html);
}

これは私の資産プラグインで動作します(WP4用に更新されました):

{
    apply: function(compiler) {
        //let compilerOpts = this._compiler.options;
        compiler.plugin('done', function(stats, done) {
            let assets = {};
            stats.compilation.namedChunks.forEach((chunk, name) => {
                assets[name] = chunk.files;
            });

            fs.writeFile('webpack.stats.json', JSON.stringify({
                assetsByChunkName: assets,
                publicPath: stats.compilation.outputOptions.publicPath
            }), done);
        });
    }
},

これはすべて次のようなものを吐き出します:

<script src="/assets/runtime~main.a23dfea309e23d13bfcb.js" charset="utf-8"></script>
<link href="/assets/chunk.81da97be08338e4f2807.css" rel="stylesheet" type="text/css"/>
<script src="/assets/chunk.81da97be08338e4f2807.js" charset="utf-8"></script>
<link href="/assets/chunk.b0b8758057b023f28d41.css" rel="stylesheet" type="text/css"/>
<script src="/assets/chunk.b0b8758057b023f28d41.js" charset="utf-8"></script>
<link href="/assets/chunk.00ae08b2c535eb95bb2e.css" rel="stylesheet" type="text/css" media="print"/>

カスタムJSファイルの1つを変更すると、これらのJSチャンクの1つだけが変更されます。ランタイムもベンダーバンドルも更新する必要はありません。

Iadd新しいJSファイルとrequire itを追加しても、ランタイムは更新されません。新しいファイルはメインバンドルにコンパイルされるだけなので、動的にインポートされないため、マッピングする必要はないと思います。コード分​​割を引き起こすimport() itの場合、thenランタイムが更新されます。ベンダーバンドルalsoも変更されたようです-理由はわかりません。それは避けるべきだと思った。

また、ファイルごとのハッシュを実行する方法を理解していません。 .cssファイルと同じチャンクである.jsファイルを変更すると、両方のファイル名が[chunkhash]で変更されます。


上記のアセットプラグインを更新しました。 <script>タグを含める順序が重要になると思います...これにより、その順序が維持されます。

const fs = require('fs');

class EntryChunksPlugin {

    constructor(options) {
        this.filename = options.filename;
    }

    apply(compiler) {
        compiler.plugin('done', (stats, done) => {
            let assets = {};

            // do we need to use the chunkGraph instead to determine order??? https://Gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693#gistcomment-2381967
            for(let chunkGroup of stats.compilation.chunkGroups) {
                if(chunkGroup.name) {
                    let files = [];
                    for(let chunk of chunkGroup.chunks) {
                        files.Push(...chunk.files);
                    }
                    assets[chunkGroup.name] = files;
                }
            }

            fs.writeFile(this.filename, JSON.stringify({
                assetsByChunkName: assets,
                publicPath: stats.compilation.outputOptions.publicPath
            }), done);
        });
    }
}

module.exports = EntryChunksPlugin;
6
mpen

エントリファイルの順序も重要なようです。ベンダーの前にclient.jsがあるため、メインアプリの前にベンダーのバンドルは発生しません。

entry: {
 vendor: ['react', 'react-dom', 'react-router'],
 app: paths.appIndexJs
},

SplitChunks最適化により、出力ファイル名を指定し、エントリ名ベンダーを次のように参照できます。

optimization: {
 splitChunks: {
  cacheGroups: {
    // match the entry point and spit out the file named here
    vendor: {
      chunks: 'initial',
      name: 'vendor',
      test: 'vendor',
      filename: 'vendor.js',
      enforce: true,
    },
  },
 },
},
2
Vinayak Bagaria