자스알못의 JS 이야기
(Node.js) 서버 배포 설정 by webpack 본문
최근에 배포 환경 설정과 관련해서 굉장한 삽질을 했는데요... 그 일정을 한번 적어볼까 합니다.
사실 과정은 딱히 안중요하고, 웹팩 설정만 보고 싶으신 분도 있으실 것 같아서 튜토리얼 스킵 버튼을 만들어 두었습니다. ㅎㅎ
먼저 여기서 설정하는 것은 제 개인 프로젝트의 서버단 배포이며, 프론트는 react-create-app 을 통해 설정을 하기 때문에 따로 하지 않았습니다.
저는 웹팩을 사용했습니다.
사실 원래는 배포라고 막 제가 하려는 작업이 많은 것이 아니라, uglify, compress 정도만 알고 있었기 때문에 이정도만 해주려고 했습니다.
그래서 웹팩은 초기 학습 곡선이 높다고 들었으니, 자동화 툴 정도만 써서 간단하게 배포 환경을 구축해보자! 하고 gulp를 시도했었으나..
1. 경로 설정의 난관
처음은 언제나 순조롭게, gulp 설치와 간단한 사용법을 익힌 후 바로 적용을 해봤습니다.
정말 간단하죠!
그냥 gulp plugin 만 설치하고 로드한 다음에, 이렇게 설정만 해주면 된답니다.
는 사실 문제가, 원래 저기 js 변수에다가 와일드카드 형태로 'app/api/*.js' 이런 식으로 넣어줬는데 와일드카드는 이 친구가 알아보지 못하더라구요.
그래서 ... glob 으로 와일드카드 파일들을 다 가져와서 넣도록 삽질을 한번 해주고 다시 시도! 를 했습니다.
하지만 glob 으로 가져오면 파일 디렉토리 구조가 남지를 않아서... 다시 말해서 저는
[ 'app/**/*.js' , 'config/**/*.js', 'server.js' ]
이런 식으로 js 파일을 가져오도록 만들어 놓았는데, 중간에 /**/ 가 들어간 부분은 가져오는 js 파일이 어느 정도 뎁스에서 가져와지는지, 어느 경로로 오는지를 알 수가 없어서 기존의 파일 구조를 유지하기가 어렵더라구요. 다시 말해서, glob 은 파일들만 가져오기 때문에 그냥 config 던, route 던, model이던 상관없이 저 gulp의 처리를 거치게 되고, dist 폴더에 저장이 됩니다.
원래는 파일 구조를 유지하지 않아도 되야 되는게 맞는데, 제가 저기서 concat 이라는 compress 모듈을 뺀 이유가 따로 있습니다 ㅎㅎ
하나로 번들링 해서 배포를 하면 정말 좋지만, 그게 쉽지 않더군요.
2. 모듈 로드
concat을 저도 처음에는 써봤습니다..! 잘 작동하더군요 ㅎㅎ
물론 concat 기능 자체가 잘 작동한다는 거고, 번들링 파일이 잘 작동한다는 말은 아니었습니다 :)
저는 지금 프론트 js 를 처리하는게 아니라, 서버쪽 js 파일들을 처리하고 있고, 서버쪽 파일들은 서로 module과 require 형태로 연결되어 있습니다.
즉, require('../app/model/chat.js') 이런 식으로 경로 참조를 쓴다는 것이죠
는 또 다시 말해서 위에서 처리했던 상에서 파일 구조 유지가 안되면 작동하지 않는 이유이기도 하죠!!
이는 파일들이 한 폴더 내에서 여러개로 존재하던, 한 파일 내에 inline으로 존재하던 다르지 않습니다.
파일 구조가 유지되지 않는 한, 파일이 여러개로 존재하더라도 require 가 작동하지 않고, 한 파일 내에 있어도 당연히 안되겠죠. 그 경로에 파일이 없는데...
결론적으로 말하면, 파일 구조를 유지하면서 minify 를 진행해서 원래 있던 경로에 다들 잘 넣어주어야 제가 원하는 minify 만 간단하게! 진행할 수가 있다는 거죠
핫핫핫
아. 정말 개발은 신나는 것 같아요. 삽질을 하면 할수록 더 많은 삽질이 필요해지는...
3. uglify es6 호환
이 문제도 위의 머리가 아픕니다.
대체 ! 왜 ! es2015 나온지가 언젠데! es6 uglify 를 지원하지 않아서 ...
삽질을 한 것에 비해서 결론이 적어서 억울한 것도 좀 있는데,
일단 gulp 를 실행할 때 이상한 에러가 뜨는데 무엇이 문제인지 알지 못하는 부분부터 슬슬 열이 받습니다.
그게 es6 호환 때문이란 것을 알고 난 후에는, gulp-uglify 에서 다른 uglify 모듈을 사용할 수 있으며, es6를 사용하고 싶으면 harmony 를 달고 있는 버전을 써라 라는 부분을 봅니다. (이때 안될 것이라는 걸 알았어야 하는데...)
역시나! 안됩니다! 하
그럼 그렇지. 애초에 지원이 되는 거면 그냥 업데이트 해서 기능에 포함시키지 뭐하러 따로지 뭐하러 따로 호환버전을 내놓을리가 없겠죠
해서 두번째 방법인 babel로 컴파일 후, uglify 를 하도록 했습니다.
4. Gulp 결론
위와 같은 삽질을 하면서, 'gulp 는 안되겠구나' 라는 비싼 교훈을 얻게 되었다고 합니다 ㅎㅎ
5. webpack 으로의 마이그레이션 (본 내용)
그래서 알아보니까, 웹팩이 좋다고 합디다
안그래도 알긴 했지만, 이번에 react 들어가면서 조금 건드려 보기도 했고 해서 이참에 제대로 해볼까 해서 시작을 했습니다.
웹팩은 모듈 번들러라고 하던데, 제가 겪었던 문제인 require를 묶어주는 그런 목적으로 나왔습니다!
딱 제가 필요한거죠 ㅎㅎ
그리고 uglify 같은 것들이 plugin 으로 있어서 번들링할 때 적용할 수 있다고 합니다! 물론 es6도 포함!
6. 설치
일단 설치는 언제나 그렇듯 쉽습니다.
sudo npm install webpack -g
7. 환경 설정
간단합니다! 는 아니고 그냥 웹팩을 쓰고자 하는 폴더에 webpack.config.js 파일을 만들어 줍니다.
그리고 내용은 아래와 같습니다. 제가 현재 쓰고 있는 코드만을 기준으로 설명하므로, 필요하신 부분이 있다면 찾아보세요!
참고로 제 코드들은 모두 사진으로 제공됩니다. 가독성을 위한 것이지만 내용이 필요하신 분은 직접 쳐보면서 설정하는 걸 추천합니다.
저도 개발을 많이 해본 것은 아니지만, 직접 치면서 하면 조금 더 기억에 오래 남고 이해가 더 잘되는 것 같아서 자주 그렇게 합니다.
좀.. 길죠 ㅎㅎ
하지만 그렇게 별거는 없으니 찬찬히 살펴봅시다.
1) target: node
이 부분은 webpack 을 하면서 맨 처음 겪었던 문제인데, 이 부분을 설정해줘야 node 의 기본 내장 모듈들을 로드할 수 있다고 하더군요.
2) externals : [ nodeExternals() ]
이것도 1번과 비슷한 내용인데, node_modules 를 포함할 수 있도록 해주는 설정입니다.
따로 webpack-node-externals 라는 모듈을 설치를 해준 후, require 해서 사용합니다.
3) __dirname
이 부분이 바로 제가 한참 애먹었던 부분입니다.
웹팩을 써서 한참 잘 번들링 해놓고, 자꾸 can't find module ' ~~ ' 가 나오길래 왜때문인지 몰라서 많이 해맸는데요.
번들링을 하는 과정에서 require를 for 문이나 array map 같이 반복문을 통해서 로드를 해주면 그 해당 로직의 require는 제대로 컴파일이 안되는 것 같습니다!
아마도 그냥 require자체만 보고 그거를 변환하는게 아닌가, js 를 다 실행해보고 변환해주는게 아니라면 그럴 수 있다는 생각이 있었는데 모듈을 여러개 로드하는 것보다 그냥 하나하나 다 require문으로 하는게 좋다던 어느 분의 조언을 받고 그냥 전부 require를 해주는 방식으로 바꾸었습니다.
그 이후에도 문제가 계속 있었는데, 제가 require 를 할 때 require(path.join(__dirname, '../app/routes/chat.route.js')) 이런 식으로 코드를 짰었습니다.
왜 그랬는지 물어보신다면 ... 사실 제가 소스를 보고 배운 분이 그렇게 쓰고 계셔서 따라 하다 보니 이렇게 되었다고 조그마한 변명을 해봅니다.
그런데, 계속 안되길래 잘 쓰지도 않는 webstrom 을 굳이 켜서 debugging 을 해봤는데 __dirname 이 문제가 있는 것 같아서 검색을 하기 시작합니다.
찾아보니 저렇게 써놓고 node: { __dirname : true } 로 해두면 된다고 합니다.
해봤습니다.
안됩니다.
네. 안됩니다. true 로 해놓고 디버거로 __dirname 확인해보면 해당 번들링된 파일의 경로가 나오는 것을 확인할 수 있습니다.
누가 보니 자기도 왜 되는지 모르겠지만 자기는 false로 하면 된답니다.
해봤습니다.
네. 안됩니다. 그런데 사실 안되는데 이유가 경로가 아닌 것 같습니다.
분명히 경로는 맞게 나오는데.. cannot find module 이랍니다.
근데 아까부터 계속 마음에 걸리던 것이, warning 정도로 뜨던 require 에 expression 이 들어가있다던 빌드 타임 경고 메세지였는데, 혹시나 해서 아예 변수 이런거 다 빼버리고, string 으로 직접 다 기입을 해줍니다.
세상에. 됩니다.
사실 아직도 request.context 같은 여러 모듈을 한꺼번에 로드할 수 있는 그런 방법에 미련이 남아있긴 합니다만... 일단은 되는 걸로 만족을 합니다.
따라서 __dirname 은 필요가 없습니다.
안써요. 과정 중심형 블로그라 안쓰는거에 쓸데없이 내용이 긴데 ㅎㅎ 이해해주시기 바랍니다.
4) entry: 'server.js'
시작점 이라고 생각하시면 됩니다. 이 파일을 시작으로, 여기서 require되는 것들, 그 require 에서 다시 require 하는 것들 이런 식으로 chaining 하는 로직을 만들어서 bundle.js 를 만들게 됩니다.
자세한 구조는 bundle 이 만들어지고 나서 파일 소스를 천천히 확인해보시기 바랍니다.
당연하지만 uglify 를 끄고 하셔야 합니다
5) output
결과물에 대한 내용입니다.
path : 저장 위치
publicPath : url 형태로 접근할 경로
filename : 결과물 이름
chunkFilename 여러 entry 일때의 이름
6) debugging tool
어떤 것을 사용해서 debugging 을 할 것인지를 정하는 부분입니다. 저는 source-map으로 했습니다. 다른 옵션은 잘 몰라서..
7) plugin
원래 제 목적인 uglify 사용을 위해서 uglify plugin을 적어줍시다.
minify 를 하고, compress도 해주고, 주석도 없애줍시다!
(screw_ie8 옵션을 설정해주면 ie8 을 버리면서 파일 크기를 더 줄여준답니다. 그런데 원래 파일이 작아서 그런지 딱히 작아지지는 않네요 ㅎㅎ)
8) loader
마지막 부분인 로더입니다. 파일이 loader 의 테스트에 맞는 것일 경우, loader를 통과하도록 하는 것인데, es6를 쓰기 때문에 저는 바벨을 로더로 두었고,
preloader 로 eslint를 두었습니다.
..
자 이렇게 위대한 삽질이 끝났습니다.
무슨 삽질 자체도 오래 걸리는데 삽질을 어떻게 했는지 적는 것도 오래 걸리네요..
솔직히 약간 추후에 리마인더 목적으로는 좀 비효율적인 시간 소모 같기는 한데 ㅋㅋㅋㅋ
다음 부터는 좀 더 가독성이나 이해도에 초점을 맞춰야 하나 싶기도 합니다.
'Node.js' 카테고리의 다른 글
(Node.js) express 미들웨어 및 에러 핸들러 구현하기 (0) | 2016.12.13 |
---|---|
(DB) ec2 mysql 설치부터 인코딩 설정하기! (0) | 2016.12.12 |
(Node.js) JWT authentication 구현 (1) | 2016.12.06 |