Skip to content

Commit

Permalink
build: Implement build process that complies with the Angular package…
Browse files Browse the repository at this point in the history
… format (#13)
  • Loading branch information
MikeRyanDev authored and brandonroberts committed Apr 18, 2017
1 parent 41758b1 commit f4baa3e
Show file tree
Hide file tree
Showing 78 changed files with 1,048 additions and 8,391 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ __build__/**
.idea/
*.swp
!/typings/custom.d.ts
.vscode/

# Build Artifacts #
release
Expand All @@ -62,3 +63,5 @@ lerna-debug.log
/lib/
ngfactory
output
*.ngsummary.json
*.ngfactory.ts
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2017 Brandon Roberts, Mike Ryan, Rob Wormald
Copyright (c) 2017 Brandon Roberts, Mike Ryan, Victor Savkin, Rob Wormald

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
18 changes: 18 additions & 0 deletions build/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as tasks from './tasks';
import { createBuilder } from './util';


export default createBuilder([
[ 'Removing "./dist" Folder', tasks.removeDistFolder ],
[ 'Compiling packages with NGC', tasks.compilePackagesWithNgc ],
[ 'Bundling FESMs', tasks.bundleFesms ],
[ 'Down-leveling FESMs to ES5', tasks.downLevelFesmsToES5 ],
[ 'Creating UMD Bundles', tasks.createUmdBundles ],
[ 'Renaming package entry files', tasks.renamePackageEntryFiles ],
[ 'Cleaning TypeScript files', tasks.cleanTypeScriptFiles ],
[ 'Removing remaining sourcemap files', tasks.removeRemainingSourceMapFiles ],
[ 'Copying type definition files', tasks.copyTypeDefinitionFiles ],
[ 'Minifying UMD bundles', tasks.minifyUmdBundles ],
[ 'Copying package documents', tasks.copyPackageDocs ],
[ 'Removing "./dist/packages" Folder', tasks.removePackagesFolder ],
]);
4 changes: 4 additions & 0 deletions build/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Config {
packages: string[];
scope: string;
}
15 changes: 15 additions & 0 deletions build/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import build from './builder';


build({
scope: '@ngrx',
packages: [
'store',
'effects',
'router-store',
'store-devtools',
]
}).catch(err => {
console.error(err);
process.exit(1);
});
198 changes: 198 additions & 0 deletions build/tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { Config } from './config';
import * as util from './util';


/**
* Cleans the top level dist folder. All npm-ready packages are created
* in the dist folder.
*/
export async function removeDistFolder(config: Config) {
const args = [
'./dist'
];

return await util.exec('rimraf', args);
}


/**
* Uses the 'tsconfig-build.json' file in each package directory to produce
* AOT and Closure compatible JavaScript
*/
export async function compilePackagesWithNgc(config: Config) {
for (let pkg of config.packages) {
await util.exec('ngc', [
`-p ./modules/${pkg}/tsconfig-build.json`
]);

const entryTypeDefinition = `export * from './${pkg}/index';`;
const entryMetadata = `{"__symbolic":"module","version":3,"metadata":{},"exports":[{"from":"./${pkg}/index"}]}`;

util.writeFile(`./dist/packages/${pkg}.d.ts`, entryTypeDefinition);
util.writeFile(`./dist/packages/${pkg}.metadata.json`, entryMetadata);
}
}


/**
* Uses Rollup to bundle the JavaScript into a single flat file called
* a FESM (Flat Ecma Script Module)
*/
export async function bundleFesms(config: Config) {
for (let pkg of config.packages) {
await util.exec('rollup', [
`-i ./dist/packages/${pkg}/index.js`,
`-o ./dist/${pkg}/${config.scope}/${pkg}.js`,
`--sourcemap`,
]);

await util.mapSources(`./dist/${pkg}/${config.scope}/${pkg}.js`);
}
}


/**
* Copies each FESM into a TS file then uses TypeScript to downlevel
* the FESM into ES5 with ESM modules
*/
export async function downLevelFesmsToES5(config: Config) {
const tscArgs = [
'--target es5',
'--module es2015',
'--noLib',
'--sourceMap',
];

for (let pkg of config.packages) {
const file = `./dist/${pkg}/${config.scope}/${pkg}.js`;
const target = `./dist/${pkg}/${config.scope}/${pkg}.es5.ts`;

util.copy(file, target);

await util.ignoreErrors(util.exec('tsc', [ target, ...tscArgs ]));
await util.mapSources(target.replace('.ts', '.js'));
}

await util.removeRecursively(`./dist/?(${config.packages.join('|')})/${config.scope}/*.ts`);
}


/**
* Re-runs Rollup on the downleveled ES5 to produce a UMD bundle
*/
export async function createUmdBundles(config: Config) {
for (let pkg of config.packages) {
const rollupArgs = [
`-c ./modules/${pkg}/rollup.config.js`,
`--sourcemap`,
];

await util.exec('rollup', rollupArgs);
await util.mapSources(`./dist/${pkg}/bundles/${pkg}.umd.js`);
}
}


/**
* Removes any leftover TypeScript files from previous compilation steps,
* leaving any type definition files in place
*/
export async function cleanTypeScriptFiles(config: Config) {
const tsFilesGlob = './dist/packages/**/*.js';
const dtsFilesFlob = './dist/packages/**/*.d.ts';
const filesToRemove = await util.getListOfFiles(tsFilesGlob, dtsFilesFlob);

for (let file of filesToRemove) {
util.remove(file);
}
}


/**
* Renames the index files in each package to the name
* of the package.
*/
export async function renamePackageEntryFiles(config: Config) {
for (let pkg of config.packages) {
const files = await util.getListOfFiles(`./dist/packages/${pkg}/index.**`);

for (let file of files) {
const target = file.replace('index', pkg);
util.copy(file, target);
util.remove(file);
}
}
}


/**
* Removes any remaining source map files from running NGC
*/
export async function removeRemainingSourceMapFiles(config: Config) {
await util.removeRecursively(`./dist/packages/?(${config.packages.join('|')})/**/*.map`);
}


/**
* Copies the type definition files and NGC metadata files to
* the root of the distribution
*/
export async function copyTypeDefinitionFiles(config: Config) {
const files = await util.getListOfFiles(`./dist/packages/?(${config.packages.join('|')})/**/*`);

for (let file of files) {
const target = file.replace('packages/', '');
util.copy(file, target);
}

await util.removeRecursively(`./dist/packages/?(${config.packages.join('|')})`);
}


/**
* Creates minified copies of each UMD bundle
*/
export async function minifyUmdBundles(config: Config) {
const uglifyArgs = [
'-c',
'--screw-ie8',
'--comments',
];

for (let pkg of config.packages) {
const file = `./dist/${pkg}/bundles/${pkg}.umd.js`;
const out = `./dist/${pkg}/bundles/${pkg}.umd.min.js`;

await util.exec('uglifyjs', [
...uglifyArgs,
`-o ${out}`,
`--source-map ${out}.map`,
`--source-map-include-sources ${file}`,
`--in-source-map ${file}.map`
]);
}
}


/**
* Copies the README.md, LICENSE, and package.json files into
* each package
*/
export async function copyPackageDocs(config: Config) {
for (let pkg of config.packages) {
const source = `./modules/${pkg}`;
const target = `./dist/${pkg}`;

util.copy(`${source}/package.json`, `${target}/package.json`);
util.copy(`${source}/README.md`, `${target}/README.md`);
util.copy('./LICENSE', `${target}/LICENSE`);
}
}


/**
* Removes the packages folder
*/
export async function removePackagesFolder(config: Config) {
await util.removeRecursively('./dist/packages');
}
114 changes: 114 additions & 0 deletions build/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import * as fs from 'fs';
import * as cp from 'child_process';
import * as glob from 'glob';
import * as fsExtra from 'fs-extra';
import * as path from 'path';
import * as rimraf from 'rimraf';
import { Config } from './config';


export function copy(target: string, destination: string) {
fsExtra.copySync(target, destination);
}

export function remove(target: string) {
fsExtra.removeSync(target);
}

export function writeFile(target: string, contents: string) {
fs.writeFileSync(target, contents);
}

export function getListOfFiles(globPath: string, exclude?: string): Promise<string[]> {
return new Promise((resolve, reject) => {
const options = exclude ? { ignore: exclude } : { };

glob(globPath, options, (error, matches) => {
if (error) {
return reject(error);
}

resolve(matches);
});
});
}

export function removeRecursively(glob: string) {
return new Promise((resolve, reject) => {
rimraf(glob, err => {
if (err) {
reject(err);
}
else {
resolve();
}
});
});
}

export function exec(command: string, args: string[]): Promise<string> {
return new Promise((resolve, reject) => {
cp.exec(fromNpm(command) + ' ' + args.join(' '), (err, stdout, stderr) => {
if (err) {
return reject(err);
}

resolve(stdout.toString());
});
});
}

export async function ignoreErrors<T>(promise: Promise<T>): Promise<T | null> {
try {
const result = await promise;
return result;
}
catch (err) {
return null;
}
}

export function fromNpm(command: string) {
return `./node_modules/.bin/${command}`;
}

export function getPackageFilePath(pkg: string, filename: string) {
return path.resolve(process.cwd(), `./modules/${pkg}/${filename}`);
}

const sorcery = require('sorcery');
export function mapSources(file: string) {
return new Promise((resolve, reject) => {
sorcery.load(file)
.then((chain: any) => {
chain.write();
resolve();
})
.catch(reject);
});
}

const ora = require('ora');
async function runTask(name: string, taskFn: () => Promise<any>) {
const spinner = ora(name);

try {
spinner.start();

await taskFn();

spinner.succeed();
} catch (e) {
spinner.fail();

throw e;
}
}

export function createBuilder(tasks: [ string, (config: Config) => Promise<any> ][]) {
return async function (config: Config) {
for (let [ name, runner ] of tasks) {
await runTask(name, () => runner(config));
}
};
}
Loading

0 comments on commit f4baa3e

Please sign in to comment.