microsoft / onnxruntime-web-demo

demos to show the capabilities of ONNX Runtime Web
MIT License
172 stars 41 forks source link

Web Worker Incompatibility with Vite Bundler #15

Open Interpause opened 2 years ago

Interpause commented 2 years ago

When using Vite to bundle onnxruntime-web into my project, I face the following error when attempting to import it.

✘ [ERROR] Could not resolve "worker-loader?inline=no-fallback!./proxy-worker/main"

    .yarn/cache/onnxruntime-web-npm-1.11.0-a6a06c5230-b03ff8acb3.zip/node_modules/onnxruntime-web/lib/wasm/proxy-wrapper.js:123:34:
      123 │ ... proxyWorker = require('worker-loader?inline=no-fallback!./proxy-worker/main').default();
          ╵                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  You can mark the path "worker-loader?inline=no-fallback!./proxy-worker/main" as external to
  exclude it from the bundle, which will remove this error. You can also surround this "require"
  call with a try/catch block to handle this failure at run-time instead of bundle-time.

error when starting dev server:
Error: Build failed with 1 error:
.yarn/cache/onnxruntime-web-npm-1.11.0-a6a06c5230-b03ff8acb3.zip/node_modules/onnxruntime-web/lib/wasm/proxy-wrapper.js:123:34: ERROR: Could not resolve "worker-loader?inline=no-fallback!./proxy-worker/main"
    at failureErrorWithLog (/home/user/Documents/Projects/onnx-test/.yarn/unplugged/esbuild-npm-0.14.28-c2ae07b619/node_modules/esbuild/lib/main.js:1599:15)
    at /home/user/Documents/Projects/onnx-test/.yarn/unplugged/esbuild-npm-0.14.28-c2ae07b619/node_modules/esbuild/lib/main.js:1245:28
    at runOnEndCallbacks (/home/user/Documents/Projects/onnx-test/.yarn/unplugged/esbuild-npm-0.14.28-c2ae07b619/node_modules/esbuild/lib/main.js:1030:63)
    at buildResponseToResult (/home/user/Documents/Projects/onnx-test/.yarn/unplugged/esbuild-npm-0.14.28-c2ae07b619/node_modules/esbuild/lib/main.js:1243:7)
    at /home/user/Documents/Projects/onnx-test/.yarn/unplugged/esbuild-npm-0.14.28-c2ae07b619/node_modules/esbuild/lib/main.js:1352:14
    at /home/user/Documents/Projects/onnx-test/.yarn/unplugged/esbuild-npm-0.14.28-c2ae07b619/node_modules/esbuild/lib/main.js:662:9
    at handleIncomingPacket (/home/user/Documents/Projects/onnx-test/.yarn/unplugged/esbuild-npm-0.14.28-c2ae07b619/node_modules/esbuild/lib/main.js:759:9)
    at Socket.readFromStdout (/home/user/Documents/Projects/onnx-test/.yarn/unplugged/esbuild-npm-0.14.28-c2ae07b619/node_modules/esbuild/lib/main.js:629:7)
    at Socket.emit (node:events:526:28)
    at addChunk (node:internal/streams/readable:315:12)

From what I can tell, worker-loader seems to be a Webpack-specific method for loading web workers. Would it be possible to fix this? If not, I think Webpack should be added as a peer dependency to make it clearer that specifically Webpack must be used. Thanks.

sonovice commented 2 years ago

Additionally, the current solution using worker-loader is deprecated in webpack v5. It is now recommended to use Workers directly:

new Worker(new URL('./worker.js', import.meta.url));
fs-eire commented 2 years ago

I understand the issue with "worker-loader", but it resolves the big problem of file not found after deployment. With worker-loader the actual content is embedded so we no longer worry about various file-not-found issues caused by different reasons.

The problem is not about worker-loader, it's about why Vite runs into the source code of onnxruntime-web. Vite actually run into scan file 'onnxruntime-web/lib/wasm/proxy-wrapper.js'. This is not what I expected; my expectation is the bundler picks up the single file 'dist/ort-web.min.js' directly, skipping the building phrase and ignores anything under folder 'onnxruntime-web/lib'. this should be picked up by the "browser" field.

xraywu commented 2 years ago

@Interpause kindly have you found a workaround using Vite to bundle? Many thanks.

Interpause commented 2 years ago

I ended up adding onnxruntime-web via a script tag (which adds it to the global namespace). Then, I used the npm onnxruntime-web package just for typing. See https://github.com/Interpause/onnx-web-test/commit/5dcb31af219f8bd5b906ce6d56778e5827484892 for what exactly I did. So technically I did not use Vite to bundle it... Also it means dependency on being able to reach the jsdelivr CDN.

jordanparker6 commented 2 years ago

Any update as to whether support for vitejs bundling is on the roadmap?

I am using the workaround above but I am getting the following error.

TypeError: cannot resolve operator 'Erf' with opsets: ai.onnx v11

Erf operator seems to be present post v13 and the CDN is only delivering v11.

fs-eire commented 2 years ago

TypeError: cannot resolve operator 'Erf' with opsets: ai.onnx v11

this is no longer the bundling issue. the error message indicates that operator Erf v13 is not supported in WebGL backend. Switching to web assembly backend should be able to resolve this issue.

K024 commented 1 year ago

I've written a simple plugin for ViteJS which directly imports dist/ort-web.min.js as @fs-eire expected. Please have a try @Interpause

CODE

vite.config.ts ```typescript import { defineConfig, Plugin } from "vite" import fs from "node:fs/promises" export type OnnxruntimeWebPluginOptions = { /** * @default "onnxruntime-web/dist/ort-web.min.js" */ importSource?: string /** * @default "node_modules/onnxruntime-web/dist" */ ortDistDir?: string } function proxyScript(importSource: string, wasmPaths: string) { return ` import * as ort from "${importSource}"; ort.env.wasm.wasmPaths = import.meta.env.BASE_URL + "${wasmPaths}/"; export default ort; export * from "${importSource}";` } export function onnxruntimeWebPlugin(options?: OnnxruntimeWebPluginOptions): Plugin { const importSource = options?.importSource ?? "onnxruntime-web/dist/ort-web.min.js" const ortDistDir = options?.ortDistDir ?? "node_modules/onnxruntime-web/dist" const ortModuleId = "onnxruntime-web" const virtualModuleId = "\0/vite-plugin-onnxruntime-web/ort" let isDev = false let assetsDir = "assets" return { name: "vite-plugin-onnxruntime-web", enforce: "pre", configResolved(config) { isDev = config.command === "serve" assetsDir = config.build.assetsDir }, resolveId(source) { if (source === ortModuleId) return virtualModuleId }, load(id) { if (id === virtualModuleId) return proxyScript(importSource, isDev ? ortDistDir : assetsDir) }, async generateBundle() { for (const file of await fs.readdir(ortDistDir)) { if (file.endsWith(".wasm")) { this.emitFile({ type: "asset", fileName: `${assetsDir}/${file}`, source: await fs.readFile(`${ortDistDir}/${file}`), }) } } }, } } export default defineConfig({ plugins: [ onnxruntimeWebPlugin(), ] }) ```

Currently, wasm doesn't have an official way to be directly imported as an es module (MDN docs). This causes the hard parts in the plugin. Web workers are built inline in the dist files and are nothing to do with the implementation. I've tested this with vite^4.0.4 and onnxruntime-web^1.13.1, and it works correctly.


Update: a simpler way without a plugin:

ort.ts

import * as ort from "onnxruntime-web/dist/ort-web.min.js"

import wasm from "onnxruntime-web/dist/ort-wasm.wasm?url"
import wasmThreaded from "onnxruntime-web/dist/ort-wasm-threaded.wasm?url"
import wasmSimd from "onnxruntime-web/dist/ort-wasm-simd.wasm?url"
import wasmSimdThreaded from "onnxruntime-web/dist/ort-wasm-simd-threaded.wasm?url"

import modelUrl from "./model.onnx?url"

ort.env.wasm.wasmPaths = {
  "ort-wasm.wasm": wasm,
  "ort-wasm-threaded.wasm": wasmThreaded,
  "ort-wasm-simd.wasm": wasmSimd,
  "ort-wasm-simd-threaded.wasm": wasmSimdThreaded,
}

vite-env.d.ts

/// <reference types="vite/client" />

declare module "onnxruntime-web/dist/*.js" {
  export * from "onnxruntime-web"  
}
jpggvilaca commented 1 year ago

@K024 where do you put the ort.ts file?