本文將從頭開始編寫實際的代碼來完成一個angular2的demo。 題外話是其實angular2官網的快速開始項目已經很酷炫了,但其側重快速二字,只夠拿來練習玩耍,倒是github上確實已經有了一些不錯的angular2-starter。 1. 安裝必要的node環境與npm 當然TS環境也是必須的 ...
本文將從頭開始編寫實際的代碼來完成一個angular2的demo。
題外話是其實angular2官網的快速開始項目已經很酷炫了,但其側重快速二字,只夠拿來練習玩耍,倒是github上確實已經有了一些不錯的angular2-starter。
1. 安裝必要的node環境與npm
當然TS環境也是必須的,目前TS已經更新到了2.1.5+,筆者使用的就是2.1.5版本,且最好使用2.0以上版本的TS,否則會有一些尷尬的問題(包括類型定義以及編譯錯誤)。
2.關於編輯器
筆者使用的是VSCode,使用其他熱門的編輯器都可以自己喜歡就行,甚至可以用VisualStudio(但是在網上見過有人用VS2015來開發涉及到npm包的項目,即使是Mac頂配版也能卡爆炸)。
3. 底層目錄結構
想象自己在寫一個後臺語言項目,我們所寫的文件最終都要經過編譯生成目標文件才運行,ng2也是這樣,編寫的是.ts文件,最終由我們配置好的編譯器(webpack)進行編譯生成目標代碼並運行。
所以除了angular2依賴以外,必須配置好底層的webpack。所有的依賴包都通過前面安裝的npm來安裝。下麵給出package.json:
1 { 2 "name": "angular2-demo-yitim", 3 "version": "1.0.0", 4 "description": "angular2 demo by yitim", 5 "keywords": [ 6 "angular2", 7 "webpack", 8 "typescript" 9 ], 10 "author": "yitim <[email protected]>", 11 "license": "MIT", 12 "scripts": { 13 "build:aot:prod": "npm run clean:dist && npm run clean:aot && webpack --config config/webpack.prod.js --progress --profile --bail", 14 "build:aot": "npm run build:aot:prod", 15 "build:dev": "npm run clean:dist && webpack --config config/webpack.dev.js --progress --profile", 16 "build:docker": "npm run build:prod && docker build -t angular2-webpack-start:latest .", 17 "build:prod": "npm run clean:dist && webpack --config config/webpack.prod.js --progress --profile --bail", 18 "build": "npm run build:dev", 19 "clean:dll": "npm run rimraf -- dll", 20 "clean:aot": "npm run rimraf -- compiled", 21 "clean:dist": "npm run rimraf -- dist", 22 "clean:install": "npm set progress=false && npm install", 23 "clean": "npm cache clean && npm run rimraf -- node_modules doc coverage dist compiled dll", 24 "docs": "npm run typedoc -- --options typedoc.json --exclude '**/*.spec.ts' ./src/", 25 "lint": "npm run tslint \"src/**/*.ts\"", 26 "server:dev:hmr": "npm run server:dev -- --inline --hot", 27 "server:dev": "webpack-dev-server --config config/webpack.dev.js --open --progress --profile --watch --content-base src/", 28 "server:prod": "http-server dist -c-1 --cors", 29 "server": "npm run server:dev", 30 "start": "npm run server:dev", 31 "tslint": "tslint", 32 "version": "npm run build", 33 "watch:dev:hmr": "npm run watch:dev -- --hot", 34 "watch:dev": "npm run build:dev -- --watch", 35 "watch:prod": "npm run build:prod -- --watch", 36 "watch": "npm run watch:dev", 37 "webdriver-manager": "webdriver-manager", 38 "webdriver:start": "npm run webdriver-manager start", 39 "webdriver:update": "webdriver-manager update", 40 "webpack-dev-server": "webpack-dev-server", 41 "webpack": "webpack" 42 }, 43 "dependencies": { 44 "@angular/common": "2.4.6", 45 "@angular/compiler": "2.4.6", 46 "@angular/core": "2.4.6", 47 "@angular/forms": "2.4.6", 48 "@angular/http": "2.4.6", 49 "@angular/platform-browser": "2.4.6", 50 "@angular/platform-browser-dynamic": "2.4.6", 51 "@angular/platform-server": "2.4.6", 52 "@angular/router": "3.4.6", 53 "@angularclass/conventions-loader": "^1.0.2", 54 "@angularclass/hmr": "~1.2.2", 55 "@angularclass/hmr-loader": "~3.0.2", 56 "core-js": "^2.4.1", 57 "crypto-browserify": "^3.11.0", 58 "crypto-js": "^3.1.9-1", 59 "http-server": "^0.9.0", 60 "ie-shim": "^0.1.0", 61 "reflect-metadata": "^0.1.9", 62 "rxjs": "5.0.2", 63 "zone.js": "0.7.6" 64 }, 65 "devDependencies": { 66 "@angular/compiler-cli": "2.4.6", 67 "@types/hammerjs": "^2.0.33", 68 "@types/node": "^7.0.0", 69 "@types/selenium-webdriver": "~2.53.39", 70 "@types/source-map": "^0.5.0", 71 "@types/uglify-js": "^2.0.27", 72 "@types/webpack": "^2.0.0", 73 "add-asset-html-webpack-plugin": "^1.0.2", 74 "angular2-template-loader": "^0.6.0", 75 "assets-webpack-plugin": "^3.5.1", 76 "awesome-typescript-loader": "~3.0.0-beta.18", 77 "codelyzer": "~2.0.0-beta.4", 78 "copy-webpack-plugin": "^4.0.0", 79 "css-loader": "^0.26.0", 80 "exports-loader": "^0.6.3", 81 "expose-loader": "^0.7.1", 82 "extract-text-webpack-plugin": "~2.0.0-rc.2", 83 "file-loader": "^0.10.0", 84 "find-root": "^1.0.0", 85 "gh-pages": "^0.12.0", 86 "html-webpack-plugin": "^2.28.0", 87 "imports-loader": "^0.7.0", 88 "istanbul-instrumenter-loader": "1.2.0", 89 "json-loader": "^0.5.4", 90 "ng-router-loader": "^2.1.0", 91 "ngc-webpack": "1.1.0", 92 "npm-run-all": "^4.0.1", 93 "optimize-js-plugin": "0.0.4", 94 "parse5": "^3.0.1", 95 "protractor": "^4.0.14", 96 "raw-loader": "0.5.1", 97 "rimraf": "~2.5.4", 98 "sass-loader": "^4.1.1", 99 "script-ext-html-webpack-plugin": "^1.5.0", 100 "source-map-loader": "^0.1.5", 101 "string-replace-loader": "1.0.5", 102 "style-loader": "^0.13.1", 103 "to-string-loader": "^1.1.4", 104 "ts-node": "^2.0.0", 105 "tslib": "^1.5.0", 106 "tslint": "~4.4.2", 107 "tslint-loader": "^3.3.0", 108 "typedoc": "^0.5.3", 109 "typescript": "~2.1.5", 110 "url-loader": "^0.5.7", 111 "webpack": "2.2.0", 112 "webpack-dev-middleware": "^1.10.0", 113 "webpack-dev-server": "2.2.1", 114 "webpack-dll-bundles-plugin": "^1.0.0-beta.5", 115 "webpack-merge": "~2.6.1" 116 } 117 }package.json
package.json用於管理npm依賴,然後還需要tsconfig.json來配置TS,以及tsconfig.webpack.json來配合webpack編譯:
1 { 2 "compilerOptions": { 3 "target": "es5", 4 "module": "commonjs", 5 "moduleResolution": "node", 6 "emitDecoratorMetadata": true, 7 "experimentalDecorators": true, 8 "allowSyntheticDefaultImports": true, 9 "sourceMap": true, 10 "noEmit": true, 11 "noEmitHelpers": true, 12 "importHelpers": true, 13 "strictNullChecks": false, 14 "lib": [ 15 "dom", 16 "es6" 17 ], 18 "typeRoots": [ 19 "node_modules/@types", 20 "./typings/**/*.d.ts", 21 "../vendor/tslib/tslib.d.ts" 22 ], 23 "types": [ 24 "hammerjs", 25 "node", 26 "source-map", 27 "uglify-js", 28 "webpack" 29 ] 30 }, 31 "exclude": [ 32 "node_modules", 33 "dist" 34 ], 35 "awesomeTypescriptLoaderOptions": { 36 "forkChecker": true, 37 "useWebpackText": true 38 }, 39 "compileOnSave": false, 40 "buildOnSave": false, 41 "atom": { 42 "rewriteTsconfig": false 43 } 44 }tsconfig.json
1 { 2 "compilerOptions": { 3 "target": "es5", 4 "module": "es2015", 5 "moduleResolution": "node", 6 "emitDecoratorMetadata": true, 7 "experimentalDecorators": true, 8 "allowSyntheticDefaultImports": true, 9 "sourceMap": true, 10 "noEmit": true, 11 "noEmitHelpers": true, 12 "importHelpers": true, 13 "strictNullChecks": false, 14 "lib": [ 15 "es2015", 16 "dom" 17 ], 18 "typeRoots": [ 19 "node_modules/@types" 20 ], 21 "types": [ 22 "hammerjs", 23 "node" 24 ] 25 }, 26 "exclude": [ 27 "node_modules", 28 "dist", 29 "src/**/*.spec.ts", 30 "src/**/*.e2e.ts" 31 ], 32 "awesomeTypescriptLoaderOptions": { 33 "forkChecker": true, 34 "useWebpackText": true 35 }, 36 "angularCompilerOptions": { 37 "genDir": "./compiled", 38 "skipMetadataEmit": true 39 }, 40 "compileOnSave": false, 41 "buildOnSave": false, 42 "atom": { 43 "rewriteTsconfig": false 44 } 45 }tsconfig.webpack.json
然後是webpack的配置文件,入口為webpack.config.js:
1 // Look in ./config folder for webpack.dev.js 2 switch (process.env.NODE_ENV) { 3 case 'prod': 4 case 'production': 5 module.exports = require('./config/webpack.prod')({env: 'production'}); 6 break; 7 case 'dev': 8 case 'development': 9 default: 10 module.exports = require('./config/webpack.dev')({env: 'development'}); 11 }webpack.config.js
此配置文件將根據運行編譯時的參數決定使用開發環境的編譯方式還是生產環境的編譯方式,具體的編譯配置就不貼上來了,可以到文末給出的github地址查看整個項目。
4. angular2的配置
第3節的配置是項目npm依賴、TypeScript以及webpack的配置,給整個項目提供了依賴,並幫助編譯以後會寫的實際項目代碼,與angular2的關係其實不大,但是是angular2項目運行的前提。現在要來配置angular2了,以webpack作為模塊化工具的話,需要一個入口文件index.html以及幾個入口腳本:
/* * Angular bootstraping */ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { decorateModuleRef } from './app/environment'; import { bootloader } from '@angularclass/hmr'; /* * App Module * our top level module that holds all of our components */ import { AppModule } from './app'; /* * Bootstrap our Angular app with a top level NgModule */ export function main(): Promise<any> { return platformBrowserDynamic() .bootstrapModule(AppModule) .then(decorateModuleRef) .catch((err) => console.error(err)); } // needed for hmr // in prod this is replace for document ready bootloader(main);main.browser.ts
1 // TODO(gdi2290): switch to DLLs 2 3 // Polyfills 4 5 // import 'ie-shim'; // Internet Explorer 9 support 6 7 // import 'core-js/es6'; 8 // Added parts of es6 which are necessary for your project or your browser support requirements. 9 import 'core-js/es6/symbol'; 10 import 'core-js/es6/object'; 11 import 'core-js/es6/function'; 12 import 'core-js/es6/parse-int'; 13 import 'core-js/es6/parse-float'; 14 import 'core-js/es6/number'; 15 import 'core-js/es6/math'; 16 import 'core-js/es6/string'; 17 import 'core-js/es6/date'; 18 import 'core-js/es6/array'; 19 import 'core-js/es6/regexp'; 20 import 'core-js/es6/map'; 21 import 'core-js/es6/set'; 22 import 'core-js/es6/weak-map'; 23 import 'core-js/es6/weak-set'; 24 import 'core-js/es6/typed'; 25 import 'core-js/es6/reflect'; 26 // see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709 27 // import 'core-js/es6/promise'; 28 29 import 'core-js/es7/reflect'; 30 import 'zone.js/dist/zone'; 31 32 if ('production' === ENV) { 33 // Production 34 35 } else { 36 37 // Development 38 Error.stackTraceLimit = Infinity; 39 40 /* tslint:disable no-var-requires */ 41 require('zone.js/dist/long-stack-trace-zone'); 42 43 }polyfills.browser.ts
前者的作用是引導angular2程式的運行,後者的作用是管理angular2的所有依賴(由於angular2使用了很多ES新特性,所以需要一些依賴來擴展不支持新特性的瀏覽器的功能)。
實際代碼可能還需要有aot模式的引導文件(預編譯模式,更適用於生產環境,效率高非常多),以及一個自定義的類型聲明文件(幫助編寫TS代碼)。
5. 實際要寫的代碼——app目錄與assets目錄
配置好所有東西後,就輪到親手來寫angular2代碼了,專門新建一個app目錄來存放這些代碼,以及一個assets文件來存放靜態資源。
一個最簡單的angular2項目需要以下幾個文件:
1 import { BrowserModule } from '@angular/platform-browser'; 2 import { NgModule } from '@angular/core'; 3 4 import { AppComponent } from './app.component'; 5 import { ENV_PROVIDERS } from './environment'; 6 7 @NgModule({ 8 bootstrap: [ AppComponent ], 9 declarations: [ AppComponent], 10 imports: [ 11 BrowserModule 12 ], 13 providers: [ ENV_PROVIDERS ] 14 }) 15 export class AppModule { }app.module.ts
1 import { 2 Component, OnInit, ViewEncapsulation 3 } from '@angular/core'; 4 5 @Component({ 6 selector: 'my-app', 7 template: `<h1>Hello World!</h1>`, 8 }) 9 export class AppComponent implements OnInit { 10 11 public ngOnInit() { 12 console.log('load app.component'); 13 } 14 }app.component.ts
一個是根模塊一個是根組件,在此先不提angular2具體語法,先把項目成功運行起來為重。
為了讓webpack找到我們的angular2代碼,以及成功引導angular2項目,還必須添加一個環境文件以及一個索引文件:
1 // App 2 export * from './app.module';index.ts
1 // Angular 2 2 import { 3 enableDebugTools, 4 disableDebugTools 5 } from '@angular/platform-browser'; 6 import { 7 ApplicationRef, 8 enableProdMode 9 } from '@angular/core'; 10 // Environment Providers 11 let PROVIDERS: any[] = [ 12 // common env directives 13 ]; 14 15 // Angular debug tools in the dev console 16 // https://github.com/angular/angular/blob/86405345b781a9dc2438c0fbe3e9409245647019/TOOLS_JS.md 17 let _decorateModuleRef = <T>(value: T): T => { return value; }; 18 19 if ('production' === ENV) { 20 enableProdMode(); 21 22 // Production 23 _decorateModuleRef = (modRef: any) => { 24 disableDebugTools(); 25 26 return modRef; 27 }; 28 29 PROVIDERS = [ 30 ...PROVIDERS, 31 // custom providers in production 32 ]; 33 34 } else { 35 36 _decorateModuleRef = (modRef: any) => { 37 const appRef = modRef.injector.get(ApplicationRef); 38 const cmpRef = appRef.components[0]; 39 40 let _ng = (<any> window).ng; 41 enableDebugTools(cmpRef); 42 (<any> window).ng.probe = _ng.probe; 43 (<any> window).ng.coreTokens = _ng.coreTokens; 44 return modRef; 45 }; 46 47 // Development 48 PROVIDERS = [ 49 ...PROVIDERS, 50 // custom providers in development 51 ]; 52 53 } 54 55 export const decorateModuleRef = _decorateModuleRef; 56 57 export const ENV_PROVIDERS = [ 58 ...PROVIDERS 59 ];environment.ts
下麵是現在的文件目錄結構:
現在只要先運行 npm install 安裝好所有npm包,然後運行指令 npm run server:dev 就可以運行起第一個angular2項目了!
後記:
此angular2 demo的配置有使用到AngularClass的hmr插件,並且搭建的目的以學習與總結為主,實際開發中還需要配置單元測試等東西,可以直接查看AngularClass的angular-webpack-starter開源項目,其給出了一套非常完善的angular2啟動項目,值得花費一些時間來看懂。
最後給出此demo的github地址:
https://github.com/yitimo/angular2-demo-yitim