From 5c73f079f439165012fb95419a8ce803be4ea803 Mon Sep 17 00:00:00 2001 From: alexpods Date: Tue, 2 Feb 2016 18:59:39 +0300 Subject: [PATCH] Global refactoring. Added support for serveral angular2 application. --- constants.js | 40 +-- package.json | 2 + src/.utils/run_browser.ts | 15 ++ src/.utils/run_worker_app.ts | 17 ++ src/.utils/run_worker_ui.ts | 20 ++ src/.utils/serve.ts | 154 +++++++++++ src/admin/app.ts | 7 + src/admin/boot_browser.ts | 12 + src/{server/ng.html => admin/index.html} | 1 - src/admin/index.js | 24 ++ src/{server => }/app.ts | 19 +- src/app/home.spec.ts | 54 ---- src/boot_browser.ts | 23 -- src/boot_worker.ts | 52 ---- src/boot_worker_app.ts | 34 --- src/{app => main}/app.ts | 0 src/main/boot_browser.ts | 12 + src/main/boot_server.ts | 13 + src/main/boot_worker_app.ts | 22 ++ src/main/boot_worker_ui.ts | 15 ++ src/{app => main}/greeter.ts | 0 src/{app => main}/home.css | 0 src/{app => main}/home.html | 0 src/{app => main}/home.ts | 4 +- src/main/index.html | 20 ++ src/main/index.js | 26 ++ src/{app => main}/workers.ts | 0 src/server/ng.ts | 64 ----- tools/build.js | 17 +- tools/dev.js | 118 ++++----- tools/prod.js | 16 +- tsconfig.json | 9 +- typings.d.ts | 54 ++-- webpack.config.js | 319 +++++++++++++---------- 34 files changed, 677 insertions(+), 506 deletions(-) create mode 100644 src/.utils/run_browser.ts create mode 100644 src/.utils/run_worker_app.ts create mode 100644 src/.utils/run_worker_ui.ts create mode 100644 src/.utils/serve.ts create mode 100644 src/admin/app.ts create mode 100644 src/admin/boot_browser.ts rename src/{server/ng.html => admin/index.html} (64%) create mode 100644 src/admin/index.js rename src/{server => }/app.ts (67%) delete mode 100644 src/app/home.spec.ts delete mode 100644 src/boot_browser.ts delete mode 100644 src/boot_worker.ts delete mode 100644 src/boot_worker_app.ts rename src/{app => main}/app.ts (100%) create mode 100644 src/main/boot_browser.ts create mode 100644 src/main/boot_server.ts create mode 100644 src/main/boot_worker_app.ts create mode 100644 src/main/boot_worker_ui.ts rename src/{app => main}/greeter.ts (100%) rename src/{app => main}/home.css (100%) rename src/{app => main}/home.html (100%) rename src/{app => main}/home.ts (93%) create mode 100644 src/main/index.html create mode 100644 src/main/index.js rename src/{app => main}/workers.ts (100%) delete mode 100644 src/server/ng.ts diff --git a/constants.js b/constants.js index 7e94860..6af91b0 100644 --- a/constants.js +++ b/constants.js @@ -1,32 +1,34 @@ const fs = require('fs'); const path = require('path'); -exports.ROOT_DIR = path.resolve(__dirname); -exports.SRC_DIR = path.resolve(exports.ROOT_DIR, 'src'); -exports.DIST_DIR = path.resolve(exports.ROOT_DIR, 'dist'); -exports.PUBLIC_DIR = path.resolve(exports.DIST_DIR, 'public'); -exports.PRIVATE_DIR = path.resolve(exports.DIST_DIR, 'private'); -exports.SERVER_DIR = path.resolve(exports.SRC_DIR, 'server'); +exports.ROOT_DIR = path.resolve(__dirname); +exports.SRC_DIR = path.resolve(exports.ROOT_DIR, 'src'); +exports.DIST_DIR = path.resolve(exports.ROOT_DIR, 'dist'); +exports.PUBLIC_DIR = path.resolve(exports.DIST_DIR, 'public'); +exports.PRIVATE_DIR = path.resolve(exports.DIST_DIR, 'private'); +exports.MANIFESTS_DIR = path.resolve(exports.DIST_DIR, 'manifests'); +exports.SERVER_DIR = path.resolve(exports.SRC_DIR, 'server'); exports.HOST = process.env.HOST || 'localhost'; exports.PORT = +process.env.PORT || 3000; -exports.HAS_SS = 'NG2_SS' in process.env ? process.env.NG2_SS === 'true' : true; -exports.HAS_WW = 'NG2_WW' in process.env ? process.env.NG2_WW === 'true' : true; +exports.SS = 'NG2_SS' in process.env ? process.env.NG2_SS === 'true' : true; +exports.WW = 'NG2_WW' in process.env ? process.env.NG2_WW === 'true' : true; -exports.VENDOR_NAME = 'vendor'; -exports.SERVER_NAME = 'server'; -exports.BROWSER_NAME = 'browser'; -exports.WORKER_NAME = 'worker'; -exports.WORKER_APP_NAME = 'worker_app'; +exports.APPS = [ + { name: 'admin', path: path.resolve(exports.SRC_DIR, 'admin/index.js'), urlPrefix: '/admin' }, + { name: 'main', path: path.resolve(exports.SRC_DIR, 'main/index.js'), urlPrefix: '/' } +]; -exports.SERVER_SOURCE_PATH = path.resolve(exports.SRC_DIR, 'server/app.ts'); -exports.BROWSER_SOURCE_PATH = path.resolve(exports.SRC_DIR, 'boot_browser.ts'); -exports.WORKER_SOURCE_PATH = path.resolve(exports.SRC_DIR, 'boot_worker.ts'); -exports.WORKER_APP_SOURCE_PATH = path.resolve(exports.SRC_DIR, 'boot_worker_app.ts'); +exports.APPS_SERVER_BUNDLE_NAME = 'server'; +exports.APPS_BROWSER_BUNDLE_NAME = 'browser'; +exports.APPS_WORKER_UI_BUNDLE_NAME = 'worker_ui'; +exports.APPS_WORKER_APP_BUNDLE_NAME = 'worker_app'; -exports.VENDOR_DLL_MANIFEST_FILE = 'vendor-manifest.json'; -exports.VENDOR_DLL_MANIFEST_PATH = path.resolve(exports.PUBLIC_DIR, exports.VENDOR_DLL_MANIFEST_FILE); +exports.VENDOR_BUNDLE_NAME = 'vendor'; + +exports.MASTER_APP_BUNDLE_NAME = 'app'; +exports.MASTER_APP_SOURCE_PATH = path.resolve(exports.SRC_DIR, 'app.ts'); exports.PREBOOT = { appRoot: 'app', diff --git a/package.json b/package.json index c2e1f43..0a9a242 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "prod": "node tools/prod.js", "prebuild": "npm run clean:dist", "build": "node tools/build.js", + "prebuild:watch": "npm run clean:dist", "build:watch": "node tools/build.js --watch", "lint": "node tools/lint.js", "lint:watch": "node tools/lint.js --watch", @@ -47,6 +48,7 @@ "dependencies": { "angular2": "2.0.0-beta.3", "angular2-universal-preview": "0.50.0", + "basic-auth-connect": "^1.0.0", "css": "^2.2.1", "es6-promise": "^3.0.2", "es6-shim": "^0.33.3", diff --git a/src/.utils/run_browser.ts b/src/.utils/run_browser.ts new file mode 100644 index 0000000..40daf28 --- /dev/null +++ b/src/.utils/run_browser.ts @@ -0,0 +1,15 @@ +import { Router } from 'angular2/router'; + +document.addEventListener('DOMContentLoaded', function onDOMContentLoaded() { + const providers = []; + + window[__APPS_BROWSER_BUNDLE_NAME__].main(providers).then(function(compRef) { + const injector = compRef.injector; + const router: Router = injector.getOptional(Router); + + return Promise.resolve() + .then(() => router && ( router)._currentNavigation) + .then(() => new Promise(function(resolve) { setTimeout(resolve) })) + .then(() => document.dispatchEvent(new Event('BootstrapComplete'))) + }); +}); \ No newline at end of file diff --git a/src/.utils/run_worker_app.ts b/src/.utils/run_worker_app.ts new file mode 100644 index 0000000..102adcf --- /dev/null +++ b/src/.utils/run_worker_app.ts @@ -0,0 +1,17 @@ +import { Router } from 'angular2/router'; + +const global = new Function('return this')(); + +setTimeout(function() { + const providers = []; + + global[__APPS_WORKER_APP_BUNDLE_NAME__].main(providers).then(function(compRef) { + const injector = compRef.injector; + const router: Router = injector.getOptional(Router); + + return Promise.resolve() + .then(() => router && ( router)._currentNavigation) + .then(() => new Promise(function(resolve) { setTimeout(resolve) })) + .then(() => postMessage('APP_READY', undefined)) + }); +}, 100); diff --git a/src/.utils/run_worker_ui.ts b/src/.utils/run_worker_ui.ts new file mode 100644 index 0000000..5616025 --- /dev/null +++ b/src/.utils/run_worker_ui.ts @@ -0,0 +1,20 @@ +import { Provider } from 'angular2/core'; +import { WebWorkerInstance, WORKER_SCRIPT } from 'angular2/platform/worker_render'; + +document.addEventListener('DOMContentLoaded', function onDOMContentLoaded() { + const providers = [ + new Provider(WORKER_SCRIPT, { useValue: window['__WORKER_SCRIPT_URL'] }) + ]; + + const appRef = window[__APPS_WORKER_UI_BUNDLE_NAME__].main(providers); + const worker = appRef.injector.get(WebWorkerInstance).worker; + + worker.addEventListener('message', function onAppReady(event) { + if (event.data === 'APP_READY') { + worker.removeEventListener('message', onAppReady, false); + URL.revokeObjectURL(window['__WORKER_SCRIPT_URL']); + delete window['__WORKER_SCRIPT_URL']; + setTimeout(function() { document.dispatchEvent(new Event('BootstrapComplete')) }); + } + }, false); +}); \ No newline at end of file diff --git a/src/.utils/serve.ts b/src/.utils/serve.ts new file mode 100644 index 0000000..90b2824 --- /dev/null +++ b/src/.utils/serve.ts @@ -0,0 +1,154 @@ +import { Request, Response } from 'express'; +import { resolve as resolvePath, relative as relativePath } from 'path'; +import { platform, Provider, ComponentRef, ExceptionHandler } from 'angular2/core'; +import { DOM } from 'angular2/src/platform/dom/dom_adapter'; +import { DOCUMENT } from 'angular2/platform/common_dom'; +import { ROUTER_PROVIDERS, APP_BASE_HREF, Router } from 'angular2/router'; +import { REQUEST_URL } from 'angular2-universal-preview'; + +const { + createPrebootHTML, + getBrowserCode, + prebootConfigDefault +} = require('angular2-universal-preview'); + +const { + parseDocument, + serializeDocument +} = require('angular2-universal-preview/dist/server/src/platform/document'); + +const req = __non_webpack_require__; + +const VENDOR_PATH = req.resolve(__PUBLIC_DIR__ + '/' + __VENDOR_BUNDLE_NAME__); +const VENDOR_URL = '/' + relativePath(__PUBLIC_DIR__, VENDOR_PATH); + +const WORKER_APP_LOADER_ORIGIN = 'window.location.origin'; +const WORKER_APP_READY_MESSAGE = 'APP_READY'; + +function createBrowserScripts(browserUrl) { + return ` + + + `; +} + +function createWorkerScripts(workerUrl, workerAppUrl) { + return ` + + + + ` +} + +function createWorkerAppScripts(workerAppUrl) { + return ` + var importScripts_ = this.importScripts; + + this.importScripts = function importScripts() { + for (var i = 0, scripts = new Array(arguments.length); i < scripts.length; ++i) { + var script = arguments[i]; + var origin = '" + ${WORKER_APP_LOADER_ORIGIN} + "'; + + if (script.indexOf('http:') !== 0 || script.indexOf('https:') !== 0) { + script = origin + (script[0] === '/' ? script : '/' + script); + } + + scripts[i] = script; + } + + return importScripts_.apply(this, scripts); + }; + + importScripts('${VENDOR_URL}', '${workerAppUrl}'); + `; +} + +export function serveUniversal(name, indexHtml, options: any = {}) { + let server, browserScripts = '', workerScripts = '', prebootHtml = '', prebootPromise = Promise.resolve(); + + if (options.server) { + const serverPath = typeof options.server === 'string' + ? options.server + : resolvePath(__PRIVATE_DIR__, name + '-' + __APPS_SERVER_BUNDLE_NAME__ + '.js'); + + server = req(serverPath); + } + + if (options.browser) { + const browserPath = typeof options.browser === 'string' + ? options.browser + : resolvePath(__PUBLIC_DIR__, name + '-' + __APPS_BROWSER_BUNDLE_NAME__ + '.js'); + + const browserUrl = '/' + relativePath(__PUBLIC_DIR__, browserPath); + + browserScripts = createBrowserScripts(browserUrl); + } + + if (options.worker) { + const workerPaths = Array.isArray(options.worker) + ? options.worker + : [ + resolvePath(__PUBLIC_DIR__, name + '-' + __APPS_WORKER_UI_BUNDLE_NAME__ + '.js'), + resolvePath(__PUBLIC_DIR__, name + '-' + __APPS_WORKER_APP_BUNDLE_NAME__+ '.js') + ]; + + const workerUrl = '/' + relativePath(__PUBLIC_DIR__, workerPaths[0]); + const workerAppUrl = '/' + relativePath(__PUBLIC_DIR__, workerPaths[1]); + + workerScripts = createWorkerScripts(workerUrl, workerAppUrl); + } + + const prebootOptions = options.preboot !== false + ? prebootConfigDefault(Object.assign({}, __PREBOOT__, options.preboot)) + : false; + + return (req: Request, res: Response, next: Function) => prebootPromise.then(() => Promise.resolve(indexHtml)) + .then((html) => { + if (__SS__ && server) { + const REQUEST_PROVIDERS = [ + new Provider(ExceptionHandler, { useFactory: () => new ExceptionHandler(DOM, true) }), + new Provider(DOCUMENT, { useValue: parseDocument(html) }), + new Provider(REQUEST_URL, { useValue: req.originalUrl }), + ]; + + return server.main(REQUEST_PROVIDERS, req).then(function(compRef) { + const injector = compRef.injector; + const router: Router = injector.getOptional(Router); + + return Promise.resolve() + .then(() => router && ( router)._currentNavigation) + .then(() => new Promise(function(resolve) { setTimeout(resolve) })) + .then(() => serializeDocument(compRef.injector.get(DOCUMENT))) + }); + } + return html; + }) + .then((html) => { + if (prebootOptions && (workerScripts || browserScripts)) { + return getBrowserCode(prebootOptions).then((code) => { + const prebootHtml = createPrebootHTML(code, prebootOptions); + + return html.replace('', prebootHtml + '') + }); + } + return html; + }) + .then((html) => { + let scripts = ''; + + if (__WW__ && workerScripts) { + scripts = workerScripts; + } else if (browserScripts) { + scripts = browserScripts; + } + + return res.status(200).send(scripts ? html.replace('', scripts + '') : html); + }) + .catch(err => next(err)) +} diff --git a/src/admin/app.ts b/src/admin/app.ts new file mode 100644 index 0000000..03f3d4b --- /dev/null +++ b/src/admin/app.ts @@ -0,0 +1,7 @@ +import { Component } from 'angular2/core'; + +@Component({ + selector: 'app', + template: '

Admin application

' +}) +export class App {} diff --git a/src/admin/boot_browser.ts b/src/admin/boot_browser.ts new file mode 100644 index 0000000..fe4387c --- /dev/null +++ b/src/admin/boot_browser.ts @@ -0,0 +1,12 @@ +import { Provider } from 'angular2/core'; +import { bootstrap } from 'angular2/platform/browser'; +import { ROUTER_PROVIDERS, APP_BASE_HREF } from 'angular2/router'; +import { App } from './app'; + +export function main(BOOT_PROVIDERS) { + return bootstrap(App, [ + BOOT_PROVIDERS, + ROUTER_PROVIDERS, + new Provider(APP_BASE_HREF, { useValue: '/' }) + ]); +}; diff --git a/src/server/ng.html b/src/admin/index.html similarity index 64% rename from src/server/ng.html rename to src/admin/index.html index 9528a98..9b5c301 100644 --- a/src/server/ng.html +++ b/src/admin/index.html @@ -6,6 +6,5 @@ Loading... - \ No newline at end of file diff --git a/src/admin/index.js b/src/admin/index.js new file mode 100644 index 0000000..1ae0cc5 --- /dev/null +++ b/src/admin/index.js @@ -0,0 +1,24 @@ +const fs = require('fs'); +const path = require('path'); +const express = require('express'); +const basicAuth = require('basic-auth-connect'); +const Router = express.Router; + +exports.createRouter = function($) { + const router = Router(); + const indexHtml = fs.readFileSync(path.resolve(__dirname, 'index.html'), { encoding: 'utf8' }); + + router.use(basicAuth('user', 'pass')); + + router.get('/*', $.serveUniversal(indexHtml, { + browser: true, + })); + + return router; +} + +exports.createBuilds = function($) { + return [ + $.createBrowserConfig('./boot_browser.ts'), + ]; +} diff --git a/src/server/app.ts b/src/app.ts similarity index 67% rename from src/server/app.ts rename to src/app.ts index 0eb3610..d22eae6 100644 --- a/src/server/app.ts +++ b/src/app.ts @@ -1,12 +1,21 @@ -import * as serveStatic from 'serve-static'; import * as express from 'express'; +import * as serveStatic from 'serve-static'; import { Request, Response } from 'express'; -import { router as ngRouter } from './ng'; +import { serveUniversal } from './.utils/serve'; const app = express(); -app.use('/', serveStatic(PUBLIC_DIR)); -app.use('/', ngRouter); +app.use('/', serveStatic(__PUBLIC_DIR__)); + +__APPS__.forEach((options) => { + const appName = options.name; + const appPath = options.path; + const appUrlPrefix = options.urlPrefix || '/' + appName; + + app.use(appUrlPrefix, __non_webpack_require__(appPath).createRouter({ + serveUniversal: (indexHtml, options) => serveUniversal(appName, indexHtml, options) + })); +}); /** * 404 Not Found @@ -22,7 +31,7 @@ app.use((req: Request, res: Response, next: Function) => { * Errors normalization */ app.use((err: any, req: Request, res: Response, next: Function) => { - const status: number = err.status || 500; + const status: number = err.staus || 500; let stack: string = err.message; let message: string = err.stack; diff --git a/src/app/home.spec.ts b/src/app/home.spec.ts deleted file mode 100644 index b015197..0000000 --- a/src/app/home.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - describe, - it, - expect, - TestComponentBuilder, - injectAsync, - tick, - fakeAsync, - setBaseTestProviders -} from 'angular2/testing'; - -import { - TEST_BROWSER_PLATFORM_PROVIDERS, - TEST_BROWSER_APPLICATION_PROVIDERS -} from 'angular2/platform/testing/browser'; - -import { Home } from './home'; - -setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS); - -describe('Home', () => { - it('should change name to "Angular" after 1s', injectAsync([TestComponentBuilder], fakeAsync((tcb) => { - return tcb.createAsync(Home).then((fixture) => { - const { componentInstance } = fixture; - expect(componentInstance.name).toBe('World'); - tick(1000); - expect(componentInstance.name).toBe('Angular'); - }); - }))); - - it('should set message on button click', injectAsync([TestComponentBuilder], (tcb) => { - return tcb.createAsync(Home).then((fixture) => { - const { componentInstance, nativeElement } = fixture; - - expect(componentInstance.messagePreboot).toBeFalsy(); - nativeElement.querySelector('#check-preboot').click(); - expect(componentInstance.messagePreboot).toBeTruthy(); - }); - })); - - it('should lazy load service', injectAsync([TestComponentBuilder], (tcb) => { - return tcb.createAsync(Home).then((fixture) => { - const { componentInstance, nativeElement } = fixture; - - expect(componentInstance.messageLazyLoading).toBeFalsy(); - nativeElement.querySelector('#check-lazyloading').click(); - expect(componentInstance.messageLazyLoading).toBeFalsy(); - - return new Promise(resolve => setTimeout(resolve, 100)).then(() => { - expect(componentInstance.messageLazyLoading).toBeTruthy(); - }); - }); - })); -}); diff --git a/src/boot_browser.ts b/src/boot_browser.ts deleted file mode 100644 index 78aacca..0000000 --- a/src/boot_browser.ts +++ /dev/null @@ -1,23 +0,0 @@ -import 'es6-shim'; -import 'es6-promise'; -import 'reflect-metadata'; -import 'zone.js/dist/zone-microtask'; -import 'zone.js/dist/long-stack-trace-zone'; - -import { platform, ComponentRef, Injector } from 'angular2/core'; -import { BROWSER_PROVIDERS, BROWSER_APP_PROVIDERS, } from 'angular2/platform/browser'; -import { ROUTER_PROVIDERS, Router } from 'angular2/router'; -import { App } from './app/app'; - -platform(BROWSER_PROVIDERS).application(BROWSER_APP_PROVIDERS).bootstrap(App, [ - ROUTER_PROVIDERS -]) -.then((compRef: ComponentRef) => { - const injector: Injector = compRef.injector; - const router: Router = injector.get(Router); - - return ( router)._currentNavigation; -}) -.then(() => { - document.dispatchEvent(new Event('BootstrapComplete')); -}); diff --git a/src/boot_worker.ts b/src/boot_worker.ts deleted file mode 100644 index edbc8fc..0000000 --- a/src/boot_worker.ts +++ /dev/null @@ -1,52 +0,0 @@ -import 'es6-shim'; -import 'es6-promise'; -import 'reflect-metadata'; -import 'zone.js/dist/zone-microtask'; -import 'zone.js/dist/long-stack-trace-zone'; - -import { platform, provide } from 'angular2/core'; -import { - WebWorkerInstance, - WORKER_RENDER_APP, - WORKER_RENDER_PLATFORM, - WORKER_SCRIPT, - WORKER_RENDER_ROUTER -} from 'angular2/platform/worker_render'; - -const workerScriptUrl = URL.createObjectURL(new Blob([` - var importScripts_ = this.importScripts; - - this.importScripts = function importScripts() { - for (var i = 0, scripts = new Array(arguments.length); i < scripts.length; ++i) { - var script = arguments[i]; - - if (script.indexOf('http:') !== 0 || script.indexOf('https:') !== 0) { - script = '${window.location.origin}' + (script[0] === '/' ? script : '/' + script); - } - - scripts[i] = script; - } - - return importScripts_.apply(this, scripts); - }; - - importScripts('${VENDOR_NAME}.js', '${WORKER_APP_NAME}.js'); -`], { - type: 'text/javascript' -})); - -const appRef = platform(WORKER_RENDER_PLATFORM).application([ - WORKER_RENDER_APP, - WORKER_RENDER_ROUTER, - provide(WORKER_SCRIPT, { useValue: workerScriptUrl }) -]); - -const worker = appRef.injector.get(WebWorkerInstance).worker; - -worker.addEventListener('message', function onAppReady(event) { - if (event.data === 'APP_READY') { - worker.removeEventListener('message', onAppReady, false); - URL.revokeObjectURL(workerScriptUrl); - setTimeout(() => document.dispatchEvent(new Event('BootstrapComplete'))); - } -}, false); diff --git a/src/boot_worker_app.ts b/src/boot_worker_app.ts deleted file mode 100644 index 47a0e59..0000000 --- a/src/boot_worker_app.ts +++ /dev/null @@ -1,34 +0,0 @@ -import 'es6-shim'; -import 'es6-promise'; -import 'reflect-metadata'; -import 'zone.js/dist/zone-microtask'; -import 'zone.js/dist/long-stack-trace-zone'; - -import { platform, provide, ApplicationRef, ComponentRef, Injector } from 'angular2/core'; -import { - WORKER_APP_PLATFORM, - WORKER_APP_APPLICATION, - WORKER_APP_ROUTER -} from 'angular2/platform/worker_app'; -import { APP_BASE_HREF, Router } from 'angular2/router'; -import { App } from './app/app'; - -platform(WORKER_APP_PLATFORM).asyncApplication(() => Promise.resolve([ - WORKER_APP_APPLICATION, - WORKER_APP_ROUTER, - provide(APP_BASE_HREF, { useValue: '/' }), -])) -.then((appRef: ApplicationRef) => { - return appRef.bootstrap(App, []); -}) -.then((compRef: ComponentRef) => { - const injector: Injector = compRef.injector; - const router: Router = injector.get(Router); - - return ( router)._currentNavigation; -}) -.then(() => { - setTimeout(() => { - postMessage('APP_READY', undefined); - }); -}); diff --git a/src/app/app.ts b/src/main/app.ts similarity index 100% rename from src/app/app.ts rename to src/main/app.ts diff --git a/src/main/boot_browser.ts b/src/main/boot_browser.ts new file mode 100644 index 0000000..fe4387c --- /dev/null +++ b/src/main/boot_browser.ts @@ -0,0 +1,12 @@ +import { Provider } from 'angular2/core'; +import { bootstrap } from 'angular2/platform/browser'; +import { ROUTER_PROVIDERS, APP_BASE_HREF } from 'angular2/router'; +import { App } from './app'; + +export function main(BOOT_PROVIDERS) { + return bootstrap(App, [ + BOOT_PROVIDERS, + ROUTER_PROVIDERS, + new Provider(APP_BASE_HREF, { useValue: '/' }) + ]); +}; diff --git a/src/main/boot_server.ts b/src/main/boot_server.ts new file mode 100644 index 0000000..07e8a01 --- /dev/null +++ b/src/main/boot_server.ts @@ -0,0 +1,13 @@ +import { Provider } from 'angular2/core'; +import { bootstrap, SERVER_LOCATION_PROVIDERS } from 'angular2-universal-preview'; +import { ROUTER_PROVIDERS, APP_BASE_HREF } from 'angular2/router'; +import { App } from './app'; + +export function main(BOOT_PROVIDERS) { + return bootstrap(App, [ + BOOT_PROVIDERS, + ROUTER_PROVIDERS, + SERVER_LOCATION_PROVIDERS, + new Provider(APP_BASE_HREF, { useValue: '/' }), + ]); +}; diff --git a/src/main/boot_worker_app.ts b/src/main/boot_worker_app.ts new file mode 100644 index 0000000..13adee6 --- /dev/null +++ b/src/main/boot_worker_app.ts @@ -0,0 +1,22 @@ +import { platform, Provider, ApplicationRef } from 'angular2/core'; + +import { + WORKER_APP_PLATFORM, + WORKER_APP_APPLICATION, + WORKER_APP_ROUTER +} from 'angular2/platform/worker_app'; + +import { APP_BASE_HREF } from 'angular2/router'; +import { App } from './app'; + +export function main(BOOT_PROVIDERS) { + return platform(WORKER_APP_PLATFORM).asyncApplication(() => Promise.resolve([ + BOOT_PROVIDERS, + WORKER_APP_APPLICATION, + WORKER_APP_ROUTER, + new Provider(APP_BASE_HREF, { useValue: '/' }), + ])) + .then((appRef: ApplicationRef) => { + return appRef.bootstrap(App, []) + }) +} diff --git a/src/main/boot_worker_ui.ts b/src/main/boot_worker_ui.ts new file mode 100644 index 0000000..e6c88c3 --- /dev/null +++ b/src/main/boot_worker_ui.ts @@ -0,0 +1,15 @@ +import { platform } from 'angular2/core'; + +import { + WORKER_RENDER_PLATFORM, + WORKER_RENDER_APP, + WORKER_RENDER_ROUTER +} from 'angular2/platform/worker_render'; + +export function main(BOOT_PROVIDERS) { + return platform(WORKER_RENDER_PLATFORM).application([ + BOOT_PROVIDERS, + WORKER_RENDER_APP, + WORKER_RENDER_ROUTER + ]); +} \ No newline at end of file diff --git a/src/app/greeter.ts b/src/main/greeter.ts similarity index 100% rename from src/app/greeter.ts rename to src/main/greeter.ts diff --git a/src/app/home.css b/src/main/home.css similarity index 100% rename from src/app/home.css rename to src/main/home.css diff --git a/src/app/home.html b/src/main/home.html similarity index 100% rename from src/app/home.html rename to src/main/home.html diff --git a/src/app/home.ts b/src/main/home.ts similarity index 93% rename from src/app/home.ts rename to src/main/home.ts index 68527df..e5825e7 100644 --- a/src/app/home.ts +++ b/src/main/home.ts @@ -1,5 +1,3 @@ -declare var require: any; - import { Component } from 'angular2/core'; import { NgIf } from 'angular2/common'; @@ -10,7 +8,7 @@ import { NgIf } from 'angular2/common'; styles: [require('./home.css')] }) export class Home { - name = 'World'; + name = 'World2'; messagePreboot = ''; messageLazyLoading = ''; diff --git a/src/main/index.html b/src/main/index.html new file mode 100644 index 0000000..b5a9016 --- /dev/null +++ b/src/main/index.html @@ -0,0 +1,20 @@ + + + + Angular2 Universal Starter + + + + Loading... + + + \ No newline at end of file diff --git a/src/main/index.js b/src/main/index.js new file mode 100644 index 0000000..eb0e856 --- /dev/null +++ b/src/main/index.js @@ -0,0 +1,26 @@ +const fs = require('fs'); +const path = require('path'); +const express = require('express'); +const Router = express.Router; + +exports.createRouter = function($) { + const router = Router(); + const indexHtml = fs.readFileSync(path.resolve(__dirname, 'index.html'), { encoding: 'utf8' }); + + router.get('/*', $.serveUniversal(indexHtml, { + server: true, + browser: true, + worker: true + })); + + return router; +} + +exports.createBuilds = function($) { + return [ + $.createServerConfig('./boot_server.ts'), + $.createBrowserConfig('./boot_browser.ts'), + $.createWorkerUiConfig('./boot_worker_ui.ts'), + $.createWorkerAppConfig('./boot_worker_app.ts') + ]; +} diff --git a/src/app/workers.ts b/src/main/workers.ts similarity index 100% rename from src/app/workers.ts rename to src/main/workers.ts diff --git a/src/server/ng.ts b/src/server/ng.ts deleted file mode 100644 index 80a61ae..0000000 --- a/src/server/ng.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Router, Request, Response } from 'express'; -import { provide } from 'angular2/core'; -import { PlatformLocation, APP_BASE_HREF, ROUTER_PROVIDERS } from 'angular2/router'; -import { - REQUEST_URL, - SERVER_LOCATION_PROVIDERS, - selectorResolver, - selectorRegExpFactory, - renderToStringWithPreboot -} from 'angular2-universal-preview'; - -import { App } from '../app/app'; - -function reduceScripts(content, src) { - return `${content}`; -} - -const WORKER_SCRIPTS = [`${VENDOR_NAME}.js`, `${WORKER_NAME}.js`].reduce(reduceScripts, ''); -const BROWSER_SCRIPTS = [`${VENDOR_NAME}.js`, `${BROWSER_NAME}.js`].reduce(reduceScripts, ''); - -const HTML_FILE = require('./ng.html'); - -export function renderComponent(html, component, providers, prebootOptions) { - return renderToStringWithPreboot(component, providers, prebootOptions).then((serializedCmp) => { - const selector: string = selectorResolver(component); - - return html.replace(selectorRegExpFactory(selector), serializedCmp); - }); -} - -const PROVIDERS = [ - ROUTER_PROVIDERS, - SERVER_LOCATION_PROVIDERS, - provide(APP_BASE_HREF, { useValue: '/' }), -]; - -const router = Router(); - -/** - * Angular2 application - */ -router.get('/*', (req: Request, res: Response, next: Function) => { - return Promise.resolve() - .then(() => { - if (HAS_SS) { - const REQUEST_PROVIDERS = [ - provide(REQUEST_URL, { useValue: req.originalUrl }) - ]; - - return renderComponent(HTML_FILE, App, [PROVIDERS, REQUEST_PROVIDERS], PREBOOT); - } - - return HTML_FILE; - }) - .then((rawContent) => { - const scripts = HAS_WW ? WORKER_SCRIPTS : BROWSER_SCRIPTS; - const content = rawContent.replace('', scripts + ''); - - return res.send(content); - }) - .catch(error => next(error)); -}); - -export { router }; diff --git a/tools/build.js b/tools/build.js index 96b1cb0..0e99807 100644 --- a/tools/build.js +++ b/tools/build.js @@ -5,29 +5,20 @@ const constants = require('../constants'); const EOL = os.EOL; -const VENDOR_CONFIG = configs.VENDOR_CONFIG; -const SERVER_CONFIG = configs.SERVER_CONFIG; -const BROWSER_CONFIG = configs.BROWSER_CONFIG; -const WORKER_CONFIG = configs.WORKER_CONFIG; -const WORKER_APP_CONFIG = configs.WORKER_APP_CONFIG; - -const STATS_OPTIONS = configs.STATS_OPTIONS; -const WATCH_OPTIONS = configs.WATCH_OPTIONS; - const SHOULD_WATCH = process.argv.indexOf('--watch') !== -1; function printStats(stats) { - process.stdout.write(EOL + stats.toString(STATS_OPTIONS) + EOL); + process.stdout.write(EOL + stats.toString(configs.STATS_OPTIONS) + EOL); } -webpack(VENDOR_CONFIG, function(vendorError, vendorStats) { +webpack(configs.VENDOR_CONFIG, function(vendorError, vendorStats) { if (vendorError) { throw vendorError; } printStats(vendorStats); - const compiler = webpack([SERVER_CONFIG, BROWSER_CONFIG, WORKER_CONFIG, WORKER_APP_CONFIG]); + const compiler = webpack([configs.MASTER_APP_CONFIG].concat(configs.APPS_CONFIGS)); function onInvalid() { console.info('webpack: bundle is now INVALID'); @@ -46,7 +37,7 @@ webpack(VENDOR_CONFIG, function(vendorError, vendorStats) { compiler.plugin('invalid', onInvalid); if (SHOULD_WATCH) { - compiler.watch(WATCH_OPTIONS, onDone); + compiler.watch(configs.WATCH_OPTIONS, onDone); } else { compiler.run(onDone); } diff --git a/tools/dev.js b/tools/dev.js index 1d23a36..00b9232 100644 --- a/tools/dev.js +++ b/tools/dev.js @@ -1,88 +1,78 @@ -require('reflect-metadata'); -require('zone.js/dist/zone-microtask'); -require('zone.js/dist/long-stack-trace-zone'); - const path = require('path'); const webpack = require('webpack'); +const Module = require('module').Module; const WebpackDevServer = require('webpack-dev-server'); -var MemoryFileSystem = require("memory-fs"); -const constants = require('../constants'); +const consts = require('../constants'); const configs = require('../webpack.config.js'); -const HOST = constants.HOST; -const PORT = constants.PORT; - -const HAS_WW = constants.HAS_WW; - -const SERVER_NAME = constants.SERVER_NAME; -const PRIVATE_DIR = constants.PRIVATE_DIR; -const PUBLIC_DIR = constants.PUBLIC_DIR; - -const VENDOR_CONFIG = configs.VENDOR_CONFIG; -const SERVER_CONFIG = configs.SERVER_CONFIG; -const BROWSER_CONFIG = configs.BROWSER_CONFIG; -const WORKER_CONFIG = configs.WORKER_CONFIG; -const WORKER_APP_CONFIG = configs.WORKER_APP_CONFIG; - -const DEV_OPTIONS = configs.DEV_OPTIONS; - -const SERVER_DIRNAME = PRIVATE_DIR; -const SERVER_FILENAME = path.resolve(SERVER_DIRNAME, SERVER_NAME + '.js'); +const SERVER_DIRNAME = consts.PRIVATE_DIR; +const SERVER_FILENAME = path.resolve(SERVER_DIRNAME, consts.MASTER_APP_BUNDLE_NAME + '.js'); const DEV_INDEX_SRC = '/webpack-dev-server.js'; -const DEV_CLIENT_SRC = 'webpack-dev-server/client?' + HOST + ':' + PORT + '/'; +const DEV_CLIENT_SRC = 'webpack-dev-server/client?' + consts.HOST + ':' + consts.PORT + '/'; const DEV_INDEX_SCRIPT = ''; -function addDevClientScript(config) { - if (typeof config.entry === 'object' && !Array.isArray(config.entry)) { - Object.keys(config.entry).forEach(function(key) { - config.entry[key] = [DEV_CLIENT_SRC].concat(config.entry[key]) - }); - } else { - config.entry = [DEV_CLIENT_SRC].concat(config.entry); - } -} - -const configsList = [SERVER_CONFIG]; - -if (HAS_WW) { - addDevClientScript(WORKER_CONFIG); - configsList.push(WORKER_CONFIG, WORKER_APP_CONFIG); -} else { - addDevClientScript(BROWSER_CONFIG); - configsList.push(BROWSER_CONFIG); -} - -function recompileApp(content) { - const exports_ = {}; - const module_ = { exports: exports_ }; - - // TODO: Replace on vm.runInNewContext when it's possible - new Function( - 'module', 'exports', 'require', 'process', '__filename', '__dirname', content - )( module_, exports_, require, process, SERVER_FILENAME, SERVER_DIRNAME); - - return module_.exports.app; -} +const configsList = [configs.MASTER_APP_CONFIG].concat(configs.APPS_CONFIGS); + +configsList + .filter(function filterClientConfigs(config) { + return config.target === 'web' + }) + .forEach(function addDevClientScript(config) { + if (typeof config.entry === 'object' && !Array.isArray(config.entry)) { + Object.keys(config.entry).forEach(function(key) { + config.entry[key] = [DEV_CLIENT_SRC].concat(config.entry[key]) + }); + } else { + config.entry = [DEV_CLIENT_SRC].concat(config.entry); + } + }); function runDevServer() { var app; - const compiler = Object.create(webpack(configsList), { outputPath: { value: PUBLIC_DIR }}); - const server = new WebpackDevServer(compiler, DEV_OPTIONS); + const compiler = Object.create(webpack(configsList), { outputPath: { value: consts.PUBLIC_DIR }}); + const server = new WebpackDevServer(compiler, configs.DEV_OPTIONS); + const fileSystem = server.middleware.fileSystem; + + function require_(modulePath) { + if (0 === modulePath.indexOf(consts.DIST_DIR)) { + const content = fileSystem.readFileSync(modulePath, 'utf8'); + + const exports_ = {}; + const module_ = { exports: exports_ }; + + // TODO: Replace on vm.runInNewContext when it's possible + new Function( + 'module', 'exports', 'require', 'process', '__filename', '__dirname', content + )( module_, exports_, require_, process, SERVER_FILENAME, SERVER_DIRNAME); + + return module_.exports; + } + + return require(modulePath); + } + Object.assign(require_, require); + compiler.plugin('done', function onCompilationDone() { - app = recompileApp(server.middleware.fileSystem.readFileSync(SERVER_FILENAME, 'utf8')); + app = require_(SERVER_FILENAME).app; }); server.use('/', function proxyApp(req, res, next) { const send_ = res.send; res.send = function send(content) { + if (!res.statusCode) { + res.status(500); + } + if (res.statusCode >= 400) { const tag = ['body', 'head', 'html'].find(function(tag) { return !!~content.indexOf('') }); + console.log('HERE=>>', res.statusCode, content); + if (tag) { content = content.replace('', DEV_INDEX_SCRIPT + '$&'); } else { @@ -90,16 +80,16 @@ function runDevServer() { } } - return send_.call(this, content); + return send_.call(res, content); }; - + return app(req, res, next); }); - server.listen(PORT, HOST); + server.listen(consts.PORT, consts.HOST); } -webpack(VENDOR_CONFIG, function(error, stats) { +webpack(configs.VENDOR_CONFIG, function(error, stats) { if (error) { throw error; } diff --git a/tools/prod.js b/tools/prod.js index 23d86da..a75b4db 100644 --- a/tools/prod.js +++ b/tools/prod.js @@ -1,16 +1,6 @@ -require('reflect-metadata'); -require('zone.js/dist/zone-microtask'); -require('zone.js/dist/long-stack-trace-zone'); - const http = require('http'); -const constants = require('../constants'); - -const HOST = constants.HOST; -const PORT = constants.PORT; - -const PRIVATE_DIR = constants.PRIVATE_DIR; -const SERVER_NAME = constants.SERVER_NAME; +const consts= require('../constants'); -const app = require(PRIVATE_DIR + '/' + SERVER_NAME).app; +const app = require(consts.PRIVATE_DIR + '/' + consts.MASTER_APP_BUNDLE_NAME).app; -http.createServer(app).listen(PORT, HOST); +http.createServer(app).listen(consts.PORT, consts.HOST); diff --git a/tsconfig.json b/tsconfig.json index 0c10c61..2a87c5f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,12 +10,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true }, - "files": [ - "typings.d.ts", - "src/boot_browser.ts", - "src/boot_worker.ts", - "src/boot_worker_app.ts", - "src/server/app.ts", - "test/unit.spec.ts" + "exclude": [ + "node_modules" ] } \ No newline at end of file diff --git a/typings.d.ts b/typings.d.ts index edb0d2b..af5abd8 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -1,27 +1,38 @@ /// -declare const ROOT_DIR: string; -declare const SRC_DIR: string; -declare const DIST_DIR: string; -declare const PUBLIC_DIR: string; -declare const PRIVATE_DIR: string; -declare const SERVER_DIR: string; +declare const __non_webpack_require__: any; +declare const __webpack_require__: any; -declare const HOST: string; -declare const PORT: number; +declare const __ROOT_DIR__: string; +declare const __SRC_DIR__: string; +declare const __DIST_DIR__: string; +declare const __PUBLIC_DIR__: string; +declare const __PRIVATE_DIR__: string; +declare const __SERVER_DIR__: string; -declare const HAS_SS: boolean; -declare const HAS_WW: boolean; +declare const __HOST__: string; +declare const __PORT__: number; -declare const VENDOR_NAME: string; -declare const SERVER_NAME: string; -declare const BROWSER_NAME: string; -declare const WORKER_NAME: string; -declare const WORKER_APP_NAME: string; +declare const __SS__: boolean; +declare const __WW__: boolean; -declare const NODE_MODULES: string[]; +declare const __APPS__: Array<{ name: string, path: string, urlPrefix?: string }> -declare const PREBOOT: { +declare const __APPS_INDEX_FILENAME__: string; + +declare const __APPS_SERVER_BUNDLE_NAME__: string; +declare const __APPS_BROWSER_BUNDLE_NAME__: string; +declare const __APPS_WORKER_UI_BUNDLE_NAME__: string; +declare const __APPS_WORKER_APP_BUNDLE_NAME__: string; + +declare const __VENDOR_BUNDLE_NAME__: string; +declare const __VENDOR_DLL_MANIFEST_FILENAME__: string; +declare const __VENDOR_DLL_MANIFEST_PATH__: string; + +declare const __MASTER_APP_BUNDLE_NAME__: string; +declare const __MASTER_APP_SOURCE_PATH__: string; + +declare const __PREBOOT__: { appRoot: string, freeze: any, replay: string, @@ -29,3 +40,12 @@ declare const PREBOOT: { debug: boolean, uglify: boolean, }; + +declare var require: { + (id: string): any; + resolve(id: string): string; + cache: any; + extensions: any; + main: any; + ensure: Function; +}; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index cf48f02..6e0e368 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,35 +1,18 @@ -const webpack = require('webpack'); -const fs = require('fs'); -const path = require('path'); -const constants = require('./constants'); +const webpack = require('webpack'); +const fs = require('fs'); +const path = require('path'); +const consts = require('./constants'); const DefinePlugin = webpack.DefinePlugin; const DllPlugin = webpack.DllPlugin; const DllReferencePlugin = webpack.DllReferencePlugin; -const ROOT_DIR = constants.ROOT_DIR; -const SRC_DIR = constants.SRC_DIR; -const PUBLIC_DIR = constants.PUBLIC_DIR; -const PRIVATE_DIR = constants.PRIVATE_DIR; - -const VENDOR_NAME = constants.VENDOR_NAME; -const SERVER_NAME = constants.SERVER_NAME; -const BROWSER_NAME = constants.BROWSER_NAME; -const WORKER_NAME = constants.WORKER_NAME; -const WORKER_APP_NAME = constants.WORKER_APP_NAME; - -const SERVER_SOURCE_PATH = constants.SERVER_SOURCE_PATH; -const BROWSER_SOURCE_PATH = constants.BROWSER_SOURCE_PATH; -const WORKER_SOURCE_PATH = constants.WORKER_SOURCE_PATH; -const WORKER_APP_SOURCE_PATH = constants.WORKER_APP_SOURCE_PATH; - -const VENDOR_DLL_MANIFEST_FILE = constants.VENDOR_DLL_MANIFEST_FILE; -const VENDOR_DLL_MANIFEST_PATH = constants.VENDOR_DLL_MANIFEST_PATH; - -const NODE_MODULES = fs.readdirSync(ROOT_DIR + '/node_modules').filter(function(name) { +const NODE_MODULES = fs.readdirSync(consts.ROOT_DIR + '/node_modules').filter(function(name) { return name != '.bin'; }); +const SERVER_EXTERNALS = NODE_MODULES.map(function(name) { return new RegExp('^' + name) }); + const STATS_OPTIONS = { colors: { level: 2, @@ -90,182 +73,244 @@ const POSTCSS = function() { ] } -const DEFINE_CONSTANTS_PLUGIN = new DefinePlugin((function stringifyConstants() { +const RUN_BROWSER_PATH = path.resolve(consts.SRC_DIR, '.utils/run_browser.ts'); +const RUN_WORKER_UI_PATH = path.resolve(consts.SRC_DIR, '.utils/run_worker_ui.ts'); +const RUN_WORKER_APP_PATH = path.resolve(consts.SRC_DIR, '.utils/run_worker_app.ts'); + +const POLIFILLS = [ + 'es6-shim', + 'es6-promise', + 'reflect-metadata', + 'zone.js/dist/zone-microtask', + 'zone.js/dist/long-stack-trace-zone', +]; + +const CONSTANTS_DEFINE_PLUGIN = new DefinePlugin((function() { const stringifiedConstants = {}; - Object.keys(constants).forEach(function(constantName) { - stringifiedConstants[constantName] = JSON.stringify(constants[constantName]); + Object.keys(consts).forEach(function(constantName) { + stringifiedConstants['__' + constantName + '__'] = JSON.stringify(consts[constantName]); }); - + return stringifiedConstants; })()); const VENDOR_DLL_REFERENCE_PLUGIN = new DllReferencePlugin({ - context: ROOT_DIR, + context: consts.ROOT_DIR, sourceType: 'var', get manifest() { - return require(VENDOR_DLL_MANIFEST_PATH); + return require(path.resolve(consts.MANIFESTS_DIR, consts.VENDOR_BUNDLE_NAME + '.json')); } }); const VENDOR_CONFIG = { target: 'web', entry: { - [VENDOR_NAME]: [ + [consts.VENDOR_BUNDLE_NAME]: [ 'es6-shim', 'es6-promise', 'reflect-metadata', 'zone.js/dist/zone-microtask', 'zone.js/dist/long-stack-trace-zone', 'angular2/core', - 'angular2/router', + 'angular2/router' ] }, output: { - path: PUBLIC_DIR, + path: consts.PUBLIC_DIR, filename: '[name].js', - library: VENDOR_NAME, + library: '[name]', libraryTarget: 'var' }, plugins: [ new DllPlugin({ - name: VENDOR_NAME, - path: VENDOR_DLL_MANIFEST_PATH + name: consts.VENDOR_BUNDLE_NAME, + path: path.resolve(consts.MANIFESTS_DIR, consts.VENDOR_BUNDLE_NAME + '.json') }) ] }; -const BROWSER_CONFIG = { - target: 'web', - entry: { - [BROWSER_NAME]: [ - BROWSER_SOURCE_PATH - ] - }, - output: { - path: PUBLIC_DIR, - filename: '[name].js', - chunkFilename: '[id].' + BROWSER_NAME + '.js', - }, - plugins: [ - VENDOR_DLL_REFERENCE_PLUGIN - ], - resolve: { - extensions: ['', '.ts', '.js'] - }, - module: { - loaders: LOADERS - }, - postcss: POSTCSS -}; +function createServerConfig(appName, sourcePath) { + return { + target: 'node', + entry: { + [consts.APPS_SERVER_BUNDLE_NAME]: POLIFILLS.concat(sourcePath) + }, + output: { + path: consts.PRIVATE_DIR, + filename: appName + '-[name].js', + chunkFilename: appName + '-' + consts.APPS_SERVER_BUNDLE_NAME + '-[id].js', + library: '[name]', + libraryTarget: 'commonjs2' + }, + plugins: [ + VENDOR_DLL_REFERENCE_PLUGIN, + CONSTANTS_DEFINE_PLUGIN + ], + node: { + __dirname: true, + __filename: true + }, + externals: SERVER_EXTERNALS, + resolve: { + extensions: ['', '.ts', '.js'] + }, + module: { + loaders: LOADERS + }, + postcss: POSTCSS + }; +} -const WORKER_CONFIG = { - target: 'web', - entry: { - [WORKER_NAME]: [ - WORKER_SOURCE_PATH - ] - }, - output: { - path: PUBLIC_DIR, - filename: '[name].js', - chunkFilename: '[id].' + WORKER_NAME + '.js', - }, - plugins: [ - VENDOR_DLL_REFERENCE_PLUGIN, - DEFINE_CONSTANTS_PLUGIN, - ], - resolve: { - extensions: ['', '.ts', '.js'] - }, - module: { - loaders: LOADERS - }, - postcss: POSTCSS -}; +function createBrowserConfig(appName, sourcePath) { + return { + target: 'web', + entry: { + [consts.APPS_BROWSER_BUNDLE_NAME]: POLIFILLS.concat(RUN_BROWSER_PATH, sourcePath) + }, + output: { + path: consts.PUBLIC_DIR, + filename: appName + '-[name].js', + chunkFilename: appName + '-' + consts.APPS_BROWSER_BUNDLE_NAME + '-[id].js', + library: '[name]', + libraryTarget: 'var' + }, + plugins: [ + VENDOR_DLL_REFERENCE_PLUGIN, + CONSTANTS_DEFINE_PLUGIN + ], + resolve: { + extensions: ['', '.ts', '.js'] + }, + module: { + loaders: LOADERS + }, + postcss: POSTCSS + }; +} -const WORKER_APP_CONFIG = { - target: 'webworker', - entry: { - [WORKER_APP_NAME]: [ - WORKER_APP_SOURCE_PATH - ] - }, - output: { - path: PUBLIC_DIR, - filename: '[name].js', - chunkFilename: '[id].' + WORKER_APP_NAME + '.js' - }, - get plugins() { - return [ +function createWorkerUiConfig(appName, sourcePath) { + return { + target: 'web', + entry: { + [consts.APPS_WORKER_UI_BUNDLE_NAME]: POLIFILLS.concat(RUN_WORKER_UI_PATH, sourcePath) + }, + output: { + path: consts.PUBLIC_DIR, + filename: appName + '-[name].js', + chunkFilename: appName + '-' + consts.APPS_WORKER_UI_BUNDLE_NAME + '-[id].js', + library: '[name]', + libraryTarget: 'var' + }, + plugins: [ + VENDOR_DLL_REFERENCE_PLUGIN, + CONSTANTS_DEFINE_PLUGIN + ], + resolve: { + extensions: ['', '.ts', '.js'] + }, + module: { + loaders: LOADERS + }, + postcss: POSTCSS + }; +} + +function createWorkerAppConfig(appName, sourcePath) { + return { + target: 'webworker', + entry: { + [consts.APPS_WORKER_APP_BUNDLE_NAME]: POLIFILLS.concat(RUN_WORKER_APP_PATH, sourcePath) + }, + output: { + path: consts.PUBLIC_DIR, + filename: appName + '-[name].js', + chunkFilename: appName + '-' + consts.APPS_WORKER_APP_BUNDLE_NAME + '-[id].js', + library: '[name]', + libraryTarget: 'var' + }, + plugins: [ VENDOR_DLL_REFERENCE_PLUGIN, - DEFINE_CONSTANTS_PLUGIN, - ]; - } , + CONSTANTS_DEFINE_PLUGIN + ], + resolve: { + extensions: ['', '.ts', '.js'] + }, + module: { + loaders: LOADERS + }, + postcss: POSTCSS + }; +}; + +const TESTING_CONFIG = { resolve: { extensions: ['', '.ts', '.js'] }, module: { loaders: LOADERS }, - postcss: POSTCSS + devServer: { + quiet: true, + noInfo: true, + } }; -const SERVER_CONFIG = { +const APPS_CONFIGS = []; + +consts.APPS.map(function(options) { + const appName = options.name; + const appPath = options.path; + const appDir = path.dirname(appPath) + + const APP_CONFIGS = require(appPath).createBuilds({ + createServerConfig: function(sourcePath) { + return createServerConfig(appName, path.resolve(appDir, sourcePath)); + }, + createBrowserConfig: function(sourcePath) { + return createBrowserConfig(appName, path.resolve(appDir, sourcePath)); + }, + createWorkerUiConfig: function(sourcePath) { + return createWorkerUiConfig(appName, path.resolve(appDir, sourcePath)); + }, + createWorkerAppConfig: function(sourcePath) { + return createWorkerAppConfig(appName, path.resolve(appDir, sourcePath)); + } + }); + + [].push.apply(APPS_CONFIGS, APP_CONFIGS); +}); + +const MASTER_APP_CONFIG = { target: 'node', entry: { - [SERVER_NAME]: [ - SERVER_SOURCE_PATH - ] + [consts.MASTER_APP_BUNDLE_NAME]: POLIFILLS.concat(consts.MASTER_APP_SOURCE_PATH) }, output: { - path: PRIVATE_DIR, + path: consts.PRIVATE_DIR, filename: '[name].js', - chunkFilename: '[id].' + SERVER_NAME + '.js', - library: SERVER_NAME, + library: '[name]', libraryTarget: 'commonjs2' }, - plugins: [ - DEFINE_CONSTANTS_PLUGIN - ], - node: { - __dirname: true, - __filename: true - }, - externals: [ - NODE_MODULES.map(function(name) { return new RegExp('^' + name) }), - ], + plugins: [].concat( + CONSTANTS_DEFINE_PLUGIN + ), resolve: { extensions: ['', '.ts', '.js'] }, module: { loaders: LOADERS }, + externals: SERVER_EXTERNALS, postcss: POSTCSS }; -const TESTING_CONFIG = { - resolve: { - extensions: ['', '.ts', '.js'] - }, - module: { - loaders: LOADERS - }, - devServer: { - quiet: true, - noInfo: true, - } -}; - -exports = module.exports = [VENDOR_CONFIG, BROWSER_CONFIG, WORKER_CONFIG, WORKER_APP_CONFIG, SERVER_CONFIG]; - exports.VENDOR_CONFIG = VENDOR_CONFIG; -exports.SERVER_CONFIG = SERVER_CONFIG; -exports.BROWSER_CONFIG = BROWSER_CONFIG; -exports.WORKER_CONFIG = WORKER_CONFIG; -exports.WORKER_APP_CONFIG = WORKER_APP_CONFIG; +exports.MASTER_APP_CONFIG = MASTER_APP_CONFIG; exports.TESTING_CONFIG = TESTING_CONFIG; +exports.APPS_CONFIGS = APPS_CONFIGS; + exports.STATS_OPTIONS = STATS_OPTIONS; exports.WATCH_OPTIONS = WATCH_OPTIONS; exports.DEV_OPTIONS = DEV_OPTIONS;