Skip to content

Commit

Permalink
[browser] Allow JS modules to be injected as pre-loaded assets (#90392)
Browse files Browse the repository at this point in the history
Co-authored-by: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com>
  • Loading branch information
pavelsavara and ilonatommy committed Aug 11, 2023
1 parent df26db3 commit 34b6db9
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 19 deletions.
26 changes: 19 additions & 7 deletions src/mono/sample/wasm/browser-minimal-config/main.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
import { dotnet } from "./_framework/dotnet.js";
import * as runtime from "./_framework/dotnet.runtime.js";

async function fetchBinary(uri) {
return new Uint8Array(await (await fetch(uri)).arrayBuffer());
async function fetchBinary(url) {
return (await fetch(url, { cache: "no-cache" })).arrayBuffer();
}

function fetchWasm(url) {
return {
response: fetch(url, { cache: "no-cache" }),
url,
name: url.substring(url.lastIndexOf("/") + 1)
};
}

const assets = [
{
name: "dotnet.native.js",
// demo dynamic import
moduleExports: import("./_framework/dotnet.native.js"),
behavior: "js-module-native"
},
{
name: "dotnet.js",
behavior: "js-module-dotnet"
},
{
name: "dotnet.runtime.js",
// demo static import
moduleExports: runtime,
behavior: "js-module-runtime"
},
{
name: "dotnet.native.wasm",
// demo pending download promise
pendingDownload: fetchWasm("./_framework/dotnet.native.wasm"),
behavior: "dotnetwasm"
},
{
Expand All @@ -31,7 +42,8 @@ const assets = [
},
{
name: "Wasm.Browser.Config.Sample.wasm",
buffer: await fetchBinary("./_framework/Wasm.Browser.Config.Sample.wasm"),
// demo buffer promise
buffer: fetchBinary("./_framework/Wasm.Browser.Config.Sample.wasm"),
behavior: "assembly"
},
{
Expand Down
7 changes: 6 additions & 1 deletion src/mono/wasm/runtime/dotnet.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,12 @@ interface AssetEntry {
* If provided, runtime doesn't have to fetch the data.
* Runtime would set the buffer to null after instantiation to free the memory.
*/
buffer?: ArrayBuffer;
buffer?: ArrayBuffer | Promise<ArrayBuffer>;
/**
* If provided, runtime doesn't have to import it's JavaScript modules.
* This will not work for multi-threaded runtime.
*/
moduleExports?: any | Promise<any>;
/**
* It's metadata + fetch-like Promise<Response>
* If provided, the runtime doesn't have to initiate the download. It would just await the response.
Expand Down
8 changes: 5 additions & 3 deletions src/mono/wasm/runtime/loader/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,11 @@ export async function mono_download_assets(): Promise<void> {
const asset = await downloadPromise;
if (asset.buffer) {
if (!skipInstantiateByAssetTypes[asset.behavior]) {
mono_assert(asset.buffer && typeof asset.buffer === "object", "asset buffer must be array or buffer like");
mono_assert(asset.buffer && typeof asset.buffer === "object", "asset buffer must be array-like or buffer-like or promise of these");
mono_assert(typeof asset.resolvedUrl === "string", "resolvedUrl must be string");
const url = asset.resolvedUrl!;
const data = new Uint8Array(asset.buffer!);
const buffer = await asset.buffer;
const data = new Uint8Array(buffer);
cleanupAsset(asset);

// wait till after onRuntimeInitialized and after memory snapshot is loaded or skipped
Expand Down Expand Up @@ -487,7 +488,7 @@ async function start_asset_download_sources(asset: AssetEntryInternal): Promise<
return asset.pendingDownloadInternal.response;
}
if (asset.buffer) {
const buffer = asset.buffer;
const buffer = await asset.buffer;
if (!asset.resolvedUrl) {
asset.resolvedUrl = "undefined://" + asset.name;
}
Expand Down Expand Up @@ -707,6 +708,7 @@ export function cleanupAsset(asset: AssetEntryInternal) {
asset.pendingDownloadInternal = null as any; // GC
asset.pendingDownload = null as any; // GC
asset.buffer = null as any; // GC
asset.moduleExports = null as any; // GC
}

function fileName(name: string) {
Expand Down
27 changes: 20 additions & 7 deletions src/mono/wasm/runtime/loader/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,16 +428,29 @@ export async function createEmscripten(moduleFactory: DotnetModuleConfig | ((api
: createEmscriptenMain();
}

// in the future we can use feature detection to load different flavors
function importModules() {
const jsModuleRuntimeAsset = resolve_single_asset_path("js-module-runtime");
const jsModuleNativeAsset = resolve_single_asset_path("js-module-native");
mono_log_debug(`Attempting to import '${jsModuleRuntimeAsset.resolvedUrl}' for ${jsModuleRuntimeAsset.name}`);
mono_log_debug(`Attempting to import '${jsModuleNativeAsset.resolvedUrl}' for ${jsModuleNativeAsset.name}`);
return [
// keep js module names dynamic by using config, in the future we can use feature detection to load different flavors
import(/* webpackIgnore: true */jsModuleRuntimeAsset.resolvedUrl!),
import(/* webpackIgnore: true */jsModuleNativeAsset.resolvedUrl!),
];

let jsModuleRuntimePromise: Promise<RuntimeModuleExportsInternal>;
let jsModuleNativePromise: Promise<NativeModuleExportsInternal>;

if (typeof jsModuleRuntimeAsset.moduleExports === "object") {
jsModuleRuntimePromise = jsModuleRuntimeAsset.moduleExports;
} else {
mono_log_debug(`Attempting to import '${jsModuleRuntimeAsset.resolvedUrl}' for ${jsModuleRuntimeAsset.name}`);
jsModuleRuntimePromise = import(/* webpackIgnore: true */jsModuleRuntimeAsset.resolvedUrl!);
}

if (typeof jsModuleNativeAsset.moduleExports === "object") {
jsModuleNativePromise = jsModuleNativeAsset.moduleExports;
} else {
mono_log_debug(`Attempting to import '${jsModuleNativeAsset.resolvedUrl}' for ${jsModuleNativeAsset.name}`);
jsModuleNativePromise = import(/* webpackIgnore: true */jsModuleNativeAsset.resolvedUrl!);
}

return [jsModuleRuntimePromise, jsModuleNativePromise];
}

async function initializeModules(es6Modules: [RuntimeModuleExportsInternal, NativeModuleExportsInternal]) {
Expand Down
1 change: 1 addition & 0 deletions src/mono/wasm/runtime/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ async function instantiate_wasm_module(
assetToLoad.pendingDownloadInternal = null as any; // GC
assetToLoad.pendingDownload = null as any; // GC
assetToLoad.buffer = null as any; // GC
assetToLoad.moduleExports = null as any; // GC

mono_log_debug("instantiate_wasm_module done");

Expand Down
9 changes: 8 additions & 1 deletion src/mono/wasm/runtime/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,14 @@ export interface AssetEntry {
* If provided, runtime doesn't have to fetch the data.
* Runtime would set the buffer to null after instantiation to free the memory.
*/
buffer?: ArrayBuffer
buffer?: ArrayBuffer | Promise<ArrayBuffer>,

/**
* If provided, runtime doesn't have to import it's JavaScript modules.
* This will not work for multi-threaded runtime.
*/
moduleExports?: any | Promise<any>,

/**
* It's metadata + fetch-like Promise<Response>
* If provided, the runtime doesn't have to initiate the download. It would just await the response.
Expand Down

0 comments on commit 34b6db9

Please sign in to comment.