Index: lib/src/emitters.dart |
diff --git a/lib/src/emitters.dart b/lib/src/emitters.dart |
index f43ac046489d3c8a256e3a4b415bcec89baec5f4..d02476575c4b6fbd66e64f71faf3a80339de6fce 100644 |
--- a/lib/src/emitters.dart |
+++ b/lib/src/emitters.dart |
@@ -5,256 +5,53 @@ |
/** Collects several code emitters for the template tool. */ |
library emitters; |
-import 'package:csslib/parser.dart' as css; |
-import 'package:csslib/visitor.dart'; |
import 'package:html5lib/dom.dart'; |
-import 'package:html5lib/dom_parsing.dart'; |
-import 'package:html5lib/parser.dart'; |
-import 'package:source_maps/span.dart' show Span, FileLocation; |
+import 'package:html5lib/dom_parsing.dart' show TreeVisitor; |
+import 'package:html5lib/parser.dart' show parseFragment; |
+import 'package:source_maps/printer.dart'; |
+import 'package:source_maps/refactor.dart'; |
-import 'code_printer.dart'; |
-import 'compiler.dart'; |
-import 'dart_parser.dart' show DartCodeInfo; |
+import 'compiler_options.dart'; |
+import 'css_emitters.dart' show emitStyleSheet, emitOriginalCss; |
import 'html5_utils.dart'; |
-import 'html_css_fixup.dart'; |
-import 'info.dart'; |
+import 'info.dart' show ComponentInfo, FileInfo, GlobalInfo; |
import 'messages.dart'; |
-import 'compiler_options.dart'; |
-import 'paths.dart'; |
-import 'refactor.dart'; |
-import 'utils.dart'; |
- |
- |
-/** Only x-tag name element selectors are emitted as [is="x-"]. */ |
-class CssEmitter extends CssPrinter { |
- final Set _componentsTag; |
- |
- CssEmitter(this._componentsTag); |
- |
- /** |
- * If element selector is a component's tag name, then change selector to |
- * find element who's is attribute's the component's name. |
- */ |
- bool _emitComponentElement(ElementSelector node) { |
- if (_componentsTag.contains(node.name)) { |
- emit('[is="${node.name}"]'); |
- return true; |
- } |
- return false; |
- } |
- |
- void visitElementSelector(ElementSelector node) { |
- if (_componentsTag.isNotEmpty && _emitComponentElement(node)) return; |
- super.visitElementSelector(node); |
- } |
- |
-} |
- |
-/** |
- * Style sheet polyfill for a component, each CSS class name/id selector |
- * referenced (selector) is prepended with prefix_ (if prefix is non-null). In |
- * addition an element selector this is a component's tag name is transformed |
- * to the attribute selector [is="x-"] where x- is the component's tag name. |
- */ |
-class ComponentCssEmitter extends CssPrinter { |
- final String _componentTagName; |
- final CssPolyfillKind _polyfillKind; |
- bool _inHostDirective = false; |
- bool _selectorStartInHostDirective = false; |
- |
- ComponentCssEmitter(this._componentTagName, this._polyfillKind); |
- |
- /** Is the element selector an x-tag name. */ |
- bool _isSelectorElementXTag(Selector node) { |
- if (node.simpleSelectorSequences.length > 0) { |
- var selector = node.simpleSelectorSequences[0].simpleSelector; |
- return selector is ElementSelector && selector.name == _componentTagName; |
- } |
- return false; |
- } |
- |
- /** |
- * If element selector is the component's tag name, then change selector to |
- * find element who's is attribute is the component's name. |
- */ |
- bool _emitComponentElement(ElementSelector node) { |
- if (_polyfillKind == CssPolyfillKind.SCOPED_POLYFILL && |
- _componentTagName == node.name) { |
- emit('[is="$_componentTagName"]'); |
- return true; |
- } |
- return false; |
- } |
- |
- void visitSelector(Selector node) { |
- // If the selector starts with an x-tag name don't emit it twice. |
- if (!_isSelectorElementXTag(node) && |
- _polyfillKind == CssPolyfillKind.SCOPED_POLYFILL) { |
- if (_inHostDirective) { |
- // Style the element that's hosting the component, therefore don't emit |
- // the descendent combinator (first space after the [is="x-..."]). |
- emit('[is="$_componentTagName"]'); |
- // Signal that first simpleSelector must be checked. |
- _selectorStartInHostDirective = true; |
- } else { |
- // Emit its scoped as a descendent (space at end). |
- emit('[is="$_componentTagName"] '); |
- } |
- } |
- super.visitSelector(node); |
- } |
- |
- /** |
- * If first simple selector of a ruleset in a @host directive is a wildcard |
- * then don't emit the wildcard. |
- */ |
- void visitSimpleSelectorSequence(SimpleSelectorSequence node) { |
- if (_selectorStartInHostDirective) { |
- _selectorStartInHostDirective = false; |
- if (_polyfillKind == CssPolyfillKind.SCOPED_POLYFILL && |
- node.simpleSelector.isWildcard) { |
- // Skip the wildcard if first item in the sequence. |
- return; |
- } |
- assert(node.isCombinatorNone); |
- } |
- |
- super.visitSimpleSelectorSequence(node); |
- } |
- |
- void visitClassSelector(ClassSelector node) { |
- if (_polyfillKind == CssPolyfillKind.MANGLED_POLYFILL) { |
- emit('.${_componentTagName}_${node.name}'); |
- } else { |
- super.visitClassSelector(node); |
- } |
- } |
- |
- void visitIdSelector(IdSelector node) { |
- if (_polyfillKind == CssPolyfillKind.MANGLED_POLYFILL) { |
- emit('#${_componentTagName}_${node.name}'); |
- } else { |
- super.visitIdSelector(node); |
- } |
- } |
- |
- void visitElementSelector(ElementSelector node) { |
- if (_emitComponentElement(node)) return; |
- super.visitElementSelector(node); |
- } |
- |
- /** |
- * If we're polyfilling scoped styles the @host directive is stripped. Any |
- * ruleset(s) processed in an @host will fixup the first selector. See |
- * visitSelector and visitSimpleSelectorSequence in this class, they adjust |
- * the selectors so it styles the element hosting the compopnent. |
- */ |
- void visitHostDirective(HostDirective node) { |
- if (_polyfillKind == CssPolyfillKind.SCOPED_POLYFILL) { |
- _inHostDirective = true; |
- emit('/* @host */'); |
- for (var ruleset in node.rulesets) { |
- ruleset.visit(this); |
- } |
- _inHostDirective = false; |
- emit('/* end of @host */\n'); |
- } else { |
- super.visitHostDirective(node); |
- } |
- } |
-} |
- |
-/** |
- * Helper function to emit the contents of the style tag outside of a component. |
- */ |
-String emitStyleSheet(StyleSheet ss, FileInfo file) => |
- (new CssEmitter(file.components.keys.toSet()) |
- ..visitTree(ss, pretty: true)).toString(); |
- |
-/** Helper function to emit a component's style tag content. */ |
-String emitComponentStyleSheet(StyleSheet ss, String tagName, |
- CssPolyfillKind polyfillKind) => |
- (new ComponentCssEmitter(tagName, polyfillKind) |
- ..visitTree(ss, pretty: true)).toString(); |
+import 'paths.dart' show PathMapper; |
+import 'utils.dart' show escapeDartString, path; |
/** Generates the class corresponding to a single web component. */ |
-class WebComponentEmitter { |
- final Messages messages; |
- final FileInfo _fileInfo; |
- final CssPolyfillKind cssPolyfillKind; |
- |
- WebComponentEmitter(this._fileInfo, this.messages, this.cssPolyfillKind); |
- |
- CodePrinter run(ComponentInfo info, PathMapper pathMapper, |
- TextEditTransaction transaction) { |
- var templateNode = info.element.nodes.firstWhere( |
- (n) => n.tagName == 'template', orElse: () => null); |
- |
- if (templateNode != null) { |
- if (!info.styleSheets.isEmpty && !messages.options.processCss) { |
- // TODO(terry): Only one style tag per component. |
- |
- // TODO(jmesserly): csslib + html5lib should work together. |
- // We shouldn't need to call a different function to serialize CSS. |
- // Calling innerHtml on a StyleElement should be enought - like a real |
- // browser. CSSOM and DOM should work together in the same tree. |
- var styleText = emitComponentStyleSheet( |
- info.styleSheets[0], info.tagName, cssPolyfillKind); |
- |
- templateNode.insertBefore( |
- new Element.html('<style>\n$styleText\n</style>'), |
- templateNode.hasChildNodes() ? templateNode.children[0] : null); |
- } |
- } |
- |
- bool hasExtends = info.extendsComponent != null; |
- var codeInfo = info.userCode; |
- var classDecl = info.classDeclaration; |
- if (classDecl == null) return null; |
- |
- if (transaction == null) { |
- transaction = new TextEditTransaction(codeInfo.code, codeInfo.sourceFile); |
- } |
- |
- // Expand the headers to include polymer imports, unless they are already |
- // present. |
- var libraryName = (codeInfo.libraryName != null) |
- ? codeInfo.libraryName |
- : info.tagName.replaceAll(new RegExp('[-./]'), '_'); |
- var header = new CodePrinter(0); |
- header.add(_header(path.basename(info.declaringFile.inputUrl.resolvedPath), |
- libraryName)); |
- emitImports(codeInfo, info, pathMapper, header); |
- header.addLine(''); |
- transaction.edit(0, codeInfo.directivesEnd, header); |
- |
- var classBody = new CodePrinter(1) |
- ..add('\n') |
- ..addLine('/** Original code from the component. */'); |
- |
- var pos = classDecl.leftBracket.end; |
- transaction.edit(pos, pos, classBody); |
- |
- // Emit all the code in a single printer, keeping track of source-maps. |
- return transaction.commit(); |
- } |
+NestedPrinter emitPolymerElement(ComponentInfo info, PathMapper pathMapper, |
+ TextEditTransaction transaction, CompilerOptions options) { |
+ if (info.classDeclaration == null) return null; |
+ |
+ var codeInfo = info.userCode; |
+ if (transaction == null) { |
+ // TODO(sigmund): avoid emitting this file if we don't need to do any |
+ // modifications (e.g. no @observable and not adding the libraryName). |
+ transaction = new TextEditTransaction(codeInfo.code, codeInfo.sourceFile); |
+ } |
+ if (codeInfo.libraryName == null) { |
+ // For deploy, we need to import the library associated with the component, |
+ // so we need to ensure there is a library directive. |
+ var libraryName = info.tagName.replaceAll(new RegExp('[-./]'), '_'); |
+ transaction.edit(0, 0, 'library $libraryName;'); |
+ } |
+ return transaction.commit(); |
} |
/** The code that will be used to bootstrap the application. */ |
-CodePrinter generateBootstrapCode( |
+NestedPrinter generateBootstrapCode( |
FileInfo info, FileInfo userMainInfo, GlobalInfo global, |
PathMapper pathMapper, CompilerOptions options) { |
- var printer = new CodePrinter(0) |
+ var printer = new NestedPrinter(0) |
..addLine('library app_bootstrap;') |
..addLine('') |
..addLine("import 'package:polymer/polymer.dart';") |
..addLine("import 'dart:mirrors' show currentMirrorSystem;"); |
- |
if (userMainInfo.userCode != null) { |
- printer..addLine('') |
- ..addLine("import '${pathMapper.importUrlFor(info, userMainInfo)}' " |
- "as userMain;\n"); |
+ printer..addLine("import '${pathMapper.importUrlFor(info, userMainInfo)}' " |
+ "as userMain;\n"); |
} |
int i = 0; |
@@ -272,9 +69,6 @@ CodePrinter generateBootstrapCode( |
for (var c in global.components.values) { |
if (c.hasConflict) continue; |
- var tagName = escapeDartString(c.tagName); |
- var cssMapExpression = createCssSelectorsExpression(c, |
- CssPolyfillKind.of(options, c)); |
printer.addLine("'${pathMapper.importUrlFor(info, c)}',"); |
} |
@@ -291,22 +85,15 @@ CodePrinter generateBootstrapCode( |
} |
- |
/** |
- * List of HTML4 elements which could have relative URL resource: |
- * |
- * <body background=url>, <img src=url>, <input src=url> |
- * |
- * HTML 5: |
- * |
- * <audio src=url>, <command icon=url>, <embed src=url>, |
- * <source src=url>, <video poster=url> and <video src=url> |
+ * Rewrites attributes that contain relative URL (excluding src urls in script |
+ * and link tags which are already rewritten by other parts of the compiler). |
*/ |
-class AttributeUrlTransform extends TreeVisitor { |
+class _AttributeUrlTransform extends TreeVisitor { |
final String filePath; |
final PathMapper pathMapper; |
- AttributeUrlTransform(this.filePath, this.pathMapper); |
+ _AttributeUrlTransform(this.filePath, this.pathMapper); |
visitElement(Element node) { |
if (node.tagName == 'script') return; |
@@ -314,60 +101,16 @@ class AttributeUrlTransform extends TreeVisitor { |
for (var key in node.attributes.keys) { |
if (urlAttributes.contains(key)) { |
- // Rewrite the URL attribute. |
- node.attributes[key] = pathMapper.transformUrl(filePath, |
- node.attributes[key]); |
+ node.attributes[key] = |
+ pathMapper.transformUrl(filePath, node.attributes[key]); |
} |
} |
- |
super.visitElement(node); |
} |
} |
-void _transformRelativeUrlAttributes(Document document, PathMapper pathMapper, |
- String filePath) { |
- // Transform any element's attribute which is a relative URL. |
- new AttributeUrlTransform(filePath, pathMapper).visit(document); |
-} |
- |
-void emitImports(DartCodeInfo codeInfo, LibraryInfo info, PathMapper pathMapper, |
- CodePrinter printer, [GlobalInfo global]) { |
- var seenImports = new Set(); |
- addUnique(String importString, [location]) { |
- if (!seenImports.contains(importString)) { |
- printer.addLine(importString, location: location); |
- seenImports.add(importString); |
- } |
- } |
- |
- // Add imports only for those components used by this component. |
- for (var c in info.usedComponents.keys) { |
- addUnique("import '${pathMapper.importUrlFor(info, c)}';"); |
- } |
- |
- if (global != null) { |
- for (var c in global.components.values) { |
- addUnique("import '${pathMapper.importUrlFor(info, c)}';"); |
- } |
- } |
- |
- if (info is ComponentInfo) { |
- // Inject an import to the base component. |
- var base = (info as ComponentInfo).extendsComponent; |
- if (base != null) { |
- addUnique("import '${pathMapper.importUrlFor(info, base)}';"); |
- } |
- } |
- |
- // Add existing import, export, and part directives. |
- var file = codeInfo.sourceFile; |
- for (var d in codeInfo.directives) { |
- addUnique(d.toString(), file != null ? file.location(d.offset) : null); |
- } |
-} |
- |
-final shadowDomJS = new RegExp(r'shadowdom\..*\.js', caseSensitive: false); |
-final bootJS = new RegExp(r'.*/polymer/boot.js', caseSensitive: false); |
+final _shadowDomJS = new RegExp(r'shadowdom\..*\.js', caseSensitive: false); |
+final _bootJS = new RegExp(r'.*/polymer/boot.js', caseSensitive: false); |
/** Trim down the html for the main html page. */ |
void transformMainHtml(Document document, FileInfo fileInfo, |
@@ -383,14 +126,14 @@ void transformMainHtml(Document document, FileInfo fileInfo, |
var last = src.split('/').last; |
if (last == 'dart.js' || last == 'testing.js') { |
dartLoaderFound = true; |
- } else if (shadowDomJS.hasMatch(last)) { |
+ } else if (_shadowDomJS.hasMatch(last)) { |
shadowDomFound = true; |
} |
} |
if (tag.attributes['type'] == 'application/dart') { |
tag.remove(); |
} else if (src != null) { |
- if (bootJS.hasMatch(src)) { |
+ if (_bootJS.hasMatch(src)) { |
tag.remove(); |
} else if (rewriteUrls) { |
tag.attributes["src"] = pathMapper.transformUrl(filePath, src); |
@@ -411,7 +154,7 @@ void transformMainHtml(Document document, FileInfo fileInfo, |
if (rewriteUrls) { |
// Transform any element's attribute which is a relative URL. |
- _transformRelativeUrlAttributes(document, pathMapper, filePath); |
+ new _AttributeUrlTransform(filePath, pathMapper).visit(document); |
} |
if (hasCss) { |
@@ -471,7 +214,8 @@ void transformMainHtml(Document document, FileInfo fileInfo, |
// Insert the "auto-generated" comment after the doctype, otherwise IE will |
// go into quirks mode. |
int commentIndex = 0; |
- DocumentType doctype = find(document.nodes, (n) => n is DocumentType); |
+ DocumentType doctype = |
+ document.nodes.firstWhere((n) => n is DocumentType, orElse: () => null); |
if (doctype != null) { |
commentIndex = document.nodes.indexOf(doctype) + 1; |
// TODO(jmesserly): the html5lib parser emits a warning for missing |
@@ -489,18 +233,3 @@ void transformMainHtml(Document document, FileInfo fileInfo, |
document.nodes.insert(commentIndex, parseFragment( |
'\n<!-- This file was auto-generated from $filePath. -->\n')); |
} |
- |
-/** Header with common imports, used in every generated .dart file. */ |
-String _header(String filename, String libraryName) { |
- var lib = libraryName != null ? '\nlibrary $libraryName;\n' : ''; |
- return """ |
-// Auto-generated from $filename. |
-// DO NOT EDIT. |
-$lib |
-import 'dart:html' as autogenerated; |
-import 'dart:svg' as autogenerated_svg; |
-import 'package:mdv/mdv.dart' as autogenerated_mdv; |
-import 'package:observe/observe.dart' as __observe; |
-import 'package:polymer/polymer.dart' as autogenerated; |
-"""; |
-} |