Skip to content

Commit 3569805

Browse files
devversiontinayuangao
authored andcommitted
build: move packaging in separate folder (#4719)
* 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`. * Split up build utils
1 parent 8f2a11e commit 3569805

17 files changed

+260
-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');

tools/gulp/packaging/build-bundles.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {join} from 'path';
2+
import {ScriptTarget, ModuleKind} from 'typescript';
3+
import {DIST_BUNDLES} from '../constants';
4+
import {uglifyJsFile} from './minify-sources';
5+
import {createRollupBundle} from './rollup-helpers';
6+
import {remapSourcemap} from './sourcemap-remap';
7+
import {transpileFile} from './typescript-transpile';
8+
9+
// There are no type definitions available for these imports.
10+
const uglify = require('uglify-js');
11+
const sorcery = require('sorcery');
12+
13+
/** Builds the bundles for the specified package. */
14+
export async function buildPackageBundles(entryFile: string, packageName: string) {
15+
const moduleName = `ng.${packageName}`;
16+
17+
// List of paths to the package bundles.
18+
const fesm2015File = join(DIST_BUNDLES, `${packageName}.js`);
19+
const fesm2014File = join(DIST_BUNDLES, `${packageName}.es5.js`);
20+
const umdFile = join(DIST_BUNDLES, `${packageName}.umd.js`);
21+
const umdMinFile = join(DIST_BUNDLES, `${packageName}.umd.min.js`);
22+
23+
// Build FESM-2015 bundle file.
24+
await createRollupBundle({
25+
moduleName: moduleName,
26+
entry: entryFile,
27+
dest: fesm2015File,
28+
format: 'es',
29+
});
30+
31+
await remapSourcemap(fesm2015File);
32+
33+
// Downlevel FESM-2015 file to ES5.
34+
transpileFile(fesm2015File, fesm2014File, {
35+
target: ScriptTarget.ES5,
36+
module: ModuleKind.ES2015,
37+
allowJs: true
38+
});
39+
40+
await remapSourcemap(fesm2014File);
41+
42+
// Create UMD bundle of FESM-2014 output.
43+
await createRollupBundle({
44+
moduleName: moduleName,
45+
entry: fesm2014File,
46+
dest: umdFile,
47+
format: 'umd'
48+
});
49+
50+
await remapSourcemap(umdFile);
51+
52+
// Create a minified UMD bundle using UglifyJS
53+
uglifyJsFile(umdFile, umdMinFile);
54+
55+
await remapSourcemap(umdMinFile);
56+
}

tools/gulp/packaging/build-release.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {join} from 'path';
2+
import {DIST_BUNDLES, DIST_ROOT, SOURCE_ROOT, PROJECT_ROOT} from '../constants';
3+
import {copyFiles} from '../util/copy-files';
4+
import {addPureAnnotationsToFile} from './pure-annotations';
5+
import {updatePackageVersion} from './package-versions';
6+
import {inlinePackageMetadataFiles} from './metadata-inlining';
7+
import {createTypingsReexportFile} from './typings-reexport';
8+
import {createMetadataReexportFile} from './metadata-reexport';
9+
10+
/**
11+
* Copies different output files into a folder structure that follows the `angular/angular`
12+
* release folder structure. The output will also contain a README and the according package.json
13+
* file. Additionally the package will be Closure Compiler and AOT compatible.
14+
*/
15+
export function composeRelease(packageName: string) {
16+
// To avoid refactoring of the project the package material will map to the source path `lib/`.
17+
const sourcePath = join(SOURCE_ROOT, packageName === 'material' ? 'lib' : packageName);
18+
const packagePath = join(DIST_ROOT, 'packages', packageName);
19+
const releasePath = join(DIST_ROOT, 'releases', packageName);
20+
21+
inlinePackageMetadataFiles(packagePath);
22+
23+
copyFiles(packagePath, '**/*.+(d.ts|metadata.json)', join(releasePath, 'typings'));
24+
copyFiles(DIST_BUNDLES, `${packageName}.umd?(.min).js?(.map)`, join(releasePath, 'bundles'));
25+
copyFiles(DIST_BUNDLES, `${packageName}?(.es5).js?(.map)`, join(releasePath, '@angular'));
26+
copyFiles(PROJECT_ROOT, 'LICENSE', releasePath);
27+
copyFiles(SOURCE_ROOT, 'README.md', releasePath);
28+
copyFiles(sourcePath, 'package.json', releasePath);
29+
30+
updatePackageVersion(releasePath);
31+
createTypingsReexportFile(releasePath, packageName);
32+
createMetadataReexportFile(releasePath, packageName);
33+
addPureAnnotationsToFile(join(releasePath, '@angular', `${packageName}.es5.js`));
34+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ 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 {composeRelease} from './build-release';
7+
import {buildPackageBundles} from './build-bundles';
78

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

tools/gulp/util/inline-resources.ts renamed to tools/gulp/packaging/metadata-inlining.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+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {writeFileSync} from 'fs';
2+
import {join} from 'path';
3+
4+
/** Creates a metadata file that re-exports the metadata bundle inside of the typings. */
5+
export function createMetadataReexportFile(packageDir: string, packageName: string) {
6+
const metadataReExport =
7+
`{"__symbolic":"module","version":3,"metadata":{},"exports":[{"from":"./typings/index"}]}`;
8+
writeFileSync(join(packageDir, `${packageName}.metadata.json`), metadataReExport, 'utf-8');
9+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {writeFileSync} from 'fs';
2+
3+
// There are no type definitions available for these imports.
4+
const uglify = require('uglify-js');
5+
6+
/** Minifies a JavaScript file by using UglifyJS2. Also writes sourcemaps to the output. */
7+
export function uglifyJsFile(inputPath: string, outputPath: string) {
8+
const sourcemapOut = `${outputPath}.map`;
9+
const result = uglify.minify(inputPath, {
10+
preserveComments: 'license',
11+
outSourceMap: sourcemapOut
12+
});
13+
14+
writeFileSync(outputPath, result.code);
15+
writeFileSync(sourcemapOut, result.map);
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {writeFileSync} from 'fs';
2+
import {join} from 'path';
3+
import {MATERIAL_VERSION} from '../constants';
4+
5+
/** Updates the `package.json` file of the specified package. Replaces the version placeholder. */
6+
export function updatePackageVersion(packageDir: string) {
7+
const packagePath = join(packageDir, 'package.json');
8+
const packageConfig = require(packagePath);
9+
10+
// Replace the `0.0.0-PLACEHOLDER` version name with the version of the root package.json file.
11+
packageConfig.version = packageConfig.version.replace('0.0.0-PLACEHOLDER', MATERIAL_VERSION);
12+
13+
writeFileSync(packagePath, JSON.stringify(packageConfig, null, 2));
14+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {readFileSync, writeFileSync} from 'fs';
2+
3+
/** Regex that matches downleveled class IIFE expressions. Used to add the pure annotations. */
4+
const classIfeeRegex =
5+
new RegExp('^(var (\\S+) = )(\\(function \\(\\) \\{[\\n\\r]*(?: (?:\\/\\*\\*| \\*|\\*\\/|' +
6+
'\\/\\/)[^\\n\\r]*[\\n\\r]*)* function \\2\\([^\\)]*\\) \\{[\\n\\r]*)', 'mg');
7+
8+
/** Regex that matches downleveled class IIFE expressions with _extends statements */
9+
const classExtendsIfeeRegex =
10+
/^(var (\S+) = )(\(function \(_super\) \{[\n\r]* __extends\(\2, _super\);[\n\r]*)/gm;
11+
12+
/**
13+
* Adds `@__PURE__` annotation comments to IIFEs containing ES5-downleveled classes generated by
14+
* TypeScript so that Uglify can tree-shake classes that are not referenced.
15+
*
16+
* @param fileContent The content of the file for which `@__PURE__` will be added.
17+
* @returns The content of the file with `@__PURE__` annotations added.
18+
*/
19+
export function addPureAnnotations(fileContent: string) {
20+
return fileContent
21+
// Prefix downleveled classes w/ the @__PURE__ annotation.
22+
.replace(classIfeeRegex, '$1/*@__PURE__*/$3')
23+
// Prefix downleveled classes that extend another class w/ the @__PURE__ annotation
24+
.replace(classExtendsIfeeRegex, '$1/*@__PURE__*/$3');
25+
}
26+
27+
/** Adds Uglify "@__PURE__" decorations to the specified file. */
28+
export function addPureAnnotationsToFile(inputFile: string) {
29+
const originalContent = readFileSync(inputFile, 'utf-8');
30+
const annotatedContent = addPureAnnotations(originalContent);
31+
32+
writeFileSync(inputFile, annotatedContent, 'utf-8');
33+
}

tools/gulp/util/rollup-helper.ts renamed to tools/gulp/packaging/rollup-helpers.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',
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// There are no type definitions available for these imports.
2+
const sorcery = require('sorcery');
3+
4+
/**
5+
* Finds the original sourcemap of the file and maps it to the current file.
6+
* This is useful when multiple transformation happen (e.g TSC -> Rollup -> Uglify)
7+
**/
8+
export async function remapSourcemap(sourceFile: string) {
9+
// Once sorcery loaded the chain of sourcemaps, the new sourcemap will be written asynchronously.
10+
return (await sorcery.load(sourceFile)).write();
11+
}
12+
13+
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+
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+
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+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {writeFileSync} from 'fs';
2+
import {LICENSE_BANNER} from '../constants';
3+
import {join} from 'path';
4+
5+
/** Create a typing file that links to the bundled definitions of NGC. */
6+
export function createTypingsReexportFile(outputDir: string, entryName: string) {
7+
writeFileSync(join(outputDir, `${entryName}.d.ts`),
8+
LICENSE_BANNER + '\nexport * from "./typings/index";'
9+
);
10+
}

tools/gulp/tasks/material-release.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ 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';
76
import {COMPONENTS_DIR, DIST_MATERIAL, DIST_RELEASES} from '../constants';
7+
import {composeRelease} from '../packaging/build-release';
88

99
// There are no type definitions available for these imports.
1010
const gulpRename = require('gulp-rename');

tools/gulp/util/annotate-pure.ts

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

0 commit comments

Comments
 (0)