diff --git a/tools/tasks/assets_task.ts b/tools/tasks/assets_task.ts new file mode 100644 index 000000000..a14863e4d --- /dev/null +++ b/tools/tasks/assets_task.ts @@ -0,0 +1,10 @@ +import { Task } from './task'; + +export abstract class AssetsTask extends Task { + shallRun(files: String[]) { + return files.reduce((a, f) => { + return a || (!f.endsWith('.css') && !f.endsWith('.sass') && + !f.endsWith('.scss') && !f.endsWith('.ts')); + }, false); + } +} diff --git a/tools/tasks/css_task.ts b/tools/tasks/css_task.ts new file mode 100644 index 000000000..ea5d98b11 --- /dev/null +++ b/tools/tasks/css_task.ts @@ -0,0 +1,10 @@ +import { Task } from './task'; + +export abstract class CssTask extends Task { + + shallRun(files: String[]) { + return files.some(f => + f.endsWith('.css') || f.endsWith('.sass') || f.endsWith('.scss')); + } + +} diff --git a/tools/tasks/seed/build.assets.dev.ts b/tools/tasks/seed/build.assets.dev.ts index 97efadf3b..38db5f47a 100644 --- a/tools/tasks/seed/build.assets.dev.ts +++ b/tools/tasks/seed/build.assets.dev.ts @@ -1,20 +1,25 @@ import * as gulp from 'gulp'; import { join } from 'path'; +import { AssetsTask } from '../assets_task'; import Config from '../../config'; /** * Executes the build process, copying the assets located in `src/client` over to the appropriate * `dist/dev` directory. */ -export = () => { - let paths: string[] = [ - join(Config.APP_SRC, '**'), - '!' + join(Config.APP_SRC, '**', '*.ts'), - '!' + join(Config.APP_SRC, '**', '*.scss'), - '!' + join(Config.APP_SRC, '**', '*.sass') - ].concat(Config.TEMP_FILES.map((p) => { return '!' + p; })); +export = + class BuildAssetsTask extends AssetsTask { + run() { + let paths: string[] = [ + join(Config.APP_SRC, '**'), + '!' + join(Config.APP_SRC, '**', '*.ts'), + '!' + join(Config.APP_SRC, '**', '*.scss'), + '!' + join(Config.APP_SRC, '**', '*.sass') + ].concat(Config.TEMP_FILES.map((p) => { return '!' + p; })); + + return gulp.src(paths) + .pipe(gulp.dest(Config.APP_DEST)); + } + }; - return gulp.src(paths) - .pipe(gulp.dest(Config.APP_DEST)); -}; diff --git a/tools/tasks/seed/build.bundle.rxjs.ts b/tools/tasks/seed/build.bundle.rxjs.ts index a656b2e63..e33a45c60 100644 --- a/tools/tasks/seed/build.bundle.rxjs.ts +++ b/tools/tasks/seed/build.bundle.rxjs.ts @@ -4,7 +4,7 @@ const Builder = require('systemjs-builder'); -export = (done:any) => { +export = (done: any) => { const options = { normalize: true, runtime: false, diff --git a/tools/tasks/seed/build.html_css.ts b/tools/tasks/seed/build.html_css.ts index f58a9b36a..052c4e80a 100644 --- a/tools/tasks/seed/build.html_css.ts +++ b/tools/tasks/seed/build.html_css.ts @@ -7,6 +7,7 @@ import * as util from 'gulp-util'; import { join } from 'path'; import Config from '../../config'; +import { CssTask } from '../css_task'; const plugins = gulpLoadPlugins(); const cleanCss = require('gulp-clean-css'); @@ -147,4 +148,14 @@ function processExternalCss() { /** * Executes the build process, processing the HTML and CSS files. */ -export = () => merge(processComponentStylesheets(), prepareTemplates(), processExternalStylesheets()); +export = + class BuildHtmlCss extends CssTask { + + shallRun(files: String[]) { + return super.shallRun(files) || files.some(f => f.endsWith('.html')); + } + + run() { + return merge(processComponentStylesheets(), prepareTemplates(), processExternalStylesheets()); + } + }; diff --git a/tools/tasks/seed/build.js.dev.ts b/tools/tasks/seed/build.js.dev.ts index 67875b223..da568ac1e 100644 --- a/tools/tasks/seed/build.js.dev.ts +++ b/tools/tasks/seed/build.js.dev.ts @@ -1,3 +1,4 @@ + import * as gulp from 'gulp'; import * as gulpLoadPlugins from 'gulp-load-plugins'; import * as merge from 'merge-stream'; @@ -6,6 +7,7 @@ import { join/*, sep, relative*/ } from 'path'; import Config from '../../config'; import { makeTsProject, templateLocals } from '../../utils'; +import { TypeScriptTask } from '../typescript_task'; const plugins = gulpLoadPlugins(); @@ -17,60 +19,64 @@ let typedBuildCounter = Config.TYPED_COMPILE_INTERVAL; // Always start with the * Executes the build process, transpiling the TypeScript files (except the spec and e2e-spec files) for the development * environment. */ -export = () => { - let tsProject: any; - let typings = gulp.src([ - Config.TOOLS_DIR + '/manual_typings/**/*.d.ts' - ]); - let src = [ - join(Config.APP_SRC, '**/*.ts'), - '!' + join(Config.APP_SRC, '**/*.spec.ts'), - '!' + join(Config.APP_SRC, '**/*.e2e-spec.ts'), - '!' + join(Config.APP_SRC, `**/${Config.NG_FACTORY_FILE}.ts`) - ]; +export = + class BuildJsDev extends TypeScriptTask { + run() { + let tsProject: any; + let typings = gulp.src([ + Config.TOOLS_DIR + '/manual_typings/**/*.d.ts' + ]); + let src = [ + join(Config.APP_SRC, '**/*.ts'), + '!' + join(Config.APP_SRC, '**/*.spec.ts'), + '!' + join(Config.APP_SRC, '**/*.e2e-spec.ts'), + '!' + join(Config.APP_SRC, `**/${Config.NG_FACTORY_FILE}.ts`) + ]; - let projectFiles = gulp.src(src); - let result: any; - let isFullCompile = true; + let projectFiles = gulp.src(src); + let result: any; + let isFullCompile = true; - // Only do a typed build every X builds, otherwise do a typeless build to speed things up - if (typedBuildCounter < Config.TYPED_COMPILE_INTERVAL) { - isFullCompile = false; - tsProject = makeTsProject({isolatedModules: true}); - projectFiles = projectFiles.pipe(plugins.cached()); - util.log('Performing typeless TypeScript compile.'); - } else { - tsProject = makeTsProject(); - projectFiles = merge(typings, projectFiles); - } + // Only do a typed build every X builds, otherwise do a typeless build to speed things up + if (typedBuildCounter < Config.TYPED_COMPILE_INTERVAL) { + isFullCompile = false; + tsProject = makeTsProject({isolatedModules: true}); + projectFiles = projectFiles.pipe(plugins.cached()); + util.log('Performing typeless TypeScript compile.'); + } else { + tsProject = makeTsProject(); + projectFiles = merge(typings, projectFiles); + } - result = projectFiles - .pipe(plugins.plumber()) - .pipe(plugins.sourcemaps.init()) - .pipe(tsProject()) - .on('error', () => { - typedBuildCounter = Config.TYPED_COMPILE_INTERVAL; - }); + result = projectFiles + .pipe(plugins.plumber()) + .pipe(plugins.sourcemaps.init()) + .pipe(tsProject()) + .on('error', () => { + typedBuildCounter = Config.TYPED_COMPILE_INTERVAL; + }); - if (isFullCompile) { - typedBuildCounter = 0; - } else { - typedBuildCounter++; - } + if (isFullCompile) { + typedBuildCounter = 0; + } else { + typedBuildCounter++; + } - return result.js - .pipe(plugins.sourcemaps.write()) -// Use for debugging with Webstorm/IntelliJ -// https://github.com/mgechev/angular2-seed/issues/1220 -// .pipe(plugins.sourcemaps.write('.', { -// includeContent: false, -// sourceRoot: (file: any) => -// relative(file.path, PROJECT_ROOT + '/' + APP_SRC).replace(sep, '/') + '/' + APP_SRC -// })) - .pipe(plugins.template(Object.assign( - templateLocals(), { - SYSTEM_CONFIG_DEV: jsonSystemConfig + return result.js + .pipe(plugins.sourcemaps.write()) + // Use for debugging with Webstorm/IntelliJ + // https://github.com/mgechev/angular2-seed/issues/1220 + // .pipe(plugins.sourcemaps.write('.', { + // includeContent: false, + // sourceRoot: (file: any) => + // relative(file.path, PROJECT_ROOT + '/' + APP_SRC).replace(sep, '/') + '/' + APP_SRC + // })) + .pipe(plugins.template(Object.assign( + templateLocals(), { + SYSTEM_CONFIG_DEV: jsonSystemConfig + } + ))) + .pipe(gulp.dest(Config.APP_DEST)); } - ))) - .pipe(gulp.dest(Config.APP_DEST)); -}; + }; + diff --git a/tools/tasks/task.ts b/tools/tasks/task.ts new file mode 100644 index 000000000..d8436236f --- /dev/null +++ b/tools/tasks/task.ts @@ -0,0 +1,25 @@ +/** + * Base class for all tasks. + */ +export abstract class Task { + /** + * Override this task if you want to implement some custom + * task activation mechanism. By default each task will be always executed. + * + * @param {string[]} files A list of files changed since the previous watch. + */ + shallRun(files: string[]): boolean { + return true; + } + + /** + * Implements your task behavior. + * + * @param {any} done A function which should be activated once your task completes. + * @return {ReadWriteStream | Promise | void} This method can either return a promise + * which should be resolved once your task execution completes, a stream + * which should throw an end event once your task execution completes + * or nothing in case you will manually invoke the `done` method. + */ + abstract run(done?: any): any | Promise | void; +} diff --git a/tools/tasks/typescript_task.ts b/tools/tasks/typescript_task.ts new file mode 100644 index 000000000..ad5a68d1a --- /dev/null +++ b/tools/tasks/typescript_task.ts @@ -0,0 +1,9 @@ +import { Task } from './task'; + +export abstract class TypeScriptTask extends Task { + shallRun(files: String[]) { + return files.reduce((a, f) => { + return a || f.endsWith('.ts'); + }, false); + } +} diff --git a/tools/utils/seed/code_change_tools.ts b/tools/utils/seed/code_change_tools.ts index acb112b0e..07f647f7e 100644 --- a/tools/utils/seed/code_change_tools.ts +++ b/tools/utils/seed/code_change_tools.ts @@ -3,6 +3,34 @@ import * as browserSync from 'browser-sync'; import Config from '../../config'; +class ChangeFileManager { + private _files: string[] = []; + private _pristine = true; + + get lastChangedFiles() { + return this._files.slice(); + } + + get pristine() { + return this._pristine; + } + + addFile(file: string) { + this._pristine = false; + this._files.push(file); + } + + addFiles(files: string[]) { + files.forEach(f => this.addFile(f)); + } + + clear() { + this._files = []; + } +} + +export let changeFileManager = new ChangeFileManager(); + /** * Initialises BrowserSync with the configuration defined in seed.config.ts (or if overriden: project.config.ts). */ diff --git a/tools/utils/seed/tasks_tools.ts b/tools/utils/seed/tasks_tools.ts index 44486bab2..c0f8e921f 100644 --- a/tools/utils/seed/tasks_tools.ts +++ b/tools/utils/seed/tasks_tools.ts @@ -5,6 +5,9 @@ import * as isstream from 'isstream'; import { join } from 'path'; import * as tildify from 'tildify'; +import { changeFileManager } from './code_change_tools'; +import { Task } from '../../tasks/task'; + /** * Loads the tasks within the given path. * @param {string} path - The path to load the tasks from. @@ -14,6 +17,33 @@ export function loadTasks(path: string): void { readDir(path, taskname => registerTask(taskname, path)); } +function normalizeTask(task: any, taskName: string) { + if (task instanceof Task) { + return task; + } + if (task.prototype && task.prototype instanceof Task) { + return new task(); + } + if (typeof task === 'function') { + return new class AnonTask extends Task { + run(done: any) { + if (task.length > 0) { + return task(done); + } + + const taskReturnedValue = task(); + if (isstream(taskReturnedValue)) { + return taskReturnedValue; + } + + done(); + } + }; + } + throw new Error(taskName + ' should be instance of the class ' + + 'Task, a function or a class which extends Task.'); +} + /** * Registers the task by the given taskname and path. * @param {string} taskname - The name of the task. @@ -24,19 +54,13 @@ function registerTask(taskname: string, path: string): void { util.log('Registering task', util.colors.yellow(tildify(TASK))); gulp.task(taskname, (done: any) => { - const task = require(TASK); - if (task.length > 0) { - return task(done); - } + const task = normalizeTask(require(TASK), TASK); - const taskReturnedValue = task(); - if (isstream(taskReturnedValue)) { - return taskReturnedValue; + if (changeFileManager.pristine || task.shallRun(changeFileManager.lastChangedFiles)) { + return task.run(done); + } else { + done(); } - - // TODO: add promise handling if needed at some point. - - done(); }); } diff --git a/tools/utils/seed/watch.ts b/tools/utils/seed/watch.ts index 0169acdad..e039c400a 100644 --- a/tools/utils/seed/watch.ts +++ b/tools/utils/seed/watch.ts @@ -3,6 +3,7 @@ import { join } from 'path'; import * as runSequence from 'run-sequence'; import Config from '../../config'; +import { changeFileManager } from './code_change_tools'; import { notifyLiveReload } from '../../utils'; const plugins = gulpLoadPlugins(); @@ -17,8 +18,13 @@ export function watch(taskname: string) { join(Config.APP_SRC,'**') ].concat(Config.TEMP_FILES.map((p) => { return '!'+p; })); - plugins.watch(paths, (e:any) => - runSequence(taskname, () => notifyLiveReload(e)) - ); + plugins.watch(paths, (e: any) => { + changeFileManager.addFile(e.path); + + runSequence(taskname, () => { + changeFileManager.clear(); + notifyLiveReload(e); + }); + }); }; }