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 собственности.

РЕДАКТИРОВАТЬ: К сожалению, у меня возникли проблемы с самым популярным ответом здесь. Ознакомьтесь с моим ответом.


person Tomasz Mularczyk    schedule 26.02.2018    source источник


Ответы (8)


Чтобы уменьшить размер пакета 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"
      },
    },
  },
}
person swapnil2993    schedule 24.10.2018
comment
Спасибо @ swapnil2993. Это то, что искал последние 4 часа. - person pramodpxi; 14.02.2020
comment
Почему бы вам не исключить react и react-dom из блока вашего поставщика? - person Jake; 04.04.2020
comment
Спасибо, очень полезно для анализа покрытия кода в запущенном приложении! - person Dan F; 24.04.2020

Здесь находится несколько примеров: 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
}
person glued    schedule 27.02.2018
comment
Это дает мне странные результаты. client.js не становится меньше, а vendor.js почти такой же, как client.js, содержащий некоторые динамически импортируемые пакеты. - person Tomasz Mularczyk; 27.02.2018
comment
Интересно, однако, что теперь размер кусков стал больше, что заставляет меня задуматься, стоило ли оно того. - person Tomasz Mularczyk; 28.02.2018
comment
@TomaszMularczyk Я думаю, дело в том, что код поставщика редко меняется и кешируется, в то время как ваша логика получает новый хэш фрагмента каждый раз, когда он изменяется. В идеале пользователь загружает только логический фрагмент кода при посещении сайта. - person Vahid PG; 18.07.2018
comment
Наличие vendor: в entry: противоречит документации. Не создавайте записи для поставщиков или другого материала, который не является отправной точкой выполнения. webpack.js.org/concepts/entry-points/ - person cjones; 14.11.2018
comment
Одна вещь, которую, похоже, пытался сделать OP, но я считаю, что это не касается, - это включение конкретных зависимостей в файл поставщика. Итак, что вы делали до webpack 4, например vendor: ['babel-polyfill', 'react', 'react-dom', 'redux'], вместо этого вы бы сделали, украсив точки входа, как это ["@babel/polyfill", "./client.js"] (babeljs.io/docs/en/babel-polyfill), а также в свойстве vendor объекта объект cacheGroups следующим образом: test: /[\\/]node_modules[\\/][@babel\/polyfill|react|react\-dom|redux]/ webpack.js. org / plugins / split-chunks-plugin / - person Jay; 29.01.2019
comment
@TomaszMularczyk Вы можете дополнительно оптимизировать те блоки конкретных поставщиков, которые вам не часто нужны, путем их асинхронной загрузки, когда они вам нужны во время выполнения. - person html_programmer; 15.05.2019

Вы можете удалить поставщика из свойства ввода и установить свойство оптимизации следующим образом ...

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

Проверьте этот источник примеры веб-пакетов

person jhamPac    schedule 10.03.2018
comment
но как мне указать, какие пакеты мне нужны в блоке поставщика? - person Tomasz Mularczyk; 10.03.2018
comment
@Tomasz webpack проверит, какие пакеты вы используете в своем проекте, с помощью операторов импорта, а затем автоматически передаст их поставщику. - person jhamPac; 10.03.2018
comment
Оно работает! Однако ... он объединяет все пакеты из node_modules, что не идеально. 1. Набор поставщиков становится большим. 2. если вы обновите хотя бы один маленький пакет, весь пакет получит другой хэш при следующей сборке - что превзойдет идею наличия блока поставщика для долгосрочного кэширования. - person Tomasz Mularczyk; 10.03.2018
comment
В самом деле? каждый пакет в node_modules. ???? Я не могу воспроизвести это. вы придумали решение? - person jhamPac; 11.03.2018
comment
Принятый ответ - это решение. В любом случае, я не мог представить, что webpack решит за меня, какой пакет следует включить в комплект поставщика, потому что лучше всего включать только пакеты, которые редко меняются. - person Tomasz Mularczyk; 11.03.2018
comment
@jhamPac Как я могу заставить кеш-группу поставщиков скомпилироваться с помощью Babel? - person Jousi; 27.08.2018
comment
@jousi, что ты имеешь в виду? Есть ли у вас вопрос относительно того, как его скомпилировать с помощью babel-loader. Вам нужно будет установить это в свойстве rules. - person jhamPac; 28.08.2018

Чтобы разделить поставщиков и среду выполнения, вам необходимо использовать параметр 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" то же самое будет верно для начальных фрагментов.

person Carloluis    schedule 22.03.2018
comment
Не могли бы вы мне сказать, что здесь начальное? - person hemal7735; 06.06.2018
comment
Как я могу заставить кеш-группу поставщиков скомпилироваться с помощью Babel? @Carloluis - person Jousi; 27.08.2018
comment
Спасибо ребята.... - person Mahdi zoraghi; 23.02.2021

Спустя какое-то время я обнаружил, что эта конфигурация:

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:

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, какие модули следует разделить на поставщик.

Тем не менее, я не уверен, правильно ли это на 100% или его можно улучшить, поскольку это буквально одна из самых запутанных вещей. Тем не менее, это кажется наиболее близким к документации, кажется, дает правильные фрагменты, когда я проверяю их с помощью webpack-bundle-analyzer (обновляет только те блоки, которые были изменены, а остальные остаются неизменными во всех сборках) и устраняет проблему с помощью polyfill.

person Tomasz Mularczyk    schedule 26.02.2018
comment
это буквально одна из самых запутанных вещей, когда-либо существовавших в веб-пакетах. - person digout; 28.09.2018
comment
Я понимаю, что webpack спроектирован так, чтобы быть очень гибким и настраиваемым, поэтому это усложняет настройку ... но создание пакета приложения / пакета поставщика выглядит довольно простым / стандартным требованием. Безумие, что нет четкого описания, как этого добиться :( - person Philippe; 10.03.2019
comment
Меня это совсем не смущает. Я вижу здесь читабельное, логичное и правильное решение. (Но конечно ... Я уже привык к безумию webpack: D) - person loopmode; 18.07.2019
comment
Не могли бы вы подробнее рассказать, как вы использовали webpack-bundle-analyzer, чтобы определить, какие фрагменты изменились в результате изменений кода? Вы просто имеете в виду, что вручную проверили отображение дерева вывода до и после? - person mowwwalker; 07.12.2020

Я думаю, если вы сделаете это:

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, изменяется только один из этих фрагментов 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;
person mpen    schedule 16.03.2018
comment
Как я могу заставить кеш-группу поставщиков скомпилироваться с помощью Babel? - person Jousi; 27.08.2018

Я нашел гораздо более короткий способ сделать это:

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

Когда splitChunks.name представлен в виде строки, в документации говорится: " Указание либо строки, либо функции, которая всегда возвращает одну и ту же строку, объединит все распространенные модули и поставщиков в один блок ». В сочетании с splitChunks.chunks он извлечет все зависимости.

person Motine    schedule 12.03.2020

Кажется, также имеет значение порядок файлов ввода. Поскольку у вас есть 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,
    },
  },
 },
},
person Vinayak Bagaria    schedule 17.12.2018