blob: 3d6f7749096287c165f614808a7041231fbe73f2 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:meta/meta.dart';
// This logic is taken directly from https://github.com/dart-lang/build/blob/master/build_web_compilers/lib/src/dev_compiler_bootstrap.dart#L272
// It should be fairly stable, but is otherwise required to interact with the client.js script
// vendored with DWDS.
const String _currentDirectoryScript = r'''
var _currentDirectory = (function () {
var _url;
var lines = new Error().stack.split('\n');
function lookupUrl() {
if (lines.length > 2) {
var match = lines[1].match(/^\s+at (.+):\d+:\d+$/);
// Chrome.
if (match) return match[1];
// Chrome nested eval case.
match = lines[1].match(/^\s+at eval [(](.+):\d+:\d+[)]$/);
if (match) return match[1];
// Edge.
match = lines[1].match(/^\s+at.+\((.+):\d+:\d+\)$/);
if (match) return match[1];
// Firefox.
match = lines[0].match(/[<][@](.+):\d+:\d+$/)
if (match) return match[1];
}
// Safari.
return lines[0].match(/(.+):\d+:\d+$/)[1];
}
_url = lookupUrl();
var lastSlash = _url.lastIndexOf('/');
if (lastSlash == -1) return _url;
var currentDirectory = _url.substring(0, lastSlash + 1);
return currentDirectory;
})();
''';
/// The JavaScript bootstrap script to support in-browser hot restart.
///
/// The [requireUrl] loads our cached RequireJS script file. The [mapperUrl]
/// loads the special Dart stack trace mapper. The [entrypoint] is the
/// actual main.dart file.
///
/// This file is served when the browser requests "main.dart.js" in debug mode,
/// and is responsible for bootstrapping the RequireJS modules and attaching
/// the hot reload hooks.
String generateBootstrapScript({
@required String requireUrl,
@required String mapperUrl,
@required String entrypoint,
}) {
return '''
"use strict";
// Attach source mapping.
var mapperEl = document.createElement("script");
mapperEl.defer = true;
mapperEl.async = false;
mapperEl.src = "$mapperUrl";
document.head.appendChild(mapperEl);
// Attach require JS.
var requireEl = document.createElement("script");
requireEl.defer = true;
requireEl.async = false;
requireEl.src = "$requireUrl";
// This attribute tells require JS what to load as main (defined below).
requireEl.setAttribute("data-main", "main_module.bootstrap");
document.head.appendChild(requireEl);
// Invoked by connected chrome debugger for hot reload/restart support.
window.\$hotReloadHook = function(modules) {
return new Promise(function(resolve, reject) {
if (modules == null) {
reject();
}
// If no modules change, return immediately.
if (modules.length == 0) {
resolve();
}
var reloadCount = 0;
for (var i = 0; i < modules.length; i++) {
require.undef(modules[i]);
require([modules[i]], function(module) {
reloadCount += 1;
// once we've reloaded every module, trigger the hot reload.
if (reloadCount == modules.length) {
require(["$entrypoint", "dart_sdk"], function(app, dart_sdk) {
// See the doc comment under in generateMainModule.
window.\$dartRunMain = app[Object.keys(app)[0]].main;
window.\$hotReload(resolve);
});
}
});
}
});
}
''';
}
/// Generate a synthetic main module which captures the application's main
/// method.
///
/// RE: Object.keys usage in app.main:
/// This attaches the main entrypoint and hot reload functionality to the window.
/// The app module will have a single property which contains the actual application
/// code. The property name is based off of the entrypoint that is generated, for example
/// the file `foo/bar/baz.dart` will generate a property named approximately
/// `foo__bar__baz`. Rather than attempt to guess, we assume the first property of
/// this object is the module.
String generateMainModule({@required String entrypoint}) {
return '''/* ENTRYPOINT_EXTENTION_MARKER */
// baseUrlScript
var baseUrl = (function () {
// Attempt to detect --precompiled mode for tests, and set the base url
// appropriately, otherwise set it to '/'.
var pathParts = location.pathname.split("/");
if (pathParts[0] == "") {
pathParts.shift();
}
if (pathParts.length > 1 && pathParts[1] == "test") {
return "/" + pathParts.slice(0, 2).join("/") + "/";
}
// Attempt to detect base url using <base href> html tag
// base href should start and end with "/"
if (typeof document !== 'undefined') {
var el = document.getElementsByTagName('base');
if (el && el[0] && el[0].getAttribute("href") && el[0].getAttribute
("href").startsWith("/") && el[0].getAttribute("href").endsWith("/")){
return el[0].getAttribute("href");
}
}
// return default value
return "/";
}());
$_currentDirectoryScript
// dart loader
if(!window.\$dartLoader) {
window.\$dartLoader = {
appDigests: _currentDirectory + 'basic.digests',
moduleIdToUrl: new Map(),
urlToModuleId: new Map(),
rootDirectories: new Array(),
// Used in package:build_runner/src/server/build_updates_client/hot_reload_client.dart
moduleParentsGraph: new Map(),
moduleLoadingErrorCallbacks: new Map(),
forceLoadModule: function (moduleName, callback, onError) {
if (typeof onError != 'undefined') {
var errorCallbacks = \$dartLoader.moduleLoadingErrorCallbacks;
if (!errorCallbacks.has(moduleName)) {
errorCallbacks.set(moduleName, new Set());
}
errorCallbacks.get(moduleName).add(onError);
}
requirejs.undef(moduleName);
requirejs([moduleName], function() {
if (typeof onError != 'undefined') {
errorCallbacks.get(moduleName).delete(onError);
}
if (typeof callback != 'undefined') {
callback();
}
});
},
getModuleLibraries: null, // set up by _initializeTools
};
}
let modulePaths = {};
let customModulePaths = {};
window.\$dartLoader.rootDirectories.push(window.location.origin + baseUrl);
for (let moduleName of Object.getOwnPropertyNames(modulePaths)) {
let modulePath = modulePaths[moduleName];
if (modulePath != moduleName) {
customModulePaths[moduleName] = modulePath;
}
var src = window.location.origin + '/' + modulePath + '.js';
if (window.\$dartLoader.moduleIdToUrl.has(moduleName)) {
continue;
}
\$dartLoader.moduleIdToUrl.set(moduleName, src);
\$dartLoader.urlToModuleId.set(src, moduleName);
}
// Create the main module loaded below.
define("main_module.bootstrap", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) {
dart_sdk.dart.setStartAsyncSynchronously(true);
dart_sdk._debugger.registerDevtoolsFormatter();
\$dartLoader.getModuleLibraries = dart_sdk.dart.getModuleLibraries;
if (window.\$dartStackTraceUtility && !window.\$dartStackTraceUtility.ready) {
window.\$dartStackTraceUtility.ready = true;
let dart = dart_sdk.dart;
window.\$dartStackTraceUtility.setSourceMapProvider(
function(url) {
url = url.replace(baseUrl, '/');
var module = window.\$dartLoader.urlToModuleId.get(url);
if (!module) {
if (url.endsWith('dart_sdk.js')) {
module = 'dart_sdk';
} else {
try {
module = '/packages' + url.split('packages')[1].split('.')[0] + '.dart';
} catch (e) {
return null;
}
}
}
return dart.getSourceMap(module);
});
}
if (typeof document != 'undefined') {
window.postMessage({ type: "DDC_STATE_CHANGE", state: "start" }, "*");
}
dart_sdk._isolate_helper.startRootIsolate(() => {}, []);
// See the generateMainModule doc comment.
var child = {};
child.main = app[Object.keys(app)[0]].main;
if (window.\$hotReload == null) {
window.\$hotReload = function(cb) {
dart_sdk.developer.invokeExtension("ext.flutter.disassemble", "{}").then((_) => {
dart_sdk.dart.hotRestart();
window.\$dartRunMain();
window.requestAnimationFrame(cb);
});
}
}
/* MAIN_EXTENSION_MARKER */
child.main();
});
// Require JS configuration.
require.config({
waitSeconds: 0,
});
''';
}