diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp
index e0ba72d32a1..726f7caf4a2 100644
--- a/platform/javascript/export/export.cpp
+++ b/platform/javascript/export/export.cpp
@@ -242,7 +242,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform {
 		return name;
 	}
 
-	void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects);
+	void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes);
 
 	static void _server_thread_poll(void *data);
 
@@ -280,7 +280,7 @@ public:
 	~EditorExportPlatformJavaScript();
 };
 
-void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects) {
+void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) {
 
 	String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size());
 	String str_export;
@@ -300,6 +300,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
 	config["gdnativeLibs"] = libs;
 	config["executable"] = p_name;
 	config["args"] = args;
+	config["fileSizes"] = p_file_sizes;
 	const String str_config = JSON::print(config);
 
 	for (int i = 0; i < lines.size(); i++) {
@@ -481,6 +482,8 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 		return ERR_FILE_CORRUPT;
 	}
 
+	Vector<uint8_t> html;
+	Dictionary file_sizes;
 	do {
 		//get filename
 		unz_file_info info;
@@ -489,6 +492,16 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 
 		String file = fname;
 
+		// HTML is handled later
+		if (file == "godot.html") {
+			if (custom_html.empty()) {
+				html.resize(info.uncompressed_size);
+				unzOpenCurrentFile(pkg);
+				unzReadCurrentFile(pkg, html.ptrw(), html.size());
+				unzCloseCurrentFile(pkg);
+			}
+			continue;
+		}
 		Vector<uint8_t> data;
 		data.resize(info.uncompressed_size);
 
@@ -499,15 +512,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 
 		//write
 
-		if (file == "godot.html") {
-
-			if (!custom_html.empty()) {
-				continue;
-			}
-			_fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects);
-			file = p_path.get_file();
-
-		} else if (file == "godot.js") {
+		if (file == "godot.js") {
 
 			file = p_path.get_file().get_basename() + ".js";
 		} else if (file == "godot.worker.js") {
@@ -525,6 +530,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 		} else if (file == "godot.wasm") {
 
 			file = p_path.get_file().get_basename() + ".wasm";
+			file_sizes[file.get_file()] = (uint64_t)info.uncompressed_size;
 		}
 
 		String dst = p_path.get_base_dir().plus_file(file);
@@ -547,19 +553,26 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
 			EditorNode::get_singleton()->show_warning(TTR("Could not read custom HTML shell:") + "\n" + custom_html);
 			return ERR_FILE_CANT_READ;
 		}
-		Vector<uint8_t> buf;
-		buf.resize(f->get_len());
-		f->get_buffer(buf.ptrw(), buf.size());
+		html.resize(f->get_len());
+		f->get_buffer(html.ptrw(), html.size());
 		memdelete(f);
-		_fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects);
-
+	}
+	{
+		FileAccess *f = FileAccess::open(pck_path, FileAccess::READ);
+		if (f) {
+			file_sizes[pck_path.get_file()] = (uint64_t)f->get_len();
+			memdelete(f);
+			f = NULL;
+		}
+		_fix_html(html, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects, file_sizes);
 		f = FileAccess::open(p_path, FileAccess::WRITE);
 		if (!f) {
 			EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + p_path);
 			return ERR_FILE_CANT_WRITE;
 		}
-		f->store_buffer(buf.ptr(), buf.size());
+		f->store_buffer(html.ptr(), html.size());
 		memdelete(f);
+		html.resize(0);
 	}
 
 	Ref<Image> splash;
diff --git a/platform/javascript/js/engine/config.js b/platform/javascript/js/engine/config.js
index 8c2636c7aba..82ff273ecf2 100644
--- a/platform/javascript/js/engine/config.js
+++ b/platform/javascript/js/engine/config.js
@@ -100,6 +100,11 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
 		 * @type {Array.<string>}
 		 */
 		gdnativeLibs: [],
+		/**
+		 * @ignore
+		 * @type {Array.<string>}
+		 */
+		fileSizes: [],
 		/**
 		 * A callback function for handling Godot's ``OS.execute`` calls.
 		 *
@@ -219,6 +224,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
 		this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy);
 		this.persistentPaths = parse('persistentPaths', this.persistentPaths);
 		this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs);
+		this.fileSizes = parse('fileSizes', this.fileSizes);
 		this.args = parse('args', this.args);
 		this.onExecute = parse('onExecute', this.onExecute);
 		this.onExit = parse('onExit', this.onExit);
diff --git a/platform/javascript/js/engine/engine.js b/platform/javascript/js/engine/engine.js
index e2d27c4c023..19a0552c8cc 100644
--- a/platform/javascript/js/engine/engine.js
+++ b/platform/javascript/js/engine/engine.js
@@ -35,14 +35,15 @@ const Engine = (function () {
 	 * Load the engine from the specified base path.
 	 *
 	 * @param {string} basePath Base path of the engine to load.
+	 * @param {number=} [size=0] The file size if known.
 	 * @returns {Promise} A Promise that resolves once the engine is loaded.
 	 *
 	 * @function Engine.load
 	 */
-	Engine.load = function (basePath) {
+	Engine.load = function (basePath, size) {
 		if (loadPromise == null) {
 			loadPath = basePath;
-			loadPromise = preloader.loadPromise(`${loadPath}.wasm`, true);
+			loadPromise = preloader.loadPromise(`${loadPath}.wasm`, size, true);
 			requestAnimationFrame(preloader.animateProgress);
 		}
 		return loadPromise;
@@ -96,7 +97,7 @@ const Engine = (function () {
 						initPromise = Promise.reject(new Error('A base path must be provided when calling `init` and the engine is not loaded.'));
 						return initPromise;
 					}
-					Engine.load(basePath);
+					Engine.load(basePath, this.config.fileSizes[`${basePath}.wasm`]);
 				}
 				const me = this;
 				function doInit(promise) {
@@ -137,7 +138,7 @@ const Engine = (function () {
 			 * @returns {Promise} A Promise that resolves once the file is loaded.
 			 */
 			preloadFile: function (file, path) {
-				return preloader.preload(file, path);
+				return preloader.preload(file, path, this.config.fileSizes[file]);
 			},
 
 			/**
diff --git a/platform/javascript/js/engine/preloader.js b/platform/javascript/js/engine/preloader.js
index 45bdcf26d4f..3535fdb361b 100644
--- a/platform/javascript/js/engine/preloader.js
+++ b/platform/javascript/js/engine/preloader.js
@@ -43,9 +43,9 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
 		}), { headers: response.headers });
 	}
 
-	function loadFetch(file, tracker, raw) {
+	function loadFetch(file, tracker, fileSize, raw) {
 		tracker[file] = {
-			total: 0,
+			total: fileSize || 0,
 			loaded: 0,
 			done: false,
 		};
@@ -117,16 +117,16 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
 		progressFunc = callback;
 	};
 
-	this.loadPromise = function (file, raw = false) {
-		return retry(loadFetch.bind(null, file, loadingFiles, raw), DOWNLOAD_ATTEMPTS_MAX);
+	this.loadPromise = function (file, fileSize, raw = false) {
+		return retry(loadFetch.bind(null, file, loadingFiles, fileSize, raw), DOWNLOAD_ATTEMPTS_MAX);
 	};
 
 	this.preloadedFiles = [];
-	this.preload = function (pathOrBuffer, destPath) {
+	this.preload = function (pathOrBuffer, destPath, fileSize) {
 		let buffer = null;
 		if (typeof pathOrBuffer === 'string') {
 			const me = this;
-			return this.loadPromise(pathOrBuffer).then(function (buf) {
+			return this.loadPromise(pathOrBuffer, fileSize).then(function (buf) {
 				me.preloadedFiles.push({
 					path: destPath || pathOrBuffer,
 					buffer: buf,