Skip to content

feat: lazy loading with closure compiler #4

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

Merged
merged 1 commit into from
Mar 6, 2017
Merged
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
4 changes: 2 additions & 2 deletions app/module.lazy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {NgModule, Injectable} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {RouterModule, Routes, PreloadingStrategy, Route} from '@angular/router';
import 'rxjs/add/observable/of';
import {of} from 'rxjs/observable/of';
import { Observable } from 'rxjs/Observable';

import {App} from './app';
Expand All @@ -24,7 +24,7 @@ export class PreloadSelectedModules implements PreloadingStrategy {
this.preloadedModules.push(route.path);
return load();
} else {
return Observable.of(null);
return of.call(null);
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
"aot-rollup": "ngc -p tsconfig-aot.json && rollup -c rollup-aot.js",
"aot-systemjs": "ngc -p tsconfig-aot.json && tsc -p tsconfig-systemjs-aot.json && node ./scripts/full-system-bundler.js --aot",
"aot-webpack": "webpack --config webpack-aot.js --progress --profile --bail",
"lazyloading-closure": "ngc -p tsconfig-lazy.json && ngc -p tsconfig-closure-lazy.json && bash ./scripts/build_closure_lazy.sh",
"lazyloading-systemjs": "ngc -p tsconfig-lazy.json && tsc -p tsconfig-systemjs-lazy.json && node ./scripts/full-system-bundler.js --lazy",
"lazyloading-webpack": "webpack --config webpack-lazyloading.js --progress --profile --bail",
"prerender": "tsc -p tsconfig-commonjs.json && node ./scripts/prerender.js && webpack --config webpack-prerender.js --progress --profile --bail",
"build": "npm run clean && npm run public && npm run dev && npm run bundling-app && npm run bundling-full && npm run treeshaking-rollup && npm run treeshaking-webpack && npm run aot-closure && npm run aot-systemjs && npm run aot-rollup && npm run aot-webpack && npm run lazyloading-systemjs && npm run lazyloading-webpack && npm run prerender",
"build": "npm run clean && npm run public && npm run dev && npm run bundling-app && npm run bundling-full && npm run treeshaking-rollup && npm run treeshaking-webpack && npm run aot-closure && npm run aot-systemjs && npm run aot-rollup && npm run aot-webpack && npm run lazyloading-closure && npm run lazyloading-systemjs && npm run lazyloading-webpack && npm run prerender",
"start": "http-server ./dist -s",
"watch": "tsc -w -p tsconfig-systemjs.json",
"e2e": "tsc -p tsconfig-protractor.json && concurrently -k -s first \"npm start\" \"protractor\"",
Expand Down Expand Up @@ -81,6 +82,6 @@
"script-ext-html-webpack-plugin": "1.7.1",
"raw-loader": "0.5.1",
"@ngtools/webpack": "1.2.11",
"google-closure-compiler": "^20170124.0.0"
"google-closure-compiler": "20170218.0.0"
}
}
3 changes: 2 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
},
series: series,
colors: ['#7cb5ec', '#434348', '#90ed7d', '#f7a35c', '#8085e9', '#f15c80', '#CE0058',
'#e4d354', '#2b908f', '#f45b5b', '#91e8e1', '#a5a5a5', '#9f4f23', ]
'#e4d354', '#2b908f', '#f45b5b', '#161da3', '#91e8e1', '#a5a5a5', '#9f4f23']
});
}
</script>
Expand Down Expand Up @@ -91,6 +91,7 @@ <h1 class="display-3">Let's optimize!</h1>
</div>
<div class="btn-group-vertical">
<a class="btn btn-outline-primary btn-lg disabled">Lazyloading</a>
<a class="btn btn-outline-primary btn-lg" href="./lazyloading-closure" role="button">Closure</a>
<a class="btn btn-outline-primary btn-lg" href="./lazyloading-systemjs" role="button">SystemJS</a>
<a class="btn btn-outline-primary btn-lg" href="./lazyloading-webpack" role="button">Webpack</a>
</div>
Expand Down
7 changes: 7 additions & 0 deletions public/lazyloading-closure/bootstrap.min.css

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions public/lazyloading-closure/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<title>Optimize Angular app</title>
<base href=".">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript">
var begin = Date.now();
var idPerf = setInterval(function() {
if (document.querySelector('#perf')) {
document.querySelector('#output').innerHTML = Date.now() - begin;
clearInterval(idPerf);
}
})
</script>
<link rel="stylesheet" href="bootstrap.min.css">
<script src="vendors.js"></script>
</head>
<body>
<div style="position: absolute;top: 0; right: 0; z-index: 5; color: white;"><i>lazyloading-closure</i><div id="output" style="color: rgba(0,0,0,0);"></div></div>
<my-app>Loading...</my-app>
</body>
<script>
// Polyfill and/or monkey patch System.import.
// Credits go to https://github.com/cramforce/splittable
(self.System = self.System || {}).import = function(n){
if (n.length == 0) return;
// Always end names in .js
n = n.replace(/\.js$/g,"") + ".js";
// Short circuit if the bundle is already loaded.
return (self._S["//" + n] && Promise.resolve(self._S["//" + n]))
// Short circuit if we are already loadind, otherwise create
// a promise (that will short circuit subsequent requests)
// and start loading.
|| self._S[n] || (self._S[n] = new Promise(function(r,t){
// Load via a script
var s = document.createElement("script");
// Calculate the source URL using the same algorithms as used
// during bundle generation.
s.src = (self.System.baseURL||".") + "/" + (self._S._map[n] ? self._S._map[n] : n);
// Fail promise on any error.
s.onerror = t;
// On success the trailing module in every bundle will have created
// the _S global representing the module object that is the root
// of the bundle. Resolve the promise with it.
s.onload = function(){
r(self._S["//"+n])
};
// Append the script tag.
(document.head||document.documentElement).appendChild(s);
})
)}
// Runs scheduled non-base bundles in the _S array and overrides
// .push to immediately execute incoming bundles.
self._S = self._S || [];
self._S._map = {
'app/list/list.module.ngfactory.js': 'bundle1.js',
'app/search/search.module.ngfactory.js': 'bundle2.js',
'app/subscribe/subscribe.module.ngfactory.js': 'bundle3.js'
};
self._S.push = function(f){
f.call(self)
};
</script>
<script src="main.js"></script>
<script>

</script>
</body>

</html>
121 changes: 121 additions & 0 deletions scripts/build_closure_lazy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
OPTS=(
"--language_in=ES6_STRICT"
"--language_out=ES5"
"--compilation_level=ADVANCED_OPTIMIZATIONS"
"--js_output_file=tmp/waste.js"
"--create_source_map=%outname%.map"
"--variable_renaming_report=tmp/variable_renaming_report"
"--property_renaming_report=tmp/property_renaming_report"
"--warning_level=QUIET"

# Don't include ES6 polyfills
"--rewrite_polyfills=false"

# List of path prefixes to be removed from ES6 & CommonJS modules.
"--js_module_root=node_modules/@angular/common"
"--js_module_root=node_modules/@angular/core"
"--js_module_root=node_modules/@angular/forms"
"--js_module_root=node_modules/@angular/http"
"--js_module_root=node_modules/@angular/platform-browser"
"--js_module_root=node_modules/@angular/router"
"--js_module_root=node_modules"
"--js_module_root=lib/vendor"

# Uncomment for easier debugging
#"--formatting=PRETTY_PRINT"
#"--debug"

# Include zone.js as externs rather than the source code.
# Allows us to use --dependency_mode=STRICT below.
# Otherwise the zone.js file is not imported anywhere and gets dropped.
# See index.html
#node_modules/zone.js/dist/zone.js
"lib/vendor/testability.externs.js"
"lib/vendor/zone_externs.js"

$(find lib/vendor/rxjs -name '*.js')
node_modules/@angular/core/@angular/core.js
node_modules/@angular/common/@angular/common.js
node_modules/@angular/compiler/@angular/compiler.js
node_modules/@angular/forms/@angular/forms.js
node_modules/@angular/http/@angular/http.js
node_modules/@angular/platform-browser/@angular/platform-browser.js
node_modules/@angular/router/@angular/router.js
$(find lib/vendor/@ng-bootstrap/ng-bootstrap -name '*.js')
$(find tmp/es6 -name '*.js')

# Trim files not imported (transitively) from bootstrap.js
"--dependency_mode=STRICT"
)

JVM_ARGS=""
# To attach a remote debugger:
#JVM_ARGS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"

set -ex
java $JVM_ARGS -jar node_modules/google-closure-compiler/compiler.jar --entry_point=./tmp/es6/app/main.lazy --output_manifest=tmp/0.MF $(echo ${OPTS[*]})
java $JVM_ARGS -jar node_modules/google-closure-compiler/compiler.jar --entry_point=./tmp/es6/app/list/list.module.ngfactory --output_manifest=tmp/1.MF $(echo ${OPTS[*]})
java $JVM_ARGS -jar node_modules/google-closure-compiler/compiler.jar --entry_point=./tmp/es6/app/search/search.module.ngfactory --output_manifest=tmp/2.MF $(echo ${OPTS[*]})
java $JVM_ARGS -jar node_modules/google-closure-compiler/compiler.jar --entry_point=./tmp/es6/app/subscribe/subscribe.module.ngfactory --output_manifest=tmp/3.MF $(echo ${OPTS[*]})

node ./scripts/build_closure_modules.js

OPTS=(
"--language_in=ES6_STRICT"
"--language_out=ES5"
"--compilation_level=ADVANCED_OPTIMIZATIONS"
"--module_output_path_prefix=dist/lazyloading-closure/"
"--create_source_map=%outname%.map"
"--variable_renaming_report=tmp/variable_renaming_report"
"--property_renaming_report=tmp/property_renaming_report"
"--warning_level=QUIET"

# Don't include ES6 polyfills
"--rewrite_polyfills=false"

# List of path prefixes to be removed from ES6 & CommonJS modules.
"--js_module_root=node_modules/@angular/common"
"--js_module_root=node_modules/@angular/core"
"--js_module_root=node_modules/@angular/forms"
"--js_module_root=node_modules/@angular/http"
"--js_module_root=node_modules/@angular/platform-browser"
"--js_module_root=node_modules/@angular/router"
"--js_module_root=node_modules"
"--js_module_root=lib/vendor"

# Uncomment for easier debugging
#"--formatting=PRETTY_PRINT"
#"--debug"

# Include zone.js as externs rather than the source code.
# Allows us to use --dependency_mode=STRICT below.
# Otherwise the zone.js file is not imported anywhere and gets dropped.
# See index.html
#node_modules/zone.js/dist/zone.js
"lib/vendor/testability.externs.js"
"lib/vendor/zone_externs.js"
$(cat tmp/modules.txt)

# Trim files not imported (transitively) from bootstrap.js
"--entry_point=./tmp/es6/app/main.lazy"
"--entry_point=./tmp/es6/app/list/list.module.ngfactory"
"--entry_point=./tmp/es6/app/search/search.module.ngfactory"
"--entry_point=./tmp/es6/app/subscribe/subscribe.module.ngfactory"


"--dependency_mode=STRICT"
"--output_manifest=tmp/manifest.MF"
)

JVM_ARGS=""
# To attach a remote debugger:
#JVM_ARGS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"

set -ex
java $JVM_ARGS -jar node_modules/google-closure-compiler/compiler.jar $(echo ${OPTS[*]}) \
"--module_wrapper=bundle1:(self._S=self._S||[]).push((function(){%s}));
//# sourceMappingURL=%basename%.map" \
"--module_wrapper=bundle2:(self._S=self._S||[]).push((function(){%s}));
//# sourceMappingURL=%basename%.map" \
"--module_wrapper=bundle3:(self._S=self._S||[]).push((function(){%s}));
//# sourceMappingURL=%basename%.map"
54 changes: 54 additions & 0 deletions scripts/build_closure_modules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"use strict";

const fs = require('fs');

const main = fs.readFileSync('./tmp/0.MF', 'utf-8').split('\n');
const bundle1 = fs.readFileSync('./tmp/1.MF', 'utf-8').split('\n');
const bundle2 = fs.readFileSync('./tmp/2.MF', 'utf-8').split('\n');
const bundle3 = fs.readFileSync('./tmp/3.MF', 'utf-8').split('\n');

removeDuplicates(main, bundle1);
removeDuplicates(main, bundle2);
removeDuplicates(main, bundle3);

let out = '';
out += main.join('\n');
out += '--module=main:' + (main.length - 1 + 2) + '\n\n'; //Remove empty line, add the 2 externs

out += bundle1.join('\n');
out += '\n--module=bundle1:' + bundle1.length + ':main\n\n';

out += bundle2.join('\n');
out += '\n--module=bundle2:' + bundle2.length + ':main\n\n';

out += bundle3.join('\n');
out += '\n--module=bundle3:' + bundle3.length + ':main\n\n';

fs.writeFileSync('./tmp/modules.txt', out, 'utf-8');

function removeDuplicates(reference, bundle) {
const refLength = bundle.length;
for (let i = 0; i < refLength; i++) {
const item = bundle[refLength - i -1];
if (reference.includes(item)) {
bundle.splice(refLength - i - 1, 1)
}
}
}

//TODO: modify ngfactory entry point to add the following:
// (self['_S']=self['_S']||[])["//app/search/search.module.ngfactory.js"]= {"SearchModuleNgFactory": SearchModuleNgFactory};

injectCustomExport('./tmp/es6/app/list/list.module.ngfactory.js', 'ListModuleNgFactory');
injectCustomExport('./tmp/es6/app/search/search.module.ngfactory.js', 'SearchModuleNgFactory');
injectCustomExport('./tmp/es6/app/subscribe/subscribe.module.ngfactory.js', 'SubscribeModuleNgFactory');

function injectCustomExport(filePath, moduleFactoryName) {
let source = fs.readFileSync(filePath, 'utf-8');
if (source.indexOf(`self['_S']`) == -1) {
source = source.replace('//# sourceMappingURL',
`(self['_S']=self['_S']||[])["//${filePath.replace('./tmp/es6/', '')}"]= {"${moduleFactoryName}": ${moduleFactoryName}};
//# sourceMappingURL`);
fs.writeFileSync(filePath, source, 'utf-8');
}
}
2 changes: 1 addition & 1 deletion scripts/chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const fs = require('fs');

const scenarios = ['baseline', 'dev', 'bundling-app', 'bundling-full', 'treeshaking-rollup', 'treeshaking-webpack', 'aot-closure', 'aot-rollup', 'aot-systemjs', 'aot-webpack', 'lazyloading-systemjs', 'lazyloading-webpack', 'prerender'];
const scenarios = ['baseline', 'dev', 'bundling-app', 'bundling-full', 'treeshaking-rollup', 'treeshaking-webpack', 'aot-closure', 'aot-rollup', 'aot-systemjs', 'aot-webpack', 'lazyloading-closure', 'lazyloading-systemjs', 'lazyloading-webpack', 'prerender'];

var score = {};
var size = {};
Expand Down
1 change: 1 addition & 0 deletions scripts/lighthouse.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ node ./scripts/lighthouse.js aot-closure
node ./scripts/lighthouse.js aot-rollup
node ./scripts/lighthouse.js aot-systemjs
node ./scripts/lighthouse.js aot-webpack
node ./scripts/lighthouse.js lazyloading-closure
node ./scripts/lighthouse.js lazyloading-systemjs
node ./scripts/lighthouse.js lazyloading-webpack
node ./scripts/lighthouse.js prerender
Expand Down
2 changes: 1 addition & 1 deletion scripts/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const perf = require('../tmp/perf.json');
const minPerf = avgArray(perf['baseline']);
const maxPerf = avgArray(perf['bundling-full']);

var scenarios = ['baseline', 'dev', 'bundling-app', 'bundling-full', 'treeshaking-rollup', 'treeshaking-webpack', 'aot-closure', 'aot-rollup', 'aot-systemjs', 'aot-webpack', 'lazyloading-systemjs', 'lazyloading-webpack', 'prerender'];
var scenarios = ['baseline', 'dev', 'bundling-app', 'bundling-full', 'treeshaking-rollup', 'treeshaking-webpack', 'aot-closure', 'aot-rollup', 'aot-systemjs', 'aot-webpack', 'lazyloading-closure', 'lazyloading-systemjs', 'lazyloading-webpack', 'prerender'];

var report = {};

Expand Down
1 change: 1 addition & 0 deletions scripts/vendors.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ cp dist/vendors_with_system.js dist/lazyloading-systemjs
cp dist/vendors.js dist/aot-closure
cp dist/vendors.js dist/aot-rollup
cp dist/vendors.js dist/aot-webpack
cp dist/vendors.js dist/lazyloading-closure
cp dist/vendors.js dist/lazyloading-webpack
cp dist/vendors.js dist/treeshaking-rollup
cp dist/vendors.js dist/treeshaking-webpack
Expand Down
2 changes: 1 addition & 1 deletion test/integration_spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {by, protractor, By} from 'protractor';
var fs = require('fs');

var scenarios = ['baseline', 'dev', 'bundling-app', 'bundling-full', 'treeshaking-rollup', 'treeshaking-webpack', 'aot-closure', 'aot-rollup', 'aot-systemjs', 'aot-webpack', 'lazyloading-systemjs', 'lazyloading-webpack', 'prerender'];
var scenarios = ['baseline', 'dev', 'bundling-app', 'bundling-full', 'treeshaking-rollup', 'treeshaking-webpack', 'aot-closure', 'aot-rollup', 'aot-systemjs', 'aot-webpack', 'lazyloading-closure', 'lazyloading-systemjs', 'lazyloading-webpack', 'prerender'];
var report = {};

scenarios.forEach((scenario) => {
Expand Down
28 changes: 28 additions & 0 deletions tsconfig-closure-lazy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es6",
"module": "es2015",
"moduleResolution": "node",
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"baseUrl": ".",
"stripInternal": true,
"outDir": "./tmp/es6",
"rootDir": ".",
"sourceMap": true,
"inlineSources": true,
"skipLibCheck": true,
"lib": ["es2015", "dom"]
},
"files": [
"./app/main.lazy.ts",
"./app/list/list.module.ngfactory.ts",
"./app/search/search.module.ngfactory.ts",
"./app/subscribe/subscribe.module.ngfactory.ts"
],
"angularCompilerOptions": {
"annotationsAs": "static fields",
"annotateForClosureCompiler": true
}
}