[Webpack] 궁금해서 혼자 정리하는 웹팩 3

Dan·2022년 11월 15일
0

웹팩

목록 보기
3/8
post-thumbnail

Production


실제 배포 환경을 구축하기 위한 유틸리티와 좋은 사례들에 대해 알아보자.

Setup

development 와 production의 빌드 목표는 매우 다르다. development 같은 경우에는 강력한 소스 매핑을 통한 디버깅, localhost 서버에서 라이브 리로딩이나 hot module replacement 기능 위주로 사용하게 된다. 그에 반면 production에서는 로드 시간을 줄기위 위해 번들 최소화, 가벼운 소스맵 및 에셋 최적화에 초점을 맞춘다. 논리적으로는 webpack 설정을 둘로 나눠서 작업하면 되지만 이렇게 되면 중복 환경이 중첩되게 된다.

중복을 제거하기 위해 '공통'설정을 따로 분리하고 이를 development 또는 production에 합치기 위해서 webpack-merge 유틸리티를 사용한다.

npm install --save-dev webpack-merge

  webpack-demo
  |- package.json
  |- package-lock.json
 |- webpack.common.js
 |- webpack.dev.js
 |- webpack.prod.js
  |- /dist
  |- /src
    |- index.js
    |- math.js
  |- /node_modules
  • webpack.config.js
 const path = require('path');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   entry: {
     app: './src/index.js',
   },
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Production',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   },
 };
  • webpack.dev.js
 const { merge } = require('webpack-merge');
 const common = require('./webpack.common.js');

 module.exports = merge(common, {
   mode: 'development',
   devtool: 'inline-source-map',
   devServer: {
     static: './dist',
   },
 });
  • webpack.prod.js
 const { merge } = require('webpack-merge');
 const common = require('./webpack.common.js');

 module.exports = merge(common, {
   mode: 'production',
 });

위와 같이 설정을 하면 환경별로 merge를 통해서 원하는 환경설정을 가져와 쓸 수 있게 된다.

NPM Scripts

위에 정의한 설정 파일들을 사용하기 위해 npm scripts 파일을 수정해보자.

  {
    "name": "development",
    "version": "1.0.0",
    "description": "",
    "main": "src/index.js",
    "scripts": {
      // dev 모드에서 사용
     "start": "webpack serve --open --config webpack.dev.js",
      // prod 모드에서 사용
     "build": "webpack --config webpack.prod.js"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
      "css-loader": "^0.28.4",
      "csv-loader": "^2.1.1",
      "express": "^4.15.3",
      "file-loader": "^0.11.2",
      "html-webpack-plugin": "^2.29.0",
      "style-loader": "^0.18.2",
      "webpack": "^4.30.0",
      "webpack-dev-middleware": "^1.12.0",
      "webpack-dev-server": "^2.9.1",
      "webpack-merge": "^4.1.0",
      "xml-loader": "^1.2.1"
    }
  }

TypeScript


웹팩과 타입스크립트를 통합시키는 방법을 알아보자.

Basic Setup

먼저 타입스크립트 컴파일러와 로더를 설치해야한다.

npm install --save-dev typescript ts-loader

  webpack-demo
  |- package.json
  |- package-lock.json
 |- tsconfig.json
  |- webpack.config.js
  |- /dist
    |- bundle.js
    |- index.html
  |- /src
    |- index.js
   |- index.ts
  |- /node_modules
  • tsconfig.json

jsx를 지원하도록 간단하게 설정하고 Typescript를 ES5로 컴파일한다.

{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "allowJs": true,
    "moduleResolution": "node"
  }
}
  • webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.ts',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

이렇게하면 웹팩이 ./index.ts를 통해 진입하고 , ts-loader를 통해 모든 .ts및 .tsx파일을 로드해서 bundle.js에 출력하게 된다.

Loader

ts-loader를 사용하면 다른 웹 애셋 import 같은 추가적인 웹팩 기능을 조금 더 쉽게 활성화 할 수 있지만 이미 babel-loader를 사용하여 코드를 트랜스파일하고 있는 경우라면 @babel/preset-typescript 를 사용하여 javascript와 typescript 파일을 모두 처리하도록 하게 하면 된다.

Source Maps

소스맵을 사용하려면 Typescript가 컴파일된 Javascript 파일로 인라인 소스맵을 출력하도록 설정해야한다. 아래 설정을 꼭 추가하자.

  • tsconfig.json
  {
    "compilerOptions": {
      "outDir": "./dist/",
    ++ "sourceMap": true,
      "noImplicitAny": true,
      "module": "commonjs",
      "target": "es5",
      "jsx": "react",
      "allowJs": true,
      "moduleResolution": "node",
    }
  }

webpack.config.js

  const path = require('path');

  module.exports = {
    entry: './src/index.ts',
  ++ devtool: 'inline-source-map',
    module: {
      rules: [
        {
          test: /\.tsx?$/,
          use: 'ts-loader',
          exclude: /node_modules/,
        },
      ],
    },
    resolve: {
      extensions: [ '.tsx', '.ts', '.js' ],
    },
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist'),
    },
  };

Importing Other Assets

타입스크립트에서 .svg 파일을 import 할려면 custom.d.ts파일을 통해 새로운 모듈 선언을 해줘야한다.

  • custom.d.ts
declare module '*.svg' {
  const content: any;
  export default content;
}

Public Path


모든 에셋에 대한 기본 경로를 publicPath 설정을 통해 지정할 수 있다.

Use Cases

output.publicPath 설정을 통해 원하는 기본 에셋 경로를 설정할 수 있다.

import webpack from 'webpack';

// 환경 변수를 사용하고 존재하지 않는다면 루트를 사용하세요.
const ASSET_PATH = process.env.ASSET_PATH || '/';

export default {
  output: {
    publicPath: ASSET_PATH,
  },

  plugins: [
    // 코드에서 환경 변수를 안전하게 사용할 수 있습니다.
    new webpack.DefinePlugin({
      'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH),
    }),
  ],
};

Asset Modules

앞에서 공부했듯이 애셋 모듈은 로더를 추가로 구성하지 않아도 애셋파일 사용할 수 있게 해주는 모듈이다.

webpack5 이전

  • raw-loader: 파일을 문자열로 가져올 때
  • url-loader: 파일을 data URI 형식으로 번들에 인라인 추가 할 때
  • file-loader: 파일을 출력 디렉터리로 내보낼때

webpack5 이후 (현재)

  • asset/resource는 별도의 파일을 내보내고 URL을 추출한다.
  • asset/inline은 애셋의 data URI를 내보낸다.
  • asset/source는 애셋의 소스코드를 내보낸다.
  • aseet은 data URI와 별도의 파일을 내보내기 중에서 자동으로 선택한다.

Resource assets

  • webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
 module: {
   rules: [
     {
       test: /\.png/,
       type: 'asset/resource'
     }
   ]
 },
};
  • src/index.js
import mainImage from './images/main.png';

img.src = mainImage; // '/dist/151cfcfa1bd74779aadb.png'

모든 .png 파일을 출력 디렉터리로 내보내고 해당 경로를 번들에 삽입한다. outputPath 및 publicPat를 사용자가 지정할 수 있다.

Custom output filename

asset/resource는 기본적으로 [hash][ext][query] 파일명을 사용한다. webpack 설정을 통해 파일명을 수정할 수 있다.

  • webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
++  assetModuleFilename: 'images/[hash][ext][query]'
  },
  module: {
    rules: [
      {
        test: /\.png/,
        type: 'asset/resource'
     }
     },
++     {
++       test: /\.html/,
++       type: 'asset/resource',
++       generator: {
++         filename: 'static/[hash][ext][query]'
++       }
     }
    ]
  },
};

Inlining assets

  • webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
       test: /\.svg/,
       type: 'asset/inline'
     },
    ]
  }
};
  • src/index.js
import metroMap from './images/metro.svg';

 block.style.background = `url(${metroMap})`; // url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)

모든 .svg 파일은 data URI로 번들에 삽입된다. 기본적으로 Base64 알고리즘을 사용하여 인코딩하지만 커스텀 인코딩알고리즘도 설정을 통해 할 수 있다.

const path = require('path');
 const svgToMiniDataURI = require('mini-svg-data-uri');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.svg/,
        type: 'asset/inline',
       generator: {
         dataUrl: content => {
           content = content.toString();
           return svgToMiniDataURI(content);
         }
       }
      }
    ]
  },
};

Source assets

  • webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
       }
       test: /\.txt/,
       type: 'asset/source',
      }
    ]
  },
};
  • src/example.txt
Hello World
  • src/index.js
 import exampleText from './example.txt';

 block.textContent = exampleText; // 'Hello world'

General asset type

webpack은 조건에 따라 resource 와 inline중 자동 선택하는데 크기가 8kb 미만인 파일은 inline 모듈로 처리되고 그렇지 않으면 resource로 처리된다.

  • webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
       test: /\.txt/,
       type: 'asset',
      }
    ]
  },
};

Rule.parser.dataUrlCondition.maxSize 옵션을 통해 조건을 변경할 수 있다.

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.txt/,
        type: 'asset',
       parser: {
         dataUrlCondition: {
           maxSize: 4 * 1024 // 4kb
         }
       }
      }
    ]
  },
};

Advanced entry


Multiple file types per entry

Javascript의 스타일에 import를 사용하지 않는 애플리케이션에서 CSS 및 Javascript 파일을 별도의 번들로 얻기 위해서는 엔트리 값을 배열로 사용하여 다른 유형의 파일을 제공할 수 있다.

  • webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  mode: process.env.NODE_ENV,
  entry: {
    home: ['./home.js', './home.scss'],
    account: ['./account.js', './account.scss'],
  },
  output: {
    filename: '[name].js',
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          // 개발환경에서는 style-loader로 대체 합니다
          process.env.NODE_ENV !== 'production'
            ? 'style-loader'
            : MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader',
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
  ],
};

위와 같이 설정을 하면 아래와 같은 결과물이 나온다.

  • home.js
  • home.css
  • account.js
  • account.css

마무리


이것으로 webpack의 getting started의 모든 내용을 다뤄보았다. 기존에 몰르고 썼던 것들을 이제 간략히나마 알고 쓸 수 있게 된 것 같다. 앞으로는 회사에서 사용하고 있는 module federation을 통한 micro-frontend 서비스를 직접 구현해보는 시간을 갖도록 하겠다.

profile
만들고 싶은게 많은 개발자

0개의 댓글