Update project for 2025 release: modified LICENSE year, updated dependencies in package.json and package-lock.json, improved README with new clone URL, adjusted TypeScript configuration, and refined Vue components for better user experience.

This commit is contained in:
Snowz 2025-04-12 19:23:19 +08:00
parent 0d68dc6e35
commit a15eb98b18
14 changed files with 2140 additions and 2144 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 Image Optimizer
Copyright (c) 2025 Image Optimizer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -22,7 +22,7 @@
```bash
# 克隆项目
git clone https://github.com/your-username/image-optimizer.git
git clone https://ckk.photo8.site/Snowz/image-optimizer.git
# 进入项目目录
cd image-optimizer

3780
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +1,32 @@
{
"name": "image-optimizer",
"version": "1.0.0",
"description": "A modern online image optimization and processing tool that runs entirely in the browser",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"test": "vitest",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false"
},
"dependencies": {
"@vueuse/core": "^10.3.0",
"autoprefixer": "^10.4.14",
"vue": "^3.3.11",
"vue-router": "^4.2.5",
"browser-image-compression": "^2.0.2",
"jszip": "^3.10.1",
"pinia": "^2.1.6",
"postcss": "^8.4.27",
"tailwindcss": "^3.3.3",
"vue": "^3.3.4",
"vue-router": "^4.2.4"
"jszip": "^3.10.1"
},
"devDependencies": {
"@types/node": "^20.4.5",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-vue": "^4.2.3",
"@vue/tsconfig": "^0.4.0",
"eslint": "^8.46.0",
"eslint-plugin-vue": "^9.15.1",
"typescript": "^5.1.6",
"vite": "^4.4.7",
"vitest": "^0.33.0",
"vue-tsc": "^1.8.8"
"@tsconfig/node18": "^18.2.2",
"@types/node": "^18.19.3",
"@vitejs/plugin-vue": "^4.5.2",
"@vue/tsconfig": "^0.5.0",
"autoprefixer": "^10.4.16",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.32",
"tailwindcss": "^3.3.6",
"typescript": "~5.3.0",
"vite": "^4.5.1",
"vue-tsc": "^1.8.25"
}
}

View File

@ -1,4 +1,4 @@
module.exports = {
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},

9
public/.htaccess Normal file
View File

@ -0,0 +1,9 @@
# Handle History mode in Vue Router
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /image-optimizer/
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /image-optimizer/index.html [L]
</IfModule>

View File

@ -45,7 +45,7 @@
<footer class="bg-white dark:bg-dark-800 border-t border-gray-200 dark:border-dark-700">
<div class="container mx-auto px-4 py-6 text-center text-dark-500 dark:text-dark-400">
<p>© 2023 图片优化器. 保留所有权利</p>
<p>© 2025 图片优化器. 保留所有权利</p>
</div>
</footer>
</div>

View File

@ -1,12 +1,10 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './assets/main.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

View File

@ -1,32 +1,30 @@
<template>
<div class="text-center py-12">
<h1 class="text-6xl font-bold text-gray-900 mb-4">404</h1>
<p class="text-xl text-gray-600 mb-8">{{ $t('notFound.message') }}</p>
<router-link to="/" class="btn btn-primary">
{{ $t('notFound.goHome') }}
</router-link>
<div class="min-h-screen flex items-center justify-center bg-light-50 dark:bg-dark-900">
<div class="text-center space-y-8">
<div class="text-9xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-primary-600 to-primary-500 dark:from-primary-400 dark:to-primary-300">
404
</div>
<h1 class="text-2xl font-semibold text-dark-900 dark:text-dark-50">
抱歉您访问的页面不存在
</h1>
<p class="text-dark-600 dark:text-dark-300">
该页面可能已被移动删除或暂时无法访问
</p>
<div>
<router-link
to="/"
class="inline-flex items-center px-6 py-3 bg-primary-500 hover:bg-primary-600 dark:bg-primary-600 dark:hover:bg-primary-700 text-white rounded-lg shadow-sm hover:shadow transition-all duration-200"
>
<span>返回首页</span>
<svg class="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
</router-link>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>
<i18n>
{
"en": {
"notFound": {
"message": "Oops! The page you're looking for doesn't exist.",
"goHome": "Go Home"
}
},
"zh": {
"notFound": {
"message": "抱歉!您访问的页面不存在。",
"goHome": "返回首页"
}
}
}
</i18n>
//
</script>

View File

@ -1,97 +1,92 @@
<template>
<div class="space-y-8">
<div class="text-center space-y-4">
<h1 class="text-4xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-primary-600 to-primary-500 dark:from-primary-400 dark:to-primary-300">
优化图片
</h1>
<p class="text-xl text-dark-600 dark:text-dark-300">
上传并优化您的图片
</p>
</div>
<div class="bg-white dark:bg-dark-800 rounded-xl shadow-soft transition-all duration-300 hover:shadow-soft-lg">
<div class="container mx-auto px-4 py-8">
<div class="space-y-8">
<!-- 上传区域 -->
<div
class="border-2 border-dashed rounded-xl p-8 text-center mx-6 my-6 transition-all duration-200"
:class="[
isDragging
? 'border-primary-400 bg-primary-50 dark:border-primary-500 dark:bg-primary-500/5'
: 'border-dark-200 dark:border-dark-700 hover:border-primary-300 dark:hover:border-primary-600'
]"
@dragenter.prevent="isDragging = true"
@dragleave.prevent="isDragging = false"
@dragover.prevent
class="border-2 border-dashed border-primary-300 dark:border-primary-700 rounded-xl p-8 text-center"
:class="{
'bg-primary-50 dark:bg-primary-900/20': isDragging,
'bg-white dark:bg-dark-800': !isDragging
}"
@drop.prevent="handleDrop"
@dragover.prevent="isDragging = true"
@dragleave.prevent="isDragging = false"
>
<input
type="file"
ref="fileInput"
class="hidden"
type="file"
accept="image/*"
multiple
class="hidden"
@change="handleFileSelect"
/>
<div class="space-y-4">
<div class="w-16 h-16 mx-auto rounded-xl bg-primary-100 dark:bg-primary-500/10 flex items-center justify-center">
<div class="w-16 h-16 mx-auto bg-primary-100 dark:bg-primary-500/10 rounded-lg flex items-center justify-center">
<svg class="w-8 h-8 text-primary-500 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
<div class="text-dark-600 dark:text-dark-300">
<p class="mb-2 text-lg">拖放图片到这里</p>
<p class="text-dark-400 dark:text-dark-500"></p>
<div>
<button
class="mt-4 px-6 py-2.5 bg-primary-500 hover:bg-primary-600 dark:bg-primary-600 dark:hover:bg-primary-700 text-white rounded-lg shadow-sm hover:shadow transition-all duration-200"
@click="$refs.fileInput.click()"
class="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 font-medium"
@click="(fileInput as HTMLInputElement).click()"
>
选择文件
选择图片
</button>
<span class="text-dark-600 dark:text-dark-300">或将图片拖放到此处</span>
</div>
<p class="text-sm text-dark-500 dark:text-dark-400">
支持 JPGPNGWebP 等格式
</p>
</div>
</div>
<!-- 文件列表 -->
<div v-if="files.length > 0" class="space-y-4">
<div v-for="(file, index) in files" :key="index" class="bg-white dark:bg-dark-800 rounded-lg p-4 shadow-sm">
<div class="flex items-center justify-between">
<div class="flex-1 min-w-0 pr-4">
<p class="font-medium text-dark-900 dark:text-dark-50 truncate">{{ file.originalFile.name }}</p>
<p class="text-sm text-dark-500 dark:text-dark-400">{{ formatFileSize(file.originalFile.size) }}</p>
<div v-if="file.processedSize !== undefined" class="mt-1 text-sm">
<span class="text-primary-600 dark:text-primary-400">
处理完成
- 压缩率 {{ Math.round((1 - file.processedSize / file.originalFile.size) * 100) }}%
</span>
</div>
</div>
<div class="flex items-center space-x-2">
<button
v-if="file.processedSize !== undefined"
class="px-3 py-1 bg-primary-500 hover:bg-primary-600 dark:bg-primary-600 dark:hover:bg-primary-700 text-white rounded"
@click="downloadFile(file)"
>
下载
</button>
<button
class="p-1 text-dark-500 hover:text-dark-700 dark:text-dark-400 dark:hover:text-dark-200"
@click="removeFile(index)"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
</div>
<div v-if="files.length > 0" class="border-t border-dark-100 dark:border-dark-700 px-6 py-6 space-y-4">
<div v-for="(file, index) in files" :key="index"
class="flex items-center space-x-4 p-4 rounded-lg transition-all duration-200"
:class="[
file.processed
? 'bg-green-50 dark:bg-green-500/5'
: 'bg-dark-50 dark:bg-dark-700/50'
]"
>
<div class="w-16 h-16 rounded-lg overflow-hidden bg-dark-100 dark:bg-dark-600">
<img :src="file.preview" class="w-full h-full object-cover" />
</div>
<div class="flex-1 min-w-0">
<p class="font-medium text-dark-900 dark:text-dark-50 truncate">{{ file.name }}</p>
<p class="text-sm text-dark-500 dark:text-dark-400">{{ formatFileSize(file.size) }}</p>
<div v-if="file.processed" class="text-sm text-green-600 dark:text-green-400 flex items-center space-x-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
<span>已优化 ({{ formatFileSize(file.processedSize) }})</span>
<span class="text-dark-400 dark:text-dark-500">
- 压缩率 {{ Math.round((1 - file.processedSize! / file.size) * 100) }}%
</span>
</div>
</div>
<button
class="p-2 text-dark-400 hover:text-red-500 dark:text-dark-500 dark:hover:text-red-400 rounded-lg hover:bg-red-50 dark:hover:bg-red-500/10 transition-colors duration-200"
@click="removeFile(index)"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 pt-4">
<!-- 控制面板 -->
<div v-if="files.length > 0" class="bg-white dark:bg-dark-800 rounded-lg p-6 shadow-sm space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-dark-700 dark:text-dark-300 mb-2">
<label class="block text-sm font-medium text-dark-900 dark:text-dark-50 mb-2">
输出格式
</label>
<select
v-model="options.format"
class="w-full px-4 py-2.5 rounded-lg bg-dark-50 dark:bg-dark-700 border border-dark-200 dark:border-dark-600 text-dark-900 dark:text-dark-100 focus:ring-2 focus:ring-primary-500/20 dark:focus:ring-primary-500/10 focus:border-primary-500 dark:focus:border-primary-500 transition-all duration-200"
<select
v-model="outputFormat"
class="w-full bg-light-50 dark:bg-dark-700 border border-dark-200 dark:border-dark-600 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400"
>
<option value="jpeg">JPEG</option>
<option value="png">PNG</option>
@ -99,51 +94,38 @@
</select>
</div>
<div>
<label class="block text-sm font-medium text-dark-700 dark:text-dark-300 mb-2">
质量
<label class="block text-sm font-medium text-dark-900 dark:text-dark-50 mb-2">
压缩质量
</label>
<div class="space-y-2">
<div class="flex items-center space-x-4">
<input
v-model="quality"
type="range"
v-model="options.quality"
min="0"
max="100"
class="w-full accent-primary-500"
max="1"
step="0.1"
class="flex-1"
/>
<div class="flex justify-between text-sm text-dark-500 dark:text-dark-400">
<span>压缩</span>
<span>{{ options.quality }}%</span>
<span>原图</span>
</div>
<span class="text-dark-900 dark:text-dark-50 w-12 text-center">
{{ Math.round(quality * 100) }}%
</span>
</div>
</div>
</div>
<div class="flex justify-end space-x-4 pt-4">
<div class="flex justify-between items-center">
<button
v-if="hasProcessedFiles"
class="px-6 py-2.5 bg-dark-100 hover:bg-dark-200 dark:bg-dark-700 dark:hover:bg-dark-600 text-dark-700 dark:text-dark-200 rounded-lg shadow-sm hover:shadow transition-all duration-200"
@click="downloadAll"
class="px-6 py-2 bg-primary-500 hover:bg-primary-600 dark:bg-primary-600 dark:hover:bg-primary-700 text-white rounded-lg shadow-sm hover:shadow transition-all duration-200"
@click="processAllFiles"
>
<span class="flex items-center space-x-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
<span>下载全部</span>
</span>
开始处理
</button>
<button
class="px-6 py-2.5 bg-primary-500 hover:bg-primary-600 dark:bg-primary-600 dark:hover:bg-primary-700 text-white rounded-lg shadow-sm hover:shadow transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="isProcessing"
@click="processFiles"
v-if="hasProcessedFiles"
class="px-6 py-2 border border-primary-500 dark:border-primary-400 text-primary-600 dark:text-primary-400 hover:bg-primary-50 dark:hover:bg-primary-900/20 rounded-lg transition-colors duration-200"
@click="downloadAllFiles"
>
<span class="flex items-center space-x-2">
<svg v-if="isProcessing" class="animate-spin w-5 h-5" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>{{ isProcessing ? '处理中...' : '处理图片' }}</span>
</span>
下载全部
</button>
</div>
</div>
@ -152,127 +134,110 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { ref, computed } from 'vue'
import imageCompression from 'browser-image-compression'
import JSZip from 'jszip'
interface ProcessedFile {
file: File
preview: string
processed?: boolean
processedSize?: number
originalFile: File
processedBlob?: Blob
processedSize?: number
}
const fileInput = ref<HTMLInputElement | null>(null)
const isDragging = ref(false)
const isProcessing = ref(false)
const files = ref<ProcessedFile[]>([])
const options = reactive({
format: 'jpeg',
quality: 80
})
const outputFormat = ref<'jpeg' | 'png' | 'webp'>('jpeg')
const quality = ref(0.8)
const hasProcessedFiles = computed(() => {
return files.value.some(file => file.processed)
return files.value.some(file => file.processedSize !== undefined)
})
const handleDrop = (e: DragEvent) => {
function handleDrop(e: DragEvent) {
isDragging.value = false
if (e.dataTransfer?.files) {
handleFiles(e.dataTransfer.files)
}
if (!e.dataTransfer) return
const droppedFiles = Array.from(e.dataTransfer.files).filter(file => file.type.startsWith('image/'))
addFiles(droppedFiles)
}
const handleFileSelect = (e: Event) => {
const input = e.target as HTMLInputElement
if (input.files) {
handleFiles(input.files)
}
function handleFileSelect(e: Event) {
const target = e.target as HTMLInputElement
if (!target.files) return
const selectedFiles = Array.from(target.files).filter(file => file.type.startsWith('image/'))
addFiles(selectedFiles)
}
const handleFiles = (fileList: FileList) => {
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i]
if (file.type.startsWith('image/')) {
const reader = new FileReader()
reader.onload = (e) => {
files.value.push({
file,
preview: e.target?.result as string
})
}
reader.readAsDataURL(file)
}
}
function addFiles(newFiles: File[]) {
const processedFiles: ProcessedFile[] = newFiles.map(file => ({
originalFile: file
}))
files.value.push(...processedFiles)
}
const removeFile = (index: number) => {
function removeFile(index: number) {
files.value.splice(index, 1)
}
const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 Bytes'
function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB']
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`
}
const processFiles = async () => {
isProcessing.value = true
async function processFile(file: ProcessedFile) {
try {
for (let i = 0; i < files.value.length; i++) {
const file = files.value[i]
if (!file.processed) {
const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true,
fileType: `image/${file.file.type.split('/')[1]}`,
initialQuality: 0.8
}
try {
const compressedFile = await imageCompression(file.file, options)
const processedBlob = new Blob([compressedFile], { type: `image/${file.file.type.split('/')[1]}` })
files.value[i] = {
...file,
processed: true,
processedSize: compressedFile.size,
processedBlob
}
} catch (error) {
console.error('Error processing file:', error)
}
}
const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true,
fileType: `image/${outputFormat.value}`,
initialQuality: quality.value
}
} finally {
isProcessing.value = false
const compressedBlob = await imageCompression(file.originalFile, options)
file.processedBlob = compressedBlob
file.processedSize = compressedBlob.size
} catch (error) {
console.error('Error processing file:', error)
}
}
const downloadAll = async () => {
async function processAllFiles() {
for (const file of files.value) {
await processFile(file)
}
}
function downloadFile(file: ProcessedFile) {
if (!file.processedBlob) return
const link = document.createElement('a')
link.href = URL.createObjectURL(file.processedBlob)
link.download = `optimized_${file.originalFile.name.replace(/\.[^/.]+$/, '')}.${outputFormat.value}`
link.click()
URL.revokeObjectURL(link.href)
}
async function downloadAllFiles() {
const zip = new JSZip()
for (const file of files.value) {
if (file.processed && file.processedBlob) {
const extension = file.file.name.split('.').pop()
const newFileName = `${file.file.name.split('.')[0]}_optimized.${extension}`
zip.file(newFileName, file.processedBlob)
if (file.processedBlob) {
const fileName = `optimized_${file.originalFile.name.replace(/\.[^/.]+$/, '')}.${outputFormat.value}`
zip.file(fileName, file.processedBlob)
}
}
const content = await zip.generateAsync({ type: 'blob' })
const url = URL.createObjectURL(content)
const link = document.createElement('a')
link.href = url
link.href = URL.createObjectURL(content)
link.download = 'optimized_images.zip'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
URL.revokeObjectURL(link.href)
}
</script>

12
tsconfig.app.json Normal file
View File

@ -0,0 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@ -1,28 +1,11 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
"types": ["vite/client", "node"],
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"allowJs": true,
"checkJs": false,
"allowSyntheticDefaultImports": true
}
{
"path": "./tsconfig.app.json"
}
]
}

10
tsconfig.node.json Normal file
View File

@ -0,0 +1,10 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

View File

@ -1,13 +1,15 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
base: '/image-optimizer/',
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
server: {
@ -21,10 +23,9 @@ export default defineConfig({
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia', 'vue-i18n'],
'ui-vendor': ['tailwindcss'],
},
},
'vue-vendor': ['vue', 'vue-router']
}
}
},
},
})