taglib-wasm
Version:
TagLib-Wasm is the universal tagging library for TypeScript/JavaScript platforms: Browsers, Node.js, Deno, Bun, Cloudflare Workers, and Electron apps
123 lines (122 loc) • 4.09 kB
JavaScript
import { isDeno, supportsExnref } from "../detector.js";
import { ModuleLoadError } from "./types.js";
import { errorMessage } from "../../errors/classes.js";
import { fileUrlToPath } from "../../utils/path.js";
import { getNodeFsSync } from "../../utils/node-fs.js";
function isWindows() {
return isDeno() ? Deno.build.os === "windows" : globalThis.process ? globalThis.process.platform === "win32" : false;
}
function getPreopens() {
if (!isWindows()) return { "/": "/" };
const statSync = isDeno() ? Deno.statSync : getNodeFsSync()?.statSync;
if (!statSync) {
console.warn(
"[taglib-wasm] Windows drive detection unavailable: node:fs could not be loaded. Only C:\\ is registered as a WASI preopen."
);
return { "/C": "C:\\" };
}
const preopens = {};
for (const letter of "CDEFGHIJKLMNOPQRSTUVWXYZAB") {
const root = `${letter}:\\`;
try {
statSync(root);
preopens[`/${letter}`] = root;
} catch {
}
}
return Object.keys(preopens).length > 0 ? preopens : { "/C": "C:\\" };
}
function resolveWasmPath(relativePath) {
const url = new URL(relativePath, import.meta.url);
return url.protocol === "file:" ? fileUrlToPath(url) : url.href;
}
async function loadModule(wasmType, runtime, options) {
if (wasmType === "wasi") {
return await loadWasiModuleWithFallback(runtime, options);
} else {
return {
module: await loadEmscriptenModule(options),
actualWasmType: "emscripten"
};
}
}
async function loadWasiModuleWithFallback(runtime, options) {
const defaultWasmPath = resolveWasmPath("../../../taglib-wasi.wasm");
try {
const { loadWasiHost } = await import("../wasi-host-loader.js");
const wasiModule = await loadWasiHost({
wasmPath: options.wasmUrl || defaultWasmPath,
preopens: getPreopens()
});
return { module: wasiModule, actualWasmType: "wasi" };
} catch (hostError) {
if (options.forceWasmType === "wasi") {
throw new ModuleLoadError(
`WASI backend failed to load: ${errorMessage(hostError)}. forceWasmType is "wasi", so the Emscripten fallback was not attempted`,
"wasi",
hostError
);
}
if (runtime.environment === "node-wasi" && !supportsExnref()) {
const g = globalThis;
const nodeVersion = g.process?.versions?.node ?? "";
console.warn(
`[taglib-wasm] WASI unavailable: Node.js ${nodeVersion} requires --experimental-wasm-exnref. Falling back to Emscripten. Run with: node --experimental-wasm-exnref your-script.js`
);
} else if (options.debug) {
console.warn(`[UnifiedLoader] WASI host failed:`, hostError);
}
}
if (options.debug) {
console.warn(`[UnifiedLoader] WASI loader failed, using Emscripten`);
}
return {
module: await loadEmscriptenModule(options),
actualWasmType: "emscripten"
};
}
async function loadEmscriptenModule(options) {
try {
let createModule;
try {
const module2 = await import("../../../taglib-wrapper.js");
createModule = module2.default;
} catch {
try {
const module2 = await import("../../../taglib-wrapper.js");
createModule = module2.default;
} catch {
throw new ModuleLoadError(
"Could not load Emscripten module from build or dist",
"emscripten"
);
}
}
const moduleConfig = {};
if (options.wasmBinary) {
moduleConfig.wasmBinary = options.wasmBinary;
}
if (options.wasmUrl) {
moduleConfig.locateFile = (path) => {
return path.endsWith(".wasm") ? options.wasmUrl : path;
};
} else if (!options.wasmBinary) {
const wasmUrl = new URL("../../../taglib-web.wasm",
import.meta.url
);
moduleConfig.locateFile = (path) => path.endsWith(".wasm") ? wasmUrl.href : path;
}
const module = await createModule(moduleConfig);
return module;
} catch (error) {
throw new ModuleLoadError(
`Failed to load Emscripten module: ${errorMessage(error)}`,
"emscripten",
error
);
}
}
export {
getPreopens,
loadModule
};