From 0d32e5e791d163d1b309ca0add4a3b42b7b5cde1 Mon Sep 17 00:00:00 2001 From: Dan Bucholtz Date: Thu, 2 Mar 2017 14:28:15 -0600 Subject: [PATCH] chore(gulp): updates to build process to use app-scripts for watch/build updates to build process to use app-scripts for watch/build --- scripts/README.md | 12 +- scripts/demos/copy.config.js | 26 +- scripts/demos/demos.template.dev.html | 57 --- .../{demos.template.prod.html => index.html} | 4 +- scripts/demos/main.ts | 5 - scripts/demos/watch.config.js | 15 + scripts/e2e/copy.config.js | 28 +- scripts/e2e/e2e.template.dev.html | 83 ---- scripts/e2e/e2e.template.js | 2 +- .../{e2e.template.prod.html => index.html} | 4 +- scripts/e2e/main.ts | 5 - scripts/e2e/variables.scss | 2 +- scripts/gulp/constants.ts | 2 +- scripts/gulp/declarations.d.ts | 1 + scripts/gulp/gulpfile.ts | 1 - scripts/gulp/tasks/build.ts | 67 ++- scripts/gulp/tasks/demos.dev.ts | 205 ++------- scripts/gulp/tasks/demos.prod.ts | 348 +++++++-------- scripts/gulp/tasks/e2e.dev.ts | 248 ++--------- scripts/gulp/tasks/e2e.prod.ts | 401 +++++++++--------- scripts/gulp/tasks/e2e.ts | 55 --- scripts/gulp/tasks/snapshot.ts | 26 +- scripts/gulp/tasks/test.ts | 4 + scripts/gulp/util.ts | 112 +++-- .../gulp/utils/app-scripts-worker-client.ts | 102 +++++ scripts/gulp/utils/interfaces.ts | 18 + scripts/npm/package.json | 2 +- scripts/snapshot/ionic.snapshot.js | 7 +- scripts/templates/component/module.ts.tmpl | 17 + scripts/templates/page/module.ts.tmpl | 17 + 30 files changed, 808 insertions(+), 1068 deletions(-) delete mode 100644 scripts/demos/demos.template.dev.html rename scripts/demos/{demos.template.prod.html => index.html} (80%) delete mode 100644 scripts/demos/main.ts create mode 100644 scripts/demos/watch.config.js delete mode 100644 scripts/e2e/e2e.template.dev.html rename scripts/e2e/{e2e.template.prod.html => index.html} (91%) delete mode 100644 scripts/e2e/main.ts delete mode 100644 scripts/gulp/tasks/e2e.ts create mode 100644 scripts/gulp/utils/app-scripts-worker-client.ts create mode 100644 scripts/gulp/utils/interfaces.ts create mode 100644 scripts/templates/component/module.ts.tmpl create mode 100644 scripts/templates/page/module.ts.tmpl diff --git a/scripts/README.md b/scripts/README.md index 835b7354ba..6f861e955e 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -28,20 +28,18 @@ Run `gulp build` or `gulp watch` to watch for changes. #### Development -1. Run `gulp e2e` or `gulp e2e.watch` to watch for changes. -2. Navigate to `http://localhost:8000/dist/e2e` +1. Run `gulp e2e.watch --folder nav/basic` to watch for changes, where `nav` is the component, and `basic` is the test name +2. The browser will launch just like when using `ionic serve`. Make changes to an app in the `src` directory and the app will rebuild. #### Validation The following commands take longer to run because they use AoT compilation. They should really only be used to validate that our components work with AoT, and fix them if not. -1. Run `gulp e2e.prod` to bundle all e2e tests. Folder is optional, see the flags section below. -2. Run `gulp e2e.watchProd` with a folder passed to watch a test. Folder is required, see the flags section below. -3. Navigate to `http://localhost:8000/dist/e2e` +1. Run `gulp e2e.prod` to bundle all e2e tests. ##### Flags -- `--f | -folder` will run the command with a test folder. For example, `gulp e2e.watchProd --f=select/single-value` will watch the test in `src/components/select/test/single-value`. +- `--f | -folder` will run the command with a test folder. - `--debug` will run the `ionic-app-scripts` command with debug output printed. @@ -98,8 +96,6 @@ To remove the linked version of `ionic-angular` do `npm rm ionic-angular`, and t - `gulp snapshot` will run the `gulp e2e.prod` task with AoT compilation. - `gulp snapshot.skipBuild` will skip the `gulp e2e.prod` task with AoT compilation. -- `gulp snapshot.dev` will run a development build using the `gulp e2e` task. -- `gulp snapshot.quick` will skip the build and run snapshot without uploading to the server. #### Flags diff --git a/scripts/demos/copy.config.js b/scripts/demos/copy.config.js index 769ba9d6ea..bbf7187de9 100644 --- a/scripts/demos/copy.config.js +++ b/scripts/demos/copy.config.js @@ -1,7 +1,25 @@ // we don't want to run copy for the demos, so just override the config for now +var path = require('path'); + module.exports = { - copyAssets: { }, - copyIndexContent: { }, - copyFonts: { }, - copyPolyfills: { } + copyAssets: { + src: [path.join(path.dirname(process.env.IONIC_APP_ENTRY_POINT), '..', 'assets', '**', '*')], + dest: '{{WWW}}/assets' + }, + copyIndexContent: { + src: [path.join(process.cwd(), 'scripts', 'demos', 'index.html')], + dest: '{{WWW}}' + }, + copyFonts: { + src: [`${process.cwd()}/node_modules/ionicons/dist/fonts/**/*`, `${process.cwd()}/src/fonts/**/*`], + dest: '{{WWW}}/assets/fonts' + }, + copyPolyfills: { + src: [path.join(process.cwd(), 'dist', 'demos', 'polyfills', 'polyfills.js')], + dest: '{{BUILD}}' + }, + sharedCss: { + src: [path.join(process.cwd(), 'scripts', 'demos', 'demos.shared.css')], + dest: `{{BUILD}}` + } } \ No newline at end of file diff --git a/scripts/demos/demos.template.dev.html b/scripts/demos/demos.template.dev.html deleted file mode 100644 index 78e5775974..0000000000 --- a/scripts/demos/demos.template.dev.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - Ionic E2E - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/demos/demos.template.prod.html b/scripts/demos/index.html similarity index 80% rename from scripts/demos/demos.template.prod.html rename to scripts/demos/index.html index b5b48d9bd5..f9debb29f3 100644 --- a/scripts/demos/demos.template.prod.html +++ b/scripts/demos/index.html @@ -7,13 +7,13 @@ - + - + diff --git a/scripts/demos/main.ts b/scripts/demos/main.ts deleted file mode 100644 index 6af7a5b2ae..0000000000 --- a/scripts/demos/main.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - -import { AppModule } from './app.module'; - -platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/scripts/demos/watch.config.js b/scripts/demos/watch.config.js new file mode 100644 index 0000000000..0ecfa672e2 --- /dev/null +++ b/scripts/demos/watch.config.js @@ -0,0 +1,15 @@ +var path = require('path'); + +var watch = require('../../node_modules/@ionic/app-scripts/dist/watch'); + +var entryPointDirectory = path.dirname(process.env.IONIC_APP_ENTRY_POINT) + +module.exports = { + demoSrc: { + paths: [path.join(entryPointDirectory, '..', '**', '*.(ts|html|s(c|a)ss)')], + options: { ignored: [path.join(entryPointDirectory, '..', '**', '*.spec.ts'), + path.join(entryPointDirectory, '..', '**', '*.e2e.ts'), + '**/*.DS_Store'] }, + callback: watch.buildUpdate + } +} diff --git a/scripts/e2e/copy.config.js b/scripts/e2e/copy.config.js index 769ba9d6ea..6e0656e814 100644 --- a/scripts/e2e/copy.config.js +++ b/scripts/e2e/copy.config.js @@ -1,7 +1,25 @@ // we don't want to run copy for the demos, so just override the config for now +var path = require('path'); + module.exports = { - copyAssets: { }, - copyIndexContent: { }, - copyFonts: { }, - copyPolyfills: { } -} \ No newline at end of file + copyAssets: { + src: [path.join(path.dirname(process.env.IONIC_APP_ENTRY_POINT), '..', 'assets', '**', '*')], + dest: '{{WWW}}/assets' + }, + copyIndexContent: { + src: [path.join(process.cwd(), 'scripts', 'e2e', 'index.html')], + dest: '{{WWW}}' + }, + copyFonts: { + src: [`${process.cwd()}/node_modules/ionicons/dist/fonts/**/*`, `${process.cwd()}/src/fonts/**/*`], + dest: '{{WWW}}/assets/fonts' + }, + copyPolyfills: { + src: [path.join(process.cwd(), 'dist', 'e2e', 'polyfills', 'polyfills.ng.js')], + dest: '{{BUILD}}' + }, + sharedCss: { + src: [path.join(process.cwd(), 'scripts', 'e2e', 'e2e.shared.css')], + dest: `{{BUILD}}` + } +} diff --git a/scripts/e2e/e2e.template.dev.html b/scripts/e2e/e2e.template.dev.html deleted file mode 100644 index 321cf7acc5..0000000000 --- a/scripts/e2e/e2e.template.dev.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - Ionic E2E - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/e2e/e2e.template.js b/scripts/e2e/e2e.template.js index 34aeb4a0ba..b46280f263 100644 --- a/scripts/e2e/e2e.template.js +++ b/scripts/e2e/e2e.template.js @@ -1,4 +1,4 @@ -describe('<%= relativePath %>: <%= platform %>', function() { +describe('<%= relativePathBackwardsCompatibility %>: <%= platform %>', function() { it('should init', function() { browser.get('http://localhost:<%= buildConfig.protractorPort %>/dist/e2e/<%= relativePath %>/index.html?ionicplatform=<%= platform %>&ionicOverlayCreatedDiff=0&ionicanimate=false&snapshot=true'); diff --git a/scripts/e2e/e2e.template.prod.html b/scripts/e2e/index.html similarity index 91% rename from scripts/e2e/e2e.template.prod.html rename to scripts/e2e/index.html index 657bd5ee96..fe5971a76d 100644 --- a/scripts/e2e/e2e.template.prod.html +++ b/scripts/e2e/index.html @@ -7,7 +7,7 @@ - + - + diff --git a/scripts/e2e/main.ts b/scripts/e2e/main.ts deleted file mode 100644 index 6af7a5b2ae..0000000000 --- a/scripts/e2e/main.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - -import { AppModule } from './app.module'; - -platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/scripts/e2e/variables.scss b/scripts/e2e/variables.scss index a3697d5886..c0f8fe6c14 100644 --- a/scripts/e2e/variables.scss +++ b/scripts/e2e/variables.scss @@ -3,7 +3,7 @@ // Font path is used to include ionicons, // roboto, and noto sans fonts -$font-path: "../../../../../fonts"; +$font-path: "../assets/fonts"; @import "ionic.globals"; diff --git a/scripts/gulp/constants.ts b/scripts/gulp/constants.ts index 5ba3feb6b9..d266752d1b 100644 --- a/scripts/gulp/constants.ts +++ b/scripts/gulp/constants.ts @@ -31,7 +31,7 @@ export const DIST_BUILD_ROOT = join(DIST_ROOT, PACKAGE_NAME); export const DIST_BUNDLE_ROOT = join(DIST_BUILD_ROOT, BUNDLES); export const DIST_BUILD_UMD_ROOT = join(DIST_BUILD_ROOT, UMD_MODULE); export const DIST_BUILD_UMD_BUNDLE_ENTRYPOINT = join(DIST_BUILD_ROOT, INDEX_JS); -export const DIST_BUILD_ES2015_ROOT = join(DIST_BUILD_ROOT, ES_2015); +export const DIST_BUILD_ESM_ROOT = join(DIST_BUILD_ROOT, 'esm'); export const DIST_VENDOR_ROOT = join(DIST_ROOT, VENDOR_NAME); export const NODE_MODULES_ROOT = join(PROJECT_ROOT, NODE_MODULES); export const SCRIPTS_ROOT = join(PROJECT_ROOT, SCRIPTS_NAME); diff --git a/scripts/gulp/declarations.d.ts b/scripts/gulp/declarations.d.ts index 02f8f35893..c908c3f5aa 100644 --- a/scripts/gulp/declarations.d.ts +++ b/scripts/gulp/declarations.d.ts @@ -15,6 +15,7 @@ declare module 'gulp-tslint'; declare module 'gulp-typescript'; declare module 'html-entities'; declare module 'inquirer'; +declare module 'p-all'; declare module 'path'; declare module 'rollup'; declare module 'rollup-plugin-commonjs'; diff --git a/scripts/gulp/gulpfile.ts b/scripts/gulp/gulpfile.ts index f5a0716ed0..4c9fd9142b 100644 --- a/scripts/gulp/gulpfile.ts +++ b/scripts/gulp/gulpfile.ts @@ -5,7 +5,6 @@ import './tasks/demos'; import './tasks/demos.dev'; import './tasks/demos.prod'; import './tasks/docs'; -import './tasks/e2e'; import './tasks/e2e.dev'; import './tasks/e2e.prod'; import './tasks/lint'; diff --git a/scripts/gulp/tasks/build.ts b/scripts/gulp/tasks/build.ts index 30a0026a76..213b444824 100644 --- a/scripts/gulp/tasks/build.ts +++ b/scripts/gulp/tasks/build.ts @@ -1,6 +1,6 @@ import { task } from 'gulp'; -import { DIST_BUILD_ROOT, DIST_BUILD_ES2015_ROOT, DIST_BUILD_UMD_ROOT, ES5, ES_2015, PROJECT_ROOT, UMD_MODULE } from '../constants'; -import { copySourceToDest, createTempTsConfig, deleteFiles, runNgc } from '../util'; +import { DIST_BUILD_ROOT, DIST_BUILD_ESM_ROOT, DIST_BUILD_UMD_ROOT, ES5, ES_2015, PROJECT_ROOT, UMD_MODULE } from '../constants'; +import { copySourceToDest, createTempTsConfig, deleteFiles, runNgc, runTsc } from '../util'; export function buildIonicAngularUmd(excludeSpec: boolean, stripDebug: boolean, done: Function) { @@ -23,11 +23,51 @@ export function buildIonicAngularUmd(excludeSpec: boolean, stripDebug: boolean, }); } +export function buildIonicAngularUmdTsc(excludeSpec: boolean, stripDebug: boolean, done: Function) { + const stream = copySourceToDest(DIST_BUILD_UMD_ROOT, excludeSpec, true, stripDebug); + stream.on('end', () => { + // the source files are copied, copy over a tsconfig from + createTempTsConfig(['./**/*.ts'], ES5, UMD_MODULE, `${PROJECT_ROOT}/tsconfig.json`, `${DIST_BUILD_UMD_ROOT}/tsconfig.json`); + runTsc(`${DIST_BUILD_UMD_ROOT}/tsconfig.json`, (err) => { + if (err) { + done(err); + return; + } + + // clean up any .ts files that remain as well as ngc metadata + deleteFiles([`${DIST_BUILD_UMD_ROOT}/**/*.ts`, + `${DIST_BUILD_UMD_ROOT}/node_modules`, + `${DIST_BUILD_UMD_ROOT}/tsconfig.json`, + `!${DIST_BUILD_UMD_ROOT}/**/*.d.ts`], done); + }); + }); +} + + export function buildIonicAngularEsm(stripDebug: boolean, done: Function) { + const stream = copySourceToDest(DIST_BUILD_ESM_ROOT, true, true, stripDebug); + stream.on('end', () => { + // the source files are copied, copy over a tsconfig from + createTempTsConfig(['./**/*.ts'], ES_2015, ES_2015, `${PROJECT_ROOT}/tsconfig.json`, `${DIST_BUILD_ESM_ROOT}/tsconfig.json`); + runNgc(`${DIST_BUILD_ESM_ROOT}/tsconfig.json`, (err) => { + if (err) { + done(err); + return; + } + // clean up any .ts files that remain as well as ngc metadata + deleteFiles([`${DIST_BUILD_ESM_ROOT}/**/*.ts`, + `${DIST_BUILD_ESM_ROOT}/node_modules`, + `${DIST_BUILD_ESM_ROOT}/tsconfig.json`, + `!${DIST_BUILD_ESM_ROOT}/**/*.d.ts`], done); + }); + }); +} + +export function buildIonicPureEs6(stripDebug: boolean, done: Function) { const stream = copySourceToDest(DIST_BUILD_ROOT, true, true, stripDebug); stream.on('end', () => { // the source files are copied, copy over a tsconfig from - createTempTsConfig(['./**/*.ts'], ES5, ES_2015, `${PROJECT_ROOT}/tsconfig.json`, `${DIST_BUILD_ROOT}/tsconfig.json`); + createTempTsConfig(['./**/*.ts'], ES_2015, ES_2015, `${PROJECT_ROOT}/tsconfig.json`, `${DIST_BUILD_ROOT}/tsconfig.json`); runNgc(`${DIST_BUILD_ROOT}/tsconfig.json`, (err) => { if (err) { done(err); @@ -42,28 +82,9 @@ export function buildIonicAngularEsm(stripDebug: boolean, done: Function) { }); } -export function buildIonicPureEs6(stripDebug: boolean, done: Function) { - const stream = copySourceToDest(DIST_BUILD_ES2015_ROOT, true, true, stripDebug); - stream.on('end', () => { - // the source files are copied, copy over a tsconfig from - createTempTsConfig(['./**/*.ts'], ES_2015, ES_2015, `${PROJECT_ROOT}/tsconfig.json`, `${DIST_BUILD_ES2015_ROOT}/tsconfig.json`); - runNgc(`${DIST_BUILD_ES2015_ROOT}/tsconfig.json`, (err) => { - if (err) { - done(err); - return; - } - // clean up any .ts files that remain as well as ngc metadata - deleteFiles([`${DIST_BUILD_ES2015_ROOT}/**/*.ts`, - `${DIST_BUILD_ES2015_ROOT}/node_modules`, - `${DIST_BUILD_ES2015_ROOT}/tsconfig.json`, - `!${DIST_BUILD_ES2015_ROOT}/**/*.d.ts`], done); - }); - }); -} - /* this task builds out the necessary stuff for karma */ task('compile.karma', (done: Function) => { - buildIonicAngularUmd(false, false, done); + buildIonicAngularUmdTsc(false, false, done); }); /* this task builds out the ionic-angular (commonjs and esm) directories for release */ diff --git a/scripts/gulp/tasks/demos.dev.ts b/scripts/gulp/tasks/demos.dev.ts index b4008112fe..f43f787223 100644 --- a/scripts/gulp/tasks/demos.dev.ts +++ b/scripts/gulp/tasks/demos.dev.ts @@ -1,183 +1,44 @@ -import { readFileSync } from 'fs'; -import { dirname, join } from 'path'; +import { join } from 'path'; -import { dest, src, start, task } from 'gulp'; -import * as babel from 'gulp-babel'; -import * as cache from 'gulp-cached'; -import * as concat from 'gulp-concat'; -import * as connect from 'gulp-connect'; -import * as gulpif from 'gulp-if'; -import * as remember from 'gulp-remember'; -import * as tsc from 'gulp-typescript'; -import * as watch from 'gulp-watch'; -import { template } from 'lodash'; -import * as merge from 'merge2'; -import * as runSequence from 'run-sequence'; -import { obj } from 'through2'; -import * as VinylFile from 'vinyl'; +import { task } from 'gulp'; -import { DEMOS_NAME, DIST_DEMOS_ROOT, DIST_NAME, ES5, ES_2015, SCRIPTS_ROOT } from '../constants'; +import { DEMOS_ROOT, DIST_DEMOS_ROOT, ES_2015, PROJECT_ROOT } from '../constants'; +import { createTempTsConfig, getFolderInfo, runAppScriptsServe } from '../util'; -const buildConfig = require('../../build/config'); - -/** - * Builds Ionic demos tests to dist/demos and creates the necessary files for tests - * to run. - */ -task('demos', demosBuild); - -function demosBuild(done: (err: any) => void) { - runSequence( - 'demos.clean', - 'demos.build', - 'demos.polyfill', - 'demos.copyExternalDependencies', - 'demos.sass', - 'demos.fonts', - 'demos.bundle', - done); -} - -/** - * Builds Ionic demos tests to dist/demos. - */ -task('demos.build', function () { - var indexTemplate = template( - readFileSync(`${SCRIPTS_ROOT}/${DEMOS_NAME}/demos.template.dev.html`).toString() - )({ - buildConfig: buildConfig - }); - - // Get each test folder with src - var tsResult = src([ - 'demos/src/*/**/*.ts' - ]) - .pipe(cache('demos.ts')) - .pipe(tsc(getTscOptions(), undefined, tscReporter)) - .on('error', function (error) { - console.log(error.message); - }) - .pipe(gulpif(/app.module.js$/, createIndexHTML())); - - var testFiles = src([ - 'demos/src/*/**/*', - '!demos/src/*/**/*.ts' - ]) - .pipe(cache('demos.files')); - - return merge([ - tsResult, - testFiles - ]) - .pipe(dest(DIST_DEMOS_ROOT)) - .pipe(connect.reload()); - - function createIndexHTML() { - return obj(function (file, enc, next) { - this.push(new VinylFile({ - base: file.base, - contents: new Buffer(indexTemplate), - path: join(dirname(file.path), 'index.html'), - })); - next(null, file); - }); +task('demos.watch', ['demos.prepare'], (done: Function) => { + const folderInfo = getFolderInfo(); + if (!folderInfo || !folderInfo.componentName ) { + done(new Error(`Usage: gulp e2e.watch --folder modal`)); } -}); -/** - * Creates SystemJS bundle from Ionic source files. - */ -task('demos.bundle', function () { - return tsCompile(getTscOptions('es6'), 'system') - .pipe(babel(babelOptions)) - .pipe(remember('system')) - .pipe(concat('ionic.system.js')) - .pipe(dest(`${DIST_NAME}/bundles`)) - .pipe(connect.reload()); -}); - -function tsCompile(options, cacheName) { - return src([ - 'typings/main.d.ts', - 'src/**/*.ts', - '!src/**/*.d.ts', - '!src/components/*/test/**/*', - '!src/util/test/*', - '!src/config/test/*', - '!src/platform/test/*', - '!src/**/*.spec.ts' - ]) - .pipe(cache(cacheName, { optimizeMemory: true })) - .pipe(tsc(options, undefined, tscReporter)); -} - -function getTscOptions(name?: string) { - var opts = { - emitDecoratorMetadata: true, - experimentalDecorators: true, - target: ES5, - module: 'commonjs', - isolatedModules: true, - typescript: require('typescript'), - declaration: false - }; - - if (name === 'typecheck') { - opts.declaration = true; - delete opts.isolatedModules; - } else if (name === 'es6') { - opts.target = 'es6'; - delete opts.module; - } - return opts; -} - -var tscReporter = { - error: function (error) { - console.error(error.message); - } -}; - -// We use Babel to easily create named System.register modules -// See: https://github.com/Microsoft/TypeScript/issues/4801 -// and https://github.com/ivogabe/gulp-typescript/issues/211 -const babelOptions = { - moduleIds: true, - getModuleId: function (name) { - return 'ionic-angular/' + name; - }, - plugins: ['transform-es2015-modules-systemjs'], - presets: [ES_2015] -}; - -/** - * Builds demos tests to dist/demos and watches for changes. Runs 'demos.bundle' or - * 'sass' on Ionic source changes and 'demos.build' for demos test changes. - */ -task('demos.watch', ['demos'], function () { - watchTask('demos.bundle'); - - watch('demos/src/**/*', function (file) { - start('demos.build'); + serveDemo(folderInfo.componentName).then(() => { + done(); + }).catch((err: Error) => { + done(err); }); }); -function watchTask(task) { - watch([ - 'src/**/*.ts', - '!src/components/*/test/**/*', - '!src/util/test/*' - ], - function (file) { - if (file.event !== 'unlink') { - start(task); - } - } - ); +function serveDemo(folderName: any) { - watch('src/**/*.scss', function () { - start('demos.sass'); - }); + const ionicAngularDir = join(PROJECT_ROOT, 'src'); + const srcTestRoot = join(DEMOS_ROOT, 'src', folderName); + const distDemoRoot = join(DIST_DEMOS_ROOT, folderName); + const includeGlob = [ join(ionicAngularDir, '**', '*.ts'), + join(srcTestRoot, '**', '*.ts')]; - start('demos.serve'); + + const pathToWriteFile = join(distDemoRoot, 'tsconfig.json'); + const pathToReadFile = join(PROJECT_ROOT, 'tsconfig.json'); + + createTempTsConfig(includeGlob, ES_2015, ES_2015, pathToReadFile, pathToWriteFile, { removeComments: true}); + + const sassConfigPath = join('scripts', 'demos', 'sass.config.js'); + const copyConfigPath = join('scripts', 'demos', 'copy.config.js'); + const watchConfigPath = join('scripts', 'demos', 'watch.config.js'); + + const appEntryPoint = join(srcTestRoot, 'app', 'main.ts'); + const appNgModulePath = join(srcTestRoot, 'app', 'app.module.ts'); + const distDir = join(distDemoRoot, 'www'); + + return runAppScriptsServe(folderName, appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath, watchConfigPath); } diff --git a/scripts/gulp/tasks/demos.prod.ts b/scripts/gulp/tasks/demos.prod.ts index 7f90b10731..fc3a473e26 100644 --- a/scripts/gulp/tasks/demos.prod.ts +++ b/scripts/gulp/tasks/demos.prod.ts @@ -1,193 +1,193 @@ -import { accessSync, F_OK, readFileSync, stat } from 'fs'; -import { dirname, join } from 'path'; +import { dirname, join, relative } from 'path'; +import { readFileSync } from 'fs'; -import { dest, src, start, task } from 'gulp'; -import * as gulpif from 'gulp-if'; -import * as watch from 'gulp-watch'; +import * as glob from 'glob'; +import { task } from 'gulp'; +import * as del from 'del'; +import { template } from 'lodash'; import * as runSequence from 'run-sequence'; -import { obj } from 'through2'; -import * as VinylFile from 'vinyl'; +import { argv } from 'yargs'; -import { DEMOS_SRC_ROOT, DIST_DEMOS_ROOT, DIST_NAME, DEMOS_NAME, ES5, ES_2015, LOCAL_SERVER_PORT, SCRIPTS_ROOT } from '../constants'; -import { createTempTsConfig, getFolderInfo, getFolders, runAppScripts } from '../util'; -task('demos.prod', demosBuild); +import { DEMOS_SRC_ROOT, ES_2015, PROJECT_ROOT, SRC_ROOT, SRC_COMPONENTS_ROOT, SCRIPTS_ROOT } from '../constants'; +import { createTempTsConfig, getFolderInfo, readFileAsync, runAppScriptsBuild, writeFileAsync, writePolyfills } from '../util'; -function demosBuild(done: (err: any) => void) { - runSequence( - 'demos.copyIonic', - 'demos.clean', - 'demos.polyfill', - 'demos.copySource', - 'demos.copyExternalDependencies', - 'demos.sass', - 'demos.fonts', - 'demos.compileTests', - done); +import * as pAll from 'p-all'; + +task('demos.prepare', (done: Function) => { + runSequence('demos.clean', 'demos.polyfill', (err: any) => done(err)); +}); + +task('demos.prod', ['demos.prepare'], (done: Function) => { + + // okay, first find out all of the demos tests to run by finding all of the 'main.ts' files + filterDemosEntryPoints().then((filePaths: string[]) => { + console.log(`Compiling ${filePaths.length} Demos ...`); + console.log('filePaths: ', filePaths); + return buildDemos(filePaths); + }).then(() => { + done(); + }).catch((err: Error) => { + done(err); + }); +}); + +function filterDemosEntryPoints() { + return getDemosEntryPoints().then((entryPoints: string[]) => { + const folderInfo = getFolderInfo(); + if (folderInfo && folderInfo.componentName) { + const filtered = entryPoints.filter(entryPoint => { + return entryPoint.indexOf(folderInfo.componentName) >= 0; + }); + return filtered; + } + return entryPoints; + }); } -task('demos.copyIonic', (done: (err: any) => void) => { - runSequence( - 'compile.release', - 'release.compileSass', - 'release.fonts', - 'release.sass', - 'release.createUmdBundle', - done); -}); - -task('demos.copySource', (done: Function) => { - const stream = src([`${DEMOS_SRC_ROOT}/**/*`]) - .pipe(gulpif(/app.module.ts$/, createIndexHTML())) - .pipe(dest(DIST_DEMOS_ROOT)); - - stream.on('end', done); - - function createIndexHTML() { - const indexTemplate = readFileSync(`${SCRIPTS_ROOT}/${DEMOS_NAME}/demos.template.prod.html`); - const indexTs = readFileSync(`${SCRIPTS_ROOT}/${DEMOS_NAME}/main.ts`); - - return obj(function (file, enc, next) { - this.push(new VinylFile({ - base: file.base, - contents: new Buffer(indexTemplate), - path: join(dirname(file.path), 'index.html'), - })); - this.push(new VinylFile({ - base: file.base, - contents: new Buffer(indexTs), - path: join(dirname(file.path), 'main.ts'), - })); - next(null, file); - }); - } -}); - -task('demos.compileTests', (done: Function) => { - let folderInfo = getFolderInfo(); - - if (folderInfo.componentName && folderInfo.componentTest) { - buildTest(folderInfo); - } else { - buildAllTests(done); - } -}); - -function buildTest(folderInfo: any) { - let includeGlob = [`./dist/demos/${folderInfo.componentName}/*.ts`]; - let pathToWriteFile = `${DIST_DEMOS_ROOT}/${folderInfo.componentName}/tsconfig.json`; - - createTempTsConfig(includeGlob, ES5, ES_2015, `${DEMOS_SRC_ROOT}/tsconfig.json`, pathToWriteFile); - - let sassConfigPath = 'scripts/demos/sass.config.js'; - - let appEntryPoint = `dist/demos/${folderInfo.componentName}/main.ts`; - let appNgModule = `dist/demos/${folderInfo.componentName}/app.module.ts`; - let distDir = `dist/demos/${folderInfo.componentName}/`; - - return runAppScripts(folderInfo, sassConfigPath, appEntryPoint, appNgModule, distDir); -} - -function buildAllTests(done: Function) { - let folders = getFolders('./dist/demos/'); - let promises: Promise[] = []; - - folders.forEach(folder => { - stat(`./dist/demos/${folder}/app.module.ts`, function(err, stat) { - if (err == null) { - let folderInfo = { - componentName: folder, - componentTest: 'basic' - }; - const promise = buildTest(folderInfo); - promises.push(promise); +function getDemosEntryPoints() { + return new Promise((resolve, reject) => { + const mainGlob = join(DEMOS_SRC_ROOT, '**', 'main.ts'); + glob(mainGlob, (err: Error, matches: string[]) => { + if (err) { + return reject(err); } + resolve(matches); }); }); +} - Promise.all(promises).then(() => { + +function buildDemos(filePaths: string[]) { + const functions = filePaths.map(filePath => () => { + return buildDemo(filePath); + }); + return pAll(functions, {concurrency: 8}); +} + +function buildDemo(filePath: string) { + const start = Date.now(); + const ionicAngularDir = join(process.cwd(), 'src'); + + const componentDir = dirname(dirname(filePath)); + const relativePathFromComponents = relative(dirname(DEMOS_SRC_ROOT), componentDir); + console.log('relativePathFromComponents: ', relativePathFromComponents); + + const distTestRoot = join(process.cwd(), 'dist', 'demos', relativePathFromComponents); + + const includeGlob = [ join(ionicAngularDir, '**', '*.ts'), join(componentDir, '**', '*.ts')]; + const pathToWriteFile = join(distTestRoot, 'tsconfig.json'); + const pathToReadFile = join(PROJECT_ROOT, 'tsconfig.json'); + + createTempTsConfig(includeGlob, ES_2015, ES_2015, pathToReadFile, pathToWriteFile, { removeComments: true}); + + const sassConfigPath = join('scripts', 'demos', 'sass.config.js'); + const copyConfigPath = join('scripts', 'demos', 'copy.config.js'); + + const appEntryPoint = filePath; + const appNgModulePath = join(dirname(filePath), 'app.module.ts'); + const distDir = join(distTestRoot, 'www'); + + return runAppScriptsBuild(appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath).then(() => { + const end = Date.now(); + console.log(`${filePath} took a total of ${(end - start) / 1000} seconds to build`); + }); +} + +function copyProtractorTestContent(filePaths: string[]): Promise { + return readDemosTestFiles(filePaths) + .then((map: Map) => { + return applyTemplate(map); + }).then((map: Map) => { + writeDemosJsFiles(map); + }); +} + +function applyTemplate(filePathContent: Map) { + const buildConfig = require('../../build/config'); + const templateFileContent = readFileSync(join(SCRIPTS_ROOT, 'demos', 'demos.template.js')); + const templater = template(templateFileContent.toString()); + const modifiedMap = new Map(); + const platforms = ['android', 'ios', 'windows']; + filePathContent.forEach((fileContent: string, filePath: string) => { + const srcRelativePath = relative(SRC_ROOT, dirname(filePath)); + const wwwRelativePath = join(srcRelativePath, 'www'); + platforms.forEach(platform => { + const platformContents = templater({ + contents: fileContent, + buildConfig: buildConfig, + relativePath: wwwRelativePath, + platform: platform, + relativePathBackwardsCompatibility: dirname(wwwRelativePath) + }); + const newFilePath = join(wwwRelativePath, `${platform}.demos.js`); + modifiedMap.set(newFilePath, platformContents); + }); + }); + return modifiedMap; +} + +function writeDemosJsFiles(map: Map) { + const promises: Promise[] = []; + map.forEach((fileContent: string, filePath: string) => { + const destination = join(process.cwd(), 'dist', 'demos', filePath); + promises.push(writeFileAsync(destination, fileContent)); + }); + return Promise.all(promises); +} + + +function readDemosTestFiles(mainFilePaths: string[]): Promise> { + const demosFiles = mainFilePaths.map(mainFilePath => { + return join(dirname(mainFilePath), 'demos.ts'); + }); + + const promises: Promise[] = []; + const map = new Map(); + for (const demosFile of demosFiles) { + const promise = readDemosFile(demosFile); + promises.push(promise); + promise.then((content: string) => { + map.set(demosFile, content); + }); + } + + return Promise.all(promises).then(() => { + return map; + }); +} + +function readDemosFile(filePath: string) { + return readFileAsync(filePath).then((content: string) => { + // purge the import statement at the top + const purgeImportRegex = /.*?import.*?'protractor';/g; + return content.replace(purgeImportRegex, ''); + }); +} + + + +task('demos.clean', (done: Function) => { + // this is a super hack, but it works for now + if (argv.skipClean) { + return done(); + } + + del(['dist/demos/**']).then(() => { done(); }).catch(err => { done(err); }); -} - -task('demos.watchProd', (done: Function) => { - const folderInfo = getFolderInfo(); - let demoTestPath = DEMOS_SRC_ROOT; - - if (folderInfo.componentName && folderInfo.componentTest) { - demoTestPath = join(DEMOS_SRC_ROOT, folderInfo.componentName, 'app.module.ts'); - } - - try { - accessSync(demoTestPath, F_OK); - } catch (e) { - done(new Error(`Could not find demos test: ${demoTestPath}`)); - return; - } - - if (demosComponentsExists(folderInfo)) { - // already generated the demos directory - demosWatch(folderInfo.componentName, folderInfo.componentTest); - - } else { - // generate the demos directory - console.log('Generate demo builds first...'); - demosBuild(() => { - demosWatch(folderInfo.componentName, folderInfo.componentTest); - }); - } }); -function demosWatch(componentName: string, componentTest: string) { - // If any tests change within components then run demos.resources. - watch([ - 'demos/src/**/*' - ], - function (file) { - console.log('start demos.resources - ' + JSON.stringify(file.history, null, 2)); - start('demos.copyAndCompile'); - }); +task('demos.polyfill', (done: Function) => { + if (argv.skipPolyfill) { + return done(); + } - // If any src files change except for tests then transpile only the source ionic files - watch([ - 'src/**/*.ts', - '!src/components/*/test/**/*', - '!src/util/test/*' - ], - function (file) { - console.log('start demos.ngcSource - ' + JSON.stringify(file.history, null, 2)); - start('demos.copyAndCompile'); - }); - - // If any scss files change then recompile all sass - watch(['src/**/*.scss'], (file) => { - console.log('start sass - ' + JSON.stringify(file.history, null, 2)); - start('demos.sass'); + writePolyfills('dist/demos/polyfills').then(() => { + done(); + }).catch(err => { + done(err); }); - - let serverUrl = `http://localhost:${LOCAL_SERVER_PORT}/${DIST_NAME}/${DEMOS_NAME}`; - if (componentName) { - serverUrl += `/${componentName}`; - } - - console.log(serverUrl); - - start('demos.serve'); -} - -function demosComponentsExists(folderInfo: any): boolean { - let componentPath = DIST_DEMOS_ROOT; - - if (folderInfo.componentName && folderInfo.componentTest) { - componentPath += `/${folderInfo.componentName}/build`; - } - - try { - accessSync(componentPath, F_OK); - } catch (e) { - return false; - } - return true; -} +}); diff --git a/scripts/gulp/tasks/e2e.dev.ts b/scripts/gulp/tasks/e2e.dev.ts index 78cf3164c7..9baa4db825 100644 --- a/scripts/gulp/tasks/e2e.dev.ts +++ b/scripts/gulp/tasks/e2e.dev.ts @@ -1,222 +1,50 @@ +import { dirname, join } from 'path'; import { readFileSync } from 'fs'; -import { dirname, join, sep } from 'path'; -import { dest, src, start, task } from 'gulp'; -import * as babel from 'gulp-babel'; -import * as cache from 'gulp-cached'; -import * as concat from 'gulp-concat'; -import * as connect from 'gulp-connect'; -import * as gulpif from 'gulp-if'; -import * as remember from 'gulp-remember'; -import * as rename from 'gulp-rename'; -import * as tsc from 'gulp-typescript'; -import * as watch from 'gulp-watch'; -import { template } from 'lodash'; -import * as merge from 'merge2'; -import * as runSequence from 'run-sequence'; -import { obj } from 'through2'; -import * as VinylFile from 'vinyl'; +import { task } from 'gulp'; -import { DIST_E2E_ROOT, DIST_NAME, E2E_NAME, ES5, ES_2015, SCRIPTS_ROOT } from '../constants'; +import { ES_2015, PROJECT_ROOT } from '../constants'; +import { createTempTsConfig, getFolderInfo, runAppScriptsServe } from '../util'; -const buildConfig = require('../../build/config'); - -/** - * Builds Ionic e2e tests to dist/e2e and creates the necessary files for tests - * to run. - */ -task('e2e', e2eBuild); - -function e2eBuild(done: (err: any) => void) { - runSequence( - 'e2e.clean', - 'e2e.build', - 'e2e.polyfill', - 'e2e.copyExternalDependencies', - 'e2e.sass', - 'e2e.fonts', - 'e2e.bundle', - done); -} - -/** - * Builds Ionic e2e tests to dist/e2e. - */ -task('e2e.build', function () { - var indexTemplate = template( - readFileSync(`${SCRIPTS_ROOT}/${E2E_NAME}/e2e.template.dev.html`).toString() - )({ - buildConfig: buildConfig - }); - - // Get each test folder with src - var tsResult = src([ - 'src/components/*/test/*/**/*.ts', - '!src/components/*/test/*/**/*.spec.ts' - ]) - .pipe(cache('e2e.ts')) - .pipe(tsc(getTscOptions(), undefined, tscReporter)) - .on('error', function (error) { - console.log(error.message); - }) - .pipe(gulpif(/app.module.js$/, createIndexHTML())) - .pipe(gulpif(/e2e.js$/, createPlatformTests())); - - var testFiles = src([ - 'src/components/*/test/*/**/*', - '!src/components/*/test/*/**/*.ts' - ]) - .pipe(cache('e2e.files')); - - return merge([ - tsResult, - testFiles - ]) - .pipe(rename(function (file) { - file.dirname = file.dirname.replace(sep + 'test' + sep, sep); - })) - .pipe(dest(DIST_E2E_ROOT)) - .pipe(connect.reload()); - - function createIndexHTML() { - return obj(function (file, enc, next) { - this.push(new VinylFile({ - base: file.base, - contents: new Buffer(indexTemplate), - path: join(dirname(file.path), 'index.html'), - })); - next(null, file); - }); +task('e2e.watch', ['e2e.prepare'], (done: Function) => { + const folderInfo = getFolderInfo(); + if (!folderInfo || !folderInfo.componentName || !folderInfo.componentTest) { + done(new Error(`Usage: gulp e2e.watch --folder nav/basic`)); } - function createPlatformTests() { - let platforms = [ - 'android', - 'ios', - 'windows' - ]; - - let testTemplate = template(readFileSync(`${SCRIPTS_ROOT}/${E2E_NAME}/e2e.template.js`).toString()); - - return obj(function (file, enc, next) { - let self = this; - - let relativePath = dirname(file.path.replace(/^.*?src(\/|\\)components(\/|\\)/, '')); - relativePath = relativePath.replace('/test/', '/'); - - let contents = file.contents.toString(); - platforms.forEach(function (platform) { - let platformContents = testTemplate({ - contents: contents, - buildConfig: buildConfig, - relativePath: relativePath, - platform: platform - }); - self.push(new VinylFile({ - base: file.base, - contents: new Buffer(platformContents), - path: file.path.replace(/e2e.js$/, platform + '.e2e.js') - })); - }); - next(); - }); - } -}); - -/** - * Creates SystemJS bundle from Ionic source files. - */ -task('e2e.bundle', function () { - return tsCompile(getTscOptions('es6'), 'system') - .pipe(babel(babelOptions)) - .pipe(remember('system')) - .pipe(concat('ionic.system.js')) - .pipe(dest(`${DIST_NAME}/bundles`)) - .pipe(connect.reload()); -}); - -function tsCompile(options, cacheName) { - return src([ - 'typings/main.d.ts', - 'src/**/*.ts', - '!src/**/*.d.ts', - '!src/components/*/test/**/*', - '!src/util/test/*', - '!src/config/test/*', - '!src/platform/test/*', - '!src/**/*.spec.ts' - ]) - .pipe(cache(cacheName, { optimizeMemory: true })) - .pipe(tsc(options, undefined, tscReporter)); -} - -function getTscOptions(name?: string) { - var opts = { - emitDecoratorMetadata: true, - experimentalDecorators: true, - target: ES5, - module: 'commonjs', - isolatedModules: true, - typescript: require('typescript'), - declaration: false - }; - - if (name === 'typecheck') { - opts.declaration = true; - delete opts.isolatedModules; - } else if (name === 'es6') { - opts.target = 'es6'; - delete opts.module; - } - return opts; -} - -var tscReporter = { - error: function (error) { - console.error(error.message); - } -}; - -// We use Babel to easily create named System.register modules -// See: https://github.com/Microsoft/TypeScript/issues/4801 -// and https://github.com/ivogabe/gulp-typescript/issues/211 -const babelOptions = { - moduleIds: true, - getModuleId: function (name) { - return 'ionic-angular/' + name; - }, - plugins: ['transform-es2015-modules-systemjs'], - presets: [ES_2015] -}; - -/** - * Builds e2e tests to dist/e2e and watches for changes. Runs 'e2e.bundle' or - * 'sass' on Ionic source changes and 'e2e.build' for e2e test changes. - */ -task('e2e.watch', ['e2e'], function () { - watchTask('e2e.bundle'); - - watch('src/components/*/test/**/*', function (file) { - start('e2e.build'); + serveTest(folderInfo).then(() => { + done(); + }).catch((err: Error) => { + done(err); }); }); -function watchTask(task) { - watch([ - 'src/**/*.ts', - '!src/components/*/test/**/*', - '!src/util/test/*' - ], - function (file) { - if (file.event !== 'unlink') { - start(task); - } - } - ); +function serveTest(folderInfo: any) { - watch('src/**/*.scss', function () { - start('e2e.sass'); - }); + const ionicAngularDir = join(PROJECT_ROOT, 'src'); + const srcTestRoot = join(PROJECT_ROOT, 'src', 'components', folderInfo.componentName, 'test', folderInfo.componentTest); + const distTestRoot = join(PROJECT_ROOT, 'dist', 'e2e', 'components', folderInfo.componentName, 'test', folderInfo.componentTest); + const includeGlob = [ join(ionicAngularDir, '**', '*.ts')]; + const pathToWriteFile = join(distTestRoot, 'tsconfig.json'); + const pathToReadFile = join(PROJECT_ROOT, 'tsconfig.json'); - start('e2e.serve'); + createTempTsConfig(includeGlob, ES_2015, ES_2015, pathToReadFile, pathToWriteFile, { removeComments: true}); + + const sassConfigPath = join('scripts', 'e2e', 'sass.config.js'); + const copyConfigPath = join('scripts', 'e2e', 'copy.config.js'); + + let appEntryPoint = join(srcTestRoot, 'app', 'main.ts'); + try { + // check if the entry point exists, otherwise fall back to the legacy entry point without 'app' folder + readFileSync(appEntryPoint); + } catch (ex) { + // the file doesn't exist, so use the legacy entry point + appEntryPoint = join(srcTestRoot, 'main.ts'); + } + + // this assume that app.module.ts and main.ts are peers, which they should be no matter what + const appNgModulePath = join(dirname(appEntryPoint), 'app.module.ts'); + const distDir = join(distTestRoot, 'www'); + + return runAppScriptsServe(folderInfo.componentName + '/' + folderInfo.componentTest, appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath, null); } diff --git a/scripts/gulp/tasks/e2e.prod.ts b/scripts/gulp/tasks/e2e.prod.ts index 0d2da8b53e..9838205326 100644 --- a/scripts/gulp/tasks/e2e.prod.ts +++ b/scripts/gulp/tasks/e2e.prod.ts @@ -1,237 +1,220 @@ -import { accessSync, F_OK, readFileSync, stat } from 'fs'; -import { dirname, join } from 'path'; +import { dirname, join, relative } from 'path'; +import { readFileSync, writeFileSync } from 'fs'; -import { dest, src, start, task } from 'gulp'; -import * as gulpif from 'gulp-if'; -import * as watch from 'gulp-watch'; +import * as glob from 'glob'; +import { task } from 'gulp'; +import * as del from 'del'; import { template } from 'lodash'; import * as runSequence from 'run-sequence'; -import { obj } from 'through2'; -import * as VinylFile from 'vinyl'; +import { argv } from 'yargs'; -import { DIST_E2E_ROOT, DIST_NAME, E2E_NAME, ES5, ES_2015, LOCAL_SERVER_PORT, DEMOS_SRC_ROOT, SCRIPTS_ROOT, SRC_ROOT } from '../constants'; -import { createTempTsConfig, getFolderInfo, getFolders, runAppScripts} from '../util'; -task('e2e.prod', e2eBuild); +import { ES_2015, PROJECT_ROOT, SRC_ROOT, SRC_COMPONENTS_ROOT, SCRIPTS_ROOT } from '../constants'; +import { createTempTsConfig, createTimestamp, getFolderInfo, readFileAsync, runAppScriptsBuild, writeFileAsync, writePolyfills } from '../util'; -function e2eBuild(done: (err: any) => void) { - runSequence( - 'e2e.copyIonic', - 'e2e.clean', - 'e2e.polyfill', - 'e2e.copySource', - 'e2e.copyExternalDependencies', - 'e2e.sass', - 'e2e.fonts', - 'e2e.compileTests', - done); -} +import * as pAll from 'p-all'; -task('e2e.copyIonic', (done: (err: any) => void) => { - runSequence( - 'compile.release', - 'release.compileSass', - 'release.fonts', - 'release.sass', - 'release.createUmdBundle', - done); +task('e2e.prepare', (done: Function) => { + runSequence('e2e.clean', 'e2e.polyfill', 'e2e.prepareSass', (err: any) => done(err)); }); -task('e2e.copySource', (done: Function) => { +task('e2e.prepareSass', (done: Function) => { + const version = `E2E-${createTimestamp()}`; + writeFileSync(join(SRC_ROOT, 'themes/version.scss'), `$ionic-version: "${version}";`); + done(); +}); - const buildConfig = require('../../build/config'); +task('e2e.prod', ['e2e.prepare'], (done: Function) => { - const stream = src([`${SRC_ROOT}/**/*`, `!${SRC_ROOT}/**/*.spec.ts`]) - .pipe(gulpif(/app.module.ts$/, createIndexHTML())) - .pipe(gulpif(/e2e.ts$/, createPlatformTests())) - .pipe(dest(DIST_E2E_ROOT)); + // okay, first find out all of the e2e tests to run by finding all of the 'main.ts' files + filterE2eTestfiles().then((filePaths: string[]) => { + console.log(`Compiling ${filePaths.length} E2E tests ...`); + return buildTests(filePaths); + }).then(() => { + done(); + }).catch((err: Error) => { + done(err); + }); +}); - stream.on('end', done); - - function createIndexHTML() { - const indexTemplate = readFileSync(`${SCRIPTS_ROOT}/${E2E_NAME}/e2e.template.prod.html`); - const indexTs = readFileSync(`${SCRIPTS_ROOT}/${E2E_NAME}/main.ts`); - - return obj(function (file, enc, next) { - this.push(new VinylFile({ - base: file.base, - contents: new Buffer(indexTemplate), - path: join(dirname(file.path), 'index.html'), - })); - this.push(new VinylFile({ - base: file.base, - contents: new Buffer(indexTs), - path: join(dirname(file.path), 'main.ts'), - })); - next(null, file); +function filterE2eTestfiles() { + return getE2eTestFiles().then((filePaths: string[]) => { + const entryPoints = filePaths.map(filePath => { + const directoryName = dirname(filePath); + return join(directoryName, 'app', 'main.ts'); }); - } - - // TODO this is almost the same as dev, diff and combine - function createPlatformTests() { - let platforms = [ - 'android', - 'ios', - 'windows' - ]; - - let testTemplate = template(readFileSync(`${SCRIPTS_ROOT}/${E2E_NAME}/e2e.template.js`).toString()); - - return obj(function (file, enc, next) { - let self = this; - - let relativePath = dirname(file.path.replace(/^.*?src(\/|\\)/, '')); - - let contents = file.contents.toString(); - platforms.forEach(function (platform) { - let platformContents = testTemplate({ - contents: contents, - buildConfig: buildConfig, - relativePath: relativePath, - platform: platform - }); - self.push(new VinylFile({ - base: file.base, - contents: new Buffer(platformContents), - path: file.path.replace(/e2e.ts$/, platform + '.e2e.js') - })); + return entryPoints; + }).then((entryPoints: string[]) => { + const folderInfo = getFolderInfo(); + if (folderInfo && folderInfo.componentName && folderInfo.componentTest) { + const filtered = entryPoints.filter(entryPoint => { + return entryPoint.indexOf(folderInfo.componentName) >= 0 && entryPoint.indexOf(folderInfo.componentTest) >= 0; }); - next(); - }); - } -}); - -task('e2e.compileTests', (done: Function) => { - let folderInfo = getFolderInfo(); - - if (folderInfo.componentName && folderInfo.componentTest) { - buildTest(folderInfo); - } else { - buildAllTests(done); - } -}); - -function buildTest(folderInfo: any) { - let includeGlob = [`./dist/e2e/components/${folderInfo.componentName}/test/${folderInfo.componentTest}/*.ts`]; - let pathToWriteFile = `${DIST_E2E_ROOT}/components/${folderInfo.componentName}/test/${folderInfo.componentTest}/tsconfig.json`; - - createTempTsConfig(includeGlob, ES5, ES_2015, `${DEMOS_SRC_ROOT}/tsconfig.json`, pathToWriteFile); - - let sassConfigPath = 'scripts/e2e/sass.config.js'; - - let appEntryPoint = `dist/e2e/components/${folderInfo.componentName}/test/${folderInfo.componentTest}/main.ts`; - let appNgModule = `dist/e2e/components/${folderInfo.componentName}/test/${folderInfo.componentTest}/app.module.ts`; - let distDir = `dist/e2e/components/${folderInfo.componentName}/test/${folderInfo.componentTest}/`; - - return runAppScripts(folderInfo, sassConfigPath, appEntryPoint, appNgModule, distDir); + return filtered; + } + return entryPoints; + }); } -function buildAllTests(done: Function) { - let folders = getFolders('./dist/e2e/components'); - let promises: Promise[] = []; - - folders.forEach(folder => { - console.log(folder); - stat(`./dist/e2e/components/${folder}/test`, function(err, stat) { - if (err == null) { - let testFolders = getFolders(`./dist/e2e/components/${folder}/test`); - - testFolders.forEach(test => { - console.log('build test for ', folder, test); - let folderInfo = { - componentName: folder, - componentTest: test - }; - const promise = buildTest(folderInfo); - promises.push(promise); - }); +function getE2eTestFiles() { + return new Promise((resolve, reject) => { + const mainGlob = join(SRC_COMPONENTS_ROOT, '*', 'test', '*', 'e2e.ts'); + glob(mainGlob, (err: Error, matches: string[]) => { + if (err) { + return reject(err); } + resolve(matches); }); }); +} - Promise.all(promises).then(() => { + +function buildTests(filePaths: string[]) { + const functions = filePaths.map(filePath => () => { + return buildTest(filePath); + }); + return pAll(functions, {concurrency: 8}).then(() => { + // copy over all of the protractor tests to the correct location now + return copyProtractorTestContent(filePaths); + }); +} + +function buildTest(filePath: string) { + const start = Date.now(); + const ionicAngularDir = join(process.cwd(), 'src'); + + let appEntryPoint = filePath; + let srcTestRoot = dirname(dirname(appEntryPoint)); + try { + // check if the entry point exists, otherwise fall back to the legacy entry point without 'app' folder + readFileSync(appEntryPoint); + } catch (ex) { + // the file doesn't exist, so use the legacy entry point + appEntryPoint = join(dirname(dirname(appEntryPoint)), 'main.ts'); + srcTestRoot = dirname(appEntryPoint); + } + + const relativePathFromComponents = relative(dirname(SRC_COMPONENTS_ROOT), srcTestRoot); + const distTestRoot = join(process.cwd(), 'dist', 'e2e', relativePathFromComponents); + + const includeGlob = [ join(ionicAngularDir, '**', '*.ts')]; + const pathToWriteFile = join(distTestRoot, 'tsconfig.json'); + const pathToReadFile = join(PROJECT_ROOT, 'tsconfig.json'); + + createTempTsConfig(includeGlob, ES_2015, ES_2015, pathToReadFile, pathToWriteFile, { removeComments: true}); + + const sassConfigPath = join('scripts', 'e2e', 'sass.config.js'); + const copyConfigPath = join('scripts', 'e2e', 'copy.config.js'); + + + + + const appNgModulePath = join(dirname(appEntryPoint), 'app.module.ts'); + const distDir = join(distTestRoot, 'www'); + + return runAppScriptsBuild(appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath).then(() => { + const end = Date.now(); + console.log(`${filePath} took a total of ${(end - start) / 1000} seconds to build`); + }); +} + +function copyProtractorTestContent(filePaths: string[]): Promise { + const e2eTestPaths = filePaths.map(filePath => { + return join(dirname(dirname(filePath)), 'e2e.ts'); + }); + return readE2ETestFiles(e2eTestPaths) + .then((map: Map) => { + return applyTemplate(map); + }).then((map: Map) => { + writeE2EJsFiles(map); + }); +} + +function applyTemplate(filePathContent: Map) { + const buildConfig = require('../../build/config'); + const templateFileContent = readFileSync(join(SCRIPTS_ROOT, 'e2e', 'e2e.template.js')); + const templater = template(templateFileContent.toString()); + const modifiedMap = new Map(); + const platforms = ['android', 'ios', 'windows']; + filePathContent.forEach((fileContent: string, filePath: string) => { + const srcRelativePath = relative(SRC_ROOT, dirname(filePath)); + const wwwRelativePath = join(srcRelativePath, 'www'); + platforms.forEach(platform => { + const platformContents = templater({ + contents: fileContent, + buildConfig: buildConfig, + relativePath: wwwRelativePath, + platform: platform, + relativePathBackwardsCompatibility: dirname(wwwRelativePath) + }); + const newFilePath = join(wwwRelativePath, `${platform}.e2e.js`); + modifiedMap.set(newFilePath, platformContents); + }); + }); + return modifiedMap; +} + +function writeE2EJsFiles(map: Map) { + const promises: Promise[] = []; + map.forEach((fileContent: string, filePath: string) => { + const destination = join(process.cwd(), 'dist', 'e2e', filePath); + promises.push(writeFileAsync(destination, fileContent)); + }); + return Promise.all(promises); +} + + +function readE2ETestFiles(mainFilePaths: string[]): Promise> { + const e2eFiles = mainFilePaths.map(mainFilePath => { + return join(dirname(mainFilePath), 'e2e.ts'); + }); + + const promises: Promise[] = []; + const map = new Map(); + for (const e2eFile of e2eFiles) { + const promise = readE2EFile(e2eFile); + promises.push(promise); + promise.then((content: string) => { + map.set(e2eFile, content); + }); + } + + return Promise.all(promises).then(() => { + return map; + }); +} + +function readE2EFile(filePath: string) { + return readFileAsync(filePath).then((content: string) => { + // purge the import statement at the top + const purgeImportRegex = /.*?import.*?'protractor';/g; + return content.replace(purgeImportRegex, ''); + }); +} + + + +task('e2e.clean', (done: Function) => { + // this is a super hack, but it works for now + if (argv.skipClean) { + return done(); + } + + del(['dist/e2e/**']).then(() => { done(); }).catch(err => { done(err); }); -} - -task('e2e.watchProd', (done: Function) => { - const folderInfo = getFolderInfo(); - let e2eTestPath = SRC_ROOT; - - if (folderInfo.componentName && folderInfo.componentTest) { - e2eTestPath = join(`${SRC_ROOT}/components/${folderInfo.componentName}/test/${folderInfo.componentTest}/app.module.ts`); - } - - try { - accessSync(e2eTestPath, F_OK); - } catch (e) { - done(new Error(`Could not find e2e test: ${e2eTestPath}`)); - return; - } - - if (e2eComponentsExists(folderInfo)) { - // already generated the e2e directory - e2eWatch(folderInfo.componentName, folderInfo.componentTest); - - } else { - // generate the e2e directory - console.log('Generate e2e builds first...'); - e2eBuild(() => { - e2eWatch(folderInfo.componentName, folderInfo.componentTest); - }); - } }); -function e2eWatch(componentName: string, componentTest: string) { - // If any tests change within components then run e2e.resources. - watch([ - 'e2e/src/**/*' - ], - function (file) { - console.log('start e2e.resources - ' + JSON.stringify(file.history, null, 2)); - start('e2e.copyAndCompile'); - }); +task('e2e.polyfill', (done: Function) => { + if (argv.skipPolyfill) { + return done(); + } - // If any src files change except for tests then transpile only the source ionic files - watch([ - 'src/**/*.ts', - '!src/components/*/test/**/*', - '!src/util/test/*' - ], - function (file) { - console.log('start e2e.ngcSource - ' + JSON.stringify(file.history, null, 2)); - start('e2e.copyAndCompile'); - }); - - // If any scss files change then recompile all sass - watch(['src/**/*.scss'], (file) => { - console.log('start sass - ' + JSON.stringify(file.history, null, 2)); - start('e2e.sass'); + writePolyfills('dist/e2e/polyfills').then(() => { + done(); + }).catch(err => { + done(err); }); - - let serverUrl = `http://localhost:${LOCAL_SERVER_PORT}/${DIST_NAME}/${E2E_NAME}`; - if (componentName) { - serverUrl += `/${componentName}`; - } - - console.log(serverUrl); - - start('e2e.serve'); -} - -function e2eComponentsExists(folderInfo: any): boolean { - let componentPath = `${DIST_E2E_ROOT}/components`; - - if (folderInfo.componentName && folderInfo.componentTest) { - componentPath += `/${folderInfo.componentName}/test/${folderInfo.componentTest}/build`; - } - - try { - accessSync(componentPath, F_OK); - } catch (e) { - return false; - } - return true; -} +}); diff --git a/scripts/gulp/tasks/e2e.ts b/scripts/gulp/tasks/e2e.ts deleted file mode 100644 index 25644c5b0b..0000000000 --- a/scripts/gulp/tasks/e2e.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { dest, src, task } from 'gulp'; -import * as connect from 'gulp-connect'; -import * as del from 'del'; -import * as runSequence from 'run-sequence'; - -import { DIST_E2E_ROOT, LOCAL_SERVER_PORT, SCRIPTS_ROOT } from '../constants'; -import { compileSass, copyFonts, createTimestamp, setSassIonicVersion, writePolyfills } from '../util'; - -task('e2e.clean', (done: Function) => { - del(['dist/e2e/**']).then(() => { - done(); - }).catch(err => { - done(err); - }); -}); - -task('e2e.polyfill', (done: Function) => { - writePolyfills('dist/e2e/polyfills').then(() => { - done(); - }).catch(err => { - done(err); - }); -}); - -task('e2e.copyAndCompile', (done: (err: any) => void) => { - runSequence( - 'e2e.copySource', - 'e2e.compileTests', - 'e2e.bundle', - done); -}); - -task('e2e.copyExternalDependencies', () => { - src([`${SCRIPTS_ROOT}/e2e/*.css`]).pipe(dest(`${DIST_E2E_ROOT}/css`)); -}); - -task('e2e.sass', () => { - // ensure there is a version.scss file - setSassIonicVersion(`E2E-${createTimestamp()}`); - return compileSass(`${DIST_E2E_ROOT}/css`); -}); - -task('e2e.fonts', () => { - return copyFonts(`${DIST_E2E_ROOT}/fonts`); -}); - -task('e2e.serve', function() { - connect.server({ - root: './', - port: LOCAL_SERVER_PORT, - livereload: { - port: 35700 - } - }); -}); diff --git a/scripts/gulp/tasks/snapshot.ts b/scripts/gulp/tasks/snapshot.ts index 3902048615..437d22dcb3 100644 --- a/scripts/gulp/tasks/snapshot.ts +++ b/scripts/gulp/tasks/snapshot.ts @@ -7,27 +7,19 @@ import { task } from 'gulp'; import * as serveStatic from 'serve-static'; import { argv } from 'yargs'; -import { DIST_E2E_ROOT, DIST_E2E_COMPONENTS_ROOT, PROJECT_ROOT, SCRIPTS_ROOT } from '../constants'; +import { DIST_E2E_COMPONENTS_ROOT, PROJECT_ROOT, SCRIPTS_ROOT } from '../constants'; import { mergeObjects } from '../util'; task('snapshot', ['e2e.prod'], (done: Function) => { - snapshot(false, false, done); + snapshot(false, done); }); -task('snapshot.skipBuild', ['e2e.sass'], (done: Function) => { - snapshot(false, false, done); +task('snapshot.skipBuild', (done: Function) => { + snapshot(false, done); }); -task('snapshot.dev', ['e2e'], (done: Function) => { - snapshot(false, true, done); -}); - -task('snapshot.quick', ['e2e.sass'], (done: Function) => { - snapshot(true, true, done); -}); - -function snapshot(quickMode: boolean, devMode: boolean, callback: Function) { +function snapshot(quickMode: boolean, callback: Function) { const snapshotConfig = require('../../snapshot/snapshot.config').config; const protractorConfigFile = resolve(SCRIPTS_ROOT, 'snapshot/protractor.config.js'); @@ -41,6 +33,7 @@ function snapshot(quickMode: boolean, devMode: boolean, callback: Function) { let component = '*'; let e2eSpecs = '*'; + const folderArg: string = argv.folder || argv.f; if (folderArg && folderArg.length) { const folderArgPaths = folderArg.split('/'); @@ -49,10 +42,10 @@ function snapshot(quickMode: boolean, devMode: boolean, callback: Function) { e2eSpecs = folderArgPaths[1]; } } - var specs = join(DIST_E2E_COMPONENTS_ROOT, component, 'test', e2eSpecs, '*e2e.js'); - if (devMode) specs = join(DIST_E2E_ROOT, component, e2eSpecs, '*e2e.js'); - console.log('[snapshot] Running with', devMode ? 'Development' : 'Production', 'build'); + const specs = join(DIST_E2E_COMPONENTS_ROOT, component, 'test', e2eSpecs, 'www', '*e2e.js'); + + console.log('[snapshot] Running with', 'Production', 'build'); console.log(`[snapshot] Specs: ${specs}`); const testId = generateTestId(); @@ -71,7 +64,6 @@ function snapshot(quickMode: boolean, devMode: boolean, callback: Function) { '--params.height=' + snapshotValues.params.height, '--params.test_id=' + snapshotValues.params.test_id, '--params.upload=' + snapshotValues.params.upload, - '--params.dev=' + devMode, '--specs=' + specs ]; diff --git a/scripts/gulp/tasks/test.ts b/scripts/gulp/tasks/test.ts index d10c1aaf62..bba7e5789a 100644 --- a/scripts/gulp/tasks/test.ts +++ b/scripts/gulp/tasks/test.ts @@ -6,6 +6,10 @@ task('test', ['test.assembleVendorJs', 'compile.karma'], (done: Function) => { karmaTest(false, done); }); +task('test.fast', ['compile.karma'], (done: Function) => { + karmaTest(false, done); +}); + task('test.watch', ['test.assembleVendorJs', 'compile.karma'], (done: Function) => { karmaTest(true, done); }); diff --git a/scripts/gulp/util.ts b/scripts/gulp/util.ts index 644855af1c..651eb26e21 100644 --- a/scripts/gulp/util.ts +++ b/scripts/gulp/util.ts @@ -1,8 +1,8 @@ -import { spawnSync } from 'child_process'; +import { spawn } from 'child_process'; import { NODE_MODULES_ROOT, SRC_ROOT } from './constants'; import { src, dest } from 'gulp'; -import { join } from 'path'; -import { readdirSync, readFileSync, statSync, writeFileSync } from 'fs'; +import { dirname, join } from 'path'; +import { ensureDirSync, readdirSync, readFile, readFileSync, statSync, writeFile, writeFileSync } from 'fs-extra'; import { rollup } from 'rollup'; import { Replacer } from 'strip-function'; import * as commonjs from 'rollup-plugin-commonjs'; @@ -12,6 +12,8 @@ import * as through from 'through2'; import * as uglifyPlugin from 'rollup-plugin-uglify'; import { argv } from 'yargs'; +import { runWorker } from './utils/app-scripts-worker-client'; + // These packages lack of types. const resolveBin = require('resolve-bin'); @@ -39,7 +41,7 @@ function getRootTsConfig(pathToReadFile): any { return tsConfig; } -export function createTempTsConfig(includeGlob: string[], target: string, moduleType: string, pathToReadFile: string, pathToWriteFile: string): any { +export function createTempTsConfig(includeGlob: string[], target: string, moduleType: string, pathToReadFile: string, pathToWriteFile: string, overrideCompileOptions: any = null): any { let config = getRootTsConfig(pathToReadFile); if (!config.compilerOptions) { config.compilerOptions = {}; @@ -53,12 +55,20 @@ export function createTempTsConfig(includeGlob: string[], target: string, module config.compilerOptions.target = target; } config.include = includeGlob; + + if (overrideCompileOptions) { + config.compilerOptions = Object.assign(config.compilerOptions, overrideCompileOptions); + } + let json = JSON.stringify(config, null, 2); + + const dirToCreate = dirname(pathToWriteFile); + ensureDirSync(dirToCreate); writeFileSync(pathToWriteFile, json); } function removeDebugStatements() { - let replacer = new Replacer(['console.debug', 'assert', 'runInDev']); + let replacer = new Replacer(['console.debug', 'console.time', 'console.timeEnd', 'assert', 'runInDev']); return through.obj(function (file, encoding, callback) { const content = file.contents.toString(); const cleanedJs = replacer.replace(content); @@ -75,7 +85,7 @@ export function copySourceToDest(destinationPath: string, excludeSpecs: boolean, glob.push(`${SRC_ROOT}/**/*.spec.ts`); } if (excludeE2e) { - glob.push(`!${SRC_ROOT}/components/*/test/*/*.ts`); + glob.push(`!${SRC_ROOT}/components/*/test/**/*.ts`); } let stream = src(glob); if (stripDebug) { @@ -178,41 +188,58 @@ export function runWebpack(pathToWebpackConfig: string, done: Function) { }); } -export function runAppScripts(folderInfo: any, sassConfigPath: string, appEntryPoint: string, appNgModulePath: string, distDir: string) { - console.log('Running ionic-app-scripts build with', folderInfo.componentName, '/', folderInfo.componentTest); - let tsConfig = distDir + 'tsconfig.json'; +export function runAppScriptsServe(testOrDemoName: string, appEntryPoint: string, appNgModulePath: string, srcDir: string, distDir: string, tsConfig: string, ionicAngularDir: string, sassConfigPath: string, copyConfigPath: string, watchConfigPath: string) { + console.log('Running ionic-app-scripts serve with', testOrDemoName); let scriptArgs = [ - 'build', - '--sass', sassConfigPath, + 'serve', '--appEntryPoint', appEntryPoint, '--appNgModulePath', appNgModulePath, - '--srcDir', distDir, + '--srcDir', srcDir, '--wwwDir', distDir, - '--tsconfig', tsConfig - ]; + '--tsconfig', tsConfig, + '--readConfigJson', 'false', + '--experimentalParseDeepLinks', 'true', + '--ionicAngularDir', ionicAngularDir, + '--sass', sassConfigPath, + '--copy', copyConfigPath + ]; + + if (watchConfigPath) { + scriptArgs.push('--watch'); + scriptArgs.push(watchConfigPath); + } const debug: boolean = argv.debug; if (debug) { scriptArgs.push('--debug'); - scriptArgs.push('--aot'); - } else { - scriptArgs.push('--prod'); } - try { - console.log('$ node ./node_modules/.bin/ionic-app-scripts', scriptArgs.join(' ')); - const scriptsCmd = spawnSync('node', ['./node_modules/.bin/ionic-app-scripts'].concat(scriptArgs)); + return new Promise((resolve, reject) => { + const args = ['./node_modules/.bin/ionic-app-scripts'].concat(scriptArgs); + console.log(`node ${args.join(' ')}`); + const spawnedCommand = spawn('node', args); - if (scriptsCmd.status !== 0) { - console.log(scriptsCmd.stderr.toString()); - return Promise.reject(scriptsCmd.stderr.toString()); - } + spawnedCommand.stdout.on('data', (buffer: Buffer) => { + console.log(buffer.toString()); + }); - console.log(scriptsCmd.output.toString()); - return Promise.resolve(); - } catch (ex) { - return Promise.reject(ex); - } + spawnedCommand.stderr.on('data', (buffer: Buffer) => { + console.error(buffer.toString()); + }); + + spawnedCommand.on('close', (code: number) => { + if (code === 0) { + return resolve(); + } + reject(new Error('App-scripts failed with non-zero status code')); + }); + }); +} + +export function runAppScriptsBuild(appEntryPoint: string, appNgModulePath: string, srcDir: string, distDir: string, tsConfig: string, ionicAngularDir: string, sassConfigPath: string, copyConfigPath: string) { + const pathToAppScripts = join(NODE_MODULES_ROOT, '.bin', 'ionic-app-scripts'); + const debug: boolean = argv.debug; + return runWorker(pathToAppScripts, debug, appEntryPoint, appNgModulePath, srcDir, distDir, tsConfig, ionicAngularDir, sassConfigPath, copyConfigPath); } /** Resolves the path for a node package executable. */ @@ -302,7 +329,10 @@ function bundlePolyfill(pathsToIncludeInPolyfill: string[], outputPath: string) }), commonjs(), uglifyPlugin() - ] + ], + onwarn: () => { + return () => {}; + } }).then((bundle) => { return bundle.write({ format: 'iife', @@ -333,3 +363,25 @@ export function getFolders(dir) { return statSync(join(dir, file)).isDirectory(); }); } + +export function readFileAsync(filePath: string) { + return new Promise((resolve, reject) => { + readFile(filePath, (err: Error, buffer: Buffer) => { + if (err) { + return reject(err); + } + return resolve(buffer.toString()); + }); + }); +} + +export function writeFileAsync(filePath: string, fileContent: string) { + return new Promise((resolve, reject) => { + writeFile(filePath, fileContent, (err: Error) => { + if (err) { + return reject(err); + } + return resolve(); + }); + }); +} diff --git a/scripts/gulp/utils/app-scripts-worker-client.ts b/scripts/gulp/utils/app-scripts-worker-client.ts new file mode 100644 index 0000000000..31f24d1981 --- /dev/null +++ b/scripts/gulp/utils/app-scripts-worker-client.ts @@ -0,0 +1,102 @@ +import { fork, ChildProcess } from 'child_process'; +import { join } from 'path'; + +import { MessageToWorker, WorkerProcess } from './interfaces'; + +export function runWorker(pathToAppScripts: string, debug: boolean, appEntryPoint: string, appNgModulePath: string, srcDir: string, distDir: string, tsConfig: string, ionicAngularDir: string, sassConfigPath: string, copyConfigPath: string) { + return new Promise((resolve, reject) => { + + const msgToWorker: MessageToWorker = { + pathToAppScripts: pathToAppScripts, + appEntryPoint: appEntryPoint, + appNgModulePath: appNgModulePath, + debug: debug, + srcDir: srcDir, + distDir: distDir, + tsConfig: tsConfig, + ionicAngularDir: ionicAngularDir, + sassConfigPath: sassConfigPath, + copyConfigPath: copyConfigPath + }; + + const worker = createWorker(msgToWorker); + console.log(`Starting to build test ${appEntryPoint}`); + + worker.on('error', (err: any) => { + console.error(`worker error, entrypoint: ${appEntryPoint}, pid: ${worker.pid}, error: ${err}`); + reject(err); + }); + + worker.on('exit', (code: number) => { + console.log(`Finished building test ${appEntryPoint}`); + if (code === 0) { + resolve(code); + } else { + reject(new Error(`${appEntryPoint} exited with non-zero status code`)); + } + }); + }); +} + + +export function createWorker(msg: MessageToWorker): any { + for (var i = workers.length - 1; i >= 0; i--) { + if (workers[i].appEntryPoint === msg.appEntryPoint) { + try { + workers[i].worker.kill('SIGKILL'); + } catch (e) { + console.log(`createWorker, kill('SIGKILL'): ${e}`); + } finally { + delete workers[i].worker; + workers.splice(i, 1); + } + } + } + + try { + let scriptArgs = [ + 'build', + '--aot', + '--optimizejs', + '--appEntryPoint', msg.appEntryPoint, + '--appNgModulePath', msg.appNgModulePath, + '--srcDir', msg.srcDir, + '--wwwDir', msg.distDir, + '--tsconfig', msg.tsConfig, + '--readConfigJson', 'false', + '--experimentalParseDeepLinks', 'true', + '--experimentalManualTreeshaking', 'false', + '--experimentalPurgeDecorators', 'false', + '--ionicAngularDir', msg.ionicAngularDir, + '--sass', msg.sassConfigPath, + '--copy', msg.copyConfigPath, + '--enableLint', 'false', + // '--disableLogging', 'true' + ]; + + if (msg.debug) { + scriptArgs.push('--debug'); + } + + const workerModule = join(process.cwd(), 'node_modules', '@ionic', 'app-scripts', 'bin', 'ionic-app-scripts.js'); + const worker = fork(workerModule, scriptArgs, { + env: { + FORCE_COLOR: true, + npm_config_argv: process.env.npm_config_argv + } + }); + + workers.push({ + appEntryPoint: msg.appEntryPoint, + worker: worker + }); + + return worker; + + } catch (e) { + throw new Error(`unable to create worker-process: ${e.msg}`); + } +} + + +export const workers: WorkerProcess[] = []; diff --git a/scripts/gulp/utils/interfaces.ts b/scripts/gulp/utils/interfaces.ts new file mode 100644 index 0000000000..454118a118 --- /dev/null +++ b/scripts/gulp/utils/interfaces.ts @@ -0,0 +1,18 @@ + +export interface WorkerProcess { + appEntryPoint: string; + worker: any; +}; + +export interface MessageToWorker { + pathToAppScripts: string; + debug: boolean; + appEntryPoint: string; + appNgModulePath: string; + srcDir: string; + distDir: string; + tsConfig: string; + ionicAngularDir: string; + sassConfigPath: string; + copyConfigPath: string; +}; diff --git a/scripts/npm/package.json b/scripts/npm/package.json index 74150c65d2..6bd821bc24 100644 --- a/scripts/npm/package.json +++ b/scripts/npm/package.json @@ -8,7 +8,7 @@ "url": "https://github.com/driftyco/ionic.git" }, "license": "MIT", - "main": "umd/index.js", + "main": "esm/index.js", "module": "index.js", "peerDependencies": { "@angular/common": "", diff --git a/scripts/snapshot/ionic.snapshot.js b/scripts/snapshot/ionic.snapshot.js index 0ab8d9d7e8..a86a674b32 100644 --- a/scripts/snapshot/ionic.snapshot.js +++ b/scripts/snapshot/ionic.snapshot.js @@ -115,10 +115,13 @@ var IonicSnapshot = function(options) { var specIdString = '[' + (spec.id+1) + '/' + self.testData.total_specs + ']'; self.testData.spec_index = spec.id; + // console.log('spec.id: ', spec.id); self.testData.highest_mismatch = self.highestMismatch; self.testData.png_base64 = pngBase64; - self.testData.description = spec.getFullName().replace('components/', '').replace('test/', ''); - self.testData.url = currentUrl.replace('dist', '').replace('components/', '').replace('test/', '').replace('&ionicanimate=false', ''); + self.testData.description = spec.getFullName().replace('components/', '').replace('test/', '').replace('www', ''); + self.testData.url = currentUrl.replace('dist', '').replace('components/', '').replace('test/', '').replace('&ionicanimate=false', '').replace('www/', ''); + //console.log('self.testData.description: ', self.testData.description); + //console.log('self.testData.url: ', self.testData.url); pngBase64 = null; var requestDeferred = q.defer(); diff --git a/scripts/templates/component/module.ts.tmpl b/scripts/templates/component/module.ts.tmpl new file mode 100644 index 0000000000..c2d02061fc --- /dev/null +++ b/scripts/templates/component/module.ts.tmpl @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { $CLASSNAMEComponent } from './$FILENAME'; +import { IonicModule } from 'ionic-angular'; + +@NgModule({ + declarations: [ + $CLASSNAMEComponent, + ], + imports: [ + IonicModule.forChild($CLASSNAMEComponent) + ], + entryComponents: [ + $CLASSNAMEComponent + ], + providers: [] +}) +export class $CLASSNAMEComponentModule {} diff --git a/scripts/templates/page/module.ts.tmpl b/scripts/templates/page/module.ts.tmpl new file mode 100644 index 0000000000..4f779d4060 --- /dev/null +++ b/scripts/templates/page/module.ts.tmpl @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { $CLASSNAMEPage } from './$FILENAME'; +import { IonicModule } from 'ionic-angular'; + +@NgModule({ + declarations: [ + $CLASSNAMEPage, + ], + imports: [ + IonicModule.forChild($CLASSNAMEPage) + ], + entryComponents: [ + $CLASSNAMEPage + ], + providers: [] +}) +export class $CLASSNAMEPageModule {}