feat: frontend support.

This commit is contained in:
2026-02-28 18:44:57 +08:00
parent d75671596c
commit f1054e6476
25 changed files with 3681 additions and 120 deletions

0
js/.gitignore vendored Normal file
View File

View File

@@ -4,12 +4,19 @@
"workspaces": {
"": {
"name": "anisette-js",
"dependencies": {
"@types/node": "^25.3.2",
},
"devDependencies": {
"typescript": "^5.4.0",
},
},
},
"packages": {
"@types/node": ["@types/node@25.3.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
}
}

View File

@@ -7,16 +7,24 @@
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
"types": "./dist/index.d.ts",
"default": "./src/browser.ts",
"node": "./src/index.ts"
}
},
"files": [
"dist",
"./src/anisette_rs.js",
"./src/anisette_rs.node.js"
],
"scripts": {
"build": "bun build src/index.ts --outfile ../dist/anisette.js --target node --format esm --minify-syntax --minify-whitespace",
"build:cjs": "bun build src/index.ts --outfile dist/anisette.cjs --target node --format cjs --minify",
"build": "tsc && cp src/anisette_rs.js dist/ && cp src/anisette_rs.node.js dist/",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.4.0"
},
"dependencies": {
"@types/node": "^25.3.2"
}
}

25
js/src/browser.ts Normal file
View File

@@ -0,0 +1,25 @@
//@ts-expect-error no types for the Emscripten module factory
import ModuleFactory from "./anisette_rs.js";
export * from './index';
export type EmscriptenModule = any;
export interface ModuleOverrides {
[key: string]: any;
}
const wasmUrl = new URL("./anisette_rs.wasm", import.meta.url).href;
export async function loadWasmModule(
moduleOverrides: ModuleOverrides = {}
): Promise<EmscriptenModule> {
return ModuleFactory({
...moduleOverrides,
locateFile: (filename: string) => {
if (filename.endsWith(".wasm")) return wasmUrl;
return filename;
},
});
}
export const loadWasm = loadWasmModule;
export const isNodeEnvironment = () => false;
export const getWasmBinaryPath = () => wasmUrl;

View File

@@ -1,7 +1,6 @@
// Public entry point — re-exports everything users need
export { Anisette } from "./anisette.js";
export { loadWasm } from "./wasm-loader.js";
export { WasmBridge } from "./wasm-bridge.js";
export { Device } from "./device.js";
export { LibraryStore } from "./library.js";
@@ -15,3 +14,12 @@ export type {
DeviceJson,
} from "./types.js";
export type { AnisetteOptions } from "./anisette.js";
export {
loadWasmModule,
loadWasmModule as loadWasm,
isNodeEnvironment,
getWasmBinaryPath,
type EmscriptenModule,
type ModuleOverrides,
} from "./wasm-loader.js";

View File

@@ -1,7 +1,94 @@
// @ts-expect-error — glue file is generated, no types available
import ModuleFactory from "../../dist/anisette_rs.node.js";
// Unified WASM loader with automatic environment detection
// Uses import.meta.url + protocol detection to support both Node.js and Browser
//
// Browser (Vite/Webpack/etc): https://... or http://... -> anisette_rs.js + .wasm
// Node.js: file://... -> anisette_rs.node.js + .wasm
// Get the base URL from import.meta.url
const MODULE_URL = new URL(import.meta.url);
const IS_NODE = MODULE_URL.protocol === "file:";
// Determine which WASM build to use based on environment
const WASM_JS_PATH = IS_NODE
? new URL("../dist/anisette_rs.node.js", MODULE_URL)
: new URL("../dist/anisette_rs.js", MODULE_URL);
const WASM_BINARY_PATH = IS_NODE
? new URL("../dist/anisette_rs.node.wasm", MODULE_URL)
: new URL("../dist/anisette_rs.wasm", MODULE_URL);
// Module overrides type (Emscripten module configuration)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function loadWasm(moduleOverrides?: Record<string, any>): Promise<any> {
return ModuleFactory({ ...moduleOverrides });
export type EmscriptenModule = any;
export interface ModuleOverrides {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
/**
* Load the Emscripten WASM module with automatic environment detection.
*
* This function automatically:
* - Detects Node.js vs Browser environment via import.meta.url protocol
* - Loads the appropriate WASM build (node or web)
* - Configures locateFile to find the .wasm binary
* - Initializes the module with optional overrides
*
* @param moduleOverrides - Optional Emscripten module configuration overrides
* @returns Initialized Emscripten module with all exports (_malloc, _free, _anisette_* etc.)
*
* @example
* ```ts
* // Browser (Vue/React/Next.js) and Node.js - same code!
* const module = await loadWasmModule();
*
* // With custom overrides
* const module = await loadWasmModule({
* print: (text: string) => console.log("WASM:", text),
* printErr: (text: string) => console.error("WASM Error:", text),
* });
* ```
*/
export async function loadWasmModule(
moduleOverrides: ModuleOverrides = {}
): Promise<EmscriptenModule> {
// Dynamic import of the appropriate Emscripten glue file
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { default: ModuleFactory } = await import(/* @vite-ignore */ WASM_JS_PATH.href) as { default: (config: ModuleOverrides) => Promise<EmscriptenModule> };
// In browser: let Emscripten use default fetch behavior
// In Node.js: provide locateFile to resolve the .wasm path
const config: ModuleOverrides = IS_NODE
? {
...moduleOverrides,
locateFile: (filename: string) => {
if (filename.endsWith(".wasm")) {
// In Node.js, return the absolute file path
return WASM_BINARY_PATH.pathname;
}
return filename;
},
}
: moduleOverrides;
return ModuleFactory(config);
}
/**
* Convenience function to check if running in Node.js environment.
* Uses the same protocol detection as the loader.
*/
export function isNodeEnvironment(): boolean {
return IS_NODE;
}
/**
* Get the resolved WASM binary path (useful for debugging).
*/
export function getWasmBinaryPath(): string {
return IS_NODE ? WASM_BINARY_PATH.pathname : WASM_BINARY_PATH.href;
}
// Re-export for backward compatibility
export { loadWasmModule as loadWasm };

View File

@@ -1,9 +1,9 @@
{
"compilerOptions": {
"target": "ES2020",
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"lib": ["ES2022", "DOM"],
"outDir": "./dist",
"declaration": true,
"declarationMap": true,