프론트엔드 개발환경 - 웹팩(기본)

200908 webpack icon

프론트엔드 개발환경 - 웹팩(기본)

정보 : 김정환님의 프론트엔드 개발환경의 이해와 실습을 공부하고 정리한 내용.
해당 내용은 김정환님의 블로그 내용을 토대로 재작성되었으며 내용을 추가하거나 일부 변경했습니다.

1. 배경 🚀

Javascript로 개발을 할 때 모듈의 필요성과 한계가 나타나고 그에 따라 웹팩(Webpack)이 등장하게 되었다.

function sum(a, b) {
  return a + b;
} // 전역 공간에 sum이 노출

sum(1, 2); // 결과는 3

sum = 3;
sum(1, 2); // Uncaught TypeError: sum is not a function

상단처럼 전역(Global)에서 설정된 변수의 내용이 임의로 변하는 것을 막는 방법이 필요했다.

1.1. IIFE 방식의 모듈

IIFE(즉시 실행 함수 표현)는 즉시 함수를 실행하여 외부에서 접근을 막는 함수이다.

var math = math || {}; // math 네임스페이스

(function () {
  function sum(a, b) {
    return a + b;
  }
  math.sum = sum; // 네이스페이스에 추가
})();

math.sum(1, 2); // 결과는 3

sum(1, 2); // Uncaught TypeError: sum is not a function

1.2. 다양한 모듈 스펙

자바스크립트의 모듈을 구현하는 대표적인 명세는 AMDCommonJS가 있다.

그리고 ES2015에서 표준 모듈 시스템이 나오게 되었다.

./math.js

export function sum(a, b) {
  return a + b;
}

./app.js

import * as math from './math.js';
math.sum(1, 2); // 3

문제는 모든 브라우저에서 이 모듈 기능을 지원하지 않는다는 것이다 😱.

2. 웹팩(Webpack) 📦

웹팩은 여러 개의 파일을 하나로 합쳐주는 번들러(bundler)다.

그럼 webpack을 설치해보자.

$ npm install -D webpack webpack-cli

webpack을 설치한 후에 옵션을 보자.

# node_modules/.bin/webpack --help를 입력하면 정보를 알 수 있다.
$ node_modules/.bin/webpack --help

  --mode                 Enable production optimizations or development hints.
                                     [선택: "development", "production", "none"]
  --entry      The entry point(s) of the compilation.                   [문자열]
  --output, -o                  The output path and file for compilation assets

여기서 --mode, --entry, --output 세 개 옵션만 사용하면 코드를 묶을 수 있다.

$ node_modules/.bin/webpack --mode development --entry ./src/app.js --output dist/main.js
  • --mode는 웹팩 실행 모드를 의미하는데 개발 버전인 development를 지정한다.
  • --entry는 시작점 경로를 지정하는 옵션이다.
  • --output은 번들링 결과물을 위치할 경로다.

위 명령어를 실행하면 dist/main.js에 번들된 결과가 저장된다.

> lecture-frontend-dev-env@1.0.0 build /Users/davidyang/Documents/lecture-frontend-dev-env
> webpack

Hash: 5610665b6c989a4f05ee
Version: webpack 4.44.1
Time: 97ms
Built at: 2020-09-08 16:04:38
  Asset      Size  Chunks             Chunk Names
main.js  19.9 KiB    main  [emitted]  main
Entrypoint main = main.js
[./src/app.js] 145 bytes {main} [built]

그리고 index.html에 로딩하면 번들링의 결과와 동일하게 된다.

<script src="dist/main.js"></script>

또한 webpack.config.js를 생성해 사용할 수 있다.

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/app.js',
  },
  output: {
    filename: '[name].js',
    path: path.resolve('./dist'),
  },
};

그리고 package.json에 명령어를 추가한다.

{
  "scripts": {
    "build": "./node_modules/.bin/webpack"
    //"build": "webpack" // 이렇게 해도 된다.
  }
}

이제 터미널에 npm run build로 webpack을 실행할 수 있다.

$ npm run build

이후 마찬가지로 index.html에 로딩하면 된다.

3. 로더 🔋

웹팩은 모든 파일을 모듈로 관리하는데 이미지, 폰트, 스타일시트도 모두 모듈로 관리한다.

웹팩은 자바스크립트밖에 인식할 수 없기 때문에 이미지, 폰트, 스타일시트를 웹팩이 이해할 수 있도록 변경시켜야 한다.

로더가 바로 이 역할을 한다.

3.1. 로더의 역할

로더는 타입스크립트 같은 다른 언어를 자바스크립트 문법으로 변환해 주거나 이미지를 data URL 형식의 문자열로 변환한다.

뿐만 아니라 CSS 파일을 자바스크립트에서 직접 로딩할 수 있도록 해준다.

3.2. 커스텀 로더 만들기

커스텀으로 로더를 만들어 보자.

./myloader.js

module.exports = function myloader(content) {
  console.log('myloader 테스트');
  return content;
};

커스텀 로더를 제작한 후에 웹팩 설정파일의 module 객체에 추가한다.

./webpack.config.js

module: {
  rules: [{
    test: /\.js$/, // .js 확장자로 끝나는 모든 파일.
    use: [path.resolve('./myloader.js')] // 방금 만든 로더를 적용한다.
  }],
}

module.rules 배열에 모듈을 추가하는데 test와 use로 구성된 객체를 전달한다.

test에는 로딩에 적용할 파일을 지정한다.

use에는 패턴에 해당하는 파일에 적용할 로더를 설정하는 부분으로 방금 만든 myloader 함수의 경로를 지정한다.

npm run build을 실행해보자.

$ npm run build

> lecture-frontend-dev-env@1.0.0 build /Users/davidyang/Documents/lecture-frontend-dev-env
> webpack

myloader 테스트
Hash: 5610665b6c989a4f05ee
Version: webpack 4.44.1
Time: 115ms
Built at: 2020-09-09 23:16:09
  Asset      Size  Chunks             Chunk Names
main.js  19.9 KiB    main  [emitted]  main
Entrypoint main = main.js
[./src/app.js] 145 bytes {main} [built]

터미널에 myloader 테스트’ 문자열이 찍힌다.

myloader() 함수가 동작했다.

4. 자주 사용하는 로더 🌟 🔋

자주 사용하는 로더를 확인해보자.

4.1. css-loader

웹팩에 css-loader를 추가하여 모듈로 변환한다.

./app.js

import './style.css';

./style.css

body {
  background-color: green;
}

CSS 파일을 자바스크립트에서 불러와 사용할 수 있게 CSS를 모듈로 변환해보자.

css-loader를 설치하자.

npm install -D css-loader

웹팩 설정에 로더를 추가한다.

./webpack.config.js:

// 미완성된 css-loader.
module.exports = {
  module: {
    rules: [
      {
        test: /\.css\$/, // .css 확장자로 끝나는 모든 파일.
        use: ['css-loader'], // css-loader를 적용한다.
      },
    ],
  },
};

이제 웹팩은 모듈을 검색하다가 CSS 파일을 찾으면 css-loader로 처리하게 된다.

use.loader에 로더 경로를 설정하는 대신 배열에 로더 이름을 문자열로 전달해도 된다.

이제 npm run build를 하면 CSS가 자바스크립트로 변환된 것을 확인할 수 있다.

4.2. style-loader

모듈로 변경된 스타일 시트는 DOM에 추가되어야 브라우저가 해석할 수 있다.

css-loader로 처리하면 자바스크립트 코드로만 변경되었을 뿐 DOM에 적용되지 않았기 때문에 스타일이 적용되지 않는다.

style-loader자바스크립트로 변경된 스타일을 동적으로 DOM에 추가하는 로더이다.

🚨 중요 :CSS를 번들링하기 위해서는 css-loader와 style-loader를 함께 사용한다.

먼저 스타일 로더를 다운로드한다.

$ npm install -D style-loader

그리고 웹팩 설정에 로더를 추가한다.

./package.json

module.exports = {
  module: {
    rules: [
      {
        test: /\.css\$/,
        use: ['style-loader', 'css-loader'], // style-loader를 앞에 추가한다.
      },
    ],
  },
};

배열로 설정하면 ~뒤에서부터 앞으로 순서대로 로더가 동작~한다.

위 설정은 모든 .css 확장자로 끝나는 모듈을 읽어 들여 css-loader를 적용하고 그다음 style-loader를 적용한다.

4.3. file-loader

소스코드에서 사용하는 모든 파일을 모듈로 사용하게 할 수 있다.

파일을 모듈 형태로 지원하고 웹팩 아웃풋에 파일을 옮겨주는 것file-loader가하는 일이다.

$ npm install -D file-loader

예를 들어 CSS에서 url() 함수에 이미지 파일 경로를 지정할 수 있는데 웹팩은 file-loader를 이용해서 이 파일을 처리한다.

./style.css

body {
  background-image: url(bg.png);
}

위에서 배경 이미지를 bg.png 파일로 지정했다.

웹팩은 엔트리 포인트인 app.js가 로딩하는 style.css 파일을 읽을 것이다.

그리고 이 스타일시트는 url() 함수로 bg.png를 사용하는데 이때 로더를 동작시킨다.

./webpack.config.js

module.exports = {
  module: {
    rules: [
      // 이미 module과 rules가 존재한다면 여기서부터(오브젝트 구간) rules 안에 추가하면 된다.
      {
        test: /\.png$/, // .png 확장자로 마치는 모든 파일.
        loader: 'file-loader', // 파일 로더를 적용한다.
      },
    ],
  },
};

웹팩이 .png 파일을 발견하면 file-loader를 실행할 것이다.

로더가 동작하고 나면 아웃풋에 설정한 경로로 이미지 파일이 복사된다.

파일명은 해시코드로 변경되는데 이대로 index.html 파일을 브라우저에 로딩하면 이미지를 제대로 로딩하지 못할 것이다.

CSS를 로딩하면 background-image: url(bg.png) 코드에 의해 동일 폴더에서 이미지를 찾으려고 시도할 것이다.

그러나 웹팩으로 빌드한 이미지 파일은 output인 dist 폴더 아래로 이동했기 때문에 이미지 로딩에 실패할 것이다.

file-loader 옵션을 조정해서 경로를 바로 잡아 주어야 한다.

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif|svg)$/, // .(png|jpg|gif|svg) 확장자로 마치는 모든 파일.
        loader: 'file-loader',
        options: {
          publicPath: './dist/', // prefix를 아웃풋 경로로 지정.
          name: '[name].[ext]?[hash]', // 파일명 형식.
        },
      },
    ],
  },
};

publicPath 옵션은 file-loader가 처리하는 파일을 모듈로 사용할 때 경로 앞에 추가되는 문자열이다.

output에 설정한 ‘dist’ 폴더에 이미지 파일을 옮길 것이므로 publicPath 값을 이것으로 지정했다.

예를 들어 파일을 사용하는 측에서는 ‘bg.png’를 ‘dist/bg.png’로 변경하여 사용할 것이다.

또한 name 옵션을 사용했는데 이것은 로더가 파일을 아웃풋에 복사할 때 사용하는 파일 이름이다.

기본적으로 설정된 해시값을 쿼리스트링으로 옮겨서 ‘bg.png?6453a9c65953c5c28aa2130dd437bbde’ 형식으로 파일을 요청하도록 변경했다.

이렇게 스타일시트에서 불러온 파일이 동작한다.

4.4. url-loader

사용하는 이미지 개수가 많다면 네트워크 리소스를 사용하는 부담이 있고 사이트 성능에 영향을 줄 수도 있다.

만약 한 페이지에서 작은 이미지를 여러 개 사용한다면 Data URI Scheme을 이용하는 방법이 더 나은 예도 있다.

이미지를 Base64로 인코딩하여 문자열 형태로 소스코드에 넣는 형식이다.

url-loader는 이러한 처리를 자동화해주는 녀석이다.

먼저 로더를 설치한다.

$ npm install -D url-loader

그리고 웹팩 설정을 추가한다.

./webpack.config.js

{
  test: /\.(png|jpg|gif|svg)$/,
  use: {
    // 기존 file-loader가 있다면 이름만 url-loader로 변경하면 된다.
    loader: 'url-loader', // url 로더를 설정한다.
    options: {
      publicPath: './dist/', // file-loader와 동일.
      name: '[name].[ext]?[hash]', // file-loader와 동일.
      limit: 5000 // 5kb 미만 파일만 data url로 처리.
    }
  }
}

file-loader와 옵션 설정이 거의 비슷하고 마지막 limit 속성만 추가했다.

모듈로 사용한 파일 중 크기가 5kb 미만인 파일만 url-loader를 적용하는 설정이다.

만약 이보다 크면 file-loader가 처리하는데 옵션 중 fallback 기본값이 file-loader이기 때문이다.

아이콘처럼 용량이 적거나 사용 빈도가 높은 이미지는 파일을 그대로 사용하기보다는 Data URI Scheeme을 적용하기 위해 url-loader를 사용하는 것을 권장한다.

5. 플러그인 🔌

로더가 파일 단위로 처리하는 반면 플러그인번들된 결과물을 처리한다.

5.1. 플러그인의 역할

번들된 자바스크립트를 난독화 한다거나 특정 텍스트를 추출하는 용도로 사용한다.

5.2. 커스텀 플러그인 만들기

웹팩 문서의 Writing a plugin을 보면 클래스로 플러그인을 정의하도록 한다.

./myplugin.js

class MyPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('My Plugin', (stats) => {
      console.log('MyPlugin: done');
    });
  }
}

module.exports = MyPlugin;

로더와 다르게 플러그인은 클래스로 제작한다.

apply 함수를 구현하면 되는데 이 코드에서는 인자로 받은 compiler 객체 안에 있는 tap() 함수를 사용하는 코드다.

플러그인 작업이 완료되는(done) 시점에 로그를 찍는 코드로 보인다.

플러그인을 웹팩 설정에 추가한다.

./webpack.config.js

const MyPlugin = require('./myplugin');

module.exports = {
  plugins: [new MyPlugin()],
};

웹팩 설정 객체의 plugins 배열에 설정한다.

클래스로 제공되는 플러그인의 생성자 함수를 실행해서 넘기는 방식이다.

이걸 이용해서 번들 결과 상단에 아래와 같은 배너를 추가하는 플러그인으로 만들어 보자.

./myplugin.js

class MyPlugin {
  apply(compiler) {
    compiler.plugin('emit', (compilation, callback) => {
      const source = compilation.assets['main.js'].source();
      compilation.assets['main.js'].source = () => {
        const banner = [
          '/**',
          ' * 이것은 BannerPlugin이 처리한 결과입니다.',
          ' * Build Date: 2019-10-10',
          ' */',
          '',
        ].join('\n');
        return banner + '\n' + source;
      };

      callback();
    });
  }
}

module.exports = MyPlugin;

번들 소스를 얻어오는 함수 source()를 재정의했다.

배너 문자열과 기존 소스 코드를 합친 문자열을 반환하도록 말이다.

이렇게 커스텀 플러그인을 만들 수 있다.

6. 자주 사용하는 플러그인 🌟 🔌

웹팩에서 자주 사용하는 플러그인에 대해 알아보자.

6.1. BannerPlugin

웹팩에서 기본으로 제공하는 플러그인으로 BannerPlugin은 웹팩으로 빌드한 결과물에 빌드 정보나 커밋 버전을 입력하여 추가할 수 있다.

./webpack.config.js

const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.BannerPlugin({
      banner: '이것은 배너입니다.',
    }),
  ],
};

생성자 함수에 전달하는 옵션 객체의 banner 속성에 문자열을 전달한다.

웹팩 컴파일 타임에 얻을 수 있는 정보, 가령 빌드 시간이나 커밋정보를 전달하기 위해 함수로 전달할 수도 있다.

new webpack.BannerPlugin({
  banner: () => `빌드 날짜: ${new Date().toLocaleString()}`,
});

배너 정보가 많다면 별로 파일로 분리하자.

const banner = require('./banner.js');

new webpack.BannerPlugin(banner);

빌드 날짜 외에 커밋 해시와 빌드한 유저 정보까지 추가해 보자.

./banner.js

const childProcess = require('child_process');

module.exports = function banner() {
  const commit = childProcess.execSync('git rev-parse --short HEAD');
  const user = childProcess.execSync('git config user.name');
  const date = new Date().toLocaleString();

  return (
    `commitVersion: ${commit}` + `Build Date: ${date}\n` + `Author: ${user}`
  );
};

이렇게 하면 빌드한뒤 플러그인이 처리하여 빌드 날짜와 커밋, 유저 정보가 나타난다.

6.2. DefinePlugin

애플리케이션은 개발환경과 운영환경으로 나눠서 운영한다.

같은 소스 코드를 두 환경에 배포하기 위해서는 이러한 환경 의존적인 정보를 소스가 아닌 곳에서 관리하는 것이 좋다.

웹팩은 이러한 환경 정보를 제공하기 위해 기본으로 DefinePlugin을 제공한다.

./webpack.config.js

const webpack = require('webpack');

export default {
  plugins: [new webpack.DefinePlugin({})],
};

빈 객체를 전달해도 기본적으로 넣어주는 값이 있다.

노드 환경정보는 process.env.NODE_ENV에서 확인하는데 웹팩 설정의 mode에 설정한 값이 여기에 들어간다.

“development”를 설정했기 때문에 애플리케이션 코드에서 process.env.NODE_ENV 변수로 접근하면 “development” 값을 얻을 수 있다.

./app.js

console.log(process.env.NODE_ENV); // "development"

이 외에도 웹팩 컴파일 시간에 결정되는 값을 전역 상수 문자열로 애플리케이션에 주입할 수 있다.

new webpack.DefinePlugin({
  TWO: '1+1',
});

TWO라는 전역 변수에 1+1 이란 코드 조각을 넣었다.

실제 애플리케이션 코드에서 이것을 출력해보면 ~2~가 나올 것이다.

./app.js

console.log(TWO); // 2

코드가 아닌 값을 입력하려면 문자열화 한 뒤 넘긴다.

new webpack.DefinePlugin({
  VERSION: JSON.stringify('v.1.2.3'),
  PRODUCTION: JSON.stringify(false),
  MAX_COUNT: JSON.stringify(999),
  'api.domain': JSON.stringify('http://dev.api.domain.com'),
});

./app.js

console.log(VERSION); // 'v.1.2.3'
console.log(PRODUCTION); // true
console.log(MAX_COUNT); // 999
console.log(api.domain); // 'http://dev.api.domain.com'

빌드 타임에 결정된 값을 애플리케이션에 전달할 때는 이 플러그인을 사용하자.

6.3. HtmlWebpackPlugin

HtmlWebpackPluginHTML 파일을 후처리하는데 사용하는 써드 파티 패키지이다.

빌드 타임의 값을 넣거나 코드를 압축할 수 있다.

먼저 패키지를 다운로드한다.

$ npm install -D html-webpack-plugin

이 플러그인으로 빌드하면 HTML파일로 아웃풋에 생성될 것이다.

index.html 파일을 src/index.html로 옮긴뒤 다음과 같이 작성해 보자.

./src/index.html

<!DOCTYPE html>

<html>
  <head>
    <title>타이틀<%= env %></title>
  </head>
  <body>
    <!-- 로딩 스크립트 제거 -->
    <!-- <script src="dist/main.js"></script> -->
  </body>
</html>

타이틀 부분에 ejs 문법을 이용하는데 = env 는 전달받은 env 변수 값을 출력한다.

HtmlWebpackPlugin변수에 데이터를 주입해 동적으로 HTML 코드를 생성한다.

그뿐만 아니라 웹팩으로 빌드한 결과물을 자동으로 로딩하는 코드를 주입해 준다.

그래서 스크립트 로딩 코드도 제거한다.

./webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html', // 템플릿 경로를 지정.
      templateParameters: {
        // 템플릿에 주입할 파라매터 변수 지정.
        env: process.env.NODE_ENV === 'development' ? '(개발용)' : '',
      },
    }),
  ],
};

환경 변수에 따라 타이틀 명 뒤에 “(개발용)” 문자열을 붙이거나 떼거나 하도록 했다.

NODE_ENV=development로 설정해서 빌드하면 빌드결과 “타이틀(개발용)“으로 나온다.

NODE_ENV=production으로 설정해서 빌드하면 빌드결과 “타이틀”로 나온다.

개발 환경과 달리 운영 환경에서는 파일을 압축하고 불필요한 주석을 제거하는 것이 좋다.

./webpack.config.js

new HtmlWebpackPlugin({
  minify:
    process.env.NODE_ENV === 'production'
      ? {
          collapseWhitespace: true, // 빈칸 제거.
          removeComments: true, // 주석 제거.
        }
      : false,
});

(문서에는 minifiy 옵션이 웹팩 버전 3 기준으로 되어 있다.)

환경변수에 따라 minify 옵션을 켰다.

NODE_ENV=production npm run build로 빌드하면 아래처럼 코드가 압축된다.

물론 주석도 제거되었다.

정적파일을 배포하면 즉각 브라우저에 반영되지 않는 경우가 있다.

브라우저 캐시가 원인일 경우가 있는데 이를 위한 예방 옵션도 있다.

./webpack.config.js

new HtmlWebpackPlugin({
  hash: true, // 정적 파일을 불러올 때 쿼리문자열에 웹팩 해시값을 추가한다.
});

🚨 중요 hash: true 옵션을 추가하면 빌드할 시 생성하는 해시값을 정적파일 로딩 주소의 쿼리 문자열로 붙여서 HTML을 생성한다.

6.4. CleanWebpackPlugin

CleanWebpackPlugin빌드 이전의 결과물을 제거하는 플러그인이다.

빌드 결과물은 아웃풋 경로에 모이는데 과거 파일이 남아 있을 수 있다.

이전 빌드내용이 덮여 쓰이면 상관없지만 그렇지 않으면 아웃풋 폴더에 여전히 남아 있을 수 있다.

이러한 현상을 CleanWebpackPlugin로 해결해 보자.

먼저 패키지를 설치한다.

$ npm install -D clean-webpack-plugin

웹팩 설정을 추가한다.

./webpack.config.js

// clean-webpack-plugin가 default export가 아니기 때문에 {}로 받아야 한다.
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  plugins: [new CleanWebpackPlugin()],
};

이후 빌드를 하면 아웃풋 폴더인 dist 폴더가 모두 삭제된 후 결과물이 생성된다.

6.5. MiniCssExtractPlugin

스타일시트가 점점 많아지면 하나의 자바스크립트 결과물로 만드는 것이 부담일 수 있다.

번들 결과에서 스트일시트 코드만 뽑아서 별도의 CSS 파일로 만들어 역할에 따라 파일을 분리하는 것이 좋다.

브라우저에서 큰 파일 하나를 내려받는 것 보다, 여러 개의 작은 파일을 동시에 다운로드하는 것이 더 빠르다.

MiniCssExtractPluginCSS를 별로 파일로 뽑아내는 플러그인이다.

먼저 패키지를 설치한다.

$ npm install -D mini-css-extract-plugin

웹팩 설정을 추가한다.

./webpack.config.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  plugins: [
    ...(process.env.NODE_ENV === 'production'
      ? [new MiniCssExtractPlugin({ filename: `[name].css` })]
      : []),
  ],
};

프로덕션 환경일 경우만 이 플러그인을 추가했다.

filename에 설정한 값으로 아웃풋 경로에 CSS 파일이 생성될 것이다.

개발 환경에서는 css-loader에 의해 자바스크립트 모듈로 변경된 스타일시트를 적용하기 위해 style-loader를 권장한다.

반면 프로덕션 환경에서는 별도의 CSS 파일로 추출하는 플러그인을 적용한다면 다른 로더가 필요하다.

module.exports = {
  module: {
    rules: [
      // 기존 로더의 위치에 css 설정이 있을 경우 하단과 같이 수정하면 된다.
      {
        test: /\.css$/,
        use: [
          process.env.NODE_ENV === 'production'
            ? MiniCssExtractPlugin.loader
            : 'style-loader',
          'css-loader',
        ],
      },
    ],
  },
};

플러그인에서 제공하는 MiniCssExtractPlugin.loader 로더를 추가한다.

NODE_ENV=production npm run build로 빌드를 한다면 dist/main.css가 생성되고 index.html에 이 파일을 로딩하는 코드가 추가될 것이다.

7. 참조 📋

👋


Written by@davidyang2149 (About me)
Hello World! This is David Yang Dev Blogs.

GitHub