Вроде, все просто и ясно:

var module = require("./some_module.js");

module.f(); // запуск экспортируемой функции модуля
console.log(module.obj); // печать экспортируемого объекта модуля
module(); // запуск самого модуля

Что бы экспортировать что-то в самом модуле нужно прописать:

exports.f = function() { return 123; };
exports.obj = { name: "Foobar", age: 33 };

// или даже так, что вместе с предыдущими строчками
// в одном модуле не сработает!
module.exports = function() { return "I have not a name"; }

Почему не всегда работает?

Ответ на этот вопрос лежит в понимании работы функции require().

При вызове require(), как я помучившись понял, создает объект под именем exports, считывает переданный файл в переменную и выполняет весь код из этой переменной. После чего, возвращает нам изменённый в процессе выполнения кода объект exports.

Попробуем создать свою функцию require(). Для упрощения восприятия мы будем передавать нашей функции не имя файла, а уже, как бы, содержимое файла в виде строковой переменной. Назовем её script.

Определение нашей функции require будет выглядеть так:

function require(script) {
    var exports = {};
    eval(script);

    return exports;
}

Но как быть, если нам нужно экспортировать безымянный объект? Помните module.exports = function() {}? Добавляем объект с именем module, и его свойству exports присваиваем объект.

Наша функция require немного усложняется:

function require(script) {
    var module = {};
    var exports = {};

    eval(script);

    return module.exports ? module.exports : exports;
}

Запускаем тест:

var script = "exports.foo = 'foo'; exports.bar = 'bar';"; // код модуля
var foobar = require(script); // импортируем

console.log(foobar.foo); // печатает: foo
console.log(foobar.bar); // печатает: bar

Работает!

Запускаем тест с экспортом безымянного объекта:

var script = "module.exports = function() { return 'foobar'; }"; // код модуля
var foobar = require(script); // импортируем

console.log(foobar()); // печатает: foobar

Тоже работает.

Запускаем тест с экспортом безымянного объекта и именованных объектов модуля:

var script = "exports.arr = [1, 2, 3]; module.exports = function() { return 'foobar'; }"; // код модуля
var foobar = require(script); // импортируем

console.log(foobar()); // печатает: foobar
console.log(foobar.arr); // печатает: undefined

Не работает!

К стати, не работает и при использовании штатной функции node.js require(). Это показывает, что наша функция мало чем отличается от неё, и мы на верном пути.

А как же экспортировать нам и безымянный и именованные объекты? Ответ — нужно использовать только module.exports, причём, именованные объекты экспортируются обязательно после безымянного.

Откорректируем предыдущий пример:

var script = "module.exports = function() { return 'foobar'; }\n"
        + "module.exports.arr = [1, 2, 3];\n"
        + "module.exports.result = 'Ok'\n";
var foobar = require(script); // импортируем

console.log(foobar()); // печатает: foobar
console.log(foobar.arr); // печатает: [1, 2, 3]
console.log(foobar.result); // печатает: Ok

Работает!

Надеюсь, что мои примеры, дорогой читатель, помогли вам глубже понять механизм экспорта-импорта модулей в node.js. На первый взгляд, он очень прост, о чем пишут и сами разработчики. Но без понимания работы встроенной функции require() иногда оказываешься наедине с чУдными результатами работы своего кода.

Комментарии (5)


  1. k12th
    08.02.2016 14:37
    +6

    Есть ли смысл сейчас изучать внутренности require, ведь грядут нативные модули во все поля?

    // или даже так, что вместе с предыдущими строчками
    // в одном модуле не сработает!
    module.exports = function() { return "I have not a name"; }
    

    Конечно не сработает, мы же только что переписали значение переменной целиком. Все равно что сделать:
    global.answer = 42;
    myVar = {answer: 24}

    и удивляться, что a.answer !== 42


    1. gbazil
      08.02.2016 15:40
      -4

      А так сработоет:

      в модуле

      module.exports = function() { return «I have not a name»; }
      module.exports.abc = ['a', 'b', '!'];

      далее в другом файле

      var f = require('./module.js');

      var str = f();
      str += f.abc[2];

      console.log(str); // печатает: I have not a name!

      Что мне и было нужно.

      Рад, что у Вас не возникает таких заморочек )


      1. k12th
        08.02.2016 15:46
        +6

        Правильно, так работает. Это эквивалентно такому:

        function myFunction () { return "I have not a name"; }
        myFunction.abc = ['a', 'b', '!'];
        
        module.exports = myFunction;
        

        Мы не переписываем значение module.exports, а создаем новое поле в этом объекте.


  1. zBit
    08.02.2016 18:30

    thenodeway.io/posts/how-require-actually-works
    Вообще, советую прочитать полностью всё что есть на этом сайте. Благо там не так уж и много всего.

    Единственная мысль, которая была подмечена правильно: нужно стараться использовать только module.exports. А в остальном — всё плохо…


  1. SamKrew
    08.02.2016 18:30
    +1