├── .gitignore ├── LICENSE ├── README.md ├── blog ├── .editorconfig ├── .eslintrc.js ├── .github │ └── FUNDING.yml ├── .gitignore ├── .prettierignore ├── @types │ └── mdx-js-react.d.ts ├── CHANGELOG.md ├── LICENSE ├── README.md ├── content │ ├── author.yaml │ ├── avatars │ │ ├── haegyun.jung.png │ │ ├── juunone.jpeg │ │ ├── minseok.suh.jpeg │ │ ├── taegeon.choi.jpg │ │ └── yongbeen.im.png │ ├── pages │ │ └── about │ │ │ └── index.mdx │ └── posts │ │ ├── cra-custom-template │ │ ├── images │ │ │ ├── giphy_1.gif │ │ │ ├── giphy_2.gif │ │ │ ├── giphy_3.gif │ │ │ ├── npmjs.png │ │ │ ├── publishtonpm.gif │ │ │ └── screencast.svg │ │ └── index.md │ │ ├── event-loop │ │ ├── image_1.png │ │ └── index.md │ │ ├── introduce-web-front-end-team-blog │ │ ├── image_1.png │ │ ├── image_2.png │ │ ├── image_3.png │ │ ├── image_4.png │ │ ├── image_5.png │ │ ├── image_6.png │ │ ├── image_7.png │ │ └── index.md │ │ ├── making-generic-component │ │ ├── any.png │ │ ├── componentGeneric.png │ │ ├── dropdown.png │ │ ├── dropdownT.png │ │ ├── dropdownprops.png │ │ ├── index.md │ │ ├── noTypeError.png │ │ ├── objectValue.png │ │ ├── option.png │ │ ├── optionT.png │ │ ├── options.png │ │ ├── transformedT.png │ │ └── typeError1.png │ │ └── preparing-feconf-part-1 │ │ ├── image_1.jpg │ │ ├── image_2.jpg │ │ └── index.md ├── gatsby-config.js ├── gatsby-node.js ├── package.json ├── src │ ├── @lekoarts │ │ ├── gatsby-theme-minimal-blog-core │ │ │ ├── components │ │ │ │ ├── blog.tsx │ │ │ │ ├── homepage.tsx │ │ │ │ ├── post.tsx │ │ │ │ ├── tag.tsx │ │ │ │ └── tags.tsx │ │ │ └── templates │ │ │ │ ├── blog-query.tsx │ │ │ │ ├── homepage-query.tsx │ │ │ │ ├── page-query.tsx │ │ │ │ ├── post-query.tsx │ │ │ │ ├── tag-query.tsx │ │ │ │ └── tags-query.tsx │ │ └── gatsby-theme-minimal-blog │ │ │ ├── components │ │ │ ├── blog-list-item.tsx │ │ │ ├── blog.tsx │ │ │ ├── header.tsx │ │ │ ├── homepage.tsx │ │ │ ├── item-tags.tsx │ │ │ ├── layout.tsx │ │ │ ├── listing.tsx │ │ │ ├── post.tsx │ │ │ └── tag.tsx │ │ │ └── texts │ │ │ └── hero.mdx │ ├── components │ │ ├── Author.tsx │ │ ├── GithubProfileLink.tsx │ │ └── Image.tsx │ ├── types │ │ ├── AllPostEdge.ts │ │ ├── AllPostNode.ts │ │ ├── AllPostResult.ts │ │ ├── Author.ts │ │ ├── Post.ts │ │ ├── PostResult.ts │ │ ├── Tag.ts │ │ └── index.ts │ └── utils │ │ ├── getPostFromQuery.ts │ │ ├── getPostsFromQuery.ts │ │ └── index.ts ├── static │ ├── android-chrome-192x192.png │ ├── android-chrome-384x384.png │ ├── apple-touch-icon-180x180.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── favicon.png │ └── robots.txt ├── tsconfig.json └── yarn.lock ├── coc └── index.md ├── conventions ├── code-review │ └── index.md └── commit │ └── index.md ├── daily-meeting ├── images │ └── daily-log-screenshot.png └── index.md ├── design └── index.md ├── ofc └── README.md ├── retrospective └── index.md ├── study ├── README.md └── refactoring │ ├── catalogs │ ├── change-function-declaration.md │ ├── change-reference-to-value.md │ ├── change-value-to-reference.md │ ├── collapse-hierarchy.md │ ├── consolidate-conditional-expression.md │ ├── decompose-conditional.md │ ├── encapsulate-collection.md │ ├── encapsulate-record.md │ ├── extract-class.md │ ├── extract-function.md │ ├── extract-superclass.md │ ├── hide-delegate.md │ ├── imgs │ │ ├── decompose-conditional.png │ │ ├── extract-function.png │ │ ├── inline-function.png │ │ ├── replace-temp-with-query.png │ │ └── split-loop.png │ ├── inline-class.md │ ├── inline-function.md │ ├── introduce-assertion.md │ ├── introduce-parameter-object.md │ ├── introduce-special-case.md │ ├── move-field.md │ ├── move-function.md │ ├── move-statements-into-function.md │ ├── move-statements-to-callers.md │ ├── parameterize-function.md │ ├── preserve-whole-object.md │ ├── pull-up-constructor-body.md │ ├── pull-up-field.md │ ├── pull-up-method.md │ ├── push-down-field.md │ ├── push-down-method.md │ ├── remove-dead-code.md │ ├── remove-flag-argument.md │ ├── remove-intermediary.md │ ├── remove-setting-method.md │ ├── rename-field.md │ ├── rename-variable.md │ ├── replace-command-with-function.md │ ├── replace-conditional-with-polymorphism.md │ ├── replace-constructor-with-factory-function.md │ ├── replace-control-flag-with-break.md │ ├── replace-derived-variable-with-query.md │ ├── replace-error-code-with-exception.md │ ├── replace-exception-with-pre-check.md │ ├── replace-function-with-command.md │ ├── replace-inline-code-with-function-call.md │ ├── replace-loop-with-pipeline.md │ ├── replace-magic-literal.md │ ├── replace-nested-conditional-with-guard-clasuses.md │ ├── replace-parameter-with-query.md │ ├── replace-primitive-with-object.md │ ├── replace-query-with-parameter.md │ ├── replace-subclass-with-delegate.md │ ├── replace-superclass-with-delegate.md │ ├── replace-temp-with-query.md │ ├── replace-type-code-with-subclasses.md │ ├── return-modified-value.md │ ├── separate-query-from-modifier.md │ ├── split-loop.md │ ├── split-variable.md │ └── substitute-algorithm.md │ ├── index.md │ └── practices │ ├── automata-complex-class.md │ ├── create-common-component.md │ ├── refactor-authstore-in-vroong-urban-web.md │ ├── region-polygon-class.md │ ├── urban-transport-orders-add-store.md │ └── user-table-handle-click-add.md ├── technical-debt └── README.md └── tests ├── .gitignore ├── describe_fully.md ├── do_not_dependent_on_ui_structure.md ├── do_not_miss_await_keyword.md ├── do_not_use_too_much_test_doubles.md ├── given_when_then.md ├── group_by_relevance.md ├── images ├── state-transition-tree.png └── state-transition.png ├── index.md ├── not_catch_exception_but_expect.md ├── not_too_much_nipticking.md ├── single_responsibility.md ├── test_case_design_methods.md ├── test_interface.md ├── test_meaningful_points.md ├── test_your_boundaries.md └── write_enough_and_edge_cases.md /.gitignore: -------------------------------------------------------------------------------- 1 | .history 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mesh Korea Front-end Engineering 2 | 3 | 메쉬코리아의 프론트엔드 엔지니어링 사례를 공유하는 저장소입니다. 학습하고, 적용하고, 경험하고, 개선하며 더 나은 방향을 찾아가고 있습니다. 4 | 5 | 1) 여기에 적힌 내용이 항상 정답은 아닙니다. 피드백을 환영합니다. 6 | 2) 이 문서는 살아 있습니다. 새로운 아이디어가 생기면 언제든 문서를 수정할 수 있습니다. 7 | 3) 모든 문서는 메쉬코리아 웹 프론트엔드 파트가 관리합니다. 8 | 9 | 10 | 11 | ## 목차 12 | 13 | - [Blog](./blog/README.md) 14 | 15 | - [Code Of Conduct](./coc/index.md) 16 | 17 | - [설계 원칙](./design/index.md) 18 | 19 | - [데일리 미팅](./daily-meeting/index.md) 20 | 21 | - [커밋 컨벤션](./conventions/commit/index.md) 22 | 23 | - [코드 리뷰](./conventions/code-review/index.md) 24 | 25 | - [테스트 작성](./tests/index.md) 26 | 27 | - [스터디](./study/README.md) 28 | 29 | - [OFC(Open Feedback Circle)](./ofc/README.md) 30 | 31 | - [회고 가이드](./retrospective/index.md) 32 | 33 | - [기술부채 관리 전략](./technical-debt/README.md) 34 | 35 | 36 | 37 | ## 라이센스 38 | 39 | 이 저장소의 모든 문서는 CC-By 3.0 라이센스를 적용 받습니다. 이 라이센스에 대한 자세한 내용은 를 참고하세요. 40 | 41 | Creative Commons License 42 | 43 | -------------------------------------------------------------------------------- /blog/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /blog/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "airbnb-typescript", 4 | "airbnb/hooks", 5 | "plugin:@typescript-eslint/recommended", 6 | "prettier", 7 | "prettier/react", 8 | "prettier/@typescript-eslint", 9 | "plugin:prettier/recommended", 10 | ], 11 | plugins: ["react", "@typescript-eslint", "eslint-plugin-import-helpers"], 12 | env: { 13 | browser: true, 14 | es6: true, 15 | }, 16 | globals: { 17 | Atomics: "readonly", 18 | SharedArrayBuffer: "readonly", 19 | }, 20 | parser: "@typescript-eslint/parser", 21 | parserOptions: { 22 | ecmaFeatures: { 23 | jsx: true, 24 | }, 25 | }, 26 | rules: { 27 | "linebreak-style": "off", 28 | "prettier/prettier": [ 29 | "error", 30 | { 31 | endOfLine: "auto", 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /blog/.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [LekoArts] 4 | patreon: lekoarts 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: lekoarts 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL -------------------------------------------------------------------------------- /blog/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules 37 | jspm_packages 38 | **/node_modules/** 39 | 40 | # Optional npm cache directory 41 | .npm 42 | 43 | # Optional eslint cache 44 | .eslintcache 45 | 46 | # Optional REPL history 47 | .node_repl_history 48 | 49 | # Output of 'npm pack' 50 | *.tgz 51 | 52 | # Yarn Integrity file 53 | .yarn-integrity 54 | 55 | # dotenv environment variables file 56 | .env 57 | .env.* 58 | !.env.example 59 | 60 | .cache 61 | **/.cache 62 | public 63 | 64 | .idea 65 | .vscode 66 | .DS_Store 67 | -------------------------------------------------------------------------------- /blog/.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | -------------------------------------------------------------------------------- /blog/@types/mdx-js-react.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@mdx-js/react" { 2 | import React from 'react' 3 | export const MDXProvider: React.ComponentType<{ 4 | components: Record> 5 | }> 6 | } 7 | -------------------------------------------------------------------------------- /blog/LICENSE: -------------------------------------------------------------------------------- 1 | The BSD Zero Clause License (0BSD) 2 | 3 | Copyright (c) 2020 LekoArts 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /blog/README.md: -------------------------------------------------------------------------------- 1 | # Mesh Front-end Team Blog 2 | 3 | > :warning: Deprecated: 메쉬코리아 웹 프론트엔드 팀 블로그의 주소가 [이전](https://medium.com/mesh-korea-fe-team-blog)되었습니다. 4 | 5 |
6 | 7 | 메쉬코리아 웹 프론트엔드 팀에서 운영하는 블로그 입니다! [구경하기](https://mesh.dev/front-end-engineering) 8 | 9 | ## 무엇으로 만들어졌나요? 10 | 11 | 블로그는 Gatsby로 개발되었고 Github Pages로 호스팅됩니다. 12 | 13 | 블로그에 사용된 테마는 Gatsby 스타터 [Minimal Blog](https://github.com/LekoArts/gatsby-starter-minimal-blog) 입니다. 14 | 15 | ## 어떻게 글을 쓰나요? 16 | 17 | ### 프로젝트 가져오기 18 | 19 | 1. front-end-engineering 저장소를 clone하고, `feat/blog` 브랜치로 체크아웃 합니다. 20 | 21 | ```bash 22 | $ git clone https://github.com/meshkorea/front-end-engineering 23 | $ git checkout feat/blog 24 | ``` 25 | 26 | 2. 글을 작성하기 위해 브랜치를 체크아웃 합니다. 브랜치 이름은 `blog/[작성하고 싶은 글의 키워드]`로 만들어주시면 좋습니다. 27 | 28 | ```bash 29 | $ git checkout -b blog/introduce-react-hooks 30 | ``` 31 | 32 | ### 디렉토리 구조 33 | 34 | 블로그의 컨텐츠는 `blog/content` 디렉토리에 있습니다. 35 | 36 | 각 디렉토리 or 파일의 역할은 다음과 같습니다. 37 | 38 | - `avatars` : 글 작가 정보를 표시할 때 사용하는 프로필 이미지를 저장합니다. 39 | - `posts` : 마크다운 형식의 블로그 글을 저장합니다. 40 | - `author.yaml` : 작가 정보를 추가하는 파일입니다. 41 | - `pages` : 마크다운으로 페이지를 추가할 때 사용합니다. 글을 작성할 때는 사용하지 않습니다. 42 | 43 | ### 글 쓰기 44 | 45 | 1. 이제 글을 작성하기 위해 `blog/content/posts` 하위에 폴더를 하나 만듭니다. 이름으로 작성하시는 글의 주제를 나타내주시면 좋습니다! 46 | 47 | 2. `index.md` 파일을 생성합니다. 48 | 49 | 3. 이 때 마크다운 파일 안에 frontmatter를 반드시 추가해주셔야 합니다. 50 | 51 | ### 예시 52 | ```md 53 | 54 | --- 55 | slug: "/introduce-react-hooks-api" 56 | title: React Hooks API 소개 57 | date: 2021-02-25 58 | author: 홍길동 59 | description: React Hooks API가 왜 나오게 되었는지 살펴보고, 각각의 API에 대해서 자세하게 살펴봅시다. 60 | tags: 61 | - Tutorial 62 | - Dark Arts 63 | --- 64 | 65 | # 소개 66 | 67 | 이번 글에서는 React hooks API에 대해서 알아보려고 합니다. 68 | React Hooks는 언제 업데이트 되었으며... 69 | 70 | ``` 71 | 72 | frontmatter는 `---`로 감싸서 정의하며, 여러 필드로 글의 메타 데이터를 추가할 수 있습니다. 73 | 74 | 이 중에서 `title`, `description` 같은 필드들은 HTML meta 태그가 되어 검색 엔진에 노출됩니다. 75 | 76 | 이 필드들은 블로그가 배포되는데 **꼭 필요합니다.** 77 | 78 | - `slug` : 여기에 작성하는 문자열은 글에 접근할 때 사용하는 URL 경로가 됩니다. (ex. `"/introduce-react-hooks-api" `) 79 | - `title` : 글의 제목으로 사용됩니다. 80 | - `date` : 글의 작성일 81 | - `author` : 작가 이름, `author.yaml`에 작성하시는 분의 `id`와 **일치해야 합니다.** 82 | 83 | 이 필드들은 안써주셔도 되지만, 써주시면 더 좋습니다! 84 | 85 | - `description` : 글에 대한 설명을 간단하게 소개해주시면 좋습니다. 꼭 길어야 할 필요는 없을 것 같아요! 86 | - `tags` : 글의 태그를 작성해주시면 태그 별로 글을 모아서 볼 때도 유용하게 사용할 수 있습니다. 87 | 88 | ### PR 올리기 89 | 90 | 마지막으로 커밋 후에 push하고, PR을 생성해주시면 됩니다. 91 | 92 | PR이 머지되는 브랜치는 브랜치를 체크아웃했던 `feat/blog`로 지정해주세요! 93 | 94 | 감사합니다. 🤗 95 | -------------------------------------------------------------------------------- /blog/content/author.yaml: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------- 2 | # author.yaml 작성법. 🤗 3 | # -------------------------------------------------------------------- 4 | # 다음은 필수로 입력해야 합니다. 5 | # - id: 식별자로 사용되니 영문으로 입력 부탁드립니다. 6 | # - name: 이름을 입력해주세요! 닉네임도 좋습니다. 7 | # - avatar: 프로필 이미지 경로를 작성해주세요. ex. avatars/gildong.hong.png 8 | # - bio: 간단한 자기 소개를 해주세요! 9 | # 다음은 선택 필드 입니다. 10 | # - github: 자랑하고 싶은 깃헙 프로필 주소를 작성해주세요! 11 | # -------------------------------------------------------------------- 12 | - id: taegeon.choi 13 | name: 최태건 14 | avatar: avatars/taegeon.choi.jpg 15 | github: https://github.com/mindfull 16 | bio: 메쉬코리아 웹프론트엔드팀의 팀장과 K팝 처돌이를 맡고있습니다. 17 | - id: yongbeen.im 18 | name: 임용빈 19 | avatar: avatars/yongbeen.im.png 20 | github: https://github.com/jungpaeng 21 | bio: 메쉬코리아의 운영 이슈를 처리하고 있습니다. 22 | - id: songwon.park 23 | name: 박송원 24 | avatar: 25 | github: 26 | bio: 27 | - id: hyeseon.yeom 28 | name: 염혜선 29 | avatar: 30 | github: 31 | bio: 32 | - id: junwon.choi 33 | name: 최준원 34 | avatar: avatars/juunone.jpeg 35 | github: https://github.com/juunone 36 | bio: 메쉬코리아 TMS 서비스의 Front-End 개발을 담당하고 있습니다. 37 | - id: minseok.suh 38 | name: 서민석 39 | avatar: avatars/minseok.suh.jpeg 40 | github: https://github.com/minseoksuh 41 | bio: 메쉬코리아 TMS 서비스의 Front-End 개발을 담당하고 있습니다. 42 | - id: joonmo.kim 43 | name: 김준모 44 | avatar: 45 | github: 46 | bio: 47 | - id: haegyun.jung 48 | name: 정해균 49 | avatar: avatars/haegyun.jung.png 50 | github: https://github.com/haeguri 51 | bio: 메쉬코리아의 4륜 서비스 프론트엔드 개발을 담당하고 있습니다. 52 | - id: yunjae.jung 53 | name: 정윤재 54 | avatar: 55 | github: 56 | bio: 57 | - id: yonghyun.kang 58 | name: 강용현 59 | avatar: 60 | github: 61 | bio: 62 | -------------------------------------------------------------------------------- /blog/content/avatars/haegyun.jung.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/avatars/haegyun.jung.png -------------------------------------------------------------------------------- /blog/content/avatars/juunone.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/avatars/juunone.jpeg -------------------------------------------------------------------------------- /blog/content/avatars/minseok.suh.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/avatars/minseok.suh.jpeg -------------------------------------------------------------------------------- /blog/content/avatars/taegeon.choi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/avatars/taegeon.choi.jpg -------------------------------------------------------------------------------- /blog/content/avatars/yongbeen.im.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/avatars/yongbeen.im.png -------------------------------------------------------------------------------- /blog/content/pages/about/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: About 3 | slug: "/about" 4 | --- 5 | 6 | ### Code Of Conduct 7 | 8 | 우리는 구성원 사이의 강력한 신뢰를 바탕으로 촘촘하게 협업하고 높은 기술 전문성을 추구함으로써 회사의 비즈니스 성공에 기여합니다. 이를 위해서 아래의 내용을 적극 실천합니다. 9 | 10 | 1. 서로 돕고 함께 성장합니다. 11 | 혼자서는 우리의 비즈니스 목표를 달성할 수가 없습니다. 일이 흘러가는 큰 흐름 안에서 제품을 만드는 모두의 노력이 한 데에 어우러져야만 합니다. 그래서 분업이 아닌, 협업을 합니다. 12 | 13 | 혼자가 아닌 함께 해야 하는 일이기에 내가 아는 것을 동료와 나눌 때, 그래서 함께 성장할 때, 전체의 힘이 더 빠르게 강해질 수 있다고 믿습니다. 14 | 15 | 우리는 배운 것을 동료와 기꺼이 나눠 함께 성장하고, 일의 완성을 향해서 조건과 경계 없이 서로 돕습니다. 16 | 17 | 2. 일을 기다리지 않습니다. 18 | 높은 품질의 소프트웨어를 생산적으로 개발하기 위해서 우리는 위계가 아닌 전문가로서의 양심 위에서 일을 합니다. 일이 흘러가는 큰 흐름 안에서 스스로 문제를 정의하고 한 발 앞서 서로를 돕습니다. 19 | 20 | 디자인이 없으면 디자이너를 찾고, 디자이너가 없으면 더미로 구현을 할 수 있습니다. 서버 API가 없으면 인터페이스만 협의하고 페이크 서버를 이용해서 개발을 할 수도 있습니다. 프로세스에 문제가 있다고 생각한다면 망설이지 말고 조직장이나 동료에게 개선을 요청하세요. 21 | 22 | 우리는 일의 완성을 기다리지 않습니다. 우리는 일이 되도록 만듭니다. 23 | -------------------------------------------------------------------------------- /blog/content/posts/cra-custom-template/images/giphy_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/cra-custom-template/images/giphy_1.gif -------------------------------------------------------------------------------- /blog/content/posts/cra-custom-template/images/giphy_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/cra-custom-template/images/giphy_2.gif -------------------------------------------------------------------------------- /blog/content/posts/cra-custom-template/images/giphy_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/cra-custom-template/images/giphy_3.gif -------------------------------------------------------------------------------- /blog/content/posts/cra-custom-template/images/npmjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/cra-custom-template/images/npmjs.png -------------------------------------------------------------------------------- /blog/content/posts/cra-custom-template/images/publishtonpm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/cra-custom-template/images/publishtonpm.gif -------------------------------------------------------------------------------- /blog/content/posts/event-loop/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/event-loop/image_1.png -------------------------------------------------------------------------------- /blog/content/posts/introduce-web-front-end-team-blog/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/introduce-web-front-end-team-blog/image_1.png -------------------------------------------------------------------------------- /blog/content/posts/introduce-web-front-end-team-blog/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/introduce-web-front-end-team-blog/image_2.png -------------------------------------------------------------------------------- /blog/content/posts/introduce-web-front-end-team-blog/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/introduce-web-front-end-team-blog/image_3.png -------------------------------------------------------------------------------- /blog/content/posts/introduce-web-front-end-team-blog/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/introduce-web-front-end-team-blog/image_4.png -------------------------------------------------------------------------------- /blog/content/posts/introduce-web-front-end-team-blog/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/introduce-web-front-end-team-blog/image_5.png -------------------------------------------------------------------------------- /blog/content/posts/introduce-web-front-end-team-blog/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/introduce-web-front-end-team-blog/image_6.png -------------------------------------------------------------------------------- /blog/content/posts/introduce-web-front-end-team-blog/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/introduce-web-front-end-team-blog/image_7.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/any.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/any.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/componentGeneric.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/componentGeneric.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/dropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/dropdown.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/dropdownT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/dropdownT.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/dropdownprops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/dropdownprops.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: "/making-generic-component" 3 | title: 제네릭 타입을 사용하는 리액트 드랍다운을 만들어 보자 4 | date: 2021-04-15 5 | author: minseok.suh 6 | description: 타입스크립트 제네릭 문법을 사용하여 리액트 컴포넌트 내의 데이터를 명확하게 하고 싶었습니다. 7 | tags: 8 | - React 9 | - Typescript 10 | --- 11 | 12 | ## 제네릭 타입을 사용하는 리액트 드랍다운을 만들어 보자 13 | 14 | ### What is the problem? 15 | 16 | 메쉬코리아에서 개발해 관리하는 리액트 컴포넌트 라이브러리인 vroong-design-system 안에는 17 | 드랍다운 컴포넌트가 있다. 18 | 19 | ![dropdown](./dropdown.png) 20 | 21 | 이 드랍다운 컴포넌트는 선택지를 만들어주는 options 프라퍼티를 필수적으로 받고 있는데, 22 | 아래 옵션 데이터를 배열로 받고 있다. 23 | 24 | ![option](./option.png) 25 | 26 | - 코드 내부적으로 데이터를 찾고 관리하기 위해 사용되는 key 27 | - 사용자에게 보여질 옵션을 설정하기 위해 사용되는 label 28 | - 그리고 실제 해당 옵션의 데이터인 value 29 | 30 | 그런데 현재 value의 타입이 any로 설정이 되어 있어서 컴포넌트 사용중 생기는 문제가 있다. 31 | 분명히 드롭다운 컴포넌트 내에서 옵션마다 사용되는 value의 형태는 같은데 코드 상에서 그 타입을 유추해 내지 못하기 때문에 32 | 33 | ![any](./any.png) 34 | 35 | 위의 onChange 처럼 없는 프라퍼티에 접근하는게 가능해진다던가, value로 만약 undefined 가 들어간다면 36 | 37 | > Uncaught TypeError: Cannot read property 'please' of undefined 38 | 39 | 를 볼 수도 있다. 40 | 41 | ### 컴포넌트에 제네릭 타입을 사용해 문제 해결 해보기 42 | 43 | 만약 일반함수에 제네릭 타입을 정해 사용하듯이 컴포넌트에도 제네릭 타입을 사용할 수 있다면 option의 value로 어떤 값을 사용하는지 특정할 수 있고 44 | value가 any여서 생길 수 있는 여러 문제를 미연에 방지 할 수 있을 것이다. 45 | 46 | 여러 시행착오를 거치면서 우선 원하는대로 제네릭 컴포넌트를 구현하는것에는 성공했지만 아쉬운 점이라던가 걸리는 부분이 남아있다. 47 | 48 | 우선 구현법을 먼저 설명하고 아쉬운 점에 대해 추가로 메모를 첨부하려고 한다. 49 | 50 | #### 구현법 51 | 52 | 우선 드랍다운이 받는 프라퍼티가 제네릭을 사용하도록 수정한다. 53 | value와 options props들에 제네릭 타입을 적용시켜준다. 54 | 55 | ![dropdownprops](./dropdownprops.png) 56 | 57 | 그리고 그 제네릭 타입을 내려줘서 옵션의 value로 설정해준다. 58 | 59 | ![optionT](./optionT.png) 60 | 61 | 이제 수정된 프라퍼티를 컴포넌트가 사용하도록 코드를 수정한다. 62 | 63 | FC를 사용하는 syntax 에서는 문법이 잘 작동하지 않기 때문에 프라퍼티 앞에 제너릭 선언을 해주고 그 제너릭을 prop에 적용시켰다. 64 | 65 | ![dropdownT](./dropdownT.png) 66 | 67 | _extends unknown 을 굳이 한 이유는 arrow function 형태에서 `` 이렇게만 하면 syntax 오류가 생기기 때문이다. 그렇게 사용하고 싶다면 일반 함수로 컴포넌트를 만들면 된다._ 68 | 69 | 자 이제 제네릭을 적용하기 위해서 컴포넌트가 사용되는 곳에 가서 70 | 71 | ![componentGeneric](./componentGeneric.png) 72 | 73 | 이렇게 컴포넌트를 사용하는곳에 제네릭 타입 문법을 붙여준다. 우선은 흔히 사용되는 value 값인 `` 을 사용했다. 74 | 75 | 그럼 제네릭 타입을 그냥 `string`으로 설정해 준다면? 76 | 77 | ![typeError1](./typeError1.png) 78 | 79 | ![options](./options.png) 80 | 81 | undefined 가 value 가 될 수 있는 변수를 options props에 넣으려고 하니 타입 오류가 잡힌다! 제너릭을 통해 드랍다운이 사용하는 value 값의 타입을 특정할수 있게 된 것이다. 82 | 83 | 단순 데이터가 아닌 오브젝트를 value 로 사용할때도 타입이 정해져 있기 때문에 훨씬 안전하게 사용이 가능하다. 84 | 85 | ![objectValue](./objectValue.png) 86 | 87 | #### 아쉬운점 88 | 89 | 에 일단 되게는 만들었지만 업무에 잘 사용하기에는 아쉬운점들이 있다. 90 | 91 | 1. 타입스크립트의 제너릭 문법과 리액트의 jsx가 둘다 < > 기호를 사용하다보니 syntax 오류가 많이 잡히면서 지원되지 않는 문법들이 존재한다. 92 | 93 | 2. 컴포넌트에 매번 제너릭을 정해주기가 불편하니 `const Test = (props: Prop)` 이런 식으로 default 타입을 설정해봤지만 막상 컴포넌트 옆에 제너릭을 정하지 않으니 T 에 대한 값이 string으로 강제되지 않는 문제가 생겼다. 94 | 95 | ![transformedT](./transformedT.png) 96 | 97 | _default 타입은 string인데 options 값으로 `string | undefined` 가 들어오니 컴포넌트의 T 값이 해당 값을 받을 수 있도록 변형된것을 볼 수 있다._ 98 | 99 | 3. 만약 타입스크립트에서 제너릭 함수를 선언할때 제너릭의 default 타입이 없고, 함수를 사용할때 제너릭을 설정해주지 않는다면 타입 에러가 나는데. 제너릭 컴포넌트는 default type이 없더라도 타입 오류를 잡아주지 않는다. 100 | 101 | ![noTypeError](./noTypeError.png) 102 | 103 | _위에 제너릭 기본 타입이 없기 때문애, 드롭다운 컴포넌트를 사용할때 제너릭 타입을 정해주지 않는다면 타입 오류로 처리해주면 좋겠지만 현재 그런 기능은 없는것 같다._ 104 | 105 | 1, 2 번 문제는 불편한대로 참고 사용할 수 있을 것 같지만 3번 문제는 해당 컴포넌트가 제너릭인지 모르는 사람이라면 계속 모르고 사용할 가능성이 있기 때문에 lint 룰을 추가하든가 해서 오류로 잡아줄 수 있다면 현재 코드베이스에 개선점으로 반영시켜도 좋을 것 같다. 106 | 107 | 다음에는 그 작업을 이어서 해보려고 한다. 108 | 109 | 참고: 110 | 111 | - [https://dev.to/janjakubnanista/a-peculiar-journey-to-a-generic-react-component-using-typescript-3cm8](https://dev.to/janjakubnanista/a-peculiar-journey-to-a-generic-react-component-using-typescript-3cm8) 112 | 113 | - [https://www.reddit.com/r/typescript/comments/dv07sp/react_typing_generic_function_components/](https://www.reddit.com/r/typescript/comments/dv07sp/react_typing_generic_function_components/) 114 | -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/noTypeError.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/noTypeError.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/objectValue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/objectValue.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/option.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/option.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/optionT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/optionT.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/options.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/transformedT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/transformedT.png -------------------------------------------------------------------------------- /blog/content/posts/making-generic-component/typeError1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/making-generic-component/typeError1.png -------------------------------------------------------------------------------- /blog/content/posts/preparing-feconf-part-1/image_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/preparing-feconf-part-1/image_1.jpg -------------------------------------------------------------------------------- /blog/content/posts/preparing-feconf-part-1/image_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meshkorea/front-end-engineering/5360d73c41883eb9a0878b78b52c017ef941e9a4/blog/content/posts/preparing-feconf-part-1/image_2.jpg -------------------------------------------------------------------------------- /blog/gatsby-config.js: -------------------------------------------------------------------------------- 1 | require(`dotenv`).config({ 2 | path: `.env`, 3 | }); 4 | 5 | const shouldAnalyseBundle = process.env.ANALYSE_BUNDLE; 6 | 7 | module.exports = { 8 | pathPrefix: "/front-end-engineering", 9 | siteMetadata: { 10 | // Used for the title template on pages other than the index site 11 | siteTitle: "Mesh Korea FE Blog", 12 | // Default title of the page 13 | siteTitleAlt: "Mesh Korea Frontend Blog", 14 | // Will be set on the tag 15 | siteLanguage: "ko", 16 | // Used for SEO 17 | siteDescription: "메쉬코리아 프론트엔드 팀 블로그 입니다.", 18 | // Can be used for e.g. JSONLD 19 | siteHeadline: `Mesh Koera FE Blog`, 20 | // Will be used to generate absolute URLs for og:image etc. 21 | siteUrl: `https://mesh.dev/front-end-engineering`, 22 | }, 23 | mapping: { 24 | "Mdx.frontmatter.author": `AuthorYaml`, 25 | }, 26 | plugins: [ 27 | "gatsby-plugin-sharp", 28 | "gatsby-transformer-sharp", 29 | { 30 | resolve: `@lekoarts/gatsby-theme-minimal-blog`, 31 | // See the theme's README for all available options 32 | options: { 33 | formatString: "YYYY.MM.DD", 34 | navigation: [ 35 | { 36 | title: `Blog`, 37 | slug: `/blog`, 38 | }, 39 | { 40 | title: `About`, 41 | slug: `/about`, 42 | }, 43 | ], 44 | externalLinks: [ 45 | { 46 | name: `Career`, 47 | url: `https://meshkorea.net/kr/career/`, 48 | }, 49 | ], 50 | }, 51 | }, 52 | { 53 | resolve: `gatsby-plugin-google-analytics`, 54 | options: { 55 | trackingId: process.env.GOOGLE_ANALYTICS_ID, 56 | }, 57 | }, 58 | `gatsby-plugin-sitemap`, 59 | { 60 | resolve: `gatsby-plugin-manifest`, 61 | options: { 62 | name: `Mesh Korea Web Frontend Blog`, 63 | short_name: `mesh-fe-blog`, 64 | description: `This site is Mesh Korea's Web Front-end blog. All articles are published by our team member.`, 65 | start_url: `/`, 66 | background_color: `#fff`, 67 | theme_color: `#1b3993`, 68 | display: `standalone`, 69 | icons: [ 70 | { 71 | src: `/android-chrome-192x192.png`, 72 | sizes: `192x192`, 73 | type: `image/png`, 74 | }, 75 | { 76 | src: `/android-chrome-512x512.png`, 77 | sizes: `512x512`, 78 | type: `image/png`, 79 | }, 80 | ], 81 | }, 82 | }, 83 | `gatsby-plugin-offline`, 84 | `gatsby-plugin-netlify`, 85 | shouldAnalyseBundle && { 86 | resolve: `gatsby-plugin-webpack-bundle-analyser-v2`, 87 | options: { 88 | analyzerMode: `static`, 89 | reportFilename: `_bundle.html`, 90 | openAnalyzer: false, 91 | }, 92 | }, 93 | { 94 | resolve: `gatsby-source-filesystem`, 95 | options: { 96 | path: `${__dirname}/content`, 97 | name: "content", 98 | }, 99 | }, 100 | { 101 | resolve: `gatsby-plugin-mdx`, 102 | options: { 103 | extensions: [`.md`], 104 | gatsbyRemarkPlugins: [ 105 | "gatsby-remark-copy-linked-files", 106 | { 107 | resolve: `gatsby-remark-images`, 108 | options: { 109 | maxWidth: 590, 110 | showCaptions: true, 111 | }, 112 | }, 113 | ], 114 | plugins: ["gatsby-transformer-yaml"], 115 | }, 116 | }, 117 | ].filter(Boolean), 118 | }; 119 | -------------------------------------------------------------------------------- /blog/gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | exports.onCreateNode = ({ node }) => { 4 | if (node.internal.type === "MarkdownRemark") { 5 | let { slug: frontmatterSlug } = node.frontmatter; 6 | 7 | if (!frontmatterSlug) { 8 | throw new Error("'slug' field is required in frontmatter."); 9 | } 10 | } 11 | }; 12 | 13 | exports.onCreateWebpackConfig = ({ actions }) => { 14 | actions.setWebpackConfig({ 15 | resolve: { 16 | alias: { 17 | utils: path.resolve(__dirname, "src/utils/"), 18 | types: path.resolve(__dirname, "src/types/"), 19 | components: path.resolve(__dirname, "src/components/") 20 | }, 21 | }, 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /blog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "minimal-blog", 4 | "description": "Typography driven, feature-rich blogging theme with minimal aesthetics. Includes tags/categories support and extensive features for code blocks such as live preview, line numbers, and line highlighting.", 5 | "version": "1.3.9", 6 | "author": "LekoArts ", 7 | "license": "0BSD", 8 | "starter-name": "gatsby-starter-minimal-blog", 9 | "scripts": { 10 | "build": "gatsby build", 11 | "develop": "gatsby develop", 12 | "develop:cypress": "cross-env CYPRESS_SUPPORT=y yarn develop", 13 | "build:cypress": "cross-env CYPRESS_SUPPORT=y yarn build", 14 | "start": "gatsby develop", 15 | "serve": "gatsby serve", 16 | "clean": "gatsby clean", 17 | "deploy": "rimraf public && gatsby build --prefix-paths && gh-pages -d public" 18 | }, 19 | "dependencies": { 20 | "@lekoarts/gatsby-theme-minimal-blog": "^2.7.3", 21 | "@types/theme-ui": "^0.3.7", 22 | "gatsby": "^2.25.3", 23 | "gatsby-plugin-google-analytics": "^2.4.1", 24 | "gatsby-plugin-manifest": "^2.5.2", 25 | "gatsby-plugin-netlify": "^2.4.0", 26 | "gatsby-plugin-offline": "^3.3.3", 27 | "gatsby-plugin-sharp": "^2.14.3", 28 | "gatsby-plugin-sitemap": "^2.5.1", 29 | "gatsby-plugin-webpack-bundle-analyser-v2": "^1.1.13", 30 | "gatsby-remark-copy-linked-files": "^2.10.0", 31 | "gatsby-remark-images": "^3.11.1", 32 | "gatsby-source-filesystem": "^2.9.0", 33 | "gatsby-transformer-remark": "^2.14.0", 34 | "gatsby-transformer-sharp": "^2.12.0", 35 | "gatsby-transformer-yaml": "^2.9.0", 36 | "react": "^16.13.1", 37 | "react-dom": "^16.13.1", 38 | "rimraf": "3.0.2", 39 | "typescript": "^4.1.3" 40 | }, 41 | "devDependencies": { 42 | "@typescript-eslint/eslint-plugin": "^4.13.0", 43 | "@typescript-eslint/parser": "^4.13.0", 44 | "cross-env": "^7.0.2", 45 | "eslint": "7.2.0", 46 | "eslint-config-airbnb": "18.2.1", 47 | "eslint-config-airbnb-typescript": "^12.0.0", 48 | "eslint-config-prettier": "^7.1.0", 49 | "eslint-plugin-import": "^2.22.1", 50 | "eslint-plugin-jsx-a11y": "^6.4.1", 51 | "eslint-plugin-prettier": "^3.3.1", 52 | "eslint-plugin-react": "^7.21.5", 53 | "eslint-plugin-react-hooks": "4.0.0", 54 | "gh-pages": "^3.1.0", 55 | "prettier": "^2.2.1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog-core/components/blog.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Blog from "@lekoarts/gatsby-theme-minimal-blog/src/components/blog"; 3 | 4 | import { getPostsFromQuery } from "utils"; 5 | import { AllPostResult } from "types"; 6 | 7 | type Props = { 8 | data: { 9 | allPost: AllPostResult; 10 | }; 11 | [key: string]: any; 12 | }; 13 | 14 | export default function MinimalBlogCoreBlog({ ...props }: Props) { 15 | const { 16 | data: { allPost }, 17 | } = props; 18 | 19 | const posts = getPostsFromQuery(allPost); 20 | 21 | return ; 22 | } 23 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog-core/components/homepage.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { AllPostNode, AllPostEdge } from "types"; 4 | import { getPostsFromQuery } from "utils"; 5 | import Homepage from "../../gatsby-theme-minimal-blog/components/homepage"; 6 | 7 | type Props = { 8 | data: { 9 | allPost: { 10 | nodes: AllPostNode[]; 11 | edges: AllPostEdge[]; 12 | }; 13 | }; 14 | }; 15 | 16 | export default function MinimalBlogCoreHomepage({ ...props }: Props) { 17 | const { 18 | data: { allPost }, 19 | } = props; 20 | 21 | const posts = getPostsFromQuery(allPost); 22 | return ; 23 | } 24 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog-core/components/post.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { PostResult } from "types"; 4 | import { getPostFromQuery } from "utils"; 5 | import Post from "../../gatsby-theme-minimal-blog/components/post"; 6 | 7 | type Props = { 8 | data: { 9 | post: PostResult; 10 | }; 11 | [key: string]: any; 12 | }; 13 | 14 | export default function MinimalBlogCorePost({ ...props }: Props) { 15 | const post = getPostFromQuery(props.data.post); 16 | return ; 17 | } 18 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog-core/components/tag.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Tag from "@lekoarts/gatsby-theme-minimal-blog/src/components/tag"; 3 | 4 | import { getPostsFromQuery } from "utils"; 5 | import { AllPostResult } from "types"; 6 | 7 | type Props = { 8 | data: { 9 | allPost: AllPostResult; 10 | }; 11 | pageContext: { 12 | isCreatedByStatefulCreatePages: boolean; 13 | slug: string; 14 | name: string; 15 | [key: string]: any; 16 | }; 17 | [key: string]: any; 18 | }; 19 | 20 | export default function MinimalBlogCoreTag({ ...props }: Props) { 21 | const { 22 | data: { allPost }, 23 | } = props; 24 | 25 | const posts = getPostsFromQuery(allPost); 26 | 27 | return ; 28 | } 29 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog-core/components/tags.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Tags from "@lekoarts/gatsby-theme-minimal-blog/src/components/tags"; 3 | 4 | type Props = { 5 | data: { 6 | allPost: { 7 | group: { 8 | fieldValue: string; 9 | totalCount: number; 10 | }[]; 11 | }; 12 | }; 13 | [key: string]: any; 14 | }; 15 | 16 | export default function MinimalBlogCoreTags({ ...props }: Props) { 17 | const { 18 | data: { allPost }, 19 | } = props; 20 | 21 | return ; 22 | } 23 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog-core/templates/blog-query.tsx: -------------------------------------------------------------------------------- 1 | import { graphql } from "gatsby"; 2 | import BlogComponent from "@lekoarts/gatsby-theme-minimal-blog-core/src/components/blog"; 3 | 4 | export default BlogComponent; 5 | 6 | export const query = graphql` 7 | query($formatString: String!) { 8 | allPost(sort: { fields: date, order: DESC }) { 9 | nodes { 10 | id 11 | slug 12 | title 13 | date(formatString: $formatString) 14 | excerpt 15 | timeToRead 16 | tags { 17 | name 18 | slug 19 | } 20 | } 21 | edges { 22 | node { 23 | ... on MdxPost { 24 | parent { 25 | ... on Mdx { 26 | id 27 | frontmatter { 28 | author { 29 | id 30 | name 31 | bio 32 | avatar { 33 | publicURL 34 | } 35 | } 36 | } 37 | } 38 | id 39 | } 40 | } 41 | id 42 | } 43 | } 44 | } 45 | } 46 | `; 47 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog-core/templates/homepage-query.tsx: -------------------------------------------------------------------------------- 1 | import { graphql } from "gatsby"; 2 | import HomepageComponent from "@lekoarts/gatsby-theme-minimal-blog-core/src/components/homepage"; 3 | 4 | export default HomepageComponent; 5 | 6 | export const query = graphql` 7 | query($formatString: String!) { 8 | allPost(sort: { fields: date, order: DESC }, limit: 3) { 9 | nodes { 10 | id 11 | slug 12 | title 13 | date(formatString: $formatString) 14 | excerpt 15 | timeToRead 16 | tags { 17 | name 18 | slug 19 | } 20 | } 21 | edges { 22 | node { 23 | ... on MdxPost { 24 | parent { 25 | ... on Mdx { 26 | id 27 | frontmatter { 28 | author { 29 | id 30 | name 31 | bio 32 | avatar { 33 | publicURL 34 | } 35 | } 36 | } 37 | } 38 | id 39 | } 40 | } 41 | id 42 | } 43 | } 44 | } 45 | } 46 | `; 47 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog-core/templates/page-query.tsx: -------------------------------------------------------------------------------- 1 | import { graphql } from "gatsby"; 2 | import PageComponent from "@lekoarts/gatsby-theme-minimal-blog-core/src/components/page"; 3 | 4 | export default PageComponent; 5 | 6 | export const query = graphql` 7 | query($slug: String!) { 8 | page(slug: { eq: $slug }) { 9 | title 10 | slug 11 | excerpt 12 | body 13 | } 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog-core/templates/post-query.tsx: -------------------------------------------------------------------------------- 1 | import { graphql } from "gatsby"; 2 | import PostComponent from "@lekoarts/gatsby-theme-minimal-blog-core/src/components/post"; 3 | 4 | export default PostComponent; 5 | 6 | export const query = graphql` 7 | query($slug: String!, $formatString: String!) { 8 | post(slug: { eq: $slug }) { 9 | slug 10 | title 11 | date(formatString: $formatString) 12 | tags { 13 | name 14 | slug 15 | } 16 | description 17 | body 18 | excerpt 19 | timeToRead 20 | ... on MdxPost { 21 | id 22 | parent { 23 | ... on Mdx { 24 | id 25 | frontmatter { 26 | author { 27 | id 28 | name 29 | bio 30 | github 31 | avatar { 32 | publicURL 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | `; 42 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog-core/templates/tag-query.tsx: -------------------------------------------------------------------------------- 1 | import { graphql } from "gatsby"; 2 | import TagComponent from "@lekoarts/gatsby-theme-minimal-blog-core/src/components/tag"; 3 | 4 | export default TagComponent; 5 | 6 | export const query = graphql` 7 | query($slug: String!, $formatString: String!) { 8 | allPost( 9 | sort: { fields: date, order: DESC } 10 | filter: { tags: { elemMatch: { slug: { eq: $slug } } } } 11 | ) { 12 | nodes { 13 | id 14 | slug 15 | title 16 | date(formatString: $formatString) 17 | excerpt 18 | timeToRead 19 | tags { 20 | name 21 | slug 22 | } 23 | } 24 | edges { 25 | node { 26 | ... on MdxPost { 27 | parent { 28 | ... on Mdx { 29 | id 30 | frontmatter { 31 | author { 32 | id 33 | name 34 | bio 35 | avatar { 36 | publicURL 37 | } 38 | } 39 | } 40 | } 41 | id 42 | } 43 | } 44 | id 45 | } 46 | } 47 | } 48 | } 49 | `; 50 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog-core/templates/tags-query.tsx: -------------------------------------------------------------------------------- 1 | import { graphql } from "gatsby"; 2 | import TagsComponent from "@lekoarts/gatsby-theme-minimal-blog-core/src/components/tags"; 3 | 4 | export default TagsComponent; 5 | 6 | export const query = graphql` 7 | query { 8 | allPost(sort: { fields: tags___name, order: DESC }) { 9 | group(field: tags___name) { 10 | fieldValue 11 | totalCount 12 | } 13 | } 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog/components/blog-list-item.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import React from "react"; 3 | import { jsx, Box } from "theme-ui"; 4 | import { Link } from "gatsby"; 5 | 6 | import ItemTags from "./item-tags"; 7 | import Post from "types/Post"; 8 | 9 | type BlogListItemProps = { 10 | post: Post; 11 | showTags?: boolean; 12 | }; 13 | 14 | const BlogListItem = ({ post, showTags = true }: BlogListItemProps) => ( 15 | 16 | 17 | {post.title} 18 | 19 |

27 | 28 | {", "} 29 | {post.author.name} 30 | {post.tags && showTags && ( 31 | 32 | {", "} 33 | 34 | 35 | 36 | 37 | )} 38 |

39 |
40 | ); 41 | 42 | export default BlogListItem; 43 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog/components/blog.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx, Heading } from "theme-ui"; 3 | import { Link } from "gatsby"; 4 | import { Flex } from "@theme-ui/components"; 5 | import useMinimalBlogConfig from "@lekoarts/gatsby-theme-minimal-blog/src/hooks/use-minimal-blog-config"; 6 | import replaceSlashes from "@lekoarts/gatsby-theme-minimal-blog/src/utils/replaceSlashes"; 7 | import Layout from "@lekoarts/gatsby-theme-minimal-blog/src/components/layout"; 8 | import SEO from "@lekoarts/gatsby-theme-minimal-blog/src/components/seo"; 9 | 10 | import Listing from "./listing"; 11 | import { PostListItem } from "types"; 12 | 13 | type PostsProps = { 14 | posts: PostListItem[]; 15 | [key: string]: any; 16 | }; 17 | 18 | const Blog = ({ posts }: PostsProps) => { 19 | const { tagsPath, basePath } = useMinimalBlogConfig(); 20 | 21 | return ( 22 | 23 | 24 | 31 | 32 | Blog 33 | 34 | 38 | 모든 태그 39 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default Blog; 47 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog/components/header.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx, useColorMode } from "theme-ui"; 3 | import { Flex } from "@theme-ui/components"; 4 | import HeaderTitle from "@lekoarts/gatsby-theme-minimal-blog/src/components/header-title"; 5 | import ColorModeToggle from "@lekoarts/gatsby-theme-minimal-blog/src/components/colormode-toggle"; 6 | import useMinimalBlogConfig from "@lekoarts/gatsby-theme-minimal-blog/src/hooks/use-minimal-blog-config"; 7 | import Navigation from "@lekoarts/gatsby-theme-minimal-blog/src/components/navigation"; 8 | import HeaderExternalLinks from "@lekoarts/gatsby-theme-minimal-blog/src/components/header-external-links"; 9 | 10 | const Header = () => { 11 | const { navigation: nav } = useMinimalBlogConfig(); 12 | const [colorMode, setColorMode] = useColorMode(); 13 | const isDark = colorMode === `dark`; 14 | const toggleColorMode = (e: any) => { 15 | e.preventDefault(); 16 | setColorMode(isDark ? `light` : `dark`); 17 | }; 18 | 19 | return ( 20 |
21 | 22 | 23 | 24 | 25 |
38 | 39 | 40 |
41 |
42 | ); 43 | }; 44 | 45 | export default Header; 46 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog/components/homepage.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui"; 3 | import { Link } from "gatsby"; 4 | import replaceSlashes from "@lekoarts/gatsby-theme-minimal-blog/src/utils/replaceSlashes"; 5 | import Title from "@lekoarts/gatsby-theme-minimal-blog/src/components/title"; 6 | import Layout from "@lekoarts/gatsby-theme-minimal-blog/src/components/layout"; 7 | import Listing from "@lekoarts/gatsby-theme-minimal-blog/src/components/listing"; 8 | import useMinimalBlogConfig from "@lekoarts/gatsby-theme-minimal-blog/src/hooks/use-minimal-blog-config"; 9 | 10 | // @ts-ignore 11 | // ignore mdx import ts error 12 | import Hero from "../texts/hero"; 13 | import { PostListItem } from "types"; 14 | 15 | type HomePageProps = { 16 | posts: PostListItem[]; 17 | [key: string]: any; 18 | }; 19 | 20 | const Homepage = ({ posts }: HomePageProps) => { 21 | const { basePath, blogPath } = useMinimalBlogConfig(); 22 | return ( 23 | 24 |
31 | 32 |
33 | 34 | <Link to={replaceSlashes(`/${basePath}/${blogPath}`)}>더보기</Link> 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | export default Homepage; 42 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog/components/item-tags.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui"; 3 | import React from "react"; 4 | import { Link } from "gatsby"; 5 | import replaceSlashes from "@lekoarts/gatsby-theme-minimal-blog/src/utils/replaceSlashes"; 6 | import useMinimalBlogConfig from "@lekoarts/gatsby-theme-minimal-blog/src/hooks/use-minimal-blog-config"; 7 | 8 | import { Tag } from "types"; 9 | 10 | type TagsProps = { 11 | tags: Tag[]; 12 | }; 13 | 14 | const ItemTags = ({ tags }: TagsProps) => { 15 | const { tagsPath, basePath } = useMinimalBlogConfig(); 16 | 17 | const tagDelimiter = " "; 18 | const tagPrefix = "#"; 19 | return ( 20 | 21 | {tags.map((tag, i) => ( 22 | 23 | {!!i && tagDelimiter} 24 | 28 | {`${tagPrefix}${tag.name}`} 29 | 30 | 31 | ))} 32 | 33 | ); 34 | }; 35 | 36 | export default ItemTags; 37 | -------------------------------------------------------------------------------- /blog/src/@lekoarts/gatsby-theme-minimal-blog/components/layout.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import React from "react"; 3 | import { MDXProvider } from "@mdx-js/react"; 4 | import { Global } from "@emotion/core"; 5 | import { Box, Container, jsx, ImageProps } from "theme-ui"; 6 | import "typeface-ibm-plex-sans"; 7 | import SEO from "@lekoarts/gatsby-theme-minimal-blog/src/components/seo"; 8 | import Header from "./header"; 9 | import Footer from "@lekoarts/gatsby-theme-minimal-blog/src/components/footer"; 10 | import CodeStyles from "@lekoarts/gatsby-theme-minimal-blog/src/styles/code"; 11 | import SkipNavLink from "@lekoarts/gatsby-theme-minimal-blog/src/components/skip-nav"; 12 | import Image from "components/Image"; 13 | 14 | type LayoutProps = { children: React.ReactNode; className?: string }; 15 | 16 | const Layout = ({ children, className = `` }: LayoutProps) => ( 17 | 18 | ({ 20 | "*": { 21 | boxSizing: `inherit`, 22 | }, 23 | html: { 24 | WebkitTextSizeAdjust: `100%`, 25 | }, 26 | img: { 27 | borderStyle: `none`, 28 | }, 29 | pre: { 30 | fontFamily: `monospace`, 31 | fontSize: `1em`, 32 | }, 33 | "[hidden]": { 34 | display: `none`, 35 | }, 36 | "::selection": { 37 | backgroundColor: theme.colors.text, 38 | color: theme.colors.background, 39 | }, 40 | a: { 41 | transition: `all 0.3s ease-in-out`, 42 | color: `text`, 43 | }, 44 | figcaption: { 45 | marginBottom: "50px", 46 | color: "#8d8d8d", 47 | textAlign: "center", 48 | fontStyle: "italic", 49 | }, 50 | })} 51 | /> 52 | 53 | Skip to content 54 | 55 |
56 | 57 | , 60 | }} 61 | > 62 | {children} 63 | 64 | 65 |