Comment regrouper séparément les scripts des fournisseurs et les exiger selon les besoins avec Webpack?

173

J'essaie de faire quelque chose qui, à mon avis, devrait être possible, mais je ne comprends vraiment pas comment le faire uniquement à partir de la documentation du Webpack.

J'écris une bibliothèque JavaScript avec plusieurs modules qui peuvent ou non dépendre les uns des autres. En plus de cela, jQuery est utilisé par tous les modules et certains d'entre eux peuvent avoir besoin de plugins jQuery. Cette bibliothèque sera ensuite utilisée sur plusieurs sites Web différents qui peuvent nécessiter certains ou tous les modules.

Définir les dépendances entre mes modules était très facile, mais définir leurs dépendances tierces semble être plus difficile que je ne le pensais.

Ce que je voudrais réaliser : pour chaque application, je souhaite avoir deux fichiers bundle, l'un avec les dépendances tierces nécessaires et l'autre avec les modules nécessaires de ma bibliothèque.

Exemple : imaginons que ma bibliothèque comporte les modules suivants:

  • a (nécessite: jquery, jquery.plugin1)
  • b (nécessite: jquery, a)
  • c (nécessite: jquery, jquery.ui, a, b)
  • d (nécessite: jquery, jquery.plugin2, a)

Et j'ai une application (voyez-la comme un fichier d'entrée unique) qui nécessite les modules a, b et c. Webpack dans ce cas doit générer les fichiers suivants:

  • bundle fournisseur : avec jquery, jquery.plugin1 et jquery.ui;
  • ensemble de sites Web : avec les modules a, b et c;

En fin de compte, je préférerais avoir jQuery en tant que global afin de ne pas avoir besoin de l'exiger sur chaque fichier (je pourrais l'exiger uniquement sur le fichier principal, par exemple). Et les plugins jQuery ne feraient qu'étendre le $ global au cas où ils seraient nécessaires (ce n'est pas un problème s'ils sont disponibles pour d'autres modules qui n'en ont pas besoin).

En supposant que cela soit possible, quel serait un exemple de fichier de configuration Webpack dans ce cas? J'ai essayé plusieurs combinaisons de chargeurs, d'externals et de plugins sur mon fichier de configuration, mais je ne comprends pas vraiment ce qu'ils font et lesquels dois-je utiliser. Je vous remercie!

Bensampaio
la source
2
quelle est ta solution? avez-vous réussi à trouver une approche décente. Si oui, merci de le publier! merci
GeekOnGadgets

Réponses:

140

dans mon fichier webpack.config.js (Version 1,2,3), j'ai

function isExternal(module) {
  var context = module.context;

  if (typeof context !== 'string') {
    return false;
  }

  return context.indexOf('node_modules') !== -1;
}

dans mon tableau de plugins

plugins: [
  new CommonsChunkPlugin({
    name: 'vendors',
    minChunks: function(module) {
      return isExternal(module);
    }
  }),
  // Other plugins
]

Maintenant, j'ai un fichier qui n'ajoute que des bibliothèques tierces à un fichier selon les besoins.

Si vous souhaitez obtenir plus de précision lorsque vous séparez vos fournisseurs et vos fichiers de point d'entrée:

plugins: [
  new CommonsChunkPlugin({
    name: 'common',
    minChunks: function(module, count) {
      return !isExternal(module) && count >= 2; // adjustable
    }
  }),
  new CommonsChunkPlugin({
    name: 'vendors',
    chunks: ['common'],
    // or if you have an key value object for your entries
    // chunks: Object.keys(entry).concat('common')
    minChunks: function(module) {
      return isExternal(module);
    }
  })
]

Notez que l'ordre des plugins compte beaucoup.

De plus, cela va changer dans la version 4. Quand c'est officiel, je mets à jour cette réponse.

Mise à jour: changement de recherche d'indexOf pour les utilisateurs de Windows

Rafael De Leon
la source
1
Je ne sais pas si c'était déjà possible lorsque j'ai posté ma question, mais c'est bien ce que je cherchais. Avec cette solution, je n'ai plus besoin de spécifier mon bloc d'entrée de fournisseur. Merci beaucoup!
bensampaio
1
isExternalen minChunksfait ma journée. Comment cela n'est-il pas documenté? Il y a des inconvénients?
Wesley Schleumer de Góes
Merci, mais changez userRequest.indexOf ('/ node_modules /') en userRequest.indexOf ('node_modules') pour les chemins Windows
Kinjeiro
@ WesleySchleumerdeGóes c'est documenté mais sans exemple options.minChunks (number|Infinity|function(module, count) -> boolean):je ne vois pas encore d'inconvénient.
Rafael De Leon
2
Cela ne fonctionnera pas lors de l'utilisation de chargeurs, car le chemin du chargeur sera également dans module.userRequest(et le chargeur est probablement dans node_modules). Mon code pour isExternal():return typeof module.userRequest === 'string' && !!module.userRequest.split('!').pop().match(/(node_modules|bower_components|libraries)/);
cdauth
54

Je ne sais pas si je comprends parfaitement votre problème, mais comme j'ai eu un problème similaire récemment, je vais essayer de vous aider.

Bundle fournisseur.

Vous devriez utiliser CommonsChunkPlugin pour cela. dans la configuration, vous spécifiez le nom du bloc (par exemple vendor) et le nom du fichier qui sera généré (vendor.js ).

new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity),

Maintenant, partie importante, vous devez maintenant spécifier ce que cela signifie vendorbibliothèque et vous le faites dans une section d'entrée. Un élément de plus dans la liste d'entrée avec le même nom que le nom du bloc nouvellement déclaré (c'est-à-dire «vendeur» dans ce cas). La valeur de cette entrée doit être la liste de tous les modules que vous souhaitez passer à vendorregrouper. dans votre cas, cela devrait ressembler à quelque chose comme:

entry: {
    app: 'entry.js',
    vendor: ['jquery', 'jquery.plugin1']
}

JQuery comme global

J'ai eu le même problème et l'a résolu avec providePlugin . ici, vous ne définissez pas un objet global mais une sorte de raccourcis vers les modules. c'est à dire que vous pouvez le configurer comme ça:

new webpack.ProvidePlugin({
    $: "jquery"
})

Et maintenant, vous pouvez simplement utiliser $ n'importe où dans votre code - webpack le convertira automatiquement en

require('jquery')

J'espère que cela a aidé. vous pouvez également consulter mon fichier de configuration webpack qui est ici

J'adore webpack, mais je suis d'accord que la documentation n'est pas la plus belle du monde ... mais bon ... les gens disaient la même chose à propos de la documentation Angular au début :)


Éditer:

Pour avoir des segments de fournisseur spécifiques aux points d'entrée, utilisez simplement CommonsChunkPlugins plusieurs fois:

new webpack.optimize.CommonsChunkPlugin("vendor-page1", "vendor-page1.js", Infinity),
new webpack.optimize.CommonsChunkPlugin("vendor-page2", "vendor-page2.js", Infinity),

puis déclarez différentes bibliothèques extenrales pour différents fichiers:

entry: {
    page1: ['entry.js'],
    page2: ['entry2.js'],
    "vendor-page1": [
        'lodash'
    ],
    "vendor-page2": [
        'jquery'
    ]
},

Si certaines bibliothèques se chevauchent (et pour la plupart d'entre elles) entre les points d'entrée, vous pouvez les extraire dans un fichier commun en utilisant le même plugin avec une configuration différente. Voyez cet exemple.

Michał Margiel
la source
Merci beaucoup pour votre réponse. C'était la meilleure approche que j'ai vue jusqu'à présent mais malheureusement cela ne résout toujours pas mon problème ... J'ai testé votre exemple et le fichier vendor.js contiendra toujours tout le code de 'jquery' et 'jquery.plugin1' même si ils ne sont requis par aucun de mes modules. Cela signifie qu'à la fin, ils seront toujours chargés dans le navigateur. Si j'ai beaucoup de plugins jquery, cela entraînera un très gros fichier même si seulement la moitié d'entre eux sont utilisés. N'y a-t-il aucun moyen d'inclure «jquery.plugin1» dans le bundle du fournisseur uniquement si cela est nécessaire?
bensampaio
merci, j'ai donc appris quelque chose aussi :) J'ai mis à jour ma réponse avec la création de plusieurs segments de fournisseurs. peut-être que maintenant cela vous conviendra mieux.
Michał Margiel
4
Le problème avec cette solution est qu'elle suppose que je connais les dépendances pour chaque page. Mais je ne peux pas prédire que ... jQuery ne devrait être inclus sur un bundle de fournisseur que s'il est requis par l'un des modules utilisés dans la page. En spécifiant que sur le fichier de configuration, il sera toujours dans le bundle du fournisseur même s'il n'est requis par aucun module utilisé dans la page, non? En gros, je ne peux pas prédire le contenu des bundles des fournisseurs, sinon j'aurai un sacré boulot car je n'ai pas que 2 pages J'ai des centaines ... Avez-vous le problème? Des idées? :)
bensampaio
Je comprends ce que vous dites, mais je ne vois pas cela comme un problème. Si vous utilisez une nouvelle bibliothèque dans une page, ajoutez-la simplement à une liste de bibliothèques de fournisseurs pour cette page. Ce ne sont que quelques personnages. Quoi qu'il en soit, dans votre solution, vous devez le faire en spécifiant loader.Si vous ne savez pas quelles pages utiliseront votre module nouvellement créé, laissez le plugin CommonChuncks extraire automatiquement les bibliothèques communes de vos modules.
Michał Margiel
Comment puis-je définir le contexte séparément pour les fichiers fournisseur?
harshes53
44

Si vous souhaitez regrouper automatiquement vos scripts séparément de ceux des fournisseurs:

var webpack = require('webpack'),
    pkg     = require('./package.json'),  //loads npm config file
    html    = require('html-webpack-plugin');

module.exports = {
  context : __dirname + '/app',
  entry   : {
    app     : __dirname + '/app/index.js',
    vendor  : Object.keys(pkg.dependencies) //get npm vendors deps from config
  },
  output  : {
    path      : __dirname + '/dist',
    filename  : 'app.min-[hash:6].js'
  },
  plugins: [
    //Finally add this line to bundle the vendor code separately
    new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.min-[hash:6].js'),
    new html({template : __dirname + '/app/index.html'})
  ]
};

Vous pouvez en savoir plus sur cette fonctionnalité dans la documentation officielle .

Freezystem
la source
4
Veuillez noter que vendor : Object.keys(pkg.dependencies) cela ne fonctionne pas toujours et dépend de la façon dont le package est construit.
markyph
1
Vous dépendez toujours de la façon dont vous êtes package.jsonréglé. Cette solution de contournement fonctionne dans la plupart des cas, mais il existe des exceptions où vous devrez emprunter un chemin différent. Il pourrait être intéressant de publier votre propre réponse à la question pour aider la communauté.
Freezystem
17
J'aime ça. Ça m'a fait pipi un peu.
cgatian
3
notez qu'il inclura même des paquets que vous n'utilisez peut-être même pas du tout dans votre code ... car Object.keys(pkg.dependencies)ils regrouperont tout !!!! disons que vous avez un tas de chargeurs listés là-bas ... ouais qui seront inclus !!! alors faites attention ... séparez soigneusement ce qui est devDependency et ce qu'est la dépendance
Rafael Milewski
1
@RafaelMilewski pourquoi auriez-vous des chargeurs dependencies?
Pantalon
13

Je ne sais pas non plus si je comprends parfaitement votre cas, mais voici un extrait de configuration pour créer des blocs de fournisseur distincts pour chacun de vos ensembles:

entry: {
  bundle1: './build/bundles/bundle1.js',
  bundle2: './build/bundles/bundle2.js',
  'vendor-bundle1': [
    'react',
    'react-router'
  ],
  'vendor-bundle2': [
    'react',
    'react-router',
    'flummox',
    'immutable'
  ]
},

plugins: [
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor-bundle1',
    chunks: ['bundle1'],
    filename: 'vendor-bundle1.js',
    minChunks: Infinity
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor-bundle2',
    chunks: ['bundle2'],
    filename: 'vendor-bundle2-whatever.js',
    minChunks: Infinity
  }),
]

Et lien vers la CommonsChunkPlugindocumentation: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin

Alex Fedoseev
la source
Je pense que le problème avec cette solution est le même que celui fourni par Michal. Vous supposez que je connais les dépendances du fournisseur pour bundle1 et bundle2, mais je ne le fais pas ... Imaginez que vous ayez 200 bundles, voudriez-vous spécifier tout cela sur le fichier de configuration? En utilisant votre exemple, reactne doit être présent dans le bundle du fournisseur que s'il est explicitement requis par bundle1 et bundl2. Je ne devrais pas avoir à le spécifier sur le fichier de configuration ... Cela a-t-il un sens? Des idées?
bensampaio
@Anakin, la question est de savoir pourquoi voulez-vous regrouper l'outil de 200 fournisseurs dans un fichier séparé. Je regrouperais uniquement les outils communs dans un fichier séparé et conserverais le reste avec les lots de projets.
maxisam
@Anakin Je pense que je suis confronté au même problème, corrige-moi si je me trompe? stackoverflow.com/questions/35944067/…
pjdicke