병합 요청 Diff 프론트엔드 개요

이 문서는 프론트엔드 diffs Vue 애플리케이션이 어떻게 작동하는지와 존재하는 다양한 부분에 대한 개요를 제공합니다. 기여자들이 도움을 받을 수 있도록 하기 위해 다음을 포함합니다:

  • diffs Vue 앱이 어떻게 설정되어 있는지 이해하기.
  • 개선이 필요한 영역 식별하기.

이 문서는 살아있는 문서입니다. diffs 애플리케이션에서 중요한 변경이 있을 때마다 업데이트하세요.

Diffs Vue 앱

구성 요소

Diffs를 렌더링하기 위한 Vue 앱은 GitLab 앱의 다른 영역과 공유되는 여러 Vue 구성 요소를 사용합니다. 아래 차트는 어떤 구성 요소가 렌더링되는지에 대한 방향을 보여줍니다.

이 차트는 여러 유형의 항목을 포함합니다:

기호 항목 해석
xxx~~, ee-xxx~~ 축약된 디렉토리 경로 이름. [ee]/app/assets/javascripts에서 찾을 수 있으며 0..n 중첩 폴더를 생략합니다.
직사각형 노드 파일.
타원 노드 더 깊은 개념을 설명하는 일반 언어.
이중 직사각형 노드 단순화된 코드 분기.
다이아몬드 및 원형 노드 2(다이아몬드) 또는 3+(원형) 옵션을 가진 분기.
펜던트 / 배너 노드 (왼쪽 노치, 오른쪽 정사각형) 중첩 경로를 단축하기 위한 상위 디렉토리.
./ 가장 가까운 상위 디렉토리 펜던트 노드에 대한 상대 경로. 상위 펜던트 노드 아래 중첩된 비상대 경로는 해당 디렉토리에 없습니다.
%%{init: { "fontFamily": "GitLab Sans" }}%% flowchart TB accTitle: 구성 요소 렌더링 accDescr: GitLab 프런트 엔드에서 구성 요소가 렌더링되는 방식의 플로우차트 classDef code font-family: monospace; A["diffs~~app.vue"] descVirtualScroller(["가상 스크롤러"]) codeForFiles[["v-for(diffFiles)"]] B["diffs~~diff_file.vue"] C["diffs~~diff_file_header.vue"] D["diffs~~diff_stats.vue"] E["diffs~~diff_content.vue"] boolFileIsText{isTextFile} boolOnlyWhitespace{isWhitespaceOnly} boolNotDiffable{notDiffable} boolNoPreview{noPreview} descShowChanges(["“변경 사항 보기” 버튼"]) %% 비텍스트 변경 dirDiffViewer>"vue_shared~~diff_viewer"] F["./viewers/not_diffable.vue"] G["./viewers/no_preview.vue"] H["./diff_viewer.vue"] I["diffs~~diff_view.vue"] boolIsRenamed{isRenamed} boolIsModeChanged{isModeChanged} boolFileHasNoPath{hasNewPath} boolIsImage{isImage} J["./viewers/renamed.vue"] K["./viewers/mode_changed.vue"] descNoViewer(["렌더링된 뷰어가 없습니다."]) L["./viewers/image_diff_viewer.vue"] M["./viewers/download.vue"] N["vue_shared~~download_diff_viewer.vue"] boolImageIsReplaced{isReplaced} O["vue_shared~~image_viewer.vue"] switchImageMode((image_diff_viewer.mode)) P["./viewers/image_diff/onion_skin_viewer.vue"] Q["./viewers/image_diff/swipe_viewer.vue"] R["./viewers/image_diff/two_up_viewer.vue"] S["diffs~~image_diff_overlay.vue"] codeForImageDiscussions[["v-for(discussions)"]] T["vue_shared~~design_note_pin.vue"] U["vue_shared~~user_avatar_link.vue"] V["diffs~~diff_discussions.vue"] W["batch_comments~~diff_file_drafts.vue"] codeForTwoUpDiscussions[["v-for(discussions)"]] codeForTwoUpDrafts[["v-for(drafts)"]] X["notes~~notable_discussion.vue"] %% 텍스트 파일 변경 codeForDiffLines[["v-for(diffLines)"]] Y["diffs~~diff_expansion_cell.vue"] Z["diffs~~diff_row.vue"] AA["diffs~~diff_line.vue"] AB["batch_comments~~draft_note.vue"] AC["diffs~~diff_comment_cell.vue"] AD["diffs~~diff_gutter_avatars.vue"] AE["ee-diffs~~inline_findings_gutter_icon_dropdown.vue"] AF["notes~~noteable_note.vue"] AG["notes~~note_actions.vue"] AH["notes~~note_body.vue"] AI["notes~~note_header.vue"] AJ["notes~~reply_button.vue"] AK["notes~~note_awards_list.vue"] AL["notes~~note_edited_text.vue"] AM["notes~~note_form.vue"] AN["vue_shared~~awards_list.vue"] AO["emoji~~picker.vue"] AP["emoji~~emoji_list.vue"] descEmojiVirtualScroll(["가상 스크롤러"]) AQ["emoji~~category.vue"] AR["emoji~emoji_category.vue"] AS["vue_shared~~markdown_editor.vue"] class codeForFiles,codeForImageDiscussions code; class codeForTwoUpDiscussions,codeForTwoUpDrafts code; class codeForDiffLines code; %% 이 스위치 노드에도 코드 스타일 적용 class switchImageMode code; %% 이 불리언 노드에도 코드 스타일 적용 class boolFileIsText,boolOnlyWhitespace,boolNotDiffable,boolNoPreview code; class boolIsRenamed,boolIsModeChanged,boolFileHasNoPath,boolIsImage code; class boolImageIsReplaced code; A --> descVirtualScroller A -->|"가상 스크롤러는 페이지 내 검색 (Cmd/Ctrl+f)를 사용할 때 비활성화됩니다."|codeForFiles descVirtualScroller --> codeForFiles codeForFiles --> B --> C --> D B --> E %% 파일 보기 플래그 전파 E --> boolFileIsText boolFileIsText --> |yes| I boolFileIsText --> |no| boolOnlyWhitespace boolOnlyWhitespace --> |yes| descShowChanges boolOnlyWhitespace --> |no| dirDiffViewer dirDiffViewer --> H H --> boolNotDiffable boolNotDiffable --> |yes| F boolNotDiffable --> |no| boolNoPreview boolNoPreview --> |yes| G boolNoPreview --> |no| boolIsRenamed boolIsRenamed --> |yes| J boolIsRenamed --> |no| boolIsModeChanged boolIsModeChanged --> |yes| K boolIsModeChanged --> |no| boolFileHasNoPath boolFileHasNoPath --> |yes| boolIsImage boolFileHasNoPath --> |no| descNoViewer boolIsImage --> |yes| L boolIsImage --> |no| M M --> N %% 이미지 diff 뷰어 L --> boolImageIsReplaced boolImageIsReplaced --> |yes| switchImageMode boolImageIsReplaced --> |no| O switchImageMode -->|"'twoup' (기본값)"| R switchImageMode -->|'onion'| P switchImageMode -->|'swipe'| Q P & Q --> S S --> codeForImageDiscussions S --> AM R-->|"노트 컨테이너 div에서 렌더링"|U & W & V %% 위의 "P & Q --> S" 문과 결합하지 마세요 %% 이 노드 관계의 순서는 %% 그래프의 레이아웃을 정의하며, 우리는 이 순서가 필요합니다. R --> S V --> codeForTwoUpDiscussions W --> codeForTwoUpDrafts %% 이 보이지 않는 링크는 `noteable_discussion` %% 이 `design_note_pin` 위에 렌더링되도록 강제합니다. X ~~~ T codeForTwoUpDrafts --> AB codeForImageDiscussions & codeForTwoUpDiscussions & codeForTwoUpDrafts --> T codeForTwoUpDiscussions --> X %% 텍스트 파일 diff 뷰어 I --> codeForDiffLines codeForDiffLines --> Z codeForDiffLines -->|"isMatchLine?"| Y codeForDiffLines -->|"hasCodeQuality?"| AA codeForDiffLines -->|"hasDraftNote(s)?"| AB Z -->|"hasCodeQuality?"| AE Z -->|"hasDiscussions?"| AD AA --> AC %% 드래프트 노트 AB --> AF AF --> AG & AH & AI AG --> AJ AH --> AK & AL & AM AK --> AN --> AO --> AP --> descEmojiVirtualScroll --> AQ --> AR AM --> AS

일부 구성 요소는 다른 구성 요소보다 자주 렌더링되지만, 기본 구성 요소는 diff_row.vue입니다. 이 구성 요소는 diff 파일의 모든 diff 라인을 렌더링합니다. 성능상의 이유로 이 구성 요소는 기능적 구성 요소로 되어 있습니다. 그러나 Vue 3로 업그레이드하면 더 이상 필요하지 않습니다.

주요 diff 앱 구성 요소는 diff 앱의 주요 진입점입니다. 이 구성 요소의 가장 중요한 부분 중 하나는 diff 라인에 토론을 할당하는 작업을 디스패치하는 것입니다. 이 작업은 메타데이터 요청이 완료되고 배치 diffs 요청이 완료된 후에 디스패치됩니다. 또한 diff 파일 배열과 노트 배열의 변경을 감시하는 감시자가 설정되어 있습니다. 이곳에서 변경이 발생할 때마다 설정된 토론 작업이 디스패치됩니다.

DiffRow 구성 요소는 diff 라인 데이터를 하나의 형식으로 저장할 수 있도록 설정되어 있습니다. 이전에는 인라인 및 나란히 두 가지 형식에 대해 요청해야 했습니다. DiffRow 구성 요소는 이 표준 형식을 사용하여 diff 라인 데이터를 렌더링합니다. 이 표준 형식을 사용하면 사용자가 데이터를 다시 가져오지 않고도 인라인과 나란히 전환할 수 있습니다.

참고: 이 구성 요소에 사용되고 렌더링되는 많은 데이터는 다양한 조건에 따라 메모이즈되고 캐시됩니다. 데이터가 때때로 각기 다른 구성 요소 렌더링 간에 캐시될 수 있습니다.

Vuex 스토어

diffs 앱을 위한 Vuex 스토어는 3개의 서로 다른 모듈로 구성되어 있습니다:

  • Notes
  • Diffs
  • Batch comments

Notes 모듈은 diff 논의를 포함한 토론을 담당합니다. 이 모듈에서는 논의가 가져오고, 새로운 논의를 위한 polling이 설정됩니다. 이 모듈은 문제(issue) 앱과 공유되므로, 여기의 변경 사항은 문제 및 병합 요청에서 모두 테스트해야 합니다.

Diffs 모듈은 diffs와 관련된 모든 것을 담당합니다. 여기에는 diffs를 가져오는 것, diff 논의를 줄(line)에 할당하는 것, 그리고 diff 논의를 생성하는 것이 포함되지만 이에 국한되지 않습니다.

마지막으로, batch comments 모듈은 복잡하지 않으며 초안 댓글(draft comments) 기능만을 담당합니다. 하지만 이 모듈은 초안 댓글이 게시될 때마다 notes 및 diffs 모듈에 액션을 dispatch합니다.

API 요청

메타데이터

diffs 메타데이터 엔드포인트는 모든 diff 파일을 가져올 필요 없이 diffs 앱이 요구하는 기본 데이터를 신속하게 가져오기 위해 존재합니다. 여기에는, 하지만 이에 국한되지 않는 다음이 포함됩니다:

  • Diff 파일 이름, 일부 추가 메타데이터 포함
  • 추가된 줄 번호와 제거된 줄 번호
  • 브랜치 이름
  • Diff 버전

메타데이터 응답의 가장 중요한 부분은 diff 파일 이름입니다. 이 데이터는 diffs 앱이 모든 배치 diffs 요청이 완료될 때까지 기다리지 않고도 파일 브라우저를 렌더링할 수 있도록 합니다.

메타데이터 응답을 수신하면, diff 파일 데이터는 프론트엔드가 파일 브라우저를 트리 뷰(tree view) 또는 리스트 뷰(list view)로 렌더링하는 데 필요한 올바른 구조로 처리됩니다.

이 파일 객체의 구조는 다음과 같습니다:

{
  "key": "",
  "path": "",
  "name": "",
  "type": "",
  "tree": [],
  "changed": true,
  "diffLoaded": false,
  "filePaths": {
    "old": file.old_path,
    "new": file.new_path
  },
  "tempFile": false,
  "deleted": false,
  "fileHash": "",
  "addedLines": 1,
  "removedLines": 1,
  "parentPath": "/",
  "submodule": false
}

배치 diffs

diffs 엔드포인트의 응답 크기를 줄이기 위해, 우리는 이 응답을 여러 요청으로 나누고 있습니다, 즉:

  • 각 요청의 응답 크기를 줄입니다.
  • diffs 앱이 첫 번째 요청이 완료되는 즉시 diffs를 렌더링하기 시작할 수 있도록 허용합니다.

첫 번째 요청을 더 빨리 만들기 위해, 요청은 적은 양의 diffs를 요구합니다. 요청된 diffs 수는 증가하여 최대 요청당 30개의 diffs로 증가합니다.

요청이 완료되면, diffs 앱은 수신된 데이터를 diffs 앱이 diffs 줄을 렌더링하기 쉽게 포맷팅합니다.

%%{init: { "fontFamily": "GitLab Sans" }}%% graph TD accTitle: diffs 포맷팅 accDescr: diff를 렌더링할 때의 단계 흐름도, 검색 및 표시 준비를 포함합니다. A[fetchDiffFilesBatch] --> B[commit SET_DIFF_DATA_BATCH] --> C[prepareDiffData] --> D[prepareRawDiffFile] --> E[ensureBasicDiffFileLines] --> F[prepareDiffFileLines] --> G[finalizeDiffFile] --> H[deduplicateFilesList]

이 모든 과정이 완료된 후, diffs 앱은 이제 diff 줄을 렌더링하기 시작할 수 있습니다. 그러나 아무 것도 렌더링되기 전에 diffs 앱은 한 번 더 포맷팅을 합니다. diff 줄 데이터를 가져와서 인라인 및 나란히(side-by-side) 모드 간의 전환을 쉽게 하기 위해 데이터를 매핑합니다. 이 포맷팅은 diff_content.vue 컴포넌트 내부의 계산 프로퍼티(computed property)에서 발생합니다.

렌더 큐

참고:
이제는 필요하지 않을 수도 있습니다. 렌더 큐의 미래를 결정하기 위해 조사 작업이 필요합니다. 우리가 만든 가상 스크롤 바는 아마도 이 접근 방식으로 얻은 성능 이점을 제거했을 것입니다.

빠르게 차이를 렌더링하기 위해, 우리는 브라우저가 유휴 상태일 때만 차이를 렌더링할 수 있는 렌더 큐를 가지고 있습니다. 이는 한 번에 많은 큰 차이를 렌더링할 때 브라우저가 멈추는 것을 방지하고 총 차단 시간을 줄일 수 있게 합니다.

이 파일의 모든 차이 파일에 대해 아래의 조건이 모두 true일 때만 파일 렌더링 파이프라인이 발생합니다. 이 중 하나라도 false이면 렌더 큐가 발생하지 않으며 차이는 예상대로 렌더링됩니다.

  • 이 파일의 차이가 이미 렌더링되었습니까?
  • 이 차이에 뷰어가 있습니까? (즉, 다운로드가 아닙니다.)
  • 차이가 확장되어 있습니까?

다음 차트는 발생하는 파이프라인에 대한 개요를 제공합니다:

%%{init: { "fontFamily": "GitLab Sans" }}%% graph TD accTitle: 렌더 큐 파이프라인 accDescr: 렌더 큐 파이프라인의 단계 흐름도 A[startRenderDiffsQueue] -->B B[현재 파일 인덱스 RENDER_FILE 커밋] -->C C[다음 파일을 렌더링할 수 있습니까?] C -->|예| D[파일 렌더링] -->B C -->|아니오| E[requestIdleCallback 다시 실행] -->C

일어나는 확인 사항:

  • 남은 유휴 시간이 5ms 미만입니까?
  • 이 파일을 이미 4번 렌더링하려고 시도했습니까?

이 확인이 발생한 후, 파일은 Vuex에서 렌더링 가능으로 표시되어 차이 앱이 차이 라인과 논의를 렌더링을 시작할 수 있게 합니다.