Last Updated: 2021-02-08

cover

이번 포스트에서는 오픈 소스(Open-Source)를 만들기 위한 구조와 오픈 소스(Open-Source) 개발에 필요한 모듈을 정의해보려 한다.

개발자라면 한 번쯤은 오픈 소스(Open-Source)를 만들어 공유나 배포를 해보았을 것이다. 본인들이 사용하는 각종 모듈과 기술들이 있다면 그대로 사용하면 되지만 보통 오픈 소스(Open-Source)를 만들기 시작할 때 어떤 구조와 형태로 만들어야 하는지 감이 잡히질 않는다.
작성자 역시 처음에 오픈 소스(Open-Source)를 만들 때 여러 번 실패를 했던 것으로 기억한다. 사실 이 오픈 소스(Open-Source) 구조를 잡는데 정확한 답은 없다. 정상적으로 기능이 동작하고 배포하고 테스트가 되면 그만이겠지만 오픈 소스(Open-Source)는 보통 개인이 진행하기도 하지만 여러 사람과 함께 진행하는 경우도 있기 때문에 이에 맞는 모듈과 구조가 필요하다.

오픈 소스(Open-Source)를 개발하고 개발 생태계에 기여하고 싶은 개발자를 위하여 오픈 소스(Open-Source)를 만드는 방법과 필요한 모듈, 그리고 구조에 대해 얘기해보려고 한다.


시작하기 앞서 오픈 소스(Open-Source)에 필요한 키워드만 먼저 살펴 보고 하나하나 집고 나가자.

Keyword

  • git/github
  • README
  • circleci
  • editorconfig
  • eslint
  • babel
  • commitlint
  • standard-version
  • husky
  • jest
  • yarn/npm
  • license
  • changelog
  • codecov

프로젝트 관리

오픈 소스(Open-Source)는 말할 필요 없이 gitgithub를 사용하여 관리한다. github를 사용함으로 인하여 오픈 소스(Open-Source)를 관리하고 개발자에게 제공할 수 있다.

github에 대한 설명은 다른 블로그에서도 많으니 검색하여 계정이 없다면 하나 정도는 만들어두도록 하자.

README.md

README는 오픈 소스(Open-Source)에 있어서 중요한 부분이다. 오픈 소스(Open-Source)를 만들어도 사용법을 알아야 하는데 이런 역할은 README.md가 한다. 즉, REAMD는 오픈 소스(Open-Source)의 가이드라인이나 매뉴얼로 보면 된다.

github README.md 파일을 위치시키면 자동을 읽어서 첫 페이지로 출력된다. 그렇기 때문에 다른 사용자가 해당 오픈 소스(Open-Source)의 레파지토리에 접근한다면 최초로 보는 페이지이기 때문에 신중하게 작성할 필요가 있다.

CI

CI는 빌드 시스템이다.

내가 올린 오픈 소스(Open-Source)를 빌드하고 설정에 따라 테스트와 배포까지 진행 할 수 있다. github로 프로젝트를 관리하였다면 CI를 통해서 관리되는 오픈 소스(Open-Source)의 테스트 및 빌드, 배포를 자동화 할 수 있다.
CI는 종류가 많지만, 필자의 경우 CircleCI 사용하고 있다. 어떤 ci를 선택해도 무관하지만 중요한 건 오픈 소스(Open-Source)를 자동으로 빌드, 테스트, 배포를 위해서 사용해야 한다. 매번 수동으로 테스트와 배포를 할 수 없진 않은가.

이 CI를 github 내 나의 오픈 소스(Open-Source)와 연동함으로써 github REAMDE에 뱃지를 달 수 있다.

보통 github의 오픈 소스(Open-Source)를 보게 되면 README.md에 프로젝트의 버전, 다운로드 수, 빌드 상태 등의 상태 정보를 볼 수 있는데 이것을 뱃지라 하며 각 뱃지는 특정 시스템들과 연동이 되어 출력된다.

아래를 보면 npm version, 다운로드 수, 빌드 상태, 커버지리 및 코드 스타일 등의 뱃지를 볼 수 있다.

github-readme-badge

.editorconfig

editorconfig는 설정 파일이며, 협업을 위한 하나의 도구이다.

editorconfig를 설정함으로써 해당 오픈 소스(Open-Source) 코드의 포맷을 정의할 수 있다. 완벽한 스타일 정의를 잡기는 쉽지 않지만 어느 정도의 기준을 잡을 수 있다.

오픈 소스(Open-Source)를 만들었는데 누군가 해당 오픈 소스(Open-Source)에 기여하기 위하여 fork를 받고 수정 후 PR(Pull Request)을 요청했는데 코드 스타일이 전혀 다르면 Merge를 시킬 수 없다.
이런 기준은 .editorconfig가 지정해 준다. 이 외에 pretter를 사용해도 되기 때문에 원하는 것을 사용하기 바란다.

e.g) .editorconfig

# editorconfig.org
root = true

[*]
indent_size = 2
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

eslint

eslint는 개발자면 대부분 아는 모듈이다.

협업뿐만 아니라 개인이 개발할 때도 필수요소이다. 보통 eslint 설정이 귀찮아 사용하지 않는 경우가 많은데 eslint는 문제점을 야기할 수 있는 부분들을 사전에 방지하며 코드 분석을 하기 때문에 필수로 사용하기 바란다.

babel

babel 역시 Front-End 개발자라면 알 것이다. 최신 버전(ES6+)의 자바스크립트 문법을 IE를 포함한 구형 브라우저에서 동작할 수 있도록 도와주는 컴파일러이다.

오픈 소스(Open-Source)를 만든다는 것은 한정된 브라우저에 동작하게 하진 않을 것이다. 보통 브라우저와 Node.js의 버전은 정해지겠지만 IE, Chrome, Firefox, Safari 등의 브라우저에서 호환이 되도록 개발할 것이기에 Babel은 필수이다.

commitlint

commitlint는 협업을 위한 도구이기도 하지만 일관성 있는 commit message를 작성하기 위한 도구이다.

개발을 하다 보면 간혹 commit 메시지 작성에 귀찮을 때가 있는데 이런 부분을 일부 제어해준다. 규칙에 맞지 않는 commit message는 허용하지 않으며 이로 인해 commit 자체를 하지 못하기 때문이다.
commitlint는 commit 시 입력된 메시지가 규칙에 맞는지 검사하는 도구이기 때문에 일부 패턴이 있으며 이는 Conventional Commit 규칙을 따른다.

commitlintstandard-versionhusky, CHAGNELOG과 연관이 있으며 연관성을 차근차근 알아보자.

e.g) Conventional Commits 규칙

<type>[optional scope]: <description>

fix: 버그수정
chore(depd): 디펜덴시 모듈 수정
feat: 기능추가

CHANGELOG

CHAGNELOG는 해당 오픈 소스(Open-Source)의 릴리즈노트이다.

오픈 소스(Open-Source)를 수정하고 commit 시 수동적으로 수정해도 되는 파일이지만 이는 매우 번거롭다. 그렇기 때문에 이 CHAGNELOG을 자동으로 생성해야 하는데 이는 위에서 설명한 commitlint와 아래에서 설명할 standard-version과 연관이 있다.
commitlint를 통하여 규칙에 허용되는 commit message를 입력하면 내용을 기준으로 standard-versionCHAGNELOG을 동적으로 생성 및 수정한다.

생성된 CHANGELOG에는 버전과 commit message 그리고 수정된 파일 리비전이 같이 생성이 된다.

여기서 생각해 볼 것은 만약 commit message가 엉망이라면 이로 인해서 CHAGNELOG까지 엉망이 될 것이다. 그렇기 때문에 commitlint를 사용하는 이유도 있다.

e.g) CHANGELOG.md
change-log

standard-version

standard-version은 위에서 설명한 commit 규칙인 Conventional Commit으로 구동된다.
standard-version의 역할은 시멘틱 버저닝인 semver를 기준으로 CHANGELOG를 생성한다.

husky

huskygit hook을 손쉽게 제어할 수 있다.

오픈 소스(Open-Source)에서 husky의 역할은 아래와 같다.

  • git commit message를 입력 시 commitlint를 구동
  • git commit 실행 전 eslint 실행
  • git push 실행 전 lint & test 실행

즉, 위에서 배운 commitlint, eslint 등을 git 명령어 실행 시 사전에 구동하는 편리한 도구이다. 그렇기 때문에 commitlint를 설명할 때 husky가 연관이 있다고 한 것이다.

jest

jest는 단위 테스트 프레임워크이다.

여기서 굳이 jest를 사용하지 않아도 된다. mochajasmine과 같은 훌륭한 도구들도 많다. 만약 Mocha에 대해서 궁금하다면 JavaScript 단위 테스트 프레임워크 - Mocha를 참고하도록 하자.

작성자가 jest를 사용하는 이유는 jest의 설정이 다소 간편한 편이고 소규모의 프로젝트에 적합하기 때문이다. 오픈 소스(Open-Source)의 구조나 규모가 클 수도 있겠지만 보통 작은 모듈의 단위이기 때문에 jest가 적합하다고 판단한다. 또한 vuereact와 같은 프레임워크와 같이 사용하기 복잡함이 없기 때문이다.

어느 단위 테스트 프레임워크를 사용하냐는 여러분의 몫이지만 단위 테스트는 필수로 사용하기 바란다.

codecov

codecov는 코드 커버리지를 그룹화, 병합, 보관할 수 있다.

jest를 사용했다면 단위 테스트를 진행할 때 coverage를 생성할 수 있는데 이를 codecov와 연동하여 사용할 수 있다.

e.g) codecov
codecov-sample

codecov를 사용하여 오픈 소스(Open-Source)의 coverage를 관리할 수 있다.

yarn/npm

둘 중 어느 것을 사용해도 무방하다. 보통 추세는 더 빠른 yarn을 사용하지만 여기서는 딱히 설명하지 않도록 하겠다.

license

우리의 오픈 소스(Open-Source)는 MIT로 하도록 하자. 개발 생태계에 기여를 하고 돈을 받진 안도록 하자.

이 글을 보는 개발자들도 오픈 소스(Open-Source)를 많이 사용하고 있진 않은가? 받은 만큼 돌려주도록 하자.


여기까지가 오픈 소스(Open-Source) 개발에 필요한 모듈과 설정들이다. 이제 이를 사용하여 하나의 오픈 소스(Open-Source) 프로젝트 구조를 잡아보도록 하자.

오픈 소스(Open-Source) 만들기

1. Git Repository 생성

설명은 생략하겠다. GitHub에 적당한 Repository를 생성하도록 하자.

2. 폴더 생성 및 Test 폴더 생성

적당한 Repository가 생성되었다면 실제 Core 코드가 들어갈 폴더 lib를 하나 만들자. 그리고 단위 테스트를 위해 test라는 폴더도 하나 만들자.

3. editorconfig 설정

.editorconfig 파일을 생성하여 코드 스타일에 대한 규칙을 생성하자.

.editorconfig

# editorconfig.org
root = true

[*]
indent_size = 2
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

4. Node Module 설치

이제 위에서 배운 모든 모듈을 설치하자.

npm이나 yarn을 통해서 설치해도 되지만 아래 내용을 package.json에 입력 후 npm install로 설치하도록 하자.

여기서 필자는 오픈 소스(Open-Source) 개발에 필요한 모든 도구는 최신 버전(latest)로 설치한다. 이렇게 하면 호환성을 유지하는데 약간 귀찮은 면이 있지만, 이후 이 오픈 소스(Open-Source)를 fork를 통해서 수정하는 자가 있다면 그 개발자도 최신을 유지해야 하기 때문에 이 오픈 소스(Open-Source)는 언제나 최신일 것이다.

{
"devDependencies": {
"@babel/core": "latest",
"@babel/preset-env": "latest",
"@commitlint/cli": "latest",
"@commitlint/config-conventional": "latest",
"babel-eslint": "latest",
"babel-jest": "latest",
"codecov": "latest",
"eslint": "latest",
"eslint-config-standard": "latest",
"eslint-plugin-import": "latest",
"eslint-plugin-jest": "latest",
"eslint-plugin-node": "latest",
"eslint-plugin-promise": "latest",
"eslint-plugin-standard": "latest",
"husky": "latest",
"jest": "latest",
"standard-version": "latest"
}
}

5. package.json 수정

package.json을 오픈 소스(Open-Source)에 맞도록 수정하자. 보통 오픈 소스(Open-Source)가 개발되면 npm package로서 배포도 가능하므로 이를 고려하여 작성한다.

npm 패키지 배포빠르게 배우는 NPM 패키지 생성부터 배포까지 완벽 가이드를 참고하도록 하자.

package.json

{
"name": "open-source-example",
"version": "1.0.0",
"description": "",
"main": "lib/module.js", // main 모듈
"homepage": "", // git repositry 또는 homepage
"repository": "", // git repository
"bugs": {
"url": "", //github issues url
"email": "kdydesign@gmail.com" // mail
},
"scripts": {
"dev": "", // 로컬 서버 실행 스크립트
"lint": "eslint lib test", // eslint 실행 스크립트
"test": "yarn lint && jest", // lint 및 test 실행 스크립트
"release": "yarn test && standard-version && git push --follow-tags && npm publish" // 오픈 소스(Open-Source) 배포 및 CHANGELOG 생성
},
"keywords": [
"open-source" // npm registry 키워드
],
"contributors": [
{
"name": "Dev.DY <kdydesign@gmail.com>" // author 또는 contributors(기여)
}
],
"license": "MIT",
"publishConfig": {
"access": "public" // npm publish 허용
},
"files": [
"lib" // npm 패키지 배포될 경로
],
"dependencies": {},
"devDependencies": {
"@babel/core": "latest",
"@babel/preset-env": "latest",
"@commitlint/cli": "latest",
"@commitlint/config-conventional": "latest",
"babel-eslint": "latest",
"babel-jest": "latest",
"codecov": "latest",
"eslint": "latest",
"eslint-config-standard": "latest",
"eslint-plugin-import": "latest",
"eslint-plugin-jest": "latest",
"eslint-plugin-node": "latest",
"eslint-plugin-promise": "latest",
"eslint-plugin-standard": "latest",
"husky": "latest",
"jest": "latest",
"standard-version": "latest"
}
}

위 내용을 참고하여 작성하도록 하자.

6. eslint 설정

.eslintrc.js.eslintignore를 생성하여 eslint에 대한 설정을 진행하자.

설정은 프로젝트마다 다르기 때문에 자세한 내용은 참고만 하도록 하자.

.eslintrc.js

module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
'jest/globals': true
},
extends: [
'standard'
],
plugins: [
'jest'
],
rules: {}
}

.eslintignore

coverage
node_modules

7. babel 설정

babel.config.js를 생성하여 babel에 대한 설정을 진행하자.

babel 설정 역시 프로젝트마다 다르기 때문에 내용은 참고만 하도록 하자.

babel.config.js

module.exports = {
presets: [
[
'@babel/preset-env', {
targets: {
esmodules: true
}
}
]
]
}

8.commitlint 설정

commitlint.config.js를 생성하여 commitlint의 설정을 진행하자.

기본적으로 아래와 같이 사용하며 추가할 내용이 있다면 추가해도 된다.

commitlint.config.js

module.exports = {
extends: [
'@commitlint/config-conventional'
]
}

9. husky 설정

husky.config.js를 생성하여 husky에 대한 설정을 진행하자.

보통 아래와 같이 필자는 사용하지만 pre-push는 생략해도 무관하다.

husky.config.js

module.exports = {
hooks: {
'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS',
'pre-commit': 'yarn lint',
'pre-push': 'yarn lint'
}
}

10. jest 설정

jest.config.js를 생성하여 jest에 대한 설정을 진행하자.

collectCoverage를 활성화함으로써 coverage를 생성할 수 있다.

jest.config.js

module.exports = {
collectCoverage: true,
collectCoverageFrom: ['lib/**/*.js'], // 프로젝트 구조에 따라 다르게 설정하자.
testEnvironment: 'node'
}

11. circleCI 설정

.circleci 폴더를 생성하고 config.yml을 생성하자.

자세한 circleci 설정 및 연동 방법과 codecov 연동 방법은 구글 검색을 통해서 정보를 찾아보자.

config.yml

version: 2
jobs:
build:
docker:
- image: circleci/node
steps:
# Checkout repository
- checkout

# Restore cache
- restore_cache:
key: yarn-cache-{{ checksum "yarn.lock" }}

# Install dependencies
- run:
name: Install Dependencies
command: NODE_ENV=dev yarn

# Keep cache
- save_cache:
key: yarn-cache-{{ checksum "yarn.lock" }}
paths:
- "node_modules"

# Lint
- run:
name: Lint
command: yarn lint

# Tests
- run:
name: Tests
command: yarn jest

# Coverage
- run:
name: Coverage
command: yarn codecov

12. LICENSE

기여하도록하자.!

LICENSE

MIT License

Copyright (c) Dev.DY

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

위와 같이 설정하면 아래와 같은 구조를 가지게 된다.

오픈 소스(Open-Source) 기본 구조
open-source-sample-structure

CHANGELOG는 standard-version을 실행하면 생성된다.


오픈 소스(Open-Source) 구조에 정답은 없다. 자신이 사용하는 모듈이 다를 수도 있고 사용을 하지 않을 수도 있다. 하지만 기본적인 골격을 잡아 놓는다면 많은 오픈 소스(Open-Source)를 개발할 때 유용하다.
뿐만 아니라 설정을 해나가면서 기술에 대한 지식도 쌓아가니 일거양득으로 볼 수 있다.

필자도 처음에는 막막하기만 했지만 여러 오픈 소스(Open-Source)를 찾아가거나 일을 하다가 새로운 구조를 보고 정리해 나아가다 보니 어느 정도 정착이 되었다.

구조가 중요한 것은 아니지만 구조를 잡기 위한 방법들을 배우는 것은 중요한 부분을 차지한다. 해본 것과 안 해본 것의 차이는 크기 때문이다. 또한 여기에서 설정한 모듈은 때로는 불필요하게 느껴질 수 있겠지만 필수로 생각하자.

특히 linter와 test는 필수요소이다.