서비스 배포

웹 호스팅하기 (4) - vercel + react + esbuild 배포하기 (2)

syleemomo 2024. 5. 28. 15:04
728x90

https://stackoverflow.com/questions/77413274/esbuild-bundle-html-css-js-into-a-single-file

 

esbuild bundle html css js into a single file

i have a directory named src with 3 files: index.html index.js index.css this is how my html file looks like what esbuild recommends <!doctype html> <html> <head> <link...

stackoverflow.com

 

https://blog.logrocket.com/getting-started-esbuild/

 

Getting started with esbuild - LogRocket Blog

Learn how to quickly and efficiently bundle TypeScript, React, CSS, and image files in this comprehensive esbuild tutorial.

blog.logrocket.com

 

* esbuild 플러그인 사용하기 

npm install --save-dev @craftamap/esbuild-plugin-html

플러그인을 사용하기 위하여 설치한다. 

import * as esbuild from 'esbuild'
import { htmlPlugin } from '@craftamap/esbuild-plugin-html'

// html 
esbuild
    .build({
        entryPoints: ['public/index.html'],
        outfile: 'build/index.html',
        loader: { '.html': 'copy' }
    })
    .then(() => console.log('⚡Bundle build complete ⚡'))
    .catch(e => {
        console.log('❌Failed to bundle ❌')
        console.log(e)
        process.exit(1)
    })

// js, css, files 
esbuild
    .build({
        entryPoints: ['src/index.js'],
        bundle: true, 
        minify: true,
        // outfile: 'build/bundle.js',
        loader: { '.js': 'jsx',  '.png': 'file', '.jpg': 'file', '.svg': 'file'},
        format: 'cjs',
        define: {
            'process.env.REACT_APP_BASE_URL': '"https://vercel-express-deploy-tutorial.vercel.app"'
        },
        metafile: true, 
        outdir: 'build/',
        plugins: [
            htmlPlugin({
                files: [
                    {
                        entryPoints: [
                            'src/index.js',
                        ],
                        filename: 'index.html',
                        htmlTemplate: `
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
            </head>
            <body>
                <div id="root">
                </div>
            </body>
            </html>
          `,
                    }
                ]
            })
        ]
    })
    .then(() => console.log('⚡Bundle build complete ⚡'))
    .catch(e => {
        console.log('❌Failed to bundle ❌')
        console.log(e)
        process.exit(1)
    })

build.js 파일을 위와 같이 수정한다.

import { htmlPlugin } from '@craftamap/esbuild-plugin-html'

플러그인을 사용하기 위하여 임포트한다.

// outfile: 'build/bundle.js',

해당 부분은 주석처리한다. 

metafile: true, 
        outdir: 'build/',
        plugins: [
            htmlPlugin({
                files: [
                    {
                        entryPoints: [
                            'src/index.js',
                        ],
                        filename: 'index.html',
                        htmlTemplate: `
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
            </head>
            <body>
                <div id="root">
                </div>
            </body>
            </html>
          `,
                    }
                ]
            })
        ]

src/index.js 파일의 빌드가 완료되면 build 폴더에 index.js 라는 이름으로 추가한다. 또한, build 폴더에 index.html 이라는 파일을 생성하고 html 파일의 내용은 htmlTemplate 속성에 설정한 문자열을 사용한다. 또한, 빌드가 끝난 index.js 파일을 index.html 파일에 주입한다. 물론 css 파일이 있다면 build 폴더에 index.css 파일도 추가한다. 또한, 빌드가 끝난 indeex.css 파일도 index.html 파일에 주입한다. 

npm run build-js

해당 명령어를 실행하면 빌드된다. 

build 폴더에서 index.html 파일을 열어보면 빌드가 끝난 index.js 파일과 index.css 파일이 주입된 것을 확인할 수 있다. 

 

import * as esbuild from 'esbuild'
import { htmlPlugin } from '@craftamap/esbuild-plugin-html'

// html 
esbuild
    .build({
        entryPoints: ['public/index.html'],
        outfile: 'build/index.html',
        loader: { '.html': 'copy' }
    })
    .then(() => console.log('⚡Bundle build complete ⚡'))
    .catch(e => {
        console.log('❌Failed to bundle ❌')
        console.log(e)
        process.exit(1)
    })

// js, css, files 
esbuild
    .context({
        entryPoints: ['src/index.js'],
        bundle: true, 
        minify: true,
        // outfile: 'build/bundle.js',
        loader: { '.js': 'jsx',  '.png': 'file', '.jpg': 'file', '.svg': 'file'},
        format: 'cjs',
        define: {
            'process.env.REACT_APP_BASE_URL': '"https://vercel-express-deploy-tutorial.vercel.app"'
        },
        metafile: true, 
        outdir: 'build/',
        plugins: [
            htmlPlugin({
                files: [
                    {
                        entryPoints: [
                            'src/index.js',
                        ],
                        filename: 'index.html',
                        htmlTemplate: `
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
            </head>
            <body>
                <div id="root">
                </div>
            </body>
            </html>
          `,
                    }
                ]
            })
        ]
    })
    .then(async (ctx) => {
        console.log('⚡Bundle build complete ⚡')
        await ctx.watch().then(() => console.log('watching...'))
        await ctx.serve({ servedir: 'build' }).then(() => console.log('serve at http://127.0.0.1:8000/'))
    })
    .catch(e => {
        console.log('❌Failed to bundle ❌')
        console.log(e)
        process.exit(1)
    })

start.js 파일을 위와 같이 수정한다. 

import { htmlPlugin } from '@craftamap/esbuild-plugin-html'

플러그인을 사용하기 위하여 임포트한다.

// outfile: 'build/bundle.js',

해당 부분은 주석처리한다. 

metafile: true, 
        outdir: 'build/',
        plugins: [
            htmlPlugin({
                files: [
                    {
                        entryPoints: [
                            'src/index.js',
                        ],
                        filename: 'index.html',
                        htmlTemplate: `
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
            </head>
            <body>
                <div id="root">
                </div>
            </body>
            </html>
          `,
                    }
                ]
            })
        ]

src/index.js 파일의 빌드가 완료되면 build 폴더에 index.js 라는 이름으로 추가한다. 또한, build 폴더에 index.html 이라는 파일을 생성하고 html 파일의 내용은 htmlTemplate 속성에 설정한 문자열을 사용한다. 또한, 빌드가 끝난 index.js 파일을 index.html 파일에 주입한다. 물론 css 파일이 있다면 build 폴더에 index.css 파일도 추가한다. 또한, 빌드가 끝난 indeex.css 파일도 index.html 파일에 주입한다. 

npm run start-js

해당 명령어를 실행하면 웹서버가 시작된다.

build 폴더에서 index.html 파일을 열어보면 빌드가 끝난 index.js 파일과 index.css 파일이 주입된 것을 확인할 수 있다. 

브라우저에서 웹서버 주소로 접속하면 웹서비스가 정상적으로 동작한다.

import React, { useState, useEffect } from 'react'
import './App.css'
import logo from './logo.svg'

function App(){
    const [user, setUser] = useState(null)

    console.log(process.env.REACT_APP_BASE_URL)
    useEffect(() => {
        console.log('서버주소 @: ', process.env.REACT_APP_BASE_URL)
    
        fetch(`${process.env.REACT_APP_BASE_URL}/user`, {
            headers: {
                'Content-Type': 'application/json'
            },
            method: 'POST',
            body: JSON.stringify({
                name: "esbuild",
                email: "esbuild@gmail.com",
                userId: "esbuild",
                password: "esbuild123@"  
            })
        })
        .then(res => res.json())
        .then(result => {
          console.log(result)
          setUser(result.newUser)
        })
      }, [])

    return (
        <div className='App'>
            <h1>Hello, ebsuild ! welcome to my world !</h1>
            <img src={logo} alt="logo" width={500} height={500}/>
            {user ? (
                        <>
                        <h1>회원정보</h1>
                        <p>이름: {user.name}</p>
                        <p>연락처: {user.email}</p>
                        <p>아이디: {user.userId}</p>
                        </>
                    ) : "사용자정보 조회중..."}
        </div>
    )
}
export default App

하지만 src > App.js 파일에서 코드를 변경하고 Ctrl + S 를 눌러서 저장해도 웹사이트는 업데이트된 화면을 보여주지 못한다. 즉, HMR(Hot Module Replacement)가 동작하지 않는다. 

htmlTemplate: `
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
            </head>
            <body>
                <div id="root">
                </div>
                <script>
                    // HMR (Hot Module Replacement)
                    new EventSource('/esbuild').addEventListener('change', () => {
                        console.log("reloading...")
                        location.reload()
                    })
                </script>
            </body>
            </html>
          `

HMR 을 적용하기 위하여 build.js 파일과 start.js 파일의 해당 부분을 모두 위와 같이 수정한다. 

npm run start-js

다시 해당 명령어를 실행하여 재빌드하고, 웹서버를 재시작한다.

build 폴더에서 index.html 파일을 열어보면 이제 HMR 이 적용되었다.

import React, { useState, useEffect } from 'react'
import './App.css'
import logo from './logo.svg'

function App(){
    const [user, setUser] = useState(null)

    console.log(process.env.REACT_APP_BASE_URL)
    useEffect(() => {
        console.log('서버주소 @: ', process.env.REACT_APP_BASE_URL)
    
        fetch(`${process.env.REACT_APP_BASE_URL}/user`, {
            headers: {
                'Content-Type': 'application/json'
            },
            method: 'POST',
            body: JSON.stringify({
                name: "esbuild",
                email: "esbuild@gmail.com",
                userId: "esbuild",
                password: "esbuild123@"  
            })
        })
        .then(res => res.json())
        .then(result => {
          console.log(result)
          setUser(result.newUser)
        })
      }, [])

    return (
        <div className='App'>
            <h1>Hello, ebsuild !  welcome to my world !!!</h1>
            <img src={logo} alt="logo" width={500} height={500}/>
            {user ? (
                        <>
                        <h1>회원정보</h1>
                        <p>이름: {user.name}</p>
                        <p>연락처: {user.email}</p>
                        <p>아이디: {user.userId}</p>
                        </>
                    ) : "사용자정보 조회중..."}
        </div>
    )
}
export default App

src > App.js 파일을 다시 수정하고, Ctrl + S 를 누르면 HRM 이 잘 적용된다.

하지만 이렇게 되면 esbuild 가 index.html 파일을 자동생성하므로,  public 폴더의 index.html 파일은 필요없게 된다. public 폴더를 사용하는 방법과 비교해보면 자바스크립트와 CSS 파일 경로를 자동으로 주입해준다는 것 외에 큰 메리트가 없다. 

728x90