| 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..a9a4d595cba2c24af36233bdf5ebc9dd0aa9be22 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,187 @@ 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 file = new SourceFile.text(_simpleUriForSource(dartLibrary), 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));
|
| + return _initializersOf(targetId, transform, logger)
|
| + .then(initializers.addAll);
|
| + }
|
| +
|
| + // Similarly, include anything from exports except what's filtered by
|
| + // the show/hide combinators.
|
| + if (directive is ExportDirective) {
|
| + var targetId = resolve(dartLibrary, directive.uri.stringValue,
|
| + logger, _getSpan(file, directive));
|
| + return _initializersOf(targetId, transform, logger)
|
| + .then((r) => _processExportDirective(directive, r, initializers));
|
| + }
|
| + }).then((_) {
|
| + // Scan the code for classes and top-level functions.
|
| + for (var node in unit.declarations) {
|
| + if (node is ClassDeclaration) {
|
| + _processClassDeclaration(node, initializers, file, logger);
|
| + } else if (node is FunctionDeclaration &&
|
| + node.metadata.any(_isInitMethodAnnotation)) {
|
| + _processFunctionDeclaration(node, initializers, file, logger);
|
| + }
|
| + }
|
| + return initializers;
|
| + });
|
| + });
|
| + }
|
| +
|
| + static String _simpleUriForSource(AssetId source) =>
|
| + source.path.startsWith('lib/')
|
| + ? 'package:${source.package}/${source.path.substring(4)}' : source.path;
|
| +
|
| + /**
|
| + * Filter [exportedInitializers] according to [directive]'s show/hide
|
| + * combinators and add the result to [initializers].
|
| + */
|
| + // TODO(sigmund): call the analyzer's resolver instead?
|
| + static _processExportDirective(ExportDirective directive,
|
| + List<_Initializer> exportedInitializers,
|
| + List<_Initializer> initializers) {
|
| + for (var combinator in directive.combinators) {
|
| + if (combinator is ShowCombinator) {
|
| + var show = combinator.shownNames.map((n) => n.name).toSet();
|
| + exportedInitializers.retainWhere((e) => show.contains(e.symbolName));
|
| + } else if (combinator is HideCombinator) {
|
| + var hide = combinator.hiddenNames.map((n) => n.name).toSet();
|
| + exportedInitializers.removeWhere((e) => hide.contains(e.symbolName));
|
| + }
|
| + }
|
| + initializers.addAll(exportedInitializers);
|
| + }
|
| +
|
| + /**
|
| + * Add an initializer to register [node] as a polymer element if it contains
|
| + * an appropriate [CustomTag] annotation.
|
| + */
|
| + static _processClassDeclaration(ClassDeclaration node,
|
| + List<_Initializer> initializers, SourceFile file,
|
| + TransformLogger logger) {
|
| + 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));
|
| + }
|
| + }
|
| +
|
| + /** a method initializer for [function]. */
|
| + static _processFunctionDeclaration(FunctionDeclaration function,
|
| + List<_Initializer> initializers, SourceFile file,
|
| + TransformLogger logger) {
|
| + var name = function.name.name;
|
| + if (name.startsWith('_')) {
|
| + logger.error('@initMethod is no longer supported on private '
|
| + 'functions: $name', span: _getSpan(file, function.name));
|
| + return;
|
| + }
|
| + initializers.add(new _InitMethodInitializer(name));
|
| + }
|
| +}
|
| +
|
| +/** 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;
|
|
|
|
|