Come scrivere un modulo che funzioni con Node.js, RequireJS e senza di essi

Sto lavorando su una libreria JavaScript per l’ elaborazione JSON / XML . La mia libreria funziona sia su browser sia su Node.js (con i moduli xmldom e xmlhttprequest ).

Uno degli utenti ha recentemente chiesto il supporto RequireJS. Ho dato un’occhiata alla cosa RequireJS / AMD e penso che sia un buon approccio quindi mi piacerebbe fornirlo.

Tuttavia mi piacerebbe mantenere la portabilità: la mia biblioteca deve funzionare nei browser (con e senza RequireJS) e Node.js. E nell’ambiente del browser non dipende da xmldom o xmlhttprequest poiché queste cose sono fornite dal browser stesso.

La mia domanda è: come posso implementare la mia libreria in modo che funzioni nei browser e in Node.js con un RequireJS?

Un po ‘di storia e la mia attuale soluzione

Inizialmente ho scritto la mia libreria per i browser. Quindi ha appena creato un object a portata globale e ha messo tutto al suo interno:

 var Jsonix = { ... }; 

Più tardi gli utenti hanno chiesto il supporto per Node.js. Quindi ho aggiunto:

 if(typeof require === 'function'){ module.exports.Jsonix = Jsonix; } 

Ho anche dovuto importare alcuni moduli sopra menzionati. L’ho fatto in modo condizionale, a seconda che la funzione di require sia disponibile o meno:

 if (typeof require === 'function') { var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; return new XMLHttpRequest(); } 

Ora c’è questa storia con RequireJS. Se RequireJS è presente, è presente anche la funzione require . Ma il caricamento dei moduli funziona in modo diverso, devo usare la funzione define ecc. Inoltre, non posso require solo delle cose, poiché require un’API asincrona in RequireJS. Inoltre, se la mia libreria viene caricata tramite RequireJS, sembra che elabori il codice sorgente e rilevi require('something') anche se lo faccio condizionatamente come

 if (typeof require === 'function' && typeof require.specified !== 'function) ... 

RequireJS rileva ancora require('xmlhttprequest') un tentativo di caricare il file JS corrispondente.

Attualmente sto arrivando alla seguente soluzione.

 // Module factory function, AMD style var _jsonix = function(_jsonix_xmldom, _jsonix_xmlhttprequest, _jsonix_fs) { // Complete Jsonix script is included below var Jsonix = { ... }; // Complete Jsonix script is included above return { Jsonix: Jsonix }; }; // If require function exists ... if (typeof require === 'function') { // ... but define function does not exists, assume we're in the Node.js environment // In this case, load the define function via amdefine if (typeof define !== 'function') { var define = require('amdefine')(module); define(["xmldom", "xmlhttprequest", "fs"], _jsonix); } else { // Otherwise assume we're in the RequireJS environment define([], _jsonix); } } // Since require function does not exists, // assume we're neither in Node.js nor in RequireJS environment // This is probably a browser environment else { // Call the module factory directly var Jsonix = _jsonix(); } 

E questo è il modo in cui controllo le dipendenze ora:

 if (typeof _jsonix_xmlhttprequest !== 'undefined') { var XMLHttpRequest = _jsonix_xmlhttprequest.XMLHttpRequest; return new XMLHttpRequest(); } 

Se ho require ma non define suppongo che questo sia un ambiente Node.js. Uso amdefine per definire il modulo e passare le dipendenze richieste.

Se ho require e define thet, presumo che si tratti di un ambiente RequireJS, quindi uso semplicemente la funzione define . Attualmente presumo anche che questo sia un ambiente di browser, quindi le dipendenze come xmldom e xmlhttprequest non sono disponibili e non richiedono. (Questo probabilmente non è corretto).

Se non ho la funzione require , presumo che si tratti di un ambiente browser senza il supporto RequireJS / AMD, quindi invoco direttamente il modulo factory _jsonix ed esporta il risultato come object globale.

Quindi, questo è il mio approccio finora. Sembra un po ‘imbarazzante per me, e come novizio di RequireJS / AMD sto cercando consigli. È l’approccio giusto? Ci sono modi migliori per affrontare il problema? Ti sarei grato per il tuo aiuto.

Dai un’occhiata a come underscore.js lo gestisce.

 // Export the Underscore object for **Node.js**, with // backwards-compatibility for the old `require()` API. If we're in // the browser, add `_` as a global object. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } 

 // AMD registration happens at the end for compatibility with AMD loaders // that may not enforce next-turn semantics on modules. Even though general // practice for AMD registration is to be anonymous, underscore registers // as a named module because, like jQuery, it is a base library that is // popular enough to be bundled in a third party lib, but not be part of // an AMD load request. Those cases could generate an error when an // anonymous define() is called outside of a loader request. if (typeof define === 'function' && define.amd) { define('underscore', [], function() { return _; }); } 

Questo è quello che ho finito con:

 // If the require function exists ... if (typeof require === 'function') { // ... but the define function does not exists if (typeof define !== 'function') { // Assume we're in the Node.js environment // In this case, load the define function via amdefine var define = require('amdefine')(module); // Use xmldom and xmlhttprequests as dependencies define(["xmldom", "xmlhttprequest", "fs"], _jsonix_factory); } else { // Otherwise assume we're in the browser/RequireJS environment // Load the module without xmldom and xmlhttprequests dependencies define([], _jsonix_factory); } } // If the require function does not exists, we're not in Node.js and therefore in browser environment else { // Just call the factory and set Jsonix as global. var Jsonix = _jsonix_factory().Jsonix; } 

Ecco un modello che sto usando attualmente, è sia AMD che compatibile con i nodes, anche se non è direttamente caricabile stand-alone nel browser …

Il principale vantaggio di questo approccio è che il codice specifico del dominio non ha bisogno di preoccuparsi di cosa lo ha importato, nel caso generale.

 /********************************************************************** * * * **********************************************************************/ ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)( function(require){ var module={} // makes module AMD/node compatible... /*********************************************************************/ /*********************************************************************/ /********************************************************************** * vim:set ts=4 sw=4 : */ return module })