Angular2 + Webpack项目搭建Demo
本文将从头开始编写实际的代码来完成一个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:
{ "name": "angular2-demo-yitim", "version": "1.0.0", "description": "angular2 demo by yitim", "keywords": [ "angular2", "webpack", "typescript" ], "author": "yitim <[email protected]>", "license": "MIT", "scripts": { "build:aot:prod": "npm run clean:dist && npm run clean:aot && webpack --config config/webpack.prod.js --progress --profile --bail", "build:aot": "npm run build:aot:prod", "build:dev": "npm run clean:dist && webpack --config config/webpack.dev.js --progress --profile", "build:docker": "npm run build:prod && docker build -t angular2-webpack-start:latest .", "build:prod": "npm run clean:dist && webpack --config config/webpack.prod.js --progress --profile --bail", "build": "npm run build:dev", "clean:dll": "npm run rimraf -- dll", "clean:aot": "npm run rimraf -- compiled", "clean:dist": "npm run rimraf -- dist", "clean:install": "npm set progress=false && npm install", "clean": "npm cache clean && npm run rimraf -- node_modules doc coverage dist compiled dll", "docs": "npm run typedoc -- --options typedoc.json --exclude '**/*.spec.ts' ./src/", "lint": "npm run tslint \"src/**/*.ts\"", "server:dev:hmr": "npm run server:dev -- --inline --hot", "server:dev": "webpack-dev-server --config config/webpack.dev.js --open --progress --profile --watch --content-base src/", "server:prod": "http-server dist -c-1 --cors", "server": "npm run server:dev", "start": "npm run server:dev", "tslint": "tslint", "version": "npm run build", "watch:dev:hmr": "npm run watch:dev -- --hot", "watch:dev": "npm run build:dev -- --watch", "watch:prod": "npm run build:prod -- --watch", "watch": "npm run watch:dev", "webdriver-manager": "webdriver-manager", "webdriver:start": "npm run webdriver-manager start", "webdriver:update": "webdriver-manager update", "webpack-dev-server": "webpack-dev-server", "webpack": "webpack" }, "dependencies": { "@angular/common": "2.4.6", "@angular/compiler": "2.4.6", "@angular/core": "2.4.6", "@angular/forms": "2.4.6", "@angular/http": "2.4.6", "@angular/platform-browser": "2.4.6", "@angular/platform-browser-dynamic": "2.4.6", "@angular/platform-server": "2.4.6", "@angular/router": "3.4.6", "@angularclass/conventions-loader": "^1.0.2", "@angularclass/hmr": "~1.2.2", "@angularclass/hmr-loader": "~3.0.2", "core-js": "^2.4.1", "crypto-browserify": "^3.11.0", "crypto-js": "^3.1.9-1", "http-server": "^0.9.0", "ie-shim": "^0.1.0", "reflect-metadata": "^0.1.9", "rxjs": "5.0.2", "zone.js": "0.7.6" }, "devDependencies": { "@angular/compiler-cli": "2.4.6", "@types/hammerjs": "^2.0.33", "@types/node": "^7.0.0", "@types/selenium-webdriver": "~2.53.39", "@types/source-map": "^0.5.0", "@types/uglify-js": "^2.0.27", "@types/webpack": "^2.0.0", "add-asset-html-webpack-plugin": "^1.0.2", "angular2-template-loader": "^0.6.0", "assets-webpack-plugin": "^3.5.1", "awesome-typescript-loader": "~3.0.0-beta.18", "codelyzer": "~2.0.0-beta.4", "copy-webpack-plugin": "^4.0.0", "css-loader": "^0.26.0", "exports-loader": "^0.6.3", "expose-loader": "^0.7.1", "extract-text-webpack-plugin": "~2.0.0-rc.2", "file-loader": "^0.10.0", "find-root": "^1.0.0", "gh-pages": "^0.12.0", "html-webpack-plugin": "^2.28.0", "imports-loader": "^0.7.0", "istanbul-instrumenter-loader": "1.2.0", "json-loader": "^0.5.4", "ng-router-loader": "^2.1.0", "ngc-webpack": "1.1.0", "npm-run-all": "^4.0.1", "optimize-js-plugin": "0.0.4", "parse5": "^3.0.1", "protractor": "^4.0.14", "raw-loader": "0.5.1", "rimraf": "~2.5.4", "sass-loader": "^4.1.1", "script-ext-html-webpack-plugin": "^1.5.0", "source-map-loader": "^0.1.5", "string-replace-loader": "1.0.5", "style-loader": "^0.13.1", "to-string-loader": "^1.1.4", "ts-node": "^2.0.0", "tslib": "^1.5.0", "tslint": "~4.4.2", "tslint-loader": "^3.3.0", "typedoc": "^0.5.3", "typescript": "~2.1.5", "url-loader": "^0.5.7", "webpack": "2.2.0", "webpack-dev-middleware": "^1.10.0", "webpack-dev-server": "2.2.1", "webpack-dll-bundles-plugin": "^1.0.0-beta.5", "webpack-merge": "~2.6.1" } }package.json
package.json用于管理npm依赖,然后还需要tsconfig.json来配置TS,以及tsconfig.webpack.json来配合webpack编译:
{ "compilerOptions": { "target": "es5", "module": "commonjs", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "sourceMap": true, "noEmit": true, "noEmitHelpers": true, "importHelpers": true, "strictNullChecks": false, "lib": [ "dom", "es6" ], "typeRoots": [ "node_modules/@types", "./typings/**/*.d.ts", "../vendor/tslib/tslib.d.ts" ], "types": [ "hammerjs", "node", "source-map", "uglify-js", "webpack" ] }, "exclude": [ "node_modules", "dist" ], "awesomeTypescriptLoaderOptions": { "forkChecker": true, "useWebpackText": true }, "compileOnSave": false, "buildOnSave": false, "atom": { "rewriteTsconfig": false } }tsconfig.json
{ "compilerOptions": { "target": "es5", "module": "es2015", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "sourceMap": true, "noEmit": true, "noEmitHelpers": true, "importHelpers": true, "strictNullChecks": false, "lib": [ "es2015", "dom" ], "typeRoots": [ "node_modules/@types" ], "types": [ "hammerjs", "node" ] }, "exclude": [ "node_modules", "dist", "src/**/*.spec.ts", "src/**/*.e2e.ts" ], "awesomeTypescriptLoaderOptions": { "forkChecker": true, "useWebpackText": true }, "angularCompilerOptions": { "genDir": "./compiled", "skipMetadataEmit": true }, "compileOnSave": false, "buildOnSave": false, "atom": { "rewriteTsconfig": false } }tsconfig.webpack.json
然后是webpack的配置文件,入口为webpack.config.js:
// Look in ./config folder for webpack.dev.js switch (process.env.NODE_ENV) { case 'prod': case 'production': module.exports = require('./config/webpack.prod')({env: 'production'}); break; case 'dev': case 'development': default: module.exports = require('./config/webpack.dev')({env: 'development'}); }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
// TODO(gdi2290): switch to DLLs // Polyfills // import 'ie-shim'; // Internet Explorer 9 support // import 'core-js/es6'; // Added parts of es6 which are necessary for your project or your browser support requirements. import 'core-js/es6/symbol'; import 'core-js/es6/object'; import 'core-js/es6/function'; import 'core-js/es6/parse-int'; import 'core-js/es6/parse-float'; import 'core-js/es6/number'; import 'core-js/es6/math'; import 'core-js/es6/string'; import 'core-js/es6/date'; import 'core-js/es6/array'; import 'core-js/es6/regexp'; import 'core-js/es6/map'; import 'core-js/es6/set'; import 'core-js/es6/weak-map'; import 'core-js/es6/weak-set'; import 'core-js/es6/typed'; import 'core-js/es6/reflect'; // see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709 // import 'core-js/es6/promise'; import 'core-js/es7/reflect'; import 'zone.js/dist/zone'; if ('production' === ENV) { // Production } else { // Development Error.stackTraceLimit = Infinity; /* tslint:disable no-var-requires */ require('zone.js/dist/long-stack-trace-zone'); }polyfills.browser.ts
前者的作用是引导angular2程序的运行,后者的作用是管理angular2的所有依赖(由于angular2使用了很多ES新特性,所以需要一些依赖来扩展不支持新特性的浏览器的功能)。
实际代码可能还需要有aot模式的引导文件(预编译模式,更适用于生产环境,效率高非常多),以及一个自定义的类型声明文件(帮助编写TS代码)。
5. 实际要写的代码——app目录与assets目录
配置好所有东西后,就轮到亲手来写angular2代码了,专门新建一个app目录来存放这些代码,以及一个assets文件来存放静态资源。
一个最简单的angular2项目需要以下几个文件:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { ENV_PROVIDERS } from './environment'; @NgModule({ bootstrap: [ AppComponent ], declarations: [ AppComponent], imports: [ BrowserModule ], providers: [ ENV_PROVIDERS ] }) export class AppModule { }app.module.ts
import { Component, OnInit, ViewEncapsulation } from '@angular/core'; @Component({ selector: 'my-app', template: `<h1>Hello World!</h1>`, }) export class AppComponent implements OnInit { public ngOnInit() { console.log('load app.component'); } }app.component.ts
一个是根模块一个是根组件,在此先不提angular2具体语法,先把项目成功运行起来为重。
为了让webpack找到我们的angular2代码,以及成功引导angular2项目,还必须添加一个环境文件以及一个索引文件:
// App export * from './app.module';index.ts
// Angular 2 import { enableDebugTools, disableDebugTools } from '@angular/platform-browser'; import { ApplicationRef, enableProdMode } from '@angular/core'; // Environment Providers let PROVIDERS: any[] = [ // common env directives ]; // Angular debug tools in the dev console // https://github.com/angular/angular/blob/86405345b781a9dc2438c0fbe3e9409245647019/TOOLS_JS.md let _decorateModuleRef = <T>(value: T): T => { return value; }; if ('production' === ENV) { enableProdMode(); // Production _decorateModuleRef = (modRef: any) => { disableDebugTools(); return modRef; }; PROVIDERS = [ ...PROVIDERS, // custom providers in production ]; } else { _decorateModuleRef = (modRef: any) => { const appRef = modRef.injector.get(ApplicationRef); const cmpRef = appRef.components[0]; let _ng = (<any> window).ng; enableDebugTools(cmpRef); (<any> window).ng.probe = _ng.probe; (<any> window).ng.coreTokens = _ng.coreTokens; return modRef; }; // Development PROVIDERS = [ ...PROVIDERS, // custom providers in development ]; } export const decorateModuleRef = _decorateModuleRef; export const ENV_PROVIDERS = [ ...PROVIDERS ];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