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
.
РЕДАКТИРОВАТЬ: К сожалению, у меня возникли проблемы с наиболее популярным ответом здесь. Проверьте мой ответ.
Ответы
Ответ 1
Вот несколько примеров: 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
}
Ответ 2
Вы можете удалить vendor из свойства entry и установить свойство оптимизации, например, так...
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
},
}
}
}
Проверьте этот источник примеры веб-пакетов
Ответ 3
Чтобы разделить поставщиков и среду выполнения, вам нужно использовать опцию 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.
Кроме того, вы можете добиться того же, изменив свойство optimization.splitChunks.chunks
на "all"
. Узнайте больше здесь
Примечание. Вы можете настроить его с помощью optimization.splitChunks
. Примеры говорят что-то о чанках, по умолчанию это работает только для асинхронных чанков, но с optimization.splitChunks.chunks: "all"
то же самое будет верно для начальных чанков.
Ответ 4
Для уменьшения размера комплекта 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('@', '')}';
},
},
},
},
}
Если кто-то хочет сгруппировать несколько пакетов и разбить их на части, то обратитесь к следующему пункту.
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"
},
},
},
}
Ответ 5
Через некоторое время я узнал, что это конфигурация:
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
что @babel/polyfill
к ошибкам несовместимости браузера... Поэтому недавно я посмотрел обновленную документацию веб-пакета и нашел способ создать явный чанк поставщика, который правильно загружал @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
}
}
}
}
Обратите внимание, что я создаю одну запись со всем включенным кодом, а затем указываю в splitChunks.cacheGroups.vendor.test
какие модули должны быть splitChunks.cacheGroups.vendor.test
в блок вендора.
Тем не менее, я не уверен, правильно ли это на 100% или можно ли его улучшить, поскольку это буквально одна из самых запутанных вещей за всю историю. Тем не менее, это, кажется, ближе всего к документации, похоже, производит правильные чанки, когда я проверяю их с помощью webpack-bundle-analyzer (обновляет только чанки, которые были изменены, а остальные остаются неизменными во всех сборках) и исправляет проблему с polyfill,
Ответ 6
Я думаю, если вы это сделаете:
optimization: {
splitChunks: {
chunks: 'all',
},
runtimeChunk: true,
}
Он создаст для вас фрагмент 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
}
}
}
который уже включает в себя пакет 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 файлов, изменяется только один из этих JS-фрагментов. Не нужно обновлять среду выполнения и пакет поставщиков.
Если я добавлю новый JS файл и require
, он будет обновлен. Я думаю, потому что новый файл будет просто скомпилирован в основной пакет - он не должен находиться в отображении, потому что он не динамически импортируется. Если я import()
он, который вызывает разделение кода, то среда выполнения обновляется. Сообщается, что пакет поставщиков также изменился - я не уверен, почему. Я думал, что этого следует избегать.
Я также не понял, как делать хэши для каждого файла. Если вы измените файл .js, который является тем же самым фрагментом, что и файл .css, оба их имени будут изменены с помощью [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;
Ответ 7
Кажется, порядок входных файлов также имеет значение. Поскольку у вас есть 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,
},
},
},
},
Ответ 8
Это выходные данные сборки приложения React, выполняемой путем выполнения команды yarn build:prod
в Crisp React:
5,770 LoginSPA.6cd2f37becf02e095e2e.bundle.js
3,260 MainSPA.6cd2f37becf02e095e2e.bundle.js
1,591 runtime.6cd2f37becf02e095e2e.bundle.js
275,323 vendor.6cd2f37becf02e095e2e.bundle.js
70,088 vendor.6cd2f37becf02e095e2e.bundle.js.br
83,117 vendor.6cd2f37becf02e095e2e.bundle.js.gz
Приложение React разделено на 2 SPA: "Логин" и "Главный". Каждое СПА оформляется отдельным пакетом. Другие пакеты используются повторно. Пакет vendor автоматически сжимается (используя gzip и Brotli) из-за его размера.
webpack.config.js использует
splitChunks: {
cacheGroups: {
vendor: {
test: /node_modules/,
chunks: 'initial',
name: 'vendor',
enforce: true
},
},
},
Как видите, размер сжатого пакета "вендора" составляет 70 КБ. Учитывая, что большинство браузеров теперь поддерживают Brotli, такое расположение вполне разумно.
Решение гарантирует, что старые клиенты запрашивают и получают gzip или несжатый пакет.
Просто для справки: выполните yarn build
, чтобы выполнить разработку, и размер "поставщика" составляет 3,58 МБ.