node.js - cachegroups - webpack split chunks vendor
Webpack 4-crear trozo de proveedor (6)
Con el fin de reducir el tamaño del paquete js vendedor. Podemos dividir los paquetes de módulos de nodo en diferentes archivos de paquete. Me referí a este blog para dividir el voluminoso archivo de proveedor generado por el paquete web. Lo esencial de ese enlace que usé inicialmente:
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(''@'', '''')}`;
},
},
},
},
}
Si uno quiere agrupar varios paquetes y partes en diferentes paquetes, consulte la siguiente información.
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"
},
},
},
}
En una configuración de webpack 3, usaría el siguiente código para crear una porción vendor.js
separada:
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'',
}),
],
Con todos los cambios, no estoy seguro de cómo hacerlo con Webpack 4. Sé que CommonChunksPlugin
se eliminó, así que hay una manera diferente de lograrlo. También he leído este tutorial pero aún no estoy seguro de extraer el fragmento de tiempo de ejecución y de definir correctamente output
propiedad de output
.
EDIT: Desafortunadamente, estaba experimentando problemas con la respuesta más popular aquí. Echa un vistazo a mi respuesta .
Creo que si haces esto:
optimization: {
splitChunks: {
chunks: ''all'',
},
runtimeChunk: true,
}
Creará una porción de vendors~
y runtime~
para usted. Sokra dijo que el valor predeterminado para splitChunks
es este:
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
}
}
}
Que ya incluye un vendors
y paquete default
. En las pruebas, no he visto aparecer un paquete default
.
No sé cuál es el flujo de trabajo esperado para incluir estos archivos, pero escribí esta función auxiliar en 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);
}
Que funciona con mi complemento de activos (actualizado para 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);
});
}
},
Todo esto escupe algo como:
<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"/>
Ahora cuando modifico uno de mis archivos JS personalizados, solo uno de esos fragmentos JS cambia. Ni el tiempo de ejecución ni el paquete de proveedores deben actualizarse.
Si agrego un nuevo archivo JS y lo require
, el tiempo de ejecución aún no está actualizado. Creo que porque el nuevo archivo solo se compilará en el paquete principal, no es necesario que esté en el mapeo porque no se importa dinámicamente. Si lo import()
, lo que causa la división del código, el tiempo de ejecución se actualiza. El paquete de proveedores también parece haber cambiado, no estoy seguro de por qué. Pensé que se suponía que se debía evitar.
Tampoco he descubierto cómo hacer hashes por archivo. Si modifica un archivo .js que es el mismo fragmento que un archivo .css, ambos nombres de archivos cambiarán con [chunkhash]
.
He actualizado el plugin de activos arriba. Creo que el orden en el que incluyes las etiquetas <script>
puede ser importante ... esto mantendrá ese orden 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;
Después de un tiempo descubrí que esta configuración:
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
}
faltaba de alguna manera cargar @babel/polyfill
que estaba causando errores de incompatibilidad con el navegador ... Así que hace poco miré la documentación actualizada del paquete web y encontré una manera de crear un fragmento de proveedor explícito que cargaba correctamente @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
}
}
}
}
Tenga en cuenta que creo una entrada con todo el código incluido y luego especifico con splitChunks.cacheGroups.vendor.test
qué módulos deben dividirse en la parte del proveedor .
- Documentación del webpack en SplitChunksPlugin .
- Guía de webpack sobre caching en caching
- Webpack answer autor sobre el mismo problema.
Sin embargo, no estoy seguro si esto es correcto al 100% o si podría mejorarse, ya que es, literalmente, una de las cosas más confusas de la historia. Sin embargo, esto parece ser lo más parecido a la documentación, parece producir fragmentos correctos cuando los inspecciono con webpack-bundle-analyzer (solo actualiza los fragmentos que se cambiaron y el resto de ellos sigue igual) y soluciona el problema con polyfill .
Hay algunos ejemplos que se encuentran aquí: https://github.com/webpack/webpack/tree/master/examples
Basado en tu ejemplo creo que esto se traduce a:
// 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
}
Para separar los proveedores y el tiempo de ejecución , debe utilizar la opción de optimization
.
Posible configuración del 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: {
vendors: {
test: /[///]node_modules[///]/,
name: ''vendors'',
enforce: true,
chunks: ''all''
}
}
}
}
Más información relacionada con W4 se puede encontrar en esta Webpack-Demo .
Además, puede lograr lo mismo cambiando la propiedad optimization.splitChunks.chunks
a "all"
. Lea más here
Nota: Puede configurarlo a través de
optimization.splitChunks
. Los ejemplos dicen algo acerca de los fragmentos, de manera predeterminada, solo funciona para los fragmentos asíncronos, pero conoptimization.splitChunks.chunks: "all"
lo mismo sería cierto para los fragmentos iniciales.
Podría eliminar al proveedor de la propiedad de entrada y establecer la propiedad de optimización como ...
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
},
}
}
}
Revisa los ejemplos de esta fuente webpack