8 Commits

Author SHA1 Message Date
typist
91c0686a46 0.0.23
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m11s
2025-10-29 09:24:17 +08:00
typist
40bfde8e57 feat: implement ID generator component for UUIDs and Nano ID
- Added IDGenerator component to handle the display and regeneration of UUIDs and Nano ID.
- Integrated clipboard functionality to copy generated IDs with user feedback via toast notifications.
- Refactored the Tool component to utilize the new IDGenerator for improved user interaction and code organization.
2025-10-29 09:24:01 +08:00
typist
b4ba7a2219 0.0.22
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m52s
2025-10-29 09:11:11 +08:00
typist
25e42e3af5 fix: update caching strategy for ipinfo API in Vite config
- Changed caching handler from 'NetworkFirst' to 'CacheFirst' for improved performance.
- Extended cache expiration time from 5 minutes to 1 hour to enhance data availability.
2025-10-29 09:10:59 +08:00
typist
4398b53ea7 0.0.21
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m14s
2025-10-29 09:07:27 +08:00
typist
3e14bc652f feat: add terser for advanced minification and improve React chunking
- Added terser as a dependency for enhanced code minification.
- Updated Vite configuration to enable aggressive minification using terser with options to drop console logs and unused code.
- Refined manual chunking for React libraries to improve code splitting and loading efficiency.
2025-10-29 09:06:56 +08:00
typist
aca7e11835 0.0.20
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m10s
2025-10-29 08:58:11 +08:00
typist
782de6e38a feat: integrate PWA support and service worker registration
- Added vite-plugin-pwa for Progressive Web App capabilities.
- Configured service worker with caching strategies for improved offline support.
- Registered service worker in main.tsx to handle updates and caching.
- Updated package.json to include new dependencies for PWA functionality.
2025-10-29 08:58:02 +08:00
6 changed files with 2986 additions and 29 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "litek", "name": "litek",
"private": true, "private": true,
"version": "0.0.19", "version": "0.0.23",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -44,11 +44,14 @@
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.22", "eslint-plugin-react-refresh": "^0.4.22",
"globals": "^16.4.0", "globals": "^16.4.0",
"terser": "^5.44.0",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"typescript-eslint": "^8.45.0", "typescript-eslint": "^8.45.0",
"vite": "npm:rolldown-vite@7.1.14" "vite": "npm:rolldown-vite@7.1.14",
"vite-plugin-pwa": "^1.1.0",
"workbox-window": "^7.3.0"
}, },
"pnpm": { "pnpm": {
"overrides": { "overrides": {

2811
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,94 @@
import { type FC } from "react"; import { type FC, useState } from "react";
import { RefreshCw, Copy } from "lucide-react";
import * as uuid from 'uuid' import * as uuid from 'uuid'
import { nanoid } from 'nanoid' import { nanoid } from 'nanoid'
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
interface IDGeneratorProps {
label: string;
value: string;
onRegenerate: () => void;
}
const IDGenerator: FC<IDGeneratorProps> = ({ label, value, onRegenerate }) => {
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(value);
toast(`${label} has been copied to clipboard`);
} catch (err) {
toast.error("Copy failed");
}
};
return (
<div className="flex flex-col gap-2">
<label className="font-medium">{label}</label>
<div className="flex items-center gap-2">
<span className="flex-1 px-3 py-2 bg-muted rounded-md font-mono text-sm break-all max-w-[400px]">
{value}
</span>
<Button
size="icon"
variant="outline"
onClick={onRegenerate}
title="Regenerate"
>
<RefreshCw className="h-4 w-4" />
</Button>
<Button
size="icon"
variant="outline"
onClick={copyToClipboard}
title="Copy"
>
<Copy className="h-4 w-4" />
</Button>
</div>
</div>
);
};
const Tool: FC = () => { const Tool: FC = () => {
const [uuidV1, setUuidV1] = useState(() => uuid.v1());
const [uuidV4, setUuidV4] = useState(() => uuid.v4());
const [uuidV6, setUuidV6] = useState(() => uuid.v6());
const [uuidV7, setUuidV7] = useState(() => uuid.v7());
const [nanoId, setNanoId] = useState(() => nanoid());
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<span className="text-sm text-muted-foreground">Refresh the page to generate new UUID</span> <span className="text-sm text-muted-foreground">Click the refresh button to regenerate the corresponding ID</span>
<label>UUID Version 1</label>
<span>{uuid.v1()}</span> <IDGenerator
<label>UUID Version 4</label> label="UUID Version 1"
<span>{uuid.v4()}</span> value={uuidV1}
<label>UUID Version 6</label> onRegenerate={() => setUuidV1(uuid.v1())}
<span>{uuid.v6()}</span> />
<label>UUID Version 7</label>
<span>{uuid.v7()}</span> <IDGenerator
<label>Nano ID</label> label="UUID Version 4"
<span>{nanoid()}</span> value={uuidV4}
onRegenerate={() => setUuidV4(uuid.v4())}
/>
<IDGenerator
label="UUID Version 6"
value={uuidV6}
onRegenerate={() => setUuidV6(uuid.v6())}
/>
<IDGenerator
label="UUID Version 7"
value={uuidV7}
onRegenerate={() => setUuidV7(uuid.v7())}
/>
<IDGenerator
label="Nano ID"
value={nanoId}
onRegenerate={() => setNanoId(nanoid())}
/>
</div> </div>
); );
}; };

View File

@@ -13,3 +13,21 @@ createRoot(document.getElementById('root')!).render(
<Toaster /> <Toaster />
</StrictMode> </StrictMode>
) )
// 注册 Service Worker
if ('serviceWorker' in navigator && import.meta.env.PROD) {
import('workbox-window').then(({ Workbox }) => {
const wb = new Workbox('/sw.js')
wb.addEventListener('installed', (event) => {
if (event.isUpdate) {
console.log('New service worker installed, reloading page...')
window.location.reload()
}
})
wb.register()
}).catch((error) => {
console.error('Failed to register service worker:', error)
})
}

3
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-pwa/client" />

View File

@@ -2,10 +2,55 @@ import path from "path"
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import tailwindcss from "@tailwindcss/vite" import tailwindcss from "@tailwindcss/vite"
import { VitePWA } from 'vite-plugin-pwa'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react(), tailwindcss()], plugins: [
react(),
tailwindcss(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['lite.svg', 'robots.txt', 'sitemap.xml'],
manifest: {
name: 'Lite Kit - Lightweight Online Tools',
short_name: 'Lite Kit',
description: 'Free online tools including UUID generator, JSON formatter, Base64 encoder/decoder, network testing tools and more',
theme_color: '#000000',
background_color: '#000000',
display: 'standalone',
icons: [
{
src: '/lite.svg',
type: 'image/svg+xml',
sizes: 'any'
}
]
},
workbox: {
globPatterns: ['**/*.{js,css,html,svg,png,ico,woff2}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/ipinfo\.io\/.*/i,
handler: 'CacheFirst', // 改为 CacheFirst优先使用缓存
options: {
cacheName: 'ipinfo-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 // 延长到 1 小时
},
cacheableResponse: {
statuses: [0, 200]
}
}
}
],
cleanupOutdatedCaches: true,
clientsClaim: true,
skipWaiting: true
}
})
],
resolve: { resolve: {
alias: { alias: {
"@": path.resolve(__dirname, "./src"), "@": path.resolve(__dirname, "./src"),
@@ -15,11 +60,15 @@ export default defineConfig({
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: (id) => { manualChunks: (id) => {
// React核心 // React 核心拆分得更细
if (id.includes('node_modules/react') || if (id.includes('node_modules/react/') && !id.includes('node_modules/react-dom')) {
id.includes('node_modules/react-dom') || return 'react-core';
id.includes('node_modules/react-router-dom')) { }
return 'react-vendor'; if (id.includes('node_modules/react-dom/')) {
return 'react-dom';
}
if (id.includes('node_modules/react-router-dom')) {
return 'react-router';
} }
// Radix UI组件 // Radix UI组件
if (id.includes('node_modules/@radix-ui')) { if (id.includes('node_modules/@radix-ui')) {
@@ -29,9 +78,26 @@ export default defineConfig({
if (id.includes('node_modules/lucide-react')) { if (id.includes('node_modules/lucide-react')) {
return 'icons'; return 'icons';
} }
// 其他工具库
if (id.includes('node_modules/')) {
return 'vendor';
}
}, },
}, },
}, },
// 启用更激进的压缩
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log'],
// 移除未使用的代码
unused: true,
// 移除死代码
dead_code: true,
},
},
chunkSizeWarningLimit: 500, chunkSizeWarningLimit: 500,
}, },
}) })