mirror of
https://github.com/lainbo/component-party.git
synced 2026-04-04 20:39:02 +08:00
244 lines
7.2 KiB
TypeScript
244 lines
7.2 KiB
TypeScript
import { defineConfig } from "vite";
|
||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||
import fs from "node:fs/promises";
|
||
import path from "node:path";
|
||
import { Eta } from "eta";
|
||
import { minify as htmlMinify } from "html-minifier-terser";
|
||
import { frameworks } from "./frameworks";
|
||
import pluginGenerateFrameworkContent from "./build/generateContentVitePlugin";
|
||
import generateSitemap from "./scripts/generateSitemap";
|
||
import { svelteInspector } from "@sveltejs/vite-plugin-svelte-inspector";
|
||
import tailwindcss from "@tailwindcss/vite";
|
||
import { FRAMEWORK_SEPARATOR } from "./src/constants.ts";
|
||
|
||
// Helper function to create framework comparison URLs
|
||
const createFrameworkUrl = (frameworks: string[]) =>
|
||
`/?f=${frameworks.join(FRAMEWORK_SEPARATOR)}`;
|
||
|
||
const footerNavigation = [
|
||
{
|
||
title: "最热门框架对比",
|
||
links: [
|
||
{ name: "React vs Vue", url: createFrameworkUrl(["react", "vue3"]) },
|
||
{
|
||
name: "React vs Angular",
|
||
url: createFrameworkUrl(["react", "angularRenaissance"]),
|
||
},
|
||
{ name: "Vue vs React", url: createFrameworkUrl(["vue3", "react"]) },
|
||
{
|
||
name: "Vue vs Angular",
|
||
url: createFrameworkUrl(["vue3", "angularRenaissance"]),
|
||
},
|
||
{
|
||
name: "Angular vs React",
|
||
url: createFrameworkUrl(["angularRenaissance", "react"]),
|
||
},
|
||
{
|
||
name: "Angular vs Vue",
|
||
url: createFrameworkUrl(["angularRenaissance", "vue3"]),
|
||
},
|
||
{
|
||
name: "Ember vs React",
|
||
url: createFrameworkUrl(["emberPolaris", "react"]),
|
||
},
|
||
{
|
||
name: "Ember vs Vue",
|
||
url: createFrameworkUrl(["emberPolaris", "vue3"]),
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: "热门框架 vs 新兴框架",
|
||
links: [
|
||
{
|
||
name: "React vs Svelte",
|
||
url: createFrameworkUrl(["react", "svelte5"]),
|
||
},
|
||
{ name: "React vs Solid", url: createFrameworkUrl(["react", "solid"]) },
|
||
{ name: "Vue vs Svelte", url: createFrameworkUrl(["vue3", "svelte5"]) },
|
||
{ name: "Vue vs Solid", url: createFrameworkUrl(["vue3", "solid"]) },
|
||
{
|
||
name: "Angular vs Svelte",
|
||
url: createFrameworkUrl(["angularRenaissance", "svelte5"]),
|
||
},
|
||
{
|
||
name: "Angular vs Solid",
|
||
url: createFrameworkUrl(["angularRenaissance", "solid"]),
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: "旧版本 vs 当前版本",
|
||
links: [
|
||
{
|
||
name: "Svelte 4 vs Svelte 5",
|
||
url: createFrameworkUrl(["svelte4", "svelte5"]),
|
||
},
|
||
{ name: "Vue 2 vs Vue 3", url: createFrameworkUrl(["vue2", "vue3"]) },
|
||
{
|
||
name: "Angular vs Angular Renaissance",
|
||
url: createFrameworkUrl(["angular", "angularRenaissance"]),
|
||
},
|
||
{
|
||
name: "Aurelia 1 vs Aurelia 2",
|
||
url: createFrameworkUrl(["aurelia1", "aurelia2"]),
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: "当前版本 vs 即将发布版本",
|
||
links: [
|
||
{
|
||
name: "Ember Octane vs Ember Polaris",
|
||
url: createFrameworkUrl(["emberOctane", "emberPolaris"]),
|
||
},
|
||
],
|
||
},
|
||
];
|
||
|
||
const templateDataDefaults = {
|
||
title: "Component Party 中文版",
|
||
url: "https://component-party.lainbo.com/",
|
||
description: `JavaScript 框架语法对比:React、Vue、Angular、Svelte、Solid.js 等。查看 Web 开发框架的语法差异、特性和代码示例。`,
|
||
keywords:
|
||
"JavaScript 框架, React, Vue, Angular, Svelte, Solid.js, 框架对比, Web 开发, 前端框架, 组件库, JavaScript 库, 代码对比, 编程工具, 开发者工具, Web 组件, JSX, TypeScript, 现代 JavaScript",
|
||
image: "https://component-party.lainbo.com/banner2.png",
|
||
frameworkCount: frameworks.length,
|
||
};
|
||
|
||
// https://vitejs.dev/config/
|
||
export default defineConfig({
|
||
resolve: {
|
||
alias: {
|
||
"@frameworks": path.resolve(import.meta.dirname, "frameworks"),
|
||
},
|
||
},
|
||
plugins: [
|
||
pluginGenerateFrameworkContent(),
|
||
svelte(),
|
||
svelteInspector(), // https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/inspector.md
|
||
generateHtmlPagesPlugin([
|
||
{
|
||
outputPath: "index.html",
|
||
template: "dist/index.html",
|
||
templateData: {
|
||
...templateDataDefaults,
|
||
navigations: footerNavigation,
|
||
},
|
||
},
|
||
]),
|
||
tailwindcss(),
|
||
],
|
||
optimizeDeps: {
|
||
entries: ["src/**/*"],
|
||
},
|
||
build: {
|
||
rollupOptions: {
|
||
external: (id) => {
|
||
return id.includes("/content/");
|
||
},
|
||
output: {
|
||
manualChunks: {
|
||
vendor: ["svelte"],
|
||
frameworks: ["@frameworks"],
|
||
},
|
||
},
|
||
},
|
||
minify: "terser",
|
||
terserOptions: {
|
||
compress: {
|
||
drop_console: true,
|
||
drop_debugger: true,
|
||
},
|
||
},
|
||
sourcemap: false,
|
||
target: "esnext",
|
||
},
|
||
});
|
||
|
||
async function generateHtmlPagesPlugin(pages: unknown[]) {
|
||
const eta = new Eta({ views: "." });
|
||
|
||
const template = {
|
||
footer: await fs.readFile(
|
||
path.resolve(import.meta.dirname, "build/template/footer.html"),
|
||
"utf8",
|
||
),
|
||
};
|
||
|
||
const htmlTransform = {
|
||
include(html: string) {
|
||
for (const [templateName, templateContent] of Object.entries(template)) {
|
||
html = html.replace(
|
||
`<!--template:${templateName}-->`,
|
||
eta.renderString(templateContent, { navigations: footerNavigation }),
|
||
);
|
||
}
|
||
return html;
|
||
},
|
||
render(htmlEta: string, data: unknown) {
|
||
return eta.renderString(htmlEta, data as object);
|
||
},
|
||
};
|
||
|
||
return {
|
||
name: "generate-html-pages",
|
||
transformIndexHtml(html: string, ctx: unknown) {
|
||
html = htmlTransform.include(html);
|
||
if ((ctx as { server?: unknown }).server) {
|
||
const matchedPage = pages.find(
|
||
(page: unknown) =>
|
||
(ctx as { originalUrl?: string }).originalUrl ===
|
||
filePathToUrl((page as { outputPath: string }).outputPath),
|
||
);
|
||
if (matchedPage) {
|
||
html = htmlTransform.render(
|
||
html,
|
||
(matchedPage as { templateData: unknown }).templateData,
|
||
);
|
||
} else {
|
||
html = htmlTransform.render(html, templateDataDefaults);
|
||
}
|
||
}
|
||
return html;
|
||
},
|
||
async closeBundle() {
|
||
// Generate sitemap
|
||
await generateSitemap();
|
||
|
||
for (const page of pages) {
|
||
const template =
|
||
(page as { template?: string }).template || "index.html";
|
||
const templateData =
|
||
(page as { templateData?: unknown }).templateData || {};
|
||
const templatePath = path.join(import.meta.dirname, template);
|
||
const outputPath = path.join(
|
||
import.meta.dirname,
|
||
"dist",
|
||
(page as { outputPath: string }).outputPath,
|
||
);
|
||
|
||
const templateContent = await fs.readFile(templatePath, "utf8");
|
||
const compiledHtml = eta.renderString(templateContent, templateData);
|
||
const minifiedHtml = await htmlMinify(compiledHtml);
|
||
const dirPath = path.dirname(outputPath);
|
||
await fs.mkdir(dirPath, { recursive: true });
|
||
await fs.writeFile(outputPath, minifiedHtml, "utf8");
|
||
}
|
||
},
|
||
};
|
||
}
|
||
|
||
function filePathToUrl(filePath: string) {
|
||
const normalizedPath = path.normalize(filePath);
|
||
const baseName = path.basename(normalizedPath);
|
||
|
||
if (baseName === "index.html") {
|
||
return path.dirname(normalizedPath) === "."
|
||
? "/"
|
||
: path.dirname(normalizedPath) + "/";
|
||
} else {
|
||
return normalizedPath.replace(/.html$/, "");
|
||
}
|
||
}
|