Skip to content

Commit

Permalink
[js/web] make ort-web export compatible with webpack
Browse files Browse the repository at this point in the history
  • Loading branch information
fs-eire committed Sep 23, 2024
1 parent b636b27 commit 5371650
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 15 deletions.
35 changes: 30 additions & 5 deletions js/web/lib/build-def.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ interface BuildDefinitions {
* defines whether to disable proxy feature in WebAssembly backend in the build.
*/
readonly DISABLE_WASM_PROXY: boolean;
/**
* defines whether to disable training APIs in WebAssembly backend.
*/
readonly DISABLE_TRAINING: boolean;
/**
* defines whether to disable dynamic importing WASM module in the build.
*/
Expand All @@ -48,9 +44,38 @@ interface BuildDefinitions {
/**
* placeholder for the import.meta.url in ESM. in CJS, this is undefined.
*/
readonly ESM_IMPORT_META_URL: string|undefined;
readonly ESM_IMPORT_META_URL: string | undefined;

// #endregion

/**
* placeholder for the bundle filename.
*
* This is used for bundler compatibility fix when using Webpack with `import.meta.url` inside ESM module.
*
* The default behavior of some bundlers (eg. Webpack) is to rewrite `import.meta.url` to the file local path at
* compile time. This behavior will break the following code:
* ```js
* new Worker(new URL(import.meta.url), { type: 'module' });
* ```
*
* This is because the `import.meta.url` will be rewritten to a local path, so the line above will be equivalent to:
* ```js
* new Worker(new URL('file:///path/to/your/file.js'), { type: 'module' });
* ```
*
* This will cause the browser fails to load the worker script.
*
* To fix this, we need to align with how the bundlers deal with `import.meta.url`:
* ```js
* new Worker(new URL('path-to-bundle.mjs', import.meta.url), { type: 'module' });
* ```
*
* This will make the browser load the worker script correctly.
*
* Since we have multiple bundle outputs, we need to define this placeholder in the build definitions.
*/
readonly BUNDLE_FILENAME: string;
}

declare const BUILD_DEFS: BuildDefinitions;
11 changes: 11 additions & 0 deletions js/web/lib/wasm/proxy-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ export const initializeWebAssemblyAndOrtRuntime = async (): Promise<void> => {
proxyWorker.onmessage = onProxyWorkerMessage;
initWasmCallbacks = [resolve, reject];
const message: OrtWasmMessage = { type: 'init-wasm', in: env };
if (BUILD_DEFS.IS_ESM && !message.in!.wasm.wasmPaths && BUILD_DEFS.ESM_IMPORT_META_URL?.startsWith('file:')) {
// if `import.meta.url` is a file URL, it means it is overwriten by the bundler. in this case, unless the
// overrided wasm path is set, we need to use the bundler preferred URL format:
// new URL('filename', import.meta.url)
// so that the bundler can handle the file using corresponding loaders.
message.in!.wasm.wasmPaths = {
wasm: !BUILD_DEFS.DISABLE_JSEP
? new URL('ort-wasm-simd-threaded.jsep.wasm', BUILD_DEFS.ESM_IMPORT_META_URL).href
: new URL('ort-wasm-simd-threaded.wasm', BUILD_DEFS.ESM_IMPORT_META_URL).href,
};
}
proxyWorker.postMessage(message);
temporaryObjectUrl = objectUrl;
} catch (e) {
Expand Down
21 changes: 15 additions & 6 deletions js/web/lib/wasm/wasm-utils-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,24 @@ export const scriptSrc =
// if Nodejs, return undefined
isNode
? undefined
: // if It's ESM, use import.meta.url
(BUILD_DEFS.ESM_IMPORT_META_URL ??
// use `document.currentScript.src` if available
(typeof document !== 'undefined'
: BUILD_DEFS.IS_ESM // if It's ESM, use import.meta.url
? // For ESM, if the import.meta.url is a file URL, this usually means the bundler rewrites `import.meta.url` to
// the file path at compile time. In this case, this file path cannot be used to determine the runtime URL.
//
// We need to use the URL constructor like this:
// ```js
// new URL('actual-bundle-name.js', import.meta.url).href
// ```
// So that bundler can preprocess the URL correctly.
BUILD_DEFS.ESM_IMPORT_META_URL?.startsWith('file:')
? new URL(BUILD_DEFS.BUNDLE_FILENAME, BUILD_DEFS.ESM_IMPORT_META_URL).href
: BUILD_DEFS.ESM_IMPORT_META_URL
: typeof document !== 'undefined'
? (document.currentScript as HTMLScriptElement)?.src
: // use `self.location.href` if available
typeof self !== 'undefined'
? self.location?.href
: undefined));
: undefined;

/**
* The origin of the current location.
Expand Down Expand Up @@ -117,7 +126,7 @@ export const importProxyWorker = async (): Promise<[undefined | string, Worker]>
}

// If the script source is from the same origin, we can use the embedded proxy module directly.
if (isSameOrigin(scriptSrc)) {
if (BUILD_DEFS.DISABLE_DYNAMIC_IMPORT || isSameOrigin(scriptSrc)) {
return [undefined, createProxyWorker!()];
}

Expand Down
2 changes: 1 addition & 1 deletion js/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
},
"./wasm": {
"node": null,
"import": "./dist/ort.wasm.min.mjs",
"import": "./dist/ort.wasm.bundle.min.mjs",
"require": "./dist/ort.wasm.min.js",
"types": "./types.d.ts"
},
Expand Down
50 changes: 47 additions & 3 deletions js/web/script/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,35 @@ async function minifyWasmModuleJsForBrowser(filepath: string): Promise<string> {
const TIME_TAG = `BUILD:terserMinify:${filepath}`;
console.time(TIME_TAG);

const contents = await fs.readFile(filepath, { encoding: 'utf-8' });
let contents = await fs.readFile(filepath, { encoding: 'utf-8' });

// Replace the following line to create worker:
// ```
// new Worker(new URL(import.meta.url), ...
// ```
// with:
// ```
// new Worker(import.meta.url.startsWith('file:')
// ? new URL(BUILD_DEFS.BUNDLE_FILENAME, import.meta.url)
// : new URL(import.meta.url), ...
// ```
//
// NOTE: this is a workaround for some bundlers that does not support runtime import.meta.url.
// TODO: in emscripten 3.1.61+, need to update this code.

// First, check if there is exactly one occurrence of "new Worker(new URL(import.meta.url)".
const matches = [...contents.matchAll(/new Worker\(new URL\(import\.meta\.url\),/g)];
if (matches.length !== 1) {
throw new Error(
`Unexpected number of matches for "new Worker(new URL(import.meta.url)" in "${filepath}": ${matches.length}.`,
);
}

// Replace the only occurrence.
contents = contents.replace(
/new Worker\(new URL\(import\.meta\.url\),/,
`new Worker(import.meta.url.startsWith('file:')?new URL(BUILD_DEFS.BUNDLE_FILENAME, import.meta.url):new URL(import.meta.url),`,
);

// Find the first and the only occurrence of minified function implementation of "_emscripten_thread_set_strongref":
// ```js
Expand Down Expand Up @@ -265,8 +293,11 @@ async function buildOrt({
const external = isNode
? ['onnxruntime-common']
: ['node:fs/promises', 'node:fs', 'node:os', 'module', 'worker_threads'];
const bundleFilename = `${outputName}${isProduction ? '.min' : ''}.${format === 'esm' ? 'mjs' : 'js'}`;
const plugins: esbuild.Plugin[] = [];
const defineOverride: Record<string, string> = {};
const defineOverride: Record<string, string> = {
'BUILD_DEFS.BUNDLE_FILENAME': JSON.stringify(bundleFilename),
};
if (!isNode) {
defineOverride.process = 'undefined';
defineOverride['globalThis.process'] = 'undefined';
Expand All @@ -285,7 +316,7 @@ async function buildOrt({

await buildBundle({
entryPoints: ['web/lib/index.ts'],
outfile: `web/dist/${outputName}${isProduction ? '.min' : ''}.${format === 'esm' ? 'mjs' : 'js'}`,
outfile: `web/dist/${bundleFilename}`,
platform,
format,
globalName: 'ort',
Expand Down Expand Up @@ -619,6 +650,19 @@ async function main() {
outputName: 'ort.wasm',
define: { ...DEFAULT_DEFINE, 'BUILD_DEFS.DISABLE_JSEP': 'true', 'BUILD_DEFS.DISABLE_WEBGL': 'true' },
});
// ort.wasm.bundle.min.mjs
await buildOrt({
isProduction: true,
outputName: 'ort.wasm.bundle',
format: 'esm',
define: {
...DEFAULT_DEFINE,
'BUILD_DEFS.DISABLE_JSEP': 'true',
'BUILD_DEFS.DISABLE_WEBGL': 'true',
'BUILD_DEFS.DISABLE_DYNAMIC_IMPORT': 'true',
},
});

// ort.webgl[.min].[m]js
await addAllWebBuildTasks({
outputName: 'ort.webgl',
Expand Down

0 comments on commit 5371650

Please sign in to comment.