Làm cách nào để gói các tập lệnh của nhà cung cấp riêng biệt và yêu cầu chúng khi cần với Webpack?


173

Tôi đang cố gắng làm điều gì đó mà tôi tin là có thể, nhưng tôi thực sự không thể hiểu làm thế nào chỉ từ tài liệu webpack.

Tôi đang viết một thư viện JavaScript với một số mô-đun có thể phụ thuộc lẫn nhau. Trên hết, jQuery được sử dụng bởi tất cả các mô-đun và một số trong số chúng có thể cần các plugin jQuery. Thư viện này sau đó sẽ được sử dụng trên một số trang web khác nhau có thể yêu cầu một số hoặc tất cả các mô-đun.

Xác định sự phụ thuộc giữa các mô-đun của tôi rất dễ dàng, nhưng việc xác định sự phụ thuộc của bên thứ ba của họ dường như khó hơn tôi mong đợi.

Những gì tôi muốn đạt được : đối với mỗi ứng dụng tôi muốn có hai tệp bó một với các phụ thuộc bên thứ ba cần thiết và khác với các mô-đun cần thiết từ thư viện của tôi.

Ví dụ : Hãy tưởng tượng rằng thư viện của tôi có các mô-đun sau:

  • a (yêu cầu: jquery, jquery.plugin1)
  • b (yêu cầu: jquery, a)
  • c (yêu cầu: jquery, jquery.ui, a, b)
  • d (yêu cầu: jquery, jquery.plugin2, a)

Và tôi có một ứng dụng (xem nó như một tệp nhập duy nhất) yêu cầu các mô-đun a, b và c. Webpack cho trường hợp này sẽ tạo ra các tệp sau:

  • gói nhà cung cấp : với jquery, jquery.plugin1 và jquery.ui;
  • gói trang web : với các mô-đun a, b và c;

Cuối cùng, tôi muốn có jQuery như một toàn cầu vì vậy tôi không cần phải yêu cầu nó trên mỗi tệp duy nhất (ví dụ: tôi chỉ có thể yêu cầu nó trên tệp chính). Và các plugin jQuery sẽ chỉ mở rộng $ global trong trường hợp chúng được yêu cầu (không có vấn đề gì nếu chúng có sẵn cho các mô-đun khác không cần chúng).

Giả sử điều này là có thể, ví dụ về tệp cấu hình webpack cho trường hợp này là gì? Tôi đã thử một số kết hợp các trình tải, bên ngoài và plugin trên tệp cấu hình của mình, nhưng tôi không thực sự hiểu những gì chúng đang làm và những gì tôi nên sử dụng. Cảm ơn bạn!


2
giải pháp của bạn là gì? bạn đã quản lý để tìm một cách tiếp cận tốt. Nếu vậy xin vui lòng gửi nó! cảm ơn
GeekOnGadgets

Câu trả lời:


140

trong tệp webpack.config.js (Phiên bản 1,2,3) của tôi, tôi có

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

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

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

trong mảng plugin của tôi

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

Bây giờ tôi có một tệp chỉ thêm libs của bên thứ 3 vào một tệp theo yêu cầu.

Nếu bạn muốn có được chi tiết hơn, nơi bạn tách các nhà cung cấp và các tệp điểm nhập:

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

Lưu ý rằng thứ tự của các plugin rất quan trọng.

Ngoài ra, điều này sẽ thay đổi trong phiên bản 4. Khi đó là chính thức, tôi cập nhật câu trả lời này.

Cập nhật: thay đổi tìm kiếm indexOf cho người dùng windows


1
Tôi không biết nếu điều này đã có thể khi tôi đăng câu hỏi của mình, nhưng đây thực sự là điều tôi đang tìm kiếm. Với giải pháp này, tôi không còn cần phải xác định chunk mục nhà cung cấp của mình. Cảm ơn rất nhiều!
bensampaio

1
isExternaltrong minChunksngày của tôi. Làm thế nào là điều này không được ghi nhận? Có nhược điểm nào không?
Wesley Schleumer de Góes

Thx, nhưng thay đổi userRequest.indexOf ('/ node_modules /') thành userRequest.indexOf ('node_modules') cho các đường dẫn windows
Kinjeiro

@ WesleySchleumerdeGóes nó được ghi lại nhưng không có ví dụ options.minChunks (number|Infinity|function(module, count) -> boolean):tôi vẫn chưa thấy nhược điểm.
Rafael De Leon

2
Điều này sẽ không hoạt động khi sử dụng các trình tải, vì đường dẫn của trình tải cũng sẽ ở trong module.userRequest(và trình nạp có thể nằm trong node_modules). Mã của tôi cho isExternal():return typeof module.userRequest === 'string' && !!module.userRequest.split('!').pop().match(/(node_modules|bower_components|libraries)/);
cdauth 7/12/2016

54

Tôi không chắc chắn nếu tôi hoàn toàn hiểu vấn đề của bạn nhưng vì tôi có vấn đề tương tự gần đây, tôi sẽ cố gắng giúp bạn.

Nhà cung cấp bó.

Bạn nên sử dụng CommonsChunkPlugin cho điều đó. trong cấu hình, bạn chỉ định tên của đoạn (ví dụ vendor) và tên tệp sẽ được tạo ( vendor.js).

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

Bây giờ phần quan trọng, bây giờ bạn phải xác định vendorthư viện nghĩa là gì và bạn làm điều đó trong phần nhập cảnh. Thêm một mục nữa vào danh sách mục nhập có cùng tên với tên của đoạn mã mới được khai báo (nghĩa là 'nhà cung cấp' trong trường hợp này). Giá trị của mục nhập đó phải là danh sách của tất cả các mô-đun mà bạn muốn chuyển sang vendorgói. trong trường hợp của bạn, nó sẽ trông giống như:

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

JQuery là toàn cầu

Có cùng một vấn đề và giải quyết nó với ProvPlugin . Ở đây bạn không định nghĩa đối tượng toàn cầu mà là loại chuyển đổi thành các mô-đun. tức là bạn có thể cấu hình nó như thế:

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

Và bây giờ bạn có thể sử dụng $bất cứ nơi nào trong mã của mình - webpack sẽ tự động chuyển đổi nó thành

require('jquery')

Tôi hy vọng nó đã giúp. bạn cũng có thể xem tập tin cấu hình webpack của tôi ở đây

Tôi yêu webpack, nhưng tôi đồng ý rằng tài liệu này không phải là tài liệu đẹp nhất trên thế giới ... nhưng này .. mọi người đã nói điều tương tự về tài liệu Angular khi bắt đầu :)


Biên tập:

Để có các khối nhà cung cấp dành riêng cho điểm nhập cảnh, chỉ cần sử dụng CommonsChunkPlugins nhiều lần:

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

và sau đó khai báo các thư viện mở rộng khác nhau cho các tệp khác nhau:

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

Nếu một số thư viện chồng chéo (và đối với hầu hết trong số chúng) giữa các điểm nhập thì bạn có thể trích xuất chúng thành tệp chung bằng cách sử dụng cùng một plugin với cấu hình khác nhau. Xem ví dụ này .


Cảm ơn bạn rất nhiều vì đã trả lời của bạn. Đây là cách tiếp cận tốt nhất tôi thấy cho đến bây giờ nhưng thật không may, nó vẫn không giải quyết được vấn đề của tôi ... Tôi đã kiểm tra ví dụ của bạn và tệp Vend.js vẫn sẽ chứa tất cả mã từ 'jquery' và 'jquery.plugin1' ngay cả khi chúng không được yêu cầu bởi bất kỳ mô-đun nào của tôi. Điều này có nghĩa là cuối cùng chúng sẽ luôn được tải lên trình duyệt. Nếu tôi có nhiều plugin jquery, điều này sẽ dẫn đến một tệp rất lớn ngay cả khi chỉ một nửa trong số chúng được sử dụng. Có cách nào để bao gồm 'jquery.plugin1' trong gói nhà cung cấp chỉ khi được yêu cầu không?
bensampaio

cảm ơn, vì vậy tôi cũng đã học được điều gì đó :) Tôi đã cập nhật câu trả lời của mình với việc tạo ra nhiều khối nhà cung cấp. có lẽ bây giờ nó sẽ phù hợp với bạn hơn
Michał Margiel

4
Vấn đề với giải pháp này là nó giả định rằng tôi biết các phụ thuộc cho mỗi trang là gì. Nhưng tôi không thể dự đoán rằng ... jQuery chỉ nên được bao gồm trong gói nhà cung cấp nếu được yêu cầu bởi một trong các mô-đun được sử dụng trong trang. Bằng cách chỉ định rằng trên tệp cấu hình, nó sẽ luôn nằm trong gói nhà cung cấp ngay cả khi không yêu cầu bởi bất kỳ mô-đun nào được sử dụng trong trang, phải không? Về cơ bản, tôi không thể dự đoán nội dung của các gói nhà cung cấp, nếu không tôi sẽ có một công việc khủng khiếp vì tôi không có 2 trang tôi có hàng trăm ... Bạn có gặp vấn đề gì không? Có ý kiến ​​gì không? :)
bensampaio

Tôi hiểu những gì bạn đang nói nhưng tôi không thấy đó là một vấn đề. Nếu bạn sử dụng thư viện mới trong một trang, thì chỉ cần thêm nó vào danh sách thư viện của nhà cung cấp cho trang đó. Nó chỉ là vài nhân vật. Dù sao, trong giải pháp của bạn, bạn phải thực hiện bằng cách chỉ định trình tải. Nếu bạn không biết trang nào sẽ sử dụng mô-đun mới tạo của mình - thì hãy để plugin CommonChuncks tự động trích xuất các thư viện từ mô-đun của bạn.
Michał Margiel

Làm cách nào để đặt bối cảnh riêng cho các tập tin của nhà cung cấp?
harshes53

44

Trong trường hợp bạn quan tâm đến việc tự động đóng gói các tập lệnh của mình với các tập lệnh của nhà cung cấp:

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

Bạn có thể đọc thêm về tính năng này trong tài liệu chính thức .


4
Xin lưu ý rằng vendor : Object.keys(pkg.dependencies) không phải lúc nào cũng hoạt động và phụ thuộc vào cách gói được xây dựng.
markyph

1
Bạn luôn luôn phụ thuộc vào cách bạn package.jsonđược thiết lập. Cách giải quyết này hoạt động trong hầu hết các trường hợp nhưng có những trường hợp ngoại lệ mà bạn sẽ phải đi một con đường khác. Có thể thú vị để gửi câu trả lời của riêng bạn cho câu hỏi để giúp cộng đồng.
Freezystem

16
Tôi thích điều này. Nó làm tôi đi tiểu một chút.
cgatian

3
lưu ý rằng nó thậm chí sẽ bao gồm các gói mà bạn thậm chí có thể không sử dụng trong mã của mình ... do Object.keys(pkg.dependencies)sẽ gói mọi thứ !!!! giả sử bạn có một loạt các trình tải được liệt kê ở đó ... vâng sẽ được bao gồm !!! vì vậy hãy cẩn thận ... tách biệt một cách cẩn thận thế nào là sự phụ thuộc và sự phụ thuộc là gì
Rafael Milewski

1
@RafaelMilewski tại sao bạn lại có bộ nạp dependencies?
Quần

13

Cũng không chắc chắn nếu tôi hoàn toàn hiểu trường hợp của bạn, nhưng đây là đoạn cấu hình để tạo các đoạn nhà cung cấp riêng cho từng gói của bạn:

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

Và liên kết đến CommonsChunkPlugintài liệu: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin


Tôi tin rằng vấn đề với giải pháp này giống như vấn đề được cung cấp bởi Michal. Bạn đang giả sử tôi biết các phụ thuộc của nhà cung cấp cho bund1 và bund2, nhưng tôi không ... Hãy tưởng tượng bạn có 200 gói bạn muốn chỉ định tất cả những thứ đó trên tệp cấu hình? Sử dụng ví dụ của bạn, reactchỉ nên có mặt trong gói nhà cung cấp nếu được yêu cầu rõ ràng bởi bundle1 và bundl2. Tôi không cần phải xác định rằng trên tập tin cấu hình ... Điều này có hợp lý không? Có ý kiến ​​gì không?
bensampaio

@Anakin câu hỏi là tại sao bạn muốn gói 200 công cụ nhà cung cấp vào một tệp riêng biệt. Tôi sẽ chỉ gói các công cụ phổ biến vào một tệp riêng biệt và giữ phần còn lại với các gói dự án.
maxisam

@Anakin Tôi nghĩ tôi đang xử lý vấn đề tương tự, sửa tôi nếu tôi sai? stackoverflow.com/questions/35944067/iêu
pjdicke 11/03/2016
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.