随着rc(release candidate,候选版本)版本的推出,万众瞩目的angular2终于离正式发布不远啦!五月初举办的ng-conf大会已经过去了整整一个月,大多数api都如愿保持在了相对稳定的状态——当然也有router这样的例外,在rc阶段还在大面积返工,让人颇为不解——不过总得说来,现在学习angular2不失为一个恰当的时机。
Google为angular2准备了完善的文档和教程,按理说,官网(https://angular.io)自然是学习新框架的最好教材。略显遗憾的是,在Basic章节的Overview部分中,作者在开篇就明确提到这是一份准备给有经验的开发者的一份文档(This is a practical guide to Angular for experienced programmers who are building client applications in HTML and TypeScript)。换言之,如果要较好地理解官网上的文档和教程,对阅读者是有一定要求的。最显而易见的是,在angular2中,连运行入门级的hello world都需要配置编译环境和后端支持——别忘了,即使是去年火遍大江南北的react,我们在学习hello world时还能通过script引入babel来在运行时解析jsx呢(当然在生产环境上可不能这么干)。如果要细数下来,要求就更多了:node和npm、webpack或者systemjs之类的打包和模块化工具、面向对象编程、es6的基本语法和概念、Web Components等新标准……
博主在新技术新框架面前并不是一个因循守旧的人,只是有一点让我比较担忧:如果angular2的入门门槛太高,那对以后的框架推广和社区发展会不会有一些不利的影响呢?出于这个想法,博主决定推出一个step by step的博客系列,希望将angular2带给更多感兴趣、但可能暂时缺少相关开发经验的同学。
下面就是本系列博客的第一篇:Hello World与自动化环境搭建。
官网在quickstart中使用了systemjs、lite-server,配置较为复杂;为了简单起见,本文将使用大家更熟悉的gulp、browserify方案。出于相同的目的,编译的目标版本将选择es6而非es5,所以请读者在继续阅读之前请确保已经将chrome升级到最新版本。另外,node环境也请先安装好,具体步骤这里就不赘述了。
1. 创建项目,初始化package.json
创建一个新文件夹angular2-learn,然后使用输入npm init命令初始化npm环境。
npm init
如果没有特殊需求,这里可以一直使用回车键,直到整个过程完成。此时查看angular2-learn文件夹,应该多了一个package.json文件,它最重要的作用之一是保存当前项目所需要的依赖。
2. 使用npm安装运行依赖
在正式开发之前,我们需要将angular2的相关类库下载到本地,以便于之后的打包和运行。在本篇教程中,我们暂时只需要如下几个依赖:@angular/common, @angular/compiler, @angular/core, @angular/platform-browser, @angular/platform-browser-dynamic
在angular2-learn文件夹下,执行如下命令:
npm install --save @angular/common @angular/compiler @angular/core @angular/platform-browser @angular/platform-browser-dynamic --registry=https://registry.npm.taobao.org
执行完成后,再检查angular2-learn目录,发现多了node_modules文件夹,里面就是刚刚通过npm install安装的依赖。
package.json的dependencies字段下也多了很多记录,这是--save参数的作用:如果没有--save参数,这些文件依旧会被下载下来,但是不会被保存到package.json中。
--registry参数用于指定npm的镜像仓库,由于网络被墙,不使用--registry很多时候都无法完成下载;registry.npm.taobao.org是阿里给广大前端同学的福利,长期维护且高速稳定,在这里顺便也向阿里的同学说一声谢谢 :)
3. 安装gulp和其他开发依赖
在上一步中,我们下载了angular2框架,angular2框架是程序运行时所依赖的。但是我们在编译时还需要一些其他的类库或框架支持。
首先是gulp,我们要靠它完成编译流程的自动化,比如检测文件修改、自动编译、生成目标js等。
我们还需要browserify,因为angular2和大多数托管在npm的类库遵循的都是commonjs标准而非amd标准,它们适用于nodejs环境而非浏览器环境。browserify,正如它的名字所暗示的一样,可以帮助我们把符合commonjs标准的代码“浏览器化”,使之成为符合amd标准的代码。
当然我们也需要typescript编译器。博主本来想用非模块化的angular2框架以及原生javascript来为大家演示angular2 hello world的例子,但是尝试后发现,这样做虽然看起来简单一些,但实在是味同嚼蜡——缺少了模块化和元信息注解的支持,angular2的韵味一下就掉了一大半。因此这里我们还是坚持用typescript作为编程语言,可以预见的是,angular2团队将来会把大部分精力花在typescript的相关支持上,typescript一定是angular2的默认推荐语言。对于不熟悉typescript的同学,其实也不用担心,typescript是javascript的超集,所有合法的javascript代码都是合法的typescript代码,只要面向对象的基础比较扎实,学习起来会有一脉相承的感觉,曲线不会太陡峭。
首先来安装gulp
npm install -g gulp --registry=https://registry.npm.taobao.org
注意,这一步中我们使用了-g而非--save参数。这是因为gulp安装在全局,并不是为某个特定项目安装的,g指的就是global。Linux和Mac环境下的读者在这一步时请注意读写权限问题。
然后回到angular2-learn文件夹,为项目安装编译时依赖:
npm install --save-dev gulp gulp-browserify gulp-typescript --registry=https://registry.npm.taobao.org
这里为什么使用--save-dev而非--save呢?因为这些工具只在脚本编译的时候使用,最终生成的js并没有它们的身影,因此只能算作开发依赖。
安装完成后检查package.json,发现刚刚的几个工具都被加到了devDependencies字段。
到这里我们就完成了hello world例子所依赖的类库的安装。
4. 配置gulpfile.js
正如上文所提到的,我们使用gulp的目的是搭建自动化的编译环境,使之产出可以在浏览器中运行的js代码。更详细地说,我们需要gulp检测ts(typescript脚本的默认后缀名)文件的变化,一旦有变化发生(比如新建、修改、删除等),就使用gulp-typescript将脚本从typescript编译为javascript;由于angular2本身遵循的是适用于nodejs的commonjs标准,从typescript编译为javascript脚本后,我们还需要使用browserify将它转变为符合amd标准的javascript文件。流程见下图:
按照这个思路,我们可以写出如下gulpfile.js,并将其放到angular2-learn目录下:
var gulp = require(‘gulp‘); var typescript = require(‘gulp-typescript‘); var browserify = require(‘gulp-browserify‘); gulp.task(‘compile‘, function () { gulp.src(‘app/**.ts‘) // typescript编译配置信息 .pipe(typescript({ target: ‘es6‘, module: ‘commonjs‘, moduleResolution: ‘node‘, sourceMap: true, emitDecoratorMetadata: true, experimentalDecorators: true, removeComments: false, noImplicitAny: false })) .on(‘error‘, function () { console.log(‘unhandled error from typescript compilation‘); }) // 将typescript编译后的javascript文件存放到out目录下 .pipe(gulp.dest(‘./out‘)) .on(‘end‘, function () { console.log(‘typescript compilation done‘); // 将main.js作为入口文件,使用browserify按amd规范进行合并 gulp.src(‘out/main.js‘) .pipe(browserify()) .on(‘error‘, function (error) { console.log(‘unhandled error from browserify resolution‘); console.log(error); }) // 将browserify后的文件放到项目根目录 .pipe(gulp.dest(‘./‘)) .on(‘end‘, function () { console.log(‘browserify work done‘); }); }); }); gulp.task(‘default‘, [‘compile‘], function () { gulp.watch(‘app/**.ts‘, [‘compile‘]); });
不熟悉gulp的同学也不用紧张,这个配置文件其实就是为了表达出上图中的编译流程。另外,在typescript编译配置部分,这里除了将target设为es6和官网不同外,其余配置都是使用的angular2推荐的配置。为什么要编译到es6而不是es5呢,因为es6的大多数特性新版的浏览器已经支持了,而且我们的hello world本身也没有必要考虑浏览器兼容性,直接使用es6更为方便。
gulpfile配置好之后,对angular2编译环境的配置到这里就结束了。
5. 入口组件app.component.ts
接下来我们就进入到angular2 hello world的编码部分。作为一门新的前端框架,angular2吸收了很多前端社区近年来的发展成果,比如我们马上就要看到的基于es6的模块化开发和typescript提供的装饰器(decorator)。typescript虽然是javascript的超集,但其语言特性并不是天马行空的,大多数还是javascript正常发展的自然延续,例如装饰器将来就有很大希望成为新的ecmascript标准的一部分。
好了回到正题。我们先在angular2-learn根目录下创建app文件夹,作为存放所有typescript脚本的目录。
然后在app文件夹中创建app.component.ts文件,用于入口组件的定义。
在组件的定义中,我们需要使用到@angular/core模块中的Component装饰器。
import {Component} from ‘@angular/core‘;
有Java开发经验的同学会发现装饰器的使用和注解(annotation)的使用方法十分相似。在Component装饰器中,我们需要定义两个属性,selector和template。selector的作用在于告知angular要在哪些元素上使用这个组件,它本身类似于css选择器。template是Component对应的模板,在本篇教程可以暂时认为它是html代码的容器。
@Component({
selector: ‘hello-world‘,
template: ‘<h1>hello world</h1>‘
})
最后是AppComponent类的定义。本节中暂时还不需要为其添加额外的属性,但是作为模块化开发的一部分,为了将该类暴露给其他的文件,我们需要在类的定义前加上export关键字。
export class AppComponent {}
app.component.ts的完整文件内容:
// 从@angular/core中引入Component import {Component} from ‘@angular/core‘; // Component装饰器的使用 @Component({ // selector告知angular在哪里初始化AppComponent这个组件 selector: ‘hello-world‘, // AppComponent组件的具体模板 template: ‘<h1>hello world</h1>‘ }) // 定义AppComponent类并将其暴露给外部环境 export class AppComponent {};
6. 启动脚本main.ts
上文中,AppComponent类的定义已经完成了,我们还需要一个类似于C语言main函数或者Java静态main方法的启动器来开始angular2的初始化。
依然在app目录下,这次创建main.ts文件。
angular2在浏览器中的启动依赖于定义在@angular/platform-dynamic-browser上的bootstrap函数。为什么要将bootstrap定义在这样一个模块中而非@angular/core中呢,难道启动不是核心功能吗?事实上,angular2这样做是为了解耦框架的核心运行时逻辑和初始化逻辑,方便将来后端、原生移动端上的加载和初始化。
除此以外,我们还需要引入刚刚定义的AppComponent。
main.ts的完整文件内容:
// 从@angular/platform-browser-dynamic引入bootstrap函数 import {bootstrap} from ‘@angular/platform-browser-dynamic‘; // 从app.component.ts引入AppComponent import {AppComponent} from ‘./app.component‘; // 调用bootstrap函数,并将AppComponent作为参数,即入口组件 bootstrap(AppComponent);
上面的代码中有一个小地方需要注意,引入AppComponent时,from ‘./app.component‘中不能写./app.component.ts,.ts的后缀必须被省略掉。否则在typescript的编译完成后,browserify就找不到相应的类而只能抛出异常了。
7. 创建index.html
html!终于来到了本篇教程的最后一个编码步骤!
由于angular2的部分运行时依赖我们没有通过gulp打包进去,这里需要通过cdn加载,当然不嫌麻烦的同学也可以下载到本地。
body里需要放入hello-world标签,和AppComponent中selector属性所定义的hello-world相对应。
最后引入gulp编译、打包结束后生成的在angular2-learn根目录下的main.js。
index.html的完整内容:
<!doctype html> <html> <head> <meta charset="utf-8" /> <title>angular2-learn</title> <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" /> <script src="http://cdn.bootcss.com/reflect-metadata/0.1.3/Reflect.min.js"></script> <script src="http://cdn.bootcss.com/angular.js/2.0.0-beta.17/Rx.umd.min.js"></script> <script src="http://cdn.bootcss.com/zone.js/0.6.12/zone.min.js"></script> </head> <body> <hello-world></hello-world> <script src="main.js"></script> </body> </html>
8. 运行gulp并在浏览器中打开
上面的步骤完成后,angular2-learn目录下的结构应该如图所示:
在命令行中回到angular2-learn目录,输入如下命令并回车:
gulp
如果上面的代码没有错误,应该可以看见
typescript compilation done
browserify work done
的提示,并且angular2-learn目录下多了out文件夹和main.js。此时在浏览器中打开本地index.html,应该就可以看到最终的效果了。
因为gulp监视了ts文件的变动,如果之后要修改和调试,只需要在browserify work done的提示出来之后刷新网页即可。
angular2 hello world的例子以及随后的教程所需要的自动化编译环境就搭建完啦,对angular2是不是多多少少有了点感觉呢?欢迎继续阅读本系列的后续教程。