Как написать модуль, который работает как с Node.js, RequireJS, так и без них

Я работаю над библиотекой JavaScript для обработки JSON/XML. Моя библиотека работает как в браузере, так и в Node.js (с модулями xmldom и xmlhttprequest).

Один из пользователей недавно попросил поддержку RequireJS. Я взглянул на RequireJS/AMD и думаю, что это хороший подход, поэтому я хотел бы предоставить его.

Однако я хотел бы сохранить переносимость: моя библиотека должна работать в браузерах (с RequireJS и без него), а также в Node.js. И в среде браузера я не завишу от xmldom или xmlhttprequest, поскольку эти вещи предоставляются самим браузером.

Мой вопрос: как мне реализовать мою библиотеку, чтобы она работала в браузерах так же, как и в Node.js без RequireJS?

Немного истории и мое текущее решение

Я изначально написал свою библиотеку для браузеров. Поэтому он просто создал объект глобальной области видимости и поместил в него все:

var Jsonix = { ... };

Позже пользователи обратились за поддержкой Node.js. Поэтому я добавил:

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

Мне также пришлось импортировать несколько модулей, упомянутых выше. Я сделал это условно, в зависимости от того, доступна функция require или нет:

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

Вот такая история с RequireJS. Если присутствует RequireJS, то также присутствует функция require. Но загрузка модуля работает по-другому, я должен использовать функцию define и т. д. Я также не могу просто require делать вещи, так как require имеет асинхронный API в RequireJS. Более того, если моя библиотека загружается через RequireJS, она вроде бы обрабатывает исходный код и обнаруживает require('something'), даже если я делаю это условно, например

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

RequireJS по-прежнему обнаруживает require('xmlhttprequest') и пытается загрузить соответствующий файл JS.

В настоящее время я прихожу к следующему решению.

// 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();
}

И вот как я сейчас проверяю зависимости:

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

Если у меня есть require, но нет define, я предполагаю, что это среда Node.js. Я использую amdefine для определения модуля и передачи необходимых зависимостей.

Если у меня есть require и define, я предполагаю, что это среда RequireJS, поэтому я просто использую функцию define. В настоящее время я также предполагаю, что это среда браузера, поэтому такие зависимости, как xmldom и xmlhttprequest, недоступны и не требуют их. (Вероятно, это неправильно.)

Если у меня нет функции require, я предполагаю, что это среда браузера без поддержки RequireJS/AMD, поэтому я напрямую вызываю фабрику модулей _jsonix и экспортирую результат как глобальный объект.

Итак, это мой подход на данный момент. Мне это кажется немного неудобным, и, как новичок в RequireJS/AMD, я ищу совета. Это правильный подход? Есть ли лучшие способы решения проблемы? Буду признателен за вашу помощь.


person lexicore    schedule 12.08.2014    source источник
comment
это может быть полезно: github.com/umdjs/umd   -  person go-oleg    schedule 13.08.2014


Ответы (3)


Посмотрите, как с этим справляется underscore.js.

// 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 _;
  });
}
person Timothy Strimple    schedule 12.08.2014

Вот что у меня получилось:

// 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;
}
person lexicore    schedule 17.08.2014

Вот шаблон, который я сейчас использую, он совместим как с AMD, так и с узлом, хотя и не загружается напрямую в браузере...

Основное преимущество этого подхода заключается в том, что в общем случае коду предметной области не нужно заботиться о том, что его импортировало.

/**********************************************************************
* 
*
*
**********************************************************************/
((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 })
person Alex Naanou    schedule 20.08.2016