├── .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 |
64 | 모드 변경 (현재 모드 : {mode})
65 |
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 | navigate(0)} className="btn">
27 | 새로고침
28 |
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 |
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 |
--------------------------------------------------------------------------------