Skip to content

Commit

Permalink
[wasm] Emscripten uses require even when targeting ES6. This deals …
Browse files Browse the repository at this point in the history
…with the consequences. (dotnet#63718)
  • Loading branch information
pavelsavara committed Jan 13, 2022
1 parent a13ee96 commit 01cdd1d
Show file tree
Hide file tree
Showing 12 changed files with 63 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ async function createRuntime() {
return createDotnetRuntime({
configSrc: "./mono-config.json",
disableDotnet6Compatibility: true,
scriptDirectory: "/",
locateFile: (path, prefix) => {
return '/' + path;
},
instantiateWasm: async (imports, successCallback) => {
try {
const arrayBufferResult = await WebAssembly.instantiate(arrayBuffer, imports);
Expand Down
1 change: 0 additions & 1 deletion src/mono/sample/wasm/browser-webpack/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ async function dotnetMeaning() {
try {
const { BINDING } = await createDotnetRuntime({
configSrc: "./mono-config.json",
scriptDirectory: "./",
});
const meaningFunction = BINDING.bind_static_method("[Wasm.Browser.WebPack.Sample] Sample.Test:Main");
return meaningFunction();
Expand Down
9 changes: 0 additions & 9 deletions src/mono/sample/wasm/console-node-es6/main.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import { createRequire } from 'module';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import createDotnetRuntime from './dotnet.js'

const { MONO } = await createDotnetRuntime(() => ({
imports: {
//TODO internalize into dotnet.js if possible
require: createRequire(import.meta.url)
},
//TODO internalize into dotnet.js if possible
scriptDirectory: dirname(fileURLToPath(import.meta.url)) + '/',
disableDotnet6Compatibility: true,
configSrc: "./mono-config.json",
}));
Expand Down
9 changes: 0 additions & 9 deletions src/mono/sample/wasm/console-node-ts/main.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import { createRequire } from 'module';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import createDotnetRuntime from '@microsoft/dotnet-runtime'

const { MONO } = await createDotnetRuntime(() => ({
imports: {
//TODO internalize into dotnet.js if possible
require: createRequire(import.meta.url)
},
//TODO internalize into dotnet.js if possible
scriptDirectory: dirname(fileURLToPath(import.meta.url)) + '/',
disableDotnet6Compatibility: true,
configSrc: "./mono-config.json",
}));
Expand Down
14 changes: 5 additions & 9 deletions src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,17 @@
const DotnetSupportLib = {
$DOTNET: {},
// these lines will be placed early on emscripten runtime creation, passing import and export objects into __dotnet_runtime IFFE
// we replace implementation of readAsync and fetch
// replacement of require is there for consistency with ES6 code
$DOTNET__postset: `
let __dotnet_replacements = {scriptDirectory, readAsync, fetch: globalThis.fetch, require};
let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require};
let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports(
{ isES6:false, isGlobal:ENVIRONMENT_IS_GLOBAL, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_ },
{ isESM:false, isGlobal:ENVIRONMENT_IS_GLOBAL, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, requirePromise:Promise.resolve(require)},
{ mono:MONO, binding:BINDING, internal:INTERNAL, module:Module },
__dotnet_replacements);
// here we replace things which are not exposed in another way
scriptDirectory = __dotnet_replacements.scriptDirectory;
readAsync = __dotnet_replacements.readAsync;
var fetch = __dotnet_replacements.fetch;
if (ENVIRONMENT_IS_NODE) {
__dirname = __dotnet_replacements.scriptDirectory;
require = __dotnet_replacements.require;
}
require = __dotnet_replacements.requireOut;
`,
};

Expand Down
1 change: 0 additions & 1 deletion src/mono/wasm/runtime/dotnet.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ declare type DotnetModuleConfig = {
disableDotnet6Compatibility?: boolean;
config?: MonoConfig | MonoConfigError;
configSrc?: string;
scriptDirectory?: string;
onConfigLoaded?: (config: MonoConfig) => Promise<void>;
onDotnetReady?: () => void;
imports?: DotnetModuleConfigImports;
Expand Down
39 changes: 24 additions & 15 deletions src/mono/wasm/runtime/es6/dotnet.es6.lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,23 @@
const DotnetSupportLib = {
$DOTNET: {},
// this line will be placed early on emscripten runtime creation, passing import and export objects into __dotnet_runtime IFFE
// Emscripten uses require function for nodeJS even in ES6 module. We need https://nodejs.org/api/module.html#modulecreaterequirefilename
// We use dynamic import because there is no "module" module in the browser.
// This is async init of it, note it would become available only after first tick.
// Also fix of scriptDirectory would be delayed
// Emscripten's getBinaryPromise is not async for NodeJs, but we would like to have it async, so we replace it.
// We also replace implementation of readAsync and fetch
$DOTNET__postset: `
let __dotnet_replacements = {scriptDirectory, readAsync, fetch: globalThis.fetch, require};
let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports(
{ isES6:true, isGlobal:false, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_ },
{ mono:MONO, binding:BINDING, internal:INTERNAL, module:Module },
__dotnet_replacements);
// here we replace things which are not exposed in another way
scriptDirectory = __dotnet_replacements.scriptDirectory;
readAsync = __dotnet_replacements.readAsync;
var fetch = __dotnet_replacements.fetch;
// here we replace things which are broken on NodeJS for ES6
let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require};
if (ENVIRONMENT_IS_NODE) {
__dirname = __dotnet_replacements.scriptDirectory;
require = __dotnet_replacements.require;
__dotnet_replacements.requirePromise = import('module').then(mod => {
const require = mod.createRequire(import.meta.url);
const path = require('path');
const url = require('url');
__dotnet_replacements.require = require;
__dirname = scriptDirectory = path.dirname(url.fileURLToPath(import.meta.url)) + '/';
return require;
});
getBinaryPromise = async () => {
if (!wasmBinary) {
try {
Expand All @@ -46,7 +47,15 @@ if (ENVIRONMENT_IS_NODE) {
}
return getBinary(wasmBinaryFile);
}
}`,
}
let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports(
{ isESM:true, isGlobal:false, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, requirePromise:__dotnet_replacements.requirePromise },
{ mono:MONO, binding:BINDING, internal:INTERNAL, module:Module },
__dotnet_replacements);
readAsync = __dotnet_replacements.readAsync;
var fetch = __dotnet_replacements.fetch;
require = __dotnet_replacements.requireOut;
`,
};

// the methods would be visible to EMCC linker
Expand Down
25 changes: 11 additions & 14 deletions src/mono/wasm/runtime/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ let exportedAPI: DotnetPublicAPI;
// it exports methods to global objects MONO, BINDING and Module in backward compatible way
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
function initializeImportsAndExports(
imports: { isES6: boolean, isGlobal: boolean, isNode: boolean, isShell: boolean, isWeb: boolean, locateFile: Function, quit_: Function },
imports: { isESM: boolean, isGlobal: boolean, isNode: boolean, isShell: boolean, isWeb: boolean, locateFile: Function, quit_: Function, requirePromise: Promise<Function> },
exports: { mono: any, binding: any, internal: any, module: any },
replacements: { scriptDirectory: any, fetch: any, readAsync: any, require: any },
replacements: { fetch: any, readAsync: any, require: any, requireOut: any },
): DotnetPublicAPI {
const module = exports.module as DotnetModule;
const globalThisAny = globalThis as any;
Expand Down Expand Up @@ -169,15 +169,15 @@ function initializeImportsAndExports(
}
module.imports = module.imports || <DotnetModuleConfigImports>{};
if (!module.imports.require) {
const originalRequire = replacements.require;
module.imports.require = (name) => {
const resolve = (<any>module.imports)[name];
if (!resolve && originalRequire) {
return originalRequire(name);
const resolved = (<any>module.imports)[name];
if (resolved) {
return resolved;
}
if (!resolve)
throw new Error(`Please provide Module.imports.${name} or Module.imports.require`);
return resolve;
if (replacements.require) {
return replacements.require(name);
}
throw new Error(`Please provide Module.imports.${name} or Module.imports.require`);
};
}

Expand All @@ -187,15 +187,12 @@ function initializeImportsAndExports(
else {
runtimeHelpers.fetch = fetch_like;
}
if (module.scriptDirectory) {
replacements.scriptDirectory = module.scriptDirectory;
}
replacements.fetch = runtimeHelpers.fetch;
replacements.readAsync = readAsync_like;
replacements.require = module.imports.require;
replacements.requireOut = module.imports.require;

if (typeof module.disableDotnet6Compatibility === "undefined") {
module.disableDotnet6Compatibility = imports.isES6;
module.disableDotnet6Compatibility = imports.isESM;
}
// here we expose objects global namespace for tests and backward compatibility
if (imports.isGlobal || !module.disableDotnet6Compatibility) {
Expand Down
8 changes: 5 additions & 3 deletions src/mono/wasm/runtime/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,30 @@ export let BINDING: any;
export let INTERNAL: any;

// these are imported and re-exported from emscripten internals
export let ENVIRONMENT_IS_GLOBAL: boolean;
export let ENVIRONMENT_IS_ESM: boolean;
export let ENVIRONMENT_IS_NODE: boolean;
export let ENVIRONMENT_IS_SHELL: boolean;
export let ENVIRONMENT_IS_WEB: boolean;
export let locateFile: Function;
export let quit: Function;
export let requirePromise: Promise<Function>;

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function setImportsAndExports(
imports: { isGlobal: boolean, isNode: boolean, isShell: boolean, isWeb: boolean, locateFile: Function, quit_: Function },
imports: { isESM: boolean, isNode: boolean, isShell: boolean, isWeb: boolean, locateFile: Function, quit_: Function, requirePromise: Promise<Function> },
exports: { mono: any, binding: any, internal: any, module: any },
): void {
MONO = exports.mono;
BINDING = exports.binding;
INTERNAL = exports.internal;
Module = exports.module;
ENVIRONMENT_IS_GLOBAL = imports.isGlobal;
ENVIRONMENT_IS_ESM = imports.isESM;
ENVIRONMENT_IS_NODE = imports.isNode;
ENVIRONMENT_IS_SHELL = imports.isShell;
ENVIRONMENT_IS_WEB = imports.isWeb;
locateFile = imports.locateFile;
quit = imports.quit_;
requirePromise = imports.requirePromise;
}

let monoConfig: MonoConfig;
Expand Down
12 changes: 9 additions & 3 deletions src/mono/wasm/runtime/polyfills.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { ENVIRONMENT_IS_NODE, Module } from "./imports";
import { ENVIRONMENT_IS_NODE, Module, requirePromise } from "./imports";

let node_fs: any | undefined = undefined;
let node_url: any | undefined = undefined;

export async function fetch_like(url: string): Promise<Response> {
try {
if (typeof (globalThis.fetch) === "function") {
return globalThis.fetch(url, { credentials: "same-origin" });
}
else if (ENVIRONMENT_IS_NODE) {
const node_fs = Module.imports!.require!("fs");
const node_url = Module.imports!.require!("url");
if (!node_fs) {
const node_require = await requirePromise;
node_url = node_require("url");
node_fs = node_require("fs");
}
if (url.startsWith("file://")) {
url = node_url.fileURLToPath(url);
}
Expand Down
7 changes: 6 additions & 1 deletion src/mono/wasm/runtime/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

import { AllAssetEntryTypes, assert, AssetEntry, CharPtrNull, DotnetModule, GlobalizationMode, MonoConfig, MonoConfigError, wasm_type_symbol } from "./types";
import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, locateFile, Module, MONO, runtimeHelpers } from "./imports";
import { ENVIRONMENT_IS_ESM, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, locateFile, Module, MONO, requirePromise, runtimeHelpers } from "./imports";
import cwraps from "./cwraps";
import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug";
import { mono_wasm_globalization_init, mono_wasm_load_icu_data } from "./icu";
Expand Down Expand Up @@ -90,6 +90,11 @@ async function mono_wasm_pre_init(): Promise<void> {

Module.addRunDependency("mono_wasm_pre_init");

// wait for locateFile setup on NodeJs
if (ENVIRONMENT_IS_NODE && ENVIRONMENT_IS_ESM) {
await requirePromise;
}

if (moduleExt.configSrc) {
try {
// sets MONO.config implicitly
Expand Down
1 change: 0 additions & 1 deletion src/mono/wasm/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ export type DotnetModuleConfig = {

config?: MonoConfig | MonoConfigError,
configSrc?: string,
scriptDirectory?: string,
onConfigLoaded?: (config: MonoConfig) => Promise<void>;
onDotnetReady?: () => void;

Expand Down

0 comments on commit 01cdd1d

Please sign in to comment.