This article mainly introduces the materials related to the implementation of AngularJS delayed loading by Webpack. If you need it, you can refer to it. As your single-page application expands, the download time is getting longer and longer. This will not improve the user experience (Note: but the user experience is exactly why we develop a single-page application ). More code means a larger file, until the code compression can no longer meet your needs, the only thing you can do for your users is not to let them download the entire application at a time. At this time, the delayed loading will come in handy. Instead of downloading all files at a time, you can only download the files you need.
So. How can you implement delayed loading for your application? It is basically divided into two things. Split your modules into small blocks and implement some mechanisms to allow them to be loaded as needed. It sounds like there is a lot of work, isn't it? This is not the case if you use Webpack. It supports out-of-the-box code splitting. In this article, I assume that you are familiar with Webpack, but if you don't, here is an introduction. For a long time, we will also use AngularUI Router and ocLazyLoad.
The code can be found on GitHub. You can fork it at any time.
Webpack Configuration
Nothing special, really. In fact, you can copy and paste them directly from the document. The only difference is that ng-annotate is used to keep our code concise, and use babel to use some magic features of ECMAScript 2015. If you are interested in ES6, you can read this previous post. Although these things are great, they are not essential for implementing delayed loading.
// webpack.config.jsvar config = {entry: {app: ['./src/core/bootstrap.js'],},output: {path: __dirname + '/build/',filename: 'bundle.js',},resolve: {root: __dirname + '/src/',},module: {noParse: [],loaders: [{ test: /\.js$/, exclude: /node_modules/,loader: 'ng-annotate!babel' },{ test: /\.html$/, loader: 'raw' },]}};module.exports = config;
Application
The application module is the main file and must be included in bundle. js. This is mandatory for download on every page. As you can see, we will not load anything complicated except global dependencies. Different from loading controllers, we only load route configurations.
// app.js'use strict';export default require('angular').module('lazyApp', [require('angular-ui-router'),require('oclazyload'),require('./pages/home/home.routing').name,require('./pages/messages/messages.routing').name,]);
Route Configuration
All delayed loading is implemented in route configuration. As I said, we are using AngularUI Router because we need to implement nested views. We have several use cases. We can load the entire module (including the sub-state Controller) or one controller for each state (regardless of the dependency on the parent state ).
Load the entire module
When you enter the/home path, the browser downloads the home module. It includes two controllers for the home and home. about states. We can implement delayed loading through the resolve attribute in the state configuration object. Thanks to the require. ensure method of Webpack, we can create the home module as the first code block. It is called 1. bundle. js. If no $ ocLazyLoad. load, we will find an error Argument 'homecontroller' is not a function, got undefined, because in Angular design, the method of loading files after starting the application is not feasible. However, $ ocLazyLoad. load allows us to register a module during the startup phase and use it after it is loaded.
// home.routing.js'use strict';function homeRouting($urlRouterProvider, $stateProvider) {$urlRouterProvider.otherwise('/home');$stateProvider.state('home', {url: '/home',template: require('./views/home.html'),controller: 'HomeController as vm',resolve: {loadHomeController: ($q, $ocLazyLoad) => {return $q((resolve) => {require.ensure([], () => {// load whole modulelet module = require('./home');$ocLazyLoad.load({name: 'home'});resolve(module.controller);});});}}}).state('home.about', {url: '/about',template: require('./views/home.about.html'),controller: 'HomeAboutController as vm',});}export default angular.module('home.routing', []).config(homeRouting);
The Controller is regarded as a module dependency.
// home.js'use strict';export default angular.module('home', [require('./controllers/home.controller').name,require('./controllers/home.about.controller').name]);
Load controller only
What we do is take the first step forward, so we proceed to the next step. This time, there will be no large modules, only a simplified controller.
// messages.routing.js'use strict';function messagesRouting($stateProvider) {$stateProvider.state('messages', {url: '/messages',template: require('./views/messages.html'),controller: 'MessagesController as vm',resolve: {loadMessagesController: ($q, $ocLazyLoad) => {return $q((resolve) => {require.ensure([], () => {// load only controller modulelet module = require('./controllers/messages.controller');$ocLazyLoad.load({name: module.name});resolve(module.controller);})});}}}).state('messages.all', {url: '/all',template: require('./views/messages.all.html'),controller: 'MessagesAllController as vm',resolve: {loadMessagesAllController: ($q, $ocLazyLoad) => {return $q((resolve) => {require.ensure([], () => {// load only controller modulelet module = require('./controllers/messages.all.controller');$ocLazyLoad.load({name: module.name});resolve(module.controller);})});}}})
I believe there is nothing special here, and the rules can remain unchanged.
Load Views)
Now, let's temporarily release the controller and take a look at the view. As you may have noticed, we have embedded the view into the routing configuration. If we didn't put all the routing configurations in bundle. js, this would not be a problem, but we need to do so now. In this case, the routing configuration is not delayed, but the view. This is very simple when we use Webpack.
// messages.routing.js....state('messages.new', {url: '/new',templateProvider: ($q) => {return $q((resolve) => {// lazy load the viewrequire.ensure([], () => resolve(require('./views/messages.new.html')));});},controller: 'MessagesNewController as vm',resolve: {loadMessagesNewController: ($q, $ocLazyLoad) => {return $q((resolve) => {require.ensure([], () => {// load only controller modulelet module = require('./controllers/messages.new.controller');$ocLazyLoad.load({name: module.name});resolve(module.controller);})});}}});}export default angular.module('messages.routing', []).config(messagesRouting);
Be careful about repeated Dependencies
Let's take a look at the content of messages. all. controller and messages. new. controller.
// messages.all.controller.js'use strict';class MessagesAllController {constructor(msgStore) {this.msgs = msgStore.all();}}export default angular.module('messages.all.controller', [require('commons/msg-store').name,]).controller('MessagesAllController', MessagesAllController);// messages.all.controller.js'use strict';class MessagesNewController {constructor(msgStore) {this.text = '';this._msgStore = msgStore;}create() {this._msgStore.add(this.text);this.text = '';}}export default angular.module('messages.new.controller', [require('commons/msg-store').name,]).controller('MessagesNewController', MessagesNewController);
The root cause of our problem is require ('commons/msg-store'). name. It requires the msgStore service to share messages between controllers. This service exists in both packages. There is one in messages. all. controller and another in messages. new. controller. Now, there is no room for optimization. How can this problem be solved? You only need to add msgStore as the dependency of the application module. Although this is not perfect, it is enough in most cases.
// app.js'use strict';export default require('angular').module('lazyApp', [require('angular-ui-router'),require('oclazyload'),// msgStore as global dependencyrequire('commons/msg-store').name,require('./pages/home/home.routing').name,require('./pages/messages/messages.routing').name,]);
Unit Test skills
Changing msgStore to a global dependency does not mean you should delete it from the Controller. If you did this, and you did not simulate this dependency during the writing test, it would not work properly. In unit testing, you only load this controller, not the entire application module.
// messages.all.controller.spec.js'use strict';describe('MessagesAllController', () => {var controller,msgStoreMock;beforeEach(angular.mock.module(require('./messages.all.controller').name));beforeEach(inject(($controller) => {msgStoreMock = require('commons/msg-store/msg-store.service.mock');spyOn(msgStoreMock, 'all').and.returnValue(['foo', ]);controller = $controller('MessagesAllController', { msgStore: msgStoreMock });}));it('saves msgStore.all() in msgs', () => {expect(msgStoreMock.all).toHaveBeenCalled();expect(controller.msgs).toEqual(['foo', ]);});});
The above content is a small part of the Webpack to share with you the implementation of AngularJS delayed loading, I hope to help you!