Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** Transfomer that combines multiple dart script tags into a single one. */ | 5 /** Transfomer that combines multiple dart script tags into a single one. */ |
| 6 library polymer.src.build.script_compactor; | 6 library polymer.src.build.script_compactor; |
| 7 | 7 |
| 8 import 'dart:async'; | 8 import 'dart:async'; |
| 9 import 'dart:convert'; | 9 import 'dart:convert'; |
| 10 | 10 |
| 11 import 'package:analyzer/src/generated/ast.dart'; | |
| 12 import 'package:analyzer/src/generated/error.dart'; | |
| 13 import 'package:analyzer/src/generated/java_core.dart' show CharSequence; | |
| 14 import 'package:analyzer/src/generated/parser.dart'; | |
| 15 import 'package:analyzer/src/generated/scanner.dart'; | |
| 11 import 'package:barback/barback.dart'; | 16 import 'package:barback/barback.dart'; |
| 12 import 'package:html5lib/parser.dart' show parseFragment; | 17 import 'package:html5lib/parser.dart' show parseFragment; |
| 13 import 'package:path/path.dart' as path; | 18 import 'package:path/path.dart' as path; |
| 19 import 'package:source_maps/span.dart' show SourceFile; | |
| 14 | 20 |
| 15 import 'code_extractor.dart'; // import just for documentation. | 21 import 'code_extractor.dart'; // import just for documentation. |
| 16 import 'common.dart'; | 22 import 'common.dart'; |
| 17 | 23 |
| 18 /** | 24 /** |
| 19 * Combines Dart script tags into a single script tag, and creates a new Dart | 25 * Combines Dart script tags into a single script tag, and creates a new Dart |
| 20 * file that calls the main function of each of the original script tags. | 26 * file that calls the main function of each of the original script tags. |
| 21 * | 27 * |
| 22 * This transformer assumes that all script tags point to external files. To | 28 * This transformer assumes that all script tags point to external files. To |
| 23 * support script tags with inlined code, use this transformer after running | 29 * support script tags with inlined code, use this transformer after running |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 70 mainLibraryId = resolve(id, src, logger, tag.sourceSpan); | 76 mainLibraryId = resolve(id, src, logger, tag.sourceSpan); |
| 71 mainScriptTag = tag; | 77 mainScriptTag = tag; |
| 72 } | 78 } |
| 73 | 79 |
| 74 if (mainScriptTag == null) { | 80 if (mainScriptTag == null) { |
| 75 // We didn't find any main library, nothing to do. | 81 // We didn't find any main library, nothing to do. |
| 76 transform.addOutput(transform.primaryInput); | 82 transform.addOutput(transform.primaryInput); |
| 77 return null; | 83 return null; |
| 78 } | 84 } |
| 79 | 85 |
| 86 // Emit the bootstrap .dart file | |
| 80 var bootstrapId = id.addExtension('_bootstrap.dart'); | 87 var bootstrapId = id.addExtension('_bootstrap.dart'); |
| 81 mainScriptTag.attributes['src'] = | 88 mainScriptTag.attributes['src'] = |
| 82 path.url.basename(bootstrapId.path); | 89 path.url.basename(bootstrapId.path); |
| 83 | 90 |
| 84 libraries.add(mainLibraryId); | 91 libraries.add(mainLibraryId); |
| 85 var urls = libraries.map((id) => assetUrlFor(id, bootstrapId, logger)) | 92 var urls = libraries.map((id) => assetUrlFor(id, bootstrapId, logger)) |
| 86 .where((url) => url != null).toList(); | 93 .where((url) => url != null).toList(); |
| 87 var buffer = new StringBuffer()..writeln(MAIN_HEADER); | 94 var buffer = new StringBuffer()..writeln(MAIN_HEADER); |
| 88 int i = 0; | 95 int i = 0; |
| 89 for (; i < urls.length; i++) { | 96 for (; i < urls.length; i++) { |
| 90 buffer.writeln("import '${urls[i]}' as i$i;"); | 97 buffer.writeln("import '${urls[i]}' as i$i;"); |
| 91 } | 98 } |
| 92 | 99 |
| 93 buffer..write('\n') | 100 buffer..write('\n') |
| 94 ..writeln('void main() {') | 101 ..writeln('void main() {') |
| 95 ..writeln(' configureForDeployment([') | 102 ..writeln(' configureForDeployment(['); |
| 96 ..writeAll(urls.map((url) => " '$url',\n")) | |
| 97 ..writeln(' ]);') | |
| 98 ..writeln(' i${i - 1}.main();') | |
| 99 ..writeln('}'); | |
| 100 | 103 |
| 101 transform.addOutput(new Asset.fromString( | 104 // Inject @CustomTag and @initMethod initializations for each library |
| 102 bootstrapId, buffer.toString())); | 105 // that is sourced in a script tag. |
| 103 transform.addOutput(new Asset.fromString(id, document.outerHtml)); | 106 i = 0; |
| 107 return Future.forEach(libraries, (lib) { | |
| 108 return _initializersOf(lib, transform, logger).then((initializers) { | |
| 109 for (var init in initializers) { | |
| 110 var code = init.asCode('i$i'); | |
| 111 buffer.write(" $code,\n"); | |
| 112 } | |
| 113 i++; | |
| 114 }); | |
| 115 }).then((_) { | |
| 116 buffer..writeln(' ]);') | |
| 117 ..writeln(' i${urls.length - 1}.main();') | |
| 118 ..writeln('}'); | |
| 119 | |
| 120 transform.addOutput(new Asset.fromString( | |
| 121 bootstrapId, buffer.toString())); | |
| 122 transform.addOutput(new Asset.fromString(id, document.outerHtml)); | |
| 123 }); | |
| 124 }); | |
| 125 }); | |
| 126 } | |
| 127 | |
| 128 /** | |
| 129 * Computes the initializers of [dartLibrary]. That is, a closure that calls | |
| 130 * Polymer.register for each @CustomTag, and any public top-level methods | |
| 131 * labeled with @initMethod. | |
| 132 */ | |
| 133 Future<List<_Initializer>> _initializersOf( | |
| 134 AssetId dartLibrary, Transform transform, TransformLogger logger) { | |
| 135 var initializers = []; | |
| 136 return transform.readInputAsString(dartLibrary).then((code) { | |
| 137 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.
| |
| 138 ? 'package:${dartLibrary.package}/${dartLibrary.path.substring(4)}' | |
| 139 : dartLibrary.path; | |
| 140 var file = new SourceFile.text(url, code); | |
| 141 var unit = _parseCompilationUnit(code); | |
| 142 | |
| 143 return Future.forEach(unit.directives, (directive) { | |
| 144 // Include anything from parts. | |
| 145 if (directive is PartDirective) { | |
| 146 var targetId = resolve(dartLibrary, directive.uri.stringValue, | |
| 147 logger, _getSpan(file, directive), allowPackageColonUrls: true); | |
| 148 return _initializersOf(targetId, transform, logger) | |
| 149 .then(initializers.addAll); | |
| 150 } | |
| 151 | |
| 152 // Similarly, include anything from exports except what's filtered by | |
| 153 // 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
| |
| 154 if (directive is ExportDirective) { | |
| 155 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
| |
| 156 logger, _getSpan(file, directive), allowPackageColonUrls: true); | |
| 157 return _initializersOf(targetId, transform, logger).then((result) { | |
| 158 for (var combinator in directive.combinators) { | |
| 159 if (combinator is ShowCombinator) { | |
| 160 var show = combinator.shownNames.map((n) => n.name).toSet(); | |
| 161 result.retainWhere((e) => show.contains(e.symbolName)); | |
| 162 } else if (combinator is HideCombinator) { | |
| 163 var hide = combinator.hiddenNames.map((n) => n.name).toSet(); | |
| 164 result.removeWhere((e) => hide.contains(e.symbolName)); | |
| 165 } | |
| 166 } | |
| 167 initializers.addAll(result); | |
| 168 }); | |
| 169 } | |
| 170 }).then((_) { | |
| 171 // Scan the code for classes and top-level functions. | |
| 172 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
| |
| 173 if (node is ClassDeclaration) { | |
| 174 for (var meta in node.metadata) { | |
| 175 if (!_isCustomTagAnnotation(meta)) continue; | |
| 176 var args = meta.arguments.arguments; | |
| 177 if (args == null || args.length == 0) { | |
| 178 logger.error('Missing argument in @CustomTag annotation', | |
| 179 span: _getSpan(file, meta)); | |
| 180 continue; | |
| 181 } | |
| 182 | |
| 183 var tagName = args[0].stringValue; | |
| 184 var typeName = node.name.name; | |
| 185 if (typeName.startsWith('_')) { | |
| 186 logger.error('@CustomTag is no longer supported on private ' | |
| 187 'classes: $tagName', span: _getSpan(file, node.name)); | |
| 188 continue; | |
| 189 } | |
| 190 initializers.add(new _CustomTagInitializer(tagName, typeName)); | |
| 191 } | |
| 192 } else if (node is FunctionDeclaration && | |
| 193 node.metadata.any(_isInitMethodAnnotation)) { | |
| 194 var methodName = node.name.name; | |
| 195 if (methodName.startsWith('_')) { | |
| 196 logger.error('@initMethod is no longer supported on private ' | |
| 197 'functions: $methodName', span: _getSpan(file, node.name)); | |
| 198 continue; | |
| 199 } | |
| 200 initializers.add(new _InitMethodInitializer(methodName)); | |
| 201 } | |
| 202 } | |
| 203 return initializers; | |
| 104 }); | 204 }); |
| 105 }); | 205 }); |
| 106 } | 206 } |
| 107 } | 207 } |
| 108 | 208 |
| 209 /** Parse [code] using analyzer. */ | |
| 210 CompilationUnit _parseCompilationUnit(String code) { | |
| 211 var errorListener = new _ErrorCollector(); | |
| 212 var reader = new CharSequenceReader(new CharSequence(code)); | |
| 213 var scanner = new Scanner(null, reader, errorListener); | |
| 214 var token = scanner.tokenize(); | |
| 215 var parser = new Parser(null, errorListener); | |
| 216 return parser.parseCompilationUnit(token); | |
| 217 } | |
| 218 | |
| 219 class _ErrorCollector extends AnalysisErrorListener { | |
| 220 final errors = <AnalysisError>[]; | |
| 221 onError(error) => errors.add(error); | |
| 222 } | |
| 223 | |
| 224 // TODO(sigmund): consider support for importing annotations with prefixes. | |
| 225 bool _isInitMethodAnnotation(Annotation node) => | |
| 226 node.name.name == 'initMethod' && node.constructorName == null && | |
| 227 node.arguments == null; | |
| 228 bool _isCustomTagAnnotation(Annotation node) => node.name.name == 'CustomTag'; | |
| 229 | |
| 230 abstract class _Initializer { | |
| 231 String get symbolName; | |
| 232 String asCode(String prefix); | |
| 233 } | |
| 234 | |
| 235 class _InitMethodInitializer implements _Initializer { | |
| 236 String methodName; | |
| 237 String get symbolName => methodName; | |
| 238 _InitMethodInitializer(this.methodName); | |
| 239 | |
| 240 String asCode(String prefix) => "$prefix.$methodName"; | |
| 241 } | |
| 242 | |
| 243 class _CustomTagInitializer implements _Initializer { | |
| 244 String tagName; | |
| 245 String typeName; | |
| 246 String get symbolName => typeName; | |
| 247 _CustomTagInitializer(this.tagName, this.typeName); | |
| 248 | |
| 249 String asCode(String prefix) => | |
| 250 "() => Polymer.register('$tagName', $prefix.$typeName)"; | |
| 251 } | |
| 252 | |
| 253 _getSpan(SourceFile file, ASTNode node) => file.span(node.offset, node.end); | |
| 254 | |
| 109 const MAIN_HEADER = """ | 255 const MAIN_HEADER = """ |
| 110 library app_bootstrap; | 256 library app_bootstrap; |
| 111 | 257 |
| 112 import 'package:polymer/polymer.dart'; | 258 import 'package:polymer/polymer.dart'; |
| 113 """; | 259 """; |
| OLD | NEW |