Skip to content

Commit 14031f3

Browse files
committed
build: move packaging in separate folder
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`.
1 parent c946631 commit 14031f3

File tree

11 files changed

+245
-263
lines changed

11 files changed

+245
-263
lines changed

tools/gulp/gulpfile.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {createPackageBuildTasks} from './util/package-tasks';
1+
import {createPackageBuildTasks} from './packaging/build-tasks-gulp';
22

33
/** Create gulp tasks to build the different packages in the project. */
44
createPackageBuildTasks('cdk');
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {join} from 'path';
2+
import {createRollupBundle} from './rollup-helper';
3+
import {inlinePackageMetadataFiles} from './inline-resources';
4+
import {transpileFile} from './ts-compiler';
5+
import {ScriptTarget, ModuleKind} from 'typescript';
6+
import {DIST_BUNDLES, DIST_ROOT, SOURCE_ROOT, PROJECT_ROOT} from '../constants';
7+
import {copyFiles} from '../util/copy-files';
8+
import {
9+
addPureAnnotationsToFile, createMetadataFile, createTypingFile, remapSourcemap, uglifyFile,
10+
updatePackageVersion
11+
} from './build-utils';
12+
13+
// There are no type definitions available for these imports.
14+
const uglify = require('uglify-js');
15+
const sorcery = require('sorcery');
16+
17+
/**
18+
* Copies different output files into a folder structure that follows the `angular/angular`
19+
* release folder structure. The output will also contain a README and the according package.json
20+
* file. Additionally the package will be Closure Compiler and AOT compatible.
21+
*/
22+
export function composeRelease(packageName: string) {
23+
// To avoid refactoring of the project the package material will map to the source path `lib/`.
24+
const sourcePath = join(SOURCE_ROOT, packageName === 'material' ? 'lib' : packageName);
25+
const packagePath = join(DIST_ROOT, 'packages', packageName);
26+
const releasePath = join(DIST_ROOT, 'releases', packageName);
27+
28+
inlinePackageMetadataFiles(packagePath);
29+
30+
copyFiles(packagePath, '**/*.+(d.ts|metadata.json)', join(releasePath, 'typings'));
31+
copyFiles(DIST_BUNDLES, `${packageName}.umd?(.min).js?(.map)`, join(releasePath, 'bundles'));
32+
copyFiles(DIST_BUNDLES, `${packageName}?(.es5).js?(.map)`, join(releasePath, '@angular'));
33+
copyFiles(PROJECT_ROOT, 'LICENSE', releasePath);
34+
copyFiles(SOURCE_ROOT, 'README.md', releasePath);
35+
copyFiles(sourcePath, 'package.json', releasePath);
36+
37+
updatePackageVersion(releasePath);
38+
createTypingFile(releasePath, packageName);
39+
createMetadataFile(releasePath, packageName);
40+
addPureAnnotationsToFile(join(releasePath, '@angular', `${packageName}.es5.js`));
41+
}
42+
43+
/** Builds the bundles for the specified package. */
44+
export async function buildPackageBundles(entryFile: string, packageName: string) {
45+
const moduleName = `ng.material.${packageName}`;
46+
47+
// List of paths to the package bundles.
48+
const fesm2015File = join(DIST_BUNDLES, `${packageName}.js`);
49+
const fesm2014File = join(DIST_BUNDLES, `${packageName}.es5.js`);
50+
const umdFile = join(DIST_BUNDLES, `${packageName}.umd.js`);
51+
const umdMinFile = join(DIST_BUNDLES, `${packageName}.umd.min.js`);
52+
53+
// Build FESM-2015 bundle file.
54+
await createRollupBundle({
55+
moduleName: moduleName,
56+
entry: entryFile,
57+
dest: fesm2015File,
58+
format: 'es',
59+
});
60+
61+
await remapSourcemap(fesm2015File);
62+
63+
// Downlevel FESM-2015 file to ES5.
64+
transpileFile(fesm2015File, fesm2014File, {
65+
target: ScriptTarget.ES5,
66+
module: ModuleKind.ES2015,
67+
allowJs: true
68+
});
69+
70+
await remapSourcemap(fesm2014File);
71+
72+
// Create UMD bundle of FESM-2014 output.
73+
await createRollupBundle({
74+
moduleName: moduleName,
75+
entry: fesm2014File,
76+
dest: umdFile,
77+
format: 'umd'
78+
});
79+
80+
await remapSourcemap(umdFile);
81+
82+
// Create a minified UMD bundle using UglifyJS
83+
uglifyFile(umdFile, umdMinFile);
84+
85+
await remapSourcemap(umdMinFile);
86+
}

tools/gulp/util/package-tasks.ts renamed to tools/gulp/packaging/build-tasks-gulp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import {task, watch} from 'gulp';
22
import {join} from 'path';
33
import {main as tsc} from '@angular/tsc-wrapped';
44
import {SOURCE_ROOT, DIST_ROOT} from '../constants';
5-
import {sequenceTask, sassBuildTask, copyTask, triggerLivereload} from './task_helpers';
6-
import {buildPackageBundles, composeRelease} from './package-build';
5+
import {sequenceTask, sassBuildTask, copyTask, triggerLivereload} from '../util/task_helpers';
6+
import {buildPackageBundles, composeRelease} from './build-functions';
77

88
// There are no type definitions available for these imports.
99
const inlineResources = require('../../../scripts/release/inline-resources');

tools/gulp/packaging/build-utils.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import {writeFileSync, readFileSync} from 'fs-extra';
2+
import {join} from 'path';
3+
import {LICENSE_BANNER, MATERIAL_VERSION} from '../constants';
4+
5+
// There are no type definitions available for these imports.
6+
const uglify = require('uglify-js');
7+
const sorcery = require('sorcery');
8+
9+
/** Regex that matches downleveled class IIFE expressions. Used to add the pure annotations. */
10+
const classIfeeRegex =
11+
new RegExp('^(var (\\S+) = )(\\(function \\(\\) \\{[\\n\\r]*(?: (?:\\/\\*\\*| \\*|\\*\\/|' +
12+
'\\/\\/)[^\\n\\r]*[\\n\\r]*)* function \\2\\([^\\)]*\\) \\{[\\n\\r]*)', 'mg');
13+
14+
/** Regex that matches downleveled class IIFE expressions with _extends statements */
15+
const classExtendsIfeeRegex =
16+
/^(var (\S+) = )(\(function \(_super\) \{[\n\r]* __extends\(\2, _super\);[\n\r]*)/gm;
17+
18+
/**
19+
* Finds the original sourcemap of the file and maps it to the current file.
20+
* This is useful when multiple transformation happen (e.g TSC -> Rollup -> Uglify)
21+
**/
22+
export async function remapSourcemap(sourceFile: string) {
23+
// Once sorcery loaded the chain of sourcemaps, the new sourcemap will be written asynchronously.
24+
return (await sorcery.load(sourceFile)).write();
25+
}
26+
27+
/** Minifies a JavaScript file using UglifyJS2. Also writes sourcemaps to the output. */
28+
export function uglifyFile(inputPath: string, outputPath: string) {
29+
const sourcemapOut = `${outputPath}.map`;
30+
const result = uglify.minify(inputPath, {
31+
preserveComments: 'license',
32+
outSourceMap: sourcemapOut
33+
});
34+
35+
writeFileSync(outputPath, result.code);
36+
writeFileSync(sourcemapOut, result.map);
37+
}
38+
39+
/** Updates the `package.json` file of the specified package. Replaces the version placeholder. */
40+
export function updatePackageVersion(packageDir: string) {
41+
const packagePath = join(packageDir, 'package.json');
42+
const packageConfig = require(packagePath);
43+
44+
// Replace the `0.0.0-PLACEHOLDER` version name with the version of the root package.json file.
45+
packageConfig.version = packageConfig.version.replace('0.0.0-PLACEHOLDER', MATERIAL_VERSION);
46+
47+
writeFileSync(packagePath, JSON.stringify(packageConfig, null, 2));
48+
}
49+
50+
/** Create a typing file that links to the bundled definitions of NGC. */
51+
export function createTypingFile(outputDir: string, entryName: string) {
52+
writeFileSync(join(outputDir, `${entryName}.d.ts`),
53+
LICENSE_BANNER + '\nexport * from "./typings/index";'
54+
);
55+
}
56+
57+
/** Creates a metadata file that re-exports the metadata bundle inside of the typings. */
58+
export function createMetadataFile(packageDir: string, packageName: string) {
59+
const metadataReExport =
60+
`{"__symbolic":"module","version":3,"metadata":{},"exports":[{"from":"./typings/index"}]}`;
61+
writeFileSync(join(packageDir, `${packageName}.metadata.json`), metadataReExport, 'utf-8');
62+
}
63+
64+
/**
65+
* Adds `@__PURE__` annotation comments to IIFEs containing ES5-downleveled classes generated by
66+
* TypeScript so that Uglify can tree-shake classes that are not referenced.
67+
*
68+
* @param fileContent The content of the file for which `@__PURE__` will be added.
69+
* @returns The content of the file with `@__PURE__` annotations added.
70+
*/
71+
export function addPureAnnotations(fileContent: string) {
72+
return fileContent
73+
// Prefix downleveled classes w/ the @__PURE__ annotation.
74+
.replace(classIfeeRegex, '$1/*@__PURE__*/$3')
75+
// Prefix downleveled classes that extend another class w/ the @__PURE__ annotation
76+
.replace(classExtendsIfeeRegex, '$1/*@__PURE__*/$3');
77+
}
78+
79+
/** Adds Uglify "@__PURE__" decorations to the specified file. */
80+
export function addPureAnnotationsToFile(inputFile: string) {
81+
const originalContent = readFileSync(inputFile, 'utf-8');
82+
const annotatedContent = addPureAnnotations(originalContent);
83+
84+
writeFileSync(inputFile, annotatedContent, 'utf-8');
85+
}

tools/gulp/util/inline-resources.ts renamed to tools/gulp/packaging/inline-resources.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import {readFileSync} from 'fs';
1+
import {readFileSync, writeFileSync} from 'fs';
22
import {basename} from 'path';
3+
import {sync as glob} from 'glob';
4+
import {join} from 'path';
35

46
/**
57
* Recurse through a parsed metadata.json file and inline all html and css.
@@ -32,3 +34,24 @@ export function inlineMetadataResources(metadata: any, componentResources: Map<s
3234
}
3335
}
3436
}
37+
38+
39+
/** Inlines HTML and CSS resources into `metadata.json` files. */
40+
export function inlinePackageMetadataFiles(packagePath: string) {
41+
// Create a map of fileName -> fullFilePath. This is needed because the templateUrl and
42+
// styleUrls for each component use just the filename because, in the source, the component
43+
// and the resources live in the same directory.
44+
const componentResources = new Map<string, string>();
45+
46+
glob(join(packagePath, '**/*.+(html|css)')).forEach(resourcePath => {
47+
componentResources.set(basename(resourcePath), resourcePath);
48+
});
49+
50+
// Find all metadata files. For each one, parse the JSON content, inline the resources, and
51+
// reserialize and rewrite back to the original location.
52+
glob(join(packagePath, '**/*.metadata.json')).forEach(path => {
53+
const metadata = JSON.parse(readFileSync(path, 'utf-8'));
54+
inlineMetadataResources(metadata, componentResources);
55+
writeFileSync(path , JSON.stringify(metadata), 'utf-8');
56+
});
57+
}

tools/gulp/util/rollup-helper.ts renamed to tools/gulp/packaging/rollup-helper.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ export type BundleConfig = {
4848
moduleName: string;
4949
};
5050

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

59-
let writeOptions = {
59+
const writeOptions = {
6060
// Keep the moduleId empty because we don't want to force developers to a specific moduleId.
6161
moduleId: '',
6262
moduleName: config.moduleName || 'ng.material',

tools/gulp/packaging/ts-compiler.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as ts from 'typescript';
2+
import * as path from 'path';
3+
import * as fs from 'fs';
4+
import * as chalk from 'chalk';
5+
6+
/** Reads a input file and transpiles it into a new file. */
7+
export function transpileFile(inputPath: string, outputPath: string, options: ts.CompilerOptions) {
8+
const inputFile = fs.readFileSync(inputPath, 'utf-8');
9+
const transpiled = ts.transpileModule(inputFile, { compilerOptions: options });
10+
11+
reportDiagnostics(transpiled.diagnostics);
12+
13+
fs.writeFileSync(outputPath, transpiled.outputText);
14+
15+
if (transpiled.sourceMapText) {
16+
fs.writeFileSync(`${outputPath}.map`, transpiled.sourceMapText);
17+
}
18+
}
19+
20+
/** Formats the TypeScript diagnostics into a error string. */
21+
export function formatDiagnostics(diagnostics: ts.Diagnostic[], baseDir: string): string {
22+
return diagnostics.map(diagnostic => {
23+
let res = `• ${chalk.red(`TS${diagnostic.code}`)} - `;
24+
25+
if (diagnostic.file) {
26+
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
27+
const filePath = path.relative(baseDir, diagnostic.file.fileName);
28+
29+
res += `${filePath}(${line + 1},${character + 1}): `;
30+
}
31+
res += `${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`;
32+
33+
return res;
34+
}).join('\n');
35+
}
36+
37+
/** Checks and reports diagnostics if present. */
38+
export function reportDiagnostics(diagnostics: ts.Diagnostic[], baseDir?: string) {
39+
if (diagnostics && diagnostics.length && diagnostics[0]) {
40+
console.error(formatDiagnostics(diagnostics, baseDir));
41+
throw new Error('TypeScript compilation failed.');
42+
}
43+
}

tools/gulp/tasks/material-release.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {join} from 'path';
33
import {writeFileSync} from 'fs';
44
import {Bundler} from 'scss-bundle';
55
import {sequenceTask} from '../util/task_helpers';
6-
import {composeRelease} from '../util/package-build';
6+
import {composeRelease} from '../packaging/build-functions';
77
import {COMPONENTS_DIR, DIST_MATERIAL, DIST_RELEASES} from '../constants';
88

99
// There are no type definitions available for these imports.

tools/gulp/util/annotate-pure.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)