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 import 'dart:json' as json; |
| 10 |
| 11 import 'package:analyzer_experimental/src/generated/ast.dart' show Directive, Ur
iBasedDirective; |
| 12 import 'package:csslib/visitor.dart' show StyleSheet, treeToDebugString; |
| 13 import 'package:html5lib/dom.dart'; |
| 14 import 'package:html5lib/parser.dart'; |
| 15 import 'package:observe/transform.dart' show transformObservables; |
| 16 import 'package:source_maps/span.dart' show Span; |
| 17 import 'package:source_maps/refactor.dart' show TextEditTransaction; |
| 18 import 'package:source_maps/printer.dart'; |
| 19 |
| 20 import 'analyzer.dart'; |
| 21 import 'css_analyzer.dart' show analyzeCss, findUrlsImported, |
| 22 findImportsInStyleSheet, parseCss; |
| 23 import 'css_emitters.dart' show rewriteCssUris, |
| 24 emitComponentStyleSheet, emitOriginalCss, emitStyleSheet; |
| 25 import 'dart_parser.dart'; |
| 26 import 'emitters.dart'; |
| 27 import 'file_system.dart'; |
| 28 import 'files.dart'; |
| 29 import 'info.dart'; |
| 30 import 'messages.dart'; |
| 31 import 'compiler_options.dart'; |
| 32 import 'paths.dart'; |
| 33 import 'utils.dart'; |
| 34 |
| 35 /** |
| 36 * Parses an HTML file [contents] and returns a DOM-like tree. |
| 37 * Note that [contents] will be a [String] if coming from a browser-based |
| 38 * [FileSystem], or it will be a [List<int>] if running on the command line. |
| 39 * |
| 40 * Adds emitted error/warning to [messages], if [messages] is supplied. |
| 41 */ |
| 42 Document parseHtml(contents, String sourcePath, Messages messages) { |
| 43 var parser = new HtmlParser(contents, generateSpans: true, |
| 44 sourceUrl: sourcePath); |
| 45 var document = parser.parse(); |
| 46 |
| 47 // Note: errors aren't fatal in HTML (unless strict mode is on). |
| 48 // So just print them as warnings. |
| 49 for (var e in parser.errors) { |
| 50 messages.warning(e.message, e.span); |
| 51 } |
| 52 return document; |
| 53 } |
| 54 |
| 55 /** Compiles an application written with Dart web components. */ |
| 56 class Compiler { |
| 57 final FileSystem fileSystem; |
| 58 final CompilerOptions options; |
| 59 final List<SourceFile> files = <SourceFile>[]; |
| 60 final List<OutputFile> output = <OutputFile>[]; |
| 61 |
| 62 String _mainPath; |
| 63 String _resetCssFile; |
| 64 StyleSheet _cssResetStyleSheet; |
| 65 PathMapper _pathMapper; |
| 66 Messages _messages; |
| 67 |
| 68 FutureGroup _tasks; |
| 69 Set _processed; |
| 70 |
| 71 /** Information about source [files] given their href. */ |
| 72 final Map<String, FileInfo> info = new SplayTreeMap<String, FileInfo>(); |
| 73 final _edits = new Map<DartCodeInfo, TextEditTransaction>(); |
| 74 |
| 75 final GlobalInfo global = new GlobalInfo(); |
| 76 |
| 77 /** Creates a compiler with [options] using [fileSystem]. */ |
| 78 Compiler(this.fileSystem, this.options, this._messages) { |
| 79 _mainPath = options.inputFile; |
| 80 var mainDir = path.dirname(_mainPath); |
| 81 var baseDir = options.baseDir != null ? options.baseDir : mainDir; |
| 82 var outputDir = options.outputDir != null ? options.outputDir : mainDir; |
| 83 var packageRoot = options.packageRoot != null ? options.packageRoot |
| 84 : path.join(path.dirname(_mainPath), 'packages'); |
| 85 |
| 86 if (options.resetCssFile != null) { |
| 87 _resetCssFile = options.resetCssFile; |
| 88 if (path.isRelative(_resetCssFile)) { |
| 89 // If CSS reset file path is relative from our current path. |
| 90 _resetCssFile = path.resolve(_resetCssFile); |
| 91 } |
| 92 } |
| 93 |
| 94 // Normalize paths - all should be relative or absolute paths. |
| 95 if (path.isAbsolute(_mainPath) || path.isAbsolute(baseDir) || |
| 96 path.isAbsolute(outputDir) || path.isAbsolute(packageRoot)) { |
| 97 if (path.isRelative(_mainPath)) _mainPath = path.resolve(_mainPath); |
| 98 if (path.isRelative(baseDir)) baseDir = path.resolve(baseDir); |
| 99 if (path.isRelative(outputDir)) outputDir = path.resolve(outputDir); |
| 100 if (path.isRelative(packageRoot)) { |
| 101 packageRoot = path.resolve(packageRoot); |
| 102 } |
| 103 } |
| 104 _pathMapper = new PathMapper( |
| 105 baseDir, outputDir, packageRoot, options.forceMangle, |
| 106 options.rewriteUrls); |
| 107 } |
| 108 |
| 109 /** Compile the application starting from the given input file. */ |
| 110 Future run() { |
| 111 if (path.basename(_mainPath).endsWith('.dart')) { |
| 112 _messages.error("Please provide an HTML file as your entry point.", |
| 113 null); |
| 114 return new Future.value(null); |
| 115 } |
| 116 return _parseAndDiscover(_mainPath).then((_) { |
| 117 _analyze(); |
| 118 |
| 119 // Analyze all CSS files. |
| 120 _time('Analyzed Style Sheets', '', () => |
| 121 analyzeCss(_pathMapper.packageRoot, files, info, |
| 122 global.pseudoElements, _messages, |
| 123 warningsAsErrors: options.warningsAsErrors)); |
| 124 |
| 125 // TODO(jmesserly): need to go through our errors, and figure out if some |
| 126 // of them should be warnings instead. |
| 127 if (_messages.hasErrors || options.analysisOnly) return; |
| 128 _transformDart(); |
| 129 _emit(); |
| 130 }); |
| 131 } |
| 132 |
| 133 /** |
| 134 * Asynchronously parse [inputFile] and transitively discover web components |
| 135 * to load and parse. Returns a future that completes when all files are |
| 136 * processed. |
| 137 */ |
| 138 Future _parseAndDiscover(String inputFile) { |
| 139 _tasks = new FutureGroup(); |
| 140 _processed = new Set(); |
| 141 _processed.add(inputFile); |
| 142 _tasks.add(_parseHtmlFile(new UrlInfo(inputFile, inputFile, null))); |
| 143 return _tasks.future; |
| 144 } |
| 145 |
| 146 void _processHtmlFile(UrlInfo inputUrl, SourceFile file) { |
| 147 if (file == null) return; |
| 148 |
| 149 bool isEntryPoint = _processed.length == 1; |
| 150 |
| 151 files.add(file); |
| 152 |
| 153 var fileInfo = _time('Analyzed definitions', inputUrl.url, () { |
| 154 return analyzeDefinitions(global, inputUrl, file.document, |
| 155 _pathMapper.packageRoot, _messages, isEntryPoint: isEntryPoint); |
| 156 }); |
| 157 info[inputUrl.resolvedPath] = fileInfo; |
| 158 |
| 159 if (isEntryPoint && _resetCssFile != null) { |
| 160 _processed.add(_resetCssFile); |
| 161 _tasks.add(_parseCssFile(new UrlInfo(_resetCssFile, _resetCssFile, |
| 162 null))); |
| 163 } |
| 164 |
| 165 _setOutputFilenames(fileInfo); |
| 166 _processImports(fileInfo); |
| 167 |
| 168 // Load component files referenced by [file]. |
| 169 for (var link in fileInfo.componentLinks) { |
| 170 _loadFile(link, _parseHtmlFile); |
| 171 } |
| 172 |
| 173 // Load stylesheet files referenced by [file]. |
| 174 for (var link in fileInfo.styleSheetHrefs) { |
| 175 _loadFile(link, _parseCssFile); |
| 176 } |
| 177 |
| 178 // Load .dart files being referenced in the page. |
| 179 _loadFile(fileInfo.externalFile, _parseDartFile); |
| 180 |
| 181 // Process any @imports inside of a <style> tag. |
| 182 var urlInfos = findUrlsImported(fileInfo, fileInfo.inputUrl, |
| 183 _pathMapper.packageRoot, file.document, _messages, options); |
| 184 for (var urlInfo in urlInfos) { |
| 185 _loadFile(urlInfo, _parseCssFile); |
| 186 } |
| 187 |
| 188 // Load .dart files being referenced in components. |
| 189 for (var component in fileInfo.declaredComponents) { |
| 190 if (component.externalFile != null) { |
| 191 _loadFile(component.externalFile, _parseDartFile); |
| 192 } else if (component.userCode != null) { |
| 193 _processImports(component); |
| 194 } |
| 195 |
| 196 // Process any @imports inside of the <style> tag in a component. |
| 197 var urlInfos = findUrlsImported(component, |
| 198 component.declaringFile.inputUrl, _pathMapper.packageRoot, |
| 199 component.element, _messages, options); |
| 200 for (var urlInfo in urlInfos) { |
| 201 _loadFile(urlInfo, _parseCssFile); |
| 202 } |
| 203 } |
| 204 } |
| 205 |
| 206 /** |
| 207 * Helper function to load [urlInfo] and parse it using [loadAndParse] if it |
| 208 * hasn't been loaded before. |
| 209 */ |
| 210 void _loadFile(UrlInfo urlInfo, Future loadAndParse(UrlInfo inputUrl)) { |
| 211 if (urlInfo == null) return; |
| 212 var resolvedPath = urlInfo.resolvedPath; |
| 213 if (!_processed.contains(resolvedPath)) { |
| 214 _processed.add(resolvedPath); |
| 215 _tasks.add(loadAndParse(urlInfo)); |
| 216 } |
| 217 } |
| 218 |
| 219 void _setOutputFilenames(FileInfo fileInfo) { |
| 220 var filePath = fileInfo.dartCodeUrl.resolvedPath; |
| 221 fileInfo.outputFilename = _pathMapper.mangle(path.basename(filePath), |
| 222 '.dart', path.extension(filePath) == '.html'); |
| 223 for (var component in fileInfo.declaredComponents) { |
| 224 var externalFile = component.externalFile; |
| 225 var name = null; |
| 226 if (externalFile != null) { |
| 227 name = _pathMapper.mangle( |
| 228 path.basename(externalFile.resolvedPath), '.dart'); |
| 229 } else { |
| 230 var declaringFile = component.declaringFile; |
| 231 var prefix = path.basename(declaringFile.inputUrl.resolvedPath); |
| 232 if (declaringFile.declaredComponents.length == 1 |
| 233 && !declaringFile.codeAttached && !declaringFile.isEntryPoint) { |
| 234 name = _pathMapper.mangle(prefix, '.dart', true); |
| 235 } else { |
| 236 var componentName = component.tagName.replaceAll('-', '_'); |
| 237 name = _pathMapper.mangle('${prefix}_$componentName', '.dart', true); |
| 238 } |
| 239 } |
| 240 component.outputFilename = name; |
| 241 } |
| 242 } |
| 243 |
| 244 /** Parse an HTML file. */ |
| 245 Future _parseHtmlFile(UrlInfo inputUrl) { |
| 246 if (!_pathMapper.checkInputPath(inputUrl, _messages)) { |
| 247 return new Future<SourceFile>.value(null); |
| 248 } |
| 249 var filePath = inputUrl.resolvedPath; |
| 250 return fileSystem.readTextOrBytes(filePath) |
| 251 .catchError((e) => _readError(e, inputUrl)) |
| 252 .then((source) { |
| 253 if (source == null) return; |
| 254 var file = new SourceFile(filePath); |
| 255 file.document = _time('Parsed', filePath, |
| 256 () => parseHtml(source, filePath, _messages)); |
| 257 _processHtmlFile(inputUrl, file); |
| 258 }); |
| 259 } |
| 260 |
| 261 /** Parse a Dart file. */ |
| 262 Future _parseDartFile(UrlInfo inputUrl) { |
| 263 if (!_pathMapper.checkInputPath(inputUrl, _messages)) { |
| 264 return new Future<SourceFile>.value(null); |
| 265 } |
| 266 var filePath = inputUrl.resolvedPath; |
| 267 return fileSystem.readText(filePath) |
| 268 .catchError((e) => _readError(e, inputUrl)) |
| 269 .then((code) { |
| 270 if (code == null) return; |
| 271 var file = new SourceFile(filePath, type: SourceFile.DART); |
| 272 file.code = code; |
| 273 _processDartFile(inputUrl, file); |
| 274 }); |
| 275 } |
| 276 |
| 277 /** Parse a stylesheet file. */ |
| 278 Future _parseCssFile(UrlInfo inputUrl) { |
| 279 if (!options.emulateScopedCss || |
| 280 !_pathMapper.checkInputPath(inputUrl, _messages)) { |
| 281 return new Future<SourceFile>.value(null); |
| 282 } |
| 283 var filePath = inputUrl.resolvedPath; |
| 284 return fileSystem.readText(filePath) |
| 285 .catchError((e) => _readError(e, inputUrl, isWarning: true)) |
| 286 .then((code) { |
| 287 if (code == null) return; |
| 288 var file = new SourceFile(filePath, type: SourceFile.STYLESHEET); |
| 289 file.code = code; |
| 290 _processCssFile(inputUrl, file); |
| 291 }); |
| 292 } |
| 293 |
| 294 |
| 295 SourceFile _readError(error, UrlInfo inputUrl, {isWarning: false}) { |
| 296 var message = 'unable to open file "${inputUrl.resolvedPath}"'; |
| 297 if (options.verbose) { |
| 298 message = '$message. original message:\n $error'; |
| 299 } |
| 300 if (isWarning) { |
| 301 _messages.warning(message, inputUrl.sourceSpan); |
| 302 } else { |
| 303 _messages.error(message, inputUrl.sourceSpan); |
| 304 } |
| 305 return null; |
| 306 } |
| 307 |
| 308 void _processDartFile(UrlInfo inputUrl, SourceFile dartFile) { |
| 309 if (dartFile == null) return; |
| 310 |
| 311 files.add(dartFile); |
| 312 |
| 313 var resolvedPath = inputUrl.resolvedPath; |
| 314 var fileInfo = new FileInfo(inputUrl); |
| 315 info[resolvedPath] = fileInfo; |
| 316 fileInfo.inlinedCode = parseDartCode(resolvedPath, dartFile.code); |
| 317 fileInfo.outputFilename = |
| 318 _pathMapper.mangle(path.basename(resolvedPath), '.dart', false); |
| 319 |
| 320 _processImports(fileInfo); |
| 321 } |
| 322 |
| 323 void _processImports(LibraryInfo library) { |
| 324 if (library.userCode == null) return; |
| 325 |
| 326 for (var directive in library.userCode.directives) { |
| 327 _loadFile(_getDirectiveUrlInfo(library, directive), _parseDartFile); |
| 328 } |
| 329 } |
| 330 |
| 331 void _processCssFile(UrlInfo inputUrl, SourceFile cssFile) { |
| 332 if (cssFile == null) return; |
| 333 |
| 334 files.add(cssFile); |
| 335 |
| 336 var fileInfo = new FileInfo(inputUrl); |
| 337 info[inputUrl.resolvedPath] = fileInfo; |
| 338 |
| 339 var styleSheet = parseCss(cssFile.code, _messages, options); |
| 340 if (inputUrl.url == _resetCssFile) { |
| 341 _cssResetStyleSheet = styleSheet; |
| 342 } else if (styleSheet != null) { |
| 343 _resolveStyleSheetImports(inputUrl, cssFile.path, styleSheet); |
| 344 fileInfo.styleSheets.add(styleSheet); |
| 345 } |
| 346 } |
| 347 |
| 348 /** Load and parse all style sheets referenced with an @imports. */ |
| 349 void _resolveStyleSheetImports(UrlInfo inputUrl, String processingFile, |
| 350 StyleSheet styleSheet) { |
| 351 var urlInfos = _time('CSS imports', processingFile, () => |
| 352 findImportsInStyleSheet(styleSheet, _pathMapper.packageRoot, inputUrl, |
| 353 _messages)); |
| 354 |
| 355 for (var urlInfo in urlInfos) { |
| 356 if (urlInfo == null) break; |
| 357 // Load any @imported stylesheet files referenced in this style sheet. |
| 358 _loadFile(urlInfo, _parseCssFile); |
| 359 } |
| 360 } |
| 361 |
| 362 String _directiveUri(Directive directive) { |
| 363 var uriDirective = (directive as UriBasedDirective).uri; |
| 364 return (uriDirective as dynamic).value; |
| 365 } |
| 366 |
| 367 UrlInfo _getDirectiveUrlInfo(LibraryInfo library, Directive directive) { |
| 368 var uri = _directiveUri(directive); |
| 369 if (uri.startsWith('dart:')) return null; |
| 370 if (uri.startsWith('package:') && uri.startsWith('package:polymer/')) { |
| 371 // Don't process our own package -- we'll implement @observable manually. |
| 372 return null; |
| 373 } |
| 374 |
| 375 var span = library.userCode.sourceFile.span( |
| 376 directive.offset, directive.end); |
| 377 return UrlInfo.resolve(uri, library.dartCodeUrl, span, |
| 378 _pathMapper.packageRoot, _messages); |
| 379 } |
| 380 |
| 381 /** |
| 382 * Transform Dart source code. |
| 383 * Currently, the only transformation is [transformObservables]. |
| 384 * Calls _emitModifiedDartFiles to write the transformed files. |
| 385 */ |
| 386 void _transformDart() { |
| 387 var libraries = _findAllDartLibraries(); |
| 388 |
| 389 var transformed = []; |
| 390 for (var lib in libraries) { |
| 391 var userCode = lib.userCode; |
| 392 var transaction = transformObservables(userCode.compilationUnit, |
| 393 userCode.sourceFile, userCode.code, _messages); |
| 394 if (transaction != null) { |
| 395 _edits[lib.userCode] = transaction; |
| 396 if (transaction.hasEdits) { |
| 397 transformed.add(lib); |
| 398 } else if (lib.htmlFile != null) { |
| 399 // All web components will be transformed too. Track that. |
| 400 transformed.add(lib); |
| 401 } |
| 402 } |
| 403 } |
| 404 |
| 405 _findModifiedDartFiles(libraries, transformed); |
| 406 |
| 407 libraries.forEach(_fixImports); |
| 408 |
| 409 _emitModifiedDartFiles(libraries); |
| 410 } |
| 411 |
| 412 /** |
| 413 * Finds all Dart code libraries. |
| 414 * Each library will have [LibraryInfo.inlinedCode] that is non-null. |
| 415 * Also each inlinedCode will be unique. |
| 416 */ |
| 417 List<LibraryInfo> _findAllDartLibraries() { |
| 418 var libs = <LibraryInfo>[]; |
| 419 void _addLibrary(LibraryInfo lib) { |
| 420 if (lib.inlinedCode != null) libs.add(lib); |
| 421 } |
| 422 |
| 423 for (var sourceFile in files) { |
| 424 var file = info[sourceFile.path]; |
| 425 _addLibrary(file); |
| 426 file.declaredComponents.forEach(_addLibrary); |
| 427 } |
| 428 |
| 429 // Assert that each file path is unique. |
| 430 assert(_uniquePaths(libs)); |
| 431 return libs; |
| 432 } |
| 433 |
| 434 bool _uniquePaths(List<LibraryInfo> libs) { |
| 435 var seen = new Set(); |
| 436 for (var lib in libs) { |
| 437 if (seen.contains(lib.inlinedCode)) { |
| 438 throw new StateError('internal error: ' |
| 439 'duplicate user code for ${lib.dartCodeUrl.resolvedPath}.' |
| 440 ' Files were: $files'); |
| 441 } |
| 442 seen.add(lib.inlinedCode); |
| 443 } |
| 444 return true; |
| 445 } |
| 446 |
| 447 /** |
| 448 * Queue modified Dart files to be written. |
| 449 * This will not write files that are handled by [WebComponentEmitter] and |
| 450 * [EntryPointEmitter]. |
| 451 */ |
| 452 void _emitModifiedDartFiles(List<LibraryInfo> libraries) { |
| 453 for (var lib in libraries) { |
| 454 // Components will get emitted by WebComponentEmitter, and the |
| 455 // entry point will get emitted by MainPageEmitter. |
| 456 // So we only need to worry about other .dart files. |
| 457 if (lib.modified && lib is FileInfo && |
| 458 lib.htmlFile == null && !lib.isEntryPoint) { |
| 459 var transaction = _edits[lib.userCode]; |
| 460 |
| 461 // Save imports that were modified by _fixImports. |
| 462 for (var d in lib.userCode.directives) { |
| 463 transaction.edit(d.offset, d.end, d.toString()); |
| 464 } |
| 465 |
| 466 if (!lib.userCode.isPart) { |
| 467 var pos = lib.userCode.firstPartOffset; |
| 468 // Note: we use a different prefix than "autogenerated" to make |
| 469 // ChangeRecord unambiguous. Otherwise it would be imported by this |
| 470 // and polymer, resulting in a collision. |
| 471 // TODO(jmesserly): only generate this for libraries that need it. |
| 472 transaction.edit(pos, pos, "\nimport " |
| 473 "'package:observe/observe.dart' as __observe;\n"); |
| 474 } |
| 475 _emitFileAndSourceMaps(lib, transaction.commit(), lib.dartCodeUrl); |
| 476 } |
| 477 } |
| 478 } |
| 479 |
| 480 /** |
| 481 * This method computes which Dart files have been modified, starting |
| 482 * from [transformed] and marking recursively through all files that import |
| 483 * the modified files. |
| 484 */ |
| 485 void _findModifiedDartFiles(List<LibraryInfo> libraries, |
| 486 List<FileInfo> transformed) { |
| 487 |
| 488 if (transformed.length == 0) return; |
| 489 |
| 490 // Compute files that reference each file, then use this information to |
| 491 // flip the modified bit transitively. This is a lot simpler than trying |
| 492 // to compute it the other way because of circular references. |
| 493 for (var lib in libraries) { |
| 494 for (var directive in lib.userCode.directives) { |
| 495 var importPath = _getDirectiveUrlInfo(lib, directive); |
| 496 if (importPath == null) continue; |
| 497 |
| 498 var importInfo = info[importPath.resolvedPath]; |
| 499 if (importInfo != null) { |
| 500 importInfo.referencedBy.add(lib); |
| 501 } |
| 502 } |
| 503 } |
| 504 |
| 505 // Propegate the modified bit to anything that references a modified file. |
| 506 void setModified(LibraryInfo library) { |
| 507 if (library.modified) return; |
| 508 library.modified = true; |
| 509 library.referencedBy.forEach(setModified); |
| 510 } |
| 511 transformed.forEach(setModified); |
| 512 |
| 513 for (var lib in libraries) { |
| 514 // We don't need this anymore, so free it. |
| 515 lib.referencedBy = null; |
| 516 } |
| 517 } |
| 518 |
| 519 void _fixImports(LibraryInfo library) { |
| 520 // Fix imports. Modified files must use the generated path, otherwise |
| 521 // we need to make the path relative to the input. |
| 522 for (var directive in library.userCode.directives) { |
| 523 var importPath = _getDirectiveUrlInfo(library, directive); |
| 524 if (importPath == null) continue; |
| 525 var importInfo = info[importPath.resolvedPath]; |
| 526 if (importInfo == null) continue; |
| 527 |
| 528 String newUri = null; |
| 529 if (importInfo.modified) { |
| 530 // Use the generated URI for this file. |
| 531 newUri = _pathMapper.importUrlFor(library, importInfo); |
| 532 } else if (options.rewriteUrls) { |
| 533 // Get the relative path to the input file. |
| 534 newUri = _pathMapper.transformUrl( |
| 535 library.dartCodeUrl.resolvedPath, directive.uri.value); |
| 536 } |
| 537 if (newUri != null) { |
| 538 directive.uri = createStringLiteral(newUri); |
| 539 } |
| 540 } |
| 541 } |
| 542 |
| 543 /** Run the analyzer on every input html file. */ |
| 544 void _analyze() { |
| 545 var uniqueIds = new IntIterator(); |
| 546 for (var file in files) { |
| 547 if (file.isHtml) { |
| 548 _time('Analyzed contents', file.path, () => |
| 549 analyzeFile(file, info, uniqueIds, global, _messages, |
| 550 options.emulateScopedCss)); |
| 551 } |
| 552 } |
| 553 } |
| 554 |
| 555 /** Emit the generated code corresponding to each input file. */ |
| 556 void _emit() { |
| 557 for (var file in files) { |
| 558 if (file.isDart || file.isStyleSheet) continue; |
| 559 _time('Codegen', file.path, () { |
| 560 var fileInfo = info[file.path]; |
| 561 _emitComponents(fileInfo); |
| 562 }); |
| 563 } |
| 564 |
| 565 var entryPoint = files[0]; |
| 566 assert(info[entryPoint.path].isEntryPoint); |
| 567 _emitMainDart(entryPoint); |
| 568 _emitMainHtml(entryPoint); |
| 569 |
| 570 assert(_unqiueOutputs()); |
| 571 } |
| 572 |
| 573 bool _unqiueOutputs() { |
| 574 var seen = new Set(); |
| 575 for (var file in output) { |
| 576 if (seen.contains(file.path)) { |
| 577 throw new StateError('internal error: ' |
| 578 'duplicate output file ${file.path}. Files were: $output'); |
| 579 } |
| 580 seen.add(file.path); |
| 581 } |
| 582 return true; |
| 583 } |
| 584 |
| 585 /** Emit the main .dart file. */ |
| 586 void _emitMainDart(SourceFile file) { |
| 587 var fileInfo = info[file.path]; |
| 588 |
| 589 var codeInfo = fileInfo.userCode; |
| 590 if (codeInfo != null) { |
| 591 var printer = new NestedPrinter(0); |
| 592 if (codeInfo.libraryName == null) { |
| 593 printer.addLine('library ${fileInfo.libraryName};'); |
| 594 } |
| 595 printer.add(codeInfo.code); |
| 596 _emitFileAndSourceMaps(fileInfo, printer, fileInfo.dartCodeUrl); |
| 597 } |
| 598 } |
| 599 |
| 600 // TODO(jmesserly): refactor this out of Compiler. |
| 601 /** Generate an html file with the (trimmed down) main html page. */ |
| 602 void _emitMainHtml(SourceFile file) { |
| 603 var fileInfo = info[file.path]; |
| 604 |
| 605 var bootstrapName = '${path.basename(file.path)}_bootstrap.dart'; |
| 606 var bootstrapPath = path.join(path.dirname(file.path), bootstrapName); |
| 607 var bootstrapOutPath = _pathMapper.outputPath(bootstrapPath, ''); |
| 608 var bootstrapOutName = path.basename(bootstrapOutPath); |
| 609 var bootstrapInfo = new FileInfo(new UrlInfo('', bootstrapPath, null)); |
| 610 var printer = generateBootstrapCode(bootstrapInfo, fileInfo, global, |
| 611 _pathMapper, options); |
| 612 printer.build(bootstrapOutPath); |
| 613 output.add(new OutputFile( |
| 614 bootstrapOutPath, printer.text, source: file.path)); |
| 615 |
| 616 var document = file.document; |
| 617 var hasCss = _emitAllCss(); |
| 618 transformMainHtml(document, fileInfo, _pathMapper, hasCss, |
| 619 options.rewriteUrls, _messages, global, bootstrapOutName); |
| 620 output.add(new OutputFile(_pathMapper.outputPath(file.path, '.html'), |
| 621 document.outerHtml, source: file.path)); |
| 622 } |
| 623 |
| 624 // TODO(jmesserly): refactor this and other CSS related transforms out of |
| 625 // Compiler. |
| 626 /** |
| 627 * Generate an CSS file for all style sheets (main and components). |
| 628 * Returns true if a file was generated, otherwise false. |
| 629 */ |
| 630 bool _emitAllCss() { |
| 631 if (!options.emulateScopedCss) return false; |
| 632 |
| 633 var buff = new StringBuffer(); |
| 634 |
| 635 // Emit all linked style sheet files first. |
| 636 for (var file in files) { |
| 637 var css = new StringBuffer(); |
| 638 var fileInfo = info[file.path]; |
| 639 if (file.isStyleSheet) { |
| 640 for (var styleSheet in fileInfo.styleSheets) { |
| 641 // Translate any URIs in CSS. |
| 642 rewriteCssUris(_pathMapper, fileInfo.inputUrl.resolvedPath, |
| 643 options.rewriteUrls, styleSheet); |
| 644 css.write( |
| 645 '/* Auto-generated from style sheet href = ${file.path} */\n' |
| 646 '/* DO NOT EDIT. */\n\n'); |
| 647 css.write(emitStyleSheet(styleSheet, fileInfo)); |
| 648 css.write('\n\n'); |
| 649 } |
| 650 |
| 651 // Emit the linked style sheet in the output directory. |
| 652 if (fileInfo.inputUrl.url != _resetCssFile) { |
| 653 var outCss = _pathMapper.outputPath(fileInfo.inputUrl.resolvedPath, |
| 654 ''); |
| 655 output.add(new OutputFile(outCss, css.toString())); |
| 656 } |
| 657 } |
| 658 } |
| 659 |
| 660 // Emit all CSS for each component (style scoped). |
| 661 for (var file in files) { |
| 662 if (file.isHtml) { |
| 663 var fileInfo = info[file.path]; |
| 664 for (var component in fileInfo.declaredComponents) { |
| 665 for (var styleSheet in component.styleSheets) { |
| 666 // Translate any URIs in CSS. |
| 667 rewriteCssUris(_pathMapper, fileInfo.inputUrl.resolvedPath, |
| 668 options.rewriteUrls, styleSheet); |
| 669 |
| 670 if (buff.isEmpty) { |
| 671 buff.write( |
| 672 '/* Auto-generated from components style tags. */\n' |
| 673 '/* DO NOT EDIT. */\n\n'); |
| 674 } |
| 675 buff.write( |
| 676 '/* ==================================================== \n' |
| 677 ' Component ${component.tagName} stylesheet \n' |
| 678 ' ==================================================== */\n'); |
| 679 |
| 680 var tagName = component.tagName; |
| 681 if (!component.hasAuthorStyles) { |
| 682 if (_cssResetStyleSheet != null) { |
| 683 // If component doesn't have apply-author-styles then we need to |
| 684 // reset the CSS the styles for the component (if css-reset file |
| 685 // option was passed). |
| 686 buff.write('\n/* Start CSS Reset */\n'); |
| 687 var style; |
| 688 if (options.emulateScopedCss) { |
| 689 style = emitComponentStyleSheet(_cssResetStyleSheet, tagName); |
| 690 } else { |
| 691 style = emitOriginalCss(_cssResetStyleSheet); |
| 692 } |
| 693 buff.write(style); |
| 694 buff.write('/* End CSS Reset */\n\n'); |
| 695 } |
| 696 } |
| 697 if (options.emulateScopedCss) { |
| 698 buff.write(emitComponentStyleSheet(styleSheet, tagName)); |
| 699 } else { |
| 700 buff.write(emitOriginalCss(styleSheet)); |
| 701 } |
| 702 buff.write('\n\n'); |
| 703 } |
| 704 } |
| 705 } |
| 706 } |
| 707 |
| 708 if (buff.isEmpty) return false; |
| 709 |
| 710 var cssPath = _pathMapper.outputPath(_mainPath, '.css', true); |
| 711 output.add(new OutputFile(cssPath, buff.toString())); |
| 712 return true; |
| 713 } |
| 714 |
| 715 /** Emits the Dart code for all components in [fileInfo]. */ |
| 716 void _emitComponents(FileInfo fileInfo) { |
| 717 for (var component in fileInfo.declaredComponents) { |
| 718 // TODO(terry): Handle more than one stylesheet per component |
| 719 if (component.styleSheets.length > 1 && options.emulateScopedCss) { |
| 720 var span = component.externalFile != null |
| 721 ? component.externalFile.sourceSpan : null; |
| 722 _messages.warning( |
| 723 'Component has more than one stylesheet - first stylesheet used.', |
| 724 span); |
| 725 } |
| 726 var printer = emitPolymerElement( |
| 727 component, _pathMapper, _edits[component.userCode], options); |
| 728 _emitFileAndSourceMaps(component, printer, component.externalFile); |
| 729 } |
| 730 } |
| 731 |
| 732 /** |
| 733 * Emits a file that was created using [NestedPrinter] and it's corresponding |
| 734 * source map file. |
| 735 */ |
| 736 void _emitFileAndSourceMaps( |
| 737 LibraryInfo lib, NestedPrinter printer, UrlInfo dartCodeUrl) { |
| 738 // Bail if we had an error generating the code for the file. |
| 739 if (printer == null) return; |
| 740 |
| 741 var libPath = _pathMapper.outputLibraryPath(lib); |
| 742 var dir = path.dirname(libPath); |
| 743 var filename = path.basename(libPath); |
| 744 printer.add('\n//# sourceMappingURL=$filename.map'); |
| 745 printer.build(libPath); |
| 746 var sourcePath = dartCodeUrl != null ? dartCodeUrl.resolvedPath : null; |
| 747 output.add(new OutputFile(libPath, printer.text, source: sourcePath)); |
| 748 // Fix-up the paths in the source map file |
| 749 var sourceMap = json.parse(printer.map); |
| 750 var urls = sourceMap['sources']; |
| 751 for (int i = 0; i < urls.length; i++) { |
| 752 urls[i] = path.relative(urls[i], from: dir); |
| 753 } |
| 754 output.add(new OutputFile(path.join(dir, '$filename.map'), |
| 755 json.stringify(sourceMap))); |
| 756 } |
| 757 |
| 758 _time(String logMessage, String filePath, callback(), |
| 759 {bool printTime: false}) { |
| 760 var message = new StringBuffer(); |
| 761 message.write(logMessage); |
| 762 var filename = path.basename(filePath); |
| 763 for (int i = (60 - logMessage.length - filename.length); i > 0 ; i--) { |
| 764 message.write(' '); |
| 765 } |
| 766 message.write(filename); |
| 767 return time(message.toString(), callback, |
| 768 printTime: options.verbose || printTime); |
| 769 } |
| 770 } |
OLD | NEW |