[ddc] Rolling internal dart_library.js into SDK

Change-Id: Ifb1cf1aed53b04eaa0a258668eab6b4b8f19df80
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/286609
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Commit-Queue: Mark Zhou <markzipan@google.com>
This commit is contained in:
Mark Zhou 2023-03-07 19:44:26 +00:00 committed by Commit Queue
parent 6414db5327
commit a92274cae8
3 changed files with 353 additions and 119 deletions

View file

@ -28,23 +28,55 @@ if (!dart_library) {
dart_library.libraryImports = libraryImports;
const _metrics = Symbol('metrics');
const _logMetrics = false;
// Returns a map from module name to various metrics for module.
function metrics() {
function moduleMetrics() {
const map = {};
const keys = Array.from(_libraries.keys());
for (const key of keys) {
const lib = _libraries.get(key);
map[lib._name] = lib._library[_metrics];
map[lib._name] = lib.firstLibraryValue[_metrics];
}
return map;
}
dart_library.metrics = metrics;
dart_library.moduleMetrics = moduleMetrics;
// Returns an application level overview of the module metrics.
function appMetrics() {
const metrics = moduleMetrics();
let dartSize = 0;
let jsSize = 0;
let sourceMapSize = 0;
let evaluatedModules = 0;
const keys = Array.from(_libraries.keys());
let firstLoadStart = Number.MAX_VALUE;
let lastLoadEnd = Number.MIN_VALUE;
for (const module of keys) {
let data = metrics[module];
if (data != null) {
evaluatedModules++;
dartSize += data.dartSize;
jsSize += data.jsSize;
sourceMapSize += data.sourceMapSize;
firstLoadStart = Math.min(firstLoadStart, data.loadStart);
lastLoadEnd = Math.max(lastLoadEnd, data.loadEnd);
}
}
return {
'dartSize': dartSize,
'jsSize': jsSize,
'sourceMapSize': sourceMapSize,
'evaluatedModules': evaluatedModules,
'loadTimeMs': lastLoadEnd - firstLoadStart
};
}
dart_library.appMetrics = appMetrics;
function _sortFn(key1, key2) {
const t1 = _libraries.get(key1)._library[_metrics].loadTime;
const t2 = _libraries.get(key2)._library[_metrics].loadTime;
const t1 = _libraries.get(key1).firstLibraryValue[_metrics].loadStart;
const t2 = _libraries.get(key2).firstLibraryValue[_metrics].loadStart;
return t1 - t2;
}
@ -52,18 +84,19 @@ if (!dart_library) {
// in CSV format.
function metricsCsv() {
let buffer =
'Module, JS Size, Dart Size, Load Time, Cumulative JS Size\n';
'Module, JS Size, Dart Size, Load Start, Load End, Cumulative JS Size\n';
const keys = Array.from(_libraries.keys());
keys.sort(_sortFn);
let cumulativeJsSize = 0;
for (const key of keys) {
const lib = _libraries.get(key);
const jsSize = lib._library[_metrics].jsSize;
const jsSize = lib.firstLibraryValue[_metrics].jsSize;
cumulativeJsSize += jsSize;
const dartSize = lib._library[_metrics].dartSize;
const loadTime = lib._library[_metrics].loadTime;
const dartSize = lib.firstLibraryValue[_metrics].dartSize;
const loadStart = lib.firstLibraryValue[_metrics].loadStart;
const loadEnd = lib.firstLibraryValue[_metrics].loadEnd;
buffer += '"' + lib._name + '", ' + jsSize + ', ' + dartSize + ', ' +
loadTime + ', ' + cumulativeJsSize + '\n';
loadStart + ', ' + loadEnd + ', ' + cumulativeJsSize + '\n';
}
return buffer;
}
@ -103,11 +136,32 @@ if (!dart_library) {
let _reverseImports = new Map();
// Set of libraries that were not only loaded on the page but also executed.
let _executedLibraries = new Set();
// App name to set of libraries that were not only loaded on the page but
// also executed.
const _executedLibraries = new Map();
dart_library.executedLibraryCount = function() {
let count = 0;
_executedLibraries.forEach(function(executedLibraries, _) {
count += executedLibraries.size;
});
return count;
};
// Library instance that is going to be loaded or has been loaded.
class LibraryInstance {
constructor(libraryValue) {
this.libraryValue = libraryValue;
// Cyclic import detection
this.loadingState = LibraryLoader.NOT_LOADED;
}
get isNotLoaded() {
return this.loadingState == LibraryLoader.NOT_LOADED;
}
}
class LibraryLoader {
constructor(name, defaultValue, imports, loader, data) {
constructor(name, defaultLibraryValue, imports, loader, data) {
imports.forEach(function(i) {
let deps = _reverseImports.get(i);
if (!deps) {
@ -117,41 +171,71 @@ if (!dart_library) {
deps.add(name);
});
this._name = name;
this._library = defaultValue ? defaultValue : {};
this._defaultLibraryValue =
defaultLibraryValue ? defaultLibraryValue : {};
this._imports = imports;
this._loader = loader;
data.jsSize = loader.toString().length;
data.loadTime = Infinity;
data.loadStart = NaN;
data.loadEnd = NaN;
this._metrics = data;
// Cyclic import detection
this._state = LibraryLoader.NOT_LOADED;
// First loaded instance for supporting logic that assumes there is only
// one app.
// TODO(b/204209941): Remove _firstLibraryInstance after debugger and
// metrics support multiple apps.
this._firstLibraryInstance =
new LibraryInstance(this._deepCopyDefaultValue());
this._firstLibraryInstanceUsed = false;
// App name to instance map.
this._instanceMap = new Map();
}
loadImports() {
let results = [];
for (let name of this._imports) {
results.push(import_(name));
/// First loaded value for supporting logic that assumes there is only
/// one app.
get firstLibraryValue() {
return this._firstLibraryInstance.libraryValue;
}
/// The loaded instance value for the given `appName`.
libraryValueInApp(appName) {
return this._instanceMap.get(appName).libraryValue;
}
load(appName) {
let instance = this._instanceMap.get(appName);
if (!instance && !this._firstLibraryInstanceUsed) {
// If `_firstLibraryInstance` is already assigned to an app, creates a
// new instance clone (with deep copy) and assigns it the given app.
// Otherwise, reuse `_firstLibraryInstance`.
instance = this._firstLibraryInstance;
this._firstLibraryInstanceUsed = true;
this._instanceMap.set(appName, instance);
}
if (!instance) {
instance = new LibraryInstance(this._deepCopyDefaultValue());
this._instanceMap.set(appName, instance);
}
return results;
}
load() {
// Check for cycles
if (this._state == LibraryLoader.LOADING) {
if (instance.loadingState == LibraryLoader.LOADING) {
throwLibraryError('Circular dependence on library: ' + this._name);
} else if (this._state >= LibraryLoader.READY) {
return this._library;
} else if (instance.loadingState >= LibraryLoader.READY) {
return instance.libraryValue;
}
_executedLibraries.add(this._name);
this._state = LibraryLoader.LOADING;
if (!_executedLibraries.has(appName)) {
_executedLibraries.set(appName, new Set());
}
_executedLibraries.get(appName).add(this._name);
instance.loadingState = LibraryLoader.LOADING;
// Handle imports
let args = this.loadImports();
let args = this._loadImports(appName);
// Load the library
let loader = this;
let library = this._library;
let library = instance.libraryValue;
library[libraryImports] = this._imports;
library[loadedModule] = library;
@ -160,40 +244,50 @@ if (!dart_library) {
if (this._name == 'dart_sdk') {
// Eagerly load the SDK.
if (!!self.performance) {
library[_metrics].loadTime = self.performance.now();
if (!!self.performance && !!self.performance.now) {
library[_metrics].loadStart = self.performance.now();
}
if (_logMetrics) console.time('Load ' + this._name);
this._loader.apply(null, args);
if (_logMetrics) console.timeEnd('Load ' + this._name);
if (!!self.performance && !!self.performance.now) {
library[_metrics].loadEnd = self.performance.now();
}
} else {
// Load / parse other modules on demand.
let done = false;
this._library = new Proxy(library, {
instance.libraryValue = new Proxy(library, {
get: function(o, name) {
if (name == _metrics) {
return o[name];
}
if (!done) {
done = true;
if (!!self.performance) {
library[_metrics].loadTime = self.performance.now();
if (!!self.performance && !!self.performance.now) {
library[_metrics].loadStart = self.performance.now();
}
if (_logMetrics) console.time('Load ' + loader._name);
loader._loader.apply(null, args);
if (_logMetrics) console.timeEnd('Load ' + loader._name);
if (!!self.performance && !!self.performance.now) {
library[_metrics].loadEnd = self.performance.now();
}
}
return o[name];
}
});
}
this._state = LibraryLoader.READY;
return this._library;
instance.loadingState = LibraryLoader.READY;
return instance.libraryValue;
}
stub() {
return this._library;
_loadImports(appName) {
let results = [];
for (let name of this._imports) {
results.push(import_(name, appName));
}
return results;
}
_deepCopyDefaultValue() {
return JSON.parse(JSON.stringify(this._defaultLibraryValue));
}
}
LibraryLoader.NOT_LOADED = 0;
@ -208,7 +302,7 @@ if (!dart_library) {
dart_library.debuggerLibraries = function() {
let debuggerLibraries = [];
_libraries.forEach(function(value, key, map) {
debuggerLibraries.push(value.load());
debuggerLibraries.push(value.load(_firstStartedAppName));
});
debuggerLibraries.__proto__ = null;
return debuggerLibraries;
@ -217,21 +311,24 @@ if (!dart_library) {
// Invalidate a library and all things that depend on it
function _invalidateLibrary(name) {
let lib = _libraries.get(name);
if (lib._state == LibraryLoader.NOT_LOADED) return;
lib._state = LibraryLoader.NOT_LOADED;
lib._library = {};
if (lib._instanceMap.size === 0) return;
lib._firstLibraryInstance =
new LibraryInstance(lib._deepCopyDefaultValue());
lib._firstLibraryInstanceUsed = false;
lib._instanceMap.clear();
let deps = _reverseImports.get(name);
if (!deps) return;
deps.forEach(_invalidateLibrary);
}
function library(name, defaultValue, imports, loader, data = {}) {
function library(name, defaultLibraryValue, imports, loader, data = {}) {
let result = _libraries.get(name);
if (result) {
console.log('Re-loading ' + name);
_invalidateLibrary(name);
}
result = new LibraryLoader(name, defaultValue, imports, loader, data);
result =
new LibraryLoader(name, defaultLibraryValue, imports, loader, data);
_libraries.set(name, result);
return result;
}
@ -240,20 +337,33 @@ if (!dart_library) {
// Store executed modules upon reload.
if (!!self.addEventListener && !!self.localStorage) {
self.addEventListener('beforeunload', function(event) {
let libraryCache = {
'time': new Date().getTime(),
'modules': Array.from(_executedLibraries.keys())
};
self.localStorage.setItem(
'dartLibraryCache', JSON.stringify(libraryCache));
_nameToApp.forEach(function(_, appName) {
if (!_executedLibraries.get(appName)) {
return;
}
let libraryCache = {
'time': new Date().getTime(),
'modules': Array.from(_executedLibraries.get(appName).keys()),
};
self.localStorage.setItem(
`dartLibraryCache:${appName}`, JSON.stringify(libraryCache));
});
});
}
// Map from module name to corresponding proxy library.
// Map from module name to corresponding app to proxy library map.
let _proxyLibs = new Map();
function import_(name) {
let proxy = _proxyLibs.get(name);
function import_(name, appName) {
// For backward compatibility.
if (!appName && _lastStartedSubapp) {
appName = _lastStartedSubapp.appName;
}
let proxy;
if (_proxyLibs.has(name)) {
proxy = _proxyLibs.get(name).get(appName);
}
if (proxy) return proxy;
let proxyLib = new Proxy({}, {
get: function(o, p) {
@ -266,9 +376,21 @@ if (!dart_library) {
xhr.open('GET', sourceURL, false);
xhr.withCredentials = true;
xhr.send();
// Add inline policy to make eval() call Trusted Types compatible
// when running in a TT compatible browser
let policy = {
createScript: function(script) {
return script;
}
};
if (self.trustedTypes && self.trustedTypes.createPolicy) {
policy = self.trustedTypes.createPolicy(
'dartDdcModuleLoading#dart_library', policy);
}
// Append sourceUrl so the resource shows up in the Chrome
// console.
eval(xhr.responseText + '//@ sourceURL=' + sourceURL);
eval(policy.createScript(
xhr.responseText + '//@ sourceURL=' + sourceURL));
lib = _libraries.get(name);
}
}
@ -277,10 +399,13 @@ if (!dart_library) {
}
// Always load the library before accessing a property as it may have
// been invalidated.
return lib.load()[p];
return lib.load(appName)[p];
}
});
_proxyLibs.set(name, proxyLib);
if (!_proxyLibs.has(name)) {
_proxyLibs.set(name, new Map());
}
_proxyLibs.get(name).set(appName, proxyLib);
return proxyLib;
}
dart_library.import = import_;
@ -297,7 +422,12 @@ if (!dart_library) {
let _debuggerInitialized = false;
// Called to initiate a hot restart of the application.
// Caches the last N runIds to prevent hot reload requests from the same
// runId from executing more than once.
const _hotRestartRunIdCache = new Array();
// Called to initiate a hot restart of the application for a given uuid. If
// it is not set, the last started application will be hot restarted.
//
// "Hot restart" means all application state is cleared, the newly compiled
// modules are loaded, and `main()` is called.
@ -316,100 +446,190 @@ if (!dart_library) {
// 3. Call dart:_runtime's `hotRestart()` function to clear any state that
// `dartdevc` is tracking, such as initialized static fields and type
// caches.
// 4. Call `window.$dartWarmReload()` (provided by the HTML page) to reload
// the relevant JS modules, passing a callback that will invoke `main()`.
// 5. `$dartWarmReload` calls the callback to rerun main.
// 4. Call `self.$dartReloadModifiedModules()` (provided by the HTML page)
// to reload the relevant JS modules, passing a callback that will invoke
// `main()`.
// 5. `$dartReloadModifiedModules` calls the callback to rerun main.
//
function reload(clearState) {
// TODO(jmesserly): once we've rolled out `clearState` make it the
// default, and eventually remove the parameter.
if (clearState == null) clearState = true;
// TODO(jmesserly): we may want to change these APIs to use the
// "hot restart" terminology for consistency with Flutter. In Flutter,
// "hot reload" refers to keeping the application state and attempting to
// patch the code for the application while it is executing
// (https://flutter.io/hot-reload/), whereas "hot restart" refers to what
// dartdevc supports: tear down the app, update the code, and rerun the
// app.
if (!self || !self.$dartWarmReload) {
async function hotRestart(config) {
if (!self || !self.$dartReloadModifiedModules) {
console.warn('Hot restart not supported in this environment.');
return;
}
// Call the application's `onReloadStart()` function, if provided.
let result;
if (_lastLibrary && _lastLibrary.onReloadStart) {
result = _lastLibrary.onReloadStart();
// If `config.runId` is set (e.g. a unique build ID that represent the
// current build and shared by multiple subapps), skip the following runs
// with the same id.
if (config && config.runId) {
if (_hotRestartRunIdCache.indexOf(config.runId) >= 0) {
// The run has already started (by other subapp or app)
return;
}
_hotRestartRunIdCache.push(config.runId);
// Only cache the runIds for the last N runs. We assume that there are
// less than N requests with different runId can happen in a very short
// period of time (e.g. 1 second).
if (_hotRestartRunIdCache.length > 10) {
_hotRestartRunIdCache.shift();
}
}
let sdk = _libraries.get('dart_sdk');
self.console.clear();
const sdk = _libraries.get('dart_sdk');
/// Once the `onReloadStart()` completes, this finishes the restart.
function finishHotRestart() {
self.console.clear();
if (clearState) {
// This resets all initialized fields and clears type caches and other
// temporary data structures used by the compiler/SDK.
sdk._library.dart.hotRestart();
// Finds out what apps and their subapps should be hot restarted in
// their starting order.
const dirtyAppNames = new Array();
const dirtySubapps = new Array();
if (config && config.runId) {
_nameToApp.forEach(function(app, appName) {
dirtySubapps.push(...app.uuidToSubapp.values());
dirtyAppNames.push(appName);
});
} else {
dirtySubapps.push(_lastStartedSubapp);
dirtyAppNames.push(_lastStartedSubapp.appName);
}
// Invokes onReloadStart for each subapp in reversed starting order.
const onReloadStartPromises = new Array();
for (const subapp of dirtySubapps.reverse()) {
// Call the application's `onReloadStart()` function, if provided.
if (subapp.library && subapp.library.onReloadStart) {
const result = subapp.library.onReloadStart();
if (result && result.then) {
let resolve;
onReloadStartPromises.push(new Promise(function(res, _) {
resolve = res;
}));
const dart = sdk.libraryValueInApp(subapp.appName).dart;
result.then(dart.dynamic, function() {
resolve();
});
}
}
}
// Reverse the subapps back to starting order.
dirtySubapps.reverse();
await Promise.all(onReloadStartPromises);
// Invokes SDK `hotRestart` to reset all initialized fields and clears
// type caches and other temporary data structures used by the
// compiler/SDK.
for (const appName of dirtyAppNames) {
sdk.libraryValueInApp(appName).dart.hotRestart();
}
// Starts the subapps in their starting order.
for (const subapp of dirtySubapps) {
// Call the module loader to reload the necessary modules.
self.$dartWarmReload(() => {
self.$dartReloadModifiedModules(subapp.appName, function() {
// Once the modules are loaded, rerun `main()`.
start(_lastModuleName, _lastLibraryName, true);
start(
subapp.appName, subapp.uuid, subapp.moduleName,
subapp.libraryName, true);
});
}
}
dart_library.reload = hotRestart;
if (result && result.then) {
result.then(sdk._library.dart.Dynamic)(finishHotRestart);
} else {
finishHotRestart();
/// An App contains one or multiple Subapps, all of the subapps share the
/// same memory copy of library instances, and as a result they share state
/// in Dart statics and top-level fields. There can be one or multiple Apps
/// in a browser window, all of the Apps are isolated from each other
/// (i.e. they create different instances even for the same module).
class App {
constructor(name) {
this.name = name;
// Subapp's uuid to subapps in initial starting order.
// (ES6 preserves iteration order)
this.uuidToSubapp = new Map();
}
}
dart_library.reload = reload;
class Subapp {
constructor(uuid, appName, moduleName, libraryName, library) {
this.uuid = uuid;
this.appName = appName;
this.moduleName = moduleName;
this.libraryName = libraryName;
this.library = library;
let _lastModuleName;
let _lastLibraryName;
let _lastLibrary;
let _originalBody;
this.originalBody = null;
}
}
function start(moduleName, libraryName, isReload) {
// App name to App map in initial starting order.
// (ES6 preserves iteration order)
const _nameToApp = new Map();
let _firstStartedAppName;
let _lastStartedSubapp;
/// Starts a subapp that is identified with `uuid`, `moduleName`, and
/// `libraryName` inside a parent app that is identified by `appName`.
function start(appName, uuid, moduleName, libraryName, isReload) {
console.info(
`DDC: Subapp Module [${appName}:${moduleName}:${uuid}] is starting`);
if (libraryName == null) libraryName = moduleName;
_lastModuleName = moduleName;
_lastLibraryName = libraryName;
let library = import_(moduleName)[libraryName];
_lastLibrary = library;
let dart_sdk = import_('dart_sdk');
const library = import_(moduleName, appName)[libraryName];
let app = _nameToApp.get(appName);
if (!isReload) {
if (!app) {
app = new App(appName);
_nameToApp.set(appName, app);
}
let subapp = app.uuidToSubapp.get(uuid);
if (!subapp) {
subapp = new Subapp(uuid, appName, moduleName, libraryName, library);
app.uuidToSubapp.set(uuid, subapp);
}
_lastStartedSubapp = subapp;
if (!_firstStartedAppName) {
_firstStartedAppName = appName;
}
}
const subapp = app.uuidToSubapp.get(uuid);
const sdk = import_('dart_sdk', appName);
if (!_debuggerInitialized) {
// This import is only needed for chrome debugging. We should provide an
// option to compile without it.
dart_sdk._debugger.registerDevtoolsFormatter();
sdk._debugger.registerDevtoolsFormatter();
// Create isolate.
_debuggerInitialized = true;
}
if (isReload) {
// subapp may have been modified during reload, `subapp.library` needs
// to always point to the latest data.
subapp.library = library;
if (library.onReloadEnd) {
library.onReloadEnd();
return;
} else {
if (!!self.document) {
// Note: we expect _originalBody to be undefined in non-browser
// Note: we expect originalBody to be undefined in non-browser
// environments, but in that case so is the body.
if (!_originalBody && !!self.document.body) {
if (!subapp.originalBody && !!self.document.body) {
self.console.warn('No body saved to update on reload');
} else {
self.document.body = _originalBody;
self.document.body = subapp.originalBody;
}
}
}
} else {
// If not a reload then store the initial html to reset it on reload.
if (!!self.document && !!self.document.body) {
_originalBody = self.document.body.cloneNode(true);
// If not a reload and `onReloadEnd` is not defined, store the initial
// html to reset it on reload.
if (!library.onReloadEnd && !!self.document && !!self.document.body) {
subapp.originalBody = self.document.body.cloneNode(true);
}
}
library.main([]);

View file

@ -350,6 +350,8 @@ class TestDriver {
var bootstrapFile = File(htmlBootstrapper.toFilePath())..createSync();
var moduleName = compiler.metadata!.name;
var mainLibraryName = compiler.metadataForLibraryUri(input).name;
var appName = p.relative(
p.withoutExtension(compiler.metadataForLibraryUri(input).importUri));
switch (setup.moduleFormat) {
case ModuleFormat.ddc:
@ -368,6 +370,9 @@ class TestDriver {
var dartLibraryPath =
escaped(p.join(ddcPath, 'lib', 'js', 'legacy', 'dart_library.js'));
var outputPath = output.toFilePath();
// This is used in the DDC module system for multiapp workflows and is
// stubbed here.
var uuid = '00000000-0000-0000-0000-000000000000';
bootstrapFile.writeAsStringSync('''
<script src='$dartLibraryPath'></script>
<script src='$dartSdkPath'></script>
@ -384,7 +389,8 @@ class TestDriver {
sdk.dart.nonNullAsserts(true);
sdk.dart.nativeNonNullAsserts(true);
sdk._debugger.registerDevtoolsFormatter();
dart_library.start('$moduleName', '$mainLibraryName');
dart_library.start('$appName', '$uuid', '$moduleName', '$mainLibraryName',
false);
</script>
''');
break;

View file

@ -136,6 +136,14 @@ void main(List<String> args) async {
var libname =
js_names.pathToJSIdentifier(p.relative(p.withoutExtension(entry)));
// This is used in the DDC module system and usually corresponds to the
// entrypoint's path.
var appname = p.relative(p.withoutExtension(entry));
// This is used in the DDC module system for multiapp workflows and is
// stubbed in ddb.
var uuid = "00000000-0000-0000-0000-000000000000";
// By default (no `-d`), we use the `dartdevc` binary on the user's path to
// compute the SDK we use for execution. I.e., we assume that `dart` is
// under `$DART_SDK/bin/dart` and use that to find `dartdevc` and related
@ -353,7 +361,7 @@ sdk.dart.nativeNonNullAsserts($nativeNonNullAsserts);
// Invoke main through the d8 preamble to ensure the code is running
// within the fake event loop.
self.dartMainRunner(function () {
dart_library.start("$basename", "$libname");
dart_library.start("$appname", "$uuid", "$basename", "$libname", false);
});
''';
var dart2jsD8Preamble =