1. 使い分け
- 
exportsは const だと思った方が良い (常にexports.hoge = fuga;の形で使う)
- 
module.exportsは自由
- モジュール内で併用しない
これらの条件を守っていれば、どちらを使っても同じ。
class をまるっと入れたいときは module.exports になる。
2. 仕組み
イメージ
let a = {}; /* module.exports */
let b = a;  /* exports */
// 
b.foo = 'foo';
// 
a = {bar: 'bar'}
// 
return a;
- 
bに新しいオブジェクトを入れても反映されない
- 
aに新しいオブジェクトを入れるとbが無視される
- どちらも新しいオブジェクトを入れなければ両方反映 (でも、統一したほうが見やすい)
参考「exports と module.exports の違い - Qiita」
参考「Node.js : exports と module.exports の違い(解説編) - ぼちぼち日記」
3. 循環参照の対策
以下のいずれか。
- 
module.exportsの参照を破壊しないで使う
- 
exportsを使う ( = 参照を破壊しない)
- 
module.exportsの参照をrequire()前に書き換える
- ソースコードの先頭ではなく使うときに  require()
- 「依存性の注入 (Dependency injection)」
参考「exports と module.exportsの使い分け - Qiita」
参考「webpackなどでCommonJSモジュール循環参照を回避する方法 - Qiita」
参考「node.jsにおける循環参照に対処するための3つの方法 - Qiita」
4. おまけ
exports だけでなく module も似た感じなので、オブジェクトをまるっと入れるとまずい。
(module = {exports: {hoge: fuga}}; は意味がない)
require() 疑似コード
function require(/* ... */) {
  const module = { exports: {} };
  ((module, exports) => {
    // ... モジュールのコード
  })(module, module.exports);
  return module.exports;
}
参考「exports shortcut - Modules | Node.js v9.6.1 Documentation」