├── .bowerrc ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.markdown ├── bower.json ├── build.js ├── package.json └── src ├── app ├── hello │ ├── main.js │ └── main.template.txt └── world │ ├── main.js │ └── main.template.txt ├── common.js ├── hello.html └── world.html /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "src/app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /app/bower_components/ 2 | /build/ 3 | /node_modules/ 4 | /src/app/bower_components/ 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "camelcase": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "es3": false, 6 | "forin": false, 7 | "immed": true, 8 | "indent": false, 9 | "latedef": "nofunc", 10 | "newcap": true, 11 | "noarg": true, 12 | "noempty": true, 13 | "nonew": true, 14 | "plusplus": false, 15 | "quotmark": true, 16 | "undef": true, 17 | "unused": "vars", 18 | "strict": true, 19 | "trailing": true, 20 | 21 | "boss": false, 22 | "debug": false, 23 | "smarttabs": false, 24 | "eqnull": true, 25 | "laxcomma": true, 26 | 27 | "browser": true, 28 | "devel": true, 29 | "jquery": true, 30 | 31 | "globals": { 32 | "define": false 33 | ,"require": false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function(grunt) { 3 | 'use strict'; 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | // Metadata. 8 | pkg: grunt.file.readJSON('package.json'), 9 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 10 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 11 | '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + 12 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + 13 | ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n', 14 | // Task configuration. 15 | jshint: { 16 | options: { 17 | jshintrc: '.jshintrc' 18 | }, 19 | gruntfile: { 20 | src: 'Gruntfile.js' 21 | }, 22 | sourcefiles: { 23 | src: ['src/**/*.js', '!src/app/bower_components/**/*.js'] 24 | } 25 | }, 26 | requirejs: { 27 | options: { 28 | 'appDir': 'src', 29 | 'dir': 'build', 30 | 'mainConfigFile': 'src/common.js', 31 | 'optimize': 'uglify2', 32 | 'normalizeDirDefines': 'skip', 33 | 'skipDirOptimize': true, 34 | }, 35 | centralized: { 36 | options: { 37 | 'modules': [{ 38 | 'name': 'common', 39 | 'include': ['jquery', 40 | 'underscore', 41 | 'backbone', 42 | 'text', 43 | 'app/hello/main', 44 | 'app/world/main', 45 | ], 46 | } 47 | ] 48 | } 49 | }, 50 | centralizedAlmond: { 51 | options: { 52 | almond: true, 53 | replaceRequireScript: [{ 54 | files: ['build/hello.html'], 55 | module: 'common', 56 | modulePath: 'common' 57 | }, { 58 | files: ['build/world.html'], 59 | module: 'common', 60 | modulePath: 'common' 61 | }], 62 | 'modules': [{ 63 | 'name': 'common', 64 | 'include': ['jquery', 65 | 'underscore', 66 | 'backbone', 67 | 'text', 68 | 'app/hello/main', 69 | 'app/world/main', 70 | ], 71 | }, 72 | ], 73 | } 74 | }, 75 | independent: { 76 | options: { 77 | replaceRequireScript: [{ 78 | files: ['build/hello.html'], 79 | module: 'app/hello/main', 80 | modulePath: 'app/hello/main' 81 | }, { 82 | files: ['build/world.html'], 83 | module: 'app/world/main', 84 | modulePath: 'app/world/main' 85 | }], 86 | 'modules': [{ 87 | name: 'app/hello/main', 88 | include: ['backbone', 'common'], 89 | }, { 90 | name: 'app/world/main', 91 | include: ['backbone', 'common'], 92 | } 93 | ], 94 | } 95 | }, 96 | independentAlmond: { 97 | options: { 98 | almond: true, 99 | wrap: true, 100 | replaceRequireScript: [{ 101 | files: ['build/hello.html'], 102 | module: 'app/hello/main', 103 | modulePath: 'app/hello/main' 104 | }, { 105 | files: ['build/world.html'], 106 | module: 'app/world/main', 107 | modulePath: 'app/world/main' 108 | }], 109 | 'modules': [{ 110 | name: 'app/hello/main', 111 | include: ['backbone'], 112 | insertRequire: ['app/hello/main'] 113 | }, { 114 | name: 'app/world/main', 115 | include: ['backbone'], 116 | insertRequire: ['app/world/main'] 117 | } 118 | ], 119 | } 120 | }, 121 | shared: { 122 | options: { 123 | 'modules': [{ 124 | 'name': 'common', 125 | 'include': ['jquery', 126 | 'underscore', 127 | 'backbone', 128 | 'text', 129 | ], 130 | }, 131 | { 132 | 'name': 'app/hello/main', 133 | 'exclude': ['common'] 134 | }, 135 | { 136 | 'name': 'app/world/main', 137 | 'exclude': ['common'] 138 | } 139 | ], 140 | } 141 | }, 142 | } 143 | }); 144 | 145 | // These plugins provide necessary tasks. 146 | grunt.loadNpmTasks('grunt-contrib-jshint'); 147 | grunt.loadNpmTasks('grunt-requirejs'); 148 | 149 | // Default task. 150 | grunt.registerTask('default', ['jshint']); 151 | }; 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Cloud Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Requirejs Bundle Types 2 | =============== 3 | > This example project is inspired by [a question from StackOverflow](http://stackoverflow.com/questions/17035609/most-efficient-multipage-requirejs-and-almond-setup) 4 | 5 | ## 3 ways to optimize requirejs bundle files 6 | > 1. Optimize a single JS file for the whole site (w/ or w/o Almond) 7 | 2. Optimize a single JS file for each page (w/ or w/o Almond) 8 | 3. Optimize one JS file for common modules, and then another for each specific page. (Almond doesn't fit in with this way) 9 | 10 | ## Naming them for easier understanding 11 | 1. Centralized / Centralized-Almond 12 | 2. Independent / Independent-Almond 13 | 3. Shared 14 | 15 | ## What does this project have? 16 | _It contains 2 pages, 1 common module, 2 individual modules and corresponding 2 template files_ 17 | 18 | ### Folder structure 19 | ``` 20 | src 21 | ├── app/ 22 | │ ├── bower_components/ 23 | │ │ ├── backbone/ 24 | │ │ ├── jquery/ 25 | │ │ ├── requirejs/ 26 | │ │ ├── requirejs-text/ 27 | │ │ └── underscore/ 28 | │ ├── hello/ 29 | │ │ ├── main.js 30 | │ │ └── main.template.txt 31 | │ └── world/ 32 | │ ├── main.js 33 | │ └── main.template.txt 34 | ├── common.js 35 | ├── hello.html 36 | └── world.html 37 | ``` 38 | 39 | #### 1. Pages 40 | > Including requirejs library and data-main script 41 | 42 | - src/hello.html 43 | - src/world.html 44 | 45 | #### 2. Common module 46 | > including requirejs.config({}) and callback() to launch app 47 | 48 | - src/common.js 49 | 50 | #### 3. Individual modules and corresponding template files 51 | > Individual module and its dependencies 52 | 53 | - src/app/hello/main.js 54 | - src/app/hello/main.template.txt 55 | - src/app/world/main.js 56 | - src/app/world/main.template.txt 57 | 58 | #### 4. Vendor resources 59 | > External static libraries 60 | 61 | _Every library has its own short module/path name in requirejs.config({}) definition_ 62 | 63 | _They are installed by [Bower](http://bower.io) which is not important here_ 64 | - src/app/bower_components/* 65 | 66 | Building bundle files 67 | =============== 68 | 69 | ### 1. Requirejs native build file or grunt-requirejs task? 70 | 71 | _ | Share common configurations | Almond support | Script tag substitution 72 | ---------------------| :-------------------------: | :------------: | :---------------------: 73 | build file | No | Yes | No 74 | grunt-requirejs task | Yes | Yes | Yes 75 | 76 | > [grunt-requirejs](https://github.com/asciidisco/grunt-requirejs) wins! 77 | 78 | ### 2. Predefined grunt tasks 79 | - `grunt requirejs:centralized` and `grunt requirejs:centralizedAlmond` 80 | - `grunt requirejs:independent` and `grunt requirejs:independentAlmond` 81 | - `grunt requirejs:shared` 82 | 83 | ### 3. Before optimization 84 | #### Every page has 8 requests. 85 | 86 | ![Before](https://f.cloud.github.com/assets/44489/1102603/ad0ceeae-1838-11e3-9251-21c1090a58a4.png) 87 | 88 | ### 4. After optimization 89 | 90 | ##### `grunt requirejs:centralized` 91 | 92 | > All modules are bundled into one file 93 | 94 | > Every page shares this bundule file 95 | 96 | > Reduced to 3 requests 97 | 98 | > ![grunt requirejs:centralized](https://f.cloud.github.com/assets/44489/1102618/686bcf52-183a-11e3-9839-443fb87c7927.png) 99 | 100 | ##### `grunt requirejs:centralizedAlmond` 101 | 102 | > All modules are bundled into one file (including Almond) 103 | 104 | > Every page shares this bundule file without additional require.js 105 | 106 | > Reduced to 2 requests 107 | 108 | > ![grunt requirejs:centralizedAlmond](https://f.cloud.github.com/assets/44489/1102617/686ba19e-183a-11e3-857d-e239073b53fc.png) 109 | 110 | #### `grunt requirejs:independent` 111 | 112 | > Every individual module is bundle with common modules into its own bundle file 113 | 114 | > Every page loads its own bundule file 115 | 116 | > Reduced to 3 requests 117 | 118 | > ![grunt requirejs:independent](https://f.cloud.github.com/assets/44489/1103240/66e31342-1895-11e3-83aa-efb0466cf1a3.png) 119 | 120 | 121 | #### `grunt requirejs:independentAlmond` 122 | > Every individual module is bundled with common modules into its own bundle file (including Almond) 123 | 124 | > Every page loads its own bundule file without additional require.js 125 | 126 | > Reduced to 2 requests 127 | 128 | > ![grunt requirejs:independentAlmond](https://f.cloud.github.com/assets/44489/1103232/de79d3ec-1894-11e3-8e21-879434dc1a41.png) 129 | 130 | #### `grunt requirejs:shared` 131 | > Every individual module is bundled into its own bundle file without common modules 132 | 133 | > All common modules are bundled into another file which is shared for all pages 134 | 135 | > Reduced to 4 requests 136 | 137 | > ![grunt requirejs:shared](https://f.cloud.github.com/assets/44489/1103256/1cf532a4-1897-11e3-9055-f13dcdc65ff6.png) 138 | 139 | Which one is better? 140 | =============== 141 | 142 | ### It depends on your requirement actually. 143 | 144 | - `grunt requirejs:shared` is most efficient at getting rid of duplicated downloading and utilizing cache as much as possible. 145 | - `grunt requirejs:centralized` is sutiable for small bundle file or mobile site. Download once, use everywhere. 146 | - `grunt requirejs:independent` has least usage scenarios. Might suitable for one-off project. 147 | 148 | Notes 149 | =============== 150 | ## Prefer `paths` rather than `maps` configuration if possible 151 | 152 | It's better to use `paths` to achieve same objective. 153 | 154 | ``` 155 | paths: { 156 | 'text': 'requirejs-text/text', 157 | }, 158 | ``` 159 | Utilizing `paths` rather than `maps` is better for bundle in some cases. 160 | Because Almond doesn't support `maps` configuration. 161 | 162 | ``` 163 | map: { 164 | '*': { 165 | 'text': 'requirejs-text/text' 166 | } 167 | }, 168 | ``` 169 | 170 | ## Write `require.config({})` once, use everywhere 171 | 172 | - Development and build environment share same configuration 173 | - Other environments, for example, test environment can use it as well 174 | - `common.js` is right place to hold it 175 | - **Keep DRY** 176 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "requirejs-bundle-example", 3 | "description": "Typical Requirejs Bundle Examples", 4 | "devDependencies": { 5 | "jquery": "~2.0.3", 6 | "requirejs": "~2.1.8", 7 | "requirejs-text": "~2.0.10", 8 | "backbone": "~1.0.0", 9 | "underscore": "~1.5.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | { 2 | "appDir": "src", 3 | "dir": "build", 4 | "mainConfigFile": "src/common.js", 5 | //"optimize": "uglify2", 6 | "optimize": "none", 7 | //"optimizeCss": "standard", 8 | //"preserveLicenseComments": false, 9 | //"generateSourceMaps": false, 10 | "normalizeDirDefines": "all", // 11 | "skipDirOptimize": false, //speed up non-build bundle 12 | //"skipModuleInsertion": false, 13 | //"stubModules": ["text"], 14 | //"removeCombined": true, 15 | "optimizeAllPluginResources": true, 16 | "modules": [ 17 | { 18 | "name": "common", 19 | "include": ["jquery", 20 | "underscore", 21 | "backbone", 22 | "text" 23 | ] 24 | }, 25 | { 26 | "name": "app/hello/main", 27 | "exclude": ["common"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "requirejs-bundle-examples", 3 | "description": "Typical Requirejs Bundle Examples", 4 | "devDependencies": { 5 | "grunt-contrib-jshint": "~0.6.4", 6 | "grunt": "~0.4.1", 7 | "grunt-requirejs": "git://github.com/cloudchen/grunt-requirejs" 8 | }, 9 | "scripts": { 10 | "postinstall": "bower install" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app/hello/main.js: -------------------------------------------------------------------------------- 1 | define(function(require){ 2 | 'use strict'; 3 | 4 | var $ = require('jquery'), 5 | _ = require('underscore'), 6 | template = require('text!./main.template.txt'); 7 | 8 | var elm = $('body'); 9 | elm.append(template); 10 | 11 | _.each([1,2,3], function(v){ 12 | elm.append(v + '
'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/app/hello/main.template.txt: -------------------------------------------------------------------------------- 1 |

Typical Requirejs Project

2 |

Hello Module Content

3 | -------------------------------------------------------------------------------- /src/app/world/main.js: -------------------------------------------------------------------------------- 1 | define(function(require){ 2 | 'use strict'; 3 | 4 | var $ = require('jquery'), 5 | _ = require('underscore'), 6 | template = require('text!./main.template.txt'); 7 | 8 | var elm = $('body'); 9 | elm.append(template); 10 | 11 | _.each([1,2,3], function(v){ 12 | elm.append(v + '
'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/app/world/main.template.txt: -------------------------------------------------------------------------------- 1 |

Typical Requirejs Project

2 |

World Module Content

3 | -------------------------------------------------------------------------------- /src/common.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | baseUrl: 'app/bower_components', 3 | paths: { 4 | 'jquery': 'jquery/jquery.min', 5 | 'backbone': 'backbone/backbone-min', 6 | 'underscore': 'underscore/underscore-min', 7 | 'text': 'requirejs-text/text', 8 | 9 | 'app': '..', 10 | 'common': '../../common' 11 | }, 12 | shim: { 13 | backbone: { 14 | deps: ['underscore'], 15 | exports: 'Backbone' 16 | }, 17 | underscore: { 18 | exports: '_' 19 | } 20 | }, 21 | deps: ['require'], 22 | callback: function(require) { 23 | 'use strict'; 24 | 25 | var filename = location.pathname.match(/\/([^\/]*)$/), 26 | modulename; 27 | 28 | if (filename && filename[1] !== '') { 29 | modulename = ['app', filename[1].split('.')[0], 'main'] 30 | .join('/'); 31 | 32 | require([modulename]); 33 | } else { 34 | if (window.console) { 35 | console.log('no modulename found via location.pathname'); 36 | } 37 | } 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /src/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/world.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------