AngularJS - 使用RequireJS仍是Browserify?

http://www.html-js.com/article/2126javascript

 

AngularJS - 使用RequireJS仍是Browserify?

 

AngularJS之因此吸引了不少開發者的關注,很大一部分緣由是由於它爲你如何架構你的應用提供了一種選擇。一般來講,不少的選擇並非什麼好事,由於以爲多數開發者並不但願你把一個「正確的」應用架構模式強加給他們。html

JavaScript來講,存在的一種狀況是有至關數量的一部分人都等待着有人 – 隨即是誰都好 – 爲他們提供一種關於由大型或者人員變化頻繁的團隊建立的企業級別應用的擴展和維護。總而言之,咱們不只僅須要一個基礎概念,咱們須要的是一個構建計劃。java

AngularJS應用的構建藍圖

angularjs提供的藍圖很是的基礎並且簡單 – javascript沒有一個模塊系統,所以AngularJS爲你提供了一個。AngularJS確保了當你的應用在運行時,全部的代碼已經加載完畢,而且可用。AngularJS主要經過依賴注入完成這項工做。jquery

如今假設咱們有一個很是簡單的應用。其中只有一個子視圖。它有一個對應的控制器。這個控制器反過來又爲這個子視圖注入一個服務來提供數據接入。當應用運行起來時,AngularJS可以確保全部的「字符串」表明的都是須要注入的實際模型。angularjs

代碼以下所示:bootstrap

// 使用Angular Kendo UI來編寫UI組件和進行數據層抽象 (function () { var app = angular.module('app', ['ngRoute']);  // routeProvider在這裏被注入(須要require Angular.Route) app.config(['$routeProvider', function ($routeProvider) { $routeProvider.when('/home', { templateUrl: 'partials/home.html', controller: 'HomeController' }) .otherwise( { redirectTo: '/home' }) }]); app.controller('HomeController', ['$scope', 'productsDataSource', function($scope, $productsDataSource) { $scope.title = 'Home'; $scope.productsDataSource = $productsDataSource; $scope.listViewTemplate = '<p>{{ ShipCity }}</p>'; }]); app.factory('productsDataSource', function () { new kendo.data.DataSource({ type: 'odata', transport: { read: 'http://demos.telerik.com/kendo-ui/service/Northwind.svc/Orders' }, pageSize: 20, serverPaging: true }); }); }()); 

上面的代碼作了很多事情:瀏覽器

  • 聲明瞭一個應用模塊;
  • 建立了一個工廠服務用來返回一個Kendo UI DataSource;
  • 針對子視圖將DataSource注入HomeController;
  • 定義路由將子視圖和控制器匹配起來

在AngularJS中,一個很是棒的設計就是它並不關心上面這些事情聲明的順序。服務器

只要第一個app模塊存在,你就能夠以任何順序建立任何後續的工廠服務,控制器,路由等等東西。AngularJS在後續會幫助你檢查並載入依賴項,即使你在一個依賴模塊聲明以前就指明瞭依賴項。若是你寫過一段時間的JavaScript,你必定可以明白它爲你解決了一個多麼大的問題。架構

應用代碼架構 VS 物理項目架構

目前來看的話咱們的代碼整潔度還能夠使人接受。而後,上面這個應用的代碼太羅嗦了,並且它基本上來講什麼也沒作。你可以想象一個真實世界中的應用代碼看起來是什麼樣子嗎?app

下一個邏輯步驟將會把全部的控制器、服務、以及全部可以分開的東西分隔到不一樣的文件中。這是一種物理項目架構,它可讓每一個文件中的代碼整潔一下。擺在咱們面前的選擇有兩個 – Browserify 和 RequireJS。

使用Browserify

「app」對象是Angular應用正常運行的關鍵。在通常的使用中,Angular會假設在應用「啓動」時文檔已經加載完畢。根據AngularJS的文檔,Angular會在DOMContentLoaded事件發生時完成「自動初始化」。

換句話說:在document.readState被設置爲complete時,angular.js腳本會被調用。在實際運行中,當DOM已經加載完成以後,Angular都會進行的步驟以下:

  • 加載有ng-app指定的模塊
  • 建立一個應用注入器 - 它可以根據字符串的值將對象注入到相應模塊中
  • 編譯ng-app屬性所在標籤下面的DOM樹

以上的步驟就是通常狀況下Angular會作的事情。只要全部的腳本在DOMContentLoaded事件以前加載完畢(能夠將它看做document.ready),一切都能正常運行。這使得Browserify能夠很輕易的將Angular應用分紅不一樣的物理文件。

針對上面的例子,咱們能夠講文件分隔爲下面的結構:

app
    partials
        home.html controllers homeController.js services productsDataSource.js app.js 

Browserify容許在瀏覽器中使用CommonJS模塊。這意味着每個模塊須要將它本身經過exports暴露給外界,以便其餘模塊可以require它。

homeController.js文件以下所示:

// controllers/homeController.js module.exports = function() { return function ($scope, $productsDataSource) { $scope.title = 'Home'; $scope.productsDataSource = $productsDataSource; $scope.listViewTemplate = '<p>#: ShipCity #</p>'; }; }; 

productsDataSource.js工廠服務的代碼也相似:

// services/productsDataSource.js

module.exports = function () { // the productsDataSource 服務被注入到控制器中 return new kendo.data.DataSource({ type: 'odata', transport: { read: 'http://demos.telerik.com/kendo-ui/service/Northwind.svc/Orders' }, pageSize: 20, serverPaging: true }); };

而app.js文件就是全部魔法發生的地方:

// app.js  // require 全部的核心庫 require('../vendor/jquery/jquery.min'); require('../vendor/angular/angular.min'); require('../vendor/angular-route/angular-route.min'); require('../vendor/kendo-ui-core/js/kendo.ui.core.min'); require('../vendor/angular-kendo/angular-kendo');  // 拉出咱們全部咱們須要的模塊(控制器,服務,等等) var homeController = require('./controllers/homeController'); var productsDataSource = require('./services/productsDataSource');  // 建立模塊 var app = angular.module('app', [ 'ngRoute', 'kendo.directives' ]);  // 配置路由 app.config(['$routeProvider', function($routeProvider) { $routeProvider .when('/home', { templateUrl: 'partials/home.html', controller: 'HomeController' }) .otherwise( { redirectTo: '/home' }); }]);  // 建立工廠服務 app.factory('productsDataSource', productsDataSource);  // 建立控制器 app.controller('HomeController', ['$scope', 'productsDataSource', homeController]); 

接着,使用下面的命令行便可:

$> watchify js/app/**/*.js -o build/main.js 

Watchify是一個小巧的功能插件,它可以監視文件夾並」browserify」你全部的代碼。

對於Browserify來講,你能夠在app.js文件中直接require全部的庫文件。除此以外,Browserify還能正確處理好你require這些文件的順序。多麼難以想象的事情!

可是另外一個事實是你依然須要手動的建立控制器、工廠服務以及那些不在app.js文件中的東西。我須要在某個模塊中定義這些東西而後將它們在app.js中進行合併。總的來講,我全部的Angular代碼事實上都位於app.js文件中,同時全部的文件都僅僅只是JavaScript。固然,這是JavaScript,因此沒有什麼可抱怨的。

可是總的來講,Angular和Browserify協同工做的很棒。

接下來,咱們要討論一件不那麼好的事情:在AngularJS中使用RequireJS。

使用RequireJS

RequireJS是一個好東西,可是若是和AngularJS一塊兒使用,就會出現問題。核心問題在於Angular須要在DOM徹底加載以後開始運行,它並不想要玩異步遊戲。因爲RequireJS中一切都是異步的(AMD即異步模塊定義),你很難將兩者很好的結合起來。

因爲腳本載入是異步的,全部的ng-app屬性如今徹底不可用了。你不可使用它來指明Angular應用。

另外一個討厭的事情是app模塊。爲了傳遞它你必須使用瘋狂的環形依賴。

下面咱們來使用RequireJS進行文件架構:

app partials home.html controllers index.js module.js homeController.js services index.js modules.js productsDataSource.js app.js main.js routes.js

咱們先從main.js這個文件開始,它在其中配置了全部的庫文件信息,而且對於不遵循AMD標準的模塊進行了shim設置。該文件的目的是確保全部的js文件都能經過正確的路徑載入。

require.config({ paths: { 'jquery': 'vendor/jquery/jquery', 'angular': 'vendor/angular/angular', 'kendo': 'vendor/kendo/kendo', 'angular-kendo': 'vendor/angular-kendo', 'app': 'app' }, shim: {  // 確保kendo在angular-kendo以前載入 'angular-kendo': ['kendo'],  // make sure that 'app': { deps: ['jquery', 'angular', 'kendo', 'angular-kendo'] } } }); define(['routes'], function () {  // 使用bootstrap方法啓動Angular應用 angular.bootstrap(document, ['app']); }); 

注意到在這裏咱們須要來手動啓動Angular應用。main.js這個文件完成的事情簡單來講就是:載入全部文件,而後在document上運行Angular並將ng-app屬性設置爲’app’。這些文件由於是由RequireJS異步載入,所以咱們須要來「手動啓動」Angular應用。

在angular.bootstrap方法運行以前,全部的文件已經載入完畢了。這些依賴工做由RequireJS進行解析。注意到上面的代碼中define函數對route.js文件發出了請求。RequireJS接着就會在執行angular.bootstrap方法以前載入該文件。

// routes.js define([ './app' ], function (app) {  // app是Angular應用對象 return app.config(['$routeProvider', function ($routeProvider) { $routeProvider .when('/home', { templateUrl: '/app/partials/home.html', controller: 'homeController' }) .otherwise( { redirectTo: '/home' }); }]); }); 

route.js文件聲明app.js爲依賴項。app.js文件建立了一個Angular應用對象,而且將它暴露給外部以便於路由能夠獲取它。

// app.js define([ './controllers/index', './services/index' ], function (controllers, index) {  // 返回真正的Angular應用對象,在聲明時指明瞭依賴的項目 return angular.module('app', [ 'ngRoute', 'kendo.directives', 'app.controllers', 'app.services' ]); }); 

app.js文件建立了模型而且注入了全部所須要的依賴項。其中包含ngRoute服務,Angular Kendo UI 指定以及其餘兩個模塊,這兩個模塊都在文件的頂部定義。咱們首先來看看」controllers/index.js」文件。

// controllers/index.js define([ './homeController' ], function () { }); 

除了載入依賴項之外,上面的這段代碼沒有作別的事。到目前爲止,咱們只有一個控制器,可是隨着應用的逐漸變大,咱們會有愈來愈多的控制器。全部的控制器都將在這個文件中被加載。每個控制器的代碼又包含在一個單獨的文件中。

// controllers/homeController.js define([ './module' ], function (module) { module.controller('homeController', ['$scope', '$productsDataSource', function ($scope, $productsDataSource) { $scope.title = 'Home'; $scope.productsDataSource = $productsDataSource; $scope.listViewTemplate = '<p>#: ShipCity #</p>'; }; ); }); 

這段代碼和以前HomeController的代碼很類似,可是它在運行以前還須要一個module.js文件。它的做用在於建立app.controller模塊以便於咱們能在任何的控制器文件中使用它。

// controllers/module.js define([ ], function () { return angular.module('app.controllers', []); }); 

咱們如今來回顧一下從一開始到如今究竟發生了些什麼:

「main.js」 requires 「routes.js」 「routes.js」 requires 「app.js」 「app.js」 requires 「controllers/index.js」 「controllers/index.js」 requires 全部的控制器 全部的控制器 require 「module.js」 「module.js」 建立了 「app.controllers」 模塊 

這有點像一顆過於龐大的依賴樹,可是它的可擴展性確實很好。若是你想添加一個新的控制器,你只須要添加」controllers/nameController.js」文件,並在」controllers/index.js」文件中添加相同的依賴項便可。

服務的運做方式和控制器相似。app.js會require services/index.js文件,它require了全部的服務。全部的服務同時會require services/module.js文件,它可以簡單的建立並提供app.services模塊。

如今回到app.js文件,全部的項目都在其中被加載並傳遞給咱們建立的Angular應用模塊。最後一件發生的事情是main.js文件中所發生的angular.bootstrap。簡單來講,咱們第一眼看到的代碼其實在最後纔會執行。

這實在是有點難以理解。

RequireJS會在應用運行以前加載全部的代碼。這意味着咱們並無實現代碼的延遲加載。

最終的選擇?

RequireJS在絕大多數的項目中確實很是好用,可是在Angular應用中,Browserify是一個更好的選擇。

固然,RequireJS和Browserify並非僅有的選擇,若是你有興趣的話,能夠去研究一下WebPack,它不經能使用AMD和CommonJS,同時也能在服務器端和瀏覽器端同時使用。它甚至可以處理一些預處理器例如LESS、CoffeeScript,Jade等等。

但願本文可以幫助你建立出更加性感並健壯的AngularJS應用。


本文參考自Requiring vs Browserifying Angular,原文地址http://developer.telerik.com/featured/requiring-vs-browerifying-angular/

相關文章
相關標籤/搜索