├── LICENSE.md ├── README.md ├── index.js └── package.json /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 SoundCloud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Split By Name Webpack Plugin 2 | 3 | This plugin will split your entry bundle into any number of arbitrarily defined smaller bundles. 4 | 5 | ### Why? 6 | 7 | - Browsers will open [between 6 and 10][browserscope] parallel connections to a single host. By splitting up one large 8 | file (your main bundle) into a number of smaller ones, you can leverage these connections to download the files 9 | [faster][stevesouders]. 10 | - It's likely that you will have some third party scripts which you change infrequently. By putting these into their own 11 | bundle, then if they haven't changed between builds, your users may still be able to use the cached version from before. 12 | 13 | ### How? 14 | 15 | Configuration of the plugin is simple. You instantiate the plugin with a single option: `buckets` which should be an 16 | array of objects, each containing the keys `name` and `regex`. Any modules which are in your entry chunk which match the 17 | bucket's regex (first matching bucket is used), are then moved to a new chunk with the given name. 18 | 19 | Creating a 'catch-all' bucket is not necessary: anything which doesn't match one of the defined buckets will be left in 20 | the original chunk. 21 | 22 | ### Example 23 | 24 | ```js 25 | var SplitByNamePlugin = require('split-by-name-webpack-plugin'); 26 | module.exports = { 27 | entry: { 28 | app: 'app.js' 29 | }, 30 | output: { 31 | path: __dirname + '/public', 32 | filename: "[name]-[chunkhash].js", 33 | chunkFilename: "[name]-[chunkhash].js" 34 | }, 35 | plugins: [ 36 | new SplitByNamePlugin({ 37 | buckets: [{ 38 | name: 'vendor', 39 | regex: /vendor\// 40 | }, { 41 | name: 'views', 42 | regex: /views\// 43 | }] 44 | }) 45 | ] 46 | }; 47 | ``` 48 | 49 | An an example structure of modules included in the entry chunk: 50 | 51 | ``` 52 | /lib 53 | /views 54 | /list.js 55 | /grid.js 56 | /url.js 57 | /vendor 58 | /jquery.js 59 | /backbone.js 60 | /views 61 | /home.js 62 | /banner.js 63 | /app.js 64 | ``` 65 | 66 | The output would be three files: 67 | 68 | - `app-[hash].js`, containing: 69 | - `app.js` 70 | - `lib/url.js` 71 | - `vendor-[hash].js`, containing: 72 | - `vendor/jquery.js` 73 | - `vendor/backbone.js` 74 | - `views-[hash].js`, containing: 75 | - `lib/views/list.js` 76 | - `lib/views/grid.js` 77 | - `views/home.js` 78 | - `views/banner.js` 79 | 80 | [browserscope]: http://www.browserscope.org/?category=network&v=1 81 | [stevesouders]: http://www.stevesouders.com/blog/2013/09/05/domain-sharding-revisited/ 82 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var SplitByNamePlugin = module.exports = function (options) { 2 | this.options = options; 3 | 4 | // process buckets 5 | this.options.buckets = this.options.buckets.slice(0).map(function (bucket) { 6 | if (!(bucket.regex instanceof RegExp)) { 7 | bucket.regex = new RegExp(bucket.regex); 8 | } 9 | return bucket; 10 | }); 11 | }; 12 | 13 | SplitByNamePlugin.prototype.apply = function(compiler) { 14 | var options = this.options; 15 | 16 | function findMatchingBucket(chunk) { 17 | var match = null; 18 | options.buckets.some(function (bucket) { 19 | if (bucket.regex.test(chunk.rawRequest)) { 20 | match = bucket; 21 | return true; 22 | } 23 | }); 24 | return match; 25 | } 26 | 27 | compiler.plugin("compilation", function(compilation) { 28 | var extraChunks = {}; 29 | 30 | // Find the chunk which was already created by this bucket. 31 | // This is also the grossest function name I've written today. 32 | function bucketToChunk(bucket) { 33 | return extraChunks[bucket.name]; 34 | } 35 | 36 | compilation.plugin("optimize-chunks", function(chunks) { 37 | var addChunk = this.addChunk.bind(this); 38 | chunks 39 | // only parse the entry chunk 40 | .filter(function (chunk) { 41 | return chunk.entry; 42 | }) 43 | .forEach(function(chunk) { 44 | chunk.modules.slice().forEach(function (mod) { 45 | var bucket = findMatchingBucket(mod), 46 | newChunk; 47 | if (!bucket) { 48 | // it stays in the original bucket 49 | return; 50 | } 51 | if (!(newChunk = bucketToChunk(bucket))) { 52 | newChunk = extraChunks[bucket.name] = addChunk(bucket.name); 53 | } 54 | // add the module to the new chunk 55 | newChunk.addModule(mod); 56 | mod.addChunk(newChunk); 57 | // remove it from the existing chunk 58 | mod.removeChunk(chunk); 59 | }); 60 | 61 | options.buckets 62 | .map(bucketToChunk) 63 | .filter(Boolean) 64 | .concat(chunk) 65 | .forEach(function (chunk, index, allChunks) { // allChunks = [bucket0, bucket1, .. bucketN, orig] 66 | if (index) { // not the first one, they get the first chunk as a parent 67 | chunk.parents = [allChunks[0]]; 68 | } else { // the first chunk, it gets the others as 'sub' chunks 69 | chunk.chunks = allChunks.slice(1); 70 | } 71 | chunk.initial = chunk.entry = !index; 72 | }); 73 | }); 74 | }); 75 | }); 76 | }; 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "split-by-name-webpack-plugin", 3 | "version": "0.0.3", 4 | "description": "Split a Webpack entry bundle into any number of arbitrarily defined smaller bundles", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/soundcloud/split-by-name-webpack-plugin" 9 | }, 10 | "keywords": [ 11 | "webpack" 12 | ], 13 | "author": "SoundCloud, Nick Fisher", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/soundcloud/split-by-name-webpack-plugin/issues" 17 | }, 18 | "homepage": "https://github.com/soundcloud/split-by-name-webpack-plugin" 19 | } 20 | --------------------------------------------------------------------------------