À mesure que votre application monopage grandit, son temps de téléchargement augmente également. Cela n’apportera pas grand-chose à l’expérience utilisateur (indice : mais l’expérience utilisateur est la raison pour laquelle nous développons des applications à page unique). Plus de code signifie des fichiers plus gros, et jusqu'à ce que la compression du code ne réponde plus à vos besoins, la seule chose que vous pouvez faire pour vos utilisateurs est d'arrêter de leur demander de télécharger l'intégralité de l'application en une seule fois. C’est là que le chargement paresseux s’avère utile. Au lieu de télécharger tous les fichiers en même temps, l'utilisateur est autorisé à télécharger uniquement les fichiers dont il a besoin actuellement.
Alors. Comment faire en sorte que votre application soit paresseuse à chargement ? Il est essentiellement divisé en deux choses. Divisez votre module en petits morceaux et implémentez un mécanisme permettant à ces morceaux d'être chargés à la demande. Cela ressemble à beaucoup de travail, n'est-ce pas ? Pas si vous utilisez Webpack. Il prend en charge les fonctionnalités de fractionnement de code prêtes à l'emploi. Dans cet article, je suppose que vous connaissez Webpack, mais si ce n'est pas le cas, voici une introduction. Pour faire court, nous utiliserons également AngularUI Router et ocLazyLoad.
Le code est disponible sur GitHub. Vous pouvez le débourser à tout moment.
Configuration du Webpack
Rien de spécial, vraiment. Vous pouvez en fait simplement copier et coller à partir de la documentation, la seule différence est l'utilisation de ng-annotate pour garder notre code propre, et de babel pour utiliser une partie de la magie ECMAScript 2015. Si vous êtes intéressé par ES6, vous pouvez consulter ce post précédent . Bien que ces éléments soient tous excellents, aucun d’entre eux n’est nécessaire pour implémenter le chargement différé.
// webpack.config.js var 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;
Postuler
Le module d'application est le fichier principal, qui doit être inclus dans bundle.js, qui doit être téléchargé sur chaque page. Comme vous pouvez le voir, nous ne chargeons rien de compliqué à part des dépendances globales. Contrairement au chargement du contrôleur, nous chargeons uniquement la configuration du routage.
// 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, ]);
Configuration du routage
Tous les chargements différés sont implémentés dans la configuration de l'itinéraire. Comme je l'ai dit, nous utilisons AngularUI Router car nous devons implémenter des vues imbriquées. Nous avons plusieurs cas d'utilisation. Nous pouvons charger le module entier (y compris les contrôleurs d'état enfants) ou charger un contrôleur par état (indépendamment des dépendances sur les états parents).
Charger l'intégralité du module
Lorsque l'utilisateur entre le chemin /home, le navigateur téléchargera le module home. Il comprend deux contrôleurs, pour les deux états home et home.about. Nous pouvons implémenter un chargement paresseux via l'attribut solve dans l'objet de configuration d'état. Grâce à la méthode require.ensure de Webpack, nous pouvons créer le module home comme premier bloc de code. Cela s'appelle 1.bundle.js . Sans $ocLazyLoad.load , nous constaterons que nous obtenons une erreur Argument 'HomeController' is not a function, got undefined , car dans la conception d'Angular, il n'est pas possible de charger des fichiers après le démarrage de l'application. Mais $ocLazyLoad.load nous permet d'enregistrer un module au démarrage puis de l'utiliser après son chargement.
// 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 module let 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);
Les contrôleurs sont traités comme des dépendances de module.
// home.js 'use strict'; export default angular .module('home', [ require('./controllers/home.controller').name, require('./controllers/home.about.controller').name ]);
Contrôleur de charge uniquement
Ce que nous faisons est le premier pas en avant, puis passons à l’étape suivante. Cette fois, il n’y aura pas de gros modules, juste des contrôleurs rationalisés.
// 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 module let 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 module let module = require('./controllers/messages.all.controller'); $ocLazyLoad.load({name: module.name}); resolve(module.controller); }) }); } } })
Je pense qu'il n'y a rien de spécial ici et que les règles peuvent rester les mêmes.
Chargement des vues
Maintenant, laissons de côté le contrôleur un instant et concentrons-nous sur la vue. Comme vous l'avez peut-être remarqué, nous intégrons la vue dans la configuration du routage. Cela ne serait pas un problème si nous n'avions pas mis toute la configuration de routage dans bundle.js , mais nous devons maintenant le faire. Ce cas ne concerne pas la configuration de la route de chargement paresseux mais les vues, donc lorsque nous utiliserons Webpack pour l'implémenter, ce sera très simple.
// messages.routing.js ... .state('messages.new', { url: '/new', templateProvider: ($q) => { return $q((resolve) => { // lazy load the view require.ensure([], () => resolve(require('./views/messages.new.html'))); }); }, controller: 'MessagesNewController as vm', resolve: { loadMessagesNewController: ($q, $ocLazyLoad) => { return $q((resolve) => { require.ensure([], () => { // load only controller module let module = require('./controllers/messages.new.controller'); $ocLazyLoad.load({name: module.name}); resolve(module.controller); }) }); } } }); } export default angular .module('messages.routing', []) .config(messagesRouting);
Méfiez-vous des dépendances en double
Jetons un coup d'œil au contenu de messages.all.controller et 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);
La source de notre problème est require('commons/msg-store').name . Le service msgStore est nécessaire pour réaliser le partage de messages entre les contrôleurs. Ce service est présent dans les deux forfaits. Il y en a un dans messages.all.controller et un autre dans messages.new.controller. Désormais, il n’y a plus de place à l’optimisation. Comment le résoudre ? Ajoutez simplement msgStore en tant que dépendance du module d'application. Même si ce n’est pas parfait, dans la plupart des cas, cela suffit.
// app.js 'use strict'; export default require('angular') .module('lazyApp', [ require('angular-ui-router'), require('oclazyload'), // msgStore as global dependency require('commons/msg-store').name, require('./pages/home/home.routing').name, require('./pages/messages/messages.routing').name, ]);
单元测试的技巧
把 msgStore 改成是全局依赖并不意味着你应该从控制器中删除它。如果你这样做了,在你编写测试的时候,如果没有模拟这一个依赖,那么它就无法正常工作了。因为在单元测试中,你只会加载这一个控制器而非整个应用模块。
// 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', ]); }); });
以上内容是小编给大家分享的Webpack 实现 AngularJS 的延迟加载,希望对大家有所帮助!