mirror of
https://github.com/Xevion/grain.git
synced 2025-12-06 01:15:10 -06:00
feat: migrate from React to Preact, add Vitest testing, and optimize bundle
Major framework and tooling changes: - Migrate from React 19 to Preact 10 with preact-iso for routing - Replace @mantine/hooks with custom hooks (useBooleanToggle, useViewportSize) - Switch from @heroicons/react to lucide-preact for icons - Replace chance.js with random-js for RNG Testing infrastructure: - Add Vitest with @testing-library/preact and happy-dom - Set up test configuration and initial App tests - Add Vitest UI for interactive test running Build optimizations: - Add cssnano for CSS minification - Configure aggressive bundle optimizations in Vite - Update to Tailwind CSS 4.1.17 Project structure: - Consolidate entry point from main.tsx to index.tsx - Reorganize CSS location (styles/index.css → index.css) - Add ESLint with preact config - Update TypeScript configuration for Preact
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,6 +7,8 @@ yarn-error.log*
|
|||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
|
stats.html
|
||||||
|
*.js
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
|||||||
12
index.html
12
index.html
@@ -19,10 +19,7 @@
|
|||||||
property="article:published_time"
|
property="article:published_time"
|
||||||
content="2022-11-25T08:54:58.977Z "
|
content="2022-11-25T08:54:58.977Z "
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta property="og:image" content="https://grain.xevion.dev/bg.jpeg" />
|
||||||
property="og:image"
|
|
||||||
content="https://grain.xevion.dev/bg.jpeg"
|
|
||||||
/>
|
|
||||||
<meta
|
<meta
|
||||||
property="og:image:secure_url"
|
property="og:image:secure_url"
|
||||||
content="https://grain.xevion.dev/bg.jpeg"
|
content="https://grain.xevion.dev/bg.jpeg"
|
||||||
@@ -34,10 +31,7 @@
|
|||||||
content="A simple gradient image generated with Grain."
|
content="A simple gradient image generated with Grain."
|
||||||
/>
|
/>
|
||||||
<meta property="og:image:type" content="jpeg" />
|
<meta property="og:image:type" content="jpeg" />
|
||||||
<meta
|
<meta name="twitter:image" content="https://grain.xevion.dev/bg.jpeg" />
|
||||||
name="twitter:image"
|
|
||||||
content="https://grain.xevion.dev/bg.jpeg"
|
|
||||||
/>
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:url" content="https://grain.xevion.dev/" />
|
<meta name="twitter:url" content="https://grain.xevion.dev/" />
|
||||||
<meta name="twitter:domain" content="https://grain.xevion.dev/" />
|
<meta name="twitter:domain" content="https://grain.xevion.dev/" />
|
||||||
@@ -49,6 +43,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script prerender type="module" src="/src/index.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
45
package.json
45
package.json
@@ -1,33 +1,42 @@
|
|||||||
{
|
{
|
||||||
"name": "noise",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"preinstall": "npx only-allow pnpm",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"test": "vitest",
|
||||||
|
"test:ui": "vitest --ui",
|
||||||
|
"test:run": "vitest run",
|
||||||
|
"test:coverage": "vitest run --coverage"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.2.0",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@mantine/hooks": "^8.2.4",
|
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
|
||||||
"@use-it/event-listener": "^0.1.7",
|
|
||||||
"cssnano": "^7.1.0",
|
"cssnano": "^7.1.0",
|
||||||
|
"lucide-preact": "^0.468.0",
|
||||||
|
"preact": "^10.27.2",
|
||||||
|
"preact-iso": "^2.11.0",
|
||||||
|
"preact-render-to-string": "^6.6.3",
|
||||||
"random-js": "^2.1.0",
|
"random-js": "^2.1.0",
|
||||||
"react": "^19.1.1",
|
"tailwindcss": "^4.1.17"
|
||||||
"react-dom": "^19.1.1",
|
|
||||||
"tailwindcss": "^4.1.11"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.2.1",
|
"@preact/preset-vite": "^2.10.2",
|
||||||
"@types/react": "^19.1.9",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@testing-library/preact": "^3.2.4",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@types/node": "^24.10.0",
|
||||||
"rollup-plugin-visualizer": "^6.0.3",
|
"@vitest/ui": "4.0.7",
|
||||||
"typescript": "^5.9.2",
|
"eslint": "^9.39.1",
|
||||||
"vite": "^7.1.1",
|
"eslint-config-preact": "^2.0.0",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"happy-dom": "^20.0.10",
|
||||||
|
"rollup-plugin-visualizer": "^6.0.5",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vite": "^7.2.1",
|
||||||
|
"vitest": "^4.0.7"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "preact"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.1+sha512.1acb565e6193efbebda772702950469150cf12bcc764262e7587e71d19dc98a423dff9536e57ea44c49bdf790ff694e83c27be5faa23d67e0c033b583be4bfcf"
|
"packageManager": "pnpm@9.15.1+sha512.1acb565e6193efbebda772702950469150cf12bcc764262e7587e71d19dc98a423dff9536e57ea44c49bdf790ff694e83c27be5faa23d67e0c033b583be4bfcf"
|
||||||
}
|
}
|
||||||
|
|||||||
3231
pnpm-lock.yaml
generated
3231
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
|||||||
import { SparklesIcon } from "@heroicons/react/20/solid";
|
import { Sparkles, ShieldAlert } from "lucide-preact";
|
||||||
import { ShieldExclamationIcon } from "@heroicons/react/24/solid";
|
|
||||||
|
|
||||||
const Post = () => {
|
const Post = () => {
|
||||||
return (
|
return (
|
||||||
@@ -23,7 +22,7 @@ const Post = () => {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
className="hover:text-yellow-600 transition-colors cursor-pointer"
|
className="hover:text-yellow-600 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
<SparklesIcon className="h-4 inline mb-2.5 m-2 " />
|
<Sparkles className="h-4 inline mb-2.5 m-2 " />
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,24 +46,24 @@ const Post = () => {
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
By using a SVG with a{" "}
|
By using a SVG with a{" "}
|
||||||
<pre className="inline"><feTurbulence></pre> filter inside,
|
<code><feTurbulence></code> filter inside,
|
||||||
stacked upon several <pre className="inline">radial-gradient</pre>{" "}
|
stacked upon several <code>radial-gradient</code>{" "}
|
||||||
background images, the same effect can be created. Since SVGs do not
|
background images, the same effect can be created. Since SVGs do not
|
||||||
naturally repeat internally, the SVG itself must be generated in
|
naturally repeat internally, the SVG itself must be generated in
|
||||||
such a way that the noise always displays the same way.
|
such a way that the noise always displays the same way.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
React comes in handy here, allowing composition of an SVG, and then
|
React comes in handy here, allowing composition of an SVG, and then
|
||||||
conversion to a <pre className="inline">base64</pre> encoded string.
|
conversion to a <code>base64</code> encoded string.
|
||||||
As a <pre className="inline">base64</pre> image, it can be fed into
|
As a <code>base64</code> image, it can be fed into
|
||||||
the <pre className="inline">background</pre> CSS property, allowing
|
the <code>background</code> CSS property, allowing
|
||||||
dynamic SVG generation.
|
dynamic SVG generation.
|
||||||
</p>
|
</p>
|
||||||
<div className="pt-3">
|
<div className="pt-3">
|
||||||
<a href="https://github.com/Xevion/grain">
|
<a href="https://github.com/Xevion/grain">
|
||||||
<div className="inline text-white text-medium drop-shadow-lg rounded border-2 shadow-xl border-zinc-600/75 m-2 p-2 bg-linear-to-r from-red-500 via-orange-500 to-orange-700">
|
<div className="inline text-white text-medium drop-shadow-lg rounded border-2 shadow-xl border-zinc-600/75 m-2 p-2 bg-linear-to-r from-red-500 via-orange-500 to-orange-700">
|
||||||
In Progress
|
In Progress
|
||||||
<ShieldExclamationIcon className="inline h-[1.4rem] ml-3 drop-shadow-2xl" />
|
<ShieldAlert className="inline h-[1.4rem] ml-3 drop-shadow-2xl" />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
import { useViewportSize, useToggle } from "@mantine/hooks";
|
import { hydrate, prerender as ssr } from "preact-iso";
|
||||||
import useBackground from "@/utils/useBackground";
|
|
||||||
import Post from "@/components/Post";
|
|
||||||
|
|
||||||
import {
|
import "./index.css";
|
||||||
ArrowPathIcon,
|
|
||||||
EyeIcon,
|
|
||||||
EyeSlashIcon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
|
|
||||||
function App() {
|
import { useViewportSize } from "./utils/useViewportSize";
|
||||||
|
import { useBooleanToggle } from "./utils/useBooleanToggle";
|
||||||
|
import useBackground from "./utils/useBackground";
|
||||||
|
import Post from "./components/Post";
|
||||||
|
import { RefreshCw, Eye, EyeOff } from "lucide-preact";
|
||||||
|
import { useMemo } from "preact/hooks";
|
||||||
|
|
||||||
|
export function App() {
|
||||||
const { width, height } = useViewportSize();
|
const { width, height } = useViewportSize();
|
||||||
const { svg, backgrounds, regenerate } = useBackground({
|
const { svg, backgrounds, regenerate } = useBackground({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
ratio: 0.4,
|
ratio: 0.4,
|
||||||
});
|
});
|
||||||
const [postHidden, toggleHidden] = useToggle([false, true]);
|
const [postHidden, toggleHidden] = useBooleanToggle(false);
|
||||||
|
const [iconSpinning, toggleIconSpinning] = useBooleanToggle(false);
|
||||||
const [iconSpinning, toggleIconSpinning] = useToggle([false, true]);
|
|
||||||
|
|
||||||
const style = useMemo(() => {
|
const style = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@@ -42,7 +41,7 @@ function App() {
|
|||||||
setTimeout(() => toggleIconSpinning(false), 200);
|
setTimeout(() => toggleIconSpinning(false), 200);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArrowPathIcon
|
<RefreshCw
|
||||||
className={`transition-transform duration-200 ${
|
className={`transition-transform duration-200 ${
|
||||||
iconSpinning ? "rotate-180" : "rotate-0"
|
iconSpinning ? "rotate-180" : "rotate-0"
|
||||||
}`}
|
}`}
|
||||||
@@ -52,14 +51,13 @@ function App() {
|
|||||||
className="block p-2 w-10 h-10 rounded mx-auto xs:mx-0 xs:ml-5 mt-5 shadow-inner-md bg-zinc-700 text-zinc-100 button"
|
className="block p-2 w-10 h-10 rounded mx-auto xs:mx-0 xs:ml-5 mt-5 shadow-inner-md bg-zinc-700 text-zinc-100 button"
|
||||||
onClick={() => toggleHidden()}
|
onClick={() => toggleHidden()}
|
||||||
>
|
>
|
||||||
{postHidden ? <EyeIcon /> : <EyeSlashIcon />}
|
{postHidden ? <Eye /> : <EyeOff />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`h-screen transition-opacity ease-in-out duration-75 ${
|
className={`h-screen transition-opacity ease-in-out duration-75 ${
|
||||||
postHidden ? "opacity-0 pointer-events-none" : ""
|
postHidden ? "opacity-0 pointer-events-none" : ""
|
||||||
} flex col-span-9 sm:col-span-6 md:col-span-5 w-full min-h-screen`}
|
} flex col-span-9 sm:col-span-6 md:col-span-5 w-full min-h-screen`}
|
||||||
|
|
||||||
>
|
>
|
||||||
<div className="bg-white overflow-y-auto">
|
<div className="bg-white overflow-y-auto">
|
||||||
<Post />
|
<Post />
|
||||||
@@ -71,4 +69,10 @@ function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
if (typeof window !== "undefined") {
|
||||||
|
hydrate(<App />, document.getElementById("root"));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function prerender(data: any) {
|
||||||
|
return await ssr(<App {...data} />);
|
||||||
|
}
|
||||||
10
src/main.tsx
10
src/main.tsx
@@ -1,10 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
|
||||||
import App from "@/components/App";
|
|
||||||
import "@/styles/index.css";
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
|
||||||
48
src/test/App.test.tsx
Normal file
48
src/test/App.test.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||||
|
import { h } from "preact";
|
||||||
|
import { render } from "@testing-library/preact";
|
||||||
|
import type { FunctionComponent } from "preact";
|
||||||
|
|
||||||
|
describe("App", () => {
|
||||||
|
let App: FunctionComponent;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Mock window dimensions for useViewportSize hook
|
||||||
|
Object.defineProperty(window, "innerWidth", {
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
value: 1920,
|
||||||
|
});
|
||||||
|
Object.defineProperty(window, "innerHeight", {
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
value: 1080,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock btoa for useBackground hook
|
||||||
|
global.btoa = vi.fn((str) => Buffer.from(str).toString("base64"));
|
||||||
|
|
||||||
|
// Dynamically import App after mocks are set up
|
||||||
|
const module = await import("../index");
|
||||||
|
App = module.App;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders without crashing", () => {
|
||||||
|
expect(() => render(h(App, {}))).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the Post component with main heading", () => {
|
||||||
|
const { getAllByText } = render(h(App, {}));
|
||||||
|
expect(getAllByText("Grain").length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders author information", () => {
|
||||||
|
const { getAllByText } = render(h(App, {}));
|
||||||
|
expect(getAllByText("Ryan Walters").length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders with gradient class", () => {
|
||||||
|
const { container } = render(h(App, {}));
|
||||||
|
expect(container.innerHTML).toContain("gradient");
|
||||||
|
});
|
||||||
|
});
|
||||||
2
src/test/setup.ts
Normal file
2
src/test/setup.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
import "preact/debug";
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import { Random } from "random-js";
|
import { Random } from "random-js";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "preact/hooks";
|
||||||
import ReactDOMServer from "react-dom/server";
|
import { getEdgePoint } from "./helpers";
|
||||||
import { getEdgePoint } from "@/utils/helpers";
|
|
||||||
|
|
||||||
interface useBackgroundProps {
|
interface useBackgroundProps {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@@ -27,11 +25,7 @@ const generateBackground = (): string[] => {
|
|||||||
.fill(null)
|
.fill(null)
|
||||||
.map(() => random.pick(palette))
|
.map(() => random.pick(palette))
|
||||||
.map((color) => {
|
.map((color) => {
|
||||||
const [x, y] = getEdgePoint(
|
const [x, y] = getEdgePoint(random.integer(0, 400), 100, 100);
|
||||||
random.integer(0, 400),
|
|
||||||
100,
|
|
||||||
100
|
|
||||||
);
|
|
||||||
return `radial-gradient(farthest-corner at ${x}% ${y}%, ${color}, transparent 100%)`;
|
return `radial-gradient(farthest-corner at ${x}% ${y}%, ${color}, transparent 100%)`;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -47,35 +41,20 @@ const useBackground = ({
|
|||||||
setBackground(generateBackground());
|
setBackground(generateBackground());
|
||||||
};
|
};
|
||||||
|
|
||||||
const noise = useMemo(() => {
|
const noise = (): string => {
|
||||||
const svgWidth = Math.ceil((width ?? 1920) * ratio);
|
const svgWidth = Math.ceil((width ?? 1920) * ratio);
|
||||||
const svgHeight = Math.ceil((height ?? 1080) * ratio);
|
const svgHeight = Math.ceil((height ?? 1080) * ratio);
|
||||||
return (
|
const seed = random.integer(0, 1000000);
|
||||||
<svg
|
|
||||||
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
|
return `<svg viewBox="0 0 ${svgWidth} ${svgHeight}" xmlns="http://www.w3.org/2000/svg"><filter id="noiseFilter"><feTurbulence type="fractalNoise" baseFrequency="2.1" numOctaves="2" seed="${seed}" stitchTiles="stitch"/></filter><g opacity="0.9"><rect width="100%" height="100%" filter="url(#noiseFilter)"/></g></svg>`;
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
};
|
||||||
>
|
|
||||||
<filter id="noiseFilter">
|
|
||||||
<feTurbulence
|
|
||||||
type="fractalNoise"
|
|
||||||
baseFrequency="2.1"
|
|
||||||
numOctaves="2"
|
|
||||||
seed={random.integer(0, 1000000)}
|
|
||||||
stitchTiles="stitch"
|
|
||||||
/>
|
|
||||||
</filter>
|
|
||||||
<g opacity={0.9}>
|
|
||||||
<rect width="100%" height="100%" filter="url(#noiseFilter)" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}, [width, height, ratio]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
regenerate,
|
regenerate,
|
||||||
svg: `data:image/svg+xml;base64,${window.btoa(
|
svg:
|
||||||
ReactDOMServer.renderToString(noise)
|
typeof window !== "undefined"
|
||||||
)}`,
|
? `data:image/svg+xml;base64,${window.btoa(noise())}`
|
||||||
|
: "",
|
||||||
backgrounds: background,
|
backgrounds: background,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
18
src/utils/useBooleanToggle.ts
Normal file
18
src/utils/useBooleanToggle.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { useCallback, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
export function useBooleanToggle(
|
||||||
|
initialValue: boolean = false
|
||||||
|
): [boolean, (nextValue?: boolean) => void] {
|
||||||
|
const [value, setValue] = useState<boolean>(initialValue);
|
||||||
|
|
||||||
|
const toggle = useCallback((nextValue?: boolean) => {
|
||||||
|
if (typeof nextValue === "boolean") {
|
||||||
|
setValue(nextValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setValue((prev) => !prev);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return [value, toggle];
|
||||||
|
}
|
||||||
|
|
||||||
24
src/utils/useViewportSize.ts
Normal file
24
src/utils/useViewportSize.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
export interface ViewportSize {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useViewportSize(): ViewportSize {
|
||||||
|
const [size, setSize] = useState<ViewportSize>({
|
||||||
|
width: typeof window !== "undefined" ? window.innerWidth : 0,
|
||||||
|
height: typeof window !== "undefined" ? window.innerHeight : 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setSize({ width: window.innerWidth, height: window.innerHeight });
|
||||||
|
};
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
||||||
"allowJs": false,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": false,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
|
||||||
|
/* Preact Config */
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"baseUrl": "./src/",
|
"jsxImportSource": "preact",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"react": ["node_modules/preact/compat/"],
|
||||||
}
|
"react-dom": ["node_modules/preact/compat/"],
|
||||||
|
"@/*": ["src/*"]
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"outDir": "./dist"
|
||||||
|
},
|
||||||
|
"include": ["node_modules/vite/client.d.ts", "src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Node",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { defineConfig, loadEnv } from "vite";
|
import { defineConfig, loadEnv } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import preact from "@preact/preset-vite";
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import { visualizer } from "rollup-plugin-visualizer";
|
import { visualizer } from "rollup-plugin-visualizer";
|
||||||
import cssnano from "cssnano";
|
import cssnano from "cssnano";
|
||||||
@@ -12,11 +11,13 @@ export default ({ mode }) => {
|
|||||||
return defineConfig({
|
return defineConfig({
|
||||||
base: "/",
|
base: "/",
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
preact({
|
||||||
tsconfigPaths(),
|
prerender: {
|
||||||
|
enabled: true,
|
||||||
|
renderTarget: "#root",
|
||||||
|
},
|
||||||
|
}),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
cssnano(),
|
|
||||||
|
|
||||||
visualizer({
|
visualizer({
|
||||||
template: "treemap",
|
template: "treemap",
|
||||||
open: true, // Automatically open the report in your browser after build
|
open: true, // Automatically open the report in your browser after build
|
||||||
@@ -25,17 +26,5 @@ export default ({ mode }) => {
|
|||||||
brotliSize: true, // Show brotli size
|
brotliSize: true, // Show brotli size
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
treeshake: {
|
|
||||||
// Remove unused module exports
|
|
||||||
moduleSideEffects: false,
|
|
||||||
// Optimize property access
|
|
||||||
propertyReadSideEffects: false,
|
|
||||||
// Remove unused imports
|
|
||||||
tryCatchDeoptimization: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
28
vitest.config.ts
Normal file
28
vitest.config.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
import preact from "@preact/preset-vite";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [preact()],
|
||||||
|
test: {
|
||||||
|
environment: "happy-dom",
|
||||||
|
globals: true,
|
||||||
|
setupFiles: ["./src/test/setup.ts"],
|
||||||
|
coverage: {
|
||||||
|
reporter: ["text", "json", "html"],
|
||||||
|
exclude: [
|
||||||
|
"node_modules/",
|
||||||
|
"src/test/",
|
||||||
|
"**/*.config.ts",
|
||||||
|
"**/*.d.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"react": "preact/compat",
|
||||||
|
"react-dom": "preact/compat",
|
||||||
|
"react-dom/test-utils": "preact/test-utils",
|
||||||
|
},
|
||||||
|
dedupe: ["preact"],
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user