네 ! 안녕하세요 최근에 개인적으로 자동화를 Server 쪽에서 진행하다보니 Node.js Express로 많이 짜게 되었는데요

 

자동화를 하면서 놓쳣던 부분들 ( pm2의 로깅관리, 서버의 주소 노출 등등 ) 의 경험을 다시 덧붙여서 TypeScript & Express 보일러 플레이트를 하루정도 들여서 만들었던 과정을 포스팅하려고합니다.

 

TS를 설명하는 부분들은 아니니 TS는 건너가고 webpack 에 대한 설명도 어느정도는 건너 서 말씀드리겠습니다.

 

Node.js

일단 위의 모든 것들을 행하기 위해서는 Node.js를 설치 하여야 합니다.

 

https://nodejs.org/en/

 

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

유명한 사이트죠 현재 포스팅 기준은 Windows에서 하였기 때문에 Windows로 진행 하도록 하겠습니다.

 

설치 같은 경우는 많이 포스팅하기도했고 검색해도 많이 나오기 때문에 스킵 하도록 하겠습니다.

 

Node.js가 설치가 완료 되었다면 이제 환경 설정을 할 차례입니다.

 

Windows PowerShell, CMD, Visual Studio Code 등의 Terminal 에서 내가 작업할 프로젝트 폴더를 만들고 이동합니다.

 

ex: mkdir network 

     cd network

그 후에 기본적인 npm 세팅을 하도록 합시다.

 

npm init -y  // npm 기본 세팅 
npm i -D  // devDependencies 참조 
npm i // dependencies 참조 

 

npm i -D : 개발디펜던시로 devDependencies 를 기준으로 설치 하시면 됩니다.

npm i : 프로젝트 디펜던시로 dependencies 를 기준으로 설치 하시면 됩니다.

 

** npm i -D typescript @types./axios

    npm i axios 

 

webpack 에 대해 설명을 제대로 하자면 더 길어질수있어 기존에 작성하였던 webpack 포스팅을 참조 드립니다.

https://ipex.tistory.com/entry/TypeScript-TS-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95-with-webpack-v4TypeScript-Environment-Configuration-with-webpack-v4

 

이번 포스팅에서는 webpack 과 typescript + express 구조에 대해서 말씀을 드리려고합니다.

 

일단 express부터 간단하게 구조를 짜보자면

 

이와 같은 구조가 일단 만들어집니다.

 

log - 서버 로깅 폴더

public - static 파일을 serving 하기 위한 공유 폴더

route - express Route 폴더 

views - index.html 등 뷰 템플릿 엔진을 사용하기 위한 폴더

index.ts - express 서버의 진입점 ( 시작점 ) 

폴더 구성 후 tsconfig.json 을 생성 

{

  "compilerOptions": {

    "module": "commonjs",

    "target": "es6",

    "moduleResolution": "node",

    "strict": true,

    "alwaysStrict": true,

    "noImplicitAny": true,

    "allowSyntheticDefaultImports": true,

    "outDir": "dist",

    "listEmittedFiles": true,

  },

  "include": [

    "src/**/*"

  ]

}

src 하위에 있는 ts가 전부 js로 변환됨을 확인하실수 있습니다. outDir에 의해서 dist 폴더로 말이죠

프로젝트/src 구조이기 때문에 outDir은 단순히 dist로만 해도 /프로젝트/dist에 결과 파일이 생성됩니다.

( 폴더 구조 그대로요 )

 

그래서 사실 저도 여기까진 전혀 문제가 되지 않았습니다.

Express 구조 

src / 

    log

    public

    route

    utils

    views

    index.ts

라는 간단한구조에서 tsc만 돌리면 폴더 구조 그대로 dist에 빼주기 때문에 package.json 에서

tsc후 바로 실행 

npm start 에 tsc후에 node dist로 실행해버리면 ( 트랜스 파일 후 서버까지 실행 ) 

이 정상적으로 되기 때문입니다.    하지만 문제는 public 폴더입니다. 단순하게 테스트용도로 바로 쓸수있는 형태의 페이지를 써야해서 nunjucks라는 Node.js 템플릿 엔진을 사용하는데요

 

그렇게 되면 public 폴더도 같이 서빙해야됩니다. ( index.html 과 수 많은 js css 등 ) 

 

그래서 webpack 을 같이 쓰게 되고 webpack + tsc의 콜라보가 발생합니다. ( FE코드와 BE코드의 각각 분리된 컴파일 ) 

 

FE 코드 트랜스파일링 (webpack)

public폴더와 views폴더만 따로 배포하기 위해서 webpack 을 사용하였습니다.  하지만 

entry :{

 'index':'./src/public/js/index.js' 

}

 

와 같이 입력하면 dist/index.js 로 빌드가 됩니다.  우리의 FE 코드들은 dist/public/index.js가 되어야 하기 때문입니다.

 

그래서 entry 를 수정합니다.

 

entry : {

  'public/index':'./src/public/js/index.js'

}

 

dist/public/index.js 에 정확하게 번들링되어 결과가 정상적으로 나타납니다.  하지만 여기서 문제가 생깁니다.

views도 같이 뽑아내기위해서  HtmlWebPackPlugin 을 사용하는데요  해당 플러그인을 쓰게되면 자동으로 css와 js들의 태그를 붙여 주게 됩니다.

 

<link> href="../public/index.css" rel="stylesheet">





<body>
     ....
    <script type="text/javascript"src="../public/index.js"></script>
</body>

이와 같이 말이죠 Express에서 index.html 의 위치는

src

   views

        index.html 

입니다. 그렇기 때문에 해당 js와 css의 접근을 위해서는 ../public 으로 접근을 해야 합니다.

src

   views

       index.html

   public

       index.js

       index.css

 

여기까진 전혀 문제가 없어 보이지만 큰 문제가 하나있습니다.

app.use(express.static(path.join(__dirname, "public")));

express에서는 static 파일을 서빙하기 위해서 public폴더를 지정하였는데요 

../public이 되어버리면  해당 파일들이 서빙이 되지 않습니다. ( send.render 시에 404 not found가 발생 ) 

 

그래서 해당 방법을 위해서 

 

webpack output 에 publicPath 를 이용합니다.

 output:{
       publicPath:'',
       path:path.join(__dirname,'./dist'),
       filename: '[name].js'
    }, // 결과 파일

이와같이 변경하게 되면 ../public은 사라지지만 /public  이 남습니다.

express.static으로 public이 이미 지정되어있기 때문에 /public/index.css가 아니라 index.css가 되어야합니다.

 

그리고 webpack 에서 또한 리소스 파일들 ( 폰트 이미지 등)도 같이 dist로 나가야 하는 상황입니다. 

 

현재 문제점

 

expres는 public 폴더를 static으로 쓰고있는 상황이여서 파일 경로가

 

public/index.css 라면

 

index.css 로 요청을 해야 express내부적으로 public/index.css로 요청을 하게 되지만

 

webpack 옵션으로 인해 /public/index.css로 요청하게 되어 express에서는

 

public/public/index.css를 찾게 되는 현상이 발생 

 

해당 현상을 해결하기전에 아래와같이 필요한 파일들을 이동하였습니다.

 

[ 파비콘 index.html 등 리소스 파일 이동 ] 

 plugins:[
        
        new HtmlWebPackPlugin({
            template:'./src/views/index.html',
            filename:'./views/index.html',
            showErrors:true
        }),
        new MiniCssExtractPlugin({
            filename:'[name].css',
            chunkFilename:'[id].css'
        }),
        new CopyWebpackPlugin([
            {
                context:'./src/public/resource/',
                from:'**/*',
                to:'./resource'
            },
            {
                from:'./src/views/favicon.ico',
                to:'./'
            },
        ])
    ],

const CopyWebpackPlugin = require('copy-webpack-plugin'); 을 이용 했습니다. context : 루트 위치고 from 에서 to로 이동합니다.  

** 유의 할점은 번들링하기전에 파일들을 이동하는 거 이기 때문에 webpack 번들링과 연관되지않습니다. ( 그래서 플러그인이기도하지요)

 

그리고 해당 경로를 해결하기 위해서 생각을 해보았습니다. ts는 

 

/dist

   /express구조

 

 로 만들어 냅니다.

 

webpack 을 똑같이 사용하기엔 express에서는 webpack 에서 번들링되는 부분들은 public 에서만 쓰면 됩니다.

 

위와같이 entry 이름을 'public/index' 로 해버리면 public 경로가 잡혀버려서 경로 문제가 생기기 때문에

 

entry 는 다시 index 로 변경합니다.

 

그렇게 되면 

 

dist/

   index.css 로 생성되는데

 

  output:{
       publicPath:'',
       path:path.join(__dirname,'./dist/public'),
       filename: '[name].js'
    }, // 결과 파일

path 경로를 dist/public으로 잡으면 됩니다. 

 

그렇게 되면

 

dist/public/

            index.css

            index.js 등 으로 나타나게 됩니다.

 

그리고 나선 express 에서의 코드를 간단하게 수정합니다.

 

nunjucks.configure('views');  -> nunjucks.configure('dist/public/views')

    nunjucks.configure('dist/public/views',{
      autoescape:true,
      express:this.app
    });
    
    this.app.use(express.static(path.join(__dirname,"public")));
    this.app.use(favicon(path.join(__dirname,"/public/resource/fav","favicon.ico")));

 

** 혹시 의아 하실수 있습니다. dist에서 app.js가 실행되는데 왜 ./public/views 가 아니라 dist인지

configure에서 path 는 current working directory 이기 때문에  root/dist/public/views 로 되어집니다.

 

1. express template -> 경로 수정 

2. favicon ( express 에서 faviocn 을 미리 response하기 위해서 serve-favicon 이라는 모듈을 사용) 경로 변경

3. webpack config 수정 

const path = require('path');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
    target:"node",
    devServer:{
        contentBase:path.resolve(__dirname,'/src'),
        disableHostCheck:true,
        host:'0.0.0.0'
    },
    name:'network',
    mode:'development', // "production" | "development" | "none"
    devtool:'eval',  // source-map   hidden-source-map
    resolve:{
        modules:['node_modules'],
        extensions:['.scss','.css','.js'],
        alias:{
            "@gdlUtils":path.resolve(__dirname,'src/gdlUtils'),
            "@userCSS":path.resolve(__dirname,'src/public/css'),
            "@res":path.resolve(__dirname,'src/public/resource'),
            'public':path.join(__dirname,'public')
        }
    },
    entry:{
        'index':'./src/public/js/index.js'
  
    }, 
    module: {
    rules: [
  
      {
        test:/\.html$/, // html loader
        use:[
            {
                loader:'html-loader',
                options:{minimize:false}
            }
        ]
      },
  
       {
          test:/\.css$/i,
          use:[
              {
                loader:MiniCssExtractPlugin.loader,
              },
             'css-loader'
          ],
          
       },
      {
          test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
          use:{
              loader:'url-loader',
              options:{
                  name:'[path][hash].[ext]',
                  limit:10*1024 // 10kb
              }
          }
      },
      {
        test: /\.ico$/,
        loader: 'file-loader'
     }
        
           
    ]
    }, 
    
    plugins:[
        
        new HtmlWebPackPlugin({
            template:'./src/views/index.html',
            filename:'./views/index.html',
            showErrors:true
        }),
        new MiniCssExtractPlugin({
            filename:'[name].css',
            chunkFilename:'[id].css'
        }),
        new CopyWebpackPlugin([
            {
                context:'./src/public/resource/',
                from:'**/*',
                to:'./resource'
            },
        ])
    ],
    optimization:{},
    output:{
       publicPath:'',
       path:path.join(__dirname,'./dist/public'),
       filename: '[name].js'
    }, 
  }

 

4. 환경에 맞게 pakcage.json 수정 ( scripts 부분만 )

  "scripts": {
    "build": "webpack -p",
    "nms-dev": "webpack && tsc && node dist",
    "dev": "nodemon --exec npm run nms-dev",
    "restart": "pm2 restart 'NMS'",
    "deploy": "webpack && tsc && pm2 start dist --name 'NMS'",
    "stop": "pm2 stop 'NMS'",
    "delete": "pm2 delete 'NMS'",
    "log": "pm2 log 'NMS'",
    "abcde": "tsc && node dist",
    "be-dev": "nodemon --exec npm run abcde",
    "tsc": "tsc",
    "f": "pm2 stop 'NMS' && pm2 delete 'NMS'",
    "webpack": "webpack"
  },

network 경로 위치에서 npm run nms-dev 라고 입력 해봅시다

 

[결과 화면]

 

이로써 기본적인 대쉬보드를 만들기 위한 아주 최소한의 기본작어빙 완료 되었습니다.

 

앞으로 해야할건 nunjucks를 통한 템플릿 화 + 자동화된 케이스 및 코드 작성 연결

 

nunjucks를 -> SPA FE로 포팅 정도가 있겠네요

 

(이번에는 너무많이 작업해놔서 포스팅을 못할정도가 되지 않길 )

 

긴글 읽어주셔서 감사합니다~

 

질문은 언제나 환영합니다.

 

소스코드 : 

https://github.com/lgance/network/commits/master

 

lgance/network

Contribute to lgance/network development by creating an account on GitHub.

github.com

의 Commit 13499cfa87c1aab7c4506bf54b561ba4090f6937 을 찾으시면 됩니다.

 

커밋 메시지는 typeScript & express boilerplate 입니다. 

감사합니다. ~

+ Recent posts