├── .gitignore
├── README.md
├── frontend-performance.gif
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.css
├── App.js
├── App.test.js
├── components
│ ├── AdvancedFeatures.js
│ ├── Dashboard.js
│ ├── Home.js
│ ├── PreFetchLink.js
│ ├── Profile.js
│ ├── Settings.js
│ └── advanced
│ │ ├── DataVisualization.js
│ │ └── MachineLearning.js
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
└── setupTests.js
├── webpack.config.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /dist
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | # IntelliJ
27 | /.idea
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 프론트엔드 성능 최적화 데모
2 |
3 | 이 저장소는 React 기반 프론트엔드 애플리케이션의 성능 최적화를 시연하기 위해 만들어졌습니다. 다양한 최적화 기법을 적용하여 애플리케이션의 성능을 향상시키는 방법을 각 커밋을 통해 확인해보세요.
4 |
5 | - 적용된 기법
6 | - 동적 라우팅을 이용한 코드 분할(Code Splitting with Dynamic Routing)
7 | - 커밋: [a789455](https://github.com/Violet-Bora-Lee/react-code-package-level-optimization/commit/a7894559217e086941c8edff3d92c34b30271eb2)
8 | - `package.json` 최적화(`dependencies`, `devDependencies`)
9 | - 커밋: [ef2950d](https://github.com/Violet-Bora-Lee/react-code-package-level-optimization/commit/ef2950de888129d9a6b337b0f6167ed89fe2a93c)
10 | - 트리쉐이킹(Use tree shaking by importing only necessary code)
11 | - 커밋: [1df2740](https://github.com/Violet-Bora-Lee/react-code-package-level-optimization/commit/1df274004acc9d7356e23e4c813639fd74f1d285)
12 | - 웹팩 설정으로 JS 파일 압축하기(Webpack configuration for advanced JS minification)
13 | - 커밋: [7dac277](https://github.com/Violet-Bora-Lee/react-code-package-level-optimization/commit/7dac2771b902b5017e416f9ced44b4f827a9aa5d)
14 | - 다양한 프리페칭 전략(Implement pre-fetching strategies)
15 | - 커밋: [f838d69](https://github.com/Violet-Bora-Lee/react-code-package-level-optimization/commit/f838d696501d00e695c040542f8cde26d8a2989b)
16 |
17 | 
18 |
19 | ## 시작하기
20 |
21 | 이 프로젝트는 [Create React App](https://github.com/facebook/create-react-app)으로 부트스트랩되었습니다.
22 |
23 | ### 필수 조건
24 |
25 | - Node.js (>= 14.x)
26 | - npm (>= 6.x) 또는 yarn (>= 1.x)
27 |
28 | ### 설치
29 |
30 | 프로젝트를 클론하고 필요한 패키지를 설치합니다.
31 |
32 | ```sh
33 | git clone https://github.com/Violet-Bora-Lee/react-code-package-level-optimization.git
34 | cd react-code-package-level-optimization
35 | npm install (또는 yarn install)
36 | ```
37 |
38 | ### 실행
39 |
40 | ```sh
41 | npm start (또는 yarn start)
42 | ```
43 |
44 | ### 분석 방법
45 | ```sh
46 | npm run build:analyze (또는 yarn run build:analyze)
47 | ```
48 |
--------------------------------------------------------------------------------
/frontend-performance.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Violet-Bora-Lee/react-code-package-level-optimization/182a2a6c7a1e4d4ff1421849de11fad9ee6e406b/frontend-performance.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "code-splitting-demo",
3 | "version": "1.0.0",
4 | "description": "Frontend Performance Optimalization Demo",
5 | "author": "Bora Lee with Claude",
6 | "dependencies": {
7 | "chart.js": "^4.3.0",
8 | "react": "^18.2.0",
9 | "react-chartjs-2": "^5.2.0",
10 | "react-dom": "^18.2.0",
11 | "react-router-dom": "^6.11.2"
12 | },
13 | "devDependencies": {
14 | "@babel/core": "^7.22.1",
15 | "@babel/preset-env": "^7.22.4",
16 | "@babel/preset-react": "^7.22.3",
17 | "babel-loader": "^9.1.3",
18 | "css-loader": "^7.1.2",
19 | "css-minimizer-webpack-plugin": "^7.0.0",
20 | "html-webpack-plugin": "^5.5.0",
21 | "style-loader": "^4.0.0",
22 | "terser-webpack-plugin": "^5.3.10",
23 | "webpack": "^5.84.1",
24 | "webpack-bundle-analyzer": "^4.9.0",
25 | "webpack-cli": "^5.1.1",
26 | "webpack-dev-server": "^4.15.0"
27 | },
28 | "scripts": {
29 | "start": "webpack serve --mode development",
30 | "build": "webpack --mode production",
31 | "build:analyze": "webpack --mode production --env analyze"
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Violet-Bora-Lee/react-code-package-level-optimization/182a2a6c7a1e4d4ff1421849de11fad9ee6e406b/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Code Splitting Demo
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Violet-Bora-Lee/react-code-package-level-optimization/182a2a6c7a1e4d4ff1421849de11fad9ee6e406b/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Violet-Bora-Lee/react-code-package-level-optimization/182a2a6c7a1e4d4ff1421849de11fad9ee6e406b/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | // App.js
2 | import React, { Suspense, lazy } from 'react';
3 | import { BrowserRouter as Router, Route, Link, Routes } from 'react-router-dom';
4 | import PreFetchLink from './components/PreFetchLink';
5 |
6 | const Home = lazy(() => import(/* webpackPrefetch: true */ './components/Home'));
7 | const Dashboard = lazy(() => import(/* webpackPrefetch: true */ './components/Dashboard'));
8 | const Profile = lazy(() => import('./components/Profile'));
9 | const Settings = lazy(() => import('./components/Settings'));
10 | const AdvancedFeatures = lazy(() => import('./components/AdvancedFeatures'));
11 |
12 |
13 | function App() {
14 | return (
15 |
16 |
17 |
56 |
57 |
Loading... }>
58 |
59 | } />
60 | } />
61 | } />
62 | } />
63 | } />
64 |
65 |
66 |
67 |
68 | );
69 | }
70 |
71 | export default App;
72 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/AdvancedFeatures.js:
--------------------------------------------------------------------------------
1 | import React, { useState, Suspense, lazy } from 'react';
2 |
3 | const DataVisualization = lazy(() => import('./advanced/DataVisualization'));
4 | const MachineLearning = lazy(() => import('./advanced/MachineLearning'));
5 |
6 | function AdvancedFeatures() {
7 | const [activeFeature, setActiveFeature] = useState(null);
8 |
9 | return (
10 |
11 |
Advanced Features
12 |
13 |
14 |
15 | Loading feature...}>
16 | {activeFeature === 'dataViz' && }
17 | {activeFeature === 'ml' && }
18 |
19 |
20 | );
21 | }
22 |
23 | export default AdvancedFeatures;
--------------------------------------------------------------------------------
/src/components/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | function Dashboard() {
4 | const [count, setCount] = useState(0);
5 |
6 | return (
7 |
8 |
Dashboard
9 |
You clicked {count} times
10 |
13 |
14 | );
15 | }
16 |
17 | export default Dashboard;
18 |
--------------------------------------------------------------------------------
/src/components/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Home() {
4 | return Home
;
5 | }
6 |
7 | export default Home;
--------------------------------------------------------------------------------
/src/components/PreFetchLink.js:
--------------------------------------------------------------------------------
1 | // components/PreFetchLink.js
2 | import React from 'react';
3 | import { Link } from 'react-router-dom';
4 |
5 | const PreFetchLink = ({ to, children, prefetch }) => {
6 | const prefetchComponent = () => {
7 | if (prefetch) {
8 | prefetch();
9 | }
10 | };
11 |
12 | return (
13 |
14 | {children}
15 |
16 | );
17 | };
18 |
19 | export default PreFetchLink;
--------------------------------------------------------------------------------
/src/components/Profile.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Profile() {
4 | return Profile
;
5 | }
6 |
7 | export default Profile;
8 |
--------------------------------------------------------------------------------
/src/components/Settings.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Settings() {
4 | return Settings
;
5 | }
6 |
7 | export default Settings;
8 |
--------------------------------------------------------------------------------
/src/components/advanced/DataVisualization.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | function DataVisualization() {
4 | const [ChartComponent, setChartComponent] = useState(null);
5 |
6 | useEffect(() => {
7 | const loadChart = async () => {
8 | const { Chart, BarController, CategoryScale, LinearScale, BarElement } = await import('chart.js');
9 | Chart.register(BarController, CategoryScale, LinearScale, BarElement);
10 | const { Bar } = await import('react-chartjs-2');
11 | setChartComponent(() => Bar);
12 | };
13 |
14 | loadChart();
15 | }, []);
16 |
17 | const data = {
18 | labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
19 | datasets: [{
20 | label: '# of Votes',
21 | data: [12, 19, 3, 5, 2, 3],
22 | backgroundColor: 'rgba(75, 192, 192, 0.2)',
23 | borderColor: 'rgba(75, 192, 192, 1)',
24 | borderWidth: 1
25 | }]
26 | };
27 |
28 | const options = {
29 | scales: {
30 | y: {
31 | beginAtZero: true
32 | }
33 | }
34 | };
35 |
36 | return (
37 |
38 |
Data Visualization
39 | {ChartComponent && }
40 |
41 | );
42 | }
43 |
44 | export default DataVisualization;
--------------------------------------------------------------------------------
/src/components/advanced/MachineLearning.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function MachineLearning() {
4 | return Machine Learning Component
;
5 | }
6 |
7 | export default MachineLearning;
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 | import App from './App';
4 |
5 | const container = document.getElementById('root');
6 | const root = createRoot(container);
7 | root.render(
8 |
9 |
10 |
11 | );
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
4 | const TerserPlugin = require('terser-webpack-plugin');
5 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
6 |
7 | module.exports = (env, argv) => {
8 | const isProduction = argv.mode === 'production';
9 |
10 | const config = {
11 | entry: './src/index.js',
12 | output: {
13 | path: path.resolve(__dirname, 'dist'),
14 | filename: '[name].[contenthash].js',
15 | clean: true,
16 | publicPath: '/',
17 | },
18 | module: {
19 | rules: [
20 | {
21 | test: /\.(js|jsx)$/,
22 | exclude: /node_modules/,
23 | use: {
24 | loader: 'babel-loader',
25 | options: {
26 | presets: ['@babel/preset-env', '@babel/preset-react']
27 | }
28 | }
29 | },
30 | // CSS 규칙 추가 (만약 CSS 파일을 사용한다면)
31 | {
32 | test: /\.css$/,
33 | use: ['style-loader', 'css-loader'],
34 | },
35 | ]
36 | },
37 | optimization: {
38 | minimize: isProduction,
39 | minimizer: [
40 | new TerserPlugin({
41 | terserOptions: {
42 | parse: {
43 | ecma: 8,
44 | },
45 | compress: {
46 | ecma: 5,
47 | warnings: false,
48 | comparisons: false,
49 | inline: 2,
50 | },
51 | mangle: {
52 | safari10: true,
53 | },
54 | output: {
55 | ecma: 5,
56 | comments: false,
57 | ascii_only: true,
58 | },
59 | },
60 | parallel: true,
61 | }),
62 | new CssMinimizerPlugin(),
63 | ],
64 | splitChunks: {
65 | chunks: 'all',
66 | },
67 | },
68 | devServer: {
69 | static: {
70 | directory: path.join(__dirname, 'public'),
71 | },
72 | historyApiFallback: true,
73 | open: true,
74 | hot: true,
75 | port: 3000,
76 | },
77 | resolve: {
78 | extensions: ['.js', '.jsx'],
79 | },
80 | plugins: [
81 | new HtmlWebpackPlugin({
82 | template: './public/index.html',
83 | }),
84 | ],
85 | devtool: isProduction ? 'source-map' : 'eval-source-map',
86 | };
87 |
88 | if (env.analyze) {
89 | config.plugins.push(new BundleAnalyzerPlugin());
90 | }
91 |
92 | return config;
93 | };
--------------------------------------------------------------------------------