Documentation Index Fetch the complete documentation index at: https://mintlify.com/go-gitea/gitea/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Gitea’s frontend is built with Vue.js 3, TypeScript, and Webpack, combining server-rendered templates with reactive components.
Technology Stack
Framework Vue.js 3.5+
Composition API
Single-file components
Reactive state management
Language TypeScript
Type-safe JavaScript
Better IDE support
Fewer runtime errors
Build Tool Webpack 5
Module bundling
Asset optimization
Code splitting
Package Manager pnpm 10+
Fast, efficient
Monorepo support
Disk space savings
Directory Structure
web_src/
├── js/
│ ├── components/ # Vue components
│ │ ├── RepoActionView.vue
│ │ ├── DiffFileTree.vue
│ │ └── ...
│ ├── features/ # Feature-specific code
│ │ ├── repo-issue.ts
│ │ ├── repo-diff.ts
│ │ └── ...
│ ├── modules/ # Utility modules
│ │ ├── fetch.ts
│ │ ├── toast.ts
│ │ └── ...
│ └── index.ts # Entry point
├── css/
│ ├── base.css
│ ├── repo.css
│ └── ...
└── svg/ # SVG icons
Build System
Development Build
# Install dependencies
pnpm install
# Build frontend (one-time)
make frontend
# Watch mode (auto-rebuild on changes)
make watch-frontend
Production Build
# Optimized production build
NODE_ENV = production make frontend
Build Configuration
Webpack configuration in webpack.config.ts:
export default {
entry: {
index: resolve ( 'web_src/js/index.ts' ),
} ,
output: {
path: resolve ( 'public/assets' ),
filename: 'js/[name].[contenthash:8].js' ,
chunkFilename: 'js/[name].[contenthash:8].js' ,
} ,
module: {
rules: [
{
test: / \. vue $ / ,
loader: 'vue-loader' ,
},
{
test: / \. ts $ / ,
use: 'esbuild-loader' ,
},
],
} ,
} ;
Vue Components
Component Structure
< script setup lang = "ts" >
import { ref , computed } from 'vue' ;
interface Props {
repoId : number ;
issueId : number ;
}
const props = defineProps < Props >();
const emit = defineEmits <{
update : [ id : number ];
}>();
const isLoading = ref ( false );
const data = ref ([]);
const filteredData = computed (() => {
return data . value . filter ( item => item . visible );
});
const loadData = async () => {
isLoading . value = true ;
try {
const response = await fetch ( `/api/v1/repos/ ${ props . repoId } /issues/ ${ props . issueId } ` );
data . value = await response . json ();
} finally {
isLoading . value = false ;
}
};
</ script >
< template >
< div class = "issue-view" >
< div v-if = " isLoading " class = "loading" > Loading... </ div >
< div v-else >
< div v-for = " item in filteredData " : key = " item . id " >
{{ item . title }}
</ div >
</ div >
</ div >
</ template >
< style scoped >
.issue-view {
padding : 1 rem ;
}
.loading {
text-align : center ;
color : var ( --color-text-light );
}
</ style >
Registering Components
// web_src/js/index.ts
import { createApp } from 'vue' ;
import RepoActionView from './components/RepoActionView.vue' ;
// Mount Vue components
for ( const el of document . querySelectorAll ( '.vue-repo-action-view' )) {
const data = el . getAttribute ( 'data-props' );
const props = data ? JSON . parse ( data ) : {};
createApp ( RepoActionView , props ). mount ( el );
}
Template Integration
<!-- templates/repo/actions/view.tmpl -->
< div class = "vue-repo-action-view"
data-props = '{{JsonUtils.EncodeToString .Props}}' >
</ div >
Styling
CSS Architecture
Gitea uses a combination of:
Base styles : Core styling and resets
Component styles : Scoped to Vue components
Utility classes : Tailwind-inspired utilities
CSS variables : For theming
CSS Variables
:root {
--color-primary : #74ac54 ;
--color-text : #0b132a ;
--color-background : #ffffff ;
--spacing-unit : 8 px ;
--border-radius : 4 px ;
}
[ data-theme = "dark" ] {
--color-text : #e6e9ef ;
--color-background : #1a1a1a ;
}
Responsive Design
/* Mobile first */
.container {
padding : 1 rem ;
}
/* Tablet and up */
@media ( min-width : 768 px ) {
.container {
padding : 2 rem ;
}
}
/* Desktop */
@media ( min-width : 1024 px ) {
.container {
max-width : 1200 px ;
margin : 0 auto ;
}
}
TypeScript
Type Definitions
// web_src/js/types.ts
export interface Repository {
id : number ;
owner : string ;
name : string ;
full_name : string ;
private : boolean ;
html_url : string ;
}
export interface Issue {
id : number ;
number : number ;
title : string ;
state : 'open' | 'closed' ;
user : User ;
created_at : string ;
updated_at : string ;
}
export interface User {
id : number ;
login : string ;
avatar_url : string ;
}
API Client
// web_src/js/modules/fetch.ts
import type { Repository , Issue } from '../types' ;
export async function fetchRepo ( owner : string , repo : string ) : Promise < Repository > {
const response = await fetch ( `/api/v1/repos/ ${ owner } / ${ repo } ` );
if ( ! response . ok ) {
throw new Error ( `Failed to fetch repository: ${ response . statusText } ` );
}
return response . json ();
}
export async function fetchIssues ( owner : string , repo : string ) : Promise < Issue []> {
const response = await fetch ( `/api/v1/repos/ ${ owner } / ${ repo } /issues` );
if ( ! response . ok ) {
throw new Error ( `Failed to fetch issues: ${ response . statusText } ` );
}
return response . json ();
}
State Management
Reactive Store
// web_src/js/stores/repo.ts
import { reactive , readonly } from 'vue' ;
import type { Repository } from '../types' ;
interface RepoState {
current : Repository | null ;
isLoading : boolean ;
}
const state = reactive < RepoState >({
current: null ,
isLoading: false ,
});
export function useRepoStore () {
const setRepo = ( repo : Repository ) => {
state . current = repo ;
};
const loadRepo = async ( owner : string , name : string ) => {
state . isLoading = true ;
try {
const repo = await fetchRepo ( owner , name );
state . current = repo ;
} finally {
state . isLoading = false ;
}
};
return {
state: readonly ( state ),
setRepo ,
loadRepo ,
};
}
Testing
Unit Tests
// web_src/js/components/RepoCard.test.ts
import { describe , it , expect } from 'vitest' ;
import { mount } from '@vue/test-utils' ;
import RepoCard from './RepoCard.vue' ;
describe ( 'RepoCard' , () => {
it ( 'renders repository name' , () => {
const wrapper = mount ( RepoCard , {
props: {
repo: {
id: 1 ,
name: 'gitea' ,
owner: 'go-gitea' ,
},
},
});
expect ( wrapper . text ()). toContain ( 'gitea' );
});
});
E2E Tests
Playwright tests in tests/e2e/:
import { test , expect } from '@playwright/test' ;
test ( 'create repository' , async ({ page }) => {
await page . goto ( '/repo/create' );
await page . fill ( 'input[name="repo_name"]' , 'test-repo' );
await page . click ( 'button[type="submit"]' );
await expect ( page ). toHaveURL ( / \/ . * \/ test-repo/ );
});
Code Splitting
// Lazy load large components
const MarkdownEditor = () => import ( './components/MarkdownEditor.vue' );
Asset Optimization
Image optimization : WebP format with fallbacks
Icon sprites : SVG sprite sheets
CSS minification : Production builds
Tree shaking : Remove unused code
Best Practices
Use TypeScript Always type your components and functions
Scoped Styles Use <style scoped> to prevent style leakage
Composition API Prefer Composition API over Options API
Accessibility Use semantic HTML and ARIA attributes
See Also
Architecture Overall architecture overview
Building Build Gitea from source
Contributing Contribution guidelines