Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(94)

Side by Side Diff: pkg/polymer/lib/src/build/script_compactor.dart

Issue 189213003: Refactoring two pieces of polymer: (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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;
Siggi Cherem (dart-lang) 2014/03/06 22:45:23 there is no new logic on this file, just code refa
7 7
8 import 'dart:async'; 8 import 'dart:async';
9 import 'dart:convert'; 9 import 'dart:convert';
10 10
11 import 'package:html5lib/dom.dart' show Document, Element;
11 import 'package:analyzer/src/generated/ast.dart'; 12 import 'package:analyzer/src/generated/ast.dart';
12 import 'package:barback/barback.dart'; 13 import 'package:barback/barback.dart';
13 import 'package:path/path.dart' as path; 14 import 'package:path/path.dart' as path;
14 import 'package:source_maps/span.dart' show SourceFile; 15 import 'package:source_maps/span.dart' show SourceFile;
15 16
16 import 'import_inliner.dart' show ImportInliner; // just for docs. 17 import 'import_inliner.dart' show ImportInliner; // just for docs.
17 import 'common.dart'; 18 import 'common.dart';
18 19
19 /// Combines Dart script tags into a single script tag, and creates a new Dart 20 /// 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. 21 /// file that calls the main function of each of the original script tags.
21 /// 22 ///
22 /// This transformer assumes that all script tags point to external files. To 23 /// This transformer assumes that all script tags point to external files. To
23 /// support script tags with inlined code, use this transformer after running 24 /// support script tags with inlined code, use this transformer after running
24 /// [ImportInliner] on an earlier phase. 25 /// [ImportInliner] on an earlier phase.
25 /// 26 ///
26 /// Internally, this transformer will convert each script tag into an import 27 /// Internally, this transformer will convert each script tag into an import
27 /// statement to a library, and then uses `initPolymer` (see polymer.dart) to 28 /// statement to a library, and then uses `initPolymer` (see polymer.dart) to
28 /// process `@initMethod` and `@CustomTag` annotations in those libraries. 29 /// process `@initMethod` and `@CustomTag` annotations in those libraries.
29 class ScriptCompactor extends Transformer with PolymerTransformer { 30 class ScriptCompactor extends Transformer {
30 final TransformOptions options; 31 final TransformOptions options;
31 32
32 ScriptCompactor(this.options); 33 ScriptCompactor(this.options);
33 34
34 /// Only run on entry point .html files. 35 /// Only run on entry point .html files.
35 Future<bool> isPrimary(Asset input) => 36 Future<bool> isPrimary(Asset input) =>
36 new Future.value(options.isHtmlEntryPoint(input.id)); 37 new Future.value(options.isHtmlEntryPoint(input.id));
37 38
38 Future apply(Transform transform) { 39 Future apply(Transform transform) =>
39 var id = transform.primaryInput.id; 40 new _ScriptCompactor(transform, options).apply();
40 var secondaryId = id.addExtension('.scriptUrls'); 41 }
41 var logger = transform.logger;
42 return readPrimaryAsHtml(transform).then((document) {
43 return transform.readInputAsString(secondaryId).then((libraryIds) {
44 var libraries = (JSON.decode(libraryIds) as Iterable).map(
45 (data) => new AssetId.deserialize(data)).toList();
46 var mainLibraryId;
47 var mainScriptTag;
48 bool changed = false;
49 42
50 for (var tag in document.querySelectorAll('script')) { 43 /// Helper class mainly use to flatten the async code.
51 var src = tag.attributes['src']; 44 class _ScriptCompactor extends PolymerTransformer {
Jennifer Messerly 2014/03/06 22:54:17 wow, this is so much more readable
52 if (src == 'packages/polymer/boot.js') { 45 final TransformOptions options;
53 tag.remove(); 46 final Transform transform;
54 continue; 47 final TransformLogger logger;
55 } 48 final AssetId docId;
56 if (tag.attributes['type'] != 'application/dart') continue; 49 final AssetId bootstrapId;
57 if (src == null) {
58 logger.warning('unexpected script without a src url. The '
59 'ScriptCompactor transformer should run after running the '
60 'InlineCodeExtractor', span: tag.sourceSpan);
61 continue;
62 }
63 if (mainLibraryId != null) {
64 logger.warning('unexpected script. Only one Dart script tag '
65 'per document is allowed.', span: tag.sourceSpan);
66 tag.remove();
67 continue;
68 }
69 mainLibraryId = resolve(id, src, logger, tag.sourceSpan);
70 mainScriptTag = tag;
71 }
72 50
73 if (mainScriptTag == null) { 51 Document document;
74 // We didn't find any main library, nothing to do. 52 List<AssetId> entryLibraries;
75 transform.addOutput(transform.primaryInput); 53 AssetId mainLibraryId;
76 return null; 54 Element mainScriptTag;
77 } 55 final Map<AssetId, List<_Initializer>> initializers = {};
78 56
79 // Emit the bootstrap .dart file 57 _ScriptCompactor(Transform transform, this.options)
80 var bootstrapId = id.addExtension('_bootstrap.dart'); 58 : transform = transform,
81 mainScriptTag.attributes['src'] = 59 logger = transform.logger,
82 path.url.basename(bootstrapId.path); 60 docId = transform.primaryInput.id,
61 bootstrapId = transform.primaryInput.id.addExtension('_bootstrap.dart');
83 62
84 libraries.add(mainLibraryId); 63 Future apply() =>
85 var urls = libraries.map((id) => assetUrlFor(id, bootstrapId, logger)) 64 _loadDocument()
86 .where((url) => url != null).toList(); 65 .then(_loadEntryLibraries)
Siggi Cherem (dart-lang) 2014/03/06 22:45:23 one subtle, probably not relevant, difference: thi
87 var buffer = new StringBuffer()..writeln(MAIN_HEADER); 66 .then(_processHtml)
88 int i = 0; 67 .then(_emitNewEntrypoint);
89 for (; i < urls.length; i++) {
90 buffer.writeln("import '${urls[i]}' as i$i;");
91 }
92 68
93 buffer..write('\n') 69 /// Loads the primary input as an html document.
94 ..writeln('void main() {') 70 Future _loadDocument() =>
95 ..writeln(' configureForDeployment(['); 71 readPrimaryAsHtml(transform).then((doc) { document = doc; });
96 72
97 // Inject @CustomTag and @initMethod initializations for each library 73 /// Populates [entryLibraries] as a list containing the asset ids of each
98 // that is sourced in a script tag. 74 /// library loaded on a script tag. The actual work of computing this is done
99 i = 0; 75 /// in an earlier phase and emited in the `entrypoint.scriptUrls` asset.
100 return Future.forEach(libraries, (lib) { 76 Future _loadEntryLibraries(_) =>
101 return _initializersOf(lib, transform, logger).then((initializers) { 77 transform.readInputAsString(docId.addExtension('.scriptUrls'))
102 for (var init in initializers) { 78 .then((libraryIds) {
Jennifer Messerly 2014/03/06 22:54:17 IIRC when I asked the Pub folks, the way they do t
Siggi Cherem (dart-lang) 2014/03/06 23:09:58 interesting. I might prefer the style I had here,
103 var code = init.asCode('i$i'); 79 entryLibraries = (JSON.decode(libraryIds) as Iterable)
104 buffer.write(" $code,\n"); 80 .map((data) => new AssetId.deserialize(data)).toList();
105 } 81 });
106 i++;
107 });
108 }).then((_) {
109 buffer..writeln(' ]);')
110 ..writeln(' i${urls.length - 1}.main();')
111 ..writeln('}');
112 82
113 transform.addOutput(new Asset.fromString( 83 /// Removes unnecessary script tags, and identifies the main entry point Dart
114 bootstrapId, buffer.toString())); 84 /// script tag (if any).
115 transform.addOutput(new Asset.fromString(id, document.outerHtml)); 85 void _processHtml(_) {
116 }); 86 for (var tag in document.querySelectorAll('script')) {
87 var src = tag.attributes['src'];
88 if (src == 'packages/polymer/boot.js') {
89 tag.remove();
90 continue;
91 }
92 if (tag.attributes['type'] != 'application/dart') continue;
93 if (src == null) {
94 logger.warning('unexpected script without a src url. The '
95 'ScriptCompactor transformer should run after running the '
96 'InlineCodeExtractor', span: tag.sourceSpan);
97 continue;
98 }
99 if (mainLibraryId != null) {
100 logger.warning('unexpected script. Only one Dart script tag '
101 'per document is allowed.', span: tag.sourceSpan);
102 tag.remove();
103 continue;
104 }
105 mainLibraryId = resolve(docId, src, logger, tag.sourceSpan);
106 mainScriptTag = tag;
107 }
108 }
109
110 /// Emits the main HTML and Dart bootstrap code for the application. If there
111 /// were not Dart entry point files, then this simply emits the original HTML.
112 Future _emitNewEntrypoint(_) {
113 if (mainScriptTag == null) {
114 // We didn't find any main library, nothing to do.
115 transform.addOutput(transform.primaryInput);
116 return null;
117 }
118
119 // Emit the bootstrap .dart file
120 mainScriptTag.attributes['src'] = path.url.basename(bootstrapId.path);
121 entryLibraries.add(mainLibraryId);
122 return _computeInitializers().then(_createBootstrapCode).then((code) {
123 transform.addOutput(new Asset.fromString(bootstrapId, code));
124 transform.addOutput(new Asset.fromString(docId, document.outerHtml));
125 });
126 }
127
128 /// Emits the actual bootstrap code.
129 String _createBootstrapCode(_) {
130 StringBuffer code = new StringBuffer()..writeln(MAIN_HEADER);
131 for (int i = 0; i < entryLibraries.length; i++) {
132 var url = assetUrlFor(entryLibraries[i], bootstrapId, logger);
133 if (url != null) code.writeln("import '$url' as i$i;");
134 }
135
136 code..write('\n')
137 ..writeln('void main() {')
138 ..writeln(' configureForDeployment([');
139
140 // Inject @CustomTag and @initMethod initializations for each library
141 // that is sourced in a script tag.
142 for (int i = 0; i < entryLibraries.length; i++) {
143 for (var init in initializers[entryLibraries[i]]) {
144 var initCode = init.asCode('i$i');
145 code.write(" $initCode,\n");
146 }
147 }
148 code..writeln(' ]);')
149 ..writeln(' i${entryLibraries.length - 1}.main();')
150 ..writeln('}');
151 return code.toString();
152 }
153
154 /// Computes initializers needed for each library in [entryLibraries]. Results
155 /// are available afterwards in [initializers].
156 Future _computeInitializers() => Future.forEach(entryLibraries, (lib) {
157 return _initializersOf(lib).then((res) {
158 initializers[lib] = res;
117 }); 159 });
118 }); 160 });
119 }
120 161
121 /// Computes the initializers of [dartLibrary]. That is, a closure that calls 162 /// Computes the initializers of [dartLibrary]. That is, a closure that calls
122 /// Polymer.register for each @CustomTag, and any public top-level methods 163 /// Polymer.register for each @CustomTag, and any public top-level methods
123 /// labeled with @initMethod. 164 /// labeled with @initMethod.
124 Future<List<_Initializer>> _initializersOf( 165 Future<List<_Initializer>> _initializersOf(AssetId dartLibrary) {
Siggi Cherem (dart-lang) 2014/03/06 22:45:23 I considered fixing more below, but I might be cha
125 AssetId dartLibrary, Transform transform, TransformLogger logger) { 166 var result = [];
126 var initializers = [];
127 return transform.readInputAsString(dartLibrary).then((code) { 167 return transform.readInputAsString(dartLibrary).then((code) {
128 var file = new SourceFile.text(_simpleUriForSource(dartLibrary), code); 168 var file = new SourceFile.text(_simpleUriForSource(dartLibrary), code);
129 var unit = parseCompilationUnit(code); 169 var unit = parseCompilationUnit(code);
130 170
131 return Future.forEach(unit.directives, (directive) { 171 return Future.forEach(unit.directives, (directive) {
132 // Include anything from parts. 172 // Include anything from parts.
133 if (directive is PartDirective) { 173 if (directive is PartDirective) {
134 var targetId = resolve(dartLibrary, directive.uri.stringValue, 174 var targetId = resolve(dartLibrary, directive.uri.stringValue,
135 logger, _getSpan(file, directive)); 175 logger, _getSpan(file, directive));
136 return _initializersOf(targetId, transform, logger) 176 return _initializersOf(targetId).then(result.addAll);
137 .then(initializers.addAll);
138 } 177 }
139 178
140 // Similarly, include anything from exports except what's filtered by 179 // Similarly, include anything from exports except what's filtered by
141 // the show/hide combinators. 180 // the show/hide combinators.
142 if (directive is ExportDirective) { 181 if (directive is ExportDirective) {
143 var targetId = resolve(dartLibrary, directive.uri.stringValue, 182 var targetId = resolve(dartLibrary, directive.uri.stringValue,
144 logger, _getSpan(file, directive)); 183 logger, _getSpan(file, directive));
145 return _initializersOf(targetId, transform, logger) 184 return _initializersOf(targetId).then(
146 .then((r) => _processExportDirective(directive, r, initializers)); 185 (r) => _processExportDirective(directive, r, result));
147 } 186 }
148 }).then((_) { 187 }).then((_) {
149 // Scan the code for classes and top-level functions. 188 // Scan the code for classes and top-level functions.
150 for (var node in unit.declarations) { 189 for (var node in unit.declarations) {
151 if (node is ClassDeclaration) { 190 if (node is ClassDeclaration) {
152 _processClassDeclaration(node, initializers, file, logger); 191 _processClassDeclaration(node, result, file, logger);
153 } else if (node is FunctionDeclaration && 192 } else if (node is FunctionDeclaration &&
154 node.metadata.any(_isInitMethodAnnotation)) { 193 node.metadata.any(_isInitMethodAnnotation)) {
155 _processFunctionDeclaration(node, initializers, file, logger); 194 _processFunctionDeclaration(node, result, file, logger);
156 } 195 }
157 } 196 }
158 return initializers; 197 return result;
159 }); 198 });
160 }); 199 });
161 } 200 }
162 201
163 static String _simpleUriForSource(AssetId source) => 202 static String _simpleUriForSource(AssetId source) =>
164 source.path.startsWith('lib/') 203 source.path.startsWith('lib/')
165 ? 'package:${source.package}/${source.path.substring(4)}' : source.path; 204 ? 'package:${source.package}/${source.path.substring(4)}' : source.path;
166 205
167 /// Filter [exportedInitializers] according to [directive]'s show/hide 206 /// Filter [exportedInitializers] according to [directive]'s show/hide
168 /// combinators and add the result to [initializers]. 207 /// combinators and add the result to [result].
169 // TODO(sigmund): call the analyzer's resolver instead? 208 // TODO(sigmund): call the analyzer's resolver instead?
170 static _processExportDirective(ExportDirective directive, 209 static _processExportDirective(ExportDirective directive,
171 List<_Initializer> exportedInitializers, 210 List<_Initializer> exportedInitializers,
172 List<_Initializer> initializers) { 211 List<_Initializer> result) {
173 for (var combinator in directive.combinators) { 212 for (var combinator in directive.combinators) {
174 if (combinator is ShowCombinator) { 213 if (combinator is ShowCombinator) {
175 var show = combinator.shownNames.map((n) => n.name).toSet(); 214 var show = combinator.shownNames.map((n) => n.name).toSet();
176 exportedInitializers.retainWhere((e) => show.contains(e.symbolName)); 215 exportedInitializers.retainWhere((e) => show.contains(e.symbolName));
177 } else if (combinator is HideCombinator) { 216 } else if (combinator is HideCombinator) {
178 var hide = combinator.hiddenNames.map((n) => n.name).toSet(); 217 var hide = combinator.hiddenNames.map((n) => n.name).toSet();
179 exportedInitializers.removeWhere((e) => hide.contains(e.symbolName)); 218 exportedInitializers.removeWhere((e) => hide.contains(e.symbolName));
180 } 219 }
181 } 220 }
182 initializers.addAll(exportedInitializers); 221 result.addAll(exportedInitializers);
183 } 222 }
184 223
185 /// Add an initializer to register [node] as a polymer element if it contains 224 /// Add an initializer to register [node] as a polymer element if it contains
186 /// an appropriate [CustomTag] annotation. 225 /// an appropriate [CustomTag] annotation.
187 static _processClassDeclaration(ClassDeclaration node, 226 static _processClassDeclaration(ClassDeclaration node,
188 List<_Initializer> initializers, SourceFile file, 227 List<_Initializer> result, SourceFile file,
189 TransformLogger logger) { 228 TransformLogger logger) {
190 for (var meta in node.metadata) { 229 for (var meta in node.metadata) {
191 if (!_isCustomTagAnnotation(meta)) continue; 230 if (!_isCustomTagAnnotation(meta)) continue;
192 var args = meta.arguments.arguments; 231 var args = meta.arguments.arguments;
193 if (args == null || args.length == 0) { 232 if (args == null || args.length == 0) {
194 logger.error('Missing argument in @CustomTag annotation', 233 logger.error('Missing argument in @CustomTag annotation',
195 span: _getSpan(file, meta)); 234 span: _getSpan(file, meta));
196 continue; 235 continue;
197 } 236 }
198 237
199 var tagName = args[0].stringValue; 238 var tagName = args[0].stringValue;
200 var typeName = node.name.name; 239 var typeName = node.name.name;
201 if (typeName.startsWith('_')) { 240 if (typeName.startsWith('_')) {
202 logger.error('@CustomTag is no longer supported on private ' 241 logger.error('@CustomTag is no longer supported on private '
203 'classes: $tagName', span: _getSpan(file, node.name)); 242 'classes: $tagName', span: _getSpan(file, node.name));
204 continue; 243 continue;
205 } 244 }
206 initializers.add(new _CustomTagInitializer(tagName, typeName)); 245 result.add(new _CustomTagInitializer(tagName, typeName));
207 } 246 }
208 } 247 }
209 248
210 /// Add a method initializer for [function]. 249 /// Add a method initializer for [function].
211 static _processFunctionDeclaration(FunctionDeclaration function, 250 static _processFunctionDeclaration(FunctionDeclaration function,
212 List<_Initializer> initializers, SourceFile file, 251 List<_Initializer> result, SourceFile file,
213 TransformLogger logger) { 252 TransformLogger logger) {
214 var name = function.name.name; 253 var name = function.name.name;
215 if (name.startsWith('_')) { 254 if (name.startsWith('_')) {
216 logger.error('@initMethod is no longer supported on private ' 255 logger.error('@initMethod is no longer supported on private '
217 'functions: $name', span: _getSpan(file, function.name)); 256 'functions: $name', span: _getSpan(file, function.name));
218 return; 257 return;
219 } 258 }
220 initializers.add(new _InitMethodInitializer(name)); 259 result.add(new _InitMethodInitializer(name));
221 } 260 }
222 } 261 }
223 262
224 // TODO(sigmund): consider support for importing annotations with prefixes. 263 // TODO(sigmund): consider support for importing annotations with prefixes.
225 bool _isInitMethodAnnotation(Annotation node) => 264 bool _isInitMethodAnnotation(Annotation node) =>
226 node.name.name == 'initMethod' && node.constructorName == null && 265 node.name.name == 'initMethod' && node.constructorName == null &&
227 node.arguments == null; 266 node.arguments == null;
228 bool _isCustomTagAnnotation(Annotation node) => node.name.name == 'CustomTag'; 267 bool _isCustomTagAnnotation(Annotation node) => node.name.name == 'CustomTag';
229 268
230 abstract class _Initializer { 269 abstract class _Initializer {
(...skipping 18 matching lines...) Expand all
249 String asCode(String prefix) => 288 String asCode(String prefix) =>
250 "() => Polymer.register('$tagName', $prefix.$typeName)"; 289 "() => Polymer.register('$tagName', $prefix.$typeName)";
251 } 290 }
252 291
253 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); 292 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end);
254 293
255 const MAIN_HEADER = """ 294 const MAIN_HEADER = """
256 library app_bootstrap; 295 library app_bootstrap;
257 296
258 import 'package:polymer/polymer.dart'; 297 import 'package:polymer/polymer.dart';
298 import 'package:smoke/static.dart' as smoke;
259 """; 299 """;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698