CRA 없이 React 환경설정하기

HYl·2022년 3월 28일
0
post-custom-banner

Github Commit 내역 입니다.


npm init 을 하여 package.json 을 먼저 생성합니다.

npm i react react-dom
npm i typescript
npm i @types/react @types/react-dom
npm i -D eslint
  • eslint : 안쓰는 코드, 변수, 오타 잡아주는 도구

npm i -D prettier eslint-plugin-prettier eslint-config-prettier
  • prettier : 코드 자동 정렬, 단점은 자유자재로 커스터마이징 할 수 없다.


eslint, prettierrc, tsconfig.json 설정

파일 최상단에 .eslintrc .prettierrc tsconfig.json 파일을 생성합니다.

// .eslintrc

{
  "extends": ["plugin:prettier/recommended", "react-app"]
}
// .prettierrc

{
  "printWidth": 120,
  "tabWidth": 2,
  "singleQuote": true,
  "trailingComma": "all",
  "semi": true
}
// tsconfig.json

{
  "compilerOptions": {
    "esModuleInterop": true,
    "sourceMap": true,
    "lib": ["ES2020", "DOM"],
    "jsx": "react",
    "module": "esnext",
    "moduleResolution": "Node",
    "target": "es5",
    "strict": true,
    "resolveJsonModule": true,
    "baseUrl": "."
  }
}
  • tsconfig.json 에 설정된 사항들을 토대로 ts -> js 파일로 변환된다.

index.html, client.tsx, App.tsx 설정

index.html

<html>
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>resume</title>
    <link
      rel="stylesheet"
      href="https://a.slack-edge.com/bv1-8/client-boot-styles.57e47b5.css"
      crossorigin="anonymous"
    />
    <link rel="shortcut icon" href="https://a.slack-edge.com/cebaa/img/ico/favicon.ico" />
    <link
      href="https://a.slack-edge.com/bv1-8/slack-icons-v2-40cccfd.woff2"
      rel="preload"
      as="font"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <div id="app"></div>
    <script src="./dist/app.js"></script>
  </body>
</html>

client.tsx

import React from 'react';
import { render } from 'react-dom';

import App from './layouts/App';

render(<App />, document.querySelector('#app'));

App.tsx

import React from 'react';

const App = () => {
  return <div>기본 설정</div>;
};

export default App;

webpack.config.ts 설정

npm i -D webpack @babel/core babel-loader @babel/preset-env @babel/preset-react
npm i -D @types/webpack @types/node @babel/preset-typescript
npm i style-loader css-loader
npm i cross-env
npm i -D ts-node

webpack.config.ts

import path from 'path';
import webpack from 'webpack';

const isDevelopment = process.env.NODE_ENV !== 'production';

const config: webpack.Configuration = {
  name: 'resume', // file name
  mode: isDevelopment ? 'development' : 'production',
  devtool: !isDevelopment ? 'hidden-source-map' : 'eval',
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], // babel이 처리할 확장자 목록
  },
  entry: {
    app: './client',
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                targets: { browsers: ['last 2 chrome versions'] },
                debug: isDevelopment,
              },
            ],
            '@babel/preset-react',
            '@babel/preset-typescript',
          ],
        },
        exclude: path.join(__dirname, 'node_modules'),
      },
      {
        test: /\.css?$/,
        use: ['style-loader', 'css-loader'], // js로 변환
      },
    ],
  },
  plugins: [new webpack.EnvironmentPlugin({ NODE_ENV: isDevelopment ? 'development' : 'production' })],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
    publicPath: '/dist/',
  },
};

export default config;

tsconfig-for-webpack-config.json

{
  "compilerOptions": {
    "module": "commonjs",
    "moduleResolution": "Node",
    "target": "es5",
    "esModuleInterop": true
  }
}

package.json

scriptsbuild 를 추가해줍니다.

{
  "name": "resume",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-for-webpack-config.json\" webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@types/react": "^17.0.43",
    "@types/react-dom": "^17.0.14",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.1",
    "path": "^0.12.7",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "style-loader": "^3.3.1",
    "typescript": "^4.6.3",
    "webpack-bundle-analyzer": "^4.5.0"
  },
  "devDependencies": {
    "@babel/core": "^7.17.8",
    "@babel/preset-env": "^7.16.11",
    "@babel/preset-react": "^7.16.7",
    "@babel/preset-typescript": "^7.16.7",
    "@types/node": "^17.0.23",
    "@types/webpack": "^5.28.0",
    "babel-loader": "^8.2.4",
    "eslint": "^8.12.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-prettier": "^4.0.0",
    "fork-ts-checker-webpack-plugin": "^7.2.1",
    "prettier": "^2.6.1",
    "ts-node": "^10.7.0",
    "webpack": "^5.70.0",
    "webpack-cli": "^4.9.2"
  }
}

build

npm run build

build 를 하면 dist 폴더 안에 app.js 파일이 생성됩니다.

여기까지 했을 때, 단점은 핫리로딩 적용이 안 되어있어서, 매번 수정할 사항들이 바로 적용이 안되는 단점이 있습니다. 이제 핫 리로딩을 적용시켜 볼 것입니다.


npm i webpack-dev-server -D
npm i webpack-cli
npm i -D @types/webpack-dev-server
npm i @pmmmwh/react-refresh-webpack-plugin
npm i react-refresh

최종 파일

webpack-dev-server를 설정하여 핫리로딩을 추가해주었습니다.
npm run dev 로 실행합니다.

webpack.config.js

import path from 'path';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import webpack, { Configuration as WebpackConfiguration } from 'webpack';
import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server';

interface Configuration extends WebpackConfiguration {
  devServer?: WebpackDevServerConfiguration;
}

import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';

const isDevelopment = process.env.NODE_ENV !== 'production';

const config: Configuration = {
  name: 'resume',
  mode: isDevelopment ? 'development' : 'production',
  devtool: isDevelopment ? 'hidden-source-map' : 'inline-source-map',
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
  },
  entry: {
    app: './client',
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                targets: { browsers: ['IE 10'] },
                debug: isDevelopment,
              },
            ],
            '@babel/preset-react',
            '@babel/preset-typescript',
          ],
          env: {
            development: {
              plugins: [require.resolve('react-refresh/babel')],
            },
          },
        },
        exclude: path.join(__dirname, 'node_modules'),
      },
      {
        test: /\.css?$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin({
      async: false,
      // eslint: {
      //   files: "./src/**/*",
      // },
    }),
    new webpack.EnvironmentPlugin({ NODE_ENV: isDevelopment ? 'development' : 'production' }),
  ],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
    publicPath: '/dist/',
  },
  devServer: {
    historyApiFallback: true,
    port: 3090,
    devMiddleware: { publicPath: '/dist/' },
    static: { directory: path.resolve(__dirname) },
  },
};

if (isDevelopment && config.plugins) {
  config.plugins.push(new webpack.HotModuleReplacementPlugin());
  config.plugins.push(
    new ReactRefreshWebpackPlugin({
      overlay: {
        useURLPolyfill: true,
      },
    }),
  );
}
if (!isDevelopment && config.plugins) {
}

export default config;

package.json

{
  "name": "resume",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "cross-env TS_NODE_PROJECT=\\\"tsconfig-for-webpack-config.json\\\" webpack serve --env development",
    "build": "cross-env TS_NODE_PROJECT=\\\"tsconfig-for-webpack-config.json\\\" NODE_ENV=production webpack"
  },
  "author": "ZeroCho",
  "license": "MIT",
  "dependencies": {
    "@types/react": "^17.0.2",
    "@types/react-dom": "^17.0.1",
    "axios": "^0.21.1",
    "core-js": "^3.15.1",
    "cross-env": "^7.0.3",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-router": "^5.2.0",
    "react-router-dom": "^5.2.0",
    "typescript": "^4.4.2"
  },
  "devDependencies": {
    "@babel/core": "^7.13.8",
    "@babel/preset-env": "^7.13.8",
    "@babel/preset-react": "^7.12.13",
    "@babel/preset-typescript": "^7.13.0",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-rc.0",
    "@types/fork-ts-checker-webpack-plugin": "^0.4.5",
    "@types/node": "^14.14.31",
    "@types/react-router-dom": "^5.1.7",
    "@types/webpack": "^5.28.0",
    "@types/webpack-dev-server": "^4.0.3",
    "babel-loader": "^8.2.2",
    "css-loader": "^6.2.0",
    "eslint": "^7.20.0",
    "eslint-config-prettier": "^8.1.0",
    "eslint-plugin-prettier": "^3.3.1",
    "fork-ts-checker-webpack-plugin": "^6.1.0",
    "prettier": "^2.2.1",
    "react-refresh": "^0.10.0",
    "style-loader": "^3.2.1",
    "ts-node": "^10.0.0",
    "webpack": "^5.24.2",
    "webpack-cli": "^4.5.0",
    "webpack-dev-server": "^4.0.0"
  }
}
profile
꾸준히 새로운 것을 알아가는 것을 좋아합니다.
post-custom-banner

0개의 댓글