// 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, }); '''; }