프론트엔드 개발환경의 이해와 실습 1

1️⃣

🖥 NPM

1 - 1. 프로젝트 생성

1 - 2. 외부 패키지를 관리하는 방법

"dependencies": {
    "react": "^16.13.1"
    }

🖥 웹팩(Webpack) - 기본편

2 - 1. 웹팩이 필요한 이유와 기본 동작

// math.js
function sum(a, b){
    return a + b;
}

// app.js
console.log(sum(1, 2));
// math.js
var math = math || {};

(function(){
  function sum(a, b){
      return a + b;
  }
  math.sum = sum;
})()

// app.js
console.log(math.sum(1, 2));

AMD(Asynchronous Module Definition)는 비동기로 로딩되는 환경에서 모듈을 사용하는 것이 목표다. 주로 브라우져 환경이다.

// math.js
export function sum(a, b){
    return a + b
}

// app.js
import * as math from './math'
console.log(math.sum(1, 2));
// index.html
<script type="module" src="app.js"></script>

html 파일을 바로 열고싶을때 $ open index.html(파일이름) 간단하게 서버를 킬때 $ npm lite-server

2 - 2. 엔트리/아웃풋 실습

$ node_modules/.bin/webpack –help

  • webpack 필수요소
  • 1️⃣ –mode : “development”, “production”, “none” / 개발환경이냐 운영환경이냐에 따라 설정
  • 개발용 정보 추가 -> development / 운영에 배포하기위한 최적화 설정 -> production
  • 2️⃣ –entry : 모듈의 시작점 / 엔트리를 지정해줘야함 ( 필수 )
  • 엔트리를 통해서 웹팩이 모든 모듈을 하나로 합치고 그 결과를 저장하는경로를 -> output

// webpack.config.js
const path = require('path') // node 모듈

module.exports = {
  // module.exports -> node 의 모듈 시스템
  mode: "development",
  entry: {
    main: "./src/app.js",
    // 경로 뿐만 아니라 main 이라는 key 값도 설정
  },
  output: {
    path: path.resolve("./dist"),
    // path 는 output 디렉토리명을 입력, 절대경로 입력
    filename: "[name].js",
    // entry에서 설정한 key값으로 설정( ex main)
    // entry 가 여러개일수도있기때문에, 동적으로 만들어내기위해 [name] 으로 설정
  },
};

2 - 3. 엔트리와 아웃풋(실습)

2 - 4. 엔트리와 아웃풋(풀이)

2 - 5. 로더

// my-webpack-loader.js
module.exports = function myWebpackLoader(content){
    // 로더는 함수형태로 작성한다.
    console.log('myWebpackLoader 작동함');
    return content;
}

// webpack.config.js
const path = require('path')

module.exports = {
    mode: "development",
    entry: {
        main: "./src/app.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "[name].js"
    },
    module: {
        rules: [
            {
                test: /\.js/$,
                // test : 로더가 처리해야할 파일의 패턴 (정규표현식)
                // js로 끝나는 모든 파일
                use: [
                    path.resolve('./my-webpack-loader.js')
                ]
                // use : 사용할 로더 명시
                // 모든 자바스크립트 코드에 대해서 my-webpack-loader 가 실행되게끔 하는 설정
            }
        ]
    }
}

2 - 6. 자주 사용하는 로더

    test: /\.css$/,
    use: [
        'css-loader'
    ]
    test: /\.css$/,
    use: [
        'style-loader',
        'css-loader'
    ]
    test: /\.png$/,
    use: [
        'file-loader'
    ]

webpack 은 빌드 할때마다 유니크한 값을 생성 -> 해쉬값 캐쉬갱신을 위해 / 정적파일의 경우 브라우저에서 캐쉬 흔함 js, img, css, font 등 성능을 위해 캐쉬 -> 파일내용이 달라지고 이름이 같으면 이전의 캐쉬로 저장했던 내용을 브라우저가 사용 -> 이를 예방하는 방법중 하나가 이름을 변경

{
    test: /\.(png|jpg|gif|svg)$/,
    loader: 'file-loader',
    options: {
        publicPath: './dist/',
        name:'[name].[ext]?[hash]'
    }
    // publicPath : 파일로드가 처리하는 파일을 모듈로 사용했을때 경로앞에 추가되는 문자열
    // 우리는 output 을 dist로 설정했기때문에 여기서도 dist
    // 파일을 호출할때 앞에 dist 를 붙이고 호출한다.
    // name : 파일로더가 파일아웃풋에 복사할때 사용하는 파일이름
    // 원본파일명 = [name], 확장자명 = [ext], 
}
// app.js
import './app.css';
import nyancat from './nyancat.jpg'

document.addEventListener('DOMContentLoaded', ()=>{
    document.body.innerHTML = `
        <img src="${nyancat}" />
    `
})
// 돔이 만들어 졌을때 이미지태그를 추가

// webpack.config.js
 {
    test: /\.(png|jpg|gif|svg)$/,
    loader: 'url-loader',
    options: {
        publicPath: './dist/',
        name:'[name].[ext]?[hash]',
        limit: 20000, //20kb
    }
    // limit : 파일 용량 셋팅, 20kb 미만의 파일은 url-loader 로 base64로 변환
    // limit 이상일때는 file-loader 실행
    // limit 미만은 js 문자열로 변환 / 그 이상은 파일로 복사
}

2 - 7. 로더(실습)

"scripts": {
    "build": "webpack --progress"
},

2 - 8. 로더(풀이)

$ rm -rf dist : dist 폴더 삭제

2 - 9. 플러그인

// my-webpack-plugin.js
class MyWebpackPlugin {
    // js 에서 클래스를 만들때 앞은 대문자로 !
    apply(compiler) { // 플러그인의 메소드
        compiler.hooks.done.tap("My Plugin", stats => {
            // 플러그인이 완료됬을때 동작하는 콜백함수
            console.log("MyPlugin: done")
        })
    }
}

module.exports = MyWebpackPlugin

// webpack.config.js
const MyWebpackPlugin = require('./my-webpack-plugin')

plugins: [
    // 플러그인은 plugins 라는 배열에 추가
    // 이후 만든 클래스 생성자 함수를 가져온다
    new MyWebpackPlugin(),
]
// my-webpack-plugin.js

// compiler.plugin() 함수로 후처리한다
    compiler.plugin("emit", (compilation, callback) => {
        const source = compilation.assets["main.js"].source()
        // 그중 main.js 소스코드를 가져오는 함수.
        compilation.assets['main.js'].source = () => {
            const banner = [
                '/**',
                ' * 이것은 BannerPlugin이 처리한 결과입니다.',
                ' * Build Date: 2019-10-10',
                ' */'
            ].join('\n');
            return banner + '\n' + source;
        }
        callback()
    })
    // compilation 을 통해서 웹팩이 빌드한 결과물에 접근할수있다.
}

2 - 10. 자주 사용하는 플러그인

// webpack.config.js
const webpack = require('webpack')

plugins: [
    new webpack.BannerPlugin({
        // 객체 전달
        banner: '이것은 배너입니다.'
    })
]

// webpack.config.js
const webpack = require('webpack')
const childProcess = require('child_process')
//childProcess 를 이용해서 터미널명령어를 확인할 수있다.

plugins: [
    new webpack.BannerPlugin({
        // 객체 전달
        banner: `
        Build Date: ${new Date().toLocaleString()}
        Commit version: ${ childProcess.execSync('git rev-parse --short HEAD') }
        Author: ${childProcess.execSync('git config user.name')}
        `
    })
    // childProcess.execSync('터미널명령어를 넣어준다.')
]
// webpack.config.js
const webpack = require('webpack')

plugins: [
    new webpack.DefinePlugin({
        TWO:'1+1',
        // 어플리케이션에서는 TWO 라는 전역변수로 접근할수있고, '1+1' 은 코드이기때문에 2 가 찍힌다.
        TWOTWO:JSON.stringify('1+1')
        // 코드가 아닌 값을 넣고싶다면 위와 같이 JSON.stringify 이용
        'api.domain': JSON.stringify('http://dev.api.donmain.com')
        // 위와 같은 형태의 객체타입도 지원한다.
    })
]

// src/app.js
console.log(process.env.NODE_ENV); // -> webpack.config.js 에서 mode: "development"이기때문에 development 가 찍힌다.
console.log(TWO); // -> 2 가 찍힘
console.log(TWOTWO); // -> 1 + 1 찍힘
console.log(api.domain); // -> http://dev.api.donmain.com 찍힘
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')

new HtmlWebpackPlugin({
    // 옵션을 전달할때 template 경로를 전달할수있다.
    template: './src/index.html'
}),

// webpack.config.js
    {
    test: /\.(png|jpg|gif|svg)$/,
    loader: "url-loader",
    options: {
        // publicPath: "./dist/",
        // dist/index 로 경로가 바꼈기때문에 publicpath 를 지워줘도 가능
    },
    },
// index.html
<title>Document <%= env %></title>
<!-- <%= env %> : ejs 문법 / env라는 변수를 넣을수있는 템플릿 문법 -->

// webpack.config.js
new HtmlWebpackPlugin({
    template: './src/index.html',
    templateParameters: {
        env : process.env.NODE_ENV === 'development' ? '(개발용)' : ''
    }
}),
// webpack.config.js
new HtmlWebpackPlugin({
    //...
    minify: {
        collapseWhitespace: true,
        // 공백 제거
        removeComments: true
        // 주석 제거
      }
}),
// webpack.config.js
new HtmlWebpackPlugin({
    //...
    minify: process.env.NODE_ENV === 'production' ? {
        collapseWhitespace: true,
        removeComments: true
    } : false
}),
// webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
// CleanWebpackPlugin 은 default export 되어 있지 않기때문에 {} 에 넣어준다.

  plugins: [
    //... 웹팩 플러그인들
    new CleanWebpackPlugin()
  ],
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

  plugins: [
    new CleanWebpackPlugin(),
    ...(process.env.NODE_ENV === 'production' 
      ? [new MiniCssExtractPlugin({ filename: '[name].css' })] // filename: 생성될 파일이름
      : [])
      // 나머지 연산자 이용
      // 환경변수에 따라서 플러그인을 키거나 끌수있다.
      // 다른 플러그인과 다르게 로더를 설정해주어야 한다.
  ],

  // 이후 css 로더도 수정해주어야한다.
   module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          process.env.NODE_ENV === 'production'
          ? MiniCssExtractPlugin.loader
          : "style-loader", 
          "css-loader"
        ],
      },
    ],
  },

⌨️정리