build: make packaging more flexible (#6143)

* Makes the packaging more flexible by no longer including checks that are specific to the Angular Material project (e.g `material` => `lib` mapping)
* Updates the code to be more readable by making the packaging more object orientated (TS compilation helpers shouldn't be part of the gulp task generation)
This commit is contained in:
Paul Gschwendtner
2017-08-01 23:21:39 +02:00
committed by tinayuangao
parent 235f664eed
commit 056d3b2e81
9 changed files with 159 additions and 128 deletions
+4 -4
View File
@@ -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';
+9
View File
@@ -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');
+7 -10
View File
@@ -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
+2 -13
View File
@@ -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}`,
+82
View File
@@ -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});
}
}
+13 -16
View File
@@ -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;
}
+40 -84
View File
@@ -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)});
}
}
+1
View File
@@ -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.
@@ -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 [];
}