Webpack 實(shí)現(xiàn) AngularJS 的延遲加載

字號(hào):


    這篇文章主要介紹了Webpack 實(shí)現(xiàn) AngularJS 的延遲加載的相關(guān)資料,需要的朋友可以參考下
    隨著你的單頁(yè)應(yīng)用擴(kuò)大,其下載時(shí)間也越來(lái)越長(zhǎng)。這對(duì)提高用戶(hù)體驗(yàn)不會(huì)有好處(提示:但用戶(hù)體驗(yàn)正是我們開(kāi)發(fā)單頁(yè)應(yīng)用的原因)。更多的代碼意味著更大的文件,直到代碼壓縮已經(jīng)不能滿足你的需求,你唯一能為你的用戶(hù)做的就是不要再讓他一次性下載整個(gè)應(yīng)用。這時(shí),延遲加載就派上用場(chǎng)了。不同于一次性下載所有文件,而是讓用戶(hù)只下載他現(xiàn)在需要的文件。
    所以。如何讓你的應(yīng)用程序?qū)崿F(xiàn)延遲加載?它基本上是分成兩件事情。把你的模塊拆分成小塊,并實(shí)施一些機(jī)制,允許按需加載這些塊。聽(tīng)起來(lái)似乎有很多工作量,不是嗎?如果你使用 Webpack 的話,就不會(huì)這樣。它支持開(kāi)箱即用的代碼分割特性。在這篇文章中我假定你熟悉 Webpack,但如果你不會(huì)的話,這里有一篇介紹 。為了長(zhǎng)話短說(shuō),我們也將使用 AngularUI Router 和 ocLazyLoad 。
    代碼可以在 GitHub 上。你可以隨時(shí) fork 它。
    Webpack 的配置
    沒(méi)什么特別的,真的。實(shí)際上從你可以直接從文檔中復(fù)制然后粘貼,唯一的區(qū)別是采用了 ng-annotate ,以讓我們的代碼保持簡(jiǎn)潔,以及采用 babel 來(lái)使用一些 ECMAScript 2015 的魔法特性。如果你對(duì) ES6 感興趣,可以看看 這篇以前的帖子 。雖然這些東西都是非常棒的,但是它們都不是實(shí)現(xiàn)延遲加載所必需的東西。
    // 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;
    應(yīng)用
    應(yīng)用模塊是主文件,它必須被包括在 bundle.js 內(nèi),這是在每一個(gè)頁(yè)面上都需要強(qiáng)制下載的。正如你所看到的,我們不會(huì)加載任何復(fù)雜的東西,除了全局的依賴(lài)。不同于加載控制器,我們只加載路由配置。
    // 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,
    ]);
    路由配置
    所有的延遲加載都在路由配置中實(shí)現(xiàn)。正如我所說(shuō),我們正在使用 AngularUI Router ,因?yàn)槲覀冃枰獙?shí)現(xiàn)嵌套視圖。我們有幾個(gè)使用案例。我們可以加載整個(gè)模塊(包括子狀態(tài)控制器)或每個(gè) state 加載一個(gè)控制器(不去考慮對(duì)父級(jí) state 的依賴(lài))。
    加載整個(gè)模塊
    當(dāng)用戶(hù)輸入 /home 路徑,瀏覽器就會(huì)下載 home 模塊。它包括兩個(gè)控制器,針對(duì) home 和 home.about 這兩個(gè)state。我們通過(guò) state 的配置對(duì)象中的 resolve 屬性就可以實(shí)現(xiàn)延遲加載。得益于 Webpack 的 require.ensure 方法,我們可以把 home 模塊創(chuàng)建成第一個(gè)代碼塊。它就叫做 1.bundle.js 。如果沒(méi)有 $ocLazyLoad.load ,我們會(huì)發(fā)現(xiàn)得到一個(gè)錯(cuò)誤 Argument 'HomeController' is not a function, got undefined ,因?yàn)樵?Angular 的設(shè)計(jì)中,啟動(dòng)應(yīng)用之后再加載文件的方式是不可行的。 但是 $ocLazyLoad.load 使得我們可以在啟動(dòng)階段注冊(cè)一個(gè)模塊,然后在它加載完之后再去使用它。
    // 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);
    控制器被當(dāng)作是模塊的依賴(lài)。
    // home.js
    'use strict';
    export default angular
    .module('home', [
    require('./controllers/home.controller').name,
    require('./controllers/home.about.controller').name
    ]);
    僅加載控制器
    我們所做的是向前邁出的第一步,那么我們接著進(jìn)行下一步。這一次,將沒(méi)有大的模塊,只有精簡(jiǎn)的控制器。
    // 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);
    })
    });
    }
    }
    })
    我相信在這里沒(méi)有什么特別的,規(guī)則可以保持不變。
    加載視圖(Views)
    現(xiàn)在,讓我們暫時(shí)放開(kāi)控制器而去關(guān)注一下視圖。正如你可能已經(jīng)注意到的,我們把視圖嵌入到了路由配置里面。如果我們沒(méi)有把里面所有的路由配置放進(jìn) bundle.js ,這就不會(huì)是一個(gè)問(wèn)題,但現(xiàn)在我們需要這么做。這個(gè)案例不是要延遲加載路由配置而是視圖,那么當(dāng)我們使用 Webpack 來(lái)實(shí)現(xiàn)的時(shí)候,這會(huì)非常簡(jiǎn)單。
    // 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);
    當(dāng)心重復(fù)的依賴(lài)
    讓我們來(lái)看看 messages.all.controller 和 messages.new.controller 的內(nèi)容。
    // 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);
    我們的問(wèn)題的根源是 require('commons/msg-store').name 。它需要 msgStore 這一個(gè)服務(wù),來(lái)實(shí)現(xiàn)控制器之間的消息共享。此服務(wù)在兩個(gè)包中都存在。在 messages.all.controller 中有一個(gè),在 messages.new.controller 中又有一個(gè)?,F(xiàn)在,它已經(jīng)沒(méi)有任何優(yōu)化的空間。如何解決呢?只需要把 msgStore 添加為應(yīng)用模塊的依賴(lài)。雖然這還不夠完美,在大多數(shù)情況下,這已經(jīng)足夠了。
    // 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,
    ]);
    單元測(cè)試的技巧
    把 msgStore 改成是全局依賴(lài)并不意味著你應(yīng)該從控制器中刪除它。如果你這樣做了,在你編寫(xiě)測(cè)試的時(shí)候,如果沒(méi)有模擬這一個(gè)依賴(lài),那么它就無(wú)法正常工作了。因?yàn)樵趩卧獪y(cè)試中,你只會(huì)加載這一個(gè)控制器而非整個(gè)應(yīng)用模塊。
    // 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', ]);
    });
    });
    以上內(nèi)容是小編給大家分享的Webpack 實(shí)現(xiàn) AngularJS 的延遲加載,希望對(duì)大家有所幫助!