자바스크립트 모듈 / 모듈번들러 웹팩 (Javascript Module / Webpack)

SeoYng·2021년 2월 16일
1
post-thumbnail

📚 모듈

애플리케이션을 구성하는 개별적 요소로서 재사용 가능한 코드 조각
세부 사항을 캡슐화하고 공개가 필요한 API만을 외부에 노출

  • 자바스크립트는 다른 언어에서처럼 모듈 시스템이 자체적으로 지원되지는 않았음
  • 모듈 시스템의 필요성이 부각되어 CommonJS와 AMD라는 모듈 라이브러리에서 표준화
  • ES6에서 모듈 표준이 지정되긴 했지만, babel과 같은 번들러가 이 키워드를 인식하여 번들 후에는 CommonJS를 써서 require 문으로 바꾸어 주는 것이 일반적
  • 요즘 브라우저는 자체적으로 es6 표준을 지키는 모듈 시스템을 지원하지만, 여러 상황을 고려해 볼 때 babel과 같은 모듈 번들러를 사용하는것 권장

■ (CJS)CommonJS

동기로 동작하며 서버사이드 JavaScript 환경을 전제로 함
require / exports 키워드를 활용

  • 스코프(Scope): 독립적인 실행 영역
  • 정의(Definition): exports 객체를 이용하여 정의
  • 사용(Usage): require 함수를 이용하여 사용
// myModule.js

// require을 통해 변수에 담기
const calcModule = require('package/calculator');

function addAlert (a, b) {
    alert(calcModule.add(a, b));
}

// 다른 모듈로 추출
module.exports = {
  foobar: addAlert
};
// main.js
var my = require('myModule');
console.log(my.foobar);

// 동기로 동작
var foo = require('foo');
var bar = require('bar');

foo.log('It is foo');
bar.log('It is bar');

■ AMD(Asynchronous Module Definition)

비동기적 모듈 선언으로 필요한 모듈을 네트워크를 이용해 내려받아야 하는 브라우저 환경에서도 모듈을 사용할 수 있도록 표준을 만드는 일

  • 비동기 상황에서 자바스크립트 모듈을 사용하기 위해 CommonJS에서 함께 논의하다 합의점을 이루지 못하고 독립
  • 자바스크립트를 브라우저 밖으로 꺼내기 위해 탄생된 그룹

■ RequireJS

AMD 방식으로 구현된 가장 유명한 스크립트
define / require 키워드를 활용

<script src="require.js"></script>
// myModule.js

// 모듈을 선언부의 첫 번째 파라미터에 넣으면, 콜백 함수의 파라미터 안에 담김
define(['package/calculator'], function (calcModule) {
  // 의존 모듈들이 로딩 완료되면 콜백 함수를 실행
  // 로드된 종속 모듈 사용
  function addAlert (a, b) {
    alert(calcModule.add(a, b));
  }

  // 다른 모듈로 추출
  return {
    foobar: addAlert
  };
});
// main.js
require(['package/myModule'], function (myModule) {
  myModule.foobar(19, 2);
});

// 순서가 중요할 때 중첩으로 사용
require(['js/first'], function (first) {
  require(['js/second'], function (second) {
    //
  });
});

■ UMD

코드라기 보다는 디자인 패턴,
AMD와 CommonJS를 쓰는 두 그룹으로 나누어지다보니 서로 호환이 안됨
어떤 모듈을 쓰든지 동작되게 하기 위한 것

(function (root, factory) {
  if (typeof define === 'function' && define.amd) { // AMD
    define(['jquery', 'moduleA'], factory);
  } else if (typeof module === 'object' && module.exports) { // CommonJS
    module.exports = factory(require('jquery'), require('moduleA'));
  } else { // window
    root.myModule = factory(root.$, root.A); 
  }
}(this, function($, A) {
  return {
    attr1: $,
    attr2: A,
  };
});

■ ESM(ES6 Moudule)

네이티브 자바스크립트 모듈은 importexport 문(statement)에 의존적

# 모듈 밖으로 내보내려는 항목 앞에 (export를) 배치

❗️ 최상위 항목이어야 함 예를들어, 함수 안에서 export를 사용할 수 없음

//square.js

//한 개씩
export const name = 'square';
export const color = 'red';

// 여러 항목을 내보내는 더 편리한 방법
export { name, color };
//main.js
import { name, color } from './modules/square.js';

# exports default 이용

//square.js
export default randomSquare;
//main.js
import randomSquare from './modules/square.js'; // 괄호 없음
// import {default as randomSquare} from './modules/square.js';

# as 키워드를 새 함수의 이름으로 함께 사용

방법 1

// inside module.js
export {
  function1 as newFunctionName,
  function2 as anotherNewFunctionName
};

// inside main.js
import { newFunctionName, anotherNewFunctionName } from './modules/module.js';

방법 2

// inside module.js
export { function1, function2 };

// inside main.js
import { function1 as newFunctionName,
         function2 as anotherNewFunctionName } from './modules/module.js';

# * 사용 하여 모두 불러오기

import * as Square from './modules/square.js';
import * as Circle from './modules/circle.js';

console.log(Square.name)
console.log(Circle.name)

# 동적 로딩

import('/modules/myModule.js')
  .then((module) => {
    // Do something with the module.
  });
squareBtn.addEventListener('click', () => {
  import('//modules/dynamic-module/square.js').then((Module) => {
    let square1 = new Module.Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
    square1.draw();
    square1.reportArea();
    square1.reportPerimeter();
  })
});

MDN
naver D2
poiemaweb
참고 글


📚 WebPack

최신 프런트엔드 프레임워크에서 가장 많이 사용되는 모듈 번들러
자원을 모두 각각의 모듈로 보고 이를 조합해서 병합된 하나의 결과물을 만드는 도구

■ 등장배경

  • 파일 단위의 자바스크립트 모듈 관리의 필요성
  • 웹 개발 작업 자동화 도구
  • 웹 애플리케이션의 빠른 로딩 속도와 높은 성능
  • 전역스코프 오염 위험

■ 장점

  • 파일을 합쳐서 요청하기 때문에 request를 줄이는데 이점이 있음
  • 모듈간의 의존관계를 알아서 처리해줌 (연관관계 파악)
  • 브라우저를 위해 미리 컴파일하는 도구
  • 빌드 파일을 일일히 작성하지 않고 설정파일로 셋팅해줌
  • 기본적으로 필요한 자원은 미리 로딩하는게 아니라 그 때 요청하자는 철학을 갖고 있음
  • Code Splitting 기능을 이용하여 원하는 모듈을 원하는 타이밍에 로딩 (Dynamic Loading & Lazy Loading)

■ 설정파일 예시

📃 webpack.config.json

var MiniCssExtractPlugin = require("mini-css-extract-plugin");
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    // mode - 개발 / 운영 / none 등 설정
	mode: 'production',
    // entry -  최초 진입점이자 자바스크립트 파일 경로 : 여러개 가능
	entry: {
      login: './src/LoginView.js',
      main: './src/MainView.js'
    },
    // output - 웹팩을 돌리고 난 결과물의 파일 경로
	output: {
			path: path.resolve(__dirname, 'build'),
			filename: '[chunkhash].bundle.js' // 캐시 방지
	},
    // module(loader) - 자바스크립트 파일이 아닌 웹 자원들을 변환할 수 있도록 도와주는 속성
	module: { // 해석하고 변환하는 과정에 관여
		rules: [
          {
			test: /\.m?js$/, // 로더를 적용할 파일 유형 (일반적으로 정규 표현식 사용)
			exclude: /(node_modules|bower_components)/, // 적용 제외시킬 대상
			use: { // 해당 파일에 적용할 로더의 이름
				loader: 'babel-loader',
				options: {
					presets: ['@babel/preset-env']
				}
			}
		  },
          {
            test: /\.scss$/,
            use: ['style-loader', 'css-loader', 'sass-loader'] // 오른쪽에서 왼쪽 순으로 적용
          }
        ]
	},
    // resolve - 파일의 해석방식 정의
    resolve: {
      alias: {
        'vue$': 'vue/dist/vue.esm.js'
      },
      extensions: ['*', '.js', '.vue', '.json']
    },
    // plugins - 웹팩의 기본적인 동작에 추가적인 기능을 제공, 해당 결과물의 형태를 바꾸는 역할
    plugins: [ // 플러그인의 배열에는 생성자 함수로 생성한 객체 인스턴스만 추가 가능
      new MiniCssExtractPlugin(), 
      new HtmlWebpackPlugin({
        // index.html 템플릿을 기반으로 빌드 결과물을 추가해줌
        template: 'index.html',
      }),
    ],
    // devServer - 파일 변경시 바로 반영되는 서버 사용
    devServer: {
        historyApiFallback: true,
        noInfo: true,
        overlay: true
    },
    // performance - 성능 관련 설정
    performance: {
      hints: false
    },
    // devtool - 개발자도구 관련
	devtool: 'source-map',  // 난독화된 파일를 source탭에서 분리해서 보여주는 속성

};

📃 package.json

{
  "name": "getting-started",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack" // npm run build로 실행 (--mode=none) // 설정가능 but 비효율 -> webpackConfig 사용
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.42.0",
    "webpack-cli": "^3.3.11"
  },
  "dependencies": {
    "lodash": "^4.17.15"
  }
}

■ WebPack Dev Server

파일이 변경 되었을 때 매번 웹팩 명령어를 실행하지 않아도 저장하면 바로 반영해주는 서버
명령어를 치고 브라우저를 새로 고침하는 시간, 빌드 시간을 줄여주기 때문에
웹팩 기반의 웹 애플리케이션 개발에 필수로 사용

  • 파일 변경 후 저장시 로컬서버에 바로 반영
  • 빌드 결과물이 나오는 것이 아니라 메모리에 올려놓기만 함

📃 webpack.config.json

var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  ...
  devServer: {
    port: 9000, // 포트 설정
    hot: true, // 핫리로드 사용 ㅡ 화면전체 갱신이 아니라 바뀐부분만 리프래시
  },
  ...
};

📃 package.json

{
  ...
  "scripts": {
    "dev": "webpack serve --progress", // npm run dev로 서버띄움
    "build": "webpack" 
  },
  ...
}

■ 최적화

👀 웹팩심화 - 김정환님

  • optimazation 속성으로 최적화: 파일 압축
  • 코드 스플리팅: 결과물을 여러개로 쪼개 브라우져 다운로드 속도를 높임 (다이나믹로드...)
  • externals: 이미 빌드된 파일 재빌드 하지 않고 복사(라이브러리 등)



웹팩 공식문서
loaders 문서
plugins 문서
웹팩 핸드북 - 캡틴판교님


profile
Junior Web FE Developer

0개의 댓글