OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2017, 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 /// Standalone utility that manages loading source maps for all Dart scripts | |
6 /// on the page compiled with DDC. | |
7 /// | |
8 /// Example JavaScript usage: | |
9 /// $dartStackTraceUtility.addLoadedListener(function() { | |
10 /// // All Dart source maps are now loaded. It is now safe to start your | |
11 /// // Dart application compiled with DDC. | |
12 /// dart_library.start('your_dart_application'); | |
13 /// }) | |
14 /// | |
15 /// If $dartStackTraceUtility is set, the dart:core StackTrace class calls | |
16 /// $dartStackTraceUtility.mapper(someJSStackTrace) | |
17 /// to apply source maps. | |
18 /// | |
19 /// This utility can be compiled to JavaScript using Dart2JS while the rest | |
20 /// of the application is compiled with DDC or could be compiled with DDC. | |
21 | |
22 @JS() | |
23 library stack_trace_mapper; | |
24 | |
25 import 'dart:async'; | |
26 import 'dart:html'; | |
27 | |
28 import 'package:js/js.dart'; | |
29 import 'package:path/path.dart' as path; | |
30 import 'package:source_map_stack_trace/source_map_stack_trace.dart'; | |
31 import 'package:source_maps/source_maps.dart'; | |
32 import 'package:source_span/source_span.dart'; | |
33 import 'package:stack_trace/stack_trace.dart'; | |
34 | |
35 typedef void ReadyCallback(); | |
36 | |
37 /// Global object DDC uses to see if a stack trace utility has been registed. | |
vsm
2017/03/08 15:34:44
registed -> registered
Jacob
2017/03/09 02:28:42
Done.
| |
38 @JS(r'$dartStackTraceUtility') | |
39 external set dartStackTraceUtility(DartStackTraceUtility value); | |
40 | |
41 typedef String StackTraceMapper(String stackTrace); | |
42 typedef dynamic AddLoadedListener(ReadyCallback callback); | |
43 | |
44 @JS() | |
45 @anonymous | |
46 class DartStackTraceUtility { | |
47 external factory DartStackTraceUtility( | |
48 {StackTraceMapper mapper, AddLoadedListener addLoadedListener}); | |
49 } | |
50 | |
51 /// Source mapping that is waits to parse source maps until they match the uri | |
52 /// of a requested source map. | |
53 /// | |
54 /// This improves startup performance compared to using MappingBundle directly. | |
55 /// The unparsed data for the source maps must still be loaded before | |
56 /// LazyMapping is used. | |
57 class LazyMapping extends Mapping { | |
58 MappingBundle _bundle = new MappingBundle(); | |
59 | |
60 /// Map from url to unparsed source map. | |
61 Map<String, String> _sourceMaps; | |
62 | |
63 LazyMapping(this._sourceMaps) {} | |
64 | |
65 List toJson() => _bundle.toJson(); | |
66 | |
67 SourceMapSpan spanFor(int line, int column, | |
68 {Map<String, SourceFile> files, String uri}) { | |
69 if (uri == null) { | |
70 throw new ArgumentError.notNull('uri'); | |
71 } | |
72 var rawMap = _sourceMaps[uri]; | |
73 | |
74 if (rawMap != null && rawMap.isNotEmpty && !_bundle.containsMapping(uri)) { | |
75 SingleMapping mapping = parse(rawMap); | |
76 mapping | |
77 ..targetUrl = uri | |
78 ..sourceRoot = '${path.dirname(uri)}/'; | |
79 _bundle.addMapping(mapping); | |
80 } | |
81 | |
82 return _bundle.spanFor(line, column, files: files, uri: uri); | |
83 } | |
84 } | |
85 | |
86 String _toSourceMapLocation(String url) { | |
87 // The url may have cache busting query parameters which we need to maintain | |
88 // in the source map url. | |
89 // For example: | |
90 // http://localhost/foo.js?cachebusting=23419 | |
91 // Should get source map | |
92 // http://localhost/foo.js.map?cachebusting=23419 | |
93 var uri = Uri.parse(url); | |
94 return uri.replace(path: '${uri.path}.map').toString(); | |
95 } | |
96 | |
97 /// Load a source map for the specified url. | |
98 /// | |
99 /// Returns a null string rather than reporting an error if the file cannot be | |
100 /// found as we don't want to throw errors if a few source maps are missing. | |
101 Future<String> loadSourceMap(String url) async { | |
102 try { | |
103 return await HttpRequest.getString(_toSourceMapLocation(url)); | |
104 } catch (e) { | |
105 return null; | |
106 } | |
107 } | |
108 | |
109 LazyMapping _mapping; | |
110 | |
111 String mapper(String rawStackTrace) { | |
112 if (_mapping == null) { | |
113 // This should not happen if the user has waited for the ReadyCallback | |
114 // to start the application. | |
115 throw new StateError('Souce maps are not done loading.'); | |
vsm
2017/03/08 15:34:44
Souce -> Source
Jacob
2017/03/09 02:28:42
Done.
Clealy my compute keyboad 'r' key needs to
| |
116 } | |
117 return mapStackTrace(_mapping, new Trace.parse(rawStackTrace)).toString(); | |
118 } | |
119 | |
120 Future<Null> addLoadedListener(ReadyCallback callback) async { | |
121 var scripts = document.querySelectorAll('script'); | |
122 var sourceMapFutures = <Future<String>>[]; | |
123 var urls = <String>[]; | |
124 for (ScriptElement script in scripts) { | |
vsm
2017/03/08 15:34:44
Would it make more sense to do this in the bootstr
Jacob
2017/03/09 02:28:42
Good point.
I've switched up the code so the API
| |
125 // By convention only attempt to load source maps for scripts tagged with | |
126 // the data attribute loadSourceMap avoiding triggering 404 errors for | |
127 // regular JS scripts on the page and Dart scripts that we should not load | |
128 // source maps for. | |
129 if (!script.dataset.containsKey('loadSourceMap')) continue; | |
130 var src = script.src; | |
131 if (src.isEmpty) continue; | |
132 urls.add(src); | |
133 sourceMapFutures.add(loadSourceMap(src)); | |
134 } | |
135 List<String> sourceMaps = await Future.wait(sourceMapFutures); | |
136 _mapping = new LazyMapping(new Map.fromIterables(urls, sourceMaps)); | |
137 callback(); | |
138 } | |
139 | |
140 main() { | |
141 // Register with DDC. | |
142 dartStackTraceUtility = new DartStackTraceUtility( | |
143 mapper: allowInterop(mapper), | |
144 addLoadedListener: allowInterop(addLoadedListener)); | |
145 } | |
OLD | NEW |