diff --git a/tools/gulp/gulpfile.ts b/tools/gulp/gulpfile.ts index 8b3970a6773..76149774890 100644 --- a/tools/gulp/gulpfile.ts +++ b/tools/gulp/gulpfile.ts @@ -1,9 +1,9 @@ import {createPackageBuildTasks} from 'material2-build-tools'; +import {cdkPackage, examplesPackage, materialPackage} from './packages'; -// Create gulp tasks to build the different packages in the project. -createPackageBuildTasks('cdk', [], {useSecondaryEntryPoints: true}); -createPackageBuildTasks('material', ['cdk']); -createPackageBuildTasks('material-examples', ['material', 'cdk']); +createPackageBuildTasks(cdkPackage); +createPackageBuildTasks(materialPackage); +createPackageBuildTasks(examplesPackage); import './tasks/ci'; import './tasks/clean'; diff --git a/tools/gulp/packages.ts b/tools/gulp/packages.ts new file mode 100644 index 00000000000..5a31abab8f4 --- /dev/null +++ b/tools/gulp/packages.ts @@ -0,0 +1,9 @@ +import {BuildPackage, buildConfig} from 'material2-build-tools'; +import {join} from 'path'; + +export const cdkPackage = new BuildPackage('cdk'); +export const materialPackage = new BuildPackage('material', [cdkPackage]); +export const examplesPackage = new BuildPackage('material-examples', [materialPackage, cdkPackage]); + +// To avoid refactoring of the project the material package will map to the source path `lib/`. +materialPackage.packageRoot = join(buildConfig.packagesDir, 'lib'); diff --git a/tools/gulp/tasks/material-release.ts b/tools/gulp/tasks/material-release.ts index 712de95deef..183cf0bea50 100644 --- a/tools/gulp/tasks/material-release.ts +++ b/tools/gulp/tasks/material-release.ts @@ -3,36 +3,33 @@ import {join} from 'path'; import {writeFileSync, mkdirpSync} from 'fs-extra'; import {Bundler} from 'scss-bundle'; import {composeRelease, buildConfig, sequenceTask} from 'material2-build-tools'; +import {materialPackage} from '../packages'; // There are no type definitions available for these imports. const gulpRename = require('gulp-rename'); -const {packagesDir, outputDir} = buildConfig; +const {outputDir} = buildConfig; +const {packageRoot, packageOut} = materialPackage; /** Path to the directory where all releases are created. */ const releasesDir = join(outputDir, 'releases'); -/** Path to the output of the Material package. */ -const materialOutputPath = join(outputDir, 'packages', 'material'); - -// Path to the sources of the Material package. -const materialPath = join(packagesDir, 'lib'); // Path to the release output of material. const releasePath = join(releasesDir, 'material'); // The entry-point for the scss theming bundle. -const themingEntryPointPath = join(materialPath, 'core', 'theming', '_all-theme.scss'); +const themingEntryPointPath = join(packageRoot, 'core', 'theming', '_all-theme.scss'); // Output path for the scss theming bundle. const themingBundlePath = join(releasePath, '_theming.scss'); // Matches all pre-built theme css files -const prebuiltThemeGlob = join(materialOutputPath, '**/theming/prebuilt/*.css?(.map)'); +const prebuiltThemeGlob = join(packageOut, '**/theming/prebuilt/*.css?(.map)'); // Matches all SCSS files in the library. -const allScssGlob = join(materialPath, '**/*.scss'); +const allScssGlob = join(packageRoot, '**/*.scss'); /** * Overwrite the release task for the material package. The material release will include special * files, like a bundled theming SCSS file or all prebuilt themes. */ -task('material:build-release', ['material:prepare-release'], () => composeRelease('material')); +task('material:build-release', ['material:prepare-release'], () => composeRelease(materialPackage)); /** * Task that will build the material package. It will also copy all prebuilt themes and build diff --git a/tools/package-tools/build-bundles.ts b/tools/package-tools/build-bundles.ts index e08a8986dda..ab84af91386 100644 --- a/tools/package-tools/build-bundles.ts +++ b/tools/package-tools/build-bundles.ts @@ -5,12 +5,10 @@ import {createRollupBundle} from './rollup-helpers'; import {remapSourcemap} from './sourcemap-remap'; import {transpileFile} from './typescript-transpile'; import {buildConfig} from './build-config'; -import {getSecondaryEntryPointsForPackage} from './secondary-entry-points'; /** Directory where all bundles will be created in. */ const bundlesDir = join(buildConfig.outputDir, 'bundles'); - /** Builds bundles for the primary entry-point w/ given entry file, e.g. @angular/cdk */ export async function buildPrimaryEntryPointBundles(entryFile: string, packageName: string) { return createBundlesForEntryPoint({ @@ -23,18 +21,9 @@ export async function buildPrimaryEntryPointBundles(entryFile: string, packageNa }); } -/** Builds bundles for all secondary entry-points for a given package, e.g. 'cdk' */ -export async function buildAllSecondaryEntryPointBundles(packageName: string) { - const rootPackageDir = join(buildConfig.outputDir, 'packages', packageName); - - return Promise.all(getSecondaryEntryPointsForPackage(packageName) - .map(entryPointName => buildSecondaryEntryPointBundles( - join(rootPackageDir, entryPointName, `index.js`), packageName, entryPointName))); -} - /** Builds bundles for a single secondary entry-point w/ given entry file, e.g. @angular/cdk/a11y */ -export async function buildSecondaryEntryPointBundles( - entryFile: string, packageName: string, entryPointName: string) { +export async function buildSecondaryEntryPointBundles(entryFile: string, packageName: string, + entryPointName: string) { return createBundlesForEntryPoint({ entryFile, moduleName: `ng.${packageName}.${entryPointName}`, diff --git a/tools/package-tools/build-package.ts b/tools/package-tools/build-package.ts new file mode 100644 index 00000000000..39041c11b67 --- /dev/null +++ b/tools/package-tools/build-package.ts @@ -0,0 +1,82 @@ +import {join} from 'path'; +import {main as tsc} from '@angular/tsc-wrapped'; +import {buildConfig} from './build-config'; +import {getSecondaryEntryPointsForPackage} from './secondary-entry-points'; +import { + buildPrimaryEntryPointBundles, + buildSecondaryEntryPointBundles +} from 'material2-build-tools'; + +const {packagesDir, outputDir} = buildConfig; + +/** Name of the tsconfig file that is responsible for building a package. */ +const buildTsconfigName = 'tsconfig-build.json'; + +/** Name of the tsconfig file that is responsible for building the tests. */ +const testsTsconfigName = 'tsconfig-tests.json'; + +export class BuildPackage { + + /** Path to the package sources. */ + packageRoot: string; + + /** Path to the package output. */ + packageOut: string; + + /** Secondary entry points for the package. */ + secondaryEntryPoints: string[]; + + /** Path to the entry file of the package in the output directory. */ + private entryFilePath: string; + + /** Path to the tsconfig file, which will be used to build the package. */ + private tsconfigBuild: string; + + /** Path to the tsconfig file, which will be used to build the tests. */ + private tsconfigTests: string; + + constructor(public packageName: string, public dependencies: BuildPackage[] = []) { + this.packageRoot = join(packagesDir, packageName); + this.packageOut = join(outputDir, 'packages', packageName); + + this.tsconfigBuild = join(this.packageRoot, buildTsconfigName); + this.tsconfigTests = join(this.packageRoot, testsTsconfigName); + + this.entryFilePath = join(this.packageOut, 'index.js'); + + this.secondaryEntryPoints = getSecondaryEntryPointsForPackage(packageName); + } + + /** Compiles the package sources with all secondary entry points. */ + async compile() { + await this._compileEntryPoint(buildTsconfigName); + + // Walk through every secondary entry point and build the TypeScript sources sequentially. + for (const entryPoint of this.secondaryEntryPoints) { + await this._compileEntryPoint(buildTsconfigName, entryPoint); + } + } + + /** Compiles the TypeScript test source files for the package. */ + async compileTests() { + await this._compileEntryPoint(testsTsconfigName); + } + + /** Creates all bundles for the package and all associated entry points. */ + async createBundles() { + await buildPrimaryEntryPointBundles(this.entryFilePath, this.packageName); + + for (const entryPoint of this.secondaryEntryPoints) { + const entryPointEntryFilePath = join(this.packageOut, entryPoint, 'index.js'); + await buildSecondaryEntryPointBundles(entryPointEntryFilePath, this.packageName, entryPoint); + } + } + + /** Compiles the TypeScript sources of a primary or secondary entry point. */ + private async _compileEntryPoint(tsconfigName: string, secondaryEntryPoint?: string) { + const entryPointPath = join(this.packageRoot, secondaryEntryPoint || ''); + const entryPointTsconfigPath = join(entryPointPath, tsconfigName); + + await tsc(entryPointTsconfigPath, {basePath: entryPointPath}); + } +} diff --git a/tools/package-tools/build-release.ts b/tools/package-tools/build-release.ts index 93a1dde89cf..cfc8f49e2b0 100644 --- a/tools/package-tools/build-release.ts +++ b/tools/package-tools/build-release.ts @@ -8,6 +8,7 @@ import {createMetadataReexportFile} from './metadata-reexport'; import {getSecondaryEntryPointsForPackage} from './secondary-entry-points'; import {createEntryPointPackageJson} from './entry-point-package-json'; import {buildConfig} from './build-config'; +import {BuildPackage} from './build-package'; const {packagesDir, outputDir, projectDir} = buildConfig; @@ -19,46 +20,46 @@ const bundlesDir = join(outputDir, 'bundles'); * release folder structure. The output will also contain a README and the according package.json * file. Additionally the package will be Closure Compiler and AOT compatible. */ -export function composeRelease(packageName: string, options: ComposeReleaseOptions = {}) { - // To avoid refactoring of the project the package material will map to the source path `lib/`. - const sourcePath = join(packagesDir, packageName === 'material' ? 'lib' : packageName); - const packagePath = join(outputDir, 'packages', packageName); +export function composeRelease(buildPackage: BuildPackage) { + const {packageName, packageOut, packageRoot} = buildPackage; const releasePath = join(outputDir, 'releases', packageName); - inlinePackageMetadataFiles(packagePath); + inlinePackageMetadataFiles(packageOut); - copyFiles(packagePath, '**/*.+(d.ts|metadata.json)', join(releasePath, 'typings')); + copyFiles(packageOut, '**/*.+(d.ts|metadata.json)', join(releasePath, 'typings')); copyFiles(bundlesDir, `${packageName}?(-*).umd.js?(.map)`, join(releasePath, 'bundles')); copyFiles(bundlesDir, `${packageName}?(.es5).js?(.map)`, join(releasePath, '@angular')); copyFiles(join(bundlesDir, packageName), '**', join(releasePath, '@angular', packageName)); copyFiles(projectDir, 'LICENSE', releasePath); copyFiles(packagesDir, 'README.md', releasePath); - copyFiles(sourcePath, 'package.json', releasePath); + copyFiles(packageRoot, 'package.json', releasePath); replaceVersionPlaceholders(releasePath); createTypingsReexportFile(releasePath, './typings/index', packageName); createMetadataReexportFile(releasePath, './typings/index', packageName); - if (options.useSecondaryEntryPoints) { - createFilesForSecondaryEntryPoint(packageName, packagePath, releasePath); + if (buildPackage.secondaryEntryPoints.length) { + createFilesForSecondaryEntryPoint(buildPackage, releasePath); } } /** Creates files necessary for a secondary entry-point. */ -function createFilesForSecondaryEntryPoint(packageName: string, packagePath: string, - releasePath: string) { +function createFilesForSecondaryEntryPoint(buildPackage: BuildPackage, releasePath: string) { + const {packageName, packageOut} = buildPackage; + getSecondaryEntryPointsForPackage(packageName).forEach(entryPointName => { // Create a directory in the root of the package for this entry point that contains // * A package.json that lists the different bundle locations // * An index.d.ts file that re-exports the index.d.ts from the typings/ directory // * A metadata.json re-export for this entry-point's metadata. const entryPointDir = join(releasePath, entryPointName); + mkdirpSync(entryPointDir); createEntryPointPackageJson(entryPointDir, packageName, entryPointName); // Copy typings and metadata from tsc output location into the entry-point. copyFiles( - join(packagePath, entryPointName), + join(packageOut, entryPointName), '**/*.+(d.ts|metadata.json)', join(entryPointDir, 'typings')); @@ -73,7 +74,3 @@ function createFilesForSecondaryEntryPoint(packageName: string, packagePath: str createMetadataReexportFile(releasePath, `./${entryPointName}/index`, entryPointName); }); } - -interface ComposeReleaseOptions { - useSecondaryEntryPoints?: boolean; -} diff --git a/tools/package-tools/gulp/build-tasks-gulp.ts b/tools/package-tools/gulp/build-tasks-gulp.ts index eaa9cf54069..11edecc745f 100644 --- a/tools/package-tools/gulp/build-tasks-gulp.ts +++ b/tools/package-tools/gulp/build-tasks-gulp.ts @@ -1,20 +1,15 @@ import {dest, src, task} from 'gulp'; import {join} from 'path'; -import {main as tsc} from '@angular/tsc-wrapped'; -import {buildConfig} from '../build-config'; import {composeRelease} from '../build-release'; -import {buildAllSecondaryEntryPointBundles, buildPrimaryEntryPointBundles} from '../build-bundles'; import {inlineResourcesForDirectory} from '../inline-resources'; import {buildScssTask} from './build-scss-task'; import {sequenceTask} from './sequence-task'; import {watchFiles} from './watch-files'; -import {getSecondaryEntryPointsForPackage} from '../secondary-entry-points'; +import {BuildPackage} from '../build-package'; // There are no type definitions available for these imports. const htmlmin = require('gulp-htmlmin'); -const {packagesDir, outputDir} = buildConfig; - const htmlMinifierOptions = { collapseWhitespace: true, removeComments: true, @@ -22,130 +17,91 @@ const htmlMinifierOptions = { removeAttributeQuotes: false }; -/** - * Creates a set of gulp tasks that can build the specified package. - * @param packageName Name of the package. Needs to be similar to the directory name in `src/`. - * @param dependencies Required packages that will be built before building the current package. - * @param options Options that can be passed to adjust the gulp package tasks. - */ -export function createPackageBuildTasks(packageName: string, dependencies: string[] = [], - options: PackageTaskOptions = {}) { +/** Creates a set of gulp tasks that can build the specified package. */ +export function createPackageBuildTasks(buildPackage: BuildPackage) { + // Name of the package build tasks for Gulp. + const taskName = buildPackage.packageName; - // To avoid refactoring of the project the package material will map to the source path `lib/`. - const packageRoot = join(packagesDir, packageName === 'material' ? 'lib' : packageName); - const packageOut = join(outputDir, 'packages', packageName); - - const tsconfigBuild = join(packageRoot, 'tsconfig-build.json'); - const tsconfigTests = join(packageRoot, 'tsconfig-tests.json'); - - // Paths to the different output files and directories. - const esmMainFile = join(packageOut, 'index.js'); + // Name of all dependencies of the current package. + const dependencyNames = buildPackage.dependencies.map(p => p.packageName); // Glob that matches all style files that need to be copied to the package output. - const stylesGlob = join(packageRoot, '**/*.+(scss|css)'); + const stylesGlob = join(buildPackage.packageRoot, '**/*.+(scss|css)'); // Glob that matches every HTML file in the current package. - const htmlGlob = join(packageRoot, '**/*.html'); + const htmlGlob = join(buildPackage.packageRoot, '**/*.html'); // List of watch tasks that need run together with the watch task of the current package. - const dependentWatchTasks = dependencies.map(pkgName => `${pkgName}:watch`); + const dependentWatchTasks = buildPackage.dependencies.map(p => `${p.packageName}:watch`); /** * Main tasks for the package building. Tasks execute the different sub-tasks in the correct * order. */ - task(`${packageName}:clean-build`, sequenceTask('clean', `${packageName}:build`)); + task(`${taskName}:clean-build`, sequenceTask('clean', `${taskName}:build`)); - task(`${packageName}:build`, sequenceTask( + task(`${taskName}:build`, sequenceTask( // Build all required packages before building. - ...dependencies.map(pkgName => `${pkgName}:build`), + ...dependencyNames.map(pkgName => `${pkgName}:build`), // Build ESM and assets output. - [`${packageName}:build:esm`, `${packageName}:assets`], + [`${taskName}:build:esm`, `${taskName}:assets`], // Inline assets into ESM output. - `${packageName}:assets:inline`, + `${taskName}:assets:inline`, // Build bundles on top of inlined ESM output. - `${packageName}:build:bundles`, + `${taskName}:build:bundles`, )); - task(`${packageName}:build-tests`, sequenceTask( + task(`${taskName}:build-tests`, sequenceTask( // Build all required tests before building. - ...dependencies.map(pkgName => `${pkgName}:build-tests`), + ...dependencyNames.map(pkgName => `${pkgName}:build-tests`), // Build the ESM output that includes all test files. Also build assets for the package. - [`${packageName}:build:esm:tests`, `${packageName}:assets`], + [`${taskName}:build:esm:tests`, `${taskName}:assets`], // Inline assets into ESM output. - `${packageName}:assets:inline` + `${taskName}:assets:inline` )); /** * Release tasks for the package. Tasks compose the release output for the package. */ - task(`${packageName}:build-release:clean`, sequenceTask('clean', `${packageName}:build-release`)); - task(`${packageName}:build-release`, [`${packageName}:build`], () => { - return composeRelease(packageName, {useSecondaryEntryPoints: options.useSecondaryEntryPoints}); - }); + task(`${taskName}:build-release:clean`, sequenceTask('clean', `${taskName}:build-release`)); + task(`${taskName}:build-release`, [`${taskName}:build`], () => composeRelease(buildPackage)); /** * TypeScript compilation tasks. Tasks are creating ESM, FESM, UMD bundles for releases. */ - task(`${packageName}:build:esm`, () => { - const primaryEntryPointResult = tsc(tsconfigBuild, {basePath: packageRoot}); + task(`${taskName}:build:esm`, () => buildPackage.compile()); + task(`${taskName}:build:esm:tests`, () => buildPackage.compileTests()); - if (options.useSecondaryEntryPoints) { - return Promise.all([ - compileSecondaryEntryPointsEsm(packageName, packageRoot), - primaryEntryPointResult - ]); - } - - return primaryEntryPointResult; - }); - task(`${packageName}:build:esm:tests`, () => tsc(tsconfigTests, {basePath: packageRoot})); - - task(`${packageName}:build:bundles`, () => { - const primaryBundlePromise = buildPrimaryEntryPointBundles(esmMainFile, packageName); - - return options.useSecondaryEntryPoints ? - Promise.all([primaryBundlePromise, buildAllSecondaryEntryPointBundles('cdk')]) : - primaryBundlePromise; - }); + task(`${taskName}:build:bundles`, () => buildPackage.createBundles()); /** * Asset tasks. Building SASS files and inlining CSS, HTML files into the ESM output. */ - task(`${packageName}:assets`, [ - `${packageName}:assets:scss`, - `${packageName}:assets:copy-styles`, - `${packageName}:assets:html` + task(`${taskName}:assets`, [ + `${taskName}:assets:scss`, + `${taskName}:assets:copy-styles`, + `${taskName}:assets:html` ]); - task(`${packageName}:assets:scss`, buildScssTask(packageOut, packageRoot, true)); - task(`${packageName}:assets:copy-styles`, () => { - return src(stylesGlob).pipe(dest(packageOut)); + task(`${taskName}:assets:scss`, buildScssTask( + buildPackage.packageOut, buildPackage.packageRoot, true) + ); + + task(`${taskName}:assets:copy-styles`, () => { + return src(stylesGlob).pipe(dest(buildPackage.packageOut)); }); - task(`${packageName}:assets:html`, () => { - return src(htmlGlob).pipe(htmlmin(htmlMinifierOptions)).pipe(dest(packageOut)); + task(`${taskName}:assets:html`, () => { + return src(htmlGlob).pipe(htmlmin(htmlMinifierOptions)).pipe(dest(buildPackage.packageOut)); }); - task(`${packageName}:assets:inline`, () => inlineResourcesForDirectory(packageOut)); + task(`${taskName}:assets:inline`, () => inlineResourcesForDirectory(buildPackage.packageOut)); /** * Watch tasks, that will rebuild the package whenever TS, SCSS, or HTML files change. */ - task(`${packageName}:watch`, dependentWatchTasks, () => { - watchFiles(join(packageRoot, '**/*.+(ts|scss|html)'), [`${packageName}:build`]); + task(`${taskName}:watch`, dependentWatchTasks, () => { + watchFiles(join(buildPackage.packageRoot, '**/*.+(ts|scss|html)'), [`${taskName}:build`]); }); } - - -interface PackageTaskOptions { - useSecondaryEntryPoints?: boolean; -} - -/** Sequentially runs tsc for all secondary entry-points for a package. */ -async function compileSecondaryEntryPointsEsm(packageName: string, packageRoot: string) { - for (const p of getSecondaryEntryPointsForPackage(packageName)) { - await tsc(join(packageRoot, p, 'tsconfig-build.json'), {basePath: join(packageRoot, p)}); - } -} diff --git a/tools/package-tools/index.ts b/tools/package-tools/index.ts index 0a2e65bd6d6..dec848ca5b8 100644 --- a/tools/package-tools/index.ts +++ b/tools/package-tools/index.ts @@ -2,6 +2,7 @@ export * from './build-config'; export * from './build-bundles'; export * from './build-release'; +export * from './build-package'; export * from './copy-files'; // Expose gulp utilities. diff --git a/tools/package-tools/secondary-entry-points.ts b/tools/package-tools/secondary-entry-points.ts index fca0b036e64..4660b0e76f6 100644 --- a/tools/package-tools/secondary-entry-points.ts +++ b/tools/package-tools/secondary-entry-points.ts @@ -31,5 +31,5 @@ export function getSecondaryEntryPointsForPackage(packageName: string) { return CDK_ENTRY_POINTS; } - throw Error('Only the cdk supports secondary entry-points right now.'); + return []; }