OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 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. |
| 4 |
| 5 /** Collects several code emitters for the template tool. */ |
| 6 library emitters; |
| 7 |
| 8 import 'package:html5lib/dom.dart'; |
| 9 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; |
| 10 import 'package:html5lib/parser.dart' show parseFragment; |
| 11 import 'package:source_maps/printer.dart'; |
| 12 import 'package:source_maps/refactor.dart'; |
| 13 |
| 14 import 'compiler_options.dart'; |
| 15 import 'css_emitters.dart' show emitStyleSheet, emitOriginalCss; |
| 16 import 'html5_utils.dart'; |
| 17 import 'info.dart' show ComponentInfo, FileInfo, GlobalInfo; |
| 18 import 'messages.dart'; |
| 19 import 'paths.dart' show PathMapper; |
| 20 import 'utils.dart' show escapeDartString, path; |
| 21 |
| 22 /** Generates the class corresponding to a single web component. */ |
| 23 NestedPrinter emitPolymerElement(ComponentInfo info, PathMapper pathMapper, |
| 24 TextEditTransaction transaction, CompilerOptions options) { |
| 25 if (info.classDeclaration == null) return null; |
| 26 |
| 27 var codeInfo = info.userCode; |
| 28 if (transaction == null) { |
| 29 // TODO(sigmund): avoid emitting this file if we don't need to do any |
| 30 // modifications (e.g. no @observable and not adding the libraryName). |
| 31 transaction = new TextEditTransaction(codeInfo.code, codeInfo.sourceFile); |
| 32 } |
| 33 if (codeInfo.libraryName == null) { |
| 34 // For deploy, we need to import the library associated with the component, |
| 35 // so we need to ensure there is a library directive. |
| 36 var libraryName = info.tagName.replaceAll(new RegExp('[-./]'), '_'); |
| 37 transaction.edit(0, 0, 'library $libraryName;'); |
| 38 } |
| 39 return transaction.commit(); |
| 40 } |
| 41 |
| 42 /** The code that will be used to bootstrap the application. */ |
| 43 NestedPrinter generateBootstrapCode( |
| 44 FileInfo info, FileInfo userMainInfo, GlobalInfo global, |
| 45 PathMapper pathMapper, CompilerOptions options) { |
| 46 |
| 47 var printer = new NestedPrinter(0) |
| 48 ..addLine('library app_bootstrap;') |
| 49 ..addLine('') |
| 50 ..addLine("import 'package:polymer/polymer.dart';") |
| 51 ..addLine("import 'dart:mirrors' show currentMirrorSystem;"); |
| 52 if (userMainInfo.userCode != null) { |
| 53 printer..addLine("import '${pathMapper.importUrlFor(info, userMainInfo)}' " |
| 54 "as userMain;\n"); |
| 55 } |
| 56 |
| 57 int i = 0; |
| 58 for (var c in global.components.values) { |
| 59 if (c.hasConflict) continue; |
| 60 printer.addLine("import '${pathMapper.importUrlFor(info, c)}' as i$i;"); |
| 61 i++; |
| 62 } |
| 63 |
| 64 printer..addLine('') |
| 65 ..addLine('void main() {') |
| 66 ..indent += 1 |
| 67 ..addLine("initPolymer([") |
| 68 ..indent += 2; |
| 69 |
| 70 for (var c in global.components.values) { |
| 71 if (c.hasConflict) continue; |
| 72 printer.addLine("'${pathMapper.importUrlFor(info, c)}',"); |
| 73 } |
| 74 |
| 75 return printer |
| 76 ..indent -= 1 |
| 77 ..addLine('],') |
| 78 ..addLine(userMainInfo.userCode != null ? 'userMain.main,' : '() {},') |
| 79 ..addLine( |
| 80 "currentMirrorSystem().findLibrary(const Symbol('app_bootstrap'))") |
| 81 ..indent += 2 |
| 82 ..addLine(".first.uri.toString());") |
| 83 ..indent -= 4 |
| 84 ..addLine('}'); |
| 85 } |
| 86 |
| 87 |
| 88 /** |
| 89 * Rewrites attributes that contain relative URL (excluding src urls in script |
| 90 * and link tags which are already rewritten by other parts of the compiler). |
| 91 */ |
| 92 class _AttributeUrlTransform extends TreeVisitor { |
| 93 final String filePath; |
| 94 final PathMapper pathMapper; |
| 95 |
| 96 _AttributeUrlTransform(this.filePath, this.pathMapper); |
| 97 |
| 98 visitElement(Element node) { |
| 99 if (node.tagName == 'script') return; |
| 100 if (node.tagName == 'link') return; |
| 101 |
| 102 for (var key in node.attributes.keys) { |
| 103 if (urlAttributes.contains(key)) { |
| 104 node.attributes[key] = |
| 105 pathMapper.transformUrl(filePath, node.attributes[key]); |
| 106 } |
| 107 } |
| 108 super.visitElement(node); |
| 109 } |
| 110 } |
| 111 |
| 112 final _shadowDomJS = new RegExp(r'shadowdom\..*\.js', caseSensitive: false); |
| 113 final _bootJS = new RegExp(r'.*/polymer/boot.js', caseSensitive: false); |
| 114 |
| 115 /** Trim down the html for the main html page. */ |
| 116 void transformMainHtml(Document document, FileInfo fileInfo, |
| 117 PathMapper pathMapper, bool hasCss, bool rewriteUrls, |
| 118 Messages messages, GlobalInfo global, String bootstrapOutName) { |
| 119 var filePath = fileInfo.inputUrl.resolvedPath; |
| 120 |
| 121 var dartLoaderTag = null; |
| 122 bool shadowDomFound = false; |
| 123 for (var tag in document.queryAll('script')) { |
| 124 var src = tag.attributes['src']; |
| 125 if (src != null) { |
| 126 var last = src.split('/').last; |
| 127 if (last == 'dart.js' || last == 'testing.js') { |
| 128 dartLoaderTag = tag; |
| 129 } else if (_shadowDomJS.hasMatch(last)) { |
| 130 shadowDomFound = true; |
| 131 } |
| 132 } |
| 133 if (tag.attributes['type'] == 'application/dart') { |
| 134 tag.remove(); |
| 135 } else if (src != null) { |
| 136 if (_bootJS.hasMatch(src)) { |
| 137 tag.remove(); |
| 138 } else if (rewriteUrls) { |
| 139 tag.attributes["src"] = pathMapper.transformUrl(filePath, src); |
| 140 } |
| 141 } |
| 142 } |
| 143 |
| 144 for (var tag in document.queryAll('link')) { |
| 145 var href = tag.attributes['href']; |
| 146 var rel = tag.attributes['rel']; |
| 147 if (rel == 'component' || rel == 'components' || rel == 'import') { |
| 148 tag.remove(); |
| 149 } else if (href != null && rewriteUrls && !hasCss) { |
| 150 // Only rewrite URL if rewrite on and we're not CSS polyfilling. |
| 151 tag.attributes['href'] = pathMapper.transformUrl(filePath, href); |
| 152 } |
| 153 } |
| 154 |
| 155 if (rewriteUrls) { |
| 156 // Transform any element's attribute which is a relative URL. |
| 157 new _AttributeUrlTransform(filePath, pathMapper).visit(document); |
| 158 } |
| 159 |
| 160 if (hasCss) { |
| 161 var newCss = pathMapper.mangle(path.basename(filePath), '.css', true); |
| 162 var linkElem = new Element.html( |
| 163 '<link rel="stylesheet" type="text/css" href="$newCss">'); |
| 164 document.head.insertBefore(linkElem, null); |
| 165 } |
| 166 |
| 167 var styles = document.queryAll('style'); |
| 168 if (styles.length > 0) { |
| 169 var allCss = new StringBuffer(); |
| 170 fileInfo.styleSheets.forEach((styleSheet) => |
| 171 allCss.write(emitStyleSheet(styleSheet, fileInfo))); |
| 172 styles[0].nodes.clear(); |
| 173 styles[0].nodes.add(new Text(allCss.toString())); |
| 174 for (var i = styles.length - 1; i > 0 ; i--) { |
| 175 styles[i].remove(); |
| 176 } |
| 177 } |
| 178 |
| 179 // TODO(jmesserly): put this in the global CSS file? |
| 180 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
css-additions |
| 181 document.head.nodes.insert(0, parseFragment( |
| 182 '<style>template { display: none; }</style>')); |
| 183 |
| 184 // Move all <element> declarations to the main HTML file |
| 185 // TODO(sigmund): remove this once we have HTMLImports implemented. |
| 186 for (var c in global.components.values) { |
| 187 document.body.nodes.insert(0, new Text('\n')); |
| 188 var fragment = c.element; |
| 189 for (var tag in fragment.queryAll('script')) { |
| 190 // TODO(sigmund): leave script tags around when we start using "boot.js" |
| 191 if (tag.attributes['type'] == 'application/dart') { |
| 192 tag.remove(); |
| 193 } |
| 194 } |
| 195 document.body.nodes.insert(0, fragment); |
| 196 } |
| 197 |
| 198 if (!shadowDomFound) { |
| 199 // TODO(jmesserly): we probably shouldn't add this automatically. |
| 200 document.body.nodes.add(parseFragment('<script type="text/javascript" ' |
| 201 'src="packages/shadow_dom/shadow_dom.debug.js"></script>\n')); |
| 202 |
| 203 // JS interop code required for Polymer CSS shimming. |
| 204 document.body.nodes.add(parseFragment('<script type="text/javascript" ' |
| 205 'src="packages/browser/interop.js"></script>\n')); |
| 206 } |
| 207 |
| 208 var bootstrapScript = parseFragment( |
| 209 '<script type="application/dart" src="$bootstrapOutName"></script>'); |
| 210 if (dartLoaderTag == null) { |
| 211 document.body.nodes.add(bootstrapScript); |
| 212 // TODO(jmesserly): turn this warning on. |
| 213 //messages.warning('Missing script to load Dart. ' |
| 214 // 'Please add this line to your HTML file: $dartLoader', |
| 215 // document.body.sourceSpan); |
| 216 // TODO(sigmund): switch to 'boot.js' |
| 217 document.body.nodes.add(parseFragment('<script type="text/javascript" ' |
| 218 'src="packages/browser/dart.js"></script>\n')); |
| 219 } else if (dartLoaderTag.parent != document.body) { |
| 220 document.body.nodes.add(bootstrapScript); |
| 221 } else { |
| 222 document.body.insertBefore(bootstrapScript, dartLoaderTag); |
| 223 } |
| 224 |
| 225 // Insert the "auto-generated" comment after the doctype, otherwise IE will |
| 226 // go into quirks mode. |
| 227 int commentIndex = 0; |
| 228 DocumentType doctype = |
| 229 document.nodes.firstWhere((n) => n is DocumentType, orElse: () => null); |
| 230 if (doctype != null) { |
| 231 commentIndex = document.nodes.indexOf(doctype) + 1; |
| 232 // TODO(jmesserly): the html5lib parser emits a warning for missing |
| 233 // doctype, but it allows you to put it after comments. Presumably they do |
| 234 // this because some comments won't force IE into quirks mode (sigh). See |
| 235 // this link for more info: |
| 236 // http://bugzilla.validator.nu/show_bug.cgi?id=836 |
| 237 // For simplicity we emit the warning always, like validator.nu does. |
| 238 if (doctype.tagName != 'html' || commentIndex != 1) { |
| 239 messages.warning('file should start with <!DOCTYPE html> ' |
| 240 'to avoid the possibility of it being parsed in quirks mode in IE. ' |
| 241 'See http://www.w3.org/TR/html5-diff/#doctype', doctype.sourceSpan); |
| 242 } |
| 243 } |
| 244 document.nodes.insert(commentIndex, parseFragment( |
| 245 '\n<!-- This file was auto-generated from $filePath. -->\n')); |
| 246 } |
OLD | NEW |