ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React Native로 모바일과 웹을 한번에 개발하기
    개발여정 2024. 7. 19. 18:31

    무더위가 점점 무르익어가는 7월 초쯤... 회사에서 현재 React Native로 개발된 애플리케이션의 기능을 웹에서도 제공할 수 있는지 검토해달라는 요청을 받게 되었습니다... 🥲

     

    어떻게 개발할 수 있을까? 🤔

    처음에는 "꼭 필요한 작업일까요!?", "꼭 해야하나요!?" 라고 말하며 방어를 열심히 했지만 일단 요청을 받았으니 해야하겠죠...!! 혼미해지는 정신을 붙잡고 어떻게 React Native로 개발된 애플리케이션을 웹에서 제공할 수 있을지 고민해 보았습니다.

     

    첫 번째 방안은 React로 웹 애플리케이션을 처음부터 다시 만드는 것이었습니다. 하지만 이 방안은 처음부터 개발해야 하는 고통스러운 작업이 필요할거라 생각이 들었습니다. 회사에서는 한정적인 시간과 최소한의 리소스로 빠르게 결과물을 내기를 원하는 상황이었기 때문에 이 방안은 적합하지 않았습니다.

     

    두 번째 방안은 비즈니스 로직은 그대로 사용하고 React Native로 개발한 컴포넌트를 웹에서 사용하는 방법입니다. 구글링을 해보면 React Native Web이라는 패키지를 사용해서 React Native로 개발한 컴포넌트를 웹에서 제공하는 사례를 찾아볼 수 있었습니다.

     

    React Native Web 이란 무엇일까?

    제한된 시간과 한정적인 리소스로 인해 두 번째 방안인 React Native Web 패키지를 사용해서 모바일 애플리케이션을 웹 애플리케이션으로 전환할 수 있다는 것이 굉장히 매력적으로 느껴졌고 빠르게 React Native Web 패키지에 대한 검토를 하게 되었습니다.

     

    공식 문서를 살펴보게 되면 "React Native for Web은 React DOM과 React Native 사이의 호환성 레이어입니다. 이는 새로운 애플리케이션과 기존 애플리케이션, 웹 전용 및 다중 플랫폼 애플리케이션에서 모두 사용될 수 있습니다." 라고 설명하고 있습니다.

     

    React Native Web의 역할

    공식 문서 설명 중 "React Native for Web은 React DOM과 React Native 사이의 호환성 레이어" 라는 말이 궁금해졌고 이에 대해서 자세히 살펴보게 되었습니다.

    // webpack.config.js
    module.exports = {
      // ...the rest of your config
    
      resolve: {
        alias: {
          'react-native$': 'react-native-web'
        }
      }
    }

     

    React Native Web 공식 문서를 살펴보게 되면 Webpack을 사용해서 프로젝트를 번들링하는 것을 찾아볼 수 있었고, Webpack 설정 중 resolve 옵션에 대해서 이해하면서 "React Native for Web은 React DOM과 React Native 사이의 호환성 레이어" 라는 말을 이해할 수 있었습니다.

     

    Webpack이란 무엇일까?

    먼저 Webpack은 자바스크립트 어플리케이션을 위한 모듈 번들러(Module Bundler)입니다. 웹팩은 애플리케이션을 구성하는 자원(HTML, CSS, JavaScript, 이미지 등)을 모두 모듈로 보고 이를 하나 또는 여러 개의 번들로 묶어서 정적 파일을 생성합니다. 이 과정에서 여러 모듈 간의 의존성도 해결하고, 필요한 최적화 작업도 함께 수행할 수 있습니다.

     

    그래서 resolve 옵션이 뭐야?

    Webpack 설정은 webpack.config.js 파일에 정의할 수 있고 여러가지 옵션 중 resolve 옵션에 대한 설명은 공식문서에서 다음과 같이 찾아 볼 수 있었습니다.

    resolve 옵션은 모듈을 해석하는 방식을 변경 할 수 있습니다.
    const path = require('path');
    
    module.exports = {
      //...
      resolve: {
        alias: {
          xyz$: path.resolve(__dirname, 'path/to/file.js'),
        },
      },
    };​

     

    위의 Webpack 의 resolve 설정을 해석해보면 import 구문이나 require를 이용해서 다른 모듈을 가져올 때 해당 모듈의 경로가 xyz로 정확하게 일치($)한다면 경로를 path/to/file.js로 변경하게 됩니다.

    위의 설정을 적용했을 때 모듈의 경로 해석은 다음과 같게 됩니다.

    import Test1 from 'xyz'; // 정확한 매칭이므로 path/to/file.js로 해석되어 가져옵니다.
    import Test2 from 'xyz/file.js'; // 정확한 매칭이 아니고 일반적인 방식으로 가져옵니다.

     

     

    이 설명을 이해하고 다시 React Native Web의 설정을 살펴봅시다.

    // webpack.config.js
    module.exports = {
      // ...the rest of your config
    
      resolve: {
        alias: {
          // 모듈을 불러올 때 모듈의 경로가 react-native와 정확하게 일치한다면 react-native-web으로 변경
          'react-native$': 'react-native-web'
        }
      }
    }

     

    만약 위와 같이 설정하게 된다면 import 구문이나 require를 이용해서 다른 모듈을 불러올 때, react-native와 정확하게 일치하게 된다면 react-native-web으로 변경해주게 됩니다.

    // react-native 모듈에서 View와 Text를 가져오도록 되어 있지만
    // Webpack resolve 설정에 의해서 react-native-web 패키지의 View와 Text를 가져오게 됩니다.
    import {View, Text} from 'react-native';
    
    function App() {
      return (
        <View>
          <Text>Hello, React Native</Text>
        </View>
      );
    }
    
    export default App;

     

    프로젝트 시작

    이제 React Native 에서 제공하는 컴포넌트나 API가 어떻게 웹에서 사용될 수 있는지 살펴보았으니 React Native Web 패키지를 이용해서 적용하는 일만 남았습니다!

    프로젝트 소스코드는 https://github.com/ro-ssang/ReactNativeWebPoc 에서 살펴보실 수 있습니다.

     

    프로젝트는 Node.js v20.11.0에서 진행되었고, 포스팅하는 시점의 React Native 최신 버전인 v0.74.3 으로 프로젝트를 진행하려고 합니다. 다음 명령어로 프로젝트를 시작해봅시다.

    # React Native CLI로 프로젝트 시작
    npx react-native init ReactNativeWebPoc --version 0.74.3 --npm
    
    # 생성한 프로젝트 디렉토리로 이동
    cd ReactNativeWebPoc

     

    위 명령어는 React Native CLI로 프로젝트를 시작하는 명령어입니다.

    --version 으로 React Native의 버전을 명시하고, --npm 을 옵션을 이용해 npm을 패키지 매니저로 사용하도록 했습니다.

     

    프로젝트 진행 순서

    프로젝트는 다음과 같은 순서로 진행될 것입니다. 이번 포스팅에서는 1~3번까지의 과정을 다룰 예정이며 나머지 과정은 다음 포스팅에서 다루도록 하겠습니다.

    1. Webpack으로 웹 애플리케이션 개발환경 구성
    2. React 웹 애플리케이션 개발환경 구성
    3. React Native 코드로 웹 애플리케이션 실행
    4. Styling > Style styled-components 전환
    5. 이미지 로드
    6. 타입스크립트로 전환
    7. 빌드
    8. 라우팅 설정
    9. 코드 스플리팅
    10. 환경 분리 (development, production)

     

    1. Webpack으로 웹 애플리케이션 개발환경 구성

    먼저 Webpack을 이용해서 웹 애플리케이션 개발 환경을 구성해봅시다.

    1-1 public/index.html 파일 생성

    1. 프로젝트 root에 public 디렉토리를 생성합니다.

    2. 다음과 같이 index.html 파일을 생성합니다.

    <!DOCTYPE html>
    <html lang="ko">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>React Native for Web</title>
    </head>
    <body>
      <h1>Hello, React Native Web!</h1>
    </body>
    </html>

    1-2 Webpack 패키지 설치

    다음 명령어를 이용해 Webpack 관련 패키지를 설치해주세요.

    # webpack 관련 패키지 설치
    npm install --save-dev webpack webpack-cli
    • webpack : 자바스크립트 애플리케이션을 위한 모듈 번들러
    • webpack-cli : Webpack을 커맨드 라인에서 실행하고 구성하기 위한 도구

    1-3 Webpack 설정

    이제 Webpack 설정을 할 차례입니다. 프로젝트 root에 webpack.config.js 파일을 생성하고 다음과 같이 작성합니다.

    const path = require('path');
    
    // 애플리케이션의 root 디렉토리의 경로를 절대 경로로 나타냄
    const appDirectory = path.resolve(__dirname);
    
    module.exports = {
      entry: path.join(appDirectory, 'index.web.js'),
      output: {
        filename: 'bundle.js', // 번들링이 된 파일의 이름
        path: path.join(appDirectory, '/dist'), // 번들링이 된 파일이 생성될 경로
      },
    };

     

    설정을 자세히 보시면 entry 와 output 이라는 키워드를 보실 수 있는데, 각각 다음 역할을 담당합니다.

    • entry : Webpack이 번들링 작업을 시작하는 진입점(entry point)을 설정하는 옵션입니다. 진입점은 보통 애플리케이션의 메인 로직이 시작되는 부분으로 선택됩니다.
    • output : Webpack이 번들링한 결과물을 어디에 어떻게 내보낼지 설정하는 옵션입니다.

    1-4 index.web.js 파일 생성

    Webpack 설정에서 번들링 작업을 시작할 entry를 보면 프로젝트 root에 index.web.js 를 애플리케이션의 진입점으로 설정한 것을 볼 수 있습니다. index.web.js 파일을 생성하고 다음과 같이 작성합니다.

    console.log('Hello, Webpack!');

     

    이제 index.html 파일에 Webpack 번들링의 결과로 나올 js 파일을 연결해 봅시다. 번들링 결과물은 Webpack 설정을 살펴보면 output.path 에서 /dist 에 생성되도록 하였고 생성될 파일의 이름은 bundle.js 로 설정하였습니다. 따라서 public/index.html 에 다음과 같은 script 태그를 추가합니다.

    <!-- ... -->
    <body>
      <h1>Hello, React Native Web!</h1>
      <!-- 다음 script 태그를 추가 -->
      <script src="../dist/bundle.js"></script> 
    </body>
    <!-- ... -->

     

    1-5 package.json 설정

    이제 Webpack 명령어를 사용해 번들링을 하고 결과를 확인해 봅시다. package.json 파일에 다음 설정을 추가하고 실행합니다.

    {
        "scripts": {
          "start:web": "webpack --config ./webpack.config.js"
        },
    }
    # Webpack으로 번들링하기
    npm run start:web

    1-6 번들링 결과 확인

    npm run start:web 명령어를 실행하면 dist 디렉토리가 생성되고 디렉토리 하위에 bundle.js 파일이 생성된 것을 확인할 수 있습니다. 이제 public/index.html 파일을 열어보면 다음과 같은 결과를 확인할 수 있습니다.

    1-7 Webpack 플러그인 추가

    이렇게 Webpack 개발환경 구성을 마칠 수 있으나, js 파일을 수정할 때마다 npm run start:web 명령어를 실행해 번들링을 하고 index.html 파일을 새로고침해야 하는 불편함이 생길 수 있습니다. 이런 불편함을 해소하기 위해서 Webpack에서는 플러그인의 형태로 hot reload 기능을 지원하고 있습니다. hot reload 기능과 추가적인 기능을 위해서 다음 명령어를 실행해 Webpack 플러그인을 설치합니다.

    npm install --save-dev webpack-dev-server html-webpack-plugin
    • webpack-dev-server : Hot Module Replacement(HMR)을 지원하여 코드를 수정하면 페이지 새로고침 없이 모듈을 실시간으로 교체할 수 있습니다.
    • html-webpack-plugin : Webpack이 번들링한 결과물을 기반으로 HTML 파일을 생성하며, CSS나 JavaScript 파일들을 자동으로 <link>나 <script> 태그로 삽입합니다.

    플러그인을 설치했으니 다음과 같이 webpack.config.js 파일에 다음 코드를 추가합니다.

    const HTMLWebpackPlugin = require('html-webpack-plugin');
    
    // ...
    
    // Webpack이 번들링한 결과물인 CSS나 JavaScript 파일들을 자동으로 <link>나 <script> 태그로 삽입함
    const HTMLWebpackPluginConfig = new HTMLWebpackPlugin({
      template: path.resolve(__dirname, './public/index.html'),
      filename: 'index.html',
      inject: 'body',
    });
    
    module.exports = {
      // ...
      plugins: [HTMLWebpackPluginConfig],
      devServer: {
        open: true, // 개발 서버를 실행할 때 자동으로 브라우저를 열지 여부를 설정
        historyApiFallback: true, // SPA(Single Page Application)에서 브라우저의 History API를 사용하기 위함
        hot: true, // 코드 변경 시 페이지 새로고침 없이 모듈을 실시간으로 교체할지 여부
      },
    };

     

    html-webpack-plugin 에서 번들링한 결과물인 CSS나 JavaScript 파일들을 자동으로 <link>나 <script> 태그로 삽입하므로 이전에 public/index.html 파일에 추가한 script 태그를 제거합니다.

    <!-- ... -->
    <body>
      <h1>Hello, React Native Web!</h1>
      <!-- 다음 script 태그를 제거 -->
      <script src="../dist/bundle.js"></script> 
    </body>
    <!-- ... -->

     

    마지막으로 package.json 파일을 다음과 같이 수정하여 npm run start:web 명령어를 실행하여 hot reload 기능을 사용해 봅시다.

    {
        "scripts": {
          "start:web": "webpack serve --config ./webpack.config.js --mode development",
        },
    }
    # Webpack dev server 실행
    npm run start:web

     

    명령어를 실행하면 웹 브라우저에 index.html 파일이 열리는 것을 확인할 수 있습니다. 또한 index.web.js 파일을 수정하면 수정사항이 바로 반영되는 것을 확인할 수 있습니다.

     

    2.  React 웹 애플리케이션 개발환경 구성

    이제 Webpack으로 웹 개발환경 설정을 마쳤으니 본격적으로 웹 애플리케이션 개발을 할 차례입니다. 다음 단계에서 React Native Web 패키지를 사용할 것인데, React Native Web의 역할은 React Native 코드를 React에서 사용할 수 있도록 변환해 주는 역할을 합니다. 따라서 먼저 애플리케이션이 React 기반으로 동작하도록 해주어야 합니다.

    2-1 react-dom 패키지 추가

    package.json을 살펴보면 이미 react 패키지가 추가되어 있으므로 react로 DOM을 구성할 수 있도록 다음 명령어를 이용해 react-dom 패키지를 추가합니다.

    npm install react-dom --legacy-peer-deps

    2-2 babel 설정

    그리고 babel을 이용해서 JavaScript 최신 문법을 사용하고 JSX를 사용할 수 있도록 다음과 같이 패키지를 설치하고 webpack.config.js 파일을 수정합니다.

    # babel 관련 패키지 설치
    npm install --save-dev babel-loader @babel/core @babel/preset-react --legacy-peer-deps
    • babel-loader : Webpack에서 Babel을 통합하여 사용할 수 있게해주며, Webpack의 빌드 과정에서 JavaScript 파일에 대해 Babel을 자동으로 실행하여 변환 작업을 수행합니다.
    • @babel/core : 최신 ECMAScript 문법 (ES6+ 등)을 이전 버전의 자바스크립트 문법으로 변환하여 모든 브라우저에서 실행 가능하게 합니다.
    • @babel/preset-react : Babel에서 React의 JSX 문법을 변환하는 프리셋(preset)입니다. JSX는 React에서 컴포넌트를 작성할 때 사용되는 문법이며, 이를 일반적인 자바스크립트로 변환하여 브라우저에서 실행될 수 있게 합니다.
    // ...
    
    // JavaScript 파일 변환을 위한 Babel 로더 설정
    const babelLoaderConfiguration = {
      test: /\.jsx?$/,  // .js 또는 .jsx 확장자를 가진 파일을 대상으로 함
      include: [
        path.resolve(appDirectory, 'index.web.js'),  // index.web.js 파일 포함
        path.resolve(appDirectory, 'App.web.js'),    // App.web.js 파일 포함
        path.resolve(appDirectory, 'src'),           // src 디렉토리 포함
      ],
      exclude: /node_modules\/(?!()\/).*/,  // 번들링할 때 node_modules에 있는 파일을 제외함
      use: {
        loader: 'babel-loader',  // babel-loader 사용
        options: {
          cacheDirectory: true,  // 변환 결과를 캐시하여 성능을 향상시킴
          presets: ['@babel/preset-react'],  // React 프리셋 사용
        },
      },
    };
    
    module.exports = {
      // ...
      module: {
        rules: [babelLoaderConfiguration], // babel 설정 적용
      },
      // ...
    };

    2-3 React 코드 작성

    이제 애플리케이션이 React 기반으로 동작할 수 있는 설정을 마쳤으니 React 코드를 작성해서 웹 애플리케이션 개발을 해봅시다. 먼저 프로젝트 root에 App.web.js 파일을 생성하고 다음과 같이 작성합니다.

    import React from 'react';
    
    function App() {
      return <h1>Hello, React</h1>;
    }
    
    export default App;

     

    React 로 생성한 컴포넌트를 html에서 렌더링할 수 있도록 index.web.js 파일을 다음과 같이 수정합니다.

    import React from 'react';
    import App from './App.web';
    
    import {createRoot} from 'react-dom/client';
    
    const container = document.getElementById('app');
    const root = createRoot(container);
    root.render(<App />);

     

    index.web.js 파일을 살펴보면 app 이라는 id를 가진 DOM을 선택한 것을 볼 수 있습니다. 따라서 index.html 파일을 다음과 같이 수정합니다.

    <!DOCTYPE html>
    <html lang="ko">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>React Native for Web</title>
    </head>
    <body>
      <div id="app"></div>
    </body>
    </html>

    2-4 결과 확인

    이제 코드 작성이 끝났으니 다음 명령어를 사용해 웹 애플리케이션을 실행해 봅시다.

    npm run start:web

     

    3. React Native 코드로 웹 애플리케이션 실행

    이제까지 한 것은 웹 애플리케이션을 React Native 코드가 아닌 React 코드로 작성했습니다. 이번 단계에서는 React Native에서 제공하는 컴포넌트를 이용해서 웹 애플리케이션을 개발할 것입니다.

    3-1 react-native-web 패키지 설치

    다음 명령어로 react-native-web 패키지를 추가합니다.

    npm install react-native-web --legacy-peer-deps
    • react-native-web :  React Native의 컴포넌트와 API를 활용하여 웹 애플리케이션을 개발할 수 있는 환경을 제공

    3-2 webpack.config.js 설정 추가

    이제 Webpack 설정에 다음과 같은 설정을 추가합니다.

    // ...
    
    module.exports = {
      // ...
      resolve: {
        // Webpack이 모듈을 해석할 때 인식할 확장자들을 설정합니다.
        extensions: ['.ts', '.tsx', '.js', '.json'],
        // 모듈을 간결하게 참조하거나 다른 모듈로 대체할 수 있는 별칭(alias)을 설정합니다.
        alias: {
          // 'react-native' 모듈을 'react-native-web'으로 대체하여 웹에서 React Native 코드를 실행할 수 있도록 합니다.
          'react-native$': 'react-native-web',
        },
      },
      // ...
    };

     

    위 설정은 설치한 패키지들에서 react-native 에서 제공하는 API를 참조하려고 할 때, react-native가 아니라 react-native-web에서 제공하는 API를 참조하도록 하는 설정입니다.

    3-3 React Native 코드 작성

    React Native 코드를 사용하여 웹 애플리케이션 개발을 위한 설정을 마쳤으니 React Native 코드를 사용해볼 차례입니다. App.web.js 파일을 다음과 같이 수정해 봅시다.

    import React from 'react';
    import {View, Text} from 'react-native';
    
    function App() {
      return (
        <View>
          <Text>Hello, React Native</Text>
        </View>
      );
    }
    
    export default App;

    3-4 결과 확인

    이제 코드 작성이 끝났으니 다음 명령어를 사용해 웹 애플리케이션을 실행해 봅시다.

    npm run start:web

     

    마무리

    이렇게 React Native Web 패키지를 이용해서 React Native에서 제공하는 컴포넌트를 웹에서 제공할 수 있도록 해보았습니다. 다음 글에서는 Styling하는 방법, 이미지 로드 설정, 타입스크립트로 전환, 프로젝트 빌드 및 라우팅 설정에 대해서 살펴보고 최적화를 위한 코드 스플리팅과 개발 환경뿐만 아니라 운영 환경 설정까지 알아보도록 하겠습니다.

    '개발여정' 카테고리의 다른 글

    엑셀 파일 다운로드 기능 구현 여정 🚀  (1) 2024.07.12
    Masonry Layout 구현 여정 🚀  (0) 2024.07.05
    OCR 구현 여정 🚀  (0) 2024.06.28

    댓글

Designed by Tistory.