├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── LICENSE.md ├── README.md ├── TODO.md ├── addon ├── .gitkeep ├── lazy-router.js ├── resolver.js ├── routes │ └── -lazy-loader.js ├── services │ └── lazy-loader.js └── utils │ ├── -resource-helper.js │ ├── get-owner.js │ └── load-assets.js ├── app ├── .gitkeep ├── routes │ └── -lazy-loader.js └── services │ └── lazy-loader.js ├── approvals ├── a_dev_build.contains_an_index_file.approved.txt ├── a_dev_build.contains_dummy_css.approved.txt ├── a_dev_build.contains_dummy_js.approved.txt ├── a_dev_build.contains_package1_css.approved.txt ├── a_dev_build.contains_package1_js.approved.txt ├── a_dev_build.contains_package2_css.approved.txt ├── a_dev_build.contains_package2_js.approved.txt ├── a_dev_build.contains_vendor_css.approved.txt ├── a_dev_build.contains_vendor_js.approved.txt ├── config.js └── test.js ├── blueprints ├── .jshintrc ├── ember-cli-bundle-loader │ ├── files │ │ ├── app │ │ │ ├── resolver.js │ │ │ └── router.js │ │ ├── config │ │ │ ├── bundles.js │ │ │ └── package-names.js │ │ ├── ember-cli-build.js │ │ └── packages │ │ │ └── .gitkeep │ └── index.js └── package │ ├── files │ └── packages │ │ └── __name__ │ │ ├── components │ │ └── .gitkeep │ │ ├── controllers │ │ └── .gitkeep │ │ ├── helpers │ │ └── .gitkeep │ │ ├── models │ │ └── .gitkeep │ │ ├── resolvers │ │ └── .gitkeep │ │ ├── routes │ │ └── .gitkeep │ │ ├── styles │ │ └── app.scss │ │ └── templates │ │ └── .gitkeep │ └── index.js ├── bower.json ├── config ├── deploy.js ├── ember-try.js ├── environment.js └── release.js ├── ember-cli-build.js ├── index.js ├── lib ├── broccoli │ └── ember-app-with-packages.js └── utils │ └── get-bundle-configuration.js ├── package.json ├── testem.js ├── tests-node └── test.js ├── tests ├── .jshintrc ├── acceptance │ ├── a-resolver-test.js │ ├── get-owner-test.js │ ├── loading-substate-test.js │ ├── loads-dependent-package-test.js │ ├── query-params-test.js │ └── services │ │ └── lazy-loader-test.js ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ ├── .gitkeep │ │ │ ├── loading-for-test.js │ │ │ └── my-component.js │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── resolver.js │ │ ├── router.js │ │ ├── routes │ │ │ ├── .gitkeep │ │ │ ├── application.js │ │ │ ├── boot.js │ │ │ └── package1 │ │ │ │ └── nested.js │ │ ├── styles │ │ │ └── app.scss │ │ └── templates │ │ │ ├── application.hbs │ │ │ ├── boot.hbs │ │ │ ├── components │ │ │ ├── .gitkeep │ │ │ ├── loading-for-test.hbs │ │ │ └── my-component.hbs │ │ │ ├── loading.hbs │ │ │ ├── package1.hbs │ │ │ └── package1 │ │ │ └── nested.hbs │ ├── config │ │ ├── bundles.js │ │ ├── environment.js │ │ └── package-names.js │ ├── packages │ │ ├── dependent │ │ │ ├── routes │ │ │ │ └── dependent.js │ │ │ ├── styles │ │ │ │ └── app.scss │ │ │ └── templates │ │ │ │ └── .gitkeep │ │ ├── link-source │ │ │ ├── styles │ │ │ │ └── app.scss │ │ │ └── templates │ │ │ │ └── link-source.hbs │ │ ├── link-target │ │ │ ├── controllers │ │ │ │ └── link-target.js │ │ │ ├── styles │ │ │ │ └── app.scss │ │ │ └── templates │ │ │ │ ├── .gitkeep │ │ │ │ └── link-target.hbs │ │ ├── load-assets-test │ │ │ ├── routes │ │ │ │ └── load-assets-test.js │ │ │ ├── styles │ │ │ │ └── app.scss │ │ │ └── templates │ │ │ │ └── .gitkeep │ │ ├── package1 │ │ │ ├── components │ │ │ │ └── .gitkeep │ │ │ ├── controllers │ │ │ │ └── .gitkeep │ │ │ ├── helpers │ │ │ │ └── .gitkeep │ │ │ ├── models │ │ │ │ └── .gitkeep │ │ │ ├── resolvers │ │ │ │ └── .gitkeep │ │ │ ├── routes │ │ │ │ ├── .gitkeep │ │ │ │ └── package1.js │ │ │ ├── styles │ │ │ │ └── app.scss │ │ │ └── templates │ │ │ │ ├── .gitkeep │ │ │ │ ├── package1.hbs │ │ │ │ └── package1 │ │ │ │ └── nested.hbs │ │ ├── package2 │ │ │ ├── components │ │ │ │ └── .gitkeep │ │ │ ├── controllers │ │ │ │ └── .gitkeep │ │ │ ├── helpers │ │ │ │ └── .gitkeep │ │ │ ├── models │ │ │ │ └── .gitkeep │ │ │ ├── resolvers │ │ │ │ └── .gitkeep │ │ │ ├── routes │ │ │ │ ├── .gitkeep │ │ │ │ └── package2.js │ │ │ ├── styles │ │ │ │ └── app.scss │ │ │ └── templates │ │ │ │ ├── .gitkeep │ │ │ │ └── package2.hbs │ │ ├── slow │ │ │ ├── routes │ │ │ │ └── slow.js │ │ │ ├── styles │ │ │ │ └── app.scss │ │ │ └── templates │ │ │ │ └── slow.hbs │ │ └── with-dependency │ │ │ ├── routes │ │ │ └── with-dependency.js │ │ │ ├── styles │ │ │ └── app.scss │ │ │ └── templates │ │ │ └── .gitkeep │ └── public │ │ ├── assets │ │ ├── load-assets-test2.css │ │ └── load-assets-test2.js │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── destroy-app.js │ ├── module-for-acceptance.js │ ├── resolver.js │ ├── skip-if-phantom.js │ └── start-app.js ├── index.html ├── integration │ ├── .gitkeep │ └── components │ │ └── my-component-test.js ├── test-helper.js └── unit │ ├── .gitkeep │ └── utils │ ├── load-assets-test.js │ └── resource-helper-test.js ├── vendor └── .gitkeep └── yarn.lock /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | approvals/**/*.received.* 19 | yarn-error.log 20 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /bower_components 2 | /config/ember-try.js 3 | /dist 4 | /tests 5 | /tmp 6 | **/.gitkeep 7 | .bowerrc 8 | .editorconfig 9 | .ember-cli 10 | .gitignore 11 | .jshintrc 12 | .watchmanconfig 13 | .travis.yml 14 | bower.json 15 | ember-cli-build.js 16 | testem.js 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "4.4.0" 5 | 6 | sudo: false 7 | 8 | cache: 9 | yarn: true 10 | directories: 11 | - node_modules 12 | 13 | env: 14 | - EMBER_TRY_SCENARIO=ember-1-13 15 | - EMBER_TRY_SCENARIO=ember-lts-2.4 16 | - EMBER_TRY_SCENARIO=ember-lts-2.8 17 | - EMBER_TRY_SCENARIO=ember-release 18 | - EMBER_TRY_SCENARIO=ember-beta 19 | - EMBER_TRY_SCENARIO=ember-canary 20 | 21 | matrix: 22 | fast_finish: true 23 | allow_failures: 24 | - env: EMBER_TRY_SCENARIO=ember-canary 25 | 26 | before_install: 27 | - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH 28 | - "npm config set spin false" 29 | - npm install yarn --global 30 | - yarn --version 31 | - npm install -g bower 32 | - "npm install -g npm@^2" 33 | - "npm install -g phantomjs-prebuilt" 34 | 35 | install: 36 | - yarn 37 | - bower install 38 | 39 | script: 40 | - npm test 41 | - ember try $EMBER_TRY_SCENARIO test 42 | 43 | branches: 44 | only: 45 | - master 46 | - beta 47 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Ember-cli-bundle-loader [](https://travis-ci.org/MiguelMadero/ember-cli-bundle-loader) [](https://www.npmjs.com/package/ember-cli-bundle-loader) [](http://emberobserver.com/addons/ember-cli-bundle-loader) 4 | 5 | [](https://codeclimate.com/github/MiguelMadero/ember-cli-bundle-loader) 6 | [](https://codeclimate.com/github/MiguelMadero/ember-cli-bundle-loader/coverage) 7 | [](https://www.versioneye.com/user/projects/57df010fbf3e4c0034e22b35) 8 | [](https://www.versioneye.com/user/projects/57df010b037c2000475cd3e9) 9 | [](https://badge.fury.io/js/ember-cli-bundle-loader) 10 | 11 | This project lets you build your app in different packages and load those packages lazily. The main goal of this is faster boot times for large applications, but it can also help with independentl build and deployments for different sub-products or sections of your app. 12 | 13 | You can see a demo live on http://miguelmadero.com/ember-cli-bundle-loader/ 14 | 15 | ## How it works 16 | 17 | We treat each package as an EmberApp, each of them will have its own JS and CSS output. For each package, it also changes certain things to avoid unnecessary processing of addons to get faster builds for packages. 18 | 19 | ## Installation 20 | 21 | ``` 22 | ember install MiguelMadero/ember-cli-bundle-loader # follow the prompts and diff 23 | ember generate package package-name 24 | ``` 25 | 26 | On a new app, simply override all the files when prompted during the `ember install` step. For existing apps, you can do a diff of each file. Most of the changes are simple and you can see an [example diff](https://github.com/MiguelMadero/ember-cli-bundle-loader-consumer/commit/d5791080ef915d84b7095e261701134267a73fd8). 27 | 28 | Now you can add routes to the main app (`app/router.js`) and to each package `(package-name/router.js`). Additionally, you will need to edit `config/bundles.js` with information about your package and the routes it handles. 29 | 30 | ## File Structure 31 | 32 | ### Current Ember Apps 33 | 34 | The typical output of an Ember App looks something like: 35 | 36 | - dist/ 37 | - index.html 38 | - assets/ 39 | app.js 40 | app.css 41 | vendor.js 42 | vendor.css 43 | 44 | ### Output of Packages with this addon 45 | 46 | - dist/ 47 | - index.html 48 | - assets/ 49 | boot.js 50 | boot.css 51 | package1.js 52 | package1.css 53 | package2.js 54 | package2.css 55 | vedor.js* 56 | vendor.csss 57 | 58 | \* We could also break vendor into smaller assets and define them as a dependency. More on this later. 59 | 60 | ### Source File Structure 61 | 62 | - app/ 63 | - index.html, *router.js*, resolver.js, app.js 64 | - routes/, controllers/, helpers/, models/, styles/, templates/, etc/ 65 | - pods/ 66 | - config 67 | - environment.js 68 | - bundles.js (new) 69 | - package-names.js (new) 70 | - packages (new) 71 | - package-name/ 72 | - index.html, *router.js*, resolver.js, app.js 73 | - routes/, controllers/, helpers/, models/, styles/, templates/, etc/ 74 | - pods/ 75 | - another-package/ 76 | - index.html, router.js, resolver.js, app.js 77 | - routes/, controllers/, helpers/, models/, styles/, templates/, etc/ 78 | - pods/ 79 | 80 | The app folder is identical to a normal EmberApp, it is, in fact just a normal EmberApp. This app is responsible for the booting everything. Only put here anything that is strictly required to get to the first page(s) or whatever is more important for a first time load. For example, your login page, the navbar, the default landing page. Each app will have different requirements, but the guidance is to keep it small. Initializers also have to be here since packages won't be available when we create the app. We could, in theory, lazy load initializers, but that doesn't make a lot of sense, so I have not tested it. 81 | 82 | There is a new top-level folder called `packages`, each package is like a normal EmberApp, with the exception that we don't need an index.html and we can't initializers. In the future this packages will become engines. 83 | 84 | Tests. Currently, all tests live under the traditional folders. It is suggested to create sub-folders like `tests/unit/package-name/` to provide some separation. In the future, tests for a package will move under each package folder. (Future: `packages/my-package/tests` and `packages/my-package/app`). In-repo-addons and engine have the same problem today see [issues#4461](https://github.com/ember-cli/ember-cli/issues/4461). 85 | 86 | ### JS 87 | 88 | All the JS modules are namespaced with the package name. For example, the route defined in `package1/routes/package1.js` is defined as `define('package1/routes/package1'` in the output code. This is because that's simply the name of the app when building. That gives us the advantage of being explicit about the way we consume and refer in code. However, this also requires a new resolver that can lookup for routes under the packages see `app/resolvers/packages.js` for more info. Please be aware of this namespace, it's especially important for `utils` or any other code that you import, for a hypothetical date-formatter utility under `package1/utils/date-formatter` will be imported as `import dateFormatter from 'package1/utils/date-formatter'`. Please be aware of this for tests. 89 | 90 | #### Vendor assets 91 | 92 | [ ] TODO: test 93 | We can define vendor assets as dependencies. Today, ember-cli supports specifying an `outputFile` when calling `app.import`. This works with static libraries and we'll add support to do it for addons. Once you have different vendor assets, you can simply define them as dependencies in `config/bundles.js`. For example: 94 | 95 | ``` 96 | // ember-cli-build.js 97 | var EmberAppWithPackages = require('./lib/broccoli/ember-app-with-packages'); 98 | module.exports = function (defaults) { 99 | var app = new EmberAppWithPackages(defaults); 100 | app.import('bower_components/moment/moment.js', {outputFile: 'vendor2.js'}); 101 | app.import('bower_components/lodash/lodash.js', {outputFile: 'vendor3.js'}); 102 | return app.toTree(); 103 | }; 104 | 105 | // /config/bundles.js 106 | module.exports = [{ 107 | name: 'package1', 108 | routeNames: ['^package1'] 109 | }, { 110 | name: 'package2', 111 | routeNames: ['^package2'] 112 | dependsOn: ['package1'] 113 | }]; 114 | ``` 115 | 116 | Based on the example above, when we go to `/package2`, we will load `vendor2.js`, `package1.js` and `vendor3.js`, only if they have not been loaded before. While the lbiraries are loaded in parallel, they will be executed in the order they were definied. 117 | 118 | #### Vendor assets from components 119 | 120 | Sometimes we can't depend on routes to load the packages, but instead, 121 | components are the ones that depend on a vendor lib. In this case 122 | they're responsible of initiating the load and providing a "loading" state. 123 | The following is an example using handsontable as a reference, assumine 124 | we have a handsontable wrapper: 125 | 126 | ``` 127 | // components/handson-table.js 128 | export default Ember.Component.extend({ 129 | lazyLoader: Ember.inject.service(), 130 | init () { 131 | this._super.apply(this, arguments); 132 | this.get('lazyLoader').loadBundle('handsontable').then(() => 133 | this.initHandsOnTableInstance()); 134 | }, 135 | didInsertElement () { 136 | this.get('isInDom', true); 137 | this.initHandsOnTableInstance(); 138 | }, 139 | initHandsOnTableInstance () { 140 | if (this.get('lazyLoader').isBundleLoaded('handsontable') && this.get('isInDom')) { 141 | // actual initialization of the wrapper 142 | // Often components need to wait for didInsert, in this case we need both, didInsert/ 143 | // and loading the assets. 144 | } 145 | } 146 | }) 147 | ``` 148 | 149 | On the HBS for the wrapper: 150 | 151 | ``` 152 | {{#if lazyLoader.loadedBundles.handsontable}} 153 | {{!-- markup for handsontable --}} 154 | {{else}} 155 |