Angular1距离2009年发布已经好多年了,Angular2也已经出了Beta版,估计今年就能正式发布。大多数人对于Angular1.X的认识仅限于能够在项目中使用,对于其中的深层原理知道的并不多。市面上也没有特别好的介绍Angular实现原理的教程或者书籍。今天在看技术文档的时候偶然发现了一本比较好的Angular底层原理书籍《build your own AngularJS》,费了好大功夫买下了全本,随之开始了Angular1.X的底层实现的探索之旅。本系列文章会按照书中的章节,每一章节独立成为一篇文章,按书中的介绍一步步来动手实现自己的AngularJS,以便深入学习。本系列文章的目的主要是以更为简单容易接受的方式让读者轻松学习整个过程而不用购买这本书(30多刀啊),同时记录自己的学习过程,关于本系列的所有源代码参见这里。
本文的主要内容是构建一个可运行的项目作为实现AngularJS的基础代码项目库,初始化整个项目,包括代码打包,模块化,测试,代码lint,使用NPM Scripts进行自动化脚本运行等,这些工作是今后实现的基础。
1.建立项目并初始化package.json文件
首先确保你的机器上已经安装了NodeJS和NPM,接着运行以下命令行
mkdir myangular cd myangular mkdir test mkdir src
首先创建项目根目录myangular,然后在根目录下创建test和src两个文件夹,分别用来存放测试文件和源文件,接着输入以下命令行
npm -y init
会在项目根目录下创建一个package.json文件,用来存放NPM相关的配置信息。
2.创建源文件并启用JSHint
对于一个框架的实现需要保证代码一致性和遵循一定的规范,这就需要用到JSHint插件。首先在src文件夹下创建一个hello.js的文件,用来测试,内容如下:
function sayHello() { return "Hello, world!"; }
接着输入以下命令安装JSHint
npm install --save-dev jshint
这会在项目根目录下创建node_modules文件夹并将所有NPM install安装的文件都放在这里。并在package.json的devDependencies配置项中加入一条关于JSHint的配置信息,表示这是在开发模式中需要加入的依赖包,在生产模式下不需要。接着在项目根目录下创建一个.jshintrc文件,用来存放JSHint需要读取的配置,它的内容如下:
{ "browser": true, "browserify": true, "devel": true }
当我们运行JShint的时候,就会遵循这个配置文件下的信息来查看代码是否符合规范。
接着在package.json文件中添加如下配置信息,用来运行JShint,检查src目录下的文件是否存在问题。
"scripts": { "lint": "jshint src" }
最后,使用如下命令行运行JSHint
npm run lint
3.为项目加入单元测试
在单元测试阶段需要用到Jasmine,karma以及Sinon.JS,其中Jasmine是一个单元测试框架,karma是一个test runner,Sinon.js是需要用到的一个测试库。
首先安装Jasmine及Sinon.js
npm install --save-dev jasmine-core sinon
接着安装karma及其相关插件
npm install --save-dev karma karma-jasmine karma-jshint-preprocessor
最后安装Phantom.js作为浏览器的测试环境
npm install --save-dev phantomjs karma-phantomjs-launcher
以上都安装完成之后需要对karma进行配置,在项目根目录下创建一个karma.conf.js文件,其内容如下:
module.exports = function(config) { config.set({ frameworks: [‘jasmine‘], files: [ ‘src/**/*.js‘, ‘test/**/*_spec.js‘ ], preprocessors: { ‘test/**/*.js‘: [‘jshint‘], ‘src/**/*.js‘: [‘jshint‘] }, browsers: [‘PhantomJS‘] }) }
主要作用是告诉karma使用jasmine作为测试框架,需要测试的文件主要是src目录和test目录下的文件,在处理这些文件之前需要使用jshint进行预处理,同时测试的浏览器环境是PhantomJS.
由于我们需要在测试文件中使用全局变量诸如describe等,所以需要在.jshintrc文件中设置,修改该文件,其被修改的内容如下:
{ "browser": true, "browserify": true, "devel": true, "globals": { "jasmine": false, "describe": false, "it": false, "expect": false, "beforeEach": false, "afterEach": false } }
接着修改package.json文件中的scripts配置项如下,用来运行自动化脚本。
"scripts": { "lint": "jshint src test", "test": "karma start" }
这时,使用npm run lint就能够运行JShint去检测test和src文件夹下的文件是否符合语法规范,使用npm run test 就能运行全局的测试文件。
最后,在test文件夹下创建一个hello_spec.js文件,运来存放我们的测试用例,其内容如下:
describe("Hello", function() { it("says hello", function() { expect(sayHello()).toBe("Hello, world!"); }); });
这时运行npm test 就能运行hello_spec.js这个测试用例,在命令行中出现诸如以下的结果:
可以看出,它不仅有我们测试用例自身运行的结果,在测试用例运行的时候,还会启动JSHint并将运行结果显示出来。
4.为项目添加模块化解决方案
由于Angular本身出现的较早,当时还没有AMD,CommonJS等模块化解决方案,所以它实际上是采用全局变量及函数直接定义整个代码库的,但是在该项目中我们使用CommonJS辅以browserify作为我们的模块化解决方案。
首先安装browserify及其相关插件
npm install --save-dev browserify karma-browserify
安装成功后修改src目录下的hello.js文件,让其符合CommonJS格式,其内容如下:
module.exports = function sayHello() { return "Hello, world!"; };
同时修改test目录下的hello_spec.js文件,让其符合CommonJS格式。
var sayHello = require(‘../src/hello‘); describe("Hello", function() { it("says hello", function() { expect(sayHello()).toBe("Hello, world!"); }); });
最后,修改karma.conf.js让其和browserify结合起来使用,修改后的内容如下:
module.exports = function(config) { config.set({ frameworks: [‘browserify‘, ‘jasmine‘], files: [ ‘src/**/*.js‘, ‘test/**/*_spec.js‘ ], preprocessors: { ‘test/**/*.js‘: [‘jshint‘, ‘browserify‘], ‘src/**/*.js‘: [‘jshint‘, ‘browserify‘] }, browsers: [‘PhantomJS‘], browserify: { debug: true } }) }
配置中发生变化的主要是告诉karma使用browserify并且在进行测试前使用browserify进行预处理,同时启用sourcemap便于程序debug.
5.为项目中添加Lodash和jQuery
Angular自身的实现是没有jQuery的,但是由于我们更加关注的是Angular自身的实现而不是其对于Utility函数或者某些DOM操作的实现,所以为了简化,这里使用Lodash来为我们提供对对象或者数组的处理,使用jQuery进行DOM查询及操作。
npm install --save lodash jquery
安装完成后,修改src目录下的hello.js,其内容被修改为使用Lodash的方法如下:
var _ = require(‘lodash‘); module.exports = function sayHello(to) { return _.template("Hello, <%= name %>!")({name: to}); };
并修改test目录下的hello_spec.js,如下:
var sayHello = require(‘../src/hello‘); describe("Hello", function() { it("says hello", function() { expect(sayHello(‘Jane‘)).toBe("Hello, Jane!"); }); });
运行npm test可以看到测试的最终效果。
至此,我们已经搭建成了一个自己实现Angular的基础环境,今后所有的代码及实现都会在这个代码库中进行。所有的代码都需要严格遵循JShint代码规范并进行单元测试,接下来一起来进行Angular底层实现吧!PS:本项目的所有代码在这里,该系列文章会不定期更新。