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 |