gowm 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/package.json +13 -1
- package/src/bridge-browser.js +157 -0
- package/src/browser.js +77 -0
- package/src/loader-browser.js +113 -0
- package/src/loader.js +13 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
Toutes les modifications notables de ce projet seront documentées dans ce fichier.
|
|
4
4
|
|
|
5
|
+
## [1.0.1] - 2025-05-29
|
|
6
|
+
|
|
7
|
+
### Corrigé
|
|
8
|
+
- **Compatibilité navigateur** : Résolution du problème avec les modules Node.js `fs` et `path` qui empêchaient l'utilisation dans React/Vue/navigateur
|
|
9
|
+
- Importation conditionnelle des modules Node.js uniquement en environnement serveur
|
|
10
|
+
- Ajout de points d'entrée séparés pour Node.js et navigateur dans `package.json`
|
|
11
|
+
- Création de versions navigateur dédiées (`loader-browser.js`, `bridge-browser.js`, `browser.js`)
|
|
12
|
+
|
|
13
|
+
### Ajouté
|
|
14
|
+
- Support ES modules pour les environnements navigateur
|
|
15
|
+
- Configuration `package.json` avec champs `browser`, `module`, et `exports`
|
|
16
|
+
- Documentation sur la résolution du problème de compatibilité (`BROWSER_SUPPORT.md`)
|
|
17
|
+
|
|
5
18
|
## [1.0.0] - 2025-05-28
|
|
6
19
|
|
|
7
20
|
### Ajouté
|
package/package.json
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gowm",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Go WebAssembly Manager - Simplifiez l'intégration de modules WebAssembly Go dans vos projets JavaScript",
|
|
5
5
|
"main": "src/index.js",
|
|
6
|
+
"browser": "src/browser.js",
|
|
7
|
+
"module": "src/browser.js",
|
|
6
8
|
"types": "types/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"node": "./src/index.js",
|
|
12
|
+
"browser": "./src/browser.js",
|
|
13
|
+
"import": "./src/browser.js",
|
|
14
|
+
"require": "./src/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./hooks": "./hooks/useWasm.js",
|
|
17
|
+
"./composables": "./composables/useWasm.js"
|
|
18
|
+
},
|
|
7
19
|
"files": [
|
|
8
20
|
"src/",
|
|
9
21
|
"types/",
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
export default class WasmBridge {
|
|
2
|
+
constructor(wasmModule, options = {}) {
|
|
3
|
+
this.module = wasmModule;
|
|
4
|
+
this.callbacks = new Map();
|
|
5
|
+
this.name = options.name || 'unnamed';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Appel de fonction avec gestion d'erreurs
|
|
9
|
+
call(funcName, ...args) {
|
|
10
|
+
try {
|
|
11
|
+
// Vérifier d'abord dans les exports WASM
|
|
12
|
+
if (this.module.exports && this.module.exports[funcName]) {
|
|
13
|
+
return this.module.exports[funcName](...args);
|
|
14
|
+
}
|
|
15
|
+
// Puis dans les fonctions globales JavaScript (pour Go WASM)
|
|
16
|
+
else if (globalThis[funcName] && typeof globalThis[funcName] === 'function') {
|
|
17
|
+
return globalThis[funcName](...args);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
throw new Error(`Function ${funcName} not found`);
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error(`Error calling ${funcName}:`, error);
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Appel asynchrone de fonction
|
|
29
|
+
async callAsync(funcName, ...args) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
try {
|
|
32
|
+
const result = this.call(funcName, ...args);
|
|
33
|
+
if (result instanceof Promise) {
|
|
34
|
+
result.then(resolve).catch(reject);
|
|
35
|
+
} else {
|
|
36
|
+
resolve(result);
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
reject(error);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Créer un buffer en mémoire WASM
|
|
45
|
+
createBuffer(data) {
|
|
46
|
+
let buffer;
|
|
47
|
+
let size;
|
|
48
|
+
|
|
49
|
+
if (data instanceof Float64Array) {
|
|
50
|
+
buffer = data;
|
|
51
|
+
size = data.length * 8; // 8 bytes par float64
|
|
52
|
+
} else if (data instanceof Uint8Array) {
|
|
53
|
+
buffer = data;
|
|
54
|
+
size = data.length;
|
|
55
|
+
} else if (typeof data === 'string') {
|
|
56
|
+
const encoder = new TextEncoder();
|
|
57
|
+
buffer = encoder.encode(data);
|
|
58
|
+
size = buffer.length;
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error('Unsupported data type for buffer');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Allouer de la mémoire dans WASM
|
|
64
|
+
const ptr = this.call('malloc', size);
|
|
65
|
+
if (!ptr) {
|
|
66
|
+
throw new Error('Failed to allocate memory in WASM');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Copier les données dans la mémoire WASM
|
|
70
|
+
const wasmMemory = new Uint8Array(this.module.exports.memory.buffer, ptr, size);
|
|
71
|
+
wasmMemory.set(buffer);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
ptr,
|
|
75
|
+
buffer: wasmMemory,
|
|
76
|
+
size,
|
|
77
|
+
free: () => {
|
|
78
|
+
try {
|
|
79
|
+
this.call('free', ptr);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// Ignorer les erreurs de libération
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Enregistrer un callback JavaScript appelable depuis WASM
|
|
88
|
+
registerCallback(name, callback) {
|
|
89
|
+
this.callbacks.set(name, callback);
|
|
90
|
+
globalThis[`__gowm_callback_${name}`] = callback;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Désinscrire un callback
|
|
94
|
+
unregisterCallback(name) {
|
|
95
|
+
this.callbacks.delete(name);
|
|
96
|
+
delete globalThis[`__gowm_callback_${name}`];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Obtenir la liste des fonctions disponibles
|
|
100
|
+
getAvailableFunctions() {
|
|
101
|
+
const functions = [];
|
|
102
|
+
|
|
103
|
+
// Fonctions exports WASM
|
|
104
|
+
if (this.module.exports) {
|
|
105
|
+
Object.keys(this.module.exports).forEach(key => {
|
|
106
|
+
if (typeof this.module.exports[key] === 'function') {
|
|
107
|
+
functions.push(key);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Fonctions globales (pour Go WASM)
|
|
113
|
+
Object.keys(globalThis).forEach(key => {
|
|
114
|
+
if (typeof globalThis[key] === 'function' && !key.startsWith('__gowm_')) {
|
|
115
|
+
functions.push(key);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return [...new Set(functions)].sort();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Vérifier si une fonction existe
|
|
123
|
+
hasFunction(funcName) {
|
|
124
|
+
return (
|
|
125
|
+
(this.module.exports && typeof this.module.exports[funcName] === 'function') ||
|
|
126
|
+
(typeof globalThis[funcName] === 'function')
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Obtenir les statistiques du module
|
|
131
|
+
getStats() {
|
|
132
|
+
return {
|
|
133
|
+
ready: this.module.ready,
|
|
134
|
+
functions: this.getAvailableFunctions(),
|
|
135
|
+
callbacks: Array.from(this.callbacks.keys()),
|
|
136
|
+
memoryUsage: this.module.exports.memory ? this.module.exports.memory.buffer.byteLength : 0,
|
|
137
|
+
name: this.name
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Nettoyage des ressources
|
|
142
|
+
cleanup() {
|
|
143
|
+
// Désinscrire tous les callbacks
|
|
144
|
+
for (const name of this.callbacks.keys()) {
|
|
145
|
+
this.unregisterCallback(name);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Arrêter le runtime Go si possible
|
|
149
|
+
if (this.module.go && typeof this.module.go.exit === 'function') {
|
|
150
|
+
try {
|
|
151
|
+
this.module.go.exit(0);
|
|
152
|
+
} catch (e) {
|
|
153
|
+
// Ignorer les erreurs
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
package/src/browser.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Version navigateur de GOWM sans dépendances Node.js
|
|
2
|
+
import WasmLoader from './loader-browser.js';
|
|
3
|
+
import WasmBridge from './bridge-browser.js';
|
|
4
|
+
|
|
5
|
+
class GoWM {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.loader = new WasmLoader();
|
|
8
|
+
this.modules = new Map();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async load(wasmPath, options = {}) {
|
|
12
|
+
const module = await this.loader.loadModule(wasmPath, options);
|
|
13
|
+
const bridge = new WasmBridge(module, options);
|
|
14
|
+
|
|
15
|
+
const moduleId = options.name || 'default';
|
|
16
|
+
this.modules.set(moduleId, { module, bridge });
|
|
17
|
+
|
|
18
|
+
return bridge;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get(name = 'default') {
|
|
22
|
+
const entry = this.modules.get(name);
|
|
23
|
+
return entry ? entry.bridge : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async loadFromNPM(packageName, options = {}) {
|
|
27
|
+
// Pour le navigateur, on assume que le fichier est dans /wasm/{packageName}.wasm
|
|
28
|
+
const wasmPath = `/wasm/${packageName}.wasm`;
|
|
29
|
+
return this.load(wasmPath, { ...options, name: packageName });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
unload(name = 'default') {
|
|
33
|
+
const entry = this.modules.get(name);
|
|
34
|
+
if (entry) {
|
|
35
|
+
entry.bridge.cleanup();
|
|
36
|
+
this.loader.unloadModule(name);
|
|
37
|
+
return this.modules.delete(name);
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
listModules() {
|
|
43
|
+
return Array.from(this.modules.keys());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getStats() {
|
|
47
|
+
const stats = {};
|
|
48
|
+
for (const [name, entry] of this.modules) {
|
|
49
|
+
stats[name] = entry.bridge.getStats();
|
|
50
|
+
}
|
|
51
|
+
return stats;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
unloadAll() {
|
|
55
|
+
for (const name of this.modules.keys()) {
|
|
56
|
+
this.unload(name);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Instance globale
|
|
62
|
+
const gowm = new GoWM();
|
|
63
|
+
|
|
64
|
+
// API simplifiée
|
|
65
|
+
export const load = (wasmPath, options) => gowm.load(wasmPath, options);
|
|
66
|
+
export const get = (name) => gowm.get(name);
|
|
67
|
+
export const loadFromNPM = (packageName, options) => gowm.loadFromNPM(packageName, options);
|
|
68
|
+
export const unload = (name) => gowm.unload(name);
|
|
69
|
+
export const listModules = () => gowm.listModules();
|
|
70
|
+
export const getStats = () => gowm.getStats();
|
|
71
|
+
export const unloadAll = () => gowm.unloadAll();
|
|
72
|
+
|
|
73
|
+
// Export des classes
|
|
74
|
+
export { GoWM, WasmLoader, WasmBridge };
|
|
75
|
+
|
|
76
|
+
// Export par défaut
|
|
77
|
+
export default gowm;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export default class WasmLoader {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.modules = new Map();
|
|
4
|
+
this.isNode = false; // Toujours faux en environnement navigateur
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async loadModule(wasmPath, options = {}) {
|
|
8
|
+
const moduleId = options.name || wasmPath.split('/').pop().replace('.wasm', '');
|
|
9
|
+
|
|
10
|
+
if (this.modules.has(moduleId)) {
|
|
11
|
+
return this.modules.get(moduleId);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// Charger wasm_exec.js si nécessaire
|
|
16
|
+
if (!globalThis.Go) {
|
|
17
|
+
await this.loadGoRuntime(options.goRuntimePath);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const go = new globalThis.Go();
|
|
21
|
+
|
|
22
|
+
const response = await fetch(wasmPath);
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(`Failed to fetch WASM file: ${response.status} ${response.statusText}`);
|
|
25
|
+
}
|
|
26
|
+
const wasmBytes = await response.arrayBuffer();
|
|
27
|
+
|
|
28
|
+
const result = await WebAssembly.instantiate(wasmBytes, go.importObject);
|
|
29
|
+
|
|
30
|
+
// Pré-initialisation pour optimiser les performances
|
|
31
|
+
if (options.preInit !== false) {
|
|
32
|
+
// Démarrer Go en arrière-plan pour éviter le blocage
|
|
33
|
+
go.run(result.instance);
|
|
34
|
+
|
|
35
|
+
// Attendre que le module soit prêt
|
|
36
|
+
await this.waitForReady(moduleId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const module = {
|
|
40
|
+
instance: result.instance,
|
|
41
|
+
go: go,
|
|
42
|
+
exports: result.instance.exports,
|
|
43
|
+
ready: true
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
this.modules.set(moduleId, module);
|
|
47
|
+
return module;
|
|
48
|
+
|
|
49
|
+
} catch (error) {
|
|
50
|
+
throw new Error(`Failed to load WASM module ${moduleId}: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async waitForReady(moduleId, timeout = 5000) {
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const checkReady = () => {
|
|
59
|
+
// Vérifier plusieurs signaux de prêt
|
|
60
|
+
const isReady = globalThis.__gowm_ready ||
|
|
61
|
+
(globalThis.Go && globalThis.Go._initialized) ||
|
|
62
|
+
(globalThis.add && typeof globalThis.add === 'function');
|
|
63
|
+
|
|
64
|
+
if (isReady || Date.now() - startTime > timeout) {
|
|
65
|
+
if (isReady) {
|
|
66
|
+
resolve();
|
|
67
|
+
} else {
|
|
68
|
+
reject(new Error(`Module ${moduleId} failed to initialize within ${timeout}ms`));
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
setTimeout(checkReady, 10);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
checkReady();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async loadGoRuntime(customPath) {
|
|
79
|
+
const runtimePath = customPath || '/wasm_exec.js';
|
|
80
|
+
await this.loadScript(runtimePath);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
loadScript(src) {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const script = document.createElement('script');
|
|
86
|
+
script.src = src;
|
|
87
|
+
script.onload = resolve;
|
|
88
|
+
script.onerror = reject;
|
|
89
|
+
document.head.appendChild(script);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getModule(name) {
|
|
94
|
+
return this.modules.get(name);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
unloadModule(name) {
|
|
98
|
+
const module = this.modules.get(name);
|
|
99
|
+
if (module && module.go) {
|
|
100
|
+
// Nettoyage des ressources Go
|
|
101
|
+
try {
|
|
102
|
+
module.go.exit(0);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
// Ignorer les erreurs de nettoyage
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return this.modules.delete(name);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
listModules() {
|
|
111
|
+
return Array.from(this.modules.keys());
|
|
112
|
+
}
|
|
113
|
+
}
|
package/src/loader.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
1
|
class WasmLoader {
|
|
5
2
|
constructor() {
|
|
6
3
|
this.modules = new Map();
|
|
7
4
|
this.isNode = typeof window === 'undefined';
|
|
5
|
+
|
|
6
|
+
// Importer les modules Node.js seulement en environnement Node.js
|
|
7
|
+
if (this.isNode) {
|
|
8
|
+
this.fs = require('fs');
|
|
9
|
+
this.path = require('path');
|
|
10
|
+
}
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
async loadModule(wasmPath, options = {}) {
|
|
11
|
-
const moduleId = options.name || path.basename(wasmPath, '.wasm');
|
|
14
|
+
const moduleId = options.name || (this.isNode ? this.path.basename(wasmPath, '.wasm') : wasmPath.split('/').pop().replace('.wasm', ''));
|
|
12
15
|
|
|
13
16
|
if (this.modules.has(moduleId)) {
|
|
14
17
|
return this.modules.get(moduleId);
|
|
@@ -24,10 +27,10 @@ class WasmLoader {
|
|
|
24
27
|
let wasmBytes;
|
|
25
28
|
|
|
26
29
|
if (this.isNode) {
|
|
27
|
-
if (!fs.existsSync(wasmPath)) {
|
|
30
|
+
if (!this.fs.existsSync(wasmPath)) {
|
|
28
31
|
throw new Error(`WASM file not found: ${wasmPath}`);
|
|
29
32
|
}
|
|
30
|
-
wasmBytes = fs.readFileSync(wasmPath);
|
|
33
|
+
wasmBytes = this.fs.readFileSync(wasmPath);
|
|
31
34
|
} else {
|
|
32
35
|
const response = await fetch(wasmPath);
|
|
33
36
|
if (!response.ok) {
|
|
@@ -91,11 +94,11 @@ class WasmLoader {
|
|
|
91
94
|
|
|
92
95
|
if (this.isNode) {
|
|
93
96
|
// Vérifier que le fichier existe
|
|
94
|
-
if (!fs.existsSync(runtimePath)) {
|
|
97
|
+
if (!this.fs.existsSync(runtimePath)) {
|
|
95
98
|
console.warn(`Go runtime not found at ${runtimePath}, using fallback`);
|
|
96
99
|
// Utiliser le fallback du dossier runtime
|
|
97
|
-
const fallbackPath = path.join(__dirname, '../runtime/wasm_exec.js');
|
|
98
|
-
if (fs.existsSync(fallbackPath)) {
|
|
100
|
+
const fallbackPath = this.path.join(__dirname, '../runtime/wasm_exec.js');
|
|
101
|
+
if (this.fs.existsSync(fallbackPath)) {
|
|
99
102
|
require(fallbackPath);
|
|
100
103
|
} else {
|
|
101
104
|
throw new Error(`Go runtime not found. Please ensure wasm_exec.js is available`);
|
|
@@ -110,7 +113,7 @@ class WasmLoader {
|
|
|
110
113
|
|
|
111
114
|
getDefaultRuntimePath() {
|
|
112
115
|
if (this.isNode) {
|
|
113
|
-
return path.join(__dirname, '../runtime/wasm_exec.js');
|
|
116
|
+
return this.path.join(__dirname, '../runtime/wasm_exec.js');
|
|
114
117
|
}
|
|
115
118
|
return '/wasm_exec.js';
|
|
116
119
|
}
|