Skip to content

build: ship individual component bundles #4325

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions tools/gulp/tasks/material-release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import {task, src, dest} from 'gulp';
import {join} from 'path';
import {writeFileSync} from 'fs';
import {Bundler} from 'scss-bundle';
import {execNodeTask, sequenceTask} from '../util/task_helpers';
import {sequenceTask} from '../util/task_helpers';
import {composeRelease} from '../util/package-build';
import {COMPONENTS_DIR, DIST_MATERIAL, DIST_RELEASES} from '../constants';
import {composeSubpackages, buildAllSubpackages} from '../util/sub-packages';

// There are no type definitions available for these imports.
const gulpRename = require('gulp-rename');
Expand All @@ -24,17 +25,23 @@ const allScssGlob = join(COMPONENTS_DIR, '**/*.scss');
* Overwrite the release task for the material package. The material release will include special
* files, like a bundled theming SCSS file or all prebuilt themes.
*/
task('material:build-release', ['material:prepare-release'], () => composeRelease('material'));
task('material:build-release', ['material:prepare-release'], async () => {
await composeRelease('material');
await composeSubpackages('material');
});

/**
* Task that will build the material package. It will also copy all prebuilt themes and build
* a bundled SCSS file for theming
*/
task('material:prepare-release', sequenceTask(
'material:build',
['material:copy-prebuilt-themes', 'material:bundle-theming-scss']
['material:copy-prebuilt-themes', 'material:bundle-theming-scss', 'material:build-subpackages']
));

/** Task that builds all subpackages of the Material package. Necessary for releases. */
task('material:build-subpackages', () => buildAllSubpackages('material'));

/** Copies all prebuilt themes into the release package under `prebuilt-themes/` */
task('material:copy-prebuilt-themes', () => {
src(prebuiltThemeGlob)
Expand Down
4 changes: 2 additions & 2 deletions tools/gulp/util/package-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export async function buildPackageBundles(entryFile: string, packageName: string
* 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)
**/
async function remapSourcemap(sourceFile: string) {
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();
}
Expand All @@ -107,7 +107,7 @@ function uglifyFile(inputPath: string, outputPath: string) {
writeFileSync(sourcemapOut, result.map);
}

function copyFiles(fromPath: string, fileGlob: string, outDir: string) {
export function copyFiles(fromPath: string, fileGlob: string, outDir: string) {
glob(fileGlob, {cwd: fromPath}).forEach(filePath => {
let fileDestPath = join(outDir, filePath);
mkdirpSync(dirname(fileDestPath));
Expand Down
9 changes: 6 additions & 3 deletions tools/gulp/util/rollup-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {LICENSE_BANNER} from '../constants';
// There are no type definitions available for these imports.
const rollup = require('rollup');

const ROLLUP_GLOBALS = {
export const ROLLUP_GLOBALS = {
// Angular dependencies
'@angular/animations': 'ng.animations',
'@angular/core': 'ng.core',
Expand Down Expand Up @@ -44,14 +44,17 @@ export type BundleConfig = {
dest: string;
format: string;
moduleName: string;
external?: (moduleId: string) => boolean;
paths?: (moduleId: string) => string;
};

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

let writeOptions = {
Expand Down
109 changes: 109 additions & 0 deletions tools/gulp/util/sub-packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {join, basename, normalize, isAbsolute, dirname, relative} from 'path';
import {sync as glob} from 'glob';
import {DIST_ROOT, DIST_BUNDLES, DIST_RELEASES} from '../constants';
import {remapSourcemap, copyFiles} from './package-build';
import {createRollupBundle, ROLLUP_GLOBALS} from './rollup-helper';
import {transpileFile} from './ts-compiler';
import {ScriptTarget, ModuleKind} from 'typescript';
import {writeFileSync} from 'fs';

/** Modules that will be always treated as external */
const ROLLUP_EXTERNALS = Object.keys(ROLLUP_GLOBALS);

/**
* Builds FESM bundles for all available sub-packages of the package. A subpackage is every
* directory inside of the root package.
*/
export async function buildAllSubpackages(packageName: string) {
const packageOut = join(DIST_ROOT, 'packages', packageName);
const subPackages = glob(join(packageOut, '*/'));

await Promise.all(subPackages.map(packagePath => {
return buildSubpackage(basename(packagePath), packagePath, packageName);
}));
}

/**
* Builds FESM bundles for the specified sub-package of a package. Subpackage bundles are
* used to improve tree-shaking in bundlers like Webpack.
*/
async function buildSubpackage(packageName: string, packagePath: string, rootPackage: string) {
const entryFile = join(packagePath, 'index.js');
const moduleName = `ng.${rootPackage}.${packageName}`;

// List of paths to the subpackage bundles.
const fesm2015File = join(DIST_BUNDLES, rootPackage, `${packageName}.js`);
const fesm2014File = join(DIST_BUNDLES, rootPackage, `${packageName}.es5.js`);

// Build a FESM-2015 bundle for the subpackage folder.
await createRollupBundle({
moduleName: moduleName,
entry: entryFile,
dest: fesm2015File,
format: 'es',
// Rewrite all internal paths to scoped package imports
paths: importPath => importPath.includes(DIST_ROOT) && `@angular/${rootPackage}`,
// Function to only bundle all files inside of the current subpackage.
external: importPath => {
return ROLLUP_EXTERNALS.indexOf(importPath) !== -1 ||
isAbsolute(importPath) && !importPath.includes(packageName);
},
});

await remapSourcemap(fesm2015File);

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

await remapSourcemap(fesm2014File);
}

/**
* Function that composes a release with the subpackages included. Needs to run after the main
* compose release function.
*/
export async function composeSubpackages(packageName: string) {
const packagePath = join(DIST_ROOT, 'packages', packageName);
const bundlesDir = join(DIST_BUNDLES, packageName);
const bundlePaths = glob(join(bundlesDir, '!(*.es5).js')).map(normalize);

const fesmOutputDir = join(DIST_RELEASES, packageName, '@angular');
const fesm2015File = join(fesmOutputDir, `${packageName}.js`);
const fesm2014File = join(fesmOutputDir, `${packageName}.es5.js`);

const moduleIndexPath = join(fesmOutputDir, `module-index.js`);

let indexContent = `export * from '../../../packages/${packageName}/module';\n`;

indexContent += bundlePaths.map(bundle => `export * from './${basename(bundle)}';`).join('\n');

// Write index file to disk. Afterwards run rollup to bundle.
writeFileSync(moduleIndexPath, indexContent);

// Copy all bundles into the release directory.
copyFiles(bundlesDir, '*.js', fesmOutputDir);

await createRollupBundle({
moduleName: `ng.${packageName}`,
entry: moduleIndexPath,
dest: fesm2015File,
format: 'es',
external: importPath => importPath !== moduleIndexPath && !importPath.includes('module'),
paths: importPath => {
if (importPath.includes(packagePath)) { return `@angular/${packageName}`; }
if (dirname(importPath) === fesmOutputDir) { return 'TEST (intentionally committed!)'; }
}
});


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