├── .gitignore ├── .travis.yml ├── README.md ├── constants.js ├── ecosystem.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── app.ts │ ├── greeter.ts │ ├── home.css │ ├── home.html │ ├── home.spec.ts │ └── home.ts ├── boot_browser.ts ├── boot_worker.ts ├── boot_worker_app.ts ├── polyfills.ts └── server │ ├── app.ts │ ├── ng.html │ └── ng.ts ├── test ├── e2e │ └── app.spec.js └── unit.spec.ts ├── tools ├── build.js ├── dev.js ├── lint.js └── prod.js ├── tsconfig.json ├── tslint.json ├── typings.d.ts ├── typings.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | logs 4 | typings 5 | npm-debug.log 6 | test.sh 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 5.4.0 5 | 6 | # before_install: 7 | # - export CHROME_BIN=/usr/bin/google-chrome 8 | # - export DISPLAY=:99.0 9 | # - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16" 10 | # - sudo apt-get update 11 | # - sudo apt-get install -y libappindicator1 fonts-liberation 12 | # - wget http://deepin.mirror.garr.it/mirrors/deepin/pool/non-free/g/google-chrome-stable/google-chrome-stable_47.0.2526.80-1_amd64.deb 13 | # - sudo dpkg -i google-chrome*.deb 14 | # - npm install semver 15 | # - export NPM_V=$(npm -v) 16 | # - 'if node -e "process.exit(require(''semver'').gt(process.env.NPM_V, ''3.0.0''))"; then npm install -g npm@3.0.0; fi' 17 | 18 | install: 19 | - npm install 20 | 21 | before_script: 22 | # - "npm run webdriver:start &" 23 | 24 | script: 25 | # Linting 26 | - npm run lint 27 | 28 | # Minification tests 29 | - npm run build 30 | - "if [ $(du -k ./dist/public/vendor.js | cut -f1) -gt 950 ]; then exit 1; fi" 31 | - "if [ $(du -k ./dist/public/browser.js | cut -f1) -gt 10 ]; then exit 1; fi" 32 | - "if [ $(du -k ./dist/public/worker.js | cut -f1) -gt 5 ]; then exit 1; fi" 33 | - "if [ $(du -k ./dist/public/worker_app.js | cut -f1) -gt 12 ]; then exit 1; fi" 34 | 35 | # Starting/stopping 36 | - npm start 37 | - npm stop 38 | 39 | # Unit tests 40 | - npm run unit 41 | 42 | # - "kill $(lsof -t -i:3000) | true" 43 | 44 | # - NG2_WW=true NG2_SS=true $(npm bin)/pm2 start ecosystem.json 45 | # - npm run e2e 46 | # - $(npm bin)/pm2 delete ecosystem.json; 47 | 48 | # - "kill $(lsof -t -i:3000) | true" 49 | 50 | # - NG2_WW=false NG2_SS=true $(npm bin)/pm2 start ecosystem.json 51 | # - npm run e2e 52 | # - $(npm bin)/pm2 delete ecosystem.json; 53 | 54 | # - "kill $(lsof -t -i:3000) | true" 55 | 56 | # - NG2_WW=false NG2_SS=false $(npm bin)/pm2 start ecosystem.json 57 | # - npm run e2e 58 | # - $(npm bin)/pm2 delete ecosystem.json; 59 | 60 | # - "kill $(lsof -t -i:3000) | true" 61 | 62 | # - "NG2_WW=true NG2_SS=true npm run dev &" 63 | # - DEV_SERVER_PID=$! 64 | # - sleep 5 65 | # - npm run e2e 66 | # - kill $DEV_SERVER_PID 67 | 68 | # - "kill $(lsof -t -i:3000) | true" 69 | 70 | # - "NG2_WW=false NG2_SS=true npm run dev &" 71 | # - DEV_SERVER_PID=$! 72 | # - sleep 5 73 | # - npm run e2e 74 | # - kill $DEV_SERVER_PID 75 | 76 | # - "kill $(lsof -t -i:3000) | true" 77 | 78 | # - "NG2_WW=false NG2_SS=false npm run dev &" 79 | # - DEV_SERVER_PID=$! 80 | # - sleep 5 81 | # - npm run e2e 82 | # - kill $DEV_SERVER_PID 83 | 84 | # - "kill $(lsof -t -i:3000) | true" 85 | 86 | # - npm restart 87 | # - npm test 88 | # - npm stop 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular2 Universal Starter Kit 2 | 3 | [![Build Status](https://travis-ci.org/alexpods/angular2-universal-starter.svg?branch=master)](https://travis-ci.org/alexpods/angular2-universal-starter) 4 | [![Dependency Status](https://david-dm.org/alexpods/angular2-universal-starter.svg)](https://david-dm.org/alexpods/angular2-universal-starter) 5 | [![devDependency Status](https://david-dm.org/alexpods/angular2-universal-starter/dev-status.svg)](https://david-dm.org/alexpods/angular2-universal-starter#info=devDependencies) 6 | [![Issue Stats](http://issuestats.com/github/alexpods/angular2-universal-starter/badge/pr?style=flat-square)](http://issuestats.com/github/alexpods/angular2-universal-starter) 7 | [![Issue Stats](http://issuestats.com/github/alexpods/angular2-universal-starter/badge/issue?style=flat-square)](http://issuestats.com/github/alexpods/angular2-universal-starter) 8 | [![Join the chat at https://gitter.im/alexpods/angular2-universal-starter](https://badges.gitter.im/alexpods/angular2-universal-starter.svg)](https://gitter.im/alexpods/angular2-universal-starter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 9 | 10 | 11 | #What we've got here 12 | 13 | - [Server Side rendering](https://angularu.com/VideoSession/2015sf/angular-2-server-rendering) for instant page loading 14 | - Entire Angular2 application is running in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) (UI always will be smooth) 15 | - [Preboot](https://www.npmjs.com/package/preboot) to catch browser events before Angular2 is ready to work (you can experiment with its options [here](https://github.com/alexpods/angular2-universal-starter/blob/master/constants.js#L25)) 16 | - [Webpack](https://webpack.github.io/) and its awesome [Code Splitting](https://webpack.github.io/docs/code-splitting.html) feature which allows us to lazy load parts of an application. 17 | - Live Reloading, a browser will be reloaded on any change in server or browser code. It works well for both a main thread and web workers. 18 | - [Typescript](http://www.typescriptlang.org/) with [Typings](https://github.com/typings/typings) 19 | - [PostCSS](https://github.com/postcss/postcss) with [CSSNext](http://cssnext.io/) 20 | - Linting with [TSLint](http://palantir.github.io/tslint/) 21 | - [Express](http://expressjs.com/) - de facto standard for Node.js web apps. 22 | - [PM2](http://pm2.keymetrics.io/) - most advanced Node.js process manager 23 | - Unit testing with [Karma](http://karma-runner.github.io/) 24 | - End-to-End testing with [Protractor](https://angular.github.io/protractor) 25 | 26 | ##Requirements 27 | 28 | - **`node`** >= **4.2.0** 29 | - **`npm`** >= **3.0.0** 30 | 31 | #Quick start 32 | ```bash 33 | # clone the repo without git history 34 | git clone --depth 1 https://github.com/alexpods/angular2-universal-starter.git 35 | 36 | # change current directory to angular2-universal-starter 37 | cd angular2-universal-starter 38 | 39 | # install dependencies 40 | npm install 41 | 42 | # run the production server 43 | npm start 44 | ``` 45 | Go to [http://localhost:3000](http://localhost:3000) in your browser. 46 | 47 | You may want to stop or restart the production server: 48 | ```bash 49 | # stop the production server 50 | npm stop 51 | 52 | # restart the production server 53 | npm restart 54 | ``` 55 | 56 | ## Development with Live Reloading 57 | ```bash 58 | # make sure that the production server is not running 59 | npm stop 60 | 61 | # run the development server with live reloading support 62 | npm run dev 63 | ``` 64 | 65 | The development server will watch for any changes, make rebuilds and reload a browser. All built code will be kept in 66 | memory, so `dist` folder will not be generated (**all** means code for **both** **client** and **server** sides). 67 | 68 | ##Turning server side rendering and web workers on/off 69 | You can optionally turn server side rendering or web workers suport on/off. You just need 70 | to change `HAS_SS` and `HAS_WW` in `constants.js` [here](https://github.com/alexpods/angular2-universal-starter/blob/master/constants.js#L14): 71 | 72 | ```js 73 | // ... 74 | 75 | // Server side rendering. Set it to `false` to turn it of. 76 | exports.HAS_SS = 'NG2_SS' in process.env ? process.env.NG2_SS === 'true' : true; 77 | // For example: 78 | // exports.HAS_SS = false; 79 | 80 | // Web workers support. Set it to `false` to turn it of. 81 | exports.HAS_WW = 'NG2_WW' in process.env ? process.env.NG2_WW === 'true' : true; 82 | // For example: 83 | // exports.HAS_WW = 'NG2_WW' in process.env ? process.env.NG2_WW === 'true' : false; 84 | 85 | //... 86 | ``` 87 | Then you need to restart the server to apply the changes: 88 | ```bash 89 | # for production server 90 | npm restart 91 | 92 | # for development server - stop its process and run it again 93 | npm run dev 94 | ``` 95 | 96 | #Building 97 | ```bash 98 | # build the project 99 | npm run build 100 | 101 | # build the project and start watching for its changes 102 | npm run build:watch 103 | ``` 104 | 105 | #Linting 106 | ```bash 107 | # check the project (source files) 108 | npm run lint 109 | 110 | # check the project and start watching for its changes 111 | npm run lint:watch 112 | ``` 113 | If you're not agree with the default rules ([`tslint.json`](https://github.com/alexpods/angular2-universal-starter/blob/master/tslint.json)), feel free to tell me about it. 114 | 115 | #Testing 116 | The next command will run both unit and end-to-end tests. 117 | 118 | For end-to-end tests you need to start Selenium Server first (see [End-to-End Testing](#end-to-end-testing)). 119 | ```bash 120 | # run all tests (single run) 121 | npm test 122 | ``` 123 | 124 | ##Unit Testing 125 | ```bash 126 | # run unit tests (single run) 127 | npm run unit 128 | 129 | # run unit tests and start watch for changes 130 | npm run unit:watch 131 | 132 | # run unit tests for specified directory (path must be relative to root directory) 133 | # currently you can specify paths only for "src" directory 134 | npm run unit src/app 135 | 136 | # run unit tests for specified file and start watch for changes 137 | npm run unit:watch src/app/home.spec.ts 138 | ``` 139 | 140 | ##End-to-End Testing 141 | For end-to-end tests you nedd to start Selenium Server (webdriver) first. 142 | ```bash 143 | # start Selenium Server (webdriver) 144 | npm run webdriver:start 145 | 146 | # run end-to-end test (single run) 147 | npm run e2e 148 | ``` 149 | 150 | #Cleaning 151 | ```bash 152 | # remove "dist" and "logs" folders 153 | npm run clean 154 | 155 | # remove "dist" folder 156 | npm run clean:dist 157 | 158 | # remove "logs" folder 159 | npm run clean:logs 160 | ``` 161 | 162 | #License 163 | The MIT License (MIT) 164 | 165 | Copyright (c) 2016 Aleksey Podskrebyshev 166 | 167 | Permission is hereby granted, free of charge, to any person obtaining a copy 168 | of this software and associated documentation files (the "Software"), to deal 169 | in the Software without restriction, including without limitation the rights 170 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 171 | copies of the Software, and to permit persons to whom the Software is 172 | furnished to do so, subject to the following conditions: 173 | 174 | The above copyright notice and this permission notice shall be included in all 175 | copies or substantial portions of the Software. 176 | 177 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 178 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 179 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 180 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 181 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 182 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 183 | SOFTWARE. 184 | 185 | -------------------------------------------------------------------------------- /constants.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | exports.ROOT_DIR = path.resolve(__dirname); 5 | exports.SRC_DIR = path.resolve(exports.ROOT_DIR, 'src'); 6 | exports.DIST_DIR = path.resolve(exports.ROOT_DIR, 'dist'); 7 | exports.PUBLIC_DIR = path.resolve(exports.DIST_DIR, 'public'); 8 | exports.PRIVATE_DIR = path.resolve(exports.DIST_DIR, 'private'); 9 | exports.SERVER_DIR = path.resolve(exports.SRC_DIR, 'server'); 10 | 11 | exports.HOST = process.env.HOST || 'localhost'; 12 | exports.PORT = +process.env.PORT || 3000; 13 | exports.PROTOCOL = process.env.PROTOCOL || 'http'; 14 | 15 | exports.HAS_SS = 'NG2_SS' in process.env ? process.env.NG2_SS === 'true' : true; 16 | exports.HAS_WW = 'NG2_WW' in process.env ? process.env.NG2_WW === 'true' : true; 17 | 18 | exports.VENDOR_NAME = 'vendor'; 19 | exports.SERVER_NAME = 'server'; 20 | exports.BROWSER_NAME = 'browser'; 21 | exports.WORKER_NAME = 'worker'; 22 | exports.WORKER_APP_NAME = 'worker_app'; 23 | 24 | exports.SERVER_SOURCE_PATH = path.resolve(exports.SRC_DIR, 'server/app.ts'); 25 | exports.BROWSER_SOURCE_PATH = path.resolve(exports.SRC_DIR, 'boot_browser.ts'); 26 | exports.WORKER_SOURCE_PATH = path.resolve(exports.SRC_DIR, 'boot_worker.ts'); 27 | exports.WORKER_APP_SOURCE_PATH = path.resolve(exports.SRC_DIR, 'boot_worker_app.ts'); 28 | 29 | exports.VENDOR_DLL_MANIFEST_FILE = 'vendor-manifest.json'; 30 | exports.VENDOR_DLL_MANIFEST_PATH = path.resolve(exports.PUBLIC_DIR, exports.VENDOR_DLL_MANIFEST_FILE); 31 | 32 | exports.PREBOOT = { 33 | appRoot: 'app', 34 | freeze: { name: 'spinner' }, 35 | replay: 'rerender', 36 | buffer: true, 37 | debug: true, 38 | uglify: false, 39 | }; 40 | -------------------------------------------------------------------------------- /ecosystem.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-universal-starter", 3 | "script": "./tools/prod.js", 4 | "watch": "./dist/private", 5 | "log_date_format": "YYYY-MM-DD HH:mm Z", 6 | "error_file": "./logs/errors.log", 7 | "out_file": "./logs/output.log", 8 | "autorestart": true, 9 | "env": {} 10 | } -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpackConfig = require('./webpack.config.js'); 3 | 4 | const WEBPACK_TESTING_CONFIG = webpackConfig.TESTING_CONFIG; 5 | 6 | function getTestPath(args) { 7 | for (var i = 0; i < args.length; ++i) { 8 | if (args[i] === '--path--') { 9 | return path.relative(__dirname, args[i+1] || ''); 10 | } 11 | } 12 | return ''; 13 | } 14 | 15 | module.exports = function(config) { 16 | config.set({ 17 | 18 | // base path that will be used to resolve all patterns (eg. files, exclude) 19 | basePath: '', 20 | 21 | 22 | // frameworks to use 23 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 24 | frameworks: ['jasmine'], 25 | 26 | 27 | // list of files / patterns to load in the browser 28 | files: [ 29 | 'test/unit.spec.ts' 30 | ], 31 | 32 | 33 | // list of files to exclude 34 | exclude: [ 35 | ], 36 | 37 | client: { 38 | args: [{ 39 | testPath: getTestPath(process.argv) 40 | }], 41 | // other client-side config 42 | captureConsole: true 43 | }, 44 | 45 | 46 | // preprocess matching files before serving them to the browser 47 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 48 | preprocessors: { 49 | 'test/unit.spec.ts': ['webpack', 'sourcemap'] 50 | }, 51 | 52 | webpack: WEBPACK_TESTING_CONFIG, 53 | 54 | webpackMiddleware: WEBPACK_TESTING_CONFIG['devServer'], 55 | 56 | // test results reporter to use 57 | // possible values: 'dots', 'progress' 58 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 59 | reporters: ['progress'], 60 | 61 | 62 | // web server port 63 | port: 9876, 64 | 65 | 66 | // enable / disable colors in the output (reporters and logs) 67 | colors: true, 68 | 69 | 70 | // level of logging 71 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 72 | logLevel: config.LOG_INFO, 73 | 74 | 75 | // enable / disable watching file and executing tests whenever any file changes 76 | autoWatch: true, 77 | 78 | 79 | // start these browsers 80 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 81 | browsers: ['PhantomJS'], 82 | 83 | 84 | // Continuous Integration mode 85 | // if true, Karma captures browsers, runs the tests and exits 86 | singleRun: false, 87 | 88 | // Concurrency level 89 | // how many browser should be started simultaneous 90 | concurrency: Infinity 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-universal-starter", 3 | "version": "0.5.0", 4 | "description": "Enjoy Server Side rendering and Web Workers in your Angular2 Application", 5 | "author": "Alex Podskrebyshev ", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/alexpods/angular2-universal-starter.git" 10 | }, 11 | "scripts": { 12 | "postinstall": "typings install && npm run webdriver:update", 13 | "reinstall": "npm run clean:dist && rimraf node_modules && npm install", 14 | "dev": "node tools/dev.js", 15 | "prod": "node tools/prod.js", 16 | "prebuild": "npm run clean:dist", 17 | "build": "node tools/build.js", 18 | "build:watch": "node tools/build.js --watch", 19 | "lint": "node tools/lint.js", 20 | "lint:watch": "node tools/lint.js --watch", 21 | "prestart": "npm run build", 22 | "start": "pm2 start ecosystem.json", 23 | "restart": "npm stop && npm start", 24 | "stop": "pm2 delete ecosystem.json", 25 | "test": "npm run unit && npm run e2e", 26 | "unit": "karma start --single-run --path--", 27 | "unit:watch": "karma start --path--", 28 | "e2e": "protractor protractor.conf.js", 29 | "clean": "rimraf dist logs", 30 | "clean:dist": "rimraf dist", 31 | "clean:logs": "rimraf logs", 32 | "status": "pm2 show $(node -e \"console.log(require('./ecosystem.json').name)\")", 33 | "logs": "pm2 logs $(node -e \"console.log(require('./ecosystem.json').name)\")", 34 | "webdriver:update": "webdriver-manager update", 35 | "webdriver:start": "webdriver-manager start", 36 | "pm2": "pm2", 37 | "webpack": "webpack", 38 | "typings": "typings", 39 | "karma": "karma", 40 | "protractor": "protractor", 41 | "webdriver": "webdriver-manager" 42 | }, 43 | "engines": { 44 | "node": ">= 4.2.0", 45 | "npm": ">= 3" 46 | }, 47 | "dependencies": { 48 | "@angular/common": "2.0.0-rc.4", 49 | "@angular/compiler": "2.0.0-rc.4", 50 | "@angular/core": "2.0.0-rc.4", 51 | "@angular/http": "2.0.0-rc.4", 52 | "@angular/platform-browser": "2.0.0-rc.4", 53 | "@angular/platform-browser-dynamic": "2.0.0-rc.4", 54 | "@angular/platform-server": "2.0.0-rc.4", 55 | "angular2-universal": "~0.104.5", 56 | "core-js": "^2.4.0", 57 | "css": "^2.2.1", 58 | "express": "^4.13.3", 59 | "parse5": "^1.5.1", 60 | "preboot": "^2.1.2", 61 | "rxjs": "5.0.0-beta.6", 62 | "serve-static": "^1.10.2", 63 | "zone.js": "0.6.12" 64 | }, 65 | "devDependencies": { 66 | "chalk": "^1.1.1", 67 | "chokidar": "^1.4.2", 68 | "jasmine-core": "^2.4.1", 69 | "json-loader": "^0.5.4", 70 | "karma": "^0.13.21", 71 | "karma-chrome-launcher": "^0.2.2", 72 | "karma-jasmine": "^0.3.6", 73 | "karma-phantomjs-launcher": "^1.0.0", 74 | "karma-sourcemap-loader": "^0.3.7", 75 | "karma-webpack": "^1.7.0", 76 | "memory-fs": "^0.3.0", 77 | "phantomjs-prebuilt": "^2.1.4", 78 | "pm2": "^1.0.1", 79 | "postcss-cssnext": "^2.4.0", 80 | "postcss-loader": "^0.10.0", 81 | "protractor": "^3.1.1", 82 | "raw-loader": "^0.5.1", 83 | "rimraf": "^2.5.2", 84 | "ts-loader": "^0.8.0", 85 | "tsd": "^0.6.5", 86 | "tslint": "^3.4.0", 87 | "typescript": "~1.8.10", 88 | "typings": "0.6.8", 89 | "webpack": "1.13.1", 90 | "webpack-dev-server": "1.14.1" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | baseUrl: 'http://127.0.0.1:3000/', 3 | seleniumAddress: 'http://127.0.0.1:4444/wd/hub', 4 | framework: 'jasmine', 5 | suites: { 6 | app: 'test/e2e/app.spec.js' 7 | }, 8 | capabilities: { 9 | browserName: 'chrome', 10 | chromeOptions: { 11 | args: ['show-fps-counter=true'] 12 | } 13 | }, 14 | onPrepare: function() { 15 | browser.ignoreSynchronization = true; 16 | }, 17 | directConnect: true, 18 | useAllAngular2AppRoots: true 19 | }; 20 | -------------------------------------------------------------------------------- /src/app/app.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Home } from './home'; 3 | 4 | @Component({ 5 | selector: 'app', 6 | directives: [Home], 7 | template: ` 8 | 9 | `, 10 | }) 11 | export class App {} 12 | -------------------------------------------------------------------------------- /src/app/greeter.ts: -------------------------------------------------------------------------------- 1 | export function greet() { 2 | return 'Hello, Lazy Loading!!!'; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/home.css: -------------------------------------------------------------------------------- 1 | button { 2 | background-color:#44c767; 3 | border-radius:28px; 4 | border:1px solid #18ab29; 5 | display:inline-block; 6 | cursor:pointer; 7 | color:#ffffff; 8 | font-family:Arial; 9 | font-size:17px; 10 | padding:16px 31px; 11 | text-decoration:none; 12 | text-shadow:0px 1px 0px #2f6627; 13 | outline: none; 14 | } 15 | button:hover { 16 | background-color:#5cbf2a; 17 | } 18 | button:active { 19 | position:relative; 20 | top:1px; 21 | } 22 | 23 | p { 24 | font: 12pt/1.5 Helvetica; 25 | padding: 15px; 26 | color: #31708f; 27 | border: 1px solid #bce8f1; 28 | border-radius: 4px; 29 | background-color: #D9EDF8; 30 | } 31 | 32 | span { 33 | padding: 15px; 34 | color: #3c763d; 35 | border: 1px solid #d6e9c6; 36 | border-radius: 4px; 37 | background-color: #dff0d8; 38 | } -------------------------------------------------------------------------------- /src/app/home.html: -------------------------------------------------------------------------------- 1 |

Hello, {{ name }}!!!

2 |
3 |

4 | Try to push the button before angular2 will be loaded. 5 | Preboot will catch the "click" event and replay it later. 6 |

7 |
8 | 9 | {{ messagePreboot }} 10 |
11 |
12 |
13 |

14 | For big applications it’s not efficient to put all code into a single file, 15 | especially if some blocks of code are only required under some circumstances. It's much better 16 | to split your application into several parts and load them only if they're needed. 17 | That's where Webpack's Code Spliting is shinning. 18 | You just need to use its require.ensure() function to specify split points in your 19 | application and webpack will do the rest for you. 20 | Read more about code splitting and how to use it here. 21 | If you push the next button, the greeting message will be lazy loaded. 22 |

23 |
24 | 25 | {{ messageLazyLoading }} 26 |
27 |
-------------------------------------------------------------------------------- /src/app/home.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | it, 4 | expect, 5 | TestComponentBuilder, 6 | inject, 7 | tick, 8 | fakeAsync, 9 | } from '@angular/core/testing'; 10 | 11 | import { Home } from './home'; 12 | 13 | describe('Home', () => { 14 | it('should change name to "Angular" after 1s', fakeAsync(inject([TestComponentBuilder], (tcb) => { 15 | return tcb.createAsync(Home).then((fixture) => { 16 | const { componentInstance } = fixture; 17 | expect(componentInstance.name).toBe('World'); 18 | tick(1000); 19 | expect(componentInstance.name).toBe('Angular'); 20 | }); 21 | }))); 22 | 23 | it('should set message on button click', inject([TestComponentBuilder], (tcb) => { 24 | return tcb.createAsync(Home).then((fixture) => { 25 | const { componentInstance, nativeElement } = fixture; 26 | expect(componentInstance.messagePreboot).toBeFalsy(); 27 | nativeElement.querySelector('#check-preboot').click(); 28 | expect(componentInstance.messagePreboot).toBeTruthy(); 29 | }); 30 | })); 31 | 32 | it('should lazy load service', inject([TestComponentBuilder], (tcb) => { 33 | return tcb.createAsync(Home).then((fixture) => { 34 | const { componentInstance, nativeElement } = fixture; 35 | 36 | expect(componentInstance.messageLazyLoading).toBeFalsy(); 37 | nativeElement.querySelector('#check-lazyloading').click(); 38 | expect(componentInstance.messageLazyLoading).toBeFalsy(); 39 | 40 | return new Promise(resolve => setTimeout(resolve, 100)).then(() => { 41 | expect(componentInstance.messageLazyLoading).toBeTruthy(); 42 | }); 43 | }); 44 | })); 45 | }); 46 | -------------------------------------------------------------------------------- /src/app/home.ts: -------------------------------------------------------------------------------- 1 | declare var require: any; 2 | 3 | import { Component } from '@angular/core'; 4 | 5 | @Component({ 6 | selector: 'home', 7 | template: require('./home.html'), 8 | styles: [require('./home.css')] 9 | }) 10 | export class Home { 11 | name = 'World'; 12 | messagePreboot = ''; 13 | messageLazyLoading = ''; 14 | 15 | constructor() { 16 | setTimeout(() => this.name = 'Angular', 1000); 17 | } 18 | 19 | onCheckPreboot() { 20 | console.log('Check preboot'); 21 | this.messagePreboot = 'Preboot is working'; 22 | } 23 | 24 | onCheckLazyLoading() { 25 | require.ensure([], () => { 26 | const greeter = require('./greeter.ts'); 27 | this.messageLazyLoading = greeter.greet(); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/boot_browser.ts: -------------------------------------------------------------------------------- 1 | import './polyfills'; 2 | 3 | import { bootstrap } from '@angular/platform-browser-dynamic'; 4 | import { App } from './app/app'; 5 | 6 | bootstrap(App, []); 7 | -------------------------------------------------------------------------------- /src/boot_worker.ts: -------------------------------------------------------------------------------- 1 | import './polyfills'; 2 | 3 | import { bootstrapWorkerUi } from '@angular/platform-browser-dynamic'; 4 | 5 | const workerScriptUrl = URL.createObjectURL(new Blob([` 6 | var importScripts_ = this.importScripts; 7 | 8 | this.importScripts = function importScripts() { 9 | for (var i = 0, scripts = new Array(arguments.length); i < scripts.length; ++i) { 10 | var script = arguments[i]; 11 | 12 | if (script.indexOf('http:') !== 0 || script.indexOf('https:') !== 0) { 13 | script = '${window.location.origin}' + (script[0] === '/' ? script : '/' + script); 14 | } 15 | 16 | scripts[i] = script; 17 | } 18 | 19 | return importScripts_.apply(this, scripts); 20 | }; 21 | 22 | importScripts('${VENDOR_NAME}.js', '${WORKER_APP_NAME}.js'); 23 | `], { 24 | type: 'text/javascript' 25 | })); 26 | 27 | bootstrapWorkerUi(workerScriptUrl, []); 28 | -------------------------------------------------------------------------------- /src/boot_worker_app.ts: -------------------------------------------------------------------------------- 1 | import './polyfills'; 2 | 3 | import { bootstrapWorkerApp } from '@angular/platform-browser-dynamic'; 4 | import { App } from './app/app'; 5 | 6 | bootstrapWorkerApp(App, []); 7 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | import 'core-js/es6/symbol'; 2 | import 'core-js/es6/object'; 3 | import 'core-js/es6/function'; 4 | import 'core-js/es6/parse-int'; 5 | import 'core-js/es6/parse-float'; 6 | import 'core-js/es6/number'; 7 | import 'core-js/es6/math'; 8 | import 'core-js/es6/string'; 9 | import 'core-js/es6/date'; 10 | import 'core-js/es6/array'; 11 | import 'core-js/es6/regexp'; 12 | import 'core-js/es6/map'; 13 | import 'core-js/es6/set'; 14 | import 'core-js/es6/weak-map'; 15 | import 'core-js/es6/weak-set'; 16 | import 'core-js/es6/typed'; 17 | import 'core-js/es6/reflect'; 18 | 19 | import 'core-js/es7/reflect'; 20 | 21 | import 'zone.js/dist/zone'; 22 | import 'zone.js/dist/long-stack-trace-zone'; 23 | -------------------------------------------------------------------------------- /src/server/app.ts: -------------------------------------------------------------------------------- 1 | import * as serveStatic from 'serve-static'; 2 | import * as express from 'express'; 3 | import { Request, Response } from 'express'; 4 | import { router as ngRouter } from './ng'; 5 | 6 | const app = express(); 7 | 8 | app.use('/', serveStatic(PUBLIC_DIR)); 9 | app.use('/', ngRouter); 10 | 11 | /** 12 | * 404 Not Found 13 | */ 14 | app.use((req: Request, res: Response, next: Function) => { 15 | const err: any = new Error('Not Found'); 16 | err.status = 404; 17 | 18 | return next(err); 19 | }); 20 | 21 | /** 22 | * Errors normalization 23 | */ 24 | app.use((err: any, req: Request, res: Response, next: Function) => { 25 | const status: number = err.status || 500; 26 | 27 | let stack: string = err.message; 28 | let message: string = err.stack; 29 | 30 | if (message.length > 100) { 31 | stack = message + (stack ? ('\n\n' + stack) : ''); 32 | message = 'Server Error'; 33 | } 34 | 35 | return next({ status, message, stack }); 36 | }); 37 | 38 | /** 39 | * Development error handler. 40 | * Print error message with a stacktrace. 41 | */ 42 | app.use((err: any, req: Request, res: Response, next: Function) => { 43 | return res.status(err.status).send(` 44 |

${err.message}

45 |

${err.status}

46 |
${err.stack}
47 | `); 48 | }); 49 | 50 | export { app }; 51 | -------------------------------------------------------------------------------- /src/server/ng.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Angular2 Universal Starter 5 | 6 | 7 | 8 | Loading... 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/server/ng.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response } from 'express'; 2 | import { provide } from '@angular/core'; 3 | import { APP_BASE_HREF } from '@angular/common'; 4 | import { 5 | REQUEST_URL, 6 | NODE_LOCATION_PROVIDERS, 7 | ORIGIN_URL, 8 | selectorResolver, 9 | selectorRegExpFactory, 10 | renderToStringWithPreboot, 11 | Bootloader 12 | } from 'angular2-universal'; 13 | 14 | import { App } from '../app/app'; 15 | 16 | function reduceScripts(content, src) { 17 | return `${content}`; 18 | } 19 | 20 | function getBaseUrlFromRequest(request: Request): string { 21 | return `${request.protocol}://${request.get('HOST')}/`; 22 | } 23 | 24 | const WORKER_SCRIPTS = [`${VENDOR_NAME}.js`, `${WORKER_NAME}.js`].reduce(reduceScripts, ''); 25 | const BROWSER_SCRIPTS = [`${VENDOR_NAME}.js`, `${BROWSER_NAME}.js`].reduce(reduceScripts, ''); 26 | 27 | const HTML_FILE = require('./ng.html'); 28 | 29 | export function renderComponent(html, component, providers, prebootOptions) { 30 | return renderToStringWithPreboot(component, providers, prebootOptions).then((serializedCmp) => { 31 | const selector: string = selectorResolver(component); 32 | 33 | return html.replace(selectorRegExpFactory(selector), serializedCmp); 34 | }); 35 | } 36 | 37 | if (!global['_bootloader']) { 38 | global['_bootloader'] = new Bootloader({ 39 | template: HTML_FILE, 40 | platformProviders: [ 41 | ...NODE_LOCATION_PROVIDERS, 42 | provide(APP_BASE_HREF, { useValue: '/' }), 43 | ] 44 | }); 45 | } 46 | 47 | const bootloader = global['_bootloader']; 48 | 49 | const router = Router(); 50 | 51 | /** 52 | * Angular2 application 53 | */ 54 | router.get('/*', (req: Request, res: Response, next: Function) => { 55 | return Promise.resolve() 56 | .then(() => { 57 | if (HAS_SS) { 58 | return bootloader.serializeApplication({ 59 | directives: [App], 60 | preboot: PREBOOT, 61 | providers: [ 62 | provide(REQUEST_URL, { useValue: req.originalUrl }), 63 | provide(ORIGIN_URL, { useValue: getBaseUrlFromRequest(req) }) 64 | ] 65 | }); 66 | } 67 | 68 | return HTML_FILE; 69 | }) 70 | .then((rawContent) => { 71 | const scripts = HAS_WW ? WORKER_SCRIPTS : BROWSER_SCRIPTS; 72 | const content = rawContent.replace('', scripts + ''); 73 | 74 | return res.send(content); 75 | }) 76 | .catch(error => next(error)); 77 | }); 78 | 79 | export { router }; 80 | -------------------------------------------------------------------------------- /test/e2e/app.spec.js: -------------------------------------------------------------------------------- 1 | describe('angular2-universal-starter application', function() { 2 | 3 | describe('Home page', function() { 4 | beforeEach(function() { 5 | browser.get('/'); 6 | browser.sleep(2000); // TODO: Remove when problem with preboot is resolved 7 | }); 8 | 9 | it('should show "Preboot is working" message', function () { 10 | const button = browser.wait(protractor.until.elementLocated(by.id('check-preboot')), 10000); 11 | const message = element(by.id('message-preboot')); 12 | 13 | expect(message.isPresent()).toBeFalsy(); 14 | button.click(); 15 | browser.wait(function() { return message.isPresent() }, 10000); 16 | }); 17 | 18 | it('should show lazy loaded greeting message', function () { 19 | const button = browser.wait(protractor.until.elementLocated(by.id('check-lazyloading')), 10000); 20 | const message = element(by.id('message-lazyloading')); 21 | 22 | expect(message.isPresent()).toBeFalsy(); 23 | button.click(); 24 | expect(message.isPresent()); 25 | browser.wait(function() { return message.isPresent() }, 10000); 26 | }); 27 | }); 28 | 29 | // describe('Workers page', function() { 30 | // beforeEach(function() { 31 | // browser.get('/workers'); 32 | // }); 33 | 34 | // it('should contain "Workers Page" header', function () { 35 | // const header = browser.wait(protractor.until.elementLocated(by.css('h1')), 10000); 36 | // expect(header.getText()).toBe('Workers Page'); 37 | // }) 38 | // }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/unit.spec.ts: -------------------------------------------------------------------------------- 1 | declare var require: any; 2 | declare var __karma__: any; 3 | 4 | import 'core-js/es6'; 5 | import 'core-js/es7/reflect'; 6 | 7 | import 'zone.js/dist/zone'; 8 | import 'zone.js/dist/long-stack-trace-zone'; 9 | import 'zone.js/dist/jasmine-patch'; 10 | import 'zone.js/dist/async-test'; 11 | import 'zone.js/dist/sync-test'; 12 | import 'zone.js/dist/fake-async-test'; 13 | 14 | import 'rxjs/Rx' 15 | 16 | import { setBaseTestProviders } from '@angular/core/testing' 17 | 18 | import { 19 | TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, 20 | TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS 21 | } from '@angular/platform-browser-dynamic/testing'; 22 | 23 | 24 | setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS); 25 | 26 | const args = __karma__.config.args; 27 | const opts = args[0]; 28 | 29 | const testsContext = require.context('../src', true, /\.spec\.ts/); 30 | 31 | let modules = testsContext.keys(); 32 | let testPath = opts.testPath; 33 | 34 | if (testPath) { 35 | testPath = './' + testPath.slice(4); 36 | modules = modules.filter(modulePath => modulePath.startsWith(testPath)); 37 | } 38 | 39 | modules.forEach(testsContext); 40 | -------------------------------------------------------------------------------- /tools/build.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const webpack = require('webpack'); 3 | const configs = require('../webpack.config.js'); 4 | const constants = require('../constants'); 5 | 6 | const EOL = os.EOL; 7 | 8 | const VENDOR_CONFIG = configs.VENDOR_CONFIG; 9 | const SERVER_CONFIG = configs.SERVER_CONFIG; 10 | const BROWSER_CONFIG = configs.BROWSER_CONFIG; 11 | const WORKER_CONFIG = configs.WORKER_CONFIG; 12 | const WORKER_APP_CONFIG = configs.WORKER_APP_CONFIG; 13 | 14 | const STATS_OPTIONS = configs.STATS_OPTIONS; 15 | const WATCH_OPTIONS = configs.WATCH_OPTIONS; 16 | 17 | const SHOULD_WATCH = process.argv.indexOf('--watch') !== -1; 18 | 19 | function printStats(stats) { 20 | process.stdout.write(EOL + stats.toString(STATS_OPTIONS) + EOL); 21 | } 22 | 23 | // BROWSER CODE MINIFICATION 24 | const UGLIFY_PLUGIN = new webpack.optimize.UglifyJsPlugin({ 25 | compress: { 26 | warnings: false, 27 | drop_console: true, 28 | unsafe: true 29 | } 30 | }); 31 | 32 | VENDOR_CONFIG.plugins.push(UGLIFY_PLUGIN); 33 | BROWSER_CONFIG.plugins.push(UGLIFY_PLUGIN); 34 | WORKER_CONFIG.plugins.push(UGLIFY_PLUGIN); 35 | WORKER_APP_CONFIG.plugins.push(UGLIFY_PLUGIN); 36 | 37 | webpack(VENDOR_CONFIG, function(vendorError, vendorStats) { 38 | if (vendorError) { 39 | throw vendorError; 40 | } 41 | 42 | printStats(vendorStats); 43 | 44 | const compiler = webpack([SERVER_CONFIG, BROWSER_CONFIG, WORKER_CONFIG, WORKER_APP_CONFIG]); 45 | 46 | function onInvalid() { 47 | console.info('webpack: bundle is now INVALID'); 48 | } 49 | 50 | function onDone(projectError, projectStats) { 51 | if (projectError) { 52 | throw projectError; 53 | } 54 | 55 | printStats(projectStats); 56 | 57 | console.info('webpack: bundle is now VALID'); 58 | } 59 | 60 | compiler.plugin('invalid', onInvalid); 61 | 62 | if (SHOULD_WATCH) { 63 | compiler.watch(WATCH_OPTIONS, onDone); 64 | } else { 65 | compiler.run(onDone); 66 | } 67 | }); -------------------------------------------------------------------------------- /tools/dev.js: -------------------------------------------------------------------------------- 1 | require('core-js/es6/reflect'); 2 | require('core-js/es7/reflect'); 3 | require('zone.js/dist/zone-node'); 4 | require('zone.js/dist/long-stack-trace-zone'); 5 | 6 | const path = require('path'); 7 | const webpack = require('webpack'); 8 | const WebpackDevServer = require('webpack-dev-server'); 9 | var MemoryFileSystem = require("memory-fs"); 10 | const constants = require('../constants'); 11 | const configs = require('../webpack.config.js'); 12 | 13 | const HOST = constants.HOST; 14 | const PORT = constants.PORT; 15 | const PROTOCOL = constants.PROTOCOL; 16 | 17 | const HAS_WW = constants.HAS_WW; 18 | 19 | const SERVER_NAME = constants.SERVER_NAME; 20 | const PRIVATE_DIR = constants.PRIVATE_DIR; 21 | const PUBLIC_DIR = constants.PUBLIC_DIR; 22 | 23 | const VENDOR_CONFIG = configs.VENDOR_CONFIG; 24 | const SERVER_CONFIG = configs.SERVER_CONFIG; 25 | const BROWSER_CONFIG = configs.BROWSER_CONFIG; 26 | const WORKER_CONFIG = configs.WORKER_CONFIG; 27 | const WORKER_APP_CONFIG = configs.WORKER_APP_CONFIG; 28 | 29 | const DEV_OPTIONS = configs.DEV_OPTIONS; 30 | 31 | const SERVER_DIRNAME = PRIVATE_DIR; 32 | const SERVER_FILENAME = path.resolve(SERVER_DIRNAME, SERVER_NAME + '.js'); 33 | 34 | const DEV_INDEX_SRC = '/webpack-dev-server.js'; 35 | const DEV_CLIENT_SRC = 'webpack-dev-server/client?'+ PROTOCOL + '://' + HOST + ':' + PORT + '/'; 36 | 37 | const DEV_INDEX_SCRIPT = ''; 38 | 39 | function addDevClientScript(config) { 40 | if (typeof config.entry === 'object' && !Array.isArray(config.entry)) { 41 | Object.keys(config.entry).forEach(function(key) { 42 | config.entry[key] = [DEV_CLIENT_SRC].concat(config.entry[key]) 43 | }); 44 | } else { 45 | config.entry = [DEV_CLIENT_SRC].concat(config.entry); 46 | } 47 | } 48 | 49 | const configsList = [SERVER_CONFIG]; 50 | 51 | if (HAS_WW) { 52 | addDevClientScript(WORKER_CONFIG); 53 | configsList.push(WORKER_CONFIG, WORKER_APP_CONFIG); 54 | } else { 55 | addDevClientScript(BROWSER_CONFIG); 56 | configsList.push(BROWSER_CONFIG); 57 | } 58 | 59 | function recompileApp(content) { 60 | const exports_ = {}; 61 | const module_ = { exports: exports_ }; 62 | 63 | // TODO: Replace on vm.runInNewContext when it's possible 64 | new Function( 65 | 'module', 'exports', 'require', 'process', '__filename', '__dirname', content 66 | )( module_, exports_, require, process, SERVER_FILENAME, SERVER_DIRNAME); 67 | 68 | return module_.exports.app; 69 | } 70 | 71 | function runDevServer() { 72 | var app; 73 | 74 | const compiler = Object.create(webpack(configsList), { outputPath: { value: PUBLIC_DIR }}); 75 | const server = new WebpackDevServer(compiler, DEV_OPTIONS); 76 | 77 | compiler.plugin('done', function onCompilationDone() { 78 | app = recompileApp(server.middleware.fileSystem.readFileSync(SERVER_FILENAME, 'utf8')); 79 | }); 80 | 81 | server.use('/', function proxyApp(req, res, next) { 82 | const send_ = res.send; 83 | 84 | res.send = function send(content) { 85 | if (res.statusCode >= 400) { 86 | const tag = ['body', 'head', 'html'].find(function(tag) { return !!~content.indexOf('') }); 87 | 88 | if (tag) { 89 | content = content.replace('', DEV_INDEX_SCRIPT + '$&'); 90 | } else { 91 | content += DEV_INDEX_SCRIPT; 92 | } 93 | } 94 | 95 | return send_.call(this, content); 96 | }; 97 | 98 | return app(req, res, next); 99 | }); 100 | 101 | server.listen(PORT, HOST); 102 | } 103 | 104 | webpack(VENDOR_CONFIG, function(error, stats) { 105 | if (error) { 106 | throw error; 107 | } 108 | 109 | runDevServer(); 110 | }); 111 | 112 | 113 | // Shut up enableProdMode() message 114 | // TODO: Think about another way to do it 115 | const log = console.log; 116 | console.log = function(message) { 117 | if (typeof message === 'string' && message.indexOf('Angular 2 is running') === 0) return; 118 | return log.apply(this, arguments); 119 | } 120 | -------------------------------------------------------------------------------- /tools/lint.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const chalk = require('chalk'); 5 | const Linter = require('tslint'); 6 | const chokidar = require('chokidar'); 7 | const constants = require('../constants'); 8 | const tslintJson = require('../tslint.json'); 9 | 10 | const EOL = os.EOL; 11 | 12 | const SRC_DIR = constants.SRC_DIR; 13 | const SUCCESS_MESSAGE = 'TSLint: Everything is OK'; 14 | 15 | const SHOULD_WATCH = process.argv.indexOf('--watch') !== -1; 16 | 17 | const watcher = chokidar.watch([ 18 | SRC_DIR + '/**/*.ts' 19 | ], { 20 | persistent: true 21 | }); 22 | 23 | var isReady = false; 24 | var readyPromises = []; 25 | var failedFiles = {}; 26 | var failedFilesCount = 0; 27 | 28 | function logSuccess(done) { 29 | process.stdout.write(EOL + chalk.green(SUCCESS_MESSAGE + EOL), done); 30 | } 31 | 32 | function logFailedFiles(done) { 33 | var output = ''; 34 | 35 | for (var path in failedFiles) { 36 | var failedFile = failedFiles[path]; 37 | 38 | if (failedFile) { 39 | output += EOL + chalk.red(failedFile.output); 40 | } 41 | } 42 | 43 | process.stdout.write(output, done); 44 | } 45 | 46 | function lintFile(path, contents) { 47 | const linter = new Linter(path, contents, { 48 | formatter: 'prose', 49 | configuration: tslintJson 50 | }); 51 | 52 | const results = linter.lint(); 53 | 54 | if (results.failureCount) { 55 | if (!failedFiles[path]) { 56 | failedFilesCount++; 57 | } 58 | 59 | failedFiles[path] = results; 60 | 61 | process.stdout.write(EOL + chalk.red(results.output)); 62 | } else { 63 | if (failedFiles[path]) { 64 | failedFilesCount--; 65 | } 66 | 67 | failedFiles[path] = false; 68 | 69 | process.stdout.write(chalk.green(path) + EOL); 70 | 71 | if (isReady) { 72 | if (failedFilesCount) { 73 | logFailedFiles(); 74 | } else { 75 | logSuccess(); 76 | } 77 | } 78 | } 79 | } 80 | 81 | function checkFile(path) { 82 | return new Promise(function(resolve, reject) { 83 | fs.readFile(path, 'utf8', function(error, contents) { 84 | if (error) { 85 | return reject(error); 86 | } 87 | 88 | lintFile(path, contents); 89 | 90 | return resolve(); 91 | }); 92 | }); 93 | } 94 | 95 | function ensureExit() { 96 | if (!SHOULD_WATCH) { 97 | process.exit(failedFilesCount ? 2 : 0); 98 | } 99 | } 100 | 101 | function onChange(path) { 102 | const promise = checkFile(path); 103 | 104 | if (isReady === false) { 105 | readyPromises.push(promise); 106 | } 107 | } 108 | 109 | function onRemove(path) { 110 | if (failedFiles[path]) { 111 | failedFiles[path] = false; 112 | failedFilesCount++; 113 | } 114 | } 115 | 116 | function onReady() { 117 | Promise.all(readyPromises).then(function() { 118 | isReady = true; 119 | readyPromises = []; 120 | 121 | if (failedFilesCount === 0) { 122 | return logSuccess(ensureExit); 123 | } else { 124 | return ensureExit(); 125 | } 126 | }); 127 | } 128 | 129 | 130 | watcher.on('add', onChange); 131 | watcher.on('change', onChange); 132 | watcher.on('remove', onRemove); 133 | watcher.on('ready', onReady); 134 | -------------------------------------------------------------------------------- /tools/prod.js: -------------------------------------------------------------------------------- 1 | require('core-js/es6/reflect'); 2 | require('core-js/es7/reflect'); 3 | require('zone.js/dist/zone-node'); 4 | require('zone.js/dist/long-stack-trace-zone'); 5 | 6 | const http = require('http'); 7 | const constants = require('../constants'); 8 | 9 | const HOST = constants.HOST; 10 | const PORT = constants.PORT; 11 | 12 | const PRIVATE_DIR = constants.PRIVATE_DIR; 13 | const SERVER_NAME = constants.SERVER_NAME; 14 | 15 | const app = require(PRIVATE_DIR + '/' + SERVER_NAME).app; 16 | 17 | http.createServer(app).listen(PORT, HOST); 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "removeComments": true, 9 | "noEmitHelpers": false, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true 12 | }, 13 | "exclude": [ 14 | "node_modules", 15 | "dist" 16 | ] 17 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "align": true, 4 | "class-name": true, 5 | "comment-format": [ 6 | true, 7 | "check-space" 8 | ], 9 | "curly": true, 10 | "eofline": true, 11 | "forin": false, 12 | "indent": [true, "spaces"], 13 | "interface-name": false, 14 | "label-position": true, 15 | "label-undefined": true, 16 | "max-line-length": [true, 120], 17 | "member-access": false, 18 | "member-ordering": [ 19 | true, 20 | "public-before-private", 21 | "static-before-instance", 22 | "variables-before-functions" 23 | ], 24 | "no-any": false, 25 | "no-bitwise": false, 26 | "no-conditional-assignment": false, 27 | "no-consecutive-blank-lines": true, 28 | "no-construct": true, 29 | "no-constructor-vars": false, 30 | "no-debugger": true, 31 | "no-duplicate-key": true, 32 | "no-duplicate-variable": true, 33 | "no-empty": false, 34 | "no-eval": true, 35 | "no-inferrable-types": true, 36 | "no-null-keyword": false, 37 | "no-require-imports": false, 38 | "no-shadowed-variable": true, 39 | "no-string-literal": false, 40 | "no-switch-case-fall-through": false, 41 | "no-trailing-whitespace": true, 42 | "no-unreachable": true, 43 | "no-unused-expression": true, 44 | "no-unused-variable": false, 45 | "no-use-before-declare": true, 46 | "no-var-keyword": true, 47 | "no-var-requires": false, 48 | "object-literal-sort-keys": false, 49 | "one-line": [ 50 | true, 51 | "check-open-brace", 52 | "check-catch", 53 | "check-else", 54 | "check-whitespace" 55 | ], 56 | "quotemark": [true, "single"], 57 | "radix": true, 58 | "semicolon": true, 59 | "triple-equals": true, 60 | "variable-name": false, 61 | "whitespace": [ 62 | true, 63 | "check-branch", 64 | "check-decl", 65 | "check-operator", 66 | "check-module", 67 | "check-separator", 68 | "check-type", 69 | "check-typecast" 70 | ] 71 | } 72 | } -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare const ROOT_DIR: string; 4 | declare const SRC_DIR: string; 5 | declare const DIST_DIR: string; 6 | declare const PUBLIC_DIR: string; 7 | declare const PRIVATE_DIR: string; 8 | declare const SERVER_DIR: string; 9 | 10 | declare const HOST: string; 11 | declare const PORT: number; 12 | 13 | declare const HAS_SS: boolean; 14 | declare const HAS_WW: boolean; 15 | 16 | declare const VENDOR_NAME: string; 17 | declare const SERVER_NAME: string; 18 | declare const BROWSER_NAME: string; 19 | declare const WORKER_NAME: string; 20 | declare const WORKER_APP_NAME: string; 21 | 22 | declare const NODE_MODULES: string[]; 23 | 24 | declare const PREBOOT: { 25 | appRoot: string, 26 | freeze: any, 27 | replay: string, 28 | buffer: boolean, 29 | debug: boolean, 30 | uglify: boolean, 31 | }; 32 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": {}, 3 | "devDependencies": {}, 4 | "ambientDependencies": { 5 | "es6-promise": "github:DefinitelyTyped/DefinitelyTyped/es6-promise/es6-promise.d.ts#830e8ebd9ef137d039d5c7ede24a421f08595f83", 6 | "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#6697d6f7dadbf5773cb40ecda35a76027e0783b2", 7 | "express": "github:DefinitelyTyped/DefinitelyTyped/express/express.d.ts#f743b7f4955076199e2252ace18b01e33280db69", 8 | "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#26c98c8a9530c44f8c801ccc3b2057e2101187ee", 9 | "mime": "github:DefinitelyTyped/DefinitelyTyped/mime/mime.d.ts#cb5206a8ac1c9a3ddfd126f5ecea6729b2361452", 10 | "node": "github:DefinitelyTyped/DefinitelyTyped/node/node.d.ts#20e1eb9616922d382d918cc5a21870a9dbe255f5", 11 | "serve-static": "github:DefinitelyTyped/DefinitelyTyped/serve-static/serve-static.d.ts#0fa4e9e61385646ea6a4cba2aef357353d2ce77f", 12 | "zone.js": "github:DefinitelyTyped/DefinitelyTyped/zone.js/zone.js.d.ts#b923a5aaf013ac84c566f27ba6b5843211981c7a" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const constants = require('./constants'); 5 | 6 | const DefinePlugin = webpack.DefinePlugin; 7 | const DllPlugin = webpack.DllPlugin; 8 | const DllReferencePlugin = webpack.DllReferencePlugin; 9 | 10 | const ROOT_DIR = constants.ROOT_DIR; 11 | const SRC_DIR = constants.SRC_DIR; 12 | const PUBLIC_DIR = constants.PUBLIC_DIR; 13 | const PRIVATE_DIR = constants.PRIVATE_DIR; 14 | 15 | const VENDOR_NAME = constants.VENDOR_NAME; 16 | const SERVER_NAME = constants.SERVER_NAME; 17 | const BROWSER_NAME = constants.BROWSER_NAME; 18 | const WORKER_NAME = constants.WORKER_NAME; 19 | const WORKER_APP_NAME = constants.WORKER_APP_NAME; 20 | 21 | const SERVER_SOURCE_PATH = constants.SERVER_SOURCE_PATH; 22 | const BROWSER_SOURCE_PATH = constants.BROWSER_SOURCE_PATH; 23 | const WORKER_SOURCE_PATH = constants.WORKER_SOURCE_PATH; 24 | const WORKER_APP_SOURCE_PATH = constants.WORKER_APP_SOURCE_PATH; 25 | 26 | const VENDOR_DLL_MANIFEST_FILE = constants.VENDOR_DLL_MANIFEST_FILE; 27 | const VENDOR_DLL_MANIFEST_PATH = constants.VENDOR_DLL_MANIFEST_PATH; 28 | 29 | const NODE_MODULES = fs.readdirSync(ROOT_DIR + '/node_modules').filter(function(name) { 30 | return name != '.bin'; 31 | }); 32 | 33 | const STATS_OPTIONS = { 34 | colors: { 35 | level: 2, 36 | hasBasic: true, 37 | has256: true, 38 | has16m: false 39 | }, 40 | cached: false, 41 | cachedAssets: false, 42 | modules: true, 43 | chunks: false, 44 | reasons: true, 45 | errorDetails: true, 46 | chunkOrigins: false, 47 | exclude: ['node_modules'] 48 | }; 49 | 50 | const WATCH_OPTIONS = { 51 | aggregateTimeout: 100, 52 | poll: undefined 53 | }; 54 | 55 | const DEV_OPTIONS = { 56 | contentBase: false, 57 | queit: false, 58 | noInfo: false, 59 | stats: STATS_OPTIONS 60 | }; 61 | 62 | const LOADERS = [{ 63 | test: /\.ts$/, 64 | loader: 'ts', 65 | query: { 66 | ignoreDiagnostics: [ 67 | 2403, // 2403 -> Subsequent variable declarations 68 | 2300, // 2300 -> Duplicate identifier 69 | 2374, // 2374 -> Duplicate number index signature 70 | 2375, // 2375 -> Duplicate string index signature, 71 | 2435, 72 | 2436, 73 | 2502 74 | ] 75 | }, 76 | exclude: [ 77 | /node_modules/ 78 | ] 79 | }, { 80 | test: /\.html$/, 81 | loader: 'raw' 82 | }, { 83 | test: /\.css$/, 84 | loaders: ['raw', 'postcss'] 85 | }, { 86 | test: /\.json$/, 87 | loader: 'json' 88 | }]; 89 | 90 | const POSTCSS = function() { 91 | return [ 92 | require('postcss-cssnext') 93 | ] 94 | } 95 | 96 | const DEFINE_CONSTANTS_PLUGIN = new DefinePlugin((function stringifyConstants() { 97 | const stringifiedConstants = {}; 98 | 99 | Object.keys(constants).forEach(function(constantName) { 100 | stringifiedConstants[constantName] = JSON.stringify(constants[constantName]); 101 | }); 102 | 103 | return stringifiedConstants; 104 | })()); 105 | 106 | const VENDOR_DLL_REFERENCE_PLUGIN = new DllReferencePlugin({ 107 | context: ROOT_DIR, 108 | sourceType: 'var', 109 | get manifest() { 110 | return require(VENDOR_DLL_MANIFEST_PATH); 111 | } 112 | }); 113 | 114 | const VENDOR_CONFIG = { 115 | target: 'web', 116 | entry: { 117 | [VENDOR_NAME]: [ 118 | 'core-js/es6/symbol', 119 | 'core-js/es6/object', 120 | 'core-js/es6/function', 121 | 'core-js/es6/parse-int', 122 | 'core-js/es6/parse-float', 123 | 'core-js/es6/number', 124 | 'core-js/es6/math', 125 | 'core-js/es6/string', 126 | 'core-js/es6/date', 127 | 'core-js/es6/array', 128 | 'core-js/es6/regexp', 129 | 'core-js/es6/map', 130 | 'core-js/es6/set', 131 | 'core-js/es6/weak-map', 132 | 'core-js/es6/weak-set', 133 | 'core-js/es6/typed', 134 | 'core-js/es6/reflect', 135 | 'core-js/es7/reflect', 136 | 'zone.js/dist/zone', 137 | 'zone.js/dist/long-stack-trace-zone', 138 | '@angular/core', 139 | '@angular/common', 140 | '@angular/platform-browser', 141 | '@angular/platform-browser-dynamic', 142 | '@angular/http', 143 | ] 144 | }, 145 | output: { 146 | path: PUBLIC_DIR, 147 | filename: '[name].js', 148 | library: VENDOR_NAME, 149 | libraryTarget: 'var' 150 | }, 151 | plugins: [ 152 | new DllPlugin({ 153 | name: VENDOR_NAME, 154 | path: VENDOR_DLL_MANIFEST_PATH 155 | }) 156 | ] 157 | }; 158 | 159 | const BROWSER_CONFIG = { 160 | target: 'web', 161 | entry: { 162 | [BROWSER_NAME]: [ 163 | BROWSER_SOURCE_PATH 164 | ] 165 | }, 166 | output: { 167 | path: PUBLIC_DIR, 168 | filename: '[name].js', 169 | chunkFilename: '[id].' + BROWSER_NAME + '.js', 170 | }, 171 | plugins: [ 172 | VENDOR_DLL_REFERENCE_PLUGIN 173 | ], 174 | resolve: { 175 | extensions: ['', '.ts', '.js'] 176 | }, 177 | module: { 178 | loaders: LOADERS 179 | }, 180 | postcss: POSTCSS 181 | }; 182 | 183 | const WORKER_CONFIG = { 184 | target: 'web', 185 | entry: { 186 | [WORKER_NAME]: [ 187 | WORKER_SOURCE_PATH 188 | ] 189 | }, 190 | output: { 191 | path: PUBLIC_DIR, 192 | filename: '[name].js', 193 | chunkFilename: '[id].' + WORKER_NAME + '.js', 194 | }, 195 | plugins: [ 196 | VENDOR_DLL_REFERENCE_PLUGIN, 197 | DEFINE_CONSTANTS_PLUGIN, 198 | ], 199 | resolve: { 200 | extensions: ['', '.ts', '.js'] 201 | }, 202 | module: { 203 | loaders: LOADERS 204 | }, 205 | postcss: POSTCSS 206 | }; 207 | 208 | const WORKER_APP_CONFIG = { 209 | target: 'webworker', 210 | entry: { 211 | [WORKER_APP_NAME]: [ 212 | WORKER_APP_SOURCE_PATH 213 | ] 214 | }, 215 | output: { 216 | path: PUBLIC_DIR, 217 | filename: '[name].js', 218 | chunkFilename: '[id].' + WORKER_APP_NAME + '.js' 219 | }, 220 | get plugins() { 221 | return [ 222 | VENDOR_DLL_REFERENCE_PLUGIN, 223 | DEFINE_CONSTANTS_PLUGIN, 224 | ]; 225 | } , 226 | resolve: { 227 | extensions: ['', '.ts', '.js'] 228 | }, 229 | module: { 230 | loaders: LOADERS 231 | }, 232 | postcss: POSTCSS 233 | }; 234 | 235 | const SERVER_CONFIG = { 236 | target: 'node', 237 | entry: { 238 | [SERVER_NAME]: [ 239 | SERVER_SOURCE_PATH 240 | ] 241 | }, 242 | output: { 243 | path: PRIVATE_DIR, 244 | filename: '[name].js', 245 | chunkFilename: '[id].' + SERVER_NAME + '.js', 246 | library: SERVER_NAME, 247 | libraryTarget: 'commonjs2' 248 | }, 249 | plugins: [ 250 | DEFINE_CONSTANTS_PLUGIN 251 | ], 252 | node: { 253 | __dirname: true, 254 | __filename: true 255 | }, 256 | externals: [ 257 | NODE_MODULES.map(function(name) { return new RegExp('^' + name) }), 258 | ], 259 | resolve: { 260 | extensions: ['', '.ts', '.js'] 261 | }, 262 | module: { 263 | loaders: LOADERS 264 | }, 265 | postcss: POSTCSS 266 | }; 267 | 268 | const TESTING_CONFIG = { 269 | resolve: { 270 | extensions: ['', '.ts', '.js'] 271 | }, 272 | module: { 273 | loaders: LOADERS 274 | }, 275 | devServer: { 276 | quiet: true, 277 | noInfo: true, 278 | } 279 | }; 280 | 281 | exports = module.exports = [VENDOR_CONFIG, BROWSER_CONFIG, WORKER_CONFIG, WORKER_APP_CONFIG, SERVER_CONFIG]; 282 | 283 | exports.VENDOR_CONFIG = VENDOR_CONFIG; 284 | exports.SERVER_CONFIG = SERVER_CONFIG; 285 | exports.BROWSER_CONFIG = BROWSER_CONFIG; 286 | exports.WORKER_CONFIG = WORKER_CONFIG; 287 | exports.WORKER_APP_CONFIG = WORKER_APP_CONFIG; 288 | exports.TESTING_CONFIG = TESTING_CONFIG; 289 | 290 | exports.STATS_OPTIONS = STATS_OPTIONS; 291 | exports.WATCH_OPTIONS = WATCH_OPTIONS; 292 | exports.DEV_OPTIONS = DEV_OPTIONS; 293 | --------------------------------------------------------------------------------