Make module names and uris match source map relative paths

- Make previous behavior non-conditional
  (already default and flag uses are removed)

- Use relative  paths instead of package uris as module names
  under --debugging-module-names flag, false by default.
  (used as server paths in the browser, need to match relative
   paths in source maps)

- Pass packageConfig to Javascript bundle API

- Add tests

Closes: https://github.com/dart-lang/sdk/issues/49667
Change-Id: I57c2e2629ba5dd756cce9a8ec6ae891fb092e1d9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255826
Commit-Queue: Anna Gringauze <annagrin@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Anna Gringauze 2022-08-29 17:41:36 +00:00 committed by Commit Bot
parent bd89be9efa
commit 6c548cedc2
5 changed files with 291 additions and 208 deletions

View file

@ -178,7 +178,6 @@ Future<void> main(List<String> args) async {
"$rootPath/flutter_patched_sdk/",
"--incremental",
"--target=flutter",
"--debugger-module-names",
"--output-dill",
"$rootPath/flutter_server_tmp.dill",
"--packages",

View file

@ -187,7 +187,7 @@ ArgParser argParser = ArgParser(allowTrailingOptions: true)
..addOption('libraries-spec',
help: 'A path or uri to the libraries specification JSON file')
..addFlag('debugger-module-names',
help: 'Use debugger-friendly modules names', defaultsTo: true)
help: 'Use debugger-friendly modules names', defaultsTo: false)
..addFlag('experimental-emit-debug-metadata',
help: 'Emit module and library metadata for the debugger',
defaultsTo: false)
@ -285,7 +285,7 @@ abstract class CompilerInterface {
/// accepted state.
Future<void> rejectLastDelta();
/// This let's compiler know that source file identifed by `uri` was changed.
/// This let's compiler know that source file identified by `uri` was changed.
void invalidate(Uri uri);
/// Resets incremental compiler accept/reject status so that next time
@ -668,7 +668,7 @@ class FrontendCompiler implements CompilerInterface {
previouslyReportedDependencies = uris;
}
/// Write a JavaScript bundle containg the provided component.
/// Write a JavaScript bundle containing the provided component.
Future<void> writeJavascriptBundle(KernelCompilationResults results,
String filename, String fileSystemScheme, String moduleFormat,
{bool fullComponent}) async {
@ -689,10 +689,13 @@ class FrontendCompiler implements CompilerInterface {
soundNullSafety: soundNullSafety,
);
if (fullComponent) {
await _bundler.initialize(component, _mainSource);
await _bundler.initialize(component, _mainSource, packageConfig);
} else {
await _bundler.invalidate(
component, _generator.lastKnownGoodResult?.component, _mainSource);
component,
_generator.lastKnownGoodResult?.component,
_mainSource,
packageConfig);
}
// Create JavaScript bundler.
@ -908,7 +911,7 @@ class FrontendCompiler implements CompilerInterface {
/// Program compilers per module.
///
/// Produced suring initial compilation of the module to JavaScript,
/// Produced during initial compilation of the module to JavaScript,
/// cached to be used for expression compilation in [compileExpressionToJs].
final Map<String, ProgramCompiler> cachedProgramCompilers = {};

View file

@ -55,7 +55,8 @@ class IncrementalJavaScriptBundler {
StrongComponents _strongComponents;
/// Initialize the incremental bundler from a full component.
Future<void> initialize(Component fullComponent, Uri mainUri) async {
Future<void> initialize(
Component fullComponent, Uri mainUri, PackageConfig packageConfig) async {
_lastFullComponent = fullComponent;
_currentComponent = fullComponent;
_strongComponents = StrongComponents(
@ -65,13 +66,16 @@ class IncrementalJavaScriptBundler {
_fileSystem,
);
await _strongComponents.computeModules();
_updateSummaries(_strongComponents.modules.keys);
_updateSummaries(_strongComponents.modules.keys, packageConfig);
}
/// Update the incremental bundler from a partial component and the last full
/// component.
Future<void> invalidate(Component partialComponent,
Component lastFullComponent, Uri mainUri) async {
Future<void> invalidate(
Component partialComponent,
Component lastFullComponent,
Uri mainUri,
PackageConfig packageConfig) async {
_currentComponent = partialComponent;
_updateFullComponent(lastFullComponent, partialComponent);
_strongComponents = StrongComponents(
@ -89,7 +93,7 @@ class IncrementalJavaScriptBundler {
for (Library library in partialComponent.libraries)
_strongComponents.moduleAssignment[library.importUri],
};
_updateSummaries(invalidated);
_updateSummaries(invalidated, packageConfig);
}
void _updateFullComponent(Component lastKnownGood, Component candidate) {
@ -115,7 +119,7 @@ class IncrementalJavaScriptBundler {
}
/// Update the summaries [moduleKeys].
void _updateSummaries(Iterable<Uri> moduleKeys) {
void _updateSummaries(Iterable<Uri> moduleKeys, PackageConfig packageConfig) {
for (Uri uri in moduleKeys) {
final List<Library> libraries = _strongComponents.modules[uri].toList();
final Component summaryComponent = Component(
@ -126,17 +130,13 @@ class IncrementalJavaScriptBundler {
summaryComponent.setMainMethodAndMode(
null, false, _currentComponent.mode);
var baseName = urlForComponentUri(uri);
var baseName = urlForComponentUri(uri, packageConfig);
_moduleImportForSummary[uri] = '$baseName.lib.js';
if (useDebuggerModuleNames) {
_moduleImportNameForSummary[uri] = makeDebuggerModuleName(baseName);
}
_moduleImportNameForSummary[uri] = makeModuleName(baseName);
_uriToComponent[uri] = summaryComponent;
// debugger loads modules by modules names, not paths
var moduleImport = useDebuggerModuleNames
? _moduleImportNameForSummary[uri]
: _moduleImportForSummary[uri];
// module loaders loads modules by modules names, not paths
var moduleImport = _moduleImportNameForSummary[uri];
var oldSummaries = <Component>[];
for (Component summary in _summaryToModule.keys) {
@ -193,14 +193,8 @@ class IncrementalJavaScriptBundler {
// module name to use in trackLibraries
// use full path for tracking if module uri is not a package uri.
String moduleName = urlForComponentUri(moduleUri);
if (useDebuggerModuleNames) {
// Skip the leading '/' as module names are used to require
// modules using module paths mape in RequireJS, which treats
// names with leading '/' or '.js' extensions specially
// and tries to load them without mapping.
moduleName = makeDebuggerModuleName(moduleName);
}
final moduleUrl = urlForComponentUri(moduleUri, packageConfig);
final moduleName = makeModuleName(moduleUrl);
var compiler = ProgramCompiler(
_currentComponent,
@ -223,7 +217,6 @@ class IncrementalJavaScriptBundler {
// Save program compiler to reuse for expression evaluation.
kernel2JsCompilers[moduleName] = compiler;
final moduleUrl = urlForComponentUri(moduleUri);
String sourceMapBase;
if (moduleUri.isScheme('package')) {
// Source locations come through as absolute file uris. In order to
@ -284,12 +277,44 @@ class IncrementalJavaScriptBundler {
return kernel2JsCompilers;
}
}
String urlForComponentUri(Uri componentUri) => componentUri.isScheme('package')
? '/packages/${componentUri.path}'
: componentUri.path;
/// Module name used in the browser to load modules.
///
/// Module names are used to load modules using module
/// paths maps in RequireJS, which treats names with
/// leading '/' or '.js' extensions specially, and tries
/// to load them without mapping.
/// Skip the leading '/' to always load modules via module
/// path maps.
String makeModuleName(String name) {
return name.startsWith('/') ? name.substring(1) : name;
}
String makeDebuggerModuleName(String name) {
return name.startsWith('/') ? name.substring(1) : name;
/// Create component url.
///
/// Used as a server path in the browser for the module created
/// from the component.
String urlForComponentUri(Uri componentUri, PackageConfig packageConfig) {
if (!componentUri.isScheme('package')) {
return componentUri.path;
}
if (!useDebuggerModuleNames) {
return '/packages/${componentUri.path}';
}
// Match relative directory structure of server paths to the
// actual directory structure, so the sourcemaps relative paths
// can be resolved by the browser.
final resolvedUri = packageConfig.resolve(componentUri);
final package = packageConfig.packageOf(resolvedUri);
final root = package.root;
final relativeRoot = root.pathSegments
.lastWhere((segment) => segment.isNotEmpty, orElse: null);
final relativeUrl = resolvedUri.toString().replaceFirst('$root', '');
// Relative component url (used as server path in the browser):
// `packages/<package directory>/<path to file.dart>`
return relativeRoot == null
? 'packages/$relativeUrl'
: 'packages/$relativeRoot/$relativeUrl';
}
}

View file

@ -4,7 +4,7 @@ description: A resident kernel compiler
publish_to: none
environment:
sdk: "^2.15.0"
sdk: '>=2.15.0 <3.0.0'
# Use 'any' constraints here; we get our versions from the DEPS file.
dependencies:

View file

@ -83,179 +83,233 @@ void main() {
{
'name': 'a',
'rootUri': 'file:///pkg/a',
'packagesUri': '',
'packageUri': 'lib/',
}
],
}, Uri.base);
final multiRootScheme = 'org-dartlang-app';
test('compiles JavaScript code', () async {
final uri = Uri.file('/c.dart');
final library = Library(
uri,
fileUri: uri,
procedures: [
Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method,
FunctionNode(Block([])),
fileUri: uri)
],
);
final testComponent = Component(libraries: [library, ...testCoreLibraries]);
final javaScriptBundler = IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
);
for (final debuggerNames in [true, false]) {
group('Debugger module names: $debuggerNames |', () {
test('Creates module uris for file paths', () async {
final fileUri = Uri.file('/c.dart');
await javaScriptBundler.initialize(testComponent, uri);
final javaScriptBundler = IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
useDebuggerModuleNames: debuggerNames,
);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
final sourcemapSink = _MemorySink();
final metadataSink = _MemorySink();
final symbolsSink = _MemorySink();
final coreTypes = CoreTypes(testComponent);
final moduleUrl =
javaScriptBundler.urlForComponentUri(fileUri, packageConfig);
final moduleName = javaScriptBundler.makeModuleName(moduleUrl);
expect(moduleUrl, '/c.dart');
expect(moduleName, 'c.dart');
});
final compilers = await javaScriptBundler.compile(
ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
test('Creates module uris for package paths', () async {
final packageUri = Uri.parse('package:a/a.dart');
final Map manifest = json.decode(utf8.decode(manifestSink.buffer));
final String code = utf8.decode(codeSink.buffer);
final javaScriptBundler = IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
useDebuggerModuleNames: debuggerNames,
);
expect(manifest, {
'/c.dart.lib.js': {
'code': [0, codeSink.buffer.length],
'sourcemap': [0, sourcemapSink.buffer.length],
},
final moduleUrl =
javaScriptBundler.urlForComponentUri(packageUri, packageConfig);
final moduleName = javaScriptBundler.makeModuleName(moduleUrl);
expect(moduleUrl,
debuggerNames ? 'packages/a/lib/a.dart' : '/packages/a/a.dart');
expect(moduleName,
debuggerNames ? 'packages/a/lib/a.dart' : 'packages/a/a.dart');
});
test('compiles JavaScript code', () async {
final uri = Uri.file('/c.dart');
final library = Library(
uri,
fileUri: uri,
procedures: [
Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method,
FunctionNode(Block([])),
fileUri: uri)
],
);
final testComponent =
Component(libraries: [library, ...testCoreLibraries]);
final javaScriptBundler = IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
useDebuggerModuleNames: debuggerNames,
);
await javaScriptBundler.initialize(testComponent, uri, packageConfig);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
final sourcemapSink = _MemorySink();
final metadataSink = _MemorySink();
final symbolsSink = _MemorySink();
final coreTypes = CoreTypes(testComponent);
final compilers = await javaScriptBundler.compile(
ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final Map manifest = json.decode(utf8.decode(manifestSink.buffer));
final String code = utf8.decode(codeSink.buffer);
expect(manifest, {
'/c.dart.lib.js': {
'code': [0, codeSink.buffer.length],
'sourcemap': [0, sourcemapSink.buffer.length],
},
});
expect(code, contains('ArbitrarilyChosen'));
// Verify source map url is correct.
expect(code, contains('sourceMappingURL=c.dart.lib.js.map'));
// Verify program compilers are created.
final moduleUrl = javaScriptBundler.urlForComponentUri(
library.importUri, packageConfig);
final moduleName = javaScriptBundler.makeModuleName(moduleUrl);
expect(compilers.keys, equals([moduleName]));
});
test('converts package: uris into /packages/ uris', () async {
var importUri = Uri.parse('package:a/a.dart');
var fileUri = packageConfig.resolve(importUri);
final library = Library(
importUri,
fileUri: fileUri,
procedures: [
Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method,
FunctionNode(Block([])),
fileUri: fileUri)
],
);
final testComponent =
Component(libraries: [library, ...testCoreLibraries]);
final javaScriptBundler = IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
useDebuggerModuleNames: debuggerNames,
);
await javaScriptBundler.initialize(
testComponent, importUri, packageConfig);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
final sourcemapSink = _MemorySink();
final metadataSink = _MemorySink();
final symbolsSink = _MemorySink();
final coreTypes = CoreTypes(testComponent);
await javaScriptBundler.compile(
ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final Map manifest = json.decode(utf8.decode(manifestSink.buffer));
final String code = utf8.decode(codeSink.buffer);
final moduleUrl = javaScriptBundler.urlForComponentUri(
library.importUri, packageConfig);
expect(manifest, {
'$moduleUrl.lib.js': {
'code': [0, codeSink.buffer.length],
'sourcemap': [0, sourcemapSink.buffer.length],
},
});
expect(code, contains('ArbitrarilyChosen'));
// Verify source map url is correct.
expect(code, contains('sourceMappingURL=a.dart.lib.js.map'));
});
test('multi-root uris create modules relative to the root', () async {
var importUri = Uri.parse('$multiRootScheme:/web/main.dart');
var fileUri = importUri;
final library = Library(
importUri,
fileUri: fileUri,
procedures: [
Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method,
FunctionNode(Block([])),
fileUri: fileUri)
],
);
final testComponent =
Component(libraries: [library, ...testCoreLibraries]);
final javaScriptBundler = IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
useDebuggerModuleNames: debuggerNames,
);
await javaScriptBundler.initialize(
testComponent, importUri, packageConfig);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
final sourcemapSink = _MemorySink();
final metadataSink = _MemorySink();
final symbolsSink = _MemorySink();
final coreTypes = CoreTypes(testComponent);
await javaScriptBundler.compile(
ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final Map manifest = json.decode(utf8.decode(manifestSink.buffer));
final String code = utf8.decode(codeSink.buffer);
expect(manifest, {
'${importUri.path}.lib.js': {
'code': [0, codeSink.buffer.length],
'sourcemap': [0, sourcemapSink.buffer.length],
},
});
expect(code, contains('ArbitrarilyChosen'));
// Verify source map url is correct.
expect(code, contains('sourceMappingURL=main.dart.lib.js.map'));
});
});
expect(code, contains('ArbitrarilyChosen'));
// verify source map url is correct.
expect(code, contains('sourceMappingURL=c.dart.lib.js.map'));
// verify program compilers are created.
expect(compilers.keys, equals([urlForComponentUri(library.importUri)]));
});
test('converts package: uris into /packages/ uris', () async {
var importUri = Uri.parse('package:a/a.dart');
var fileUri = packageConfig.resolve(importUri);
final library = Library(
importUri,
fileUri: fileUri,
procedures: [
Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method,
FunctionNode(Block([])),
fileUri: fileUri)
],
);
final testComponent = Component(libraries: [library, ...testCoreLibraries]);
final javaScriptBundler = IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
);
await javaScriptBundler.initialize(testComponent, importUri);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
final sourcemapSink = _MemorySink();
final metadataSink = _MemorySink();
final symbolsSink = _MemorySink();
final coreTypes = CoreTypes(testComponent);
await javaScriptBundler.compile(
ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final Map manifest = json.decode(utf8.decode(manifestSink.buffer));
final String code = utf8.decode(codeSink.buffer);
expect(manifest, {
'/packages/a/a.dart.lib.js': {
'code': [0, codeSink.buffer.length],
'sourcemap': [0, sourcemapSink.buffer.length],
},
});
expect(code, contains('ArbitrarilyChosen'));
// verify source map url is correct.
expect(code, contains('sourceMappingURL=a.dart.lib.js.map'));
});
test('multi-root uris create modules relative to the root', () async {
var importUri = Uri.parse('$multiRootScheme:/web/main.dart');
var fileUri = importUri;
final library = Library(
importUri,
fileUri: fileUri,
procedures: [
Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method,
FunctionNode(Block([])),
fileUri: fileUri)
],
);
final testComponent = Component(libraries: [library, ...testCoreLibraries]);
final javaScriptBundler = IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
);
await javaScriptBundler.initialize(testComponent, importUri);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
final sourcemapSink = _MemorySink();
final metadataSink = _MemorySink();
final symbolsSink = _MemorySink();
final coreTypes = CoreTypes(testComponent);
await javaScriptBundler.compile(
ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final Map manifest = json.decode(utf8.decode(manifestSink.buffer));
final String code = utf8.decode(codeSink.buffer);
expect(manifest, {
'${importUri.path}.lib.js': {
'code': [0, codeSink.buffer.length],
'sourcemap': [0, sourcemapSink.buffer.length],
},
});
expect(code, contains('ArbitrarilyChosen'));
// verify source map url is correct.
expect(code, contains('sourceMappingURL=main.dart.lib.js.map'));
});
}
test('can combine strongly connected components', () async {
// Create three libraries A, B, C where A is the entrypoint and B & C
@ -286,7 +340,7 @@ void main() {
multiRootScheme,
);
await javaScriptBundler.initialize(testComponent, uriA);
await javaScriptBundler.initialize(testComponent, uriA, packageConfig);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
@ -321,14 +375,14 @@ void main() {
r"define(['dart_sdk'], (function load__c_dart(dart_sdk) {";
expect(code, contains(cModuleHeader));
// verify source map url is correct.
// Verify source map url is correct.
expect(code, contains('sourceMappingURL=a.dart.lib.js.map'));
final offsets = manifest['/a.dart.lib.js']['sourcemap'];
final sourcemapModuleA = json.decode(
utf8.decode(sourcemapSink.buffer.sublist(offsets.first, offsets.last)));
// verify source maps are pointing at correct source files.
// Verify source maps are pointing at correct source files.
expect(sourcemapModuleA['file'], 'a.dart.lib.js');
});
@ -364,7 +418,7 @@ void main() {
multiRootScheme,
);
await javaScriptBundler.initialize(testComponent, uriA);
await javaScriptBundler.initialize(testComponent, uriA, packageConfig);
// Now change A and B so that they no longer import each other.
final libraryC2 = Library(uriC, fileUri: uriC);
@ -385,7 +439,8 @@ void main() {
final partialComponent =
Component(libraries: [libraryA2, libraryB2, libraryC2]);
await javaScriptBundler.invalidate(partialComponent, testComponent, uriA);
await javaScriptBundler.invalidate(
partialComponent, testComponent, uriA, packageConfig);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
@ -422,14 +477,14 @@ void main() {
final cModuleHeader =
r"define(['dart_sdk'], (function load__c_dart(dart_sdk) {";
expect(code, contains(cModuleHeader));
// verify source map url is correct.
// Verify source map url is correct.
expect(code, contains('sourceMappingURL=a.dart.lib.js.map'));
final offsets = manifest['/a.dart.lib.js']['sourcemap'];
final sourcemapModuleA = json.decode(
utf8.decode(sourcemapSink.buffer.sublist(offsets.first, offsets.last)));
// verify source maps are pointing at correct source files.
// Verify source maps are pointing at correct source files.
expect(sourcemapModuleA['file'], 'a.dart.lib.js');
});
@ -470,7 +525,7 @@ void main() {
multiRootScheme,
);
await javaScriptBundler.initialize(testComponent, uriA);
await javaScriptBundler.initialize(testComponent, uriA, packageConfig);
// Create a new component that only contains C.
final libraryC2 = Library(
@ -485,7 +540,8 @@ void main() {
final partialComponent = Component(libraries: [libraryC2]);
await javaScriptBundler.invalidate(partialComponent, testComponent, uriA);
await javaScriptBundler.invalidate(
partialComponent, testComponent, uriA, packageConfig);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();