Skip to content

Commit

Permalink
chore(build): embed build version info in deployed files
Browse files Browse the repository at this point in the history
Build deployed JS files with version-SHA information
* update package tools and gulp tools [to match latest processes in Angular Material].
* improve tslint
  • Loading branch information
ThomasBurleson committed Aug 18, 2017
1 parent 81b41e6 commit d6b45a6
Show file tree
Hide file tree
Showing 26 changed files with 619 additions and 229 deletions.
3 changes: 2 additions & 1 deletion tools/gulp/gulpfile.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {createPackageBuildTasks} from 'lib-build-tools';
import {flexLayoutPackage} from './packages';

/** Create gulp tasks to build the different packages in the project. */
createPackageBuildTasks('flex-layout');
createPackageBuildTasks(flexLayoutPackage);

import './tasks/aot';
import './tasks/build-release'; // release build `github.com/angular/flex-layout-builds`
Expand Down
7 changes: 7 additions & 0 deletions tools/gulp/packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {BuildPackage, buildConfig} from 'lib-build-tools';
import {join} from 'path';

export const flexLayoutPackage = new BuildPackage('flex-layout', []);

// To avoid refactoring of the project the Flex-Layout package will map to the source path `lib/`.
flexLayoutPackage.packageRoot = join(buildConfig.packagesDir, 'lib');
4 changes: 2 additions & 2 deletions tools/gulp/tasks/build-release.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {task} from 'gulp';
import {composeRelease, sequenceTask} from 'lib-build-tools';

import {flexLayoutPackage} from '../packages';
/**
* Overwrite the release task for the Flex-Layout package. The Flex-Layout release will include special
* files, like a bundled theming SCSS file or all prebuilt themes.
*/
task('flex-layout:build-release', ['flex-layout:prepare-release'], () => composeRelease('flex-layout'));
task('flex-layout:build-release', ['flex-layout:prepare-release'], () => composeRelease(flexLayoutPackage));

/**
* Task that will build the Flex-Layout package. It will also copy all prebuilt themes and build
Expand Down
14 changes: 7 additions & 7 deletions tools/gulp/tasks/development.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {task, watch} from 'gulp';
import {task} from 'gulp';
import {tsBuildTask, copyTask, buildAppTask, serverTask} from '../util/task_helpers';
import {join} from 'path';
import {
buildConfig, copyFiles, buildScssTask, triggerLivereload, sequenceTask
buildConfig, copyFiles, buildScssTask, triggerLivereload, sequenceTask, watchFiles
} from 'lib-build-tools';

// These imports don't have any typings provided.
Expand All @@ -27,12 +27,12 @@ const appVendors = [
const vendorGlob = `+(${appVendors.join('|')})/**/*.+(html|css|js|map)`;

task(':watch:devapp', () => {
watch(join(appDir, '**/*.ts'), [':build:devapp:ts', triggerLivereload]);
watch(join(appDir, '**/*.scss'), [':build:devapp:scss', triggerLivereload]);
watch(join(appDir, '**/*.html'), [':build:devapp:assets', triggerLivereload]);
watch(join(appDir, '**/*.css'), [':build:devapp:assets', triggerLivereload]);
watchFiles(join(appDir, '**/*.ts'), [':build:devapp:ts']);
watchFiles(join(appDir, '**/*.scss'), [':build:devapp:scss']);
watchFiles(join(appDir, '**/*.html'), [':build:devapp:assets']);
watchFiles(join(appDir, '**/*.css'), [':build:devapp:assets']);

watch(join(libOutPath, '**/*.css'), [':build:devapp:scss', triggerLivereload]);
watchFiles(join(libOutPath, '**/*.css'), [':build:devapp:scss']);
});

/** Path to the demo-app tsconfig file. */
Expand Down
28 changes: 24 additions & 4 deletions tools/gulp/tasks/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import {task} from 'gulp';
import {execNodeTask} from '../util/task_helpers';
import {join} from 'path';
import {buildConfig} from 'lib-build-tools';
import {red} from 'chalk';

// These types lack of type definitions
const madge = require('madge');

/** Glob that matches all SCSS or CSS files that should be linted. */
const stylesGlob = '+(tools|src)/**/!(*.bundle).+(css|scss)';
Expand All @@ -17,10 +21,7 @@ const cdkOutPath = join(buildConfig.outputDir, 'packages', 'cdk');

task('lint', ['tslint', 'stylelint', 'madge']);

/** Task that runs madge to detect circular dependencies. */
task('madge', ['flex-layout:clean-build'], execNodeTask(
'madge', ['--circular', libOutPath])
);


/** Task to lint Angular Flex-Layout's scss stylesheets. */
task('stylelint', execNodeTask(
Expand All @@ -32,3 +33,22 @@ task('tslint', execNodeTask('tslint', tsLintBaseFlags));

/** Task that automatically fixes TSLint warnings. */
task('tslint:fix', execNodeTask('tslint', [...tsLintBaseFlags, '--fix']));

/** Task that runs madge to detect circular dependencies. */
task('madge', ['flex-layout:clean-build'], () => {
madge([libOutPath]).then((res: any) => {
const circularModules = res.circular();

if (circularModules.length) {
console.error();
console.error(red(`Madge found modules with circular dependencies.`));
console.error(formatMadgeCircularModules(circularModules));
console.error();
}
});
});

/** Returns a string that formats the graph of circular modules. */
function formatMadgeCircularModules(circularModules: string[][]): string {
return circularModules.map((modulePaths: string[]) => `\n - ${modulePaths.join(' > ')}`).join('');
}
2 changes: 1 addition & 1 deletion tools/gulp/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"compilerOptions": {
"experimentalDecorators": true,
"noUnusedParameters": true,
"lib": ["es2015", "dom"],
"lib": ["es2015", "dom", "es2016.array.include"],
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../../dist/tools/gulp",
Expand Down
72 changes: 52 additions & 20 deletions tools/package-tools/build-bundles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,81 @@ import {buildConfig} from './build-config';
/** Directory where all bundles will be created in. */
const bundlesDir = join(buildConfig.outputDir, 'bundles');

/** Builds the bundles for the specified package. */
export async function buildPackageBundles(entryFile: string, packageName: string) {
const moduleName = `ng.${packageName}`;
/** 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({
entryFile,
moduleName: `ng.${packageName}`,
fesm2015Dest: join(bundlesDir, `${packageName}.js`),
fesm2014Dest: join(bundlesDir, `${packageName}.es5.js`),
umdDest: join(bundlesDir, `${packageName}.umd.js`),
umdMinDest: join(bundlesDir, `${packageName}.umd.min.js`),
});
}

// List of paths to the package bundles.
const fesm2015File = join(bundlesDir, `${packageName}.js`);
const fesm2014File = join(bundlesDir, `${packageName}.es5.js`);
const umdFile = join(bundlesDir, `${packageName}.umd.js`);
const umdMinFile = join(bundlesDir, `${packageName}.umd.min.js`);
/** 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) {
return createBundlesForEntryPoint({
entryFile,
moduleName: `ng.${packageName}.${entryPointName}`,
fesm2015Dest: join(bundlesDir, `${packageName}`, `${entryPointName}.js`),
fesm2014Dest: join(bundlesDir, `${packageName}`, `${entryPointName}.es5.js`),
umdDest: join(bundlesDir, `${packageName}-${entryPointName}.umd.js`),
umdMinDest: join(bundlesDir, `${packageName}-${entryPointName}.umd.min.js`),
});
}

/**
* Creates the ES5, ES2015, and UMD bundles for the specified entry-point.
* @param config Configuration that specifies the entry-point, module name, and output
* bundle paths.
*/
async function createBundlesForEntryPoint(config: BundlesConfig) {
// Build FESM-2015 bundle file.
await createRollupBundle({
moduleName: moduleName,
entry: entryFile,
dest: fesm2015File,
moduleName: config.moduleName,
entry: config.entryFile,
dest: config.fesm2015Dest,
format: 'es',
});

await remapSourcemap(fesm2015File);
await remapSourcemap(config.fesm2015Dest);

// Downlevel FESM-2015 file to ES5.
transpileFile(fesm2015File, fesm2014File, {
transpileFile(config.fesm2015Dest, config.fesm2014Dest, {
importHelpers: true,
target: ScriptTarget.ES5,
module: ModuleKind.ES2015,
allowJs: true,
newLine: NewLineKind.LineFeed
});

await remapSourcemap(fesm2014File);
await remapSourcemap(config.fesm2014Dest);

// Create UMD bundle of FESM-2014 output.
await createRollupBundle({
moduleName: moduleName,
entry: fesm2014File,
dest: umdFile,
moduleName: config.moduleName,
entry: config.fesm2014Dest,
dest: config.umdDest,
format: 'umd'
});

await remapSourcemap(umdFile);
await remapSourcemap(config.umdDest);

// Create a minified UMD bundle using UglifyJS
uglifyJsFile(umdFile, umdMinFile);
uglifyJsFile(config.umdDest, config.umdMinDest);

await remapSourcemap(config.umdMinDest);
}


await remapSourcemap(umdMinFile);
/** Configuration for creating library bundles. */
interface BundlesConfig {
entryFile: string;
moduleName: string;
fesm2015Dest: string;
fesm2014Dest: string;
umdDest: string;
umdMinDest: string;
}
86 changes: 86 additions & 0 deletions tools/package-tools/build-package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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 './build-bundles';


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;

/** 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;

/** Secondary entry points for the package. */
get secondaryEntryPoints(): string[] {
if (!this._secondaryEntryPoints) {
this._secondaryEntryPoints = getSecondaryEntryPointsForPackage(this);
}

return this._secondaryEntryPoints;
}

private _secondaryEntryPoints: 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');
}

/** 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});
}
}
65 changes: 51 additions & 14 deletions tools/package-tools/build-release.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import {join} from 'path';
import {mkdirpSync} from 'fs-extra';
import {copyFiles} from './copy-files';
import {addPureAnnotationsToFile} from './pure-annotations';
import {updatePackageVersion} from './package-versions';
import {replaceVersionPlaceholders} from './version-placeholders';
import {inlinePackageMetadataFiles} from './metadata-inlining';
import {createTypingsReexportFile} from './typings-reexport';
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;

Expand All @@ -17,23 +20,57 @@ 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) {
// To avoid refactoring of the project the package 'flex-layout' will map to the source path `lib/`.
const sourcePath = join(packagesDir, packageName === 'flex-layout' ? '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(bundlesDir, `${packageName}.umd?(.min).js?(.map)`, join(releasePath, 'bundles'));
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);

updatePackageVersion(releasePath);
createTypingsReexportFile(releasePath, packageName);
createMetadataReexportFile(releasePath, packageName);
addPureAnnotationsToFile(join(releasePath, '@angular', `${packageName}.es5.js`));
replaceVersionPlaceholders(releasePath);
createTypingsReexportFile(releasePath, './typings/index', packageName);
createMetadataReexportFile(releasePath, './typings/index', packageName);

if (buildPackage.secondaryEntryPoints.length) {
createFilesForSecondaryEntryPoint(buildPackage, releasePath);
}
}

/** Creates files necessary for a secondary entry-point. */
function createFilesForSecondaryEntryPoint(buildPackage: BuildPackage, releasePath: string) {
const {packageName, packageOut} = buildPackage;

getSecondaryEntryPointsForPackage(buildPackage).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(packageOut, entryPointName),
'**/*.+(d.ts|metadata.json)',
join(entryPointDir, 'typings'));

// Create a typings and a metadata re-export within the entry-point to point to the
// typings we just copied.
createTypingsReexportFile(entryPointDir, `./typings/index`, 'index');
createMetadataReexportFile(entryPointDir, `./typings/index`, 'index');

// Finally, create both a d.ts and metadata file for this entry-point in the root of
// the package that re-exports from the entry-point's directory.
createTypingsReexportFile(releasePath, `./${entryPointName}/index`, entryPointName);
createMetadataReexportFile(releasePath, `./${entryPointName}/index`, entryPointName);
});
}
Loading

0 comments on commit d6b45a6

Please sign in to comment.