Chromium Code Reviews| 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; |