Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## [v1.2.5] - 2025-09-14

### Added

- Implement comprehensive bundle optimization and code splitting (79fa266)

### Fixed

- Resolve typescript errors in vite config for pnpm es modules (cc42a21)
- Resolve typescript errors in vue components (beebe25)

### Maintenance

- Prepare v1.2.5 hotfix (825a6f7)


## [v1.2.3] - 2025-09-14

### Fixed
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pico-api-docs",
"version": "1.2.4",
"version": "1.2.5",
"description": "PICO SulTeng API Documentation - COVID-19 Sulawesi Tengah Data API",
"main": "index.js",
"scripts": {
Expand All @@ -17,10 +17,12 @@
"@tailwindcss/postcss": "^4.1.13",
"@tailwindcss/typography": "^0.5.16",
"@types/aos": "^3.0.7",
"@types/node": "^24.4.0",
"@vitejs/plugin-vue": "^6.0.1",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
"tailwindcss": "3.4.15",
"terser": "^5.44.0",
"typescript": "^5.9.2",
"vite": "^7.1.4",
"vue-tsc": "^3.0.6"
Expand Down
12 changes: 9 additions & 3 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
<template>
<div id="app" class="transition-colors duration-200">
<router-view v-slot="{ Component, route }">
<transition
:name="route.meta.transition || 'slide-left'"
<transition
:name="(route.meta?.transition as string) || 'slide-left'"
mode="out-in"
>
<component :is="Component" :key="route.path" />
<Suspense>
<component :is="Component" :key="route.path" />
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</transition>
</router-view>
</div>
Expand All @@ -14,6 +19,7 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { useTheme } from '@/composables/useTheme'
import LoadingSpinner from '@/components/LoadingSpinner.vue'

// Initialize theme on app start
const { initTheme } = useTheme()
Expand Down
6 changes: 4 additions & 2 deletions src/components/BaseCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { computed, useSlots } from 'vue'

interface Props {
variant?: 'default' | 'elevated' | 'outlined' | 'ghost'
Expand All @@ -58,6 +58,8 @@ const props = withDefaults(defineProps<Props>(), {
padding: 'md'
})

const slots = useSlots()

// Base classes
const baseClasses = 'relative transition-all duration-300'

Expand Down Expand Up @@ -143,7 +145,7 @@ const headerClasses = computed(() => [
const contentClasses = computed(() => [
{
[paddingClasses[props.padding]]: props.padding !== 'none',
'pt-0': (props.$slots.header || props.title) && props.padding !== 'none'
'pt-0': (slots.header || props.title) && props.padding !== 'none'
}
])

Expand Down
6 changes: 3 additions & 3 deletions src/components/CodeBlock.vue
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ const formatLanguageName = (lang: string): string => {
}

// Get badge variant based on language
const getBadgeVariant = (lang: string) => {
const variantMap: Record<string, string> = {
const getBadgeVariant = (lang: string): 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' => {
const variantMap: Record<string, 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info'> = {
'javascript': 'warning',
'typescript': 'primary',
'python': 'success',
Expand All @@ -218,7 +218,7 @@ const getBadgeVariant = (lang: string) => {
'html': 'danger',
'sql': 'success'
}

return variantMap[lang] || 'default'
}

Expand Down
12 changes: 12 additions & 0 deletions src/components/LoadingSpinner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<template>
<div class="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-gray-900">
<div class="text-center">
<div class="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-pico-sky"></div>
<p class="mt-4 text-gray-600 dark:text-gray-400">{{ $t('loading') || 'Loading...' }}</p>
</div>
</div>
</template>

<script setup lang="ts">
// Simple loading component for route transitions
</script>
27 changes: 21 additions & 6 deletions src/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
import { createI18n } from 'vue-i18n'
import en from './locales/en.json'
import id from './locales/id.json'

const messages = {
en,
id
// Lazy load locale messages
const loadLocaleMessages = async () => {
const modules = import.meta.glob('./locales/*.json')
const messages: Record<string, any> = {}

for (const path in modules) {
const mod = await modules[path]() as { default: any }
const locale = path.replace('./locales/', '').replace('.json', '')
messages[locale] = mod.default
}

return messages
}

// Get saved language or default to English
const savedLanguage = localStorage.getItem('pico-language') || 'en'

// Create i18n instance with lazy loading
const i18n = createI18n({
legacy: false,
locale: savedLanguage,
fallbackLocale: 'en',
messages
messages: {}
})

// Load messages asynchronously
loadLocaleMessages().then(messages => {
Object.keys(messages).forEach(locale => {
i18n.global.setLocaleMessage(locale, messages[locale])
})
})

export default i18n
9 changes: 3 additions & 6 deletions src/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import Documentation from '@/views/Documentation.vue'
import ApiReference from '@/views/ApiReference.vue'

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: Home
component: () => import('@/views/Home.vue')
},
{
path: '/docs',
name: 'documentation',
component: Documentation
component: () => import('@/views/Documentation.vue')
},
{
path: '/api',
name: 'api-reference',
component: ApiReference
component: () => import('@/views/ApiReference.vue')
}
]
})
Expand Down
2 changes: 1 addition & 1 deletion src/views/Documentation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ import AuthenticationSection from '@/components/documentation/AuthenticationSect
import ErrorHandlingSection from '@/components/documentation/ErrorHandlingSection.vue'
import GlossarySection from '@/components/documentation/GlossarySection.vue'

const { t, locale } = useI18n()
const { locale } = useI18n()

// Version from package.json
const version = packageJson.version
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.node.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
"strict": true,
"types": ["node"]
},
"include": ["vite.config.ts"]
}
72 changes: 69 additions & 3 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,87 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { fileURLToPath, URL } from 'node:url'

export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'terser',
target: 'esnext',
reportCompressedSize: false,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
main: fileURLToPath(new URL('./index.html', import.meta.url)),
},
output: {
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
manualChunks: (id) => {
// Node modules vendor chunks
if (id.includes('node_modules')) {
// Vue core
if (id.includes('vue/') && !id.includes('vue-router') && !id.includes('vue-i18n')) {
return 'vue-vendor'
}
// Vue ecosystem
if (id.includes('vue-router') || id.includes('vue-i18n')) {
return 'vue-ecosystem'
}
// Code syntax highlighting and math
if (id.includes('prismjs') || id.includes('vue-prism-component') || id.includes('katex')) {
return 'syntax-vendor'
}
// Animation and UI libraries
if (id.includes('aos')) {
return 'ui-vendor'
}
// Other vendor libraries
return 'vendor'
}

// Components chunks
if (id.includes('src/components/documentation/')) {
return 'docs-components'
}
if (id.includes('src/components/')) {
return 'components'
}

// Locale chunks
if (id.includes('src/locales/')) {
return 'locales'
}
},
},
},
// Increase chunk size warning limit to 1000kB for vendor chunks
chunkSizeWarningLimit: 1000,
// Split chunks more aggressively
cssCodeSplit: true,
},
// Performance optimizations
optimizeDeps: {
include: [
'vue',
'vue-router',
'vue-i18n',
'prismjs',
'katex',
'aos'
],
},
})