Index: pkg/polymer/lib/src/build/script_compactor.dart |
diff --git a/pkg/polymer/lib/src/build/script_compactor.dart b/pkg/polymer/lib/src/build/script_compactor.dart |
index e869a2cb55c253bf7ffcc6f26bde5078241dcec8..4392f4d070a7d6cd581a6d9e0fee623ec811b557 100644 |
--- a/pkg/polymer/lib/src/build/script_compactor.dart |
+++ b/pkg/polymer/lib/src/build/script_compactor.dart |
@@ -8,9 +8,15 @@ library polymer.src.build.script_compactor; |
import 'dart:async'; |
import 'dart:convert'; |
+import 'package:analyzer/src/generated/ast.dart'; |
+import 'package:analyzer/src/generated/error.dart'; |
+import 'package:analyzer/src/generated/java_core.dart' show CharSequence; |
+import 'package:analyzer/src/generated/parser.dart'; |
+import 'package:analyzer/src/generated/scanner.dart'; |
import 'package:barback/barback.dart'; |
import 'package:html5lib/parser.dart' show parseFragment; |
import 'package:path/path.dart' as path; |
+import 'package:source_maps/span.dart' show SourceFile; |
import 'code_extractor.dart'; // import just for documentation. |
import 'common.dart'; |
@@ -77,6 +83,7 @@ class ScriptCompactor extends Transformer with PolymerTransformer { |
return null; |
} |
+ // Emit the bootstrap .dart file |
var bootstrapId = id.addExtension('_bootstrap.dart'); |
mainScriptTag.attributes['src'] = |
path.url.basename(bootstrapId.path); |
@@ -92,20 +99,159 @@ class ScriptCompactor extends Transformer with PolymerTransformer { |
buffer..write('\n') |
..writeln('void main() {') |
- ..writeln(' configureForDeployment([') |
- ..writeAll(urls.map((url) => " '$url',\n")) |
- ..writeln(' ]);') |
- ..writeln(' i${i - 1}.main();') |
- ..writeln('}'); |
- |
- transform.addOutput(new Asset.fromString( |
- bootstrapId, buffer.toString())); |
- transform.addOutput(new Asset.fromString(id, document.outerHtml)); |
+ ..writeln(' configureForDeployment(['); |
+ |
+ // Inject @CustomTag and @initMethod initializations for each library |
+ // that is sourced in a script tag. |
+ i = 0; |
+ return Future.forEach(libraries, (lib) { |
+ return _initializersOf(lib, transform, logger).then((initializers) { |
+ for (var init in initializers) { |
+ var code = init.asCode('i$i'); |
+ buffer.write(" $code,\n"); |
+ } |
+ i++; |
+ }); |
+ }).then((_) { |
+ buffer..writeln(' ]);') |
+ ..writeln(' i${urls.length - 1}.main();') |
+ ..writeln('}'); |
+ |
+ transform.addOutput(new Asset.fromString( |
+ bootstrapId, buffer.toString())); |
+ transform.addOutput(new Asset.fromString(id, document.outerHtml)); |
+ }); |
+ }); |
+ }); |
+ } |
+ |
+ /** |
+ * Computes the initializers of [dartLibrary]. That is, a closure that calls |
+ * Polymer.register for each @CustomTag, and any public top-level methods |
+ * labeled with @initMethod. |
+ */ |
+ Future<List<_Initializer>> _initializersOf( |
+ AssetId dartLibrary, Transform transform, TransformLogger logger) { |
+ var initializers = []; |
+ return transform.readInputAsString(dartLibrary).then((code) { |
+ var url = dartLibrary.path.startsWith('lib/') |
Jennifer Messerly
2014/02/03 19:09:32
refactor this into a function?
Siggi Cherem (dart-lang)
2014/02/04 00:20:35
Done.
|
+ ? 'package:${dartLibrary.package}/${dartLibrary.path.substring(4)}' |
+ : dartLibrary.path; |
+ var file = new SourceFile.text(url, code); |
+ var unit = _parseCompilationUnit(code); |
+ |
+ return Future.forEach(unit.directives, (directive) { |
+ // Include anything from parts. |
+ if (directive is PartDirective) { |
+ var targetId = resolve(dartLibrary, directive.uri.stringValue, |
+ logger, _getSpan(file, directive), allowPackageColonUrls: true); |
+ return _initializersOf(targetId, transform, logger) |
+ .then(initializers.addAll); |
+ } |
+ |
+ // Similarly, include anything from exports except what's filtered by |
+ // the show/hide combinators. |
Jennifer Messerly
2014/02/03 19:09:32
hmm. This seems to be reimplementing top level nam
Siggi Cherem (dart-lang)
2014/02/04 00:20:35
I hear you - I've added a TODO. I'm afraid of usin
|
+ if (directive is ExportDirective) { |
+ var targetId = resolve(dartLibrary, directive.uri.stringValue, |
Jennifer Messerly
2014/02/03 19:09:32
idea: pull this out into a separate method? e.g. _
Siggi Cherem (dart-lang)
2014/02/04 00:20:35
Funny, I thought of it too, but wasn't sure it was
|
+ logger, _getSpan(file, directive), allowPackageColonUrls: true); |
+ return _initializersOf(targetId, transform, logger).then((result) { |
+ for (var combinator in directive.combinators) { |
+ if (combinator is ShowCombinator) { |
+ var show = combinator.shownNames.map((n) => n.name).toSet(); |
+ result.retainWhere((e) => show.contains(e.symbolName)); |
+ } else if (combinator is HideCombinator) { |
+ var hide = combinator.hiddenNames.map((n) => n.name).toSet(); |
+ result.removeWhere((e) => hide.contains(e.symbolName)); |
+ } |
+ } |
+ initializers.addAll(result); |
+ }); |
+ } |
+ }).then((_) { |
+ // Scan the code for classes and top-level functions. |
+ for (var node in unit.declarations) { |
Jennifer Messerly
2014/02/03 19:09:32
is it possible for the library to conflict with an
Siggi Cherem (dart-lang)
2014/02/04 00:20:35
yeah, I think that would be caught as a Dart error
|
+ if (node is ClassDeclaration) { |
+ for (var meta in node.metadata) { |
+ if (!_isCustomTagAnnotation(meta)) continue; |
+ var args = meta.arguments.arguments; |
+ if (args == null || args.length == 0) { |
+ logger.error('Missing argument in @CustomTag annotation', |
+ span: _getSpan(file, meta)); |
+ continue; |
+ } |
+ |
+ var tagName = args[0].stringValue; |
+ var typeName = node.name.name; |
+ if (typeName.startsWith('_')) { |
+ logger.error('@CustomTag is no longer supported on private ' |
+ 'classes: $tagName', span: _getSpan(file, node.name)); |
+ continue; |
+ } |
+ initializers.add(new _CustomTagInitializer(tagName, typeName)); |
+ } |
+ } else if (node is FunctionDeclaration && |
+ node.metadata.any(_isInitMethodAnnotation)) { |
+ var methodName = node.name.name; |
+ if (methodName.startsWith('_')) { |
+ logger.error('@initMethod is no longer supported on private ' |
+ 'functions: $methodName', span: _getSpan(file, node.name)); |
+ continue; |
+ } |
+ initializers.add(new _InitMethodInitializer(methodName)); |
+ } |
+ } |
+ return initializers; |
}); |
}); |
} |
} |
+/** Parse [code] using analyzer. */ |
+CompilationUnit _parseCompilationUnit(String code) { |
+ var errorListener = new _ErrorCollector(); |
+ var reader = new CharSequenceReader(new CharSequence(code)); |
+ var scanner = new Scanner(null, reader, errorListener); |
+ var token = scanner.tokenize(); |
+ var parser = new Parser(null, errorListener); |
+ return parser.parseCompilationUnit(token); |
+} |
+ |
+class _ErrorCollector extends AnalysisErrorListener { |
+ final errors = <AnalysisError>[]; |
+ onError(error) => errors.add(error); |
+} |
+ |
+// TODO(sigmund): consider support for importing annotations with prefixes. |
+bool _isInitMethodAnnotation(Annotation node) => |
+ node.name.name == 'initMethod' && node.constructorName == null && |
+ node.arguments == null; |
+bool _isCustomTagAnnotation(Annotation node) => node.name.name == 'CustomTag'; |
+ |
+abstract class _Initializer { |
+ String get symbolName; |
+ String asCode(String prefix); |
+} |
+ |
+class _InitMethodInitializer implements _Initializer { |
+ String methodName; |
+ String get symbolName => methodName; |
+ _InitMethodInitializer(this.methodName); |
+ |
+ String asCode(String prefix) => "$prefix.$methodName"; |
+} |
+ |
+class _CustomTagInitializer implements _Initializer { |
+ String tagName; |
+ String typeName; |
+ String get symbolName => typeName; |
+ _CustomTagInitializer(this.tagName, this.typeName); |
+ |
+ String asCode(String prefix) => |
+ "() => Polymer.register('$tagName', $prefix.$typeName)"; |
+} |
+ |
+_getSpan(SourceFile file, ASTNode node) => file.span(node.offset, node.end); |
+ |
const MAIN_HEADER = """ |
library app_bootstrap; |