Skip to content

Commit 5cc84ed

Browse files
committed
feat(transformers): implement initializing deferred libraries
Implement deferred libraries to work with dependency injection and other angular codegen. This is done by not initializing the library in the parent ng_deps file when it is declared as deferred, rewriting the import and, chaining a future that initializes the library in any files that are using deferred libraries which need angular codegen.
1 parent 2f08ed8 commit 5cc84ed

File tree

46 files changed

+459
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+459
-2
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
library angular2.transform.deferred_rewriter.rewriter;
2+
3+
import 'dart:async';
4+
5+
import 'package:analyzer/analyzer.dart';
6+
import 'package:analyzer/src/generated/ast.dart';
7+
import 'package:angular2/src/transform/common/asset_reader.dart';
8+
import 'package:angular2/src/transform/common/logging.dart';
9+
import 'package:angular2/src/transform/common/names.dart';
10+
import 'package:barback/barback.dart';
11+
import 'package:code_transformers/assets.dart';
12+
import 'package:quiver/iterables.dart' as it;
13+
14+
class Rewriter {
15+
final AssetId _entryPoint;
16+
final AssetReader _reader;
17+
18+
Rewriter(this._entryPoint, this._reader);
19+
20+
/// Rewrites the provided code by finding all the deferred library imports
21+
/// and loadLibrary invocations. Then it removes any libraries that don't
22+
/// require angular codegen. For the remaining libraries it rewrites the
23+
/// import to the corresponding ng_dep file, and chains a future which
24+
/// first initializes the library.
25+
///
26+
/// To the extent possible, this method does not change line numbers or
27+
/// offsets in the provided code to facilitate debugging via source maps.
28+
Future<String> rewrite() async {
29+
var code = await _reader.readAsString(_entryPoint);
30+
var node = parseCompilationUnit(code);
31+
if (node == null) return null;
32+
33+
var visitor = new _FindDeferredLibraries(_reader, _entryPoint);
34+
node.accept(visitor);
35+
// Look to see if we found any deferred libraries
36+
if (!visitor.hasDeferredLibrariesToRewrite()) return null;
37+
// Remove any libraries that don't need angular codegen.
38+
await visitor.cull();
39+
// Check again if there are any deferred libraries.
40+
if (!visitor.hasDeferredLibrariesToRewrite()) return null;
41+
42+
var compare = (AstNode a, AstNode b) => a.offset - b.offset;
43+
visitor.deferredImports.sort(compare);
44+
visitor.loadLibraryInvocations.sort(compare);
45+
46+
var buf = new StringBuffer();
47+
var idx = visitor.deferredImports.fold(0,
48+
(int lastIdx, ImportDirective node) {
49+
buf.write(code.substring(lastIdx, node.offset));
50+
51+
var import = code.substring(node.offset, node.end);
52+
buf.write(import.replaceFirst('.dart', DEPS_EXTENSION));
53+
return node.end;
54+
});
55+
56+
idx = visitor.loadLibraryInvocations.fold(idx,
57+
(int lastIdx, MethodInvocation node) {
58+
buf.write(code.substring(lastIdx, node.offset));
59+
var value = node.realTarget as SimpleIdentifier;
60+
var prefix = value.name;
61+
// Chain a future that initializes the reflector.
62+
buf.write('$prefix.loadLibrary().then((_) {$prefix.initReflector();})');
63+
return node.end;
64+
});
65+
if (idx < code.length) buf.write(code.substring(idx));
66+
return '$buf';
67+
}
68+
}
69+
70+
/// Visitor responsible for finding the deferred libraries that need angular
71+
/// codegen. Those are the libraries that are loaded deferred and have a
72+
/// corresponding ng_deps file.
73+
class _FindDeferredLibraries extends Object with RecursiveAstVisitor<Object> {
74+
var deferredImports = new List<ImportDirective>();
75+
var loadLibraryInvocations = new List<MethodInvocation>();
76+
final AssetReader _reader;
77+
final AssetId _entryPoint;
78+
79+
_FindDeferredLibraries(this._reader, this._entryPoint);
80+
81+
@override
82+
Object visitImportDirective(ImportDirective node) {
83+
if (node.deferredKeyword != null) {
84+
deferredImports.add(node);
85+
}
86+
return null;
87+
}
88+
89+
@override
90+
Object visitMethodInvocation(MethodInvocation node) {
91+
if (node.methodName.name == 'loadLibrary') {
92+
loadLibraryInvocations.add(node);
93+
}
94+
return super.visitMethodInvocation(node);
95+
}
96+
97+
bool hasDeferredLibrariesToRewrite() {
98+
if (deferredImports.isEmpty) {
99+
logger.fine('There are no deferred library imports.');
100+
return false;
101+
}
102+
if (loadLibraryInvocations.isEmpty) {
103+
logger.fine(
104+
'There are no loadLibrary invocations that need to be rewritten.');
105+
return false;
106+
}
107+
return true;
108+
}
109+
110+
// Remove all deferredImports that do not have a ng_dep file
111+
// then remove all loadLibrary invocations that are not in the set of
112+
// prefixes that are left.
113+
Future cull() async {
114+
// Determine whether a deferred import has ng_deps.
115+
var hasInputs = await Future.wait(deferredImports
116+
.map((import) => stringLiteralToString(import.uri))
117+
.map((uri) => toDepsExtension(uri))
118+
.map((depsUri) => uriToAssetId(_entryPoint, depsUri, logger, null,
119+
errorOnAbsolute: false))
120+
.map((asset) => _reader.hasInput(asset)));
121+
122+
// Filter out any deferred imports that do not have ng_deps.
123+
deferredImports = it
124+
.zip([deferredImports, hasInputs])
125+
.where((importHasInput) => importHasInput[1])
126+
.map((importHasInput) => importHasInput[0])
127+
.toList();
128+
129+
// Find the set of prefixes which have ng_deps.
130+
var prefixes =
131+
new Set.from(deferredImports.map((import) => import.prefix.name));
132+
133+
// Filters out any load library invocations where the prefix is not a known
134+
// library with ng_deps.
135+
loadLibraryInvocations = loadLibraryInvocations.where((library) {
136+
var value = library.realTarget as SimpleIdentifier;
137+
return prefixes.contains(value.name);
138+
}).toList();
139+
140+
return;
141+
}
142+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
library angular2.transform.deferred_rewriter.transformer;
2+
3+
import 'dart:async';
4+
5+
import 'package:angular2/src/transform/common/asset_reader.dart';
6+
import 'package:angular2/src/transform/common/logging.dart' as log;
7+
import 'package:angular2/src/transform/common/names.dart';
8+
import 'package:angular2/src/transform/common/options.dart';
9+
import 'package:barback/barback.dart';
10+
11+
import 'rewriter.dart';
12+
13+
/// Transformer responsible for rewriting deferred library loads to enable
14+
/// initializing the reflector in a deferred way to keep the code with the
15+
/// deferred library.
16+
class DeferredRewriter extends Transformer {
17+
final TransformerOptions options;
18+
19+
DeferredRewriter(this.options);
20+
21+
@override
22+
bool isPrimary(AssetId id) =>
23+
id.extension.endsWith('dart') && !id.path.endsWith(DEPS_EXTENSION);
24+
25+
@override
26+
Future apply(Transform transform) async {
27+
log.init(transform);
28+
29+
try {
30+
var asset = transform.primaryInput;
31+
var reader = new AssetReader.fromTransform(transform);
32+
var transformedCode = await rewriteDeferredLibraries(reader, asset.id);
33+
if (transformedCode != null) {
34+
transform.addOutput(
35+
new Asset.fromString(transform.primaryInput.id, transformedCode));
36+
}
37+
} catch (ex, stackTrace) {
38+
log.logger.warning('Rewritting deferred libraries failed.\n'
39+
'Exception: $ex\n'
40+
'Stack Trace: $stackTrace');
41+
}
42+
}
43+
}
44+
45+
// Visible for testing
46+
Future<String> rewriteDeferredLibraries(AssetReader reader, AssetId id) async {
47+
var rewriter = new Rewriter(id, reader);
48+
return await rewriter.rewrite();
49+
}

modules/angular2/src/transform/directive_processor/rewriter.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
9393
void _maybeWriteImport() {
9494
if (_wroteBaseLibImport) return;
9595
_wroteBaseLibImport = true;
96-
writer.print('''import '${path.basename(assetId.path)}';''');
96+
var origDartFile = path.basename(assetId.path);
97+
writer.print('''import '$origDartFile';''');
98+
writer.print('''export '$origDartFile';''');
9799
writer.print("import '$_REFLECTOR_IMPORT' as $_REF_PREFIX;");
98100
}
99101

@@ -106,6 +108,11 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
106108
Object visitImportDirective(ImportDirective node) {
107109
_maybeWriteImport();
108110
_updateUsesNonLangLibs(node);
111+
// Ignore deferred imports here so as to not load the deferred libraries
112+
// code in the current library causing much of the code to not be
113+
// deferred. Instead `DeferredRewriter` will rewrite the code as to load
114+
// `ng_deps` in a deferred way.
115+
if (node.deferredKeyword != null) return null;
109116
return node.accept(_copyVisitor);
110117
}
111118

modules/angular2/src/transform/transformer.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ library angular2.transform;
33
import 'package:barback/barback.dart';
44
import 'package:dart_style/dart_style.dart';
55

6+
import 'deferred_rewriter/transformer.dart';
67
import 'directive_linker/transformer.dart';
78
import 'directive_metadata_extractor/transformer.dart';
89
import 'directive_processor/transformer.dart';
@@ -29,7 +30,11 @@ class AngularTransformerGroup extends TransformerGroup {
2930
phases.addAll(new List.generate(
3031
options.optimizationPhases, (_) => [new EmptyNgDepsRemover()]));
3132
phases.addAll([
32-
[new DirectiveLinker(), new DirectiveMetadataExtractor()],
33+
[
34+
new DirectiveLinker(),
35+
new DirectiveMetadataExtractor(),
36+
new DeferredRewriter(options)
37+
],
3338
[new BindGenerator(options)],
3439
[new TemplateCompiler(options)]
3540
]);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
library angular2.test.transform.deferred_rewriter.all_tests;
2+
3+
import 'package:barback/barback.dart';
4+
import 'package:angular2/src/transform/deferred_rewriter/transformer.dart';
5+
import 'package:angular2/src/transform/common/annotation_matcher.dart';
6+
import 'package:angular2/src/transform/common/asset_reader.dart';
7+
import 'package:angular2/src/transform/common/logging.dart' as log;
8+
import 'package:code_transformers/messages/build_logger.dart';
9+
import 'package:dart_style/dart_style.dart';
10+
import 'package:guinness/guinness.dart';
11+
import 'package:path/path.dart' as path;
12+
import '../common/read_file.dart';
13+
14+
var formatter = new DartFormatter();
15+
16+
main() {
17+
allTests();
18+
}
19+
20+
void allTests() {
21+
_testRewriteDeferredLibraries(
22+
'should return null when no deferred libraries found.',
23+
'no_deferred_libraries/index.dart');
24+
_testRewriteDeferredLibraries(
25+
'should return null when deferred libraries with no ng_deps.',
26+
'no_ng_deps_libraries/index.dart');
27+
_testRewriteDeferredLibraries(
28+
'should rewrite deferred libraries with ng_deps.',
29+
'simple_deferred_example/index.dart');
30+
_testRewriteDeferredLibraries(
31+
'should not rewrite deferred libraries without ng_deps.',
32+
'deferred_example_no_ng_deps/index.dart');
33+
_testRewriteDeferredLibraries(
34+
'should rewrite deferred libraries with ng_deps leave other deferred library alone.',
35+
'complex_deferred_example/index.dart');
36+
}
37+
38+
void _testRewriteDeferredLibraries(String name, String inputPath) {
39+
it(name, () async {
40+
var inputId = _assetIdForPath(inputPath);
41+
var reader = new TestAssetReader();
42+
var expectedPath = path.join(
43+
path.dirname(inputPath), 'expected', path.basename(inputPath));
44+
var expectedId = _assetIdForPath(expectedPath);
45+
46+
var output = await rewriteDeferredLibraries(reader, inputId);
47+
var input = await reader.readAsString(expectedId);
48+
if (input == null) {
49+
// Null input signals no output. Ensure that is true.
50+
expect(output).toBeNull();
51+
} else {
52+
expect(formatter.format(output)).toEqual(formatter.format(input));
53+
}
54+
});
55+
}
56+
57+
AssetId _assetIdForPath(String path) =>
58+
new AssetId('angular2', 'test/transform/deferred_rewriter/$path');
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
library web_foo;
2+
3+
import 'package:angular2/src/core/application.dart';
4+
import 'package:angular2/src/reflection/reflection.dart';
5+
import 'package:angular2/src/reflection/reflection_capabilities.dart';
6+
import 'hello.ng_deps.dart' deferred as a; // ng_deps. Should be rewritten.
7+
import 'b.dart' deferred as b; // No ng_deps. Shouldn't be rewritten.
8+
9+
void main() {
10+
reflector.reflectionCapabilities = new ReflectionCapabilities();
11+
a.loadLibrary().then((_) {
12+
a.initReflector();
13+
}).then((_) {
14+
bootstrap(a.HelloCmp);
15+
});
16+
b.loadLibrary();
17+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
library examples.src.hello_world.absolute_url_expression_files;
2+
3+
import 'package:angular2/angular2.dart'
4+
show bootstrap, Component, Directive, View, NgElement;
5+
6+
@Component(selector: 'hello-app')
7+
@View(templateUrl: 'package:other_package/template.html')
8+
class HelloCmp {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
library examples.src.hello_world.absolute_url_expression_files.ng_deps.dart;
2+
3+
import 'hello.dart';
4+
export 'hello.dart';
5+
import 'package:angular2/src/reflection/reflection.dart' as _ngRef;
6+
import 'package:angular2/angular2.dart'
7+
show bootstrap, Component, Directive, View, NgElement;
8+
9+
var _visited = false;
10+
void initReflector() {
11+
if (_visited) return;
12+
_visited = true;
13+
_ngRef.reflector
14+
..registerType(HelloCmp, {
15+
'factory': () => new HelloCmp(),
16+
'parameters': const [],
17+
'annotations': const [
18+
const Component(selector: 'hello-app'),
19+
const View(
20+
template: r'''{{greeting}}''',
21+
templateUrl: r'package:other_package/template.html')
22+
]
23+
});
24+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
library web_foo;
2+
3+
import 'package:angular2/src/core/application.dart';
4+
import 'package:angular2/src/reflection/reflection.dart';
5+
import 'package:angular2/src/reflection/reflection_capabilities.dart';
6+
import 'hello.dart' deferred as a; // ng_deps. Should be rewritten.
7+
import 'b.dart' deferred as b; // No ng_deps. Shouldn't be rewritten.
8+
9+
void main() {
10+
reflector.reflectionCapabilities = new ReflectionCapabilities();
11+
a.loadLibrary().then((_) {
12+
bootstrap(a.HelloCmp);
13+
});
14+
b.loadLibrary();
15+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
library examples.src.hello_world.absolute_url_expression_files;
2+
3+
import 'package:angular2/angular2.dart'
4+
show bootstrap, Component, Directive, View, NgElement;
5+
6+
@Component(selector: 'hello-app')
7+
@View(templateUrl: 'package:other_package/template.html')
8+
class HelloCmp {}

0 commit comments

Comments
 (0)