Index: pkg/polymer/lib/src/emitters.dart |
diff --git a/pkg/polymer/lib/src/emitters.dart b/pkg/polymer/lib/src/emitters.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2e4e008f472d83b28768a32490695fd22086f714 |
--- /dev/null |
+++ b/pkg/polymer/lib/src/emitters.dart |
@@ -0,0 +1,246 @@ |
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+/** Collects several code emitters for the template tool. */ |
+library emitters; |
+ |
+import 'package:html5lib/dom.dart'; |
+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 'compiler_options.dart'; |
+import 'css_emitters.dart' show emitStyleSheet, emitOriginalCss; |
+import 'html5_utils.dart'; |
+import 'info.dart' show ComponentInfo, FileInfo, GlobalInfo; |
+import 'messages.dart'; |
+import 'paths.dart' show PathMapper; |
+import 'utils.dart' show escapeDartString, path; |
+ |
+/** Generates the class corresponding to a single web component. */ |
+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. */ |
+NestedPrinter generateBootstrapCode( |
+ FileInfo info, FileInfo userMainInfo, GlobalInfo global, |
+ PathMapper pathMapper, CompilerOptions options) { |
+ |
+ 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("import '${pathMapper.importUrlFor(info, userMainInfo)}' " |
+ "as userMain;\n"); |
+ } |
+ |
+ int i = 0; |
+ for (var c in global.components.values) { |
+ if (c.hasConflict) continue; |
+ printer.addLine("import '${pathMapper.importUrlFor(info, c)}' as i$i;"); |
+ i++; |
+ } |
+ |
+ printer..addLine('') |
+ ..addLine('void main() {') |
+ ..indent += 1 |
+ ..addLine("initPolymer([") |
+ ..indent += 2; |
+ |
+ for (var c in global.components.values) { |
+ if (c.hasConflict) continue; |
+ printer.addLine("'${pathMapper.importUrlFor(info, c)}',"); |
+ } |
+ |
+ return printer |
+ ..indent -= 1 |
+ ..addLine('],') |
+ ..addLine(userMainInfo.userCode != null ? 'userMain.main,' : '() {},') |
+ ..addLine( |
+ "currentMirrorSystem().findLibrary(const Symbol('app_bootstrap'))") |
+ ..indent += 2 |
+ ..addLine(".first.uri.toString());") |
+ ..indent -= 4 |
+ ..addLine('}'); |
+} |
+ |
+ |
+/** |
+ * 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 { |
+ final String filePath; |
+ final PathMapper pathMapper; |
+ |
+ _AttributeUrlTransform(this.filePath, this.pathMapper); |
+ |
+ visitElement(Element node) { |
+ if (node.tagName == 'script') return; |
+ if (node.tagName == 'link') return; |
+ |
+ for (var key in node.attributes.keys) { |
+ if (urlAttributes.contains(key)) { |
+ node.attributes[key] = |
+ pathMapper.transformUrl(filePath, node.attributes[key]); |
+ } |
+ } |
+ super.visitElement(node); |
+ } |
+} |
+ |
+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, |
+ PathMapper pathMapper, bool hasCss, bool rewriteUrls, |
+ Messages messages, GlobalInfo global, String bootstrapOutName) { |
+ var filePath = fileInfo.inputUrl.resolvedPath; |
+ |
+ var dartLoaderTag = null; |
+ bool shadowDomFound = false; |
+ for (var tag in document.queryAll('script')) { |
+ var src = tag.attributes['src']; |
+ if (src != null) { |
+ var last = src.split('/').last; |
+ if (last == 'dart.js' || last == 'testing.js') { |
+ dartLoaderTag = tag; |
+ } else if (_shadowDomJS.hasMatch(last)) { |
+ shadowDomFound = true; |
+ } |
+ } |
+ if (tag.attributes['type'] == 'application/dart') { |
+ tag.remove(); |
+ } else if (src != null) { |
+ if (_bootJS.hasMatch(src)) { |
+ tag.remove(); |
+ } else if (rewriteUrls) { |
+ tag.attributes["src"] = pathMapper.transformUrl(filePath, src); |
+ } |
+ } |
+ } |
+ |
+ for (var tag in document.queryAll('link')) { |
+ var href = tag.attributes['href']; |
+ var rel = tag.attributes['rel']; |
+ if (rel == 'component' || rel == 'components' || rel == 'import') { |
+ tag.remove(); |
+ } else if (href != null && rewriteUrls && !hasCss) { |
+ // Only rewrite URL if rewrite on and we're not CSS polyfilling. |
+ tag.attributes['href'] = pathMapper.transformUrl(filePath, href); |
+ } |
+ } |
+ |
+ if (rewriteUrls) { |
+ // Transform any element's attribute which is a relative URL. |
+ new _AttributeUrlTransform(filePath, pathMapper).visit(document); |
+ } |
+ |
+ if (hasCss) { |
+ var newCss = pathMapper.mangle(path.basename(filePath), '.css', true); |
+ var linkElem = new Element.html( |
+ '<link rel="stylesheet" type="text/css" href="$newCss">'); |
+ document.head.insertBefore(linkElem, null); |
+ } |
+ |
+ var styles = document.queryAll('style'); |
+ if (styles.length > 0) { |
+ var allCss = new StringBuffer(); |
+ fileInfo.styleSheets.forEach((styleSheet) => |
+ allCss.write(emitStyleSheet(styleSheet, fileInfo))); |
+ styles[0].nodes.clear(); |
+ styles[0].nodes.add(new Text(allCss.toString())); |
+ for (var i = styles.length - 1; i > 0 ; i--) { |
+ styles[i].remove(); |
+ } |
+ } |
+ |
+ // TODO(jmesserly): put this in the global CSS file? |
+ // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#css-additions |
+ document.head.nodes.insert(0, parseFragment( |
+ '<style>template { display: none; }</style>')); |
+ |
+ // Move all <element> declarations to the main HTML file |
+ // TODO(sigmund): remove this once we have HTMLImports implemented. |
+ for (var c in global.components.values) { |
+ document.body.nodes.insert(0, new Text('\n')); |
+ var fragment = c.element; |
+ for (var tag in fragment.queryAll('script')) { |
+ // TODO(sigmund): leave script tags around when we start using "boot.js" |
+ if (tag.attributes['type'] == 'application/dart') { |
+ tag.remove(); |
+ } |
+ } |
+ document.body.nodes.insert(0, fragment); |
+ } |
+ |
+ if (!shadowDomFound) { |
+ // TODO(jmesserly): we probably shouldn't add this automatically. |
+ document.body.nodes.add(parseFragment('<script type="text/javascript" ' |
+ 'src="packages/shadow_dom/shadow_dom.debug.js"></script>\n')); |
+ |
+ // JS interop code required for Polymer CSS shimming. |
+ document.body.nodes.add(parseFragment('<script type="text/javascript" ' |
+ 'src="packages/browser/interop.js"></script>\n')); |
+ } |
+ |
+ var bootstrapScript = parseFragment( |
+ '<script type="application/dart" src="$bootstrapOutName"></script>'); |
+ if (dartLoaderTag == null) { |
+ document.body.nodes.add(bootstrapScript); |
+ // TODO(jmesserly): turn this warning on. |
+ //messages.warning('Missing script to load Dart. ' |
+ // 'Please add this line to your HTML file: $dartLoader', |
+ // document.body.sourceSpan); |
+ // TODO(sigmund): switch to 'boot.js' |
+ document.body.nodes.add(parseFragment('<script type="text/javascript" ' |
+ 'src="packages/browser/dart.js"></script>\n')); |
+ } else if (dartLoaderTag.parent != document.body) { |
+ document.body.nodes.add(bootstrapScript); |
+ } else { |
+ document.body.insertBefore(bootstrapScript, dartLoaderTag); |
+ } |
+ |
+ // Insert the "auto-generated" comment after the doctype, otherwise IE will |
+ // go into quirks mode. |
+ int commentIndex = 0; |
+ 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 |
+ // doctype, but it allows you to put it after comments. Presumably they do |
+ // this because some comments won't force IE into quirks mode (sigh). See |
+ // this link for more info: |
+ // http://bugzilla.validator.nu/show_bug.cgi?id=836 |
+ // For simplicity we emit the warning always, like validator.nu does. |
+ if (doctype.tagName != 'html' || commentIndex != 1) { |
+ messages.warning('file should start with <!DOCTYPE html> ' |
+ 'to avoid the possibility of it being parsed in quirks mode in IE. ' |
+ 'See http://www.w3.org/TR/html5-diff/#doctype', doctype.sourceSpan); |
+ } |
+ } |
+ document.nodes.insert(commentIndex, parseFragment( |
+ '\n<!-- This file was auto-generated from $filePath. -->\n')); |
+} |