Build an Instagram clone with AngularJS, Satellizer, Node.js and MongoDB
这节里面我们将会讲述一些通用的优化技巧,用以加速你的应用。首先,让我们来看看没有做任何改变之前我们需要多长时间来加载页面。
这是最近的 25 个请求,总通信 1.40 MB 用时 442ms (+/- 100ms) 来加载该页面。编译工具可以拯救它。如果你有读过我前一篇教程的话,你就应该知道我有多爱 gulp.js。
为了提高我们的 web 应用,这有一些我们很容易就能做到的简单优化技巧:
不过首先,让我们对文件结构做一个小小的调整。把 package.json 从 instagram/server 提升一级到 instagram 目录。
然后,把 “start” 路径改为和新文件结构对应的 “node server/server.js”。这样之后,你仍然可以用 npm start 来自动启动你的 Express 应用。你的 package.json 看起来应该像这样:
<!-- lang: js -->
{
"name": "instagram-server",
"version": "0.0.0",
"scripts": {
"start": "node server/server.js"
},
"dependencies": {
"bcryptjs": "^2.0.2",
"body-parser": "^1.8.1",
"cors": "^2.4.2",
"express": "^4.9.0",
"jwt-simple": "^0.2.0",
"moment": "^2.8.3",
"mongoose": "^3.8.17",
"request": "^2.44.0"
}
}
打开终端窗口,然后进入到 instagram 工程目录,使用如下命令:
<!-- lang: js -->
npm install --save-dev gulp gulp-csso gulp-recess gulp-ng-annotate gulp-uglify gulp-concat gulp-angular-templatecache gulp-complexity gulp-header
注意: –save-dev 标签会安装指定的包,并且会自动把它们添加到 package.json 的 devDependencies 对象里面,如下图:
在 instagram 目录下创建一个新的文件,叫 gulpfile.js,内容如下:
<!-- lang: js -->
var gulp = require('gulp');
var csso = require('gulp-csso');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
var recess = require('gulp-recess');
var header = require('gulp-header');
var gulpFilter = require('gulp-filter');
var complexity = require('gulp-complexity');
var ngAnnotate = require('gulp-ng-annotate');
var templateCache = require('gulp-angular-templatecache');
我们创建的第一个任务,将会处理 JavaScript 的压缩,串联, AngularJS 内嵌注释和模板的缓存。此外,我们使用 gulp-header 来追加一个通用内容到压缩后文件,包括 工程名, 作者名, 授权 和 最后更新日。虽然对于我们的最终目的这些都不是必须的,但是我觉得在这里介绍一下感觉很赞,因为我觉得应该把 Gulp 也顺带介绍一下。把下面的内容贴到依赖项之后:
<!-- lang: js -->
var banner = ['/**',
' * Instagram Demo',
' * (c) 2014 Author Name',
' * License: MIT',
' * Last Updated: <%= new Date().toUTCString() %>',
' */',
''].join('\n');
gulp.task('minify', function() {
var templatesFilter = gulpFilter('clients/views/*.html');
return gulp.src([
'client/vendor/angular.js',
'client/vendor/*.js',
'client/app.js',
'client/templates.js',
'client/controllers/*.js',
'client/services/*.js',
'client/directives/*.js'
])
.pipe(templatesFilter)
.pipe(templateCache({ root: 'views', module: 'Instagram' }))
.pipe(templatesFilter.restore())
.pipe(concat('app.min.js'))
.pipe(ngAnnotate())
.pipe(uglify())
.pipe(header(banner))
.pipe(gulp.dest('client'));
});
在 Gulp 中,所有的流程都是从上到下的,每个 .pipe() 方法会执行修改之后以某种格式或类型输出,一直执行到 gulp.dest() 为止,然后保存到文件中。
之所以传入那么长的一个源文件数组是为了把它们串联起来。比如说,我们必须加载 angular.js 先于所有的 Angular 模块/库,同样我们必须加载 Angular 模块先于加载 app.js ,诸如此类。所以我们要在 index.html 里面 顺序声明 <script>
标签:
<!-- lang: js -->
<script src="vendor/angular.js"></script>
<script src="vendor/angular-route.js"></script>
<script src="vendor/angular-messages.js"></script>
<script src="vendor/satellizer.js"></script>
<script src="app.js"></script>
<script src="controllers/home.js"></script>
<script src="controllers/navbar.js"></script>
<script src="controllers/login.js"></script>
<script src="controllers/signup.js"></script>
<script src="controllers/detail.js"></script>
<script src="services/api.js"></script>
<script src="directives/serverError.js"></script>
然后,我们用 gulp-filter “绕个弯路” 到另外的 gulp.src() 上把 HTML 模板转换成 JavaScript,以便能用上 Angular 的 $templateCache 特性。我们之所以这样做,是因为 Gulp 不允许你在一个任务里面指定多个源文件。在我们完成了 angularTemplateCache 之后在过滤器里面调用 restore() 方法,这样就会把我们重新带回到原来的 gulp.src() 上。
注意: 另外一个代替方案是我们创建另外一个任务,叫 templates,在 minify 任务之前调用它,然后把 templates 任务生成的文件 (比如 templates.js) 导入到 minify 任务,最后,我们把所有的任务都执行完之后删除 templates.js 。这样会多出来很多工作,不过我想如果你不想用 gulp-filter 的话,我应该给你指出来还有这种方法。
剩下来在 minify 里面的事情都不用解释了,字面意思。这里是最后生成的 app.min.js 压缩文件的预览:
打开 index.html 然后把原来的 <script>
标签注释掉或者删掉,我们只需要导入一个压缩后的文件:
<!-- lang: js -->
<!--<script src="vendor/angular.js"></script>-->
<!--<script src="vendor/angular-route.js"></script>-->
<!--<script src="vendor/angular-messages.js"></script>-->
<!--<script src="vendor/satellizer.js"></script>-->
<!--<script src="app.js"></script>-->
<!--<script src="controllers/home.js"></script>-->
<!--<script src="controllers/navbar.js"></script>-->
<!--<script src="controllers/login.js"></script>-->
<!--<script src="controllers/signup.js"></script>-->
<!--<script src="controllers/detail.js"></script>-->
<!--<script src="services/api.js"></script>-->
<!--<script src="directives/serverError.js"></script>-->
<script src="app.min.js"></script>
然后下一个 Gulp 任务 – gulp-complexity。它是一个 JavaScript 混淆分析工具。重申一下,这不是优化的一部分,不过我觉得提一下比较赞。下面是这个任务的内容:
<!-- lang: js -->
gulp.task('complexity', function() {
return gulp.src([
'!client/vendor/*.*',
'!client/app.min.js',
'client/**/*.js'
])
.pipe(complexity());
});
这里,我们用 感叹号(!)来给 Gulp 指出哪些文件和目录应该忽略,现在如果你在终端执行 gulp complexity,你会看到像下面这样的画面:
剩下的两个任务,我们将要对 CSS 来进行压缩和提升性能的代码分析。
<!-- lang: js -->
gulp.task('styles', function() {
gulp.src([
'client/css/sweet-alert.css',
'client/css/styles.css'
])
.pipe(concat('styles.min.css'))
.pipe(csso())
.pipe(gulp.dest('client/css'));
});
gulp.task('recess', function() {
gulp.src('client/css/styles.css')
.pipe(recess())
.pipe(recess.reporter())
.pipe(gulp.dest('client/css'));
});
打开 index.html 然后更新样式引用,从这样:
<!-- lang: js -->
<link rel="stylesheet" href="css/sweet-alert.css">
<link rel="stylesheet" href="css/styles.css">
改成这样:
<!-- lang: js -->
<link rel="stylesheet" href="css/styles.min.css">
现在,三个全尺寸的 CSS 文件被我们的一个压缩过的 CSS 文件替代了。我特地把 recess 放到了一个独立的任务里面,因为如果它执行失败,那么它会中止整个 Gulp 进程,这是相当烦人的,特别是当你在后台打开那些“开过就忘的” Gulp 监视器的时候。但是,你可以用 gulp-plumber 或者手动捕获 Gulp 异常事件,如果你想这样做的话。让我们执行一下 gulp recess 看看它运行起来会怎样:
最后剩下来的是,我们要添加监控和默认任务:
<!-- lang: js -->
gulp.task('watch', function() {
gulp.watch(['client/css/*.css', '!client/css/styles.min.css'], ['styles']);
gulp.watch([
'client/app.js',
'client/services/*.js',
'client/directives/*.js',
'client/controllers/*.js',
'client/views/*.html'
], ['minify']);
});
gulp.task('default', ['watch', 'styles', 'minify']);
注意: 如果你把你客户端的代码组织得很好的话,你不需要用上面这种傻兮兮的重复路径名了。我只是在开始的时候没有想过我的教程会写成这样。如果你把你的 Angular 应用放在 src 或者 app 目录,把你的样式,vendor 库和压缩过的 app.min.js 在一两层目录外面,那么你可以简单的写成 gulp.watch(‘src//.{html, js}’, [‘minify’])*。这和我们上面做的效果一样。
好了,如果现在你执行 gulp 它会自动的运行 styles 和 minify 任务,然后开始监视所有文件的改变。也就是说,如果你的 CSS 文件发生改变, Gulp 会自动的重新执行 styles 任务,同样的,如果你的 AngularJS 应用有任何改变的话, Gulp 会自动的重新执行 minify 任务。
这就是我说的 “开过就忘”。我通常都是在终端打开 Gulp 进程,然后把窗口最小化由它去吧。
好了,我们现在已经把 Gulp 弄完了。下面,我们会加上 gzip 压缩和 Express 中的静态资源缓存。安装下面的 NPM 模块:
<!-- lang: js -->
npm install --save compression
然后把它加到 server/server.js 的依赖列表里面:
<!-- lang: js -->
var compress = require('compression');
最后在所有的 Express 中间件之前把 compress() 中间件加上去:
<!-- lang: js -->
app.set('port', process.env.PORT || 3000);
app.use(compress());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
为了静态资源缓存可用,修改静态资源中间件如下:
<!-- lang: js -->
app.use(express.static(path.join(__dirname, 'public'), { maxAge: 2628000000 }));
注意: 一个月相当于 2628000000 毫秒。你其实应该创建ige独立的变量,比如说 oneDay,oneWeek,oneMonth,或 oneYear,而不是直接在中间件中用毫秒。
好了就是这样,我们已经完成了优化。让我们来再看看浏览器的 Network 选项卡,我们可以看到比原先更小的请求数目以及更小的负载大小了。
这是我们优化结果的一个简单摘要:
这就是我说的 唾手可得 的页面优化: 我们不需要对应用逻辑做任何改变,就能得到很不错的结果。
最后最后,我希望你能在本教程中学到其中一些技巧,并在你现在或者将来的工程中应用它们。
让我们把这个应用发布到云上吧,好吧?