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

AngularJS - 使用RequireJS还是Browserify?

AngularJS之所以吸引了很多开发者的关注,很大一部分原因是因为它为你如何架构你的应用提供了一种选择。通常来说,很多的选择并不是什么好事,因为觉得多数开发者并不希望你把一个“正确的”应用架构模式强加给他们。

JavaScript来说,存在的一种情况是有相当数量的一部分人都等待着有人 – 随便是谁都好 – 为他们提供一种关于由大型或者人员变化频繁的团队创建的企业级别应用的扩展和维护。总而言之,我们不仅仅需要一个基础概念,我们需要的是一个构建计划。

AngularJS应用的构建蓝图

angularjs提供的蓝图非常的基础而且简单 – javascript没有一个模块系统,因此AngularJS为你提供了一个。AngularJS确保了当你的应用在运行时,所有的代码已经加载完毕,并且可用。AngularJS主要通过依赖注入完成这项工作。

现在假设我们有一个非常简单的应用。其中只有一个子视图。它有一个对应的控制器。这个控制器反过来又为这个子视图注入一个服务来提供数据接入。当应用运行起来时,AngularJS能够确保所有的“字符串”代表的都是需要注入的实际模型。

代码如下所示:

  1. // 使用Angular Kendo UI来编写UI组件和进行数据层抽象
  2. (function () {
  3. var app = angular.module('app', ['ngRoute']);
  4. // routeProvider在这里被注入(需要require Angular.Route)
  5. app.config(['$routeProvider', function ($routeProvider) {
  6. $routeProvider.when('/home',
  7. {
  8. templateUrl: 'partials/home.html',
  9. controller: 'HomeController'
  10. })
  11. .otherwise(
  12. {
  13. redirectTo: '/home'
  14. })
  15. }]);
  16. app.controller('HomeController', ['$scope', 'productsDataSource', function($scope, $productsDataSource) {
  17. $scope.title = 'Home';
  18. $scope.productsDataSource = $productsDataSource;
  19. $scope.listViewTemplate = '<p>{{ ShipCity }}</p>';
  20. }]);
  21. app.factory('productsDataSource', function () {
  22. new kendo.data.DataSource({
  23. type: 'odata',
  24. transport: {
  25. read: 'http://demos.telerik.com/kendo-ui/service/Northwind.svc/Orders'
  26. },
  27. pageSize: 20,
  28. serverPaging: true
  29. });
  30. });
  31. }());

上面的代码做了不少事情:

  • 声明了一个应用模块;
  • 创建了一个工厂服务用来返回一个Kendo UI DataSource;
  • 针对子视图将DataSource注入HomeController;
  • 定义路由将子视图和控制器匹配起来

在AngularJS中,一个非常棒的设计就是它并不关心上面这些事情声明的顺序。

只要第一个app模块存在,你就可以以任何顺序创建任何后续的工厂服务,控制器,路由等等东西。AngularJS在后续会帮助你检查并载入依赖项,即便你在一个依赖模块声明之前就指明了依赖项。如果你写过一段时间的JavaScript,你一定能够明白它为你解决了一个多么大的问题。

应用代码架构 VS 物理项目架构

目前来看的话我们的代码整洁度还可以令人接受。然后,上面这个应用的代码太罗嗦了,而且它基本上来说什么也没做。你能够想象一个真实世界中的应用代码看起来是什么样子吗?

下一个逻辑步骤将会把所有的控制器、服务、以及所有能够分开的东西分隔到不同的文件中。这是一种物理项目架构,它可以让每个文件中的代码整洁一下。摆在我们面前的选择有两个 – 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应用分成不同的物理文件。

针对上面的例子,我们可以讲文件分隔为下面的结构:

  1. app
  2. partials
  3. home.html
  4. controllers
  5. homeController.js
  6. services
  7. productsDataSource.js
  8. app.js

Browserify允许在浏览器中使用CommonJS模块。这意味着每一个模块需要将它自己通过exports暴露给外界,以便其他模块能够require它。

homeController.js文件如下所示:

  1. // controllers/homeController.js
  2. module.exports = function() {
  3. return function ($scope, $productsDataSource) {
  4. $scope.title = 'Home';
  5. $scope.productsDataSource = $productsDataSource;
  6. $scope.listViewTemplate = '<p>#: ShipCity #</p>';
  7. };
  8. };

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文件就是所有魔法发生的地方:

  1. // app.js
  2. // require 所有的核心库
  3. require('../vendor/jquery/jquery.min');
  4. require('../vendor/angular/angular.min');
  5. require('../vendor/angular-route/angular-route.min');
  6. require('../vendor/kendo-ui-core/js/kendo.ui.core.min');
  7. require('../vendor/angular-kendo/angular-kendo');
  8. // 拉出我们所有我们需要的模块(控制器,服务,等等)
  9. var homeController = require('./controllers/homeController');
  10. var productsDataSource = require('./services/productsDataSource');
  11. // 创建模块
  12. var app = angular.module('app', [ 'ngRoute', 'kendo.directives' ]);
  13. // 配置路由
  14. app.config(['$routeProvider', function($routeProvider) {
  15. $routeProvider
  16. .when('/home',
  17. {
  18. templateUrl: 'partials/home.html',
  19. controller: 'HomeController'
  20. })
  21. .otherwise(
  22. {
  23. redirectTo: '/home'
  24. });
  25. }]);
  26. // 创建工厂服务
  27. app.factory('productsDataSource', productsDataSource);
  28. // 创建控制器
  29. app.controller('HomeController', ['$scope', 'productsDataSource', homeController]);

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

  1. $> 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文件都能通过正确的路径载入。

  1. require.config({
  2. paths: {
  3. 'jquery': 'vendor/jquery/jquery',
  4. 'angular': 'vendor/angular/angular',
  5. 'kendo': 'vendor/kendo/kendo',
  6. 'angular-kendo': 'vendor/angular-kendo',
  7. 'app': 'app'
  8. },
  9. shim: {
  10. // 确保kendo在angular-kendo之前载入
  11. 'angular-kendo': ['kendo'],
  12. // make sure that
  13. 'app': {
  14. deps: ['jquery', 'angular', 'kendo', 'angular-kendo']
  15. }
  16. }
  17. });
  18. define(['routes'], function () {
  19. // 使用bootstrap方法启动Angular应用
  20. angular.bootstrap(document, ['app']);
  21. });

注意到在这里我们需要来手动启动Angular应用。main.js这个文件完成的事情简单来说就是:载入所有文件,然后在document上运行Angular并将ng-app属性设置为’app’。这些文件因为是由RequireJS异步载入,因此我们需要来“手动启动”Angular应用。

在angular.bootstrap方法运行之前,所有的文件已经载入完毕了。这些依赖工作由RequireJS进行解析。注意到上面的代码中define函数对route.js文件发出了请求。RequireJS接着就会在执行angular.bootstrap方法之前载入该文件。

  1. // routes.js
  2. define([
  3. './app'
  4. ], function (app) {
  5. // app是Angular应用对象
  6. return app.config(['$routeProvider', function ($routeProvider) {
  7. $routeProvider
  8. .when('/home',
  9. {
  10. templateUrl: '/app/partials/home.html',
  11. controller: 'homeController'
  12. })
  13. .otherwise(
  14. {
  15. redirectTo: '/home'
  16. });
  17. }]);
  18. });

route.js文件声明app.js为依赖项。app.js文件创建了一个Angular应用对象,并且将它暴露给外部以便于路由可以获取它。

  1. // app.js
  2. define([
  3. './controllers/index',
  4. './services/index'
  5. ], function (controllers, index) {
  6. // 返回真正的Angular应用对象,在声明时指明了依赖的项目
  7. return angular.module('app', [
  8. 'ngRoute',
  9. 'kendo.directives',
  10. 'app.controllers',
  11. 'app.services'
  12. ]);
  13. });

app.js文件创建了模型并且注入了所有所需要的依赖项。其中包含ngRoute服务,Angular Kendo UI 指定以及其他两个模块,这两个模块都在文件的顶部定义。我们首先来看看”controllers/index.js”文件。

  1. // controllers/index.js
  2. define([
  3. './homeController'
  4. ], function () {
  5. });

除了载入依赖项以外,上面的这段代码没有做别的事。到目前为止,我们只有一个控制器,但是随着应用的逐渐变大,我们会有越来越多的控制器。所有的控制器都将在这个文件中被加载。每一个控制器的代码又包含在一个单独的文件中。

  1. // controllers/homeController.js
  2. define([
  3. './module'
  4. ], function (module) {
  5. module.controller('homeController', ['$scope', '$productsDataSource',
  6. function ($scope, $productsDataSource) {
  7. $scope.title = 'Home';
  8. $scope.productsDataSource = $productsDataSource;
  9. $scope.listViewTemplate = '<p>#: ShipCity #</p>';
  10. };
  11. );
  12. });

这段代码和之前HomeController的代码很相似,但是它在运行之前还需要一个module.js文件。它的作用在于创建app.controller模块以便于我们能在任何的控制器文件中使用它。

  1. // controllers/module.js
  2. define([
  3. ], function () {
  4. return angular.module('app.controllers', []);
  5. });

我们现在来回顾一下从一开始到现在究竟发生了些什么:

  1. main.js requires routes.js
  2. routes.js requires app.js
  3. app.js requires controllers/index.js
  4. controllers/index.js requires 所有的控制器
  5. 所有的控制器 require module.js
  6. 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/

AngularJS - 使用RequireJS还是Browserify?的更多相关文章

  1. Integrating AngularJS with RequireJS

    Integrating AngularJS with RequireJS When I first started developing with AngularJS keeping my contr ...

  2. AngularJS与RequireJS集成方案

    关于angularjs.requirejs的基础知识请自行学习 一.简单事例的项目目录如下: -index.html -scripts文件夹 --controller文件夹 --- mianContr ...

  3. 基于angularJS和requireJS的前端架构

    1.概要描述 1.1.angularJS描述:angularJS是可以用来构建WEB应用的,WEB应用中的一种端对端的完整解决方案.通过开发者呈现一个更高层次的抽象来简化应用的开发.最适合的就是用它来 ...

  4. AngularJS + ui-router + RequireJS异步加载注册controller/directive/filter/service

    一般情况下我们会将项目所用到的controller/directive/filter/sercive预先加载完再初始化AngularJS模块,但是当项目比较复杂的情况下,应该是打开对应的界面才加载对应 ...

  5. angularjs集成requirejs

    其实说成使用requirejs加载angularjs应用会更贴切一些 <body> <span ng-controller="homeController"> ...

  6. AngularJS结合RequireJS做文件合并压缩的那些坑

    我在项目使用了AngularJS框架,用RequireJS做异步模块加载(AMD),在做文件合并压缩时,遇到了一些坑,有些只是解决了,但不明白原因. 那些坑 1. build.js里面的paths必须 ...

  7. angularJS和requireJS和angularAMD

    最近因为要用到angularJS开发项目,因为涉及到的静态资源比较多,所以想把js文件通过requireJS来按需加载,这两个框架以前都使用过,但是结合到一起还没有用过,那就试一下,看能否达到目的. ...

  8. 从Java的角度理解前端框架,nodejs,reactjs,angularjs,requirejs,seajs

    [前端神秘的面纱] 对后端开发来说,前端是神秘的, 眼花缭乱的技术,繁多的框架, 如果你还停留在前端等于只用jquery做开发,那么你out了, 本文从Java的角度简述下目前前端流行的一些框架. 水 ...

  9. 新建一个angularjs+requirejs+bootstrap+typescript+gulp+vscode+git的项目

    环境 windows 10 准备工具 Visual Studio Code Node.js Git 需求 必须支持IE8 步骤开始: 执行命令行工具 mkdir Demo && cd ...

随机推荐

  1. Android 面向协议编程 体会优雅编程之旅

    Android中面向协议编程的深入浅出 http://blog.csdn.net/sk719887916/article/details skay编写 说起协议,现实生活中大家第一感觉会想到规则或者约 ...

  2. TSVN客户端复制文件

    TSVN客户端复制文件 代码重构中,可能需要将一个大文件拆分成2个小文件,同时要保证拆分后的小文件继承原来的SVN历史记录. TSVN客户端只有Rename功能,没有Copy功能. 可进入Browse ...

  3. N个数组中所有元素的排列组合(笛卡尔积)算法

    (1)N个数组对象中所有元素排列组合算法 private List<List<Object>> combineAlg(List<Object[]> nArray) ...

  4. ROS_Kinetic_12 ROS程序基础Eclipse_C++(三)usb camera

    ROS_Kinetic_12 ROS程序基础Eclipse_C++(三)usb camera 软件包下载地址:https://github.com/bosch-ros-pkg/usb_cam 下载后, ...

  5. Chipmunk僵尸物理对象的出现和解决(六)

    既然出现了这个问题下面就是如何找到原因. 因为该问题不是每次都出现,偶尔反弹棒碰到五角星时才会多出一个僵尸棒,现象比较随机,较难悉知具体原因. 有时多次触碰又没有出现问题,有时短时间内每次触碰都出现问 ...

  6. Chipmunk僵尸物理对象的出现和解决(一)

    最近在写的BrickHit游戏App中出现了一个比较头疼的问题. 该问题很难用常规手段调试,因为其发生看起来貌似是随机的. 我想在这里将这个问题的现象和解决过程详细的记录下来,一来避免其他童鞋走弯路, ...

  7. 【一天一道LeetCode】#68. Text Justification

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...

  8. 小强的HTML5移动开发之路(8)——坦克大战游戏2

    来自:http://blog.csdn.net/cai_xingyun/article/details/48629015 在上一篇文章中我们已经画出了自己的坦克,并且可以控制自己的坦克移动,我们继续接 ...

  9. of这个变态

    英式口语还能听懂,一到美式,连读,爆破,就让人疯掉. 尤其big bang theory, of就是个变态,其读法有,英[əv, əv, v, f] 美[əv, ɑv,əv].但大部分都是/əv/. ...

  10. Ubuntu 14 安装Skype 4.3

    Ubuntu 14 安装Skype 4.3Step 1: 删除老版本sudo apt-get remove skype skype-bin:i386 skype:i386 sudo apt-get i ...