OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2016, 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 import 'package:args/args.dart' show ArgParser, ArgResults; |
| 6 import 'package:analyzer/analyzer.dart' |
| 7 show AnalysisError, CompilationUnit, ErrorSeverity; |
| 8 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; |
| 9 import 'package:analyzer/src/generated/java_engine.dart' show AnalysisException; |
| 10 import 'package:analyzer/src/generated/source_io.dart' show Source, SourceKind; |
| 11 import 'package:func/func.dart' show Func1; |
| 12 import 'package:path/path.dart' as path; |
| 13 |
| 14 import '../analyzer/context.dart' |
| 15 show AnalyzerOptions, createAnalysisContextWithSources; |
| 16 import 'extension_types.dart' show ExtensionTypeSet; |
| 17 import 'code_generator.dart' show CodeGenerator; |
| 18 import 'error_helpers.dart' show errorSeverity, formatError, sortErrors; |
| 19 |
| 20 /// Compiles a set of Dart files into a single JavaScript module. |
| 21 /// |
| 22 /// For a single [BuildUnit] definition, this will produce a [JSModuleFile]. |
| 23 /// Those objects are record types that record the data consumed and produced |
| 24 /// for a single compile. |
| 25 /// |
| 26 /// This class exists to cache global state associated with a single in-memory |
| 27 /// AnalysisContext, such as information about extension types in the Dart SDK. |
| 28 /// It can be used once to produce a single module, or reused to save warm-up |
| 29 /// time. (Currently there is no warm up, but there may be in the future.) |
| 30 /// |
| 31 /// The SDK source code is assumed to be immutable for the life of this class. |
| 32 /// |
| 33 /// For all other files, it is up to the [AnalysisContext] to decide whether or |
| 34 /// not any caching is performed. By default an analysis context will assume |
| 35 /// sources are immutable for the life of the context, and cache information |
| 36 /// about them. |
| 37 class ModuleCompiler { |
| 38 final AnalysisContext context; |
| 39 final _extensionTypes = new ExtensionTypeSet(); |
| 40 |
| 41 ModuleCompiler.withContext(this.context); |
| 42 |
| 43 ModuleCompiler(AnalyzerOptions analyzerOptions) |
| 44 : this.withContext(createAnalysisContextWithSources(analyzerOptions)); |
| 45 |
| 46 /// Compiles a single Dart build unit into a JavaScript module. |
| 47 /// |
| 48 /// *Warning* - this may require resolving the entire world. |
| 49 /// If that is not desired, the analysis context must be pre-configured using |
| 50 /// summaries before calling this method. |
| 51 JSModuleFile compile(BuildUnit unit, CompilerOptions options) { |
| 52 var trees = <CompilationUnit>[]; |
| 53 var errors = <AnalysisError>[]; |
| 54 |
| 55 for (var sourcePath in unit.sources) { |
| 56 String sourceUri = sourcePath; |
| 57 if (path.isRelative(sourcePath)) { |
| 58 sourceUri = path.absolute(sourceUri); |
| 59 } |
| 60 sourceUri = path.toUri(sourceUri).toString(); |
| 61 Source source = context.sourceFactory.forUri(sourceUri); |
| 62 if (source == null) { |
| 63 throw new AnalysisException('could not create a source for $sourcePath.' |
| 64 ' The file name is in the wrong format or was not found.'); |
| 65 } |
| 66 |
| 67 // Ignore parts. They need to be handled in the context of their library. |
| 68 if (context.getKindOf(source) == SourceKind.PART) { |
| 69 continue; |
| 70 } |
| 71 |
| 72 var resolvedTree = context.resolveCompilationUnit2(source, source); |
| 73 trees.add(resolvedTree); |
| 74 errors.addAll(context.computeErrors(source)); |
| 75 |
| 76 var library = resolvedTree.element.library; |
| 77 for (var part in library.parts) { |
| 78 trees.add(context.resolveCompilationUnit(part.source, library)); |
| 79 errors.addAll(context.computeErrors(part.source)); |
| 80 } |
| 81 } |
| 82 |
| 83 sortErrors(context, errors); |
| 84 var messages = <String>[]; |
| 85 for (var e in errors) { |
| 86 var m = formatError(context, e); |
| 87 if (m != null) messages.add(m); |
| 88 } |
| 89 |
| 90 if (!options.unsafeForceCompile && |
| 91 errors.any((e) => errorSeverity(context, e) == ErrorSeverity.ERROR)) { |
| 92 return new JSModuleFile.invalid(unit.name, messages); |
| 93 } |
| 94 |
| 95 var codeGenerator = new CodeGenerator(context, options, _extensionTypes); |
| 96 return codeGenerator.compile(unit, trees, messages); |
| 97 } |
| 98 } |
| 99 |
| 100 enum ModuleFormat { es6, legacy, node } |
| 101 |
| 102 ModuleFormat parseModuleFormat(String s) => { |
| 103 'es6': ModuleFormat.es6, |
| 104 'node': ModuleFormat.node, |
| 105 'legacy': ModuleFormat.legacy |
| 106 }[s]; |
| 107 |
| 108 class CompilerOptions { |
| 109 /// Whether to emit the source mapping file. |
| 110 /// |
| 111 /// This supports debugging the original source code instead of the generated |
| 112 /// code. |
| 113 final bool sourceMap; |
| 114 |
| 115 /// If [sourceMap] is emitted, this will emit a `sourceMappingUrl` comment |
| 116 /// into the output JavaScript module. |
| 117 final bool sourceMapComment; |
| 118 |
| 119 /// Whether to emit a summary file containing API signatures. |
| 120 /// |
| 121 /// This is required for a modular build process. |
| 122 final bool summarizeApi; |
| 123 |
| 124 /// Whether to force compilation of code with static errors. |
| 125 final bool unsafeForceCompile; |
| 126 |
| 127 /// Whether to emit Closure Compiler-friendly code. |
| 128 final bool closure; |
| 129 |
| 130 /// Enable ES6 destructuring of named parameters. Off by default. |
| 131 /// |
| 132 /// Older V8 versions do not accept default values with destructuring in |
| 133 /// arrow functions yet (e.g. `({a} = {}) => 1`) but happily accepts them |
| 134 /// with regular functions (e.g. `function({a} = {}) { return 1 }`). |
| 135 /// |
| 136 /// Supporting the syntax: |
| 137 /// * Chrome Canary (51) |
| 138 /// * Firefox |
| 139 /// |
| 140 /// Not yet supporting: |
| 141 /// * Atom (1.5.4) |
| 142 /// * Electron (0.36.3) |
| 143 // TODO(ochafik): Simplify this code when our target platforms catch up. |
| 144 final bool destructureNamedParams; |
| 145 |
| 146 /// Which module format to support. |
| 147 /// Currently 'es6' and 'legacy' are supported. |
| 148 final ModuleFormat moduleFormat; |
| 149 |
| 150 const CompilerOptions( |
| 151 {this.sourceMap: true, |
| 152 this.sourceMapComment: true, |
| 153 this.summarizeApi: true, |
| 154 this.unsafeForceCompile: false, |
| 155 this.closure: false, |
| 156 this.destructureNamedParams: false, |
| 157 this.moduleFormat: ModuleFormat.legacy}); |
| 158 |
| 159 CompilerOptions.fromArguments(ArgResults args) |
| 160 : sourceMap = args['source-map'], |
| 161 sourceMapComment = args['source-map-comment'], |
| 162 summarizeApi = args['summarize'], |
| 163 unsafeForceCompile = args['unsafe-force-compile'], |
| 164 closure = args['closure-experimental'], |
| 165 destructureNamedParams = args['destructure-named-params'], |
| 166 moduleFormat = parseModuleFormat(args['modules']); |
| 167 |
| 168 static ArgParser addArguments(ArgParser parser) => parser |
| 169 ..addFlag('summarize', help: 'emit an API summary file', defaultsTo: true) |
| 170 ..addFlag('source-map', help: 'emit source mapping', defaultsTo: true) |
| 171 ..addFlag('source-map-comment', |
| 172 help: 'adds a sourceMappingURL comment to the end of the JS,\n' |
| 173 'disable if using X-SourceMap header', |
| 174 defaultsTo: true) |
| 175 ..addOption('modules', |
| 176 help: 'module pattern to emit', |
| 177 allowed: ['es6', 'legacy', 'node'], |
| 178 allowedHelp: { |
| 179 'es6': 'es6 modules', |
| 180 'legacy': 'a custom format used by dartdevc, similar to AMD', |
| 181 'node': 'node.js modules (https://nodejs.org/api/modules.html)' |
| 182 }, |
| 183 defaultsTo: 'legacy') |
| 184 ..addFlag('closure-experimental', |
| 185 help: 'emit Closure Compiler-friendly code (experimental)', |
| 186 defaultsTo: false) |
| 187 ..addFlag('destructure-named-params', |
| 188 help: 'Destructure named parameters', defaultsTo: false) |
| 189 ..addFlag('unsafe-force-compile', |
| 190 help: 'Compile code even if it has errors. ಠ_ಠ\n' |
| 191 'This has undefined behavior!', |
| 192 defaultsTo: false); |
| 193 } |
| 194 |
| 195 /// A unit of Dart code that can be built into a single JavaScript module. |
| 196 class BuildUnit { |
| 197 /// The name of this module. |
| 198 final String name; |
| 199 |
| 200 /// The list of sources in this module. |
| 201 /// |
| 202 /// The set of Dart files can be arbitrarily large, but it must contain |
| 203 /// complete libraries including all of their parts, as well as all libraries |
| 204 /// that are part of a library cycle. |
| 205 final List<String> sources; |
| 206 |
| 207 /// Given an imported library URI, this will determine to what Dart/JS module |
| 208 /// it belongs to. |
| 209 // TODO(jmesserly): we should replace this with another way of tracking |
| 210 // build units. |
| 211 final Func1<Source, String> libraryToModule; |
| 212 |
| 213 BuildUnit(this.name, this.sources, this.libraryToModule); |
| 214 } |
| 215 |
| 216 /// The output of Dart->JS compilation. |
| 217 /// |
| 218 /// This contains the file contents of the JS module, as well as a list of |
| 219 /// Dart libraries that are contained in this module. |
| 220 class JSModuleFile { |
| 221 /// The name of this module. |
| 222 final String name; |
| 223 |
| 224 /// The list of messages (errors and warnings) |
| 225 final List<String> errors; |
| 226 |
| 227 /// The JavaScript code for this module. |
| 228 /// |
| 229 /// If a [sourceMap] is available, this will include the `sourceMappingURL` |
| 230 /// comment at end of the file. |
| 231 final String code; |
| 232 |
| 233 /// The JSON of the source map, if generated, otherwise `null`. |
| 234 /// |
| 235 /// The source paths will initially be absolute paths. They can be adjusted |
| 236 /// using [placeSourceMap]. |
| 237 final Map sourceMap; |
| 238 |
| 239 /// The binary contents of the API summary file, including APIs from each of |
| 240 /// the [libraries] in this module. |
| 241 final List<int> summaryBytes; |
| 242 |
| 243 JSModuleFile( |
| 244 this.name, this.errors, this.code, this.sourceMap, this.summaryBytes); |
| 245 |
| 246 JSModuleFile.invalid(this.name, this.errors) |
| 247 : code = null, |
| 248 sourceMap = null, |
| 249 summaryBytes = null; |
| 250 |
| 251 /// True if this library was successfully compiled. |
| 252 bool get isValid => code != null; |
| 253 |
| 254 /// Adjusts the source paths in [sourceMap] to be relative to [sourceMapPath], |
| 255 /// and returns the new map. |
| 256 /// |
| 257 /// See also [writeSourceMap]. |
| 258 Map placeSourceMap(String sourceMapPath) { |
| 259 var dir = path.dirname(sourceMapPath); |
| 260 |
| 261 var map = new Map.from(this.sourceMap); |
| 262 List list = new List.from(map['sources']); |
| 263 map['sources'] = list; |
| 264 for (int i = 0; i < list.length; i++) { |
| 265 list[i] = path.relative(list[i], from: dir); |
| 266 } |
| 267 return map; |
| 268 } |
| 269 } |
OLD | NEW |