본문 바로가기
프론트엔드/React

CRA없이 리액트(타입스크립트) 프로젝트 생성하기

by SeungYn 2023. 3. 12.

웹팩이란?

모듈 번들러 중 하나이다.

그럼 번들러는 또 뭔데

번들러는 여러 개의 모듈을 하나의 파일로 묶어주는 도구이다. 이걸 사용하는 이유는

  1. 모듈 시스템 사용: 모듈 시스템의 필요성 즉 변수를 파일 단위로 관리 가능
  2. 파일 크기 최적화: 여러 개의 파일을 하나로 묶으면, 네트워크 비용 감소. 번들러는 여러 파일을 하나의 파일로 묶어서 요청 횟수를 줄이고, 파일 크기를 최적화해줌
  3. 코드 최적화: 번들러는 불필요한 코드를 제거하거나, 코드를 압축하여 파일 크기를 최소화해줌
  4. 다양한 형식의 파일 로드: 번들러는 자바스크립트 파일뿐만 아니라 CSS, 이미지, 폰트 등 다양한 혁신의 파일도 로드할 수 있음.

근데 왜 웹팩 써야 함?

웹팩이 선호되는 이유 중 몇 가지는

  • 다양한 플러그인 시스템과 로더를 통해서 훨씬 많은 것을 할 수 있고 강력한 기능들을 사용할 수 있음
  • 오랫동안 사용되고 개발 래퍼런스가 다양한 가장 안정적인 번들러임.
  • 수정된 사항을 새로 고침 없이 실시간으로 반영해 주는 HMR(Hot Module Replacement)이 적용된 Dev Server이 존재(무지성 yarn start를 하면 결과물 창이 뜨는 이유)

간단하게 웹팩을 살펴보자

entry

애플리케이션이 실행되며 웹팩이 번들링을 시작하는 곳이다. 웹팩은 entry point가 의존하는 다른 모듈과 라이브러리들을 찾아냄

module.exports = {
	entry: paths.appIndexJs,

};

output

output 속성은 번들 결과물의 경로를 설정하고 이름을 지정하는 옵션 등을 추가할 수 있음.

path: 번들링 된 결과물의 위치를 설정 할 수 있음

filename: 번들링된 결과물의 이름을 설정할 수 있음

module.exports = {
	output: {
	  // The build folder.
	  path: paths.appBuild,
	  // Add /* filename */ comments to generated require()s in the output.
	  pathinfo: isEnvDevelopment,
	  // There will be one main bundle, and one file per asynchronous chunk.
	  // In development, it does not produce real files.
	  filename: isEnvProduction
	    ? 'static/js/[name].[contenthash:8].js'
	    : isEnvDevelopment && 'static/js/bundle.js',
	  // There are also additional JS chunk files if you use code splitting.
	  chunkFilename: isEnvProduction
	    ? 'static/js/[name].[contenthash:8].chunk.js'
	    : isEnvDevelopment && 'static/js/[name].chunk.js',
	  assetModuleFilename: 'static/media/[name].[hash][ext]',
	  // webpack uses `publicPath` to determine where the app is being served from.
	  // It requires a trailing slash, or the file assets will get an incorrect path.
	  // We inferred the "public path" (such as / or /my-project) from homepage.
	  publicPath: paths.publicUrlOrPath,
	  // Point sourcemap entries to original disk location (format as URL on Windows)
	  devtoolModuleFilenameTemplate: isEnvProduction
	    ? info =>
	        path
	          .relative(paths.appSrc, info.absoluteResourcePath)
	          .replace(/\\\\\\\\/g, '/')
	    : isEnvDevelopment &&
	      (info => path.resolve(info.absoluteResourcePath).replace(/\\\\\\\\/g, '/')),
	},

}

loader

웹 애플리케이션을 해석할 때 자바스크립트 파일이 아닌 HTML, CSS 등 웹 자원들을 변환할 수 있도록 도와줌

로더는 babel-loader, css-loader, style-loader, file-loader 등이 있음

test: loader를 적용시킬 파일 유형 명시(정규표현식)

use: 해당 파일에 적용할 loader 명시

exclude: loader를 배제시킬 파일 명시

module: {
  strictExportPresence: true,
  rules: [
		{
		  test: /\\.svg$/,
		  use: [
		    {
		      loader: require.resolve('@svgr/webpack'),
		      options: {
		        prettier: false,
		        svgo: false,
		        svgoConfig: {
		          plugins: [{ removeViewBox: false }],
		        },
		        titleProp: true,
		        ref: true,
		      },
		    },
		    {
		      loader: require.resolve('file-loader'),
		      options: {
		        name: 'static/media/[name].[hash].[ext]',
		      },
		    },
		  ],
		  issuer: {
		    and: [/\\.(ts|tsx|js|jsx|md|mdx)$/],
		  },
		},
		// Process application JS with Babel.
		// The preset includes JSX, Flow, TypeScript, and some ESnext features.
		{
		  test: /\\.(js|mjs|jsx|ts|tsx)$/,
		  include: paths.appSrc,
		  loader: require.resolve('babel-loader'),
		  options: {
		    customize: require.resolve(
		      'babel-preset-react-app/webpack-overrides'
		    ),
		    presets: [
		      [
		        require.resolve('babel-preset-react-app'),
		        {
		          runtime: hasJsxRuntime ? 'automatic' : 'classic',
		        },
		      ],
		    ],
		    
		    plugins: [
		      isEnvDevelopment &&
		        shouldUseReactRefresh &&
		        require.resolve('react-refresh/babel'),
		    ].filter(Boolean),
		    // This is a feature of `babel-loader` for webpack (not Babel itself).
		    // It enables caching results in ./node_modules/.cache/babel-loader/
		    // directory for faster rebuilds.
		    cacheDirectory: true,
		    // See #6846 for context on why cacheCompression is disabled
		    cacheCompression: false,
		    compact: isEnvProduction,
		  },
		},
		{
      test: sassRegex,
      exclude: sassModuleRegex,
      use: getStyleLoaders(
        {
          importLoaders: 3,
          sourceMap: isEnvProduction
            ? shouldUseSourceMap
            : isEnvDevelopment,
          modules: {
            mode: 'icss',
          },
        },
        'sass-loader'
      ),
      // Don't consider CSS imports dead code even if the
      // containing package claims to have no side effects.
      // Remove this when webpack adds a warning or an error for this.
      // See <https://github.com/webpack/webpack/issues/6571>
      sideEffects: true,
    },
    // Adds support for CSS Modules, but using SASS
    // using the extension .module.scss or .module.sass
    {
      test: sassModuleRegex,
      use: getStyleLoaders(
        {
          importLoaders: 3,
          sourceMap: isEnvProduction
            ? shouldUseSourceMap
            : isEnvDevelopment,
          modules: {
            mode: 'local',
            getLocalIdent: getCSSModuleLocalIdent,
          },
        },
        'sass-loader'
      ),
    
},

다른 건 필요할 때 찾아서 추가해야겠다.

이제 시작해 보자

1. 리액트와 타입스크립트를 설치해 준다

npm install react react-dom
npm install -D @types/react @types/react-dom
npm install -D typescript ts-node

2. tsc 설정파일 생성

npm install -D typescript ts-node

3. 웹팩 설치

npm i -D webpack webpack-cli webpack-dev-server
npm i -D file-loader url-loader ts-loader
npm i -D babel-loader @babel/core @babel/preset-env @babel/preset-react
npm i -D html-webpack-plugin clean-webpack-plugin

4. 웹팩 설정(wepack.config.js)

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  // development(개발 모드) or production(배포 모드)
  mode: 'development',

  // 번들링 할 파일 확장자
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
  },

  // 번들링 진입점
  entry: './src/index',

  // 번들링 결과물 설정
  output: {
    path: path.resolve(__dirname, 'build'), // 빌드되는 파일들이 만들어지는 위치, __dirname: 현재 디렉토리
    filename: 'bundle.js', // 번들파일 이름
  },

  // 로더 설정
  module: {
    rules: [
      {
        test: /\\.(ts|js)x?$/, // loader를 적용시킬 파일 정규식 명시
        use: [
          // 사용할 loader
          {
            // es6 > es5 변환 (transpiler)
            loader: 'babel-loader',
          },
        ],
        exclude: /node_modules/, // loader를 배제시킬 파일 명시
      },
      {
        test: /\\.tsx?$/,
        use: ['ts-loader'],
        exclude: ['/node_modules/'],
      },
      {
        test: /\\.css$/,
        // css-loader: css를 js처럼 사용할 수 있도록 처리, style-loader : js로 처리된 스타일시트 코드를 html에 적용
        // use에 선언된 가장 오른쪽의 로더가 먼저 실행 (오른쪽에서 왼쪽 순으로)
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\\.(png|jpe?g|gif|woff|woff2|ttf|svg|ico)$/i,
        use: [
          {
            loader: 'file-loader',
          },
        ],
      },
    ],
  },

  // webpack 서버 설정
  devServer: {
    static: path.join(__dirname, 'build'),
    port: 8088,
  },

  plugins: [
    // 분리된 css, js 파일들을 각각 html에 link 자동화
    new HtmlWebpackPlugin({
      template: `./public/index.html`,
    }),

    // output.path 디렉토리에 있는 이전에 빌드된 결과물 삭제
    new CleanWebpackPlugin(),
  ],
};

5. package.json 수정

"scripts": {
    "test": "echo \\"Error: no test specified\\" && exit 1",
    "dev": "webpack-dev-server --open",
    "build": "webpack --config webpack.config.js"
  },

6. tsconfig.json 설정

{
  "compilerOptions": {
    "target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
    "lib": [
      "dom",
      "ES2016"
    ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
    "jsx": "react" /* Specify what JSX code is generated. */,
    "module": "commonjs" /* Specify what module code is generated. */,
    "rootDir": "./src" /* Specify the root folder within your source files. */,
    "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
    "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
    "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
    "strict": true /* Enable all strict type-checking options. */,
    "skipLibCheck": true /* Skip type checking all .d.ts files. */
  },
  "exclude": ["./webpack.config.js"],
  "allowSyntheticDefaultImports": true,
  "esModuleInterop": true,
  "forceConsistentCasingInFileNames": true
}

7. public 폴더에 index.html 생성

 

8. src 폴더에 App.tsx, index.tsx 생성

 

9. 마지막으로 실행

npm run dev

10. 결과

그냥 codepen을 이용하는 게 나을 것 같기도 하다.

 

 

출처: https://tech.osci.kr/2022/10/24/create-react-app-%EC%97%86%EC%9D%B4-%EB%B0%B0%EC%9A%B0%EB%8A%94-webpack/

'프론트엔드 > React' 카테고리의 다른 글

setState 함수는 Dependency list에 넣어줘야 할까?  (0) 2024.01.17
상태(state) 업데이트의 비밀(batch)🤫  (1) 2023.06.12
Portals  (0) 2023.03.11
연속된 커스텀 훅(훅인마)  (0) 2023.03.09
리액트 hydrate  (0) 2023.02.21