diff --git a/.gitignore b/.gitignore index cb9d10c0d..f550832ad 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__ node_modules .cache .DS_STORE +deno.lock # Do not track build artifacts/generated files /dist diff --git a/package.json b/package.json index 8fd8857e6..3bea70483 100644 --- a/package.json +++ b/package.json @@ -82,13 +82,6 @@ "README.md", "LICENSE" ], - "browser": { - "fs": false, - "path": false, - "url": false, - "sharp": false, - "onnxruntime-node": false - }, "publishConfig": { "access": "public" }, diff --git a/src/env.js b/src/env.js index 5e81af486..fd3924213 100644 --- a/src/env.js +++ b/src/env.js @@ -22,9 +22,9 @@ * @module env */ -import fs from 'fs'; -import path from 'path'; -import url from 'url'; +import fs from 'node:fs'; +import path from 'node:path'; +import url from 'node:url'; const VERSION = '3.5.2'; @@ -40,6 +40,10 @@ const IS_NODE_ENV = IS_PROCESS_AVAILABLE && process?.release?.name === 'node'; const IS_FS_AVAILABLE = !isEmpty(fs); const IS_PATH_AVAILABLE = !isEmpty(path); +// Runtime detection +const IS_DENO_RUNTIME = typeof globalThis.Deno !== 'undefined'; +const IS_BUN_RUNTIME = typeof globalThis.Bun !== 'undefined'; + /** * A read-only object containing information about the APIs available in the current environment. */ @@ -62,7 +66,7 @@ export const apis = Object.freeze({ /** Whether the Node.js process API is available */ IS_PROCESS_AVAILABLE, - /** Whether we are running in a Node.js environment */ + /** Whether we are running in a Node.js-like environment (node, deno, bun) */ IS_NODE_ENV, /** Whether the filesystem API is available */ @@ -143,7 +147,7 @@ export const env = { useFS: IS_FS_AVAILABLE, /////////////////// Cache settings /////////////////// - useBrowserCache: IS_WEB_CACHE_AVAILABLE, + useBrowserCache: IS_WEB_CACHE_AVAILABLE && !IS_DENO_RUNTIME, useFSCache: IS_FS_AVAILABLE, cacheDir: DEFAULT_CACHE_DIR, diff --git a/src/utils/audio.js b/src/utils/audio.js index 7d915c24d..23af5dc8a 100644 --- a/src/utils/audio.js +++ b/src/utils/audio.js @@ -15,9 +15,8 @@ import { calculateReflectOffset, saveBlob, } from './core.js'; import { apis } from '../env.js'; -import fs from 'fs'; import { Tensor, matmul } from './tensor.js'; - +import fs from 'node:fs'; /** * Helper function to read audio from a path/URL. diff --git a/src/utils/hub.js b/src/utils/hub.js index 5b3bb7520..fa700a898 100755 --- a/src/utils/hub.js +++ b/src/utils/hub.js @@ -5,8 +5,8 @@ * @module utils/hub */ -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import { apis, env } from '../env.js'; import { dispatchCallback } from './core.js'; diff --git a/tests/test_utils.js b/tests/test_utils.js index c42c5f201..3311b9aeb 100644 --- a/tests/test_utils.js +++ b/tests/test_utils.js @@ -1,6 +1,6 @@ -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; export async function loadAudio(url) { // NOTE: Since the Web Audio API is not available in Node.js, we will need to use the `wavefile` library to obtain the raw audio data. diff --git a/tests/utils/hub.test.js b/tests/utils/hub.test.js index f819efc35..00fd5f196 100644 --- a/tests/utils/hub.test.js +++ b/tests/utils/hub.test.js @@ -1,7 +1,7 @@ import { AutoModel, PreTrainedModel } from "../../src/models.js"; import { MAX_TEST_EXECUTION_TIME, DEFAULT_MODEL_OPTIONS } from "../init.js"; -import fs from "fs"; +import fs from "node:fs"; // TODO: Set cache folder to a temp directory diff --git a/webpack.config.js b/webpack.config.js index 09611f1e4..53b5ccd9d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,10 +1,35 @@ +import { fileURLToPath } from "node:url"; +import path from "node:path"; +import fs from "node:fs"; +import webpack from "webpack"; import TerserPlugin from "terser-webpack-plugin"; -import { fileURLToPath } from "url"; -import path from "path"; -import fs from "fs"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); +/** + * Plugin to strip the "node:" prefix from module requests. + * + * This is necessary to ensure both web and node builds work correctly, + * otherwise we would get an error like: + * ``` + * Module build failed: UnhandledSchemeError: Reading from "node:path" is not handled by plugins (Unhandled scheme). + * Webpack supports "data:" and "file:" URIs by default. + * You may need an additional plugin to handle "node:" URIs. + * ``` + * + * NOTE: We then do not need to use the `node:` prefix in the resolve.alias configuration. + */ +class StripNodePrefixPlugin extends webpack.NormalModuleReplacementPlugin { + constructor() { + super( + /^node:(.+)$/, + resource => { + resource.request = resource.request.replace(/^node:/, ''); + } + ); + } +} + /** * Plugin to post-process build files. Required to solve certain issues with ESM module output. * See https://github.com/webpack/webpack/issues/17121 for more information. @@ -55,7 +80,6 @@ function buildConfig({ plugins = [], } = {}) { const outputModule = type === "module"; - const alias = Object.fromEntries( ignoreModules.map((module) => [module, false]), ); @@ -137,13 +161,15 @@ const NODE_EXTERNAL_MODULES = [ "onnxruntime-common", "onnxruntime-node", "sharp", - "fs", - "path", - "url", + "node:fs", + "node:path", + "node:url", ]; -// Do not bundle onnxruntime-node when packaging for the web. -const WEB_IGNORE_MODULES = ["onnxruntime-node"]; +// Do not bundle node-only packages when bundling for the web. +// NOTE: We can exclude the "node:" prefix for built-in modules here, +// since we apply the `StripNodePrefixPlugin` to strip it. +const WEB_IGNORE_MODULES = ["onnxruntime-node", "sharp", "fs", "path", "url"]; // Do not bundle the following modules with webpack (mark as external) const WEB_EXTERNAL_MODULES = [ @@ -157,12 +183,19 @@ const WEB_BUILD = buildConfig({ type: "module", ignoreModules: WEB_IGNORE_MODULES, externalModules: WEB_EXTERNAL_MODULES, + plugins: [ + new StripNodePrefixPlugin() + ] }); // Web-only build, bundled with onnxruntime-web const BUNDLE_BUILD = buildConfig({ type: "module", - plugins: [new PostBuildPlugin()], + ignoreModules: WEB_IGNORE_MODULES, + plugins: [ + new StripNodePrefixPlugin(), + new PostBuildPlugin(), + ], }); // Node-compatible builds