THE
BLOG
nextjs 加载 iconify 如何 SSR 化

> 由于开发项目页面多且复杂,所以选择使用到 iconify 图标库。使用到的是 nextjs,由于 iconify 在ssr中的一些缺陷,只能通过 json 去将图标 ssr 化,导致搭配 vscode 插件 Iconify IntelliSense 预览图标和书写体验不佳,所以进行封装改善开发体验。 ## 使用: ```typescript <AxIcon icon="line-md:close-circle-twotone" className="mr-2" size={48} /> ``` ## 完整代码 ```typescript "use client"; import { cn } from "@/lib/utils"; import { icons as entypoIcons, IconifyJSON } from "@iconify-json/entypo"; import { icons as lineMDIcons } from "@iconify-json/line-md"; import { icons as phIcons } from "@iconify-json/ph"; import { icons as tablerIcons } from "@iconify-json/tabler"; import { replaceIDs } from "@iconify/react"; import { getIconData, iconToHTML, iconToSVG } from "@iconify/utils"; interface AxIconProps { icon: string; size?: string | number; className?: string; } export default function AxIcon({ icon, size = 16, className }: AxIconProps) { const [brand, iconName] = icon.split(":"); const brandIcons: Record<string, IconifyJSON> = { tabler: tablerIcons, entypo: entypoIcons, ph: phIcons, "line-md": lineMDIcons }; if (!brandIcons[brand]) return; const iconData = getIconData(brandIcons[brand], iconName); if (!iconData) return; const renderData = iconToSVG(iconData, { height: size, width: size }); const svgStr = iconToHTML(replaceIDs(renderData.body), { ...renderData.attributes, class: cn("!w-auto !h-auto", className) }); return <div dangerouslySetInnerHTML={{ __html: svgStr }} />; } ```

avatar
出自
a1ex0012
nestjs
JavaScript
后端
nestjs 链接 notion API

测试

avatar
出自
a1ex0012
高级轮播效果实现

[https://spring-slider.uiinitiative.com/](https://spring-slider.uiinitiative.com/)

avatar
出自
a1ex0012
vitest
playwright
单元测试
nextjs 单元测试 编写

> 最近在写国际化适配阿拉伯语时,发现在跑翻译的时候,有一些json解析翻译出来有些引号被替换,导致JSON.parse 报错,其中几个页面会直接error但是并没有发现,所以打算写一个测试,测试所有页面是否加载正常。 ### 组件单元测试(vitest) 1. 准备工作 1. vitest.config.mts ```typescript import react from "@vitejs/plugin-react"; import tsconfigPaths from "vite-tsconfig-paths"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [tsconfigPaths(), react()], test: { globals: true, environment: "jsdom", setupFiles: ["./vitest.setup.ts"], include: ["**/*.test.{js,jsx,ts,tsx}"], exclude: ["**/node_modules/**", "**/dist/**"] } }); ``` b. vitest.setup.ts ```typescript import { cleanup } from "@testing-library/react"; import { afterEach, vi } from "vitest"; afterEach(() => { cleanup(); vi.clearAllMocks(); }); ``` 2. 基本使用 1. 判断 渲染组件后关键DOM是否存在 ```typescript import Footer from "@/components/Footer"; import { render, screen } from "@testing-library/react"; import { expect, test } from "vitest"; test("test", () => { render(<Footer />); expect(screen.getByText("[赣ICP备2022002397号]")).toBeDefined(); }); ``` ### e2e测试(playwright) 1. 准备工作 1. playwright.config.ts ```typescript import { defineConfig, devices } from "@playwright/test"; import path from "path"; // Use process.env.PORT by default and fallback to port 3000 const PORT = process.env.PORT || 3000; // Set webServer.url and use.baseURL with the location of the WebServer respecting the correct set port const baseURL = `http://localhost:${PORT}`; // Reference: https://playwright.dev/docs/test-configuration export default defineConfig({ // Timeout per test timeout: 30 * 1000, // Test directory testDir: path.join(__dirname, "e2e"), // If a test fails, retry it additional 2 times retries: 0, // Artifacts folder where screenshots, videos, and traces are stored. outputDir: "test-results/", // Run your local dev server before starting the tests: // https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests webServer: { command: "pnpm run dev", url: baseURL, timeout: 120 * 1000, reuseExistingServer: !process.env.CI }, use: { // Use baseURL so to make navigations relative. // More information: https://playwright.dev/docs/api/class-testoptions#test-options-base-url baseURL, // Retry a test if its failing with enabled tracing. This allows you to analyze the DOM, console logs, network traffic etc. // More information: https://playwright.dev/docs/trace-viewer trace: "retry-with-trace" // All available context options: https://playwright.dev/docs/api/class-browser#browser-new-context // contextOptions: { // ignoreHTTPSErrors: true, // }, }, projects: [ { name: "Desktop Chrome", use: { ...devices["Desktop Chrome"] } } // { // name: 'Desktop Firefox', // use: { // ...devices['Desktop Firefox'], // }, // }, // { // name: 'Desktop Safari', // use: { // ...devices['Desktop Safari'], // }, // }, // Test against mobile viewports. // { // name: "Mobile Chrome", // use: { // ...devices["Pixel 5"] // } // }, // { // name: "Mobile Safari", // use: devices["iPhone 12"] // } ] }); ``` 1. 基本使用 1. 加载所有页面是否正常 svg 过渡变换 并且包含预览 codebox ```typescript import { RouteInfo, scanRoutes } from "@/scripts/scanRoutes"; import { expect, test } from "@playwright/test"; import { filter } from "lodash"; const routes = scanRoutes(); const isStaticRoute = filter(routes, (r: RouteInfo) => !r.isDynamic); const generatePageTestByRoute = (route: RouteInfo) => { test.describe(`<${route.path}> 加载测试`, () => { test(`<${route.path}> 加载成功`, async ({ page }) => { // 检查页面响应状态码 const response = await page.goto(route.path); expect(response?.status()).toBe(200); }); if (route.path.includes("svgTransform")) { test(`<${route.path}> svg 过渡变换 并且包含预览 codebox`, async ({ page }) => { const response = await page.goto(route.path); expect(response?.status()).toBe(200); const htmlContent = await page.content(); const bol = htmlContent.includes("IconTransformProps"); expect(bol).toBe(true); }); } }); }; isStaticRoute.forEach(generatePageTestByRoute); ```

avatar
出自
a1ex0012
vitest
playwright
单元测试
nextjs 单元测试 编写

> 最近在写国际化适配阿拉伯语时,发现在跑翻译的时候,有一些json解析翻译出来有些引号被替换,导致JSON.parse 报错,其中几个页面会直接error但是并没有发现,所以打算写一个测试,测试所有页面是否加载正常。 ### 组件单元测试(vitest) 1. 准备工作 1. vitest.config.mts ```typescript import react from "@vitejs/plugin-react"; import tsconfigPaths from "vite-tsconfig-paths"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [tsconfigPaths(), react()], test: { globals: true, environment: "jsdom", setupFiles: ["./vitest.setup.ts"], include: ["**/*.test.{js,jsx,ts,tsx}"], exclude: ["**/node_modules/**", "**/dist/**"] } }); ``` b. vitest.setup.ts ```typescript import { cleanup } from "@testing-library/react"; import { afterEach, vi } from "vitest"; afterEach(() => { cleanup(); vi.clearAllMocks(); }); ``` 2. 基本使用 1. 判断 渲染组件后关键DOM是否存在 ```typescript import Footer from "@/components/Footer"; import { render, screen } from "@testing-library/react"; import { expect, test } from "vitest"; test("test", () => { render(<Footer />); expect(screen.getByText("[赣ICP备2022002397号]")).toBeDefined(); }); ``` ### e2e测试(playwright) 1. 准备工作 1. playwright.config.ts ```typescript import { defineConfig, devices } from "@playwright/test"; import path from "path"; // Use process.env.PORT by default and fallback to port 3000 const PORT = process.env.PORT || 3000; // Set webServer.url and use.baseURL with the location of the WebServer respecting the correct set port const baseURL = `http://localhost:${PORT}`; // Reference: https://playwright.dev/docs/test-configuration export default defineConfig({ // Timeout per test timeout: 30 * 1000, // Test directory testDir: path.join(__dirname, "e2e"), // If a test fails, retry it additional 2 times retries: 0, // Artifacts folder where screenshots, videos, and traces are stored. outputDir: "test-results/", // Run your local dev server before starting the tests: // https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests webServer: { command: "pnpm run dev", url: baseURL, timeout: 120 * 1000, reuseExistingServer: !process.env.CI }, use: { // Use baseURL so to make navigations relative. // More information: https://playwright.dev/docs/api/class-testoptions#test-options-base-url baseURL, // Retry a test if its failing with enabled tracing. This allows you to analyze the DOM, console logs, network traffic etc. // More information: https://playwright.dev/docs/trace-viewer trace: "retry-with-trace" // All available context options: https://playwright.dev/docs/api/class-browser#browser-new-context // contextOptions: { // ignoreHTTPSErrors: true, // }, }, projects: [ { name: "Desktop Chrome", use: { ...devices["Desktop Chrome"] } } // { // name: 'Desktop Firefox', // use: { // ...devices['Desktop Firefox'], // }, // }, // { // name: 'Desktop Safari', // use: { // ...devices['Desktop Safari'], // }, // }, // Test against mobile viewports. // { // name: "Mobile Chrome", // use: { // ...devices["Pixel 5"] // } // }, // { // name: "Mobile Safari", // use: devices["iPhone 12"] // } ] }); ``` 1. 基本使用 1. 加载所有页面是否正常 svg 过渡变换 并且包含预览 codebox ```typescript import { RouteInfo, scanRoutes } from "@/scripts/scanRoutes"; import { expect, test } from "@playwright/test"; import { filter } from "lodash"; const routes = scanRoutes(); const isStaticRoute = filter(routes, (r: RouteInfo) => !r.isDynamic); const generatePageTestByRoute = (route: RouteInfo) => { test.describe(`<${route.path}> 加载测试`, () => { test(`<${route.path}> 加载成功`, async ({ page }) => { // 检查页面响应状态码 const response = await page.goto(route.path); expect(response?.status()).toBe(200); }); if (route.path.includes("svgTransform")) { test(`<${route.path}> svg 过渡变换 并且包含预览 codebox`, async ({ page }) => { const response = await page.goto(route.path); expect(response?.status()).toBe(200); const htmlContent = await page.content(); const bol = htmlContent.includes("IconTransformProps"); expect(bol).toBe(true); }); } }); }; isStaticRoute.forEach(generatePageTestByRoute); ```

avatar
出自
a1ex0012
vitest
playwright
单元测试
nextjs 单元测试 编写

> 最近在写国际化适配阿拉伯语时,发现在跑翻译的时候,有一些json解析翻译出来有些引号被替换,导致JSON.parse 报错,其中几个页面会直接error但是并没有发现,所以打算写一个测试,测试所有页面是否加载正常。 ### 组件单元测试(vitest) 1. 准备工作 1. vitest.config.mts ```typescript import react from "@vitejs/plugin-react"; import tsconfigPaths from "vite-tsconfig-paths"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [tsconfigPaths(), react()], test: { globals: true, environment: "jsdom", setupFiles: ["./vitest.setup.ts"], include: ["**/*.test.{js,jsx,ts,tsx}"], exclude: ["**/node_modules/**", "**/dist/**"] } }); ``` b. vitest.setup.ts ```typescript import { cleanup } from "@testing-library/react"; import { afterEach, vi } from "vitest"; afterEach(() => { cleanup(); vi.clearAllMocks(); }); ``` 2. 基本使用 1. 判断 渲染组件后关键DOM是否存在 ```typescript import Footer from "@/components/Footer"; import { render, screen } from "@testing-library/react"; import { expect, test } from "vitest"; test("test", () => { render(<Footer />); expect(screen.getByText("[赣ICP备2022002397号]")).toBeDefined(); }); ``` ### e2e测试(playwright) 1. 准备工作 1. playwright.config.ts ```typescript import { defineConfig, devices } from "@playwright/test"; import path from "path"; // Use process.env.PORT by default and fallback to port 3000 const PORT = process.env.PORT || 3000; // Set webServer.url and use.baseURL with the location of the WebServer respecting the correct set port const baseURL = `http://localhost:${PORT}`; // Reference: https://playwright.dev/docs/test-configuration export default defineConfig({ // Timeout per test timeout: 30 * 1000, // Test directory testDir: path.join(__dirname, "e2e"), // If a test fails, retry it additional 2 times retries: 0, // Artifacts folder where screenshots, videos, and traces are stored. outputDir: "test-results/", // Run your local dev server before starting the tests: // https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests webServer: { command: "pnpm run dev", url: baseURL, timeout: 120 * 1000, reuseExistingServer: !process.env.CI }, use: { // Use baseURL so to make navigations relative. // More information: https://playwright.dev/docs/api/class-testoptions#test-options-base-url baseURL, // Retry a test if its failing with enabled tracing. This allows you to analyze the DOM, console logs, network traffic etc. // More information: https://playwright.dev/docs/trace-viewer trace: "retry-with-trace" // All available context options: https://playwright.dev/docs/api/class-browser#browser-new-context // contextOptions: { // ignoreHTTPSErrors: true, // }, }, projects: [ { name: "Desktop Chrome", use: { ...devices["Desktop Chrome"] } } // { // name: 'Desktop Firefox', // use: { // ...devices['Desktop Firefox'], // }, // }, // { // name: 'Desktop Safari', // use: { // ...devices['Desktop Safari'], // }, // }, // Test against mobile viewports. // { // name: "Mobile Chrome", // use: { // ...devices["Pixel 5"] // } // }, // { // name: "Mobile Safari", // use: devices["iPhone 12"] // } ] }); ``` 1. 基本使用 1. 加载所有页面是否正常 svg 过渡变换 并且包含预览 codebox ```typescript import { RouteInfo, scanRoutes } from "@/scripts/scanRoutes"; import { expect, test } from "@playwright/test"; import { filter } from "lodash"; const routes = scanRoutes(); const isStaticRoute = filter(routes, (r: RouteInfo) => !r.isDynamic); const generatePageTestByRoute = (route: RouteInfo) => { test.describe(`<${route.path}> 加载测试`, () => { test(`<${route.path}> 加载成功`, async ({ page }) => { // 检查页面响应状态码 const response = await page.goto(route.path); expect(response?.status()).toBe(200); }); if (route.path.includes("svgTransform")) { test(`<${route.path}> svg 过渡变换 并且包含预览 codebox`, async ({ page }) => { const response = await page.goto(route.path); expect(response?.status()).toBe(200); const htmlContent = await page.content(); const bol = htmlContent.includes("IconTransformProps"); expect(bol).toBe(true); }); } }); }; isStaticRoute.forEach(generatePageTestByRoute); ```

avatar
出自
a1ex0012
vitest
playwright
单元测试
nextjs 单元测试 编写

> 最近在写国际化适配阿拉伯语时,发现在跑翻译的时候,有一些json解析翻译出来有些引号被替换,导致JSON.parse 报错,其中几个页面会直接error但是并没有发现,所以打算写一个测试,测试所有页面是否加载正常。 ### 组件单元测试(vitest) 1. 准备工作 1. vitest.config.mts ```typescript import react from "@vitejs/plugin-react"; import tsconfigPaths from "vite-tsconfig-paths"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [tsconfigPaths(), react()], test: { globals: true, environment: "jsdom", setupFiles: ["./vitest.setup.ts"], include: ["**/*.test.{js,jsx,ts,tsx}"], exclude: ["**/node_modules/**", "**/dist/**"] } }); ``` b. vitest.setup.ts ```typescript import { cleanup } from "@testing-library/react"; import { afterEach, vi } from "vitest"; afterEach(() => { cleanup(); vi.clearAllMocks(); }); ``` 2. 基本使用 1. 判断 渲染组件后关键DOM是否存在 ```typescript import Footer from "@/components/Footer"; import { render, screen } from "@testing-library/react"; import { expect, test } from "vitest"; test("test", () => { render(<Footer />); expect(screen.getByText("[赣ICP备2022002397号]")).toBeDefined(); }); ``` ### e2e测试(playwright) 1. 准备工作 1. playwright.config.ts ```typescript import { defineConfig, devices } from "@playwright/test"; import path from "path"; // Use process.env.PORT by default and fallback to port 3000 const PORT = process.env.PORT || 3000; // Set webServer.url and use.baseURL with the location of the WebServer respecting the correct set port const baseURL = `http://localhost:${PORT}`; // Reference: https://playwright.dev/docs/test-configuration export default defineConfig({ // Timeout per test timeout: 30 * 1000, // Test directory testDir: path.join(__dirname, "e2e"), // If a test fails, retry it additional 2 times retries: 0, // Artifacts folder where screenshots, videos, and traces are stored. outputDir: "test-results/", // Run your local dev server before starting the tests: // https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests webServer: { command: "pnpm run dev", url: baseURL, timeout: 120 * 1000, reuseExistingServer: !process.env.CI }, use: { // Use baseURL so to make navigations relative. // More information: https://playwright.dev/docs/api/class-testoptions#test-options-base-url baseURL, // Retry a test if its failing with enabled tracing. This allows you to analyze the DOM, console logs, network traffic etc. // More information: https://playwright.dev/docs/trace-viewer trace: "retry-with-trace" // All available context options: https://playwright.dev/docs/api/class-browser#browser-new-context // contextOptions: { // ignoreHTTPSErrors: true, // }, }, projects: [ { name: "Desktop Chrome", use: { ...devices["Desktop Chrome"] } } // { // name: 'Desktop Firefox', // use: { // ...devices['Desktop Firefox'], // }, // }, // { // name: 'Desktop Safari', // use: { // ...devices['Desktop Safari'], // }, // }, // Test against mobile viewports. // { // name: "Mobile Chrome", // use: { // ...devices["Pixel 5"] // } // }, // { // name: "Mobile Safari", // use: devices["iPhone 12"] // } ] }); ``` 1. 基本使用 1. 加载所有页面是否正常 svg 过渡变换 并且包含预览 codebox ```typescript import { RouteInfo, scanRoutes } from "@/scripts/scanRoutes"; import { expect, test } from "@playwright/test"; import { filter } from "lodash"; const routes = scanRoutes(); const isStaticRoute = filter(routes, (r: RouteInfo) => !r.isDynamic); const generatePageTestByRoute = (route: RouteInfo) => { test.describe(`<${route.path}> 加载测试`, () => { test(`<${route.path}> 加载成功`, async ({ page }) => { // 检查页面响应状态码 const response = await page.goto(route.path); expect(response?.status()).toBe(200); }); if (route.path.includes("svgTransform")) { test(`<${route.path}> svg 过渡变换 并且包含预览 codebox`, async ({ page }) => { const response = await page.goto(route.path); expect(response?.status()).toBe(200); const htmlContent = await page.content(); const bol = htmlContent.includes("IconTransformProps"); expect(bol).toBe(true); }); } }); }; isStaticRoute.forEach(generatePageTestByRoute); ```

avatar
出自
a1ex0012
JavaScript
h5
前端
canvas 和 svg

> 基础的基础 ## **svg** 学svg前先了解位图和矢量图大的区别 - 位图: 放大会失真图像边缘模糊,因为它是由橡树点组成的 - 矢量图:放大不会失真,使用xml描述图形 svg便是矢量图,所以金额已使用css设置样式,也可以用js对其草操作 ### **基本使用** ```text <svg></svg> ``` 默认w300h150 ### **基本图形** > 属性不赘述一眼能看出来 1. 矩形 ```text <rect x="20" y="50" width="100" height="100" rx="20" ry="10"></rect> ``` 2. 圆形 ```text <circle cx="50" cy="0" r="20"></circle> ``` 3. 直线 ```text <line x1="20" y1="30" x2="50" y2="100" stroke="#008c8c" /> ``` 4. 折线 ```text <polyline points="1 1 20 20 100 50 60 80" stroke="#F0F" /> ``` 5. 多边形 ```text <polygon points="1 1 20 20 100 50 60 80" stroke="#F0F" fill="#F0F" /> ``` 6. 路径 ```text <path d="M 10 10 L 100 50 L 10 80 Z" stroke="blue" fill="none" /> ``` 关于d属性 - M: 起始点坐标,`moveto` 的意思。每个路径都必须以 `M` 开始。`M` 传入 `x` 和 `y` 坐标,用逗号或者空格隔开。 - `L`: 轮廓坐标,`lineto` 的意思。`L` 是跟在 `M` 后面的。它也是可以传入一个或多个坐标。大写的 `L` 是一个**绝对位置**。 - l: 这是小写 `L`,和 `L` 的作用差不多,但 `l` 是一个**相对位置**。 - `H`: 和上一个点的Y坐标相等,是 `horizontal lineto` 的意思。它是一个**绝对位置**。 - `h`: 和 `H` 差不多,但 `h` 使用的是**相对定位**。 - `V`: 和上一个点的X坐标相等,是`vertical lineto` 的意思。它是一个**绝对位置**。 - `v`: 这是一个小写的 `v` ,和大写 `V` 的差不多,但小写 `v` 是一个相对定位。 - `Z`: 关闭当前路径,`closepath` 的意思。它会绘制一条直线回到当前子路径的起点。 关于样式 1. 填充颜色 - 使用属性fill - 透明度 fill-opacity 2. 描边 - 使用属性stroke - 透明度 stroke-opacity - 宽度 stroke-width - 虚线描边stroke-dasharray - 虚线偏移量 stroke-dashoffset ## **canvas** canvas翻译过来就是画布,可以用javascript在上面绘画,是H5的新API ### **应用场景** - 游戏 - 可视化数据图表 ### **用法** `<canvas></canvas>`,默认w300h150 通过获取绘图上下文精选绘制 ### **绘制路径** canvas坐标原点的定位在左上角 - moveTo 设置路径的起点 - lineTo 绘制一条直线到起点 - beginPath/closePath 作用是将 不同绘制的形状进行隔离, 每次执行此方法,表示重新绘制一个路径,跟之前的绘制的墨迹可以进行分开样式设置和管理。 - stroke 描边 绘制一个基本图形的过程 ```text // 1. 开始  ctx?.beginPath();  // 2. 选择起始点  ctx?.moveTo(20, 20);  // 3. 画线  ctx?.lineTo(50, 80);  ctx?.lineTo(100, 180);  ctx?.lineTo(0, 20);  // 填充  ctx?.fill();  // 4。 闭合路径  ctx?.closePath();  // 5. 描边  ctx?.stroke(); ``` 基础图形 ```text // 篡改就矩形  ctx?.beginPath();  ctx?.rect(10, 20, 40, 80);  ctx?.fill();  ctx?.closePath();  ctx?.stroke();  // 画饼状图  ctx?.beginPath();  ctx?.moveTo(100, 100);  ctx?.arc(100, 100, 150, 0, Math.sin(40), false);  ctx?.fill();  ctx?.stroke();  ctx?.closePath(); ​  // 写字  ctx?.beginPath();  ctx!.font = "18px '微软雅黑'";  ctx!.textAlign = "center";  ctx?.fillText("test", 300, 300, 100);  ctx?.stroke();  ctx?.closePath(); ​  // 画图像  ctx?.beginPath();  // 相当于复刻相对比例图片  ctx?.drawImage(canvasRef.value as CanvasImageSource, 300, 0);  ctx?.stroke();  ctx?.closePath(); ``` ### **关于样式** - fillStyle : 设置或返回用于填充绘画的颜色 - strokeStyle: 设置或返回用于笔触的颜色 - 设置阴影,渐变样式 ## **svg和canvas的区别** ### **svg** - 矢量图放大不失真 - 支持事件处理器 - 文字独立、可编辑可搜索 ### **canvans** - 绘制出来的图形是位图具有很好的渲染性能 - 适合数据量比较大(>1000) - 大量图形高频率交互 - 适合游戏 - 可以导出jpg/png图片 [https://raw.githubusercontent.com/A1ex-01/drawing-board/main/blog/blog-bg.png](https://raw.githubusercontent.com/A1ex-01/drawing-board/main/blog/blog-bg.png)

avatar
出自
a1ex0012