diff --git a/build/template/footer.eta b/build/template/footer.eta new file mode 100644 index 00000000..2d39928c --- /dev/null +++ b/build/template/footer.eta @@ -0,0 +1,47 @@ + \ No newline at end of file diff --git a/frameworks.mjs b/frameworks.mjs index 6c69b727..6dbd62a1 100644 --- a/frameworks.mjs +++ b/frameworks.mjs @@ -8,10 +8,12 @@ function sortAllFilenames(files, filenamesSorted) { ].filter(Boolean); } -export default [ +const frameworks = [ { id: "svelte4", title: "Svelte 4", + frameworkName: "Svelte", + isCurrentVersion: true, img: "framework/svelte.svg", eslint: { files: ["**/svelte4/*.svelte"], @@ -28,6 +30,8 @@ export default [ { id: "react", title: "React", + frameworkName: "React", + isCurrentVersion: true, img: "framework/react.svg", eslint: { files: ["**/react/*.jsx", "**/react/*.tsx"], @@ -53,6 +57,8 @@ export default [ { id: "vue3", title: "Vue 3", + frameworkName: "Vue", + isCurrentVersion: true, img: "framework/vue.svg", eslint: { files: ["**/vue3/*.vue"], @@ -76,6 +82,8 @@ export default [ { id: "angular", title: "Angular", + frameworkName: "Angular", + isCurrentVersion: true, img: "framework/angular.svg", eslint: [ { @@ -131,6 +139,8 @@ export default [ { id: "lit", title: "Lit", + frameworkName: "Lit", + isCurrentVersion: true, img: "framework/lit.svg", eslint: { files: ["**/lit/**"], @@ -149,6 +159,8 @@ export default [ { id: "vue2", title: "Vue 2", + frameworkName: "Vue", + isCurrentVersion: false, img: "framework/vue.svg", eslint: { files: ["**/vue2/*.vue"], @@ -169,6 +181,8 @@ export default [ { id: "ember", title: "Ember", + frameworkName: "Ember", + isCurrentVersion: true, img: "framework/ember.svg", eslint: { files: ["**/ember/**"], @@ -186,7 +200,9 @@ export default [ }, { id: "solid", - title: "SolidJS", + title: "Solid.js", + frameworkName: "Solid", + isCurrentVersion: true, img: "framework/solid.svg", eslint: { files: ["**/solid/*.jsx"], @@ -204,6 +220,8 @@ export default [ { id: "alpine", title: "Alpine", + frameworkName: "Alpine", + isCurrentVersion: true, img: "framework/alpine.svg", eslint: { files: ["**/alpine/**"], @@ -220,6 +238,8 @@ export default [ { id: "mithril", title: "Mithril", + frameworkName: "Mithril", + isCurrentVersion: true, img: "framework/mithril.svg", eslint: { env: { @@ -241,6 +261,8 @@ export default [ { id: "aurelia2", title: "Aurelia 2", + frameworkName: "Aurelia", + isCurrentVersion: true, img: "framework/aurelia.svg", eslint: { env: { @@ -269,6 +291,8 @@ export default [ { id: "qwik", title: "Qwik", + frameworkName: "Qwik", + isCurrentVersion: true, img: "framework/qwik.svg", eslint: { env: { @@ -299,6 +323,8 @@ export default [ { id: "marko", title: "Marko", + frameworkName: "Marko", + isCurrentVersion: true, img: "framework/marko.svg", eslint: { files: ["!**"], // Marko’s linter/prettyprinter doesn’t use eslint @@ -314,6 +340,8 @@ export default [ { id: "aurelia1", title: "Aurelia 1", + frameworkName: "Aurelia", + isCurrentVersion: false, img: "framework/aurelia.svg", eslint: { env: { @@ -341,6 +369,8 @@ export default [ { id: "svelte5", title: "Svelte 5 (preview)", + frameworkName: "Svelte", + isCurrentVersion: false, img: "framework/svelte.svg", eslint: { files: ["**/TODO-THIS-IS-DISABLED-svelte5/*.svelte"], @@ -355,3 +385,12 @@ export default [ mainPackageName: "svelte", }, ]; + +export function matchFrameworkId(id){ + return frameworks.find((framework) => + framework.id === id || + (framework.isCurrentVersion && + framework.frameworkName.toLowerCase() === id)) +} + +export default frameworks; \ No newline at end of file diff --git a/index.html b/index.html index 6e50796d..bb5eb997 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - Component Party + <%= it.title %> - - + + + - - - - + + + + - - - - + + + + + diff --git a/src/Index.svelte b/src/Index.svelte new file mode 100644 index 00000000..638ebf0f --- /dev/null +++ b/src/Index.svelte @@ -0,0 +1,453 @@ + + + + +
+ +
+
+ + diff --git a/src/components/GithubStarButton.svelte b/src/components/GithubStarButton.svelte index 1f82580a..e07bfec3 100644 --- a/src/components/GithubStarButton.svelte +++ b/src/components/GithubStarButton.svelte @@ -66,14 +66,12 @@ aria-label={`Star ${REPOSITORY_PATH} on GitHub`} on:click={onButtonClick} > - + - Star + Star {#if isFetchingStarCount || starCount !== 0} -
+
{#if isFetchingStarCount && starCount === 0} 0} + {/if} diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 4df77f53..573852a6 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -1,4 +1,4 @@ module.exports = { - content: ["./src/**/*.{html,js,svelte,ts}", "./index.html"], + content: ["./src/**/*.{html,js,svelte,ts,eta}", "./index.html", "build/template/*.{html,eta}"], plugins: [require("@tailwindcss/typography")], }; diff --git a/vite.config.js b/vite.config.js index 891546c6..37029cfd 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,23 +1,84 @@ import { defineConfig } from "vite"; import { svelte } from "@sveltejs/vite-plugin-svelte"; import sveltePreprocess from "svelte-preprocess"; -import { createHtmlPlugin } from "vite-plugin-html"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { Eta } from "eta"; import FRAMEWORKS from "./frameworks.mjs"; import pluginGenerateFrameworkContent from "./build/generateContentVitePlugin.js"; +// @TODO: sitemap + +const footerNavigation = [ + { + title: "Most Popular Frameworks", + links: [ + { name: "React vs. Vue", url: "/compare/react-vs-vue" }, + { name: "React vs. Angular", url: "/compare/react-vs-angular" }, + { name: "Vue vs. React", url: "/compare/vue-vs-react" }, + { name: "Vue vs. Angular", url: "/compare/vue-vs-angular" }, + { name: "Angular vs. React", url: "/compare/angular-vs-react" }, + { name: "Angular vs. Vue", url: "/compare/angular-vs-vue" }, + ], + }, + { + title: "Popular frameworks vs. Rising frameworks", + links: [ + { name: "React vs. Svelte", url: "/compare/react-vs-svelte" }, + { name: "React vs. Solid", url: "/compare/react-vs-solid" }, + { name: "Vue vs. Svelte", url: "/compare/vue-vs-svelte" }, + { name: "Vue vs. Solid", url: "/compare/vue-vs-solid" }, + { name: "Angular vs. Svelte", url: "/compare/angular-vs-svelte" }, + { name: "Angular vs. Solid", url: "/compare/angular-vs-solid" }, + ], + }, + { + title: "Comparing Legacy version & Current Version", + links: [ + { name: "Vue 2 vs. Vue 3", url: "/compare/vue2-vs-vue3" }, + { + name: "Aurelia 1 vs. Aurelia 2", + url: "/compare/aurelia1-vs-aurelia2", + }, + ], + }, + { + title: "Comparing Current Version & Upcoming Version", + links: [ + { name: "Svelte 4 vs. Svelte 5", url: "/compare/svelte4-vs-svelte5" }, + ], + }, +]; + +const footerLinks = footerNavigation.map((n) => n.links).flat(); + +const templateDataDefaults = { + title: "Component Party", + url: "https://component-party.dev/", + description: `Web component JS frameworks overview by their syntax and features: ${FRAMEWORKS.map((f) => f.title).join(", ")}`, + image: "https://component-party.dev/banner2.png", +}; + // https://vitejs.dev/config/ export default defineConfig({ plugins: [ pluginGenerateFrameworkContent(), svelte(), - createHtmlPlugin({ - minify: true, - inject: { - data: { - frameworkList: FRAMEWORKS.map((f) => f.title).join(", "), + generateHtmlPagesPlugin([ + ...footerLinks.map((link) => ({ + outputPath: `${link.url}.html`, + template: "dist/index.html", + templateData: { + ...templateDataDefaults, + title: `${link.name} - ${templateDataDefaults.title}`, }, + })), + { + outputPath: "index.html", + template: "dist/index.html", + templateData: templateDataDefaults, }, - }), + ]), ], ignore: ["content"], preprocess: [ @@ -26,3 +87,75 @@ export default defineConfig({ }), ], }); + +async function generateHtmlPagesPlugin(pages) { + const eta = new Eta({ views: "." }); + + const template = { + footer: await fs.readFile( + path.resolve(__dirname, "build/template/footer.eta"), + "utf8" + ), + }; + + const htmlTransform = { + include(html) { + for (const [templateName, templateContent] of Object.entries(template)) { + html = html.replace( + ``, + eta.renderString(templateContent, { navigations: footerNavigation }) + ); + } + return html; + }, + render(htmlEta, data) { + return eta.renderString(htmlEta, data); + }, + }; + + return { + name: "generate-html-pages", + transformIndexHtml(html, ctx) { + html = htmlTransform.include(html); + if (ctx.server) { + const matchedPage = pages.find( + (page) => ctx.originalUrl === filePathToUrl(page.outputPath) + ); + if (matchedPage) { + html = htmlTransform.render(html, matchedPage.templateData); + } else { + html = htmlTransform.render(html, templateDataDefaults); + } + } + return html; + }, + async closeBundle() { + for (const page of pages) { + const template = page.template || "index.html"; + const templateData = page.templateData || {}; + const templatePath = path.join(__dirname, template); + const outputPath = path.join(__dirname, "dist", page.outputPath); + + const templateContent = await fs.readFile(templatePath, "utf8"); + const compiledHtml = eta.renderString(templateContent, templateData); + // @TODO: add minify + const dirPath = path.dirname(outputPath); + await fs.mkdir(dirPath, { recursive: true, force: true }); + await fs.writeFile(outputPath, compiledHtml, "utf8"); + } + }, + }; +} + +function filePathToUrl(filePath) { + let normalizedPath = path.normalize(filePath); + let baseName = path.basename(normalizedPath); + + if (baseName === "index.html") { + return path.dirname(normalizedPath) === "." + ? "/" + : path.dirname(normalizedPath) + "/"; + } else { + return normalizedPath.replace(/.html$/, ""); + } +}