node.js Webpack 4 - 创建供应商块

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/48985780/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 18:42:43  来源:igfitidea点击:

Webpack 4 - create vendor chunk

node.jswebpackcode-splittingwebpack-4

提问by Tomasz Mularczyk

In a webpack 3 configuration I would use the code below to create separate vendor.jschunk:

在 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',
    }),
],

With all the changes I'm not sure how to do it with Webpack 4. I know that CommonChunksPluginwas removed, so there is a different way to achieve that. I've also read this tutorialbut I'm still not sure about extracting runtime chunk and properly defining outputproperty.

对于所有更改,我不确定如何使用 Webpack 4。我知道它CommonChunksPlugin已被删除,因此有一种不同的方法可以实现。我也阅读了本教程,但我仍然不确定如何提取运行时块并正确定义output属性。

EDIT:Unfortunately, I was experiencing issues with the most popular answer here. Check out my answer.

编辑:不幸的是,我在这里遇到了最受欢迎的答案问题。看看我的回答

回答by glued

There are a few examples located here: https://github.com/webpack/webpack/tree/master/examples

这里有几个例子:https: //github.com/webpack/webpack/tree/master/examples

Based on your example i believe this translate to:

根据您的示例,我相信这会转化为:

// 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
}

回答by jhamPac

You could remove vendor out of the entry property and set the optimization property like so...

您可以从 entry 属性中删除 vendor 并像这样设置优化属性...

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
    },
   }
  } 
 }

Check this source webpack examples

检查此源webpack 示例

回答by swapnil2993

In order to reduce the vendor js bundle size. We can split the node module packages into different bundle file. I referred this blogfor splitting the bulky vendor file generated by webpack. Gist of that link which I used initially:

为了减少供应商 js 包的大小。我们可以将节点模块包拆分为不同的捆绑文件。我参考这个博客来拆分由 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('@', '')}`;
      },
    },
  },
 },
}

If one wants to group multiple packages and chunk then into different bundles then refer following gist.

如果想要将多个包分组并分块,然后分成不同的包,请参考以下要点。

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"
    },
    },
  },
}

回答by Carloluis

In order to separate the vendorsand the runtimeyou need to use the optimizationoption.

为了将供应商运行时分开,您需要使用该optimization选项。

Possible Webpack 4 configuration:

可能的 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'
            }
        }
    }
}

More info related with W4 can be found in this Webpack-Demo.

更多与 W4 相关的信息可以在这个Webpack-Demo 中找到

Also, you can achieve the same changing the optimization.splitChunks.chunksproperty to "all". Read more here

此外,您可以实现相同的将optimization.splitChunks.chunks属性更改为"all". 在这里阅读更多

Note: You can configure it via optimization.splitChunks. The examples say something about chunks, by default it only works for async chunks, but with optimization.splitChunks.chunks: "all"the same would be true for initial chunks.

注意:您可以通过optimization.splitChunks. 这些示例说明了一些关于块的内容,默认情况下它仅适用于异步块,但optimization.splitChunks.chunks: "all"对于初始块也是如此。

回答by Tomasz Mularczyk

After some time I found out that this configuration:

一段时间后我发现这个配置:

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
}

was failing to somehow to load @babel/polyfillwhich was causing browser incompatibility errors... So recently I looked up to the updated webpack documentationand found a wayto create explicit vendor chunk that was properly loading @babel/polyfill:

无法以某种方式加载@babel/polyfill导致浏览器不兼容错误......所以最近我查看了更新的 webpack 文档并找到了一种创建正确加载的显式供应商块的方法@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
        }
      }
    }
  }

Notice that I create oneentry with allof the code included and thenI specify with splitChunks.cacheGroups.vendor.testwhich modules should be split out to the vendorchunk.

请注意,我创建了一个包含所有代码的条目,然后指定splitChunks.cacheGroups.vendor.test应将哪些模块拆分为供应商块。

  • SplitChunksPlugin 上的Webpack文档。
  • Webpack缓存指南
  • Webpack 作者对同一问题的回答

Still, I'm not sure if this is 100% correct or if it could be improved as this is literally one of the most confusing things ever. However, this seems to be closest to the documentation, seems to produce correct chunks when I inspect them with webpack-bundle-analyzer(only updates the chunks that were changed and rest of them stays the same across builds) and fixes the issue with polyfill.

不过,我不确定这是否 100% 正确,或者是否可以改进,因为这实际上是有史以来最令人困惑的事情之一。但是,这似乎与文档最接近,当我使用webpack-bundle-analyzer检查它们时似乎会产生正确的块(仅更新已更改的块,其余的在构建中保持不变)并修复了polyfill的问题.

回答by mpen

I think if you do this:

我想如果你这样做:

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

It will create a vendors~and runtime~chunk for you. Sokra saidthe default for splitChunksis this:

它将为您创建一个vendors~runtime~块。Sokra 说默认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
        }
    }
}

Which already includes a vendorsand defaultbundle. In testing, I haven't seen a defaultbundle appear.

其中已经包括一个vendorsdefault捆绑。在测试中,我还没有看到default捆绑包出现。

I don't know what the expected workflow for including these files is, but I wrote this helper function in PHP:

我不知道包含这些文件的预期工作流程是什么,但我用 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);
}

Which works with my assets plugin (updated for WP4):

适用于我的资产插件(针对 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);
        });
    }
},

All of this spits out something like:

所有这些都吐出类似的东西:

<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"/>

Now when I modify one of my custom JS files, only one of those JS chunks changes. Neither the runtime nor the vendors bundle needs to be updated.

现在,当我修改我的自定义 JS 文件之一时,只有其中一个 JS 块会发生变化。运行时和供应商包都不需要更新。

If I adda new JS file and requireit, the runtime still isn't updated. I think because the new file will just be compiled into the main bundle -- it doesn't need to be in the mapping because it's not dynamically imported. If I import()it, which causes code-splitting, thenthe runtime gets updated. The vendors bundle alsoappears to have changed -- I'm not sure why. I thought that was supposed to be avoided.

如果我添加一个新的 JS 文件require,运行时仍然没有更新。我认为因为新文件只会被编译到主包中——它不需要在映射中,因为它不是动态导入的。如果import()它会导致代码拆分,运行时会更新。这些厂商捆绑似乎已经改变了-我不知道为什么。我认为应该避免这种情况。

I also haven't figured out how to do per-file hashes. If you modify a .js file which is the same chunk as a .css file, both their filenames will change with [chunkhash].

我也没有弄清楚如何进行每个文件的哈希。如果您修改与 .css 文件相同块的 .js 文件,则它们的文件名都将更改为[chunkhash].



I updated the assets plugin above. I think the order in which you include the <script>tags might matter... this will maintain that order AFAICT:

我更新了上面的资产插件。我认为您包含<script>标签的顺序可能很重要……这将保持该顺序 AFAICT:

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;

回答by Vinayak Bagaria

It seems the order of entry filesalso matter. Since you have client.js before vendor, the bundling doesn't happen of vendor before your main app.

似乎入口文件顺序也很重要。由于您在供应商之前拥有 client.js,因此在您的主应用程序之前供应商不会发生捆绑。

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

Now with the SplitChunksoptimisation you can specify the output file name and refer to the entry name vendor as:

现在使用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,
    },
  },
 },
},

回答by Motine

I found a much shorter way to do this:

我找到了一种更短的方法来做到这一点:

optimization: {
  splitChunks: { name: 'vendor', chunks: 'all' }
}

When splitChunks.nameis given as a string, the documentationsays: "Specifying either a string or a function that always returns the same string will merge all common modules and vendors into a single chunk." In combination with splitChunks.chunks, it will extract all dependencies.

splitChunks.name作为字符串给出时,文档说:“指定一个字符串或一个总是返回相同字符串的函数将把所有常见的模块和供应商合并成一个单一的块。” 与 结合使用splitChunks.chunks,它将提取所有依赖项。