InfoGrab DocsInfoGrab Docs

JavaScript 스타일 가이드

요약

GitLab은 JavaScript 스타일 가이드라인 대부분을 Airbnb JavaScript 스타일 가이드와 그에 딸린 린터로 관리합니다. Airbnb가 정한 스타일 가이드라인에 더해, 아래와 같은 몇 가지 별도 규칙이 있습니다.

GitLab은 JavaScript 스타일 가이드라인 대부분을 Airbnb JavaScript 스타일 가이드와 그에 딸린 린터로 관리합니다.

Airbnb가 정한 스타일 가이드라인에 더해, 아래와 같은 몇 가지 별도 규칙이 있습니다.

Note

yarn run lint:eslint:all 또는 yarn run lint:eslint $PATH_TO_FILE을 실행하면 로컬에서 ESLint를 실행할 수 있습니다.

준비 완료 신호#

기능 테스트가 JavaScript 초기화를 기다려야 한다면 사용자에게 보이는 결과를 관찰하는 방식을 우선합니다. 페이지에 단언할 만한 UI 상태가 없을 때만 준비 완료 신호를 추가합니다.

해당 이니셜라이저가 완료될 때 설정되는, 범위가 정확한 data-* 속성을 사용합니다.

document.body.dataset.rightSidebarInitialized = 'true';

page-initialised처럼 타이머에 기반한 클래스를 완료 신호로 사용하지 않습니다. 타임아웃은 초기화가 끝났다는 증거가 되지 않습니다. 기능 테스트 동기화 패턴은 wait_for_requests 나 wait_for_all_requests를 사용하지 않습니다를 참고합니다.

forEach 사용 지양#

데이터를 변경할 때는 forEach를 피합니다. 데이터를 변경할 때는 forEach 대신 map, reduce, filter를 사용합니다. 이렇게 하면 함수 안의 변경이 줄어들며, 이는 Airbnb 스타일 가이드와도 부합합니다.

// bad
users.forEach((user, index) => {
  user.id = index;
});

// good
const usersWithId = users.map((user, index) => {
  return Object.assign({}, user, { id: index });
});

매개변수 수 제한#

함수나 메서드의 매개변수가 3개를 넘으면 매개변수 대신 객체 하나를 사용합니다.

// bad
function a(p1, p2, p3, p4) {
  // ...
};

// good
function a({ p1, p2, p3, p4 }) {
  // ...
};

DOM 이벤트 처리 시 클래스 사용 지양#

클래스의 유일한 목적이 DOM 이벤트를 바인딩하고 콜백을 처리하는 것이라면 함수를 사용합니다.

// bad
class myClass {
  constructor(config) {
    this.config = config;
  }

  init() {
    document.addEventListener('click', () => {});
  }
}

// good

const myFunction = () => {
  document.addEventListener('click', () => {
    // handle callback here
  });
}

생성자에 엘리먼트 컨테이너 전달#

클래스가 DOM을 조작한다면 엘리먼트 컨테이너를 매개변수로 받습니다. 이렇게 하면 유지 보수성과 성능이 더 좋습니다.

// bad
class a {
  constructor() {
    document.querySelector('.b');
  }
}

// good
class a {
  constructor(options) {
    options.container.querySelector('.b');
  }
}

문자열을 정수로 변환#

문자열을 정수로 변환할 때 Number는 의미가 분명하고 더 읽기 쉬울 수 있습니다. 둘 다 사용할 수 있지만 Number가 유지 보수 측면에서 조금 더 유리합니다.

Warning

parseInt에는 radix 인수를 반드시 포함해야 합니다.

// bad (missing radix argument)
parseInt('10');

// good
parseInt("106", 10);

// good
Number("106");
// bad (missing radix argument)
things.map(parseInt);

// good
things.map(Number);
Note

문자열이 정수가 아닌 값(소수점이 포함된 수)을 나타낼 수 있다면 parseInt를 사용하지 않습니다. 대신 Number 나 parseFloat를 검토합니다.

CSS 셀렉터 - js- 접두사 사용#

CSS 클래스를 JavaScript에서 엘리먼트 참조용으로만 사용한다면 클래스 이름에 js- 접두사를 붙입니다.

// bad
<button class="add-user"></button>

// good
<button class="js-add-user"></button>

ES 모듈 문법#

대부분의 JavaScript 파일에서는 모듈을 가져오거나 내보낼 때 ES 모듈 문법을 사용합니다. 이름 일관성이 높아지므로 명명된 내보내기를 우선합니다.

// bad (with exceptions, see below)
export default SomeClass;
import SomeClass from 'file';

// good
export { SomeClass };
import { SomeClass } from 'file';

다음과 같은 일부 상황에서는 기본 내보내기를 사용해도 됩니다.

  • Vue 단일 파일 컴포넌트(SFC)
  • Vuex 뮤테이션 파일

자세한 내용은 RFC 20을 참고합니다.

CommonJS 모듈 문법#

GitLab의 Node 구성은 CommonJS 모듈 문법을 요구합니다. 명명된 내보내기를 우선합니다.

// bad
module.exports = SomeClass;
const SomeClass = require('./some_class');

// good
module.exports = { SomeClass };
const { SomeClass } = require('./some_class');

모듈의 절대 경로 vs 상대 경로#

가져오려는 모듈이 두 단계 미만 위에 있으면 상대 경로를 사용합니다.

// bad
import GitLabStyleGuide from '~/guides/GitLabStyleGuide';

// good
import GitLabStyleGuide from '../GitLabStyleGuide';

가져오려는 모듈이 두 단계 이상 위에 있으면 절대 경로를 사용합니다.

// bad
import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';

// good
import GitLabStyleGuide from '~/GitLabStyleGuide';

또한 전역 네임스페이스에 추가하지 않습니다.

페이지 모듈이 아닌 곳에서 DOMContentLoaded 사용 금지#

가져온 모듈은 로드될 때마다 동일하게 동작해야 합니다. DOMContentLoaded 이벤트는 webpack으로 동적으로 로드되는 /pages/* 디렉터리의 모듈에서만 허용됩니다.

XSS 방지#

콘텐츠를 설정할 때 innerHTML, append(), html()을 사용하지 않습니다. 너무 많은 취약점이 생깁니다.

ESLint#

ESLint의 동작은 도구 가이드에서 확인할 수 있습니다.

IIFEs#

IIFE(즉시 실행 함수 표현식) 사용을 피합니다. 파일 내용을 IIFE로 감싼 예시가 많이 남아 있지만, Sprockets에서 webpack으로 전환한 뒤로는 더 이상 필요하지 않습니다. 이제는 사용하지 않으며, 레거시 코드를 리팩터링할 때 자유롭게 제거해도 됩니다.

전역 네임스페이스#

전역 네임스페이스에 추가하지 않습니다.

// bad
window.MyClass = class { /* ... */ };

// good
export default class MyClass { /* ... */ }

사이드 이펙트#

최상위 레벨 사이드 이펙트#

export가 있는 스크립트에서는 최상위 레벨 사이드 이펙트가 금지됩니다.

// bad
export default class MyClass { /* ... */ }

document.addEventListener("DOMContentLoaded", function(event) {
  new MyClass();
}

생성자에서의 사이드 이펙트 지양#

constructor에서 비동기 호출, API 요청, DOM 조작을 하지 않습니다. 대신 별도 함수로 옮깁니다. 이렇게 하면 테스트를 작성하기 쉬워지고 단일 책임 원칙을 위반하지 않게 됩니다.

// bad
class myClass {
  constructor(config) {
    this.config = config;
    axios.get(this.config.endpoint)
  }
}

// good
class myClass {
  constructor(config) {
    this.config = config;
  }

  makeRequest() {
    axios.get(this.config.endpoint)
  }
}
const instance = new myClass();
instance.makeRequest();

순수 함수와 데이터 변경#

작은 순수 함수를 많이 작성하고 변경이 일어나는 지점을 최소화합니다

// bad
const values = {foo: 1};

function impureFunction(items) {
  const bar = 1;

  items.foo = items.a * bar + 2;

  return items.a;
}

const c = impureFunction(values);

// good
var values = {foo: 1};

function pureFunction (foo) {
  var bar = 1;

  foo = foo * bar + 2;

  return foo;
}

var c = pureFunction(values.foo);

상수를 원시값으로 내보내기#

객체를 내보내는 대신 공통 네임스페이스를 가진 상수 원시값을 내보내는 방식을 우선합니다. 이렇게 하면 컴파일 시점 참조 검사가 더 잘 동작하고 런타임에 의도치 않은 undefined를 피할 수 있습니다. 또한 번들 크기를 줄이는 데도 도움이 됩니다.

prop 유효성 검사기처럼 상수를 순회해야 할 때만 상수를 컬렉션(배열 또는 객체)으로 내보냅니다.

// bad
export const VARIANT = {
  WARNING: 'warning',
  ERROR: 'error',
};

// good
export const VARIANT_WARNING = 'warning';
export const VARIANT_ERROR = 'error';

// good, if the constants need to be iterated over
export const VARIANTS = [VARIANT_WARNING, VARIANT_ERROR];

오류 처리#

서버가 500을 반환하는 내부 서버 오류에는 일반적인 오류 메시지를 반환해야 합니다.

백엔드가 오류를 반환할 때는 그 오류가 사용자에게 그대로 표시하기에 적합해야 합니다.

그렇게 하기 어려운 경우에는 최후의 수단으로 접두사를 붙여 특정 오류 메시지를 선별할 수 있습니다.

  1. 백엔드가 표시할 오류 메시지에 다음과 같이 접두사를 붙이는지 확인합니다.

    Gitlab::Utils::ErrorMessage.to_user_facing('Example user-facing error-message')
    
  2. app/assets/javascripts/lib/utils/error_message.js에 있는 오류 메시지 유틸리티 함수를 사용합니다.

이 유틸리티는 매개변수 두 개를 받습니다. 서버 응답에서 받은 오류 객체와 기본 오류 메시지입니다. 유틸리티는 오류 객체의 메시지를 살펴 그 메시지가 사용자에게 표시할 것인지 나타내는 접두사가 있는지 확인합니다. 사용자에게 표시할 메시지이면 그대로 반환합니다. 그렇지 않으면 매개변수로 전달된 기본 오류 메시지를 반환합니다.

import { parseErrorMessage } from '~/lib/utils/error_message';

onError(error) {
  const errorMessage = parseErrorMessage(error, genericErrorText);
}

이 접두사 방식은 API 응답에 사용하면 안 됩니다. 오류 객체를 다루는 방법은 REST API 또는 GraphQL 가이드를 따릅니다.

JavaScript 스타일 가이드

GitLab v19.4
원문 보기

요약

GitLab은 JavaScript 스타일 가이드라인 대부분을 Airbnb JavaScript 스타일 가이드와 그에 딸린 린터로 관리합니다. Airbnb가 정한 스타일 가이드라인에 더해, 아래와 같은 몇 가지 별도 규칙이 있습니다.

GitLab은 JavaScript 스타일 가이드라인 대부분을 Airbnb JavaScript 스타일 가이드와 그에 딸린 린터로 관리합니다.

Airbnb가 정한 스타일 가이드라인에 더해, 아래와 같은 몇 가지 별도 규칙이 있습니다.

Note

yarn run lint:eslint:all 또는 yarn run lint:eslint $PATH_TO_FILE을 실행하면 로컬에서 ESLint를 실행할 수 있습니다.

준비 완료 신호#

기능 테스트가 JavaScript 초기화를 기다려야 한다면 사용자에게 보이는 결과를 관찰하는 방식을 우선합니다. 페이지에 단언할 만한 UI 상태가 없을 때만 준비 완료 신호를 추가합니다.

해당 이니셜라이저가 완료될 때 설정되는, 범위가 정확한 data-* 속성을 사용합니다.

document.body.dataset.rightSidebarInitialized = 'true';

page-initialised처럼 타이머에 기반한 클래스를 완료 신호로 사용하지 않습니다. 타임아웃은 초기화가 끝났다는 증거가 되지 않습니다. 기능 테스트 동기화 패턴은 wait_for_requests 나 wait_for_all_requests를 사용하지 않습니다를 참고합니다.

forEach 사용 지양#

데이터를 변경할 때는 forEach를 피합니다. 데이터를 변경할 때는 forEach 대신 map, reduce, filter를 사용합니다. 이렇게 하면 함수 안의 변경이 줄어들며, 이는 Airbnb 스타일 가이드와도 부합합니다.

// bad
users.forEach((user, index) => {
  user.id = index;
});

// good
const usersWithId = users.map((user, index) => {
  return Object.assign({}, user, { id: index });
});

매개변수 수 제한#

함수나 메서드의 매개변수가 3개를 넘으면 매개변수 대신 객체 하나를 사용합니다.

// bad
function a(p1, p2, p3, p4) {
  // ...
};

// good
function a({ p1, p2, p3, p4 }) {
  // ...
};

DOM 이벤트 처리 시 클래스 사용 지양#

클래스의 유일한 목적이 DOM 이벤트를 바인딩하고 콜백을 처리하는 것이라면 함수를 사용합니다.

// bad
class myClass {
  constructor(config) {
    this.config = config;
  }

  init() {
    document.addEventListener('click', () => {});
  }
}

// good

const myFunction = () => {
  document.addEventListener('click', () => {
    // handle callback here
  });
}

생성자에 엘리먼트 컨테이너 전달#

클래스가 DOM을 조작한다면 엘리먼트 컨테이너를 매개변수로 받습니다. 이렇게 하면 유지 보수성과 성능이 더 좋습니다.

// bad
class a {
  constructor() {
    document.querySelector('.b');
  }
}

// good
class a {
  constructor(options) {
    options.container.querySelector('.b');
  }
}

문자열을 정수로 변환#

문자열을 정수로 변환할 때 Number는 의미가 분명하고 더 읽기 쉬울 수 있습니다. 둘 다 사용할 수 있지만 Number가 유지 보수 측면에서 조금 더 유리합니다.

Warning

parseInt에는 radix 인수를 반드시 포함해야 합니다.

// bad (missing radix argument)
parseInt('10');

// good
parseInt("106", 10);

// good
Number("106");
// bad (missing radix argument)
things.map(parseInt);

// good
things.map(Number);
Note

문자열이 정수가 아닌 값(소수점이 포함된 수)을 나타낼 수 있다면 parseInt를 사용하지 않습니다. 대신 Number 나 parseFloat를 검토합니다.

CSS 셀렉터 - js- 접두사 사용#

CSS 클래스를 JavaScript에서 엘리먼트 참조용으로만 사용한다면 클래스 이름에 js- 접두사를 붙입니다.

// bad
<button class="add-user"></button>

// good
<button class="js-add-user"></button>

ES 모듈 문법#

대부분의 JavaScript 파일에서는 모듈을 가져오거나 내보낼 때 ES 모듈 문법을 사용합니다. 이름 일관성이 높아지므로 명명된 내보내기를 우선합니다.

// bad (with exceptions, see below)
export default SomeClass;
import SomeClass from 'file';

// good
export { SomeClass };
import { SomeClass } from 'file';

다음과 같은 일부 상황에서는 기본 내보내기를 사용해도 됩니다.

  • Vue 단일 파일 컴포넌트(SFC)
  • Vuex 뮤테이션 파일

자세한 내용은 RFC 20을 참고합니다.

CommonJS 모듈 문법#

GitLab의 Node 구성은 CommonJS 모듈 문법을 요구합니다. 명명된 내보내기를 우선합니다.

// bad
module.exports = SomeClass;
const SomeClass = require('./some_class');

// good
module.exports = { SomeClass };
const { SomeClass } = require('./some_class');

모듈의 절대 경로 vs 상대 경로#

가져오려는 모듈이 두 단계 미만 위에 있으면 상대 경로를 사용합니다.

// bad
import GitLabStyleGuide from '~/guides/GitLabStyleGuide';

// good
import GitLabStyleGuide from '../GitLabStyleGuide';

가져오려는 모듈이 두 단계 이상 위에 있으면 절대 경로를 사용합니다.

// bad
import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';

// good
import GitLabStyleGuide from '~/GitLabStyleGuide';

또한 전역 네임스페이스에 추가하지 않습니다.

페이지 모듈이 아닌 곳에서 DOMContentLoaded 사용 금지#

가져온 모듈은 로드될 때마다 동일하게 동작해야 합니다. DOMContentLoaded 이벤트는 webpack으로 동적으로 로드되는 /pages/* 디렉터리의 모듈에서만 허용됩니다.

XSS 방지#

콘텐츠를 설정할 때 innerHTML, append(), html()을 사용하지 않습니다. 너무 많은 취약점이 생깁니다.

ESLint#

ESLint의 동작은 도구 가이드에서 확인할 수 있습니다.

IIFEs#

IIFE(즉시 실행 함수 표현식) 사용을 피합니다. 파일 내용을 IIFE로 감싼 예시가 많이 남아 있지만, Sprockets에서 webpack으로 전환한 뒤로는 더 이상 필요하지 않습니다. 이제는 사용하지 않으며, 레거시 코드를 리팩터링할 때 자유롭게 제거해도 됩니다.

전역 네임스페이스#

전역 네임스페이스에 추가하지 않습니다.

// bad
window.MyClass = class { /* ... */ };

// good
export default class MyClass { /* ... */ }

사이드 이펙트#

최상위 레벨 사이드 이펙트#

export가 있는 스크립트에서는 최상위 레벨 사이드 이펙트가 금지됩니다.

// bad
export default class MyClass { /* ... */ }

document.addEventListener("DOMContentLoaded", function(event) {
  new MyClass();
}

생성자에서의 사이드 이펙트 지양#

constructor에서 비동기 호출, API 요청, DOM 조작을 하지 않습니다. 대신 별도 함수로 옮깁니다. 이렇게 하면 테스트를 작성하기 쉬워지고 단일 책임 원칙을 위반하지 않게 됩니다.

// bad
class myClass {
  constructor(config) {
    this.config = config;
    axios.get(this.config.endpoint)
  }
}

// good
class myClass {
  constructor(config) {
    this.config = config;
  }

  makeRequest() {
    axios.get(this.config.endpoint)
  }
}
const instance = new myClass();
instance.makeRequest();

순수 함수와 데이터 변경#

작은 순수 함수를 많이 작성하고 변경이 일어나는 지점을 최소화합니다

// bad
const values = {foo: 1};

function impureFunction(items) {
  const bar = 1;

  items.foo = items.a * bar + 2;

  return items.a;
}

const c = impureFunction(values);

// good
var values = {foo: 1};

function pureFunction (foo) {
  var bar = 1;

  foo = foo * bar + 2;

  return foo;
}

var c = pureFunction(values.foo);

상수를 원시값으로 내보내기#

객체를 내보내는 대신 공통 네임스페이스를 가진 상수 원시값을 내보내는 방식을 우선합니다. 이렇게 하면 컴파일 시점 참조 검사가 더 잘 동작하고 런타임에 의도치 않은 undefined를 피할 수 있습니다. 또한 번들 크기를 줄이는 데도 도움이 됩니다.

prop 유효성 검사기처럼 상수를 순회해야 할 때만 상수를 컬렉션(배열 또는 객체)으로 내보냅니다.

// bad
export const VARIANT = {
  WARNING: 'warning',
  ERROR: 'error',
};

// good
export const VARIANT_WARNING = 'warning';
export const VARIANT_ERROR = 'error';

// good, if the constants need to be iterated over
export const VARIANTS = [VARIANT_WARNING, VARIANT_ERROR];

오류 처리#

서버가 500을 반환하는 내부 서버 오류에는 일반적인 오류 메시지를 반환해야 합니다.

백엔드가 오류를 반환할 때는 그 오류가 사용자에게 그대로 표시하기에 적합해야 합니다.

그렇게 하기 어려운 경우에는 최후의 수단으로 접두사를 붙여 특정 오류 메시지를 선별할 수 있습니다.

  1. 백엔드가 표시할 오류 메시지에 다음과 같이 접두사를 붙이는지 확인합니다.

    Gitlab::Utils::ErrorMessage.to_user_facing('Example user-facing error-message')
    
  2. app/assets/javascripts/lib/utils/error_message.js에 있는 오류 메시지 유틸리티 함수를 사용합니다.

이 유틸리티는 매개변수 두 개를 받습니다. 서버 응답에서 받은 오류 객체와 기본 오류 메시지입니다. 유틸리티는 오류 객체의 메시지를 살펴 그 메시지가 사용자에게 표시할 것인지 나타내는 접두사가 있는지 확인합니다. 사용자에게 표시할 메시지이면 그대로 반환합니다. 그렇지 않으면 매개변수로 전달된 기본 오류 메시지를 반환합니다.

import { parseErrorMessage } from '~/lib/utils/error_message';

onError(error) {
  const errorMessage = parseErrorMessage(error, genericErrorText);
}

이 접두사 방식은 API 응답에 사용하면 안 됩니다. 오류 객체를 다루는 방법은 REST API 또는 GraphQL 가이드를 따릅니다.