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 }); |
104 }); | 124 }); |
105 }); | 125 }); |
106 } | 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 file = new SourceFile.text(_simpleUriForSource(dartLibrary), code); |
| 138 var unit = _parseCompilationUnit(code); |
| 139 |
| 140 return Future.forEach(unit.directives, (directive) { |
| 141 // Include anything from parts. |
| 142 if (directive is PartDirective) { |
| 143 var targetId = resolve(dartLibrary, directive.uri.stringValue, |
| 144 logger, _getSpan(file, directive)); |
| 145 return _initializersOf(targetId, transform, logger) |
| 146 .then(initializers.addAll); |
| 147 } |
| 148 |
| 149 // Similarly, include anything from exports except what's filtered by |
| 150 // the show/hide combinators. |
| 151 if (directive is ExportDirective) { |
| 152 var targetId = resolve(dartLibrary, directive.uri.stringValue, |
| 153 logger, _getSpan(file, directive)); |
| 154 return _initializersOf(targetId, transform, logger) |
| 155 .then((r) => _processExportDirective(directive, r, initializers)); |
| 156 } |
| 157 }).then((_) { |
| 158 // Scan the code for classes and top-level functions. |
| 159 for (var node in unit.declarations) { |
| 160 if (node is ClassDeclaration) { |
| 161 _processClassDeclaration(node, initializers, file, logger); |
| 162 } else if (node is FunctionDeclaration && |
| 163 node.metadata.any(_isInitMethodAnnotation)) { |
| 164 _processFunctionDeclaration(node, initializers, file, logger); |
| 165 } |
| 166 } |
| 167 return initializers; |
| 168 }); |
| 169 }); |
| 170 } |
| 171 |
| 172 static String _simpleUriForSource(AssetId source) => |
| 173 source.path.startsWith('lib/') |
| 174 ? 'package:${source.package}/${source.path.substring(4)}' : source.path; |
| 175 |
| 176 /** |
| 177 * Filter [exportedInitializers] according to [directive]'s show/hide |
| 178 * combinators and add the result to [initializers]. |
| 179 */ |
| 180 // TODO(sigmund): call the analyzer's resolver instead? |
| 181 static _processExportDirective(ExportDirective directive, |
| 182 List<_Initializer> exportedInitializers, |
| 183 List<_Initializer> initializers) { |
| 184 for (var combinator in directive.combinators) { |
| 185 if (combinator is ShowCombinator) { |
| 186 var show = combinator.shownNames.map((n) => n.name).toSet(); |
| 187 exportedInitializers.retainWhere((e) => show.contains(e.symbolName)); |
| 188 } else if (combinator is HideCombinator) { |
| 189 var hide = combinator.hiddenNames.map((n) => n.name).toSet(); |
| 190 exportedInitializers.removeWhere((e) => hide.contains(e.symbolName)); |
| 191 } |
| 192 } |
| 193 initializers.addAll(exportedInitializers); |
| 194 } |
| 195 |
| 196 /** |
| 197 * Add an initializer to register [node] as a polymer element if it contains |
| 198 * an appropriate [CustomTag] annotation. |
| 199 */ |
| 200 static _processClassDeclaration(ClassDeclaration node, |
| 201 List<_Initializer> initializers, SourceFile file, |
| 202 TransformLogger logger) { |
| 203 for (var meta in node.metadata) { |
| 204 if (!_isCustomTagAnnotation(meta)) continue; |
| 205 var args = meta.arguments.arguments; |
| 206 if (args == null || args.length == 0) { |
| 207 logger.error('Missing argument in @CustomTag annotation', |
| 208 span: _getSpan(file, meta)); |
| 209 continue; |
| 210 } |
| 211 |
| 212 var tagName = args[0].stringValue; |
| 213 var typeName = node.name.name; |
| 214 if (typeName.startsWith('_')) { |
| 215 logger.error('@CustomTag is no longer supported on private ' |
| 216 'classes: $tagName', span: _getSpan(file, node.name)); |
| 217 continue; |
| 218 } |
| 219 initializers.add(new _CustomTagInitializer(tagName, typeName)); |
| 220 } |
| 221 } |
| 222 |
| 223 /** a method initializer for [function]. */ |
| 224 static _processFunctionDeclaration(FunctionDeclaration function, |
| 225 List<_Initializer> initializers, SourceFile file, |
| 226 TransformLogger logger) { |
| 227 var name = function.name.name; |
| 228 if (name.startsWith('_')) { |
| 229 logger.error('@initMethod is no longer supported on private ' |
| 230 'functions: $name', span: _getSpan(file, function.name)); |
| 231 return; |
| 232 } |
| 233 initializers.add(new _InitMethodInitializer(name)); |
| 234 } |
107 } | 235 } |
108 | 236 |
| 237 /** Parse [code] using analyzer. */ |
| 238 CompilationUnit _parseCompilationUnit(String code) { |
| 239 var errorListener = new _ErrorCollector(); |
| 240 var reader = new CharSequenceReader(new CharSequence(code)); |
| 241 var scanner = new Scanner(null, reader, errorListener); |
| 242 var token = scanner.tokenize(); |
| 243 var parser = new Parser(null, errorListener); |
| 244 return parser.parseCompilationUnit(token); |
| 245 } |
| 246 |
| 247 class _ErrorCollector extends AnalysisErrorListener { |
| 248 final errors = <AnalysisError>[]; |
| 249 onError(error) => errors.add(error); |
| 250 } |
| 251 |
| 252 // TODO(sigmund): consider support for importing annotations with prefixes. |
| 253 bool _isInitMethodAnnotation(Annotation node) => |
| 254 node.name.name == 'initMethod' && node.constructorName == null && |
| 255 node.arguments == null; |
| 256 bool _isCustomTagAnnotation(Annotation node) => node.name.name == 'CustomTag'; |
| 257 |
| 258 abstract class _Initializer { |
| 259 String get symbolName; |
| 260 String asCode(String prefix); |
| 261 } |
| 262 |
| 263 class _InitMethodInitializer implements _Initializer { |
| 264 String methodName; |
| 265 String get symbolName => methodName; |
| 266 _InitMethodInitializer(this.methodName); |
| 267 |
| 268 String asCode(String prefix) => "$prefix.$methodName"; |
| 269 } |
| 270 |
| 271 class _CustomTagInitializer implements _Initializer { |
| 272 String tagName; |
| 273 String typeName; |
| 274 String get symbolName => typeName; |
| 275 _CustomTagInitializer(this.tagName, this.typeName); |
| 276 |
| 277 String asCode(String prefix) => |
| 278 "() => Polymer.register('$tagName', $prefix.$typeName)"; |
| 279 } |
| 280 |
| 281 _getSpan(SourceFile file, ASTNode node) => file.span(node.offset, node.end); |
| 282 |
109 const MAIN_HEADER = """ | 283 const MAIN_HEADER = """ |
110 library app_bootstrap; | 284 library app_bootstrap; |
111 | 285 |
112 import 'package:polymer/polymer.dart'; | 286 import 'package:polymer/polymer.dart'; |
113 """; | 287 """; |
OLD | NEW |