Skip to content

Commit

Permalink
build: move packaging in separate folder
Browse files Browse the repository at this point in the history
Moves all related packaging code into a `packaging` folder inside of gulp.

At some point we can move the folder completely to `tools/` and can share it with other repositories like `flex-layout`.
  • Loading branch information
devversion committed May 22, 2017
1 parent c946631 commit 14031f3
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 263 deletions.
2 changes: 1 addition & 1 deletion tools/gulp/gulpfile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {createPackageBuildTasks} from './util/package-tasks';
import {createPackageBuildTasks} from './packaging/build-tasks-gulp';

/** Create gulp tasks to build the different packages in the project. */
createPackageBuildTasks('cdk');
Expand Down
86 changes: 86 additions & 0 deletions tools/gulp/packaging/build-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {join} from 'path';
import {createRollupBundle} from './rollup-helper';
import {inlinePackageMetadataFiles} from './inline-resources';
import {transpileFile} from './ts-compiler';
import {ScriptTarget, ModuleKind} from 'typescript';
import {DIST_BUNDLES, DIST_ROOT, SOURCE_ROOT, PROJECT_ROOT} from '../constants';
import {copyFiles} from '../util/copy-files';
import {
addPureAnnotationsToFile, createMetadataFile, createTypingFile, remapSourcemap, uglifyFile,
updatePackageVersion
} from './build-utils';

// There are no type definitions available for these imports.
const uglify = require('uglify-js');
const sorcery = require('sorcery');

/**
* Copies different output files into a folder structure that follows the `angular/angular`
* 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 material will map to the source path `lib/`.
const sourcePath = join(SOURCE_ROOT, packageName === 'material' ? 'lib' : packageName);
const packagePath = join(DIST_ROOT, 'packages', packageName);
const releasePath = join(DIST_ROOT, 'releases', packageName);

inlinePackageMetadataFiles(packagePath);

copyFiles(packagePath, '**/*.+(d.ts|metadata.json)', join(releasePath, 'typings'));
copyFiles(DIST_BUNDLES, `${packageName}.umd?(.min).js?(.map)`, join(releasePath, 'bundles'));
copyFiles(DIST_BUNDLES, `${packageName}?(.es5).js?(.map)`, join(releasePath, '@angular'));
copyFiles(PROJECT_ROOT, 'LICENSE', releasePath);
copyFiles(SOURCE_ROOT, 'README.md', releasePath);
copyFiles(sourcePath, 'package.json', releasePath);

updatePackageVersion(releasePath);
createTypingFile(releasePath, packageName);
createMetadataFile(releasePath, packageName);
addPureAnnotationsToFile(join(releasePath, '@angular', `${packageName}.es5.js`));
}

/** Builds the bundles for the specified package. */
export async function buildPackageBundles(entryFile: string, packageName: string) {
const moduleName = `ng.material.${packageName}`;

// List of paths to the package bundles.
const fesm2015File = join(DIST_BUNDLES, `${packageName}.js`);
const fesm2014File = join(DIST_BUNDLES, `${packageName}.es5.js`);
const umdFile = join(DIST_BUNDLES, `${packageName}.umd.js`);
const umdMinFile = join(DIST_BUNDLES, `${packageName}.umd.min.js`);

// Build FESM-2015 bundle file.
await createRollupBundle({
moduleName: moduleName,
entry: entryFile,
dest: fesm2015File,
format: 'es',
});

await remapSourcemap(fesm2015File);

// Downlevel FESM-2015 file to ES5.
transpileFile(fesm2015File, fesm2014File, {
target: ScriptTarget.ES5,
module: ModuleKind.ES2015,
allowJs: true
});

await remapSourcemap(fesm2014File);

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

await remapSourcemap(umdFile);

// Create a minified UMD bundle using UglifyJS
uglifyFile(umdFile, umdMinFile);

await remapSourcemap(umdMinFile);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {task, watch} from 'gulp';
import {join} from 'path';
import {main as tsc} from '@angular/tsc-wrapped';
import {SOURCE_ROOT, DIST_ROOT} from '../constants';
import {sequenceTask, sassBuildTask, copyTask, triggerLivereload} from './task_helpers';
import {buildPackageBundles, composeRelease} from './package-build';
import {sequenceTask, sassBuildTask, copyTask, triggerLivereload} from '../util/task_helpers';
import {buildPackageBundles, composeRelease} from './build-functions';

// There are no type definitions available for these imports.
const inlineResources = require('../../../scripts/release/inline-resources');
Expand Down
85 changes: 85 additions & 0 deletions tools/gulp/packaging/build-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {writeFileSync, readFileSync} from 'fs-extra';
import {join} from 'path';
import {LICENSE_BANNER, MATERIAL_VERSION} from '../constants';

// There are no type definitions available for these imports.
const uglify = require('uglify-js');
const sorcery = require('sorcery');

/** Regex that matches downleveled class IIFE expressions. Used to add the pure annotations. */
const classIfeeRegex =
new RegExp('^(var (\\S+) = )(\\(function \\(\\) \\{[\\n\\r]*(?: (?:\\/\\*\\*| \\*|\\*\\/|' +
'\\/\\/)[^\\n\\r]*[\\n\\r]*)* function \\2\\([^\\)]*\\) \\{[\\n\\r]*)', 'mg');

/** Regex that matches downleveled class IIFE expressions with _extends statements */
const classExtendsIfeeRegex =
/^(var (\S+) = )(\(function \(_super\) \{[\n\r]* __extends\(\2, _super\);[\n\r]*)/gm;

/**
* Finds the original sourcemap of the file and maps it to the current file.
* This is useful when multiple transformation happen (e.g TSC -> Rollup -> Uglify)
**/
export async function remapSourcemap(sourceFile: string) {
// Once sorcery loaded the chain of sourcemaps, the new sourcemap will be written asynchronously.
return (await sorcery.load(sourceFile)).write();
}

/** Minifies a JavaScript file using UglifyJS2. Also writes sourcemaps to the output. */
export function uglifyFile(inputPath: string, outputPath: string) {
const sourcemapOut = `${outputPath}.map`;
const result = uglify.minify(inputPath, {
preserveComments: 'license',
outSourceMap: sourcemapOut
});

writeFileSync(outputPath, result.code);
writeFileSync(sourcemapOut, result.map);
}

/** Updates the `package.json` file of the specified package. Replaces the version placeholder. */
export function updatePackageVersion(packageDir: string) {
const packagePath = join(packageDir, 'package.json');
const packageConfig = require(packagePath);

// Replace the `0.0.0-PLACEHOLDER` version name with the version of the root package.json file.
packageConfig.version = packageConfig.version.replace('0.0.0-PLACEHOLDER', MATERIAL_VERSION);

writeFileSync(packagePath, JSON.stringify(packageConfig, null, 2));
}

/** Create a typing file that links to the bundled definitions of NGC. */
export function createTypingFile(outputDir: string, entryName: string) {
writeFileSync(join(outputDir, `${entryName}.d.ts`),
LICENSE_BANNER + '\nexport * from "./typings/index";'
);
}

/** Creates a metadata file that re-exports the metadata bundle inside of the typings. */
export function createMetadataFile(packageDir: string, packageName: string) {
const metadataReExport =
`{"__symbolic":"module","version":3,"metadata":{},"exports":[{"from":"./typings/index"}]}`;
writeFileSync(join(packageDir, `${packageName}.metadata.json`), metadataReExport, 'utf-8');
}

/**
* Adds `@__PURE__` annotation comments to IIFEs containing ES5-downleveled classes generated by
* TypeScript so that Uglify can tree-shake classes that are not referenced.
*
* @param fileContent The content of the file for which `@__PURE__` will be added.
* @returns The content of the file with `@__PURE__` annotations added.
*/
export function addPureAnnotations(fileContent: string) {
return fileContent
// Prefix downleveled classes w/ the @__PURE__ annotation.
.replace(classIfeeRegex, '$1/*@__PURE__*/$3')
// Prefix downleveled classes that extend another class w/ the @__PURE__ annotation
.replace(classExtendsIfeeRegex, '$1/*@__PURE__*/$3');
}

/** Adds Uglify "@__PURE__" decorations to the specified file. */
export function addPureAnnotationsToFile(inputFile: string) {
const originalContent = readFileSync(inputFile, 'utf-8');
const annotatedContent = addPureAnnotations(originalContent);

writeFileSync(inputFile, annotatedContent, 'utf-8');
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {readFileSync} from 'fs';
import {readFileSync, writeFileSync} from 'fs';
import {basename} from 'path';
import {sync as glob} from 'glob';
import {join} from 'path';

/**
* Recurse through a parsed metadata.json file and inline all html and css.
Expand Down Expand Up @@ -32,3 +34,24 @@ export function inlineMetadataResources(metadata: any, componentResources: Map<s
}
}
}


/** Inlines HTML and CSS resources into `metadata.json` files. */
export function inlinePackageMetadataFiles(packagePath: string) {
// Create a map of fileName -> fullFilePath. This is needed because the templateUrl and
// styleUrls for each component use just the filename because, in the source, the component
// and the resources live in the same directory.
const componentResources = new Map<string, string>();

glob(join(packagePath, '**/*.+(html|css)')).forEach(resourcePath => {
componentResources.set(basename(resourcePath), resourcePath);
});

// Find all metadata files. For each one, parse the JSON content, inline the resources, and
// reserialize and rewrite back to the original location.
glob(join(packagePath, '**/*.metadata.json')).forEach(path => {
const metadata = JSON.parse(readFileSync(path, 'utf-8'));
inlineMetadataResources(metadata, componentResources);
writeFileSync(path , JSON.stringify(metadata), 'utf-8');
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ export type BundleConfig = {
moduleName: string;
};

/** Creates a rollup bundles of the Material components.*/
/** Creates a rollup bundle of a specified JavaScript file.*/
export function createRollupBundle(config: BundleConfig): Promise<any> {
let bundleOptions = {
const bundleOptions = {
context: 'this',
external: Object.keys(ROLLUP_GLOBALS),
entry: config.entry
};

let writeOptions = {
const writeOptions = {
// Keep the moduleId empty because we don't want to force developers to a specific moduleId.
moduleId: '',
moduleName: config.moduleName || 'ng.material',
Expand Down
43 changes: 43 additions & 0 deletions tools/gulp/packaging/ts-compiler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as ts from 'typescript';
import * as path from 'path';
import * as fs from 'fs';
import * as chalk from 'chalk';

/** Reads a input file and transpiles it into a new file. */
export function transpileFile(inputPath: string, outputPath: string, options: ts.CompilerOptions) {
const inputFile = fs.readFileSync(inputPath, 'utf-8');
const transpiled = ts.transpileModule(inputFile, { compilerOptions: options });

reportDiagnostics(transpiled.diagnostics);

fs.writeFileSync(outputPath, transpiled.outputText);

if (transpiled.sourceMapText) {
fs.writeFileSync(`${outputPath}.map`, transpiled.sourceMapText);
}
}

/** Formats the TypeScript diagnostics into a error string. */
export function formatDiagnostics(diagnostics: ts.Diagnostic[], baseDir: string): string {
return diagnostics.map(diagnostic => {
let res = `• ${chalk.red(`TS${diagnostic.code}`)} - `;

if (diagnostic.file) {
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
const filePath = path.relative(baseDir, diagnostic.file.fileName);

res += `${filePath}(${line + 1},${character + 1}): `;
}
res += `${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`;

return res;
}).join('\n');
}

/** Checks and reports diagnostics if present. */
export function reportDiagnostics(diagnostics: ts.Diagnostic[], baseDir?: string) {
if (diagnostics && diagnostics.length && diagnostics[0]) {
console.error(formatDiagnostics(diagnostics, baseDir));
throw new Error('TypeScript compilation failed.');
}
}
2 changes: 1 addition & 1 deletion tools/gulp/tasks/material-release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {join} from 'path';
import {writeFileSync} from 'fs';
import {Bundler} from 'scss-bundle';
import {sequenceTask} from '../util/task_helpers';
import {composeRelease} from '../util/package-build';
import {composeRelease} from '../packaging/build-functions';
import {COMPONENTS_DIR, DIST_MATERIAL, DIST_RELEASES} from '../constants';

// There are no type definitions available for these imports.
Expand Down
22 changes: 0 additions & 22 deletions tools/gulp/util/annotate-pure.ts

This file was deleted.

Loading

0 comments on commit 14031f3

Please sign in to comment.