web-dev-qa-db-fra.com

Webpack 4 - créer un bloc fournisseur

Dans une configuration de Webpack 3, j'utiliserais le code ci-dessous pour créer un bloc vendor.js séparé:

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

Avec tous les changements, je ne sais pas comment procéder avec Webpack 4. Je sais que CommonChunksPlugin a été supprimé, il existe donc un moyen différent d'y parvenir. J'ai aussi lu ce tutoriel mais je ne suis toujours pas sûr d'extraire un bloc d'exécution ni de définir correctement la propriété output.

EDIT: Malheureusement, je rencontrais des problèmes avec la réponse la plus populaire ici. Découvrez ma réponse .

29
Tomasz Mularczyk

Voici quelques exemples: https://github.com/webpack/webpack/tree/master/examples

D'après votre exemple, je pense que cela se traduit par:

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

Vous pouvez supprimer le fournisseur de la propriété entry et définir la propriété d'optimisation comme suit ...

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

Vérifiez cette source exemples de webpack

17
jhamPac

Afin de séparer vendors et runtime, vous devez utiliser l'option optimization.

Configuration possible de 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'
            }
        }
    }
}

Plus d'informations sur W4 peuvent être trouvées dans ceci Webpack-Demo .

De même, vous pouvez obtenir le même résultat en modifiant la propriété optimization.splitChunks.chunks en "all". En savoir plus ici

Remarque: Vous pouvez le configurer via optimization.splitChunks. Les exemples disent quelque chose à propos des morceaux, par défaut, cela ne fonctionne que pour les morceaux asynchrones, mais avec optimization.splitChunks.chunks: "all", il en serait de même pour les morceaux initiaux.

11
Carloluis

Après un certain temps, j'ai découvert que cette 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
}

échouait d'une manière ou d'une autre à charger @babel/polyfill, ce qui causait des erreurs d'incompatibilité de navigateur ... Alors récemment, j'ai consulté la mise à jour de la documentation Webpack et trouvé un moyen de créer un bloc fournisseur explicite qui se chargeait correctement @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
        }
      }
    }
  }

Notez que je crée une entrée one avec all du code inclus et then Je précise avec splitChunks.cacheGroups.vendor.test quels modules doivent être fractionnés en un bloc fournisseur.

Néanmoins, je ne suis pas sûr si cela est correct à 100% ou s'il pourrait être amélioré car c'est littéralement l'une des choses les plus déroutantes de tous les temps. Cependant, cela semble être le plus proche de la documentation, semble produire les bons morceaux lorsque je les inspecte avec webpack-bundle-analyszer (met à jour uniquement les morceaux qui ont été modifiés et le reste reste le même dans toutes les versions) et corrige le problème avec (polyfill).

5
Tomasz Mularczyk

Je pense que si vous faites ceci:

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

Cela va créer un morceau vendors~ et runtime~ pour vous. Sokra a dit la valeur par défaut pour splitChunks est la suivante:

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

Qui inclut déjà un ensemble vendors et default. Lors des tests, je n’ai pas vu un paquet default apparaître.

Je ne sais pas quel est le flux de travail attendu pour inclure ces fichiers, mais j'ai écrit cette fonction d'aide 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);
}

Qui fonctionne avec mon plugin assets (mis à jour pour 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);
        });
    }
},

Tout cela crée quelque chose comme:

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

Désormais, lorsque je modifie l'un de mes fichiers JS personnalisés, seul l'un de ces fragments JS change. Ni le runtime ni le bundle des vendeurs ne doivent être mis à jour.

Si je ajoute un nouveau fichier JS et require, le moteur d'exécution n'est toujours pas mis à jour. Étant donné que le nouveau fichier sera simplement compilé dans le paquet principal, je pense qu'il n'est pas nécessaire qu'il soit dans le mappage car il n'est pas importé de manière dynamique. Si je import(), ce qui provoque la division du code, then l'exécution est mise à jour Le paquet de fournisseurs aussi semble avoir changé - je ne sais pas pourquoi. Je pensais que c'était supposé être évité.

Je n'ai pas non plus compris comment faire des hachages par fichier. Si vous modifiez un fichier .js qui est identique à un fichier .css, leur nom de fichier changera avec [chunkhash].


J'ai mis à jour le plugin assets ci-dessus. Je pense que l'ordre dans lequel vous incluez les balises <script> pourrait avoir de l'importance ... cela maintiendra cet ordre 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;
5
mpen

Afin de réduire la taille du paquet js du vendeur. Nous pouvons diviser les packages de modules de nœuds en différents fichiers de bundles. Je me suis référé à ce blog pour avoir scindé le fichier fournisseur volumineux généré par webpack. Résumé de ce lien que j'ai utilisé initialement: 

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 on veut regrouper plusieurs paquets et les diviser en plusieurs paquets, se référer à la suite de 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"
    },
    },
  },
}
2
swap0129

Il semble que le ordre des fichiers d'entrée importe également. Puisque vous avez client.js avant le fournisseur, le groupement n’a pas lieu avant le fournisseur avant votre application principale.

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

Maintenant, avec l’optimisation SplitChunks, vous pouvez spécifier le nom du fichier de sortie et faire référence au nom du fournisseur par:

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,
    },
  },
 },
},
0
Vinayak Bagaria