| Index: pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart
|
| diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart
|
| index 9797dbb60f546d8fd3ce5080dd46d55ac10955cf..48be52d6f6b931cd0bfc9599ccd3258567665dd3 100644
|
| --- a/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart
|
| +++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart
|
| @@ -4,15 +4,36 @@
|
|
|
| library dart2js.js_emitter.startup_emitter.model_emitter;
|
|
|
| +import 'dart:convert' show JsonEncoder;
|
| +
|
| +import '../../common.dart';
|
| +
|
| import '../../constants/values.dart' show ConstantValue, FunctionConstantValue;
|
| import '../../dart2jslib.dart' show Compiler;
|
| import '../../elements/elements.dart' show ClassElement, FunctionElement;
|
| +import '../../hash/sha1.dart' show Hasher;
|
| +
|
| +import '../../io/code_output.dart';
|
| +
|
| +import '../../io/line_column_provider.dart' show
|
| + LineColumnCollector,
|
| + LineColumnProvider;
|
| +
|
| +import '../../io/source_map_builder.dart' show
|
| + SourceMapBuilder;
|
| +
|
| import '../../js/js.dart' as js;
|
| import '../../js_backend/js_backend.dart' show
|
| JavaScriptBackend,
|
| Namer,
|
| ConstantEmitter;
|
|
|
| +import '../../util/util.dart' show
|
| + NO_LOCATION_SPANNABLE;
|
| +
|
| +import '../../util/uri_extras.dart' show
|
| + relativize;
|
| +
|
| import '../js_emitter.dart' show AstContainer, NativeEmitter;
|
|
|
| import 'package:js_runtime/shared/embedded_names.dart' show
|
| @@ -35,6 +56,7 @@ import 'package:js_runtime/shared/embedded_names.dart' show
|
| import '../js_emitter.dart' show NativeGenerator, buildTearOffCode;
|
| import '../model.dart';
|
|
|
| +part 'deferred_fragment_hash.dart';
|
| part 'fragment_emitter.dart';
|
|
|
| class ModelEmitter {
|
| @@ -42,6 +64,11 @@ class ModelEmitter {
|
| final Namer namer;
|
| ConstantEmitter constantEmitter;
|
| final NativeEmitter nativeEmitter;
|
| + final bool shouldGenerateSourceMap;
|
| +
|
| + // The full code that is written to each hunk part-file.
|
| + final Map<Fragment, CodeOutput> outputBuffers = <Fragment, CodeOutput>{};
|
| +
|
|
|
| JavaScriptBackend get backend => compiler.backend;
|
|
|
| @@ -53,7 +80,8 @@ class ModelEmitter {
|
|
|
| static const String typeNameProperty = r"builtin$cls";
|
|
|
| - ModelEmitter(Compiler compiler, Namer namer, this.nativeEmitter)
|
| + ModelEmitter(Compiler compiler, Namer namer, this.nativeEmitter,
|
| + this.shouldGenerateSourceMap)
|
| : this.compiler = compiler,
|
| this.namer = namer {
|
| this.constantEmitter = new ConstantEmitter(
|
| @@ -126,55 +154,239 @@ class ModelEmitter {
|
| }
|
|
|
| int emitProgram(Program program) {
|
| - List<Fragment> fragments = program.fragments;
|
| - MainFragment mainFragment = fragments.first;
|
| -
|
| - int totalSize = 0;
|
| + MainFragment mainFragment = program.fragments.first;
|
| + List<DeferredFragment> deferredFragments =
|
| + new List<DeferredFragment>.from(program.fragments.skip(1));
|
|
|
| FragmentEmitter fragmentEmitter =
|
| new FragmentEmitter(compiler, namer, backend, constantEmitter, this);
|
|
|
| - // We have to emit the deferred fragments first, since we need their
|
| - // deferred hash (which depends on the output) when emitting the main
|
| - // fragment.
|
| - List<js.Expression> fragmentsCode = fragments.skip(1).map(
|
| - (DeferredFragment deferredFragment) {
|
| - js.Expression types =
|
| - program.metadataTypesForOutputUnit(deferredFragment.outputUnit);
|
| - return fragmentEmitter.emitDeferredFragment(
|
| - deferredFragment, types, program.holders);
|
| - }).toList();
|
| + Map<DeferredFragment, _DeferredFragmentHash> deferredHashTokens =
|
| + new Map<DeferredFragment, _DeferredFragmentHash>();
|
| + for (DeferredFragment fragment in deferredFragments) {
|
| + deferredHashTokens[fragment] = new _DeferredFragmentHash(fragment);
|
| + }
|
| +
|
| + js.Statement mainCode =
|
| + fragmentEmitter.emitMainFragment(program, deferredHashTokens);
|
|
|
| - js.Statement mainAst = fragmentEmitter.emitMainFragment(program);
|
| + Map<DeferredFragment, js.Expression> deferredFragmentsCode =
|
| + <DeferredFragment, js.Expression>{};
|
| +
|
| + for (DeferredFragment fragment in deferredFragments) {
|
| + js.Expression types =
|
| + program.metadataTypesForOutputUnit(fragment.outputUnit);
|
| + deferredFragmentsCode[fragment] = fragmentEmitter.emitDeferredFragment(
|
| + fragment, types, program.holders);
|
| + }
|
|
|
| js.TokenCounter counter = new js.TokenCounter();
|
| - fragmentsCode.forEach(counter.countTokens);
|
| - counter.countTokens(mainAst);
|
| + deferredFragmentsCode.values.forEach(counter.countTokens);
|
| + counter.countTokens(mainCode);
|
|
|
| program.finalizers.forEach((js.TokenFinalizer f) => f.finalizeTokens());
|
|
|
| - for (int i = 0; i < fragmentsCode.length; ++i) {
|
| - String code = js.prettyPrint(fragmentsCode[i], compiler).getText();
|
| - totalSize += code.length;
|
| - compiler.outputProvider(fragments[i+1].outputFileName, deferredExtension)
|
| - ..add(code)
|
| - ..close();
|
| - }
|
| + Map<DeferredFragment, String> hunkHashes =
|
| + writeDeferredFragments(deferredFragmentsCode);
|
|
|
| - String mainCode = js.prettyPrint(mainAst, compiler).getText();
|
| - compiler.outputProvider(mainFragment.outputFileName, 'js')
|
| - ..add(buildGeneratedBy(compiler))
|
| - ..add(mainCode)
|
| - ..close();
|
| - totalSize += mainCode.length;
|
| + // Now that we have written the deferred hunks, we can update the hash
|
| + // tokens in the main-fragment.
|
| + deferredHashTokens.forEach((DeferredFragment key,
|
| + _DeferredFragmentHash token) {
|
| + token.setHash(hunkHashes[key]);
|
| + });
|
| +
|
| + writeMainFragment(mainFragment, mainCode);
|
| +
|
| + if (backend.requiresPreamble &&
|
| + !backend.htmlLibraryIsLoaded) {
|
| + compiler.reportHint(NO_LOCATION_SPANNABLE, MessageKind.PREAMBLE);
|
| + }
|
|
|
| - return totalSize;
|
| + if (compiler.deferredMapUri != null) {
|
| + writeDeferredMap();
|
| + }
|
| +
|
| + // Return the total program size.
|
| + return outputBuffers.values.fold(0, (a, b) => a + b.length);
|
| }
|
|
|
| + /// Generates a simple header that provides the compiler's build id.
|
| String buildGeneratedBy(compiler) {
|
| var suffix = '';
|
| if (compiler.hasBuildId) suffix = ' version: ${compiler.buildId}';
|
| return '// Generated by dart2js (fast startup), '
|
| 'the Dart to JavaScript compiler$suffix.\n';
|
| }
|
| +
|
| + /// Writes all deferred fragment's code into files.
|
| + ///
|
| + /// Returns a map from fragment to its hashcode (as used for the deferred
|
| + /// library code).
|
| + ///
|
| + /// Updates the shared [outputBuffers] field with the output.
|
| + Map<DeferredFragment, String> writeDeferredFragments(
|
| + Map<DeferredFragment, js.Expression> fragmentsCode) {
|
| + Map<DeferredFragment, String> hunkHashes = <DeferredFragment, String>{};
|
| +
|
| + fragmentsCode.forEach((DeferredFragment fragment, js.Expression code) {
|
| + hunkHashes[fragment] = writeDeferredFragment(fragment, code);
|
| + });
|
| +
|
| + return hunkHashes;
|
| + }
|
| +
|
| + // Writes the given [fragment]'s [code] into a file.
|
| + //
|
| + // Updates the shared [outputBuffers] field with the output.
|
| + void writeMainFragment(MainFragment fragment, js.Statement code) {
|
| + LineColumnCollector lineColumnCollector;
|
| + List<CodeOutputListener> codeOutputListeners;
|
| + if (shouldGenerateSourceMap) {
|
| + lineColumnCollector = new LineColumnCollector();
|
| + codeOutputListeners = <CodeOutputListener>[lineColumnCollector];
|
| + }
|
| +
|
| + CodeOutput mainOutput = new StreamCodeOutput(
|
| + compiler.outputProvider('', 'js'),
|
| + codeOutputListeners);
|
| + outputBuffers[fragment] = mainOutput;
|
| +
|
| + mainOutput.addBuffer(js.prettyPrint(code, compiler,
|
| + monitor: compiler.dumpInfoTask));
|
| +
|
| + if (shouldGenerateSourceMap) {
|
| + mainOutput.add(
|
| + generateSourceMapTag(compiler.sourceMapUri, compiler.outputUri));
|
| + }
|
| +
|
| + mainOutput.close();
|
| +
|
| + if (shouldGenerateSourceMap) {
|
| + outputSourceMap(mainOutput, lineColumnCollector, '',
|
| + compiler.sourceMapUri, compiler.outputUri);
|
| + }
|
| + }
|
| +
|
| + // Writes the given [fragment]'s [code] into a file.
|
| + //
|
| + // Returns the deferred fragment's hash.
|
| + //
|
| + // Updates the shared [outputBuffers] field with the output.
|
| + String writeDeferredFragment(DeferredFragment fragment, js.Expression code) {
|
| + List<CodeOutputListener> outputListeners = <CodeOutputListener>[];
|
| + Hasher hasher = new Hasher();
|
| + outputListeners.add(hasher);
|
| +
|
| + LineColumnCollector lineColumnCollector;
|
| + if (shouldGenerateSourceMap) {
|
| + lineColumnCollector = new LineColumnCollector();
|
| + outputListeners.add(lineColumnCollector);
|
| + }
|
| +
|
| + String hunkPrefix = fragment.outputFileName;
|
| +
|
| + CodeOutput output = new StreamCodeOutput(
|
| + compiler.outputProvider(hunkPrefix, deferredExtension),
|
| + outputListeners);
|
| +
|
| + outputBuffers[fragment] = output;
|
| +
|
| + // The [code] contains the function that must be invoked when the deferred
|
| + // hunk is loaded.
|
| + // That function must be in a map from its hashcode to the function. Since
|
| + // we don't know the hash before we actually emit the code we store the
|
| + // function in a temporary field first:
|
| + //
|
| + // deferredInitializer.current = <pretty-printed code>;
|
| + // deferredInitializer[<hash>] = deferredInitializer.current;
|
| +
|
| + output.add('\n${deferredInitializersGlobal}.current = ');
|
| +
|
| + output.addBuffer(js.prettyPrint(code, compiler,
|
| + monitor: compiler.dumpInfoTask));
|
| +
|
| + // Make a unique hash of the code (before the sourcemaps are added)
|
| + // This will be used to retrieve the initializing function from the global
|
| + // variable.
|
| + String hash = hasher.getHash();
|
| +
|
| + // Now we copy the deferredInitializer.current into its correct hash.
|
| + output.add('\n${deferredInitializersGlobal}["$hash"] = '
|
| + '${deferredInitializersGlobal}.current');
|
| +
|
| + if (shouldGenerateSourceMap) {
|
| + Uri mapUri, partUri;
|
| + Uri sourceMapUri = compiler.sourceMapUri;
|
| + Uri outputUri = compiler.outputUri;
|
| +
|
| + if (sourceMapUri != null) {
|
| + String mapFileName =
|
| + hunkPrefix + deferredExtension + ".map";
|
| + List<String> mapSegments = sourceMapUri.pathSegments.toList();
|
| + mapSegments[mapSegments.length - 1] = mapFileName;
|
| + mapUri = compiler.sourceMapUri.replace(pathSegments: mapSegments);
|
| + }
|
| +
|
| + if (outputUri != null) {
|
| + String hunkFileName = hunkPrefix + deferredExtension;
|
| + List<String> partSegments = outputUri.pathSegments.toList();
|
| + partSegments[partSegments.length - 1] = hunkFileName;
|
| + partUri = compiler.outputUri.replace(pathSegments: partSegments);
|
| + }
|
| +
|
| + output.add(generateSourceMapTag(mapUri, partUri));
|
| + output.close();
|
| + outputSourceMap(output, lineColumnCollector, hunkPrefix, mapUri, partUri);
|
| + } else {
|
| + output.close();
|
| + }
|
| +
|
| + return hash;
|
| + }
|
| +
|
| + String generateSourceMapTag(Uri sourceMapUri, Uri fileUri) {
|
| + if (sourceMapUri != null && fileUri != null) {
|
| + String sourceMapFileName = relativize(fileUri, sourceMapUri, false);
|
| + return '''
|
| +
|
| +//# sourceMappingURL=$sourceMapFileName
|
| +''';
|
| + }
|
| + return '';
|
| + }
|
| +
|
| +
|
| + void outputSourceMap(CodeOutput output,
|
| + LineColumnProvider lineColumnProvider,
|
| + String name,
|
| + [Uri sourceMapUri,
|
| + Uri fileUri]) {
|
| + if (!shouldGenerateSourceMap) return;
|
| + // Create a source file for the compilation output. This allows using
|
| + // [:getLine:] to transform offsets to line numbers in [SourceMapBuilder].
|
| + SourceMapBuilder sourceMapBuilder =
|
| + new SourceMapBuilder(sourceMapUri, fileUri, lineColumnProvider);
|
| + output.forEachSourceLocation(sourceMapBuilder.addMapping);
|
| + String sourceMap = sourceMapBuilder.build();
|
| + compiler.outputProvider(name, 'js.map')
|
| + ..add(sourceMap)
|
| + ..close();
|
| + }
|
| +
|
| + /// Writes a mapping from library-name to hunk files.
|
| + ///
|
| + /// The output is written into a separate file that can be used by outside
|
| + /// tools.
|
| + void writeDeferredMap() {
|
| + Map<String, dynamic> mapping = new Map<String, dynamic>();
|
| + // Json does not support comments, so we embed the explanation in the
|
| + // data.
|
| + mapping["_comment"] = "This mapping shows which compiled `.js` files are "
|
| + "needed for a given deferred library import.";
|
| + mapping.addAll(compiler.deferredLoadTask.computeDeferredMap());
|
| + compiler.outputProvider(compiler.deferredMapUri.path, 'deferred_map')
|
| + ..add(const JsonEncoder.withIndent(" ").convert(mapping))
|
| + ..close();
|
| + }
|
| }
|
|
|