Skip to content

构建argular.js应用的最佳实践

jnotnull edited this page Jul 20, 2014 · 24 revisions

Burke Holland had a fantastic post explaining how Angular loads an application and comparing the merits of browserify vs require.js in an Angular app. Burke Holland之前发了一篇文章解释了Angular如何构建应用,并且比较了browserify和require.js在Angular中的优点。

I’ve worked with Angular on quite a few apps at this point, and have seen many different ways to structure them. I’m writing a book on architecting Angular apps right now with the MEAN stack and as such have researched heavily into this specific topic. I think I’ve set on a pretty specific structure I’m very happy with. It’s a simpler approach than what Burke Holland has proposed. 关于这点,其实我已经在好多项目中也涉及到了,我也找到了好多方法构建他们。我正在写一本如何通过MEAN来构建Angular应用的书,因此对这个课题研究的也不叫多。我想我已经找到了一个非常具体的框架。它比Burke Holland提议的简单的多。

I must note that if I was on a project with his structure, I would be content. It’s good. 特别说明的是,如果我在一个使用他的框架的项目中,我也会很满足的。它还是很不错的选择的。

Before we start though, the concept of modules in the world of Angular can be a bit confusing, so let me lay out the current state of affairs. 然而在我们开始之前,Angular中的模块概念还是有点扑朔迷离,让我们花点时间来澄清一下。

What modules are in JavaScript JS中的模块是什么 JavaScript comes with no ability to load modules. A “module” means different things to different people. For this article, let’s use this definition: JS本身没有加载模块的能力。模块的含义就是不同的人做不同的事情。在本文中,我们使用如下定义:

Modules allow code to be compartmentalized to provide logical separation for the developers. In JavaScript, it also prevents the problem of conflicting globals. 模块允许我们按照不同的逻辑把代码切割成不同的部分。在JS中,它还可以阻止global中的变量冲突问题。

People new to JavaScript get a little confused about why we make such a big deal about modules. I want to make one thing clear: Modules are NOT for lazy-loading JavaScript components when needed. Require.js does have this functionality, but that is not the reason it is important. Modules are important to due the language not having support for it, and JavaScript desperately needing it. JS开发新手可能会认为我们对模块有点小题大做。我想澄清一件问题:模块不是为了懒加载时候需要加载的模块。Require.js确实有这个功能,但是这个不是它重要的原因。模块之所以重要是因为语言没有提供这个功能,但是JS确实很需要这个功能。

A module can be different things. It could be Angular, lodash (you’re not still using underscore, are you?), shared code in your organization, some gist you found online, or separating features out inside your codebase. 模块可能由不同的框架组成。它可以是Angular,也可能是lodash,抑或是你项目中别人分享的代码,一些你冲网上找到的礼物,或者中你代码中分离出来的特性。

JavaScript doesn’t support modules, so we’ve traditionally had a few various approaches. (Feel free to skip this next section if you understand JavaScript modules) JS没有模块,因此我们必须通过一些途径去解决没有模块带来的问题(如果你理解了JS模块那你可以忽略掉接下来的章节)

.noConflict() .noConflict() Let me illustrate the problem. Let’s say you want to include jQuery in your project. jQuery will define the global variable ‘$’. If, in your code, you have an existing variable ‘$’ those variables will conflict. For years, we got around this problem with a .noConflict() function. Basically .noConflict() allows you to change the variable name of the library you’re using. 让我们来描述下问题。加入你想在你的项目中使用jQuery。jQuery会定义一个全局变量$。而恰好在你的代码中已经存在了这个变量,那么就会导致冲突。多年来,我们都考.noConfict()方法来解决这个问题。确实.noConfict()允许我们去改变你引用库的名称。

If you had this problem, you would use it like this: 如果你有这个问题,你可以这样使用:

<script> var $ = 'myobject that jquery will conflict with' </script> <script src='//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js'></script> <script> // $ is jQuery since we added that script tag var jq = jQuery.noConflict(); // $ is back to 'myobject that jquery will conflict with' // jq is now jQuery </script>

This has been a common practice in most JavaScript libraries, but it’s not a fantastic solution. It doesn’t provide very good compartmentalizing of code, it forces you to declare things before you use them, and it requires the imported code (either a library or your own code) to actually implement a .noConflict() function. 这已经成为大多数JS中最常见的解决方案,但却不是最优的解决方案。它没有很好的划分你的代码,它强制你在使用之前定义了一些东西,它要求导入的代码都要去实现一个.noConfict()方法。

If that’s confusing, read up on it. It’s important to understand the problem before you continue onto the solutions below. 如果你感觉有点疑惑,那接着向下读。在看下面的解决方案之前,去了解问题是非常重要的。

Nobody was happy with .noConflict(), so they started looking into other ways to solve the problem. We have 4 solutions worth mentioning in this context: 没人会喜欢使用.noConfict(),因此他们寻找了一些其他方法去解决问题。我们在这里提供了四种方案:

Require.js (Implementation of AMD) Browserify (Implementation of CommonJS) Angular dependency injection ES6 modules Require.js (AMD标准实现) Browserify (CommonJS标准实现) Angular dependency injection(Angular的依赖注入) ES6 modules(ES6的模块)

Each one has its pros and cons, and each works quite a bit differently. You can even use 1 or 2 in tandem (Burke used 2). I’ll cover what each does, how they work with Angular, and which one I suggest. 每个方案都有它的优点和缺点,他们每个都有所不同。你甚至一起使用他们。我将讲述他们每一个都提供了什么,如何配合Angular,我建议选择哪一个。

Sample App 样例 Let’s get a little Angular app together so we can talk about it. 让我们开始一个简单的Angular应用。

Here is a simple app that lists users off Github. 这里是获得Github用户李彪的简单实现。

The code is here, but it’s the completed version we will build in this post. Read through for no spoilers! 代码在这里,但是它是一个完整版本了。认真读完别剧透! All the JavaScript could be in this one file: 所有的代码可以在一个文件中: var app = angular.module('app', [])

app.factory('GithubSvc', function ($http) { return { fetchStories: function () { return $http.get('https://api.github.com/users') } } })

app.controller('GithubCtrl', function ($scope, GithubSvc) { GithubSvc.fetchStories().success(function (users) { $scope.users = users }) })

First we declare an ‘app’ object that is our module. We then define a service ‘GithubSvc’ with one function that can serve us users from Github. 首先我们定义了一个app对象,也就是我们的模块。然后我们定义了一个能提供github上用户离别的服务GithubSvc。

After that, we define a controller that uses the service to load that array into $scope. (This is the HTML page that renders it) 后面我们定义了一个控制器用来通过调用service去获得用户数组并加载到$scope中。

Splitting into separate files 把代码切割刀不同文件中 The trouble is that this code is all in one file. Totally unreasonable for a real app. Maybe I’m a curmudgeon, but when I first started looking at Angular and the code samples all showed how to do this, all I wanted to see was a real world solution with proper separation. 这里的代码都在一个文件中,这太糟糕了。这对于一个app来说绝对不可以的。也许我是一个倔老头,但是当我第一次看Angular和它的样例都解释是这样做的,我想说的是合理的去分割代码真的很有必要。

I would like to have this code in a structure like this: 我想我的代码有下面这样的结构: src/module.js src/github/github.svc.js src/github/github.ctrl.js

Note: If this app got large, it might make sense to have a separate ‘github’ module as well. 注:如果应用变得很大,我们还需要分解gitbub模块 The alternate way to do this would be to split things out by functionality rather than part of the codebase: 一个可行的办法就是按照功能去分解。 src/module.js src/services/github.svc.js src/controllers/github.ctrl.js

I don’t have a strong preference either way. Probably very large apps would benefit from the former, and smaller ones the latter. 无论哪种我都能能接受。一个大的应用可能倾向于前者,而小的引用则倾向于后者。

Regardless, without using a module loader like browserify or require.js, we would have to add a script tag for every one of these files. That’s a no go. That could easily grow to hundreds of files. 但是,如果我们不使用browserify或者require.js加载器,那我们必须通过添加script标签去加载每个文件,这可能很快增长到上百个文件。

There are performance reasons why you don’t want to have tons of script tags too. The browser does pipeline them, but it can only do so many at a time. They have overhead, and the latency would be killer to our friends outside of California. 因为有性能原因,我们不能有太多的script标签,浏览器是链式加载的,但是在一个时间点只能加载一个。糟糕的速度可能会让我们的用户流失。

So here is the goal: 因此这是目标:

We need a way to have many Angular files in dev, but they need to be loaded into the browser in bulk (not a script tag for each one). 我们需要在开发的时候拥有多个文件,但是在浏览器运行时候可以按需加载(不是通过script标签去一个一个加载)

This is why people look to module loaders like require.js or browserify. Angular allows you to logically separate out code, but not files. I’m going to show an easier way, but first let’s examine the available module loaders. 这就是为啥我们研究诸如require.js和browserify。Angular允许你按照逻辑去分割代码,而不是按照文件。我将给个更加简单的方法,但是首先让我们比较下当前可用的模块加载器。

Require.js — Too complicated Require.js-太复杂 Require.js was the first major push towards coming up with a consistent way to have modules inside of JavaScript. Require.js allows you to define dependencies inside a JavaScript file that you depend on. It runs inside the browser and is capable of loading modules as needed. Require.js在推动JS模块化上功不可没。它允许你把需要的JS文件定义为依赖,同时可以在浏览器端按需加载。

It accomplishes 2 general tasks, loading of modules and handling the load order. 它完成两项任务:加载模块和控制加载的顺序。

Unfortunately it’s really complicated to setup, requires your code to be written in a specific way, certainly has the steepest learning curve, and can’t deal with circular dependencies well — and that can happen when trying to use a module system on top of Angular. 不幸的是,这要求你的代码必须用一种特殊的方式去写,而这是有陡峭的学习曲线的,我们可能对循环引用控制的不好。而这个就可能发生在Angular的顶层模块系统中。

Burke Holland covered the issues with using require.js with Angular very well, so I encourage you to read that for a clearer reason why you should not use Angular with require.js. Burke Holland列出了在Angular中使用require.js的场景,因此我建议你再认真读一遍,最好能找出一个你不用require.js的理由。

Working with RequireJS and AngularJS was a vacation on Shutter Island. On the surface everything looks very normal. Under that surface is Ben Kingsley and a series of horrific flashbacks. — Burke Holland RequireJS和AngularJS一起使用就像是在禁闭岛上度假。水面上看起来一切非常平静,但是水下确实暗流涌动-Burke Holland

The ability for require.js to load modules on demand is also something that won’t work with Angular (at least, in a reasonable situation). That seems to be something people want, but I’ve certainly never worked on a project that needed it. require.js的按需加载模块的能力是另一个我不想它和Angular一起使用的原因。这个可能是一些人想要的,但是我绝对不会再任何项目中这样做。

I want to emphasize that last point as people get this wrong: Module systems are not so that you only load the code you need. Yes require.js does do that, but it’s not why require.js is useful. Modules are useful to logically separate code for developers to reason about it easier. 我要强调一点人们经常犯的错误:模块系统不仅仅是按照需要加载模块。是的,require.js确实这么做的,但是这不是require.js有用的原因。模块是用于按照逻辑来分解代码。

In any case, it’s a bad solution and I won’t show you how to do it. I bring it up because people often ask me how to integrate require.js with Angular. 不管怎么说,这是一个糟糕的方案,因此我不会告诉你早呢么做。我为什么要说呢,因为总有人问我如何集成require.js和Angular。

Browserify — A much better module loader Browserify-一个更好的模块加载器 Where require.js has the browser load the modules, browserify runs on the server before it runs in the browser. You can’t take a browserify file and run it in a browser, you have to ‘bundle’ it first. require.js用于在浏览器端加载模块,而browserify则运行于服务端。你不能把browserify文件运行于浏览器端。

It uses a similar format (and is almost 100% compatible with) the Node.js module loading. It looks like this: 它使用了类似于Node.js的模块加载方式,我们看下代码: var moduleA = require('my-module') var moduleB = require('your-module')

moduleA.doSomething(moduleB)

It’s a really pretty, easy to read format. You simply declare a variable and ‘require()’ your module into it. Writing code that exports a module is very easy too. 这看起来非常完美,格式很容易月度。你就简单的定义一个变量,然后"require()"你的模块。导出模块也很容易写。

In Node, it’s great. The reason it can’t work in the browser, however, is that it’s synchronous. The browser would have to wait when hitting one of those require sections, then make an http call to load the code in. Synchronous http in a browser is an absolute no-no. 在Node中,这确实非常棒。但是它不能运行于浏览器端,因为它是同步的。如果需要某个资源,http会去下载,而这整过过程浏览器都必须在等待。

It works in Node since the files are on the local filesystem, so the time it takes to do one of those ‘requires()’ is very fast. 在Node中非常好用是因为这些文件都在本地文件系统中,因此加载一个模块非常的快。

So with browserify, you can take code like this and run it with browserify and it will combine all the files together in a bundle that the browser can use. Once again, Burke’s article covers using browserify with Angular very well. 对于browserify,你可以这样写,它会合并所有的文件打包成一个文件供浏览器使用。同样,Burke的文章也说明了这一点。

By the way, if everything I just said about browserify is confusing, don’t worry about it. It’s certainly more confusing than the solution I’m about to propose. 另外,如果我刚才关于browserify的一些东东有些懵懂的话,不要担心,后面我说的建议肯定会比这个简单。

It is a great tool I would jump to use on a non-Angular project. With Angular, however, we can do something simpler. 如果在非Angular项目中使用这个,确实非常棒。但是对于Angular,我们可以更加简单。

Angular Dependency Injection — Solves most of our problems Angular依赖注入-解决我们的大部分问题 Go back and look at our sample app’s app.js. I want to point out a couple of things: 回到之前我们的例子,我想说下两点:

It doesn’t matter what order we create the service or the controller. Angular handles that for us with its built-in Dependency Injection. It also allows us to do things like mocking out the service in a unit test. It’s great, and my number one favorite feature inside Angular. 是创建service在先还是创建controller在先都无所谓。Angular会自己管理它们的依赖关系。这就为我们通过mocking service提供了方便。确实很棒,这是我推崇的第一个特性。

Having said that, with this method, we do need to declare the module first to use that ‘app’ object. It’s the only place that order of declarations matter in Angular, but it’s important. 强调一点,对于这个方法,我们必须首先定义模块,然后才能使用app对象。这是Angular中为一个一个注意声明顺序的地方,但是非常重要。

What I want to do, is simply concatenate all the files together into one, then require just that JavaScript file in our HTML. Because the app object has to be declared first, we just need to make sure that it’s declared before anything else. 我想要做的就是简单的把所有文件串联到一个文件,然后加载这个文件到HTML中。因为app对象必须首先被声明,因此我们只需要确定它是在其他东东之前就可以了。

Gulp Concat Gulp合并 To do this, I will be using Gulp. Don’t worry about learning a newfangled tool though, I’m going to use it in a very simple way and you can easily port this over to Grunt, Make, or whatever build tool you want (shockingly, even asset pipeline). You just need something that can concat files. 我将使用Gulp来完成这项工作。不要担心使用新的工具,我会用一个非常简单的方式使用它,你也会非常方便的把它集成到Grunt、Make或者其他任何编译工具中。你只需要他来和并文件。

I’ve played around with all the popular build systems and Gulp is far and away my favorite. When it comes to building css and javascript, specifically, it’s bliss. 我已经在所有流行的编译系统中做了尝试,Gulp远远超出我的期望。用它来编译CSS和JS,就是个福音。

You might be thinking I’m just replacing one build tool (browserify) with another (gulp), and you would be correct. Gulp, however, is much more general purpose. You can compose this Gulp config with other tools like minification, CoffeeScript precompilation (if you’re into that sort of thing), sourcemaps, rev hash appending, etc. Yes it’s nothing browserify can’t do, but once you learn how to do it with Gulp you can do the same on any other asset (like css). Ultimately it’s much less to learn. 你可能会想,我只是替换一个编译工具为另一个工具,你可能是对的。但是Gulp不仅仅如此。你还能够合并Gulp和其他工具,比如minification, CoffeeScript precompilation,sourcemaps, rev hash appending等等。诚然这些工作browserify也能做,但是一旦你学会如何使用Gulp来做的话,你能够用它来做一切东东。你只需要学习一点点东东。

You can use it to process png’s, compile your sass, start a dev node server, or running any code you can write in node. It’s easy to learn, and will provide a consistent interface to your other developers. It provides us a platform to extend on later. 你可以使用它来处理png,编译你的sass,开启一个开发环境Node服务器,或者运行任何你在node中写的代码。非常容易学习,它提供了一个接口给其他开发者。它提供给我们一个平台方便以后扩展。

I would much rather just type ‘gulp watch’ and have that properly watch all my static assets in dev mode than have to run ‘watchify’, a separate node server, a separate sass watcher, and whatever else you need to keep your static files up to date. 我宁愿只要使用gulp watch就可以监视我所有的开发模式下的静态断言,而不用运行一个独立的node服务器,一个独立的sass监视器,还有其他需要管理文件的工具。

First I’ll install Gulp and gulp-concat (gotta be in the project and global): 首先我将安装Gulp和gulp-concat

$ npm install --global gulp $ npm install --save-dev gulp gulp-concat By the way, you’ll need a package.json in your app and have Node installed. Here’s a little trick I do to start my Node apps (npm init is too whiny): 要说的是你需要在你的应用中存在一个package.json文件并且安装了Node。下面是我启动Node应用的小招:

$ echo '{}' > package.json

Then toss in this gulpfile.js: 然后我们进入gulpfile.js文件做些改动:

var gulp = require('gulp') var concat = require('gulp-concat')

gulp.task('js', function () { gulp.src(['src//module.js', 'src//*.js']) .pipe(concat('app.js')) .pipe(gulp.dest('.')) })

This is a simple task that takes in the JavaScript files in src/ and concatenates them into app.js. Because it expects this array, any file named module.js will be included first. Don’t worry too much about understanding this code, when we get to minification I’ll clear it up. 这是个任务非常简单,他把src下的所有js文件合并到appljs文件中。因为参数是一个数组,所以名称为module.js的文件会被优先合并。不要太担心这段代码,当我把它压缩后我就会删除它。

If you want to play along at home, use these files, then run ‘gulp js’ to build the assets. Donezo. 如果你想一个人在家玩,你可以运行gul js去编译即可。

For more on Gulp, read my article on setting up a full project with it. 关于Gulp的更多诶荣,你可以阅读我的文章,它会介绍如何使用它构建一个项目。

Icky Globals Icky全局变量 We can do better. You know how you create that ‘app’ variable? That’s a global. Probably not a problem to have one ‘app’ global, but it might be a problem when we grow to have more and more modules, they may conflict. 你可以做的更好。你知道你如何创建了一个app变量么?它是一个全局变量。也许只有一个app全局变量不会有啥问题,但是如果我们用于太多的模块,将会产生冲突的问题。

Luckily Angular can solve this for us very easily. The function angular.module() is both a getter and a setter. If you call it with 2 arguments: 幸运的是Angular能够非常容易的解决这些问题。angular.module()既是一个getter也是一个setter,如果你使用两个参数来调用:

angular.module('app', ['ngRoute'])

That’s a setter. You just created a module ‘app’ that has ‘ngRoute’ as a dependency. (I won’t be using ngRoute here, but I wanted to show what it looks like with a dependent module) 这个是一个setter。你创建了一个依赖ngRoute的模块app。(在这里我不会使用ngRoute,我只是想告诉大家这个看起来很像一个依赖的模块)

Calling that setter will also return the module as an object (that’s what we put into var app). Unfortunately you can only call it once. Disappointingly, getting this stuff wrong throws nasty error messages that can be frustrating to newbies. Stick to the xxx method and all will be good though. 调用这个setter将获得一个模块对象,不幸的是你只能调用它一次。更令人失望的是,调用失败产生的错误信息可能会迷惑新手。 If we call angular.module() with a single argument: 如果我们调用angular.module()只用一个参数:

angular.module('app')

It’s a getter and also returns the module as an object, but we can call it as many times as we want. For this reason, we can rewrite our components from this: 这十个设置方法同时也返回了一个模块对象,但是我们可以我们我可以无限制的访问。基于这个原因,我们重写下我们的代码:

app.factory('GithubSvc', function ($http) { return { fetchStories: function () { return $http.get('https://api.github.com/users') } } }) 重写为 angular.module('app') .factory('GithubSvc', function ($http) { return { fetchStories: function () { return $http.get('https://api.github.com/users') } } })

The difference is subtle and might seem innocuous to new JavaScript developers. The advanced ones are nodding along now though. To maintain a large JavaScript codebase is to prevent the usage of globals. 这里微妙的不同对于JS入门者来说可能没有什么。但是后者更加符合大众需求。对于大型项目来说我们要禁止使用全局变量。

To you pedants: I realize that there is still a global ‘angular’ object, but there’s almost certainly no point in avoiding that. 对于深究者:我知道这里仍有一个全局对象angular,但是再避免它也没有啥意义了。

Here we have a pretty well functioning way to build the assets, but there are a few more steps we need to get to the point of a fine-tuned build environment. Namely, it’s a pain to have to run ‘gulp js’ every time we want to rebuild ‘app.js’. 这里我们拥有了一个非常好的方法去构建原始文件。但是仍有基本才能构建一个运行良好的环境。比如,如果每次都不得不运行gulp js会变的非常痛苦。

Gulp Watch Gulp Watch This is really easy, and I think the code speaks for itself (Lines 10-12): 这非常简单

var gulp = require('gulp') var concat = require('gulp-concat')

gulp.task('js', function () { gulp.src(['src//module.js', 'src//*.js']) .pipe(concat('app.js')) .pipe(gulp.dest('.')) })

gulp.task('watch', ['js'], function () { gulp.watch('src/**/*.js', ['js']) })

This just defines a ‘gulp watch’ task we can call that will fire off the ‘js’ task every time a file matching ‘src/**/*.js’ changes. Blammo. 这里定义了一个gulp watch任务,如果js文件发生ianhua,那就能被他检视到。

Minification Minification Alright, let’s talk minification. In Gulp we create streams from files (gulp.src), then pipe them through various tools (minification, concatenation, etc), and finally output them to a gulp.dest pipe. If you know unix pipes, this is the same philosophy. 下面我们来谈下压缩。在Gulp中,我们能够冲文件中创建一个流(gulp.src),然后把它们传递到其他工具中(minification, concatenation等等),然后输出到gulp.dest管道中。如果你知道unix管道,那就是相同的道理。

In other words, we just need to add minification as a pipe. First, install gulp-uglify to minify: 总之我们只需要把minification添加到管道中,首先我们安装gulp-uglify去压缩:

$ npm install -D gulp-uglify

var gulp = require('gulp') var concat = require('gulp-concat') var uglify = require('gulp-uglify')

gulp.task('js', function () { gulp.src(['src//module.js', 'src//*.js']) .pipe(concat('app.js')) .pipe(uglify()) .pipe(gulp.dest('.')) })

But we have a problem! It has munged the function argument names Angular needs to do dependency injection! Now our app doesn’t work. If you’re not familiar with this problem, read up. 但是我们有一个问题!它弄脏了Angrular需要依赖的函数参数。这个代码不会工作了,如果你对这个问题不了解,那接着读。

We can either use the ugly array syntax in your code, or we can introduce ng-gulp-annotate. 我们能够使用guly数组语法,也可以看下我们的ng-gulp-annotate。

NPM install: $ npm install -D gulp-ng-annotate And here’s the new gulpfile: 下面是最新的代码:

var gulp = require('gulp') var concat = require('gulp-concat') var uglify = require('gulp-uglify') var ngAnnotate = require('gulp-ng-annotate')

gulp.task('js', function () { gulp.src(['src//module.js', 'src//*.js']) .pipe(concat('app.js')) .pipe(ngAnnotate()) .pipe(uglify()) .pipe(gulp.dest('.')) })

I hope you’re starting to see the value in Gulp here. How I can use a conventional format of Gulp plugins to quickly solve each of these build problems I am running into. 我希望你认真看我传递的值。我如何通过使用转换Gulp插件格式的方法去快速解决构建中我遇到的各种问题。

Sourcemaps Sourcemaps Everyone loves their debugger. The issue with what we’ve built so far is that it’s now this minified hunk of JavaScript. If you want to console.log in chrome, or run a debugger, it won’t be able to show you relevant info. 每个人都喜欢调试。我们已经构建了一个JS文件,当前也该讨论这个话题了。如果你想在chrome中使用console.log或者运行debugger,它不会展示你想要的信息。

Here’s a Gulp task that will do just that! (Install gulp-sourcemaps) 下面就是Gulp任务要做的东东(install gulp-sourcemaps)

var gulp = require('gulp') var concat = require('gulp-concat') var sourcemaps = require('gulp-sourcemaps') var uglify = require('gulp-uglify') var ngAnnotate = require('gulp-ng-annotate')

gulp.task('js', function () { gulp.src(['src//module.js', 'src//*.js']) .pipe(sourcemaps.init()) .pipe(concat('app.js')) .pipe(ngAnnotate()) .pipe(uglify()) .pipe(sourcemaps.write()) .pipe(gulp.dest('.')) })

Why Concat is Better 为啥合并是更好呢 Concat works better here because it’s simpler. Angular is handling all of the code loading for us, we just need to assist it with the files. So long as we get that module setter before the getters, we have nothing to worry about. 合并文件因为简单所以更好。Angular 已经提供了代码加载给我们,我们只需要管理下文件。到目前我们知道的模块setter和getters,我们没有啥可以担心的。

It’s also great because any new files we just add into the directory. No manifest like we would need in browserify. No dependencies like we would need in require.js. 同样任何新的文件都可以被探知。没有动作表明我们需要browserify,我们也不需要require.js来管理依赖。

It’s also just generally one less moving part, one less thing to learn. 这就使得我们少操心一件事了,也不用学习多余的东西了。

What we built 我们构建了什么 Here is the final code. It’s an awesome starting point to build out your Angular app. 这是我们最终的代码。这是一个相当完美的开局。

It’s got structure. It’s got a dev server. It’s got minification. It’s got source maps. It’s got style. (The Vincent Chase kind, not the CSS kind) It doesn’t have globals. It doesn’t have shitloads of <script> tags. It doesn’t have a complex build setup. 一个框架 一个开发服务器 一个压缩工具 一个代码映射 一个样式 没有全局变量 没有过多的script标签 没有复杂的构建步骤

I tried to make this not about Gulp, but as you can tell: I freaking love the thing. As I mentioned earlier, you could achieve a similar setup with anything that can concat. 我列出这些无关Gulp,但是你可能听说:我实在太喜欢这个东东了。正如我之前提到的,你可以实现一个类似的合并工具。

If there is interest, I could easily extend this to add testing/css/templates/etc. I already have the code. 如果感兴趣,我可以轻松的扩展去增加测试/CSS/模板等等。我已经实现了代码

Third-party code 第三方代码 For third-party code: if it’s something available on a CDN (Google CDN, cdnjs, jsdelivr, etc), use that. If the user has already loaded it from another site, the browser will reuse it. They also have very long cache times. 对于第三方代码:如果它在CDN上,那就使用它。如果用户已经从网上加载了代码,因为有缓存,浏览器会重用它。

If it’s something not available on a CDN, I would still probably use a new script tag but load it off the same server as the app code. Bower is good for keeping these sorts of things in check.

If you have a lot of third-party code, you should look into minifying and concatenating them like above, but I would keep it separate from your app code so you don’t have just one huge file. 如果你有很多第三方代码,你应该合并和压缩它们,但是我可能会在我们的代码中分开存放,防止有一个巨型文件。

ES6 Modules — The real solution The next version of JavaScript will solve this problem with built-in modules. They worked hard to ensure that it works well for both fans of CommonJS (browserify) and AMD (require.js). This version is a ways out, and you probably won’t be able to depend on the functionality without a shim of some kind for at least a year, probably a few. When it does come out, however, this post will be a relic explaining things you won’t need to worry about (or at least it’ll be horrifically incorrect).

Angular 2.0 Angular 2.0 It’s worth mentioning that Angular 2.0 will use ES6 modules, and at that point we’ll be in bliss. It’s nowhere close to release though, so for now, if you want to use Angular, you need a different option. Angular 2.0 will be a dream. It’s going to look a lot more like a series of useful packages than a framework, allowing you to pick and choose functionality, or bake them into an existing framework (like Ember or Backbone). 值得一说的是,Angular2.0将会使用ES6的模块机制,到那个时候我们就彻底解放了。当前如果你使用Angular,你需要不同的选择。 Angular 2.0还是值得期待的,它将带来一系列有用的包机制。

Angular 2.0 will use a separate library di.js that will handle all of this. It’s way simpler, and it’s only a light layer on top of ES6 modules. We should be able to easily use it in all apps, not just Angular apps. The unfortunate thing for you is that you will need to deal with the crufty state of affairs with JavaScript modules until then. Angular 2.0会使用一个独立的库di.js来处理这些。它非常简单,它只是基于ES6模块上的轻量级的层。我们可以在项目中很容易的使用它。但是在这之前你不得这样来使用JS模块来处理这些问题。

Man. I love all these great ways JavaScript is improving, but god damn is it a lot to keep learning. 同志们,我为JS在进步赶到高兴,但是我们不得不学习更多的东西。

P.S. I have some code samples you can use to asynchronously load an Angular app. Any interest in reading about that? PS 我有这些例子的异步实现,你有兴趣么?

Clone this wiki locally