diff --git a/README.md b/README.md index 383cf0b..9902efa 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # vite-plugin-cos-cdnizer -> 在日常开发中,经常需要将静态资源上传到云服务的对象存储中。传统方式需要开发者手动登录云服务器控制台进行上传,步骤繁琐。使用本插件后,开发者可以将静态资源放置在项目本地,插件将自动将这些资源上传到对象存储中。同时,插件会自动替换代码中的 `import xxx from 'xxx.png'` 为 CDN 地址,简化了开发流程,提高了效率。 +> 在日常开发中,经常需要将静态资源上传到云服务的对象存储中。传统方式需要开发者手动登录云服务器控制台进行上传,步骤繁琐。 + +使用本插件后,只需要将静态资源放置在项目本地,插件将自动将命中的资源上传到对象存储中。同时,插件会自动将代码中的 import xxx from 'xxx.png' 替换为 CDN 地址,简化开发流程。 ## 安装 @@ -27,11 +29,21 @@ export default defineConfig({ }); ``` -
+## 效果展示 + +插件会自动将本地引入的静态资源上传至 CDN,并替换原有代码中的引用地址为 CDN 地址。 + +### 使用前 + +![image-20240228下午63239358](https://static.rux.ink/uPic/image-20240228%E4%B8%8B%E5%8D%8863239358.png) + +### 使用后 + +![image-20240228下午63154556](https://static.rux.ink/uPic/image-20240228%E4%B8%8B%E5%8D%8863154556.png) ![success](https://static.rux.ink/uPic/success.gif) -> 已上传的文件会自动创建 `.cache.json` 进行记录,以便减少无用上传。 +> 插件会自动创建 `.cache.json` 文件进行记录,以减少无用的重复上传。 ![cache](https://static.rux.ink/uPic/cache.gif) diff --git a/examples/vite5-react/package.json b/examples/vite5-react/package.json index 98cdbab..93ca5ed 100644 --- a/examples/vite5-react/package.json +++ b/examples/vite5-react/package.json @@ -19,6 +19,7 @@ "@typescript-eslint/eslint-plugin": "^7.0.2", "@typescript-eslint/parser": "^7.0.2", "@vitejs/plugin-react": "^4.2.1", + "dotenv": "^16.4.5", "eslint": "^8.56.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", diff --git a/examples/vite5-react/src/app.css b/examples/vite5-react/src/app.css index b9d355d..338535f 100644 --- a/examples/vite5-react/src/app.css +++ b/examples/vite5-react/src/app.css @@ -1,42 +1,156 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; +@layer demo { + .compare { + display: grid; + + > * { + grid-area: 1 / 1; + } + + > section { + display: grid; + place-content: center; + } + } + + .before { + clip-path: inset(0 calc(100% - var(--pos, 50%)) 0 0); + } + + .after { + clip-path: inset(0 0 0 var(--pos, 50%)); + } + + input[type='range'] { + z-index: 1; + appearance: none; + appearance: none; + background: transparent; + cursor: pointer; + + &::-webkit-slider-thumb { + appearance: none; + width: 4px; + height: 100dvh; + background-color: CanvasText; + } + + &::-moz-range-thumb { + appearance: none; + width: 4px; + height: 100dvh; + background-color: CanvasText; + } + } +} + +@layer demo.support { + * { + box-sizing: border-box; + margin: 0; + } + + html { + block-size: 100%; + color-scheme: dark light; + } + + body { + min-block-size: 100%; + font-family: system-ui, sans-serif; + display: grid; + } + + img { + max-block-size: 80dvh; + } +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: 'Figtree', sans-serif; +} + +body { + display: grid; + place-content: center; + min-height: 100vh; + background: #000; +} + +.container { + position: relative; + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr 1fr; + gap: 1em; + width: 800px; + height: 500px; + transition: all 400ms; +} + +.container:hover .box { + filter: grayscale(100%) opacity(24%); +} + +.box { + position: relative; + background: var(--img) center center; + background-size: cover; + transition: all 400ms; + display: flex; + justify-content: center; + align-items: center; } -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; +.container .box:hover { + filter: grayscale(0%) opacity(100%); } -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); + +.container:has(.box-1:hover) { + grid-template-columns: 3fr 1fr 1fr 1fr 1fr; } -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); + +.container:has(.box-2:hover) { + grid-template-columns: 1fr 3fr 1fr 1fr 1fr; +} + +.container:has(.box-3:hover) { + grid-template-columns: 1fr 1fr 3fr 1fr 1fr; +} + +.container:has(.box-4:hover) { + grid-template-columns: 1fr 1fr 1fr 3fr 1fr; +} + +.container:has(.box-5:hover) { + grid-template-columns: 1fr 1fr 1fr 1fr 3fr; } -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } +.box:nth-child(odd) { + transform: translateY(-16px); } -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } +.box:nth-child(even) { + transform: translateY(16px); } -.card { - padding: 2em; +.box::after { + content: attr(data-text); + position: absolute; + bottom: 20px; + background: #000; + color: #fff; + padding: 10px 10px 10px 14px; + letter-spacing: 4px; + text-transform: uppercase; + transform: translateY(60px); + opacity: 0; + transition: all 400ms; } -.read-the-docs { - color: #888; +.box:hover::after { + transform: translateY(0); + opacity: 1; + transition-delay: 400ms; } diff --git a/examples/vite5-react/src/app.tsx b/examples/vite5-react/src/app.tsx index afe48ac..0098ed6 100644 --- a/examples/vite5-react/src/app.tsx +++ b/examples/vite5-react/src/app.tsx @@ -1,35 +1,76 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' +import React, { useState } from 'react'; +import Roboto from './assets/Roboto.svg'; +import Runner from './assets/Runner.svg'; +import img1 from './assets/img-1.jpg'; +import img2 from './assets/img-2.jpg'; +import img3 from './assets/img-3.jpg'; +import img4 from './assets/img-4.jpg'; +import img5 from './assets/img-5.jpg'; +import './app.css'; -function App() { - const [count, setCount] = useState(0) +const peoples = [ + { + img: img1, + name: 'Renji' + }, + { + img: img2, + name: 'Sora' + }, + { + img: img3, + name: 'Kaito' + }, + { + img: img4, + name: 'Tsuki' + }, + { + img: img5, + name: 'Mitsui' + } +]; - return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) -} +export default function App() { + const [rangeValue, setRangeValue] = useState('50'); -export default App + return ( + <> +
+
+ +
+
+ +
+ ) => + setRangeValue(e.target.value) + } + /> +
+
+ {peoples.map((item, index) => ( +
+ ))} +
+ + ); +} diff --git a/examples/vite5-react/src/assets/Roboto.svg b/examples/vite5-react/src/assets/Roboto.svg new file mode 100644 index 0000000..3d6683d --- /dev/null +++ b/examples/vite5-react/src/assets/Roboto.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/vite5-react/src/assets/Runner.svg b/examples/vite5-react/src/assets/Runner.svg new file mode 100644 index 0000000..a499607 --- /dev/null +++ b/examples/vite5-react/src/assets/Runner.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/vite5-react/src/assets/img-1.jpg b/examples/vite5-react/src/assets/img-1.jpg new file mode 100644 index 0000000..f2aa0db Binary files /dev/null and b/examples/vite5-react/src/assets/img-1.jpg differ diff --git a/examples/vite5-react/src/assets/img-2.jpg b/examples/vite5-react/src/assets/img-2.jpg new file mode 100644 index 0000000..b97291e Binary files /dev/null and b/examples/vite5-react/src/assets/img-2.jpg differ diff --git a/examples/vite5-react/src/assets/img-3.jpg b/examples/vite5-react/src/assets/img-3.jpg new file mode 100644 index 0000000..17de42b Binary files /dev/null and b/examples/vite5-react/src/assets/img-3.jpg differ diff --git a/examples/vite5-react/src/assets/img-4.jpg b/examples/vite5-react/src/assets/img-4.jpg new file mode 100644 index 0000000..1be262e Binary files /dev/null and b/examples/vite5-react/src/assets/img-4.jpg differ diff --git a/examples/vite5-react/src/assets/img-5.jpg b/examples/vite5-react/src/assets/img-5.jpg new file mode 100644 index 0000000..b4f107c Binary files /dev/null and b/examples/vite5-react/src/assets/img-5.jpg differ diff --git a/examples/vite5-react/src/assets/react.svg b/examples/vite5-react/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/examples/vite5-react/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/vite5-react/src/index.css b/examples/vite5-react/src/index.css deleted file mode 100644 index 6119ad9..0000000 --- a/examples/vite5-react/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/examples/vite5-react/src/main.tsx b/examples/vite5-react/src/main.tsx index 3d7150d..62a33ee 100644 --- a/examples/vite5-react/src/main.tsx +++ b/examples/vite5-react/src/main.tsx @@ -1,10 +1,4 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.tsx' -import './index.css' +import ReactDOM from 'react-dom/client'; +import App from './app'; -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , -) +ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/examples/vite5-react/vite.config.ts b/examples/vite5-react/vite.config.ts index 5a33944..f5511d8 100644 --- a/examples/vite5-react/vite.config.ts +++ b/examples/vite5-react/vite.config.ts @@ -1,7 +1,20 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import dotenv from 'dotenv'; +import path from 'node:path'; +import cdnizer from '../../dist/index'; + +dotenv.config({ path: path.resolve(process.cwd(), '../', '.env.local') }); -// https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], -}) + plugins: [ + react(), + cdnizer({ + secretId: process.env.VITE_SECRET_ID!, + secretKey: process.env.VITE_SECRET_KEY!, + bucket: 'md-1307877784', + region: 'ap-beijing', + domain: 'https://static.rux.ink/' + }) + ] +}); diff --git a/src/index.ts b/index.ts similarity index 84% rename from src/index.ts rename to index.ts index 659f764..0a681fc 100644 --- a/src/index.ts +++ b/index.ts @@ -2,8 +2,8 @@ import path from 'node:path'; import fs from 'node:fs'; import crypto from 'node:crypto'; import COS from 'cos-nodejs-sdk-v5'; +import chalk from 'chalk'; import { normalizePath, type Plugin } from 'vite'; -import { concatDomainAndPath, getTime, log, type TLogLevel } from './utils'; interface IStaticCdnizerPluginOptions { /** @@ -57,6 +57,28 @@ type TUploadFileResp = { type TCacheData = Record; +type TLogLevel = keyof typeof chalkConfig; + +type TLogMethods = { + [K in TLogLevel]: (msg: any) => void; +}; + +// Log config +const chalkConfig = { + success: chalk.bold.green, + cache: chalk.bold.blue, + error: chalk.bold.red, + info: chalk.bold.gray +}; + +const logger = Object.keys(chalkConfig).reduce( + (acc, cur) => ({ + ...acc, + [cur as TLogLevel]: (msg: any) => console.log(chalkConfig[cur as TLogLevel](msg)) + }), + {} as TLogMethods +); + const LOG_BANNER = '\n-------------------------------------------------------\n\t\t 📝 COS uploadFile log\n-------------------------------------------------------'; @@ -74,6 +96,17 @@ const statusCodeLogLevels: Record = { [StatusCode.NOTFOUND]: 'info' }; +const concatDomainAndPath = (domain: string, path: string) => + `${domain.replace(/\/$/, '')}/${path.replace(/^\//, '')}`; + +export const getTime = () => { + const date = new Date(); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + return `${hours}:${minutes}:${seconds}`; +}; + export default function StaticCdnizerPlugin(options: IStaticCdnizerPluginOptions): Plugin { const { bucket, @@ -209,15 +242,15 @@ export default function StaticCdnizerPlugin(options: IStaticCdnizerPluginOptions : include.includes(fileExtension) ) { if (++isFirst === 1) { - log.info(LOG_BANNER); + logger.info(LOG_BANNER); } // 只关注获取到的结果 const { status, url, message } = await uploadFile(normalizedFile); - // log 统一处理 + // logger 统一处理 const logLevel = statusCodeLogLevels[status]; const logPrefix = message ? `${logLevel}: ${message}` : logLevel; - log[logLevel](`[${getTime()}] (${logPrefix}) ${file} => ${url}`); + logger[logLevel](`[${getTime()}] (${logPrefix}) ${file} => ${url}`); if ([StatusCode.SUCCESS, StatusCode.CACHE].includes(status)) { return `export default '${url}';`; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6694fa5..9375587 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,6 +89,9 @@ importers: '@vitejs/plugin-react': specifier: ^4.2.1 version: 4.2.1(vite@5.1.4) + dotenv: + specifier: ^16.4.5 + version: 16.4.5 eslint: specifier: ^8.56.0 version: 8.57.0 @@ -893,6 +896,7 @@ packages: resolution: {integrity: sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==} cpu: [arm64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -901,6 +905,7 @@ packages: resolution: {integrity: sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==} cpu: [arm64] os: [linux] + libc: [musl] requiresBuild: true dev: true optional: true @@ -909,6 +914,7 @@ packages: resolution: {integrity: sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==} cpu: [riscv64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -917,6 +923,7 @@ packages: resolution: {integrity: sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==} cpu: [x64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -925,6 +932,7 @@ packages: resolution: {integrity: sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==} cpu: [x64] os: [linux] + libc: [musl] requiresBuild: true dev: true optional: true @@ -1632,6 +1640,11 @@ packages: is-obj: 2.0.0 dev: false + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: true + /ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} dependencies: diff --git a/src/utils/concatDomainAndPath.ts b/src/utils/concatDomainAndPath.ts deleted file mode 100644 index 55c6aeb..0000000 --- a/src/utils/concatDomainAndPath.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const concatDomainAndPath = (domain: string, path: string) => - `${domain.replace(/\/$/, '')}/${path.replace(/^\//, '')}`; diff --git a/src/utils/getTime.ts b/src/utils/getTime.ts deleted file mode 100644 index 403eea1..0000000 --- a/src/utils/getTime.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const getTime = () => { - const date = new Date(); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - const seconds = String(date.getSeconds()).padStart(2, '0'); - return `${hours}:${minutes}:${seconds}`; -}; diff --git a/src/utils/index.ts b/src/utils/index.ts deleted file mode 100644 index 2c82d1b..0000000 --- a/src/utils/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { concatDomainAndPath } from './concatDomainAndPath'; -export { getTime } from './getTime'; -export { log, TLogLevel } from './log'; diff --git a/src/utils/log.ts b/src/utils/log.ts deleted file mode 100644 index 1ec661d..0000000 --- a/src/utils/log.ts +++ /dev/null @@ -1,23 +0,0 @@ -import chalk from 'chalk'; - -export type TLogLevel = keyof typeof chalkConfig; - -export type TLogMethods = { - [K in TLogLevel]: (msg: any) => void; -}; - -// Log config -const chalkConfig = { - success: chalk.bold.green, - cache: chalk.bold.blue, - error: chalk.bold.red, - info: chalk.bold.gray -}; - -export const log = Object.keys(chalkConfig).reduce( - (acc, cur) => ({ - ...acc, - [cur as TLogLevel]: (msg: any) => console.log(chalkConfig[cur as TLogLevel](msg)) - }), - {} as TLogMethods -); diff --git a/tsconfig.json b/tsconfig.json index 67d3964..55f9274 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,7 +31,7 @@ "skipLibCheck": true }, "include": [ - "src" + "index.ts" ], "exclude": [ "node_modules"