node.js의 express.js 한줄 한줄 분석하고 있습니다.
express/lib/express.js
exports = module.exports = createApplication;
/**
* exports와 module.exports의 차이
*
* 노드의 경우 파일을 실행시키기 전에 퍄일내의 스크립트들을 즉시 실행 함수로 감쌈
*
* (function (exports, require, module, __filename, __dirname) {
* // 스크립트 위치
* });
*
* 따라서 exports와 module의 경우 즉시 실행함수를 통해 들어오는 인자에 불과함
*
*/
/**
* exports = module.exports = createApplication; 으로 모듈을 익스포트 하는 이유
*
* exports와 module.exports가 가리키는 개체의 프로퍼티를 바꾸는것이 아니라 개체 자체를 바꾸어야하는경우
*
* exports와 module.exports 둘다 바꾸지 않은경우 서로 다른것을 가리킬 위험이 있음
*/
/**
* exports와 module.exports의 참조 관계
*
* exports -> module.exports -> {}
*
* var module = new Moduel(...);
* var exports = module.exports;
*
* https://stackoverflow.com/questions/7137397/module-exports-vs-exports-in-node-js 참고
*
*/
/**
* require()호출을 통해 받는 값 -> module.exports
*
* exports는 편의 변수로 모듈 작성자가 코드를 덜 작성하도록 돕는다.
* exports는 require()함수에서 반환되지 않는다.
*
* https://edykim.com/ko/post/module.exports-and-exports-in-node.js/#요약 참고
*/
/**
* moduel.exports와 exports require 간의 관계
*
* https://medium.com/@chullino/require-exports-module-exports-공식문서로-이해하기-1d024ec5aca3 참고
*/
exports 와 module.exports에 대해
- export의 경우 module.export를 참조하고 module.export가 빈 개체를 참조하고 있다.(export -> module.export -> {})
- 개체의 경우 참조형 데이터이므로 개체의 프로퍼티를 변경하는것은 위의 참조관계를 무너뜨리지 않는다.
- 프로퍼티 변경이 아닌 개체 자체를 바꾸는 경우 exports와 moduel.export의 참조관계를 무너뜨릴수 있다.
- require의 경우 module.export가 참조하고 있는 값을 가져오므로, express에서처럼 exports = module.export = createApplication; 형태로 참조 관계를 다시 맞추어줄 필요는 없다.(exports가 module.export와는 다른 개체를 참조한다 하더라도 require함수에의해 가져오는 개체는 exports가 아닌 module.export이므로)
- 그럼에도 불구하고 express에서 exports = module.export = createApplication;구조를 사용한 이유는 express가 익스포트하는 개체는 createApplication함수가 유일하지 않기 때문이다.
/**
* Expose the prototypes.
*/
exports.application = proto;
exports.request = req;
exports.response = res;
/**
* Expose constructors.
*/
exports.Route = Route;
exports.Router = Router;
/**
* Expose middleware
*/
exports.json = bodyParser.json
exports.query = require('./middleware/query');
exports.raw = bodyParser.raw
exports.static = require('serve-static');
exports.text = bodyParser.text
exports.urlencoded = bodyParser.urlencoded
- express의 경우 createApplication함수에 몇몇 프로퍼티를 추가하여 익스포트한다.
- 이때 exports를 사용하여 프로퍼티를 추가하므로 exports = module.export = createApplication;형태로 코드를 구성하였다.(exports와 module.export가 다른 개체를 바라보고 있는경우 exports를 통해 추가한 개체는 require를 통해 가져올 수 없기 때문)
- 만약 프로퍼티를 moduel.export.Route = Route;의 형태로 저장한다면 exports = module.export = createApplication;으로 코드를 작성하지 않았을 것이다.
exports default와 module.export에 대해
- es6의 exports default의 경우 개체를 익스포트하므로 module.export와 같을것이라 생각하였다.
- 하지만 babel에 의해 변환된 코드를 보면 그렇지 않다.
- exports에 default프로퍼티를 추가하여 해당 프로퍼티에 값을 넣어준다.
- require의 경우 module.export를 가져오는데 exports default에 의해 변경된 값이 개체가 아니라 프로퍼티라면 문제가 아닐까 하는 생각을 할 수 있다.
- babel이 import문법을 require과 1대1로 치환하지 않기 때문에 해당 문제는 발생하지 않는다.
var _express = _interopRequireDefault(require("express"));
- _interopRequireDefault함수는 ex6문법에 의해 익스포트된 개체("default" 프로퍼티에 실제 값이 들어있는 개체)가 아니경우(module.export = createApplication과 같은 경우) ex6문법에 의해 익스포트된 개체로 변경(return { "default" : obj };)해서 사용한다.
- 이후 개체를 사용할 때 default프로퍼티에 접근하여 사용(=pp.set(VIEWS, _path["default"].join(__dirname, VIEWS));)한다.
require과 import에 대해
- require과 import는 단지 표현방식의 차이만 있는것이 아니다.
- require의 경우 파일의 형태로 분리되어 있는 모듈을 포함시키는 함수이다.
- require함수는 인자로 받아온 파일을 읽고, 실행하고, 모듈을 리턴한다.
- require의 경우 node.js의 빌트인 모듈(http, fs, path등)과 커뮤니티 기반 모듈(node_modules에 저장되는) 그리고 로컬 모듈만을 읽을 수 있다.
- import의 경우 ES module만 가져올 수 있다.
- import는 파일 타입의 모듈을 가져올 수 없다. (deno에서의 모듈시스템을 의미하는것 같다.)
- node.js의 경우 common js의 모듈 시스템을 사용(require, module.export)한다.
- node.js자체적으로 es6모듈 시스템을 이해할 수 없다.
- babel이 node.js가 이해할수 있도록 코드를 바꾸어주므로 위의 상황과 같은 것들이 발생한다.
https://flexiple.com/javascript-require-vs-import/ 참고
https://github.com/JsTsPractices/express/commit/d96c55cb2a670c88ac715370a7d74c9b7a511fd6