From a4b5cb837682ce61f8c07eabd7ae8c4dc3ba80a7 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 22 Dec 2015 16:24:35 -0800 Subject: [PATCH] build(node): split test and src compilation units --- .../angular2/manual_typings/globals-es6.d.ts | 2 - .../src/common/directives/ng_switch.ts | 3 +- tools/broccoli/broccoli-typescript.ts | 79 +++++++++- tools/broccoli/trees/node_tree.ts | 148 +++++++++++++----- 4 files changed, 186 insertions(+), 46 deletions(-) diff --git a/modules/angular2/manual_typings/globals-es6.d.ts b/modules/angular2/manual_typings/globals-es6.d.ts index c6e348aa79..703f7de009 100644 --- a/modules/angular2/manual_typings/globals-es6.d.ts +++ b/modules/angular2/manual_typings/globals-es6.d.ts @@ -6,8 +6,6 @@ /// /// -/// -/// // TODO: ideally the node.d.ts reference should be scoped only for files that need and not to all // the code including client code diff --git a/modules/angular2/src/common/directives/ng_switch.ts b/modules/angular2/src/common/directives/ng_switch.ts index a25cb70a08..469a9dbca2 100644 --- a/modules/angular2/src/common/directives/ng_switch.ts +++ b/modules/angular2/src/common/directives/ng_switch.ts @@ -4,7 +4,8 @@ import {ListWrapper, Map} from 'angular2/src/facade/collection'; const _WHEN_DEFAULT = CONST_EXPR(new Object()); -class SwitchView { +/** @internal */ +export class SwitchView { constructor(private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef) {} create(): void { this._viewContainerRef.createEmbeddedView(this._templateRef); } diff --git a/tools/broccoli/broccoli-typescript.ts b/tools/broccoli/broccoli-typescript.ts index ff57205306..1913d02c50 100644 --- a/tools/broccoli/broccoli-typescript.ts +++ b/tools/broccoli/broccoli-typescript.ts @@ -13,6 +13,26 @@ const FS_OPTS = { encoding: 'utf-8' }; +// Sub-directory where the @internal typing files (.d.ts) are stored +export const INTERNAL_TYPINGS_PATH: string = 'internal_typings'; + +// Monkey patch the TS compiler to be able to re-emit files with @internal symbols +let tsEmitInternal: boolean = false; + +const originalEmitFiles: Function = (ts).emitFiles; + +(ts).emitFiles = function(resolver: any, host: any, targetSourceFile: any): any { + if (tsEmitInternal) { + const orignalgetCompilerOptions = host.getCompilerOptions; + host.getCompilerOptions = () => { + let options = clone(orignalgetCompilerOptions.call(host)); + options.stripInternal = false; + options.outDir = `${options.outDir}/${INTERNAL_TYPINGS_PATH}`; + return options; + } + } + return originalEmitFiles(resolver, host, targetSourceFile); +}; /** * Broccoli plugin that implements incremental Typescript compiler. @@ -32,6 +52,9 @@ class DiffingTSCompiler implements DiffingBroccoliPlugin { private tsService: ts.LanguageService; private firstRun: boolean = true; private previousRunFailed: boolean = false; + // Whether to generate the @internal typing files (they are only generated when `stripInternal` is + // true) + private genInternalTypings: boolean = false; static includeExtensions = ['.ts']; static excludeExtensions = ['.d.ts']; @@ -44,11 +67,21 @@ class DiffingTSCompiler implements DiffingBroccoliPlugin { this.rootFilePaths = []; } + if (options.internalTypings) { + this.genInternalTypings = true; + delete options.internalTypings; + } + // the conversion is a bit awkward, see https://github.com/Microsoft/TypeScript/issues/5276 // in 1.8 use convertCompilerOptionsFromJson this.tsOpts = ts.parseJsonConfigFileContent({compilerOptions: options, files: []}, null, null).options; + if ((this.tsOpts).stripInternal === false) { + // @internal are included in the generated .d.ts, do not generate them separately + this.genInternalTypings = false; + } + // TODO: the above turns rootDir set to './' into an empty string - looks like a tsc bug // check back when we upgrade to 1.7.x if (this.tsOpts.rootDir === '') { @@ -91,6 +124,7 @@ class DiffingTSCompiler implements DiffingBroccoliPlugin { this.firstRun = false; this.doFullBuild(); } else { + tsEmitInternal = false; pathsToEmit.forEach((tsFilePath) => { let output = this.tsService.getEmitOutput(tsFilePath); @@ -117,11 +151,26 @@ class DiffingTSCompiler implements DiffingBroccoliPlugin { throw error; } else if (this.previousRunFailed) { this.doFullBuild(); + } else if (this.genInternalTypings) { + // serialize the .d.ts files containing @internal symbols + tsEmitInternal = true; + pathsToEmit.forEach((tsFilePath) => { + let output = this.tsService.getEmitOutput(tsFilePath); + if (!output.emitSkipped) { + output.outputFiles.forEach(o => { + if (endsWith(o.name, '.d.ts')) { + let destDirPath = path.dirname(o.name); + fse.mkdirsSync(destDirPath); + fs.writeFileSync(o.name, this.fixSourceMapSources(o.text), FS_OPTS); + } + }); + } + }); + tsEmitInternal = false; } } } - private collectErrors(tsFilePath): String { let allDiagnostics = this.tsService.getCompilerOptionsDiagnostics() .concat(this.tsService.getSyntacticDiagnostics(tsFilePath)) @@ -143,14 +192,27 @@ class DiffingTSCompiler implements DiffingBroccoliPlugin { } } - private doFullBuild() { let program = this.tsService.getProgram(); + + tsEmitInternal = false; let emitResult = program.emit(undefined, (absoluteFilePath, fileContent) => { fse.mkdirsSync(path.dirname(absoluteFilePath)); fs.writeFileSync(absoluteFilePath, this.fixSourceMapSources(fileContent), FS_OPTS); }); + if (this.genInternalTypings) { + // serialize the .d.ts files containing @internal symbols + tsEmitInternal = true; + program.emit(undefined, (absoluteFilePath, fileContent) => { + if (endsWith(absoluteFilePath, '.d.ts')) { + fse.mkdirsSync(path.dirname(absoluteFilePath)); + fs.writeFileSync(absoluteFilePath, fileContent, FS_OPTS); + } + }); + tsEmitInternal = false; + } + if (emitResult.emitSkipped) { let allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); let errorMessages = []; @@ -293,5 +355,16 @@ class CustomLanguageServiceHost implements ts.LanguageServiceHost { } } - export default wrapDiffingPlugin(DiffingTSCompiler); + +function clone(object: T): T { + const result: any = {}; + for (const id in object) { + result[id] = (object)[id]; + } + return result; +} + +function endsWith(str: string, substring: string): boolean { + return str.indexOf(substring, str.length - substring.length) !== -1; +} diff --git a/tools/broccoli/trees/node_tree.ts b/tools/broccoli/trees/node_tree.ts index 50fd5fb37e..9b5c40e5d1 100644 --- a/tools/broccoli/trees/node_tree.ts +++ b/tools/broccoli/trees/node_tree.ts @@ -1,7 +1,8 @@ 'use strict'; import destCopy from '../broccoli-dest-copy'; -import compileWithTypescript from '../broccoli-typescript'; +import compileWithTypescript, { INTERNAL_TYPINGS_PATH } +from '../broccoli-typescript'; var Funnel = require('broccoli-funnel'); import mergeTrees from '../broccoli-merge-trees'; var path = require('path'); @@ -11,13 +12,47 @@ var stew = require('broccoli-stew'); var projectRootDir = path.normalize(path.join(__dirname, '..', '..', '..', '..')); - module.exports = function makeNodeTree(projects, destinationPath) { // list of npm packages that this build will create var outputPackages = ['angular2', 'benchpress']; - var modulesTree = new Funnel('modules', { - include: ['angular2/**', 'benchpress/**', '**/e2e_test/**'], + let srcTree = new Funnel('modules', { + include: ['angular2/**'], + exclude: [ + '**/e2e_test/**', + 'angular2/test/**', + 'angular2/examples/**', + + 'angular2/src/testing/**', + 'angular2/testing.ts', + 'angular2/testing_internal.ts', + 'angular2/src/upgrade/**', + 'angular2/upgrade.ts', + 'angular2/platform/testing/**', + ] + }); + + // Compile the sources and generate the @internal .d.ts + let compiledSrcTreeWithInternals = + compileTree(srcTree, true, ['angular2/manual_typings/globals.d.ts']); + + var testTree = new Funnel('modules', { + include: [ + 'angular2/manual_typings/**', + 'angular2/typings/**', + + 'angular2/test/**', + 'benchpress/**', + '**/e2e_test/**', + 'angular2/examples/**/*_spec.ts', + + 'angular2/src/testing/**', + 'angular2/testing.ts', + 'angular2/testing_internal.ts', + 'angular2/src/upgrade/**', + 'angular2/upgrade.ts', + 'angular2/platform/testing/**', + ], exclude: [ // the following code and tests are not compatible with CJS/node environment 'angular2/test/animate/**', @@ -41,58 +76,48 @@ module.exports = function makeNodeTree(projects, destinationPath) { 'angular2/test/upgrade/**/*.ts', 'angular1_router/**', - 'angular2/examples/**/!(*_spec.ts)', ] }); - var typescriptTree = compileWithTypescript(modulesTree, { - emitDecoratorMetadata: true, - experimentalDecorators: true, - declaration: true, - stripInternal: true, - module: 'commonjs', - moduleResolution: 'classic', - noEmitOnError: true, - rootDir: '.', - rootFilePaths: - ['angular2/manual_typings/globals.d.ts', 'angular2/typings/es6-shim/es6-shim.d.ts'], - inlineSourceMap: true, - inlineSources: true, - target: 'es5' - }); + // Compile the tests against the src @internal .d.ts + let srcPrivateDeclarations = + new Funnel(compiledSrcTreeWithInternals, {srcDir: INTERNAL_TYPINGS_PATH}); + + testTree = mergeTrees([testTree, srcPrivateDeclarations]); + + let compiledTestTree = compileTree(testTree, false, [ + 'angular2/typings/jasmine/jasmine.d.ts', + 'angular2/typings/angular-protractor/angular-protractor.d.ts', + 'angular2/manual_typings/globals.d.ts' + ]); + + // Merge the compiled sources and tests + let compiledSrcTree = + new Funnel(compiledSrcTreeWithInternals, {exclude: [`${INTERNAL_TYPINGS_PATH}/**`]}); + + let compiledTree = mergeTrees([compiledSrcTree, compiledTestTree]); // Now we add the LICENSE file into all the folders that will become npm packages outputPackages.forEach(function(destDir) { var license = new Funnel('.', {files: ['LICENSE'], destDir: destDir}); - typescriptTree = mergeTrees([typescriptTree, license]); + // merge the test tree + compiledTree = mergeTrees([compiledTree, license]); }); // Get all docs and related assets and prepare them for js build - var docs = new Funnel(modulesTree, {include: ['**/*.md', '**/*.png'], exclude: ['**/*.dart.md']}); - docs = stew.rename(docs, 'README.js.md', 'README.md'); + var srcDocs = extractDocs(srcTree); + var testDocs = extractDocs(testTree); - // Generate shared package.json info var BASE_PACKAGE_JSON = require(path.join(projectRootDir, 'package.json')); - var COMMON_PACKAGE_JSON = { - version: BASE_PACKAGE_JSON.version, - homepage: BASE_PACKAGE_JSON.homepage, - bugs: BASE_PACKAGE_JSON.bugs, - license: BASE_PACKAGE_JSON.license, - repository: BASE_PACKAGE_JSON.repository, - contributors: BASE_PACKAGE_JSON.contributors, - dependencies: BASE_PACKAGE_JSON.dependencies, - devDependencies: BASE_PACKAGE_JSON.devDependencies, - defaultDevDependencies: {} - }; - - var packageJsons = new Funnel(modulesTree, {include: ['**/package.json']}); - packageJsons = - renderLodashTemplate(packageJsons, {context: {'packageJson': COMMON_PACKAGE_JSON}}); + var srcPkgJsons = extractPkgJsons(srcTree, BASE_PACKAGE_JSON); + var testPkgJsons = extractPkgJsons(testTree, BASE_PACKAGE_JSON); var typingsTree = new Funnel( 'modules', {include: ['angular2/typings/**/*.d.ts', 'angular2/manual_typings/*.d.ts'], destDir: '/'}); - var nodeTree = mergeTrees([typescriptTree, docs, packageJsons, typingsTree]); + + var nodeTree = + mergeTrees([compiledTree, srcDocs, testDocs, srcPkgJsons, testPkgJsons, typingsTree]); // Transform all tests to make them runnable in node nodeTree = replace(nodeTree, { @@ -132,3 +157,46 @@ module.exports = function makeNodeTree(projects, destinationPath) { return destCopy(nodeTree, destinationPath); }; + +function compileTree(tree, genInternalTypings, rootFilePaths: string[] = []) { + return compileWithTypescript(tree, { + // build pipeline options + "rootFilePaths": rootFilePaths, + "internalTypings": genInternalTypings, + // tsc options + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "declaration": true, + "stripInternal": true, + "module": "commonjs", + "moduleResolution": "classic", + "noEmitOnError": true, + "rootDir": ".", + "inlineSourceMap": true, + "inlineSources": true, + "target": "es5" + }); +} + +function extractDocs(tree) { + var docs = new Funnel(tree, {include: ['**/*.md', '**/*.png'], exclude: ['**/*.dart.md']}); + return stew.rename(docs, 'README.js.md', 'README.md'); +} + +function extractPkgJsons(tree, BASE_PACKAGE_JSON) { + // Generate shared package.json info + var COMMON_PACKAGE_JSON = { + version: BASE_PACKAGE_JSON.version, + homepage: BASE_PACKAGE_JSON.homepage, + bugs: BASE_PACKAGE_JSON.bugs, + license: BASE_PACKAGE_JSON.license, + repository: BASE_PACKAGE_JSON.repository, + contributors: BASE_PACKAGE_JSON.contributors, + dependencies: BASE_PACKAGE_JSON.dependencies, + devDependencies: BASE_PACKAGE_JSON.devDependencies, + defaultDevDependencies: {} + }; + + var packageJsons = new Funnel(tree, {include: ['**/package.json']}); + return renderLodashTemplate(packageJsons, {context: {'packageJson': COMMON_PACKAGE_JSON}}); +}