Skip to content

8.2 · Bootstrap + CRA build

บทนี้อธิบายว่าแอปเริ่มที่ไฟล์ใด ลำดับการ mount อย่างไร และ CRA สร้าง bundle อย่างไร


Entry point

CRA เริ่มที่ src/index.js (default convention) — เป็นไฟล์เดียวกับที่ public/index.html เสียบผ่าน <script src="%PUBLIC_URL%/static/js/bundle.js"> (สร้างโดย webpack ของ CRA)

1:33:vtrc-rc-backoffice/src/index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import { ApolloProvider } from 'react-apollo'

import App from './containers/App'
import * as serviceWorker from './serviceWorker'
import apolloClient from './config/apollo'
import { RecoilRoot } from 'recoil';

class Root extends Component {
  render() {
    return (
      <ApolloProvider client={apolloClient} >
        <RecoilRoot>
          <App />
        </RecoilRoot>
      </ApolloProvider>
    );
  }
}

ReactDOM.render(<Root />, document.getElementById('root'))

serviceWorker.unregister()

ลำดับการ mount

  1. ApolloProvider หุ้มด้วย apolloClient (default export จาก src/config/apollo.js) — component ทุกตัวเข้าถึง client ผ่าน withApollo HOC หรือ hook ของ react-apollo
  2. RecoilRoot หุ้ม App — ทำให้ useRecoilState / useSetRecoilState / useRecoilValue ใช้ได้ (module WorkForce, PMS)
  3. App (src/containers/App/index.js) mount ที่ #root — CSS หลัก (antd.css, main.css, redesign.css, custom-antd.css) ถูก import ที่นี่ ไม่ใช่ที่ index.js
  4. serviceWorker.unregister() — CRA default ไม่ลงทะเบียน service worker

serviceWorker.unregister

CRA default — ไม่ลงทะเบียน service worker ทำให้แอปไม่เก็บ cache ฝั่ง browser — ผู้ใช้ต้อง refresh เพื่อรับเวอร์ชันใหม่เสมอ (ตรงข้ามกับแนว PWA)


App container — root router + auth gate

16:31:vtrc-rc-backoffice/src/containers/App/index.js
function App () {
	console.log = console.warn = console.error = () => { };
	return (
		<BrowserRouter>
			<Switch>
				<Route path="/login" exact component={Login} />
				<Route path="/TheamCss" exact component={TheamCss} />
            <Route path="/pathLogin/:token/:refreshToken" exact component={PathLogin}/>
				{isLoggedIn() && <Route path="/" component={SiderLayout} />}
				{!isLoggedIn() && <Redirect to="/login" />}
				<Route component={NotFound} />
			</Switch>
		</BrowserRouter>
	)
}

สิ่งที่ต้องระวังใน App/index.js

  1. console.log = console.warn = console.error = () => {}; — ปิด console ทุกชนิดใน production ทำให้ debug ด้วย console.* ไม่ได้ ต้องใช้ debugger หรือ React DevTools
  2. ไม่มี PrivateRoute component — auth gate คือ conditional render: isLoggedIn() && <Route …/> คู่กับ !isLoggedIn() && <Redirect to="/login" />
  3. isLoggedIn() คืน boolean — ตรวจว่ามีทั้ง token และ refreshToken ใน localStorage/sessionStorage (ดู บท 7.4) ไม่ได้คืน JWT string
  4. โฟลเดอร์/หน้า TheamCss — typo ในชื่อจริง ห้ามแก้เพราะ path และ import อ้างชื่อนี้

CRA build — ภาพรวม

vtrc-rc-backoffice/package.json ใช้ react-scripts@3.4.3 (CRA ไม่ eject) — script ทั้งหมดมาจาก CRA default:

json
"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "eject": "react-scripts eject"
}

สิ่งที่ CRA ทำตอน npm run build

  1. Webpack (ฝังใน react-scripts) อ่าน public/index.html และ src/index.js
  2. Babel transpile JSX/ES2020+ → ES5 (browserslist จาก package.json)
  3. PostCSS/Autoprefixer จัดการ CSS
  4. แยก bundle:
    • static/js/main.<hash>.js — โค้ดฝั่ง app
    • static/js/<chunk>.<hash>.chunk.js — lazy chunk (เกือบไม่มี เพราะไม่ค่อยมี React.lazy)
    • static/css/main.<hash>.css — CSS รวม
    • static/media/* — assets
  5. สร้าง build/index.html พร้อมแทรก <script> และ <link> ที่มี hash

ข้อจำกัดของ CRA 3.4.3

  • webpack 4 (ล้าสมัยแล้ว — CRA 5 ใช้ webpack 5)
  • ไม่สามารถกำหนด webpack config เองได้ โดยตรง ต้องใช้ rescripts / craco หรือ eject (เลือกไม่ eject)
  • ไม่รองรับ Module Federation
  • tree-shaking อ่อน — ทำให้ bundle ใหญ่ในบางส่วน

ไฟล์ config ของ CRA

ไฟล์หน้าที่
package.jsondependencies, scripts, browserslist, eslintConfig
.envค่า REACT_APP_* สำหรับ microservice บางตัว (เช่น PMS, AI recruitment) — ไม่มี REACT_APP_API_URL และไม่ได้ขับ GraphQL
public/manifest.jsonPWA metadata
public/index.htmltemplate ของ HTML
Dockerfilemulti-stage: node:16 build → nginx:1.24.0 serve
nginx/default.confSPA try_files/index.html

ไม่มี jsconfig.json ใน repo — import ใช้ relative path เป็นหลัก


browserslist

จาก package.json:

json
"browserslist": {
  "production": [
    ">0.2%",
    "not dead",
    "not op_mini all"
  ],
  "development": [
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safari version"
  ]
}

เป้าหมายครอบคลุมเบราว์เซอร์ที่มีส่วนแบ่ง > 0.2% ทั่วโลก ไม่รวม "dead" และไม่รวม Opera Mini


Dockerfile — multi-stage + nginx

1:17:vtrc-rc-backoffice/Dockerfile
FROM node:16 AS stage-1

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install --legacy-peer-deps

COPY . .

RUN CI=false GENERATE_SOURCEMAP=false NODE_OPTIONS=--max-old-space-size=4096 npm run build

FROM nginx:1.24.0 AS stage-2

COPY ./nginx/default.conf /etc/nginx/conf.d/

COPY --from=stage-1 /usr/src/app/build/ /usr/share/nginx/html/build/

ขั้นตอนใน Docker

  1. stage-1 — base node:16 ลง dependency ด้วย npm install --legacy-peer-deps แล้ว npm run build (ปิด source map, เพิ่ม heap 4GB)
  2. stage-2 — base nginx:1.24.0 คัดลอก nginx/default.conf และ static จาก build/ ไปที่ /usr/share/nginx/html/build/
  3. nginx listen port 80 (ไม่ใช่ 3000) — SPA fallback ผ่าน try_files $uri $uri/ /index.html

ข้อระวังเรื่อง Docker

  • ไม่ใช้ serve -s — production image เป็น nginx ไม่ใช่ Node + serve
  • COPY . . คัดลอก build/ ที่มีอยู่แล้วมาด้วย ถ้า repo มี build/ commit ไว้ ทำให้ image ใหญ่และ build ช้า
  • Firebase Hosting (backoffice-vtrc.web.app) เป็นอีกช่องทาง deploy ที่ checkLinkURL() รองรับแยกต่างหาก

ขนาด bundle — ทำไมใหญ่

CRA ไม่มี built-in bundle analyzer แต่จากการประเมิน package.json dependencies:

หมวดLibraryขนาดประมาณการ
UIantd@4.15 (full import)~1.2 MB
UI@mui/*@5.11 (ใช้น้อยมาก)~300 KB
Staterecoil@0.7~50 KB
GraphQLreact-apollo@3.1 + apollo-client + apollo-link-*~250 KB
Datemoment@2.29 (with locale)~300 KB
Chartschart.js@3 + react-chartjs-2~250 KB
Reportstimulsoft-reports-js~5 MB
Excelxlsx@0.18, exceljs~800 KB
PDF@react-pdf/renderer, react-pdf, pdfjs-dist~2 MB
Misclodash, axios, crypto-js, qrcode, …~1 MB

รวมประมาณ 11-13 MB (gzip ~3-4 MB) เพราะ:

  1. ไม่มี code splittingReact.lazy + Suspense แทบไม่มี
  2. AntD v4 import แบบ full
  3. MUI v5 อยู่ใน dependencies แต่ใช้แค่ไม่กี่ไฟล์
  4. Stimulsoft ใหญ่มากแต่ใช้เฉพาะหน้า report

.env — ไม่ขับ GraphQL

ไฟล์ .env ที่ root ของ repo มีตัวแปรเช่น:

  • REACT_APP_END_POINT_RECRUITMENT_API_ATTACHFILE
  • REACT_APP_END_POINT_EXPORT
  • REACT_APP_AI_RECRUITMENT_API
  • REACT_APP_PMS_API_END_POINT
  • REACT_APP_STIMUL_CONN_STRING
  • REACT_APP_PRIVATE_KEY

ไม่มี REACT_APP_API_URL และใน src/ ไม่มีโค้ดที่อ่าน REACT_APP_API_URL

GraphQL endpoint ของ Apollo หลักตัดสินด้วย checkLinkURL() ใน src/config/apollo.js ตาม window.location.href เท่านั้น (ดู บท 7.3)

บาง axios client (เช่น axios-pms.js, axios-ai.js) อ่าน REACT_APP_* ของตัวเอง — คนละเรื่องกับ GraphQL


npm scripts ที่ใช้จริง

Scriptใช้ทำอะไร
npm startdev server ที่ http://localhost:3000 (hot reload)
npm run buildสร้าง build/ สำหรับ deploy
npm testรัน Jest (แทบไม่มี test จริง)
npm run ejecteject CRA (ห้ามกดโดยไม่จำเป็น)

เปรียบเทียบกับ vtrc-web (ดู Volume 8 บท 6.2)

ด้านvtrc-rc-backofficevtrc-web
Build systemCRA 3.4.3 (ไม่ eject)Webpack 4 (eject แล้ว)
Config ได้ไม่ได้ (ต้อง eject/craco)ได้เต็ม
Node ใน Docker (build)1614
Static serve ใน Dockernginx:1.24.0(ดู Volume 6)
console.* ปิดใช้ใช้
Bundle~11-13 MB~12 MB
Code splittingแทบไม่มีแทบไม่มี
jsconfig absoluteไม่มี(ดู Volume 6)

ขั้นตอนถัดไป

ไป บท 7.3 Apollo Client + checkLinkURL