Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement JS and CSS minification #70

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions components/utils/minify-css.utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { minifyCSS } from "./minify-css.utils";

describe("minify-css.utils", () => {
it("should minify valid CSS code", () => {
const cssCode = `body {
color: red;
}`;
const expectedMinifiedCSS = "body{color:red}";
expect(minifyCSS(cssCode)).toBe(expectedMinifiedCSS);
});

it("should handle syntactically incorrect CSS gracefully", () => {
const invalidCSS = `body color red;`;
const output = minifyCSS(invalidCSS);
expect(output).toBe("");
});
});
17 changes: 17 additions & 0 deletions components/utils/minify-css.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import CleanCSS from "clean-css";

export function minifyCSS(cssCode: string): string {
try {
const result = new CleanCSS({}).minify(cssCode);
if (result.errors.length > 0) {
throw new Error(result.errors.join(", "));
}
return result.styles;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to minify CSS: ${error.message}`);
} else {
throw new Error("Failed to minify CSS: Unknown error");
}
}
}
16 changes: 16 additions & 0 deletions components/utils/minify-js.utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { minifyJS } from "./minify-js.utils";

describe("minify-js.utils", () => {
it("should minify valid JS code", async () => {
const jsCode = `function add(a, b) {
return a + b;
}`;
const expectedMinifiedJS = "function add(a,b){return a+b}";
await expect(minifyJS(jsCode)).resolves.toBe(expectedMinifiedJS);
});

it("should throw an error for invalid JS code", async () => {
const invalidJS = `function add(a, b { return a + b; }`;
await expect(minifyJS(invalidJS)).rejects.toThrow("Unexpected token");
});
});
17 changes: 17 additions & 0 deletions components/utils/minify-js.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { minify } from "terser";

export async function minifyJS(jsCode: string): Promise<string> {
try {
const result = await minify(jsCode, { mangle: false });
if (!result.code) {
throw new Error("Minification produced no output.");
}
return result.code;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to minify JS: ${error.message}`);
} else {
throw new Error("Failed to minify JS: Unknown error");
}
}
}
6 changes: 6 additions & 0 deletions components/utils/tools-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ export const tools = [
"Resize images while maintaining aspect ratio and choose between PNG and JPEG formats with our free tool.",
link: "/utilities/image-resizer",
},
{
title: "Minify CSS/JavaScript",
description:
"Quickly minify your CSS or JavaScript code to reduce file size and improve loading times for your web applications.",
link: "/utilities/minify-js-css",
},
{
title: "JWT Parser",
description:
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"class-variance-authority": "^0.7.0",
"clean-css": "^5.3.3",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"curlconverter": "^4.10.1",
Expand All @@ -32,11 +33,13 @@
"react-dom": "^18",
"react-syntax-highlighter": "^15.5.0",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"terser": "^5.33.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
"@types/clean-css": "^4.2.11",
"@types/jest": "^29.5.12",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20",
Expand Down
103 changes: 103 additions & 0 deletions pages/utilities/minify-js-css.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { useState, useCallback } from "react";
import { Textarea } from "@/components/ds/TextareaComponent";
import PageHeader from "@/components/PageHeader";
import { Card } from "@/components/ds/CardComponent";
import { Button } from "@/components/ds/ButtonComponent";
import { Label } from "@/components/ds/LabelComponent";
import Header from "@/components/Header";
import { CMDK } from "@/components/CMDK";
import { useCopyToClipboard } from "@/components/hooks/useCopyToClipboard";
import CallToActionGrid from "@/components/CallToActionGrid";
import Meta from "@/components/Meta";
import { minifyJS } from "@/components/utils/minify-js.utils";
import { minifyCSS } from "@/components/utils/minify-css.utils";
import GitHubContribution from "@/components/GitHubContribution";

export default function MinifyJSAndCSS() {
const [input, setInput] = useState("");
const [output, setOutput] = useState("");
const [language, setLanguage] = useState<"js" | "css">("js");
const { buttonText, handleCopy } = useCopyToClipboard();

const handleChange = useCallback(
async (event: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = event.currentTarget.value;
setInput(value);

if (value.trim() === "") {
setOutput("");
return;
}

try {
const minified =
language === "js" ? await minifyJS(value) : minifyCSS(value);
setOutput(minified);
} catch (errorMessage: unknown) {
setOutput((errorMessage as Error).message);
}
},
[language]
);

return (
<main>
<Meta
title="JS and CSS Minifier | Free, Open Source & Ad-free"
description="Minify JavaScript and CSS files quickly and easily with Jam's free online minifier. Just paste your code and get the minified result. That's it."
/>
<Header />
<CMDK />

<section className="container max-w-2xl mb-12">
<PageHeader
title="JS & CSS Minifier"
description="Fast, free, open source, ad-free tools."
/>
</section>

<section className="container max-w-2xl mb-6">
<Card className="flex flex-col p-6 hover:shadow-none shadow-none rounded-xl">
<div>
<Label>Select Language</Label>
<div className="mb-4">
<Button
variant={language === "js" ? "default" : "outline"}
onClick={() => setLanguage("js")}
>
JavaScript
</Button>
<Button
variant={language === "css" ? "default" : "outline"}
onClick={() => setLanguage("css")}
>
CSS
</Button>
</div>

<Label>Input Code</Label>
<Textarea
rows={6}
placeholder={`Paste ${language.toUpperCase()} code here`}
onChange={handleChange}
className="mb-6"
value={input}
/>

<div className="flex justify-between items-center mb-2">
<Label className="mb-0">Minified Output</Label>
</div>

<Textarea value={output} rows={6} readOnly className="mb-4" />
<Button variant="outline" onClick={() => handleCopy(output)}>
{buttonText}
</Button>
</div>
</Card>
</section>

<GitHubContribution username="ayshrj" />
<CallToActionGrid />
</main>
);
}