¿Cómo agrupar scripts de proveedores por separado y requerirlos según sea necesario con Webpack?

173

Estoy tratando de hacer algo que creo que debería ser posible, pero realmente no puedo entender cómo hacerlo solo desde la documentación del paquete web.

Estoy escribiendo una biblioteca de JavaScript con varios módulos que pueden o no depender el uno del otro. Además de eso, jQuery es utilizado por todos los módulos y algunos de ellos pueden necesitar complementos de jQuery. Esta biblioteca se utilizará en varios sitios web diferentes que pueden requerir algunos o todos los módulos.

Definir las dependencias entre mis módulos fue muy fácil, pero definir sus dependencias de terceros parece ser más difícil de lo que esperaba.

Lo que me gustaría lograr : para cada aplicación quiero tener dos archivos de paquete, uno con las dependencias de terceros necesarias y otro con los módulos necesarios de mi biblioteca.

Ejemplo : imaginemos que mi biblioteca tiene los siguientes módulos:

  • a (requiere: jquery, jquery.plugin1)
  • b (requiere: jquery, a)
  • c (requiere: jquery, jquery.ui, a, b)
  • d (requiere: jquery, jquery.plugin2, a)

Y tengo una aplicación (verla como un archivo de entrada único) que requiere los módulos a, byc. Webpack para este caso debería generar los siguientes archivos:

  • paquete de proveedores : con jquery, jquery.plugin1 y jquery.ui;
  • paquete de sitio web : con los módulos a, byc;

Al final, preferiría tener jQuery como global, por lo que no necesito solicitarlo en cada archivo (podría requerirlo solo en el archivo principal, por ejemplo). Y los complementos jQuery simplemente extenderían $ global en caso de que sean necesarios (no es un problema si están disponibles para otros módulos que no los necesitan).

Suponiendo que esto sea posible, ¿cuál sería un ejemplo de un archivo de configuración de paquete web para este caso? Probé varias combinaciones de cargadores, externos y complementos en mi archivo de configuración, pero realmente no entiendo qué están haciendo y cuáles debo usar. ¡Gracias!

bensampaio
fuente
2
cual es tu solucion te las arreglaste para encontrar un enfoque decente. ¡Si es así, por favor, publicarlo! gracias
GeekOnGadgets

Respuestas:

140

en mi archivo webpack.config.js (Versión 1,2,3), tengo

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

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

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

en mi matriz de complementos

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

Ahora tengo un archivo que solo agrega bibliotecas de terceros a un archivo según sea necesario.

Si desea obtener más granular donde separa sus proveedores y archivos de punto de entrada:

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);
    }
  })
]

Tenga en cuenta que el orden de los complementos es muy importante.

Además, esto va a cambiar en la versión 4. Cuando es oficial, actualizo esta respuesta.

Actualización: cambio de búsqueda indexOf para usuarios de Windows

Rafael De Leon
fuente
1
No sé si esto ya era posible cuando publiqué mi pregunta, pero esto es lo que estaba buscando. Con esta solución ya no necesito especificar mi porción de entrada de proveedor. ¡Muchas gracias!
bensampaio
1
isExternalEn minChunkshizo mi día. ¿Cómo no se documenta esto? Hay inconvenientes?
Wesley Schleumer de Góes
Thx, pero cambie userRequest.indexOf ('/ node_modules /') a userRequest.indexOf ('node_modules') para
parches de
@ WesleySchleumerdeGóes está documentado pero, por ejemplo options.minChunks (number|Infinity|function(module, count) -> boolean):, todavía no veo inconvenientes.
Rafael De Leon
2
Esto no funcionará al usar cargadores, ya que la ruta del cargador también estará en module.userRequest(y el cargador probablemente esté en node_modules). Mi código para isExternal():return typeof module.userRequest === 'string' && !!module.userRequest.split('!').pop().match(/(node_modules|bower_components|libraries)/);
cdauth
54

No estoy seguro si entiendo completamente tu problema, pero como tuve un problema similar recientemente, intentaré ayudarte.

Paquete de proveedores.

Deberías usar CommonsChunkPlugin para eso. en la configuración, especifique el nombre del fragmento (p vendor. ej. ) y el nombre del archivo que se generará ( vendor.js).

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

Ahora parte importante, debe especificar qué significa vendorbiblioteca y hacer eso en una sección de entrada. Un elemento más a la lista de entradas con el mismo nombre que el nombre del fragmento recientemente declarado (es decir, 'proveedor' en este caso). El valor de esa entrada debe ser la lista de todos los módulos que desea mover para vendoragrupar. en su caso debería verse algo así como:

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

JQuery como global

Tuve el mismo problema y lo resolvió con ProvidePlugin . aquí no está definiendo un objeto global sino un tipo de atajos a los módulos. es decir, puedes configurarlo así:

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

Y ahora puede usar $cualquier parte de su código: webpack lo convertirá automáticamente a

require('jquery')

Espero que haya ayudado. También puede ver mi archivo de configuración de paquete web que está aquí

Me encanta el paquete web, pero estoy de acuerdo en que la documentación no es la mejor del mundo ... pero bueno, la gente decía lo mismo sobre la documentación angular al principio :)


Editar:

Para tener fragmentos de proveedores específicos de puntos de entrada, solo use CommonsChunkPlugins varias veces:

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

y luego declarar diferentes bibliotecas externas para diferentes archivos:

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

Si algunas bibliotecas se superponen (y para la mayoría de ellas) entre puntos de entrada, puede extraerlas a un archivo común usando el mismo complemento solo con una configuración diferente. Ver este ejemplo.

Michał Margiel
fuente
Muchas gracias por tu respuesta. Este fue el mejor enfoque que vi hasta ahora, pero desafortunadamente todavía no resuelve mi problema ... Probé su ejemplo y el archivo vendor.js todavía contendrá todo el código de 'jquery' y 'jquery.plugin1' incluso si no son requeridos por ninguno de mis módulos. Esto significa que al final siempre se cargarán en el navegador. Si tengo muchos complementos de jquery, esto dará como resultado un archivo muy grande, incluso si solo se usa la mitad. ¿No hay forma de incluir 'jquery.plugin1' en el paquete de proveedores solo si es necesario?
bensampaio
gracias, así que también he aprendido algo :) He actualizado mi respuesta con la creación de múltiples partes de proveedores. quizás ahora te vaya mejor.
Michał Margiel
44
El problema con esta solución es que supone que sé cuáles son las dependencias para cada página. Pero no puedo predecir que ... jQuery solo debe incluirse en un paquete de proveedor si es requerido por uno de los módulos utilizados en la página. Al especificar que en el archivo de configuración siempre estará en el paquete del proveedor, incluso si no es requerido por ningún módulo utilizado en la página, ¿verdad? Básicamente, no puedo predecir el contenido de los paquetes de proveedores, de lo contrario tendré un trabajo increíble porque no tengo solo 2 páginas, tengo cientos ... ¿Entiende el problema? ¿Algunas ideas? :)
bensampaio
Entiendo lo que estás diciendo, pero no lo veo como un problema. Si usa una nueva biblioteca en una página, simplemente agréguela a las listas de bibliotecas de proveedores para esa página. Son solo unos pocos personajes. De todos modos, en su solución, debe hacerlo especificando el cargador. Si no sabe qué páginas usarán su módulo recién creado, deje que el complemento CommonChuncks extraiga bibliotecas comunes de sus módulos automáticamente.
Michał Margiel
¿Cómo puedo configurar el contexto por separado para los archivos del proveedor?
harshes53
44

En caso de que esté interesado en agrupar automáticamente sus scripts por separado de los proveedores:

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

Puede leer más sobre esta característica en la documentación oficial .

Freezystem
fuente
44
Tenga en cuenta que vendor : Object.keys(pkg.dependencies) no siempre funciona y depende de cómo se construya el paquete.
markyph
1
Siempre dependerá de cómo package.jsonesté configurado. Esta solución funciona en la mayoría de los casos, pero hay excepciones en las que tendrá que tomar un camino diferente. Podría ser interesante publicar su propia respuesta a la pregunta para ayudar a la comunidad.
Freezystem
16
Me gusta esto. Me hizo orinar un poco.
cgatian
3
tenga en cuenta que incluso incluirá paquetes que tal vez ni siquiera esté utilizando en su código ... ¡porque Object.keys(pkg.dependencies)lo agrupará todo! digamos que tienes un montón de cargadores enumerados allí ... ¡sí, eso será incluido! así que tenga cuidado ... separe con cuidado qué es devDependency y qué es dependencia
Rafael Milewski
1
@RafaelMilewski ¿por qué tendrías cargadores dependencies?
Pantalones
13

Tampoco estoy seguro si entiendo completamente su caso, pero aquí hay un fragmento de configuración para crear fragmentos de proveedores separados para cada uno de sus paquetes:

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

Y enlace a CommonsChunkPlugindocumentos: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin

Alex Fedoseev
fuente
Creo que el problema con esta solución es el mismo que el proporcionado por Michal. Está asumiendo que conozco las dependencias de los proveedores para bundle1 y bundle2, pero no ... Imagínese que tiene 200 paquetes. ¿Desea especificar todo eso en el archivo de configuración? Usando su ejemplo, reactsolo debe estar presente en el paquete del proveedor si es explícitamente requerido por bundle1 y bundl2. No debería tener que especificar eso en el archivo de configuración ... ¿Tiene sentido? ¿Algunas ideas?
bensampaio
@Anakin la pregunta es por qué desea agrupar la herramienta de 200 proveedores en un archivo separado. Solo agruparía las herramientas comunes en un archivo separado y mantendría el resto con los paquetes del proyecto.
maxisam
@ Anakin Creo que estoy lidiando con el mismo problema, ¿me corrigen si me equivoco? stackoverflow.com/questions/35944067/…
pjdicke