├── .gitignore ├── .npmignore ├── bin └── create-project ├── docs ├── .babelrc ├── .editorconfig ├── .gitignore ├── assets │ └── react-logo.svg ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.jsx │ ├── Components │ │ ├── Content.jsx │ │ ├── Contents │ │ │ ├── BatchContent.jsx │ │ │ ├── HooksContent.jsx │ │ │ ├── InstallationContent.jsx │ │ │ ├── RenderingAPIContent.jsx │ │ │ ├── StrictModeContent.jsx │ │ │ ├── SuspenseContent.jsx │ │ │ ├── Transition.jsx │ │ │ ├── Welcome.jsx │ │ │ ├── WorkingContent.jsx │ │ │ └── index.jsx │ │ ├── ErrorBoundary.jsx │ │ ├── Fake.jsx │ │ ├── Layout.jsx │ │ ├── Loader │ │ │ ├── PageSpinner.jsx │ │ │ ├── SectionSpinner.jsx │ │ │ └── loader.css │ │ ├── NavBar.jsx │ │ ├── NavItem.jsx │ │ ├── Post.jsx │ │ ├── SideBar.jsx │ │ └── User.jsx │ ├── Utils │ │ └── Api.js │ ├── index.jsx │ └── style.css ├── webpack.config.js └── yarn.lock ├── package-lock.json ├── public └── index.html ├── src ├── cli.js ├── index.js └── main.js └── templates ├── javascript ├── .babelrc ├── .gitignore ├── package.json ├── public │ └── index.html ├── src │ ├── App.jsx │ └── index.jsx ├── webpack.config.js └── yarn.lock └── typescript ├── .babelrc ├── .gitignore ├── package.json ├── public └── index.html ├── src ├── App.tsx └── index.tsx ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | *.iml 3 | coverage/ 4 | examples/ 5 | node_modules/ 6 | typings/ 7 | sandbox/ 8 | test/ 9 | bower.json 10 | CODE_OF_CONDUCT.md 11 | COLLABORATOR_GUIDE.md 12 | CONTRIBUTING.md 13 | COOKBOOK.md 14 | ECOSYSTEM.md 15 | Gruntfile.js 16 | karma.conf.js 17 | webpack.*.js 18 | sauce_connect.log 19 | docs 20 | -------------------------------------------------------------------------------- /bin/create-project: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require = require('esm')(module /*, options*/); 4 | require('../src/cli').cli(process.argv); 5 | -------------------------------------------------------------------------------- /docs/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | ["@babel/preset-react", { "runtime": "automatic" }] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /docs/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /.yarn/* 2 | !/.yarn/patches 3 | !/.yarn/plugins 4 | !/.yarn/releases 5 | !/.yarn/sdks 6 | 7 | # Swap the comments on the following lines if you don't wish to use zero-installs 8 | # Documentation here: https://yarnpkg.com/features/zero-installs 9 | !/.yarn/cache 10 | #/.pnp.* 11 | node_modules 12 | dist 13 | .yarn 14 | -------------------------------------------------------------------------------- /docs/assets/react-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | React Logo 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react18-boilerplate-docs", 3 | "type": "module", 4 | "packageManager": "yarn@3.1.0", 5 | "scripts": { 6 | "build": "webpack", 7 | "dev": "webpack serve" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.26.1", 11 | "highlight.js": "^11.5.0", 12 | "react": "^18.0.0", 13 | "react-dom": "^18.0.0", 14 | "react-router-dom": "^6.2.2", 15 | "remark-html": "^15.0.1" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.17.8", 19 | "@babel/plugin-proposal-class-properties": "^7.16.7", 20 | "@babel/preset-env": "^7.16.11", 21 | "@babel/preset-react": "^7.16.7", 22 | "@svgr/webpack": "^6.2.1", 23 | "babel-loader": "^8.2.4", 24 | "css-loader": "^6.7.1", 25 | "file-loader": "^6.2.0", 26 | "html-loader": "^3.1.0", 27 | "html-webpack-plugin": "^5.5.0", 28 | "markdown-loader": "^8.0.0", 29 | "remark-frontmatter": "^4.0.1", 30 | "style-loader": "^3.3.1", 31 | "webpack": "^5.70.0", 32 | "webpack-cli": "^4.9.2", 33 | "webpack-dev-server": "^4.7.4" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoftproject/create-react18-boilerplate/1d46ab7e8b325c4eda944948152ac1b37ce02bbf/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React 18 BoilerPlate 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import { Routes, Route } from "react-router-dom"; 3 | import "./style.css"; 4 | 5 | import Layout from "./Components/Layout"; 6 | import NavBar from "./Components/NavBar"; 7 | import SideBar from "./Components/SideBar"; 8 | import Content from "./Components/Content"; 9 | import Posts from "./Components/Contents"; 10 | 11 | class App extends React.Component { 12 | render() { 13 | return ( 14 | Loading...}> 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | } /> 23 | } /> 24 | } /> 25 | } /> 26 | } 29 | /> 30 | } 33 | /> 34 | } /> 35 | } 38 | /> 39 | } /> 40 | Not Found} /> 41 | 42 | 43 | 44 |
45 |
46 |
47 | ); 48 | } 49 | } 50 | 51 | export default App; 52 | -------------------------------------------------------------------------------- /docs/src/Components/Content.jsx: -------------------------------------------------------------------------------- 1 | const Content = ({ children }) => { 2 | return
{children}
; 3 | }; 4 | 5 | export default Content; 6 | -------------------------------------------------------------------------------- /docs/src/Components/Contents/BatchContent.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { flushSync } from "react-dom"; 3 | import md from "../../../posts/batch.md"; 4 | 5 | const modes = { BATCH: "BATCH", FLUSH: "FLUSH" }; 6 | 7 | const BatchContent = () => { 8 | const [mode, setMode] = React.useState(modes.BATCH); 9 | const [number, setNumber] = React.useState(0); 10 | const [flag, setFlag] = React.useState(false); 11 | 12 | function handleChangeMode() { 13 | if (mode === modes.BATCH) { 14 | setMode(modes.FLUSH); 15 | } else { 16 | setMode(modes.BATCH); 17 | } 18 | } 19 | 20 | React.useEffect(() => { 21 | if (mode === "BATCH") { 22 | const timeoutId = setTimeout(() => { 23 | // NOTE 24 | // eact will only re-render once at the end 25 | setNumber((c) => c + 1); 26 | setFlag((f) => !f); 27 | }, 1000); 28 | 29 | return () => clearTimeout(timeoutId); 30 | } else if (mode === "FLUSH") { 31 | const timeoutId = setTimeout(() => { 32 | // NOTE 33 | // React will render twice, once for each state update 34 | flushSync(() => { 35 | setNumber((c) => c + 1); 36 | setFlag((f) => !f); 37 | }); 38 | }, 1000); 39 | 40 | return () => clearTimeout(timeoutId); 41 | } else { 42 | throw new Error(`Unhandled mode type (${mode})`); 43 | } 44 | }); 45 | 46 | return ( 47 | <> 48 |

자동 일괄처리

49 |

50 | 이벤트 핸들러 내에서만 일괄처리(Batch)되던 setState가 이제는 모든 51 | 코드에서 적용됩니다. 52 |

53 |
54 |
55 |
Number
56 |
Flag
57 |
58 |
59 |
{number}
60 |
{flag ? "True" : "False"}
61 |
62 |
63 | 66 |

67 | 위 예제의 코드는 68 | 72 | 여기 73 | 74 | 를 참고해주세요. 75 |

76 |
81 | 82 | ); 83 | }; 84 | 85 | export default BatchContent; 86 | -------------------------------------------------------------------------------- /docs/src/Components/Contents/HooksContent.jsx: -------------------------------------------------------------------------------- 1 | import md from "../../../posts/hooks.md"; 2 | 3 | const HooksContent = () => { 4 | return ( 5 | <> 6 |
11 | 12 | ); 13 | }; 14 | 15 | export default HooksContent; 16 | -------------------------------------------------------------------------------- /docs/src/Components/Contents/InstallationContent.jsx: -------------------------------------------------------------------------------- 1 | import md from "../../../posts/installation.md"; 2 | 3 | const InstallationContent = () => { 4 | return ( 5 | <> 6 |
11 | 12 | ); 13 | }; 14 | 15 | export default InstallationContent; 16 | -------------------------------------------------------------------------------- /docs/src/Components/Contents/RenderingAPIContent.jsx: -------------------------------------------------------------------------------- 1 | import md from "../../../posts/renderingAPI.md"; 2 | 3 | const Transition = () => { 4 | return ( 5 | <> 6 |
11 | 12 | ); 13 | }; 14 | 15 | export default Transition; 16 | -------------------------------------------------------------------------------- /docs/src/Components/Contents/StrictModeContent.jsx: -------------------------------------------------------------------------------- 1 | import md from "../../../posts/strictMode.md"; 2 | 3 | const StrictModeContent = () => { 4 | return ( 5 | <> 6 |
11 | 12 | ); 13 | }; 14 | 15 | export default StrictModeContent; 16 | -------------------------------------------------------------------------------- /docs/src/Components/Contents/SuspenseContent.jsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import Fake from "../Fake"; 4 | import ErrorBoundary from "../ErrorBoundary"; 5 | import SectionSpinner from "../Loader/SectionSpinner"; 6 | import md from "../../../posts/suspense.md"; 7 | 8 | const SuspenseContent = () => { 9 | const navigate = useNavigate(); 10 | 11 | return ( 12 | <> 13 |

개선된 기능 : Suspense

14 | 15 |

16 | 데이터를 가져오기 위한 Suspense는 <Suspense>를 사용하여 선언적으로 17 | 데이터를 비롯한 무엇이든 “기다릴” 수 있도록 해주는 새로운 기능입니다. 이 18 | 기능은 이미지, 스크립트, 그 밖의 비동기 작업을 기다리는 데에도 사용될 수 19 | 있습니다. 20 |

21 | 22 | }> 23 | 24 | 25 | 26 | 29 | 30 | 31 |

32 | 위 예제의 코드는 33 | 37 | 여기 38 | 39 | 를 참고해주세요. 40 |

41 | 42 |
47 | 48 | ); 49 | }; 50 | 51 | export default SuspenseContent; 52 | -------------------------------------------------------------------------------- /docs/src/Components/Contents/Transition.jsx: -------------------------------------------------------------------------------- 1 | import md from "../../../posts/startTransition.md"; 2 | 3 | const Transition = () => { 4 | return ( 5 | <> 6 |
11 | 12 | ); 13 | }; 14 | 15 | export default Transition; 16 | -------------------------------------------------------------------------------- /docs/src/Components/Contents/Welcome.jsx: -------------------------------------------------------------------------------- 1 | import md from "../../../posts/welcome.md"; 2 | 3 | const Welcome = () => { 4 | return ( 5 | <> 6 |
11 | 12 | ); 13 | }; 14 | 15 | export default Welcome; 16 | -------------------------------------------------------------------------------- /docs/src/Components/Contents/WorkingContent.jsx: -------------------------------------------------------------------------------- 1 | const Working = () => { 2 | return ( 3 | <> 4 |

현재 이 포스트는 작업중입니다.

5 |

빠른 시일 내에 업데이트 할 수 있도록 하겠습니다.

6 | 7 | ); 8 | }; 9 | 10 | export default Working; 11 | -------------------------------------------------------------------------------- /docs/src/Components/Contents/index.jsx: -------------------------------------------------------------------------------- 1 | import { lazy } from "react"; 2 | 3 | const Transition = lazy(() => import("./Transition")); 4 | const BatchContent = lazy(() => import("./BatchContent")); 5 | const SuspenseContent = lazy(() => import("./SuspenseContent")); 6 | const WorkingContent = lazy(() => import("./WorkingContent")); 7 | const RenderingAPIContent = lazy(() => import("./RenderingAPIContent")); 8 | const StrictModeContent = lazy(() => import("./StrictModeContent")); 9 | const HooksContent = lazy(() => import("./HooksContent")); 10 | const Welcome = lazy(() => import("./Welcome")); 11 | const InstallationContent = lazy(() => import("./InstallationContent")); 12 | 13 | export default { 14 | Transition, 15 | BatchContent, 16 | SuspenseContent, 17 | WorkingContent, 18 | RenderingAPIContent, 19 | StrictModeContent, 20 | HooksContent, 21 | Welcome, 22 | InstallationContent, 23 | }; 24 | -------------------------------------------------------------------------------- /docs/src/Components/ErrorBoundary.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | class ErrorBoundary extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { hasError: false }; 7 | } 8 | 9 | static getDerivedStateFromError(error) { 10 | return { hasError: true }; 11 | } 12 | 13 | render() { 14 | if (this.state.hasError) { 15 | return

컴포넌트 렌더링 실패

; 16 | } 17 | 18 | return this.props.children; 19 | } 20 | } 21 | 22 | export default ErrorBoundary; 23 | -------------------------------------------------------------------------------- /docs/src/Components/Fake.jsx: -------------------------------------------------------------------------------- 1 | import { fetchData } from "../Utils/Api"; 2 | 3 | const resource = fetchData(); 4 | const Fake = () => { 5 | resource.fake.read(); 6 | 7 | return ( 8 | <> 9 |

10 | 컴포넌트가 성공적으로 렌더링 되었습니다. 11 |

12 | 13 | ); 14 | }; 15 | 16 | export default Fake; 17 | -------------------------------------------------------------------------------- /docs/src/Components/Layout.jsx: -------------------------------------------------------------------------------- 1 | const Layout = ({ children }) => { 2 | return
{children}
; 3 | }; 4 | 5 | export default Layout; 6 | -------------------------------------------------------------------------------- /docs/src/Components/Loader/PageSpinner.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoftproject/create-react18-boilerplate/1d46ab7e8b325c4eda944948152ac1b37ce02bbf/docs/src/Components/Loader/PageSpinner.jsx -------------------------------------------------------------------------------- /docs/src/Components/Loader/SectionSpinner.jsx: -------------------------------------------------------------------------------- 1 | import ReactIcon from "../../../assets/react-logo.svg"; 2 | import "./loader.css"; 3 | 4 | const SectionSpinner = () => { 5 | return ( 6 |
7 | 8 |

LOADING ...

9 |
10 | ); 11 | }; 12 | 13 | export default SectionSpinner; 14 | -------------------------------------------------------------------------------- /docs/src/Components/Loader/loader.css: -------------------------------------------------------------------------------- 1 | .loader-icon { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | } 7 | 8 | .loader-icon > svg { 9 | animation: rotate_image 4s linear infinite; 10 | transform-origin: 50% 50%; 11 | } 12 | .loader-icon > p { 13 | letter-spacing: 10px; 14 | font-weight: bold; 15 | } 16 | 17 | @keyframes rotate_image { 18 | 100% { 19 | transform: rotate(360deg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/src/Components/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import ReactIcon from "../../assets/react-logo.svg"; 3 | 4 | const NavBar = () => { 5 | return ( 6 |
7 | 8 | 9 |

10 | 11 | React 18 BoilerPlate 12 | 13 |

14 |
15 |
16 | ); 17 | }; 18 | 19 | export default NavBar; 20 | -------------------------------------------------------------------------------- /docs/src/Components/NavItem.jsx: -------------------------------------------------------------------------------- 1 | import { NavLink } from "react-router-dom"; 2 | 3 | const NavItem = ({ to, children }) => { 4 | return ( 5 | 8 | isActive ? `nav_link activated link` : `nav_link link` 9 | } 10 | > 11 | {children} 12 | 13 | ); 14 | }; 15 | 16 | export default NavItem; 17 | -------------------------------------------------------------------------------- /docs/src/Components/Post.jsx: -------------------------------------------------------------------------------- 1 | import { fetchData } from "../Utils/Api"; 2 | 3 | const resource = fetchData(); 4 | 5 | const Post = () => { 6 | const posts = resource.post.read(); 7 | 8 | const mapPosts = posts.map((post) => ( 9 |
10 |

{post.title}

11 |

{post.body}

12 |
13 | )); 14 | 15 | return <>{mapPosts}; 16 | }; 17 | 18 | export default Post; 19 | -------------------------------------------------------------------------------- /docs/src/Components/SideBar.jsx: -------------------------------------------------------------------------------- 1 | import NavItem from "./NavItem"; 2 | 3 | const SideBar = () => { 4 | return ( 5 |
6 |
    7 |
  • 8 | 시작하기 9 |
  • 10 |
  • 11 | 어떻게 시작하나요? 12 |
  • 13 |
  • 14 | Automatic Batch 15 |
  • 16 |
  • 17 | Suspense 18 |
  • 19 |
  • 20 | Transition 21 |
  • 22 |
  • 23 | 새로운 클라이언트 및 서버 렌더링 API 24 |
  • 25 |
  • 26 | 새로운 Strict Mode동작 27 |
  • 28 |
  • 29 | 새로운 Hooks 30 |
  • 31 |
32 |
33 | ); 34 | }; 35 | 36 | export default SideBar; 37 | -------------------------------------------------------------------------------- /docs/src/Components/User.jsx: -------------------------------------------------------------------------------- 1 | import { fetchData } from "../Utils/Api"; 2 | 3 | const resource = fetchData(); 4 | 5 | const User = () => { 6 | const users = resource.user.read(); 7 | 8 | const mapUsers = users.map((user) => ( 9 |
10 |

11 | {user.name}({user.email}) 12 |

13 |

{user.body}

14 |
15 | )); 16 | 17 | return <>{mapUsers}; 18 | }; 19 | 20 | export default User; 21 | -------------------------------------------------------------------------------- /docs/src/Utils/Api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export const fetchData = () => { 4 | const userPromise = fetchUser(); 5 | const postPromise = fetchPosts(); 6 | const fakePromise = fetchFake(); 7 | 8 | return { 9 | user: wrapPromise(userPromise), 10 | post: wrapPromise(postPromise), 11 | fake: wrapPromise(fakePromise), 12 | }; 13 | }; 14 | 15 | const wrapPromise = (promise) => { 16 | let status = "pending"; 17 | 18 | let result; 19 | 20 | let suspender = promise.then( 21 | (response) => { 22 | status = "success"; 23 | result = response; 24 | }, 25 | (error) => { 26 | status = "error"; 27 | result = error; 28 | } 29 | ); 30 | 31 | return { 32 | read() { 33 | console.log(status); 34 | if (status === "pending") { 35 | throw suspender; 36 | } else if (status === "success") { 37 | return result; 38 | } else if (status === "error") { 39 | throw result; 40 | } 41 | }, 42 | }; 43 | }; 44 | 45 | const fetchUser = () => { 46 | return axios 47 | .get("https://jsonplaceholder.typicode.com/users") 48 | .then((response) => response.data) 49 | .catch(console.error); 50 | }; 51 | 52 | const fetchPosts = () => { 53 | return axios 54 | .get("https://jsonplaceholder.typicode.com/posts") 55 | .then((response) => response.data) 56 | .catch(console.error); 57 | }; 58 | 59 | const fetchFake = () => { 60 | return new Promise((resolve, reject) => { 61 | setTimeout(() => { 62 | if (Math.round(Math.random()) === 0) resolve(true); 63 | else return reject(false); 64 | }, 1500); 65 | }).then((response) => response); 66 | }; 67 | -------------------------------------------------------------------------------- /docs/src/index.jsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from "react-dom/client"; 2 | import { HashRouter } from "react-router-dom"; 3 | import App from "./App"; 4 | 5 | const container = document.getElementById("root"); 6 | const root = createRoot(container); 7 | 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /docs/src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg-color: #20232a; 3 | --main-pr-color: #61dafb; 4 | --main-bg-gray: #f2f2f2; 5 | 6 | --content-text-color: #282c34; 7 | --content-size: 800px; 8 | --content-size-sm: 500px; 9 | --sidebar-size: 400px; 10 | 11 | --content-font-size-small: 12px; 12 | --content-font-size-middle: 16px; 13 | --content-font-size-big: 24px; 14 | } 15 | 16 | html, 17 | body { 18 | width: 100%; 19 | padding: 0; 20 | margin: 0; 21 | } 22 | 23 | a:not(.link) { 24 | background-color: rgba(187, 239, 253, 0.3); 25 | border-bottom: 1px solid rgba(39, 36, 36, 0.2); 26 | color: #1a1a1a; 27 | } 28 | 29 | * > code { 30 | padding: 0 3px; 31 | font-size: 0.94em; 32 | word-break: break-word; 33 | background: rgba(255, 229, 100, 0.2); 34 | color: #1a1a1a; 35 | } 36 | 37 | pre { 38 | padding: 1rem; 39 | } 40 | 41 | img { 42 | width: 100%; 43 | } 44 | 45 | h1 { 46 | margin: 0.67em 0; 47 | color: #282c34; 48 | margin-bottom: 0; 49 | font-size: 60px; 50 | line-height: 65px; 51 | font-weight: 700; 52 | } 53 | 54 | h2 { 55 | font-size: 26px; 56 | font-weight: 300; 57 | color: #6d6d6d; 58 | } 59 | 60 | p { 61 | margin-top: 30px; 62 | font-size: 17px; 63 | line-height: 1.7; 64 | max-width: 42em; 65 | } 66 | 67 | h3 { 68 | display: block; 69 | font-size: 1.5em; 70 | margin-block-start: 0.83em; 71 | margin-block-end: 0.83em; 72 | margin-inline-start: 0px; 73 | margin-inline-end: 0px; 74 | font-weight: bold; 75 | } 76 | 77 | pre > code, 78 | pre { 79 | background: rgb(40, 44, 52); 80 | color: rgb(255, 255, 255); 81 | border-radius: 10px; 82 | overflow: auto; 83 | tab-size: 1.5em; 84 | font-size: 1rem; 85 | } 86 | 87 | blockquote { 88 | background-color: rgba(255, 229, 100, 0.3); 89 | border-left-color: #ffe564; 90 | border-left-width: 9px; 91 | border-left-style: solid; 92 | padding: 20px 45px 20px 26px; 93 | margin-bottom: 30px; 94 | margin-top: 20px; 95 | margin-left: -30px; 96 | margin-right: -30px; 97 | } 98 | 99 | .title { 100 | margin: 0; 101 | height: 100%; 102 | display: flex; 103 | align-items: center; 104 | cursor: pointer; 105 | user-select: none; 106 | } 107 | 108 | .title > h1 { 109 | font-size: 2rem; 110 | padding: 0px; 111 | margin: 0px; 112 | } 113 | .title > h1 > a { 114 | margin-left: 1rem; 115 | text-decoration: none; 116 | color: var(--main-pr-color); 117 | } 118 | 119 | .title:hover { 120 | color: white; 121 | } 122 | 123 | .main { 124 | margin: 0 auto; 125 | padding-top: 5rem; 126 | display: flex; 127 | justify-content: center; 128 | } 129 | 130 | .layout { 131 | width: 100%; 132 | min-height: 100vh; 133 | } 134 | 135 | .nav_bar { 136 | position: fixed; 137 | width: 100%; 138 | height: 4rem; 139 | padding: 0.5rem 1rem; 140 | background-color: var(--main-bg-color); 141 | z-index: 999; 142 | } 143 | 144 | .nav_bar > .title { 145 | margin: 0 auto; 146 | } 147 | 148 | .side_bar { 149 | min-height: 100vh; 150 | min-width: var(--sidebar-size); 151 | background-color: var(--main-bg-gray); 152 | position: fixed; 153 | left: 0; 154 | } 155 | 156 | .side_bar > ul { 157 | list-style: none; 158 | } 159 | 160 | .nav_link { 161 | text-decoration: none; 162 | color: var(--content-text-color); 163 | font-size: var(--content-font-size-middle); 164 | line-height: 30px; 165 | } 166 | 167 | .activated { 168 | font-weight: bold; 169 | } 170 | 171 | .activated::before { 172 | content: ""; 173 | width: 4px; 174 | height: 24px; 175 | border-right: 4px solid var(--main-pr-color); 176 | padding-left: 16px; 177 | position: absolute; 178 | left: 0; 179 | } 180 | 181 | .box-container { 182 | border-radius: 5px; 183 | overflow: hidden; 184 | box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; 185 | } 186 | 187 | .head { 188 | padding: 1rem 0.3rem; 189 | display: flex; 190 | background-color: var(--main-bg-color); 191 | color: var(--main-pr-color); 192 | font-size: var(--content-font-size-big); 193 | font-weight: bold; 194 | } 195 | 196 | .head > .name { 197 | flex: 1; 198 | padding: 0 1rem; 199 | } 200 | 201 | .body { 202 | display: flex; 203 | background-color: var(--main-bg-gray); 204 | font-size: var(--content-font-size-middle); 205 | } 206 | 207 | .body > .value { 208 | padding: 1rem 1rem; 209 | flex: 1; 210 | } 211 | 212 | .btn { 213 | border: none; 214 | color: var(--content-text-color); 215 | padding: 0.6rem 0.8rem; 216 | display: inline-block; 217 | font-size: 16px; 218 | background-color: rgb(97, 218, 251); 219 | padding: 10px 25px; 220 | white-space: nowrap; 221 | transition: background-color 0.2s ease-out 0s; 222 | cursor: pointer; 223 | } 224 | 225 | .change { 226 | margin-top: 1rem; 227 | } 228 | 229 | .content { 230 | flex-wrap: wrap; 231 | word-break: break-all; 232 | width: var(--content-size); 233 | min-width: var(--content-size); 234 | margin: 0 auto; 235 | padding: 1rem; 236 | overflow-y: auto; 237 | } 238 | 239 | .suspense_block { 240 | border-left-width: 9px; 241 | border-left-style: solid; 242 | padding: 20px 45px 20px 26px; 243 | margin-bottom: 30px; 244 | margin-top: 20px; 245 | margin-left: -30px; 246 | margin-right: -30px; 247 | } 248 | 249 | .warn { 250 | background-color: rgba(255, 229, 100, 0.3); 251 | border-left-color: #ffe564; 252 | } 253 | .success { 254 | background-color: rgba(187, 239, 253, 0.3); 255 | border-left-color: 1px solid rgba(0, 0, 0, 0.2); 256 | } 257 | 258 | @media (max-width: 1600px) { 259 | .main { 260 | display: flex; 261 | flex-direction: column-reverse; 262 | justify-content: start; 263 | } 264 | 265 | .side_bar { 266 | position: relative; 267 | width: 100%; 268 | min-height: 0; 269 | height: auto; 270 | background-color: var(--main-bg-gray); 271 | overflow-y: auto; 272 | } 273 | 274 | .content { 275 | min-width: 80%; 276 | min-width: 0; 277 | min-height: 80vh; 278 | margin: 0 auto; 279 | padding: 1rem; 280 | overflow-y: auto; 281 | display: flex; 282 | align-items: flex-start; 283 | justify-content: start; 284 | flex-direction: column; 285 | } 286 | 287 | .post_title { 288 | margin: 0.67em 0; 289 | color: #282c34; 290 | margin-bottom: 0; 291 | font-size: 3em; 292 | font-weight: 700; 293 | } 294 | 295 | .post_subtitle { 296 | font-size: 18px; 297 | font-weight: 300; 298 | color: #6d6d6d; 299 | } 300 | 301 | .box-container { 302 | width: 100%; 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /docs/webpack.config.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import HTMLWebpackPlugin from "html-webpack-plugin"; 3 | import RemarkHTML from "remark-html"; 4 | import RemarkFrontmatter from "remark-frontmatter"; 5 | 6 | export default { 7 | name: "React-18_Boiler-Plate", 8 | mode: "development", 9 | entry: "./src/index.jsx", 10 | output: { 11 | filename: "bundle.[hash].js", 12 | path: path.resolve("dist"), 13 | publicPath: "/", 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.(js|jsx)$/, 19 | exclude: /node_modules/, 20 | use: "babel-loader", 21 | }, 22 | { 23 | test: /\.css$/, 24 | use: ["style-loader", "css-loader"], 25 | }, 26 | { 27 | test: /\.(png)$/, 28 | use: [ 29 | { 30 | loader: "file-loader", 31 | options: { 32 | name: "images/[name].[ext]?[hash]", 33 | }, 34 | }, 35 | ], 36 | }, 37 | { 38 | test: /\.svg$/, 39 | use: ["@svgr/webpack"], 40 | }, 41 | { 42 | test: /\.md$/, 43 | use: [ 44 | { 45 | loader: "html-loader", 46 | }, 47 | { 48 | loader: "markdown-loader", 49 | options: { 50 | remarkOptions: { 51 | plugins: [RemarkFrontmatter, RemarkHTML], 52 | }, 53 | }, 54 | }, 55 | ], 56 | }, 57 | ], 58 | }, 59 | resolve: { 60 | extensions: [".js", ".jsx"], 61 | }, 62 | plugins: [ 63 | new HTMLWebpackPlugin({ 64 | template: "./public/index.html", 65 | }), 66 | ], 67 | devServer: { 68 | static: { 69 | directory: path.join(path.resolve(), "public"), 70 | }, 71 | compress: true, 72 | port: 3080, 73 | }, 74 | }; 75 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React 18 BoilerPlate 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | import arg from "arg"; 2 | import inquirer from "inquirer"; 3 | import { createProject } from "./main"; 4 | 5 | function parseArgumentsIntoOptions(rawArgs) { 6 | const args = arg( 7 | { 8 | "--skip": Boolean, 9 | "--typescript": String, 10 | "--yarn": String, 11 | 12 | "-s": "--skip", 13 | "-t": "--typescript", 14 | "-y": "--yarn", 15 | }, 16 | { 17 | argv: rawArgs.slice(2), 18 | } 19 | ); 20 | 21 | return { 22 | skipPrompt: args["--skip"] || false, 23 | template: args._[0], 24 | packageManager: args["--yarn"] || args["-y"], 25 | }; 26 | } 27 | 28 | async function promptForMissingOptions(options) { 29 | const defaultTemplate = "JavaScript"; 30 | const defaultPackageManager = "npm"; 31 | 32 | if (options.skipPrompt) { 33 | return { 34 | ...options, 35 | template: options.template || defaultTemplate, 36 | }; 37 | } 38 | 39 | const questions = []; 40 | if (!options.template) { 41 | questions.push({ 42 | type: "list", 43 | name: "template", 44 | message: "Please choose which project template to use", 45 | choices: ["JavaScript", "TypeScript"], 46 | default: defaultTemplate, 47 | }); 48 | } 49 | 50 | if (!options.packageManager) { 51 | questions.push({ 52 | type: "list", 53 | name: "packageManager", 54 | message: "Which package manager do you prefer?", 55 | choices: ["NPM", "Yarn"], 56 | default: defaultPackageManager, 57 | }); 58 | } 59 | 60 | const answer = await inquirer.prompt(questions); 61 | return { 62 | ...options, 63 | template: options.template || answer.template, 64 | packageManager: options.packageManager || answer.packageManager, 65 | }; 66 | } 67 | 68 | export async function cli(args) { 69 | let options = parseArgumentsIntoOptions(args); 70 | options = await promptForMissingOptions(options); 71 | await createProject(options); 72 | } 73 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require = require("esm")(module); 2 | require("./cli").cli(process.argv); 3 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | import fs from "fs"; 3 | import ncp from "ncp"; 4 | import path from "path"; 5 | import { promisify } from "util"; 6 | import Listr from "listr"; 7 | import { projectInstall } from "pkg-install"; 8 | 9 | const access = promisify(fs.access); 10 | const copy = promisify(ncp); 11 | 12 | async function copyTemplateFiles(options) { 13 | return copy(options.templateDirectory, options.targetDirectory, { 14 | clobber: false, 15 | }); 16 | } 17 | 18 | async function initGit(options) { 19 | const result = await execa("git", ["init"], { 20 | cwd: options.targetDirectory, 21 | }); 22 | if (result.failed) { 23 | return Promise.reject(new Error("Failed to initialize git")); 24 | } 25 | return; 26 | } 27 | 28 | export async function createProject(options) { 29 | options = { 30 | ...options, 31 | targetDirectory: options.targetDirectory || process.cwd(), 32 | }; 33 | 34 | const templateDir = path.resolve(__filename, "../../templates", options.template.toLowerCase()); 35 | options.templateDirectory = templateDir; 36 | 37 | try { 38 | await access(templateDir, fs.constants.R_OK); 39 | } catch (err) { 40 | console.error("%s Invalid template name", chalk.red.bold("ERROR")); 41 | process.exit(1); 42 | } 43 | const tasks = new Listr([ 44 | { 45 | title: "Copy Project Files", 46 | task: () => copyTemplateFiles(options), 47 | }, 48 | { 49 | title: "Install dependencies", 50 | task: () => 51 | projectInstall({ 52 | cwd: options.targetDirectory, 53 | prefer: options.packageManager.toLowerCase(), 54 | }), 55 | }, 56 | ]); 57 | 58 | await tasks.run(); 59 | 60 | options.packageManager.toLowerCase() === "npm" ? npmBuildScripts(options) : yarnBuildScripts(options); 61 | 62 | return true; 63 | } 64 | 65 | function npmBuildScripts(options) { 66 | console.log("%s Project ready", chalk.green.bold("DONE")); 67 | console.log(`Success! Created Project at ${options.targetDirectory}`); 68 | console.log(`${chalk.cyan("You can run several commands: ")}`); 69 | 70 | console.log(` 71 | ${chalk.cyan("npm run dev")} 72 | Starts the development server. 73 | 74 | ${chalk.cyan("npm run build")} 75 | Bundles the app into static files for production. 76 | `); 77 | } 78 | 79 | function yarnBuildScripts(options) { 80 | console.log("%s Project ready", chalk.green.bold("DONE")); 81 | console.log(`Success! Created Project at ${options.targetDirectory}`); 82 | console.log(`${chalk.cyan("You can run several commands: ")}`); 83 | 84 | console.log(` 85 | ${chalk.cyan("yarn dev")} 86 | Starts the development server. 87 | 88 | ${chalk.cyan("yarn run build")} 89 | Bundles the app into static files for production. 90 | `); 91 | } 92 | -------------------------------------------------------------------------------- /templates/javascript/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", ["@babel/preset-react", { "runtime": "automatic" }]] 3 | } 4 | -------------------------------------------------------------------------------- /templates/javascript/.gitignore: -------------------------------------------------------------------------------- 1 | /.yarn/* 2 | !/.yarn/patches 3 | !/.yarn/plugins 4 | !/.yarn/releases 5 | !/.yarn/sdks 6 | 7 | # Swap the comments on the following lines if you don't wish to use zero-installs 8 | # Documentation here: https://yarnpkg.com/features/zero-installs 9 | !/.yarn/cache 10 | #/.pnp.* 11 | node_modules 12 | dist 13 | .yarn 14 | -------------------------------------------------------------------------------- /templates/javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react18-boilerplate", 3 | "scripts": { 4 | "build": "webpack", 5 | "dev": "webpack serve --open" 6 | }, 7 | "dependencies": { 8 | "axios": "^0.26.1", 9 | "react": "^18.0.0", 10 | "react-dom": "^18.0.0", 11 | "react-router-dom": "^6.2.2" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "^7.17.8", 15 | "@babel/plugin-proposal-class-properties": "^7.16.7", 16 | "@babel/preset-env": "^7.16.11", 17 | "@babel/preset-react": "^7.16.7", 18 | "@svgr/webpack": "^6.2.1", 19 | "babel-loader": "^8.2.4", 20 | "css-loader": "^6.7.1", 21 | "file-loader": "^6.2.0", 22 | "html-webpack-plugin": "^5.5.0", 23 | "style-loader": "^3.3.1", 24 | "webpack": "^5.70.0", 25 | "webpack-cli": "^4.9.2", 26 | "webpack-dev-server": "^4.7.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /templates/javascript/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React 18 BoilerPlate 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /templates/javascript/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | class App extends React.Component { 4 | render() { 5 | return <>React 18 BoilerPlate; 6 | } 7 | } 8 | 9 | export default App; 10 | -------------------------------------------------------------------------------- /templates/javascript/src/index.jsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from "react-dom/client"; 2 | import App from "./App"; 3 | 4 | const container = document.getElementById("root"); 5 | const root = createRoot(container); 6 | 7 | root.render(); 8 | -------------------------------------------------------------------------------- /templates/javascript/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HTMLWebpackPlugin = require("html-webpack-plugin"); 3 | 4 | module.exports = { 5 | name: "React-18_Boiler-Plate", 6 | mode: "development", 7 | entry: "./src/index.jsx", 8 | output: { 9 | filename: "bundle.[chunkhash].js", 10 | path: path.resolve("dist"), 11 | publicPath: "/", 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.(js|jsx)$/, 17 | exclude: /node_modules/, 18 | use: "babel-loader", 19 | }, 20 | { 21 | test: /\.css$/, 22 | use: ["style-loader", "css-loader"], 23 | }, 24 | { 25 | test: /\.(png)$/, 26 | use: [ 27 | { 28 | loader: "file-loader", 29 | options: { 30 | name: "images/[name].[ext]?[chunkhash]", 31 | }, 32 | }, 33 | ], 34 | }, 35 | { 36 | test: /\.svg$/, 37 | use: ["@svgr/webpack"], 38 | }, 39 | ], 40 | }, 41 | resolve: { 42 | extensions: [".js", ".jsx"], 43 | }, 44 | plugins: [ 45 | new HTMLWebpackPlugin({ 46 | template: "./public/index.html", 47 | }), 48 | ], 49 | devServer: { 50 | static: { 51 | directory: path.join(__dirname, "public"), 52 | }, 53 | compress: true, 54 | port: 3080, 55 | open: true, 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /templates/typescript/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { "targets": { "browsers": ["last 2 versions", ">= 5% in KR"] } }], 4 | "@babel/react", 5 | "@babel/typescript" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /templates/typescript/.gitignore: -------------------------------------------------------------------------------- 1 | /.yarn/* 2 | !/.yarn/patches 3 | !/.yarn/plugins 4 | !/.yarn/releases 5 | !/.yarn/sdks 6 | 7 | # Swap the comments on the following lines if you don't wish to use zero-installs 8 | # Documentation here: https://yarnpkg.com/features/zero-installs 9 | !/.yarn/cache 10 | #/.pnp.* 11 | node_modules 12 | dist 13 | .yarn 14 | -------------------------------------------------------------------------------- /templates/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react18-boilerplate", 3 | "scripts": { 4 | "build": "webpack", 5 | "dev": "webpack serve --open" 6 | }, 7 | "dependencies": { 8 | "axios": "^0.26.1", 9 | "react": "^18.0.0", 10 | "react-dom": "^18.0.0", 11 | "react-router-dom": "^6.2.2" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "^7.17.8", 15 | "@babel/plugin-proposal-class-properties": "^7.16.7", 16 | "@babel/preset-env": "^7.16.11", 17 | "@babel/preset-react": "^7.16.7", 18 | "@babel/preset-typescript": "^7.16.7", 19 | "@svgr/webpack": "^6.2.1", 20 | "@types/react": "^17.0.43", 21 | "@types/react-dom": "^17.0.14", 22 | "babel-loader": "^8.2.4", 23 | "css-loader": "^6.7.1", 24 | "file-loader": "^6.2.0", 25 | "fork-ts-checker-webpack-plugin": "^7.2.1", 26 | "html-webpack-plugin": "^5.5.0", 27 | "style-loader": "^3.3.1", 28 | "ts-loader": "^9.2.8", 29 | "typescript": "^4.6.3", 30 | "webpack": "^5.70.0", 31 | "webpack-cli": "^4.9.2", 32 | "webpack-dev-server": "^4.7.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /templates/typescript/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React 18 BoilerPlate 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /templates/typescript/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | class App extends React.Component { 4 | render() { 5 | return <>React 18 BoilerPlate Typescript; 6 | } 7 | } 8 | 9 | export default App; 10 | -------------------------------------------------------------------------------- /templates/typescript/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from "react-dom/client"; 2 | import App from "./App"; 3 | 4 | const container = document.getElementById("root"); 5 | const root = createRoot(container); 6 | 7 | root.render(); 8 | -------------------------------------------------------------------------------- /templates/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "noResolve": false, 7 | "noImplicitAny": false, 8 | "removeComments": false, 9 | "sourceMap": true, 10 | "allowJs": true, 11 | "jsx": "react-jsx", 12 | "allowSyntheticDefaultImports": true, 13 | "keyofStringsOnly": true 14 | }, 15 | "typeRoots": ["node_modules/@types", "src/@type"], 16 | "exclude": [ 17 | "node_modules", 18 | "build", 19 | "scripts", 20 | "acceptance-tests", 21 | "webpack", 22 | "jest", 23 | "src/setupTests.ts", 24 | "./node_modules/**/*" 25 | ], 26 | "include": ["./src/**/*", "@type"] 27 | } 28 | -------------------------------------------------------------------------------- /templates/typescript/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HTMLWebpackPlugin = require("html-webpack-plugin"); 3 | const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); 4 | 5 | module.exports = { 6 | name: "React-18_Boiler-Plate", 7 | mode: "development", 8 | entry: "./src/index.tsx", 9 | output: { 10 | filename: "bundle.[chunkhash].js", 11 | path: path.resolve("dist"), 12 | publicPath: "/", 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.(ts|tsx)$/, 18 | exclude: /node_modules/, 19 | use: [ 20 | "babel-loader", 21 | { 22 | loader: "ts-loader", 23 | options: { 24 | transpileOnly: true, 25 | }, 26 | }, 27 | ], 28 | }, 29 | { 30 | test: /\.css$/, 31 | use: ["style-loader", "css-loader"], 32 | }, 33 | { 34 | test: /\.(png)$/, 35 | use: [ 36 | { 37 | loader: "file-loader", 38 | options: { 39 | name: "images/[name].[ext]?[chunkhash]", 40 | }, 41 | }, 42 | ], 43 | }, 44 | { 45 | test: /\.svg$/, 46 | use: ["@svgr/webpack"], 47 | }, 48 | ], 49 | }, 50 | resolve: { 51 | extensions: [".js", ".jsx", ".ts", ".tsx"], 52 | }, 53 | plugins: [ 54 | new HTMLWebpackPlugin({ 55 | template: "./public/index.html", 56 | }), 57 | new ForkTsCheckerWebpackPlugin(), 58 | ], 59 | devServer: { 60 | static: { 61 | directory: path.join(__dirname, "public"), 62 | }, 63 | compress: true, 64 | port: 3080, 65 | }, 66 | }; 67 | --------------------------------------------------------------------------------