Como agrupar scripts de fornecedores separadamente e solicitá-los conforme necessário com o Webpack?

173

Estou tentando fazer algo que acredito ser possível, mas realmente não consigo entender como fazê-lo apenas a partir da documentação do webpack.

Estou escrevendo uma biblioteca JavaScript com vários módulos que podem ou não depender um do outro. Além disso, o jQuery é usado por todos os módulos e alguns deles podem precisar de plugins do jQuery. Essa biblioteca será usada em vários sites diferentes, que podem exigir alguns ou todos os módulos.

Definir as dependências entre meus módulos foi muito fácil, mas definir suas dependências de terceiros parece ser mais difícil do que eu esperava.

O que eu gostaria de alcançar : para cada aplicativo, quero ter dois arquivos de pacote configurável, um com as dependências de terceiros necessárias e outro com os módulos necessários da minha biblioteca.

Exemplo : Vamos imaginar que minha biblioteca tenha os seguintes módulos:

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

E eu tenho um aplicativo (veja como um arquivo de entrada exclusivo) que requer os módulos a, bec. O Webpack para esse caso deve gerar os seguintes arquivos:

  • pacote de fornecedores : com jquery, jquery.plugin1 e jquery.ui;
  • pacote de sites : com os módulos a, bec;

No final, eu preferiria ter o jQuery como global, para não precisar exigi-lo em todos os arquivos (só poderia ser exigido no arquivo principal, por exemplo). E os plugins jQuery estenderiam apenas $ global caso sejam necessários (não é um problema se estiverem disponíveis para outros módulos que não precisam deles).

Supondo que isso seja possível, qual seria um exemplo de um arquivo de configuração do webpack para este caso? Tentei várias combinações de carregadores, externos e plugins no meu arquivo de configuração, mas realmente não entendo o que eles estão fazendo e quais devo usar. Obrigado!

bensampaio
fonte
2
qual é a sua solução? você conseguiu encontrar uma abordagem decente. Se sim, por favor publique! Obrigado
GeekOnGadgets

Respostas:

140

no meu arquivo webpack.config.js (versão 1,2,3), tenho

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

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

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

na minha matriz de plugins

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

Agora eu tenho um arquivo que adiciona apenas bibliotecas de terceiros a um arquivo, conforme necessário.

Se você deseja obter mais detalhes, separa seus fornecedores e arquivos de ponto 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);
    }
  })
]

Observe que a ordem dos plug-ins é muito importante.

Além disso, isso vai mudar na versão 4. Quando isso é oficial, eu atualizo esta resposta.

Atualização: indexOf alteração de pesquisa para usuários do Windows

Rafael De Leon
fonte
1
Não sei se isso já era possível quando postei minha pergunta, mas era isso que eu estava procurando. Com esta solução, não preciso mais especificar meu pedaço de entrada do fornecedor. Muito obrigado!
Bensampaio 18/08/16
1
isExternalno minChunksmeu dia. Como isso não está documentado? Há desvantagens?
Wesley Schleumer de Góes
Thx, mas a mudança userRequest.indexOf ( '/ node_modules /') para userRequest.indexOf ( 'node_modules') para pathes janelas
Kinjeiro
@ WesleySchleumerdeGóes está documentado, mas sem exemplo, ainda options.minChunks (number|Infinity|function(module, count) -> boolean):não estou vendo uma desvantagem.
Rafael De Leon
2
Isso não funcionará ao usar carregadores, pois o caminho do carregador também estará module.userRequest(e provavelmente o carregador está dentro node_modules). Meu código para isExternal():return typeof module.userRequest === 'string' && !!module.userRequest.split('!').pop().match(/(node_modules|bower_components|libraries)/);
cdauth
54

Não sei se entendi perfeitamente o seu problema, mas como tive um problema semelhante recentemente, tentarei ajudá-lo.

Pacote de fornecedores.

Você deve usar o CommonsChunkPlugin para isso. na configuração, você especifica o nome do pedaço (por exemplo vendor) e o nome do arquivo que será gerado ( vendor.js).

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

Agora parte importante, você precisa especificar o que significa vendorbiblioteca e fazer isso em uma seção de entrada. Mais um item na lista de entradas com o mesmo nome do pedaço recém-declarado (por exemplo, 'fornecedor'). O valor dessa entrada deve ser a lista de todos os módulos que você deseja mover para vendoragrupar. no seu caso, deve ser algo como:

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

JQuery como global

Teve o mesmo problema e resolveu-o com o ProvidePlugin . aqui você não está definindo um objeto global, mas um tipo de shurtcuts para os módulos. ou seja, você pode configurá-lo assim:

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

E agora você pode simplesmente usar $em qualquer lugar do seu código - o webpack o converterá automaticamente em

require('jquery')

Espero que tenha ajudado. você também pode olhar para o meu arquivo de configuração do webpack que está aqui

Eu amo o webpack, mas concordo que a documentação não é a mais legal do mundo ... mas ei ... as pessoas estavam dizendo a mesma coisa sobre a documentação do Angular no início :)


Editar:

Para ter blocos de fornecedores específicos para pontos de entrada, use CommonsChunkPlugins várias vezes:

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

e declare diferentes bibliotecas extenrais para arquivos diferentes:

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

Se algumas bibliotecas estiverem sobrepostas (e para a maioria delas) entre pontos de entrada, você poderá extraí-las para um arquivo comum usando o mesmo plug-in apenas com configurações diferentes. Veja este exemplo.

Michał Margiel
fonte
Muito obrigado pela sua resposta. Essa foi a melhor abordagem que vi até agora, mas infelizmente ainda não resolve o meu problema ... Testei seu exemplo e o arquivo vendor.js ainda conterá todo o código de 'jquery' e 'jquery.plugin1', mesmo que eles não são exigidos por nenhum dos meus módulos. Isso significa que, no final, eles sempre serão carregados no navegador. Se eu tiver muitos plugins jquery, isso resultará em um arquivo muito grande, mesmo que apenas metade deles seja usada. Não há como incluir 'jquery.plugin1' no pacote de fornecedores apenas se for necessário?
Bensampaio 20/05
obrigado, então também aprendi algo :) Atualizei minha resposta com a criação de vários pedaços de fornecedores. talvez agora seja melhor para você.
Michał Margiel
4
O problema com esta solução é que ela pressupõe que eu sei quais são as dependências para cada página. Mas não posso prever que ... o jQuery só deve ser incluído em um pacote de fornecedores se for exigido por um dos módulos usados ​​na página. Especificando que no arquivo de configuração ele sempre estará no pacote do fornecedor, mesmo que não seja exigido por nenhum módulo usado na página, certo? Basicamente, não posso prever o conteúdo dos pacotes de fornecedores; caso contrário, terei muito trabalho porque não tenho apenas 2 páginas, tenho centenas ... Você entendeu o problema? Alguma ideia? :)
bensampaio
Entendo o que você está dizendo, mas não vejo isso como um problema. Se você usar uma nova biblioteca em uma página, adicione-a às listas de bibliotecas de fornecedores dessa página. São apenas alguns caracteres. De qualquer forma, na sua solução, você deve especificá-lo como carregador. Se você não souber quais páginas usarão o seu módulo recém-criado, deixe o plug-in CommonChuncks extrair automaticamente as bibliotecas comuns dos seus módulos.
Michał Margiel
Como posso definir o contexto separadamente para arquivos de fornecedores?
precisa saber é o seguinte
44

Caso você esteja interessado em agrupar automaticamente seus scripts separadamente dos de fornecedores:

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

Você pode ler mais sobre esse recurso na documentação oficial .

Freezystem
fonte
4
Observe que vendor : Object.keys(pkg.dependencies) nem sempre funciona e depende de como o pacote é construído.
21416 markyph
1
Você sempre depende de como package.jsonestá definido. Essa solução alternativa funciona na maioria dos casos, mas há exceções nas quais você precisará seguir um caminho diferente. Pode ser interessante postar sua própria resposta à pergunta para ajudar a comunidade.
Freezystem
16
Eu gosto disso. Isso me fez fazer xixi um pouco.
27516 cgatian
3
note que ele inclui até pacotes que você pode nem estar usando no seu código ... devido ao Object.keys(pkg.dependencies)pacote de tudo !!!! vamos diz que você tem um monte de carregadores listados lá ... sim, isso será incluído !!! por isso tome cuidado ... separar carrefully o que é devDependency eo que é a dependência
Rafael Milewski
1
@RafaelMilewski, por que você carregaria carregadeiras dependencies?
Pants
13

Também não tenho certeza se eu entendo completamente o seu caso, mas aqui está o snippet de configuração para criar blocos de fornecedores separados para cada um de seus pacotes configuráveis:

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

E link para CommonsChunkPlugindocs: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin

Alex Fedoseev
fonte
Eu acredito que o problema com esta solução é o mesmo que o fornecido por Michal. Você está assumindo que conheço as dependências do fornecedor para o pacote1 e o pacote2, mas não ... Imagine que você tem 200 pacotes que deseja especificar tudo isso no arquivo de configuração? Usando seu exemplo, reactsó deve estar presente no pacote configurável do fornecedor se for explicitamente requerido pelo pacote configurável1 e pelo pacote configurável2. Eu não deveria ter que especificar isso no arquivo de configuração ... Isso faz sentido? Alguma ideia?
Bensampaio 26/05
@Anakin a pergunta é por que você quer agrupar a ferramenta de 200 fornecedores em um arquivo separado. Gostaria apenas de agrupar ferramentas comuns em um arquivo separado e manter o restante com os pacotes do projeto.
Maxisam 10/03
@ Anakin Acho que estou lidando com o mesmo problema, me corrija se estiver errado? stackoverflow.com/questions/35944067/…
pjdicke 11/03/16