Jak oddzielnie spakować skrypty dostawców i wymagać ich w razie potrzeby z Webpack?

173

Próbuję zrobić coś, co moim zdaniem powinno być możliwe, ale naprawdę nie mogę zrozumieć, jak to zrobić, tylko z dokumentacji pakietu internetowego.

Piszę bibliotekę JavaScript z kilkoma modułami, które mogą lub nie zależą od siebie. Ponadto jQuery jest używane przez wszystkie moduły, a niektóre z nich mogą wymagać wtyczek jQuery. Ta biblioteka będzie następnie używana na kilku różnych stronach internetowych, które mogą wymagać niektórych lub wszystkich modułów.

Definiowanie zależności między moimi modułami było bardzo łatwe, ale zdefiniowanie ich zależności od firm trzecich wydaje się być trudniejsze niż się spodziewałem.

Co chciałbym osiągnąć : dla każdej aplikacji chcę mieć dwa pliki pakietów, jeden z niezbędnymi zależnościami innych firm, a drugi z niezbędnymi modułami z mojej biblioteki.

Przykład : Wyobraźmy sobie, że moja biblioteka ma następujące moduły:

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

Mam aplikację (zobacz ją jako unikalny plik wejściowy), która wymaga modułów a, bi c. Webpack dla tego przypadku powinien generować następujące pliki:

  • pakiet dostawcy : z jquery, jquery.plugin1 i jquery.ui;
  • pakiet strony internetowej : z modułami a, bi c;

Ostatecznie wolałbym mieć jQuery jako globalny, więc nie muszę wymagać tego na każdym pojedynczym pliku (mógłbym wymagać, na przykład, tylko w pliku głównym). A wtyczki jQuery po prostu rozszerzałyby $ global na wypadek, gdyby były wymagane (nie stanowi problemu, jeśli są dostępne dla innych modułów, które ich nie potrzebują).

Zakładając, że jest to możliwe, jaki byłby przykład pliku konfiguracyjnego pakietu WebPack dla tego przypadku? Wypróbowałem kilka kombinacji programów ładujących, zewnętrznych i wtyczek w moim pliku konfiguracyjnym, ale tak naprawdę nie rozumiem, co robią i których powinienem użyć. Dziękuję Ci!

bensampaio
źródło
2
jakie jest twoje rozwiązanie czy udało ci się znaleźć przyzwoite podejście. Jeśli tak, opublikuj to! dzięki
GeekOnGadgets

Odpowiedzi:

140

w moim pliku webpack.config.js (wersja 1,2,3) mam

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

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

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

w mojej tablicy wtyczek

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

Teraz mam plik, który dodaje tylko biblioteki innych firm do jednego pliku, zgodnie z wymaganiami.

Jeśli chcesz uzyskać bardziej szczegółowe informacje, w których oddzielisz dostawców i pliki punktów wejścia:

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

Zwróć uwagę, że kolejność wtyczek ma duże znaczenie.

Również to się zmieni w wersji 4. Kiedy to oficjalne, aktualizuję tę odpowiedź.

Aktualizacja: zmiana indeksu wyszukiwania dla użytkowników systemu Windows

Rafael De Leon
źródło
1
Nie wiem, czy było to już możliwe, kiedy opublikowałem moje pytanie, ale rzeczywiście tego szukałem. Dzięki temu rozwiązaniu nie muszę już określać fragmentu wpisu dostawcy. Wielkie dzięki!
bensampaio
1
isExternalw minChunksmój dzień. Jak to nie jest udokumentowane? Są wady?
Wesley Schleumer de Góes
Dzięki, ale zmień userRequest.indexOf ('/ node_modules /') na userRequest.indexOf ('node_modules') dla poprawek Windows
Kinjeiro
@ WesleySchleumerdeGóes jest to udokumentowane, ale bez przykładu options.minChunks (number|Infinity|function(module, count) -> boolean):nie widzę jeszcze wady.
Rafael De Leon
2
To nie zadziała podczas korzystania z ładowarek, ponieważ ścieżka programu ładującego również będzie w module.userRequest(i ładowarka prawdopodobnie jest node_modules). Mój kod dla isExternal():return typeof module.userRequest === 'string' && !!module.userRequest.split('!').pop().match(/(node_modules|bower_components|libraries)/);
cdauth
54

Nie jestem pewien, czy w pełni rozumiem Twój problem, ale ponieważ ostatnio miałem podobny problem, postaram się Ci pomóc.

Pakiet dostawcy.

W tym celu należy użyć CommonsChunkPlugin . w konfiguracji określasz nazwę porcji (np. vendor) i nazwę pliku, który zostanie wygenerowany ( vendor.js).

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

Teraz ważna część, musisz teraz określić, co to znaczy vendorbiblioteka i zrobić to w sekcji wpisu. Jeszcze jedna pozycja na liście wpisów o tej samej nazwie, co nazwa nowo zadeklarowanej porcji (tj. W tym przypadku „sprzedawca”). Wartością tego wpisu powinna być lista wszystkich modułów, które chcesz przenieść do vendorpaczki. w twoim przypadku powinno to wyglądać mniej więcej tak:

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

JQuery jako globalny

Miałem ten sam problem i rozwiązałem go za pomocą ProvidePlugin . tutaj nie definiujesz obiektu globalnego, ale rodzaj skrótów do modułów. czyli możesz to skonfigurować w ten sposób:

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

Teraz możesz po prostu użyć $dowolnego miejsca w kodzie - webpack automatycznie przekonwertuje to na

require('jquery')

Mam nadzieję, że to pomogło. możesz również spojrzeć na mój plik konfiguracyjny pakietu internetowego, który jest tutaj

Uwielbiam webpack, ale zgadzam się, że dokumentacja nie należy do najładniejszych na świecie… ale hej… na początku ludzie mówili to samo o dokumentacji Angulara :)


Edytować:

Aby mieć fragmenty dostawcy specyficzne dla punktu wejścia, po prostu użyj CommonsChunkPlugins wiele razy:

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

a następnie zadeklaruj różne biblioteki zewnętrzne dla różnych plików:

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

Jeśli niektóre biblioteki nakładają się (i dla większości z nich) między punktami wejścia, możesz wyodrębnić je do wspólnego pliku za pomocą tej samej wtyczki, tylko z inną konfiguracją. Zobacz ten przykład.

Michał Margiel
źródło
Bardzo dziękuję za odpowiedź. To było najlepsze podejście, jakie widziałem do tej pory, ale niestety nadal nie rozwiązuje mojego problemu ... Przetestowałem Twój przykład i plik vendor.js nadal będzie zawierał cały kod z 'jquery' i 'jquery.plugin1', nawet jeśli nie są wymagane przez żaden z moich modułów. Oznacza to, że w końcu zawsze zostaną załadowane do przeglądarki. Jeśli mam dużo wtyczek jquery, spowoduje to powstanie bardzo dużego pliku, nawet jeśli używana jest tylko połowa z nich. Czy nie ma możliwości włączenia „jquery.plugin1” do pakietu dostawcy tylko wtedy, gdy jest to wymagane?
bensampaio
dzięki, więc też się czegoś nauczyłem :) Zaktualizowałem moją odpowiedź o tworzenie wielu fragmentów dostawców. może teraz będzie ci lepiej pasować.
Michał Margiel
4
Problem z tym rozwiązaniem polega na tym, że zakłada ono, że wiem, jakie są zależności dla każdej strony. Ale nie mogę przewidzieć, że ... jQuery powinno być zawarte w pakiecie dostawcy tylko wtedy, gdy jest to wymagane przez jeden z modułów używanych na stronie. Określając, że w pliku konfiguracyjnym zawsze będzie w pakiecie dostawcy, nawet jeśli nie jest to wymagane przez żaden moduł używany na stronie, prawda? Zasadniczo nie mogę przewidzieć zawartości paczek dostawców, w przeciwnym razie będę miał cholerną robotę, ponieważ nie mam tylko 2 stron, mam setki ... Czy masz problem? Jakieś pomysły? :)
bensampaio
Rozumiem, co mówisz, ale nie uważam tego za problem. Jeśli używasz nowej biblioteki na stronie, po prostu dodaj ją do list bibliotek dostawców dla tej strony. To tylko kilka postaci. W każdym razie w swoim rozwiązaniu musisz to zrobić, określając loader.Jeśli nie wiesz, które strony będą korzystały z twojego nowo utworzonego modułu - pozwól wtyczce CommonChuncks automatycznie wyodrębnić wspólne biblioteki z twoich modułów.
Michał Margiel
Jak oddzielnie ustawić kontekst dla plików dostawców?
ostry53
44

W przypadku, gdy interesuje Cię automatyczne grupowanie skryptów oddzielnie od skryptów dostawców:

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

Możesz przeczytać więcej o tej funkcji w oficjalnej dokumentacji .

Freezystem
źródło
4
Należy pamiętać, że vendor : Object.keys(pkg.dependencies) nie zawsze działa i jest zależne od sposobu budowy pakietu.
markyph
1
Zawsze jesteś zależny od tego, jak masz package.jsonustawione. To obejście działa w większości przypadków, ale są wyjątki, w których musisz wybrać inną ścieżkę. Może być interesujące opublikowanie własnej odpowiedzi na pytanie, aby pomóc społeczności.
Freezystem
16
Lubię to. To sprawiło, że trochę się wysikałem.
cgatian
3
zwróć uwagę, że będzie on zawierał nawet pakiety, których możesz w ogóle nie używać w swoim kodzie ... ze względu na Object.keys(pkg.dependencies)spakowanie wszystkiego !!!! powiedzmy, że masz tam wymienioną grupę ładujących ... tak, to zostanie uwzględnione !!! więc uważaj ... rozdziel ostrożnie, co to jest devDependency i co to jest zależność
Rafael Milewski
1
@RafaelMilewski dlaczego miałbyś mieć w sobie ładowarki dependencies?
Spodnie
13

Nie jestem też pewien, czy w pełni rozumiem Twój przypadek, ale oto fragment kodu konfiguracyjnego umożliwiający utworzenie oddzielnych fragmentów dostawców dla każdego z Twoich pakietów:

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

I link do CommonsChunkPlugindokumentów: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin

Alex Fedoseev
źródło
Uważam, że problem z tym rozwiązaniem jest taki sam, jak w przypadku Michała. Zakładasz, że znam zależności dostawców dla bundle1 i bundle2, ale ja nie ... Wyobraź sobie, że masz 200 pakietów, czy chcesz to wszystko określić w pliku konfiguracyjnym? Na podstawie Twojego przykładu reactpowinien być obecny w pakiecie dostawcy tylko wtedy, gdy jest to wyraźnie wymagane przez bundle1 i bundl2. Nie powinienem musieć tego określać w pliku konfiguracyjnym ... Czy to ma sens? Jakieś pomysły?
bensampaio
@Anakin pytanie brzmi, dlaczego chcesz połączyć narzędzie 200 dostawców w osobnym pliku. Umieściłbym tylko popularne narzędzia w osobnym pliku, a resztę zachowałbym w pakietach projektu.
maxisam
@Anakin Myślę, że mam do czynienia z tym samym problemem, popraw mnie, jeśli się mylę? stackoverflow.com/questions/35944067/…
pjdicke