;
5 | }
6 | }
7 |
8 | Cypress.Commands.add('customCommand', () => {
9 | cy.setCookie('UID', Cypress.env('auth_token'));
10 | cy.server();
11 | cy.route('POST', '/api/auth', 'fixture:auth.json');
12 | });
13 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const webpack = require('@cypress/webpack-preprocessor');
3 |
4 | module.exports = on => {
5 | const options = {
6 | // send in the options from your webpack.config.js, so it works the same
7 | // as your app's code
8 | webpackOptions: require('../webpack.config'),
9 | watchOptions: {},
10 | };
11 |
12 | on('file:preprocessor', webpack(options));
13 | };
14 |
--------------------------------------------------------------------------------
/.storybook/theme-decorator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ThemeProvider } from 'styled-components';
3 | import defaultTheme from '../src/commons/style/themes/default';
4 | import Normalize from '../src/commons/style/normalize';
5 | import GlobalStyles from '../src/commons/style/globalstyle';
6 |
7 | export default story => (
8 |
9 |
10 |
11 | {story()}
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/cypress/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const path = require('path');
3 |
4 | module.exports = {
5 | entry: './src/index.ts',
6 | module: {
7 | rules: [
8 | {
9 | test: /\.tsx?/,
10 | use: 'ts-loader',
11 | exclude: /node_modules/,
12 | },
13 | ],
14 | },
15 | resolve: {
16 | extensions: ['.tsx', '.ts', '.js'],
17 | },
18 | output: {
19 | filename: 'bundle.js',
20 | path: path.resolve(__dirname, 'dist'),
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ThemeProvider } from 'styled-components';
3 |
4 | import 'App.css';
5 | import GlobalStyles from './commons/style/globalstyle';
6 | import defaultTheme from 'commons/style/themes/default';
7 |
8 | const App: React.FC = () => {
9 | return (
10 |
11 |
12 |
13 |
Welcome frontend boilerplate :)
14 |
15 |
16 | );
17 | };
18 |
19 | export default App;
20 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const Dotenv = require('dotenv-webpack');
2 |
3 | module.exports = ({ config, mode }) => {
4 | config.plugins.push(new Dotenv());
5 |
6 | config.module.rules.push({
7 | test: /\.tsx?$/,
8 | use: [
9 | {
10 | loader: require.resolve('babel-loader'),
11 | options: {
12 | presets: [require.resolve('babel-preset-react-app')],
13 | },
14 | },
15 | require.resolve('react-docgen-typescript-loader'),
16 | ],
17 | });
18 |
19 | config.resolve.extensions.push('.ts', '.tsx');
20 |
21 | return config;
22 | };
23 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Frontend Workspace",
3 | "name": "Frontend Workspace",
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 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "typeRoots": ["./node_modules/@types"],
3 | "compilerOptions": {
4 | "target": "es5",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "esModuleInterop": true,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "rootDirs": ["src"],
15 | "baseUrl": "src",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "noEmit": true,
19 | "jsx": "preserve"
20 | },
21 | "include": ["src"],
22 | "exclude": ["node_modules", "build", "scripts"]
23 | }
24 |
--------------------------------------------------------------------------------
/.storybook/BeautifyStory/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import * as S from './style';
4 |
5 | const PrettyWrapper = styled.div`
6 | padding: 2rem 2rem 2rem 4rem;
7 | `;
8 |
9 | export const prettyWrapperDecorator = story => (
10 | {story()}
11 | );
12 |
13 | export const infoBody = {
14 | padding: '0',
15 | margin: '0',
16 | };
17 |
18 | export const infoStory = {
19 | padding: '3rem',
20 | boxShadow: '#ccc 2px 2px 4px 2px',
21 | margin: '4rem 0rem',
22 | };
23 |
24 | interface Props {
25 | content: string;
26 | }
27 |
28 | export function Note({ content }: Props): React.ReactElement {
29 | return (
30 | <>
31 | Note :
32 | {content}
33 | >
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 | Frontend Workspace
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Sungdong Jo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/commons/style/globalstyle.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import { theme } from 'styled-tools';
3 |
4 | const GlobalStyles = createGlobalStyle`
5 | @import url('https://fonts.googleapis.com/css?family=Gothic+A1|Noto+Sans+KR&display=swap');
6 | * {
7 | margin: 0;
8 | font-family: 'Spoqa Han Sans', 'Noto Sans KR', 'Gothic A1', sans-serif;
9 | box-sizing: border-box;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 | html {
14 | font-size: 10px;
15 | }
16 | body {
17 | height: 100%;
18 | padding: 0rem 5rem;
19 | }
20 | a, button {
21 | text-decoration: none;
22 | cursor: pointer;
23 | }
24 |
25 | h1 {
26 | ${theme('fontStyle.h1')}
27 | }
28 |
29 | h2 {
30 | ${theme('fontStyle.h2')}
31 | }
32 |
33 | h3 {
34 | ${theme('fontStyle.h3')}
35 | }
36 |
37 | h4 {
38 | ${theme('fontStyle.h4')}
39 | }
40 |
41 | h5 {
42 | ${theme('fontStyle.h5')}
43 | }
44 |
45 | h6 {
46 | ${theme('fontStyle.h6')}
47 | }
48 |
49 | p {
50 | ${theme('fontStyle.body1')}
51 | }
52 | `;
53 |
54 | export default GlobalStyles;
55 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "parserOptions": {
4 | "ecmaVersion": 2018,
5 | "sourceType": "module",
6 | "ecmaFeatures": {
7 | "impliedStrict": true
8 | }
9 | },
10 | "extends": [
11 | "react-app",
12 | "plugin:@typescript-eslint/recommended",
13 | "prettier/@typescript-eslint",
14 | "plugin:cypress/recommended"
15 | ],
16 | "plugins": ["react", "jest", "cypress"],
17 | "env": {
18 | "es6": true,
19 | "node": true,
20 | "browser": true,
21 | "jest/globals": true,
22 | "cypress/globals": true
23 | },
24 | "globals": {
25 | "Atomics": "readonly",
26 | "SharedArrayBuffer": "readonly"
27 | },
28 | "rules": {
29 | "react-hooks/rules-of-hooks": "error",
30 | "react-hooks/exhaustive-deps": "warn",
31 | "@typescript-eslint/no-unused-vars": "error",
32 | "jest/no-disabled-tests": "warn",
33 | "jest/no-focused-tests": "error",
34 | "jest/no-identical-title": "error",
35 | "jest/prefer-to-have-length": "warn",
36 | "jest/valid-expect": "off",
37 | "cypress/no-assigning-return-values": "error",
38 | "cypress/no-unnecessary-waiting": "off",
39 | "cypress/assertion-before-screenshot": "warn",
40 | "no-unused-expressions": 0
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.storybook/config.ts:
--------------------------------------------------------------------------------
1 | import { configure, addParameters, addDecorator } from '@storybook/react';
2 | import { withKnobs } from '@storybook/addon-knobs';
3 | import { themes } from '@storybook/theming';
4 | import { withInfo } from '@storybook/addon-info';
5 | import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
6 | import { withA11y } from '@storybook/addon-a11y';
7 |
8 | import themeDecorator from './theme-decorator';
9 | import routerDecorator from './router-decorator';
10 | import * as beautifyStory from './BeautifyStory';
11 |
12 | addDecorator(withKnobs);
13 | addDecorator(withInfo);
14 | addDecorator(withA11y);
15 | addDecorator(themeDecorator);
16 | addDecorator(routerDecorator);
17 | addDecorator(beautifyStory.prettyWrapperDecorator);
18 | addParameters({
19 | options: {
20 | theme: themes.light,
21 | panelPosition: 'bottom',
22 | sidebarAnimations: true,
23 | showPanel: true,
24 | hierarchySeparator: /\/|\./,
25 | hierarchyRootSeparator: '|',
26 | },
27 | info: {
28 | styles: {
29 | infoBody: beautifyStory.infoBody,
30 | infoStory: beautifyStory.infoStory,
31 | },
32 | inline: true,
33 | header: true,
34 | },
35 |
36 | viewport: {
37 | viewports: INITIAL_VIEWPORTS,
38 | defaultViewport: 'someDefault',
39 | },
40 | });
41 |
42 | configure(require.context('../src', true, /\.stories\.tsx?$/), module);
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # frontend-boilerplate
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | This repository is my frontend boilerplate. Feel free to use this.
13 |
14 | ## Stack
15 |
16 | - Typescript
17 | - React (based CRA), styled-component
18 | - Storybook
19 | - Jest, react-testing-library, Cypress
20 | - Eslint, Stylelint, Commitlint, Prettier, Editorconfig
21 |
22 | ## Library
23 | - [http-status](https://www.npmjs.com/package/http-status)
24 | - [immer](https://www.npmjs.com/package/immer)
25 | - [react-icons](https://www.npmjs.com/package/react-icons)
26 | - [shortid](https://www.npmjs.com/package/shortid)
27 | - [styled-tools](https://www.npmjs.com/package/styled-tools)
28 |
--------------------------------------------------------------------------------
/src/commons/style/themes/default.ts:
--------------------------------------------------------------------------------
1 | const theme = {
2 | palette: {
3 | primary: '#FF2D54',
4 | secondary: '#55A6FC',
5 | danger: '#d32f2f',
6 | alert: '#ffa000',
7 | success: '#388e3c',
8 | white: '#fff',
9 | black: '#212121',
10 | transparent: 'transparent',
11 | grayscale: [
12 | '#212121',
13 | '#414141',
14 | '#616161',
15 | '#9e9e9e',
16 | '#bdbdbd',
17 | '#e0e0e0',
18 | '#eeeeee',
19 | '#ffffff',
20 | ],
21 | opacityscale: [0.9, 0.8, 0.7, 0.6, 0.5],
22 | },
23 |
24 | fontStyle: {
25 | h1: `
26 | font-size: 9.6rem;
27 | letter-spacing: -0.15rem;
28 | font-weight: 300;
29 | `,
30 | h2: `
31 | font-size: 6rem;
32 | letter-spacing: -0.05rem;
33 | font-weight: 300;
34 | `,
35 | h3: `
36 | font-size: 4.8rem;
37 | font-weight: 400;
38 | `,
39 | h4: `
40 | font-size: 3.4rem;
41 | letter-spacing: -0.25rem;
42 | font-weight: 400;
43 | `,
44 | h5: `
45 | font-size: 2.4rem;
46 | font-weight: 400;
47 | `,
48 | h6: `
49 | font-size: 2rem;
50 | font-weight: 500;
51 | letter-spacing: 0.015rem;
52 | `,
53 | subtitle1: `
54 | font-size: 1.6rem;
55 | font-weight: 400;
56 | letter-spacing: 0.015rem;
57 | `,
58 | subtitle2: `
59 | font-size: 1.4rem;
60 | font-weight: 500;
61 | letter-spacing: 0.01rem;
62 | `,
63 | body1: `
64 | font-size: 1.6rem;
65 | font-weight: 400;
66 | letter-spacing: 0.05rem;
67 | `,
68 | body2: `
69 | font-size: 1.4rem;
70 | font-weight: 400;
71 | letter-spacing: 0.025rem;
72 | `,
73 | button: `
74 | font-size: 1.4rem;
75 | font-weight: bold;
76 | letter-spacing: 0.0125rem;
77 | `,
78 | caption: `
79 | font-size: 1.2rem;
80 | font-weight: 400;
81 | letter-spacing: 0.04rem;
82 | `,
83 | overline: `
84 | font-size: 1rem;
85 | font-weight: 400;
86 | letter-spacing: 0.015rem;
87 | `,
88 | },
89 | };
90 |
91 | export default theme;
92 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend-workspace",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "react-scripts start",
7 | "build": "react-scripts build",
8 | "build-storybook": "build-storybook -s public",
9 | "storybook": "start-storybook -p 9009 -s public",
10 | "cypress": "cypress run",
11 | "cypress:open": "cypress open",
12 | "cypress:record": "cypress run --record --parallel",
13 | "test": "react-scripts test --coverage --verbose --env=jsdom --watchAll=false && yarn test:cypress",
14 | "test:jest": "react-scripts test --coverage --verbose --watchAll=false --env=jsdom",
15 | "test:watch": "react-scripts test --env=jsdom --coverage --verbose --watchAll=true",
16 | "test:cypress": "start-server-and-test start http://localhost:3000 cypress",
17 | "eject": "react-scripts eject",
18 | "lint:tsc": "tsc --noEmit",
19 | "lint:eslint": "eslint . --cache --ext .ts,.tsx",
20 | "lint:stylehint": "stylelint \"src/**/*.{ts,css,scss}\"",
21 | "lint": "npm-run-all --parallel -c lint:*",
22 | "fix:eslint": "eslint . --cache --fix --ext .ts,.tsx",
23 | "fix:stylehint": "stylelint \"**/*.{ts,css,scss}\" --fix",
24 | "fix": "npm-run-all --parallel -c fix:*",
25 | "precommit": "tsc --noEmit && lint-staged",
26 | "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,scss,css,md}\""
27 | },
28 | "dependencies": {
29 | "axios": "^0.19.0",
30 | "http-status": "^1.4.2",
31 | "immer": "^5.0.0",
32 | "react": "^16.11.0",
33 | "react-dom": "^16.11.0",
34 | "react-icons": "^3.8.0",
35 | "react-router-dom": "^5.1.2",
36 | "shortid": "^2.2.15",
37 | "styled-components": "^4.4.1",
38 | "styled-tools": "^1.7.1"
39 | },
40 | "devDependencies": {
41 | "@babel/core": "^7.0.0-0",
42 | "@commitlint/cli": "^8.2.0",
43 | "@commitlint/config-conventional": "^8.2.0",
44 | "@cypress/webpack-preprocessor": "^4.1.1",
45 | "@storybook/addon-a11y": "^5.2.5",
46 | "@storybook/addon-actions": "^5.2.6",
47 | "@storybook/addon-info": "^5.2.6",
48 | "@storybook/addon-knobs": "^5.2.5",
49 | "@storybook/addon-links": "^5.2.6",
50 | "@storybook/addon-storysource": "^5.2.5",
51 | "@storybook/addon-viewport": "^5.2.5",
52 | "@storybook/addons": "^5.2.6",
53 | "@storybook/react": "^5.2.6",
54 | "@testing-library/jest-dom": "^5.0.0",
55 | "@testing-library/react": "^9.4.0",
56 | "@types/google-map-react": "^1.1.3",
57 | "@types/http-status": "^1.1.2",
58 | "@types/jest": "^24.0.23",
59 | "@types/node": "^12.12.14",
60 | "@types/react": "^16.9.13",
61 | "@types/react-dom": "^16.9.4",
62 | "@types/react-icons": "^3.0.0",
63 | "@types/react-router-dom": "^5.1.2",
64 | "@types/storybook__react": "^4.0.2",
65 | "@types/styled-components": "^4.4.0",
66 | "@typescript-eslint/eslint-plugin": "^2.6.1",
67 | "@typescript-eslint/parser": "^2.6.1",
68 | "babel-loader": "^8.0.6",
69 | "core-js": "^3.6.1",
70 | "cypress": "3.8.0",
71 | "dotenv-webpack": "^1.7.0",
72 | "eslint": "^6.6.0",
73 | "eslint-config-airbnb": "^18.0.1",
74 | "eslint-config-prettier": "^6.5.0",
75 | "eslint-plugin-cypress": "^2.7.0",
76 | "eslint-plugin-import": "^2.18.2",
77 | "eslint-plugin-jest": "^23.0.3",
78 | "eslint-plugin-prettier": "^3.1.1",
79 | "eslint-plugin-react": "^7.16.0",
80 | "eslint-plugin-react-hooks": "^1.7.0",
81 | "husky": "^3.1.0",
82 | "intersection-observer": "^0.7.0",
83 | "jest": "^24.9.0",
84 | "lint-staged": "^9.4.2",
85 | "npm-run-all": "^4.1.5",
86 | "prettier": "^1.19.1",
87 | "prop-types": "^15.0.0-0",
88 | "react-docgen-typescript-loader": "^3.4.0",
89 | "react-scripts": "^3.2.0",
90 | "start-server-and-test": "^1.10.7",
91 | "stylelint": "^12.0.1",
92 | "stylelint-config-recommended": "^3.0.0",
93 | "ts-jest": "^24.1.0",
94 | "ts-loader": "^6.2.1",
95 | "typescript": "^3.7.2"
96 | },
97 | "jest": {
98 | "transform": {
99 | "^.+\\.ts(x)?$": "ts-jest"
100 | },
101 | "collectCoverageFrom": [
102 | "src/**/*.ts?(x)",
103 | "!src/**/style.ts",
104 | "!src/**/*.stories.tsx"
105 | ],
106 | "coverageReporters": [
107 | "json",
108 | "lcov",
109 | "text"
110 | ]
111 | },
112 | "browserslist": {
113 | "production": [
114 | ">0.2%",
115 | "not dead",
116 | "not op_mini all"
117 | ],
118 | "development": [
119 | "last 1 chrome version",
120 | "last 1 firefox version",
121 | "last 1 safari version"
122 | ]
123 | },
124 | "lint-staged": {
125 | "*.{ts,tsx,js,jsx,json,scss,css,md}": [
126 | "prettier --write",
127 | "git add"
128 | ],
129 | "*.{ts,tsx,js,jsx}": [
130 | "eslint --fix",
131 | "stylelint --fix",
132 | "git add"
133 | ]
134 | },
135 | "husky": {
136 | "hooks": {
137 | "pre-commit": "yarn precommit",
138 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------