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

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

Issue 318983002: Use parseDirectives were possible. Also mark analyzer ready to be released. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 6 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 inlines polymer-element definitions from html imports. 5 /// Transfomer that inlines polymer-element definitions from html imports.
6 library polymer.src.build.import_inliner; 6 library polymer.src.build.import_inliner;
7 7
8 import 'dart:async'; 8 import 'dart:async';
9 import 'dart:convert'; 9 import 'dart:convert';
10 10
11 import 'package:analyzer/analyzer.dart';
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:code_transformers/assets.dart'; 14 import 'package:code_transformers/assets.dart';
14 import 'package:path/path.dart' as path; 15 import 'package:path/path.dart' as path;
15 import 'package:html5lib/dom.dart' show 16 import 'package:html5lib/dom.dart' show
16 Document, DocumentFragment, Element, Node; 17 Document, DocumentFragment, Element, Node;
17 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; 18 import 'package:html5lib/dom_parsing.dart' show TreeVisitor;
18 import 'package:source_maps/refactor.dart' show TextEditTransaction; 19 import 'package:source_maps/refactor.dart' show TextEditTransaction;
19 import 'package:source_maps/span.dart'; 20 import 'package:source_maps/span.dart';
20 21
(...skipping 23 matching lines...) Expand all
44 seen.add(docId); 45 seen.add(docId);
45 46
46 Document document; 47 Document document;
47 bool changed; 48 bool changed;
48 49
49 return readPrimaryAsHtml(transform).then((doc) { 50 return readPrimaryAsHtml(transform).then((doc) {
50 document = doc; 51 document = doc;
51 experimentalBootstrap = document.querySelectorAll('link').any((link) => 52 experimentalBootstrap = document.querySelectorAll('link').any((link) =>
52 link.attributes['rel'] == 'import' && 53 link.attributes['rel'] == 'import' &&
53 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML); 54 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML);
54 changed = _extractScripts(document, docId); 55 changed = _extractScripts(document);
55 return _visitImports(document); 56 return _visitImports(document);
56 }).then((importsFound) { 57 }).then((importsFound) {
57 changed = changed || importsFound; 58 changed = changed || importsFound;
58 return _removeScripts(document); 59 return _removeScripts(document);
59 }).then((scriptsRemoved) { 60 }).then((scriptsRemoved) {
60 changed = changed || scriptsRemoved; 61 changed = changed || scriptsRemoved;
61 62
62 var output = transform.primaryInput; 63 var output = transform.primaryInput;
63 if (changed) output = new Asset.fromString(docId, document.outerHtml); 64 if (changed) output = new Asset.fromString(docId, document.outerHtml);
64 transform.addOutput(output); 65 transform.addOutput(output);
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
137 } 138 }
138 } 139 }
139 } 140 }
140 141
141 /// Loads an asset identified by [id], visits its imports and collects its 142 /// Loads an asset identified by [id], visits its imports and collects its
142 /// html imports. Then inlines it into the main document. 143 /// html imports. Then inlines it into the main document.
143 Future _inlineImport(AssetId id, Element link) { 144 Future _inlineImport(AssetId id, Element link) {
144 return readAsHtml(id, transform).then((doc) { 145 return readAsHtml(id, transform).then((doc) {
145 new _UrlNormalizer(transform, id).visit(doc); 146 new _UrlNormalizer(transform, id).visit(doc);
146 return _visitImports(doc).then((_) { 147 return _visitImports(doc).then((_) {
147 _extractScripts(doc, id); 148 // _UrlNormalizer already ensures there is a library name.
149 _extractScripts(doc, injectLibraryName: false);
148 150
149 // TODO(jmesserly): figure out how this is working in vulcanizer. 151 // TODO(jmesserly): figure out how this is working in vulcanizer.
150 // Do they produce a <body> tag with a <head> and <body> inside? 152 // Do they produce a <body> tag with a <head> and <body> inside?
151 var imported = new DocumentFragment(); 153 var imported = new DocumentFragment();
152 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes); 154 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes);
153 link.replaceWith(imported); 155 link.replaceWith(imported);
154 }); 156 });
155 }); 157 });
156 } 158 }
157 159
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
191 } 193 }
192 }); 194 });
193 } 195 }
194 }).then((_) => changed); 196 }).then((_) => changed);
195 } 197 }
196 198
197 /// Split inline scripts into their own files. We need to do this for dart2js 199 /// Split inline scripts into their own files. We need to do this for dart2js
198 /// to be able to compile them. 200 /// to be able to compile them.
199 /// 201 ///
200 /// This also validates that there weren't any duplicate scripts. 202 /// This also validates that there weren't any duplicate scripts.
201 bool _extractScripts(Document doc, AssetId sourceId) { 203 bool _extractScripts(Document doc, {bool injectLibraryName: true}) {
202 bool changed = false; 204 bool changed = false;
203 for (var script in doc.querySelectorAll('script')) { 205 for (var script in doc.querySelectorAll('script')) {
204 if (script.attributes['type'] != TYPE_DART) continue; 206 if (script.attributes['type'] != TYPE_DART) continue;
205 207
206 var src = script.attributes['src']; 208 var src = script.attributes['src'];
207 if (src != null) continue; 209 if (src != null) continue;
208 210
209 final filename = path.url.basename(docId.path); 211 final filename = path.url.basename(docId.path);
210 final count = inlineScriptCounter++; 212 final count = inlineScriptCounter++;
211 var code = script.text; 213 var code = script.text;
212 // TODO(sigmund): ensure this path is unique (dartbug.com/12618). 214 // TODO(sigmund): ensure this path is unique (dartbug.com/12618).
213 script.attributes['src'] = src = '$filename.$count.dart'; 215 script.attributes['src'] = src = '$filename.$count.dart';
214 script.text = ''; 216 script.text = '';
215 changed = true; 217 changed = true;
216 218
217 var newId = docId.addExtension('.$count.dart'); 219 var newId = docId.addExtension('.$count.dart');
218 // TODO(jmesserly): consolidate this check with our other parsing of the 220 if (injectLibraryName && !_hasLibraryDirective(code)) {
219 // Dart code, so we only parse it once. 221 var libName = _libraryNameFor(docId, count);
220 if (!_hasLibraryDirective(code)) {
221 // Inject a library tag with an appropriate library name.
222
223 // Transform AssetId into a package name. For example:
224 // myPkgName|lib/foo/bar.html -> myPkgName.foo.bar_html
225 // myPkgName|web/foo/bar.html -> myPkgName.web.foo.bar_html
226 // This should roughly match the recommended library name conventions.
227 var libName = '${path.withoutExtension(sourceId.path)}_'
228 '${path.extension(sourceId.path).substring(1)}';
229 if (libName.startsWith('lib/')) libName = libName.substring(4);
230 libName = libName.replaceAll('/', '.').replaceAll('-', '_');
231 libName = '${sourceId.package}.${libName}_$count';
232
233 code = "library $libName;\n$code"; 222 code = "library $libName;\n$code";
234 } 223 }
235 extractedFiles.add(newId); 224 extractedFiles.add(newId);
236 transform.addOutput(new Asset.fromString(newId, code)); 225 transform.addOutput(new Asset.fromString(newId, code));
237 } 226 }
238 return changed; 227 return changed;
239 } 228 }
240 } 229 }
241 230
231 /// Transform AssetId into a library name. For example:
232 ///
233 /// myPkgName|lib/foo/bar.html -> myPkgName.foo.bar_html
234 /// myPkgName|web/foo/bar.html -> myPkgName.web.foo.bar_html
235 ///
236 /// This should roughly match the recommended library name conventions.
237 String _libraryNameFor(AssetId id, int suffix) {
238 var name = '${path.withoutExtension(id.path)}_'
239 '${path.extension(id.path).substring(1)}';
240 if (name.startsWith('lib/')) name = name.substring(4);
241 name = name.replaceAll('/', '.').replaceAll('-', '_');
242 return '${id.package}.${name}_$suffix';
243 }
244
242 /// Parse [code] and determine whether it has a library directive. 245 /// Parse [code] and determine whether it has a library directive.
243 bool _hasLibraryDirective(String code) => 246 bool _hasLibraryDirective(String code) =>
244 parseCompilationUnit(code).directives.any((d) => d is LibraryDirective); 247 parseDirectives(code, suppressErrors: true)
248 .directives.any((d) => d is LibraryDirective);
245 249
246 250
247 /// Recursively inlines the contents of HTML imports. Produces as output a 251 /// Recursively inlines the contents of HTML imports. Produces as output a
248 /// single HTML file that inlines the polymer-element definitions, and a text 252 /// single HTML file that inlines the polymer-element definitions, and a text
249 /// file that contains, in order, the URIs to each library that sourced in a 253 /// file that contains, in order, the URIs to each library that sourced in a
250 /// script tag. 254 /// script tag.
251 /// 255 ///
252 /// This transformer assumes that all script tags point to external files. To 256 /// This transformer assumes that all script tags point to external files. To
253 /// support script tags with inlined code, use this transformer after running 257 /// support script tags with inlined code, use this transformer after running
254 /// [InlineCodeExtractor] on an earlier phase. 258 /// [InlineCodeExtractor] on an earlier phase.
(...skipping 17 matching lines...) Expand all
272 const TYPE_DART = 'application/dart'; 276 const TYPE_DART = 'application/dart';
273 const TYPE_JS = 'text/javascript'; 277 const TYPE_JS = 'text/javascript';
274 278
275 /// Internally adjusts urls in the html that we are about to inline. 279 /// Internally adjusts urls in the html that we are about to inline.
276 class _UrlNormalizer extends TreeVisitor { 280 class _UrlNormalizer extends TreeVisitor {
277 final Transform transform; 281 final Transform transform;
278 282
279 /// Asset where the original content (and original url) was found. 283 /// Asset where the original content (and original url) was found.
280 final AssetId sourceId; 284 final AssetId sourceId;
281 285
286 /// Counter used to ensure that every library name we inject is unique.
287 int _count = 0;
288
282 _UrlNormalizer(this.transform, this.sourceId); 289 _UrlNormalizer(this.transform, this.sourceId);
283 290
284 visitElement(Element node) { 291 visitElement(Element node) {
285 node.attributes.forEach((name, value) { 292 node.attributes.forEach((name, value) {
286 if (_urlAttributes.contains(name)) { 293 if (_urlAttributes.contains(name)) {
287 if (value != '' && !value.trim().startsWith('{{')) { 294 if (value != '' && !value.trim().startsWith('{{')) {
288 node.attributes[name] = _newUrl(value, node.sourceSpan); 295 node.attributes[name] = _newUrl(value, node.sourceSpan);
289 } 296 }
290 } 297 }
291 }); 298 });
292 if (node.localName == 'style') { 299 if (node.localName == 'style') {
293 node.text = visitCss(node.text); 300 node.text = visitCss(node.text);
294 } else if (node.localName == 'script' && 301 } else if (node.localName == 'script' &&
295 node.attributes['type'] == TYPE_DART) { 302 node.attributes['type'] == TYPE_DART &&
303 !node.attributes.containsKey('src')) {
296 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony 304 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony
297 // modules. 305 // modules.
298 node.text = visitInlineDart(node.text); 306 node.text = visitInlineDart(node.text);
299 } 307 }
300 super.visitElement(node); 308 super.visitElement(node);
301 } 309 }
302 310
303 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); 311 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true);
304 static final _QUOTE = new RegExp('["\']', multiLine: true); 312 static final _QUOTE = new RegExp('["\']', multiLine: true);
305 313
306 /// Visit the CSS text and replace any relative URLs so we can inline it. 314 /// Visit the CSS text and replace any relative URLs so we can inline it.
307 // Ported from: 315 // Ported from:
308 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378 acc691f/lib/vulcan.js#L149 316 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378 acc691f/lib/vulcan.js#L149
309 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. 317 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness.
310 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. 318 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure.
311 String visitCss(String cssText) { 319 String visitCss(String cssText) {
312 var url = spanUrlFor(sourceId, transform); 320 var url = spanUrlFor(sourceId, transform);
313 var src = new SourceFile.text(url, cssText); 321 var src = new SourceFile.text(url, cssText);
314 return cssText.replaceAllMapped(_URL, (match) { 322 return cssText.replaceAllMapped(_URL, (match) {
315 // Extract the URL, without any surrounding quotes. 323 // Extract the URL, without any surrounding quotes.
316 var span = src.span(match.start, match.end); 324 var span = src.span(match.start, match.end);
317 var href = match[1].replaceAll(_QUOTE, ''); 325 var href = match[1].replaceAll(_QUOTE, '');
318 href = _newUrl(href, span); 326 href = _newUrl(href, span);
319 return 'url($href)'; 327 return 'url($href)';
320 }); 328 });
321 } 329 }
322 330
323 String visitInlineDart(String code) { 331 String visitInlineDart(String code) {
324 var unit = parseCompilationUnit(code); 332 var unit = parseDirectives(code, suppressErrors: true);
325 var file = new SourceFile.text(spanUrlFor(sourceId, transform), code); 333 var file = new SourceFile.text(spanUrlFor(sourceId, transform), code);
326 var output = new TextEditTransaction(code, file); 334 var output = new TextEditTransaction(code, file);
327 335 var foundLibraryDirective = false;
328 for (Directive directive in unit.directives) { 336 for (Directive directive in unit.directives) {
329 if (directive is UriBasedDirective) { 337 if (directive is UriBasedDirective) {
330 var uri = directive.uri.stringValue; 338 var uri = directive.uri.stringValue;
331 var span = _getSpan(file, directive.uri); 339 var span = _getSpan(file, directive.uri);
332 340
333 var id = uriToAssetId(sourceId, uri, transform.logger, span, 341 var id = uriToAssetId(sourceId, uri, transform.logger, span,
334 errorOnAbsolute: false); 342 errorOnAbsolute: false);
335 if (id == null) continue; 343 if (id == null) continue;
336 344
337 var primaryId = transform.primaryInput.id; 345 var primaryId = transform.primaryInput.id;
338 var newUri = assetUrlFor(id, primaryId, transform.logger); 346 var newUri = assetUrlFor(id, primaryId, transform.logger);
339 if (newUri != uri) { 347 if (newUri != uri) {
340 output.edit(span.start.offset, span.end.offset, "'$newUri'"); 348 output.edit(span.start.offset, span.end.offset, "'$newUri'");
341 } 349 }
350 } else if (directive is LibraryDirective) {
351 foundLibraryDirective = true;
342 } 352 }
343 } 353 }
344 354
355 if (!foundLibraryDirective) {
356 // Ensure all inline scripts also have a library name.
357 var libName = _libraryNameFor(sourceId, _count++);
358 output.edit(0, 0, "library $libName;\n");
359 }
360
345 if (!output.hasEdits) return code; 361 if (!output.hasEdits) return code;
346 362
347 // TODO(sigmund): emit source maps when barback supports it (see 363 // TODO(sigmund): emit source maps when barback supports it (see
348 // dartbug.com/12340) 364 // dartbug.com/12340)
349 return (output.commit()..build(file.url)).text; 365 return (output.commit()..build(file.url)).text;
350 } 366 }
351 367
352 String _newUrl(String href, Span span) { 368 String _newUrl(String href, Span span) {
353 var uri = Uri.parse(href); 369 var uri = Uri.parse(href);
354 if (uri.isAbsolute) return href; 370 if (uri.isAbsolute) return href;
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
395 'formaction', // in button, input 411 'formaction', // in button, input
396 'href', // in a, area, link, base, command 412 'href', // in a, area, link, base, command
397 'icon', // in command 413 'icon', // in command
398 'manifest', // in html 414 'manifest', // in html
399 'poster', // in video 415 'poster', // in video
400 'src', // in audio, embed, iframe, img, input, script, source, track, 416 'src', // in audio, embed, iframe, img, input, script, source, track,
401 // video 417 // video
402 ]; 418 ];
403 419
404 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); 420 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698