Index: pkg/polymer/lib/src/compiler.dart |
diff --git a/pkg/polymer/lib/src/compiler.dart b/pkg/polymer/lib/src/compiler.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5e2c98f4238019f05062a9c45941b43928d780fe |
--- /dev/null |
+++ b/pkg/polymer/lib/src/compiler.dart |
@@ -0,0 +1,770 @@ |
+// 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. |
+ |
+library compiler; |
+ |
+import 'dart:async'; |
+import 'dart:collection' show SplayTreeMap; |
+import 'dart:json' as json; |
+ |
+import 'package:analyzer_experimental/src/generated/ast.dart' show Directive, UriBasedDirective; |
+import 'package:csslib/visitor.dart' show StyleSheet, treeToDebugString; |
+import 'package:html5lib/dom.dart'; |
+import 'package:html5lib/parser.dart'; |
+import 'package:observe/transform.dart' show transformObservables; |
+import 'package:source_maps/span.dart' show Span; |
+import 'package:source_maps/refactor.dart' show TextEditTransaction; |
+import 'package:source_maps/printer.dart'; |
+ |
+import 'analyzer.dart'; |
+import 'css_analyzer.dart' show analyzeCss, findUrlsImported, |
+ findImportsInStyleSheet, parseCss; |
+import 'css_emitters.dart' show rewriteCssUris, |
+ emitComponentStyleSheet, emitOriginalCss, emitStyleSheet; |
+import 'dart_parser.dart'; |
+import 'emitters.dart'; |
+import 'file_system.dart'; |
+import 'files.dart'; |
+import 'info.dart'; |
+import 'messages.dart'; |
+import 'compiler_options.dart'; |
+import 'paths.dart'; |
+import 'utils.dart'; |
+ |
+/** |
+ * Parses an HTML file [contents] and returns a DOM-like tree. |
+ * Note that [contents] will be a [String] if coming from a browser-based |
+ * [FileSystem], or it will be a [List<int>] if running on the command line. |
+ * |
+ * Adds emitted error/warning to [messages], if [messages] is supplied. |
+ */ |
+Document parseHtml(contents, String sourcePath, Messages messages) { |
+ var parser = new HtmlParser(contents, generateSpans: true, |
+ sourceUrl: sourcePath); |
+ var document = parser.parse(); |
+ |
+ // Note: errors aren't fatal in HTML (unless strict mode is on). |
+ // So just print them as warnings. |
+ for (var e in parser.errors) { |
+ messages.warning(e.message, e.span); |
+ } |
+ return document; |
+} |
+ |
+/** Compiles an application written with Dart web components. */ |
+class Compiler { |
+ final FileSystem fileSystem; |
+ final CompilerOptions options; |
+ final List<SourceFile> files = <SourceFile>[]; |
+ final List<OutputFile> output = <OutputFile>[]; |
+ |
+ String _mainPath; |
+ String _resetCssFile; |
+ StyleSheet _cssResetStyleSheet; |
+ PathMapper _pathMapper; |
+ Messages _messages; |
+ |
+ FutureGroup _tasks; |
+ Set _processed; |
+ |
+ /** Information about source [files] given their href. */ |
+ final Map<String, FileInfo> info = new SplayTreeMap<String, FileInfo>(); |
+ final _edits = new Map<DartCodeInfo, TextEditTransaction>(); |
+ |
+ final GlobalInfo global = new GlobalInfo(); |
+ |
+ /** Creates a compiler with [options] using [fileSystem]. */ |
+ Compiler(this.fileSystem, this.options, this._messages) { |
+ _mainPath = options.inputFile; |
+ var mainDir = path.dirname(_mainPath); |
+ var baseDir = options.baseDir != null ? options.baseDir : mainDir; |
+ var outputDir = options.outputDir != null ? options.outputDir : mainDir; |
+ var packageRoot = options.packageRoot != null ? options.packageRoot |
+ : path.join(path.dirname(_mainPath), 'packages'); |
+ |
+ if (options.resetCssFile != null) { |
+ _resetCssFile = options.resetCssFile; |
+ if (path.isRelative(_resetCssFile)) { |
+ // If CSS reset file path is relative from our current path. |
+ _resetCssFile = path.resolve(_resetCssFile); |
+ } |
+ } |
+ |
+ // Normalize paths - all should be relative or absolute paths. |
+ if (path.isAbsolute(_mainPath) || path.isAbsolute(baseDir) || |
+ path.isAbsolute(outputDir) || path.isAbsolute(packageRoot)) { |
+ if (path.isRelative(_mainPath)) _mainPath = path.resolve(_mainPath); |
+ if (path.isRelative(baseDir)) baseDir = path.resolve(baseDir); |
+ if (path.isRelative(outputDir)) outputDir = path.resolve(outputDir); |
+ if (path.isRelative(packageRoot)) { |
+ packageRoot = path.resolve(packageRoot); |
+ } |
+ } |
+ _pathMapper = new PathMapper( |
+ baseDir, outputDir, packageRoot, options.forceMangle, |
+ options.rewriteUrls); |
+ } |
+ |
+ /** Compile the application starting from the given input file. */ |
+ Future run() { |
+ if (path.basename(_mainPath).endsWith('.dart')) { |
+ _messages.error("Please provide an HTML file as your entry point.", |
+ null); |
+ return new Future.value(null); |
+ } |
+ return _parseAndDiscover(_mainPath).then((_) { |
+ _analyze(); |
+ |
+ // Analyze all CSS files. |
+ _time('Analyzed Style Sheets', '', () => |
+ analyzeCss(_pathMapper.packageRoot, files, info, |
+ global.pseudoElements, _messages, |
+ warningsAsErrors: options.warningsAsErrors)); |
+ |
+ // TODO(jmesserly): need to go through our errors, and figure out if some |
+ // of them should be warnings instead. |
+ if (_messages.hasErrors || options.analysisOnly) return; |
+ _transformDart(); |
+ _emit(); |
+ }); |
+ } |
+ |
+ /** |
+ * Asynchronously parse [inputFile] and transitively discover web components |
+ * to load and parse. Returns a future that completes when all files are |
+ * processed. |
+ */ |
+ Future _parseAndDiscover(String inputFile) { |
+ _tasks = new FutureGroup(); |
+ _processed = new Set(); |
+ _processed.add(inputFile); |
+ _tasks.add(_parseHtmlFile(new UrlInfo(inputFile, inputFile, null))); |
+ return _tasks.future; |
+ } |
+ |
+ void _processHtmlFile(UrlInfo inputUrl, SourceFile file) { |
+ if (file == null) return; |
+ |
+ bool isEntryPoint = _processed.length == 1; |
+ |
+ files.add(file); |
+ |
+ var fileInfo = _time('Analyzed definitions', inputUrl.url, () { |
+ return analyzeDefinitions(global, inputUrl, file.document, |
+ _pathMapper.packageRoot, _messages, isEntryPoint: isEntryPoint); |
+ }); |
+ info[inputUrl.resolvedPath] = fileInfo; |
+ |
+ if (isEntryPoint && _resetCssFile != null) { |
+ _processed.add(_resetCssFile); |
+ _tasks.add(_parseCssFile(new UrlInfo(_resetCssFile, _resetCssFile, |
+ null))); |
+ } |
+ |
+ _setOutputFilenames(fileInfo); |
+ _processImports(fileInfo); |
+ |
+ // Load component files referenced by [file]. |
+ for (var link in fileInfo.componentLinks) { |
+ _loadFile(link, _parseHtmlFile); |
+ } |
+ |
+ // Load stylesheet files referenced by [file]. |
+ for (var link in fileInfo.styleSheetHrefs) { |
+ _loadFile(link, _parseCssFile); |
+ } |
+ |
+ // Load .dart files being referenced in the page. |
+ _loadFile(fileInfo.externalFile, _parseDartFile); |
+ |
+ // Process any @imports inside of a <style> tag. |
+ var urlInfos = findUrlsImported(fileInfo, fileInfo.inputUrl, |
+ _pathMapper.packageRoot, file.document, _messages, options); |
+ for (var urlInfo in urlInfos) { |
+ _loadFile(urlInfo, _parseCssFile); |
+ } |
+ |
+ // Load .dart files being referenced in components. |
+ for (var component in fileInfo.declaredComponents) { |
+ if (component.externalFile != null) { |
+ _loadFile(component.externalFile, _parseDartFile); |
+ } else if (component.userCode != null) { |
+ _processImports(component); |
+ } |
+ |
+ // Process any @imports inside of the <style> tag in a component. |
+ var urlInfos = findUrlsImported(component, |
+ component.declaringFile.inputUrl, _pathMapper.packageRoot, |
+ component.element, _messages, options); |
+ for (var urlInfo in urlInfos) { |
+ _loadFile(urlInfo, _parseCssFile); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Helper function to load [urlInfo] and parse it using [loadAndParse] if it |
+ * hasn't been loaded before. |
+ */ |
+ void _loadFile(UrlInfo urlInfo, Future loadAndParse(UrlInfo inputUrl)) { |
+ if (urlInfo == null) return; |
+ var resolvedPath = urlInfo.resolvedPath; |
+ if (!_processed.contains(resolvedPath)) { |
+ _processed.add(resolvedPath); |
+ _tasks.add(loadAndParse(urlInfo)); |
+ } |
+ } |
+ |
+ void _setOutputFilenames(FileInfo fileInfo) { |
+ var filePath = fileInfo.dartCodeUrl.resolvedPath; |
+ fileInfo.outputFilename = _pathMapper.mangle(path.basename(filePath), |
+ '.dart', path.extension(filePath) == '.html'); |
+ for (var component in fileInfo.declaredComponents) { |
+ var externalFile = component.externalFile; |
+ var name = null; |
+ if (externalFile != null) { |
+ name = _pathMapper.mangle( |
+ path.basename(externalFile.resolvedPath), '.dart'); |
+ } else { |
+ var declaringFile = component.declaringFile; |
+ var prefix = path.basename(declaringFile.inputUrl.resolvedPath); |
+ if (declaringFile.declaredComponents.length == 1 |
+ && !declaringFile.codeAttached && !declaringFile.isEntryPoint) { |
+ name = _pathMapper.mangle(prefix, '.dart', true); |
+ } else { |
+ var componentName = component.tagName.replaceAll('-', '_'); |
+ name = _pathMapper.mangle('${prefix}_$componentName', '.dart', true); |
+ } |
+ } |
+ component.outputFilename = name; |
+ } |
+ } |
+ |
+ /** Parse an HTML file. */ |
+ Future _parseHtmlFile(UrlInfo inputUrl) { |
+ if (!_pathMapper.checkInputPath(inputUrl, _messages)) { |
+ return new Future<SourceFile>.value(null); |
+ } |
+ var filePath = inputUrl.resolvedPath; |
+ return fileSystem.readTextOrBytes(filePath) |
+ .catchError((e) => _readError(e, inputUrl)) |
+ .then((source) { |
+ if (source == null) return; |
+ var file = new SourceFile(filePath); |
+ file.document = _time('Parsed', filePath, |
+ () => parseHtml(source, filePath, _messages)); |
+ _processHtmlFile(inputUrl, file); |
+ }); |
+ } |
+ |
+ /** Parse a Dart file. */ |
+ Future _parseDartFile(UrlInfo inputUrl) { |
+ if (!_pathMapper.checkInputPath(inputUrl, _messages)) { |
+ return new Future<SourceFile>.value(null); |
+ } |
+ var filePath = inputUrl.resolvedPath; |
+ return fileSystem.readText(filePath) |
+ .catchError((e) => _readError(e, inputUrl)) |
+ .then((code) { |
+ if (code == null) return; |
+ var file = new SourceFile(filePath, type: SourceFile.DART); |
+ file.code = code; |
+ _processDartFile(inputUrl, file); |
+ }); |
+ } |
+ |
+ /** Parse a stylesheet file. */ |
+ Future _parseCssFile(UrlInfo inputUrl) { |
+ if (!options.emulateScopedCss || |
+ !_pathMapper.checkInputPath(inputUrl, _messages)) { |
+ return new Future<SourceFile>.value(null); |
+ } |
+ var filePath = inputUrl.resolvedPath; |
+ return fileSystem.readText(filePath) |
+ .catchError((e) => _readError(e, inputUrl, isWarning: true)) |
+ .then((code) { |
+ if (code == null) return; |
+ var file = new SourceFile(filePath, type: SourceFile.STYLESHEET); |
+ file.code = code; |
+ _processCssFile(inputUrl, file); |
+ }); |
+ } |
+ |
+ |
+ SourceFile _readError(error, UrlInfo inputUrl, {isWarning: false}) { |
+ var message = 'unable to open file "${inputUrl.resolvedPath}"'; |
+ if (options.verbose) { |
+ message = '$message. original message:\n $error'; |
+ } |
+ if (isWarning) { |
+ _messages.warning(message, inputUrl.sourceSpan); |
+ } else { |
+ _messages.error(message, inputUrl.sourceSpan); |
+ } |
+ return null; |
+ } |
+ |
+ void _processDartFile(UrlInfo inputUrl, SourceFile dartFile) { |
+ if (dartFile == null) return; |
+ |
+ files.add(dartFile); |
+ |
+ var resolvedPath = inputUrl.resolvedPath; |
+ var fileInfo = new FileInfo(inputUrl); |
+ info[resolvedPath] = fileInfo; |
+ fileInfo.inlinedCode = parseDartCode(resolvedPath, dartFile.code); |
+ fileInfo.outputFilename = |
+ _pathMapper.mangle(path.basename(resolvedPath), '.dart', false); |
+ |
+ _processImports(fileInfo); |
+ } |
+ |
+ void _processImports(LibraryInfo library) { |
+ if (library.userCode == null) return; |
+ |
+ for (var directive in library.userCode.directives) { |
+ _loadFile(_getDirectiveUrlInfo(library, directive), _parseDartFile); |
+ } |
+ } |
+ |
+ void _processCssFile(UrlInfo inputUrl, SourceFile cssFile) { |
+ if (cssFile == null) return; |
+ |
+ files.add(cssFile); |
+ |
+ var fileInfo = new FileInfo(inputUrl); |
+ info[inputUrl.resolvedPath] = fileInfo; |
+ |
+ var styleSheet = parseCss(cssFile.code, _messages, options); |
+ if (inputUrl.url == _resetCssFile) { |
+ _cssResetStyleSheet = styleSheet; |
+ } else if (styleSheet != null) { |
+ _resolveStyleSheetImports(inputUrl, cssFile.path, styleSheet); |
+ fileInfo.styleSheets.add(styleSheet); |
+ } |
+ } |
+ |
+ /** Load and parse all style sheets referenced with an @imports. */ |
+ void _resolveStyleSheetImports(UrlInfo inputUrl, String processingFile, |
+ StyleSheet styleSheet) { |
+ var urlInfos = _time('CSS imports', processingFile, () => |
+ findImportsInStyleSheet(styleSheet, _pathMapper.packageRoot, inputUrl, |
+ _messages)); |
+ |
+ for (var urlInfo in urlInfos) { |
+ if (urlInfo == null) break; |
+ // Load any @imported stylesheet files referenced in this style sheet. |
+ _loadFile(urlInfo, _parseCssFile); |
+ } |
+ } |
+ |
+ String _directiveUri(Directive directive) { |
+ var uriDirective = (directive as UriBasedDirective).uri; |
+ return (uriDirective as dynamic).value; |
+ } |
+ |
+ UrlInfo _getDirectiveUrlInfo(LibraryInfo library, Directive directive) { |
+ var uri = _directiveUri(directive); |
+ if (uri.startsWith('dart:')) return null; |
+ if (uri.startsWith('package:') && uri.startsWith('package:polymer/')) { |
+ // Don't process our own package -- we'll implement @observable manually. |
+ return null; |
+ } |
+ |
+ var span = library.userCode.sourceFile.span( |
+ directive.offset, directive.end); |
+ return UrlInfo.resolve(uri, library.dartCodeUrl, span, |
+ _pathMapper.packageRoot, _messages); |
+ } |
+ |
+ /** |
+ * Transform Dart source code. |
+ * Currently, the only transformation is [transformObservables]. |
+ * Calls _emitModifiedDartFiles to write the transformed files. |
+ */ |
+ void _transformDart() { |
+ var libraries = _findAllDartLibraries(); |
+ |
+ var transformed = []; |
+ for (var lib in libraries) { |
+ var userCode = lib.userCode; |
+ var transaction = transformObservables(userCode.compilationUnit, |
+ userCode.sourceFile, userCode.code, _messages); |
+ if (transaction != null) { |
+ _edits[lib.userCode] = transaction; |
+ if (transaction.hasEdits) { |
+ transformed.add(lib); |
+ } else if (lib.htmlFile != null) { |
+ // All web components will be transformed too. Track that. |
+ transformed.add(lib); |
+ } |
+ } |
+ } |
+ |
+ _findModifiedDartFiles(libraries, transformed); |
+ |
+ libraries.forEach(_fixImports); |
+ |
+ _emitModifiedDartFiles(libraries); |
+ } |
+ |
+ /** |
+ * Finds all Dart code libraries. |
+ * Each library will have [LibraryInfo.inlinedCode] that is non-null. |
+ * Also each inlinedCode will be unique. |
+ */ |
+ List<LibraryInfo> _findAllDartLibraries() { |
+ var libs = <LibraryInfo>[]; |
+ void _addLibrary(LibraryInfo lib) { |
+ if (lib.inlinedCode != null) libs.add(lib); |
+ } |
+ |
+ for (var sourceFile in files) { |
+ var file = info[sourceFile.path]; |
+ _addLibrary(file); |
+ file.declaredComponents.forEach(_addLibrary); |
+ } |
+ |
+ // Assert that each file path is unique. |
+ assert(_uniquePaths(libs)); |
+ return libs; |
+ } |
+ |
+ bool _uniquePaths(List<LibraryInfo> libs) { |
+ var seen = new Set(); |
+ for (var lib in libs) { |
+ if (seen.contains(lib.inlinedCode)) { |
+ throw new StateError('internal error: ' |
+ 'duplicate user code for ${lib.dartCodeUrl.resolvedPath}.' |
+ ' Files were: $files'); |
+ } |
+ seen.add(lib.inlinedCode); |
+ } |
+ return true; |
+ } |
+ |
+ /** |
+ * Queue modified Dart files to be written. |
+ * This will not write files that are handled by [WebComponentEmitter] and |
+ * [EntryPointEmitter]. |
+ */ |
+ void _emitModifiedDartFiles(List<LibraryInfo> libraries) { |
+ for (var lib in libraries) { |
+ // Components will get emitted by WebComponentEmitter, and the |
+ // entry point will get emitted by MainPageEmitter. |
+ // So we only need to worry about other .dart files. |
+ if (lib.modified && lib is FileInfo && |
+ lib.htmlFile == null && !lib.isEntryPoint) { |
+ var transaction = _edits[lib.userCode]; |
+ |
+ // Save imports that were modified by _fixImports. |
+ for (var d in lib.userCode.directives) { |
+ transaction.edit(d.offset, d.end, d.toString()); |
+ } |
+ |
+ if (!lib.userCode.isPart) { |
+ var pos = lib.userCode.firstPartOffset; |
+ // Note: we use a different prefix than "autogenerated" to make |
+ // ChangeRecord unambiguous. Otherwise it would be imported by this |
+ // and polymer, resulting in a collision. |
+ // TODO(jmesserly): only generate this for libraries that need it. |
+ transaction.edit(pos, pos, "\nimport " |
+ "'package:observe/observe.dart' as __observe;\n"); |
+ } |
+ _emitFileAndSourceMaps(lib, transaction.commit(), lib.dartCodeUrl); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * This method computes which Dart files have been modified, starting |
+ * from [transformed] and marking recursively through all files that import |
+ * the modified files. |
+ */ |
+ void _findModifiedDartFiles(List<LibraryInfo> libraries, |
+ List<FileInfo> transformed) { |
+ |
+ if (transformed.length == 0) return; |
+ |
+ // Compute files that reference each file, then use this information to |
+ // flip the modified bit transitively. This is a lot simpler than trying |
+ // to compute it the other way because of circular references. |
+ for (var lib in libraries) { |
+ for (var directive in lib.userCode.directives) { |
+ var importPath = _getDirectiveUrlInfo(lib, directive); |
+ if (importPath == null) continue; |
+ |
+ var importInfo = info[importPath.resolvedPath]; |
+ if (importInfo != null) { |
+ importInfo.referencedBy.add(lib); |
+ } |
+ } |
+ } |
+ |
+ // Propegate the modified bit to anything that references a modified file. |
+ void setModified(LibraryInfo library) { |
+ if (library.modified) return; |
+ library.modified = true; |
+ library.referencedBy.forEach(setModified); |
+ } |
+ transformed.forEach(setModified); |
+ |
+ for (var lib in libraries) { |
+ // We don't need this anymore, so free it. |
+ lib.referencedBy = null; |
+ } |
+ } |
+ |
+ void _fixImports(LibraryInfo library) { |
+ // Fix imports. Modified files must use the generated path, otherwise |
+ // we need to make the path relative to the input. |
+ for (var directive in library.userCode.directives) { |
+ var importPath = _getDirectiveUrlInfo(library, directive); |
+ if (importPath == null) continue; |
+ var importInfo = info[importPath.resolvedPath]; |
+ if (importInfo == null) continue; |
+ |
+ String newUri = null; |
+ if (importInfo.modified) { |
+ // Use the generated URI for this file. |
+ newUri = _pathMapper.importUrlFor(library, importInfo); |
+ } else if (options.rewriteUrls) { |
+ // Get the relative path to the input file. |
+ newUri = _pathMapper.transformUrl( |
+ library.dartCodeUrl.resolvedPath, directive.uri.value); |
+ } |
+ if (newUri != null) { |
+ directive.uri = createStringLiteral(newUri); |
+ } |
+ } |
+ } |
+ |
+ /** Run the analyzer on every input html file. */ |
+ void _analyze() { |
+ var uniqueIds = new IntIterator(); |
+ for (var file in files) { |
+ if (file.isHtml) { |
+ _time('Analyzed contents', file.path, () => |
+ analyzeFile(file, info, uniqueIds, global, _messages, |
+ options.emulateScopedCss)); |
+ } |
+ } |
+ } |
+ |
+ /** Emit the generated code corresponding to each input file. */ |
+ void _emit() { |
+ for (var file in files) { |
+ if (file.isDart || file.isStyleSheet) continue; |
+ _time('Codegen', file.path, () { |
+ var fileInfo = info[file.path]; |
+ _emitComponents(fileInfo); |
+ }); |
+ } |
+ |
+ var entryPoint = files[0]; |
+ assert(info[entryPoint.path].isEntryPoint); |
+ _emitMainDart(entryPoint); |
+ _emitMainHtml(entryPoint); |
+ |
+ assert(_unqiueOutputs()); |
+ } |
+ |
+ bool _unqiueOutputs() { |
+ var seen = new Set(); |
+ for (var file in output) { |
+ if (seen.contains(file.path)) { |
+ throw new StateError('internal error: ' |
+ 'duplicate output file ${file.path}. Files were: $output'); |
+ } |
+ seen.add(file.path); |
+ } |
+ return true; |
+ } |
+ |
+ /** Emit the main .dart file. */ |
+ void _emitMainDart(SourceFile file) { |
+ var fileInfo = info[file.path]; |
+ |
+ var codeInfo = fileInfo.userCode; |
+ if (codeInfo != null) { |
+ var printer = new NestedPrinter(0); |
+ if (codeInfo.libraryName == null) { |
+ printer.addLine('library ${fileInfo.libraryName};'); |
+ } |
+ printer.add(codeInfo.code); |
+ _emitFileAndSourceMaps(fileInfo, printer, fileInfo.dartCodeUrl); |
+ } |
+ } |
+ |
+ // TODO(jmesserly): refactor this out of Compiler. |
+ /** Generate an html file with the (trimmed down) main html page. */ |
+ void _emitMainHtml(SourceFile file) { |
+ var fileInfo = info[file.path]; |
+ |
+ var bootstrapName = '${path.basename(file.path)}_bootstrap.dart'; |
+ var bootstrapPath = path.join(path.dirname(file.path), bootstrapName); |
+ var bootstrapOutPath = _pathMapper.outputPath(bootstrapPath, ''); |
+ var bootstrapOutName = path.basename(bootstrapOutPath); |
+ var bootstrapInfo = new FileInfo(new UrlInfo('', bootstrapPath, null)); |
+ var printer = generateBootstrapCode(bootstrapInfo, fileInfo, global, |
+ _pathMapper, options); |
+ printer.build(bootstrapOutPath); |
+ output.add(new OutputFile( |
+ bootstrapOutPath, printer.text, source: file.path)); |
+ |
+ var document = file.document; |
+ var hasCss = _emitAllCss(); |
+ transformMainHtml(document, fileInfo, _pathMapper, hasCss, |
+ options.rewriteUrls, _messages, global, bootstrapOutName); |
+ output.add(new OutputFile(_pathMapper.outputPath(file.path, '.html'), |
+ document.outerHtml, source: file.path)); |
+ } |
+ |
+ // TODO(jmesserly): refactor this and other CSS related transforms out of |
+ // Compiler. |
+ /** |
+ * Generate an CSS file for all style sheets (main and components). |
+ * Returns true if a file was generated, otherwise false. |
+ */ |
+ bool _emitAllCss() { |
+ if (!options.emulateScopedCss) return false; |
+ |
+ var buff = new StringBuffer(); |
+ |
+ // Emit all linked style sheet files first. |
+ for (var file in files) { |
+ var css = new StringBuffer(); |
+ var fileInfo = info[file.path]; |
+ if (file.isStyleSheet) { |
+ for (var styleSheet in fileInfo.styleSheets) { |
+ // Translate any URIs in CSS. |
+ rewriteCssUris(_pathMapper, fileInfo.inputUrl.resolvedPath, |
+ options.rewriteUrls, styleSheet); |
+ css.write( |
+ '/* Auto-generated from style sheet href = ${file.path} */\n' |
+ '/* DO NOT EDIT. */\n\n'); |
+ css.write(emitStyleSheet(styleSheet, fileInfo)); |
+ css.write('\n\n'); |
+ } |
+ |
+ // Emit the linked style sheet in the output directory. |
+ if (fileInfo.inputUrl.url != _resetCssFile) { |
+ var outCss = _pathMapper.outputPath(fileInfo.inputUrl.resolvedPath, |
+ ''); |
+ output.add(new OutputFile(outCss, css.toString())); |
+ } |
+ } |
+ } |
+ |
+ // Emit all CSS for each component (style scoped). |
+ for (var file in files) { |
+ if (file.isHtml) { |
+ var fileInfo = info[file.path]; |
+ for (var component in fileInfo.declaredComponents) { |
+ for (var styleSheet in component.styleSheets) { |
+ // Translate any URIs in CSS. |
+ rewriteCssUris(_pathMapper, fileInfo.inputUrl.resolvedPath, |
+ options.rewriteUrls, styleSheet); |
+ |
+ if (buff.isEmpty) { |
+ buff.write( |
+ '/* Auto-generated from components style tags. */\n' |
+ '/* DO NOT EDIT. */\n\n'); |
+ } |
+ buff.write( |
+ '/* ==================================================== \n' |
+ ' Component ${component.tagName} stylesheet \n' |
+ ' ==================================================== */\n'); |
+ |
+ var tagName = component.tagName; |
+ if (!component.hasAuthorStyles) { |
+ if (_cssResetStyleSheet != null) { |
+ // If component doesn't have apply-author-styles then we need to |
+ // reset the CSS the styles for the component (if css-reset file |
+ // option was passed). |
+ buff.write('\n/* Start CSS Reset */\n'); |
+ var style; |
+ if (options.emulateScopedCss) { |
+ style = emitComponentStyleSheet(_cssResetStyleSheet, tagName); |
+ } else { |
+ style = emitOriginalCss(_cssResetStyleSheet); |
+ } |
+ buff.write(style); |
+ buff.write('/* End CSS Reset */\n\n'); |
+ } |
+ } |
+ if (options.emulateScopedCss) { |
+ buff.write(emitComponentStyleSheet(styleSheet, tagName)); |
+ } else { |
+ buff.write(emitOriginalCss(styleSheet)); |
+ } |
+ buff.write('\n\n'); |
+ } |
+ } |
+ } |
+ } |
+ |
+ if (buff.isEmpty) return false; |
+ |
+ var cssPath = _pathMapper.outputPath(_mainPath, '.css', true); |
+ output.add(new OutputFile(cssPath, buff.toString())); |
+ return true; |
+ } |
+ |
+ /** Emits the Dart code for all components in [fileInfo]. */ |
+ void _emitComponents(FileInfo fileInfo) { |
+ for (var component in fileInfo.declaredComponents) { |
+ // TODO(terry): Handle more than one stylesheet per component |
+ if (component.styleSheets.length > 1 && options.emulateScopedCss) { |
+ var span = component.externalFile != null |
+ ? component.externalFile.sourceSpan : null; |
+ _messages.warning( |
+ 'Component has more than one stylesheet - first stylesheet used.', |
+ span); |
+ } |
+ var printer = emitPolymerElement( |
+ component, _pathMapper, _edits[component.userCode], options); |
+ _emitFileAndSourceMaps(component, printer, component.externalFile); |
+ } |
+ } |
+ |
+ /** |
+ * Emits a file that was created using [NestedPrinter] and it's corresponding |
+ * source map file. |
+ */ |
+ void _emitFileAndSourceMaps( |
+ LibraryInfo lib, NestedPrinter printer, UrlInfo dartCodeUrl) { |
+ // Bail if we had an error generating the code for the file. |
+ if (printer == null) return; |
+ |
+ var libPath = _pathMapper.outputLibraryPath(lib); |
+ var dir = path.dirname(libPath); |
+ var filename = path.basename(libPath); |
+ printer.add('\n//# sourceMappingURL=$filename.map'); |
+ printer.build(libPath); |
+ var sourcePath = dartCodeUrl != null ? dartCodeUrl.resolvedPath : null; |
+ output.add(new OutputFile(libPath, printer.text, source: sourcePath)); |
+ // Fix-up the paths in the source map file |
+ var sourceMap = json.parse(printer.map); |
+ var urls = sourceMap['sources']; |
+ for (int i = 0; i < urls.length; i++) { |
+ urls[i] = path.relative(urls[i], from: dir); |
+ } |
+ output.add(new OutputFile(path.join(dir, '$filename.map'), |
+ json.stringify(sourceMap))); |
+ } |
+ |
+ _time(String logMessage, String filePath, callback(), |
+ {bool printTime: false}) { |
+ var message = new StringBuffer(); |
+ message.write(logMessage); |
+ var filename = path.basename(filePath); |
+ for (int i = (60 - logMessage.length - filename.length); i > 0 ; i--) { |
+ message.write(' '); |
+ } |
+ message.write(filename); |
+ return time(message.toString(), callback, |
+ printTime: options.verbose || printTime); |
+ } |
+} |