| 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 library compiler; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:collection' show SplayTreeMap; | |
| 9 | |
| 10 import 'package:csslib/visitor.dart' show StyleSheet, treeToDebugString; | |
| 11 import 'package:html5lib/dom.dart'; | |
| 12 import 'package:html5lib/parser.dart'; | |
| 13 | |
| 14 import 'analyzer.dart'; | |
| 15 import 'css_analyzer.dart' show analyzeCss, findUrlsImported, | |
| 16 findImportsInStyleSheet, parseCss; | |
| 17 import 'css_emitters.dart' show rewriteCssUris, | |
| 18 emitComponentStyleSheet, emitOriginalCss, emitStyleSheet; | |
| 19 import 'file_system.dart'; | |
| 20 import 'files.dart'; | |
| 21 import 'info.dart'; | |
| 22 import 'messages.dart'; | |
| 23 import 'compiler_options.dart'; | |
| 24 import 'utils.dart'; | |
| 25 | |
| 26 /** | |
| 27 * Parses an HTML file [contents] and returns a DOM-like tree. | |
| 28 * Note that [contents] will be a [String] if coming from a browser-based | |
| 29 * [FileSystem], or it will be a [List<int>] if running on the command line. | |
| 30 * | |
| 31 * Adds emitted error/warning to [messages], if [messages] is supplied. | |
| 32 */ | |
| 33 Document parseHtml(contents, String sourcePath, Messages messages, | |
| 34 bool checkDocType) { | |
| 35 var parser = new HtmlParser(contents, generateSpans: true, | |
| 36 sourceUrl: sourcePath); | |
| 37 var document = parser.parse(); | |
| 38 | |
| 39 // Note: errors aren't fatal in HTML (unless strict mode is on). | |
| 40 // So just print them as warnings. | |
| 41 for (var e in parser.errors) { | |
| 42 if (checkDocType || e.errorCode != 'expected-doctype-but-got-start-tag') { | |
| 43 messages.warning(e.message, e.span); | |
| 44 } | |
| 45 } | |
| 46 return document; | |
| 47 } | |
| 48 | |
| 49 /** Compiles an application written with Dart web components. */ | |
| 50 class Compiler { | |
| 51 final FileSystem fileSystem; | |
| 52 final CompilerOptions options; | |
| 53 final List<SourceFile> files = <SourceFile>[]; | |
| 54 | |
| 55 String _mainPath; | |
| 56 String _packageRoot; | |
| 57 String _resetCssFile; | |
| 58 StyleSheet _cssResetStyleSheet; | |
| 59 Messages _messages; | |
| 60 | |
| 61 FutureGroup _tasks; | |
| 62 Set _processed; | |
| 63 | |
| 64 /** Information about source [files] given their href. */ | |
| 65 final Map<String, FileInfo> info = new SplayTreeMap<String, FileInfo>(); | |
| 66 | |
| 67 final GlobalInfo global = new GlobalInfo(); | |
| 68 | |
| 69 /** Creates a compiler with [options] using [fileSystem]. */ | |
| 70 Compiler(this.fileSystem, this.options, this._messages) { | |
| 71 _mainPath = options.inputFile; | |
| 72 var mainDir = path.dirname(_mainPath); | |
| 73 var baseDir = options.baseDir != null ? options.baseDir : mainDir; | |
| 74 _packageRoot = options.packageRoot != null ? options.packageRoot | |
| 75 : path.join(path.dirname(_mainPath), 'packages'); | |
| 76 | |
| 77 if (options.resetCssFile != null) { | |
| 78 _resetCssFile = options.resetCssFile; | |
| 79 if (path.isRelative(_resetCssFile)) { | |
| 80 // If CSS reset file path is relative from our current path. | |
| 81 _resetCssFile = path.resolve(_resetCssFile); | |
| 82 } | |
| 83 } | |
| 84 | |
| 85 // Normalize paths - all should be relative or absolute paths. | |
| 86 if (path.isAbsolute(_mainPath) || path.isAbsolute(baseDir) | |
| 87 || path.isAbsolute(_packageRoot)) { | |
| 88 if (path.isRelative(_mainPath)) _mainPath = path.resolve(_mainPath); | |
| 89 if (path.isRelative(baseDir)) baseDir = path.resolve(baseDir); | |
| 90 if (path.isRelative(_packageRoot)) { | |
| 91 _packageRoot = path.resolve(_packageRoot); | |
| 92 } | |
| 93 } | |
| 94 } | |
| 95 | |
| 96 /** Compile the application starting from the given input file. */ | |
| 97 Future run() { | |
| 98 if (path.basename(_mainPath).endsWith('.dart')) { | |
| 99 _messages.error("Please provide an HTML file as your entry point.", | |
| 100 null); | |
| 101 return new Future.value(null); | |
| 102 } | |
| 103 return _parseAndDiscover(_mainPath).then((_) { | |
| 104 _analyze(); | |
| 105 | |
| 106 // Analyze all CSS files. | |
| 107 _time('Analyzed Style Sheets', '', () => | |
| 108 analyzeCss(_packageRoot, files, info, | |
| 109 global.pseudoElements, _messages, | |
| 110 warningsAsErrors: options.warningsAsErrors)); | |
| 111 }); | |
| 112 } | |
| 113 | |
| 114 /** | |
| 115 * Asynchronously parse [inputFile] and transitively discover web components | |
| 116 * to load and parse. Returns a future that completes when all files are | |
| 117 * processed. | |
| 118 */ | |
| 119 Future _parseAndDiscover(String inputFile) { | |
| 120 _tasks = new FutureGroup(); | |
| 121 _processed = new Set(); | |
| 122 _processed.add(inputFile); | |
| 123 _tasks.add(_parseHtmlFile(new UrlInfo(inputFile, inputFile, null), true)); | |
| 124 return _tasks.future; | |
| 125 } | |
| 126 | |
| 127 void _processHtmlFile(UrlInfo inputUrl, SourceFile file) { | |
| 128 if (file == null) return; | |
| 129 | |
| 130 bool isEntryPoint = _processed.length == 1; | |
| 131 | |
| 132 files.add(file); | |
| 133 | |
| 134 var fileInfo = _time('Analyzed definitions', inputUrl.url, () { | |
| 135 return analyzeDefinitions(global, inputUrl, file.document, _packageRoot, | |
| 136 _messages); | |
| 137 }); | |
| 138 info[inputUrl.resolvedPath] = fileInfo; | |
| 139 | |
| 140 if (isEntryPoint && _resetCssFile != null) { | |
| 141 _processed.add(_resetCssFile); | |
| 142 _tasks.add(_parseCssFile(new UrlInfo(_resetCssFile, _resetCssFile, | |
| 143 null))); | |
| 144 } | |
| 145 | |
| 146 // Load component files referenced by [file]. | |
| 147 for (var link in fileInfo.componentLinks) { | |
| 148 _loadFile(link, _parseHtmlFile); | |
| 149 } | |
| 150 | |
| 151 // Load stylesheet files referenced by [file]. | |
| 152 for (var link in fileInfo.styleSheetHrefs) { | |
| 153 _loadFile(link, _parseCssFile); | |
| 154 } | |
| 155 | |
| 156 // Process any @imports inside of a <style> tag. | |
| 157 var urlInfos = findUrlsImported(fileInfo, fileInfo.inputUrl, _packageRoot, | |
| 158 file.document, _messages, options); | |
| 159 for (var urlInfo in urlInfos) { | |
| 160 _loadFile(urlInfo, _parseCssFile); | |
| 161 } | |
| 162 | |
| 163 // Load .dart files being referenced in components. | |
| 164 for (var component in fileInfo.declaredComponents) { | |
| 165 // Process any @imports inside of the <style> tag in a component. | |
| 166 var urlInfos = findUrlsImported(component, fileInfo.inputUrl, | |
| 167 _packageRoot, component.element, _messages, options); | |
| 168 for (var urlInfo in urlInfos) { | |
| 169 _loadFile(urlInfo, _parseCssFile); | |
| 170 } | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 /** | |
| 175 * Helper function to load [urlInfo] and parse it using [loadAndParse] if it | |
| 176 * hasn't been loaded before. | |
| 177 */ | |
| 178 void _loadFile(UrlInfo urlInfo, Future loadAndParse(UrlInfo inputUrl)) { | |
| 179 if (urlInfo == null) return; | |
| 180 var resolvedPath = urlInfo.resolvedPath; | |
| 181 if (!_processed.contains(resolvedPath)) { | |
| 182 _processed.add(resolvedPath); | |
| 183 _tasks.add(loadAndParse(urlInfo)); | |
| 184 } | |
| 185 } | |
| 186 | |
| 187 /** Parse an HTML file. */ | |
| 188 Future _parseHtmlFile(UrlInfo inputUrl, [bool checkDocType = false]) { | |
| 189 var filePath = inputUrl.resolvedPath; | |
| 190 return fileSystem.readTextOrBytes(filePath) | |
| 191 .catchError((e) => _readError(e, inputUrl)) | |
| 192 .then((source) { | |
| 193 if (source == null) return; | |
| 194 var file = new SourceFile(filePath); | |
| 195 file.document = _time('Parsed', filePath, | |
| 196 () => parseHtml(source, filePath, _messages, checkDocType)); | |
| 197 _processHtmlFile(inputUrl, file); | |
| 198 }); | |
| 199 } | |
| 200 | |
| 201 /** Parse a stylesheet file. */ | |
| 202 Future _parseCssFile(UrlInfo inputUrl) { | |
| 203 if (!options.emulateScopedCss) { | |
| 204 return new Future<SourceFile>.value(null); | |
| 205 } | |
| 206 var filePath = inputUrl.resolvedPath; | |
| 207 return fileSystem.readText(filePath) | |
| 208 .catchError((e) => _readError(e, inputUrl, isWarning: true)) | |
| 209 .then((code) { | |
| 210 if (code == null) return; | |
| 211 var file = new SourceFile(filePath, type: SourceFile.STYLESHEET); | |
| 212 file.code = code; | |
| 213 _processCssFile(inputUrl, file); | |
| 214 }); | |
| 215 } | |
| 216 | |
| 217 | |
| 218 SourceFile _readError(error, UrlInfo inputUrl, {isWarning: false}) { | |
| 219 var message = 'unable to open file "${inputUrl.resolvedPath}"'; | |
| 220 if (options.verbose) { | |
| 221 message = '$message. original message:\n $error'; | |
| 222 } | |
| 223 if (isWarning) { | |
| 224 _messages.warning(message, inputUrl.sourceSpan); | |
| 225 } else { | |
| 226 _messages.error(message, inputUrl.sourceSpan); | |
| 227 } | |
| 228 return null; | |
| 229 } | |
| 230 | |
| 231 void _processCssFile(UrlInfo inputUrl, SourceFile cssFile) { | |
| 232 if (cssFile == null) return; | |
| 233 | |
| 234 files.add(cssFile); | |
| 235 | |
| 236 var fileInfo = new FileInfo(inputUrl); | |
| 237 info[inputUrl.resolvedPath] = fileInfo; | |
| 238 | |
| 239 var styleSheet = parseCss(cssFile.code, _messages, options); | |
| 240 if (inputUrl.url == _resetCssFile) { | |
| 241 _cssResetStyleSheet = styleSheet; | |
| 242 } else if (styleSheet != null) { | |
| 243 _resolveStyleSheetImports(inputUrl, cssFile.path, styleSheet); | |
| 244 fileInfo.styleSheets.add(styleSheet); | |
| 245 } | |
| 246 } | |
| 247 | |
| 248 /** Load and parse all style sheets referenced with an @imports. */ | |
| 249 void _resolveStyleSheetImports(UrlInfo inputUrl, String processingFile, | |
| 250 StyleSheet styleSheet) { | |
| 251 var urlInfos = _time('CSS imports', processingFile, () => | |
| 252 findImportsInStyleSheet(styleSheet, _packageRoot, inputUrl, _messages)); | |
| 253 | |
| 254 for (var urlInfo in urlInfos) { | |
| 255 if (urlInfo == null) break; | |
| 256 // Load any @imported stylesheet files referenced in this style sheet. | |
| 257 _loadFile(urlInfo, _parseCssFile); | |
| 258 } | |
| 259 } | |
| 260 | |
| 261 /** Run the analyzer on every input html file. */ | |
| 262 void _analyze() { | |
| 263 var uniqueIds = new IntIterator(); | |
| 264 for (var file in files) { | |
| 265 if (file.isHtml) { | |
| 266 _time('Analyzed contents', file.path, () => | |
| 267 analyzeFile(file, info, uniqueIds, global, _messages, | |
| 268 options.emulateScopedCss)); | |
| 269 } | |
| 270 } | |
| 271 } | |
| 272 | |
| 273 // TODO(jmesserly): refactor this and other CSS related transforms out of | |
| 274 // Compiler. | |
| 275 /** | |
| 276 * Generate an CSS file for all style sheets (main and components). | |
| 277 * Returns true if a file was generated, otherwise false. | |
| 278 */ | |
| 279 bool _emitAllCss() { | |
| 280 if (!options.emulateScopedCss) return false; | |
| 281 | |
| 282 var buff = new StringBuffer(); | |
| 283 | |
| 284 // Emit all linked style sheet files first. | |
| 285 for (var file in files) { | |
| 286 var css = new StringBuffer(); | |
| 287 var fileInfo = info[file.path]; | |
| 288 if (file.isStyleSheet) { | |
| 289 for (var styleSheet in fileInfo.styleSheets) { | |
| 290 css.write( | |
| 291 '/* Auto-generated from style sheet href = ${file.path} */\n' | |
| 292 '/* DO NOT EDIT. */\n\n'); | |
| 293 css.write(emitStyleSheet(styleSheet, fileInfo)); | |
| 294 css.write('\n\n'); | |
| 295 } | |
| 296 } | |
| 297 } | |
| 298 | |
| 299 // Emit all CSS for each component (style scoped). | |
| 300 for (var file in files) { | |
| 301 if (file.isHtml) { | |
| 302 var fileInfo = info[file.path]; | |
| 303 for (var component in fileInfo.declaredComponents) { | |
| 304 for (var styleSheet in component.styleSheets) { | |
| 305 // Translate any URIs in CSS. | |
| 306 if (buff.isEmpty) { | |
| 307 buff.write( | |
| 308 '/* Auto-generated from components style tags. */\n' | |
| 309 '/* DO NOT EDIT. */\n\n'); | |
| 310 } | |
| 311 buff.write( | |
| 312 '/* ==================================================== \n' | |
| 313 ' Component ${component.tagName} stylesheet \n' | |
| 314 ' ==================================================== */\n'); | |
| 315 | |
| 316 var tagName = component.tagName; | |
| 317 if (!component.hasAuthorStyles) { | |
| 318 if (_cssResetStyleSheet != null) { | |
| 319 // If component doesn't have apply-author-styles then we need to | |
| 320 // reset the CSS the styles for the component (if css-reset file | |
| 321 // option was passed). | |
| 322 buff.write('\n/* Start CSS Reset */\n'); | |
| 323 var style; | |
| 324 if (options.emulateScopedCss) { | |
| 325 style = emitComponentStyleSheet(_cssResetStyleSheet, tagName); | |
| 326 } else { | |
| 327 style = emitOriginalCss(_cssResetStyleSheet); | |
| 328 } | |
| 329 buff.write(style); | |
| 330 buff.write('/* End CSS Reset */\n\n'); | |
| 331 } | |
| 332 } | |
| 333 if (options.emulateScopedCss) { | |
| 334 buff.write(emitComponentStyleSheet(styleSheet, tagName)); | |
| 335 } else { | |
| 336 buff.write(emitOriginalCss(styleSheet)); | |
| 337 } | |
| 338 buff.write('\n\n'); | |
| 339 } | |
| 340 } | |
| 341 } | |
| 342 } | |
| 343 | |
| 344 if (buff.isEmpty) return false; | |
| 345 return true; | |
| 346 } | |
| 347 | |
| 348 _time(String logMessage, String filePath, callback(), | |
| 349 {bool printTime: false}) { | |
| 350 var message = new StringBuffer(); | |
| 351 message.write(logMessage); | |
| 352 var filename = path.basename(filePath); | |
| 353 for (int i = (60 - logMessage.length - filename.length); i > 0 ; i--) { | |
| 354 message.write(' '); | |
| 355 } | |
| 356 message.write(filename); | |
| 357 return time(message.toString(), callback, | |
| 358 printTime: options.verbose || printTime); | |
| 359 } | |
| 360 } | |
| OLD | NEW |