diff --git a/tools/broccoli/broccoli-dest-copy.ts b/tools/broccoli/broccoli-dest-copy.ts index d6f2ad6dc1..e5abc7f4f0 100644 --- a/tools/broccoli/broccoli-dest-copy.ts +++ b/tools/broccoli/broccoli-dest-copy.ts @@ -1,40 +1,21 @@ -/// /// /// import fs = require('fs'); import fse = require('fs-extra'); import path = require('path'); -import TreeDiffer = require('./tree-differ'); +import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin'; /** * Intercepts each file as it is copied to the destination tempdir, * and tees a copy to the given path outside the tmp dir. */ -export = function destCopy(inputTree, outputRoot) { return new DestCopy(inputTree, outputRoot); } +class DestCopy implements DiffingBroccoliPlugin { + constructor(private inputPath, private cachePath, private outputRoot: string) {} -class DestCopy implements BroccoliTree { - treeDirtyChecker: TreeDiffer; - initialized = false; - - // props monkey-patched by broccoli builder: - inputPath = null; - cachePath = null; - outputPath = null; - - - constructor(public inputTree: BroccoliTree, public outputRoot: string) {} - - - rebuild() { - let firstRun = !this.initialized; - this.init(); - - let diffResult = this.treeDirtyChecker.diffTree(); - diffResult.log(!firstRun); - - diffResult.changedPaths.forEach((changedFilePath) => { + rebuild(treeDiff: DiffResult) { + treeDiff.changedPaths.forEach((changedFilePath) => { var destFilePath = path.join(this.outputRoot, changedFilePath); var destDirPath = path.dirname(destFilePath); @@ -42,22 +23,13 @@ class DestCopy implements BroccoliTree { fse.copySync(path.join(this.inputPath, changedFilePath), destFilePath); }); - diffResult.removedPaths.forEach((removedFilePath) => { + treeDiff.removedPaths.forEach((removedFilePath) => { var destFilePath = path.join(this.outputRoot, removedFilePath); // TODO: what about obsolete directories? we are not cleaning those up yet fs.unlinkSync(destFilePath); }); } - - - private init() { - if (!this.initialized) { - this.initialized = true; - this.treeDirtyChecker = new TreeDiffer(this.inputPath); - } - } - - - cleanup() {} } + +export default wrapDiffingPlugin(DestCopy); diff --git a/tools/broccoli/diffing-broccoli-plugin.ts b/tools/broccoli/diffing-broccoli-plugin.ts new file mode 100644 index 0000000000..952bbb8245 --- /dev/null +++ b/tools/broccoli/diffing-broccoli-plugin.ts @@ -0,0 +1,103 @@ +/// +/// +/// + +import fs = require('fs'); +import fse = require('fs-extra'); +import path = require('path'); +import {TreeDiffer, DiffResult} from './tree-differ'; +let symlinkOrCopy = require('symlink-or-copy'); + + +export {DiffResult} from './tree-differ'; + + +/** + * Makes writing diffing plugins easy. + * + * Factory method that takes a class that implements the DiffingBroccoliPlugin interface and returns + * an instance of BroccoliTree. + * + * @param pluginClass + * @returns {DiffingPlugin} + */ +export function wrapDiffingPlugin(pluginClass): DiffingPluginWrapperFactory { + return function() { return new DiffingPluginWrapper(pluginClass, arguments); }; +} + + +export interface DiffingBroccoliPlugin { + rebuild(diff: DiffResult): (Promise| void); + cleanup ? () : void; +} + + +type DiffingPluginWrapperFactory = (inputTrees: (BroccoliTree | BroccoliTree[]), options?) => + BroccoliTree; + + +class DiffingPluginWrapper implements BroccoliTree { + treeDiffer: TreeDiffer; + initialized = false; + wrappedPlugin: DiffingBroccoliPlugin = null; + inputTree = null; + inputTrees = null; + description = null; + + // props monkey-patched by broccoli builder: + inputPath = null; + cachePath = null; + outputPath = null; + + + constructor(private pluginClass, private wrappedPluginArguments) { + if (Array.isArray(wrappedPluginArguments[0])) { + this.inputTrees = wrappedPluginArguments[0]; + } else { + this.inputTree = wrappedPluginArguments[0]; + } + + this.description = this.pluginClass.name; + } + + + rebuild() { + let firstRun = !this.initialized; + this.init(); + + let diffResult = this.treeDiffer.diffTree(); + diffResult.log(!firstRun); + + var rebuildPromise = this.wrappedPlugin.rebuild(diffResult); + + if (rebuildPromise) { + return (>rebuildPromise).then(this.relinkOutputAndCachePaths.bind(this)); + } + + this.relinkOutputAndCachePaths(); + } + + + private relinkOutputAndCachePaths() { + // just symlink the cache and output tree + fs.rmdirSync(this.outputPath); + symlinkOrCopy.sync(this.cachePath, this.outputPath); + } + + + private init() { + if (!this.initialized) { + this.initialized = true; + this.treeDiffer = new TreeDiffer(this.inputPath); + this.wrappedPlugin = + new this.pluginClass(this.inputPath, this.cachePath, this.wrappedPluginArguments[1]); + } + } + + + cleanup() { + if (this.wrappedPlugin.cleanup) { + this.wrappedPlugin.cleanup(); + } + } +} diff --git a/tools/broccoli/traceur/index.ts b/tools/broccoli/traceur/index.ts index 1926a0d244..4d95f58fd0 100644 --- a/tools/broccoli/traceur/index.ts +++ b/tools/broccoli/traceur/index.ts @@ -1,66 +1,38 @@ -/// -/// /// /// import fs = require('fs'); import fse = require('fs-extra'); import path = require('path'); -import TreeDiffer = require('../tree-differ'); -import Writer = require('broccoli-writer'); +import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from '../diffing-broccoli-plugin'; let traceur = require('../../../../tools/transpiler'); -let symlinkOrCopy = require('symlink-or-copy'); let xtend = require('xtend'); -export = traceurCompiler; - -function traceurCompiler(inputTree, destExtension, destSourceMapExtension, options) { - return new TraceurCompiler(inputTree, destExtension, destSourceMapExtension, options); -} - -traceurCompiler['RUNTIME_PATH'] = traceur.RUNTIME_PATH; +class DiffingTraceurCompiler implements DiffingBroccoliPlugin { + constructor(public inputPath: string, public cachePath: string, public options) {} -class TraceurCompiler implements BroccoliTree { - treeDiffer: TreeDiffer; - initialized = false; - - // props monkey-patched by broccoli builder: - inputPath = null; - cachePath = null; - outputPath = null; - - - constructor(public inputTree: BroccoliTree, private destExtension: string, - private destSourceMapExtension: string, private options: {[key: string]: any}) {} - - - rebuild() { - let firstRun = !this.initialized; - this.init(); - - let diffResult = this.treeDiffer.diffTree(); - diffResult.log(!firstRun); - - diffResult.changedPaths.forEach((changedFilePath) => { + rebuild(treeDiff: DiffResult) { + treeDiff.changedPaths.forEach((changedFilePath) => { var extension = path.extname(changedFilePath).toLowerCase(); if (extension === '.js' || extension === '.es6' || extension === '.cjs') { - var options = xtend({filename: changedFilePath}, this.options); + var traceurOpts = xtend({filename: changedFilePath}, this.options.traceurOptions); var fsOpts = {encoding: 'utf-8'}; var absoluteInputFilePath = path.join(this.inputPath, changedFilePath); var sourcecode = fs.readFileSync(absoluteInputFilePath, fsOpts); - var result = traceur.compile(options, changedFilePath, sourcecode); + var result = traceur.compile(traceurOpts, changedFilePath, sourcecode); // TODO: we should fix the sourceMappingURL written by Traceur instead of overriding // (but we might switch to typescript first) - var mapFilepath = changedFilePath.replace(/\.\w+$/, '') + this.destSourceMapExtension; + var mapFilepath = + changedFilePath.replace(/\.\w+$/, '') + this.options.destSourceMapExtension; result.js = result.js + '\n//# sourceMappingURL=./' + path.basename(mapFilepath); - var destFilepath = changedFilePath.replace(/\.\w+$/, this.destExtension); + var destFilepath = changedFilePath.replace(/\.\w+$/, this.options.destExtension); var destFile = path.join(this.cachePath, destFilepath); fse.mkdirsSync(path.dirname(destFile)); fs.writeFileSync(destFile, result.js, fsOpts); @@ -71,25 +43,15 @@ class TraceurCompiler implements BroccoliTree { } }); - diffResult.removedPaths.forEach((removedFilePath) => { - var destFilepath = removedFilePath.replace(/\.\w+$/, this.destExtension); + treeDiff.removedPaths.forEach((removedFilePath) => { + var destFilepath = removedFilePath.replace(/\.\w+$/, this.options.destExtension); var absoluteOuputFilePath = path.join(this.cachePath, destFilepath); fs.unlinkSync(absoluteOuputFilePath); }); - - // just symlink the cache and output tree - fs.rmdirSync(this.outputPath); - symlinkOrCopy.sync(this.cachePath, this.outputPath); } - - - private init() { - if (!this.initialized) { - this.initialized = true; - this.treeDiffer = new TreeDiffer(this.inputPath); - } - } - - - cleanup() {} } + +let transpileWithTraceur = wrapDiffingPlugin(DiffingTraceurCompiler); +let TRACEUR_RUNTIME_PATH = traceur.RUNTIME_PATH; + +export {transpileWithTraceur as default, TRACEUR_RUNTIME_PATH}; diff --git a/tools/broccoli/tree-differ.spec.ts b/tools/broccoli/tree-differ.spec.ts index 93998714b0..beaf37000e 100644 --- a/tools/broccoli/tree-differ.spec.ts +++ b/tools/broccoli/tree-differ.spec.ts @@ -3,7 +3,7 @@ let mockfs = require('mock-fs'); import fs = require('fs'); -import TreeDiffer = require('./tree-differ'); +import {TreeDiffer} from './tree-differ'; describe('TreeDiffer', () => { diff --git a/tools/broccoli/tree-differ.ts b/tools/broccoli/tree-differ.ts index 0762adbb17..552723ce79 100644 --- a/tools/broccoli/tree-differ.ts +++ b/tools/broccoli/tree-differ.ts @@ -4,9 +4,7 @@ import fs = require('fs'); import path = require('path'); -export = TreeDiffer; - -class TreeDiffer { +export class TreeDiffer { private fingerprints: {[key: string]: string} = Object.create(null); private nextFingerprints: {[key: string]: string} = Object.create(null); private rootDirName: string; @@ -15,7 +13,7 @@ class TreeDiffer { public diffTree(): DiffResult { - let result = new DiffResult(this.rootDirName); + let result = new DirtyCheckingDiffResult(this.rootDirName); this.dirtyCheckPath(this.rootPath, result); this.detectDeletionsAndUpdateFingerprints(result); result.endTime = Date.now(); @@ -23,7 +21,7 @@ class TreeDiffer { } - private dirtyCheckPath(rootDir: string, result: DiffResult) { + private dirtyCheckPath(rootDir: string, result: DirtyCheckingDiffResult) { fs.readdirSync(rootDir).forEach((segment) => { let absolutePath = path.join(rootDir, segment); let pathStat = fs.statSync(absolutePath); @@ -76,7 +74,15 @@ class TreeDiffer { } -class DiffResult { +export interface DiffResult { + changedPaths: string[]; + removedPaths: string[]; + log(verbose: boolean): void; + toString(): string; +} + + +class DirtyCheckingDiffResult { public filesChecked: number = 0; public directoriesChecked: number = 0; public changedPaths: string[] = []; diff --git a/tools/broccoli/trees/browser_tree.ts b/tools/broccoli/trees/browser_tree.ts index b06e1a8913..5c675db629 100644 --- a/tools/broccoli/trees/browser_tree.ts +++ b/tools/broccoli/trees/browser_tree.ts @@ -1,6 +1,5 @@ 'use strict'; -var destCopy = require('../broccoli-dest-copy'); var Funnel = require('broccoli-funnel'); var flatten = require('broccoli-flatten'); var htmlReplace = require('../html-replace'); @@ -9,9 +8,11 @@ var path = require('path'); var replace = require('broccoli-replace'); var stew = require('broccoli-stew'); var ts2dart = require('../broccoli-ts2dart'); -var traceurCompiler = require('../traceur'); var TypescriptCompiler = require('../typescript'); +import destCopy from '../broccoli-dest-copy'; +import {default as transpileWithTraceur, TRACEUR_RUNTIME_PATH} from '../traceur/index'; + var projectRootDir = path.normalize(path.join(__dirname, '..', '..', '..', '..')); @@ -22,16 +23,20 @@ module.exports = function makeBrowserTree(options, destinationPath) { {include: ['**/**'], exclude: ['**/*.cjs', 'benchmarks/e2e_test/**'], destDir: '/'}); // Use Traceur to transpile *.js sources to ES6 - var traceurTree = traceurCompiler(modulesTree, '.es6', '.map', { - sourceMaps: true, - annotations: true, // parse annotations - types: true, // parse types - script: false, // parse as a module - memberVariables: true, // parse class fields - modules: 'instantiate', - // typeAssertionModule: 'rtts_assert/rtts_assert', - // typeAssertions: options.typeAssertions, - outputLanguage: 'es6' + var traceurTree = transpileWithTraceur(modulesTree, { + destExtension: '.es6', + destSourceMapExtension: '.map', + traceurOptions: { + sourceMaps: true, + annotations: true, // parse annotations + types: true, // parse types + script: false, // parse as a module + memberVariables: true, // parse class fields + modules: 'instantiate', + // typeAssertionModule: 'rtts_assert/rtts_assert', + // typeAssertions: options.typeAssertions, + outputLanguage: 'es6' + } }); // Use TypeScript to transpile the *.ts files to ES6 @@ -52,8 +57,11 @@ module.exports = function makeBrowserTree(options, destinationPath) { var es6Tree = mergeTrees([traceurTree, typescriptTree]); // Call Traceur again to lower the ES6 build tree to ES5 - var es5Tree = - traceurCompiler(es6Tree, '.js', '.js.map', {modules: 'instantiate', sourceMaps: true}); + var es5Tree = transpileWithTraceur(es6Tree, { + destExtension: '.js', + destSourceMapExtension: '.js.map', + traceurOptions: {modules: 'instantiate', sourceMaps: true} + }); // Now we add a few more files to the es6 tree that Traceur should not see ['angular2', 'rtts_assert'].forEach(function(destDir) { @@ -73,7 +81,7 @@ module.exports = function makeBrowserTree(options, destinationPath) { 'node_modules/rx/dist/rx.js', 'node_modules/reflect-metadata/Reflect.js', 'tools/build/snippets/runtime_paths.js', - path.relative(projectRootDir, traceurCompiler.RUNTIME_PATH) + path.relative(projectRootDir, TRACEUR_RUNTIME_PATH) ] })); var vendorScripts_benchmark = diff --git a/tools/broccoli/trees/dart_tree.ts b/tools/broccoli/trees/dart_tree.ts index 68fb36640f..1cbf02586f 100644 --- a/tools/broccoli/trees/dart_tree.ts +++ b/tools/broccoli/trees/dart_tree.ts @@ -2,7 +2,7 @@ 'use strict'; import {MultiCopy} from './../multi_copy'; -var destCopy = require('../broccoli-dest-copy'); +import destCopy from '../broccoli-dest-copy'; var Funnel = require('broccoli-funnel'); var glob = require('glob'); var mergeTrees = require('broccoli-merge-trees'); diff --git a/tools/broccoli/trees/node_tree.ts b/tools/broccoli/trees/node_tree.ts index f3caf6857b..d244231dff 100644 --- a/tools/broccoli/trees/node_tree.ts +++ b/tools/broccoli/trees/node_tree.ts @@ -1,6 +1,6 @@ 'use strict'; -var destCopy = require('../broccoli-dest-copy'); +import destCopy from '../broccoli-dest-copy'; var Funnel = require('broccoli-funnel'); var mergeTrees = require('broccoli-merge-trees'); var path = require('path'); @@ -8,7 +8,7 @@ var renderLodashTemplate = require('broccoli-lodash'); var replace = require('broccoli-replace'); var stew = require('broccoli-stew'); var ts2dart = require('../broccoli-ts2dart'); -var traceurCompiler = require('../traceur'); +import transpileWithTraceur from '../traceur/index'; var TypescriptCompiler = require('../typescript'); var projectRootDir = path.normalize(path.join(__dirname, '..', '..', '..', '..')); @@ -27,16 +27,20 @@ module.exports = function makeNodeTree(destinationPath) { ] }); - var nodeTree = traceurCompiler(modulesTree, '.js', '.map', { - sourceMaps: true, - annotations: true, // parse annotations - types: true, // parse types - script: false, // parse as a module - memberVariables: true, // parse class fields - typeAssertionModule: 'rtts_assert/rtts_assert', - // Don't use type assertions since this is partly transpiled by typescript - typeAssertions: false, - modules: 'commonjs' + var nodeTree = transpileWithTraceur(modulesTree, { + destExtension: '.js', + destSourceMapExtension: '.map', + traceurOptions: { + sourceMaps: true, + annotations: true, // parse annotations + types: true, // parse types + script: false, // parse as a module + memberVariables: true, // parse class fields + typeAssertionModule: 'rtts_assert/rtts_assert', + // Don't use type assertions since this is partly transpiled by typescript + typeAssertions: false, + modules: 'commonjs' + } }); // Transform all tests to make them runnable in node