Skip to content

Instantly share code, notes, and snippets.

@mgechev
Last active June 12, 2018 09:10
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mgechev/63d374d51ac846797879a0cdc9c3b97c to your computer and use it in GitHub Desktop.
Save mgechev/63d374d51ac846797879a0cdc9c3b97c to your computer and use it in GitHub Desktop.
Lazy loading in Angular Seed
import { appendFileSync } from 'fs';
import { join } from 'path';
import * as utils from 'gulp-util';
import * as Builder from 'systemjs-builder';
import Config from '../../config';
const BUNDLER_OPTIONS = {
format: 'cjs',
minify: true,
mangle: true
};
interface Bundle {
path: string;
module: string;
}
class BundleNode {
children: BundleNode[] = [];
constructor(public path: string) {}
isParent(node: BundleNode) {
return node.path.startsWith(this.path);
}
}
class BundleTree {
roots: BundleNode[] = [];
static buildTree(paths: string[]) {
const tree = new BundleTree();
paths.forEach((p: string) => {
if (p === '/') {
throw new Error('Invalid "/" path');
}
});
paths.sort((a: string, b: string) => a.split('/').length - b.split('/').length)
.forEach(p => tree.addNode(new BundleNode(p)));
return tree;
}
addNode(node: BundleNode) {
if (!this.roots.length) {
this.roots.push(node);
} else {
const added = this.roots.some((root: BundleNode) => this.addNodeHelper(node, root));
if (!added) {
this.roots.push(node);
}
}
}
private addNodeHelper(node: BundleNode, context: BundleNode): boolean {
const added: boolean = context.children.reduce((a: boolean, c: BundleNode) => {
return a || this.addNodeHelper(node, c);
}, false);
if (!added && context.isParent(node)) {
context.children.push(node);
return true;
}
return added;
}
}
const normalizeConfig = (bundles: any[]) => {
bundles = bundles.map((b: any) => b.path);
return bundles.map((b: string) => {
if (!b.endsWith('.js')) {
b += '.module.ngfactory.js';
}
return b;
});
};
const config = JSON.parse(JSON.stringify(Config.SYSTEM_BUILDER_CONFIG));
delete config.paths;
const addExtensions = `
$traceurRuntime = {
typeof: function (a) {
return typeof a;
}
};
System.config(${JSON.stringify(config, null, 2)});
`;
const bundleMain = () => {
const builder = new Builder(Config.SYSTEM_BUILDER_CONFIG);
const mainpath = join(Config.TMP_DIR, Config.BOOTSTRAP_FACTORY_PROD_MODULE);
const outpath = join(Config.JS_DEST, Config.JS_PROD_APP_BUNDLE);
utils.log('Bundling the bootstrap bundle');
return builder
.bundle(mainpath,
outpath,
Object.assign({ format: 'umd', sourceMaps: true }, BUNDLER_OPTIONS))
.then((res: any) => {
utils.log('The bootstrap bundle is ready!');
appendFileSync(outpath, `\nSystem.import('${mainpath}.js');${addExtensions}`);
return res.modules;
});
};
const bundleModule = (bundle: string, exclude: string[]): Promise<string[]> => {
utils.log('Bundling module with entry file', bundle);
let builder = new Builder(Config.SYSTEM_BUILDER_CONFIG);
let all = join(Config.TMP_DIR, Config.BOOTSTRAP_DIR);
let bootstrap = join(Config.TMP_DIR, Config.BOOTSTRAP_DIR, bundle);
const parts = bundle.split('/');
parts.pop();
let bootstrapDir = join(Config.TMP_DIR, Config.BOOTSTRAP_DIR, parts.join('/'));
let expression = `${bootstrap} - (${all}/**/*.js - ${bootstrapDir}/**/*.js)`;
if (exclude.length) {
expression += ` - ${exclude.join(' - ')}`;
}
return builder
.buildStatic(
expression,
join(Config.JS_DEST, '..', Config.BOOTSTRAP_DIR, bundle),
Object.assign({}, BUNDLER_OPTIONS, { format: 'umd', sourceMaps: true }))
.then((res: any) => {
utils.log('Bundling of', bundle, 'completed!');
return res;
});
};
const bundleModules = (roots: BundleNode[], exclude: string[]): Promise<any> => {
return Promise.all(roots.map((node: BundleNode) =>
bundleModule(node.path, exclude)
.then((directExclude: string[]) => {
return bundleModules(node.children, exclude.concat(directExclude));
})));
};
/**
* Executes the build process, bundling the JavaScript files using the SystemJS builder.
*/
export = (done: any) => {
const config = normalizeConfig(Config.BUNDLES);
const bundleTree = BundleTree.buildTree(config);
bundleMain()
.then((bundled: string[]) => bundleModules(bundleTree.roots, bundled))
.then(() => {
let builder = new Builder(Config.SYSTEM_BUILDER_CONFIG);
return builder
.buildStatic(join(Config.TMP_DIR, Config.MINI_APP_MODULE),
join(Config.JS_DEST, Config.MINI_APP_BUNDLE),
BUNDLER_OPTIONS);
})
.then(() => done())
.catch((e: any) => done(e));
};
import * as gulp from 'gulp';
import * as gulpLoadPlugins from 'gulp-load-plugins';
import * as merge from 'merge-stream';
import { join } from 'path';
import { getBundlesFsPath } from '../../utils/project/bundles';
import Config from '../../config';
const plugins = <any>gulpLoadPlugins();
const getTask = (target: string, destDir: string) => {
return gulp.src(join(destDir, target))
.pipe(plugins.uglify({
compress: { screw_ie8: true },
mangle: { screw_ie8: true },
output: { screw_ie8: true }
}))
.pipe(gulp.dest(destDir));
};
export = (done: any) => {
const streams = [
getTask(Config.JS_PROD_APP_BUNDLE, Config.JS_DEST),
getTask(Config.JS_PROD_SHIMS_BUNDLE, Config.JS_DEST)
];
getBundlesFsPath(Config.BUNDLES)
.forEach((f: string) => {
const path = join(Config.JS_DEST, '..', Config.BOOTSTRAP_DIR);
streams.push(getTask(f, path));
});
return merge(...streams);
};
// ...
BUNDLES: any[] = [
{ name: 'intro', path: 'app/+intro/intro' },
{ name: 'main', path: 'app/+main/main' }
];
SYSTEM_CONFIG: any = {
defaultJSExtensions: true,
packageConfigPaths: [
`${this.APP_BASE}node_modules/@ngrx/*/package.json`,
`${this.APP_BASE}node_modules/*/package.json`,
`${this.APP_BASE}node_modules/aspect.js/package.json`,
`${this.APP_BASE}node_modules/@ngrx/store/package.json`,
`${this.APP_BASE}node_modules/**/package.json`,
`${this.APP_BASE}node_modules/@angular/*/package.json`
],
packages: {
'jstimezonedetect': {
main: 'dist/jstz.js',
format: 'cjs',
defaultExtension: 'js'
}
},
paths: {
'app/*': `${this.APP_BASE}app/*/index`,
[this.BOOTSTRAP_MODULE]: `${this.APP_BASE}${this.BOOTSTRAP_MODULE}`,
'@angular/core': `${this.APP_BASE}node_modules/@angular/core/bundles/core.umd.js`,
'@angular/common': `${this.APP_BASE}node_modules/@angular/common/bundles/common.umd.js`,
'@angular/compiler': `${this.APP_BASE}node_modules/@angular/compiler/bundles/compiler.umd.js`,
'@angular/http': `${this.APP_BASE}node_modules/@angular/http/bundles/http.umd.js`,
'@angular/router': `${this.APP_BASE}node_modules/@angular/router/bundles/router.umd.js`,
'@angular/forms': `${this.APP_BASE}node_modules/@angular/forms/bundles/forms.umd.js`,
'@angular/platform-browser': `${this.APP_BASE}node_modules/@angular/platform-browser/bundles/platform-browser.umd.js`,
'@angular/platform-browser-dynamic':
`${this.APP_BASE}node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js`,
'rxjs/*': `${this.APP_BASE}node_modules/rxjs/*`,
'tracking': `${this.APP_BASE}node_modules/tracking/build/tracking.js`,
'load-image/*': `${this.APP_BASE}node_modules/blueimp-load-image/js/*`,
'tracking-face': `${this.APP_BASE}node_modules/tracking/build/data/face.js`,
'*': `${this.APP_BASE}node_modules/*`
}
};
RESOURCES = join(this.PROJECT_ROOT, 'resources');
SYSTEM_BUILDER_CONFIG: any = {
defaultJSExtensions: true,
base: this.PROJECT_ROOT,
packageConfigPaths: [
join(this.PROJECT_ROOT, 'node_modules', '*', 'package.json'),
join(this.PROJECT_ROOT, 'node_modules', '@ngrx', '*', 'package.json'),
join(this.PROJECT_ROOT, 'node_modules', '@angular', '*', 'package.json')
],
paths: {
// Note that for multiple apps this configuration need to be updated
// You will have to include entries for each individual application in
// `src/client`.
[`${this.TMP_DIR}/*`]: `${this.TMP_DIR}/*`,
'bp-meta': `${this.APP_BASE}app/utils/bp-meta`,
'tracking': 'node_modules/tracking/build/tracking-min.js',
'tracking-face': 'node_modules/tracking/build/data/face-min.js',
'load-image/*': `node_modules/blueimp-load-image/js/*`,
'*': 'node_modules/*'
},
meta: {
'*.json': {
format: 'json'
}
},
packages: {
'@angular/core': {
main: 'index.js',
defaultExtension: 'js'
},
'@angular/compiler': {
main: 'index.js',
defaultExtension: 'js'
},
'@angular/common': {
main: 'index.js',
defaultExtension: 'js'
},
'@angular/http': {
main: 'index.js',
defaultExtension: 'js'
},
'@angular/forms': {
main: 'index.js',
defaultExtension: 'js'
},
'@angular/platform-browser': {
main: 'index.js',
defaultExtension: 'js'
},
'@angular/platform-browser-dynamic': {
main: 'index.js',
defaultExtension: 'js'
},
'@angular/router-deprecated': {
main: 'index.js',
defaultExtension: 'js'
},
'@angular/router': {
main: 'index.js',
defaultExtension: 'js'
},
'jstimezonedetect': {
main: 'dist/jstz.min.js',
format: 'cjs',
defaultExtension: 'js'
},
'immutable': {
main: 'dist/immutable.js'
},
'angular2-jwt': {
main: 'angular2-jwt.js'
},
'aspect.js': {
main: 'aspect.js'
},
'aspect.js-angular': {
main: 'index.js'
},
'@ngrx/store': {
main: 'index.js',
defaultExtension: 'js'
},
'@ngrx/core': {
main: 'index.js',
defaultExtension: 'js'
},
'platform': {
main: 'platform.js'
},
'reflect-metadata': {
main: 'Reflect.js'
},
'rxjs': {
defaultExtension: 'js'
}
}
};
//...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment