Webpack 4 - 创建供应商块

Tom*_*zyk 44 node.js webpack code-splitting 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',
    }),
],
Run Code Online (Sandbox Code Playgroud)

通过所有更改,我不知道如何使用Webpack 4.我知道这CommonChunksPlugin已被删除,因此有不同的方法来实现它.我也阅读了本教程,但我仍然不确定是否提取运行时块并正确定义output属性.

编辑: 不幸的是,我遇到了最流行的答案问题.看看我的答案.

glu*_*ued 26

这里有几个例子: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
}
Run Code Online (Sandbox Code Playgroud)

  • 在`entry:`中有一个`vendor:`似乎与文档相矛盾."不要为供应商或其他不是执行起点的东西创建条目." https://webpack.js.org/concepts/entry-points/#separate-app-and-vendor-entries (3认同)
  • 它给了我一些奇怪的结果.`client.js`没有变小,`vendor.js`几乎和`client.js`一样大,包含一些动态导入的包. (2认同)
  • 不过有趣的是,现在块的整体大小更大了,让我想知道这是否值得。 (2认同)

jha*_*Pac 23

您可以从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
    },
   }
  } 
 }
Run Code Online (Sandbox Code Playgroud)

查看此源webpack示例

  • 有用!但是......它捆绑了来自`node_modules`的每个包,这是不理想的.供应商捆绑变大了 2.如果你甚至升级一个小包,整个捆绑包将在下一次构建时得到不同的哈希 - 这将超过供应商块用于长期缓存的想法. (6认同)
  • 但是,如何在供应商块中指定我想要的包? (3认同)

Car*_*uis 17

为了分离供应商运行时,您需要使用该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'
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 你能告诉我这里的“初始”是什么吗? (2认同)

swa*_*993 13

为了减小供应商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('@', '')}`;
      },
    },
  },
 },
}
Run Code Online (Sandbox Code Playgroud)

如果要分组多个包和块,然后分成不同的捆绑包,请参考以下要点。

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"
    },
    },
  },
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢@swapnil2993。这是过去 4 个小时搜索的内容。 (4认同)
  • 为什么不从你的供应商块中排除react和react-dom呢? (4认同)
  • 为什么在“测试”中使用排除模式?尝试添加“priority”(默认为0),每个可能的模块将被具有最高优先级的缓存组“test”捕获。 (2认同)

Tom*_*zyk 11

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

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
}
Run Code Online (Sandbox Code Playgroud)

无法以某种方式加载@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
        }
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

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

不过,我不确定这是100%正确还是可以改进,因为这实际上是最混乱的事情之一.但是,这似乎与文档最接近,当我用webpack-bundle-analyzer检查它们时似乎产生了正确的块(只更新了已更改的块,其余部分在构建中保持不变)并修复了polyfill的问题.

  • "这实际上是有史以来最混乱的事情之一"webpack一般 (14认同)
  • 我知道webpack的设计非常灵活且可配置,因此这使得配置更加复杂...但是构建应用程序捆绑包/供应商捆绑包看起来很基本/标准。疯狂的是,没有关于如何实现这一点的清晰描述:( (2认同)

Mot*_*ine 10

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

optimization: {
  splitChunks: { name: 'vendor', chunks: 'all' }
}
Run Code Online (Sandbox Code Playgroud)

splitChunks.name以字符串形式给出时,文档说:“指定一个字符串或一个始终返回相同字符串的函数会将所有公共模块和供应商合并到一个块中。” 与 结合使用splitChunks.chunks,它将提取所有依赖项。


mpe*_*pen 6

我想如果你这样做:

optimization: {
    splitChunks: {
        chunks: 'all',
    },
    runtimeChunk: true,
}
Run Code Online (Sandbox Code Playgroud)

这将创建一个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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

其中已包含a vendorsdefaultbundle.在测试中,我还没有看到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);
}
Run Code Online (Sandbox Code Playgroud)

哪个适用于我的资产插件(针对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);
        });
    }
},
Run Code Online (Sandbox Code Playgroud)

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

<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"/>
Run Code Online (Sandbox Code Playgroud)

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

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

我还没弄明白如何进行每个文件的哈希.如果修改与.css文件相同的块的.js文件,则它们的文件名都将随之改变[chunkhash].


我更新了上面的资产插件.我认为你包含<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;
Run Code Online (Sandbox Code Playgroud)